IT/Infra&Cloud

[aws] EKS Storage & Node

Hayley Shim 2023. 10. 29. 01:05

안녕하세요. CloudNet@ K8S Study를 진행하며 해당 내용을 이해하고 공유하기 위해 작성한 글입니다. 해당 내용은 EKS docs와 workshop을 기본으로 정리하였습니다.

실습 환경

기본 설정 및 EFS 확인

# YAML 파일 다운로드(EFS 생성 추가 & 기본 설정)
$ 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=kp-hayley3 SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=eks-hayley --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 ~/.ssh/kp-hayley3.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)

# default 네임스페이스 적용
$ kubectl ns default

# (옵션) context 이름 변경
$ NICK=<각자 자신의 닉네임>
$ NICK=hayley
$ kubectl ctx
iam-root-account@eks-hayley.ap-northeast-2.eksctl.io

$ kubectl config rename-context admin@myeks.ap-northeast-2.eksctl.io $NICK@myeks
# EFS 마운트: AWS 관리콘솔 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
$ mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-0af2a7f5ee43292ef.efs.ap-northeast-2.amazonaws.com:/ /mnt/myefs

$ df -hT --type nfs4
Filesystem                                              Type  Size  Used Avail Use% Mounted on
fs-0af2a7f5ee43292ef.efs.ap-northeast-2.amazonaws.com:/ nfs4  8.0E     0  8.0E   0% /mnt/myefs

$ mount | grep nfs4
fs-0af2a7f5ee43292ef.efs.ap-northeast-2.amazonaws.com:/ on /mnt/myefs type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,noresvport,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.1.100,local_lock=none,addr=192.168.1.145)

$ echo "efs file test" > /mnt/myefs/memo.txt
$ cat /mnt/myefs/memo.txt
efs file test
$ rm -f /mnt/myefs/memo.txt

# 스토리지클래스 및 CSI 노드 확인
$ kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  91m

$ 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-13T09:03:36Z"
  name: gp2
  resourceVersion: "264"
  uid: 688f5cfe-5aec-4bf9-9901-a10695d3833c
parameters: 
  fsType: ext4
  type: gp2
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

$ kubectl get csinodes
NAME                                               DRIVERS   AGE
ip-192-168-1-59.ap-northeast-2.compute.internal    0         84m
ip-192-168-2-120.ap-northeast-2.compute.internal   0         84m
ip-192-168-3-239.ap-northeast-2.compute.internal   0         84m

# 노드 정보 확인
$ 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-59.ap-northeast-2.compute.internal    Ready    <none>   85m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2a
ip-192-168-2-120.ap-northeast-2.compute.internal   Ready    <none>   85m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2b
ip-192-168-3-239.ap-northeast-2.compute.internal   Ready    <none>   85m   v1.24.11-eks-a59e1f0   t3.medium       ON_DEMAND      ap-northeast-2c

$ eksctl get iamidentitymapping --cluster eks-hayley
ARN            USERNAME  GROUPS     ACCOUNT
arn:aws:iam::90XXXXXX:role/eksctl-eks-hayley-nodegroup-ng1-NodeInstanceRole-1M4O84V8AHOHK system:node:{{EC2PrivateDNSName}} system:bootstrappers,system:nodes

# 노드 IP 확인 및 PrivateIP 변수 지정
$ N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
$ N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
$ N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
$ echo "export N1=$N1" >> /etc/profile
$ echo "export N2=$N2" >> /etc/profile
$ echo "export N3=$N3" >> /etc/profile
$ echo $N1, $N2, $N3
192.168.1.59, 192.168.2.120, 192.168.3.239

# 노드 보안그룹 ID 확인
$ NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
$ aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32
{
    "Return": true,
    "SecurityGroupRules": [
        {
            "SecurityGroupRuleId": "sgr-0849c7213b45658f3",
            "GroupId": "sg-0cbd71123edc870d9",
            "GroupOwnerId": "903XXXXXXX",
            "IsEgress": false,
            "IpProtocol": "-1",
            "FromPort": -1,
            "ToPort": -1,
            "CidrIpv4": "192.168.1.100/32"
        }
    ]
}

# 워커 노드 SSH 접속
$ ssh ec2-user@$N1 hostname
$ ssh ec2-user@$N2 hostname
$ ssh ec2-user@$N3 hostname

# 노드에 툴 설치
ssh ec2-user@$N1 sudo yum install links tree jq tcpdump sysstat -y
ssh ec2-user@$N2 sudo yum install links tree jq tcpdump sysstat -y
ssh ec2-user@$N3 sudo yum install links tree jq tcpdump sysstat -y

AWS LB/ExternalDNS, 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=<자신의 도메인>
$ MyDomain=wellbeconnected.com
$ MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
$ echo $MyDomain, $MyDnzHostedZoneId
wellbeconnected.com, /hostedzone/Z06204681HPKV2R55I4OR

$ curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
$ MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
serviceaccount/external-dns created
clusterrole.rbac.authorization.k8s.io/external-dns created
clusterrolebinding.rbac.authorization.k8s.io/external-dns-viewer created
deployment.apps/external-dns created

# kube-ops-view : 노드에 설치된 Pod 의 개수를 보기 편하게 해주는 도구
$ 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"
Kube Ops View URL = http://kubeopsview.wellbeconnected.com:8080/#scale=1.5

설치 정보 확인

# 이미지 정보 확인
kubectl get pods --all-namespaces -o jsonpath="{.items[*].spec.containers[*].image}" | tr -s '[[:space:]]' '\n' | sort | uniq -c
      3 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/amazon-k8s-cni:v1.12.6-eksbuild.2
      2 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/eks/coredns:v1.9.3-eksbuild.3
      3 602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/eks/kube-proxy:v1.24.10-minimal-eksbuild.2
      1 hjacobs/kube-ops-view:20.4.0
      2 public.ecr.aws/eks/aws-load-balancer-controller:v2.5.1
      1 registry.k8s.io/external-dns/external-dns:v0.13.4

