Post

EKS 스터디 3주차

CloudNet에서 주관하는 EKS 스터디 3주차를 진행하며 배운 내용을 정리한다.

아래에 사용된 이미지의 저작권은 CloudNet에 있습니다

요약

이번 포스트에서는 도메인 등록문제로 2주차때 진행하지 못했던 실습과 EKS에서 Storage 관련 내용을 직접 실습해본다. 저장 방식인 emptyDir, localPath, PV/PVC를 다뤄본다. 특히 PV/PVC에서는 AWS 서비스인 EBS, EFS를 사용하는 방법을 다룬다.

진행 순서

  • 2주차 추가진행: 도메인 등록 및 External DNS 적용
  • Volume 유형 학습
  • EmptyDir 실습 및 특징 확인
  • Local(HostPath) 실습 및 특징 확인
  • PV/PVC 실습 및 특징 확인
    • EBS Controller(동적프로비저닝, 스냅샷을 통한 복구)
    • EFS Controller(정적, 동적 프로비저닝)

2주차 추가진행

aws-load-balancer-controller 설치한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$helm repo add eks https://aws.github.io/eks-charts
"eks" has been added to your repositories

$helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "eks" chart repository
Update Complete. ⎈Happy Helming!⎈

$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

NAME: aws-load-balancer-controller
LAST DEPLOYED: Sun May  7 23:47:07 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
AWS Load Balancer controller installed!

External DNS 적용 : 개인 도메인은 사전에 준비필요!

1
2
3
4
5
6
7
8
$echo $MyDomain, $MyDnzHostedZoneId
kaneawsdns.com, /hostedzone/Z06702063E7RRITLLMJRM
#사전에 관련 매니패스트 파일을 준비해주셨다.

$curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml

# External DNS 배포
$MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

위의 envsubst 명령어에 대한 간단한 설명

envsubst : 현재 환경변수를 수레에서 사용가능한 변수로 치한 → 앞의 MyDomain, MyDnzHostedZoneId를 알맞게 externaldns.yaml 파일에 적용킨 후 해당 yaml 파일을 kubectl apply 한다

매니패스트 파일은 service account, cluster role, cluster role binding, deployment, service, configmap 등이 포함되어 있다 토글에는 자세한 내용이 담겨있다 자세한 내용은 링크를 통해서 확인할 수 있다

이제 UI 툴인 kube-ops-view를 설치해보겠다

kubeops 설치

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#repo 추가 
$helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
"geek-cookbook" has been added to your repositories
#kube-ops-view 설치
$helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
NAME: kube-ops-view
LAST DEPLOYED: Sun May  7 23:53:13 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace kube-system -l "app.kubernetes.io/name=kube-ops-view,app.kubernetes.io/instance=kube-ops-view" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:8080

#kube-ops-view 서비스의 타입을 "loadBalancer"로 변경하는 모습, patch는 수정할 때 쓰는 명령어
#`-p` 옵션은 json 형태로 수정할 때
$kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
service/kube-ops-view patched
#서비스에 대한 외부 도메인을 주석으로 달아줌(annotate 주석)
$kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
service/kube-ops-view annotated
$echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"
Kube Ops View URL = http://kubeopsview.kaneawsdns.com:8080/#scale=1.5

아래는 kubeops 실제 화면이다. 큰 클러스터안에 노드별로 구분되어있는 것을 확인할 수 있다

  • 파드의 개수를 늘린 모습, 가져다가 되면 구체적으로 정보가 나온다.

위의 DEFAULT로 나와있는 부분은 출력형식을 지정하는 것이다. 파드는 네임스페이스에 따라 구분된다.

환경 배포

환경배포파일이 저번주와 달라졌다 기존에 것에서 저번주차와 이번주차 진행할 내용들이 추가되었다. 앞으로 실습내용을 작업용 EC2에서도 확인하기 위해 EFS를 마운트한다

mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport ***<자신의 EFS FS ID>***.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs

kubectl config rename-context admin@myeks.ap-northeast-2.eksctl.io [원하는 이름]@myeks 실습환경에서 AWS LB/ExternalDNS, kube-ops-view를 추가적으로 설치한다 설치과정은 아래에서 확인할 수 있다

배포설정

먼저, 기본적인 환경세팅을 진행한다 아까 위에서 나왔던 context 변경부터 진행한다

context 변경

1
2
3
4
# context 변경 
(EKS-study@myeks:N/A) [root@myeks-bastion-EC2 ~] # kubectl config rename-context EKS-study@myeks.ap-northeast-2.eksctl.io $NICK@myeks
Context "EKS-study@myeks.ap-northeast-2.eksctl.io" renamed to "kane@myeks".
(kane@myeks:N/A) [root@myeks-bastion-EC2 ~] #

storage class 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kubectl get sc gp2 -o yaml | yh
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"name":"gp2"},"parameters":{"fsType":"ext4","type":"gp2"},"provisioner":"kubernetes.io/aws-ebs","volumeBindingMode":"WaitForFirstConsumer"}
    storageclass.kubernetes.io/is-default-class: "true"
  creationTimestamp: "2023-05-07T14:02:40Z"
  name: gp2
  resourceVersion: "266"
  uid: b8040117-dce8-4173-b8d4-2a77d7a8124f
