DatabaseKubernetes 환경에서 운영하기 위해서 반드시 필요한 것은 아래와 같다.

  • 휘발되지 않은 영구 저장소
  • pod의 Dynamic IP 주소가 아닌 Static한 접속 주소
  • 보안 감사 만족을 위한 DB 접근 통제
  • DB 상태가 정상인지 판단할 기준

💡 영구 저장소Static 접속 주소의 조건을 만족시키는 것이 바로 StatefulSet이다.


☝ StatefulSet & Headless Service의 개념을 정리하자면 ????

✔ StatefulSet 이란?

StatefulSet은 kubernetes 리소스 중 Deployment와 유사하게 pod를 관리하는 리소스이다.
scaling을 관리하며, pod들의 순서 및 고유성을 보장한다.

Deployment와 비교하여 말하자면
Deployment의 경우, pod명이 난수로 생성되어 생성되는 순서나 pod명의 고유성을 보장받지 못한다.
하지만 StatefulSet의 경우, pod명이 난수가 아닌 고유한 숫자를 가지게 되고 숫자 0부터 순서대로 생성된다. (만약 중간에 1번을 지운다고 해도 다음 생성되는 숫자는 1이다.)

아래 Deployment와 StatefulSet로 생성한 pod 명의 비교 예시를 가져 왔다.

(🐱 |DOIK-Lab:default) root@k8s-m:~# kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
deploy-cf9b57fb9-gdd68   1/1     Running   0          8s
deploy-cf9b57fb9-p2dtq   1/1     Running   0          39s
deploy-cf9b57fb9-tzxnz   1/1     Running   0          8s
(🐱 |DOIK-Lab:default) root@k8s-m:~# kubectl get pods
NAME         READY   STATUS    RESTARTS   AGE
stateful-0   1/1     Running   0          102s
stateful-1   1/1     Running   0          82s
stateful-2   1/1     Running   0          62s

✔ Headless Service 란?

Kubernetes 리소스에서 Service는 애플리케이션을 네트워크 서비스로 노출하는 방법이다.
ServiceTypes은 ClusterIP, NodePort, LoadBalancer, ExternalName가 있는데 Headless Service를 사용하려면 우선 ServiceTypesClusterIP로 설정하면 된다.

ClusterIP는 서비스를 클러스터-내부IP에 노출시키는 거라 클러스터 내에서만 서비스에 도달할 수 있다.

근데 Headless Service라니.. 말 그대로 머리가 없는 서비스다(?)
headless
(이미지 출처 : Pop art of cartoon headless businessman © studiostoks - shutterstock)


ClusterIP: None으로 설정하여, 클러스터 네트워크 내부에서 해당 Pod에 직접 접근 가능한 고유 IPDNS가 생기는 것이다.

🤘 실습으로 확인하는 StatefulSet & Headless Service

✔ StatefulSet으로 pod를 배포해 보자!

  1. web.yaml 파일 받아 오기

     (🐱 |DOIK-Lab:default) root@k8s-m:~# curl -s -O https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/application/web/web.yaml
     (🐱 |DOIK-Lab:default) root@k8s-m:~# cat web.yaml 
       ───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
               File: web.yaml
       ───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
         1     apiVersion: v1
         2     kind: Service
         3     metadata:
         4       name: nginx
         5       labels:
         6         app: nginx
         7     spec:
         8       ports:
         9       - port: 80
         10        name: web
         11      clusterIP: None
         12      selector:
         13        app: nginx
         14    ---
         15    apiVersion: apps/v1
         16    kind: StatefulSet
         17    metadata:
         18      name: web
         19    spec:
         20      serviceName: "nginx"
         21      replicas: 2
         22      selector:
         23        matchLabels:
         24          app: nginx
         25      template:
         26        metadata:
         27          labels:
         28            app: nginx
         29        spec:
         30          containers:
         31          - name: nginx
         32            image: k8s.gcr.io/nginx-slim:0.8
         33            ports:
         34            - containerPort: 80
         35              name: web
         36            volumeMounts:
         37            - name: www
         38              mountPath: /usr/share/nginx/html
         39      volumeClaimTemplates:
         40      - metadata:
         41          name: www
         42        spec:
         43          accessModes: [ "ReadWriteOnce" ]
         44          resources:
         45            requests:
         46              storage: 1Gi
         47   
         ───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    
  2. web.yaml으로 Headless Service와 StatefulSet 생성

     (🍎 |DOIK-Lab:default) root@k8s-m:~# kubectl apply -f web.yaml && kubectl get pods -w -l app=nginx
         service/nginx created
         statefulset.apps/web created
         NAME    READY   STATUS    RESTARTS   AGE
         web-0   0/1     Pending   0          0s
         web-0   0/1     Pending   0          9s
         web-0   0/1     ContainerCreating   0          9s
         web-0   0/1     ContainerCreating   0          9s
         web-0   1/1     Running             0          18s
         web-1   0/1     Pending             0          0s
         web-1   0/1     Pending             0          10s
         web-1   0/1     ContainerCreating   0          10s
         web-1   0/1     ContainerCreating   0          11s
         web-1   1/1     Running             0          20s
    
  3. web-0, web-1 pod에 접속해서 hostname 출력하기

     (🍎 |DOIK-Lab:default) root@k8s-m:~# for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
         web-0
         web-1
    
  4. 다른 터미널로 모니터링

    web_yaml

