기본 StatefulSet

기본 StatefulSet (StatefulSet) 알아보기

이 튜토리얼은 StatefulSet으로 애플리케이션을 관리하는 방법을 소개해요. StatefulSet 파드를 만들고, 삭제하고, 스케일링하고, 업데이트하는 방법을 시연합니다.

출처: 문서

본문

시작하기 전에

이 튜토리얼을 시작하기 전에 다음 쿠버네티스 개념에 익숙해져야 합니다:

kubectldefault 네임스페이스를 사용하는 컨텍스트를 사용하도록 구성해야 합니다. 기존 클러스터를 사용한다면 그 클러스터의 default 네임스페이스를 사용해도 괜찮은지 확인하세요. 이상적으로는 실제 워크로드를 실행하지 않는 클러스터에서 연습하세요.

StatefulSet에 대한 개념 페이지를 읽어보는 것도 유용합니다.

이 튜토리얼은 클러스터가 PersistentVolume을 동적으로 프로비저닝하도록 구성되어 있다고 가정합니다. 또한 기본 StorageClass가 있어야 합니다. 클러스터가 스토리지를 동적으로 프로비저닝하도록 구성되지 않았다면, 이 튜토리얼을 시작하기 전에 1 GiB 볼륨 두 개를 수동으로 프로비저닝하고, StatefulSet이 정의하는 PersistentVolumeClaim 템플릿에 그 PersistentVolume이 매핑되도록 클러스터를 설정해야 합니다.

목표 (Objectives)

StatefulSet은 상태 유지 애플리케이션(stateful applications)과 분산 시스템과 함께 사용하도록 의도되었습니다. 그러나 쿠버네티스에서 상태 유지 애플리케이션과 분산 시스템의 관리는 광범위하고 복잡한 주제입니다. StatefulSet의 기본 기능을 시연하고 전자를 후자와 혼동하지 않기 위해, StatefulSet을 사용해 간단한 웹 애플리케이션을 배포할 거예요.

이 튜토리얼 후에 다음에 익숙해질 것입니다.

  • StatefulSet을 만드는 방법
  • StatefulSet이 파드를 관리하는 방법
  • StatefulSet을 삭제하는 방법
  • StatefulSet을 스케일링하는 방법
  • StatefulSet의 파드를 업데이트하는 방법

StatefulSet 만들기

아래 예시를 사용해 StatefulSet(그리고 그것이 의존하는 Service)을 만들어 보겠습니다. 이것은 StatefulSet 개념에서 제시된 예시와 유사합니다. StatefulSet web의 파드 IP 주소를 게시하는 헤드리스 Service nginx를 만듭니다.

최소 두 개의 터미널 창이 필요할 것입니다. 첫 번째 터미널에서 kubectl get을 사용해 StatefulSet의 파드 생성을 지켜봅니다.

# use this terminal to run commands that specify --watch
# end this watch when you are asked to start a new watch
kubectl get pods --watch -l app=nginx

두 번째 터미널에서 kubectl apply를 사용해 헤드리스 Service와 StatefulSet을 만든다:

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created

위 명령은 각각 NGINX 웹서버를 실행하는 파드 두 개를 만듭니다. nginx Service를 가져온다...

kubectl get service nginx
NAME      TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx     ClusterIP    None         <none>        80/TCP    12s

...그런 다음 web StatefulSet을 가져와 둘 다 성공적으로 생성되었는지 확인한다:

kubectl get statefulset web
NAME   READY   AGE
web    2/2     37s

순서가 있는 파드 생성

StatefulSet은 기본적으로 엄격한 순서로 파드를 생성합니다.

n replicas의 StatefulSet에서 파드를 배포할 때 {0..n-1} 순서로 순차적으로 생성됩니다. 첫 번째 터미널의 kubectl get 명령 출력을 검사하세요. 결국 출력은 아래 예시와 같을 것입니다.

# Do not start a new watch;
# this should already be running
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         19s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         18s

web-1 파드가 Running(참조: Pod Phase)과 Ready(참조: Pod Conditionstype)가 될 때까지 시작되지 않는 점에 주의하세요.

이 튜토리얼의 후반부에서 병렬 시작을 연습할 것입니다.

StatefulSet의 각 파드에 할당되는 정수 서수(ordinal)를 구성하려면 Start ordinal을 참조하세요.

StatefulSet의 파드

StatefulSet의 파드는 고유한 서수 인덱스와 안정적인 네트워크 신원을 가집니다.

파드의 서수 인덱스 검사하기

StatefulSet의 파드를 가져온다:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          1m
web-1     1/1       Running   0          1m

