Amazon EKS에서 aws-ebs-csi-driver 사용시 노드에서 확인되는 `/dev/termination-log` 파일에 대한 분석

5분 분량
콘텐츠 수준: 중급
2

본 기사에서는 Amazon EKS에서 aws-ebs-csi-driver 설치시 노드에서 확인되는 /dev/termination-log 파일에 대한 분석한 내용에 대해서 설명합니다.

많은 고객들이 Amazon EKS에서 aws-ebs-csi-driver를 사용해 볼륨의 수명 주기를 관리하고 있습니다.

여기서 aws-ebs-csi-driver는 컨테이너 오케스트레이터가 Amazon EBS 볼륨의 수명 주기를 관리하는 데 사용하는 CSI 인터페이스를 제공하는데, 드라이버를 설치하면 별다른 설정을 하지 않았음에도 노드에 /dev/termination-log 파일이 생성되는 현상이 발생합니다.

본 기사에서는 해당 파일이 어떤 용도인지 그리고 생성된 이유가 무엇인지 분석하려고 합니다.

컨테이너의 "/dev/termination-log" 파일이란

/dev/termination-log 파일은 일반적으로 Pod의 종료 메시지 [1]를 기록하는 terminationMessagePath 필드의 기본값을 나타냅니다.

이는 컨테이너가 치명적인 이벤트로 인해서 종료되었을 때 종료 메시지를 기록할 수 있는 위치로 컨테이너 파일 시스템에 위치합니다.

Kubernetes는 Pod의 spec > containers > terminationMessagePath 필드에 지정된 컨테이너 파일 시스템의 위치에 종료 메시지를 작성하도록 합니다. [2] [3]

즉 해당 파일은 컨테이너의 Overlay FS에 기록되는 것으로 일반적으로 이 노드의 해당 파일이 생성되지는 않습니다.

해당 파일이 노드에서 확인되는 이유

해당 파일이 노드에서 확인되는 이유는 ebs-csi-driver의 구성과 관련되어 있습니다.

ebs-csi-driver 설치시 각 노드에 설치되는 DaemonSet인 ebs-csi-node의 manifest를 보면 아래와 같습니다.

$ kubectl get daemonset -n kube-system ebs-csi-node -o yaml

apiVersion: apps/v1
kind: DaemonSet
spec:
  selector:
    matchLabels:
      app: ebs-csi-node
      app.kubernetes.io/name: aws-ebs-csi-driver
      ...
  template:
    spec:
      containers:
      - name: ebs-plugin
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/lib/kubelet
          mountPropagation: Bidirectional
          name: kubelet-dir
        - mountPath: /csi
          name: plugin-dir
        - mountPath: /dev
          name: device-dir
        ...
      volumes:
      - hostPath:
          path: /dev
          type: Directory
        name: device-dir
      ...

여기서 device-dir이라는 hostPath /dev 위치를 ebs-plugin 컨테이너의 /dev 위치에 마운트하는 것을 확인할 수 있습니다.

즉, termination-log 파일을 생성하는 위치가 hostPath로 마운트 된 위치이기에 노드에서 /dev/termination-log가 생성되는 것입니다.

해당 파일에 아무것도 작성되지 않는 이유

위의 설명을 보면 노드에 생성된 /dev/termination-log 파일에 ebs-plugin 컨테이너의 종료 메시지가 기록될 것처럼 보이나 실제로 해당 파일에는 아무런 데이터가 존재하지 않습니다.

이러한 현상이 발생하는 이유는 해당 파일이 일시적으로 생성되었으나 컨테이너의 /dev/termination-log 파일과 같은 파일이 아니며, 컨테이너의 /dev/termination-log 파일은 다른 위치에 다시 마운트 되기 때문입니다.

