DevOps/Kubernetes

[Kubernetes] StatefulSet (스테이트풀)과 Headless Service (헤드리스 서비스)

TTOII 2022. 6. 4. 12:19
728x90

🚀 Headless Service

Headless Service란 ?!

DB와 같이 master, slave 구조가 있는 서비스들의 경우 service를 통해 로드밸런싱을 하지 않고 개별 pod의 주소를 알고 접속해야한다.

pod들은 DNS 이름을 가질 수는 있으나 {pod name}.{service name}.{namespace}.svc.cluster.local 형식의 이름을 갖기 때문에 pod를 DNS를 이용해 접근하려면 service name이 있어야 한다.
Statefulset에 의한 서비스들은 service를 이용해서 로드 밸런싱을 하는 것이 아니기 때문에 로드 밸런서의 역할은 필요없고 논리적으로 pod들을 묶어 줄 수 있는 service만 있으면 되기 때문에 헤드리스 서비스를 사용한다.

Headless 서비스를 이용하면 service 가 로드 밸런서의 역할도 하지 않고 단일 IP도 가지지 않지만 nslookup을 이용해서, headless 서비스에 의해서 묶여진 Pod들의 이름도 알 수 있고 {pod name}.{service name}.{namespace}.svc.cluster.local 이름으로, 각 pod 에 대한 접근 주소 역시 얻을 수 있다.


쿠버네티스 생성 시 svc.spec.clusterIP 필드 값을 None으로 설정하면 ClusterIP가 없는 서비스를 만들 수 있다.

헤드리스 서비스의 경우 ClusterIP가 할당되지 않고 kube-proxy가 서비스를 처리하지 않으며 로드 밸런싱 또는 프록시 동작을 수행하지 않는다. DNS가 자동으로 구성되는 방법은 서비스에 셀렉터가 정의되어 있는지 여부에 달려있다.

헤드리스 서비스에 셀렉터 필드를 구성하면 쿠버네티스 API로 확인할 수 있는 엔드포인트(endpoint)가 만들어진다.
서비스와 연결된 파드를 직접 가리키는 DNS A 레코드도 만들어진다.

만약 셀렉터가 없는 경우 엔드포인트가 만들어지지 않는다.
단, 셀렉터가 없더라도 DNS 시스템에는 ExternalName 타입의 서비스에서 사용할 CNAME 레코드가 생성된다.

myweb-rs.yaml

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      env: dev
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP

 

myweb-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: myweb-svc
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

 

myweb-svc-headless.yaml

apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-headless
spec:
  type: ClusterIP
  clusterIP: None # <-- Headless Service
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

일반 service와 Headless service의 차이를 알아보기 위해 리소스를 생성한다.

 vagrant@k8s-node1  ~/cont/sts/headless  kubectl get rs,po           
NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/myweb-rs                            3         3         3       23s
replicaset.apps/nfs-client-provisioner-758f8cd4d6   1         1         1       20h

NAME                                          READY   STATUS    RESTARTS   AGE
pod/myweb-rs-cbldm                            1/1     Running   0          23s
pod/myweb-rs-djhkw                            1/1     Running   0          23s
pod/myweb-rs-svltc                            1/1     Running   0          23s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running   0          20h

 vagrant@k8s-node1  ~/cont/sts/headless  kubectl get svc,ep
NAME                         TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)        AGE
service/kubernetes           ClusterIP      10.233.0.1      <none>            443/TCP        36h
service/myweb-svc            ClusterIP      10.233.3.43     <none>            80/TCP         30s
service/myweb-svc-headless   ClusterIP      None            <none>            80/TCP         13s

NAME                                                    ENDPOINTS                                                            AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner   <none>                                                               20h
endpoints/kubernetes                                    192.168.100.100:6443                                                 36h
endpoints/myweb-svc                                     10.233.90.77:8080,10.233.90.79:8080,10.233.92.118:8080    30s
endpoints/myweb-svc-headless                            10.233.90.77:8080,10.233.90.79:8080,10.233.92.118:8080    13s