# eksctl 설치/업데이트 addon 확인
$ eksctl get addon --cluster $CLUSTER_NAME
NAME  VERSION   STATUS ISSUES IAMROLE        UPDATE AVAILABLE CONFIGURATION VALUES
coredns  v1.9.3-eksbuild.3 ACTIVE 0         
kube-proxy v1.24.10-eksbuild.2 ACTIVE 0         
vpc-cni  v1.12.6-eksbuild.2 ACTIVE 0 arn:aws:iam::903XXXXX:role/eksctl-eks-hayley-addon-vpc-cni-Role1-1GFVAUHPMPK06

# IRSA 확인
$ eksctl get iamserviceaccount --cluster $CLUSTER_NAME
NAMESPACE NAME    ROLE ARN
kube-system aws-load-balancer-controller arn:aws:iam::903XXXXXX:role/eksctl-eks-hayley-addon-iamserviceaccount-ku-Role1-AMBNTVWDKQD5

스토리지 이해

  • 파드 내부의 데이터는 파드가 정지되면 모두 삭제되기 때문에 파드가 모두 상태가 없는(Stateless) 애플리케이션으로 언제나 종료될 수 있는 것을 감안하여 설계해야 합니다.

[참고 — Kubernetes를 위한 영구 스토리지 적용하기]

Kubernetes의 데이터 지속성

상태 저장 애플리케이션을 실행할 때, 영구 스토리지가 없으면 데이터는 포드 또는 컨테이너의 수명 주기에 연결됩니다. 포드가 충돌하거나 종료되면 데이터는 손실됩니다.

이러한 데이터 손실을 방지하고 Kubernetes에서 상태 저장 애플리케이션을 실행하려면 세 가지 간단한 스토리지 요구 사항을 준수해야 합니다:

  1. 스토리지는 포드의 수명 주기에 의존해서는 안 됩니다.
  2. 스토리지는 Kubernetes 클러스터의 모든 포드 및 노드에서 사용할 수 있어야 합니다.
  3. 스토리지는 충돌이나 애플리케이션 오류에 관계없이 가용성이 높아야 합니다.

Kubernetes볼륨(Kubernetes volumes)

임시 스토리지(Ephemeral storage)

  • 컨테이너는 temporary filesystem(tmpfs)를 사용하여 파일을 읽고 쓸 수 있습니다. 그러나 임시 스토리지는 세 가지의 저장소 요구 사항을 충족하지 않습니다. 컨테이너가 충돌하는 경우 temporary filesystem은 손실되고, 컨테이너는 깨끗한 상태로 다시 시작됩니다. 또한 여러 컨테이너가 temporary filesystem을 공유할 수 없습니다.

임시 볼륨(Ephemeral volumes)

  • 임시 Kubernetes Volume은 임시 스토리지가 직면한 두 가지 문제를 모두 해결합니다. 임시 Volume의 수명은 Pod와 연결됩니다. 이를 통해 컨테이너를 안전하게 재시작하고 Pod내의 컨테이너간 데이터를 공유할 수 있습니다. 그러나 Pod가 삭제되는 즉시 Volume도 삭제가 되므로, 이는 여전히 세 가지 요구 사항을 충족하지 못합니다.

https://aws.amazon.com/ko/blogs/tech/persistent-storage-for-kubernetes/

  • 따라서, 데이터베이스(파드)처럼 데이터 보존이 필요합니다.

상태가 있는(Stateful) 애플리케이션 : PV & PVC

스토리지와 포드 분리: 영구 볼륨(Persistent Volumes)

  • Persistent Volumes을 사용하면 애플리케이션, 컨테이너, 포드, 노드 또는 클러스터 자체의 수명 주기와 관계없이 데이터가 지속됩니다.
  • Persistent Volume(PV) 객체는 애플리케이션 데이터를 유지하는 데 사용되는 스토리지 볼륨을 나타냅니다. PV는 Kubernetes Pods의 수명 주기와 별개로 자체의 수명 주기를 가집니다.

PV는 기본적으로 두 가지로 구성됩니다.

  • PersistentVolume이라고 불리우는 백엔드 기술
  • Kubernetes에 볼륨을 마운트하는 방법을 알려주는 접근 모드

백엔드 기술(Backend technology)

  • csi: Container Storage Interface(CSI) → (예: Amazon EFS , Amazon EBS , Amazon FSx 등)
  • iscsi: iSCSI(SCSI over IP) 스토리지
  • local: 노드에 마운트된 로컬 저장 장치
  • nfs: 네트워크 파일 시스템(NFS) 스토리지

접근 모드(Access mode)

  • ReadWriteOnce: 볼륨은 동시에 하나의 노드에서만 읽기/쓰기를 허용합니다.
  • ReadOnlyMany: 볼륨은 동시에 여러 노드에서 읽기 전용 모드를 허용합니다.
  • ReadWriteMany: 볼륨은 동시에 여러 노드에서 읽기/쓰기를 허용합니다.

영구 볼륨 클레임(Persistent volume claims)

  • Persistent Volume(PV)은 실제 스토리지 볼륨을 나타냅니다. Kubernetes는 PV를 포드에 연결하는데 필요한 추가 추상화 계층인 PersistentVolumeClaim(PVC)을 가지고 있습니다.
  • PV 는 실제 스토리지 볼륨을 나타내며, PVC는 Pod가 실제 스토리지를 얻기 위해 수행하는 스토리지 요청을 나타냅니다.

기본적으로 PV 객체를 직접 Pod에 마운트할 수 없습니다. 이것은 명시적으로 요청되어져야 합니다. 그리고 그 요청은 PVC객체를 생성하고 Pod에 연결함으로써 달성됩니다. 이것이 이 추가 추상화 계층이 존재하는 유일한 이유입니다. PVC와 PV는 1:1의 관계가 있습니다(PV는 하나의 PVC에만 연결될 수 있음).