이는 컨테이너 내부 및 노드에 생성된 /dev/termination-log 파일의 inode 값[4]을 확인해보면 알 수 있습니다.

  • 컨테이너 내부의 /dev/termination-log
    root@termination-demo:/# echo "test" >> /dev/termination-log
    root@termination-demo:/# ls -la /dev/termination-log
    -rw-rw-rw-. 1 root root 5 Dec 11 00:43 /dev/termination-log
    root@termination-demo:/# stat /dev/termination-log
      File: /dev/termination-log
      Size: 5         	Blocks: 8          IO Block: 4096   regular file
    Device: 259,1	Inode: 27302570    Links: 1
    Access: (0666/-rw-rw-rw-)  Uid: (    0/    root)   Gid: (    0/    root)
    Access: 2024-12-11 00:40:18.737373221 +0000
    Modify: 2024-12-11 00:43:04.899250691 +0000
  • 노드에 생성된 /dev/termination-log
    [root@ip-10-30-108-223 dev]# ll /dev/termination-log
    -rw-r--r--. 1 root root 0 Dec 11 00:40 /dev/termination-log
    [root@ip-10-30-108-223 dev]# stat /dev/termination-log
      File: /dev/termination-log
      Size: 0               Blocks: 0          IO Block: 4096   regular empty file
    Device: 5h/5d   Inode: 482         Links: 1
    Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
    Context: system_u:object_r:device_t:s0
    Access: 2024-12-11 00:40:18.867374760 +0000
    Modify: 2024-12-11 00:40:18.867374760 +0000

즉, 두 파일 자체가 서로 다른 파일이기에 컨테이너 내부에서 종료 메시지를 작성하더라도 반영되지 않는 것입니다.

어째서 HostPath /dev 아래에 생성된 /dev/termination-log 파일은 다른 파일로 인식되는가

이는 Kubernetes에서의 terminationMessagePath 처리 로직에 의해서 발생하는 현상입니다.

Kubernetes의 Node Component인 Kubelet에서는 컨테이너의 terminationMessagePath를 별도의 path에 저장하도록 컨테이너의 볼륨 구성을 재설정하는 과정을 거칩니다.

이 과정에서 컨테이너의 terminationMessagePath에 지정된 위치를 노드의 /var/lib/kubelet/pods/{POD_ID}/containers/{CONTAINER_NAME}/{RANDOM_NUM} 위치에 마운트 하도록 컨테이너의 볼륨 명세를 수정하는데 이로 인해 terminationMessagePath의 상위 위치를 별도의 볼륨에 마운트 하더라도 마운트 된 위치에 파일이 작성되지 않는 것입니다.

이와 관련된 동작은 Kubelet의 코드에서 makeMounts 함수에서 확인하실 수 있습니다. [5]

```
// Because the PodContainerDir contains pod uid and container name which is unique enough,
// here we just add a random id to make the path unique for different instances
// of the same container.
cid := makeUID()
containerLogPath := filepath.Join(opts.PodContainerDir, cid)
fs, err := m.osInterface.Create(containerLogPath)
```