두개의 서비스 myweb-svc, myweb-svc-headless가 있다.
Headless SVC는 CLUSTER-IP값이 None이다.
두 서비스가 같은 셀렉터를 사용하고 있으므로 같은 ENDPOINTS 구성을 가지고 있다.

 

 vagrant@k8s-node1  ~/cont/sts/headless  kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.233.3.43

myweb-svc에 DNS 질의를 하면 ClusterIP 주소를 응답한다.

 

/ # host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.118
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.77
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.79

Headless service에 DNS를 질의했을 때 응답되는 ip는 누구의 ip일까 ? → 파드의 ip이다.

service의 자체 ip가 없는 것을 Headless servcie 라고 하며 Headless service를 질의하면 myweb-svc-headless.default.svc.cluster.local SVC의 ip가 응답하게 된다.

파드가 3개이기 때문에 3개의 endpoint 주소를 응답한다.

 

 vagrant@k8s-node1  ~/cont/sts/headless  kubectl scale rs myweb-rs --replicas=5                                                  
replicaset.apps/myweb-rs scaled

 vagrant@k8s-node1  ~/cont/sts/headless  kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.233.61.67
/ # 

스케일링 가능 여부와 상관없이 일반 서비스는 ClusterIP 타입의 service ip를 응답한다.

 

/ # host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.142
myweb-svc-headless.default.svc.cluster.local has address 10.233.96.115
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.139
myweb-svc-headless.default.svc.cluster.local has address 10.233.96.118
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.92

headless SVC는 5개의 파드의 ip를 응답하고 있다.

⭐️ 파드가 아닌 StatefulSet을 연결하면 FQDN이 모두 달라지고 파드에 이름이 붙는다. ⭐️

 

MySQL에 Read Replica를 구현한다고 해보자
MasterSlave가 있어야 한다.
클라이언트(일반적으로 Web app)가 데이터를 쓸 때는 마스터에게 요청해야 한다.

읽기를 할 때는 아무곳에서나 읽으면 된다.


StatefulSet을 통해 마스터와 슬레이브를 구성하게 되면 Headless SVC를 통해서 두개의 파드를 구별할 수 있게 된다.

 


 

🚀 예제 1

 vagrant@k8s-node1  ~  kubectl api-resources | grep sts
statefulsets                      sts          apps/v1                                true         StatefulSet
certificatesigningrequests        csr          certificates.k8s.io/v1                 false        CertificateSigningRequest

 vagrant@k8s-node1  ~  kubectl explain sts.spec.
   serviceName  <string> -required-

 

serviceName이 필수이다. StatefulSet은 반드시 Headless service 가 있어야 한다.

 

myweb-sts.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myweb-sts
spec:
  replicas: 3
  serviceName: myweb-svc-headless
  selector:
    matchLabels:
      app: web
      env: dev
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb
          ports:
            - containerPort: 8080
              protocol: TCP

 

myweb-svc-headless.yaml

apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-headless
spec:
  type: ClusterIP
  clusterIP: None # <-- Headless Service
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080

 

 vagrant@k8s-node1  ~/cont/sts1  kubectl get sts,po              
NAME                         READY   AGE
statefulset.apps/myweb-sts   3/3     74s

NAME                                          READY   STATUS    RESTARTS      AGE
pod/myweb-sts-0                               1/1     Running   0             74s
pod/myweb-sts-1                               1/1     Running   0             71s
pod/myweb-sts-2                               1/1     Running   0             68s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running   0             34h

기본적으로 StatefulSet에는 뒤에 서수가 붙는다.

 

 vagrant@k8s-node1  ~/cont/sts1  kubectl delete po myweb-sts-2    
pod "myweb-sts-2" deleted

 vagrant@k8s-node1  ~/cont/sts1  kubectl get sts,po           
NAME                         READY   AGE
statefulset.apps/myweb-sts   2/3     3m1s

NAME                                          READY   STATUS              RESTARTS      AGE
pod/myweb-sts-0                               1/1     Running             0             3m1s
pod/myweb-sts-1                               1/1     Running             0             2m58s
pod/myweb-sts-2                               0/1     ContainerCreating   0             2s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running             0             34h

