7.4.3 [실습] 스테이트풀셋을 이용한 간단한 상태 유지 앱 배포

이론으로 스테이트풀셋의 강력한 기능들을 살펴보았으니, 이제 직접 스테이트풀셋을 만들어보고 그 동작을 눈으로 확인할 차례입니다. 이번 실습에서는 각 파드가 고유한 데이터를 자신의 스토리지에 저장하고, 안정적인 네트워크 ID를 통해 식별될 수 있는 매우 간단한 웹 애플리케이션을 스테이트풀셋을 이용하여 배포해 보겠습니다. 이 과정을 통해 스테이트풀셋이 어떻게 헤드리스 서비스와 연동되고, volumeClaimTemplates를 통해 파드별 PVC를 생성하며, 파드들을 순차적으로 관리하는지 명확하게 이해할 수 있을 것입니다.

본 실습을 위해서는 이전 실습들과 마찬가지로 쿠버네티스 클러스터와 kubectl CLI가 준비되어 있어야 합니다. 또한, 동적 볼륨 프로비저닝(Dynamic Volume Provisioning)이 가능한 스토리지 클래스(StorageClass)가 클러스터에 설정되어 있는 것이 좋습니다. 대부분의 로컬 개발 환경(Minikube, kind, Docker Desktop)이나 클라우드 관리형 쿠버네티스 서비스는 기본 스토리지 클래스를 제공하므로 큰 어려움은 없을 것입니다. 만약 스토리지 클래스가 없다면, 수동으로 퍼시스턴트 볼륨(PV)을 미리 생성해야 하는 번거로움이 있을 수 있습니다.

자, 그럼 상태 유지 애플리케이션을 위한 특별한 지휘관, 스테이트풀셋을 직접 조종해 볼까요?

7.4.3.1 Headless Service 정의

스테이트풀셋의 파드들이 안정적인 네트워크 식별자(고유한 DNS 이름)를 갖기 위해서는 헤드리스 서비스(Headless Service)가 필요하다고 말씀드렸습니다. 헤드리스 서비스는 clusterIP: None으로 설정되어 로드밸런싱을 위한 가상 IP를 갖지 않으며, 대신 각 파드에 대한 DNS A 레코드를 생성해줍니다.

먼저, 스테이트풀셋의 파드들을 위한 헤드리스 서비스를 정의하는 YAML 파일을 작성하겠습니다. 이 서비스는 스테이트풀셋의 spec.serviceName 필드에 연결될 것입니다.

아래 내용을 my-stateful-headless-svc.yaml 파일로 저장해 주세요.

클립보드에 복사

이 YAML 파일에서 가장 중요한 부분은 spec.clusterIP: None입니다. 이것이 이 서비스를 헤드리스 서비스로 만듭니다. 또한, spec.selector.app: my-stateful-app은 이 서비스가 app: my-stateful-app 레이블을 가진 파드들을 대상으로 한다는 것을 명시합니다. 뒤이어 만들 스테이트풀셋의 파드 템플릿에도 이 레이블을 동일하게 적용할 것입니다.

이제 터미널에서 다음 명령어를 실행하여 헤드리스 서비스를 생성합니다.

클립보드에 복사

“service/my-stateful-app-svc created” 라는 메시지가 출력되면 성공입니다. 생성된 서비스를 확인해 볼까요?

클립보드에 복사

출력 결과를 보면 CLUSTER-IP 필드가 <none>으로 표시되는 것을 확인할 수 있습니다. 이것이 바로 헤드리스 서비스의 특징입니다. 아직 스테이트풀셋과 파드가 없으므로 이 서비스에 연결된 엔드포인트는 없을 것입니다. 이제 다음 단계에서 스테이트풀셋을 만들면 이 서비스가 마법을 부리기 시작할 겁니다.

7.4.3.2 스테이트풀셋 YAML 작성 (volumeClaimTemplates 포함)

이제 드디어 스테이트풀셋을 정의할 차례입니다. 이 스테이트풀셋은 앞서 만든 헤드리스 서비스와 연결되고, 각 파드에 고유한 퍼시스턴트 볼륨을 제공하기 위해 volumeClaimTemplates를 사용할 것입니다. 간단한 Nginx 컨테이너를 사용하되, 각 파드가 시작될 때 자신의 호스트 이름을 /usr/share/nginx/html/index.html 파일에 기록하도록 하여 각 파드가 고유한 상태(데이터)를 가지는지 쉽게 확인할 수 있도록 구성해 보겠습니다.

