Kubernetes StatefulSet Controller
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/
[ on-premise 환경 Dynamic Provisioning ]
https://gruuuuu.github.io/cloud/k8s-volume/#
[ mysql statefulset ]
https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/