myweb-sts-2를 지우면 종료되며 삭제되고 다시 같은 이름의 파드가 만들어진다.
myweb-sts-1을 지워도 마찬가지로 다시 같은 파드를 만들게된다.

이름이 고정적이다. → 예측 가능하다.

 vagrant@k8s-node1  ~/cont/sts1  kubectl scale sts myweb-sts --replicas 4                               
statefulset.apps/myweb-sts scaled

pod/myweb-sts-3                               1/1     Running   0             10s

다음과 같이 스케일링을 하면 예측 가능한 myweb-sts-3이 만들어진다.

스케일링을 줄여도 마찬가지다 --replicas 2로 바꾸면 myweb-sts-3, myweb-sts-2이 지워지고 지울 때도 절대로 한번에 지우지 않고 순서대로 지운다.

 

다시 --replicas 4로 변경하면 myweb-sts-2가 생성된 후에야 myweb-sts-3이 생성된다.

--replicas 0을 설정할 수도 있는데 3 2 1 0 순으로 지워진다.

 

 vagrant@k8s-node1  ~/cont/sts1  kubectl get svc,ep                      
NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kubernetes           ClusterIP   10.233.0.1      <none>        443/TCP    2d2h
service/myweb-svc-headless   ClusterIP   None            <none>        80/TCP     8m18s

NAME                                                    ENDPOINTS                                                             AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner   <none>                                                                34h
endpoints/kubernetes                                    192.168.100.100:6443                                                  2d2h
endpoints/myweb-svc-headless                            10.233.90.96:8080,10.233.92.144:8080,10.233.96.120:8080    8m18s

 vagrant@k8s-node1  ~/cont/sts1  kubectl get po -o wide                        
NAME                                      READY   STATUS    RESTARTS      AGE     IP              NODE    NOMINATED NODE   READINESS GATES
myweb-sts-0                               1/1     Running   0             9m51s   10.233.90.96    node1   <none>           <none>
myweb-sts-1                               1/1     Running   0             9m48s   10.233.96.120   node2   <none>           <none>
myweb-sts-2                               1/1     Running   0             6m52s   10.233.92.144   node3   <none>           <none>
nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running   0             34h     10.233.92.110   node3   <none>           <none>

Headless ServiceENDPOINTS와 파드의 ip가 매칭되는 것을 확인할 수 있다.

 

 vagrant@k8s-node1  ~/cont/sts1  kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.144
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.96
myweb-svc-headless.default.svc.cluster.local has address 10.233.96.12

/ # host myweb-sts-0.myweb-svc-headless
myweb-sts-0.myweb-svc-headless.default.svc.cluster.local has address 10.233.90.96

/ # host myweb-sts-0.myweb-svc-headless.default.svc.cluster.local
myweb-sts-0.myweb-svc-headless.default.svc.cluster.local has address 10.233.90.96

/ # host myweb-sts-1.myweb-svc-headless.default.svc.cluster.local
myweb-sts-1.myweb-svc-headless.default.svc.cluster.local has address 10.233.96.120

/ # host myweb-sts-2.myweb-svc-headless.default.svc.cluster.local
myweb-sts-2.myweb-svc-headless.default.svc.cluster.local has address 10.233.92.144

파드의 이름으로 쿼리를 보낼 수 있다.
FQDNmyweb-sts-0.myweb-svc-headless.default.svc.cluster.local이다. 즉, 파드를 특정할 수 있다.


파드 집합의 배포와 스케일링을 관리하며, 파드들의 순서고유성을 보장한다.
이름이 고유하므로 특정 파드를 제거하면 그 이름과 똑같은 파드가 만들어진다.

리소스마다 붙는 id도 똑같은 id로 만들어진다. id가 같다는 것은 배치되는 노드도 같다는 것이다.
어떤 하나의 리소스를 만들 때 id가 부여되면 그 id는 특정 노드에서만 실행할 수 있다.
그것이 스케줄러에 의해 고정이 되어있기 때문이다.

🚀 StatefulSet

스테이트풀셋은 다음 중 하나 또는 이상이 필요한 애플리케이션에 유용하다.

  • 안정된, 고유한 네트워크 식별자.
  • 안정된, 지속성을 갖는 스토리지.
  • 순차적인, 정상 배포(graceful deployment)와 스케일링.
  • 순차적인, 자동 롤링 업데이트.