0️⃣ Headless Service 특징 ) pod마다 고유한 주소를 갖는다

  1. netshoot 이미지로 netdebug pod 생성하여 zsh로 접속!
    • --rm : 쉘이 종료되면 pod를 자동으로 삭제해 주는 옵션이다. (pod를 일시적으로 생성)
    • --restart : Container restartPolicy 옵션을 의미하며, 사용 가능한 값은 Always, OnFailure, Never이다. (기본 값: Always)
     (🍎 |DOIK-Lab:default) root@k8s-m:~# kubectl run -it --rm netdebug --image=nicolaka/netshoot --restart=Never -- zsh
    
         If you don't see a command prompt, try pressing enter.
    		
                             dP            dP                           dP
                             88            88                           88
         88d888b. .d8888b. d8888P .d8888b. 88d888b. .d8888b. .d8888b. d8888P
         88'  `88 88ooood8   88   Y8ooooo. 88'  `88 88'  `88 88'  `88   88
         88    88 88.  ...   88         88 88    88 88.  .88 88.  .88   88
         dP    dP `88888P'   dP   `88888P' dP    dP `88888P' `88888P'   dP
    		
         Welcome to Netshoot! (github.com/nicolaka/netshoot)
    		
    		
    
             netdebug  ~
    
  2. nginx service 이름으로 도메인 질의했더니 headless service에 묶인 pod의 ip 리스트를 보여 준다.

         netdebug  ~  nslookup nginx
         Server:         10.200.1.10
         Address:        10.200.1.10#53
    		
         Name:   nginx.default.svc.cluster.local
         Address: 172.16.24.2
         Name:   nginx.default.svc.cluster.local
         Address: 172.16.158.2
    
  3. 각 pod의 레코드 값을 직접 알기 위해 -type=srv라는 태그 값을 입력해 준다.

         netdebug  ~  nslookup -type=srv nginx     
         Server:         10.200.1.10
         Address:        10.200.1.10#53
    		
         nginx.default.svc.cluster.local service = 0 50 80 web-0.nginx.default.svc.cluster.local.
         nginx.default.svc.cluster.local service = 0 50 80 web-1.nginx.default.svc.cluster.local.
    
  4. 각 pod의 고유한 주소로 특정 pod를 찾아갈 수 있다.

         netdebug  ~  nslookup web-0.nginx
         Server:         10.200.1.10
         Address:        10.200.1.10#53
    		
         Name:   web-0.nginx.default.svc.cluster.local
         Address: 172.16.158.2
    	
    	
         netdebug  ~  nslookup web-1.nginx
         Server:         10.200.1.10
         Address:        10.200.1.10#53
    		
         Name:   web-1.nginx.default.svc.cluster.local
         Address: 172.16.24.2
    
  5. 쉘 종료하니 pod가 자동 삭제됐다!

     netdebug  ~  exit
     pod "netdebug" deleted  ```
    

1️⃣ StatefulSet 특징1) pod를 삭제하면 동일한 이름을 가진 pod로 재생성된다

  1. pod 삭제 실행 후 재실행 시 pod명 확인

     (🍎 |DOIK-Lab:default) root@k8s-m:~# kubectl delete pod -l app=nginx && kubectl get pods -w -l app=nginx
     		pod "web-0" deleted
     		pod "web-1" deleted
     		NAME    READY   STATUS              RESTARTS   AGE
     		web-0   0/1     ContainerCreating   0          1s
     		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   0/1     ContainerCreating   0          1s
     		web-1   1/1     Running             0          2s
    
  2. 각 pod로 접속하여 hostname 확인

     (🍎 |DOIK-Lab:default) root@k8s-m:~# for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
     	web-0
     	web-1
    

