본문 바로가기

AWS EKS 실습/EKS Beginner

Statefulsets을 사용하는 STATEFUL CONTAINER (w/ Persistent Volume)

StatefulSet Pod 집합에 대한 deployment와 scaling을 관리하고 이들 Pod가 다음중 하나 이상의 요구 사항을 갖는 어플리케이션을 위해 안정적으로 제공되는 것을 보장한다. 

 

- Stable, unique 네트워크 식별자

- Stable, Persistent Storage

- Ordered, graceful deployment and scaling

- Ordered, automated rolling updates

 

이 장에서는 StatefulSet 및 PersistentVolume으로 Amazon Elastic Block Store(EBS)를 다음과 같이 사용하여 MySQL 데이터베이스를 배포하는 방법을 검토한다.

 

ㅁ Amazon EBS CSI Driver

 

ㅇ  Container Storage Interface (CSI)란

 

CSI(컨테이너 스토리지 인터페이스)는 Kubernetes와 같은 Container Orchestration(CO)의 컨테이너화된 워크로드에 임의 블록 및 파일 스토리지 시스템을 노출하기 위한 표준이다.

 

CSI를 사용하면 타사 스토리지 제공 업체가 핵심 Kubernetes 코드를 건드리지 않고도 Kubernetes 에서 새로운 스토리지 시스템을 노출하는 플러그인을 작성하고 배포할 수 있다.

 

ㅇ Amazon EBS CSI 드라이버 정보

 

그만큼 Amazon Elastic Block Store (Amazon EBS) CSI(Container Storage Interfac) 드라이버 Amazon Elastic kubernetes Service (Amazon EKS) Cluster가 영구 볼륨에 대한 Amazon EBS 볼륨의 수명 주기를 관리할 수 있도록 해주는 CSI 인터페이스를 제공한다.

이 주제에서는 Amazon EBS CSI  드라이버를 Amazon EKS 클러스터에 배포하고 작동하는지 확인하는 방법을 보여준다.

 

ㅇ IAM 정책 구성

 

CSI 드라이버는 Kubernetes Pod 집합으로 배포된다. 이러한 Pod에는 볼륨 생성 및 삭제, 클러스터를 구성하는 EC2 작업자 노드에 볼륨 연결과 같은 EBS API 작업을 수행할 수 있는 권한이 있어야 한다.

 

먼저 정책 JSON 문서를 다운로드 하고 IAM 정책을 생성해 보겠다.

 

$ export EBS_CSI_POLICY_NAME="Amazon_EBS_CSI_Driver"
$ 
$ mkdir ${HOME}/environment/ebs_statefulset
$ cd ${HOME}/environment/ebs_statefulset
~/ebs_statefulset $ 
~/ebs_statefulset $ # download the IAM policy document
~/ebs_statefulset $ curl -sSL -o ebs-csi-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-ebs-csi-driver/master/docs/example-iam-policy.json
~/ebs_statefulset $ 
~/ebs_statefulset $ # Create the IAM policy
~/ebs_statefulset $ aws iam create-policy \
>   --region ${AWS_REGION} \
>   --policy-name ${EBS_CSI_POLICY_NAME} \
>   --policy-document file://${HOME}/environment/ebs_statefulset/ebs-csi-policy.json
{
    "Policy": {
        "PolicyName": "Amazon_EBS_CSI_Driver",
        "PolicyId": "ANPATHIILAC3DTXES2Z6A",
        "Arn": "arn:aws:iam::221745184950:policy/Amazon_EBS_CSI_Driver",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2021-03-01T04:30:11+00:00",
        "UpdateDate": "2021-03-01T04:30:11+00:00"
    }
}
~/ebs_statefulset $ 
~/ebs_statefulset $ # export the policy ARN as a variable
~/ebs_statefulset $ export EBS_CSI_POLICY_ARN=$(aws --region ${AWS_REGION} iam list-policies --query 'Policies[?PolicyName==`'$EBS_CSI_POLICY_NAME'`].Arn' --output text)
~/ebs_statefulset $ echo $EBS_CSI_POLICY_ARN
arn:aws:iam::221745184950:policy/Amazon_EBS_CSI_Driver

 

ㅇ 서비스 계정에 대한 IAM 역할 구성

 