제한사항
StatefulSet에 붙이는 스토리지는 반드시 pvc여야 한다. emptyDir, hostPath는 붙일 수 없다.

스토리지 클래스가 존재해야 하며 동적 프로비저닝이 구성되어 있어야 한다.

파드를 삭제해도 볼륨이 삭제되지 않는다. 파드가 삭제돼도 고유성이 지켜져야하기 때문에 데이터를 유지하기 위해서다.

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myweb-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: myweb
          image: httpd
          volumeMounts:
            - name: myvol
              mountPath: /usr/local/apache2/htdocs
      volumes:
        - name: myvol
          persistentVolumeClaim:
            claimName: mypvc-dynamic

다음 파일을 통해 ReplicaSet으로 3개의 파드가 같은 이미지로부터 만들어질 것이다.

즉, myvol이라는 볼륨이 있고 이 볼륨을 다 똑같이 마운팅시킨다.


이때의 각 파드는 고유성이 없다. 파드의 이름은 다르지만 각 파드가 가지고 있는 데이터가 전부 같은 데이터이다.
데이터의 입장에서는 각 파드가 전혀 다르지않다.
임의의 파드가 a라는 데이터를 쓰기 했다면 다른 파드들도 a 데이터를 갖게된다.

실제로 고유성이 있으려면 파드마다 별도의 볼륨을 가지고 있어야 한다. 그래야만 모든 파드가 고유해진다.

만약 파드가 DB App이라고 해보자
DB App은 누구에게 질의해도 같은 값을 응답해야 한다.
이런 관점에서 스토리지의 내용, 데이터를 동기화 할 것인가는 선택사항이다.

중요한 것은 StatefulSet은 각자가 고유해야 한다. 각자가 고유하기 위해서는 별도의 스토리지가 있어야 한다.

 

 vagrant@k8s-node1  ~/cont/sts1  kubectl explain sts.spec.

   volumeClaimTemplates <[]Object>

volumeClaimTemplates : pvc를 만들 때 사용할 템플릿

pvc가 만들어지면 동적 프로비저닝을 구성했을 때 pv가 따로 만들어진다.

고유한 데이터를 별도로 가질 수 있게 된다.

 vagrant@k8s-node1  ~/cont/sts1  kubectl explain sts.spec.volumeClaimTemplates

pvc를 만들기위한 FIELDS가 있다.

 

 vagrant@k8s-node1  ~/cont/sts1  kubectl explain sts.spec.volumeClaimTemplates.spec

pvcspec이다.

파드가 삭제되더라도 볼륨(pv, pvc)을 삭제하지 않는 이유는 다시 스케일링을 하거나 다시 파드가 만들어지는 경우 해당되는 고유한 데이터를 써야하기 때문이다.

STS에 의해 파드를 만들고 반드시 Headless service를 만들어서 연결시켜야 한다.

그 외의 일반 SVC를 만들어서 붙이는 것은 선택 사항이다.

🚀 예제2 : PVC 템플릿

myweb-sts-vol.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myweb-sts-vol
spec:
  replicas: 3
  serviceName: myweb-svc-headless
  selector:
    matchLabels:
      app: web
      env: dev
  template:
    metadata:
      labels:
        app: web
        env: dev
    spec:
      containers:
        - name: myweb
          image: ghcr.io/c1t1d0s7/go-myweb:alpine
          ports:
            - containerPort: 8080
              protocol: TCP
          volumeMounts:
            - name: myweb-pvc
              mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: myweb-pvc
      spec:
        accessMode:
          - ReadWriteOnce 
        resources:
          requests:
            storage: 1G
        storageClassName: nfs-client

ReplicaSet이나 Deployment의 경우 여러개의 파드가 동시에 쓰기를 해야하는 경우가 생길 수 있어서 RWX라는 것이 필요했지만, 지금 상황은 파드가 자신만의 공간을 사용하는 것이기 때문에 굳이 Many를 세팅하지 않아도 상관없다.