StatefulSet 개념에서 언급했듯이 StatefulSet의 파드는 고정적(sticky)이고 고유한 신원을 가집니다. 이 신원은 StatefulSet 컨트롤러가 각 파드에 할당하는 고유한 서수 인덱스를 기반으로 합니다. 파드의 이름은 <statefulset name>-<ordinal index> 형태입니다. web StatefulSet이 두 개의 replica를 가지므로 web-0web-1 두 파드를 만듭니다.

안정적인 네트워크 신원 사용하기

각 파드는 서수 인덱스에 기반한 안정적인 호스트네임을 가집니다. kubectl exec을 사용해 각 파드에서 hostname 명령을 실행한다:

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
web-0
web-1

kubectl run을 사용해 dnsutils 패키지의 nslookup 명령을 제공하는 컨테이너를 실행한다. 파드의 호스트네임에 nslookup을 사용해 그들의 클러스터 내 DNS 주소를 검사할 수 있습니다:

kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

새 셸이 시작됩니다. 그 새 셸에서 다음을 실행한다:

# Run this in the dns-test container shell
nslookup web-0.nginx

출력은 다음과 비슷합니다:

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.6

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.6

(이제 컨테이너 셸을 종료하세요: exit)

헤드리스 서비스의 CNAME은 SRV 레코드(Running과 Ready인 각 파드당 하나)를 가리킵니다. SRV 레코드는 파드의 IP 주소를 포함하는 A 레코드 항목을 가리킵니다.

한 터미널에서 StatefulSet의 파드를 지켜본다:

# Start a new watch
# End this watch when you've seen that the delete is finished
kubectl get pod --watch -l app=nginx

두 번째 터미널에서 kubectl delete를 사용해 StatefulSet의 모든 파드를 삭제한다:

kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

StatefulSet이 그것들을 재시작하고 두 파드가 Running과 Ready로 전환될 때까지 기다린다:

# This should already be running
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

kubectl execkubectl run을 사용해 파드의 호스트네임과 클러스터 내 DNS 항목을 본다. 먼저 파드의 호스트네임을 본다:

for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
web-0
web-1

그런 다음 다음을 실행한다:

kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

새 셸이 시작됩니다. 그 새 셸에서 다음을 실행한다:

# Run this in the dns-test container shell
nslookup web-0.nginx

출력은 다음과 비슷합니다:

Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.1.7

nslookup web-1.nginx
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.2.8

(이제 컨테이너 셸을 종료하세요: exit)

파드의 서수, 호스트네임, SRV 레코드, A 레코드 이름은 변하지 않았지만 파드와 연관된 IP 주소는 변할 수 있습니다. 이 튜토리얼에 사용된 클러스터에서는 변했습니다. 이것이 StatefulSet의 파드에 특정 파드의 IP 주소로 연결하도록 다른 애플리케이션을 구성하면 안 되는 이유입니다(호스트네임을 확인해 파드에 연결하는 것은 괜찮습니다).

StatefulSet의 특정 파드 발견

StatefulSet의 활성 구성원을 찾아 연결해야 한다면 헤드리스 Service의 CNAME(nginx.default.svc.cluster.local)을 질의해야 합니다. CNAME과 연관된 SRV 레코드는 StatefulSet에서 Running과 Ready인 파드만 포함합니다.

애플리케이션이 이미 활성·준비(liveness/readiness)를 테스트하는 연결 로직을 구현했다면, 파드의 SRV 레코드(web-0.nginx.default.svc.cluster.local, web-1.nginx.default.svc.cluster.local)를 사용할 수 있습니다. 안정적이므로, 파드가 Running과 Ready로 전환될 때 애플리케이션이 파드 주소를 발견할 수 있습니다.

애플리케이션이 StatefulSet의 정상적인 파드를 찾고 싶어 특정 파드를 추적할 필요가 없다면, 그 StatefulSet의 파드가 뒷받침하는 type: ClusterIP Service의 IP 주소에 연결할 수도 있습니다. StatefulSet을 추적하는 같은 Service(StatefulSet의 serviceName에 지정됨)를 사용하거나 올바른 파드 집합을 선택하는 별도의 Service를 사용할 수 있습니다.

안정적인 스토리지에 쓰기

web-0web-1의 PersistentVolumeClaim을 가져온다:

kubectl get pvc -l app=nginx

출력은 다음과 비슷합니다:

NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           48s
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           48s

StatefulSet 컨트롤러가 두 PersistentVolumeClaim을 만들었고 그것들이 두 PersistentVolume에 바인딩됩니다.

이 튜토리얼에 사용된 클러스터가 PersistentVolume을 동적으로 프로비저닝하도록 구성되어 있으므로 PersistentVolume은 자동으로 생성되어 바인딩되었습니다.

NGINX 웹서버는 기본적으로 /usr/share/nginx/html/index.html에서 인덱스 파일을 제공합니다. StatefulSet의 spec에 있는 volumeMounts 필드는 /usr/share/nginx/html 디렉터리가 PersistentVolume에 의해 뒷받침되도록 보장합니다.