IAM 역할을 Kubernetes 서비스 계정과 연결 할 수 있다. 그러면 이 서비스 계정은 해당 서비스 계정을 사용하는 모든 Pod의 Container에 AWS 권한을 제공할 수 있다. 이 기능을 사용하면 해당 Node의 Pod가 AWS API를 호출 할 수도 있도록 더 이상 Amazon EKS Node IAM 역할에 대한 확장 권한을 제공할 필요가 없다.

 

# Create an IAM OIDC provider for your cluster
eksctl utils associate-iam-oidc-provider \
  --region=$AWS_REGION \
  --cluster=eks-newelite-eksctl \
  --approve

# Create a service account
eksctl create iamserviceaccount \
  --cluster eks-newelite-eksctl \
  --name ebs-csi-controller-irsa \
  --namespace kube-system \
  --attach-policy-arn $EBS_CSI_POLICY_ARN \
  --override-existing-serviceaccounts \
  --approve

ㅇ Amazon EBS CSI 드라이버 배포

- helm으로 aws-ebs-csi-driver 검색

$ # add the aws-ebs-csi-driver as a helm repo
$ helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
"aws-ebs-csi-driver" has been added to your repositories

$ # search for the driver
$ helm search  repo aws-ebs-csi-driver
NAME                                    CHART VERSION   APP VERSION     DESCRIPTION                        
aws-ebs-csi-driver/aws-ebs-csi-driver   0.9.11          0.9.0           A Helm chart for AWS EBS CSI Driver

조회된 CHART 버전으로  driver 설치

helm upgrade --install aws-ebs-csi-driver \
  --version=0.9.11 \
  --namespace kube-system \
  --set serviceAccount.controller.create=false \
  --set serviceAccount.snapshot.create=false \
  --set enableVolumeScheduling=true \
  --set enableVolumeResizing=true \
  --set enableVolumeSnapshot=true \
  --set serviceAccount.snapshot.name=ebs-csi-controller-irsa \
  --set serviceAccount.controller.name=ebs-csi-controller-irsa \
  aws-ebs-csi-driver/aws-ebs-csi-driver

kubectl -n kube-system rollout status deployment ebs-csi-controller

ㅁ STORAGE CLASS 정의

 

ㅇ 개념

 

Dynamic Volume Provisioning을 통해 필요 시 Storage Volume을 생성할 수 있다. StorageClass는 동적 Provisioning이 invoke될 때 어떤 프로비저닝 도구를 사용해야 하고 어떤 매개 변수를 전달해야 하는지 정의하기 위해 미리 생성되어 있어야 한다.

 

ㅇ Define Stroage Class

 

 다음과 같이 정의 한다.

cat << EoF > ${HOME}/environment/ebs_statefulset/mysql-storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: mysql-gp2
provisioner: ebs.csi.aws.com # Amazon EBS CSI driver
parameters:
  type: gp2
  encrypted: 'true' # EBS volumes will always be encrypted by default
reclaimPolicy: Delete
mountOptions:
- debug
EoF

- Provisioner 는 ebs.csi.aws.com

- volume type은 General Purpose SSD volume (gp2)

- encrypted parameter는 기본적으로 EBS voluem이 암호화 되어 있음을 보장

 

mysql-gp2 storageclass를 생성한다.

kubectl create -f ${HOME}/environment/ebs_statefulset/mysql-storageclass.yaml

 

 

정상적으로 생성되었는지 아래와 같이 확인한다.

$ kubectl describe storageclass mysql-gp2
Name:                  mysql-gp2
IsDefaultClass:        No
Annotations:           <none>
Provisioner:           ebs.csi.aws.com
Parameters:            encrypted=true,type=gp2
AllowVolumeExpansion:  <unset>
MountOptions:
  debug
ReclaimPolicy:      Delete
VolumeBindingMode:  Immediate
Events:             <none>

 

ㅁ ConfigMap 생성

 

Create the mysql Namespace

kubectl create namespace mysql

 

Create Configmap

cd ${HOME}/environment/ebs_statefulset

cat << EoF > ${HOME}/environment/ebs_statefulset/mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
  namespace: mysql
  labels:
    app: mysql
data:
  master.cnf: |
    # Apply this config only on the leader.
    [mysqld]
    log-bin
  slave.cnf: |
    # Apply this config only on followers.
    [mysqld]
    super-read-only
