쿠버네티스 실전 치트시트
- 쿠버네티스 기반으로 실제 서비스를 구축/운영할 때 바로 찾아보는 용도
- 기초 개념 설명은 생략, "왜 써야 하는지 + 옵션 리스트" 위주로 정리
1. 클러스터 내부 DNS (Service Discovery)
왜 써야 하나
- 클러스터 내부 통신은 반드시 내부 도메인으로. 트래픽이 클러스터 외부(LB, 공인망)로 나갔다 들어오지 않으므로 레이턴시/비용/보안 모두 이득. MSA 설계 시 필수
- Pod/Deployment 재생성으로 IP가 바뀌어도 도메인에 자동 매핑됨 → IP 하드코딩 금지
도메인 양식
| 대상 | 형식 | 예시 |
|---|---|---|
| Service | <svc>.<ns>.svc.cluster.local | order-api.commerce.svc.cluster.local |
| Pod | <pod-ip-dashed>.<ns>.pod.cluster.local | 10-10-10-10.commerce.pod.cluster.local |
| Headless Service의 개별 Pod | <pod-name>.<svc>.<ns>.svc.cluster.local | mysql-0.mysql.db.svc.cluster.local |
- 같은 네임스페이스면
order-api만으로 호출 가능, 다른 네임스페이스면order-api.commerce까지 (FQDN 전체는 안 써도 됨) - 단, 트래픽 많은 서비스는 FQDN(끝에
.포함:order-api.commerce.svc.cluster.local.)을 쓰면 ndots 기반 불필요한 DNS 질의를 줄일 수 있음
spec.dnsPolicy
| 값 | 동작 |
|---|---|
ClusterFirst | 기본값. cluster.local 매칭 안 되는 도메인은 upstream DNS로 질의 |
Default | 파드가 뜬 노드의 DNS 설정(/etc/resolv.conf)을 그대로 상속 |
ClusterFirstWithHostNet | hostNetwork: true로 실행하는 파드에 반드시 지정 |
None | 클러스터 DNS 무시. spec.dnsConfig로 직접 설정 필수 |
dnsConfig 튜닝 (외부 API 호출 많은 서비스)
yaml
spec:
dnsConfig:
options:
- name: ndots
value: "1" # 기본 5 → 외부 도메인 질의 시 search 도메인 순회 방지CoreDNS
- v1.11+ 기본 DNS.
kube-system네임스페이스, 설정은corednsConfigMap의 Corefile - 플러그인 구조:
errors,health,kubernetes,forward,cache,rewrite등 - 실무 팁
- DNS 병목 시: NodeLocal DNSCache 도입 (노드별 DNS 캐시 데몬셋, conntrack 이슈도 완화)
- 특정 외부 도메인 강제 매핑: Corefile에
rewrite또는hosts플러그인 - 디버깅:
kubectl run -it --rm dns-test --image=busybox:1.36 -- nslookup <svc>
2. Service 타입
| 타입 | 용도 | 비고 |
|---|---|---|
ClusterIP | 클러스터 내부 통신 (기본값) | 내부 MSA 호출은 전부 이것으로 |
NodePort | 노드 IP:포트(30000-32767)로 노출 | 직접 노출용으론 비권장, LB 뒷단으로만 |
LoadBalancer | CSP LB 자동 프로비저닝 | 서비스당 LB 1개 = 비용↑. Ingress/Gateway 뒤로 모으는 게 정석 |
ExternalName | 외부 도메인의 CNAME 별칭 | 외부 DB 등을 내부 도메인처럼 호출 |
Headless (clusterIP: None) | 개별 Pod DNS 필요 시 | StatefulSet(DB, Kafka 등)과 세트 |
sessionAffinity: ClientIP— 세션 고정 필요 시 (기본 None)externalTrafficPolicy: Local— 클라이언트 소스 IP 보존 + 불필요한 노드 홉 제거 (LB/NodePort 타입에서)internalTrafficPolicy: Local— 같은 노드 내 파드로만 라우팅 (데몬셋 로컬 호출 등)
3. Ingress → Gateway API
현황 (2026)
- ingress-nginx는 2026년 3월부로 리타이어(유지보수 종료). 신규 구축은 Gateway API가 표준
- Ingress 리소스 자체는 남아있지만 frozen 상태 — 신규 기능은 전부 Gateway API로만 추가됨
- 마이그레이션 도구:
ingress2gateway - 구현체: Envoy Gateway, Istio, Cilium Gateway, Traefik, NGINX Gateway Fabric, 각 CSP LB 컨트롤러
Ingress(레거시) 핵심만
- L7(HTTP) 라우팅 규칙 모음. TLS 종료, 경로/호스트 기반 라우팅
- NodePort/ExternalIP는 L4라 세부 라우팅 불가 → L7 필요하면 Ingress/Gateway
- 주요 필드:
ingressClassName,rules[].host,paths[].pathType(Prefix|Exact|ImplementationSpecific),tls[]
Gateway API 구조 (역할 분리가 핵심)
| 리소스 | 담당 | 역할 |
|---|---|---|
GatewayClass | 인프라 관리자 | 어떤 구현체(컨트롤러)를 쓸지 |
Gateway | 클러스터 운영자 | LB 인스턴스, 리스너(포트/프로토콜/TLS) 정의 |
HTTPRoute / GRPCRoute / TCPRoute / TLSRoute | 앱 개발자 | 실제 라우팅 규칙 |
yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: order-route
spec:
parentRefs:
- name: main-gateway
hostnames: ["api.example.com"]
rules:
- matches:
- path: { type: PathPrefix, value: /orders }
backendRefs:
- name: order-api
port: 8080- HTTPRoute에서 헤더 매칭, 트래픽 가중치(카나리), 헤더 수정, 리다이렉트, 미러링까지 표준 스펙으로 지원 (Ingress는 전부 벤더 어노테이션이었음)
- TLS 인증서는 cert-manager + Let's Encrypt 조합이 사실상 표준
4. 네트워크 (CNI / NetworkPolicy)
CNI 플러그인 선택
| Calico | Cilium | |
|---|---|---|
| 기반 | BGP 기반 L3 (iptables/eBPF 모드) | eBPF 네이티브 |
| 강점 | 성숙도, NetworkPolicy 완성도, 온프렘 BGP 연동 | 성능(kube-proxy 대체), L7 정책, Hubble 관측성, Gateway API 내장 |
| 특징 | 네트워크 장비가 지원하면 네이티브 라우팅 구성 가능 | XDP로 경로 단축 → 동일 노드 컨테이너 간 통신 성능 大 향상 (NIC 단계 skip) |
- 신규 클러스터라면 Cilium 우세 (kube-proxy replacement + Hubble + L7 policy)
- EKS/GKE/AKS는 기본 CNI 쓰다가 필요 시 교체 검토 (교체 비용 큼)
NetworkPolicy — 운영 시 필수
- 기본은 전부 허용. 네임스페이스 단위 default-deny부터 시작이 정석
yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {} # 네임스페이스 내 모든 파드
policyTypes: [Ingress, Egress]- 이후 필요한 통신만
podSelector/namespaceSelector/ipBlock으로 화이트리스트 - 주의: DNS(53/UDP) egress는 항상 열어줘야 함 (안 열면 전체 장애처럼 보임)
5. 워크로드 리소스 선택
| 리소스 | 언제 | 핵심 옵션 |
|---|---|---|
Deployment | 무상태 앱 (대부분) | strategy.rollingUpdate.maxSurge/maxUnavailable |
StatefulSet | DB, Kafka 등 (고정 ID/스토리지 필요) | volumeClaimTemplates, podManagementPolicy: Parallel, Headless Service 필수 |
DaemonSet | 노드당 1개 (로그수집, 모니터링 에이전트) | updateStrategy, toleration으로 마스터 노드 포함 여부 |
Job | 일회성 배치 | backoffLimit, activeDeadlineSeconds, ttlSecondsAfterFinished, completions/parallelism |
CronJob | 주기 배치 | concurrencyPolicy(Forbid |
배포 전략
- RollingUpdate 기본값:
maxSurge: 25%,maxUnavailable: 25% - 무중단 보장하려면
maxUnavailable: 0+ readinessProbe 필수 - 카나리/블루그린이 필요하면 → Argo Rollouts 또는 Gateway API 가중치 라우팅
6. Probe 3종 — 무중단 운영의 기본
| Probe | 실패 시 | 용도 |
|---|---|---|
livenessProbe | 컨테이너 재시작 | 데드락 등 회복 불가 상태 감지. 보수적으로 설정 (외부 의존성 체크 넣지 말 것 — DB 죽었다고 앱 전체 재시작 루프 돔) |
readinessProbe | Service 엔드포인트에서 제외 | 트래픽 받을 준비 확인. 외부 의존성 체크는 여기에 |
startupProbe | 재시작 (성공 전까지 다른 probe 정지) | 기동 느린 앱(JVM 등). liveness의 initialDelay 대용 |
yaml
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30 # 30 × 10s = 최대 5분 기동 허용
periodSeconds: 10
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
periodSeconds: 10
failureThreshold: 3- 공통 옵션:
initialDelaySeconds,periodSeconds,timeoutSeconds,successThreshold,failureThreshold - 방식:
httpGet|tcpSocket|exec|grpc
Graceful Shutdown (배포 시 502 방지 세트)
yaml
spec:
terminationGracePeriodSeconds: 60 # 기본 30
containers:
- lifecycle:
preStop:
exec: { command: ["sleep", "10"] } # 엔드포인트 제거 전파 대기- SIGTERM 핸들링은 앱에서 구현 (in-flight 요청 drain 후 종료)
- 순서: 엔드포인트 제거와 SIGTERM이 동시에 발생 → preStop sleep으로 시차 흡수
7. 리소스 관리 (requests/limits, QoS)
yaml
resources:
requests: { cpu: 500m, memory: 512Mi } # 스케줄링 기준
limits: { memory: 512Mi } # 초과 시 제재- requests = 스케줄러가 노드 배치에 쓰는 값. 반드시 설정 (없으면 노드 과밀 → 연쇄 장애)
- memory limit 초과 = OOMKilled, cpu limit 초과 = 스로틀링(죽진 않음)
- 실무 권장:
memory는 requests=limits 동일하게,cpu는 limits 생략(스로틀링 방지) 또는 여유있게
QoS 클래스 (노드 메모리 부족 시 축출 순서)
| 클래스 | 조건 | 축출 우선순위 |
|---|---|---|
Guaranteed | 모든 컨테이너 requests=limits | 마지막 |
Burstable | requests < limits (일부만 설정) | 중간 |
BestEffort | 아무것도 미설정 | 가장 먼저 죽음 |
- 네임스페이스 가드레일:
LimitRange(파드 기본값/상한),ResourceQuota(네임스페이스 총량) - 1.33+: In-place Pod Resize — 재시작 없이 리소스 변경 가능
8. 스케줄링 — Pod를 원하는 노드에 배치
스케줄러는 Filtering(배치 가능 노드 선별) → Scoring(최적 노드 점수화) 순으로 동작. 볼륨/리소스/토폴로지/셀렉터 필터 등이 관여
도구 선택 기준
| 도구 | 방향 | 용도 |
|---|---|---|
nodeSelector | Pod→Node | 단순 레이블 매칭 (GPU 노드 등) |
nodeAffinity | Pod→Node | 조건식(In/NotIn/Exists), soft/hard 지정 가능 |
podAffinity / podAntiAffinity | Pod→Pod | 특정 파드와 같이/떨어뜨려 배치 (HA 필수: replica를 다른 노드로) |
taint + toleration | Node→Pod | 노드가 파드를 밀어냄. 전용 노드풀(GPU, 배치 전용) 구성 |
topologySpreadConstraints | 분산 | zone/node 단위 균등 분산. anti-affinity보다 유연 |
priorityClass | 우선순위 | 리소스 부족 시 낮은 우선순위 파드 선점(preemption) |
hard vs soft
requiredDuringSchedulingIgnoredDuringExecution— 필수 (못 맞추면 Pending)preferredDuringSchedulingIgnoredDuringExecution— 선호 (best effort)
실무 기본 세트: zone 분산 + 동일 노드 회피
yaml
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway # zone 분산은 soft
labelSelector: { matchLabels: { app: order-api } }
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule # 노드 분산은 hard
labelSelector: { matchLabels: { app: order-api } }Taint 예시 (전용 노드풀)
bash
kubectl taint nodes gpu-node-1 dedicated=gpu:NoSchedule- effect:
NoSchedule|PreferNoSchedule|NoExecute(기존 파드도 축출)
9. 오토스케일링
| 도구 | 대상 | 기준 |
|---|---|---|
| HPA | Pod 수 | CPU/메모리/커스텀 메트릭 |
| VPA | Pod 리소스 크기 | 사용량 히스토리 (HPA와 같은 메트릭에 동시 사용 금지) |
| KEDA | Pod 수 (0까지) | 이벤트 소스(Kafka lag, SQS, cron 등 60+) — 배치/컨슈머는 이거 |
| Cluster Autoscaler / Karpenter | 노드 수 | Pending 파드. Karpenter가 노드풀 관리 없이 더 빠르고 유연 (AWS 표준) |
yaml
# HPA 핵심
spec:
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource: { name: cpu, target: { type: Utilization, averageUtilization: 60 } }
behavior: # 급격한 스케일인 방지
scaleDown:
stabilizationWindowSeconds: 300- HPA 쓰려면 Deployment에
replicas고정값 빼기 (GitOps 충돌 방지)
10. 가용성 (PDB)
yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: { name: order-api-pdb }
spec:
minAvailable: 2 # 또는 maxUnavailable: 1
selector: { matchLabels: { app: order-api } }- 노드 drain(업그레이드, 스팟 회수) 같은 자발적 중단에서 최소 가용 파드 수 보장
- 없으면 노드 업그레이드 때 전 replica 동시 축출 가능 → 운영 서비스 전부 필수
- 주의:
minAvailable = replicas로 잡으면 drain이 영원히 안 끝남 (best-effort 보호지 하드 보장 아님)
11. 스토리지 (PV / PVC / StorageClass)
- 구조: PV(볼륨 실체) ↔ PVC(사용자의 요청) — 파드와 스토리지를 분리(디커플링)
- 실무에선 PV 수동 생성 거의 안 함 → StorageClass + 동적 프로비저닝이 기본
| 항목 | 옵션 | 메모 |
|---|---|---|
accessModes | RWO(노드1개 rw) / ROX / RWX(다중노드 rw) / RWOP(파드1개) | 블록스토리지(EBS)는 RWO만. RWX 필요하면 NFS/EFS/CephFS |
persistentVolumeReclaimPolicy | Retain / Delete | 운영 데이터는 Retain (PVC 지워도 데이터 보존) |
volumeBindingMode | WaitForFirstConsumer / Immediate | 멀티 AZ에선 WaitForFirstConsumer (파드 뜨는 zone에 볼륨 생성) |
allowVolumeExpansion | true/false | true면 PVC 수정만으로 용량 확장 |
- 스냅샷:
VolumeSnapshot(CSI). 백업 체계는 Velero가 표준 - 상태ful 워크로드는 가능하면 매니지드(RDS 등)로 빼는 게 운영 부담 최소화
12. 설정과 시크릿
| ConfigMap | Secret | |
|---|---|---|
| 용도 | 일반 설정 | 자격증명, 인증서 |
| 주의 | 1MB 제한 | base64일 뿐 암호화 아님 → etcd encryption at rest + RBAC 필수 |
- 주입 방식:
env/envFrom/ 볼륨 마운트- 볼륨 마운트는 변경 시 자동 반영(kubelet sync 주기), env는 파드 재시작 필요
immutable: true— 변경 불가 처리 (실수 방지 + kubelet watch 부하 감소)- ConfigMap 변경 시 롤아웃 트리거: 해시를 어노테이션에 넣거나 (
checksum/config), Reloader/Kustomize configMapGenerator 사용 - 실무 시크릿 관리: External Secrets Operator(Vault/AWS Secrets Manager 연동) 또는 Sealed Secrets — 시크릿을 git에 평문으로 두지 말 것
13. 보안 기본기
securityContext (파드/컨테이너 스펙에 기본 탑재 권장)
yaml
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
seccompProfile: { type: RuntimeDefault }기타 체크리스트
- RBAC: ServiceAccount별 최소 권한. 기본 SA에 권한 주지 말고,
automountServiceAccountToken: false(API 안 쓰는 파드) - Pod Security Standards: 네임스페이스 레이블로 강제 —
pod-security.kubernetes.io/enforce: restricted - 이미지: 태그
latest금지(다이제스트/시맨틱 버전), 프라이빗 레지스트리 + 스캔(Trivy) - 정책 엔진 필요 시: Kyverno (OPA Gatekeeper보다 진입장벽 낮음)
14. 운영 kubectl 치트시트
bash
# 상태 파악 (장애 시 이 순서로)
kubectl get events --sort-by=.lastTimestamp -A # 최근 이벤트부터
kubectl get pods -o wide --field-selector=status.phase!=Running -A
kubectl describe pod <pod> # Events 섹션 확인
kubectl logs <pod> --previous # 죽기 직전 로그 (CrashLoop 필수)
kubectl top pods --sort-by=memory # 리소스 사용량
# 배포 관리
kubectl rollout status deploy/<name>
kubectl rollout undo deploy/<name> # 즉시 롤백
kubectl rollout restart deploy/<name> # 재기동 (ConfigMap 변경 반영 등)
kubectl rollout history deploy/<name>
# 디버깅
kubectl exec -it <pod> -- sh
kubectl debug <pod> -it --image=nicolaka/netshoot # 유틸 없는 distroless 디버깅
kubectl debug node/<node> -it --image=busybox # 노드 진입
kubectl port-forward svc/<svc> 8080:80
# 노드 운영
kubectl cordon <node> # 신규 스케줄 차단
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
kubectl uncordon <node>
# 리소스 탐색
kubectl explain deploy.spec.strategy # 필드 문서 즉석 확인
kubectl get <res> -o yaml | kubectl neat # 깔끔한 yaml
kubectl api-resources # 리소스 약어 확인자주 겪는 상태 → 원인
| 상태 | 1순위 의심 |
|---|---|
Pending | 리소스 부족(requests 과다), taint, PVC 바인딩 실패 → describe의 Events |
CrashLoopBackOff | 앱 기동 실패 → logs --previous |
OOMKilled (exit 137) | memory limit 초과 |
ImagePullBackOff | 이미지 태그 오타, imagePullSecrets 누락 |
Evicted | 노드 디스크/메모리 압박 |
| Readiness 실패로 트래픽 0 | probe 경로/포트 오타, 의존 서비스 다운 |
15. 프로덕션 매니페스트 최소 체크리스트
배포 전 이것만은 확인:
- [ ]
resources.requests설정 (memory는 limits까지) - [ ]
readinessProbe+livenessProbe(+ 기동 느리면startupProbe) - [ ] replicas ≥ 2 +
topologySpreadConstraints또는 podAntiAffinity - [ ]
PodDisruptionBudget - [ ] graceful shutdown:
preStopsleep +terminationGracePeriodSeconds - [ ]
securityContext(nonRoot, drop ALL) - [ ] 이미지 태그 고정 (latest 금지)
- [ ] HPA 쓰면 replicas 필드 제거
- [ ] 시크릿 git 평문 금지 (ESO/Sealed Secrets)
- [ ] NetworkPolicy default-deny + 화이트리스트
- [ ] 로그는 stdout/stderr로 (파일 로깅 금지)