파드의 호스트네임을 index.html 파일에 써서 NGINX 웹서버가 호스트네임을 제공하는지 확인한다:

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

위 curl 명령에 대해 403 Forbidden 응답이 보이면, volumeMounts가 마운트한 디렉터리의 권한을 다음을 실행해 고쳐야 합니다(hostPath 볼륨 사용 시 버그 때문):

for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done

그런 다음 위 curl 명령을 다시 시도하세요.

한 터미널에서 StatefulSet의 파드를 지켜본다:

# End this watch when you've reached the end of the section.
# At the start of "Scaling a StatefulSet" you'll start a new watch.
kubectl get pod --watch -l app=nginx

두 번째 터미널에서 StatefulSet의 모든 파드를 삭제한다:

kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

첫 번째 터미널에서 kubectl get 명령의 출력을 검사하고 모든 파드가 Running과 Ready로 전환될 때까지 기다린다.

# This should already be running
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2s
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         34s

웹 서버가 계속 호스트네임을 제공하는지 확인한다:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

web-0web-1이 다시 스케줄링되었지만, PersistentVolumeClaim과 연관된 PersistentVolume이 그 volumeMounts에 다시 마운트되므로 계속 호스트네임을 제공합니다. web-0web-1이 어떤 노드에 스케줄링되든 그 PersistentVolume이 적절한 마운트 지점에 마운트될 것입니다.

StatefulSet 스케일링하기

StatefulSet 스케일링은 replica 수를 늘리거나 줄이는 것을 말합니다(수평 스케일링). 이는 replicas 필드를 갱신해 수행됩니다. StatefulSet을 스케일링하려면 kubectl scale 또는 kubectl patch을 사용할 수 있습니다.

스케일 업

스케일 업은 replica를 더 추가하는 것을 의미합니다. 앱이 StatefulSet 전반에 작업을 분산할 수 있다면, 더 큰 파드 집합이 더 많은 작업을 수행할 수 있습니다.

한 터미널 창에서 StatefulSet의 파드를 지켜본다:

# If you already have a watch running, you can continue using that.
# Otherwise, start one.
# End this watch when there are 5 healthy Pods for the StatefulSet
kubectl get pods --watch -l app=nginx

다른 터미널 창에서 kubectl scale을 사용해 replica 수를 5로 스케일링한다:

kubectl scale sts web --replicas=5
statefulset.apps/web scaled

첫 번째 터미널에서 kubectl get 명령의 출력을 검사하고 세 개의 추가 파드가 Running과 Ready로 전환될 때까지 기다린다.

# This should already be running
kubectl get pod --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          2h
web-1     1/1       Running   0          2h
NAME      READY     STATUS    RESTARTS   AGE
web-2     0/1       Pending   0          0s
web-2     0/1       Pending   0         0s
web-2     0/1       ContainerCreating   0         0s
web-2     1/1       Running   0         19s
web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         0s
web-3     0/1       ContainerCreating   0         0s
web-3     1/1       Running   0         18s
web-4     0/1       Pending   0         0s
web-4     0/1       Pending   0         0s
web-4     0/1       ContainerCreating   0         0s
web-4     1/1       Running   0         19s

StatefulSet 컨트롤러가 replica 수를 스케일링했습니다. StatefulSet 생성 때처럼, StatefulSet 컨트롤러는 서수 인덱스에 관해 각 파드를 순차적으로 만들고, 후속 파드를 시작하기 전에 각 파드의 전임자가 Running과 Ready가 될 때까지 기다렸습니다.

스케일 다운

스케일 다운은 replica 수를 줄이는 것을 의미합니다. 예를 들어 서비스에 대한 트래픽 수준이 낮아졌고 현재 스케일에서 유휴 리소스가 있기 때문일 수 있습니다.

한 터미널에서 StatefulSet의 파드를 지켜본다:

# End this watch when there are only 3 Pods for the StatefulSet
kubectl get pod --watch -l app=nginx

다른 터미널에서 kubectl patch을 사용해 StatefulSet을 세 개의 replica로 다시 스케일링한다:

kubectl patch sts web -p '{"spec":{"replicas":3}}'
statefulset.apps/web patched

web-4web-3이 Terminating으로 전환될 때까지 기다린다.

# This should already be running
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          3h
web-1     1/1       Running             0          3h
web-2     1/1       Running             0          55s
web-3     1/1       Running             0          36s
web-4     0/1       ContainerCreating   0          18s
NAME      READY     STATUS    RESTARTS   AGE
web-4     1/1       Running   0          19s
web-4     1/1       Terminating   0         24s
web-4     1/1       Terminating   0         24s
web-3     1/1       Terminating   0         42s
web-3     1/1       Terminating   0         42s

