kubectl

2 개의 포스트

연간 600시간을 절약한 쿠버네티스 한 줄 수정 (새 탭에서 열림)

쿠버네티스 환경에서 테라폼(Terraform) 운영 도구인 아틀란티스(Atlantis)의 재시작 시간을 30분에서 수 초 내외로 단축하여, 연간 600시간의 엔지니어링 대기 시간을 줄인 사례를 소개합니다. 문제의 원인은 수백만 개의 파일을 포함한 퍼시스턴트 볼륨(PV)을 마운트할 때 쿠버네티스가 기본적으로 수행하는 파일 권한 변경 작업이었습니다. 이를 해결하기 위해 `securityContext`에 단 한 줄의 설정을 추가함으로써 불필요한 재귀적 권한 검사를 방지하고 시스템 효율성을 극대화했습니다. ### 원인 불명의 느린 재시작 문제 아틀란티스는 테라폼 프로젝트의 상태를 유지하기 위해 퍼시스턴트 볼륨(PV)을 사용하는 싱글톤 스테이트풀셋(StatefulSet)으로 운영됩니다. 자격 증명 갱신이나 프로젝트 온보딩 시 재시작이 필수적인데, 이때마다 다음과 같은 심각한 지연이 발생했습니다. * **지속적인 지연:** 매 재시작 시 30분 동안 포드가 `Init:0/1` 상태에 머물며 인프라 변경 작업이 완전히 중단됨. * **운영 부담:** 매달 약 100회의 재시작이 발생하여 월 50시간, 연간 600시간의 엔지니어링 시간이 낭비되고 온콜 엔지니어에게 불필요한 알람이 전송됨. * **한계 도달:** 파일 시스템의 아이노드(Inode) 고갈로 볼륨 크기를 키워야 하는 상황에서, 재시작 지연 문제는 더욱 두드러짐. ### Kubelet 로그를 통한 기술적 병목 파악 일반적인 `kubectl events`로는 포드가 이미지를 풀링하기 전 단계에서 왜 멈춰 있는지 알 수 없었습니다. 팀은 노드 레벨의 `kubelet` 로그를 분석하여 구체적인 원인을 찾아냈습니다. * **로그 추적:** 로그상에서 볼륨 마운트 성공 메시지 이후 `context deadline exceeded` 오류가 반복적으로 발생하며 포드 생성이 지연됨을 확인. * **fsGroup 권한 설정:** 쿠버네티스는 볼륨을 마운트할 때 포드의 `fsGroup` 설정과 일치시키기 위해 볼륨 내의 모든 파일과 디렉토리에 대해 재귀적으로 `chown` 및 `chmod`를 실행함. * **파일 개수의 영향:** 아틀란티스 볼륨에 쌓인 수백만 개의 파일에 대해 매번 이 작업을 수행하면서 30분이라는 막대한 시간이 소요됨. ### 단 한 줄의 설정 변경으로 문제 해결 쿠버네티스 1.20 버전(GA 기준)부터 도입된 `fsGroupChangePolicy` 설정을 통해 이 문제를 간단히 해결할 수 있었습니다. * **기본값(Always):** 포드가 시작될 때마다 항상 모든 파일의 권한을 재귀적으로 변경함. * **해결책(OnRootMismatch):** 볼륨 루트 디렉토리의 권한이 `fsGroup`과 일치하지 않을 때만 재귀적 변경을 수행함. 이미 권한이 올바르게 설정되어 있다면 이 과정을 건너뜀. * **적용 코드:** ```yaml securityContext: fsGroup: 1000 fsGroupChangePolicy: "OnRootMismatch" ``` ### 실용적인 권장 사항 수백만 개의 작은 파일이 포함된 대규모 볼륨을 사용하는 애플리케이션(예: Prometheus, Atlantis, Jenkins 등)을 쿠버네티스에서 운영 중이라면, `fsGroupChangePolicy: "OnRootMismatch"` 설정을 기본적으로 적용하는 것이 좋습니다. 이를 통해 볼륨 마운트 시 발생하는 불필요한 디스크 I/O를 제거하고, 포드 시작 시간을 획기적으로 개선하여 인프라 운영의 가용성을 높일 수 있습니다.