ReadWriteOnce면 충분하다.

 

 vagrant@k8s-node1  ~/cont/sts2  kubectl get sts,po,pv,pvc           
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   3/3     15s

NAME                                          READY   STATUS    RESTARTS   AGE
pod/myweb-sts-vol-0                           1/1     Running   0          15s
pod/myweb-sts-vol-1                           1/1     Running   0          11s
pod/myweb-sts-vol-2                           1/1     Running   0          9s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running   0          21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              9s
persistentvolume/pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              11s
persistentvolume/pvc-eb4d6346-a412-4697-b2ed-371e726c4826   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              14s

NAME                                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-eb4d6346-a412-4697-b2ed-371e726c4826   1G         RWO            nfs-client     15s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49   1G         RWO            nfs-client     11s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4   1G         RWO            nfs-client     9s
 vagrant@k8s-node1  ~/cont/sts2  kubectl scale sts myweb-sts-vol --replicas=2
statefulset.apps/myweb-sts-vol scaled

 vagrant@k8s-node1  ~/cont/sts2  kubectl get sts,po,pv,pvc                   
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   2/2     85s

NAME                                          READY   STATUS    RESTARTS   AGE
pod/myweb-sts-vol-0                           1/1     Running   0          85s
pod/myweb-sts-vol-1                           1/1     Running   0          81s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running   0          21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              79s
persistentvolume/pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              81s
persistentvolume/pvc-eb4d6346-a412-4697-b2ed-371e726c4826   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              84s

NAME                                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-eb4d6346-a412-4697-b2ed-371e726c4826   1G         RWO            nfs-client     85s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49   1G         RWO            nfs-client     81s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4   1G         RWO            nfs-client     79s

파드는3 → 2개로 줄었으나 pvpvc는 여전히 3개이다. 데이터를 보존하기위해 볼륨을 자동으로 지우지 않는다.

 

 vagrant@k8s-node1  ~/cont/sts2  kubectl scale sts myweb-sts-vol --replicas=3
statefulset.apps/myweb-sts-vol scaled

 vagrant@k8s-node1  ~/cont/sts2  kubectl get sts,po,pv,pvc                   
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   3/3     2m2s

NAME                                          READY   STATUS    RESTARTS   AGE
pod/myweb-sts-vol-0                           1/1     Running   0          2m2s
pod/myweb-sts-vol-1                           1/1     Running   0          118s
pod/myweb-sts-vol-2                           1/1     Running   0          2s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running   0          21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              116s
persistentvolume/pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              118s
persistentvolume/pvc-eb4d6346-a412-4697-b2ed-371e726c4826   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              2m1s

NAME                                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-eb4d6346-a412-4697-b2ed-371e726c4826   1G         RWO            nfs-client     2m2s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49   1G         RWO            nfs-client     118s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4   1G         RWO            nfs-client     116s

다시 --replicas=3으로 스케일링을 하면 기존의 pvc를 사용하게 된다.

myweb-sts-vol-2이 생성됐지만 기존의 pvc를 사용하므로 여전히 pvc, pv는 3개인 것을 확인할 수 있다.

 

 vagrant@k8s-node1  ~/cont/sts2  kubectl scale sts myweb-sts-vol --replicas=4

statefulset.apps/myweb-sts-vol scaled
 vagrant@k8s-node1  ~/cont/sts2  kubectl get sts,po,pv,pvc                   
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   3/4     3m

NAME                                          READY   STATUS              RESTARTS   AGE
pod/myweb-sts-vol-0                           1/1     Running             0          3m
pod/myweb-sts-vol-1                           1/1     Running             0          2m56s
pod/myweb-sts-vol-2                           1/1     Running             0          60s
pod/myweb-sts-vol-3                           0/1     ContainerCreating   0          2s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running             0          21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              2m54s
persistentvolume/pvc-270b33ca-6116-4961-8bcb-80e5b44e7517   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-3   nfs-client              2s
persistentvolume/pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              2m56s
persistentvolume/pvc-eb4d6346-a412-4697-b2ed-371e726c4826   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              2m59s