순서가 있는 파드 종료

컨트롤 플레인은 서수 인덱스에 관해 역순으로 한 번에 파드 하나를 삭제하고, 다음 파드를 삭제하기 전에 각 파드가 완전히 종료될 때까지 기다렸습니다.

StatefulSet의 PersistentVolumeClaim을 가져온다:

kubectl get pvc -l app=nginx
NAME        STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
www-web-0   Bound     pvc-15c268c7-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-1   Bound     pvc-15c79307-b507-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-2   Bound     pvc-e1125b27-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-3   Bound     pvc-e1176df6-b508-11e6-932f-42010a800002   1Gi        RWO           13h
www-web-4   Bound     pvc-e11bb5f8-b508-11e6-932f-42010a800002   1Gi        RWO           13h

여전히 다섯 개의 PersistentVolumeClaim과 다섯 개의 PersistentVolume이 있습니다. 파드의 안정적인 스토리지를 탐색할 때 StatefulSet의 파드에 마운트된 PersistentVolume은 StatefulSet의 파드가 삭제되어도 삭제되지 않는 것을 보았습니다. 이는 파드 삭제가 StatefulSet의 스케일 다운에 의해 발생할 때도 여전히 사실입니다.

StatefulSet 업데이트하기

StatefulSet 컨트롤러는 자동화된 업데이트를 지원합니다. 사용되는 전략은 StatefulSet API 객체의 spec.updateStrategy 필드에 의해 결정됩니다. 이 기능은 StatefulSet의 파드의 컨테이너 이미지, 리소스 요청 및/또는 한도, 라벨, 어노테이션을 업그레이드하는 데 사용할 수 있습니다.

RollingUpdate(기본값)와 OnDelete의 두 가지 유효한 업데이트 전략이 있습니다.