https://aws.amazon.com/ko/blogs/tech/persistent-storage-for-kubernetes/

CSI(Container Storage Interface) 드라이버

  • CSI (Container Storage Interface)는 Kubernetes에서 다양한 스토리지 솔루션을 쉽게 사용할 수 있도록 설계된 추상화입니다. 다양한 스토리지 공급업체는 CSI 표준을 구현하는 자체 드라이버를 개발하여 스토리지 솔루션이 Kubernetes와 함께 작동하도록 할 수 있습니다(연결되는 스토리지 솔루션의 내부에 관계없이). AWS는 Amazon EBS , Amazon EFS  Amazon FSx for Lustre 용 CSI 플러그인을 제공하고 있습니다.
  • Kubernetes 개발자는 Kubernetes 내부에 내장된 provisioner (in-tree)를 모두 삭제하고, 별도의 controller Pod을 통해 동적 provisioning을 사용할 수 있도록 만들었습니다.

정적 프로비저닝(Static provisioning)

  • “영구 볼륨 클레임” 섹션에서 설명한 바와 같이, 먼저 관리자가 하나 이상의 PV를 생성하고 애플리케이션 개발자는 PVC를 생성합니다. 이를 정적 프로비저닝이라고 합니다. Kubernetes에서 PV 및 PVC를 수동으로 만들어야 하므로 정적 입니다. 대규모 환경에서는 관리하기가 점점 더 어려워질 수 있으며, 특히 수백 개의 PV와 PVC를 관리하는 경우에는 더욱 그렇습니다.

동적 프로비저닝(Dynamic provisioning)

  • 동적 프로비저닝을 사용하면 PV객체를 생성할 필요가 없습니다. 대신에, PVC를 생성할 때 내부적으로 자동으로 생성됩니다. Kubernetes는 Storage Class라는 다른 객체를 사용하여 이를 수행합니다.
  • Storage Class는 컨테이너 애플리케이션에 사용되는 백엔드 영구 스토리지(예: Amazon EFS 파일 스토리지, Amazon EBS 블록 스토리지 등)의 클래스를 정의하는 추상화입니다.
  • Storage Class 객체는 Kubernetes가 매우 다양한 스토리지 기술을 처리할 수 있는 이유입니다. Pod 관점에서 볼 때, EFS 볼륨, EBS 볼륨, NFS 드라이브 또는 기타 어떤 것이든, 그 Pod는 PVC 객체만 보게 됩니다. 실제 스토리지 기술을 다루는 모든 내부적인 논리는 Storage Class 객체가 사용하는 프로비저너에 의해 구현됩니다.

https://aws.amazon.com/ko/blogs/tech/persistent-storage-for-kubernetes/

  • 퍼시스턴트 볼륨의 사용이 끝났을 때 해당 볼륨은 어떻게 초기화할 것인지 별도로 설정할 수 있는데, 쿠버네티스는 이를 Reclaim Policy 라고 부릅니다.
  • CSI 를 사용하면, K8S 의 공통화된 CSI 인터페이스를 통해 다양한 프로바이더를 사용할 수 있습니다.
  • 일반적인 CSI driver의 구조입니다. AWS EBS CSI driver 역시 아래와 같은 구조를 가지는데, 오른쪽 StatefulSet 또는 Deployment로 배포된 controller Pod이 AWS API를 사용하여 실제 EBS volume을 생성하는 역할을 합니다. 왼쪽 DaemonSet으로 배포된 node Pod은 AWS API를 사용하여 Kubernetes node (EC2 instance)에 EBS volume을 attach 해줍니다.

Node-specific Volume Limits

  • AWS EC2 Type에 따라 볼륨 최대 제한 : 25개 ~ 39개
# 확인
$ kubectl describe node | grep Allocatable: -A1

Allocatable:
  attachable-volumes-aws-ebs:  25
--

기본 컨테이너 환경의 임시 파일시스템 사용

# 파드 배포
# date 명령어로 현재 시간을 10초 간격으로 /home/pod-out.txt 파일에 저장
$ 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

# 파일 확인
$ kubectl get pod
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          8s

$ kubectl exec busybox -- tail -f /home/pod-out.txt
Sat May 13 10:59:08 UTC 2023
Sat May 13 10:59:18 UTC 2023

# 파드 삭제 후 다시 생성 후 파일 정보 확인 > 이전 기록이 보존되어 있는지?
# 아니다. PV가 필요한 이유
kubectl delete pod busybox
kubectl apply -f date-busybox-pod.yaml
kubectl exec busybox -- tail -f /home/pod-out.txt

# 실습 완료 후 삭제
$ kubectl delete pod busybox

호스트 Path 를 사용하는 PV/PVC : local-path-provisioner 스트리지 클래스 배포

# 배포
$ 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-all -n local-path-storage
NAME                                                   NAMESPACE           AGE
configmap/kube-root-ca.crt                             local-path-storage  20s  
configmap/local-path-config                            local-path-storage  20s  
pod/local-path-provisioner-759f6bd7c9-mz2jq            local-path-storage  20s  
serviceaccount/default                                 local-path-storage  20s  
serviceaccount/local-path-provisioner-service-account  local-path-storage  20s  
deployment.apps/local-path-provisioner                 local-path-storage  20s  
replicaset.apps/local-path-provisioner-759f6bd7c9      local-path-storage  20s  

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

$ kubectl describe cm -n local-path-storage local-path-config
Name:         local-path-config
Namespace:    local-path-storage
Labels:       <none>
Annotations:  <none>

Data
====
helperPod.yaml:
----
apiVersion: v1
kind: Pod
metadata:
  name: helper-pod
spec:
  containers:
  - name: helper-pod
    image: busybox
    imagePullPolicy: IfNotPresent