NAME                                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-eb4d6346-a412-4697-b2ed-371e726c4826   1G         RWO            nfs-client     3m
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49   1G         RWO            nfs-client     2m56s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4   1G         RWO            nfs-client     2m54s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-3   Bound    pvc-270b33ca-6116-4961-8bcb-80e5b44e7517   1G         RWO            nfs-client     2s

--replicas=4로 조정하면 pv, pvc가 4개가 된다.

 

 vagrant@k8s-node1  ~/cont/sts2  kubectl exec myweb-sts-vol-0 -it -- sh 
/ # cd /data/
/data # ls
/data # touch a b c 
/data # ls
a  b  c
/data # exit

 vagrant@k8s-node1  ~/cont/sts2  kubectl exec myweb-sts-vol-1 -it -- sh
/ # cd /data/
/data # ls
/data # touch x y z
/data # ls
x  y  z
/data # exit

myweb-sts-vol-1에서는 myweb-sts-vol-0에서 사용하던 데이터를 볼 수 없다.

 

 vagrant@k8s-node1  ~/cont/sts2  kubectl exec myweb-sts-vol-2 -it -- sh
/ # cd /data/
/data # ls
/data # exit

마찬가지로 myweb-sts-vol-0, myweb-sts-vol-1에서 사용하던 데이터를 볼 수 없다.

 

 vagrant@k8s-node1  ~/cont/sts2  kubectl delete po myweb-sts-vol-1     
pod "myweb-sts-vol-1" deleted

 vagrant@k8s-node1  ~/cont/sts2  kubectl get po                   
NAME                                      READY   STATUS    RESTARTS   AGE
myweb-sts-vol-0                           1/1     Running   0          7m10s
myweb-sts-vol-1                           1/1     Running   0          4s
myweb-sts-vol-2                           1/1     Running   0          5m10s
myweb-sts-vol-3                           1/1     Running   0          4m12s
nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running   0          21h

 vagrant@k8s-node1  ~/cont/sts2  kubectl exec myweb-sts-vol-1 -it -- sh
/ # cd /data/
/data # ls
x  y  z

myweb-sts-vol-1 파드를 지우고 새롭게 만들어진 같은 이름의 파드는 앞서 만들어둔 데이터를 볼 수 있다.

즉, 고유성을 보장할 수 있다.

🚀 예제3 : One Master Multi Slave

STS를 통해 MySQL 노드를 생성한다. Master는 R/W가 가능하고 나머지는 다 Slaver로 Read Only이다.
이러한 구성을 One Master Multi Slave라고 한다.
https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/
다음 문서를 참고하여 구성한다.

 vagrant@k8s-node1  ~/cont/sts3  kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
configmap/mysql created

 vagrant@k8s-node1  ~/cont/sts3  kubectl get cm                                                                 
NAME               DATA   AGE
kube-root-ca.crt   1      8d
mysql              2      4s

configMap을 생성한다. mysql이라는 이름으로 생성됐다.

 

 vagrant@k8s-node1  ~/cont/sts3  kubectl describe cm mysql           
Name:         mysql
Namespace:    default
Labels:       app=mysql
Annotations:  <none>

Data
====
primary.cnf:
----
# Apply this config only on the primary.
[mysqld]
log-bin

replica.cnf:
----
# Apply this config only on replicas.
[mysqld]
super-read-only


BinaryData
====

Events:  <none>

다음과 같이 두개의 데이터가 들어있는 것을 확인할 수 있다.

# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the primary: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql

mysql, mysql-read라는 SVC를 생성한다. ClusterIP값이 None이므로 Headless service를 의미한다.

mysql-readClusterIP를 설정하지 않았으므로 일반 SVC이다.


타입을 지정하지 않았기 때문에 두 SVC모두 ClusterIP 타입이며 두개의 SVC를 만들고 같은 셀렉터를 사용하는 방식이다. 즉, 같은 파드를 가리킨다.

 

Headless service를 만드는 이유는 StatefulSet을 만들기 때문이며 StatefulSetMasterSlave를 구분하기 위해 Headless service를 사용한다.

 