RollingUpdate {#rolling-update}

RollingUpdate 업데이트 전략은 StatefulSet 보장을 존중하면서 역 서수 순서로 StatefulSet의 모든 파드를 업데이트합니다.

.spec.updateStrategy.rollingUpdate.partition을 지정해 RollingUpdate 전략을 사용하는 StatefulSet에 대한 업데이트를 파티션 으로 나눌 수 있습니다. 이것은 이 튜토리얼의 후반부에서 연습할 것입니다.

먼저 간단한 롤링 업데이트를 시도해 본다.

한 터미널 창에서 web StatefulSet을 패치해 컨테이너 이미지를 다시 바꾼다:

kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.24"}]'
statefulset.apps/web patched

다른 터미널에서 StatefulSet의 파드를 지켜본다:

# End this watch when the rollout is complete
#
# If you're not sure, leave it running one more minute
kubectl get pod -l app=nginx --watch

출력은 다음과 비슷합니다:

NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          7m
web-1     1/1       Running   0          7m
web-2     1/1       Running   0          8m
web-2     1/1       Terminating   0         8m
web-2     1/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Terminating   0         8m
web-2     0/1       Pending   0         0s
web-2     0/1       Pending   0         0s
web-2     0/1       ContainerCreating   0         0s
web-2     1/1       Running   0         19s
web-1     1/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Terminating   0         8m
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         6s
web-0     1/1       Terminating   0         7m
web-0     1/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Terminating   0         7m
web-0     0/1       Pending   0         0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         10s

StatefulSet의 파드는 역 서수 순서로 업데이트됩니다. StatefulSet 컨트롤러는 각 파드를 종료하고, 다음 파드를 업데이트하기 전에 Running과 Ready로 전환될 때까지 기다립니다. StatefulSet 컨트롤러는 서수 후임자가 Running과 Ready가 될 때까지 다음 파드를 업데이트하지 않지만, 업데이트 중 실패하는 모든 파드를 해당 파드의 기존 버전으로 복원한다는 점에 주의하세요.

이미 업데이트를 받은 파드는 업데이트된 버전으로 복원되고, 아직 업데이트를 받지 않은 파드는 이전 버전으로 복원됩니다. 이렇게 해서 컨트롤러는 간헐적 실패가 있는 상황에서도 애플리케이션을 계속 정상적으로 유지하고 업데이트를 일관되게 유지하려고 시도합니다.

파드를 가져와 컨테이너 이미지를 본다:

for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24

StatefulSet의 모든 파드는 이제 이전 컨테이너 이미지를 실행 중입니다.

kubectl rollout status sts/<name>을 사용해 StatefulSet에 대한 롤링 업데이트의 상태를 볼 수도 있습니다.

업데이트 준비하기 (Staging an update)

.spec.updateStrategy.rollingUpdate.partition을 지정해 RollingUpdate 전략을 사용하는 StatefulSet에 대한 업데이트를 파티션 으로 나눌 수 있습니다.

더 많은 컨텍스트는 StatefulSet 개념 페이지의 파티션 롤링 업데이트를 읽을 수 있습니다.

.spec.updateStrategy.rollingUpdate 안의 partition 필드를 사용해 StatefulSet에 대한 업데이트를 준비할 수 있어요. 이 업데이트에서는 StatefulSet의 파드 템플릿을 변경하는 동안 StatefulSet의 기존 파드를 변경하지 않고 유지합니다. 그런 다음 당신이 — 또는 튜토리얼 밖에서는 어떤 외부 자동화가 — 준비된 업데이트를 트리거할 수 있습니다.

먼저 web StatefulSet을 패치해 updateStrategy 필드에 파티션을 추가한다:

# The value of "partition" determines which ordinals a change applies to
# Make sure to use a number bigger than the last ordinal for the
# StatefulSet
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
statefulset.apps/web patched

StatefulSet을 다시 패치해 이 StatefulSet이 사용하는 컨테이너 이미지를 바꾼다:

kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.21"}]'
statefulset.apps/web patched

StatefulSet에서 파드를 삭제한다:

kubectl delete pod web-2
pod "web-2" deleted

대체 web-2 파드가 Running과 Ready가 될 때까지 기다린다:

# End the watch when you see that web-2 is healthy
kubectl get pod -l app=nginx --watch
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running   0         18s

파드의 컨테이너 이미지를 가져온다:

kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.24

업데이트 전략이 RollingUpdate인데도 StatefulSet이 원래 컨테이너 이미지로 파드를 복원했습니다. 이는 파드의 서수가 updateStrategy가 지정한 partition보다 작기 때문입니다.

카나리 롤아웃 (Rolling out a canary)

이제 그 준비된 변경의 카나리 롤아웃을 시도할 것입니다.

위에서 지정한 partition을 감소시켜 카나리를 롤아웃할 수 있습니다(수정된 템플릿을 테스트하기 위해).

StatefulSet을 패치해 파티션을 감소시킨다:

# The value of "partition" should match the highest existing ordinal for
# the StatefulSet
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
statefulset.apps/web patched

컨트롤 플레인이 web-2에 대한 교체를 트리거합니다(정상적인 delete 후 삭제가 완료되면 새 파드를 만드는 방식으로 구현됨). 새 web-2 파드가 Running과 Ready가 될 때까지 기다립니다.

# This should already be running
kubectl get pod -l app=nginx --watch
NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          4m
web-1     1/1       Running             0          4m
web-2     0/1       ContainerCreating   0          11s
web-2     1/1       Running   0         18s

파드의 컨테이너를 가져온다:

kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.21

partition을 바꿨을 때, StatefulSet 컨트롤러가 web-2 파드를 자동으로 업데이트했습니다. 파드의 서수가 partition보다 크거나 같기 때문입니다.

web-1 파드를 삭제한다:

kubectl delete pod web-1
pod "web-1" deleted

web-1 파드가 Running과 Ready가 될 때까지 기다린다.

# This should already be running
kubectl get pod -l app=nginx --watch

출력은 다음과 비슷합니다:

NAME      READY     STATUS        RESTARTS   AGE
web-0     1/1       Running       0          6m
web-1     0/1       Terminating   0          6m
web-2     1/1       Running       0          2m
web-1     0/1       Terminating   0         6m
web-1     0/1       Terminating   0         6m
web-1     0/1       Terminating   0         6m
web-1     0/1       Pending   0         0s
web-1     0/1       Pending   0         0s
web-1     0/1       ContainerCreating   0         0s
web-1     1/1       Running   0         18s

web-1 파드의 컨테이너 이미지를 가져온다:

kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.24

web-1은 파드의 서수가 파티션보다 작았기 때문에 원래 구성으로 복원되었습니다. 파티션이 지정되면 서수가 파티션보다 크거나 같은 모든 파드는 StatefulSet의 .spec.template이 업데이트될 때 업데이트됩니다. 서수가 파티션보다 작은 파드가 삭제되거나 종료되면 원래 구성으로 복원됩니다.

단계적 롤아웃 (Phased roll outs)

파티션된 롤링 업데이트를 사용해 카나리를 롤아웃한 것과 유사한 방식으로 단계적 롤아웃(예: 선형, 기하, 지수 롤아웃)을 수행할 수 있습니다. 단계적 롤아웃을 수행하려면 컨트롤러가 업데이트를 일시 중지하길 원하는 서수로 partition을 설정하세요.

파티션은 현재 2로 설정되어 있습니다. 파티션을 0으로 설정한다:

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched

StatefulSet의 모든 파드가 Running과 Ready가 될 때까지 기다린다.

# This should already be running
kubectl get pod -l app=nginx --watch

출력은 다음과 비슷합니다:

NAME      READY     STATUS              RESTARTS   AGE
web-0     1/1       Running             0          3m
web-1     0/1       ContainerCreating   0          11s
web-2     1/1       Running             0          2m
web-1     1/1       Running   0         18s
web-0     1/1       Terminating   0         3m
web-0     1/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Terminating   0         3m
web-0     0/1       Pending   0         0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         3s

StatefulSet의 파드에 대한 컨테이너 이미지 세부 정보를 가져온다:

for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21

partition0으로 옮겨서 StatefulSet이 업데이트 과정을 계속하도록 허용했습니다.

OnDelete {#on-delete}

.spec.updateStrategy.typeOnDelete로 설정해 StatefulSet에 대해 이 업데이트 전략을 선택합니다.

web StatefulSet을 패치해 OnDelete 업데이트 전략을 사용한다:

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"OnDelete", "rollingUpdate": null}}}'
statefulset.apps/web patched