setup:
----
#!/bin/sh
set -eu
mkdir -m 0777 -p "$VOL_DIR"
teardown:
----
#!/bin/sh
set -eu
rm -rf "$VOL_DIR"
config.json:
----
{
        "nodePathMap":[
        {
                "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
                "paths":["/opt/local-path-provisioner"]
        }
        ]
}

BinaryData
====

Events:  <none>

$ kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  118m
local-path      rancher.io/local-path   Delete          WaitForFirstConsumer   false                  85s

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

PV/PVC 를 사용하는 파드 생성

# PVC 생성
$ curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/localpath1.yaml
$ cat localpath1.yaml | yh
apiVersion: v1
kind: PersistentVolumeClaim
metadata: 
  name: localpath-claim
spec: 
  accessModes: 
    - ReadWriteOnce
  resources: 
    requests: 
      storage: 1Gi
  storageClassName: "local-path"
$ kubectl apply -f localpath1.yaml

# PVC 확인
$ kubectl get pvc
NAME              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
localpath-claim   Bound    pvc-917aead0-f9bc-4cb2-9e50-b5d6da11078e   1Gi        RWO            local-path     21m

$ kubectl describe pvc

# 파드 생성
$ curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/localpath2.yaml
$ cat localpath2.yaml | yh
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: localpath-claim

$ kubectl apply -f localpath2.yaml

# 파드 확인
$ kubectl get pod,pv,pvc
NAME          READY   STATUS    RESTARTS   AGE
pod/app       1/1     Running   0          21m
pod/busybox   1/1     Running   0          2m23s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS   REASON   AGE
persistentvolume/pvc-917aead0-f9bc-4cb2-9e50-b5d6da11078e   1Gi        RWO            Delete           Bound    default/localpath-claim   local-path              21m

NAME                                    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/localpath-claim   Bound    pvc-917aead0-f9bc-4cb2-9e50-b5d6da11078e   1Gi        RWO            local-path     22m

$ kubectl describe pv    # Node Affinity 확인 : 해당 Node에 PV가 존재함
$ kubectl exec -it app -- tail -f /data/out.txt
Sat May 13 11:03:27 UTC 2023
Sat May 13 11:03:32 UTC 2023
Sat May 13 11:03:37 UTC 2023
... 

# 워커노드 중 현재 파드가 배포되어 있다만, 아래 경로에 out.txt 파일 존재 확인
$ ssh ec2-user@$N3 tree /opt/local-path-provisioner
/opt/local-path-provisioner
└── pvc-917aead0-f9bc-4cb2-9e50-b5d6da11078e_default_localpath-claim
    └── out.txt

1 directory, 1 file

# 해당 워커노드 자체에서 out.txt 파일 확인 : 아래 굵은 부분은 각자 실습 환경에 따라 다름
$ ssh ec2-user@$N1 tail -f /opt/local-path-provisioner/pvc-917aead0-f9bc-4cb2-9e50-b5d6da11078e_default_localpath-claim/out.txt
Sun Jan 29 05:13:45 UTC 2023
... 

파드 삭제 후 파드 재생성해서 데이터 유지 되는지 확인

# 파드 삭제 후 PV/PVC 확인
$ kubectl delete pod app
$ kubectl get pod,pv,pvc
NAME          READY   STATUS    RESTARTS   AGE
pod/busybox   1/1     Running   0          4m29s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS   REASON   AGE
persistentvolume/pvc-917aead0-f9bc-4cb2-9e50-b5d6da11078e   1Gi        RWO            Delete           Bound    default/localpath-claim   local-path              23m

NAME                                    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/localpath-claim   Bound    pvc-917aead0-f9bc-4cb2-9e50-b5d6da11078e   1Gi        RWO            local-path     24m

$ ssh ec2-user@$N3 tree /opt/local-path-provisioner
/opt/local-path-provisioner
└── pvc-917aead0-f9bc-4cb2-9e50-b5d6da11078e_default_localpath-claim
    └── out.txt

1 directory, 1 file

# 파드 다시 실행
kubectl apply -f localpath2.yaml
 
# 확인
$ kubectl exec -it app -- head /data/out.txt
Sat May 13 11:03:27 UTC 2023
Sat May 13 11:03:32 UTC 2023
Sat May 13 11:03:37 UTC 2023

$ kubectl exec -it app -- tail -f /data/out.txt
Sat May 13 11:26:18 UTC 2023
Sat May 13 11:26:23 UTC 2023
Sat May 13 11:26:28 UTC 2023

# 다음 실습을 위해서 파드와 PVC 삭제
kubectl delete pod app
kubectl get pv,pvc
kubectl delete pvc localpath-claim

# 확인
$ kubectl get pv
No resources found

$ ssh ec2-user@$N3 tree /opt/local-path-provisioner
/opt/local-path-provisioner

0 directories, 0 files

AWS EBS Controller

  • persistentvolume, persistentvolumeclaim의 accessModes는 ReadWriteOnce로 설정해야 합니다. EBS스토리지 기본 설정이 동일 AZ에 있는 EC2 인스턴스만 연결 할 수 있기 때문입니다.

설치 Amazon EBS CSI driver as an Amazon EKS add-on

# 아래는 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
...

# IRSA 설정 : 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

# IRSA 확인
$ kubectl get sa -n kube-system ebs-csi-controller-sa -o yaml | head -5
$ eksctl get iamserviceaccount --cluster eks-hayley
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}
AME   VERSION   STATUS  ISSUES IAMROLE           UPDATE AVAILABLE CONFIGURATION VALUES
aws-ebs-csi-driver v1.18.0-eksbuild.1 CREATING 0 arn:aws:iam::90XXXXXX:role/AmazonEKS_EBS_CSI_DriverRole      
coredns   v1.9.3-eksbuild.3 ACTIVE  0              
kube-proxy  v1.24.10-eksbuild.2 ACTIVE  0              
vpc-cni   v1.12.6-eksbuild.2 ACTIVE  0 arn:aws:iam::903XXXXX:role/eksctl-eks-hayley-addon-vpc-cni-Role1-1GFVAUHPMPK06 

