AEWS Study #3 – EKS Storage & Node 관리

AEWS 3회차는 EKS Storage와 Node 관리에 대한 내용을 다룬다.

0. 환경 구성

이번에도 스터디에서 제공 된 One Click 배포를 사용하여 환경을 구성한다.
이번에 새롭게 조금 수정 된 yaml 파일을 다운로드 받고 배포 스크립트를 실행하면 잠시의 시간이 지난 뒤 Cloudformation에서 모든 배포가 완료된 것을 확인할 수 있다.

# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick2.yaml

# CloudFormation 스택 배포
aws cloudformation deploy --template-file eks-oneclick2.yaml --stack-name myeks --parameter-overrides KeyName=aewspair SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

# 작업용 EC2 SSH 접속
ssh -i aewspair.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)

배포 완료를 확인하고 AWS LB Controller, ExternalDNS와 kube-ops-view을 설치하여 환경 구성을 마무리한다.
kube ops view를 호출해 화면이 제대로 나오는 것까지 확인을 한다.

# AWS LB Controller
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# ExternalDNS
MyDomain=bs-yang.com
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"

1. EKS Storage?

k8s에서 pod의 데이터는 pod가 정지되면 모두 삭제되게 되어있다. (stateless application)

하지만 pod에 데이터베이스를 올릴 때도 있고 데이터가 존속되어야 하는 경우도 필요하다. (stateful application) 이럴 때 사용하는 방식이 PV/PVC이다.

Pod가 생성될 때 자동으로 볼륨을 Pod에 마운틑하여 PV/PVC을 사용할 수 있게끔 하는 방식이 동적 프로비저닝(Dynamic provisioning)이라고 한다.

stateless 환경으로 배포하는 것을 테스트해보려고 한다.
data 명령으로 현재 시간을 10초 간격으로 out file하는 pod을 배포해서 시간이 찍히는 것을 확인하고 해당 pod을 삭제 후 재배포한 뒤 이전 기록이 남아있는지 확인해볼 예정이다.


위 사진을 보면 첫 사진의 시간대는 06:54:29부터 시작인데 삭제 후 재배포 된 pod에서는 06:55:21부터 시작하는 것을 확인할 수 있다. 이제 이 부분을 host path을 사용하는 PV/PVC을 통해 statefull 환경으로 배포를 해볼 예정이다.

# 배포
curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
kubectl apply -f local-path-storage.yaml

# 확인
kubectl get-all -n local-path-storage
NAME                                                   NAMESPACE           AGE
configmap/kube-root-ca.crt                             local-path-storage  7s
configmap/local-path-config                            local-path-storage  7s
pod/local-path-provisioner-759f6bd7c9-rqh7l            local-path-storage  7s
serviceaccount/default                                 local-path-storage  7s
serviceaccount/local-path-provisioner-service-account  local-path-storage  7s
deployment.apps/local-path-provisioner                 local-path-storage  7s
replicaset.apps/local-path-provisioner-759f6bd7c9      local-path-storage  7s

kubectl get pod -n local-path-storage -owide
NAME                                      READY   STATUS    RESTARTS   AGE   IP             NODE                                               NOMINATED NODE   READINESS GATES
local-path-provisioner-759f6bd7c9-rqh7l   1/1     Running   0          12s   192.168.1.29   ip-192-168-1-231.ap-northeast-2.compute.internal   <none>           <none>

kubectl describe cm -n local-path-storage local-path-config
kubectl get sc
kubectl get sc local-path
NAME         PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  84s

PV/PVC를 사용하는 pod을 배포하고 해당 pod에서 파일을 조회하고 해당 pod가 배포되어 있는 Worker node에서도 local path을 사용해서 파일을 조회해본다.
둘 다 동일한 결과값을 갖고있는 것을 확인할 수 있다.

# PVC 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/localpath1.yaml
cat localpath1.yaml | yh
kubectl apply -f localpath1.yaml

