[kubernetes] statefulSet

728x90

Headless Service

headless 서비스는 Cluster IP 가 none 이다. 즉, 서비스의 IP 가 없는 서비스를 Headless 서비스라고 부른다.

myweb-rs.yml

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.yml

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

myweb-svc-headless.yml

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

test

kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
> host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.233.58.177

> host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.81.32
myweb-svc-headless.default.svc.cluster.local has address 10.233.76.27
myweb-svc-headless.default.svc.cluster.local has address 10.233.111.28

host myweb-svc-headless 를 실행하면 myweb-svc-headless 의 서비스 주소가 none 이기 때문에 연결된 Pod 의 IP 주소가 나온다.

스테이트풀셋

공식 문서: https://kubernetes.io/ko/docs/concepts/workloads/controllers/statefulset/

pet vs cattle: http://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/

pet 은 고유성, 상태를 가지고 있고 교체 하는 것이 상당히 어렵다. 그에 반해 cattle 고유성이랄게 없고 교체가 아주 쉽고 간단하다.

대표적인 pet 은 RDBMS 가 있다.

스테이트풀셋은 애플리케이션의 스테이트풀을 관리하는데 사용하는 워크로드 API 오브젝트이다.

파드 집합의 디플로이먼트와 스케일링을 관리하며, 파드들의 순서 및 고유성을 보장한다 .

디플로이먼트와 유사하게, 스테이트풀셋은 동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다. 디플로이먼트와는 다르게, 스테이트풀셋은 각 파드의 독자성을 유지한다. 이 파드들은 동일한 스팩으로 생성되었지만, 서로 교체는 불가능하다. 다시 말해, 각각은 재스케줄링 간에도 지속적으로 유지되는 식별자를 가진다.

스토리지 볼륨을 사용해서 워크로드에 지속성을 제공하려는 경우, 솔루션의 일부로 스테이트풀셋을 사용할 수 있다. 스테이트풀셋의 개별 파드는 장애에 취약하지만, 퍼시스턴트 파드 식별자는 기존 볼륨을 실패한 볼륨을 대체하는 새 파드에 더 쉽게 일치시킬 수 있다.

스테이트풀셋 사용

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

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

위의 안정은 파드의 (재)스케줄링 전반에 걸친 지속성과 같은 의미이다. 만약 애플리케이션이 안정적인 식별자 또는 순차적인 배포, 삭제 또는 스케일링이 필요하지 않으면, 스테이트리스 레플리카셋(ReplicaSet)을 제공하는 워크로드 오브젝트를 사용해서 애플리케이션을 배포해야 한다. 디플로이먼트 또는 레플리카셋과 같은 컨트롤러가 스테이트리스 요구에 더 적합할 수 있다.

제한사항

  • 파드에 지정된 스토리지는 관리자에 의해 **퍼시스턴트 볼륨 프로비저너(PVC)**를 기반으로 하는 storage class 를 요청해서 프로비전하거나 사전에 프로비전이 되어야 한다. STS 의 파드들은 각자가 고유성을 가져야 하기 때문에 각각의 파드들은 자신만의 볼륨을 가져야 한다. (.spec.volumeClaimTemplates 사용)
  • 스테이트풀셋을 삭제 또는 스케일 다운해도 스테이트풀셋과 연관된 볼륨이 삭제되지 않는다. 이는 일반적으로 스테이트풀셋과 연관된 모든 리소스를 자동으로 제거하는 것보다 더 중요한 데이터의 안전을 보장하기 위함이다.
  • 스테이트풀셋은 현재 파드의 네트워크 신원을 책임지고 있는 헤드리스 서비스가 필요하다. 사용자가 이 서비스를 생성할 책임이 있다.
  • 스테이트풀셋은 스테이트풀셋의 삭제 시 파드의 종료에 대해 어떠한 보증을 제공하지 않는다. 스테이트풀셋에서는 파드가 순차적이고 정상적으로 종료(graceful termination)되도록 하려면, 삭제 전 스테이트풀셋의 스케일을 0으로 축소할 수 있다.
  • 롤링 업데이트와 기본 파드 매니지먼트 폴리시 (OrderedReady)를 함께 사용시 복구를 위한 수동 개입이 필요한 파손 상태로 빠질 수 있다.

