셀프서비스의 핵심 원칙, GitOps 기반 인프라 요청, Crossplane을 활용한 Kubernetes 네이티브 인프라 추상화, 그리고 승인 워크플로우를 다룹니다.
셀프서비스 인프라의 목표는 개발자가 티켓 없이, 대기 없이 필요한 인프라를 직접 프로비저닝할 수 있도록 하는 것입니다. 단, 이 자율성에는 **가드레일(Guardrail)**이 함께해야 합니다.
가드레일의 핵심 원칙:
| 원칙 | 설명 | 예시 |
|---|---|---|
| 사전 정의된 선택지 | 무한한 자유가 아닌, 검증된 옵션 제공 | DB 인스턴스: small, medium, large |
| 기본값 최적화 | 대부분의 경우 기본값이 적절하도록 설계 | 스토리지 암호화 기본 활성화 |
| 비용 투명성 | 리소스 생성 전 예상 비용 표시 | "이 DB의 월 예상 비용: 약 150 USD" |
| 정책 자동 적용 | 수동 검토 없이 정책 준수 보장 | 퍼블릭 접근 차단, 태그 자동 부여 |
셀프서비스는 "아무것이나 할 수 있다"가 아니라 "필요한 것을 빠르게 할 수 있다"를 의미합니다. 잘 설계된 가드레일은 개발자를 제한하는 것이 아니라, 실수로부터 보호하는 것입니다.
**GitOps(깃옵스)**는 Git 저장소를 인프라의 단일 진실 소스(Single Source of Truth)로 사용하는 방식입니다. 인프라 변경을 Pull Request로 관리하면 이력 추적, 리뷰, 롤백이 자연스럽게 가능합니다.
infra-gitops/
teams/
checkout-team/
dev/
databases.yaml
queues.yaml
caches.yaml
staging/
databases.yaml
queues.yaml
production/
databases.yaml
queues.yaml
caches.yaml
auth-team/
dev/
databases.yaml
staging/
databases.yaml
production/
databases.yaml
policies/
cost-limits.rego
security-baseline.rego
naming-conventions.rego
modules/
rds-postgresql/
elasticache-redis/
sqs-queue/
s3-bucket/apiVersion: infra.mycompany.com/v1
kind: DatabaseRequest
metadata:
name: checkout-db
namespace: checkout-team
labels:
team: checkout-team
environment: dev
service: checkout-service
spec:
engine: postgresql
version: "16"
size: small # small: db.t4g.micro, medium: db.t4g.medium, large: db.t4g.large
storage: 20 # GB
backup:
enabled: true
retention: 7 # days
monitoring:
enabled: true
alerts:
- type: cpu
threshold: 80
- type: storage
threshold: 85**Crossplane(크로스플레인)**은 Kubernetes를 범용 제어 평면(Control Plane)으로 확장하여, 클라우드 리소스를 Kubernetes 리소스처럼 관리할 수 있게 해주는 CNCF 프로젝트입니다.
| 관점 | Terraform | Crossplane |
|---|---|---|
| 실행 방식 | CLI / CI에서 실행 | Kubernetes 컨트롤러 |
| 상태 관리 | tfstate 파일 | Kubernetes etcd |
| 드리프트 감지 | 수동 (terraform plan) | 자동 (지속적 조정) |
| 셀프서비스 | API 래핑 필요 | kubectl / Backstage 연동 |
| 학습 곡선 | HCL 학습 필요 | YAML (Kubernetes 친숙) |
| 조합 추상화 | 모듈 | Composition(컴포지션) |
Crossplane의 가장 큰 장점은 **Composition(컴포지션)**을 통한 인프라 추상화입니다. 복잡한 클라우드 리소스 조합을 단순한 Kubernetes 리소스로 추상화할 수 있습니다.
# Helm으로 Crossplane 설치
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace
# AWS Provider 설치
kubectl apply -f - <<EOF
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws
spec:
package: xpkg.upbound.io/upbound/provider-family-aws:v1.15.0
EOF**XRD(Composite Resource Definition)**는 개발자에게 노출할 추상화된 리소스의 스키마를 정의합니다.
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xdatabases.infra.mycompany.com
spec:
group: infra.mycompany.com
names:
kind: XDatabase
plural: xdatabases
claimNames:
kind: Database
plural: databases
versions:
- name: v1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
engine:
type: string
enum: ["postgresql", "mysql"]
description: "데이터베이스 엔진"
size:
type: string
enum: ["small", "medium", "large"]
description: "인스턴스 크기"
default: "small"
storageGB:
type: integer
minimum: 20
maximum: 500
default: 20
description: "스토리지 크기 (GB)"
required:
- engine**Composition(컴포지션)**은 추상화된 리소스가 실제 클라우드 리소스로 변환되는 규칙을 정의합니다.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: database-aws
labels:
provider: aws
crossplane.io/xrd: xdatabases.infra.mycompany.com
spec:
compositeTypeRef:
apiVersion: infra.mycompany.com/v1
kind: XDatabase
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
spec:
forProvider:
region: ap-northeast-2
instanceClass: db.t4g.micro
allocatedStorage: 20
storageEncrypted: true
publiclyAccessible: false
skipFinalSnapshot: false
autoMinorVersionUpgrade: true
backupRetentionPeriod: 7
tags:
ManagedBy: crossplane
patches:
- type: FromCompositeFieldPath
fromFieldPath: "spec.engine"
toFieldPath: "spec.forProvider.engine"
- type: FromCompositeFieldPath
fromFieldPath: "spec.storageGB"
toFieldPath: "spec.forProvider.allocatedStorage"
- type: FromCompositeFieldPath
fromFieldPath: "spec.size"
toFieldPath: "spec.forProvider.instanceClass"
transforms:
- type: map
map:
small: db.t4g.micro
medium: db.t4g.medium
large: db.t4g.large
- name: security-group
base:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: SecurityGroup
spec:
forProvider:
region: ap-northeast-2
description: "Database security group"
ingress:
- fromPort: 5432
toPort: 5432
protocol: tcp
cidrBlocks:
- "10.0.0.0/8"개발자는 복잡한 클라우드 리소스 세부사항을 알 필요 없이, 단순한 Claim 리소스를 작성합니다.
apiVersion: infra.mycompany.com/v1
kind: Database
metadata:
name: checkout-db
namespace: checkout
spec:
engine: postgresql
size: small
storageGB: 50이 단순한 YAML 파일 하나로 RDS 인스턴스, 보안 그룹, 시크릿이 자동으로 생성됩니다.
Crossplane Composition은 환경별로 다르게 구성할 수 있습니다. dev 환경에서는 최소 사양으로, production에서는 Multi-AZ, 높은 백업 보존 기간 등을 자동 적용하도록 설계하세요.
새 서비스를 위한 전체 환경(네임스페이스, RBAC, 네트워크 정책, 시크릿 등)을 자동으로 프로비저닝합니다.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: provision-environment
title: "서비스 환경 프로비저닝"
description: "새 서비스를 위한 Kubernetes 환경을 생성합니다."
spec:
owner: group:default/platform-team
type: environment
parameters:
- title: "환경 정보"
required:
- serviceName
- environment
- team
properties:
serviceName:
title: "서비스 이름"
type: string
environment:
title: "환경"
type: string
enum: ["dev", "staging", "production"]
team:
title: "소유 팀"
type: string
ui:field: OwnerPicker
steps:
- id: create-namespace
name: "네임스페이스 생성"
action: kubernetes:apply
input:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: "${{ parameters.serviceName }}-${{ parameters.environment }}"
labels:
team: "${{ parameters.team }}"
environment: "${{ parameters.environment }}"
istio-injection: "enabled"
- id: create-resource-quota
name: "리소스 쿼타 설정"
action: kubernetes:apply
input:
manifest:
apiVersion: v1
kind: ResourceQuota
metadata:
name: default-quota
namespace: "${{ parameters.serviceName }}-${{ parameters.environment }}"
spec:
hard:
requests.cpu: "4"
requests.memory: "8Gi"
limits.cpu: "8"
limits.memory: "16Gi"
pods: "20"
- id: create-network-policy
name: "네트워크 정책 설정"
action: kubernetes:apply
input:
manifest:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: "${{ parameters.serviceName }}-${{ parameters.environment }}"
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
environment: "${{ parameters.environment }}"모든 인프라 요청이 자동 승인되어서는 안 됩니다. 리소스의 위험도와 비용에 따라 차등화된 승인 워크플로우를 적용합니다.
apiVersion: policy.mycompany.com/v1
kind: ApprovalPolicy
metadata:
name: infrastructure-approval
spec:
rules:
- name: "자동 승인 (저위험)"
conditions:
environment: ["dev", "staging"]
resourceType: ["namespace", "configmap", "secret"]
estimatedMonthlyCost:
lessThan: 50
approval: auto
- name: "팀 리드 승인 (중위험)"
conditions:
environment: ["production"]
resourceType: ["database", "cache", "queue"]
estimatedMonthlyCost:
lessThan: 500
approval:
required: 1
approvers:
- role: team-lead
- name: "플랫폼 팀 + 팀 리드 승인 (고위험)"
conditions:
estimatedMonthlyCost:
greaterThanOrEqual: 500
approval:
required: 2
approvers:
- role: team-lead
- role: platform-team
- name: "CISO 승인 (보안 민감)"
conditions:
resourceType: ["iam-role", "vpc-peering", "public-endpoint"]
approval:
required: 1
approvers:
- role: security-team승인 워크플로우는 보안과 속도의 균형을 맞추어야 합니다. 승인 단계가 너무 많으면 셀프서비스의 의미가 퇴색됩니다. 자동 승인의 범위를 최대한 넓히되, 고위험 작업에만 수동 승인을 적용하세요.
이번 장에서는 셀프서비스 인프라의 설계와 구현을 다루었습니다.
다음 장에서는 플랫폼 API 설계를 다룹니다. 플랫폼을 제품으로 바라보는 관점에서 API 계층을 설계하고, 추상화 수준을 결정하며, CLI 도구를 제공하는 방법을 살펴보겠습니다.
이 글이 도움이 되셨나요?
Platform as a Product 관점에서의 API 계층 설계, 추상화 수준 결정, 내부 API 버전닝, 인증과 인가, 감사 로깅, 그리고 CLI 도구 제공까지 다룹니다.
Golden Path의 정의와 철학, 옵셔널하되 매력적인 경로 설계, Terraform/Pulumi 기반 자동 인프라 프로비저닝, 그리고 80% 이상 자발적 채택률의 비결을 다룹니다.
FinOps 원칙과 플랫폼 통합, Backstage 비용 대시보드, 태그 기반 비용 할당, 리소스 생성 시점의 비용 예측, 그리고 AI 기반 비용 최적화를 다룹니다.