본문으로 건너뛰기

1.4 첫 배포

클러스터가 준비됐으니 nginx를 실제로 배포한다. 이 절은 생성, 관찰, 스케일, 노출, 업데이트, 삭제로 이어지는 한 사이클을 끝까지 돈다. 여기에 등장하는 Deployment와 ReplicaSet은 Part IV. 워크로드에서, Service는 Part V. 네트워킹에서 깊게 다루므로, 지금은 각 개념의 정의보다 전체 흐름의 감각을 익히는 것이 목표다.

Deployment 생성과 관찰

Deployment 생성

kubectl create deployment nginx --image=nginx:1.27
deployment.apps/nginx created

Deployment는 “이 이미지의 Pod를 이만큼 유지하라"는 선언을 담는 워크로드 리소스다. replica 수를 지정하지 않았으므로 기본값 1로 생성된다.

상태 확인

kubectl get deployment nginx
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   1/1     1            1           30s
kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
nginx-86dcfdf4c6-w6k5b   1/1     Running   0          45s

Pod 이름에 임의 문자열이 붙어 있다. Deployment는 Pod를 직접 만들지 않고 중간에 ReplicaSet이라는 리소스를 만들며, Pod 이름은 Deployment 이름-ReplicaSet 해시-임의 문자열 구조가 된다.

상세 관찰

kubectl describe deployment nginx

describe는 리소스 하나의 스펙과 상태, 관련 이벤트를 사람이 읽기 좋게 정리해 주는 명령으로, 무엇이든 이상할 때 가장 먼저 실행하게 된다. 출력 끝의 Events에 지금까지 일어난 일이 기록돼 있다.

Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  1m    deployment-controller  Scaled up replica set nginx-86dcfdf4c6 from 0 to 1

scale과 Service 노출

replica를 3으로 늘린다.

kubectl scale deployment nginx --replicas=3
kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
nginx-86dcfdf4c6-w6k5b   1/1     Running   0          3m
nginx-86dcfdf4c6-9zjqm   1/1     Running   0          10s
nginx-86dcfdf4c6-dv27x   1/1     Running   0          10s

1.1에서 본 reconciliation을 눈으로 확인할 기회다. Pod 하나를 지워 본다.

kubectl delete pod nginx-86dcfdf4c6-9zjqm
kubectl get pods
NAME                     READY   STATUS              RESTARTS   AGE
nginx-86dcfdf4c6-w6k5b   1/1     Running             0          4m
nginx-86dcfdf4c6-dv27x   1/1     Running             0          70s
nginx-86dcfdf4c6-hs8rt   0/1     ContainerCreating   0          2s

삭제 직후 새 Pod가 만들어지고 있다. “3개 유지"라는 선언이 살아 있는 한, Pod를 지우는 행위조차 선언과 현실 사이의 차이를 만드는 사건일 뿐이다.

Pod에는 각자 IP가 있지만 죽고 다시 만들어질 때마다 바뀐다. 고정된 접속 지점이 필요하므로 Service를 만든다.

kubectl expose deployment nginx --type=NodePort --port=80
kubectl get service nginx
NAME    TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
nginx   NodePort   10.96.145.31   <none>        80:31530/TCP   5s

NodePort 타입은 모든 노드의 같은 포트(기본 범위 30000~32767, 여기서는 31530)에서 트래픽을 받아 뒤의 Pod들로 분산한다. 다만 kind의 노드는 Docker 컨테이너라서 이 포트가 호스트에 바로 노출되지 않는다. 호스트에서 NodePort로 직접 접근하려면 kind config에 extraPortMappings를 추가해야 하는데, 학습 단계에서는 port-forward가 간단하다.

kubectl port-forward service/nginx 8080:80

다른 터미널에서 확인한다.

curl -s http://localhost:8080 | head -n 4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

롤링 업데이트

이미지 버전을 바꿔 업데이트를 일으킨다.

