03-[AEWS]-EKS Storage

EKS Storage

💡Stateless 한 Pod
기본적으로 pod는 상태를 유지하지 않는다. 즉, pod 정지 및 삭제 시 pod 안에 있는 데이터가 모두 삭제가 된다.
따라서 데이터 보존이 필요하다면 Storage를 pod에 mount하여 따로 백업을 해야 한다.
외부 데이터베이스, Cloud Storage, PV(Persistent Volume), PVC(Persistent Volume Claim), 외부 파일 시스템 마운트 등의 방법이 있다.

Volume 및 Storage 특징

Volume Type

  1. emptyDir:
    • 임시적인 스토리지를 제공합니다. Pod이 생성되고 삭제될 때까지만 데이터가 유지됩니다.
    • 여러 컨테이너 간 데이터를 공유할 때 유용합니다.
    • 클러스터 노드의 임시 디렉토리에 저장됩니다.
  2. hostPath:
    • 호스트 노드의 파일 시스템 경로를 Pod에 마운트합니다.
    • 호스트 노드 간에 데이터를 공유할 때 사용됩니다.
    • 데이터 보존 및 복구에 유용하지만, 클러스터화된 환경에서는 권장되지 않습니다.
  3. PV/PVC (Persistent Volume/Persistent Volume Claim):
    • 영구적인 스토리지를 제공하며, 클러스터 외부에 있는 외부 스토리지를 사용할 수 있습니다.
    • 다양한 스토리지 솔루션을 통합할 수 있으며, 동적 프로비저닝과 스토리지 클래스를 통해 관리됩니다.
    • 볼륨의 생명주기와 볼륨에 대한 요구사항을 정의하는 데 사용됩니다.

다양한 스토리지 솔루션

  1. Kubernetes 자체 제공 (hostPath, local):
    • 간편하게 사용할 수 있으며, 클러스터의 노드에 직접 액세스할 수 있습니다.
    • 단점으로는 클러스터 노드 간에 데이터 공유가 어렵고, 데이터 보존이 제한적일 수 있습니다.
  2. 온프렘 솔루션 (예: Ceph 등):
    • 자체 데이터 센터에 있는 스토리지 솔루션을 사용할 수 있습니다.
    • 클러스터화된 환경에서 안정적이고 확장 가능한 스토리지를 제공할 수 있습니다.
  3. NFS (Network File System):
    • 네트워크를 통해 파일 시스템을 공유하는 서비스입니다.
    • 여러 노드 간에 데이터를 공유하고, 확장성과 유연성을 제공합니다.
  4. 클라우드 스토리지 (예: AWS EBS 등):
    • 클라우드 제공업체가 제공하는 스토리지 서비스를 사용할 수 있습니다.
    • 클라우드 벤더의 관리 및 유지보수가 용이하며, 확장성과 가용성을 제공합니다.

동적 프로비저닝 & 볼륨 상태, ReclaimPolicy:

  • 동적 프로비저닝(Dynamic Provisioning):
    • PV/PVC를 사용하여 Pod에 필요한 스토리지를 동적으로 프로비저닝하는 기능입니다.
    • 요청된 볼륨 사양에 따라 스토리지 클래스에 의해 자동으로 볼륨이 프로비저닝됩니다.
  • 볼륨 상태 및 ReclaimPolicy:
    • PV는 다양한 상태를 가질 수 있으며, 해당 PV의 ReclaimPolicy에 따라 삭제된 후에도 데이터가 보존되거나 삭제됩니다.
    • ReclaimPolicy는 PV가 삭제되었을 때의 동작을 정의합니다. 예를 들어, Delete 옵션을 사용하면 PV에 연결된 스토리지를 삭제할 수 있습니다.

실습 환경