parameters:
  fsType: ext4
  type: gp2
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

이제 eks의 모든 노드들이 csinode인지 확인한다 csinodes “kubectl get csinodes” 명령은 Kubernetes 클러스터에서 사용 가능한 모든 CSINode(CSI Node)을 나열한다 CSINode은 Container Storage Interface(CSI) 스펙을 준수하는 노드이다. CSI 스펙은 Kubernetes 클러스터에서 다양한 스토리지 시스템과의 통합을 위한 표준 인터페이스를 제공한다

1
2
3
4
5
$k get csinodes
NAME                                               DRIVERS   AGE
ip-192-168-1-137.ap-northeast-2.compute.internal   0         26m
ip-192-168-2-116.ap-northeast-2.compute.internal   0         26m
ip-192-168-3-151.ap-northeast-2.compute.internal   0         26m 

--label-columns 옵션을 통해 eks node를 조회한 모습이다.

1
2
3
4
5
6
7
8
9
$kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
NAME                                               STATUS   ROLES    AGE   VERSION                INSTANCE-TYPE   CAPACITYTYPE   ZONE
ip-192-168-1-137.ap-northeast-2.compute.internal   Ready    <none>   29m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2a
ip-192-168-2-116.ap-northeast-2.compute.internal   Ready    <none>   29m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2b
ip-192-168-3-151.ap-northeast-2.compute.internal   Ready    <none>   29m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2c

$eksctl get iamidentitymapping --cluster myeks
ARN											USERNAME				GROUPS					ACCOUNT
arn:aws:iam::871103481195:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-WBR1ZJTQNHTR	system:node:	system:bootstrappers,system:nodes

배포가 모두 완료되었다. 이제 이번 주차의 주제인 스터리지에 대해 알아본다. 우선 EKS에서 사용하는 스토리지의 종류에 대해 알아보자

Volume 유형

방식은 아래의 그림과 같이 3가지가 있다

첫번째 방식은 컨테이너의 임시 저장소로, 파드의 생명주기와 데이터의 생명주기가 같다. 임시로 쓰기에는 좋으나 만약 데이터를 보존해야할 경우 2~3번째 방식을 이용해야 한다.

2,3번째 방식은 아래에서 실습을 하며 자세하게 설명할 예정이다.

CSI 프로비저닝 유형

CSI는 Container Storage Interface로 다양한 스토리지 솔류션에 대한 인터페이스이다. CSI를 통해 다음과 같은 2가지 방법으로 스토리지를 연결할 수 있다

정적 프로비저닝(Static provisioning) 먼저 관리자가 하나 이상의 PV를 생성하고 애플리케이션 개발자는 PVC를 생성한다 동적 프로비저닝(Dynamic provisioning) 동적 프로비저닝을 사용하면 PV객체를 생성할 필요가 없다 대신에, PVC를 생성할 때 내부적으로 자동으로 생성된다. Kubernetes는 Storage Class라는 다른 객체를 사용하여 이를 수행한다

EmptyDir

먼저, 가시다님의 이미지를 통해 파드의 생명주기와 데이터의 보존주기가 같은 것을 확인한 결과이다.