# PVC 확인
kubectl get pvc
kubectl describe pvc

# 파드 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/localpath2.yaml
cat localpath2.yaml | yh
kubectl apply -f localpath2.yaml

# 파드 확인
kubectl get pod,pv,pvc
kubectl describe pv    # Node Affinity 확인
kubectl exec -it app -- tail -f /data/out.txt
Thu May 11 07:00:04 UTC 2023
Thu May 11 07:00:09 UTC 2023
... 

# 워커노드 중 현재 파드가 배포되어 있다만, 아래 경로에 out.txt 파일 존재 확인
ssh ec2-user@$N2 tree /opt/local-path-provisioner
/opt/local-path-provisioner
└── pvc-6cfbd87b-10f7-49dd-ad95-a65b7ab4f3f9_default_localpath-claim
    └── out.txt

# 해당 워커노드 자체에서 out.txt 파일 확인 : 아래 굵은 부분은 각자 실습 환경에 따라 다름
ssh ec2-user@$N2 tail -f /opt/local-path-provisioner/pvc-6cfbd87b-10f7-49dd-ad95-a65b7ab4f3f9_default_localpath-claim/out.txt
Thu May 11 07:01:34 UTC 2023
Thu May 11 07:01:39 UTC 2023
... 

이제 해당 pod을 삭제했을 때도 파일이 남아있는지 그리고 재배포를 했을 때도 동일하게 파일을 조회할 수 있는지 확인해보도록 하겠다.
Local Path에도 그대로 남아있고 Pod을 새로 배포했을 때도 동일하게 파일이 조회되는 것을 확인할 수 있다.

# 파드 삭제 후 PV/PVC 확인
kubectl delete pod app
kubectl get pod,pv,pvc
ssh ec2-user@$N2 tree /opt/local-path-provisioner
/opt/local-path-provisioner
└── pvc-6cfbd87b-10f7-49dd-ad95-a65b7ab4f3f9_default_localpath-claim
    └── out.txt

# 파드 다시 실행
kubectl apply -f localpath2.yaml
 
# 확인
kubectl exec -it app -- head /data/out.txt
kubectl exec -it app -- tail -f /data/out.txt

2. AWS EBS Controller

CSI(Container Storage Interface)는의 컨테이너와 스토리지 시스템 간의 통합을 표준화하기 위한 인터페이스로 CSI를 사용하면 컨테이너 Kubernetes과 스토리지 공급자 간의 인터페이스를 표준화하여, 서로 다른 스토리지 시스템과 컨테이너 오케스트레이션 시스템 간의 호환성을 보장할 수 있다.

이전에는 Kubernetes가 FlexVolume과 같은 사용자 정의 볼륨 플러그인을 사용하여 컨테이너와 스토리지 시스템 간의 인터페이스를 구현했지만 FlexVolume은 이식성과 유연성 측면에서 한계가 있었기 때문에, CSI가 도입되면서 대체되었다.

CSI는 다음과 같은 이점을 제공합니다.

  • 유연성: 스토리지 시스템과 컨테이너 오케스트레이션 시스템 간의 인터페이스를 표준화하여, 서로 다른 스토리지 시스템과 컨테이너 오케스트레이션 시스템 간의 호환성 보장
  • 이식성: CSI 스펙을 준수하는 스토리지 공급자는 어떤 컨테이너 오케스트레이션 시스템에서도 사용 가능
  • 모듈성: CSI는 별도의 드라이버를 개발하지 않아도 되기 때문에, 스토리지 공급자는 CSI 스펙을 준수하는 드라이버만 개발 가능