구성요소

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:
  selector:
    matchLabels:
      app: nginx # .spec.template.metadata.labels 와 일치해야 한다
  serviceName: "nginx"
  replicas: 3 # 기본값은 1
  minReadySeconds: 10 # 기본값은 0
  template:
    metadata:
      labels:
        app: nginx # .spec.selector.matchLabels 와 일치해야 한다
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: k8s.gcr.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" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

안정적인 네트워크 신원

스테이트풀셋의 각 파드는 스테이트풀셋의 이름과 파드의 순번에서 호스트 이름을 얻는다. 호스트 이름을 구성하는 패턴은 $(statefulset name)-$(ordinal) 이다. 위의 예시에서 생성된 3개 파드의 이름은 web-0,web-1,web-2 이다.

스테이트풀셋은 스테이트풀셋에 있는 파드의 도메인을 제어하기위해 헤드리스 서비스를 사용할 수 있다. 이 서비스가 관리하는 도메인은 $(service name).$(namespace).svc.cluster.local 의 형식을 가지며, 여기서 "cluster.local"은 클러스터 도메인이다. 각 파드는 생성되면 $(podname).$(governing service domain) 형식을 가지고 일치되는 DNS 서브도메인을 가지며, 여기서 거버닝 서비스(governing service)는 스테이트풀셋의 serviceName 필드에 의해 정의된다.

클러스터에서 DNS가 구성된 방식에 따라, 새로 실행된 파드의 DNS 이름을 즉시 찾지 못할 수 있다. 이 동작은 클러스터의 다른 클라이언트가 파드가 생성되기 전에 파드의 호스트 이름에 대한 쿼리를 이미 보낸 경우에 발생할 수 있다. 네거티브 캐싱(DNS에서 일반적)은 이전에 실패한 조회 결과가 파드가 실행된 후에도 적어도 몇 초 동안 기억되고 재사용됨을 의미한다.

파드를 생성한 후 즉시 파드를 검색해야 하는 경우, 몇 가지 옵션이 있다.

  • DNS 조회에 의존하지 않고 쿠버네티스 API를 직접(예를 들어 watch 사용) 쿼리한다.
  • 쿠버네티스 DNS 공급자의 캐싱 시간(일반적으로 CoreDNS의 컨피그맵을 편집하는 것을 의미하며, 현재 30초 동안 캐시함)을 줄인다.

제한사항 섹션에서 언급한 것처럼 사용자는 파드의 네트워크 신원을 책임지는 헤드리스 서비스를 생성할 책임이 있다.

여기 클러스터 도메인, 서비스 이름, 스테이트풀셋 이름을 선택을 하고, 그 선택이 스테이트풀셋 파드의 DNS이름에 어떻게 영향을 주는지에 대한 약간의 예시가 있다.

예제 1

myweb-sts.yml

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

apiVersion: v1
kind: Service
metadata:
  name: myweb-svc-headless
spec:
  type: ClusterIP
  clusterIP: None # <-- Headless Service
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
$ kubectl get sts,po                              
NAME                         READY   AGE
statefulset.apps/myweb-sts   3/3     7m

NAME                                          READY   STATUS    RESTARTS       AGE
pod/myweb-sts-0                               1/1     Running   0              7m
pod/myweb-sts-1                               1/1     Running   0              6m57s
pod/myweb-sts-2                               1/1     Running   0              3m50s
pod/nettool                                   1/1     Running   0              2m2s
pod/nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (141m ago)   21h

파드를 특정해서 해당 파드의 IP 주소를 가져올 수 있다.

kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm

> host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.111.30
myweb-svc-headless.default.svc.cluster.local has address 10.233.76.32
myweb-svc-headless.default.svc.cluster.local has address 10.233.81.35

> host myweb-sts-0.myweb-svc-headless
myweb-sts-0.myweb-svc-headless.default.svc.cluster.local has address 10.233.76.32