Dirty Pipe 취약점을 이용한 컨 (새 탭에서 열림)

리눅스 커널에서 발견된 Dirty Pipe 취약점은 권한이 없는 프로세스가 읽기 권한만 가진 파일에 데이터를 쓸 수 있게 허용하며, 이를 통해 컨테이너 환경에서 호스트 시스템의 루트 권한을 탈취할 수 있는 심각한 위협을 초래합니다. 특히 Kubernetes 환경에서 널리 쓰이는 컨테이너 런타임인 runC의 실행 바이너리를 페이지 캐시 수준에서 변조함으로써, 격리된 컨테이너를 탈출하여 호스트 시스템을 완전히 장악하는 시나리오가 가능합니다. 본 글에서는 이 취약점의 기술적 배경과 함께 실제 컨테이너 탈출이 이루어지는 공격 메커니즘을 상세히 설명합니다. **컨테이너 런타임과 runC의 구조적 취약성** - Kubernetes는 containerd나 CRI-O 같은 런타임을 통해 컨테이너를 관리하며, 실제 프로세스 생성은 OCI 규격을 준수하는 하위 레벨 런타임인 runC가 담당합니다. - runC는 컨테이너 내부 프로세스를 실행할 때 자신을 포크(fork)한 뒤 `execve` 시스템 콜을 호출하는데, 이때 `/proc/self/exe` 경로를 통해 호스트에 있는 runC 이진 파일에 대한 파일 서술자(File Descriptor)를 열어두게 됩니다. - 과거 CVE-2019-5736 취약점에 대한 대응으로 runC를 읽기 전용으로 마운트하는 방어책이 도입되었으나, Dirty Pipe는 커널의 페이지 캐시를 직접 수정하므로 이러한 파일 시스템 수준의 권한 제한을 무력화합니다. **Dirty Pipe를 이용한 컨테이너 탈출 과정** - 공격자는 먼저 취약한 웹 애플리케이션 등을 통해 권한이 제한된 일반 컨테이너에 침투한 뒤, 호스트의 runC 바이너리가 실행되기를 대기합니다. - 관리자가 `kubectl exec`와 같은 명령을 수행하여 컨테이너 내부에서 runC가 구동되는 순간, 공격 프로세스는 `/proc/<runC-pid>/exe`를 통해 호스트의 runC 실행 파일에 접근합니다. - Dirty Pipe 공격 프리미티브를 활용하여 페이지 캐시에 로드된 runC 바이너리 내용을 공격자의 악성 ELF 코드로 덮어씁니다. - 이렇게 변조된 runC는 호스트의 루트 권한으로 실행되므로, 공격자는 호스트 시스템에서 임의의 명령(예: 호스트 이름 확인, 루트 권한 쉘 실행 등)을 수행하며 컨테이너 격리를 완전히 무너뜨립니다. **메모리 기반 공격의 비영구적 특성** - Dirty Pipe를 통한 바이너리 변조는 디스크의 실제 파일을 직접 수정하는 것이 아니라 커널의 페이지 캐시 내에서 발생합니다. - 따라서 공격으로 인한 변조는 시스템이 재부팅되거나 커널 캐시가 드롭(drop)되기 전까지만 유지되는 비영구적 특성을 가집니다. - 하지만 단 한 번의 실행만으로도 호스트에 백도어를 설치하거나 권한을 상승시키기에 충분하므로 그 위험성은 매우 높습니다. Dirty Pipe 취약점은 리눅스 커널 수준의 결함이므로 이를 근본적으로 해결하기 위해서는 최신 보안 패치가 적용된 커널로 신속히 업데이트해야 합니다. 또한 컨테이너 환경에서는 최소 권한 원칙을 철저히 준수하고, 런타임 보안 모니터링 도구를 도입하여 `/proc` 파일 시스템에 대한 의심스러운 접근이나 시스템 이진 파일의 비정상적인 동작을 실시간으로 감지하고 차단하는 방어 전략이 필요합니다.