이 업데이트 전략을 선택하면 StatefulSet의 .spec.template 필드에 수정이 있더라도 StatefulSet 컨트롤러는 파드를 자동으로 업데이트하지 않습니다. 롤아웃을 직접 — 수동으로 또는 별도의 자동화를 사용해 — 관리해야 합니다.

StatefulSet 삭제하기

StatefulSet은 비연속적(non-cascading)연속적(cascading) 삭제를 모두 지원합니다. 비연속적 delete에서는 StatefulSet이 삭제되어도 StatefulSet의 파드는 삭제되지 않습니다. 연속적 delete에서는 StatefulSet과 그 파드가 모두 삭제됩니다.

일반적으로 연속 삭제에 대해 배우려면 클러스터에서 연속 삭제 사용을 읽어보세요.

비연속적 삭제

한 터미널 창에서 StatefulSet의 파드를 지켜본다.

# End this watch when there are no Pods for the StatefulSet
kubectl get pods --watch -l app=nginx

kubectl delete을 사용해 StatefulSet을 삭제한다. 명령에 --cascade=orphan 파라미터를 반드시 제공하세요. 이 파라미터는 쿠버네티스에게 StatefulSet만 삭제하고 그 파드는 삭제하지 말라고 알려줍니다.

kubectl delete statefulset web --cascade=orphan
statefulset.apps "web" deleted

파드를 가져와 상태를 검사한다:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          6m
web-1     1/1       Running   0          7m
web-2     1/1       Running   0          5m

web이 삭제됐어도 모든 파드는 여전히 Running과 Ready입니다. web-0을 삭제한다:

kubectl delete pod web-0
pod "web-0" deleted

StatefulSet의 파드를 가져온다:

kubectl get pods -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-1     1/1       Running   0          10m
web-2     1/1       Running   0          7m

web StatefulSet이 삭제되었으므로 web-0은 다시 시작되지 않았습니다.

한 터미널에서 StatefulSet의 파드를 지켜본다.

# Leave this watch running until the next time you start a watch
kubectl get pods --watch -l app=nginx

두 번째 터미널에서 StatefulSet을 다시 만든다. nginx Service를 삭제하지 않았다면(그래서는 안 됩니다) Service가 이미 존재한다는 오류가 보일 것입니다.

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
statefulset.apps/web created
service/nginx unchanged

오류를 무시하세요. 이는 nginx 헤드리스 Service가 이미 존재하는데 만들어지려 시도했음을 나타낼 뿐입니다.

첫 번째 터미널에서 실행 중인 kubectl get 명령의 출력을 검사한다.

# This should already be running
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-1     1/1       Running   0          16m
web-2     1/1       Running   0          2m
NAME      READY     STATUS    RESTARTS   AGE
web-0     0/1       Pending   0          0s
web-0     0/1       Pending   0         0s
web-0     0/1       ContainerCreating   0         0s
web-0     1/1       Running   0         18s
web-2     1/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m
web-2     0/1       Terminating   0         3m

web StatefulSet이 재생성되었을 때 먼저 web-0을 다시 시작했습니다. web-1이 이미 Running과 Ready였으므로, web-0이 Running과 Ready로 전환되자 그것은 이 파드를 채택했습니다. replicas를 2로 StatefulSet을 재생성했으므로, web-0이 재생성되고 web-1이 이미 Running과 Ready로 판단되면 web-2는 종료되었습니다.

이제 파드의 웹서버가 제공하는 index.html 파일의 내용을 다시 살펴본다:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

StatefulSet과 web-0 파드를 모두 삭제했어도 여전히 원래 index.html 파일에 입력된 호스트네임을 제공합니다. StatefulSet이 파드와 연관된 PersistentVolume을 절대 삭제하지 않기 때문입니다. StatefulSet을 재생성하고 web-0을 다시 시작했을 때 원래 PersistentVolume이 다시 마운트되었습니다.

연속적 삭제