> host myweb-sts-1.myweb-svc-headless
myweb-sts-1.myweb-svc-headless.default.svc.cluster.local has address 10.233.111.30

> host myweb-sts-2.myweb-svc-headless
myweb-sts-1.myweb-svc-headless.default.svc.cluster.local has address 10.233.111.35

예제 2: PVC 템플릿 사용

myweb-svc-headless.yml

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

myweb-sts-vol.yml

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:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1G
        storageClassName: nfs-client

test

kubectl create -f .
$ kubectl get sts,po,pv,pvc
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   3/3     25s

NAME                                          READY   STATUS    RESTARTS       AGE
pod/myweb-sts-vol-0                           1/1     Running   0              25s
pod/myweb-sts-vol-1                           1/1     Running   0              22s
pod/myweb-sts-vol-2                           1/1     Running   0              19s
pod/nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (164m ago)   21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              22s
persistentvolume/pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              25s
persistentvolume/pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              19s

NAME                                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            nfs-client     25s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            nfs-client     22s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            nfs-client     19s
kubectl scale sts myweb-sts-vol --replicas=2
$ kubectl get sts,po,pv,pvc
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   2/2     82s

NAME                                          READY   STATUS    RESTARTS       AGE
pod/myweb-sts-vol-0                           1/1     Running   0              82s
pod/myweb-sts-vol-1                           1/1     Running   0              79s
pod/nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (165m ago)   21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              79s
persistentvolume/pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              82s
persistentvolume/pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              76s

NAME                                              STATUS   VOLUME
      CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            nfs-client     82s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            nfs-client     79s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            nfs-client     76s
$ kubectl scale sts myweb-sts-vol --replicas=4
$ kubectl get sts,po,pv,pvc
NAME                             READY   AGE
statefulset.apps/myweb-sts-vol   4/4     2m27s

NAME                                          READY   STATUS    RESTARTS       AGE
pod/myweb-sts-vol-0                           1/1     Running   0              2m27s
pod/myweb-sts-vol-1                           1/1     Running   0              2m24s
pod/myweb-sts-vol-2                           1/1     Running   0              26s
pod/myweb-sts-vol-3                           1/1     Running   0              24s
pod/nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (166m ago)   21h

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-1   nfs-client              2m24s
persistentvolume/pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-0   nfs-client              2m27s
persistentvolume/pvc-542ab02f-acaa-4de3-a28f-fdfeb2b6c82d   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-3   nfs-client              24s
persistentvolume/pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            Delete           Bound    default/myweb-pvc-myweb-sts-vol-2   nfs-client              2m21s

NAME                                              STATUS   VOLUME
      CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0   Bound    pvc-4d5e0960-f9c1-4ed9-bcb8-62482eb9dd84   1G         RWO            nfs-client     2m27s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1   Bound    pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1G         RWO            nfs-client     2m24s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2   Bound    pvc-a3ace980-ef11-4cc9-bce8-a6a49197bd30   1G         RWO            nfs-client     2m21s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-3   Bound    pvc-542ab02f-acaa-4de3-a28f-fdfeb2b6c82d   1G         RWO            nfs-client     24s

각각의 파드들은 고유한 volume 을 가지기 때문에 볼륨의 타입이 nfs 이어도 상관 없이 데이터를 공유하지 않는다.

$ kubectl exec -it myweb-sts-vol-0 -- sh
> cd data/
/data > ls
/data > touch a b c
/data > ls
a  b  c
/data > exit
$ kubectl exec -it myweb-sts-vol-1 -- sh
> cd data/
/data > ls

pod 가 삭제되더라도 volume 은 반영구적으로 남아있다.

$ kubectl delete pod myweb-sts-vol-0
pod "myweb-sts-vol-1" deleted

