볼륨이란, 호스트에 미리 준비된 사용 가능한 디렉토리를 일컫는다. 쿠버네티스에서는 볼륨 리소스를 생성/삭제할 수 없다. 파드에서 볼륨을 사용하기 위해 매니페스트에 볼륨을 지정하기만 할 수 있다. 볼륨은 플러그인 형태로 제공되는데, ConfigMap 과 Secret 도 볼륨 플러그인의 종류이다. 대표적인 볼륨 플러그인은 아래와 같다.

  • emptyDir
  • hostPath
  • downwardAPI
  • projected
  • nfs
  • iscsi
  • cephfs

1. emptyDir

노드에서 파드로 할당되는 호스트의 임시 디스크 영역으로, 파드가 종료되면 같이 삭제된다. 직접 마운트할 호스트의 영역을 지정할 수는 없다. 아래 매니페스트와 같이 emptyDir 볼륨을 마운트할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-emptydir
spec:
  containers:
    - image: nginx:1.27
      name: nginx-container
      volumeMounts:
        - name: cache-volume
          mountPath: /cache
  volumes:
    - name: cache-volume
      emptyDir: {}

아래 커맨드로 컨테이너의 /cache 마운트 경로에 할당된 디스크를 확인해보자

$ kubectl exec -it sample-emptydir -- df -h | grep cache
/dev/mapper/ubuntu--vg-ubuntu--lv   11G  6.6G  3.1G  69% /cache

emptyDir.sizeLimit 항목을 사용하여 해당 볼륨의 사이즈 제한을 걸 수 있다. 아래 매니페스트처럼 지정한다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-emptydir-limit
spec:
  containers:
    - image: nginx:1.27
      name: nginx-container
      volumeMounts:
        - name: cache-volume
          mountPath: /cache
  volumes:
    - name: cache-volume
      emptyDir:
        sizeLimit: 128Mi

아래 커맨드와 같이 pod 를 --watch 옵션으로 감시하며, 컨테이너 내부에서 볼륨 마운트 된 경로에 150MB 용량의 더미 파일을 생성한다.

$ kubectl get pods --watch
NAME                                READY   STATUS    RESTARTS      AGE
sample-emptydir-limit               1/1     Running   0             2m57s

# 별도의 세션에서 실행
$ kubectl exec -it sample-emptydir-limit -- dd if=/dev/zero of=/cache/dummy bs=1M count=150
150+0 records in
150+0 records out
157286400 bytes (157 MB, 150 MiB) copied, 0.803752 s, 196 MB/s

# watch 중인 파드 상태가 아래처럼 바뀌었음을 확인
NAME                                READY   STATUS      RESTARTS      AGE
sample-emptydir-limit               0/1     Completed   0             5m45s

$ kubectl describe pods sample-emptydir-limit
...
Events:
  Type     Reason     Age    From               Message
  ----     ------     ----   ----               -------
  ...
  Warning  Evicted    5m46s  kubelet            Usage of EmptyDir volume "cache-volume" exceeds the limit "128Mi".
  Normal   Killing    5m46s  kubelet            Stopping container nginx-container

용량 초과로 인해 파드가 종료된 것을 알 수 있다.

 

emptyDir 은 호스트 노드의 디스크 뿐 아니라 RAM 영역인 tmpfs 영역에서 사용할 수도 있다. 아래처럼 emptyDir.medium 항목에 Memory 를 설정한다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-emptydir-memory
spec:
  containers:
    - image: nginx:1.27
      name: nginx-container
      volumeMounts:
        - name: cache-volume
          mountPath: /cache
  volumes:
    - name: cache-volume
      emptyDir:
        medium: Memory
        sizeLimit: 128Mi

다시 아래 커맨드로 확인해보면 노드의 tmpfs 영역이 볼륨으로 마운트된 것을 볼 수 있다.

$ kubectl exec -it sample-emptydir-memory -- df -h | grep cache
tmpfs                              128M     0  128M   0% /cache

하지만, Memory 영역을 마운트한 경우에는 sizeLimit 을 초과한 용량을 사용하려고 하면 한도까지만 write 가 되고, 파드가 종료되지는 않는다는 차이가 있다.(공식문서에는 해당 스펙이 기재되어 있지 않지만 실제로 이렇게 동작함. kublet 설정에 따라 다를 수 있음.)

$ kubectl exec -it sample-emptydir-memory -- dd if=/dev/zero of=/cache/dummy bs=1M count=150

$ kubectl get pods
NAME                                READY   STATUS      RESTARTS      AGE
sample-emptydir-memory              1/1     Running     0             16m

# 128M 까지만 기록됨
$ kubectl exec -it sample-emptydir-memory -- ls -lh /cache/dummy
-rw-r--r-- 1 root root 128M Jan 27 14:54 /cache/dummy

파드에는 컨테이너별로 사용할 수 있는 메모리 제한을 걸 수 있는데, 이는 볼륨의 emptyDir.sizeLimit 에 지정한 메모리 제한보다 더 우선 적용되는 항목이기 때문에, 아래 매니페스트로 파드를 생성한 후 70MB의 더미 파일을 만들면 OOM에 의해 파드가 종료된다. 파드의 컨테이너 메모리 제한은 spec.containers[].resources.limits.memory 에 지정할 수 있다.(실제로 해보면 나의 경우에는 파드가 종료되지 않는다...)