추가적으로 위에서 확인한 것처럼 inode값을 비교하는 방법 및 컨테이너의 명세를 확인해 검증할 수 있습니다.

  • inode값 비교

    # 컨테이너 내부의 `/dev/termination-log`
    
    $ kubectl exec -it termination-demo -- bash
    root@termination-demo:/# ls -la /dev/termination-log
    -rw-rw-rw-. 1 root root 0 Dec 12 01:49 /dev/termination-log
    root@termination-demo:/# stat /dev/termination-log
      File: /dev/termination-log
      Size: 0         	Blocks: 0          IO Block: 4096   regular empty file
    Device: 259,1	Inode: 29380146    Links: 1
    Access: (0666/-rw-rw-rw-)  Uid: (    0/    root)   Gid: (    0/    root)
    Access: 2024-12-12 01:49:02.167742534 +0000
    Modify: 2024-12-12 01:49:02.167742534 +0000
    # 노드에 생성된 `/var/lib/kubelet/pods/{POD_ID}/containers/{CONTAINER_NAME}/{RANDOM_NUM}`
    
    [root@ip-10-30-65-76 termination-demo-container]# pwd
    /var/lib/kubelet/pods/5b207fae-1273-4197-8676-f27b74a9f090/containers/termination-demo-container
    
    [root@ip-10-30-65-76 termination-demo-container]# stat 9c6e1dba
      File: 9c6e1dba
      Size: 0               Blocks: 0          IO Block: 4096   regular empty file
    Device: 10301h/66305d   Inode: 29380146    Links: 1
    Access: (0666/-rw-rw-rw-)  Uid: (    0/    root)   Gid: (    0/    root)
    Context: system_u:object_r:init_var_lib_t:s0
    Access: 2024-12-12 01:49:02.167742534 +0000
    Modify: 2024-12-12 01:49:02.167742534 +0000
  • ctr 명령어를 이용해 컨테이너의 명세 확인

    [root@ip-10-30-65-76 termination-demo-container]# ctr -n k8s.io container list
    CONTAINER                                                           IMAGE          RUNTIME
    bc5a05a9241c6d7a74677a42e859ae6339958e8caa2f37f113ab51deec4a9b4d    docker.io/library/debian:latest          io.containerd.runc.v2
    ...
    
    [root@ip-10-30-65-76 termination-demo-container]# ctr -n k8s.io container info bc5a05a9241c6d7a74677a42e859ae6339958e8caa2f37f113ab51deec4a9b4d
    {
        "ID": "bc5a05a9241c6d7a74677a42e859ae6339958e8caa2f37f113ab51deec4a9b4d",
        "Labels": {
            "io.cri-containerd.kind": "container",
            "io.kubernetes.container.name": "termination-demo-container",
            "io.kubernetes.pod.name": "termination-demo",
            "io.kubernetes.pod.namespace": "default",
            "io.kubernetes.pod.uid": "5b207fae-1273-4197-8676-f27b74a9f090"
        },
        "Image": "docker.io/library/debian:latest",
        "Spec": {
            "ociVersion": "1.1.0",
            "mounts": [
                {
                    "destination": "/dev",
                    "type": "bind",
                    "source": "/dev",
                    "options": [
                        "rbind",
                        "rprivate",
                        "rw"
                    ]
                },
                {
                    "destination": "/dev/termination-log",
                    "type": "bind",
                    "source": "/var/lib/kubelet/pods/5b207fae-1273-4197-8676-f27b74a9f090/containers/termination-demo-container/e100dbca",
                    "options": [
                        "rbind",
                        "rprivate",
                        "rw"
                    ]
                },
                ...
            ],
            ...
        }

결론 및 정리

결론적으로 aws-ebs-csi-driver 사용시 각 노드에 생성되는 /dev/termination-log 파일은 드라이버의 HostPath /dev 볼륨 설정과 Kubelet의 terminationMessagePath 볼륨 처리 동작으로 인해 생성되는 것으로, Kubelet은 Pod에 별도의 볼륨을 설정하지 않더라도 내부적으로 terminationMessagePath에 대한 볼륨을 추가하는 동작이 존재하는 것을 확인할 수 있었습니다.

참고

[1] https://kubernetes.io/ko/docs/tasks/debug/debug-application/determine-reason-pod-failure/

[2] https://kubernetes.io/ko/docs/tasks/debug/debug-application/determine-reason-pod-failure/#%EC%A2%85%EB%A3%8C-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%95%EC%9D%98%ED%95%98%EA%B8%B0

[3] https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#container-v1-core

[4] https://en.wikipedia.org/wiki/Inode

[5] https://github.com/kubernetes/kubernetes/blob/release-1.32/pkg/kubelet/kuberuntime/kuberuntime_container.go#L464-L466

profile pictureAWS
지원 엔지니어
게시됨 2달 전55회 조회