Engineering/Kubernetes (K8S)

[Kubernetes] Volume - Deep Dive (EmptyDir, HostPath, Network Volume)

[앙금빵] 2021. 11. 23.

개요

  • Pod는 내부에 프로세스가 실행되고 CPU, RAM, 네트워크 인터페이스 등의 리소스를 공유하는 논리적 호스트와 유사하다.
  • Pod 내부각 컨테이너는 고유하게 분리된 파일시스템을 가진다. (파일시스템은 컨테이너 이미지에서 제공되기 때문이다.)

특정 Scenario에서는 물리 머신에서 Process를 다시 시작하는 것과 같이 새로운 컨테이너가 이전에 종료된 위치에서 계속되기를 원할 수 있다.

 

Storage Volume

쿠버네티스는 Storage Volume을 정의하는 방법으로 이 기능을 제공한다. Storage Volume은 Pod와 같은 최상위 리소스는 아니지만 Pod의 일부분으로 정의되며 Pod와 동일한 Lifecycle을 가진다.

 

즉, 파드가 시작되면 Volume이 생성되고, 파드가 삭제되면 볼륨이 삭제된다는 것을 의미한다.

 

이를 통해 생성되는 새로운 컨테이너는 이전 컨테이너가 볼륨에 기록한 모든 파일들을 볼 수 있다. 또한 파드가 여러 개의 컨테이너를 가진 경우 모든 컨테이너가 볼륨을 공유할 수 있다.

 

요약

  • 쿠버네티스 Volume은 Pod의 구성 요소로 컨테이너와 동일하게 Pod Spec에서 정의된다.
  • 볼륨은 쿠버네티스 오브젝트가 아니므로 자체적으로 생성, 삭제될 수 없다.
  • 볼륨은 Pod의 모든 컨테이너에서 사용 가능하지만 접근하려는 컨테이너에서 각각 마운트 되어야 한다. 각 컨테이너에서 파이시스템의 어느 경로에나 볼륨을 마운트할 수 있다.

1. 로컬 볼륨 : hostPath, emptyDir

로컬 볼륨 (Local Volume)

자주 사용되는 Volume종류는 아니다.

  • emptyDir: ephemeral Data를 저장하는데 사용되는 간단한 empty 디렉터리이다.
  • hostPath: Node 파일시스템을 Pod의 디렉터리로 마운트하는데 사용한다.

 

1-1. 포드 내의 컨테이너 간 임시 데이터 공유 : emptyDir

 

EmptyDir Volume 특징

Pod의 컨테이너 간에 Volume을 공유하기 위해 사용하며 컨테이너 간 파일을 공유할 때 유용하다.

Pod가 실행되는 도중에만 필요한 휘발성 데이터를 각 컨테이너가 함께 사용할 수 있도록 임시 저장 공간을 생성한다.

 

EmptyDir Volume 공유 시나리오

① emptydir-pod YAML 파일 생성 (컨테이너 1, 컨테이너 2)

② 컨테이너 1 (content-creator)에 /data 디렉토리에 test.html 작성

③ 컨테이너 2 (apache-webserver) 에서의 /usr/local/apache2/htdocs/ 디렉토리와 컨테이너 1의 /data Volume과 공유된다.

즉, 컨테이너 2의 /usr/local/apache2/htdocs/ 에 test.html 이 저장되었다고 볼 수 있으며 Pod IP 주소를 호출시 /htdocs/test.html 파일을 불러들인다.

 

emptydir-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
spec:
  containers:

# 첫 컨테이너는 content-creator 이라고 이름 짓고 alicek106/alpine-wget:latest 이미지를 실행
  - name: content-creator
    image: alicek106/alpine-wget:latest
    args: ["tail", "-f", "/dev/null"]

# my-emptydir-volume 이라는 볼륨을 컨테이너의 /data 에 마운트한다
# 마운트 = 디스크 파티션을 특정한 위치(디렉토리)에 연결시켜주는 과정
    volumeMounts:
    - name: my-emptydir-volume
      mountPath: /data                      # 1. 이 컨테이너가 /data 에 파일을 생성하면

# my-emptydir-volume이라는 볼륨을 컨테이너의 /usr/local/apache2/htdocs/에 마운트한다
  - name: apache-webserver
    image: httpd:2
    volumeMounts:
    - name: my-emptydir-volume
      mountPath: /usr/local/apache2/htdocs/  # 2. 아파치 웹 서버에서 접근 가능합니다.

# my-emptydir-volume 이란 단일 볼륨을 위의 컨테이너 2개에 마운트한다.
  volumes:
    - name: my-emptydir-volume
      emptyDir: {}                             # 포드 내에서 파일을 공유하는 emptyDir

 

EmptyDir Volume 공유 확인

root@k8s-m:~# kubectl apply -f emptydir-pod.yaml
pod/emptydir-pod created