[테스트를 위해 10초 마다 /home/pod-out.txt 에 로그를 남기는 파드 생성한다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/date-busybox-pod.yaml

$cat date-busybox-pod.yaml | yh
apiVersion: v1
kind: Pod
metadata:
  name: busybox
spec:
  terminationGracePeriodSeconds: 3
  containers:
  - name: busybox
    image: busybox
    command:
    - "/bin/sh"
    - "-c"
    - "while true; do date >> /home/pod-out.txt; cd /home; sync; sync; sleep 10; done"

$kubectl apply -f date-busybox-pod.yaml
pod/busybox created

$kubectl get pod
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          11s

[파드의 로그를 확인한다.] 정상적으로 10초마다 로그를 남기고 있는 것을 확인할 수 있다.

1
2
3
4
5
6
7
8
kubectl exec busybox -- tail -f /home/pod-out.txt
Sun May  7 15:26:19 UTC 2023
Sun May  7 15:26:29 UTC 2023
^C
$kubectl delete pod busybox
pod "busybox" deleted
$kubectl apply -f date-busybox-pod.yaml
pod/busybox created

[이제 파드를 삭제 후 재생성한다.] 삭제 후 실행하니, 기존의 로그는 사라진 것을 확인할 수 있다. 이를 통해 emptyDir 유형은 파드와 데이터의 생명주기가 같음을 확인

1
2
3
4
5
6
7
8
$kubectl exec busybox -- tail -f /home/pod-out.txt
Sun May  7 15:26:45 UTC 2023
Sun May  7 15:26:55 UTC 2023
Sun May  7 15:27:05 UTC 2023
Sun May  7 15:27:15 UTC 2023

$kubectl delete pod busybox
pod "busybox" deleted

Local

HostPath는 Kubernetes 노드의 호스트 파일 시스템에 볼륨을 프로비저닝하는 방법이다. 이것은 데이터를 저장할 수 있는 가장 간단한 방법이지만 제한적이다.

HostPath 볼륨은 단일 노드에서만 사용할 수 있으며 노드가 중단되면 데이터가 손실된다. Local Path는 인스턴스 스토어 볼륨을 프로비저닝하는 방법이다.

Instance Store는 EC2와 물리적으로 연결되어있는 스토리지이며 다른 스토리지 서비스보다 IOPS 성능이 우수하다.

그렇기에 여러 노드에서 사용할 수 있으며, 노드의 중단되어도 데이터가 손실되지 않는다. local-path에 대한 자세한 내용은 GitHub에서 확인할 수 있다. local path 관련 백업은 한승호님이 Velero blog에 올려주셨다.

아래는 Local-Path 방식을 이용하는 PV,PVC에 대한 실습이다.

[Storage Class 배포]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# strage class 다운로드
$curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml

# 배포
$kubectl apply -f local-path-storage.yaml
namespace/local-path-storage created
serviceaccount/local-path-provisioner-service-account created
clusterrole.rbac.authorization.k8s.io/local-path-provisioner-role created
clusterrolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
deployment.apps/local-path-provisioner created
storageclass.storage.k8s.io/local-path created
configmap/local-path-config created

$kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path      rancher.io/local-path   Delete          WaitForFirstConsumer   false                  25s

[Local Path 방식을 이용해 동적프로비저닝한다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$cat localpath1.yaml | yh
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: localpath-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: "local-path"

#PVC 생성
$kubectl apply -f localpath1.yaml
persistentvolumeclaim/localpath-claim created
$kubectl get pvc
NAME              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
localpath-claim   Pending                                      local-path     4s

# [data -u]로그를 남기는 파드 생성
$kubectl apply -f localpath2.yaml
pod/app created

# 자동으로 PV가 생성되는 모습
$kubectl get pod,pv,pvc
NAME      READY   STATUS              RESTARTS   AGE
pod/app   0/1     ContainerCreating   0          6s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS   REASON   AGE
persistentvolume/pvc-88293146-ce9b-4dd0-b473-1ceb489301f3   1Gi        RWO            Delete           Bound    default/localpath-claim   local-path              2s

NAME                                    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/localpath-claim   Bound    pvc-88293146-ce9b-4dd0-b473-1ceb489301f3   1Gi        RWO            local-path     48s

위의 내용을 통해 동적프로비저닝을 확인했다. 현재와 같은 방식은 노드의 볼륨을 사용하기에, PV는 파드가 존재하는 노드에 생성된다. [PV와 파드가 동일한 노드에 생성되었는지 확인해본다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$kubectl describe pv    # Node Affinity 확인 -> 자신의 워커노드에서만 생성하도록
Name:              pvc-88293146-ce9b-4dd0-b473-1ceb489301f3
...
Node Affinity:
  Required Terms:
    Term 0:        kubernetes.io/hostname in [ip-192-168-2-116.ap-northeast-2.compute.internal]
...

# 파드가 동작중인 노드 확인
$k get po -o wide
NAME   READY   STATUS    RESTARTS   AGE   IP              NODE                                               NOMINATED NODE   READINESS GATES
app    1/1     Running   0          42s   192.168.2.185   ip-192-168-2-116.ap-northeast-2.compute.internal   <none>           <none>

# 접속해서 파드가 남긴 로그 확인
ssh ec2-user@$N2 tree /opt/local-path-provisioner
/opt/local-path-provisioner
└── pvc-88293146-ce9b-4dd0-b473-1ceb489301f3_default_localpath-claim
    └── out.txt

ssh ec2-user@$N2 tail -f /opt/local-path-provisioner/pvc-88293146-ce9b-4dd0-b473-1ceb489301f3_default_localpath-claim/out.txt
Sun May 7 15:30:47 UTC 2023
Sun May 7 15:30:52 UTC 2023
Sun May 7 15:30:57 UTC 2023
Sun May 7 15:31:02 UTC 2023
Sun May 7 15:31:07 UTC 2023
Sun May 7 15:31:12 UTC 2023

[파드를 삭제 후 재생성하여 생명주기가 다른지 확인한다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$kubectl delete pod app
pod "app" deleted

# 파드가 삭제되어도 데이터는 유지됨
$ssh ec2-user@$N2 tree /opt/local-path-provisioner
/opt/local-path-provisioner
└── pvc-88293146-ce9b-4dd0-b473-1ceb489301f3_default_localpath-claim
    └── out.txt

1 directory, 1 file

# 파드를 재생성하여 데이터 보존을 한번 더 확인
$kubectl apply -f localpath2.yaml
pod/app created
$kubectl exec -it app -- head /data/out.txt
...
Sun May 7 15:30:02 UTC 2023
Sun May 7 15:30:07 UTC 2023

# 이전과는 다르게 로그가 그대로 유지됨
# 중간에 시간이 짤리는 것은 파드가 삭제 후 재시작하는 사이 시간
$kubectl exec -it app -- tail -f /data/out.txt
..
Sun May 7 15:31:27 UTC 2023
Sun May 7 15:31:32 UTC 2023
Sun May 7 15:31:37 UTC 2023
**Sun May 7 15:31:42 UTC 2023
Sun May 7 15:32:01 UTC 2023**

# 자원을 모두 제거
$kubectl delete pod app
pod "app" deleted

$kubectl delete pvc localpath-claim
persistentvolumeclaim "localpath-claim" deleted

# pv 삭제 후, 관련 볼륨이 사라진 것을 확인할 수 있음.
$ssh ec2-user@$N2 tree /opt/local-path-provisioner
/opt/local-path-provisioner

0 directories, 0 files

PV/PVC

Persistent Volume(PV)은 실제 스토리지 볼륨을 나타냅니다. 쿠버네티스는 PV 위에 추가 추상화 계층인 PersistentVolumeClaim(PVC)을 가지고 있다 구분하는 이유는 아래의 문서에서 확인할 수 있다

PV와 PVC의 구분은 Kubernetes 환경에서 두 가지 유형의 사용자가 있다는 개념과 관련이 있다

  • Kubernetes 관리자: 이 사용자는 클러스터를 유지 관리하고 운영하며 영구 스토리지와 같은 계산 리소스를 추가한다
  • Kubernetes 애플리케이션 개발자: 이 사용자는 애플리케이션을 개발하고 배포한다 PV와 PVC에 대한 내용은 AWS Blog 문서를 참고했다

이제 아래의 실습에서는 AWS 스토리지 서비스(EBS, EFS)를 PV, PVC로 사용하는 방법을 알아본다.

AWS EBS Controller

EBS-CSI를 사용하기 위한 절차이다. 컨트롤러를 통해 쉽게 EBS를 사용할 수 있다. EBS는 동적 프로비저닝이 가능하기에, PVC 와 파드를 생성하면 자동적으로 PV가 할당된다.

  1. ISRA 설정 : AWS관리형 정책 AmazonEBSCSIDriverPolicy 사용
  2. add-on 추가: eksctl 이용

    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

ebs-csi-driver 설정과정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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
2023-05-11 09:37:52 [ℹ]  building iamserviceaccount stack "eksctl-myeks-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"
2023-05-11 09:37:53 [ℹ]  deploying stack "eksctl-myeks-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"

$eksctl get iamserviceaccount --cluster myeks
NAMESPACE	NAME				ROLE ARN
kube-system	aws-load-balancer-controller	arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-RK7A5DTQNNSW
kube-system	ebs-csi-controller-sa		arn:aws:iam::871103481195:role/AmazonEKS_EBS_CSI_DriverRole

# eksctl을 이용해서 ebs-csi-driver 를 설치
$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
2023-05-11 09:41:40 [ℹ]  Kubernetes version "1.24" in use by cluster "myeks"
2023-05-11 09:41:40 [ℹ]  using provided ServiceAccountRoleARN "arn:aws:iam::871103481195:role/AmazonEKS_EBS_CSI_DriverRole"
2023-05-11 09:41:40 [ℹ]  creating addon

$eksctl get addon --cluster ${CLUSTER_NAME}
NAME			VERSION			STATUS		ISSUES	IAMROLE								UPDATE AVAILABLE	CONFIGURATION VALUES
aws-ebs-csi-driver	v1.18.0-eksbuild.1	CREATING	0	arn:aws:iam::871103481195:role/AmazonEKS_EBS_CSI_DriverRole

$k get sa -n kube-system | grep ebs
ebs-csi-controller-sa                0         33s
ebs-csi-node-sa                      0         33s

EBS 컨트롤러 설치를 완료했다. 이제 EBS를 사용하기 위한 StorageClass를 배포한다.

[스토리지 클래스 배포]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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'
> EOT

$kubectl apply -f gp3-sc.yaml
storageclass.storage.k8s.io/gp3 created

$kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  27m
gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   5s

$kubectl describe sc gp3 | grep Parameters
Parameters:            allowAutoIOPSPerGBIncrease=true,encrypted=true,type=gp3
$kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  28m
gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true       

PVC, 파드를 생성하여 동적 프로비저닝을 확인하는 과정이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
$cat <<EOT > awsebs-pvc.yaml
> apiVersion: v1
> kind: PersistentVolumeClaim
> metadata:
>   name: ebs-claim
> spec:
>   accessModes:
>     - ReadWriteOnce #### EBS는 여러 노드와 동시에 연결이 가능하기에, Once로 세팅
>   resources:
>     requests:
>       storage: 4Gi
>   storageClassName: gp3
> EOT

$kubectl apply -f awsebs-pvc.yaml
persistentvolumeclaim/ebs-claim created

$kubectl get pvc,pv
NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/ebs-claim   Pending                                      gp3            2s
$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
$kubectl apply -f awsebs-pod.yaml
pod/app created
$kubectl get pvc,pv,pod
NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/ebs-claim   Bound    pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7   4Gi        RWO            gp3            34s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
persistentvolume/pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7   4Gi        RWO            Delete           Bound    default/ebs-claim   gp3                     12s

NAME      READY   STATUS              RESTARTS   AGE
pod/app   0/1     ContainerCreating   0          16s

# 추가된 EBS Volume(PV) 확인
$kubectl get VolumeAttachment
NAME                                                                   ATTACHER          PV                                         NODE                                              ATTACHED   AGE
csi-58bd6537e51d9961539e7d5eda35c6d08ab98b890c83e527325b84f7d562eba3   ebs.csi.aws.com   pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7   ip-192-168-2-93.ap-northeast-2.compute.internal   true       14s

# 추가된 EBS Volume(PV) 확인
$aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}") | jq

{
  "Volumes": [
    {
      "Attachments": [
        {
          "AttachTime": "2023-05-11T00:48:04+00:00",
          "Device": "/dev/xvdaa",
          "InstanceId": "i-0039e023911746fbf",
          "State": "attached",
          "VolumeId": "vol-02735906e67899e1b",
          "DeleteOnTermination": false
        }
      ],
      "AvailabilityZone": "ap-northeast-2b",
      "CreateTime": "2023-05-11T00:48:00.023000+00:00",
      "Encrypted": true,
      "KmsKeyId": "arn:aws:kms:ap-northeast-2:871103481195:key/e92fb931-6368-48c5-9896-43acdaa5f663",
      "Size": 4,
      "SnapshotId": "",
      "State": "in-use",
      "VolumeId": "vol-02735906e67899e1b",
      "Iops": 3000,
      "Tags": [
        {
          "Key": "kubernetes.io/created-for/pvc/namespace",
          "Value": "default"
        },
        {
          "Key": "kubernetes.io/created-for/pvc/name",
          "Value": "ebs-claim"
        },
        {
          "Key": "Name",
          "Value": "myeks-dynamic-pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7"
        },
        {
          "Key": "KubernetesCluster",
          "Value": "myeks"
        },
        {
          "Key": "CSIVolumeName",
          "Value": "pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7"
        },
        {
          "Key": "kubernetes.io/cluster/myeks",
          "Value": "owned"
        },
        {
          "Key": "kubernetes.io/created-for/pv/name",
          "Value": "pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7"
        },
        {
          "Key": "ebs.csi.aws.com/cluster",
          "Value": "true"
        }
      ],
      "VolumeType": "gp3",
      "MultiAttachEnabled": false,
      "Throughput": 125
    }
  ]
}

Io2 Type의 EBS라면 여러 개의 인스턴스에 연결할 수 있다. 단 같은 AZ에 존재해야 한다.

EBS의 다중 연결을 사용하면 특정 볼륨을 여러 대의 EC2 인스턴스에 연결할 수 있다 각 EBS 볼륨은 Multi-Attach 설정시 해당 볼륨이 위치한 가용영역에서 최대 16개의 EC2에 연결할 수 있게 된다.

이런 경우에는 PersistentVolumeClaim을 생성할 때, AWS EBS는 다중연결이 가능하기에 access모드를 ReadWriteOnce로 설정해야 한다 EBS의 다중 연결은 GlusterFS와 같은 공유 볼륨을 사용하는 경우와 유사한다

또한, EBS는 같은 AZ에 있어야 접근이 가능하므로 nodeaffinity를 통해 같은 AZ로 PV를 생성한다

[아래는 EBS 볼륨을 확장하는 실습이다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$kubectl exec app -- tail -f /data/out.txt
Thu May 11 00:48:40 UTC 2023
Thu May 11 00:48:45 UTC 2023
Thu May 11 00:48:50 UTC 2023
Thu May 11 00:48:55 UTC 2023
Thu May 11 00:49:00 UTC 2023
Thu May 11 00:49:05 UTC 2023
Thu May 11 00:49:10 UTC 2023
Thu May 11 00:49:15 UTC 2023
Thu May 11 00:49:20 UTC 2023
Thu May 11 00:49:25 UTC 2023
Thu May 11 00:49:30 UTC 2023

$kubectl df-pv
 PV NAME                                   PVC NAME   NAMESPACE  NODE NAME                                        POD NAME  VOLUME MOUNT NAME   SIZE  USED  AVAILABLE  %USED  IUSED  IFREE   %IUSED
 pvc-7fb5199c-d3d9-4648-a0d0-5f9b9bc006d7  ebs-claim  default    ip-192-168-2-93.ap-northeast-2.compute.internal  app       persistent-storage  3Gi   28Ki  3Gi        0.00   12     262132  0.00

$kubectl exec -it app -- sh -c 'df -hT --type=overlay'
Filesystem     Type     Size  Used Avail Use% Mounted on
overlay        overlay   30G  3.6G   27G  12% /

# ebs volume 확장
$kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
persistentvolumeclaim/ebs-claim patched

# 자동으로 확장되는 모습
$kubectl exec -it app -- sh -c 'df -hT --type=ext4'
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme1n1   ext4  9.8G   28K  9.7G   1% /data
$kubectl delete pod app & kubectl delete pvc ebs-claim
[1] 7288
pod "app" deleted
persistentvolumeclaim "ebs-claim" deleted
[1]+  Done                    kubectl delete pod app
  • EBS controller 에 의해 하나의 PV가 생성되고, 용량이 바뀐 모습

AWS Volume SnapShots Controller

아래는 위와 동일하게, PVC, PV를 생성해서 파드와 연결한다 파드는 data -u 로그를 남기며 강제로 파드와 PVC를 삭제한 후, snapshots을 통해 복구하는 실습을 진행한다

snapshot 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
$kubectl apply -f ebs-volume-snapshot.yaml
volumesnapshot.snapshot.storage.k8s.io/ebs-volume-snapshot created

$kubectl get volumesnapshot
NAME                  READYTOUSE   SOURCEPVC   SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS   SNAPSHOTCONTENT                                    CREATIONTIME   AGE
ebs-volume-snapshot   false        ebs-claim                           4Gi           csi-aws-vsc     snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad   3s             3s

#아래에 나와있는 snapshot id로 확인하면, 모두 잘 설정되어있다.
$kubectl get volumesnapshot ebs-volume-snapshot -o jsonpath={.status.boundVolumeSnapshotContentName} ; echo
snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad

$kubectl describe volumesnapshot.snapshot.storage.k8s.io ebs-volume-snapshot
Name:         ebs-volume-snapshot
Namespace:    default
...
Metadata:
...
    Time:            2023-05-13T05:58:36Z
  Resource Version:  11187
  UID:               ff3a9b11-d8a5-4806-80eb-9c062c4062ad
Spec:
  Source:
    Persistent VolumeClaim Name:  ebs-claim
  Volume Snapshot Class Name:      csi-aws-vsc
Status:
  Bound Volume Snapshot Content Name:  snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad
  Creation Time:                       2023-05-13T05:58:35Z
  Ready To Use:                        false
  Restore Size:                        4Gi
Events:
  Type    Reason            Age   From                 Message
  ----    ------            ----  ----                 -------
  Normal  CreatingSnapshot  19s   snapshot-controller  Waiting for a snapshot default/ebs-volume-snapshot to be created by the CSI driver.
  Normal  SnapshotCreated   18s   snapshot-controller  Snapshot default/ebs-volume-snapshot was successfully created by the CSI driver.

###
# 블로그에서는 중복된 조회 제거
$kubectl get volumesnapshotcontents
NAME                                               READYTOUSE   RESTORESIZE   DELETIONPOLICY   DRIVER            VOLUMESNAPSHOTCLASS   VOLUMESNAPSHOT        VOLUMESNAPSHOTNAMESPACE   AGE
snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad   false        4294967296    Delete           ebs.csi.aws.com   csi-aws-vsc           ebs-volume-snapshot   default                   38s

[스냅샷을 통한 복구를 진행하기 위해 파드와 PVC를 제거한다.]

1
2
3
4
5
6
7
# 파드와 pvc를 제거하여, pv가 제거된 모습
$kubectl delete pod app && kubectl delete pvc ebs-claim
pod "app" deleted
persistentvolumeclaim "ebs-claim" deleted

$kubectl get pvc,pv
No resources found

[이제 volumesnapshot을 통해 복구를 진행한다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$k get volumesnapshot
NAME                  READYTOUSE   SOURCEPVC   SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS   SNAPSHOTCONTENT                                    CREATIONTIME   AGE
ebs-volume-snapshot   true         ebs-claim                           4Gi           csi-aws-vsc     snapcontent-ff3a9b11-d8a5-4806-80eb-9c062c4062ad   7m13s          7m13s

$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

$kubectl apply -f ebs-snapshot-restored-claim.yaml
persistentvolumeclaim/ebs-snapshot-restored-claim created

$kubectl get pvc,pv
NAME                                                STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/ebs-snapshot-restored-claim   Pending                                      gp3            3s

## 블로그에서는 curl 또한 제거
$curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ebs-snapshot-restored-pod.yaml
$cat ebs-snapshot-restored-pod.yaml | yh
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  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-snapshot-restored-claim

[파드를 다시 생성하고 로그를 확인한다. 로그가 이어지는 것을 확인할 수 있다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$kubectl apply -f ebs-snapshot-restored-pod.yaml
pod/app created

$k get po
NAME   READY   STATUS    RESTARTS   AGE
app    1/1     Running   0          13s

#로그가 이어지는 것을 확인할 수 있다.
$k exec app -- cat /data/out.txt
...
Sat May 13 05:58:23 UTC 2023
Sat May 13 05:58:28 UTC 2023
Sat May 13 06:07:12 UTC 2023
Sat May 13 06:07:17 UTC 2023

$kubectl delete pod app && kubectl delete pvc ebs-snapshot-restored-claim && kubectl delete volumesnapshots ebs-volume-snapshot
pod "app" deleted
persistentvolumeclaim "ebs-snapshot-restored-claim" deleted
volumesnapshot.snapshot.storage.k8s.io "ebs-volume-snapshot" deleted

아래는 AWS 콘솔을 통해 EBS 스냅샷을 확인한 모습이다.

AWS EFS Controller

먼저 EFS(Amazon Elastic File System)는 그림과 같이 여러 가용영역과 호환가능한 파일시스템이다.

아래는 EFS를 통해 PV,PVC를 생성하는 실습내용이다.

먼저 작업용 EC2에도 efs mount를 진행한다

1
2
3
4
$mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-0b2bc433bc72b5727.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs
$df -hT --type nfs4
Filesystem                                              Type  Size  Used Avail Use% Mounted on
fs-0b2bc433bc72b5727.efs.ap-northeast-2.amazonaws.com:/ nfs4  8.0E     0  8.0E   0% /mnt/myefs

EFS-CSI 설치과정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 정보 확인
$aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output textfs-0f70f92a10cc5af1a
# json 다운로드
$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
{
    "Policy": {
        "PolicyName": "AmazonEKS_EFS_CSI_Driver_Policy",
        "PolicyId": "ANPA4VUOQIVV6AGY3ABHQ",
        "Arn": "arn:aws:iam::871103481195:policy/AmazonEKS_EFS_CSI_Driver_Policy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2023-05-13T06:21:41+00:00",
        "UpdateDate": "2023-05-13T06:21:41+00:00"
    }
}
# efs-csi 생성
$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
2023-05-13 15:22:26 [ℹ]  created serviceaccount "kube-system/efs-csi-controller-sa"

# EFS 컨트롤러를 확인한다.
$eksctl get iamserviceaccount --cluster myeks
NAMESPACE	NAME				ROLE ARN
kube-system	aws-load-balancer-controller	arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-1OWZIVWLLX9JM
kube-system	ebs-csi-controller-sa		arn:aws:iam::871103481195:role/AmazonEKS_EBS_CSI_DriverRole
kube-system	efs-csi-controller-sa		arn:aws:iam::871103481195:role/eksctl-myeks-addon-iamserviceaccount-kube-sy-Role1-183ANMNPO7E68

[efs 구성요소를 helm을 통해 배포]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
"aws-efs-csi-driver" has been added to your repositories
$helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "aws-efs-csi-driver" chart repository
Update Complete. ⎈Happy Helming!⎈
$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
Release "aws-efs-csi-driver" does not exist. Installing it now.
NAME: aws-efs-csi-driver
LAST DEPLOYED: Sat May 13 16:18:05 2023
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
To verify that aws-efs-csi-driver has started, run:

$helm list -n kube-system
NAME              	NAMESPACE  	REVISION	UPDATED                                	STATUS  	CHART                   	APP VERSION
aws-efs-csi-driver	kube-system	1       	2023-05-13 16:18:05.885009102 +0900 KST	deployed	aws-efs-csi-driver-2.4.3	1.5.5

$kubectl get pod -n kube-system -l "app.kubernetes.io/name=aws-efs-csi-driver,app.kubernetes.io/instance=aws-efs-csi-driver"
NAME                                  READY   STATUS              RESTARTS   AGE
efs-csi-controller-6f64dcc5dc-6hbkc   0/3     ContainerCreating   0          9s
efs-csi-controller-6f64dcc5dc-wf4ks   0/3     ContainerCreating   0          9s
efs-csi-node-qs7sr                    0/3     ContainerCreating   0          9s
efs-csi-node-xtvvm                    0/3     ContainerCreating   0          9s
efs-csi-node-zvzcs                    0/3     ContainerCreating   0          9s

[이제 K8S에서 제공해준 샘플 코드를 다운받아, 실습을 진행한다.]

정적 프로비저닝 방식이라 위의 EBS와는 다르다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
$git clone https://github.com/kubernetes-sigs/aws-efs- qq-driver.git /root/efs-csiCloning into '/root/efs-csi'...
remote: Enumerating objects: 16052, done.
remote: Counting objects: 100% (269/269), done.
remote: Compressing objects: 100% (185/185), done.
remote: Total 16052 (delta 65), reused 236 (delta 57), pack-reused 15783
Receiving objects: 100% (16052/16052), 16.90 MiB | 20.51 MiB/s, done.
Resolving deltas: 100% (7777/7777), done.

$cd /root/efs-csi/examples/kubernetes/multiple_pods/specs && tree
.
├── claim.yaml
├── pod1.yaml
├── pod2.yaml
├── pv.yaml
└── storageclass.yaml
0 directories, 5 files

$cat storageclass.yaml | yh
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com

$kubectl apply -f storageclass.yaml
storageclass.storage.k8s.io/efs-sc created

$k get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
efs-sc          efs.csi.aws.com         Delete          Immediate              false                  10s
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  139m
gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   109m

# 현재 EFS ID를 지정하는 모습
$EfsFsId=$(aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text)
$sed -i "s/fs-4af69aab/$EfsFsId/g" pv.yaml
$echo $EfsFsId
fs-0f70f92a10cc5af1a

$cat pv.yaml | yh
apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: efs-sc
  csi:
    driver: efs.csi.aws.com
    volumeHandle: fs-0f70f92a10cc5af1a

EFS 정적 프로비저닝

[정적 프로비저닝방식으로 PV, PVC 생성]

정적 프로비저닝이기에 PV를 먼저 생성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$kubectl apply -f pv.yaml
persistentvolume/efs-pv created

$cat claim.yaml | yh
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi

$kubectl apply -f claim.yaml
persistentvolumeclaim/efs-claim created

$k get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
efs-pv   5Gi        RWX            Retain           Bound    default/efs-claim   efs-sc                  57s

[이전과 유사한 data -u 로그를 찍는 파드를 2개 생성]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$kubectl apply -f pod1.yaml,pod2.yaml
pod/app1 created
pod/app2 created

$k get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS        CLAIM               STORAGECLASS   REASON   AGE
efs-pv   5Gi        RWX            Retain           Terminating   default/efs-claim   efs-sc                  3m32s

$cat pod1.yaml pod2.yaml | yh
apiVersion: v1
kind: Pod
metadata:
  name: app1
...
    args: ["-c", "while true; do echo $(date -u) >> /data/out1.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: efs-claim

apiVersion: v1
kind: Pod
metadata:
  name: app2
..
    args: ["-c", "while true; do echo $(date -u) >> /data/out2.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: efs-claim

[EFS 특징, 여러 AZ에 공유되는 것을 확인한다.]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$k exec -it app1 -- 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
$k exec -it app1 -- cat /data/out1.txt
...
Sat May 13 07:25:50 UTC 2023
Sat May 13 07:25:55 UTC 2023

# 작업용 EC2에도 공유되는 모습.
$tree /mnt/myefs/
/mnt/myefs/
├── out1.txt
└── out2.txt

0 directories, 2 files

$kubectl delete pvc efs-claim && kubectl delete pv efs-pv && kubectl delete sc efs-sc
persistentvolumeclaim "efs-claim" deleted
persistentvolume "efs-pv" deleted
storageclass.storage.k8s.io "efs-sc" deleted

아래는 EFS의 네트워크 모습이다. 현재 노드가 있는 3개의 가용영역이 모두 있는 것을 확인할 수 있다

EFS 동적 프로비저닝

위에서는 정적 프로비저닝으로 테스트를 진행했지만, EFS에는 동적 프로비저닝 기능이 1.2 버전부터 지원하여 동적 프로비저닝도 진행해봤다. 위의 내용에서 동적프로비저닝을 테스트하면 다음과 같이 자동으로 PV가 할당되지 않아 PVC와 파드가 생성되지 못한다.

EFS에 대한 동적프로비저닝에 대한 설명은 AWS BlogGitHub에서 자세하게 확인할 수 있다

다른 과정은 앞에서와 똑같고, 스토리지 클래스와 파드만 달라진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
cat storageclass.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com
parameters:
  provisioningMode: efs-ap
  fileSystemId: fs-0b2bc433bc72b5727 # 자신의 EFS ID
  directoryPerms: "700"
cat pod.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: efs-app
spec:
  containers:
    - name: app
      image: centos
      command: ["/bin/sh"]
      args: ["-c", "while true; do echo $(date -u) >> /data/out; sleep 5; done"]
      volumeMounts:
        - name: persistent-storage
          mountPath: /data
  volumes:
    - name: persistent-storage
      persistentVolumeClaim:
        claimName: efs-claim
1
2
3
4
5
6
7
8
9
10
11
12
# 모니터링 
Every 2.0s: kubectl get sc efs-sc; echo; kubectl get pv,pvc,pod                                            Sat May 13 16:30:46 2023

NAME     PROVISIONER	   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
efs-sc   efs.csi.aws.com   Delete          Immediate           false                  10m

NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/efs-claim   Pending                                      efs-sc         42s

NAME	   READY   STATUS    RESTARTS   AGE
pod/app1   0/1     Pending   0          33s
pod/app2   0/1     Pending   0          33s

[파드의 로그]

1
2
3
4
5
Events:
  Type     Reason            Age                 From               Message
  ----     ------            ----                ----               -------
  Warning  FailedScheduling  15s (x4 over 100s)  default-scheduler  0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
  Normal   Scheduled         7s                  default-scheduler  Successfully assigned default/app1 to ip-192-168-2-52.ap-northeast-2.compute.internal

[PVC 로그]

1
2
3
4
5
6
7
8
Events:
  Type     Reason                Age                  From                                                                                                   Message
  ----     ------                ----                 ----                                                                                                   -------
  Normal   Provisioning          96s (x7 over 2m39s)  efs.csi.aws.com_ip-192-168-3-190.ap-northeast-2.compute.internal_d0fa0b24-c916-44ba-befc-922b6eb49470  External provisioner is provisioning volume for claim "default/efs-claim"
  Warning  ProvisioningFailed    96s (x7 over 2m39s)  efs.csi.aws.com_ip-192-168-3-190.ap-northeast-2.compute.internal_d0fa0b24-c916-44ba-befc-922b6eb49470  failed to provision volume with StorageClass "efs-sc": rpc error: code = InvalidArgument desc = Missing provisioningMode parameter
  Normal   Provisioning          96s (x7 over 2m39s)  efs.csi.aws.com_ip-192-168-1-203.ap-northeast-2.compute.internal_74db5f2e-b1e1-4f54-b9b2-cede890dddf7  External provisioner is provisioning volume for claim "default/efs-claim"
  Warning  ProvisioningFailed    96s (x7 over 2m39s)  efs.csi.aws.com_ip-192-168-1-203.ap-northeast-2.compute.internal_74db5f2e-b1e1-4f54-b9b2-cede890dddf7  failed to provision volume with StorageClass "efs-sc": rpc error: code = InvalidArgument desc = Missing provisioningMode parameter
  Normal   ExternalProvisioning  80s (x8 over 2m39s)  persistentvolume-controller                                                                            waiting for a volume to be created, either by external provisioner "efs.csi.aws.com" or manually created by system administrator
This post is licensed under CC BY 4.0 by the author.