아래 내용을 my-statefulset.yaml 파일로 저장해 주세요.

클립보드에 복사

이 YAML 파일의 주요 부분을 다시 한번 짚어보겠습니다.

  • spec.serviceName: “my-stateful-app-svc”: 앞서 만든 헤드리스 서비스의 이름을 정확히 지정하여 스테이트풀셋과 연결합니다.
  • spec.replicas: 2: 2개의 파드(my-web-app-0, my-web-app-1)를 생성하도록 합니다.
  • spec.selector.matchLabels와 spec.template.metadata.labels는 app: my-stateful-app으로 동일하게 설정하여 헤드리스 서비스의 셀렉터와도 일치시킵니다.
  • spec.template.spec.containers[0].volumeMounts: www-storage라는 이름의 볼륨을 Nginx의 웹 루트 디렉토리인 /usr/share/nginx/html에 마운트합니다.
  • spec.template.spec.containers[0].command와 args: 컨테이너가 시작될 때 /bin/sh를 실행하여, echo “Hello from $(hostname)” > /usr/share/nginx/html/index.html 명령으로 자신의 호스트 이름(파드 이름과 동일)을 index.html 파일에 쓰고, 그 후에 nginx -g ‘daemon off;’ 명령으로 Nginx를 포그라운드에서 실행합니다. 이를 통해 각 파드는 자신만의 index.html 내용을 갖게 됩니다.
  • spec.volumeClaimTemplates: 이 섹션이 스테이트풀셋의 마법 중 하나입니다.
    • metadata.name: www-storage: 이 이름으로 각 파드에 대한 PVC가 생성될 때 접두사처럼 사용됩니다 (실제 PVC 이름은 www-storage-my-web-app-0, www-storage-my-web-app-1이 됩니다).
    • spec.accessModes, spec.storageClassName, spec.resources.requests.storage: 생성될 PVC의 명세를 정의합니다. 특히 storageClassName은 현재 쿠버네티스 클러스터에서 사용 가능한 이름을 지정해야 동적 프로비저닝이 성공합니다. (만약 kubectl get sc 명령으로 확인했을 때 standard가 없다면, 사용 가능한 다른 이름으로 변경하거나 기본값 스토리지 클래스가 있다면 생략할 수도 있습니다.)

이제 터미널에서 다음 명령어를 실행하여 스테이트풀셋을 생성합니다.

클립보드에 복사

“statefulset.apps/my-web-app created” 메시지가 출력되면 성공입니다. 이제 다음 단계에서 스테이트풀셋이 어떻게 파드와 PVC를 순차적으로 생성하는지 확인해 보겠습니다.

7.4.3.3 순차적 생성 및 삭제 확인

스테이트풀셋의 중요한 특징 중 하나는 파드를 순차적으로, 그리고 점진적으로 생성하고 삭제한다는 점입니다. 이는 상태 유지 애플리케이션이 안정적으로 초기화되거나 종료될 수 있도록 돕습니다.

순차적 생성 확인:

먼저, 파드들이 어떻게 생성되는지 실시간으로 관찰해 보겠습니다. 터미널에서 다음 명령어를 실행하세요. -w (watch) 옵션은 변경 사항을 계속 지켜보도록 합니다.

클립보드에 복사

출력 결과를 보면 다음과 같은 순서로 파드가 생성되는 것을 확인할 수 있습니다.

  1. my-web-app-0 파드가 먼저 Pending 상태가 되었다가, PVC가 바인딩되고 이미지가 풀링된 후 ContainerCreating을 거쳐 Running 상태가 됩니다.
  2. my-web-app-0 파드가 완전히 Running 및 Ready 상태가 된 후에야, my-web-app-1 파드의 생성이 시작됩니다.
  3. my-web-app-1 파드도 동일한 과정을 거쳐 Running 상태가 됩니다.

이것이 바로 스테이트풀셋의 순차적 생성입니다. replicas가 더 많다면, my-web-app-2, my-web-app-3 순으로 계속 진행될 것입니다.

이제 각 파드에 대해 PVC가 제대로 생성되었는지 확인해 보겠습니다. (Ctrl+C로 이전 watch 명령을 종료한 후)

클립보드에 복사

다음과 유사한 출력을 볼 수 있습니다.

클립보드에 복사

www-storage-my-web-app-0과 www-storage-my-web-app-1이라는 이름으로 각 파드에 해당하는 PVC가 생성되었고, STATUS가 Bound인 것을 확인할 수 있습니다. 각 PVC는 volumeClaimTemplates에 정의한 대로 1GiB의 스토리지를 요청했고, standard 스토리지 클래스를 통해 실제 PV(PersistentVolume)와 연결되었습니다.