# content-creater 컨테이너에 접속하여 test.html 작성
root@k8s-m:~# kubectl exec -it emptydir-pod -c content-creator sh
/ # echo Hello, Kubernetes! >> /data/test.html

# emptydir-pod IP 주소 확인
root@k8s-m:~# kubectl describe pod emptydir-pod | grep IP
              cni.projectcalico.org/podIP: 172.16.197.4/32
              cni.projectcalico.org/podIPs: 172.16.197.4/32
IP:           172.16.197.4


# 임시 Pod를 생성하여 정상적으로 emptydir Volume이 공유되고 있는지 확인 
root@k8s-m:~# kubectl run -i --tty --rm debug --image=alicek106/ubuntu:curl --restart=Never -- curl 172.16.197.4/test.html
Hello, Kubernetes!

 

1-2. Worker Node의 로컬 디렉터리를 볼륨으로 사용 : HostPath

HostPath Volume 특징

gitRepo나 emptyDir Volume 은 Pod가 종료되면 삭제되는 반면 HostPath Volume은 그렇지 않다.

HostPath Volume은 호스트의 디렉터리(Node 단위)를 Pod와 공유하여 데이터를 저장한다.

 

hostpath-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec:

# my-container이라는 이름의 컨테이너에 /etc/data에 my-hostpath-volume 의 볼륨을 마운트
  containers:
    - name: my-container
      image: busybox
      args: [ "tail", "-f", "/dev/null" ]
      volumeMounts:
      - name: my-hostpath-volume
        mountPath: /etc/data

# /tmp 경로에 마운트
  volumes:
    - name: my-hostpath-volume
      hostPath:
        path: /tmp

 

HostPath Volume 공유 확인

# 컨테이너 內 /etc/data 디렉터리에 mydata 이름의 파일 생성
root@k8s-m:~# kubectl exec -it hostpath-pod -- touch /etc/data/mydata

# Pod가 생성된 Workernode로 접속
(admin-k8s:default) root@k8s-m:~# kubectl get po -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
hostpath-pod   1/1     Running   0          17m   172.16.46.7   k8s-w2   <none>           <none>

# 노드에 파일 생성 확인
root@k8s-w2:~# ls /tmp/mydata
/tmp/mydata

 

데이터 저장소로서의 적합성 판단

HostPath Volume을 데이터 저장소로 채택하기에는 적합하지 않다.

  • 특정 노드의 FileSystem에 저장되므로 파드가 다른 노드로 Scheduling이 되면 더 이상 이전 데이터를 볼 수 없다.
  • Pod가 어떤 노드에 Scheduling 되느냐에 따라 민감하기 때문에 일반적인 Pod에 사용하는 것은 좋은 생각이 아니다. 특수한 경우를 제외한다면 hostPath를 사용하는 것은 보안 및 활용성 측면에서 그다지 바람직하지 않다.

2. 네트워크 볼륨 (Network Volume)

네트워크 볼륨 (Network Volume)

네트워크 볼륨부터 PersistentVolume(=PV)와 PersistentVolumeClaim(=PVC) 라는 개념이 존재한다.

  • Pod 내부에서 특정 데이터를 보유해야 하는 Stateful한(DB) Application 경우 Stateless한 (Pod, Deployment) 데이터를 영속적으로 저장하기 위한 방법이 필요하다.
  • Pod에서 실행중인 애플리케이션이 디스크에 데이터를 유지해야 하고 Pod가 다른 노드로 ReScheduling된 경우에도 동일한 데이터를 사용해야 한다면 Local Volume (emptyDir, HostPath Volume)을 사용할 수 없다.

어떤 클러스터 노드에서도 접근이 필요하기에 NAS 유형의 스토리지에 저장되어야 한다.

On-Premise 환경에서도 구축할 수 있는 NFS, iSCSI, GlusterFS, Ceph와 같은 Network Volume뿐만 아니라 AWS의 EBS(Elastic Block Store), GCP의 gcePersistentDisk와 같은 클라우드 플랫폼의 Volume의 Pod에 마운트할 수 있다.

 

2-1. NFS를 네트워크 볼륨으로 사용하기

Deployment 및 Service 생성

쿠버네티스 클러스터 내부에서 임시 NFS 서버를 생성하자.

NFS 기능을 간단히 이용해보기 위한 목적으로 Deployment와 Service를 생성한다.

 

nfs-deployment.yaml

더보기
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-server
spec:
  selector:
    matchLabels:
      role: nfs-server
  template:
    metadata:
      labels:
        role: nfs-server
    spec:
      containers:
      - name: nfs-server
        image: gcr.io/google_containers/volume-nfs:0.8
        ports:
          - name: nfs
            containerPort: 2049
          - name: mountd
            containerPort: 20048
          - name: rpcbind
            containerPort: 111
        securityContext:
          privileged: true

nfs-service.yaml

