이쿠의 슬기로운 개발생활

함께 성장하기 위한 보안 개발자 EverNote 내용 공유

클라우드/Kubernetes

53. Kubernetes StatefulSet Controller

이쿠우우 2020. 12. 3. 13:48
반응형

 

 

 

 

 

 

Kubernetes StatefulSet Controller

 


[kubernetes volume 관련 글 목록]
Kubernetes Volume Object 개념
Kubernetes Volume [Static Provisioning]

Kubernetes Volume [Dynamic Provisioning]

Kubernetes StatefulSet Controller


목적

StatefulSet 이해하고 

mysql container 배포에 사용해봄

 


 

StatefulSet이란?

 

일반적으로 Container는 Stateless한 상태로 동작함.

Stateless란 어떤 이유로 컨테이너가 죽었을때 현재까지의 데이터가 사라진다는 것으로 

이러한 특징은 Web, Was 등의 Container에서는 아무 문제가 되지 않음.

하지만 앱의 특성에 따라서 Container가 죽더라도 데이터가 사라지면 안되고 보존되어야 하는 경우가 있음

예를들어 MySQL과 같은 DataBase Container의 경우는 내려가거나 재시작했다고 해서 데이터가 사라지면 안됨.

 

하지만 kubernete pod의 경우 ReplicaSet, Deployment Controller에 의해 

수시로 재기동되고 새로 생성, 제거 되기도 하는 특징을 가지고 있음.

즉 Stateless한 application에 특화되어있음.

 

하지만 MySQL, MariaDB, RDBMS DataBase 등 StateFul한 특성을 가진 Container는 

이러한 ReplicaSet, Deployment와 같은 controller로 관리하게되면 Data가 유실, 손상될 수 있음으로

StateFull Application Container를 관리할 수 있는 Controller가 필요함

 

이때 사용할 수 있는것이 kubernetes 에서 제공하고 있는 StatefullSet Controller임.

 


 

 

Kubernetes StatefulSet 구성

 

 

StatefulSet를 구축하기 위해서는 

Volume Object의 Dynamic Provisioning를 사용해야함.

 

 


 

글을 읽는데 필요한 기초 개념

 

Kubernetes Volume Object

Volume Object 중 Dynamic Provisioning에 대한 이해가 필요함.

읽어봐야할 글 : Kubernetes Volume 개념

 

 


 

 

Test 환경

 

on-premise 환경에서 진행.

 

Volume Object Dynamic Provisioning 환경을 구축해야하는데

public cloud GCP, AWS, Azure의 Storage 를 사용하면 되지만

test 환경에서는 NFS Server를 구축해서 

NFS Provisioner를 통해서 Dynamic Provisining 환경을 구성할 것임.

 

NFS Provisioner Pod가 Dynamic 방식으로 PV를 생성해주는 방식을 사용.

 

 

[Master Node server]

OS = CentOS 7

리눅스 커널 버전 : Linux 3.10.0-1062.el7.x86_64

docker version : 1.13.1

docker api verison : 1.26

 

[Worker Node server]

OS = CentOS 7

리눅스 커널 버전 : Linux 3.10.0-1062.el7.x86_64

docker version : 1.13.1

api verison : 1.26

 

[NFS  server]

OS = CentOS 7

리눅스 커널 버전 : Linux 3.10.0-1062.el7.x86_64

IP = 10.0.2.5

 

[kubernetes version]

1.19.2

 

 

 

 


 

 

 

1. NFS Server 구성

 

Dynamic Provisioning 구성하기에 앞서

Volume 종류 중 외부 디스크 (Network Stroage)를 사용하는 방법 중 하나인

NFS를 구성해놓아야함.

 

NFS란?

네트워크 상에서 다른 컴퓨터의 파일 시스템을 마운트해서 공유하는 것을 의미.

다른 컴퓨터의 파일 시스템을 사용할 수 있음

Network Attached Storage

1:N 지원

 

NFS 서버 구성

1. NFS서버 패키지 설치

yum install -y portmap nfs-utils libssapi

 

2. NFS서버 exports 설정

mkdir /k8sNFS

vi /etc/exports

 

NFS 서버의 특정 IP 호스트 접속을 허용하는 설정

/k8sNFS  아이피(rw,sync,no_root_squash)

주의 옵션에 띄워쓰기 없어야함

 

[괄호에 들어가는 옵션 설명 참고]

rw : 읽기, 쓰기 가능

ro : 읽기만 가능

secure : 클라이언트 마운트 요청시 포트를 1024 이하로 함

noaccess : 액세스 거부