apiVersion: v1
kind: Pod
metadata:
  name: sample-emptydir-memory-with-memory-limits
spec:
  containers:
    - image: nginx:1.27
      name: nginx-container
      resources:
        limits:
          memory: 64Mi
      volumeMounts:
        - mountPath: /cache
          name: cache-volume
  volumes:
    - name: cache-volume
      emptyDir:
        medium: Memory
        sizeLimit: 128Mi

 

2. hostPath

hostPath 는 emptyDir 과 다르게 호스트 노드의 지정한 영역을 마운트하는 방식이다. 파드가 종료되어도 호스트의 해당 영역은 그대로 유지된다. type 을 지정해야 하는데, 지정 가능한 값은 아래와 같다.

type 설명
DirectoryOrCreate 지정한 경로에 디렉토리가 없으면 0755 권한으로 빈 디렉토리를 생성
Directory 지정한 경로에 디렉토리가 없으면 파드 생성에 실패
FileOrCreate 지정한 경로에 파일이 없으면 0644 권한으로 빈 파일을 생성
File 지정한 경로에 파일이 없으면 파드 생성에 실패
Socket 지정한 경로에 UNIX 소켓 파일이 필요(Docker/CRI 소켓 연결시 사용)
CharDevice 지정한 경로에 문자 디바이스가 필요
BlockDevice 지정한 경로 블록 디바이스가 필요

하지만, hostPath 는 파드가 노드의 파일 시스템에 직접 접근하므로 보안상 위험하여 권장되지 않는다.

매니페스트 예시는 아래와 같다.

apiVersion: v1
kind: Pod
metadata:
  name: sample-hostpath
spec:
  containers:
    - image: nginx:1.27
      name: nginx-container
      volumeMounts:
        - name: hostpath-sample
          mountPath: /srv
  volumes:
    - name: hostpath-sample
      hostPath:
        path: /etc
        type: DirectoryOrCreate
# 호스트 노드의 OS 정보 확인
$ kubectl exec -it sample-hostpath -- cat /srv/os-release | grep "PRETTY_NAME"
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"

 

3. downwardAPI

downwardAPI 는 파드 정보나 컨테이너 정보를 파일로 컨테이너 내에 배치하기 위한 플러그인이다. 파드의 정보는 fieldRef 로, 컨테이너의 정보는 resourceFieldRef 로 가져올 수 있다. 아래 매니페스트를 적용해보자.

apiVersion: v1
kind: Pod
metadata:
  name: sample-downward-api
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      volumeMounts:
        - name: downward-api-volume
          mountPath: /srv
  volumes:
    - name: downward-api-volume
      downwardAPI:
        items:
          - path: "podname"
            fieldRef:
              fieldPath: metadata.name
          - path: "cpu-request"
            resourceFieldRef:
              containerName: nginx-container
              resource: requests.cpu

아래 커맨드로 파드명과 컨테이너의 CPU 요구사항이 파일로 배치되었는지 확인한다.

$ kubectl exec -it sample-downward-api -- tail -n +1 /srv/podname /srv/cpu-request
==> /srv/podname <==
sample-downward-api
==> /srv/cpu-request <==
0

 

4. projected

projected 는 Secret, ConfigMap, downwardAPI, serviceAccountToken 를 하나의 볼륨 마운트 경로에 통합하는 플러그인이다. 여러가지 볼륨으로 관리되는 기밀/설정 정보를 컨테이너 내 하나의 디렉터리에 배치하고자 할 때 사용한다. 아래의 매니페스트 예시를 보자.

(sample-db-auth 시크릿과 sample-configmap 컨피그맵은 미리 생성되어 있어야 한다. 없다면 아래를 참고하여 생성하자.)

apiVersion: v1
kind: Pod
metadata:
  name: sample-projected
spec:
  containers:
    - name: nginx-container
      image: nginx:1.27
      volumeMounts:
        - name: projected-volume
          mountPath: /srv
  volumes:
    - name: projected-volume
      projected:
        sources:
          - secret:
              name: sample-db-auth
              items:
                - key: username
                  path: secret/username.txt
          - configMap:
              name: sample-configmap
              items:
                - key: nginx.conf
                  path: configmap/nginx.conf
          - downwardAPI:
              items:
                - path: "podname"
                  fieldRef:
                    fieldPath: metadata.name
$ kubectl exec -it pod/sample-projected -- tail -n +1 /srv/secret/username.txt /srv/configmap/nginx.conf /srv/podname
==> /srv/secret/username.txt <==
root
==> /srv/configmap/nginx.conf <==
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

==> /srv/podname <==
sample-projected

sample-db-auth 시크릿의 username 값은 base64 디코딩되어 마운트되는 것을 알 수 있다.

블로그 이미지

망원동똑똑이

프로그래밍 지식을 자유롭게 모아두는 곳입니다.

,