더보기
apiVersion: v1
kind: Service
metadata:
  name: nfs-service
spec:
  ports:
  - name: nfs
    port: 2049
  - name: mountd
    port: 20048
  - name: rpcbind
    port: 111
  selector:
    role: nfs-server
root@k8s-m:~# kubectl apply -f nfs-deployment.yaml
deployment.apps/nfs-server created

root@k8s-m:~# kubectl apply -f nfs-service.yaml 
service/nfs-service created

 

2-2. Pod 생성

Deployment와 Service를 통해 임시 NFS 서버를 생성하였다면 이제, NFS 서버의 볼륨을 Pod에서 마운트하자.

 

nfs-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod
spec:
  containers:
    - name: nfs-mount-container
      image: busybox
      args: [ "tail", "-f", "/dev/null" ]
      volumeMounts:
      - name: nfs-volume
        mountPath: /mnt           # 포드 컨테이너 내부의 /mnt 디렉터리에 마운트
  volumes:
  - name : nfs-volume
    nfs:                            # NFS 서버의 볼륨을 포드의 컨테이너에 마운트
      path: /
      server: {NFS_SERVICE_IP}

 

YAML 파일에서 유의해야할 부분은 DNS 이름이 {NFS_SERVICE_IP} 로 설정되어 있다.

  1. NFS Volume의 마운트는 컨테이너 내부가 아닌 Worker Node에서 발생하기에 서비스의 DNS 이름으로 NFS 서버에 접근할 수 없다.
  2. 노드 내부에서는 Pod의 IP로 통신을 할 수 있지만, 쿠버네티스의 DNS를 사용하도록 설정되어 있지 않기 때문이다.

방법은 여러가지겠지만 여기에서는 NFS 서비스의 Cluster IP를 직접 얻은 뒤, YAML 파일에 사용하는 방식으로 Pod를 생성을 진행할 것이다.

 

# NFS 서버에 접근하기 위한 서비스 Cluster IP를 얻는다.
root@k8s-m:~# export NFS_CLUSTER_IP=$(kubectl get svc/nfs-service -o jsonpath='{.spec.clusterIP}')

# nfs-pod의 Server 항목을 NFS_CLUSTER_IP로 교체하여 생성
root@k8s-m:~# cat nfs-pod.yaml | sed "s/{NFS_SERVICE_IP}/$NFS_CLUSTER_IP/g" | kubectl apply -f -
pod/nfs-pod created
💡 kubectl get 명령어의 -o jsonpath는 Resource의 특정 정보만 가져올 수 있는 편리한 옵션이다.

 

NFS 마운트 확인

NFS서버가 /mnt 디렉터리에 마운트 되어 있음을 확인할 수 있다.

네트워크로 접근할 수 있는 Volume인 NFS 서버에 파일이 영속적으로 저장되며 컨테이너 내부의 /mnt 디렉터리에 저장된 파일은 Pod가 다른 node로 옮겨지거나 Pod를 재시작해도 삭제되지 않는다.

# nfs-pod 접속
root@k8s-m:~# kubectl exec -it nfs-pod -- sh

/ # df -h
Filesystem                Size      Used Available Use% Mounted on
...
10.99.241.166:/          38.7G      4.0G     34.7G  10% /mnt
...

 

Error: NFS 파드 생성 중 ContainerCreating 상태에서 더이상 진행이 되지 않을 때 해결법

root@k8s-m:~# kubectl get pod nfs-pod
NAME      READY   STATUS              RESTARTS   AGE
nfs-pod   0/1     ContainerCreating   0          16m


root@k8s-m:~# kubectl describe po  nfs-pod

Events:
  Type     Reason       Age                From               Message
  ----     ------       ----               ----               -------
  Normal   Scheduled    30s                default-scheduler  Successfully assigned default/nfs-pod to k8s-w2
  Warning  FailedMount  14s (x6 over 30s)  kubelet            MountVolume.SetUp failed for volume "nfs-volume" : mount failed: exit status 32

---

root@k8s-m:~# kubectl get po -o wide
NAME                        READY   STATUS              RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
nfs-pod                     0/1     ContainerCreating   0          9s    <none>         k8s-w2   <none>           <none>
nfs-server-ddf754fc-9vwsn   1/1     Running             0          55m   172.16.197.5   k8s-w3   <none>           <none>

# nfs-pod가 할당된 노드에 접속하여 NFS 패키지 설치
root@k8s-w2:~# sudo apt-get install nfs-common -y

# Pod 재확인
root@k8s-m:~# kubectl get po -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
nfs-pod                     1/1     Running   0          79s   172.16.46.8    k8s-w2   <none>           <none>
nfs-server-ddf754fc-9vwsn   1/1     Running   0          57m   172.16.197.5   k8s-w3   <none>           <none>

 


참조

- 쿠버네티스 인액션

- 시작하세요 도커/쿠버네티스

댓글