EoF

 

master.cnf와 slave.cnf를 ConfiMap에 저장하고 StatefulSet에 정의된 리더와 팔로워 pod가 초기화 될 때 전달한다.

- master.cnf는 이진 로그 옵션(log-bin)이 있는 MySQL 리더 Pod 용으로 팔로워 서버로 전송할 데이터 변경 기록을 제공한다.

- slave.cnf는 읽기 전용 옵션이 있는 팔로워 pod 용이다.

 

"mysql-config" ConfigMap을 생성한다.

kubectl create -f ${HOME}/environment/ebs_statefulset/mysql-configmap.yaml

 

mySQL Service 생성

cat << EoF > ${HOME}/environment/ebs_statefulset/mysql-services.yaml
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
  namespace: mysql
  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 leader: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
  namespace: mysql
  name: mysql-read
  labels:
    app: mysql
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mysql
EoF

mysql 서비스를 DNS resolution으로 StatefulSet Controller가 Pod를 배치할 때 pod-name.yaml을 사용하여 포드를 확인할 수 있다. mysql-read 는 모든 팔로워들을 위해 load balacing을 수행하는 클라이언트 서비스이다.

 

다음 명령어로 mysql과 mysql-read 서비스를 생성한다.

kubectl create -f ${HOME}/environment/ebs_statefulset/mysql-services.yaml

 

StatefulSet은 ServiceName, Replica, Template 그리고 volumeClaimTemplate으로 구성된다.

- serviceName: mysql

- replica 3

- template: cofinguration of pod

- volumeClaimTemplate은 mysql-gp2

 

ㅁ Create StatefulSet

cd ${HOME}/environment/ebs_statefulset
wget https://eksworkshop.com/beginner/170_statefulset/statefulset.files/mysql-statefulset.yaml

mysql-statefulset.yaml 내용은 다음과 같다.

더보기
apiVersion: apps/v1
kind: StatefulSet
metadata:
  namespace: mysql
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 2
  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
          # Skip the clone if data already exists.
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Skip the clone on leader (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 ]]; then
            # XtraBackup already generated a partial "CHANGE MASTER TO" query
            # because we're cloning from an existing follower.
            mv xtrabackup_slave_info change_master_to.sql.in
            # Ignore xtrabackup_binlog_info in this case (it's useless).
            rm -f xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # We're cloning directly from leader. Parse binlog position.
            [[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
            rm xtrabackup_binlog_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"
            # In case of container restart, attempt this at-most-once.
            mv change_master_to.sql.in change_master_to.sql.orig
            mysql -h 127.0.0.1 <<EOF
          $(<change_master_to.sql.orig),
            MASTER_HOST='mysql-0.mysql',
            MASTER_USER='root',
            MASTER_PASSWORD='',
            MASTER_CONNECT_RETRY=10;
          START SLAVE;
          EOF
          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-config
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: mysql-gp2
      resources:
        requests:
          storage: 10Gi

 

StatefulSet "mysql"을 생성한다.

kubectl apply -f ${HOME}/environment/ebs_statefulset/mysql-statefulset.yaml

StatefulSet의 상태를 확인한다.

kubectl -n mysql rollout status statefulset mysql

 

ㄷ 른 터미널을 열어 다음 커맨드로 pod의 생성 상태를 모니터링한다.  

다른 터미널을 열어 pod의 생성 상태를 모니터링 한다.

kubectl -n mysql get pods -l app=mysql --watch

 

다음 명령을 통해 mysql-client를 사용하여 일부 데이터를 리더인 mysql-0.mysql에 보낼 수 있다.

kubectl -n mysql run mysql-client --image=mysql:5.7 -i --rm --restart=Never --\
  mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello, from mysql-client');
EOF

 

 

 

 

 

 

 

 

'AWS EKS 실습 > EKS Beginner' 카테고리의 다른 글

EKS Cluster에서 사용자 권한 등록  (0) 2022.04.20
Spot Instance로 EKS 구성  (0) 2021.02.28
Assigning Pods to Nodes  (0) 2021.02.27
Exposing a Service  (0) 2021.02.26
EKS Network 정책 실습 (w/Calico)  (0) 2021.02.26