kubectl set image deployment/nginx nginx=nginx:1.28
kubectl rollout status deployment/nginx
Waiting for deployment "nginx" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx" rollout to finish: 1 old replicas are pending termination...
deployment "nginx" successfully rolled out

교체 도중 다른 터미널에서 kubectl get pods를 실행하면 이전 Pod와 새 Pod가 섞여 있는 순간이 보인다.

NAME                     READY   STATUS              RESTARTS   AGE
nginx-86dcfdf4c6-w6k5b   1/1     Running             0          10m
nginx-86dcfdf4c6-dv27x   1/1     Terminating         0          7m
nginx-7d98b57f45-min4x   1/1     Running             0          8s
nginx-7d98b57f45-tk2fp   0/1     ContainerCreating   0          2s

이미지가 바뀌면 Deployment는 새 ReplicaSet을 만들고(Pod 이름의 해시가 달라진 이유다), 새 Pod를 하나씩 늘리면서 이전 Pod를 하나씩 줄인다. 기본 전략인 RollingUpdate는 이 과정에서 가용 Pod 수가 일정 비율 아래로 내려가지 않게 유지하므로 서비스가 끊기지 않는다. 배포 이력도 남는다.

kubectl rollout history deployment/nginx
deployment.apps/nginx
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

문제가 생기면 kubectl rollout undo deployment/nginx로 직전 revision으로 되돌린다.

명령형에서 선언형으로

지금까지 실행한 create, scale, set image는 전부 명령형이다. 무엇을 하라고 API 서버에 지시했고, 그 결과 상태는 클러스터 안에만 남아 있다. 같은 상태를 선언형으로 표현하면 파일이 된다.

# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.28

kubectl create deployment가 붙이는 label이 app=nginx라서, 이 매니페스트는 위 실습으로 만든 Deployment와 같은 대상을 가리킨다. 적용은 kubectl apply로 한다.

kubectl apply -f nginx-deployment.yaml

명령형으로 만든 리소스에 처음 apply를 실행하면 last-applied-configuration 주석이 없다는 경고가 한 번 나오는데, apply 관리로 전환될 때 나오는 안내이고 이후에는 나오지 않는다.

선언형의 이득은 운영에서 드러난다. 파일이 곧 기록이므로 Git으로 이력을 관리하고 리뷰할 수 있으며, 같은 파일을 몇 번 적용해도 결과가 같다(멱등). replica를 늘리고 싶으면 scale 명령 대신 파일의 replicas를 고쳐 다시 적용하고, “지금 클러스터가 파일과 같은 상태인가"만 확인하면 된다.

한 리소스를 명령형과 선언형으로 번갈아 관리하지 않는다. kubectl scale로 바꾼 replica 수는 파일에 반영되지 않으므로, 다음 kubectl apply 때 파일의 값으로 되돌아간다. 실습에서는 자유롭게 섞어도 되지만, 운영 리소스는 한쪽 방식으로 통일한다.

실습이 끝났으면 만든 리소스를 삭제한다.

kubectl delete service nginx
kubectl delete deployment nginx

Deployment를 지우면 ReplicaSet과 Pod도 함께 정리된다. 클러스터 자체를 지울지는 다음 실습 일정에 따라 정하면 되고, 지웠더라도 1.3의 config 파일로 같은 환경을 다시 만들 수 있다.

정리

  • 명령형 한 줄(kubectl create deployment)로 시작해 get과 describe로 관찰하고, scale, expose, set image로 상태를 바꾸는 사이클을 돌았다.
  • Deployment는 ReplicaSet을 거쳐 Pod 수를 유지하고, Pod를 지워도 선언된 수만큼 다시 채운다.
  • 이미지 변경은 새 ReplicaSet을 만들어 점진 교체하는 롤링 업데이트로 반영되고, rollout undo로 되돌린다.
  • 같은 상태를 YAML 매니페스트와 kubectl apply로 선언하면 파일이 기록이 된다. 운영의 기본형은 이쪽이다.