root_squach : 클라이언트의 root가 서버의 root권한을 획득하는 것을 막음

no_root_squash : 클라이언트의 root와 서버의 root를 동일하게 함

sync : 파일 시스템이 변경되면 즉시 동기화.

all_squach : root를 제외하고 서버와 클라이언트의 사용자를 동일한 권한으로 설정.

no_all_squach : root를 제외하고 서버와 클라이언트의 사용자들을 하나의 권한을 가지도록 설정.

 

 

3. NFS 실행

systemctl start nfs

systemctl start rpcbind

 

4. NFS 공유 디렉토리 정상동작 확인

exportfs -v

 

 

 

NFS 클라이언트 구성

 

client에서 진행

 

1. NFS 패키지 설치

yum install -y nfs-utils

 

2. mount 후 정상동작 확인

mount -t nfs 10.0.2.5:/k8sNFS /kubeYamlTest/volume_test/k8sNfsMount

df -h

정상동작 확인 후 mount 해제

umount -t nfs 10.0.2.5:/k8sNFS /kubeYamlTest/volume_test/k8sNfsMount

 

 

 

2. Service Account 생성.

 

NFS Provisioner Pod가  kubernetes cluster에 PV를 배포할 수 있는 권한이 필요함.

PV를 배포할 수 있는 ClusterRole, Role을 가진 Service Account 를 생성함.

해당 SA는 이후 NFS Provisioner Deployment에서 사용할 것임.

 

[sa.yaml]

kind: ServiceAccount
apiVersion: v1
metadata:
  name: nfs-pod-provisioner-sa




---




kind: ClusterRole # Role of kubernetes
apiVersion: rbac.authorization.k8s.io/v1 
metadata:
  name: nfs-provisioner-clusterRole
rules:
  - apiGroups: [""] # rules on persistentvolumes
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]




---




kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-provisioner-rolebinding
subjects:
  - kind: ServiceAccount
    name: nfs-pod-provisioner-sa
    namespace: default
roleRef: # binding cluster role to service account
  kind: ClusterRole
  name: nfs-provisioner-clusterRole # name defined in clusterRole
  apiGroup: rbac.authorization.k8s.io




---




kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-pod-provisioner-otherRoles
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]




---




kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-pod-provisioner-otherRoles
subjects:
  - kind: ServiceAccount
    name: nfs-pod-provisioner-sa # same as top of the file
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: nfs-pod-provisioner-otherRoles
  apiGroup: rbac.authorization.k8s.io

 

 

 

 

 

3. NFS Provisoner Deployment 배포.

 

NFS Server를 Dynamic Provisioning으로 사용할 수 있도록 해주는

NFS Provisioner Pod를 Deployment로 배포함.

 

[ provisioner.yaml]

kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-pod-provisioner
spec:
  selector:
    matchLabels:
      app: nfs-pod-provisioner
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-pod-provisioner
    spec:
      serviceAccountName: nfs-pod-provisioner-sa # name of service account
      containers:
        - name: nfs-pod-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-provisioner
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME # do not change
              value: iksoon-nfs-test # SAME AS PROVISIONER NAME VALUE IN STORAGECLASS
            - name: NFS_SERVER # do not change
              value: 10.0.2.5 # Ip of the NFS SERVER
            - name: NFS_PATH # do not change
              value: /k8sNFS  # path to nfs directory setup
      volumes:
       - name: nfs-provisioner # same as volumemouts name
         nfs:
           server: 10.0.2.5
           path: /k8sNFS

생성 결과

 

 

 

 

4. StorageClass 생성.

 

Dynamic Provisioning을 위한 StorageClass 생성.

 

[ storageClass.yaml ]

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: iksoon-nfs-storageclass  
provisioner: iksoon-nfs-test 
parameters:
  archiveOnDelete: "false"

생성 결과

 

 

 

 

 

5. MySQL Pods StatefulSet 배포

 

원래는 Pod에 Volume Object를 mount하기 위해서 PVC를 명시해주고

해당 PVC와 StorageClass를 매칭 시켜줘야하지만

StatefulSet에서는 PVC를 따로 생성해주지 않고

StatefulSet설정에서 volumeClaimTemplates를 통해

직접 PVC를 생성함.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql-statefulset
spec:
  serviceName: mysql-service
  replicas: 3
  selector:
    matchLabels:
      app: mysql-pod
  template:
    metadata:
      labels:
        app: mysql-pod
    spec:
      containers:
      - name: mysql
        image: peksoon/iksoon_mysql:1.0.2
        ports:
        - containerPort: 3306
        volumeMounts:
        - name: pvc-test
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: pvc-test
    spec:
      storageClassName: iksoon-nfs-storageclass  
      accessModes: [ "ReadWriteMany" ] 
      resources:
        requests:
          storage: 1Gi

 