EBS CSI driver 동작은 볼륨을 생성하고 Pod에 해당 볼륨을 연결하는 동작이다.
persistentvolume, persistentvolumeclaim의 accessModes는 ReadWriteOnce로 설정해야 하는데 그 이유는 데이터 일관성과 무결성을 유지하기 위함이다. 여러 Node에서 해당 스토리지를 마운트하고 동시에 Write 작업을 수행하게 될 경우 데이터의 무결성이 보장되지 않을 수 있기 때문에 ReadWriteOnce AccessMode을 사용하여 데이터 일관성과 무결성을 유지해야 한다.
그리고 EBS 스토리지 기본 설정은 동일 AZ에 있는 EC2인스턴스와 그 Pod에 연결되는데 그 이유는 데이터 전송 속도가 빨라지고 더 높은 I/O 처리량을 얻을 수 있기 때문이다. 비용적인 측면에서도 동일 AZ에서의 데이터 전송 네트워크 대역폭 비용이 발생하지 않기 때문에 유리한 부분이 있다. 그리고 위의 AccessMode와 마찬가지로 데이터 일관성을 더 쉽게 보장 받을 수 있기 때문에 동일 AZ에 연결하게끔 되어 있다고 볼 수 있다.

EBS Controller을 설치하는 건 하기 내용을 통해 진행할 수 있다.

# 아래는 aws-ebs-csi-driver 전체 버전 정보와 기본 설치 버전(True) 정보 확인
aws eks describe-addon-versions \
    --addon-name aws-ebs-csi-driver \
    --kubernetes-version 1.24 \
    --query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" \
    --output text
v1.18.0-eksbuild.1
Tru
v1.17.0-eksbuild.1
False
...

# ISRA 설정 : AWS관리형 정책 AmazonEBSCSIDriverPolicy 사용
eksctl create iamserviceaccount \
  --name ebs-csi-controller-sa \
  --namespace kube-system \
  --cluster ${CLUSTER_NAME} \
  --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
  --approve \
  --role-only \
  --role-name AmazonEKS_EBS_CSI_DriverRole

# ISRA 확인
kubectl get sa -n kube-system ebs-csi-controller-sa -o yaml | head -5
eksctl get iamserviceaccount --cluster myeks
NAMESPACE	    NAME				            ROLE ARN
kube-system 	ebs-csi-controller-sa		arn:aws:iam::911283464785:role/AmazonEKS_EBS_CSI_DriverRole
...

# Amazon EBS CSI driver addon 추가
eksctl create addon --name aws-ebs-csi-driver --cluster ${CLUSTER_NAME} --service-account-role-arn arn:aws:iam::${ACCOUNT_ID}:role/AmazonEKS_EBS_CSI_DriverRole --force

# 확인
eksctl get addon --cluster ${CLUSTER_NAME}
kubectl get deploy,ds -l=app.kubernetes.io/name=aws-ebs-csi-driver -n kube-system
kubectl get pod -n kube-system -l 'app in (ebs-csi-controller,ebs-csi-node)'
kubectl get pod -n kube-system -l app.kubernetes.io/component=csi-driver

# ebs-csi-controller 파드에 6개 컨테이너 확인
kubectl get pod -n kube-system -l app=ebs-csi-controller -o jsonpath='{.items[0].spec.containers[*].name}' ; echo
ebs-plugin csi-provisioner csi-attacher csi-snapshotter csi-resizer liveness-probe

# csinodes 확인
kubectl get csinodes

# gp3 스토리지 클래스 생성
kubectl get sc
cat <<EOT > gp3-sc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp3
allowVolumeExpansion: true
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  allowAutoIOPSPerGBIncrease: 'true'
  encrypted: 'true'
  #fsType: ext4 # 기본값이 ext4 이며 xfs 등 변경 가능 >> 단 스냅샷 경우 ext4를 기본으로하여 동작하여 xfs 사용 시 문제가 될 수 있음 - 테스트해보자
EOT
kubectl apply -f gp3-sc.yaml
kubectl get sc
kubectl describe sc gp3 | grep Parameters

3. AWS Volume SnapShots Controller


