[AWS EKS Workshop Study] 3주차 - 스토리지, 노드 그룹

JoonHyeok Han·2024년 3월 19일
0

개요

이번 3주차 스터디에서는 EKS 에서 노드의 데이터를 영구적으로 저장하기 위한 AWS 에서 제공하는 다양한 스토리지 종류 및 특징과 워커 노드를 관리하기 위한 노드 그룹을 학습했다.

스토리지

쿠버네티스의 가장 작은 실행 단위인 파드(Pod)는 하나 이상의 컨테이너로 구성된다.

파드의 데이터 저장을 살펴보기 전에 컨테이너 이미지 생성 원리부터 먼저 살펴보자.

컨테이너 파일 시스템

컨테이너 내부에서 특정 OS 를 실행하기 위한 시스템 바이너리 파일, 의존성 라이브러리 등이 필요하고, 이를 저장하기 위해서 파일 시스템이 필요하다.

컨테이너는 호스트 OS 와 격리된 네임스페이스를 가지고 있기 때문에 컨테이너 내부에서 아무리 상위 디렉토리로 이동하려고 해도 호스트 OS 의 디렉토리로 접근할 수 없다.

마치 컨테이너를 위한 USB 를 장착한 것과 같다고 생각하면 된다.


출처: [컨테이너 인터널 #2] 컨테이너 파일시스템 [카카오 엔터프라이즈]

이때 컨테이너의 파일 시스템을 구현하기 위해 오버레이 파일 시스템을 이용하며, 위의 이미지와 같은 구조로 되어 있다.

Lower Dir 은 읽기 전용으로 되어 있고, 운영체제를 실행하기 위한 최소한의 시스템 바이너리 파일, 의존성 라이브러리 등이 저장된다.

그 다음 Upper Dir 을 올리는데, 컨테이너에서 쓰기 작업이 발생하면 Upper Dir 에서 저장된다.

그리고 맨 위의 Merge View 는 오버레이 파일 시스템이 마운트 되는 디렉토리를 의미하는데, Lower Dir 부터 Upper Dir 에 저장된 파일들이 모두 합쳐져서 보이기 때문에 Merge View 라고 표현한다.

쿠버네티스 파드의 컨테이너든 도커 컨테이너든 기본적으로 Upper Dir 에 데이터를 쓰고 지울 수 있는데, 문제는 컨테이너를 종료시키면 Upper Dir 에 저장한 데이터도 함께 사라진다는 것이다.

도커 컨테이너의 데이터 관리

컨테이너의 파일 시스템에 데이터를 저장하는 대신 도커는 컨테이너의 데이터를 저장하기 위해 크게 3가지 방법을 사용한다.


출처: Manage data in Docker [docker docs]

  1. 볼륨(volume) : 호스트 OS 의 파일 시스템에 저장한다. 컨테이너가 실행되면 볼륨이 생성되고, 컨테이너가 종료되면 볼륨이 삭제된다. 생성과 삭제는 컨테이너 런타임이 자동으로 관리한다. Ubuntu 운영체제 기준으로 /var/lib/docker/volumes 경로에 컨테이너의 데이터가 저장된다는 것을 의미한다. 컨테이너와 Docker area 로 이어지는 선에 해당하는 것이 volume 의 개념이다.
  2. 바인드 마운트(bind mount) : 호스트 OS 의 파일 시스템에 저장하지만, 컨테이너 런타임에서 관리하지 않는 영역이다. 즉, 호스트 OS 에서 의도적으로 지우지 않는 이상 계속 유지된다. 데이터를 영속적으로 관리하기 위해서 사용한다.
  3. tmpfs 마운트 : 파일 시스템이 아닌 메모리(RAM)에 저장한다. 컨테이너의 데이터를 영속적으로 유지할 필요가 없을 때 사용하며, 메모리를 사용하기 때문에 위의 2가지 방법에 비해 I/O 속도가 빠르다.

쿠버네티스 파드의 데이터 관리

쿠버네티스에서는 다양한 컨테이너 런타임(containerd, CRI-O, 도커 엔진 등)을 지원하고 있다.

쿠버네티스 파드 안에 실행되는 컨테이너도 기본적으로 컨테이너 이미지 안에 포함된 파일 시스템 레이어의 데이터를 읽고 쓸 수 있다.

하지만 이 방법으로는 데이터를 영속적으로 관리할 수 없기 때문에 쿠버네티스에서는 아래와 같은 방법을 사용하고 있다.

hostPath

hostPath 볼륨은 쿠버네티스 노드 파일 시스템에 영속적으로 데이터를 관리하기 위해 사용한다.

hostPath 볼륨의 데이터는 파드가 종료되더라도 삭제되지 않는다.

도커 컨테이너의 바인드 마운트와 유사하다고 보면 된다.

hostPath 를 사용하는 이유는 파드에서 노드의 로그 파일이나 kubeconfig(쿠버네티스 구성 파일), CA 인증서에 접근하기 위해서다.

아래의 명령어를 실행해서 kube-apiserver 가 사용하는 볼륨을 살펴보자.

kubectl describe po -n kube-system kube-apiserver

그 중에서 Volumes 에 해당하는 부분을 살펴보면 아래와 같다.

Volumes:
  ca-certs:
    Type:          HostPath (bare host directory volume)
    Path:          /etc/ssl/certs
    HostPathType:  DirectoryOrCreate
  etc-ca-certificates:
    Type:          HostPath (bare host directory volume)
    Path:          /etc/ca-certificates
    HostPathType:  DirectoryOrCreate
  etc-pki:
    Type:          HostPath (bare host directory volume)
    Path:          /etc/pki
    HostPathType:  DirectoryOrCreate
  k8s-certs:
    Type:          HostPath (bare host directory volume)
    Path:          /etc/kubernetes/pki
    HostPathType:  DirectoryOrCreate
  usr-local-share-ca-certificates:
    Type:          HostPath (bare host directory volume)
    Path:          /usr/local/share/ca-certificates
    HostPathType:  DirectoryOrCreate
  ...

하지만 쿠버네티스 공식 문서에서는 hostPath 볼륨을 애플리케이션에서 발생하는 데이터의 영속적 관리를 위해서는 사용하지 않을 것을 권장하고 있다.

Warning:

Using the hostPath volume type presents many security risks. If you can avoid using a hostPath volume, you should. For example, define a [local PersistentVolume](https://kubernetes.io/docs/concepts/storage/volumes/#local), and use that instead.

hostPath 를 사용하더라도 읽기 전용으로 사용하라고 권장하는데, hostPath 는 마운트 네임스페이스를 격리하는 것이 아닌 노드의 파일 시스템에 직접 액세스 하는 것이기 때문에 보안 상 PV(Persistent Volume)를 사용할 것을 제시하고 있다.

PV와 PVC

PV(Persistent Volume)는 hostPath 와 달리 마운트 네임스페이스를 격리해서 데이터를 저장한다.

PV 는 노드의 파일 시스템이나 네트워크 파일 시스템으로 마운트 하는 것이 가능하다. 클라우드 서비스의 AWS 의 EBS, EFS 등에 연결해서 사용할 수도 있다.

파드에서 PV 에 접근하는 방식은 아래의 이미지와 같다.


출처: [kubernetes] Volume vs Persistent Volume vs Storage Class [네이버 블로그]

파드가 PV 에 직접 접근하는 것이 아닌 PVC 를 거쳐서 PV 에 접근한다.

PVC(Persitent Volume Claim)는 PV 에 접근하기 위한 카드 키 같은 개념이다.

파드가 PVC 를 통해 PV 에 접근하는 yaml 파일을 살펴보면 아래와 같다.

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: task-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: task-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi

---
apiVersion: v1
kind: Pod
metadata:
  name: task-pv-pod
spec:
  volumes:
    - name: task-pv-storage
      persistentVolumeClaim:
        claimName: task-pv-claim
  containers:
    - name: task-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: task-pv-storage

PV 는 hostPath 를 이용해서 호스트 노드의 /mnt/data 경로에 있는 디렉토리를 10GB 사용한다. manual 이라는 이름을 가진 스토리지 클래스(Storage Class)를 사용한다.

  • 스토리지 클래스란 쿠버네티스 클러스터 안에서 어떤 종류의 스토리지를 사용하고, 클러스터가 해당 스토리지를 어떻게 프로비저닝 할 지 정의하는 객체다.
  • 스토리지 클래스를 정의하는 예시 파일은 아래와 같다.
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: manual
    provisioner: kubernetes.io/no-provisioner
    volumeBindingMode: WaitForFirstConsumer

PVC 의 resources.requests.storage 속성은 PVC 가 필요로 하는 최소한의 스토리지 용량을 의미하는데, 여기서는 최소 3GB 이상의 PV 를 필요하다는 것을 의미한다. 이 클레임은 스토리지 클래스의 이름이 manual 인 PV 에 연결한다.

파드에서는 task-pv-claim 이라는 클레임 이름을 가진 PVC 를 할당했다. 앞에서 정의한 PVC 에 따라 manual 스토리지 클래스에 해당하는 PV 의 /usr/share/nginx/html 에 경로에 데이터를 저장한다. 실제로는 노드 파일 시스템 관점에서 보면 /mnt/data/usr/share/nginx/html 경로에 파드의 데이터가 저장되는 것이다.

PVC 를 사용하는 이유는 클러스터를 이용하는 개발자가 PV 에 대해 정확히 몰라도 데이터를 저장할 수 있도록 지원하기 위함이다.

데이터를 노드 파일 시스템에 저장할 지, 네트워크 파일 시스템에 저장할 지는 시스템 관리자가 정하고, 개발자는 그 부분까지는 신경쓰지 않도록 역할을 분리한 것이다.

아래의 이미지처럼 PV 는 시스템 관리자가 직접 설정해주고, 개발자는 PVC 의 스토리지 클래스만 알면 데이터를 영속적으로 관리할 수 있다.


출처: 쿠버네티스 PV/PVC [velog]

또한, RBAC 을 이용해서 사용자나 서비스 계정에 따라 PVC 를 포함한 다양한 리소스에 권한을 제한할 수 있다.

아래의 파일은 PVC 를 생성할 수 있는 권한을 정의한 예시이다.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pvc-creator
rules:
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["create"]

pvc-creator 라는 이름의 역할은 PVC 를 생성할 수 있는 권한을 가질 수 있도록 했다.

아래의 파일은 pvc-creator 역할을 alice 라는 유저에게 부여하는 예시이다.

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pvc-creator-binding
subjects:
- kind: User
  name: alice
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pvc-creator
  apiGroup: rbac.authorization.k8s.io

PV/PVC 사용 실습

컨테이너 이미지 파일 시스템 사용하는 경우

PV 를 사용했을 때 데이터가 영구적으로 저장된다는 것을 확인하기 위해 PV 를 사용하지 않으면 파드의 삭제와 함께 데이터도 삭제되는지 확인해보자.

아래의 yaml 파일은 10초마다 현재 시간을 /home/pod-out.txt 파일의 마지막에 추가하는 파드를 정의한 것이다.

# date-busybox-pod.yaml

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 exec busybox -- tail -f /home/pod-out.txt
#Tue Mar 19 06:17:19 UTC 2024
#Tue Mar 19 06:17:29 UTC 2024
#...

마지막으로 저장된 시간은 Tue Mar 19 06:17:29 UTC 2024 이다.

이 파드를 삭제하고 다시 실행해서 파일을 살펴보면 아래와 같다.

kubectl delete pod busybox
#pod "busybox" deleted

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

kubectl exec busybox -- tail -f /home/pod-out.txt
#Tue Mar 19 06:17:45 UTC 2024

출력 내용이 이전에 삭제한 파드에서 마지막으로 저장한 시간과 다르다는 것을 알 수 있다.

즉, 기본적으로 파드는 컨테이너 이미지 파일 시스템에 파일을 저장하기 때문에 파드를 삭제하면 파일 시스템 안에 저장된 데이터도 함께 삭제되는 것이다.

PV/PVC 사용하는 경우

PV 는 local-path-provisioner(Github 링크) 를 이용해서 노드의 파일 시스템에 PV 를 생성하도록 했다.

이를 이용하면 storageClass 에 local-path 로 입력해서 PV 를 사용할 수 있다.

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

스토리지 클래스를 확인해보면 아래와 같이 local-path 로 생성된 것을 확인할 수 있다.

kubectl get sc local-path
#NAME         PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
#local-path   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  2m15s

우선 아래와 같은 PVC 를 먼저 생성한다.

# localpath1.yaml

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   Pending                                      local-path     15s

kubectl describe pvc
#Name:          localpath-claim
#Namespace:     default
#StorageClass:  local-path
#Status:        Pending
#Volume:
#Labels:        <none>
#Annotations:   <none>
#Finalizers:    [kubernetes.io/pvc-protection]
#Capacity:
#Access Modes:
#VolumeMode:    Filesystem
#Used By:       <none>
#Events:
#  Type     Reason              Age                From                         Message
#  ----     ------              ----               ----                         -------
#  Normal   WaitForFirstConsumer  32s (x12 over 3m17s)    persistentvolume-controller  waiting for first consumer to be created before binding

그리고 이번에는 5초마다 현재 시간을 파일에 저장하는 파드를 실행하고, 위에서 생성한 PVC 를 이용해서 PV 에 데이터를 저장할 수 있도록 아래의 yaml 파일을 사용했다.

# localpath2.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: localpath-claim
kubectl apply -f localpath2.yaml

생성된 파드, PV, PVC 를 확인하면 아래와 같다.

kubectl get pod,pv,pvc
#NAME      READY   STATUS    RESTARTS   AGE
#pod/app   1/1     Running   0          24s

#NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                     STORAGECLASS   REASON   AGE
#persistentvolume/pvc-fc125d30-6353-4808-83a6-b9d3c8d3a7cc   1Gi        RWO            Delete           Bound    default/localpath-claim   local-path              18s

#NAME                                    STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
#persistentvolumeclaim/localpath-claim   Bound    pvc-fc125d30-6353-4808-83a6-b9d3c8d3a7cc   1Gi        RWO            local-path     8m29s

PV 와 PVC 의 STATUS 가 Bound 로 되어 있다는 것을 확인할 수 있다.

파드 안에서 데이터가 저장되고 있는 지 확인해본다.

kubectl exec -it app -- tail -f /data/out.txt
#Tue Mar 19 06:37:56 UTC 2024
#Tue Mar 19 06:38:01 UTC 2024
#Tue Mar 19 06:38:06 UTC 2024
#Tue Mar 19 06:38:11 UTC 2024
#Tue Mar 19 06:38:16 UTC 2024
#Tue Mar 19 06:38:21 UTC 2024
#Tue Mar 19 06:38:26 UTC 2024
#Tue Mar 19 06:38:31 UTC 2024
#Tue Mar 19 06:38:36 UTC 2024
#Tue Mar 19 06:38:41 UTC 2024
#...

참고로 지금 생성된 PV 는 현재 노드에서만 사용할 수 있다.

다른 노드의 파일 시스템에서 PV 의 경로를 확인해보면 출력되지 않는 것을 확인할 수 있다.

for node in $N1 $N2 $N3; do ssh ec2-user@$node tree /opt/local-path-provisioner; done

#/opt/local-path-provisioner
#`-- pvc-fc125d30-6353-4808-83a6-b9d3c8d3a7cc_default_localpath-claim
#    `-- out.txt
#1 directory, 1 file --> 1번 노드에만 PV 가 생성되었고, 1번 노드만 사용 가능

#/opt/local-path-provisioner [error opening dir]
#0 directories, 0 files

#/opt/local-path-provisioner [error opening dir]
#0 directories, 0 files

파드를 삭제하고 다시 실행해서 데이터의 내용을 살펴보자.

이번에는 파일의 마지막이 아닌 파일의 처음 부분을 확인해볼 것이다.

kubectl delete pod app

kubectl exec -it app -- head /data/out.txt # 파일의 첫 부분을 확인
#Tue Mar 19 06:37:56 UTC 2024
#Tue Mar 19 06:38:01 UTC 2024
#Tue Mar 19 06:38:06 UTC 2024
#Tue Mar 19 06:38:11 UTC 2024
#...

파드가 삭제되었지만 파드의 데이터가 정상적으로 PV 에 저장되어 영구적으로 남아있는 것을 확인할 수 있다.

컨테이너 스토리지 인터페이스(CSI)


출처: [쿠버네티스 쉽게 이해하기 11] 데이터 저장소 사용을 위한 PV/PVC [티스토리]

파드에는 위의 이미지처럼 네트워크 볼륨을 파드에 마운트 할 수 있다.

아래의 파일은 네트워크 볼륨을 마운트 하는 예시이다.

# NFS PersistentVolume 정의
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /path/to/nfs/share
    server: nfs-server-ip

# NFS PersistentVolumeClaim 정의
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

# Pod 정의
apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - mountPath: "/usr/share/nginx/html"
      name: nfs-volume
  volumes:
  - name: nfs-volume
    persistentVolumeClaim:
      claimName: nfs-pvc

하지만 이렇게 되면 쿠버네티스와 노드 간에 결합이 강해지는 문제가 생긴다.

예를 들어, nfs 네트워크 볼륨을 마운트 하기 위해서는 nfs 서버의 IP 나 호스트를 지정해야 하는데, nfs 서버의 IP 가 변경되면 서비스를 이용할 수 없는 문제가 생긴다.


출처: [쿠버네티스 쉽게 이해하기 11] 데이터 저장소 사용을 위한 PV/PVC [티스토리]

쿠버네티스는 PV, PVC, CSI 를 중계자 역할로 만들었고, 그 중에서도 CSI 는 다양한 스토리지 솔루션(AWS EBS, EFS 등)을 이용할 때 사용한다.

CSI 는 클러스터에 별도로 설치해주어야 하며, 스토리지 솔루션의 스토리지 클래스를 생성할 수 있다.

아래의 파일은 example.com/nfs 라는 프로비저너를 통해 스토리지 클래스를 만드는 예시이다.

# NFS StorageClass 정의
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storage
provisioner: example.com/nfs
volumeBindingMode: WaitForFirstConsumer

# NFS PersistentVolumeClaim 정의
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  storageClassName: nfs-storage
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

# Pod 정의
apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - mountPath: "/usr/share/nginx/html"
      name: nfs-volume
  volumes:
  - name: nfs-volume
    persistentVolumeClaim:
      claimName: nfs-pvc

이처럼 CSI 를 이용하면 AWS, GCP, Azure 등 스토리지 솔루션을 사용할 수 있다.

AWS EBS Controller

앞서 쿠버네티스에서 파드의 데이터를 영속적으로 관리하기 위한 방법을 다루었다.

그 중에서도 AWS EBS 에 연결해서 저장하는 실습 과정을 정리했다.

참고로 EC2 에서 사용할 수 있는 스토리지는 크게 4가지이다.


참고: Storage options for your Amazon EC2 instances [aws docs]

  1. EFS : 동시에 여러 인스턴스가 사용 가능
  2. EBS : 하나의 인스턴스에만 마운트 가능
  3. Instance Store : EC2 인스턴스 종료 시 함께 사라짐. I/O 속도 가장 빠름
  4. S3 : 객체 스토리지 서비스. 데이터를 무한대로 저장 가능.

각각에 대한 설명은 자세한 생략하고 이 글에서는 S3 를 제외한 나머지 3개의 스토리지에 연결하는 것을 정리했다.

S3 도 CSI 를 지원하지만 같지만, 공식 문서에 따르면 아직 S3 는 Fargate 나 윈도우 기반 컨테이너 이미지와는 호환되지 않고, 정적 프로비저닝만 가능하다고 하다.

즉, 필요하면 동적으로 S3 버킷을 자동으로 생성해주는 동적 프로비저닝은 못한다는 의미이다.

EBS(Elastic Block Storage)

AWS EBS 는 EC2 인스턴스의 데이터를 영구적으로 저장할 수 있는 스토리지 볼륨이다.

EBS 는 EC2 인스턴스와 물리적으로 동일한 장치에 있는 인스턴스 스토어(Instance Store)가 아닌 네트워크를 통해서 EC2 인스턴스에 마운트 한다.

그래서 EC2 와 EBS 가 같은 가용 영역에 있어야 사용할 수 있다.

EBS 는 기본적으로 하나의 EC2 인스턴스에만 마운트 할 수 있다.

동시에 여러 EC2 인스턴스에 같은 EBS 를 마운트 할 수 없는데, 이 경우에는 EFS 를 사용할 수 있다.

이 같은 특성 때문에 EBS 를 PV 로 사용하고자 한다면 PV 와 PVC 의 accessModes 는 ReadWriteOnce(RWO) 로 설정해주어야 한다.

RWO 액세스 모드는 한 번에 하나의 노드에만 마운트 되는데, 이는 동시에 여러 노드에서 동일한 볼륨에 쓰기를 시도하는 경우에 발생하는 데이터 충돌을 방지하기 위해서다.

실습

EKS 에 EBS CSI driver 설치하기

EKS 에서 EBS 를 사용하기 위해서는 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 가 생성되었는지 확인한다.

eksctl get iamserviceaccount --cluster myeks
#NAMESPACE	NAME				ROLE ARN
#kube-system	ebs-csi-controller-sa		arn:aws:iam::265524074804:role/AmazonEKS_EBS_CSI_DriverRole

Amazon EBS CSI Driver addon 을 추가한다.

# 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}
#NAME			VERSION			STATUS		ISSUES	IAMROLEUPDATE AVAILABLE	CONFIGURATION VALUES
#aws-ebs-csi-driver	v1.28.0-eksbuild.1	CREATING	0	arn:aws:iam::265524074804:role/AmazonEKS_EBS_CSI_DriverRole
#...

설치가 완료되면 EBS CSI Driver 를 위한 ebs-csi-controller 파드에 총 6개 컨테이너가 생성된다.

kubectl get pod -n kube-system -l app=ebs-csi-controller -o jsonpath='{.items[0].spec.containers[*].name}' ; echo
#ebs-plugin csi-provisioner csi-attacher csi-snapshotter csi-resizer liveness-probe

EBS 스토리지 클래스 생성

아래의 yaml 파일은 EBS gp3 스토리지 클래스를 정의한 것이다.

# gp3-sc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp3
allowVolumeExpansion: true
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  allowAutoIOPSPerGBIncrease: 'true'
  encrypted: 'true'
  fsType: xfs # 기본값이 ext4

스토리지 클래스를 생성한다.

kubectl apply -f gp3-sc.yaml

스토리지 클래스를 확인하면 gp3 스토리지 클래스가 생성된 것을 확인할 수 있다.

kubectl get sc
#NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
#gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  48m
#gp3             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   10s
#local-path      rancher.io/local-path   Delete          WaitForFirstConsumer   false                  30m

PVC 생성 및 PV 연결

EBS 에 연결하기 위한 PVC 를 아래와 같이 정의했다.

# awsebs-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  storageClassName: gp3

PVC 와 PV 를 확인해보면 아래와 같이 출력된다.

kubectl get pvc,pv
#NAME                              STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
#persistentvolumeclaim/ebs-claim   Pending                                      gp3            11s

PVC 는 생성되었지만, PV 는 출력되지 않았다.

아직 파드와 PV 를 연결하지 않았기 때문이다.

파드를 생성하기 위해 아래의 yaml 파일을 사용했다. 이 파드도 5초마다 현재 시간을 파일에 저장하도록 했다.

# 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

파드를 배포하고 PVC, PV 를 확인해보면 아래와 같다.

kubectl apply -f awsebs-pod.yaml

kubectl get pvc,pv
#NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
#persistentvolumeclaim/ebs-claim   Bound    pvc-420a109f-312a-4150-8109-ceeda1ec4b2f   4Gi        RWO            gp3            3m4s

#NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
#persistentvolume/pvc-420a109f-312a-4150-8109-ceeda1ec4b2f   4Gi        RWO            Delete           Bound    default/ebs-claim   gp3                     35s

파드를 생성하면서 PVC 가 PV 를 요청했고, AWS EBS 가 새롭게 프로비저닝 되어 파드에 마운트 된 것을 확인할 수 있다.

추가로 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
#...

nodeAffinity 는 리소스를 스케줄링 할 때 어떤 노드에 붙을 지 지정하는 옵션이다.

여기서는 PV 가 바인딩 될 수 있는 노드는 EBS CSI 를 사용하고, ap-northeast-2b 가용 영역에 있는 노드로만 제한했다.

AWS 콘솔에서도 PVC 에 의해 EBS 볼륨이 생성된 것을 확인할 수 있다.

AWS Volume Snapshot Controller

AWS EBS 는 스냅샷 기능을 지원한다.

스냅샷은 EBS 의 특정 시점에 있는 데이터를 사진처럼 찍어서 나중에 필요할 때 백업을 목적으로 사용할 수 있다.

EBS Controller 와 마찬가지로 EKS 에 Snapshot Controller 를 설치하면 스냅샷을 생성하는 것이 가능하다.

실습

volumesnapshots 컨트롤러 설치

아래의 명령어를 실행해서 Snapshot CRD 를 생성했다.

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    2024-03-19T07:29:42Z
#volumesnapshotcontents.snapshot.storage.k8s.io   2024-03-19T07:29:42Z
#volumesnapshots.snapshot.storage.k8s.io          2024-03-19T07:29:42Z

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

다음으로 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

정상적으로 설치되었는지 확인한다.

# Deployment 확인
kubectl get deploy -n kube-system snapshot-controller
#NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
#snapshot-controller   2/2     2            2           3m23s

# Pod 확인
kubectl get pod -n kube-system | grep snapshot-controller
#snapshot-controller-749cb47b55-4hz6q           1/1     Running   0          3m54s
#snapshot-controller-749cb47b55-cl7jc           1/1     Running   0          3m54s

마지막으로 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
#NAME          DRIVER            DELETIONPOLICY   AGE
#csi-aws-vsc   ebs.csi.aws.com   Delete           67s

VolumeSnapshot 생성하기

이전에 사용했던 PVC 와 파드를 다시 생성해서 VolumeSnapshot 을 생성해보고자 한다.

PVC 와 파드를 생성하고 나서 아래의 yaml 파일을 이용해서 EBS 의 스냅샷을 생성한다.

# ebs-volume-snapshot.yaml

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

정상적으로 생성되었는지 확인한다.

kubectl get volumesnapshot

#NAME                  READYTOUSE   SOURCEPVC   SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS   SNAPSHOTCONTENT                                    CREATIONTIME   AGE
#ebs-volume-snapshot   false        ebs-claim                           4Gi           csi-aws-vsc     snapcontent-983260be-d89f-4746-9e8c-70e0a5b0efa3   4s             5s

AWS 콘솔에서도 스냅샷이 생성된 것을 확인할 수 있다.

스냅샷으로 복원하기

볼륨 스냅샷을 이용해서 PVC 를 생성해보자.

우선 파드와 pvc, ebs-claim 을 삭제한다.

kubectl delete pod app && kubectl delete pvc ebs-claim

스냅샷을 이용한 PVC 복원에 사용한 yaml 파일은 아래와 같다.

# 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

복원을 진행한다.

kubectl apply -f ebs-snapshot-restored-claim.yaml

PVC 는 생성되었지만 PV 는 생성되지 않았다.

kubectl get pvc,pv
#NAME                                                STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
#persistentvolumeclaim/ebs-snapshot-restored-claim   Pending                                      gp3            48m

PV 생성을 위해 파드를 생성한다.

파드 생성을 위해 아래의 yaml 파일을 사용했다.

# ebs-snapshot-restored-pod.yaml

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

PVC 와 PV 를 확인하면 PV 도 생성된 것을 확인할 수 있다.

kubectl get pvc,pv
#NAME                                                STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
#persistentvolumeclaim/ebs-snapshot-restored-claim   Bound    pvc-0cd0f9a9-77ef-4104-8e51-dad64eaf4831   4Gi        RWO            gp3            50m

#NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                 STORAGECLASS   REASON   AGE
#persistentvolume/pvc-0cd0f9a9-77ef-4104-8e51-dad64eaf4831   4Gi        RWO            Delete           Bound    default/ebs-snapshot-restored-claim   gp3                     8s

/data/out.txt 에 저장된 내용을 확인하면 아래와 같다.

kubectl exec app -- cat /data/out.txt
#Tue Mar 19 07:37:36 UTC 2024
#Tue Mar 19 07:37:41 UTC 2024
#Tue Mar 19 07:37:46 UTC 2024
#Tue Mar 19 07:37:51 UTC 2024
#Tue Mar 19 07:37:56 UTC 2024
#Tue Mar 19 07:38:01 UTC 2024
#Tue Mar 19 07:38:06 UTC 2024 ---> 스냅샷의 마지막 데이터
#Tue Mar 19 08:34:33 UTC 2024 ---> 복원 후 새롭게 생성된 데이터
#Tue Mar 19 08:34:38 UTC 2024
#Tue Mar 19 08:34:43 UTC 2024
#Tue Mar 19 08:34:48 UTC 2024
#Tue Mar 19 08:34:53 UTC 2024
#Tue Mar 19 08:34:58 UTC 2024

스냅샷의 마지막 데이터로부터 새롭게 데이터를 저장하고 있는 것을 확인할 수 있다.

AWS EFS Controller

EBS 는 한 번에 하나의 EC2 인스턴스에만 마운트 되기 때문에 여러 노드가 같은 데이터를 공유하기 어렵다는 문제가 있었다.

이를 해결하기 위해 AWS EFS 를 이용해보자.

EFS 를 이용한 클러스터 구성은 아래의 이미지와 같다.


출처: AWS EKS With EFS CSI Driver And IRSA Using CDK [dev.to]

실습

EFS CSI Driver 설치

EKS 에서 제공하는 addon(추가기능)을 이용해서 설치했다. (공식 문서 링크)

우선 IAM 역할을 생성한다.

export CLUSTER_NAME=my-eks
export ROLE_NAME=AmazonEKS_EFS_CSI_DriverRole
eksctl create iamserviceaccount \
    --name efs-csi-controller-sa \
    --namespace kube-system \
    --cluster $CLUSTER_NAME \
    --role-name $ROLE_NAME \
    --role-only \
    --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEFSCSIDriverPolicy \
    --approve

그 다음 IAM 역할에 EFS CSI Controller 와 관련된 정책을 assume 한다.

TRUST_POLICY=$(aws iam get-role --role-name $ROLE_NAME --query 'Role.AssumeRolePolicyDocument' | \
    sed -e 's/efs-csi-controller-sa/efs-csi-*/' -e 's/StringEquals/StringLike/')
aws iam update-assume-role-policy --role-name $ROLE_NAME --policy-document "$TRUST_POLICY"    

IRSA 를 확인한다.

eksctl get iamserviceaccount --cluster myeks
#NAMESPACE	NAME				ROLE ARN
#kube-system	efs-csi-controller-sa		arn:aws:iam::265524074804:role/AmazonEKS_EFS_CSI_DriverRole

EFS CSI Driver addon 을 추가하기 위해 아래의 명령어를 실행한다.

eksctl create addon \
  --name aws-efs-csi-driver \
  --cluster ${CLUSTER_NAME} \
  --service-account-role-arn arn:aws:iam::${ACCOUNT_ID}:role/AmazonEKS_EFS_CSI_DriverRole \
  --force

addon 이 설치되었는지 아래의 명령어를 실행해서 확인한다.

eksctl get addon --cluster ${CLUSTER_NAME}
#NAME			VERSION			STATUS	ISSUES	IAMROLE		UPDATE AVAILABLE	CONFIGURATION VALUES
#aws-efs-csi-driver	v1.7.6-eksbuild.1	ACTIVE	0	arn:aws:iam::265524074804:role/AmazonEKS_EFS_CSI_DriverRole

노드 연결 확인

EFS 에 여러 파드가 사용할 수 있는 지 확인해보자.

우선 EFS 를 배스천 호스트의 /mnt/myefs 경로에 마운트 하기 위해 아래의 명령어를 사용했다.

mount -t efs -o tls $EfsFsId:/ /mnt/myefs

여기서는 AWS EFS 에서 제공하는 예시를 기반으로 확인하고자 한다.

예시 코드를 다운 받는다.

# 예시 코드 다운
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

생성한 스토리지 클래스는 아래와 같다.

# storageclass.yaml

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com

스토리지 클래스를 생성한다.

kubectl apply -f storageclass.yaml

PV 생성을 위해 EFS 파일 시스템 ID 를 적용한다.

EfsFsId=$(aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text)
sed -i "s/fs-4af69aab/$EfsFsId/g" pv.yaml

pv.yaml 파일의 내용은 아래와 같다.

# pv.yaml

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-09b6e666a9f2be276

PV 를 생성한다.

kubectl apply -f pv.yaml

PVC 는 아래의 yaml 파일을 사용해서 생성했다.

# claim.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi

PVC 를 생성한다.

kubectl apply -f claim.yaml

아래의 yaml 파일을 이용해서 파드를 생성했다.

# pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app1
spec:
  containers:
  - name: app1
    image: busybox
    command: ["/bin/sh"]
    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
      
# pod2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app2
spec:
  containers:
  - name: app2
    image: busybox
    command: ["/bin/sh"]
    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

파드를 생성한다.

kubectl apply -f pod1.yaml,pod2.yaml

현재 접속한 배스천 호스트에서 EFS 파일 시스템 디렉토리를 확인한다.

tree /mnt/myefs
#/mnt/myefs
#|-- out1.txt
#`-- out2.txt

각각 다른 파드에서 실행되고 있는 pod1 과 pod2 에서 저장한 데이터를 배스천 호스트에서도 확인할 수 있다.

AWS EKS Node Group

EKS 에서는 클러스터에 부하가 높아지면 자동으로 노드(EC2 인스턴스)를 추가하는 오토 스케일링 기능을 제공한다.

이때, 오토 스케일링으로 생성되는 노드의 타입(프로세서, CPU, RAM 등)을 미리 지정해놓을 수 있다.

최대 노드를 몇 개까지 확장시킬 지, 평소에는 몇 개를 유지할 지 등 미리 사전에 설정할 수 있는 것이 노드 그룹(Node Group)이다.

참고로 오토 스케일링을 위한 도구로는 쿠버네티스 클러스터 오토스케일러(CA)와 Karpenter 가 있다.

여기서는 eksctl 을 이용해서 노드 그룹을 생성하고, 노드 그룹에서 설정한 인스턴스가 생성되는 것을 확인하고자 한다.

실습

노드 그룹 생성

여기서는 ARM 기반 graviton 프로세서를 사용하는 t4g.medium 을 노드 그룹에 생성해보고자 한다.

노드 그룹을 생성하기 위해 아래의 명령어를 이용해서 yaml 파일을 생성했다.

eksctl create nodegroup -c $CLUSTER_NAME -r $AWS_DEFAULT_REGION --subnet-ids "$PubSubnet1","$PubSubnet2","$PubSubnet3" --ssh-access \
  -n ng3 -t t4g.medium -N 1 -m 1 -M 1 --node-volume-size=30 --node-labels family=graviton --dry-run > myng3.yaml

yaml 파일 내용은 아래와 같다.

# myng3.yaml

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
managedNodeGroups:
- amiFamily: AmazonLinux2
  desiredCapacity: 1
  disableIMDSv1: true
  disablePodIMDS: false
  iam:
    withAddonPolicies:
      albIngress: false
      appMesh: false
      appMeshPreview: false
      autoScaler: false
      awsLoadBalancerController: false
      certManager: false
      cloudWatch: false
      ebs: false
      efs: false
      externalDNS: false
      fsx: false
      imageBuilder: false
      xRay: false
  instanceSelector: {}
  instanceType: t4g.medium
  labels:
    alpha.eksctl.io/cluster-name: myeks
    alpha.eksctl.io/nodegroup-name: ng3
    family: graviton
  maxSize: 1
  minSize: 1
  name: ng3
  privateNetworking: false
  releaseVersion: ""
  securityGroups:
    withLocal: null
    withShared: null
  ssh:
    allow: true
    publicKeyPath: ~/.ssh/id_rsa.pub
  subnets:
  - subnet-082b50963d6c944ef
  - subnet-03e23edb6dd18f876
  - subnet-029b2b9933d83de6f
  tags:
    alpha.eksctl.io/nodegroup-name: ng3
    alpha.eksctl.io/nodegroup-type: managed
  volumeIOPS: 3000
  volumeSize: 30
  volumeThroughput: 125
  volumeType: gp3
metadata:
  name: myeks
  region: ap-northeast-2
  version: "1.28"

아래의 명령어를 실행해서 노드 그룹을 생성한다.

eksctl create nodegroup -f myng3.yaml

노드 그룹을 확인하면 arm64 로 ng3 노드 그룹이 생성된 것을 확인할 수 있다.

kubectl get nodes --label-columns eks.amazonaws.com/nodegroup,kubernetes.io/arch
#NAME                                               STATUS   ROLES    AGE     VERSION               NODEGROUP   ARCH
#ip-192-168-1-17.ap-northeast-2.compute.internal    Ready    <none>   41m     v1.28.5-eks-5e0fdde   ng1         amd64
#ip-192-168-2-118.ap-northeast-2.compute.internal   Ready    <none>   41m     v1.28.5-eks-5e0fdde   ng1         amd64
#ip-192-168-3-186.ap-northeast-2.compute.internal   Ready    <none>   41m     v1.28.5-eks-5e0fdde   ng1         amd64
#ip-192-168-3-236.ap-northeast-2.compute.internal   Ready    <none>   2m18s   v1.28.5-eks-5e0fdde   ng3         arm64

ng3 노드 그룹에 taint 를 설정해서 파드가 스케줄링 될 때 ng3 그룹에 배치되도록 할 것이다.

파드는 toleration 옵션에서 fronted 라는 키를 가지고, NO_EXECUTE 라는 effect 가 명시되어 있어야 ng3 에 배치될 수 있다.

참고로 NO_EXECUTE 옵션은 toleration 이 없으면 파드가 스케줄링 되지 않으며, 기존에 실행되던 파드도 toleration 이 없으면 종료시키는 옵션이다.

aws eks update-nodegroup-config --cluster-name $CLUSTER_NAME --nodegroup-name **ng3** --taints "addOrUpdateTaints=[{key=frontend, value=true, effect=**NO_EXECUTE**}]"

참고로 taint 는 nodeAffinity 와 비슷한 개념이라고 생각하면 된다.

nodeAffinity 는 어느 자원에 붙을 지 명시하는 것이고, taint 는 toleration (용인) 이 적혀있는 리소스만 붙을 수 있도록 접근을 제한하는 것이다.

taint 설정이 되었는지 확인한다.

kubectl describe nodes --selector family=graviton | grep Taints
#Taints:             frontend=true:NoExecute

파드 배포

아래의 yaml 파일을 이용해서 파드를 배포한다.

# busybox.yaml

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"
  tolerations:
    - effect: NoExecute
      key: frontend
      operator: Exists
kubectl apply -f busybox.yaml

파드가 배포되었는 지 확인한다.

k get pod -owide
#NAME      READY   STATUS    RESTARTS   AGE     IP              NODE                                               NOMINATED NODE   READINESS GATES
#busybox   1/1     Running   0          6m54s   192.168.3.100   ip-192-168-3-236.ap-northeast-2.compute.internal   <none>           <none>

ip-192-168-3-236.ap-northeast-2.compute.internal 라는 이름을 가진 노드에 배포된 것을 확인할 수 있다.

ng3 이라는 노드 그룹에 속한 노드에 대한 정보를 찾아보면 아래와 같다.

kubectl describe node -l eks.amazonaws.com/nodegroup=ng3
#Name:               ip-192-168-3-236.ap-northeast-2.compute.internal
#Roles:              <none>
#Labels:             alpha.eksctl.io/cluster-name=myeks
#                    alpha.eksctl.io/nodegroup-name=ng3
#                    beta.kubernetes.io/arch=arm64
#                    beta.kubernetes.io/instance-type=t4g.medium
# ...
#Non-terminated Pods:          (5 in total)
#  Namespace                   Name                  CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
#  ---------                   ----                  ------------  ----------  ---------------  -------------  ---
#  default                     busybox               0 (0%)        0 (0%)      0 (0%)           0 (0%)         9m41s
#  kube-system                 aws-node-h6hzh        50m (2%)      0 (0%)      0 (0%)           0 (0%)         21m
#  kube-system                 ebs-csi-node-9g5wq    30m (1%)      0 (0%)      120Mi (3%)       768Mi (23%)    21m
#  kube-system                 efs-csi-node-2tcsb    0 (0%)        0 (0%)      0 (0%)           0 (0%)         21m
#  kube-system                 kube-proxy-svrqf      100m (5%)     0 (0%)      0 (0%)           0 (0%)         21m
# ...

Name 속성에는 busybox 가 배포된 노드의 이름이 적혀있고, 실행 중인 파드를 살펴봐도 busybox 라는 이름으로 실행되고 있는 것을 확인할 수 있다.

후기

시간이 갈수록 쿠버네티스에 대한 이해도가 높아지고 있는 것을 느끼고 있다.

쿠버네티스를 처음 학습할 때는 PV, PVC, CSI, taint, nodeAffinity 개념이 잘 이해되지 않았는데, 반복해서 보다보니 이해되기 시작했다.

컨테이너가 종료되면 왜 데이터가 영구적으로 남지 않고 휘발성으로 사라지는 이유를 이해하는데는 컨테이너를 직접 만들어보는 실습을 했던 것이 큰 도움이 됐다.

또한, 원활한 실습 환경을 마련해주신 가시다님 덕분에 쿠버네티스의 본질에 집중해서 공부할 수 있었다고 생각한다.

아무 것도 모르는 상태에서 나 혼자 이 모든 환경을 설정하고 삽질 했다면 시간도 많이 걸렸을 거고, 삽질하다가 지쳐버려서 포기했을 수도 있을 것 같다.

물론 삽질도 귀한 경험이지만, 쿠버네티스에 대한 이해나 AWS 생태계에 대한 이해 없이 삽질 한다는 건 정말 힘든 일 같다.

그리고 EFS CSI Driver 를 addon 으로 설치해봤는데, 생각보다 쉽게 진행되어서 놀랐다.

도전과제에 있길래 막연히 어렵다고만 생각했는데, 의외로 스터디에서 helm 으로 설치하는 방법보다 훨씬 간단했다.

실습을 진행할수록 AWS 가 정말 서비스들을 잘 만들었다는 감탄이 절로 나온다.

추상화가 잘 되어 있다보니 직접 환경 세팅에 들어가는 수고가 많이 줄어드는 것을 느꼈다.

1주차에는 내가 과연 이 스터디를 잘 따라갈 수 있을까 너무 걱정도 되고, 압박감도 많이 느꼈다.

EKS 를 실제로 써보는 것도 처음이고, 쿠버네티스도 막연하게 알고 있는 상태였기 때문에 부담을 느꼈던 것 같다.

하지만 시간이 지날수록 모르는 것들이 하나씩 이해가 되니까 재미있어졌고, 할 수 있다는 자신감도 생겼다.

모르는 것은 ChatGPT 와 Phind 와 같은 인공지능 서비스로 물어보면서 개념의 큰 흐름을 잡았다.

인공지능은 틀릴 수도 있으니 사실 확인이 필요한 부분은 공식 문서나 다른 블로그 자료를 참고하면서 완성도를 높여나갔다.

다음 스터디 주제도 꽤 기대된다!

참고

profile
성장하는 개발자, 한준혁입니다.

0개의 댓글