8. Deployment와 롤아웃
Deployment·ReplicaSet·Pod, 왜 세 층인가
Deployment는 버전을 관리하고, ReplicaSet은 개수를 관리합니다. 새 버전을 배포하면 새 ReplicaSet이 생기고 옛 ReplicaSet은 개수가 0으로 줄어들 뿐 지워지지 않습니다 — 이것이 롤백이 가능한 이유입니다.
배포 전 Deployment → RS(v1)=3 → Pod 3개
배포 중 Deployment → RS(v1)=1, RS(v2)=2 → Pod 3개
배포 후 Deployment → RS(v1)=0, RS(v2)=3 → Pod 3개
Pod 이름 가운데 토막이 ReplicaSet 해시입니다. kubectl get deploy,rs,pod로 세 계층을 한 번에 확인할 수 있습니다.
정리: 옛 ReplicaSet이 0으로 남는 것 = 롤백 가능한 이력. revisionHistoryLimit이 몇 개를 남길지 정합니다(기본 10).
핵심 필드: maxSurge / maxUnavailable
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 목표보다 최대 1개 더 만들어도 된다
maxUnavailable: 0 # 사용 가능한 개수는 절대 줄지 않는다
새 Pod를 하나 띄우고 Ready를 기다린 뒤 옛 Pod를 하나 내리는 것을 반복합니다. readinessProbe가 없으면 쿠버네티스는 컨테이너가 뜨자마자 Ready로 보고 트래픽을 보내버립니다.
실무: maxUnavailable: 0이면 배포 도중에도 용량이 줄지 않습니다.
롤아웃은 비동기다 — 반드시 확인하고 끝내라
kubectl set image deploy/shop-api app=shop-api:1.0.1
kubectl rollout status deploy/shop-api --timeout=180s
kubectl rollout history deploy/shop-api
kubectl rollout undo deploy/shop-api
kubectl rollout undo deploy/shop-api --to-revision=3
실무: CI는 apply만 하고 끝내면 실패한 배포를 성공으로 보고합니다. 반드시 rollout status로 결과를 판단해야 합니다.
배포가 멈추면(새 Pod가 Ready가 안 됨) 옛 Pod는 그대로 남아 서비스는 안전합니다. 원인을 보고 undo로 되돌리면 됩니다. 롤백은 매니페스트만 되돌릴 뿐 DB 스키마 변경 등은 되돌리지 않습니다.
무중단 배포는 다섯 가지가 다 맞아야 한다
- readinessProbe — 없으면 기동 중인 Pod로 트래픽이 간다
- maxUnavailable: 0 — 없으면 배포 중 용량이 줄어 지연·오류
- preStop sleep — 없으면 종료 중인 Pod로 트래픽이 남는다
- graceful shutdown — 없으면 처리 중이던 요청이 끊긴다
- 복제본 2개 이상 — 1개면 교체하는 동안 서비스가 빈다
스케일 조정과 HPA 충돌 주의
kubectl scale deploy/shop-api --replicas=6
kubectl autoscale deploy/shop-api --min=3 --max=10 --cpu-percent=70
주의: HPA가 붙은 Deployment의 매니페스트에 replicas: 3이 남아 있으면 apply할 때마다 3으로 되돌아갑니다.
9. Service와 클러스터 네트워킹
Pod IP를 못 쓰는 이유
Pod는 재시작·스케일·배포 때마다 IP가 바뀝니다. Service는 클러스터 수명 동안 안 바뀌는 가상 IP를 하나 가지고, 그 뒤의 실제 Pod 목록(Endpoints)만 자동으로 갱신됩니다.
Service에는 실체가 없다
ClusterIP로 ping이 안 되는 게 정상입니다. Service는 각 노드의 iptables에 박힌 규칙일 뿐, 그 IP로 Listen하는 프로세스는 어디에도 없습니다. 확인은 ping이 아니라 curl/nc로 합니다.
Endpoints — 문제 진단의 시작점
kubectl get svc shop-api
kubectl get endpoints shop-api # 여기가 비면 연결 자체가 거부된다
비어 있다면 원인은 둘 중 하나: ① Service의 selector가 Pod 라벨과 안 맞음 ② Ready인 Pod가 없음.
실무: 연결이 안 되면 get endpoints부터 봅니다.
Service의 종류
실무에서 주로 쓰는 건 ClusterIP(기본값, 클러스터 안에서만), LoadBalancer(외부, Service 하나당 LB 하나라 비쌈), Headless(clusterIP: None, StatefulSet·DB 클라이언트용). NodePort는 운영에서 잘 안 씁니다.
클러스터 DNS
<서비스이름>.<네임스페이스>.svc.cluster.local
같은 네임스페이스 → http://shop-api
다른 네임스페이스 → http://shop-api.class-6
실무: 앱 설정에는 반드시 Service 이름을 씁니다. IP를 적으면 Service를 다시 만들 때마다 전부 고쳐야 합니다.
연결이 안 될 때 — 아래에서 위로 좁힌다
curl 10.0.1.5:8080/health # ① Pod에 직접
curl 172.20.1.8:80/health # ② Service IP로
curl http://shop-api/health # ③ Service 이름으로
kubectl get endpoints shop-api # ④ Endpoints 확인
[9장 요약]
- 연결 안 됨의 90%는 Endpoints가 비어 있는 것 — 원인은 라벨 아니면 Ready 여부
- Service 세션 고정은 임시방편일 뿐, 진짜 해법은 세션을 Redis나 JWT로 밖에 빼는 것
10. 호출 흐름 추적
요청 하나가 사용자에서 컨테이너 프로세스까지 여섯 구간을 지납니다.
- ① 도메인 → LB: DNS 레코드·인증서 (dig, kubectl get ingress)
- ② LB → Pod: 대상그룹에 Pod IP 직접 등록 (readiness 통과분만)
- ③④ Service → Pod (클러스터 내부): 이름→ClusterIP→PodIP, iptables가 조용히 두 번 변환 (get endpoints로 확인)
- ⑤⑥ 컨테이너 안: 포트·바인딩 주소 (exec -- ss -ltnp)
정리: 운영에서 가장 자주 끊기는 곳은 ④입니다. readiness가 떨어지면 Endpoints에서 즉시 빠지고, 그 순간 트래픽이 멎습니다.
가장 흔한 마지막 함정: 앱이 127.0.0.1에만 바인딩하면 컨테이너 밖에서 못 붙습니다. 0.0.0.0으로 Listen해야 합니다.
장애가 나면 복구를 먼저, 원인은 그다음
- 최근 배포·설정 변경이 있었나 → 변경 있으면 먼저 되돌린다
- Pod IP로 직접 curl → 문제면 컨테이너로
- get endpoints → selector·readiness 확인
- Service 이름으로 curl → DNS 또는 규칙 문제
- get ingress ADDRESS → 컨트롤러·애노테이션 확인
실무: "원인을 알고 되돌리자"는 생각이 장애를 30분에서 3시간으로 늘립니다.
11. Ingress와 외부 노출
Ingress가 푸는 문제
Service마다 LoadBalancer를 만들면 LB 개수만큼 요금이 나갑니다. Ingress는 L7(HTTP)이라 경로·호스트·헤더로 분기해서 LB 하나를 여러 서비스가 공유하게 만듭니다.
정리: Ingress = 라우팅 규칙, Ingress Controller = 그 규칙을 실제 LB로 만드는 프로그램. 컨트롤러가 없으면 Ingress를 만들어도 아무 일도 안 일어납니다.
EKS에서는 AWS Load Balancer Controller가 표준이며, Ingress를 만들면 실제 ALB가 자동 생성됩니다.
매니페스트와 경로 우선순위
metadata:
annotations:
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/group.name: team-a
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /api
pathType: Prefix
backend: { service: { name: shop-api, port: { number: 80 } } }
경로가 겹치면 정확한 경로(Exact) > 긴 접두사 > 짧은 접두사 순으로 이깁니다.
주의: group.name을 안 주면 Ingress마다 ALB가 따로 생겨 요금이 늡니다. host 없는 Ingress는 catch-all이 되므로 운영에서는 반드시 host를 지정합니다.
HTTPS는 ALB에서 끝난다
TLS 종료는 ALB에서 일어나고 앱은 평문 HTTP를 받습니다 — 원래 프로토콜을 알려면 X-Forwarded-Proto 헤더를 봐야 합니다.
안 될 때
- ADDRESS가 계속 비어 있음 → 컨트롤러 없음·IAM 권한 부족·서브넷 태그 누락
- 502 Bad Gateway → 타깃(Pod)이 응답하지 않음
- 503 Service Unavailable → 등록된 타깃이 없음
- 504 Gateway Timeout → 앱 응답이 ALB 유휴 타임아웃(60초) 초과
12. 설정과 스토리지
설정을 왜 밖으로 빼나
같은 이미지가 모든 환경에서 돌아야 합니다(12-Factor 3번 원칙). 이미지에 설정을 넣으면 환경마다 다른 이미지가 되어 "검증한 것"과 "배포한 것"이 달라집니다.
주입 방식 두 가지, 갱신 여부가 다르다
- 환경변수(env, envFrom): 컨테이너 시작 시점에 고정. ConfigMap을 고쳐도 이미 뜬 컨테이너는 안 바뀐다
- 볼륨 마운트: ConfigMap을 고치면 파일 내용은 자동 갱신(약 1분 이내)되지만, 앱이 그 파일을 다시 읽어야 의미가 있다
체크: 설정이 이상하면 매니페스트를 읽지 말고 kubectl exec -- env로 실물을 확인합니다.
Secret은 암호화가 아니다
kubectl get secret shop-secret -o jsonpath='{.data.db-password}' | base64 -d
# 1초 만에 평문으로 되돌아온다
base64는 인코딩일 뿐입니다. RBAC으로 Secret 읽기 권한을 제한하는 것이 첫 번째 방어선입니다.
주의: Secret을 Git에 커밋하지 마세요.
볼륨의 세 가지 수명
- (볼륨 없음) — 컨테이너 재시작까지, 임시 계산용
- emptyDir — Pod 수명, 사이드카와 파일 공유
- PVC — Pod와 무관하게 유지, 업로드 파일·DB 데이터
- hostPath — 노드 수명, 쓰지 않는다(보안 위험)
PV · PVC · StorageClass
PVC는 요청서("10Gi 필요해요"), PV는 실물(실제 EBS 볼륨), StorageClass는 만드는 방법(gp3·io2 등). 앱 개발자는 PVC만 작성하면 나머지는 자동 생성됩니다.
주의: RWO 볼륨을 쓰는 Deployment는 replicas를 2 이상으로 못 올립니다 — 두 번째 Pod가 Pending에 머뭅니다.
13. 리소스와 배포 실전
requests vs limits — 쓰임이 완전히 다르다
- requests: 스케줄러가 씀, "이만큼은 보장받는다", 안 적으면 스케줄러가 0으로 본다
- limits: 노드 커널(cgroup)이 씀, "이 이상은 못 쓴다", CPU 초과 시 스로틀링, 메모리 초과 시 OOMKilled(즉시 종료)
주의: CPU와 메모리는 초과 시 동작이 다릅니다. 메모리 limit이 훨씬 위험합니다.
QoS 클래스 — 축출 순서를 정한다
- Guaranteed: requests=limits, 가장 나중에 축출 (DB·핵심 API)
- Burstable: requests < limits, 중간
- BestEffort: 아무것도 안 적음, 가장 먼저 축출
Graceful shutdown 시간 계산은 커져야 한다
preStop sleep(5초) < server.shutdown 유예(25초) < terminationGracePeriodSeconds(40초)
이 순서가 어긋나면 앱이 정리를 마치기 전에 SIGKILL이 옵니다.
배포 전 최종 점검표
- 이미지 태그가 latest가 아닌 커밋 SHA인가
- non-root로 도는가
- requests/limits가 있는가
- 세 프로브(startup·liveness·readiness)가 다 있는가
- liveness가 DB를 확인하지 않는가
- graceful shutdown이 되는가
- 비밀값이 Git 이력에도 없는가
장애 대응 순서
① 범위 확인 → ② 최근 변경 확인 → ③ 되돌리기(있으면 먼저) → ④ 영향 축소 → ⑤ 원인 조사(서비스 안정 후) → ⑥ 기록 → ⑦ 재발 방지
실무: "원인을 알고 되돌리자"는 생각이 장애를 30분에서 3시간으로 늘립니다.
'AI' 카테고리의 다른 글
| LangChain으로 LLM Agent 서비스 개발 (0) | 2026.09.09 |
|---|---|
| 쿠버네티스 - 3. 셀프 정리 (0) | 2026.09.09 |
| 쿠버네티스 - 1. 아키텍쳐, kubectl (0) | 2026.09.08 |
| 머신러닝와 딥러닝의 이해 -3. DL 아키텍쳐 (1) | 2026.09.03 |
| 머신러닝 및 딥러닝의 이해 - 2. DL (0) | 2026.09.02 |