2️⃣ StatefulSet 특징2) pod는 영속적인 저장소를 가질 수 있다

  1. pvc 확인

     (🍎 |DOIK-Lab:default) root@k8s-m:~# kubectl get pvc -l app=nginx
     	NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
     	www-web-0   Bound    pvc-7cce446e-e68a-4783-99f9-fbb891538669   1Gi        RWO            local-path     11m
     	www-web-1   Bound    pvc-7defb95e-8b02-4886-851b-e339e234fd71   1Gi        RWO            local-path     11m
    
  2. 웹 서버 index.html 에 hostname 추가 후 웹 접속 해서 확인

     (🍎 |DOIK-Lab:default) root@k8s-m:~# for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)-pv-test" > /usr/share/nginx/html/index.html'; done
     (🍎 |DOIK-Lab:default) root@k8s-m:~# for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
     	web-0-pv-test
     	web-1-pv-test
    
  3. app=nginx label 값을 가진 pod 삭제 실행 후 scaling 확인

     (🍎 |DOIK-Lab:default) root@k8s-m:~# kubectl delete pod -l app=nginx && kubectl get pods -w -l app=nginx
     	pod "web-0" deleted
     	pod "web-1" deleted
     	NAME    READY   STATUS              RESTARTS   AGE
     	web-0   0/1     ContainerCreating   0          0s
     	web-0   0/1     ContainerCreating   0          1s
     	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   0/1     ContainerCreating   0          1s
     	web-1   1/1     Running             0          2s
    
  4. 웹 접속 해서 확인 : PV 저장소 확인

    ✅ 고유 이름을 가진 pod가 삭제되고 다시 생성되더라도 해당 pod는 기존 사용하던 pv만 사용한다
    (ex. web-0라는 pod는 web-0-pv-test라는 pv만 계속 물고 온다)
    ✅ 이게 바로 영속적인 저장소를 보장받는 StatefulSet의 특징이다

     (🍎 |DOIK-Lab:default) root@k8s-m:~# for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
     	web-0-pv-test
     	web-1-pv-test
    

3️⃣ StatefulSet 특징3) pod는 고유한 이름을 가지며, 0부터 순차적으로 생성된다

  1. pod scale out

    ✅ pod가 증가 또는 감소할 때 순차적으로 생성되는 것을 확인할 수 있다.

     (🍎 |DOIK-Lab:default) root@k8s-m:~# kubectl scale sts web --replicas=5 && kubectl get pods -w -l app=nginx
     	statefulset.apps/web scaled
     	NAME    READY   STATUS    RESTARTS   AGE
     	web-0   1/1     Running   0          88s
     	web-1   1/1     Running   0          86s
     	web-2   0/1     Pending   0          0s
     	web-2   0/1     Pending   0          10s
     	web-2   0/1     ContainerCreating   0          10s
     	web-2   0/1     ContainerCreating   0          11s
     	web-2   1/1     Running             0          18s
     //...생략...
    
  2. pod scale in

    (🍎 |DOIK-Lab:default) root@k8s-m:~# kubectl patch sts web -p '{"spec":{"replicas":3}}' && kubectl get pods -w -l app=nginx
    	statefulset.apps/web patched
    	NAME    READY   STATUS        RESTARTS   AGE
    	web-0   1/1     Running       0          2m54s
    	web-1   1/1     Running       0          2m52s
    	web-2   1/1     Running       0          86s
    	web-3   1/1     Running       0          68s
    	web-4   1/1     Terminating   0          61s
    	web-4   1/1     Terminating   0          62s
    	web-4   0/1     Terminating   0          62s
    	web-4   0/1     Terminating   0          62s
    	web-4   0/1     Terminating   0          62s
    //...생략...
    

✔ 실습을 마치고 리소스 삭제!

(🍎 |DOIK-Lab:default) root@k8s-m:~# kubectl delete -f web.yaml && kubectl delete pvc --all
	service "nginx" deleted
	statefulset.apps "web" deleted
	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

📚 참고 자료

  • 🚀가시다님 노션🚀

Categories:

Updated:

Leave a comment