한 터미널 창에서 StatefulSet의 파드를 지켜본다.

# Leave this running until the next page section
kubectl get pods --watch -l app=nginx

다른 터미널에서 StatefulSet을 다시 삭제한다. 이번에는 --cascade=orphan 파라미터를 생략한다.

kubectl delete statefulset web
statefulset.apps "web" deleted

첫 번째 터미널에서 실행 중인 kubectl get 명령의 출력을 검사하고 모든 파드가 Terminating으로 전환될 때까지 기다린다.

# This should already be running
kubectl get pods --watch -l app=nginx
NAME      READY     STATUS    RESTARTS   AGE
web-0     1/1       Running   0          11m
web-1     1/1       Running   0          27m
NAME      READY     STATUS        RESTARTS   AGE
web-0     1/1       Terminating   0          12m
web-1     1/1       Terminating   0         29m
web-0     0/1       Terminating   0         12m
web-0     0/1       Terminating   0         12m
web-0     0/1       Terminating   0         12m
web-1     0/1       Terminating   0         29m
web-1     0/1       Terminating   0         29m
web-1     0/1       Terminating   0         29m

스케일 다운 섹션에서 보았듯이 파드는 서수 인덱스의 역순에 관해 한 번에 하나씩 종료됩니다. 파드를 종료하기 전에 StatefulSet 컨트롤러는 파드의 후임자가 완전히 종료될 때까지 기다립니다.

연속 삭제가 StatefulSet을 그 파드와 함께 제거하지만, 연속 삭제는 StatefulSet과 연관된 헤드리스 Service를 삭제하지 않습니다. nginx Service를 수동으로 삭제해야 합니다.

kubectl delete service nginx
service "nginx" deleted

StatefulSet과 헤드리스 Service를 한 번 더 다시 만든다:

kubectl apply -f https://k8s.io/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created

StatefulSet의 모든 파드가 Running과 Ready로 전환되면 index.html 파일의 내용을 검색한다:

for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

StatefulSet과 그 모든 파드를 완전히 삭제했어도 파드는 PersistentVolume이 마운트된 채 재생성되며, web-0web-1은 계속 호스트네임을 제공합니다.

마지막으로 nginx Service를 삭제한다...

kubectl delete service nginx
service "nginx" deleted

...그리고 web StatefulSet을 삭제한다:

kubectl delete statefulset web
statefulset "web" deleted

파드 관리 정책

일부 분산 시스템에서는 StatefulSet의 정렬 보장이 불필요하거나 바람직하지 않습니다. 이러한 시스템은 고유성(uniqueness)과 신원(identity)만 필요합니다.

이러한 엄격한 정렬을 피하기 위해 Pod 관리 정책을 지정할 수 있습니다: OrderedReady(기본값) 또는 Parallel.

OrderedReady 파드 관리

OrderedReady 파드 관리는 StatefulSet의 기본값입니다. StatefulSet 컨트롤러에게 위에서 시연된 정렬 보장을 존중하라고 알려줍니다.

애플리케이션의 새 버전을 롤아웃하는 것 같은 변경이 StatefulSet이 제공하는 서수(파드 번호)의 엄격한 순서로 일어나야 하거나 기대할 때 이것을 사용하세요. 다시 말해 파드 app-0, app-1, app-2가 있다면, 쿠버네티스가 app-0을 먼저 업데이트하고 확인합니다. 검사가 정상이면 쿠버네티스가 app-1, 마지막으로 app-2를 업데이트합니다.

파드를 두 개 더 추가하면, 쿠버네티스는 app-3을 설정하고 그것이 정상이 될 때까지 기다린 다음 app-4를 배포합니다.

이것이 기본 설정이므로 이미 그것을 사용해 연습했습니다.

Parallel 파드 관리

대안인 Parallel 파드 관리는 StatefulSet 컨트롤러에게 모든 파드를 병렬로 시작하거나 종료하라고 알려줍니다. 파드가 RunningReady가 되거나 완전히 종료될 때까지 기다리지 않고 다른 파드를 시작하거나 종료합니다.

Parallel 파드 관리 옵션은 스케일링 작업의 동작에만 영향을 줍니다. 업데이트는 영향을 받지 않습니다. 쿠버네티스는 여전히 변경 사항을 순서대로 롤아웃합니다. 이 튜토리얼에서 애플리케이션은 매우 단순합니다: 호스트네임을 알려주는 웹서버입니다(StatefulSet이므로 각 파드의 호스트네임은 다르고 예측 가능합니다).

이 매니페스트는 위에서 다운로드한 것과 동일하지만 web StatefulSet의 .spec.podManagementPolicyParallel로 설정되어 있습니다.

한 터미널에서 StatefulSet의 파드를 지켜본다.

# Leave this watch running until the end of the section
kubectl get pod -l app=nginx --watch

