Kubernetes 패턴
워크로드를 안정적으로 배포, 관리 및 디버깅하기 위한 프로덕션급 Kubernetes 패턴입니다.
활성화 시기
- Kubernetes 매니페스트 작성 (Deployments, Services, Ingress, Jobs)
- 리소스 요청/제한, 활성/준비 프로브 구성
- RBAC, 네임스페이스 또는 ServiceAccount 설정
- K8s에서 구성 및 시크릿 관리
- CrashLoopBackOff, OOMKilled, 보류 중인 Pod 또는 이미지 풀 오류 디버깅
- HPA(Horizontal Pod Autoscaler) 또는 PodDisruptionBudget 구성
- 보안 또는 정확성을 위한 K8s YAML 검토
사용 시기
위의 활성화 시기와 동일합니다. 이 별칭은 저장소 스킬 형식 규칙을 충족합니다. Kubernetes YAML 및 워크로드를 작성, 검토 또는 디버깅할 때마다 이 스킬을 사용하세요.
작동 방식
이 스킬은 작업별로 구성된 복사-붙여넣기 가능한 프로덕션급 YAML 패턴과 kubectl 디버깅 명령어를 제공합니다.
- Deployment 템플릿 — 보안 컨텍스트, 롤링 업데이트 전략, 세 가지 프로브 유형, 리소스 제한 및 ConfigMap/Secret에서 환경 변수 주입이 포함된 완전히 구성된 프로덕션 Deployment.
- 프로브 — startup vs liveness vs readiness에 대한 결정 테이블과 올바른
failureThreshold × periodSeconds계산. - Services 및 Ingress — ClusterIP, LoadBalancer 및 cert-manager 어노테이션이 있는 TLS Ingress 패턴.
- ConfigMaps 및 Secrets —
envFrom, 파일 마운트 및 외부 시크릿 사용에 대한 지침. - 리소스 관리 — 워크로드 유형(웹 API, JVM, 워커, 사이드카)별 Requests vs Limits 규칙.
- RBAC — 최소 권한 ServiceAccount → Role → RoleBinding 체인.
- HPA 및 PDB — 오토스케일링 및 노드 드레인 안전 구성.
- Jobs 및 CronJobs — 올바른
restartPolicy를 사용한 일회성 및 예약된 워크로드 패턴. - kubectl 치트시트 — 로그, exec, 롤백, 포트-포워드, dry-run 및 일반적인 오류 진단 명령어.
- 안티패턴 및 체크리스트 — 하지 말아야 할 것과 보안/안정성/관찰 가능성 체크리스트.
예제
아래 섹션에서 완전하고 실행 가능한 예제를 확인하세요. 빠른 참조:
| 작업 | 이동 |
|---|---|
| 전체 프로덕션 Deployment YAML | 핵심 워크로드 패턴 |
| 프로브 구성 | 프로브 |
| RBAC 최소 권한 설정 | RBAC |
| CrashLoopBackOff 디버그 | kubectl 디버깅 치트시트 |
| 오토스케일링 | HPA |
핵심 워크로드 패턴
Deployment — 프로덕션 템플릿
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-namespace
labels:
app: my-app
version: "1.0.0"
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 업데이트 중 1개의 추가 Pod 허용
maxUnavailable: 0 # 원하는 수 이하로 절대 줄이지 않음
template:
metadata:
labels:
app: my-app
version: "1.0.0"
spec:
# Pod 수준의 보안 컨텍스트
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
# 정상 종료
terminationGracePeriodSeconds: 30
containers:
- name: my-app
image: ghcr.io/org/my-app:1.0.0 # 절대 :latest 사용 금지
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
protocol: TCP
# 리소스 요청(request) 및 제한(limit)은 모두 필수
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
# 컨테이너 보안 컨텍스트
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
# 프로브 (아래 프로브 섹션 참조)
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
# ConfigMap 및 Secret의 환경 변수
envFrom:
- configMapRef:
name: my-app-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets
key: db-password
# readOnlyRootFilesystem: true 인 경우 쓰기 가능한 tmp 디렉토리
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
프로브 — Liveness, Readiness, Startup
각 프로브를 사용해야 하는 시점을 이해하는 것이 중요합니다:
| 프로브 | 실패 시 조치 | 사용 목적 |
|---|---|---|
startupProbe | 시작이 느리면 컨테이너 종료 | 느리게 시작되는 앱 (JVM, Python) |
livenessProbe | 컨테이너 재시작 | 데드락 / 중단 프로세스 감지 |
readinessProbe | Service 엔드포인트에서 제거 | 일시적 사용 불가 (DB 재연결) |
# 올바른 패턴: startupProbe가 느린 시작을 커버하고,
# 그 다음 liveness/readiness가 인계받음
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 30 * 5s = 150s 최대 시작 시간
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 30
failureThreshold: 3 # 3 * 30s = 90s 재시작 전
readinessProbe:
httpGet:
path: /ready # 별도 엔드포인트: DB, 캐시 등을 확인
port: 8080
periodSeconds: 10
failureThreshold: 2
# 잘못됨: startupProbe 없이 initialDelaySeconds 사용
# 앱이 시작하는 데 60초가 걸리면 대신 startupProbe를 설정하세요
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # 나쁨: 임의 대기, 경쟁 조건 발생
Services 및 Ingress
Service 유형
# ClusterIP (기본값) — 내부 전용
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: my-namespace
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: ClusterIP
# LoadBalancer — 외부 트래픽 (클라우드 제공업체)
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
TLS가 있는 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: my-namespace
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: my-app-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
ConfigMaps 및 Secrets
ConfigMap — 민감하지 않은 구성
apiVersion: v1
kind: ConfigMap
metadata:
name: my-app-config
namespace: my-namespace
data:
LOG_LEVEL: "info"
APP_ENV: "production"
MAX_CONNECTIONS: "100"
# 복잡한 구성을 위해 파일로 마운트
app.yaml: |
server:
port: 8080
timeout: 30s
# ConfigMap을 파일로 마운트
volumes:
- name: config
configMap:
name: my-app-config
items:
- key: app.yaml
path: app.yaml
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
Secrets — 민감한 데이터
# 리터럴 값으로 시크릿 생성 (CLI, 그런 다음 Vault/SOPS에 저장)
kubectl create secret generic my-app-secrets \
--from-literal=db-password='s3cr3t' \
--namespace=my-namespace \
--dry-run=client -o yaml | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
namespace: my-namespace
type: Opaque
# 값은 base64로 인코딩됨 (암호화되지 않음 — 실제 암호화는 Sealed Secrets 또는 ESO 사용)
data:
db-password: czNjcjN0 # 's3cr3t'의 base64
중요: 원시 Kubernetes Secrets는 base64로 인코딩만 될 뿐, 클러스터에서 암호화를 구성하지 않으면 저장 시 암호화되지 않습니다. 프로덕션의 경우 Sealed Secrets 또는 External Secrets Operator를 사용하세요.
리소스 Requests 및 Limits
resources:
requests: # 스케줄러가 Pod 배치에 사용
cpu: "100m" # 100 millicores = 0.1 CPU
memory: "128Mi"
limits: # 이 값을 초과하면 컨테이너 종료/조절됨
cpu: "500m"
memory: "256Mi"
경험 법칙:
| 워크로드 유형 | CPU Request | Memory Request | 참고 |
|---|---|---|---|
| 웹 API | 100–250m | 128–256Mi | 제한은 요청의 2-4배로 설정 |
| 워커/컨슈머 | 250–500m | 256–512Mi | 예측 가능성을 위해 메모리 제한 = 요청 |
| JVM 앱 | 500m–1 | 512Mi–2Gi | JVM 오버헤드를 위해 -Xmx 위에 여유 공간 확보 |
| 사이드카 | 10–50m | 32–64Mi | 최소로 유지 |
# 잘못됨: 요청 또는 제한 없음 — 예측 불가능한 스케줄링, OOM 축출
containers:
- name: app
image: myapp:latest
# 리소스 누락: {} — 프로덕션에서 위험함
# 잘못됨: 제한만 있고 요청 없음 — 요청이 제한값으로 기본 설정되어 용량 과다 확보
resources:
limits:
cpu: "2"
memory: "1Gi"
# 요청 누락 — 제한값으로 기본 설정됨
RBAC — Roles 및 ServiceAccounts
최소 권한 원칙
앱이 Kubernetes API를 호출하는지 여부에 따른 두 가지 패턴:
패턴 A — 앱이 Kubernetes API가 필요하지 않음 (대부분의 앱)
ServiceAccount에서 토큰 자동 마운트를 비활성화합니다. Role/RoleBinding이 필요하지 않습니다.
# 토큰이 비활성화된 ServiceAccount — 가장 안전한 기본값
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: false # K8s API 토큰이 Pod에 주입되지 않음
# Deployment에서 참조 — 토큰 없음, API 액세스 없음
spec:
template:
spec:
serviceAccountName: my-app-sa
automountServiceAccountToken: false # 이중 안전장치: Pod 수준에서도 설정
패턴 B — 앱이 Kubernetes API가 필요함 (운영자, 컨트롤러, 구성 감시자)
토큰을 활성화하고 실제로 필요한 권한만 부여합니다.
# 1. ServiceAccount — 이 SA에 대해 토큰 활성화
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
automountServiceAccountToken: true # 토큰 필요: 앱이 K8s API 호출
# 2. Role — 앱에 필요한 것만 부여 (네임스페이스 범위)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: my-app-role
namespace: my-namespace
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"] # 읽기 전용, 특정 리소스
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["my-app-secrets"] # 이름으로 특정 시크릿으로 제한
verbs: ["get"]
# 3. Role을 ServiceAccount에 바인딩
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-rolebinding
namespace: my-namespace
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: my-namespace
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: my-app-role
# 4. Deployment에서 SA 참조
spec:
template:
spec:
serviceAccountName: my-app-sa
# automountServiceAccountToken는 SA에서 기본적으로 true — 토큰이 주입됨
Horizontal Pod Autoscaler (HPA)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
namespace: my-namespace
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2 # 고가용성을 위해 항상 최소 2개
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # 평균 CPU > 70%일 때 확장
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
HPA를 사용하려면 모든 컨테이너에
resources.requests가 설정되어 있어야 합니다. 활용률은현재 / 요청으로 계산됩니다.
PodDisruptionBudget (PDB)
노드 드레인 또는 롤링 업데이트 중 너무 많은 Pod가 다운되는 것을 방지합니다:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: my-app-pdb
namespace: my-namespace
spec:
minAvailable: 2 # 또는 maxUnavailable: 1 사용
selector:
matchLabels:
app: my-app
네임스페이스 및 멀티 테넌시
# 리소스 할당량으로 네임스페이스 생성
kubectl create namespace my-namespace
# 네임스페이스 소비를 제한하는 ResourceQuota 적용
kubectl apply -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-namespace-quota
namespace: my-namespace
spec:
hard:
requests.cpu: "4"
requests.memory: 4Gi
limits.cpu: "8"
limits.memory: 8Gi
pods: "20"
EOF
Jobs 및 CronJobs
# 일회성 Job (DB 마이그레이션, 데이터 처리)
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate
namespace: my-namespace
spec:
backoffLimit: 3 # 실패 시 최대 3번 재시도
ttlSecondsAfterFinished: 3600 # 1시간 후 자동 삭제
template:
spec:
restartPolicy: OnFailure # Job의 경우 Never (Always 아님)
containers:
- name: migrate
image: ghcr.io/org/my-app:1.0.0
command: ["python", "manage.py", "migrate"]
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# 예약된 작업을 위한 CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: cleanup
namespace: my-namespace
spec:
schedule: "0 2 * * *" # 매일 오전 2시 (KST)
jobTemplate:
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: OnFailure
containers:
- name: cleanup
image: ghcr.io/org/my-tool:latest
command: ["cleanup", "--older-than=7d"]
kubectl 디버깅 치트시트
# Pod 로그 보기 (일반 + 이전 인스턴스)
kubectl logs -n my-namespace pod-name
kubectl logs -n my-namespace pod-name --previous # 마지막 충돌 이전 로그
# exec으로 컨테이너 진입
echo "진단 실행"
kubectl exec -n my-namespace pod-name -it -- /bin/sh
kubectl exec -n my-namespace deploy/my-app -- env # 환경 변수 확인
# 롤백
kubectl rollout undo deploy/my-app -n my-namespace
kubectl rollout history deploy/my-app -n my-namespace
# 포트 포워딩 (로컬 디버깅)
kubectl port-forward -n my-namespace pod-name 8080:8080 &
# Dry-run으로 YAML 생성 (server-side 적용하지 않고 유효성 검사)
kubectl create deploy test --image=nginx --port=80 --dry-run=client -o yaml
# 상태 확인
kubectl get pods -n my-namespace -o wide
kubectl describe pod -n my-namespace pod-name # 이벤트 및 상태 세부 정보
kubectl get events -n my-namespace --sort-by='.lastTimestamp'
일반적인 오류 진단
| 오류 | 명령어 / 확인 사항 |
|---|---|
CrashLoopBackOff | kubectl logs --previous 또는 kubectl describe pod 확인, startupProbe/livenessProbe 실패 확인 |
ImagePullBackOff / ErrImagePull | 이미지 이름, 태그 및 레지스트리 자격 증명 확인 (kubectl describe pod 해당 이벤트 확인) |
OOMKilled | 리소스 제한이 너무 낮음 — kubectl logs 확인 후 limit 증가 |
Pending Pod | kubectl describe pod — 리소스 부족(CPU/메모리), PVC 바인딩 실패 또는 node-selector 불일치 확인 |
RunContainerError | kubectl logs pod-name --previous — 앱 시작 시 충돌 확인 |
안티패턴 및 체크리스트
하지 말아야 할 것 (안티패턴)
| 안티패턴 | 이유 | 올바른 방법 |
|---|---|---|
:latest 태그 사용 | 롤백 및 감사 불가 | 버전 고정 (v1.2.3, sha256:…) |
imagePullPolicy: Always | 불필요한 네트워크 호출 | IfNotPresent가 기본값이고 대부분 경우에 충분 |
| 리소스 요청/제한 누락 | 예측 불가능한 스케줄링, OOM 위험 | 항상 requests와 limits를 설정 |
initialDelaySeconds 사용 + startupProbe 누락 | 경쟁 조건, 느린 시작을 올바르게 처리하지 못함 | 대신 startupProbe 사용 |
광범위한 RBAC (cluster-admin 또는 *) | 보안 위반 시 손상 범위 증가 | 최소 권한 Role → RoleBinding 사용 |
| 시크릿을 ConfigMap에 평문으로 저장 | 민감한 데이터 노출 | Secret 리소스 사용 + ESO(External Secrets Operator) 고려 |
restartPolicy: Always를 Job에 사용 | Job 완료되지 않음 | restartPolicy: OnFailure 또는 Never 사용 |
프로덕션 준비 체크리스트
보안
-
runAsNonRoot: true및readOnlyRootFilesystem: true설정 - 사용하지 않는 capabilities를 모두 드롭 (
drop: ["ALL"]) -
automountServiceAccountToken: false(앱이 K8s API가 필요하지 않은 경우) - Sealed Secrets 또는 ESO로 Secrets 암호화
- RBAC: 최소 권한(특정 리소스 및 verbs)
- 컨테이너 이미지를 취약점에 대해 스캔
안정성
- 최소 2개의 복제본 (
replicas: 2), 롤링 업데이트 전략 - startupProbe + livenessProbe + readinessProbe 모두 올바르게 구성
- PodDisruptionBudget 설정
- 올바른 리소스 requests 및 limits
- HPA: 최소 및 최대 복제본, 대상 활용률 설정
-
terminationGracePeriodSeconds: 30초 이상 (SIGTERM 처리에 충분한 시간)
관찰 가능성
- Health 엔드포인트 (
/health,/ready) - Prometheus 메트릭 엔드포인트 (선택 사항)
- 구조화된 로깅 (JSON)
- 중앙 집중식 로깅 설정 (예: Loki, Elastic)
리소스 및 추가 정보
- Kubernetes 공식 문서
- Kubernetes 패턴 (Red Hat)
- CNCF Landscape (Kubernetes 관련 도구 보기)
- kubectl 치트시트