읽기 작업은 MasterSlave 모두에서 가능하므로 mysql-read는 별도의 Headless service를 사용하지 않는다.

 

 vagrant@k8s-node1  ~/cont/sts3  kubectl get svc   
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.233.0.1      <none>        443/TCP    2d19h
mysql        ClusterIP   None            <none>        3306/TCP   29h
mysql-read   ClusterIP   10.233.11.154   <none>        3306/TCP   29h

mysqlCLUSTER-IPNone인 것을 확인할 수 있다.

 

 vagrant@k8s-node1  ~/cont/sts3  kubectl get svc,ep
NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   10.233.0.1      <none>        443/TCP    2d19h
service/mysql        ClusterIP   None            <none>        3306/TCP   29h
service/mysql-read   ClusterIP   10.233.11.154   <none>        3306/TCP   29h

NAME                                                    ENDPOINTS                               AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner   <none>                                  2d3h
endpoints/kubernetes                                    192.168.100.100:6443                    2d19h
endpoints/mysql                                         10.233.92.127:3306,10.233.96.100:3306   29h
endpoints/mysql-read                                    10.233.92.127:3306,10.233.96.100:3306   29h

아직은 STS을 만들지 않아서 없다.

 vagrant@k8s-node1  ~/cont/sts3  kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
statefulset.apps/mysql created

이제 STS을 만든다. 이제 구성은 끝났다.

⭐️ 공식 문서와 다름 주의 !!

 vagrant@k8s-node1  ~/cont/sts3  wget https://k8s.io/examples/application/mysql/mysql-configmap.yaml

공식문서의 명령을 실행하는 대신 wget명령으로 mysql-configmap.yaml파일을 받는다.

 

mysql-configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  labels:
    app: mysql
data:
  primary.cnf: |
    # Apply this config only on the primary.
    [mysqld]
    log-bin
  replica.cnf: |
    # Apply this config only on replicas.
    [mysqld]
    super-read-only

기존 YAML 파일에서 디렉토리 경로가 잘못되었으므로 datadir=/var/lib/mysql/mysql 전체 라인을 삭제하거나
datadir=/var/lib/mysql로 수정한다.

 

 vagrant@k8s-node1  ~/cont/sts3  kubectl get sts,po,pv,pvc,svc,ep
NAME                         READY   AGE
statefulset.apps/mysql       2/2     90s
statefulset.apps/myweb-sts   3/3     17h

NAME                                          READY   STATUS    RESTARTS      AGE
pod/mysql-0                                   2/2     Running   0             90s
pod/mysql-1                                   2/2     Running   1 (34s ago)   49s
pod/myweb-sts-0                               1/1     Running   0             17h
pod/myweb-sts-1                               1/1     Running   0             17h
pod/myweb-sts-2                               1/1     Running   0             17h
pod/nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running   0             2d3h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                  STORAGECLASS   REASON   AGE
persistentvolume/pvc-4a3ed202-ec2c-43bc-b3d0-c8cf56d3ee2c   10Gi       RWO            Delete           Bound    default/data-mysql-1   nfs-client              49s
persistentvolume/pvc-bf8333bc-491d-4ef3-8f96-36ef648cfbf7   10Gi       RWO            Delete           Bound    default/data-mysql-0   nfs-client              90s

NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/data-mysql-0   Bound    pvc-bf8333bc-491d-4ef3-8f96-36ef648cfbf7   10Gi       RWO            nfs-client     90s
persistentvolumeclaim/data-mysql-1   Bound    pvc-4a3ed202-ec2c-43bc-b3d0-c8cf56d3ee2c   10Gi       RWO            nfs-client     49s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   10.233.0.1      <none>        443/TCP    2d19h
service/mysql        ClusterIP   None            <none>        3306/TCP   29h
service/mysql-read   ClusterIP   10.233.11.154   <none>        3306/TCP   29h

NAME                                                    ENDPOINTS                               AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner   <none>                                  2d3h
endpoints/kubernetes                                    192.168.100.100:6443                    2d19h
endpoints/mysql                                         10.233.92.146:3306,10.233.96.125:3306   29h
endpoints/mysql-read                                    10.233.92.146:3306,10.233.96.125:3306   29h

