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 |