데이터 및 네트워크 식별자 확인:

각 파드가 고유한 데이터를 가지고 있는지, 그리고 안정적인 DNS 이름을 통해 접근 가능한지 확인해 봅시다. 먼저, my-web-app-0 파드의 index.html 내용을 확인합니다. 이를 위해 kubectl exec와 함께 임시 파드를 사용하여 curl을 실행하거나, 간단히 포트 포워딩을 사용할 수 있습니다. 여기서는 포트 포워딩을 사용해 보겠습니다.

새 터미널을 열고 다음 명령을 실행하여 my-web-app-0 파드의 80번 포트를 로컬 머신의 8080번 포트로 포워딩합니다.

클립보드에 복사

이제 웹 브라우저나 curl을 사용하여 로컬의 http://localhost:8080에 접속해 보세요.

클립보드에 복사

“Hello from my-web-app-0” 라는 응답을 볼 수 있을 것입니다. 이는 my-web-app-0 파드가 자신의 호스트 이름(파드 이름)을 index.html에 성공적으로 기록했음을 의미합니다.

같은 방식으로 my-web-app-1 파드의 내용도 확인해 보세요. (이전 포트 포워딩은 Ctrl+C로 종료하고, 새 터미널에서 실행하거나 다른 로컬 포트를 사용하세요.)

클립보드에 복사

“Hello from my-web-app-1” 라는 응답을 볼 수 있을 것입니다. 각 파드가 자신만의 고유한 데이터를 www-storage-<파드이름> PVC에 저장하고 있음을 확인했습니다.

이제 헤드리스 서비스를 통해 생성된 DNS 이름을 확인해 봅시다. 클러스터 내에서 DNS 조회가 가능한 임시 파드(예: busybox)를 띄워 nslookup을 실행해 볼 수 있습니다.

클립보드에 복사

위 명령은 my-web-app-0.my-stateful-app-svc라는 DNS 이름을 조회하여 my-web-app-0 파드의 실제 IP 주소를 반환하는 것을 보여줍니다. 같은 방식으로 my-web-app-1.my-stateful-app-svc도 조회할 수 있습니다.

순차적 삭제 (스케일 다운) 확인:

이제 스테이트풀셋의 replicas 수를 줄여 파드가 어떻게 삭제되는지 확인해 보겠습니다. 다음 명령으로 replicas를 1로 줄입니다.

클립보드에 복사

“statefulset.apps/my-web-app scaled” 메시지가 출력됩니다. 다시 파드 목록을 watch 모드로 확인해 보세요.

클립보드에 복사

my-web-app-1 파드가 먼저 Terminating 상태가 되었다가 사라지는 것을 볼 수 있습니다. 즉, 파드는 생성 순서의 역순(가장 높은 인덱스부터)으로, 그리고 하나씩 순차적으로 삭제됩니다. my-web-app-0 파드는 그대로 남아있습니다.

이때, my-web-app-1 파드와 연결되었던 PVC(www-storage-my-web-app-1)는 어떻게 되었을까요?

클립보드에 복사

PVC www-storage-my-web-app-1은 여전히 Bound 상태로 남아있는 것을 볼 수 있습니다! (최신 쿠버네티스 버전에서는 StatefulSetSpec.persistentVolumeClaimRetentionPolicy의 기본값에 따라 이 동작이 달라질 수 있습니다. 전통적으로는 PVC가 보존되었습니다.) 이는 데이터의 안전을 위해 스테이트풀셋이 파드를 삭제하더라도 해당 파드가 사용하던 스토리지는 기본적으로 유지하기 때문입니다. 만약 나중에 replicas를 다시 2로 늘리면, 새로 생성될 my-web-app-1 파드는 이 기존 PVC에 다시 연결되어 이전 데이터를 그대로 사용할 수 있습니다.

이 실습을 통해 스테이트풀셋이 헤드리스 서비스와 연동하여 각 파드에 안정적인 네트워크 ID를 제공하고, volumeClaimTemplates를 사용하여 파드별 고유 스토리지를 할당하며, 파드의 생성과 삭제를 순차적으로 관리하는 핵심적인 특징들을 직접 확인했습니다. 이러한 기능들 덕분에 상태를 민감하게 다뤄야 하는 복잡한 애플리케이션도 쿠버네티스 위에서 안정적으로 운영될 수 있는 것입니다.