$ kubectl get po
NAME                                      READY   STATUS    RESTARTS       AGE
myweb-sts-vol-0                           1/1     Running   0              2s
myweb-sts-vol-1                           1/1     Running   0              6m52s
myweb-sts-vol-2                           1/1     Running   0              6m58s
myweb-sts-vol-3                           1/1     Running   0              6m56s
nfs-client-provisioner-758f8cd4d6-rt2t8   1/1     Running   1 (173m ago)   21h

$ kubectl exec -it myweb-sts-vol-0 -- sh
/ > cd data/
/data > ls
a  b  c

예제 3

공식 문서: https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/

statefulse 으로 mysql 파드를 만든다. Master(RW) 와 Read Replicas 들로 구성한다.

ConfigMap

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

Service

# 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 # Headless 서비스
  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

statefulSet

command 필드를 사용해서 ENTRYPOINT 를 변경한다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Generate mysql server-id from pod ordinal index.
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          # Add an offset to avoid reserved server-id=0 value.
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # Copy appropriate conf.d files from config-map to emptyDir.
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/primary.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/replica.cnf /mnt/conf.d/
          fi          
        volumeMounts:
        - name: conf
          mountPath: /mnt/conf.d
        - name: config-map
          mountPath: /mnt/config-map
      - name: clone-mysql
        image: gcr.io/google-samples/xtrabackup:1.0
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Skip the clone if data already exists.
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Skip the clone on primary (ordinal index 0).
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          # Clone data from previous peer.
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # Prepare the backup.
          xtrabackup --prepare --target-dir=/var/lib/mysql          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
      containers:
      - name: mysql
        image: mysql:5.7
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 500m
            memory: 1Gi
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            # Check we can execute queries over TCP (skip-networking is off).
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 2
          timeoutSeconds: 1
      - name: xtrabackup
        image: gcr.io/google-samples/xtrabackup:1.0
        ports:
        - name: xtrabackup
          containerPort: 3307
        command:
        - bash
        - "-c"
        - |
          set -ex
          cd /var/lib/mysql

          # Determine binlog position of cloned data, if any.
          if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
            # XtraBackup already generated a partial "CHANGE MASTER TO" query
            # because we're cloning from an existing replica. (Need to remove the tailing semicolon!)
            cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f xtrabackup_slave_info xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # We're cloning directly from primary. Parse binlog position.
            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm -f xtrabackup_binlog_info xtrabackup_slave_info
            echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\\
                  MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
          fi

          # Check if we need to complete a clone by starting replication.
          if [[ -f change_master_to.sql.in ]]; then
            echo "Waiting for mysqld to be ready (accepting connections)"
            until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done

            echo "Initializing replication from clone position"
            mysql -h 127.0.0.1 \\
                  -e "$(<change_master_to.sql.in), \\
                          MASTER_HOST='mysql-0.mysql', \\
                          MASTER_USER='root', \\
                          MASTER_PASSWORD='', \\
                          MASTER_CONNECT_RETRY=10; \\
                        START SLAVE;" || exit 1
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
          fi

          # Start a server to send backups when requested by peers.
          exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \\
            "xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        - name: conf
          mountPath: /etc/mysql/conf.d
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

test

$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm

mysql-0 은 마스터로써 읽기와 쓰기가 모두 가능하고 mysql-1 은 read replica 이다.

/ > mysql -h mysql-0.mysql -u root

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

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

MySQL [(none)]> use test;

MySQL [test]> create table message (message VARCHAR(50));
Query OK, 0 rows affected (0.032 sec)

MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| message        |
+----------------+
1 row in set (0.004 sec)

MySQL [test]> insert into message values ("hello mysql");
Query OK, 1 row affected (0.017 sec)

MySQL [test]> select * from message;
+-------------+
| message     |
+-------------+
| hello mysql |
+-------------+
1 row in set (0.001 sec)

MySQL [(none)]> exit
/ > mysql -h mysql-1.mysql -u root

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

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

MySQL [(none)]> use test;

MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| message        |
+----------------+
1 row in set (0.004 sec)

MySQL [test]> select * from message;
+-------------+
| message     |
+-------------+
| hello mysql |
+-------------+
1 row in set (0.001 sec)

MySQL [(test)]> exit
728x90