Volume Snapshots Controller는 Kubernetes 클러스터 내에서 스냅샷을 생성하고 복원하기 위한 컨트롤이다. 이 컨트롤러는 Kubernetes Volume Snapshot API를 사용하여 스냅샷을 관리한다.
Volume Snapshot API를 사용하면 스토리지 클래스에서 스냅샷을 지원하는 경우 스냅샷을 생성할 수 있으며, 이를 사용하여 데이터를 백업하거나 특정 시점의 데이터로 복원할 수 있다.
Kubernetes Volume Snapshots Controller는 스냅샷을 생성하고 복원하기 위한 작업을 수행하고 스냅샷 생성을 위해서는 PVC(Persistent Volume Claim)을 사용하여 스냅샷 대상이 되는 볼륨을 식별하고 이를 기반으로 스냅샷을 생성한다. 스냅샷 생성 후에는 해당 스냅샷을 복원하여 이전 데이터를 다시 가져올 수 있다.
Kubernetes Volume Snapshots Controller는 또한 스냅샷 수명 주기 관리를 지원한다. 이를 통해 스냅샷을 자동으로 삭제하거나 보존할 수 있다. 스냅샷을 자동으로 삭제하면 비용을 절감하고 클러스터 용량을 확보할 수 있다. 반면에 스냅샷을 보존하면 장애 복구 및 데이터 분석 등에 유용하니 환경에 따라 유동적으로 관리할 수 있다.
정리하면 Kubernetes Volume Snapshots Controller를 사용하면 스토리지 클래스에서 스냅샷을 지원하는 경우 PVC를 사용하여 스냅샷을 생성하고 복원할 수 있고, 스냅샷 수명 주기 관리를 통해 비용을 절감하고 데이터를 보존할 수 있다.

VolumeSnapshotController을 설치하고 테스트 PVC/Pod을 통해 테스트를 진행해 볼 생각이다.
스냅샷을 생성하고 Pod와 PVC을 제거한 뒤 만들어둔 스냅샷을 통해 복원하는 과정으로 진행 된다.

# Install Snapshot CRDs
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -f snapshot.storage.k8s.io_volumesnapshots.yaml,snapshot.storage.k8s.io_volumesnapshotclasses.yaml,snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl get crd | grep snapshot
volumesnapshotclasses.snapshot.storage.k8s.io    2023-05-12T03:46:15Z
volumesnapshotcontents.snapshot.storage.k8s.io   2023-05-12T03:46:15Z
volumesnapshots.snapshot.storage.k8s.io          2023-05-12T03:46:15Z
kubectl api-resources  | grep snapshot
volumesnapshotclasses             vsclass,vsclasses   snapshot.storage.k8s.io/v1             false        VolumeSnapshotClass
volumesnapshotcontents            vsc,vscs            snapshot.storage.k8s.io/v1             false        VolumeSnapshotContent
volumesnapshots                   vs                  snapshot.storage.k8s.io/v1             true         VolumeSnapshot

# Install Common Snapshot Controller
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml
kubectl apply -f rbac-snapshot-controller.yaml,setup-snapshot-controller.yaml
serviceaccount/snapshot-controller created
clusterrole.rbac.authorization.k8s.io/snapshot-controller-runner created
clusterrolebinding.rbac.authorization.k8s.io/snapshot-controller-role created
role.rbac.authorization.k8s.io/snapshot-controller-leaderelection created
rolebinding.rbac.authorization.k8s.io/snapshot-controller-leaderelection created
deployment.apps/snapshot-controller created

kubectl get deploy -n kube-system snapshot-controller
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
snapshot-controller   2/2     2            0           19s

kubectl get pod -n kube-system -l app=snapshot-controller
NAME                                   READY   STATUS    RESTARTS   AGE
snapshot-controller-76494bf6c9-j8t2x   1/1     Running   0          42s
snapshot-controller-76494bf6c9-z7275   1/1     Running   0          42s