$ kubectl get deploy,ds -l=app.kubernetes.io/name=aws-ebs-csi-driver -n kube-system
NAMESPACE NAME    ROLE ARN
kube-system aws-load-balancer-controller arn:aws:iam::903XXXXX:role/eksctl-eks-hayley-addon-iamserviceaccount-ku-Role1-AMBNTVWDKQD5
kube-system ebs-csi-controller-sa  arn:aws:iam::903XXXXX:role/AmazonEKS_EBS_CSI_DriverRole

$ kubectl get pod -n kube-system -l 'app in (ebs-csi-controller,ebs-csi-node)'
NAME                                  READY   STATUS    RESTARTS   AGE
ebs-csi-controller-5d4666c6f8-rs7v9   6/6     Running   0          40s
ebs-csi-controller-5d4666c6f8-zqcsg   6/6     Running   0          40s
ebs-csi-node-6b946                    3/3     Running   0          40s
ebs-csi-node-gd549                    3/3     Running   0          40s
ebs-csi-node-glql8                    3/3     Running   0          40s

$ kubectl get pod -n kube-system -l app.kubernetes.io/component=csi-driver
NAME                                  READY   STATUS    RESTARTS   AGE
ebs-csi-controller-5d4666c6f8-rs7v9   6/6     Running   0          52s
ebs-csi-controller-5d4666c6f8-zqcsg   6/6     Running   0          52s
ebs-csi-node-6b946                    3/3     Running   0          52s
ebs-csi-node-gd549                    3/3     Running   0          52s
ebs-csi-node-glql8                    3/3     Running   0          52s

# 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
NAME                                               DRIVERS   AGE
ip-192-168-1-59.ap-northeast-2.compute.internal    1         172m
ip-192-168-2-120.ap-northeast-2.compute.internal   1         172m
ip-192-168-3-239.ap-northeast-2.compute.internal   1         172m

# gp3 스토리지 클래스 생성
$ kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  3h3m
local-path      rancher.io/local-path   Delete          WaitForFirstConsumer   false                  66m

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

$ kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  3h3m
gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   1s
local-path      rancher.io/local-path   Delete          WaitForFirstConsumer   false                  66m

$ kubectl describe sc gp3 | grep Parameters
Parameters:            allowAutoIOPSPerGBIncrease=true,encrypted=true,type=gp3

PVC/PV 파드 테스트

# 워커노드의 EBS 볼륨 확인 : tag(키/값) 필터링 - 링크
$ aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --output table
$ aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[*].Attachments" | jq
$ aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[*].{ID:VolumeId,Tag:Tags}" | jq
$ aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[].[VolumeId, VolumeType, Attachments[].[InstanceId, State][]][]" | jq
$ aws ec2 describe-volumes --filters Name=tag:Name,Values=$CLUSTER_NAME-ng1-Node --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" | jq
[
  {
    "VolumeId": "vol-0b5a582d3b2847e61",
    "VolumeType": "gp3",
    "InstanceId": "i-02eb4047eb5f2679e",
    "State": "attached"
  },
  {
    "VolumeId": "vol-07f37bcab8743bbff",
    "VolumeType": "gp3",
    "InstanceId": "i-0afbf50137104fc24",
    "State": "attached"
  },
  {
    "VolumeId": "vol-0d3037972ed279ab3",
    "VolumeType": "gp3",
    "InstanceId": "i-03faaddd636088a40",
    "State": "attached"
  }
]

# 워커노드에서 파드에 추가한 EBS 볼륨 확인
$ aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --output table
$ aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[*].{ID:VolumeId,Tag:Tags}" | jq
$ aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" | jq

# 워커노드에서 파드에 추가한 EBS 볼륨 모니터링
$ while true; do aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" --output text; date; sleep 1; done

# PVC 생성
$ cat <<EOT > awsebs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  storageClassName: gp3
EOT
$ kubectl apply -f awsebs-pvc.yaml
$ kubectl get pvc,pv
NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/ebs-claim   Pending                                      gp3            4s

# 파드 생성
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

# PVC, 파드 확인
$ kubectl get pvc,pv,pod

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

# PV 상세 확인 : nodeAffinity 내용의 의미는? 해당 AZ에 배포
kubectl get pv -o yaml | yh
...
    nodeAffinity: 
      required: 
        nodeSelectorTerms: 
        - matchExpressions: 
          - key: topology.ebs.csi.aws.com/zone
            operator: In
            values: 
            - ap-northeast-2c
...

$ kubectl get node --label-columns=topology.ebs.csi.aws.com/zone,topology.kubernetes.io/zone
NAME                                               STATUS   ROLES    AGE    VERSION                ZONE              ZONE
ip-192-168-1-59.ap-northeast-2.compute.internal    Ready    <none>   177m   v1.24.11-eks-a59e1f0   ap-northeast-2a   ap-northeast-2a
ip-192-168-2-120.ap-northeast-2.compute.internal   Ready    <none>   177m   v1.24.11-eks-a59e1f0   ap-northeast-2b   ap-northeast-2b
ip-192-168-3-239.ap-northeast-2.compute.internal   Ready    <none>   177m   v1.24.11-eks-a59e1f0   ap-northeast-2c   ap-northeast-2c

$ kubectl describe node | more

# 파일 내용 추가 저장 확인
$ kubectl exec app -- tail -f /data/out.txt
Sat May 13 12:09:30 UTC 2023
Sat May 13 12:09:30 UTC 2023
Sat May 13 12:09: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-c1dfda9b-e4a0-4f5d-8655-fd9d2561865d  ebs-claim  default    ip-192-168-3-239.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'
kubectl exec -it app -- sh -c 'df -hT --type=ext4'Filesystem     Type     Size  Used Avail Use% Mounted on
overlay        overlay   30G  3.7G   27G  13% /

