이 섹션의 다중 페이지 출력 화면임. 여기를 클릭하여 프린트.
상태 유지가 필요한(stateful) 애플리케이션
- 1: 스테이트풀셋 기본
- 2: 예시: WordPress와 MySQL을 퍼시스턴트 볼륨에 배포하기
- 3: 예시: 카산드라를 스테이트풀셋으로 배포하기
- 4: 분산 시스템 코디네이터 ZooKeeper 실행하기
1 - 스테이트풀셋 기본
이 튜토리얼은 스테이트풀셋(StatefulSet)을 이용하여 애플리케이션을 관리하는 방법을 소개한다. 어떻게 스테이트풀셋의 파드를 생성하고, 삭제하며, 스케일링하고, 업데이트하는지 시연한다.
시작하기 전에
튜토리얼을 시작하기 전에 다음의 쿠버네티스 컨셉에 대해 익숙해야 한다.
- 파드
- 클러스터 DNS(Cluster DNS)
- 헤드리스 서비스(Headless Services)
- 퍼시스턴트볼륨(PersistentVolumes)
- 퍼시턴트볼륨 프로비저닝
- 스테이트풀셋
- kubectl 커맨드 라인 도구
참고:
이 튜토리얼은 클러스터가 퍼시스턴스볼륨을 동적으로 프로비저닝 하도록 설정되었다고 가정한다. 만약 클러스터가 이렇게 설정되어 있지 않다면, 튜토리얼 시작 전에 수동으로 2개의 1 GiB 볼륨을 프로비저닝해야 한다.목적
스테이트풀셋은 상태 유지가 필요한(stateful) 애플리케이션과 분산시스템에서 이용하도록 의도했다. 그러나 쿠버네티스 상에 스테이트풀 애플리케이션과 분산시스템을 관리하는 것은 광범위하고 복잡한 주제이다. 스테이트풀셋의 기본 기능을 보여주기 위해 이 둘을 결합하지 않고, 스테이트풀셋을 사용한 단순 웹 애플리케이션을 배포할 것이다.
이 튜토리얼을 마치면 다음 항목에 대해 익숙해질 것이다.
- 스테이트풀셋을 어떻게 생성하는지
- 스테이트풀셋이 어떻게 파드를 관리하는지
- 스테이트풀셋을 어떻게 삭제하는지
- 스테이트풀셋은 어떻게 스케일링하는지
- 스테이트풀셋의 파드는 어떻게 업데이트하는지
스테이트풀셋 생성하기
아래 예제를 이용해서 스테이트풀셋을 생성하자. 이는
스테이트풀셋 개념에서 보인
예제와 유사하다. 이것은 web
과 이 스테이트풀셋 파드의 IP 주소를 게시하는
헤드리스 서비스인
nginx
를 생성한다.
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
위에 예제를 다운로드 받아서 파일이름을 web.yaml
으로 저장하자.
2개의 터미널창을 사용한다. 첫째 터미널에서
kubectl get
을 이용해서
스테이트풀셋의 파드가 생성되는지 감시하자.
kubectl get pods -w -l app=nginx
두 번째 터미널에서
kubectl apply
로
web.yaml
에 정의된 헤드리스 서비스와 스테이트풀셋을 생성한다.
kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
상기 명령어는 NGINX 웹 서버를
실행하는 2개의 파드를 생성한다. nginx
서비스의 정보를 가져온다.
kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 12s
그리고 web
스테이트풀셋 정보를 가져와서 모두 성공적으로 생성되었는지 확인한다.
kubectl get statefulset web
NAME DESIRED CURRENT AGE
web 2 1 20s
차례대로 파드 생성하기
N개의 레플리카를 가진 스테이트풀셋은 배포 시에
순차적으로 {0..N-1} 순으로 생성된다.
첫째 터미널에서 kubectl get
명령의 출력 내용을 살펴보자.
결국 그 내용은 아래 예와 비슷할 것이다.
kubectl get pods -w -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
파드는 web-0
파드가
Running (파드의 단계 참고)
및 Ready (파드의 컨디션에서 type
참고) 상태가 되기 전에는 시작되지 않음에 주의한다.
참고:
스테이트풀셋에서 각 파드에 할당되는 정수값의 순서를 설정하려면, 시작 순서를 확인한다.스테이트풀셋 안에 파드
스테이트풀셋 안에 파드는 고유한 순번과 동일한 네트워크 신원을 가진다.
파드 순번 살펴보기
스테이트풀셋의 파드를 가져오자.
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
스테이트풀셋 개념에서
언급했듯 스테이트풀셋의 파드는 끈끈하고 고유한 정체성을 가진다.
이 정체성은 스테이트풀셋 컨트롤러에서
각 파드에 주어지는 고유한 순번에 기인한다. 파드의 이름의 형식은
<스테이트풀셋 이름>-<순번>
이다. 앞서 web
스테이트풀셋은
2개의 레플리카를 가졌으므로 web-0
과 web-1
2개 파드를 생성한다.
안정적인 네트워크 신원 사용하기
각 파드는 각 순번에 따른 안정적인 호스트네임을 갖는다. 각 파드에서
hostname
명령어를 실행하도록
kubectl exec
를 이용하자.
for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
web-0
web-1
dnsutils
패키지에서 nslookup
명령을 제공하는 컨테이너를
실행하도록 kubectl run
을 이용하자.
파드의 호스트네임에 nslookup
을 이용하면 클러스터 내부 DNS 주소를
확인할 수 있다.
kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
위 명령으로 새로운 셸을 시작한다. 새 셸에서 다음을 실행한다.
# dns-test 컨테이너 셸에서 다음을 실행한다.
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 상태의 각 파드마다 1개). SRV 레코드는 파드의 IP 주소를 포함한 A 레코드 엔트리를 지칭한다.
첫째 터미널에서 스테이트풀셋의 파드를 가져오자.
kubectl get pod -w -l app=nginx
두 번째 터미널에서 스테이트풀셋 내에 파드를 모두 삭제하기 위해
kubectl delete
를
이용하자.
kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
스테이트풀셋이 재시작되고 두 파드가 Running과 Ready 상태로 전환되도록 기다리자.
kubectl get pod -w -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
파드의 호스트네임과 클러스터 내부 DNS 엔트리를 보기 위해
kubectl exec
과 kubectl run
을 이용하자. 먼저, 파드의 호스트네임을 확인한다.
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 /bin/sh
이 명령으로 새로운 셸이 시작된다. 새 셸에서 다음을 실행한다.
# dns-test 컨테이너 셸에서 이것을 실행한다.
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 주소는 변경될 수 있다. 이는 튜토리얼에서 사용하는 클러스터나 다른 클러스터에도 동일하다. 따라서 다른 애플리케이션이 IP 주소로 스테이트풀셋의 파드에 접속하지 않도록 하는 것이 중요하다.
스테이트풀셋의 활성 멤버를 찾아 연결할 경우
헤드리스 서비스(nginx.default.svc.cluster.local
)의 CNAME을 쿼리해야 한다.
CNAME과 연관된 SRV 레코드는 스테이트풀셋의
Running과 Ready 상태의 모든 파드들을
담고 있다.
애플리케이션에서 이미 활성상태(liveness)와 준비성(readiness) 테스트하는
연결 로직을 구현되어 있다면
파드web-0.nginx.default.svc.cluster.local
,
web-1.nginx.default.svc.cluster.local
)의 SRV레코드를 안정적으로 사용할 수 있어
애플리케이션은 파드가 Running과 Ready 상태로 전환할 때
파드의 주소를 검색할 수 있다.
안정적인 스토리지에 쓰기
web-0
과 web-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
스테이트풀셋 컨트롤러는 2개의 퍼시스턴트볼륨에 묶인 2개의 퍼시스턴트볼륨클레임을 생성했다.
본 튜토리얼에서 사용되는 클러스터는 퍼시스턴트볼륨을 동적으로 프로비저닝하도록 설정되었으므로 생성된 퍼시스턴트볼륨도 자동으로 묶인다.
NGINX 웹서버는 기본 색인 파일로
/usr/share/nginx/html/index.html
을 이용합니다.
스테이트풀셋 spec
내의 volumeMounts
필드는 /usr/share/nginx/html
디렉터리가
퍼시스턴트볼륨으로 제공되는지 보증합니다.
파드의 호스트네임을 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 -it "web-$i" -- curl 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
명령을 재시도하기 전에 위 명령을 실행해야 한다.
첫째 터미널에서 스테이트풀셋의 파드를 감시하자.
kubectl get pod -w -l app=nginx
두 번째 터미널에서 스테이트풀셋의 모든 파드를 삭제하자.
kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
첫 번째 터미널에서 실행 중인 kubectl get
명령어의 출력을 확인하고,
모든 파드가 Running과 Ready 상태로 전환될 때까지 기다리자.
kubectl get pod -w -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-0
과 web-1
이 재스케줄링되어도 계속해서
자신의 호스트네임을 제공하는데 이는 각 퍼시스턴트볼륨클레임에
연관된 퍼시스턴트볼륨이 해당 volumeMounts
로 재마운트되기 때문이다.
web-0
과 web-1
의 스케줄링에 관계없이
각각의 퍼시스턴트볼륨은 적절하게 마운트된다.
스테이트풀셋 스케일링
스테이트풀셋을 스케일링하는 것은 레플리카 개수를 늘리거나 줄이는 것을 의미한다. 이것은 replicas
필드를 갱신하여 이뤄진다.
kubectl scale
이나
kubectl patch
을
이용해서 스테이트풀셋을 스케일링할 수 있다.
스케일 업
터미널창에서 스테이트풀셋의 파드를 감시하자.
kubectl get pods -w -l app=nginx
다른 터미널창에서 kubectl scale
을 이용하여 레플리카 개수를
5로 스케일링하자.
kubectl scale sts web --replicas=5
statefulset.apps/web scaled
첫 번째 터미널에서 실행 중인 kubectl get
명령어의 출력을 확인하고,
3개의 추가 파드가 Running과 Ready 상태로 전환될 때까지 기다리자.
kubectl get pods -w -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
스테이트풀셋 컨트롤러는 레플리카 개수를 스케일링한다. 스테이트풀셋 생성으로 스테이트풀셋 컨트롤러는 각 파드을 순차적으로 각 순번에 따라 생성하고 후속 파드 시작 전에 이전 파드가 Running과 Ready 상태가 될 때까지 기다린다.
스케일 다운
터미널에서 스테이트풀셋의 파드를 감시하자.
kubectl get pods -w -l app=nginx
다른 터미널에서 kubectl patch
으로 스테이트풀셋을 다시
3개의 레플리카로 스케일링하자.
kubectl patch sts web -p '{"spec":{"replicas":3}}'
statefulset.apps/web patched
web-4
와 web-3
이 Terminating으로 전환되기까지 기다리자.
kubectl get pods -w -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
순차 파드 종료
컨트롤러는 순번의 역순으로 한 번에 1개 파드를 삭제하고 다음 파드를 삭제하기 전에 각각이 완전하게 종료되기까지 기다린다.
스테이트풀셋의 퍼시스턴트볼륨클레임을 가져오자.
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
여전히 5개의 퍼시스턴트볼륨클레임과 5개의 퍼시스턴트볼륨이 있다. 파드의 안전한 스토리지를 탐색하면서 스테이트풀셋의 파드가 삭제될 때에 파드에 마운트된 스테이트풀셋의 퍼시스턴트볼륨이 삭제되지 않은 것을 보았다. 스테이트풀셋 스케일 다운으로 파드 삭제할 때에도 여전히 사실이다.
스테이트풀셋 업데이트하기
쿠버네티스 1.7 이상에서 스테이트풀셋 컨트롤러는 자동 업데이트를 지원한다.
전략은 스테이트풀셋 API 오브젝트의 spec.updateStrategy
필드로 결정된다.
이 기능은 컨테이너 이미지, 스테이트풀셋의 리소스 요청이나
혹은 한계와 레이블과 파드의 어노테이션을 업그레이드하기 위해 사용될 수 있다.
RollingUpdate
과 OnDelete
의 2개의
유효한 업데이트 전략이 있다.
RollingUpdate
업데이트 전략은 스테이트풀셋에서 기본 값이다.
롤링 업데이트
RollingUpdate
업데이트 전략은 스테이트풀셋의 보장사항을 유지하면서
스테이트풀셋 내의 모든 파드를 역순으로 업데이트한다.
스테이트풀셋 web
의 업데이트 전략을 RollingUpdate
으로 패치하자.
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}'
statefulset.apps/web patched
터미널 창에서 스테이트풀셋 web
의 컨테이너 이미지를 바꾸도록
또 패치하자.
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"gcr.io/google_containers/nginx-slim:0.8"}]'
statefulset.apps/web patched
다른 터미널창에서 스테이트풀셋의 파드를 감시하자.
kubectl get pod -l app=nginx -w
출력 결과는 다음과 비슷하다.
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
스테이트풀셋 내에 파드는 순번의 역순으로 업데이트된다. 이 스테이트풀셋 컨트롤러는 각 파드를 종료시키고 다음 파드를 업데이트하기 전에 그것이 Running과 Ready 상태로 전환될 때까지 기다린다. 알아둘 것은 비록 스테이트풀셋 컨트롤러에서 이전 파드가 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.8
registry.k8s.io/nginx-slim:0.8
registry.k8s.io/nginx-slim:0.8
스테이트풀셋의 모든 파드가 지금은 이전 컨테이너 이미지를 실행 중이이다.
참고:
스테이트풀셋의 롤링 업데이트 상황을 살펴보기 위해kubectl rollout status sts/<name>
명령어도 사용할 수 있다.단계적으로 업데이트 하기
RollingUpdate
업데이트 전략의 파라미터인 partition
를 이용하여
스테이트풀셋의 단계적으로 업데이트할 수 있다.
단계적 업데이트는 스테이트풀셋의 모든 파드를 현재 버전으로 유지하면서
스테이트풀셋의 .spec.template
에 변경을 허용한다.
스테이트풀셋 web
의 updateStrategy
필드에 partition을 추가하자.
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
statefulset.apps/web patched
컨테이너의 이미지를 바꾸도록 스테이트풀셋을 다시 패치한다.
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.7"}]'
statefulset.apps/web patched
스테이트풀셋의 파드를 삭제하자.
kubectl delete pod web-2
pod "web-2" deleted
파드가 Running과 Ready 상태가 되기까지 기다리자.
kubectl get pod -l app=nginx -w
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.8
비록 업데이트 전략이 RollingUpdate
이지만 스테이트풀셋은
파드를 그것의 원래 컨테이너로 복원한다.
파드의 순번이 updateStrategy
에서 지정된
파티션
보다 작기 때문이다.
카나리(Canary) 롤링 아웃
위에서 지정한 partition
값을 차감시키면
변경사항을 테스트하기 위해 카나리 롤아웃을 할 수 있다.
스테이트풀셋에 partition을 차감하도록 패치하자.
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
statefulset.apps/web patched
web-2
파드가 Running과 Ready 상태가 되기까지 기다리자.
kubectl get pod -l app=nginx -w
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 po web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
registry.k8s.io/nginx-slim:0.7
partition
을 바꾸면 스테이트풀셋 컨트롤러는 자동으로
web-2
파드를 업데이트하는데
이는 해당 파드의 순번이 partition
이상이기 때문이다.
web-1
파드를 삭제하자.
kubectl delete pod web-1
pod "web-1" deleted
web-1
파드가 Running과 Ready 상태가 되기까지 기다리자.
kubectl get pod -l app=nginx -w
출력 결과는 다음과 비슷하다.
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.8
web-1
는 원래 환경설정으로 복원되었는데
이는 파드의 순번이 partition보다 작기 때문이다.
스테이트풀셋의 .spec.template
이 갱신되면,
지정된 partition 이상의 순번을 가진 모든 파드는 업데이트된다.
미만의 순번을 가진 파드라면 삭제되거나
종료되어 원래 환경설정으로 복원된다.
단계적 롤아웃
카나리 롤아웃에서 했던 방법과 비슷하게
분할된 롤링 업데이트를 이용하여 단계적 롤아웃(e.g. 선형, 기하 또는 지수적 롤아웃)을
수행할 수 있다. 단계적 롤아웃을 수행하려면
컨트롤러가 업데이트를 일시 중지할 순번으로
partition
를 정하자.
partition은 현재 2
이다. partition을 0
으로 바꾸자.
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched
스테이트풀셋의 모든 파드가 Running과 Ready 상태가 되기까지 기다리자.
kubectl get pod -l app=nginx -w
출력 결과는 다음과 비슷하다.
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
스테이트풀셋에 있는 파드의 컨테이너 이미지 상세 정보를 가져오자.
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.7
registry.k8s.io/nginx-slim:0.7
registry.k8s.io/nginx-slim:0.7
partition
을 0
으로 이동하여 스테이트풀셋에서 계속해서
업데이트 처리를 하도록 허용하였다.
삭제 시 동작
OnDelete
업데이트 전략은 예전 동작(1.6 이하)으로,
이 업데이트 전략을 선택하면,
스테이트풀셋 컨트롤러는 스테이트풀셋의
.spec.template
필드에 수정 사항이 발생해도 자동으로 파드를 업데이트하지 않는다.
이 전략은 .spec.template.updateStrategy.type
을 OnDelete
로 설정하여 선택할 수 있다.
스테이트풀셋 삭제하기
스테이트풀셋은 비종속적(non-cascading), 종속적(cascading) 삭제를 둘 다 지원한다. 비종속적 삭제에서는 스테이트풀셋이 지워질 때에 스테이트풀셋의 파드는 지워지지 않는다. 종속적 삭제에서는 스테이트풀셋과 그에 속한 파드가 모두 지워진다.
비종속적 삭제
터미널창에서 스테이트풀셋의 파드를 감시하자.
kubectl get pods -w -l app=nginx
다른 터미널에서는 스테이트풀셋을 지우기 위해
kubectl delete
명령어를 이용하자.
이 명령어에 --cascade=orphan
파라미터가 추가되었다.
이 파라미터는 쿠버네티스에 스테이트풀셋만 삭제하고 그에 속한 파드는 지우지 않도록 요청한다.
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
스테이트풀셋의 파드를 가져오자.
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
이 삭제되는 동안 web-0
은 재시작하지 않았다.
첫째 터미널에서 스테이트풀셋의 파드를 감시하자.
kubectl get pods -w -l app=nginx
두 번째 터미널에서 스테이트풀셋을 다시 생성하자.
nginx
서비스(가지지 말았어야 하는)를 삭제하기 전까지는 그 서비스가 이미 존재한다는 에러를
볼 것이라는 것을 명심하자.
kubectl apply -f web.yaml
statefulset.apps/web created
service/nginx unchanged
이 에러는 무시하자. 이것은 다만 해당 서비스가 있더라도 nginx 헤드리스 서비스를 생성하려고 했음을 뜻한다.
첫째 터미널에서 실행 중인 kubectl get
명령어의 출력을 살펴보자.
kubectl get pods -w -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
스테이트풀셋이 다시 생성될 때 먼저 web-0
시작한다.
web-1
은 이미 Running과 Ready 상태이므로 web-0
이 Running과 Ready 상태로
전환될 때는 이 파드에 적용됐다. 스테이트풀셋에 replicas
를 2로 하고
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
스테이트풀셋과 web-0
파드를 둘 다 삭제했으나 여전히 index.html
파일에 입력했던
원래 호스트네임을 제공한다. 스테이트풀셋은
파드에 할당된 퍼시스턴트볼륨을 결코 삭제하지 않기때문이다.
다시 스테이트풀셋을 생성하면 web-0
을 시작하며
원래 퍼시스턴트볼륨을 다시 마운트한다.
단계식 삭제
터미널창에서 스테이트풀셋의 파드를 감시하자.
kubectl get pods -w -l app=nginx
다른 터미널창에서 스테이트풀셋을 다시 지우자. 이번에는
--cascade=orphan
파라미터를 생략하자.
kubectl delete statefulset web
statefulset.apps "web" deleted
첫째 터미널에서 실행 중인 kubectl get
명령어의 출력을 살펴보고
모든 파드가 Terminating 상태로 전환될 때까지 기다리자.
kubectl get pods -w -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
스케일 다운 섹션에서 보았듯 파드는 각 순번의 역순으로 하나씩 종료된다. 파드가 종료될 때 스테이트풀 컨트롤러는 이전 파드가 완전히 종료되기까지 기다린다.
참고:
종속적 삭제는 파드와 함께 스테이트풀셋을 제거하지만, 스테이트풀셋과 관련된 헤드리스 서비스를 삭제하지 않는다. 꼭nginx
서비스를 수동으로 삭제해라.kubectl delete service nginx
service "nginx" deleted
스테이트풀셋과 헤드리스 서비스를 한번 더 다시 생성하자.
kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
스테이트풀셋의 모든 파드가 Running과 Ready 상태로 전환될 때
index.html
파일 내용을 검색하자.
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1
스테이트풀셋과 그 내부의 모든 파드를 삭제했지만 퍼시스턴트볼륨이 마운트된 채로
다시 생성되고 web-0
과 web-1
은 계속
각 호스트네임을 제공한다.
최종적으로 nginx
서비스를 삭제한다.
kubectl delete service nginx
service "nginx" deleted
그리고 web
스테이트풀셋을 삭제한다.
kubectl delete statefulset web
statefulset "web" deleted
파드 관리 정책
일부 분산 시스템의 경우 스테이트풀셋의 순서 보증은
불필요하거나 바람직하지 않다. 이러한 시스템은 고유성과 신원만 필요하다.
이를 해결하기 위해 쿠버네티스 1.7에서 .spec.podManagementPolicy
를
스테이트풀셋 API 오브젝트에 도입했다.
OrderedReady 파드 관리
OrderedReady
파드 관리는 스테이트풀셋에서는 기본이다.
이는 스테이트풀셋 컨트롤러가 지금까지 위에서 설명했던 순서를
보증함을 뜻한다.
Parallel 파드 관리
Parallel
파드 관리는 스테이트풀셋 컨트롤러가 모든 파드를
병렬로 시작하고 종료하는 것으로, 다른 파드를 시작/종료하기 전에
파드가 Running과 Ready 상태로 전환되거나 완전히 종료되기까지
기다리지 않음을 뜻한다.
이 옵션은 스케일링 동작에만 영향을 미치며, 업데이트 동작에는 영향을 미치지 않는다.
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
podManagementPolicy: "Parallel"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
상기 예제를 다운로드받아 파일 이름을 web-parallel.yaml
로 저장하자.
이 매니페스트는 web
스테이트풀셋의 .spec.podManagementPolicy
이
Parallel
인 것 말고는 이전에 다운로드 받았던 것과 동일하다.
터미널에서 스테이트풀셋의 파드를 감시하자.
kubectl get pod -l app=nginx -w
다른 터미널에서 매니페스트 안에 스테이트풀셋과 서비스를 생성하자.
kubectl apply -f web-parallel.yaml
service/nginx created
statefulset.apps/web created
첫째 터미널에서 실행했던 kubectl get
명령어의 출력을 살펴보자.
kubectl get pod -l app=nginx -w
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-1 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 10s
web-1 1/1 Running 0 10s
스테이트풀셋 컨트롤러는 web-0
와 web-1
를 둘 다 동시에 시작했다.
두 번째 터미널을 열어 놓고 다른 터미널창에서 스테이트풀셋을 스케일링하자.
kubectl scale statefulset/web --replicas=4
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 1/1 Running 0 10s
web-3 1/1 Running 0 26s
스테이트풀셋은 두 개의 새 파드를 시작하였다. 두 번째 것을 런칭하기 위해 먼저 런칭한 것이 Running과 Ready 상태가 될 때까지 기다리지 않는다.
정리하기
정리의 일환으로 kubectl
명령을 실행할 준비가 된 두 개의 터미널이 열려
있어야 한다.
kubectl delete sts web
# sts는 statefulset의 약자이다.
kubectl get
명령으로 해당 파드가 삭제된 것을 확인할 수 있다.
kubectl get pod -l app=nginx -w
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
삭제하는 동안, 스테이트풀셋은 모든 파드를 동시에 삭제한다. 해당 파드를 삭제하기 전에 그 파드의 순서상 후계자를 기다리지 않는다.
kubectl get
명령어가 실행된 터미널을 닫고
nginx
서비스를 삭제하자.
kubectl delete svc nginx
이 튜토리얼에서 퍼시스턴트볼륨으로 사용했던 영구 스토리지를 삭제한다.
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.
참고:
이 튜토리얼에서 사용된 퍼시턴트볼륨을 위한 퍼시스턴트 스토리지 미디어도 삭제해야 한다. 모든 스토리지를 반환하도록 환경, 스토리지 설정과 프로비저닝 방법에 따른 단계를 따르자.2 - 예시: WordPress와 MySQL을 퍼시스턴트 볼륨에 배포하기
이 튜토리얼은 WordPress 사이트와 MySQL 데이터베이스를 Minikube를 이용하여 어떻게 배포하는지 보여준다. 애플리케이션 둘 다 퍼시스턴트 볼륨과 퍼시스턴트볼륨클레임을 데이터를 저장하기 위해 사용한다.
퍼시스턴트볼륨(PV)는 관리자가 수동으로 프로비저닝한 클러스터나 쿠버네티스 스토리지클래스를 이용해 동적으로 프로비저닝된 저장소의 일부이다. 퍼시스턴트볼륨클레임(PVC)은 PV로 충족할 수 있는 사용자에 의한 스토리지 요청이다. 퍼시스턴트볼륨은 파드 라이프사이클과 독립적이며 재시작, 재스케줄링이나 파드를 삭제할 때에도 데이터를 보존한다.
경고:
이 배포는 프로덕션 사용 예로는 적절하지 않은데 이는 단일 인스턴스의 WordPress와 MySQL을 이용했기 때문이다. 프로덕션이라면 WordPress Helm Chart로 배포하기를 고려해보자.참고:
이 튜토리얼에 제공된 파일들은 GA 디플로이먼트 API를 사용하며 쿠버네티스 버전 1.9 이상을 이용한다. 이 튜토리얼을 쿠버네티스 하위 버전에서 적용한다면 API 버전을 적절히 갱신하거나 이 튜토리얼의 이전 버전을 참고하자.목적
- 퍼시스턴트볼륨클레임과 퍼시스턴트볼륨 생성
- 다음을 포함하는
kustomization.yaml
생성- 시크릿 생성자
- MySQL 리소스 구성
- WordPress 리소스 구성
kubectl apply -k ./
로 생성한 kustomization 을 적용- 정리
시작하기 전에
쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.
버전 확인을 위해서, 다음 커맨드를 실행kubectl version
.
이 예시는 kubectl
1.14 이상 버전에서 동작한다.
다음 설정 파일을 다운로드한다.
퍼시스턴트볼륨클레임과 퍼시스턴트볼륨 생성
MySQL과 Wordpress는 각각 데이터를 저장할 퍼시스턴트볼륨이 필요하다. 퍼시스턴트볼륨클레임은 배포 단계에 생성된다.
많은 클러스터 환경에서 설치된 기본 스토리지클래스(StorageClass)가 있다. 퍼시스턴트볼륨클레임에 스토리지클래스를 지정하지 않으면 클러스터의 기본 스토리지클래스를 사용한다.
퍼시스턴트볼륨클레임이 생성되면 퍼시스턴트볼륨이 스토리지클래스 설정을 기초로 동적으로 프로비저닝된다.
경고:
로컬 클러스터에서 기본 스토리지클래스는hostPath
프로비저너를 사용한다. hostPath
는 개발과 테스트 목적에만 적합하다. hostPath
볼륨인 경우 데이터는 스케쥴링된 파드의 노드에 /tmp
살아있고 노드 간에 이동하지 않는다. 파드가 죽어서 클러스터 내에 다른 노드로 스케줄링되거나 해당 노드가 재부팅되면 그 데이터는 잃어버린다.참고:
hostPath
프로비저너를 사용해야 하는 클러스터를 기동하는 경우라면 --enable-hostpath-provisioner
플래그를 controller-manager
컴포넌트에 꼭 설정해야 한다.참고:
만약 구글 쿠버네티스 엔진으로 운영하는 쿠버네티스 클러스터를 가지고 있다면 가이드를 따르도록 한다.kustomization.yaml 생성하기
시크릿 생성자 추가
시크릿은 암호나 키 같은 민감한 데이터들을 저장하는 오브젝트이다. 1.14 버전부터 kubectl
은 kustomization 파일을 이용해서 쿠버네티스 오브젝트를 관리한다. kustomization.yaml
의 제너레이터로 시크릿을 생성할 수 있다.
다음 명령어로 kustomization.yaml
내에 시크릿 제네레이터를 추가한다. YOUR_PASSWORD
는 사용하기 원하는 암호로 변경해야 한다.
cat <<EOF >./kustomization.yaml
secretGenerator:
- name: mysql-pass
literals:
- password=YOUR_PASSWORD
EOF
MySQL과 WordPress에 필요한 리소스 구성 추가하기
다음 매니페스트는 MySQL 디플로이먼트 단일 인스턴스를 기술한다. MySQL 컨케이너는 퍼시스턴트볼륨을 /var/lib/mysql에 마운트한다. MYSQL_ROOT_PASSWORD
환경 변수는 시크릿에서 가져와 데이터베이스 암호로 설정한다.
apiVersion: v1
kind: Service
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
ports:
- port: 3306
selector:
app: wordpress
tier: mysql
clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
spec:
containers:
- image: mysql:5.6
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
다음의 매니페스트는 단일-인스턴스 WordPress 디플로이먼트를 기술한다. WordPress 컨테이너는
웹사이트 데이터 파일을 위해 /var/www/html
에 퍼시스턴트볼륨을 마운트한다. WORDPRESS_DB_HOST
환경 변수에는
위에서 정의한 MySQL 서비스의 이름이 설정되며, WordPress는 서비스를 통해 데이터베이스에 접근한다.
WORDPRESS_DB_PASSWORD
환경 변수에는 kustomize가 생성한 데이터베이스 패스워드가 설정된다.
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
spec:
containers:
- image: wordpress:4.8-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: password
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wp-pv-claim
-
MySQL 디플로이먼트 구성 파일을 다운로드한다.
curl -LO https://k8s.io/examples/application/wordpress/mysql-deployment.yaml
-
WordPress 구성 파일을 다운로드한다.
curl -LO https://k8s.io/examples/application/wordpress/wordpress-deployment.yaml
-
두 파일을
kustomization.yaml
에 추가하자.
cat <<EOF >>./kustomization.yaml
resources:
- mysql-deployment.yaml
- wordpress-deployment.yaml
EOF
적용하고 확인하기
kustomization.yaml
은 WordPress 사이트와 MySQL 데이터베이스를 배포하는 모든 리소스를 포함한다.
다음과 같이 디렉터리를 적용할 수 있다.
kubectl apply -k ./
이제 모든 오브젝트가 존재하는지 확인할 수 있다.
-
시크릿이 존재하는지 다음 명령어를 실행하여 확인한다.
kubectl get secrets
응답은 아래와 비슷해야 한다.
NAME TYPE DATA AGE mysql-pass-c57bb4t7mf Opaque 1 9s
-
퍼시스턴트볼륨이 동적으로 프로비저닝되었는지 확인한다.
kubectl get pvc
참고:
PV를 프로비저닝하고 정착(bound)시키는데 수 분이 걸릴 수 있다.
응답은 아래와 비슷해야 한다.
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE mysql-pv-claim Bound pvc-8cbd7b2e-4044-11e9-b2bb-42010a800002 20Gi RWO standard 77s wp-pv-claim Bound pvc-8cd0df54-4044-11e9-b2bb-42010a800002 20Gi RWO standard 77s
-
다음 명령어를 실행하여 파드가 실행 중인지 확인한다.
kubectl get pods
참고:
파드의 상태가 `RUNNING`가 되기까지 수 분이 걸릴 수 있다.
응답은 아래와 비슷해야 한다.
NAME READY STATUS RESTARTS AGE wordpress-mysql-1894417608-x5dzt 1/1 Running 0 40s
-
다음 명령어를 실행하여 서비스가 실행 중인지 확인해보자.
kubectl get services wordpress
응답은 아래와 비슷해야 한다.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE wordpress LoadBalancer 10.0.0.89 <pending> 80:32406/TCP 4m
참고:
Minikube에서는 서비스를 `NodePort`으로만 노출할 수 있다. EXTERNAL-IP는 항상 Pending 상태이다.
-
다음 명령어를 실행하여 WordPress 서비스의 IP 주소를 얻어온다.
minikube service wordpress --url
응답은 아래와 비슷해야 한다.
http://1.2.3.4:32406
-
IP 주소를 복사해서 웹 브라우저에서 사이트를 열어 보자.
아래 스크린샷과 유사한 WordPress 설정 페이지를 볼 수 있어야 한다.
경고:
이 페이지의 WordPress 설치를 내버려 두지 말자. 다른 사용자가 이 페이지를 발견하고 귀하의 인스턴스에 웹 사이트를 설정하고 악의적인 컨텐츠를 게시하는데 사용할 수 있다.WordPress를 사용자명과 암호를 넣어 생성하거나 인스턴스를 삭제하자.
정리하기
-
다음 명령을 실행하여 시크릿, 디플로이먼트, 서비스와 퍼시스턴트볼륨클레임을 삭제하자.
kubectl delete -k ./
다음 내용
- 인트로스펙션과 디버깅을 알아보자.
- 잡를 알아보자.
- 포트 포워딩를 알아보자.
- 어떻게 컨테이너에서 셸을 사용하는지를 알아보자.
3 - 예시: 카산드라를 스테이트풀셋으로 배포하기
이 튜토리얼은 쿠버네티스에서 아파치 카산드라를 실행하는 방법을 소개한다. 데이터베이스인 카산드라는 데이터 내구성을 제공하기 위해 퍼시스턴트 스토리지가 필요하다(애플리케이션 상태). 이 예제에서 사용자 지정 카산드라 시드 공급자는 카산드라가 클러스터에 가입할 때 카산드라가 인스턴스를 검색할 수 있도록 한다.
스테이트풀셋 은 상태있는 애플리케이션을 쿠버네티스 클러스터에 쉽게 배포할 수 있게 한다. 이 튜토리얼에서 이용할 기능의 자세한 정보는 스테이트풀셋을 참조한다.
참고:
카산드라와 쿠버네티스는 클러스터 맴버라는 의미로 노드 라는 용어를 사용한다. 이 튜토리얼에서 스테이트풀셋에 속하는 파드는 카산드라 노드이며 카산드라 클로스터의 맴버(링 이라 함)이다. 해당 파드가 쿠버네티스 클러스터에서 실행될 때, 쿠버네티스 컨트롤 플레인은 해당 파드를 쿠버네티스 노드에 스케줄 한다.
카산드라 노드가 시작되면 시드 목록 을 사용해서 링에 있는 다른 노드 검색을 위한 위한 부트스트랩을 한다. 이 튜토리얼에는 데이터베이스가 쿠버네티스 클러스터 내부에 나타날 때 새로운 카산드라 파드를 검색할 수 있는 사용자 지정 카산드라 시드 공급자를 배포한다.
목적
- 카산드라 헤드리스 Service를 생성하고 검증한다.
- 스테이트풀셋(StatefulSet)을 이용하여 카산드라 링을 생성한다.
- 스테이트풀셋을 검증한다.
- 스테이트풀셋을 수정한다.
- 스테이트풀셋과 포함된 파드를 삭제한다.
시작하기 전에
쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.
이 튜토리얼을 완료하려면, 파드, 서비스, 스테이트풀셋에 대한 기본 지식이 있어야 한다.
추가적인 Minikube 설정 요령
주의:
Minikube는 2048MB 메모리와 2개 CPU가 기본 설정이다. 이 튜토리얼에서 Minikube를 기본 리소스 설정으로 실행하면 리소스 부족 오류가 발생한다. 이런 오류를 피하려면 Minikube를 다음 설정으로 실행하자.
minikube start --memory 5120 --cpus=4
카산드라를 위한 헤드리스 서비스 생성하기
쿠버네티스 에서 서비스는 동일 작업을 수행하는 파드의 집합을 기술한다.
다음의 서비스는 클러스터에서 카산드라 파드와 클라이언트 간에 DNS 찾아보기 용도로 사용한다.
apiVersion: v1
kind: Service
metadata:
labels:
app: cassandra
name: cassandra
spec:
clusterIP: None
ports:
- port: 9042
selector:
app: cassandra
cassandra-service.yaml
파일에서 카산드라 스테이트풀셋 노드를 모두 추적하는 서비스를 생성한다.
kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-service.yaml
검증하기(선택)
카산드라 서비스 살펴보기
kubectl get svc cassandra
결과는 다음과 같다.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
cassandra ClusterIP None <none> 9042/TCP 45s
cassandra
서비스가 보이지 않는다면, 이와 다른 응답이라면 서비스 생성에 실패한 것이다. 일반적인 문제에 대한
서비스 디버깅하기를
읽어보자.
카산드라 링을 생성하는 스테이트풀셋 이용하기
스테이트풀셋 매니페스트에는 다음을 포함하는데 3개 파드로 구성된 카산드라 링을 생성한다.
참고:
이 예는 Minikube를 위한 기본 프로비저너이다. 다음 스테이트풀셋을 작업하는 클라우드 환경에서 갱신한다.apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cassandra
labels:
app: cassandra
spec:
serviceName: cassandra
replicas: 3
selector:
matchLabels:
app: cassandra
template:
metadata:
labels:
app: cassandra
spec:
terminationGracePeriodSeconds: 500
containers:
- name: cassandra
image: gcr.io/google-samples/cassandra:v13
imagePullPolicy: Always
ports:
- containerPort: 7000
name: intra-node
- containerPort: 7001
name: tls-intra-node
- containerPort: 7199
name: jmx
- containerPort: 9042
name: cql
resources:
limits:
cpu: "500m"
memory: 1Gi
requests:
cpu: "500m"
memory: 1Gi
securityContext:
capabilities:
add:
- IPC_LOCK
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- nodetool drain
env:
- name: MAX_HEAP_SIZE
value: 512M
- name: HEAP_NEWSIZE
value: 100M
- name: CASSANDRA_SEEDS
value: "cassandra-0.cassandra.default.svc.cluster.local"
- name: CASSANDRA_CLUSTER_NAME
value: "K8Demo"
- name: CASSANDRA_DC
value: "DC1-K8Demo"
- name: CASSANDRA_RACK
value: "Rack1-K8Demo"
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
readinessProbe:
exec:
command:
- /bin/bash
- -c
- /ready-probe.sh
initialDelaySeconds: 15
timeoutSeconds: 5
# These volume mounts are persistent. They are like inline claims,
# but not exactly because the names need to match exactly one of
# the stateful pod volumes.
volumeMounts:
- name: cassandra-data
mountPath: /cassandra_data
# These are converted to volume claims by the controller
# and mounted at the paths mentioned above.
# do not use these in production until ssd GCEPersistentDisk or other ssd pd
volumeClaimTemplates:
- metadata:
name: cassandra-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: fast
resources:
requests:
storage: 1Gi
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: fast
provisioner: k8s.io/minikube-hostpath
parameters:
type: pd-ssd
cassandra-statefulset.yaml
파일로 카산드라 스테이트풀셋 생성
# cassandra-statefulset.yaml을 수정하지 않은 경우에 이것을 사용한다.
kubectl apply -f https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml
클러스터에 맞게 cassandra-statefulset.yaml
를 수정해야 하는 경우 다음을 다운로드한 다음
수정된 버전을 저장한 폴더에서 해당 매니페스트를 적용한다.
https://k8s.io/examples/application/cassandra/cassandra-statefulset.yaml
# cassandra-statefulset.yaml을 로컬에서 수정한 경우에 사용한다.
kubectl apply -f cassandra-statefulset.yaml
카산드라 스테이트풀셋 검증하기
-
카산드라 스테이트풀셋 얻기
kubectl get statefulset cassandra
응답은 다음과 유사하다.
NAME DESIRED CURRENT AGE cassandra 3 0 13s
StatefulSet
리소스는 순차적으로 파드를 배포한다. -
순차적으로 생성된 현황을 보기 위해 파드를 살펴보자.
kubectl get pods -l="app=cassandra"
응답은 다음과 유사하다.
NAME READY STATUS RESTARTS AGE cassandra-0 1/1 Running 0 1m cassandra-1 0/1 ContainerCreating 0 8s
모든 3개 파드가 배포되기까지 몇 분이 소요될 수 있다. 배포 후, 동일 명령은 다음과 유사하게 응답한다.
NAME READY STATUS RESTARTS AGE cassandra-0 1/1 Running 0 10m cassandra-1 1/1 Running 0 9m cassandra-2 1/1 Running 0 8m
-
첫 번째 파드 내부에 링의 상태를 보여주는 카산드라 nodetool을 실행하자.
kubectl exec -it cassandra-0 -- nodetool status
이 응답은 다음과 비슷하게 보일 것이다.
Datacenter: DC1-K8Demo ====================== Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UN 172.17.0.5 83.57 KiB 32 74.0% e2dd09e6-d9d3-477e-96c5-45094c08db0f Rack1-K8Demo UN 172.17.0.4 101.04 KiB 32 58.8% f89d6835-3a42-4419-92b3-0e62cae1479c Rack1-K8Demo UN 172.17.0.6 84.74 KiB 32 67.1% a6a1e8c2-3dc5-4417-b1a0-26507af2aaad Rack1-K8Demo
카산드라 스테이트풀셋 수정하기
kubectl edit
를 사용하여 카산드라 스테이트풀셋의 크기를 수정한다.
-
다음 명령어를 실행한다.
kubectl edit statefulset cassandra
이 명령은 터미널에서 편집기를 연다. 변경해야할 행은
replicas
필드이다. 다음 예제는 스테이트풀셋 파일에서 발췌했다.# 다음의 오브젝트를 수정한다. '#'로 시작하는 행은 무시되고, # 빈 파일은 편집을 중단한다. 저장할 때 오류가 발생하면 이 파일이 # 관련 실패와 함께 다시 열린다. # apiVersion: apps/v1 kind: StatefulSet metadata: creationTimestamp: 2016-08-13T18:40:58Z generation: 1 labels: app: cassandra name: cassandra namespace: default resourceVersion: "323" uid: 7a219483-6185-11e6-a910-42010a8a0fc0 spec: replicas: 3
-
레플리카 개수를 4로 바꾸고, 매니페스트를 저장한다.
스테이트풀셋은 4개의 파드를 실행하기 위해 스케일 한다.
-
검증하기 위해 카산드라 스테이트풀셋을 살펴보자
kubectl get statefulset cassandra
결과는 다음과 유사하다.
NAME DESIRED CURRENT AGE cassandra 4 4 36m
정리하기
스테이트풀셋을 삭제하거나 스케일링하는 것은 스테이트풀셋에 연관된 볼륨을 삭제하지 않는다. 당신의 데이터가 스테이트풀셋의 관련된 모든 리소스를 자동으로 제거하는 것보다 더 가치있기에 이 설정은 당신의 안전을 위한 것이다.
경고:
스토리지 클래스와 리클레임 정책에 따라 퍼시스턴스볼륨클레임 을 삭제하면 그와 연관된 볼륨도 삭제될 수 있다. 볼륨 요청이 삭제되어도 데이터를 접근할 수 있다고 절대로 가정하지 말자.-
다음 명령어(한 줄로 연결된)를 실행하여 카산드라 스테이트풀셋을 모두 제거하자.
grace=$(kubectl get pod cassandra-0 -o=jsonpath='{.spec.terminationGracePeriodSeconds}') \ && kubectl delete statefulset -l app=cassandra \ && echo "Sleeping ${grace} seconds" 1>&2 \ && sleep $grace \ && kubectl delete persistentvolumeclaim -l app=cassandra
-
다음 명령어를 실행하여 카산드라에 대해 설정한 서비스를 제거하자.
kubectl delete service -l app=cassandra
카산드라 컨테이너 환경 변수
이 튜토리얼의 파드 는 구글의 컨테이너 레지스트리에
gcr.io/google-samples/cassandra:v13
이미지를 이용한다.
이 도커 이미지는 debian-base에
기반하였고 OpenJDK 8을 포함한다.
이 이미지는 아파치 데비안 리포의 표준 카산드라 설치본을 포함한다.
환경 변수를 이용하여 cassandra.yaml
에 삽입된 값을 바꿀 수 있다.
환경 변수 | 기본값 |
---|---|
CASSANDRA_CLUSTER_NAME |
'Test Cluster' |
CASSANDRA_NUM_TOKENS |
32 |
CASSANDRA_RPC_ADDRESS |
0.0.0.0 |
다음 내용
- 어떻게 스테이트풀셋 스케일하는지 살펴본다.
- 쿠버네티스시드제공자에 대해 더 살펴본다.
- 커스텀 시드 제공자 설정를 살펴본다.
4 - 분산 시스템 코디네이터 ZooKeeper 실행하기
이 튜토리얼은 아파치 ZooKeeper 쿠버네티스에서 스테이트풀셋과 PodDisruptionBudget과 파드안티어피니티(PodAntiAffinity)를 이용한 Apache Zookeeper 실행을 설명한다.
시작하기 전에
이 튜토리얼을 시작하기 전에 다음 쿠버네티스 개념에 친숙해야 한다.
반드시 최소한 4개의 노드가 있는 클러스터가 필요하며, 각 노드는 적어도 2 개의 CPU와 4 GiB 메모리가 필요하다. 이 튜토리얼에서 클러스터 노드를 통제(cordon)하고 비우게(drain) 할 것이다. 이것은 클러스터를 종료하여 노드의 모든 파드를 축출(evict)하는 것으로, 모든 파드는 임시로 언스케줄된다는 의미이다. 이 튜토리얼을 위해 전용 클러스터를 이용하거나, 다른 테넌트에 간섭을 하는 혼란이 발생하지 않도록 해야 합니다.
이 튜토리얼은 클러스터가 동적으로 퍼시스턴트볼륨을 프로비저닝하도록 구성한다고 가정한다. 그렇게 설정되어 있지 않다면 튜토리얼을 시작하기 전에 수동으로 3개의 20 GiB 볼륨을 프로비저닝해야 한다.
목적
이 튜토리얼을 마치면 다음에 대해 알게 된다.
- 어떻게 스테이트풀셋을 이용하여 ZooKeeper 앙상블을 배포하는가.
- 어떻게 앙상블을 일관되게 설정하는가.
- 어떻게 ZooKeeper 서버 디플로이먼트를 앙상블 안에서 퍼뜨리는가.
- 어떻게 PodDisruptionBudget을 이용하여 계획된 점검 기간 동안 서비스 가용성을 보장하는가.
ZooKeeper
아파치 ZooKeeper는 분산 애플리케이션을 위한 분산 오픈 소스 코디네이션 서비스이다. ZooKeeper는 데이터를 읽고 쓰고 갱신을 지켜보도록 한다. 데이터는 파일시스템처럼 계층적으로 관리되고 앙상블(ZooKeeper 서버의 집합) 내에 모든 ZooKeeper서버에 복제된다. 데이터에 모든 연산은 원자적이고 순처적으로 일관된다. ZooKeeper는 Zab 합의 프로토콜을 이용하여 앙상블 내에 모든 서버에 걸쳐 상태 머신을 복제하여 이를 보장한다.
앙상블은 리더 선출을 위해 Zab 프로토콜을 사용하고, 리더 선출과 선거가 완료되기 전까지 앙상블은 데이터를 쓸 수 없다. 완료되면 앙상블은 Zab을 이용하여 확인하고 클라이언트에 보이도록 모든 쓰기를 쿼럼(quorum)에 복제한다. 가중치있는 쿼럼과 관련 없이, 쿼럼은 현재 리더를 포함하는 앙상블의 대다수 컴포넌트이다. 예를 들어 앙상블이 3개 서버인 경우, 리더와 다른 서버로 쿼럼을 구성한다. 앙상블이 쿼럼을 달성할 수 없다면, 앙상블은 데이터를 쓸 수 없다.
ZooKeeper는 전체 상태 머신을 메모리에 보존하고 모든 돌연변이를 저장 미디어의 내구성 있는 WAL(Write Ahead Log)에 기록한다. 서버 장애시 WAL을 재생하여 이전 상태를 복원할 수 있다. WAL이 무제한으로 커지는 것을 방지하기 위해 ZooKeeper는 주기적으로 저장 미디어에 메모리 상태의 스냅샷을 저장한다. 이 스냅샷은 메모리에 직접 적재할 수 있고 스냅샷 이전의 모든 WAL 항목은 삭제될 수 있다.
ZooKeeper 앙상블 생성하기
아래 매니페스트에는 헤드리스 서비스, 서비스, PodDisruptionBudget, 스테이트풀셋을 포함한다.
apiVersion: v1
kind: Service
metadata:
name: zk-hs
labels:
app: zk
spec:
ports:
- port: 2888
name: server
- port: 3888
name: leader-election
clusterIP: None
selector:
app: zk
---
apiVersion: v1
kind: Service
metadata:
name: zk-cs
labels:
app: zk
spec:
ports:
- port: 2181
name: client
selector:
app: zk
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
selector:
matchLabels:
app: zk
maxUnavailable: 1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: zk
spec:
selector:
matchLabels:
app: zk
serviceName: zk-hs
replicas: 3
updateStrategy:
type: RollingUpdate
podManagementPolicy: OrderedReady
template:
metadata:
labels:
app: zk
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- zk
topologyKey: "kubernetes.io/hostname"
containers:
- name: kubernetes-zookeeper
imagePullPolicy: Always
image: "registry.k8s.io/kubernetes-zookeeper:1.0-3.4.10"
resources:
requests:
memory: "1Gi"
cpu: "0.5"
ports:
- containerPort: 2181
name: client
- containerPort: 2888
name: server
- containerPort: 3888
name: leader-election
command:
- sh
- -c
- "start-zookeeper \
--servers=3 \
--data_dir=/var/lib/zookeeper/data \
--data_log_dir=/var/lib/zookeeper/data/log \
--conf_dir=/opt/zookeeper/conf \
--client_port=2181 \
--election_port=3888 \
--server_port=2888 \
--tick_time=2000 \
--init_limit=10 \
--sync_limit=5 \
--heap=512M \
--max_client_cnxns=60 \
--snap_retain_count=3 \
--purge_interval=12 \
--max_session_timeout=40000 \
--min_session_timeout=4000 \
--log_level=INFO"
readinessProbe:
exec:
command:
- sh
- -c
- "zookeeper-ready 2181"
initialDelaySeconds: 10
timeoutSeconds: 5
livenessProbe:
exec:
command:
- sh
- -c
- "zookeeper-ready 2181"
initialDelaySeconds: 10
timeoutSeconds: 5
volumeMounts:
- name: datadir
mountPath: /var/lib/zookeeper
securityContext:
runAsUser: 1000
fsGroup: 1000
volumeClaimTemplates:
- metadata:
name: datadir
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
터미널을 열고
kubectl apply
명령어로
매니페스트를 생성하자.
kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml
이는 zk-hs
헤드리스 서비스, zk-cs
서비스,
zk-pdb
PodDisruptionBudget과 zk
스테이트풀셋을 생성한다.
service/zk-hs created
service/zk-cs created
poddisruptionbudget.policy/zk-pdb created
statefulset.apps/zk created
kubectl get
을 사용하여
스테이트풀셋 컨트롤러가 스테이트풀셋 파드를 생성하는지 확인한다.
kubectl get pods -w -l app=zk
zk-2
파드가 Running and Ready 상태가 되면, CTRL-C
를 눌러 kubectl을 종료하자.
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 19s
zk-0 1/1 Running 0 40s
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
zk-1 0/1 ContainerCreating 0 0s
zk-1 0/1 Running 0 18s
zk-1 1/1 Running 0 40s
zk-2 0/1 Pending 0 0s
zk-2 0/1 Pending 0 0s
zk-2 0/1 ContainerCreating 0 0s
zk-2 0/1 Running 0 19s
zk-2 1/1 Running 0 40s
스테이트풀셋 컨트롤러는 3개의 파드를 생성하고, 각 파드는 ZooKeeper 서버를 포함한 컨테이너를 가진다.
리더 선출 촉진
익명 네트워크에서 리더 선출을 위한 종료 알고리즘이 없기에, Zab은 리더 선출을 위해 명시적인 멤버 구성을 해야 한다. 앙상블의 각 서버는 고유 식별자를 가져야 하고, 모든 서버는 식별자 전역 집합을 알아야 하며, 각 식별자는 네트워크 주소에 연관되어야 한다.
kubectl exec
를 이용하여 zk
스테이트풀셋의 파드의
호스트네임을 알아내자.
for i in 0 1 2; do kubectl exec zk-$i -- hostname; done
스테이트풀셋 컨트롤러는 각 순번 인덱스에 기초하여 각 파드에 고유한 호스트네임을 부여한다. 각 호스트네임은 <스테이트풀셋 이름>-<순번 인덱스>
형식을 취한다. zk
스테이트풀셋의 replicas
필드는 3
으로 설정되었기 때문에, 그 스테이트풀셋 컨트롤러는 3개 파드의 호스트네임을 zk-0
, zk-1
,
zk-2
로 정한다.
zk-0
zk-1
zk-2
ZooKeeper 앙상블에 서버들은 고유 식별자로서 자연수를 이용하고 서버 데이터 디렉터리에 my
라는 파일로 서버 식별자를 저장한다.
각 서버에서 다음 명령어를 이용하여 myid
파일의 내용을 확인하자.
for i in 0 1 2; do echo "myid zk-$i";kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done
식별자는 자연수이고, 순번 인덱스들도 음수가 아니므로, 순번에 1을 더하여 순번을 만들 수 있다.
myid zk-0
1
myid zk-1
2
myid zk-2
3
zk
스테이트풀셋의 각 파드 Fully Qualified Domain Name (FQDN)을 얻기 위해 다음 명령어를 이용하자.
for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done
zk-hs
서비스는 모든 파드를 위한 도메인인
zk-hs.default.svc.cluster.local
을 만든다.
zk-0.zk-hs.default.svc.cluster.local
zk-1.zk-hs.default.svc.cluster.local
zk-2.zk-hs.default.svc.cluster.local
쿠버네티스 DNS의 A 레코드는 FQDN을 파드의 IP 주소로 풀어낸다. 쿠버네티스가 파드를 리스케줄하면, 파드의 새 IP 주소로 A 레코드를 갱신하지만, A 레코드의 이름은 바뀌지 않는다.
ZooKeeper는 그것의 애플리케이션 환경설정을 zoo.cfg
파일에 저장한다. kubectl exec
를 이용하여 zk-0
파드의 zoo.cfg
내용을 보자.
kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg
아래 파일의 server.1
, server.2
, server.3
속성에서
1
, 2
, 3
은 ZooKeeper 서버의 myid
파일에 구분자와
연관된다.
이들은 zk
스테이트풀셋의 파드의 FQDNS을 설정한다.
clientPort=2181
dataDir=/var/lib/zookeeper/data
dataLogDir=/var/lib/zookeeper/log
tickTime=2000
initLimit=10
syncLimit=2000
maxClientCnxns=60
minSessionTimeout= 4000
maxSessionTimeout= 40000
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888
합의 달성
합의 프로토콜에서 각 참가자의 식별자는 유일해야 한다. Zab 프로토콜에서 동일한 고유 식별자를 요청하는 참가자는 없다. 이는 시스템 프로세스가 어떤 프로세스가 어떤 데이터를 커밋했는지 동의하게 하는데 필요하다. 2개 파드를 동일 순번으로 시작하였다면 두 대의 ZooKeeper 서버는 둘 다 스스로를 동일 서버로 식별한다.
합의 프로토콜에서 각 참여자의 식별자는 고유해야 한다. Zab 프로토콜에 두 참여자가 동일한 고유 식별자로 요청해서는 안된다. 이는 시스템 프로세스가 어떤 프로세스가 어떤 데이터를 커밋했는지 동의하도록 하기 위해 필수적이다. 동일 순번으로 두 개의 파드가 실행했다면 두 ZooKeeper 서버는 모두 동일한 서버로 식별된다.
kubectl get pods -w -l app=zk
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 19s
zk-0 1/1 Running 0 40s
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
zk-1 0/1 ContainerCreating 0 0s
zk-1 0/1 Running 0 18s
zk-1 1/1 Running 0 40s
zk-2 0/1 Pending 0 0s
zk-2 0/1 Pending 0 0s
zk-2 0/1 ContainerCreating 0 0s
zk-2 0/1 Running 0 19s
zk-2 1/1 Running 0 40s
각 파드의 A 레코드는 파드가 Ready 상태가 되면 입력된다. 따라서
ZooKeeper 서버의 FQDN은 단일 엔드포인트로 확인되고
해당 엔드포인트는 myid
파일에 구성된 식별자를 가진
고유한 ZooKeeper 서버가 된다.
zk-0.zk-hs.default.svc.cluster.local
zk-1.zk-hs.default.svc.cluster.local
zk-2.zk-hs.default.svc.cluster.local
이것은 ZooKeeper의 zoo.cfg
파일에 servers
속성이 정확히 구성된 앙상블로 나타나는 것을 보증한다.
server.1=zk-0.zk-hs.default.svc.cluster.local:2888:3888
server.2=zk-1.zk-hs.default.svc.cluster.local:2888:3888
server.3=zk-2.zk-hs.default.svc.cluster.local:2888:3888
서버가 Zab 프로토콜로 값을 커밋 시도하면, 합의를 이루어 값을 커밋하거나(리더 선출에 성공했고 나머지 두 개 파드도 Running과 Ready 상태라면) 실패한다(조건 중 하나라도 충족하지 않으면). 다른 서버를 대신하여 쓰기를 승인하는 상태는 발생하지 않는다.
앙상블 무결성 테스트
가장 기본적인 테스트는 한 ZooKeeper 서버에 데이터를 쓰고 다른 ZooKeeper 서버에서 데이터를 읽는 것이다.
아래 명령어는 앙상블 내에 zk-0
파드에서 /hello
경로로 world
를 쓰는 스크립트인 zkCli.sh
를 실행한다.
kubectl exec zk-0 -- zkCli.sh create /hello world
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
Created /hello
zk-1
파드에서 데이터를 읽기 위해 다음 명령어를 이용하자.
kubectl exec zk-1 -- zkCli.sh get /hello
zk-0
에서 생성한 그 데이터는 앙상블 내에 모든 서버에서
사용할 수 있다.
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x100000002
ctime = Thu Dec 08 15:13:30 UTC 2016
mZxid = 0x100000002
mtime = Thu Dec 08 15:13:30 UTC 2016
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
내구성있는 저장소 제공
ZooKeeper 기본 섹션에서 언급했듯이 ZooKeeper는 모든 항목을 내구성있는 WAL에 커밋하고 메모리 상태의 스냅샷을 저장 미디에에 주기적으로 저장한다. 내구성을 제공하기 위해 WAL을 이용하는 것은 복제된 상태 머신을 이루는 합의 프로토콜에서 이용하는 일반적인 기법이다.
kubectl delete
명령을 이용하여
zk
스테이트풀셋을 삭제하자.
kubectl delete statefulset zk
statefulset.apps "zk" deleted
스테이트풀셋의 파드가 종료되는 것을 지켜보자.
kubectl get pods -w -l app=zk
zk-0
이 완전히 종료되면 CTRL-C
를 이용해 kubectl을 종료하자.
zk-2 1/1 Terminating 0 9m
zk-0 1/1 Terminating 0 11m
zk-1 1/1 Terminating 0 10m
zk-2 0/1 Terminating 0 9m
zk-2 0/1 Terminating 0 9m
zk-2 0/1 Terminating 0 9m
zk-1 0/1 Terminating 0 10m
zk-1 0/1 Terminating 0 10m
zk-1 0/1 Terminating 0 10m
zk-0 0/1 Terminating 0 11m
zk-0 0/1 Terminating 0 11m
zk-0 0/1 Terminating 0 11m
zookeeper.yaml
매니페스트를 다시 적용한다.
kubectl apply -f https://k8s.io/examples/application/zookeeper/zookeeper.yaml
zk
스테이트풀셋 오브젝트를 생성하지만, 매니페스트에 다른 API 오브젝트는 이미 존재하므로 수정되지 않는다.
스테이트풀셋 컨트롤러가 스테트풀셋의 파드를 재생성하는 것을 확인한다.
kubectl get pods -w -l app=zk
zk-2
파드가 Running과 Ready가 되면 CTRL-C
를 이용하여 kubectl을 종료한다.
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 19s
zk-0 1/1 Running 0 40s
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
zk-1 0/1 ContainerCreating 0 0s
zk-1 0/1 Running 0 18s
zk-1 1/1 Running 0 40s
zk-2 0/1 Pending 0 0s
zk-2 0/1 Pending 0 0s
zk-2 0/1 ContainerCreating 0 0s
zk-2 0/1 Running 0 19s
zk-2 1/1 Running 0 40s
아래 명령어로 무결성 테스트에서 입력한 값을
zk-2
파드에서 얻어온다.
kubectl exec zk-2 zkCli.sh get /hello
zk
스테이트풀셋의 모든 파드를 종료하고 재생성했음에도, 앙상블은 여전히 원래 값을 돌려준다.
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x100000002
ctime = Thu Dec 08 15:13:30 UTC 2016
mZxid = 0x100000002
mtime = Thu Dec 08 15:13:30 UTC 2016
pZxid = 0x100000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
zk
스테이트풀셋의 spec
에 volumeClaimTemplates
필드는 각 파드에 프로비전될 퍼시스턴트볼륨을 지정한다.
volumeClaimTemplates:
- metadata:
name: datadir
annotations:
volume.alpha.kubernetes.io/storage-class: anything
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 20Gi
스테이트풀셋
컨트롤러는 스테이트풀셋
의 각 파드에 대한
퍼시스턴트볼륨클레임
을 생성한다.
다음 명령어를 이용하여 스테이트풀셋
의 퍼시스턴트볼륨클레임
을 살펴보자.
kubectl get pvc -l app=zk
스테이트풀셋
의 파드를 재생성할 때에 파드의 퍼시스턴트볼륨도 다시 마운트한다.
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
datadir-zk-0 Bound pvc-bed742cd-bcb1-11e6-994f-42010a800002 20Gi RWO 1h
datadir-zk-1 Bound pvc-bedd27d2-bcb1-11e6-994f-42010a800002 20Gi RWO 1h
datadir-zk-2 Bound pvc-bee0817e-bcb1-11e6-994f-42010a800002 20Gi RWO 1h
스테이트풀셋
의 컨테이너 template
의 volumeMounts
부분이 ZooKeeper 서버의 데이터 디렉터리에 퍼시스턴트볼륨 마운트하는 내용이다.
volumeMounts:
- name: datadir
mountPath: /var/lib/zookeeper
zk
스테이트풀셋이 (재)스케줄링될 때 항상 동일한 퍼시스턴트볼륨
을
ZooKeeper의 서버 디렉터리에 마운트한다.
파드를 재스케줄할 때에도 ZooKeeper의 WAL을 통해 이뤄진 모든 쓰기와
모든 그 스냅샷도 내구성을 유지한다.
일관된 구성 보장하기
리더 선출 촉진과 합의 달성 섹션에서 알렸듯이, ZooKeeper 앙상블에 서버는 리더 선출과 쿼럼을 구성하기 위한 일관된 설정이 필요하다. 또한 Zab 프로토콜의 일관된 설정도 네트워크에 걸쳐 올바르게 동작하기 위해서 필요하다. 이 예시에서는 매니페스트에 구성을 직접 포함시켜서 일관된 구성을 달성한다.
zk
스테이트풀셋을 살펴보자.
kubectl get sts zk -o yaml
…
command:
- sh
- -c
- "start-zookeeper \
--servers=3 \
--data_dir=/var/lib/zookeeper/data \
--data_log_dir=/var/lib/zookeeper/data/log \
--conf_dir=/opt/zookeeper/conf \
--client_port=2181 \
--election_port=3888 \
--server_port=2888 \
--tick_time=2000 \
--init_limit=10 \
--sync_limit=5 \
--heap=512M \
--max_client_cnxns=60 \
--snap_retain_count=3 \
--purge_interval=12 \
--max_session_timeout=40000 \
--min_session_timeout=4000 \
--log_level=INFO"
…
ZooKeeper 서버를 시작하는데 사용한 명령어는 커맨드라인 파라미터로 환경 구성을 전달했다. 환경 변수를 이용하여서도 앙상블에 환경 구성을 전달할 수 있다.
로깅 설정하기
zkGenConfig.sh
스크립트로 생성된 파일 중 하나는 ZooKeeper의 로깅을 제어한다.
ZooKeeper는 Log4j를 이용하며
기본 로깅 구성으로는 시간과 파일 크기 기준의 롤링 파일 어펜더를 사용한다.
zk
스테이트풀셋
의 한 파드에서 로깅 설정을 살펴보는 아래 명령어를 이용하자.
kubectl exec zk-0 cat /usr/etc/zookeeper/log4j.properties
아래 로깅 구성은 ZooKeeper가 모든 로그를 표준 출력 스트림으로 처리하게 한다.
zookeeper.root.logger=CONSOLE
zookeeper.console.threshold=INFO
log4j.rootLogger=${zookeeper.root.logger}
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
이는 컨테이너 내에서 안전하게 로깅하는 가장 단순한 방법이다. 표준 출력으로 애플리케이션 로그를 작성하면, 쿠버네티스는 로그 로테이션을 처리한다. 또한 쿠버네티스는 애플리케이션이 표준 출력과 표준 오류에 쓰인 로그로 인하여 로컬 저장 미디어가 고갈되지 않도록 보장하는 정상적인 보존 정책을 구현한다.
파드의 마지막 20줄의 로그를 가져오는 kubectl logs
명령을 이용하자.
kubectl logs zk-0 --tail 20
kubectl logs
를 이용하거나 쿠버네티스 대시보드에서 표준 출력과 표준 오류로 쓰인 애플리케이션 로그를 볼 수 있다.
2016-12-06 19:34:16,236 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52740
2016-12-06 19:34:16,237 [myid:1] - INFO [Thread-1136:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52740 (no session established for client)
2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52749
2016-12-06 19:34:26,155 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52749
2016-12-06 19:34:26,156 [myid:1] - INFO [Thread-1137:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52749 (no session established for client)
2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52750
2016-12-06 19:34:26,222 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52750
2016-12-06 19:34:26,226 [myid:1] - INFO [Thread-1138:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52750 (no session established for client)
2016-12-06 19:34:36,151 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52760
2016-12-06 19:34:36,152 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52760
2016-12-06 19:34:36,152 [myid:1] - INFO [Thread-1139:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52760 (no session established for client)
2016-12-06 19:34:36,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52761
2016-12-06 19:34:36,231 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52761
2016-12-06 19:34:36,231 [myid:1] - INFO [Thread-1140:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52761 (no session established for client)
2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52767
2016-12-06 19:34:46,149 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52767
2016-12-06 19:34:46,149 [myid:1] - INFO [Thread-1141:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52767 (no session established for client)
2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxnFactory@192] - Accepted socket connection from /127.0.0.1:52768
2016-12-06 19:34:46,230 [myid:1] - INFO [NIOServerCxn.Factory:0.0.0.0/0.0.0.0:2181:NIOServerCnxn@827] - Processing ruok command from /127.0.0.1:52768
2016-12-06 19:34:46,230 [myid:1] - INFO [Thread-1142:NIOServerCnxn@1008] - Closed socket connection for client /127.0.0.1:52768 (no session established for client)
쿠버네티스는 많은 로그 솔루션과 통합된다. 클러스터와 애플리케이션에 가장 적합한 로그 솔루션을 선택할 수 있다. 클러스터 수준의 로그 적재(ship)와 통합을 위해서는 로그 순환과 적재를 위해 사이드카 컨테이너를 배포하는 것을 고려한다.
권한 없는 사용자를 위해 구성하기
컨테이너 내부의 권한있는 유저로 애플리케이션을 실행할 수 있도록 하는 최상의 방법은 논쟁거리이다. 조직에서 애플리케이션을 권한 없는 사용자가 실행한다면, 진입점을 실행할 사용자를 제어하기 위해 시큐리티컨텍스트를 이용할 수 있다.
zk
스테이트풀셋
의 파드 template
은 SecurityContext
를 포함한다.
securityContext:
runAsUser: 1000
fsGroup: 1000
파드 컨테이너에서 UID 1000은 ZooKeeper 사용자이며, GID 1000은 ZooKeeper의 그룹에 해당한다.
zk-0
파드에서 프로세스 정보를 얻어오자.
kubectl exec zk-0 -- ps -elf
securityContext
오브젝트의 runAsUser
필드 값이 1000 이므로
루트 사용자로 실행하는 대신 ZooKeeper 프로세스는 ZooKeeper 사용자로 실행된다.
F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD
4 S zookeep+ 1 0 0 80 0 - 1127 - 20:46 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground
0 S zookeep+ 27 1 0 80 0 - 1155556 - 20:46 ? 00:00:19 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg
기본적으로 파드의 퍼시스턴트볼륨은 ZooKeeper 서버의 데이터 디렉터리에 마운트되고, 루트 사용자만이 접근 가능하다. 이 구성은 ZooKeeper 프로세스가 WAL에 기록하고 스냅샷을 저장하는 것을 방지한다.
zk-0
파드의 ZooKeeper 데이터 디렉터리의 권한을 얻어오는 아래 명령어를 이용하자.
kubectl exec -ti zk-0 -- ls -ld /var/lib/zookeeper/data
securityContext
오브젝트의 fsGroup
필드 값이 1000 이므로, 파드의 퍼시스턴트 볼륨의 소유권은 ZooKeeper 그룹으로 지정되어 ZooKeeper 프로세스에서 읽고 쓸 수 있다.
drwxr-sr-x 3 zookeeper zookeeper 4096 Dec 5 20:45 /var/lib/zookeeper/data
ZooKeeper 프로세스 관리하기
ZooKeeper 문서에서는 "ZooKeeper의 서버 프로세스(JVM)을 관리할 감독 프로세스를 필요할 것이다."라고 말한다. 와치독(감독 프로세스)를 활용하여 실패한 프로세스를 재시작하는 것은 분산시스템에서 일반적인 방식이다. 쿠버네티스에서 애플리케이션을 배포할 때에는 감독 프로세스로 외부 유틸리티를 사용하기보다 쿠버네티스를 애플리케이션의 와치독으로서 사용해야 한다.
앙상블 관리하기
zk
스테이트풀셋
은 RollingUpdate
업데이트 전략을 이용하도록 구성되었다.
kubectl patch
로 서버에 할당된 cpu
수를 갱신할 수 있다.
kubectl patch sts zk --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/cpu", "value":"0.3"}]'
statefulset.apps/zk patched
업데이트 상황을 지켜보기 위해 kubectl rollout status
이용하자.
kubectl rollout status sts/zk
waiting for statefulset rolling update to complete 0 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
waiting for statefulset rolling update to complete 1 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
waiting for statefulset rolling update to complete 2 pods at revision zk-5db4499664...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
statefulset rolling update complete 3 pods at revision zk-5db4499664...
이것은 파드를 역순으로 한 번에 하나씩 종료하고, 새로운 구성으로 재생성한다. 이는 롤링업데이트 동안에 쿼럼을 유지하도록 보장한다.
이력과 이전 구성을 보기 위해 kubectl rollout history
명령을 이용하자.
kubectl rollout history sts/zk
출력은 다음과 비슷할 것이다.
statefulsets "zk"
REVISION
1
2
수정사항을 롤백하기 위해 kubectl rollout undo
명령을 이용하자.
kubectl rollout undo sts/zk
출력은 다음과 비슷할 것이다.
statefulset.apps/zk rolled back
프로세스 장애 관리하기
재시작 정책은
쿠버네티스가 파드 내에 컨테이너의 진입점에서 프로세스 실패를 어떻게 다루는지 제어한다.
스테이트풀셋
의 파드에서 오직 적절한 재시작 정책
는 Always이며
이것이 기본 값이다. 상태가 유지되는 애플리케이션을 위해
기본 정책을 절대로 변경하지 말자.
zk-0
파드에서 실행 중인 ZooKeeper 서버에서 프로세스 트리를 살펴보기 위해 다음 명령어를 이용하자.
kubectl exec zk-0 -- ps -ef
컨테이너의 엔트리 포인트로 PID 1 인 명령이 사용되었으며 ZooKeeper 프로세스는 엔트리 포인트의 자식 프로세스로 PID 27 이다.
UID PID PPID C STIME TTY TIME CMD
zookeep+ 1 0 0 15:03 ? 00:00:00 sh -c zkGenConfig.sh && zkServer.sh start-foreground
zookeep+ 27 1 0 15:03 ? 00:00:03 /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dzookeeper.log.dir=/var/log/zookeeper -Dzookeeper.root.logger=INFO,CONSOLE -cp /usr/bin/../build/classes:/usr/bin/../build/lib/*.jar:/usr/bin/../share/zookeeper/zookeeper-3.4.9.jar:/usr/bin/../share/zookeeper/slf4j-log4j12-1.6.1.jar:/usr/bin/../share/zookeeper/slf4j-api-1.6.1.jar:/usr/bin/../share/zookeeper/netty-3.10.5.Final.jar:/usr/bin/../share/zookeeper/log4j-1.2.16.jar:/usr/bin/../share/zookeeper/jline-0.9.94.jar:/usr/bin/../src/java/lib/*.jar:/usr/bin/../etc/zookeeper: -Xmx2G -Xms2G -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /usr/bin/../etc/zookeeper/zoo.cfg
다른 터미널에서 다음 명령어로 zk
스테이트풀셋
의 파드를 확인한다.
kubectl get pod -w -l app=zk
또 다른 터미널에서 다음 명령어로 zk-0
파드의 ZooKeeper 프로세스를 종료시킨다.
kubectl exec zk-0 -- pkill java
ZooKeeper 프로세스의 종료는 부모 프로세스의 종료를 일으킨다. 컨테이너 재시작정책
이 Always이기 때문에 부모 프로세스를 재시작했다.
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 0 21m
zk-1 1/1 Running 0 20m
zk-2 1/1 Running 0 19m
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Error 0 29m
zk-0 0/1 Running 1 29m
zk-0 1/1 Running 1 29m
애플리케이션이 스크립트(zkServer.sh
같은)를 애플리케이션의 비지니스 로직을 구현한 프로세스를 시작하기 위해 이용한다면,
그 스크립트는 자식 프로세스와 함께 반드시 종료되어야 한다.
이는 쿠버네티스가 애플리케이션의 비지니스 로직을 구현한 프로세스가 실패할 때에
애플리케이션 컨테이너를 재시작하는 것을 보증한다.
활성도(Liveness) 테스트하기
실패한 애플리케이션을 재시작하도록 구성하는 것은 분산 시스템을 건강하게 유지하는데 충분하지 않다. 시스템의 프로세스는 살아있지만 응답이 없을 수 있고, 혹은 다른 건강하지 않은 경우의 시나리오가 있다. 애플리케이션 프로세스가 건강하지 않고 재시작해야만 한다는 것을 쿠버네티스에게 알리도록 활성도 검사를 이용해야 한다.
zk
스테이트풀셋
에 파드 template
에 활성도 검사를 명시한다.
livenessProbe:
exec:
command:
- sh
- -c
- "zookeeper-ready 2181"
initialDelaySeconds: 15
timeoutSeconds: 5
검사는 ZooKeeper의 ruok
4 글자 단어를 이용해서 서버의 건강을 테스트하는
배쉬 스크립트를 호출한다.
OK=$(echo ruok | nc 127.0.0.1 $1)
if [ "$OK" == "imok" ]; then
exit 0
else
exit 1
fi
한 터미널에서 zk
스테이트풀셋의 파드를 지켜보기 위해 다음 명령어를 이용하자.
kubectl get pod -w -l app=zk
다른 창에서 zk-0
파드의 파일시스템에서 zookeeper-ready
스크립트를 삭제하기 위해 다음 명령어를 이용하자.
kubectl exec zk-0 -- rm /opt/zookeeper/bin/zookeeper-ready
ZooKeeper의 활성도 검사에 실패하면, 쿠버네티스는 자동으로 프로세스를 재시작하여 앙상블에 건강하지 않은 프로세스를 재시작하는 것을 보증한다.
kubectl get pod -w -l app=zk
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 0 1h
zk-1 1/1 Running 0 1h
zk-2 1/1 Running 0 1h
NAME READY STATUS RESTARTS AGE
zk-0 0/1 Running 0 1h
zk-0 0/1 Running 1 1h
zk-0 1/1 Running 1 1h
준비도 테스트
준비도는 활성도와 동일하지 않다. 프로세스가 살아 있다면, 스케줄링되고 건강하다. 프로세스가 준비되면 입력을 처리할 수 있다. 활성도는 필수적이나 준비도의 조건으로는 충분하지 않다. 몇몇 경우 특별히 초기화와 종료 시에 프로세스는 살아있지만 준비되지 않을 수 있다.
준비도 검사를 지정하면, 쿠버네티스는 준비도가 통과할 때까지 애플리케이션 프로세스가 네트워크 트래픽을 수신하지 않게 한다.
ZooKeeper 서버에서는 준비도가 활성도를 내포한다. 그러므로 zookeeper.yaml
매니페스트에서
준비도 검사는 활성도 검사와 동일하다.
readinessProbe:
exec:
command:
- sh
- -c
- "zookeeper-ready 2181"
initialDelaySeconds: 15
timeoutSeconds: 5
활성도와 준비도 검사가 동일함에도 둘 다 지정하는 것은 중요하다. 이는 ZooKeeper 앙상블에 건강한 서버만 아니라 네트워크 트래픽을 수신하는 것을 보장한다.
노드 실패 방지
ZooKeeper는 변조된 데이터를 성공적으로 커밋하기 위한 서버의 쿼럼이 필요하다. 3개의 서버 앙상블에서 성공적으로 저장하려면 2개 서버는 반드시 건강해야 한다. 쿼럼 기반 시스템에서, 멤버는 가용성을 보장하는 실패 영역에 걸쳐 배포된다. 중단을 방지하기 위해 개별 시스템의 손실로 인해 모범 사례에서는 동일한 시스템에 여러 인스턴스의 응용 프로그램을 함께 배치하는 것을 배제한다.
기본적으로 쿠버네티스는 동일 노드상에 스테이트풀셋
의 파드를 위치시킬 수 있다.
생성한 3개의 서버 앙상블에서 2개의 서버가 같은 노드에 있다면, 그 노드는 실패하고
ZooKeeper 서비스 클라이언트는 그 파드들의 최소 하나가 재스케줄링될 때까지 작동 중단을 경험할 것이다.
노드 실패하는 사건 중에도 중요 시스템의 프로세스가 재스케줄될 수 있게
항상 추가적인 용량을 프로비전해야 한다. 그렇게 하면 쿠버네티스 스케줄러가
ZooKeeper 서버 하나를 다시 스케줄하는 동안까지만 작동 중단될 것이다.
그러나 서비스에서 노드 실패로 인한 다운타임을 방지하려 한다면,
파드안티어피니티
를 설정해야 한다.
zk
스테이트풀셋
의 파드의 노드를 알아보기 위해 다음 명령어를 이용하자.
for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done
zk
스테이트풀셋
에 모든 파드는 다른 노드에 배포된다.
kubernetes-node-cxpk
kubernetes-node-a5aq
kubernetes-node-2g2d
이는 zk
스테이트풀셋
의 파드에 파드안티어피니티(PodAntiAffinity)
를 지정했기 때문이다.
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: "app"
operator: In
values:
- zk
topologyKey: "kubernetes.io/hostname"
requiredDuringSchedulingIgnoredDuringExecution
필드는
쿠버네티스 스케줄러에 topologyKey
로 정의된 도메인에서 app
이 zk
라고 레이블링된
두 개 파드가 위치하지 않도록 한다.
topologyKey
kubernetes.io/hostname
은 도메인이 개별 노드임을 나타낸다.
다른 규칙과 레이블, 셀렉터를 사용하여
앙상블을 물리적인, 네트워크, 전원 장애 분야에 걸쳐 확산하도록 이 기법을 확장할 수 있다.
생존 유지
이 섹션에서는 노드를 통제(cordon)하고 비운다(drain). 공유된 클러스터에서 이 튜토리얼을 진행한다면, 다른 테넌트에 부정적인 영향을 비치지 않음을 보증해야 한다.
이전 섹션은 계획되지 않은 노드 실패에서 살아남도록 어떻게 파드를 확산할 것인가에 대해 알아보았다. 그러나 계획된 점검으로 인해 발생하는 일시적인 노드 실패에 대한 계획도 필요하다.
클러스터에서 다음 명령으로 노드를 살펴보자.
kubectl get nodes
이 튜토리얼에서는 클러스터가 최소 4개의 노드로 구성되었다고 가정한다. 클러스터의 노드가 4개보다 많다면, kubectl cordon
명령을 이용하여 4개 노드를 제외하고 다른 모든 노드를 통제(cordon)한다. 이렇게 4개 노드만 사용하도록 제한하여, 다음의 유지보수 시뮬레이션 예시에서 주키퍼 파드를 스케줄링할 때 어피니티와 PodDisruptionBudget 제약이 발생하도록 할 수 있다.
kubectl cordon <노드-이름>
zk-pdb
PodDisruptionBudget
을 살펴보고자 이 명령어를 이용하자.
kubectl get pdb zk-pdb
max-unavailable
필드는 쿠버네티스가 zk
스테이트풀셋
에서 최대 1개의 파드는
언제든지 가용하지 않을 수 있음을 나타낸다.
NAME MIN-AVAILABLE MAX-UNAVAILABLE ALLOWED-DISRUPTIONS AGE
zk-pdb N/A 1 1
한 터미널에서 zk
스테이트풀셋
의 파드를 지켜보는 이 명령어를 이용하자.
kubectl get pods -w -l app=zk
다른 터미널에서 현재 스케줄되는 파드의 노드를 살펴보자.
for i in 0 1 2; do kubectl get pod zk-$i --template {{.spec.nodeName}}; echo ""; done
출력은 다음과 비슷할 것이다.
kubernetes-node-pb41
kubernetes-node-ixsl
kubernetes-node-i4c4
zk-0
파드가 스케줄되는 노드를 통제하기 위해
kubectl drain
를 이용하자.
kubectl drain $(kubectl get pod zk-0 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data
출력은 다음과 비슷할 것이다.
node "kubernetes-node-group-pb41" cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-group-pb41, kube-proxy-kubernetes-node-group-pb41; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-o5elz
pod "zk-0" deleted
node "kubernetes-node-group-pb41" drained
클러스터에 4개 노드가 있기 때문에 kubectl drain
이 성공하여
zk-0
을 다른 노드로 재스케줄링 된다.
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 2 1h
zk-1 1/1 Running 0 1h
zk-2 1/1 Running 0 1h
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 51s
zk-0 1/1 Running 0 1m
계속해서 스테이트풀셋
의 파드를 첫 터미널에서 지켜보고
zk-1
이 스케줄된 노드를 비워보자.
kubectl drain $(kubectl get pod zk-1 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data
출력은 다음과 비슷할 것이다.
"kubernetes-node-ixsl" cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-ixsl, kube-proxy-kubernetes-node-ixsl; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-voc74
pod "zk-1" deleted
node "kubernetes-node-ixsl" drained
zk-1
파드는 스케줄되지 않는데 이는 zk
StatefulSet
이 오직 2개 노드가 스케줄되도록 파드를 위치시키는 것을 금하는
PodAntiAffinity
규칙을 포함하였기 때문이고 그 파드는 Pending 상태로 남을 것이다.
kubectl get pods -w -l app=zk
출력은 다음과 비슷할 것이다.
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 2 1h
zk-1 1/1 Running 0 1h
zk-2 1/1 Running 0 1h
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 51s
zk-0 1/1 Running 0 1m
zk-1 1/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
계속해서 스테이트풀셋의 파드를 지켜보고
zk-2
가 스케줄된 노드를 비워보자.
kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data
출력은 다음과 비슷할 것이다.
node "kubernetes-node-i4c4" cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
WARNING: Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog; Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4
There are pending pods when an error occurred: Cannot evict pod as it would violate the pod's disruption budget.
pod/zk-2
kubectl 을 종료하기 위해 CTRL-C
를 이용하자.
zk-2
를 추출하는 것은 zk-budget
을 위반하기 때문에 셋째 노드를 비울 수 없다. 그러나 그 노드는 통제 상태로 남는다.
zk-0
에서 온전성 테스트 때에 입력한 값을 가져오는 zkCli.sh
를 이용하자.
kubectl exec zk-0 zkCli.sh get /hello
PodDisruptionBudget
이 존중되기 때문에 서비스는 여전히 가용하다.
WatchedEvent state:SyncConnected type:None path:null
world
cZxid = 0x200000002
ctime = Wed Dec 07 00:08:59 UTC 2016
mZxid = 0x200000002
mtime = Wed Dec 07 00:08:59 UTC 2016
pZxid = 0x200000002
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 0
kubectl uncordon
이용하여 첫 노드의 통제를 풀자.
kubectl uncordon kubernetes-node-pb41
출력은 다음과 비슷할 것이다.
node "kubernetes-node-pb41" uncordoned
zk-1
은 이 노드에서 재스케줄된다. zk-1
이 Running과 Ready가 될 때까지 기다리자.
kubectl get pods -w -l app=zk
출력은 다음과 비슷할 것이다.
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Running 2 1h
zk-1 1/1 Running 0 1h
zk-2 1/1 Running 0 1h
NAME READY STATUS RESTARTS AGE
zk-0 1/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Terminating 2 2h
zk-0 0/1 Pending 0 0s
zk-0 0/1 Pending 0 0s
zk-0 0/1 ContainerCreating 0 0s
zk-0 0/1 Running 0 51s
zk-0 1/1 Running 0 1m
zk-1 1/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Terminating 0 2h
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 0s
zk-1 0/1 Pending 0 12m
zk-1 0/1 ContainerCreating 0 12m
zk-1 0/1 Running 0 13m
zk-1 1/1 Running 0 13m
zk-2
가 스케줄된 노드를 비워보자.
kubectl drain $(kubectl get pod zk-2 --template {{.spec.nodeName}}) --ignore-daemonsets --force --delete-emptydir-data
출력은 다음과 비슷할 것이다.
node "kubernetes-node-i4c4" already cordoned
WARNING: Deleting pods not managed by ReplicationController, ReplicaSet, Job, or DaemonSet: fluentd-cloud-logging-kubernetes-node-i4c4, kube-proxy-kubernetes-node-i4c4; Ignoring DaemonSet-managed pods: node-problem-detector-v0.1-dyrog
pod "heapster-v1.2.0-2604621511-wht1r" deleted
pod "zk-2" deleted
node "kubernetes-node-i4c4" drained
이번엔 kubectl drain
이 성공한다.
zk-2
가 재스케줄되도록 두 번째 노드의 통제를 풀어보자.
kubectl uncordon kubernetes-node-ixsl
출력은 다음과 비슷할 것이다.
node "kubernetes-node-ixsl" uncordoned
kubectl drain
을 PodDisruptionBudget
과 결합하면 유지보수 중에도 서비스를 가용하게 할 수 있다.
drain으로 노드를 통제하고 유지보수를 위해 노드를 오프라인하기 전에 파드를 추출하기 위해 사용한다면
서비스는 혼란 예산을 표기한 서비스는 그 예산이 존중은 존중될 것이다.
파드가 즉각적으로 재스케줄 할 수 있도록 항상 중요 서비스를 위한 추가 용량을 할당해야 한다.
정리하기
kubectl uncordon
은 클러스터 내에 모든 노드를 통제 해제한다.- 반드시 이 튜토리얼에서 사용한 퍼시스턴트 볼륨을 위한 퍼시스턴트 스토리지 미디어를 삭제하자. 귀하의 환경과 스토리지 구성과 프로비저닝 방법에서 필요한 절차를 따라서 모든 스토리지가 재확보되도록 하자.