(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl get sc   #여기서 sc는 storage class 의 줄임
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  98m
(pak8266@myeks:default) [root@myeks-bastion ~]# 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: "2024-03-23T08:43:26Z"
  name: gp2
  resourceVersion: "272"
  uid: 8f3a25fa-8961-4ce0-976d-e335b408fa74
parameters: 
  fsType: ext4
  type: gp2
provisioner: kubernetes.io/aws-ebs
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
  1. StorageClass 이름: gp2
  2. 프로비저너(Provisioner): kubernetes.io/aws-ebs
    • 이 스토리지 클래스는 AWS의 Elastic Block Store (EBS)를 프로비저닝하는 데 사용됩니다.
  3. 리클레임 정책(Reclaim Policy): Delete
    • 이 스토리지 클래스를 사용하여 프로비저닝된 볼륨이 삭제될 때, 해당 볼륨의 데이터가 삭제됩니다.
  4. 볼륨 바인딩 모드(Volume Binding Mode): WaitForFirstConsumer
    • 이 스토리지 클래스를 사용하여 프로비저닝된 볼륨은 첫 번째 사용자가 바인딩할 때까지 대기합니다.
  5. 파라미터(Parameters):
    • fsType: ext4: 프로비저닝된 볼륨의 파일 시스템 유형은 ext4입니다.
    • type: gp2: EBS 스토리지 유형은 gp2입니다.
  6. 생성 시간(Creation Timestamp): 2024년 3월 23일에 생성되었습니다.
  7. 자원 버전(Resource Version): 현재 스토리지 클래스의 자원 버전은 “272”입니다.
  8. 고유 식별자(UID): 스토리지 클래스의 고유 식별자는 8f3a25fa-8961-4ce0-976d-e335b408fa74입니다.
  9. 주석(Annotations):
    • kubectl.kubernetes.io/last-applied-configuration: 마지막 적용된 구성 정보입니다.
    • storageclass.kubernetes.io/is-default-class: 이 스토리지 클래스가 기본 클래스로 설정되어 있음을 나타냅니다. (true)
❓StorageClass gp2
gp2는 General Purpose SSD(Solid State Drive)의 약어로, 일반적인 워크로드에 적합한 SSD 스토리지 유형입니다. gp2 볼륨은 안정적인 성능과 비용 효율성을 제공하며, 주로 일반적인 애플리케이션 및 데이터베이스 용도로 사용됩니다. Kubernetes에서 AWS의 gp2 스토리지를 사용할 때, 해당 스토리지를 프로비저닝하기 위해 AWS EBS (Elastic Block Store) CSI 드라이버를 사용할 수 있습니다.
여기서 CSI드라이버란 컨테이너와 외부 스토리지 시스템 간의 통합을 위한 표준 인터페이스 입니다. 표준화, 확장성, 유연성, 간편한 업그레이드 및 유지보수의 이유로 사용합니다. 

K8S host Path VS Local Path Provisioner

hostPath:

  1. 특징:
    • 호스트 머신의 파일 시스템 경로를 직접 마운트하여 Pod에 볼륨을 제공합니다.
    • 클러스터 노드의 로컬 파일 시스템에 직접 액세스할 수 있습니다.
    • 주로 개발 및 테스트 환경에서 사용되며, 간단한 구성이 가능합니다.
  2. 장점:
    • 쉽게 구성할 수 있고, 별도의 스토리지 시스템이 필요하지 않습니다.
    • 빠른 성능을 제공할 수 있습니다.
    • 단일 노드 환경에서 효과적으로 사용될 수 있습니다.
  3. 단점:
    • 호스트 노드에 의존하기 때문에 이식성이 낮고, 여러 노드 간에 볼륨을 공유할 수 없습니다.
    • 클러스터 노드 간에 Pod를 이동시키거나 복제할 때 데이터의 유실이 발생할 수 있습니다.
    • 스케일링 및 고가용성을 위한 클러스터 구성에는 적합하지 않습니다.

Local Path Provisioner (StorageClass 제공):

  1. 특징:
    • Kubernetes에서 제공하는 StorageClass를 사용하여 로컬 디스크를 동적으로 프로비저닝합니다.
    • 각 노드에 있는 로컬 디스크를 사용하여 볼륨을 프로비저닝하며, Pod에 마운트됩니다.
    • 로컬 디스크를 동적으로 할당하므로 별도의 스토리지 시스템이 필요하지 않습니다.
  2. 장점:
    • 로컬 디스크를 동적으로 할당하여 스토리지를 프로비저닝하므로 호스트 경로에 대한 의존성이 없습니다.
    • 클러스터의 여러 노드에 볼륨을 공급할 수 있으며, Pod의 이동 및 복제에 대해 더 안전합니다.
    • 로컬 디스크를 사용하여 빠른 성능을 제공할 수 있습니다.
  3. 단점:
    • 로컬 디스크를 사용하므로 확장성이 제한될 수 있습니다. 클러스터에 새로운 노드를 추가할 때 스토리지 용량이 부족할 수 있습니다.
    • 로컬 디스크의 유실에 대비한 데이터 백업 및 복구 전략이 필요합니다.
    • 클러스터 전체에서 일관된 스토리지 관리가 어려울 수 있습니다.
  • hostPath는 간단하고 빠르게 설정할 수 있으며, 단일 노드 환경에서 적합합니다. 하지만 이식성이 낮고, 고가용성 및 스케일링에 적합하지 않습니다.
  • Local Path Provisioner는 로컬 디스크를 동적으로 프로비저닝하여 클러스터의 여러 노드에 볼륨을 공급할 수 있으며, 이동성 및 안정성 측면에서 더 우수합니다. 하지만 로컬 디스크에 대한 데이터 관리와 확장성에 대한 고려가 필요합니다.

1. Kubernetes Local Path 실습 환경 구성

apiVersion: v1
kind: Namespace
metadata: 
  name: local-path-storage
---
apiVersion: v1
kind: ServiceAccount
metadata: 
  name: local-path-provisioner-service-account
  namespace: local-path-storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: 
  name: local-path-provisioner-role
  namespace: local-path-storage
rules: 
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: 
  name: local-path-provisioner-role
rules: 
  - apiGroups: [""]
    resources: ["nodes", "persistentvolumeclaims", "configmaps", "pods", "pods/log"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "patch"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: 
  name: local-path-provisioner-bind
  namespace: local-path-storage
roleRef: 
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: local-path-provisioner-role
subjects: 
  - kind: ServiceAccount
    name: local-path-provisioner-service-account
    namespace: local-path-storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata: 
  name: local-path-provisioner-bind
roleRef: 
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: local-path-provisioner-role
subjects: 
  - kind: ServiceAccount
    name: local-path-provisioner-service-account
    namespace: local-path-storage
---
apiVersion: apps/v1
kind: Deployment
metadata: 
  name: local-path-provisioner
  namespace: local-path-storage
spec: 
  replicas: 1
  selector: 
    matchLabels: 
      app: local-path-provisioner
  template: 
    metadata: 
      labels: 
        app: local-path-provisioner
    spec: 
      serviceAccountName: local-path-provisioner-service-account
      containers: 
        - name: local-path-provisioner
          image: rancher/local-path-provisioner:master-head
          imagePullPolicy: IfNotPresent
          command: 
            - local-path-provisioner
            - --debug
            - start
            - --config
            - /etc/config/config.json
          volumeMounts: 
            - name: config-volume
              mountPath: /etc/config/
          env: 
            - name: POD_NAMESPACE
              valueFrom: 
                fieldRef: 
                  fieldPath: metadata.namespace
      volumes: 
        - name: config-volume
          configMap: 
            name: local-path-config
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: 
  name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
kind: ConfigMap
apiVersion: v1
metadata: 
  name: local-path-config
  namespace: local-path-storage
data: 
  config.json: |-
    {
            "nodePathMap":[
            {
                    "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
                    "paths":["/opt/local-path-provisioner"]
            }
            ]
    }
  setup: |-
    #!/bin/sh
    set -eu
    mkdir -m 0777 -p "$VOL_DIR"
  teardown: |-
    #!/bin/sh
    set -eu
    rm -rf "$VOL_DIR"
  helperPod.yaml: |-
    apiVersion: v1
    kind: Pod
    metadata:
      name: helper-pod
    spec:
      priorityClassName: system-node-critical
      tolerations:
        - key: node.kubernetes.io/disk-pressure
          operator: Exists
          effect: NoSchedule
      containers:
      - name: helper-pod
        image: busybox
        imagePullPolicy: IfNotPresent
  1. Namespace: local-path-storage:
    • 이 부분은 Kubernetes에서 사용되는 네임스페이스를 정의합니다. 다른 리소스들이 이 네임스페이스 안에서 생성됩니다.
  2. ServiceAccount: local-path-provisioner-service-account:
    • Kubernetes에서 사용되는 서비스 계정을 정의합니다. 이 서비스 계정은 볼륨 프로비저닝을 위한 권한을 가지게 됩니다.
  3. Role: local-path-provisioner-role:
    • Kubernetes에서 사용되는 역할(Role)을 정의합니다. 이 역할은 특정 작업에 대한 권한을 부여합니다.
  4. ClusterRole: local-path-provisioner-role:
    • Kubernetes 클러스터 전체에서 사용되는 역할을 정의합니다. 역시 특정 작업에 대한 권한을 부여합니다.
  5. RoleBinding: local-path-provisioner-bind:
    • 서비스 계정과 역할을 연결하여 해당 서비스 계정에 역할의 권한을 부여합니다.
  6. ClusterRoleBinding: local-path-provisioner-bind:
    • 클러스터 역할과 서비스 계정을 연결하여 해당 서비스 계정에 클러스터 역할의 권한을 부여합니다.
  7. Deployment: local-path-provisioner:
    • 애플리케이션을 배포하기 위한 Kubernetes의 Deployment를 정의합니다. 이 경우 로컬 디스크를 사용하여 볼륨을 프로비저닝하는 로컬 경로 프로비저너를 배포합니다.
    • Deployment는 하나의 Pod을 관리하며, 이 Pod은 local-path-provisioner 이미지를 사용합니다.
    • Pod 내부에는 local-path-provisioner 컨테이너가 정의되어 있으며, 이 컨테이너는 /etc/config/ 디렉토리에 마운트된 설정 파일(config.json)을 사용하여 로컬 경로 프로비저너를 실행합니다.
  8. StorageClass: local-path:
    • 볼륨을 프로비저닝하는데 사용되는 스토리지 클래스를 정의합니다. 이 스토리지 클래스는 local-path-provisioner를 사용하여 로컬 디스크 공간을 프로비저닝합니다.
  9. ConfigMap: local-path-config:
    • 설정 정보를 포함하는 ConfigMap을 정의합니다. 이 ConfigMap에는 로컬 경로 프로비저너의 설정 파일(config.json)과 로컬 경로 프로비저너의 설치 및 제거에 필요한 스크립트가 포함되어 있습니다.

2. pvc pod 생성

apiVersion: v1
kind: PersistentVolumeClaim
metadata: 
  name: localpath-claim
spec: 
  accessModes: 
    - ReadWriteOnce
  resources: 
    requests: 
      storage: 1Gi
  storageClassName: "local-path"

3. pod 생성 및 삭제 진행하면서 데이터 유지되는지 확인

EKS – EBS Controller

Amazon EBS CSI driver as an Amazon EKS add-on

설치

(pak8266@myeks:default) [root@myeks-bastion ~]# aws eks describe-addon-versions \
>     --addon-name aws-ebs-csi-driver \
>     --kubernetes-version 1.28 \
>     --query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" \
>     --output text
v1.28.0-eksbuild.1
True
v1.27.0-eksbuild.1
False
(pak8266@myeks:default) [root@myeks-bastion ~]# 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
2024-03-24 00:15:53 [ℹ]  1 existing iamserviceaccount(s) (kube-system/aws-load-balancer-controller) will be excluded
2024-03-24 00:15:53 [ℹ]  1 iamserviceaccount (kube-system/ebs-csi-controller-sa) was included (based on the include/exclude rules)
2024-03-24 00:15:53 [!]  serviceaccounts in Kubernetes will not be created or modified, since the option --role-only is used
2024-03-24 00:15:53 [ℹ]  1 task: { create IAM role for serviceaccount "kube-system/ebs-csi-controller-sa" }
2024-03-24 00:15:53 [ℹ]  building iamserviceaccount stack "eksctl-myeks-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"
2024-03-24 00:15:53 [ℹ]  deploying stack "eksctl-myeks-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"
2024-03-24 00:15:53 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"
2024-03-24 00:16:23 [ℹ]  waiting for CloudFormation stack "eksctl-myeks-addon-iamserviceaccount-kube-system-ebs-csi-controller-sa"
(pak8266@myeks:default) [root@myeks-bastion ~]# eksctl get iamserviceaccount --cluster myeks
NAMESPACE       NAME                            ROLE ARN
kube-system     aws-load-balancer-controller    arn:aws:iam::159088646233:role/eksctl-myeks-addon-iamserviceaccount-kube-sys-Role1-WAOH1sNH2E1M
kube-system     ebs-csi-controller-sa           arn:aws:iam::159088646233:role/AmazonEKS_EBS_CSI_DriverRole
(pak8266@myeks:default) [root@myeks-bastion ~]# 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
2024-03-24 00:19:27 [ℹ]  Kubernetes version "1.28" in use by cluster "myeks"
2024-03-24 00:19:27 [ℹ]  using provided ServiceAccountRoleARN "arn:aws:iam::159088646233:role/AmazonEKS_EBS_CSI_DriverRole"
2024-03-24 00:19:27 [ℹ]  creating addon
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl get sa -n kube-system ebs-csi-controller-sa -o yaml | head -5
apiVersion: v1
automountServiceAccountToken: true
kind: ServiceAccount
metadata:
  annotations:
(pak8266@myeks:default) [root@myeks-bastion ~]# eksctl get addon --cluster ${CLUSTER_NAME}
2024-03-24 00:19:54 [ℹ]  Kubernetes version "1.28" in use by cluster "myeks"
2024-03-24 00:19:54 [ℹ]  getting all addons
2024-03-24 00:19:55 [ℹ]  to see issues for an addon run `eksctl get addon --name <addon-name> --cluster <cluster-name>`
NAME                    VERSION                 STATUS          ISSUES  IAMROLE                                                                     UPDATE AVAILABLE CONFIGURATION VALUES
aws-ebs-csi-driver      v1.28.0-eksbuild.1      CREATING        0       arn:aws:iam::159088646233:role/AmazonEKS_EBS_CSI_DriverRole
coredns                 v1.10.1-eksbuild.7      ACTIVE          0
kube-proxy              v1.28.6-eksbuild.2      ACTIVE          0
vpc-cni                 v1.17.1-eksbuild.1      ACTIVE          0       arn:aws:iam::159088646233:role/eksctl-myeks-addon-vpc-cni-Role1-3zKeRHu5zLI9enableNetworkPolicy: "true"
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl get deploy,ds -l=app.kubernetes.io/name=aws-ebs-csi-driver -n kube-system

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ebs-csi-controller   2/2     2            2           30s

NAME                                  DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR              AGE
daemonset.apps/ebs-csi-node           3         3         3       3            3           kubernetes.io/os=linux     30s
daemonset.apps/ebs-csi-node-windows   0         0         0       0            0           kubernetes.io/os=windows   30s
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl get pod -n kube-system -l 'app in (ebs-csi-controller,ebs-csi-node)'
NAME                                 READY   STATUS    RESTARTS   AGE
ebs-csi-controller-765cf7cf9-55br7   6/6     Running   0          30s
ebs-csi-controller-765cf7cf9-6tst9   6/6     Running   0          30s
ebs-csi-node-6wk49                   3/3     Running   0          31s
ebs-csi-node-lnz9d                   3/3     Running   0          31s
ebs-csi-node-wwwpz                   3/3     Running   0          31s
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl get pod -n kube-system -l app.kubernetes.io/component=csi-driver
NAME                                 READY   STATUS    RESTARTS   AGE
ebs-csi-controller-765cf7cf9-55br7   6/6     Running   0          31s
ebs-csi-controller-765cf7cf9-6tst9   6/6     Running   0          31s
ebs-csi-node-6wk49                   3/3     Running   0          32s
ebs-csi-node-lnz9d                   3/3     Running   0          32s
ebs-csi-node-wwwpz                   3/3     Running   0          32s
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl get csinodes
NAME                                               DRIVERS   AGE
ip-192-168-1-29.ap-northeast-2.compute.internal    1         6h30m
ip-192-168-2-97.ap-northeast-2.compute.internal    1         6h30m
ip-192-168-3-162.ap-northeast-2.compute.internal   1         6h30m
(pak8266@myeks:default) [root@myeks-bastion ~]# 
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  6h39m
local-path      rancher.io/local-path   Delete          WaitForFirstConsumer   false                  154m
(pak8266@myeks:default) [root@myeks-bastion ~]# 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
>   #iops: "5000"
>   #throughput: "250"
>   allowAutoIOPSPerGBIncrease: 'true'
>   encrypted: 'true'
>   fsType: xfs # 기본값이 ext4
> 
EOT
(pak8266@myeks:default) [root@myeks-bastion ~]# cat gp3-sc.yaml | yh
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata: 
  name: gp3
allowVolumeExpansion: true
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters: 
  type: gp3
  allowAutoIOPSPerGBIncrease: 'true'
  encrypted: 'true'
  fsType: xfs # 기본값이 ext4
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl apply -f gp3-sc.yaml
storageclass.storage.k8s.io/gp3 created
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl get sc
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  6h40m
gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   6s
local-path      rancher.io/local-path   Delete          WaitForFirstConsumer   false                  155m
(pak8266@myeks:default) [root@myeks-bastion ~]# kubectl describe sc gp3 | grep Parameters
Parameters:            allowAutoIOPSPerGBIncrease=true,encrypted=true,fsType=xfs,type=gp3
  1. aws eks describe-addon-versions
    • AWS EBS CSI 드라이버의 버전 및 호환성을 확인
    • 해당 명령어는 Kubernetes 버전 1.28에서의 AWS EBS CSI 드라이버의 호환성을 확인
  2. eksctl create iamserviceaccount
    • EKS(IAM Service Account, ISRA)를 생성
    • 여기서는 ebs-csi-controller-sa라는 이름의 IAM Service Account를 kube-system 네임스페이스에 생성하고,
    • 해당 서비스 계정에 AmazonEKS_EBS_CSI_DriverRole IAM 역할을 연결.
  3. eksctl create addon
    • eksctl을 사용하여 AWS EBS CSI 드라이버를 EKS 클러스터에 추가
    • 이 명령은 EKS 클러스터에 AWS EBS CSI 드라이버를 설치하고 활성화
  4. kubectl get sa
    • kube-system 네임스페이스에서 ebs-csi-controller-sa라는 서비스 계정을 조회
  5. kubectl get deploy,ds
    • kube-system 네임스페이스에서 AWS EBS CSI 드라이버와 관련된 Deployment 및 DaemonSet을 조회
  6. kubectl get pod
    • kube-system 네임스페이스에서 AWS EBS CSI 드라이버와 관련된 Pod을 조회
  7. kubectl get csinodes
    • CSI 드라이버를 사용하는 노드를 조회
  8. kubectl get sc
    • 현재 사용 가능한 스토리지 클래스를 조회
  9. cat <<EOT > gp3-sc.yaml
    • gp3 스토리지 클래스를 정의하는 YAML 작성
  10. kubectl apply -f gp3-sc.yaml
    • gp3 스토리지 클래스를 Kubernetes 클러스터에 배포
    • gp3 스토리지 클래스가 생성되고 사용 가능
💡gp2 와 gp3 차이점
gp3는 IOPS 및 처리량을 조정할 수 있는 기능을 제공합니다. 이는 워크로드의 요구 사항에 따라 스토리지의 성능을 조정할 수 있어 더 유연한 운영을 가능하게 합니다.
반면에 gp2는 성능 조정 기능이 없으며, 고정된 성능을 제공합니다.

Test

# 워커노드의 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

# 워커노드에서 파드에 추가한 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

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

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

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

kubectl get node --label-columns=topology.ebs.csi.aws.com/zone,topology.kubernetes.io/zone
kubectl describe node | more

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

# 아래 명령어는 확인까지 다소 시간이 소요됨
kubectl df-pv

## 파드 내에서 볼륨 정보 확인
kubectl exec -it app -- sh -c 'df -hT --type=overlay'
kubectl exec -it app -- sh -c 'df -hT --type=xfs'

EKS Persistent Volumes for Instance Store & Add NodeGroup

💡Node가 EC2로 되어있고 EC2도 일시적이며 인스턴스가 종료되면 데이터가 소멸되므로 Persistent Volumes for Instance Store를 사용하여 이러한 인스턴스 스토어에 영구적인 스토리지를 사용하여 데이터를 영구적으로 유지하는 방법이다.
# 인스턴스 스토어 볼륨이 있는 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.large    |  50    |
|  c5d.12xlarge |  1800  |
...

# 신규 노드 그룹 생성
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=<각자 자신의 워커 노드4번 Private IP >
N4=192.168.3.160
ssh ec2-user@$N4 hostname

# 확인
ssh ec2-user@$N4 sudo nvme list
ssh ec2-user@$N4 sudo lsblk -e 7 -d
ssh ec2-user@$N4 sudo df -hT -t xfs
ssh ec2-user@$N4 sudo tree /data
ssh ec2-user@$N4 sudo cat /etc/fstab

# 기존 삭제
#curl -s -O https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
cd
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

# 측정 : 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

Leave a Comment