$ kubectl exec -it app -- sh -c 'df -hT --type=ext4'
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme1n1   ext4  3.8G   28K  3.8G   1% /data

볼륨 증가 — 링크 ⇒ 늘릴수는 있어도 줄일수는 없다

# 현재 pv 의 이름을 기준하여 4G > 10G 로 증가 : .spec.resources.requests.storage의 4Gi 를 10Gi로 변경
$ kubectl get pvc ebs-claim -o jsonpath={.spec.resources.requests.storage} ; echo
4Gi

$ kubectl get pvc ebs-claim -o jsonpath={.status.capacity.storage} ; echo
4Gi

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

$ kubectl patch pvc ebs-claim -p '{"status":{"capacity":{"storage":"10Gi"}}}' # status 는 바로 위 커멘드 적용 후 EBS 10Gi 확장 후 알아서 10Gi 반영됨

# 확인 : 볼륨 용량 수정 반영이 되어야 되니, 수치 반영이 조금 느릴수 있다
$ kubectl exec -it app -- sh -c 'df -hT --type=ext4'
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme1n1   ext4  3.8G   28K  3.8G   1% /data

$ kubectl df-pv
 PV NAME                                   PVC NAME   NAMESPACE  NODE NAME                                         POD NAME  VOLUME MOUNT NAME   SIZE  USED  AVAILABLE  %USED  IUSED  IFREE   %IUSED 
 pvc-c1dfda9b-e4a0-4f5d-8655-fd9d2561865d  ebs-claim  default    ip-192-168-3-239.ap-northeast-2.compute.internal  app       persistent-storage  3Gi   28Ki  3Gi        0.00   12     262132  0.00

$ aws ec2 describe-volumes --volume-ids $(kubectl get pv -o jsonpath="{.items[0].spec.csi.volumeHandle}") | jq

# 삭제
$ kubectl delete pod app & kubectl delete pvc ebs-claim

AWS Volume SnapShots Controller

Volumesnapshots 컨트롤러 설치

# (참고) EBS CSI Driver에 snapshots 기능 포함 될 것으로 보임
$ kubectl describe pod -n kube-system -l app=ebs-csi-controller

# 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-13T12:38:27Z
volumesnapshotcontents.snapshot.storage.k8s.io   2023-05-13T12:38:27Z
volumesnapshots.snapshot.storage.k8s.io          2023-05-13T12:38:27Z

$ 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
$ kubectl get deploy -n kube-system snapshot-controller
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
snapshot-controller   0/2     2            0           1s

$ kubectl get pod -n kube-system -l app=snapshot-controller
NAME                                   READY   STATUS    RESTARTS   AGE
snapshot-controller-76494bf6c9-66r5t   1/1     Running   0          7s
snapshot-controller-76494bf6c9-tl6pg   1/1     Running   0          7s

# 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
NAME          DRIVER            DELETIONPOLICY   AGE
csi-aws-vsc   ebs.csi.aws.com   Delete           1s

사용 예 : 테스트 PVC/파드 생성

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

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

# 파일 내용 추가 저장 확인
$ kubectl exec app -- tail -f /data/out.txt
Sat May 13 12:09:30 UTC 2023
Sat May 13 12:09:30 UTC 2023

# 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
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata: 
  name: ebs-volume-snapshot
spec: 
  volumeSnapshotClassName: csi-aws-vsc
  source: 
    persistentVolumeClaimName: ebs-claim
$ 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-cf84e78b-d6ac-415f-ad95-a4e506699e42   true         4294967296    Delete           ebs.csi.aws.com   csi-aws-vsc           ebs-volume-snapshot   default                   30s

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

# 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

스냅샷으로 복원

# 스냅샷에서 PVC 로 복원
$ kubectl get pvc,pv
No resources found

$ 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
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            5m

# 파드 생성
$ 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


$ kubectl apply -f ebs-snapshot-restored-pod.yaml

# 파일 내용 저장 확인 : 파드 삭제 전까지의 저장 기록이 남아 있다. 이후 파드 재생성 후 기록도 잘 저장되고 있다
$ kubectl exec app -- cat /data/out.txt
...
Sat May 13 12:49:13 UTC 2023
Sat May 13 12:49:18 UTC 2023
Sat May 13 12:49:23 UTC 2023
...

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

AWS EFS Controller

EFS 파일시스템 확인 및 EFS Controller 설치


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

# 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
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::90XXXXXX:role/eksctl-eks-hayley-addon-iamserviceaccount-ku-Role1-1BY3E5T889ZJI
$ eksctl get iamserviceaccount --cluster eks-hayley
NAMESPACE NAME    ROLE ARN
kube-system aws-load-balancer-controller arn:aws:iam::90XXXXXX:role/eksctl-eks-hayley-addon-iamserviceaccount-ku-Role1-AMBNTVWDKQD5
kube-system ebs-csi-controller-sa  arn:aws:iam::90XXXXXX:role/AmazonEKS_EBS_CSI_DriverRole
kube-system efs-csi-controller-sa  arn:aws:iam::90XXXXXX:role/eksctl-eks-hayley-addon-iamserviceaccount-ku-Role1-1BY3E5T889ZJI

# 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
NAME                         NAMESPACE   REVISION UPDATED                                 STATUS   CHART                              APP VERSION
aws-efs-csi-driver           kube-system 1        2023-05-13 21:54:21.185391756 +0900 KST deployed aws-efs-csi-driver-2.4.3           1.5.5      
aws-load-balancer-controller kube-system 1        2023-05-13 19:43:16.335776069 +0900 KST deployed aws-load-balancer-controller-1.5.2 v2.5.1     
kube-ops-view                kube-system 1        2023-05-13 19:44:58.969909951 +0900 KST deployed kube-ops-view-1.2.2                20.4.0   