다른 터미널에서 StatefulSet을 Parallel 파드 관리용으로 재구성한다:

kubectl apply -f https://k8s.io/examples/application/web/web-parallel.yaml
service/nginx updated
statefulset.apps/web updated

watch를 실행 중인 터미널을 열어 둔다. 다른 터미널 창에서 StatefulSet을 스케일링한다:

kubectl scale statefulset/web --replicas=5
statefulset.apps/web scaled

kubectl get 명령이 실행 중인 터미널의 출력을 검사한다. 대략 이렇게 보일 수 있습니다:

web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         0s
web-3     0/1       Pending   0         7s
web-3     0/1       ContainerCreating   0         7s
web-2     0/1       Pending   0         0s
web-4     0/1       Pending   0         0s
web-2     1/1       Running   0         8s
web-4     0/1       ContainerCreating   0         4s
web-3     1/1       Running   0         26s
web-4     1/1       Running   0         2s

StatefulSet은 세 개의 새 파드를 시작했고, 두 번째와 세 번째 파드를 시작하기 전에 첫 번째가 Running과 Ready가 될 때까지 기다리지 않았습니다.

이 접근 방식은 워크로드에 상태 유지 요소가 있거나, 파드가 예측 가능한 명명으로 서로를 식별할 수 있어야 할 때, 특히 때로는 훨씬 더 많은 용량을 빠르게 제공해야 할 때 유용합니다. 튜토리얼의 이 단순한 웹 서비스가 갑자기 분당 1,000,000개의 추가 요청을 받는다면 더 많은 파드를 실행하고 싶을 것입니다 — 그러나 각 새 파드가 시작될 때까지 기다리고 싶지는 않을 것입니다. 추가 파드를 병렬로 시작하면 추가 용량을 요청한 시점과 사용 가능해지는 시점 사이의 시간을 줄입니다.

정리하기 (Clean up)

정리의 일부로 kubectl 명령을 실행할 준비가 된 두 개의 터미널이 열려 있어야 합니다.

kubectl delete sts web
# sts is an abbreviation for statefulset

kubectl get을 지켜보며 그 파드들이 삭제되는 것을 볼 수 있습니다.

# end the watch when you've seen what you need to
kubectl get pod -l app=nginx --watch
web-3     1/1       Terminating   0         9m
web-2     1/1       Terminating   0         9m
web-3     1/1       Terminating   0         9m
web-2     1/1       Terminating   0         9m
web-1     1/1       Terminating   0         44m
web-0     1/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-3     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-1     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-2     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-2     0/1       Terminating   0         9m
web-1     0/1       Terminating   0         44m
web-1     0/1       Terminating   0         44m
web-1     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-0     0/1       Terminating   0         44m
web-3     0/1       Terminating   0         9m
web-3     0/1       Terminating   0         9m
web-3     0/1       Terminating   0         9m

삭제 중에 StatefulSet은 모든 파드를 동시에 제거합니다. 파드의 서수 후임자가 종료되기 전에 그 파드를 삭제하기 위해 기다리지 않습니다.

kubectl get 명령이 실행 중인 터미널을 닫고 nginx Service를 삭제한다:

kubectl delete svc nginx

이 튜토리얼에서 사용된 PersistentVolume의 영구 저장 미디어를 삭제한다.

kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-2bf00408-d366-4a12-bad0-1869c65d0bee   1Gi        RWO            standard       25m
www-web-1   Bound    pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4   1Gi        RWO            standard       24m
www-web-2   Bound    pvc-cba6cfa6-3a47-486b-a138-db5930207eaf   1Gi        RWO            standard       15m
www-web-3   Bound    pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752   1Gi        RWO            standard       15m
www-web-4   Bound    pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e   1Gi        RWO            standard       14m
kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752   1Gi        RWO            Delete           Bound    default/www-web-3   standard                15m
pvc-2bf00408-d366-4a12-bad0-1869c65d0bee   1Gi        RWO            Delete           Bound    default/www-web-0   standard                25m
pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e   1Gi        RWO            Delete           Bound    default/www-web-4   standard                14m
pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4   1Gi        RWO            Delete           Bound    default/www-web-1   standard                24m
pvc-cba6cfa6-3a47-486b-a138-db5930207eaf   1Gi        RWO            Delete           Bound    default/www-web-2   standard                15m
kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4
persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
persistentvolumeclaim "www-web-3" deleted
persistentvolumeclaim "www-web-4" deleted
kubectl get pvc
No resources found in default namespace.

또한 이 튜토리얼에서 사용된 PersistentVolume의 영구 저장 미디어를 삭제해야 합니다. 환경, 스토리지 구성, 프로비저닝 방법에 따라 필요한 단계를 따라 모든 스토리지가 회수되도록 하세요.

더 알아보기 (Learn more)