다음과 같이 생성된 리소스들을 확인한다.

ClusterIP 타입이므로 접속을 위해서는 컨테이너를 띄워야 한다.

 

 vagrant@k8s-node1  ~/cont/sts3  kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # host mysql
mysql.default.svc.cluster.local has address 10.233.92.127
mysql.default.svc.cluster.local has address 10.233.96.100
/ # host mysql-read
mysql-read.default.svc.cluster.local has address 10.233.11.154
/ # 
/ # mysql -h mysql-0.mysql -u root
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 51
Server version: 5.7.38-log MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sys                    |
| xtrabackup_backupfiles |
+------------------------+
5 rows in set (0.010 sec)

MySQL [(none)]> create database sohui;
Query OK, 1 row affected (0.005 sec)

MySQL [(none)]> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sohui                  |
| sys                    |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.002 sec)

MySQL [(none)]> exit
Bye
/ # mysql -h mysql-1.mysql -u root
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 88
Server version: 5.7.38 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sohui                  |
| sys                    |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.007 sec)

MySQL [(none)]> drop database sohui;
ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option so it cannot execute this statement

MySQL [(none)]> exit
Bye

두번째 파드로 진입해서 데이터베이스를 확인하면 첫번째 파드에서 만든 sohui 데이터베이스를 확인할 수 있다.
두번째 파드에서는 drop database를 할 수 없다. 해당 파드는 쓰기 작업을 할 수 없기 때문이다.

 

/ # mysql -h mysql-0.mysql -u root
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 125
Server version: 5.7.38-log MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sohui                  |
| sys                    |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.006 sec)

MySQL [(none)]> drop database sohui;
Query OK, 0 rows affected (0.009 sec)

MySQL [(none)]> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| sys                    |
| xtrabackup_backupfiles |
+------------------------+
5 rows in set (0.002 sec)

MySQL [(none)]> exit
Bye

다시 첫번째 파드에서 sohui데이터베이스를 drop하면 실행되며 두번째 파드에서 데이터베이스를 봐도 해당 데이터베이스가 삭제된 것을 확인할 수 있다.

 

확장

 vagrant@k8s-node1  ~/cont/sts3  kubectl scale sts mysql --replicas 3
statefulset.apps/mysql scaled
 vagrant@k8s-node1  ~/cont/sts3  kubectl get po                      
NAME                                      READY   STATUS     RESTARTS      AGE
mysql-0                                   2/2     Running    0             17m
mysql-1                                   2/2     Running    1 (16m ago)   16m
mysql-2                                   0/2     Init:1/2   0             4s
nfs-client-provisioner-758f8cd4d6-wpjbt   1/1     Running    0             2d3h

복제본 개수를 3개로 확장시켜본다.

 vagrant@k8s-node1  ~/cont/sts3  kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # mysql -h mysql-2.mysql -u root -e 'select * from sohui.message;'
+-------------+
| message     |
+-------------+
| hello mysql |
+-------------+
/ # 

컨테이너를 띄우고 방금 생성한 mysql-2에 접속해본다. 
mysql-2에도 동기화가 완료되어 테이블과 데이터가 만들어진 것을 확인할 수 있다.
아무런 작업도 하지 않은 mysql-2에서도 mysql-0, mysql-1과 동일한 데이터를 사용할 수 있다.
즉, 스케일링을 하게되면 만들어지자마자 바로 동기화가 된다.

 

🚀 리소스 정리

 vagrant@k8s-node1  ~/cont/sts2  kubectl delete -f .                   
statefulset.apps "myweb-sts-vol" deleted
service "myweb-svc-headless" deleted

 vagrant@k8s-node1  ~/cont/sts2  kubectl delete pvc --all         
persistentvolumeclaim "myweb-pvc-myweb-sts-vol-0" deleted
persistentvolumeclaim "myweb-pvc-myweb-sts-vol-1" deleted
persistentvolumeclaim "myweb-pvc-myweb-sts-vol-2" deleted
persistentvolumeclaim "myweb-pvc-myweb-sts-vol-3" deleted
 vagrant@k8s-node1  ~/cont/sts2  kubectl get pv,pvc      
No resources found
728x90