스테이트풀셋을 이용한 안정성 모델링예시1 : Postgresql 파드를 가진 스테이트풀셋 생성스테이트풀셋에서 초기화 컨테이너 활용하기예시. Postgresql 에서 초기화 컨테이너 이용헤드리스 서비스 생성. Postgresql 복제본 생성볼륨 클레임 템플릿으로 스토리지 요청하기스테이트풀셋 사용시 주의점잡과 크론잡을 이용한 유지보수 작업JobCronJob예시. 크론잡 생성 및 2분에 한번씩 데이터베이스 백업크론잡 보류모드로 전환유상태 애플리케이션을 위한 플랫폼 선택하기
스테이트풀셋을 이용한 안정성 모델링
- 레플리카셋을 배치하면
- 무작위 이름이 부여된 파드가 만들어지고
- 도메인 네임으로 일일이 구분되지 않으며
- 레플리카셋이 이들을 병렬로 함께 실행함
- 이에 반해 스테이트풀셋은
- 도메인 네임으로 각각 식별되는 규칙적인 이름이 부여된 파드를 생성하고
- 이들 파드는 첫 번째 파드가 Running 상태가 되면 그다음 파드가 생성되는 식으로 순서대로 생성됨
클러스터 애플리케이션이 스테이트풀셋으로 모델링하기 적합한 대상으로, 이들 애플리케이션은 대개 미리 정해진 주 인스턴스와 부 인스턴스가 함께 동작하며 고가용성을 확보함
- 디플로이먼트로는 이런 애플리케이션을 모델링할 수 없는데, 그 이유는 레플리카셋에서는 특정 파드를 주 인스턴스로 지정할 수 없기 때문
예시1 : Postgresql 파드를 가진 스테이트풀셋 생성
apiVersion: apps/v1 kind: StatefulSet metadata: name: todo-db labels: kiamol: ch08 spec: selector: matchLabels: app: todo-db serviceName: todo-db. # 스테이트풀셋은 연결된 서비스가 필요 replicas: 2 template: metadata: labels: app: todo-db spec: containers: - name: db image: postgres:11.6-alpine env: - name: POSTGRES_PASSWORD_FILE value: /secrets/postgres_password volumeMounts: - name: secret mountPath: "/secrets" volumes: - name: secret secret: secretName: todo-db-secret defaultMode: 0400 items: - key: POSTGRES_PASSWORD path: postgres_password
- 파드 이름이 스테이트풀셋의 이름 뒤에 번호가 붙는 식으로 규칙적으로 부여되기에 레이블 셀렉터에 의지하지 않고도 직접 파드를 관리할 수 있음
- 스테이트풀셋에서 파드는 0번부터 n번까지 순서대로 생성되고, 파드 수를 줄이면 뒷 번호부터 차례대로 제거됨. 파드를 수동으로 삭제하면 대체 파드가 생성되는데, 대체 파드는 삭제했던 파드 이름과 설정을 그대로 따름
- 스테이트풀셋은 연결된 서비스가 필요함
kubectl apply -f todo-list/db/ kubectl get statefulset todo-db kubectl get pods -l app=todo-db NAME READY STATUS RESTARTS AGE todo-db-0 1/1 Running 0 3h42m todo-db-1 1/1 Running 0 3h42m
스테이트풀셋에서 초기화 컨테이너 활용하기
예시. Postgresql 에서 초기화 컨테이너 이용
initContainers: - name: wait-service image: kiamol/ch03-sleep envFrom: - configMapRef: name: todo-db-env command: ['/scripts/wait-service.sh'] volumeMounts: - name: scripts mountPath: "/scripts" - name: initialize-replication image: postgres:11.6-alpine envFrom: - configMapRef: name: todo-db-env env: - name: PGPASSWORD # used as replication password valueFrom: secretKeyRef: key: POSTGRES_PASSWORD name: todo-db-secret command: ['/scripts/initialize-replication.sh'] volumeMounts: - name: scripts mountPath: "/scripts" - name: initdb mountPath: /docker-entrypoint-initdb.d
- 위 초기화 컨테이너에서 실행되는 스크립트의 기능은 두가지
- 해당 파드가 첫 번째 파드일 경우 주인스턴스임을 나타내는 로그를 찍고 종료
- 첫 번째 파드가 아니라면 주 인스턴스의 도메인 네임을 조회하여 접근이 가능한지 확인
- 두번째 초기화 컨테이너는 실제 데이터베이스 복제본을 생성
헤드리스 서비스 생성.
- 스테이트풀셋의 정의에서 서비스를 지정하여 각 파드의 도메인 네임을 정의했는데, 이를 위해서는 서비스를 헤드리스 서비스로 만들어야 한다. 클러스터 IP 주소 없이 파드에 대한 셀렉터만 기술된 서비스 예다.
apiVersion: v1 kind: Service metadata: name: todo-db labels: kiamol: ch08 spec: ports: - port: 5432 targetPort: 5432 name: postgres selector: app: todo-db # 파드 셀렉터가 스테이트풀셋과 일치 clusterIP: None # 이 서비스에는 IP 주소가 부여되지 않음
- 클러스터 ip 가 없는 서비스도 클러스터 DNS 서버에 도메인 네임이 등록된다.
- 하지만 이 서비스는 고정된 ip 주소를 갖지 않으므로 네트워크 계층에서 실제 주소와 연결될 가상 IP 주소가 없는 대신 스테이트풀셋 안에 있는 각 파드의 ip 주소가 반환됨
kubectl exec deploy/sleep -- sh -c 'nslookup todo-db | grep "^[^*]"' Server: 10.96.0.10 Address: 10.96.0.10:53 Name: todo-db.default.svc.cluster.local Address: 10.1.0.48 Name: todo-db.default.svc.cluster.local Address: 10.1.0.49 kubectl exec deploy/sleep -- sh -c 'nslookup todo-db-0.todo-db.default.svc.cluster.local | grep "^[^*]"' Server: 10.96.0.10 Address: 10.96.0.10:53 Name: todo-db-0.todo-db.default.svc.cluster.local Address: 10.1.0.49
Postgresql 복제본 생성
kubectl apply -f todo-list/db/replicated/ kubectl wait --for=condition=Ready pod -l app=todo-db kubectl logs todo-db-0 --tail 1 kubectl logs todo-db-1 --tail 2 kubectl logs todo-db-2 --tail 2 Defaulted container "db" out of: db, wait-service (init), initialize-replication (init) 2024-11-14 12:27:42.008 UTC [11] LOG: database system is ready to accept read only connections 2024-11-14 12:27:42.010 UTC [31] LOG: started streaming WAL from primary at 0/3000000 on timeline 1
- 주 인스턴스가 아닌 부 인스턴스는 주인스턴스로부터 데이터를 streaming 받아서 동기화 하고 읽기전용으로 데이터 제공
위 예시에서 빠져있는 부분은 실제 데이터가 저장되는 스토리지에 대한 내용임. 이는 스테이트풀셋에서
영구볼륨클레임 템플릿
을 기술하면 됨볼륨 클레임 템플릿으로 스토리지 요청하기
각 파드의 영구볼륨클레임마다 별도의 스토리지가 마운트되게 하려면 스테이트풀셋 정의에서 volumeClaimTemplates 필드를 기술해야 한다.
apiVersion: v1 kind: Service metadata: name: sleep-with-pvc labels: kiamol: ch08 spec: selector: app: sleep-with-pvc clusterIP: None --- apiVersion: apps/v1 kind: StatefulSet metadata: name: sleep-with-pvc labels: kiamol: ch08 spec: selector: matchLabels: app: sleep-with-pvc serviceName: sleep-with-pvc replicas: 2 template: metadata: labels: app: sleep-with-pvc spec: containers: - name: sleep image: kiamol/ch03-sleep volumeMounts: - name: data mountPath: /data volumeClaimTemplates: - metadata: name: data labels: kiamol: ch08 spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Mi
- 파드와 영구볼륨클레임의 연결 관계는 파드가 교체되더라도 유지된다.
- 스테이트풀셋이 데이터 비중이 큰 애플리케이션을 실행하기 적합한 가장 큰 이유가 이것
- 애플리케이션을 업데이트하면 새로 생성된 파드 0도 기존의 파드 0과 동일한 영구볼륨클레임을 사용하므로 애플리케이션 컨테이너 역시 이전의 애플리케이션 컨테이너와 완전히 동일한 상태를 유지할 수 있음
kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE data-sleep-with-pvc-0 Bound pvc-363c3a8f-f3da-4c8b-a83b-fff268e9bf66 5Mi RWO hostpath <unset> 6s data-sleep-with-pvc-1 Bound pvc-7d957b42-01e7-4689-a219-839a233e5ee5 5Mi RWO hostpath <unset> 0s kubectl exec sleep-with-pvc-0 -- sh -c 'echo Pod 0 > /data/pod.txt' kubectl exec sleep-with-pvc-1 -- cat /data/pod.txt cat: can't open '/data/pod.txt': No such file or directory command terminated with exit code 1
스테이트풀셋 사용시 주의점
- 스테이트풀셋은 애플리케이션에 안정된 환경을 제공하려 만든 리소스라, 업데이트에 있어서는 다른 컨트롤러 리소스에 비해 유연성이 떨어짐.
- 볼륨 클레임을 추가하는 등 근본적인 변경이 있다면 기존 스테이트풀셋을 업데이트할 수 없다. 스테이트 풀셋을 설계할 때는 애플리케이션의 요구 사항을 잘 만족하늕지 신중하게 검토해야 함.
kubectl apply -f todo-list/db/replicated/update/todo-db-pvc.yaml 1 ↵ The StatefulSet "todo-db" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden
잡과 크론잡을 이용한 유지보수 작업
Job
- 잡은 데이터 백업 및 복원 작업에 적합한 파드 컨트롤러다.
- 잡은 파드 정의를 포함하는데, 이 파드에서 수행하는 배치 작업이 완료되는 것을 보장해 주는 역할을 담당함
- 잡이 포함하는 파드는 어떤 컨테이너 이미지라도 실행할 수 있는데, 유일한 조건은 프로세스가 작업을 마치고 종료되어야 한다는 것임
apiVersion: batch/v1 kind: Job metadata: name: pi-job labels: kiamol: ch08 spec: template: spec: containers: - name: pi image: kiamol/ch05-pi command: ["dotnet", "Pi.Web.dll", "-m", "console", "-dp", "50"] restartPolicy: Never
- template 아래는 일반적인 파드의 정의지만, 추가로
restartPolicy
필드를 정의해주어야 함 - 이 필드에는 배치 작업에 실패했을 때의 대응 방침을 기술
- 잡은 자신이 생성한 파드에
job-name
레이블을 항상 부여하므로 이 레이블 값으로 파드를 찾을 수 있음
kubectl get pods --show-labels NAME READY STATUS RESTARTS AGE LABELS pi-job-mg845 0/1 ContainerCreating 0 19s batch.kubernetes.io/controller-uid=afc60226-872f-401d-b7b8-36de66004236,batch.kubernetes.io/job-name=pi-job,controller-uid=afc60226-872f-401d-b7b8-36de66004236, job-name=pi-job # Job의 로그 확인 kubectl logs -l job-name=pi-job
- 동일한 작업에 여러 입력값을 설정할 수 있는데 이렇게 생성하면 입력값 하나마다 파드가 생성되어 배치 작업이 클러스터에서 병렬로 진행됨. 이 기능을 활용하기 위해서는 아래 두 필드를 활용
completions
: 잡을 실행할 횟수를 지정. 잡 자체는 지정된 수의 파드가 원하는 횟수만큼 작업을 완료하는지만 확인. 기술하련느 잡이 작업 큐를 처리하는 형태라면, 애플리케이션 컨테이너에 다음 처리할 항목을 받아 올 방법을 알려주어야 함parallelism
: 완료 건수가 둘 이상인 잡에서 동시에 실행할 파드 수를 지정
- 잡은, 반드시 완료해야 하지만 수행 시점이 언제인지는 크게 상관없는 계싼 중심 또는 입출력 중심의 작업을 수행할 때 더욱 유용하다.
- 잡의 가장 강력한 특징은 클러스터 맥락에서 실행된다는 점으로, 허용된 것이라면 클러스터 자원을 무엇이든지 사용할 수 있음. Postgresql 을 예로 들면, 데이터베이스 백업을 잡으로 구성할 수 있다.
CronJob
- 크론잡은 잡을 관리하고, 잡은 파드를 관리. 마치 디플로이먼트와 레플리카 셋의 관계와 같음
예시. 크론잡 생성 및 2분에 한번씩 데이터베이스 백업
apiVersion: batch/v1beta1 kind: CronJob metadata: name: todo-db-backup labels: kiamol: ch08 spec: schedule: "*/2 * * * *" # see https://crontab.guru concurrencyPolicy: Forbid jobTemplate: spec: template: spec: restartPolicy: Never containers: - name: backup image: postgres:11.6-alpine command: ['sh', '-c', 'pg_dump -h $POSTGRES_SECONDARY_FQDN -U postgres -F tar -f "/backup/$(date +%y%m%d-%H%M).tar" todo'] envFrom: - configMapRef: name: todo-db-env env: - name: PGPASSWORD valueFrom: secretKeyRef: key: POSTGRES_PASSWORD name: todo-db-secret volumeMounts: - name: backup mountPath: "/backup" volumes: - name: backup persistentVolumeClaim: claimName: todo-db-backup-pvc
kubectl apply -f todo-list/db/backup/ sleep 150 # 크론잡의 상태를 확인 kubectl get cronjob todo-db-backup # 백업에 사용된 영구볼륨클레임이 마운트된 sleep 파드를 실행 kubectl apply -f sleep/sleep-with-db-backup-mount.yaml # 백업이 생성되었는지 확인 kubectl exec deploy/sleep -- ls -l /backup
- 지정된 시간이 되면 크론잡이 잡을 생성하고 다시 잡이 생성한 파드가 백업을 수행
- 크론잡은 작업을 완료한 잡과 파드를 자동으로 삭제하지 않는다. 이 일은 TTL 컨트롤러의 몫이지만, 이 기능은 아직 알파 단계에 머무르고 있기 때문에 지원하지 않는 플랫폼이 많다. TTL 컨트롤러가 없다면 작업을 마친 후 필요 없어진 잡과 파드는 직접 삭제해야 함
크론잡 보류모드로 전환
apiVersion: batch/v1beta1 kind: CronJob metadata: name: todo-db-backup labels: kiamol: ch08 spec: schedule: "*/2 * * * *" # see https://crontab.guru concurrencyPolicy: Forbid suspend: true jobTemplate: spec: template: spec: restartPolicy: Never containers: - name: backup image: postgres:11.6-alpine command: ['sh', '-c', 'pg_dump -h $POSTGRES_SECONDARY_FQDN -U postgres -F tar -f "/backup/$(date +%y%m%d-%H%M).tar" todo'] envFrom: - configMapRef: name: todo-db-env env: - name: PGPASSWORD valueFrom: secretKeyRef: key: POSTGRES_PASSWORD name: todo-db-secret volumeMounts: - name: backup mountPath: "/backup" volumes: - name: backup persistentVolumeClaim: claimName: todo-db-backup-pvc
- 크론잡이 잡을 관리하는 방법은 일반적인 컨트롤러 방식과는 다름. 레이블을 추가하지 않았다면 복잡한 JSON 참조를 통해 찾아내야함
kubectl get jobs -o jsonpath="{.items[?(@.metadata.ownerReferences[0].name=='todo-db-backup')].metadata.name}"
유상태 애플리케이션을 위한 플랫폼 선택하기
- 위에서 우리는 쿠버네티스를 통해 SQL 데이터베이스를 구축했다.
- 스테이트풀셋을 통해 데이터베이스 어플리케이션을 구성하였고(클러스터), 크론잡을 통해 일정 주기로 데이터베이스르르 백업한다.
- 클라우드 환경의 매니지드 데이터베이스와 비교해 보면 스케일링과 고가용성은 물론이고 백업이나 공격 탐지 같은 고급 추가 기능까지 클라우드 서비스 제공자가 도맡아 제공함
어느쪽이 더 나은 선택인지는 상황에 따라 다르다.
- 클라우드 환경에 애플리케이션을 배치했고 데이터가 중요하다면 아주 특별한 이유가 아닌 이상 매니지드 서비스가 좋음
- 반면 비운영 환경, 즉 테스트 환경이나 개발 환경이라면 메시지 큐와 데이터베이스를 컨테이너 상태로 사용하다가 운영 환경으로 이행하면서 매니지드 서비스로 이들을 교체하는 편이 비용으로나 개발 편의성으로 보나 이치에 맞는다.