$ 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-fdrns   3/3     Running   0          39s
efs-csi-controller-6f64dcc5dc-rcfkc   3/3     Running   0          39s
efs-csi-node-8wqcw                    3/3     Running   0          39s
efs-csi-node-9sl2m                    3/3     Running   0          39s
efs-csi-node-xbjmn                    3/3     Running   0          39s

AWS → EFS → 파일 시스템 : 네트워크 → 탑재 대상 ID 확인

EFS 파일시스템을 다수의 파드가 사용하게 설정 : Workshop 링크

# 모니터링
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

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-05699d3c12ef609e2

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

EFS 파일시스템을 다수의 파드가 사용하게 설정 : Dynamic provisioning using EFS ← Fargate node는 현재 미지원 — Workshop

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

# EFS 스토리지클래스 생성 및 확인
$ curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/examples/kubernetes/dynamic_provisioning/specs/storageclass.yaml
$ cat storageclass.yaml | yh
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata: 
  name: efs-sc
provisioner: efs.csi.aws.com
parameters: 
  provisioningMode: efs-ap
  fileSystemId: fs-92107410
  directoryPerms: "700"
  gidRangeStart: "1000" # optional
  gidRangeEnd: "2000" # optional
  basePath: "/dynamic_provisioning" # optional
$ sed -i "s/fs-92107410/$EfsFsId/g" storageclass.yaml
$ kubectl apply -f storageclass.yaml
$ kubectl get sc efs-sc
NAME     PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
efs-sc   efs.csi.aws.com   Delete          Immediate           false                  1s

# PVC/파드 생성 및 확인
$ curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/examples/kubernetes/dynamic_provisioning/specs/pod.yaml
$ cat pod.yaml | yh
$ kubectl apply -f pod.yaml
$ kubectl get pvc,pv,pod
NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/efs-claim   Pending                                      efs-sc         76s

NAME          READY   STATUS    RESTARTS   AGE
pod/busybox   1/1     Running   0          96m
pod/efs-app   0/1     Pending   0          76s

# PVC/PV 생성 로그 확인
$ kubectl logs -n kube-system -l app=efs-csi-controller -c csi-provisioner -f

# 파드 정보 확인
$ kubectl exec -it efs-app -- 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에서 확인
kubectl exec efs-app -- bash -c "cat data/out"
  • EFS → 액세스 포인트 확인

Fargate

# 변수 지정
$ PrivateSubnetRouteTable=$(aws ec2 describe-route-tables --filters Name=tag:Name,Values=$CLUSTER_NAME-PrivateSubnetRouteTable --query 'RouteTables[0].RouteTableId' --output text)

# NATGW 생성, Private 라우팅 정보 추가
$ curl -s -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/natgw.yaml
$ sed -i "s/<PublicSubnet1>/$PrivateSubnet1/g" natgw.yaml
$ sed -i "s/<PrivateSubnetRouteTable>/$PrivateSubnetRouteTable/g" natgw.yaml
$ cat natgw.yaml
$ aws cloudformation deploy --template-file natgw.yaml --stack-name $CLUSTER_NAME-natgw

확인 : NATGW, PrivateSubnetRouteTable에 라우팅 추가 확인

EKS Persistent Volumes for Instance Store & Add NodeGroup

신규 노드 그룹 ng2 생성 — Blog : c5d.large 의 EC2 인스턴스 스토어(임시 블록 스토리지) 설정 작업

c5d.large 는 인스턴스 스토어(임시블록스토리지)를 가지고 있습니다.

인스턴스 스토어는 EC2 스토리지(EBS) 정보에 출력되지는 않습니다.

# 인스턴스 스토어 볼륨이 있는 c5 모든 타입의 스토리지 크기
$ aws ec2 describe-instance-types \
 --filters "Name=instance-type,Values=c5*" "Name=instance-storage-supported,Values=true" \
 --query "InstanceTypes[].[InstanceType, InstanceStorageInfo.TotalSizeInGB]" \
 --output table
--------------------------
|  DescribeInstanceTypes |
+---------------+--------+
|  c5d.12xlarge |  1800  |
|  c5d.large    |  50    |
|  c5d.24xlarge |  3600  |
|  c5d.4xlarge  |  400   |
|  c5d.18xlarge |  1800  |
|  c5d.2xlarge  |  200   |
|  c5d.metal    |  3600  |
|  c5d.xlarge   |  100   |
|  c5d.9xlarge  |  900   |
+---------------+--------+

# 신규 노드 그룹 생성
$ eksctl create nodegroup --help
$ eksctl create nodegroup -c $CLUSTER_NAME -r $AWS_DEFAULT_REGION --subnet-ids "$PubSubnet1","$PubSubnet2","$PubSubnet3" --ssh-access \
  -n ng2 -t c5d.large -N 1 -m 1 -M 1 --node-volume-size=30 --node-labels disk=nvme --max-pods-per-node 100 --dry-run > myng2.yaml

$ cat <<EOT >> nvme.yaml
  preBootstrapCommands:
    - |
      # Install Tools
      yum install nvme-cli links tree jq tcpdump sysstat -y

      # Filesystem & Mount
      mkfs -t xfs /dev/nvme1n1
      mkdir /data
      mount /dev/nvme1n1 /data

      # Get disk UUID
      uuid=\$(blkid -o value -s UUID mount /dev/nvme1n1 /data) 

      # Mount the disk during a reboot
      echo /dev/nvme1n1 /data xfs defaults,noatime 0 2 >> /etc/fstab
EOT
$ sed -i -n -e '/volumeType/r nvme.yaml' -e '1,$p' myng2.yaml
$ eksctl create nodegroup -f myng2.yaml