# Install Snapshotclass
curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-ebs-csi-driver/master/examples/kubernetes/snapshot/manifests/classes/snapshotclass.yaml
kubectl apply -f snapshotclass.yaml
kubectl get vsclass # 혹은 volumesnapshotclasses
# PVC yaml
cat <<EOT > awsebs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  storageClassName: gp3
EOT

# Pod yaml
cat <<EOT > awsebs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  terminationGracePeriodSeconds: 3
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo \$(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim
EOT

# PVC 생성
kubectl apply -f awsebs-pvc.yaml

# 파드 생성
kubectl apply -f awsebs-pod.yaml

# 파일 내용 추가 저장 확인
kubectl exec app -- tail -f /data/out.txt

# VolumeSnapshot 생성 : Create a VolumeSnapshot referencing the PersistentVolumeClaim name >> EBS 스냅샷 확인
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ebs-volume-snapshot.yaml
cat ebs-volume-snapshot.yaml | yh
kubectl apply -f ebs-volume-snapshot.yaml

# VolumeSnapshot 확인
kubectl get volumesnapshot
kubectl get volumesnapshot ebs-volume-snapshot -o jsonpath={.status.boundVolumeSnapshotContentName} ; echo
kubectl describe volumesnapshot.snapshot.storage.k8s.io ebs-volume-snapshot
kubectl get volumesnapshotcontents
NAME                                               READYTOUSE   RESTORESIZE   DELETIONPOLICY   DRIVER            VOLUMESNAPSHOTCLASS   VOLUMESNAPSHOT        VOLUMESNAPSHOTNAMESPACE   AGE
snapcontent-3bed7592-e760-42fc-8f2c-eb59d0b6714f   false        4294967296    Delete           ebs.csi.aws.com   csi-aws-vsc           ebs-volume-snapshot   default                   16s

# VolumeSnapshot ID 확인 
kubectl get volumesnapshotcontents -o jsonpath='{.items[*].status.snapshotHandle}' ; echo

# AWS EBS 스냅샷 확인
aws ec2 describe-snapshots --owner-ids self | jq
aws ec2 describe-snapshots --owner-ids self --query 'Snapshots[]' --output table

# app & pvc 제거 : 강제로 장애 재현
kubectl delete pod app && kubectl delete pvc ebs-claim


Snapshot 생성을 확인하고 App/PVC 삭제를 진행했다.
이후 스냅샷을 통해 복원한 후 기존 내용이 그대로 유지되는지 확인하도록 한다.
Pod의 특정 경로 /data/out.txt을 조회했을 때 위의 삭제 된 Pod에서 기존에 조회한 내용과 동일한 내용이 저장되어 있는 것을 확인할 수 있다.

# 스냅샷에서 PVC 로 복원
kubectl get pvc,pv
cat <<EOT > ebs-snapshot-restored-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-snapshot-restored-claim
spec:
  storageClassName: gp3
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  dataSource:
    name: ebs-volume-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
EOT
cat ebs-snapshot-restored-claim.yaml | yh
kubectl apply -f ebs-snapshot-restored-claim.yaml

# 확인
kubectl get pvc,pv

# 파드 생성
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ebs-snapshot-restored-pod.yaml
cat ebs-snapshot-restored-pod.yaml | yh
kubectl apply -f ebs-snapshot-restored-pod.yaml

# 파일 내용 저장 확인 : 파드 삭제 전까지의 저장 기록이 남아 있다. 이후 파드 재생성 후 기록도 잘 저장되고 있다
kubectl exec app -- cat /data/out.txt
Fri May 12 04:55:23 UTC 2023
Fri May 12 04:55:28 UTC 2023
Fri May 12 04:55:33 UTC 2023
Fri May 12 04:55:38 UTC 2023
Fri May 12 04:55:43 UTC 2023
Fri May 12 04:55:48 UTC 2023
...

# 삭제
kubectl delete pod app && kubectl delete pvc ebs-snapshot-restored-claim && kubectl delete volumesnapshots ebs-volume-snapshot

4. AWS EFS Controller

EFS Controller는 k8s에서 EFS을 마운트해서 사용하기 위해 사용하는 Controller이다.
EKS EFS Controller를 사용하면 EFS 파일 시스템을 생성하고, 해당 파일 시스템에서 EFS 볼륨을 동적으로 프로비저닝하고, 해당 볼륨을 파드에 마운트할 수 있다.
EKS EFS Controller를 사용하려면, 먼저 EFS 파일 시스템을 생성하고, 해당 파일 시스템에서 EFS 볼륨을 동적으로 프로비저닝할 수 있는 권한을 갖는 IAM 역할이 있어야 한다. 그런 다음, EKS 클러스터에 EFS CSI 드라이버를 설치하고, EKS EFS Controller를 설치하면 된다.

EKS EFS Controller를 사용하면 다음과 같은 이점이 있다.

  • EFS 파일 시스템에서 동적으로 프로비저닝된 EFS 볼륨 관리 편리
  • EFS 파일 시스템에 대한 별도의 설정 불필요
  • EKS 클러스터에서 파일 시스템을 생성하고 EFS 볼륨을 동적으로 프로비저닝하기 위한 AWS 리소스를 관리 불필요
  • EFS 파일 시스템과 EFS 볼륨에 대한 권한 관리 간편

EFS Controller을 설치하고 EFS Filesystem을 만든 뒤 해당 파일 시스템을 다수의 Pod가 사용할 수 있도록 설정하는 테스트를 진행할 예정이다.

우선 아래 내용을 참고해서 EFS Controller을 설치한다.

# EFS 정보 확인 
aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text

# IAM 정책 생성
curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json
aws iam create-policy --policy-name AmazonEKS_EFS_CSI_Driver_Policy --policy-document file://iam-policy-example.json

# ISRA 설정 : 고객관리형 정책 AmazonEKS_EFS_CSI_Driver_Policy 사용
eksctl create iamserviceaccount \
  --name efs-csi-controller-sa \
  --namespace kube-system \
  --cluster ${CLUSTER_NAME} \
  --attach-policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AmazonEKS_EFS_CSI_Driver_Policy \
  --approve

# ISRA 확인
kubectl get sa -n kube-system efs-csi-controller-sa -o yaml | head -5
eksctl get iamserviceaccount --cluster myeks

# EFS Controller 설치
helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
helm repo update
helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
    --namespace kube-system \
    --set image.repository=602401143452.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/eks/aws-efs-csi-driver \
    --set controller.serviceAccount.create=false \
    --set controller.serviceAccount.name=efs-csi-controller-sa

# 확인
helm list -n kube-system
kubectl get pod -n kube-system -l "app.kubernetes.io/name=aws-efs-csi-driver,app.kubernetes.io/instance=aws-efs-csi-driver"

EFS 파일시스템을 다수의 Pod가 사용하도록 설정을 해서 배포한다.
pod1은 out1.txt에 pod2는 out2.txt에 각각 시간을 입력하는 작업을 진행한다.
Bastion에서 Pod에 연결 된 EFS을 마운트해서 로컬에서도 파일이 동일하게 열리는지 확인하였다.
아래 캡쳐화면들을 보면 Local과 각 Pod에서 문제없이 파일을 불러올 수 있는 것을 확인할 수 있다.

# 모니터링
watch 'kubectl get sc efs-sc; echo; kubectl get pv,pvc,pod'

# 실습 코드 clone
git clone https://github.com/kubernetes-sigs/aws-efs-csi-driver.git /root/efs-csi
cd /root/efs-csi/examples/kubernetes/multiple_pods/specs && tree

# EFS 스토리지클래스 생성 및 확인
cat storageclass.yaml | yh
kubectl apply -f storageclass.yaml
kubectl get sc efs-sc

# PV 생성 및 확인 : volumeHandle을 자신의 EFS 파일시스템ID로 변경
EfsFsId=$(aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text)
sed -i "s/fs-4af69aab/$EfsFsId/g" pv.yaml

# EFS 확인 : AWS 관리콘솔 EFS 확인해보자
mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport $EfsFsId.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs
df -hT --type nfs4
mount | grep nfs4

cat pv.yaml | yh

kubectl apply -f pv.yaml
kubectl get pv; kubectl describe pv

# PVC 생성 및 확인
cat claim.yaml | yh
kubectl apply -f claim.yaml
kubectl get pvc

# 파드 생성 및 연동 : 파드 내에 /data 데이터는 EFS를 사용
cat pod1.yaml pod2.yaml | yh
kubectl apply -f pod1.yaml,pod2.yaml
kubectl df-pv

# 파드 정보 확인 : PV에 5Gi 와 파드 내에서 확인한 NFS4 볼륨 크리 8.0E의 차이는 무엇? 파드에 6Gi 이상 저장 가능한가?
kubectl get pods
kubectl exec -ti app1 -- sh -c "df -hT -t nfs4"
kubectl exec -ti app2 -- sh -c "df -hT -t nfs4"
Filesystem           Type            Size      Used Available Use% Mounted on
127.0.0.1:/          nfs4            8.0E         0      8.0E   0% /data

# 공유 저장소 저장 동작 확인
tree /mnt/myefs              # 작업용EC2에서 확인
tail -f /mnt/myefs/out1.txt  # 작업용EC2에서 확인
kubectl exec -ti app1 -- tail -f /data/out1.txt
kubectl exec -ti app2 -- tail -f /data/out2.txt

5. Deploying WordPress and MySQL with Persistent Volumes

Persistent Volume을 사용하는 WorkPress와 MySQL을 배포하는 실습을 진행해보려고 한다. PV을 사용하지 않을 경우 Pod가 Node의 장애로 인해 죽게 될 경우 데이터가 유실되기 때문에 PV을 사용하는 WordPress와 MySQL을 배포해보려고 한다.
실습은 https://kubernetes.io/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/ 해당 링크를 참고하였다.

deployment yaml 파일을 다운로드 받는 작업으로 시작한다.

#workdpress, mysql deployment yaml Download
curl -s -O https://kubernetes.io/examples/application/wordpress/mysql-deployment.yaml
curl -s -O https://kubernetes.io/examples/application/wordpress/wordpress-deployment.yaml

cat mysql-deployment.yaml | yh
apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
...

cat wordpress-deployment.yaml | yh
apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
...

kustomization.yaml 파일을 생성한다. 여기서 kustomization은 Kubernetes 클러스터에서 배포하려는 리소스들을 커스터마이징하는 방법을 제공하는 도구로 kustomization.yaml 파일을 통해 배포하고자 하는 리소스들의 목록과 각 리소스별로 커스터마이징을 수행하는 설정을 지정할 수 있다.
Kustomization은 다음과 같은 기능을 갖고 있다.

  • 기존 manifest 파일을 수정하지 않고, 수정된 내용을 적용할 수 있다.
  • 배포 시에 여러 개의 환경(예: dev, stage, prod)에 대한 다른 설정을 지정할 수 있다.
  • 배포할 리소스를 기반으로 자동으로 이름을 생성하여, 리소스 이름 충돌을 방지한다.

kustomization.yaml에 secret manager을 추가해서 mysql secret을 생성할 계획이다.

cat <<EOF >./kustomization.yaml
secretGenerator:
- name: mysql-pass
  literals:
  - password=YOUR_PASSWORD
resources:
  - mysql-deployment.yaml
  - wordpress-deployment.yaml
EOF

kustomization.yaml 파일에 Resources 정보를 담았기 때문에 kustomization.yaml 파일과 deployment yaml을 같은 폴더에 두고 배포를 진행할 예정이다.
deployment yaml 파일들을 보면 pvc를 20Gi로 생성하는 것으로 되어있는데 테스트 목적이기 때문에 4Gi로 변경하고 Internet Facing NLB을 생성하기 위해 wordpress deployment을 일부분 수정하였다.

# Internet Facing NLB 생성을 위해 wordpress deployment yaml 파일 수정
apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
    service.beta.kubernetes.io/aws-load-balancer-internal: "false"
spec:
  ports:
    - port: 80
      targetPort: 80

# 배포 진행
kubectl apply -k ./
secret/mysql-pass-2ffhd9htbk unchanged
service/wordpress unchanged
service/wordpress-mysql unchanged
persistentvolumeclaim/mysql-pv-claim created
persistentvolumeclaim/wp-pv-claim created
deployment.apps/wordpress created
deployment.apps/wordpress-mysql created

# secret 생성 확인
kubectl get secrets
NAME                    TYPE     DATA   AGE
mysql-pass-2ffhd9htbk   Opaque   1      8m9s

# PVC가 Dynamic Provisioning 되는지 확인
kubectl get pvc, pv
NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/mysql-pv-claim   Bound    pvc-14773585-e62e-47df-884a-4415a1961d7a   4Gi        RWO            gp2            97s
persistentvolumeclaim/wp-pv-claim      Bound    pvc-1652c71f-ffd7-423a-8632-c59a2f3ec44c   4Gi        RWO            gp2            97s
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                    STORAGECLASS   REASON   AGE
persistentvolume/pvc-14773585-e62e-47df-884a-4415a1961d7a   4Gi        RWO            Delete           Bound    default/mysql-pv-claim   gp2                     93s
persistentvolume/pvc-1652c71f-ffd7-423a-8632-c59a2f3ec44c   4Gi        RWO            Delete           Bound    default/wp-pv-claim      gp2                     93s

# Pod 정보 확인
kubectl get pods
NAME                               READY   STATUS    RESTARTS   AGE
wordpress-664bfdc845-nmh8p         1/1     Running   0          4m
wordpress-mysql-85648459b5-qzcwj   1/1     Running   0          4m

# Service 확인
kubectl get services wordpress
NAME        TYPE           CLUSTER-IP      EXTERNAL-IP                                                                         PORT(S)        AGE
wordpress   LoadBalancer   10.100.62.174   k8s-default-wordpres-d30dc22441-01684caff9c44457.elb.ap-northeast-2.amazonaws.com   80:30479/TCP   12m

# Service NLB을 Domain에 연결
kubectl annotate service wordpress -n default "external-dns.alpha.kubernetes.io/hostname=wptest.$MyDomain"
echo -e "Wordpress Test URL = http://wptest.$MyDomain"

설치 후 NLB가 연결 된 Domain을 호출하여 WordPress 설치페이지에 접속하였다.
이후 kustomization.yaml 에서 설정한 암호를 이용해서 wordpress 설치를 진행해봤다.
설치는 무리 없이 잘 설치되었고 이를 통해 mysql password 설정 또한 제대로 됐음을 알 수 있다. (mysql password 설정에 문제가 있다면 wordpress 설치가 정상적으로 되지 않기 때문에)

해당 실습이 종료됐으니 삭제를 진행한다.

# 삭제 진행
kubectl delete -k ./
secret "mysql-pass-2ffhd9htbk" deleted
service "wordpress" deleted
service "wordpress-mysql" deleted
persistentvolumeclaim "mysql-pv-claim" deleted
persistentvolumeclaim "wp-pv-claim" deleted
deployment.apps "wordpress" deleted
deployment.apps "wordpress-mysql" deleted

6. 정리

Storage의 경우 kOps 실습 때와 크게 다르지 않았지만 wordpress 배포를 통해 조금 더 상세한 내용을 테스트해볼 수 있어서 좋았다.
컨테이너로 db 등은 운영하지 않던 예전과 다르게 최근엔 db을 운영하기도 한다고 하니 앞으로 이 부분을 잘 공부해두면 좋을 것 같다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다