[ serviceName: mysql-service 설정 설명 (공식사이트 참고함)]

스테이트풀셋의 각 파드는 스테이트풀셋의 이름과 파드의 순번에서 호스트 이름을 얻는다. 

호스트 이름을 구성하는 패턴은 $(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 필드에 의해 정의된다.

 

 

 

 

 

 


 

 

6. StatefulSet 동작 확인

 

StateFulSet 생성 결과

 

StatefulSet의 경우 다수의 Pod가 동시에 생성되지 않고 

0번 부터 순서대로 생성됨.

만약 동시에 Pod를 생성하고자 하면 podManagementPolicy: Parallel 

설정을 하면 됨.

 

또한 Deployment와는 다르게 생성되는 Pod name에 "-0~?" 와 같이 숫자가 붙음

 

 

PV,PVC 상태 확인

생성된 3개에 Pod에 맞춰서 자동으로 PV와 PVC가 생성됨.

 

 

NFS Server상태 확인

확인 했던 PV 설정과 같이 3개의 directory가 생성되어있음.

 

 

Mysql Pod에 Data생성하고 replicaSet으로 생성된 Pod에 동일하게 생성되는지 확인

 

생성된 Pod 중 0번 pod로 들어가서 

아래의 query를 통해서 data 생성

 

[ sample_db_query ]

create database testdb;
use testdb;
create table test(name varchar(10), testdata varchar(50) );
insert into test values('iksoon', 'docker example test');

 

 

[0번 Pod에 생성]

 

 

[1번 Pod  확인]

 

0번 Pod에는 testdb를 생성했었는데

1,2 Pod에 해당 data는 생성되어있지 않음.

 

 

결과

즉 각 pod마다 고유의 PV를 사용하고 있음.

 

일반적으로 StatefulSet으로 MySQL을 생성한다면

이중화와같은 Backup을 목적으로 사용할탠데

이와같이 생성된다면 이중화 개념으로 사용하지 못함.

그렇다면 Mysql Pod를 StatefulSet으로 이중화 구성한다면 어떻게

해야하는지 아래에서 알아봄

 

 


 

 

 

 

MySQL StatefulSet 이중화 구성

 

참고 사이트 : https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/

 

해당 환경구성은 상위 공식사이트에 방법이 나와있음.

 

MySQL을 StatefulSet으로 배포하지만

ReplicaSet을 통해서 생성되는 Pod끼리 동기화가 되야함으로

부가적인 설정과 Container가 사용됨.

 

1. Service Object 생성

 

해당 Service는 

ReplicaSet을 통해서 생성되는 MySQL Pod간의 통신과 외부에서 해당 pod에 접근하는 용도로 사용됨.

# 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 master: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql

 

 

 

 

 

2. ConfigMap Object 생성

 

MySQL StatefulSet에서 사용될 ConfigMap 생성.

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

 

 

 

3. MySQL SatefulSet 생성

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/master.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/slave.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
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          xtrabackup --prepare --target-dir=/var/lib/mysql
        volumeMounts:
        - name: pvc-test
          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: pvc-test
          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


          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 slave. (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 master. 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: pvc-test
          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: pvc-test
    spec:
      storageClassName: iksoon-nfs-storageclass  
      accessModes: [ "ReadWriteMany" ]    
      resources:
        requests:
          storage: 1Gi

 

 

 

 

4. 결과 확인

 

[정상 생성 확인]

 

 

 

[0번 Pod에 mysql data 생성]

 

 

 

[1번 Pod에서 0번 pod에서 생성햇던 data가 있는지 확인]

 

 

[결과]

ReplicaSet으로 생성된 Pod 가 모두 동기화 되어있음.

 

 

 

 

 


제 글을 복사할 시 출처를 명시해주세요.
글에 오타, 오류가 있다면 댓글로 알려주세요! 바로 수정하겠습니다!


 

 

 

 

참고

https://kubernetes.io/ko/docs/concepts/workloads/controllers/statefulset/

https://kubernetes.io/ko/docs/tutorials/stateful-application/basic-stateful-set/

https://bcho.tistory.com/1306

 

[ on-premise 환경 Dynamic Provisioning ]

https://gruuuuu.github.io/cloud/k8s-volume/#

 

[ mysql statefulset ]

https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/

 

 

 

 

 

 

 

 

 

 

반응형