# 노드 보안그룹 ID 확인
$ NG2SGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng2* --query "SecurityGroups[*].[GroupId]" --output text)
$ aws ec2 authorize-security-group-ingress --group-id $NG2SGID --protocol '-1' --cidr 192.168.1.100/32

# 워커 노드 SSH 접속
N4=192.168.2.41
ssh ec2-user@$N4 hostname

# 확인
$ ssh ec2-user@$N4 sudo nvme list
Node             SN                   Model                                    Namespace Usage                      Format           FW Rev  
---------------- -------------------- ---------------------------------------- --------- -------------------------- ---------------- --------
/dev/nvme0n1     vol0a9b1f26cbac3e225 Amazon Elastic Block Store               1          32.21  GB /  32.21  GB    512   B +  0 B   1.0     
/dev/nvme1n1     AWS26F6377433BB12A04 Amazon EC2 NVMe Instance Storage         1          50.00  GB /  50.00  GB    512   B +  0 B   0

$ ssh ec2-user@$N4 sudo lsblk -e 7 -d
NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
nvme0n1 259:0    0   30G  0 disk 
nvme1n1 259:1    0 46.6G  0 disk /data

$ ssh ec2-user@$N4 sudo df -hT -t xfs
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/nvme0n1p1 xfs    30G  3.3G   27G  11% /
/dev/nvme1n1   xfs    47G  365M   47G   1% /data

$ ssh ec2-user@$N4 sudo tree /data
/data

0 directories, 0 files

$ ssh ec2-user@$N4 sudo cat /etc/fstab
#
UUID=0ccd3c9e-3e0f-4e59-ae3c-9498ef40c541     /           xfs    defaults,noatime  1   1
/dev/nvme1n1 /data xfs defaults,noatime 0 2

# (옵션) max-pod 확인
$ kubectl describe node -l disk=nvme | grep Allocatable: -A7
Allocatable:
  attachable-volumes-aws-ebs:  25
  cpu:                         1930m
  ephemeral-storage:           27905944324
  hugepages-1Gi:               0
  hugepages-2Mi:               0
  memory:                      3068880Ki
  pods:                        100

# (옵션) kubelet 데몬 파라미터 확인 : --max-pods=29 --max-pods=100
ssh ec2-user@$N4 sudo ps -ef | grep kubelet
root      2973     1  1 13:22 ?        00:00:04 /usr/bin/kubelet --config /etc/kubernetes/kubelet/kubelet-config.json --kubeconfig /var/lib/kubelet/kubeconfig --container-runtime-endpoint unix:///run/containerd/containerd.sock --image-credential-provider-config /etc/eks/image-credential-provider/config.json --image-credential-provider-bin-dir /etc/eks/image-credential-provider --node-ip=192.168.2.41 --pod-infra-container-image=602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/eks/pause:3.5 --v=2 --cloud-provider=aws --container-runtime=remote --node-labels=eks.amazonaws.com/sourceLaunchTemplateVersion=1,alpha.eksctl.io/cluster-name=eks-hayley,alpha.eksctl.io/nodegroup-name=ng2,disk=nvme,eks.amazonaws.com/nodegroup-image=ami-02ddbc7e7f404a16a,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup=ng2,eks.amazonaws.com/sourceLaunchTemplateId=lt-02f535365a4e4c3e5 --max-pods=29 --max-pods=100스토리지 클래스 재생성

스토리지 클래스 재생성

# 기존 삭제
#curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
$ kubectl delete -f local-path-storage.yaml

#
$ sed -i 's/opt/data/g' local-path-storage.yaml
$ kubectl apply -f local-path-storage.yaml

# 모니터링
$ watch 'kubectl get pod -owide;echo;kubectl get pv,pvc'
$ ssh ec2-user@$N4 iostat -xmdz 1 -p nvme1n1
Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
nvme1n1           0.00     0.00    0.43    0.42     0.01     0.04   123.87     0.00    0.20    0.06    0.35   0.33   0.03

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util

Device:         rrqm/s   wrqm/s     r/s     w/s    rMB/s    wMB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util

# kubestr 툴 다운로드
$ wget https://github.com/kastenhq/kubestr/releases/download/v0.4.37/kubestr_0.4.37_Linux_amd64.tar.gz
$ tar xvfz kubestr_0.4.37_Linux_amd64.tar.gz && mv kubestr /usr/local/bin/ && chmod +x /usr/local/bin/kubestr

# 측정 : Read
#curl -s -O https://raw.githubusercontent.com/wikibook/kubepractice/main/ch10/fio-read.fio
$ kubestr fio -f fio-read.fio -s local-path --size 10G --nodeselector disk=nvme
PVC created kubestr-fio-pvc-zbzd6
Pod created kubestr-fio-pod-67q27
Running FIO test (fio-read.fio) on StorageClass (local-path) with a PVC of Size (10G)
Elapsed time- 3m42.735408402s
FIO test results:
  
FIO version - fio-3.30
Global options - ioengine=libaio verify= direct=1 gtod_reduce=

JobName: 
  blocksize= filesize= iodepth= rw=
read:
  IOPS=20310.097656 BW(KiB/s)=81240
  iops: min=15878 max=94036 avg=20318.447266
  bw(KiB/s): min=63512 max=376144 avg=81274.265625

Disk stats (read/write):
  nvme1n1: ios=2432736/10 merge=0/3 ticks=7639458/30 in_queue=7639488, util=99.958275%
  -  OK

 

 

blog migration project

written in 2023.5.13

https://medium.com/techblog-hayleyshim/aws-eks-storage-node-881c89ad89b5

'IT > Infra&Cloud' 카테고리의 다른 글

[aws] EKS Autoscaling  (0) 2023.10.30
[aws] EKS Observability  (0) 2023.10.29
[aws] EKS Networking  (0) 2023.10.29
[aws] EKS 설치 및 기본 사용  (0) 2023.10.29
[aws] IAM identity providers(SAML)  (0) 2023.10.29