20 min read

임시방편에서 표준으로: docker 와 podman 을 중심으로

주(Note): 이 글은 Docker를 깎아내리거나 Podman을 추켜세우기 위한 글이 아닙니다. 초기에는 최선의 정답이었던 구조가 환경이 바뀐 뒤에도 유연하게 대응하지 못하는 한계, 그리고 AI를 동원한 임시변통(Workaround)이 늘어날수록 오히려 기존 구조에 더 깊이 종속(Lock-in)되어 가는 현상을 짚어보고자 합니다. Docker와 Podman이라는 사례를 통해, 왜 우리가 임시방편을 넘어 구조적 표준으로 나아가야 하는지 이야기하려 합니다.
주2 (Note-2): 글의 모든 내용은 경험을 토대로 작성하였으며, 논리적 모순과 오류, 오타 등을 AI 를 통해 다듬었습니다. AI 로 처음부터 생성한 글이 아닙니다.

Docker와 Podman

Docker가 컨테이너 기술의 대중화에 획기적인 이정표를 세운 것은 사실이지만, 완전히 새로운 최초의 기술은 아니었습니다. Docker 이전에도 LXC(Linux Containers)를 비롯한 여러 컨테이너 기반 가상화 기술이 존재했으나, 설정과 사용법이 복잡해 널리 쓰이지 못하다가 Docker의 등장과 함께 본격적으로 대중화되었습니다.

Docker가 시장에서 사실상의 표준처럼 쓰이면서, 파편화되는 컨테이너 런타임과 이미지 규격에 대한 표준을 정립하기 위해 출범한 것이 OCI(Open Container Initiative)입니다. 이제는 Docker가 아니더라도 OCI 표준 규격만 준수한 런타임이라면 동일한 컨테이너 이미지를 문제없이 실행할 수 있는 환경이 마련되었습니다. 

아래 언급할 Podman 도 OCI 표준을 준수한 컨테이너 런타임 구현체 입니다. 그 외에도 얼마나 많은 OCI 표준 구현체가 있는지 알게되면 놀라실겁니다.

여전히 Docker는 높은 시장 점유율을 차지하고 있지만, 모든 작업을 중앙 데몬(dockerd)에 의존하는 구조적 한계를 안고 있었습니다. 이에 Red Hat을 중심으로 중앙 데몬 없이 동작하는 Podman이라는 컨테이너 엔진이 대안으로 부상하게 됩니다.

기본 설정의 Docker는 Rootful(루트 권한 기반) 환경에서 동작합니다. 데몬과 컨테이너가 root 권한으로 실행되기 때문에 파일 접근이나 포트 바인딩 같은 권한 충돌이 거의 발생하지 않아 편리하지만, 그만큼 심각한 보안 위험을 내포하고 있습니다.

반면 Podman은 데몬이 없는(Daemonless) 아키텍처를 채택했으며, 설계 초기부터 Rootless(일반 사용자 권한 기반) 환경을 지향했습니다. 이로 인해 초기 구성 시 권한 제약으로 인한 번거로움은 발생할지언정, 침해 사고 발생 시 보안 피해 반경을 최소화할 수 있는 구조를 갖추게 되었습니다.

컨테이너 탈출(Container Escape)과 횡이동(Lateral Movement) 문제

보안 아키텍처에서는 위협과 트래픽의 이동 경로를 흔히 노스-사우스(North-South)와 이스트-웨스트(East-West)로 구분합니다. 노스-사우스가 외부에서 내부 계층을 관통해 들어오는 수직적 흐름이라면, 이스트-웨스트는 동일한 계층(컨테이너 간, 혹은 내부 서버 간)을 수평으로 넘나드는 흐름이며 이를 보안에서는 횡이동(Lateral Movement) 문제라고 부릅니다.

컨테이너 환경에서 이 문제는 컨테이너 탈출(Container Escape)과 결합될 때 치명적인 결과를 낳습니다. 만약 공격자가 애플리케이션이나 런타임의 취약점을 악용해 컨테이너 하나를 장악한 뒤 격리 계층을 뚫고 호스트로 빠져나왔다고 가정해 봅시다. Rootful 구조의 Docker에서는 컨테이너를 관리하는 데몬 자체가 root 권한을 가지고 있으므로, 격리가 뚫리는 순간 곧바로 호스트 OS의 최고 권한(root)까지 한 번에 탈취당합니다. 공격자는 이 권한을 이용해 호스트에 떠 있는 다른 모든 컨테이너와 내부망 서버로 손쉽게 횡이동하며 전체 시스템을 장악할 수 있습니다.

반면 Rootless 환경의 Podman은 어떨까요? 취약한 컨테이너를 실행했다면 해당 컨테이너가 공격당할 수는 있지만, 애초에 Podman 프로세스 자체가 일반 사용자 계정 권한(User Namespace) 안에 갇혀 있습니다. 설령 공격자가 컨테이너 바깥으로 탈출하더라도 호스트 입장에서는 권한이 극도로 제한된 일반 사용자에 불과하므로, 호스트 OS를 장악하거나 다른 사용자의 컨테이너로 침투하는 횡이동이 원천적으로 차단됩니다.

이 때문에 당장 오류 없이 편하게 동작하는 환경을 원하는 개발자들은 Rootful Docker를 선호하곤 하지만, 보안을 책임져야 하는 인프라 엔지니어 입장에서 Rootful Docker는 한 번 뚫리면 호스트 전체가 털리는 시한폭탄 같은 구조입니다. 금융권을 비롯한 엔터프라이즈 환경에서 이러한 위험성을 엄중하게 인식하여, Docker를 쓰더라도 반드시 Rootless 모드를 강제하거나 아예 Podman을 표준으로 채택하는 이유가 바로 여기에 있습니다.

데몬(Daemon)이 없는 구조에서 발생하는 운영상의 차이

데몬의 유무는 결국 "컨테이너의 생애주기(Lifecycle)를 누가 관리하는가"의 차이로 귀결됩니다. 사실 Docker나 Podman 같은 컨테이너 엔진은 그 자체가 가상화를 직접 수행하는 것이 아니라, 리눅스 커널이 제공하는 저수준 기능(Namespace, cgroups 등)을 사용자가 다루기 쉽게 묶어주는 관리 도구에 가깝습니다.

Docker는 커널의 복잡한 기능들을 추상화하고 일정한 사용 방식을 강제함으로써 높은 사용성과 안정성을 제공하는데, 이 모든 제어의 중심에 백그라운드 상주 프로그램인 데몬(dockerd)이 있습니다. 심지어 사용자가 입력하는 docker 명령어조차 직접 컨테이너를 띄우는 것이 아니라, 데몬에게 REST API 요청을 전달하는 클라이언트 도구에 불과합니다. 즉, 데몬이 살아 있어야만 컨테이너를 제어할 수 있고, 반대로 데몬만 떠 있다면 모든 컨테이너의 상태 관리를 데몬이 알아서 처리해 줍니다.

반면 데몬이 없는 Podman은 컨테이너를 일반 리눅스 프로세스처럼 직접 생성(Fork/Exec)하고, 생애주기 관리 역시 리눅스 OS의 기본 기능에 위임합니다. 상시 떠 있는 중앙 데몬이 필요 없다는 큰 장점이 있지만, 반대로 데몬이 해주던 부가 기능들을 OS의 고유 기능과 결합해 구현해야 하므로 기존 Docker 사용자 입장에서는 운영 방식의 차이를 겪게 됩니다.

가장 대표적인 예가 재시작 정책(Restart Policy)입니다. Docker에서는 단순히 restart: always 옵션만 주면, 서버가 재부팅되더라도 Docker 데몬이 먼저 올라오면서 해당 컨테이너들을 알아서 다시 실행시킵니다. 그러나 Podman은 부팅 시 컨테이너를 깨워줄 중앙 데몬이 없으므로, 리눅스의 기본 서비스 관리자인 systemd의 힘을 빌려야 합니다. 이를 위해 Podman은 Quadlet이라는 기능을 제공하여 컨테이너 설정을 systemd unit 파일로 변환하고, 마치 일반 리눅스 시스템 데몬(Nginx, SSH 등)을 다루듯 OS가 직접 컨테이너를 기동하고 재시작하도록 만듭니다.

결국 '컨테이너 자동 재시작'이라는 간단한 기능 하나를 두고도, "독자적인 데몬이 모든 것을 관리할 것인가(Docker)" 아니면 "리눅스 OS의 표준 체계(systemd)에 자연스럽게 편입시킬 것인가(Podman)"라는 근본적인 철학의 차이가 드러나는 것입니다.

애플리케이션 패키징 방식의 변화와 전환의 장벽

Docker와 Docker Compose를 이용한 패키징 및 배포 방식은 수많은 스타트업과 솔루션 개발사들 사이에서 폭발적인 인기를 끌었습니다. 과거의 전통적인 소프트웨어 납품 방식에서는 설치 파일을 제공하고, 고객사마다 천차만별인 OS 버전과 라이브러리 환경을 사전에 맞춰야 하는 이른바 '의존성 지옥'을 감당해야 했습니다. Docker가 등장하기 전까지는 JVM이라는 가상머신을 통해 OS 종속성을 줄이고 이식성을 확보한 Java가 그나마 최선의 대안이었지만, 이제는 Docker를 통해 애플리케이션과 OS 실행 환경 전체를 '컨테이너'라는 표준화된 단위로 손쉽게 묶어 배포할 수 있게 되었습니다.

앞서 살펴봤듯 Docker는 Rootful 및 중앙 데몬 구조 덕분에 복잡한 권한 설정 없이도 매우 간편하고 안정적으로 동작했습니다. 하지만 과도한 권한 부여로 인한 보안 우려와 컨테이너 탈출(Container Escape) 취약점이 지속적으로 대두되면서, 엔터프라이즈 인프라 환경은 점차 권한을 최소화하는 Rootless 및 데몬리스(Daemonless) 방식으로 전환되고 있습니다.

문제는 이 과도기에 개발되고 패키징된 수많은 상용 솔루션들이 여전히 Docker 시절의 문법과 구조에 머물러 있다는 점입니다. 지금도 대부분의 솔루션은 docker-compose를 기준으로 배포됩니다. 후발주자인 Podman은 이러한 기존 Docker 생태계를 흡수하기 위해 CLI 명령어 수준에서 Docker와 100% 호환되도록 설계되었습니다. 덕분에 Podman만 설치된 환경이라도 호환 설정(alias 등)을 거치면 기존의 docker 명령어를 거의 그대로 사용할 수 있습니다.

물론 '명령어 호환'이 곧 '완벽히 동일한 동작'을 보장하는 것은 아닙니다. Docker Compose 문법으로 작성된 배포 파일을 podman-compose로 그대로 실행해 보면, 내부 아키텍처와 세부 명세 차이로 인해 예상치 못한 에러나 찜찜한 경고 메시지가 빈번하게 발생합니다. 특히 Rootless 특유의 권한 제약으로 인해 호스트 볼륨 마운트 시 파일 접근 권한(UID/GID)이 꼬이거나, 호스트 장치를 연결하는 과정에서 아예 실행조차 되지 않는 치명적인 오류가 발생하는 일도 부지기수입니다.

가장 대표적인 사례가 바로 NVIDIA GPU 연동입니다. 과거에는 Docker가 사실상의 표준으로 군림했기 때문에, 연산(Compute) 목적의 CUDA 환경을 컨테이너에 연결하는 기술 역시 Docker 데몬에 강하게 결합된 형태(nvidia-docker)로만 제공되었습니다. 따라서 AI나 딥러닝처럼 GPU를 필수적으로 사용해야 하는 분야에서는 사실상 Docker 외의 대안을 생각하기 어려웠습니다.

그러나 Podman을 비롯해 Docker 이외의 컨테이너 런타임 비중이 커지면서, 특정 런타임에 종속되지 않는 장치 연결 표준이 필요해졌습니다. 그 결과 현재는 CDI(Container Device Interface)라는 표준 규격을 지원하는 NVIDIA Container Toolkit이 등장하여, Podman(심지어 Rootless 환경)에서도 원활하게 GPU를 컨테이너에 할당할 수 있게 되었습니다.

그렇다면 기존에 Docker 기반으로 패키징된 수많은 AI 솔루션들을 이제 Podman 환경에서 그대로 가져다 쓸 수 있을까요? 현실은 그리 간단하지 않습니다. 기존에 작성된 docker-compose.yml을 Podman이 권장하는 네이티브 방식인 Quadlet(systemd 유닛) 형태로 일일이 변환하는 작업이 필요하기 때문입니다. podman-compose를 사용해 임시로 호환성을 유지할 수도 있지만 완벽하지 않고, 이를 Podman 규격에 맞게 재설계하는 것은 개발사 입장에서 상당한 부담이 됩니다. 게다가 애초에 컨테이너 이미지 자체가 Rootful Docker 환경(컨테이너 내부 root 권한 강제, 특권 포트 사용 등)을 당연하게 전제하고 만들어진 경우라면, 이를 Rootless Podman 환경에 이식하는 작업은 더욱 험난해집니다.

결국 현업에서 기존 Docker 기반 솔루션을 Podman으로 전환하는 과정에는 다음과 같은 현실적인 장벽들이 존재합니다.

  1. 생태계의 관성: Docker가 대중화된 시기에 성장한 대다수의 스타트업과 솔루션 벤더들이 이미 Docker 및 Docker Compose를 기준으로 자사 제품의 패키징·배포 체계를 굳혔습니다.
  2. 초기 GPU 생태계의 종속성: 과거 NVIDIA GPU의 컨테이너 지원이 Docker 중심으로만 이루어졌기 때문에, AI·데이터 분야의 레거시 자산들이 전부 Docker 기반으로 작성되었습니다.
  3. 패키징 변환(Migration) 비용: 현재는 CDI 표준을 통해 Podman에서도 GPU를 완벽히 지원하지만, 이미 docker-compose 형태로 굳어진 방대한 배포 스크립트를 Podman의 Quadlet(systemd) 체계로 단번에 재작성하기 어렵습니다.
  4. Rootful 전제 설계의 한계: 상당수의 컨테이너 이미지가 설계 단계부터 Rootful 환경을 전제로 제작되어, Rootless Podman 환경에 올렸을 때 발생하는 권한 및 네트워크 충돌을 해결하기가 까다롭습니다.
  5. 전환 동기의 불일치: 인프라·보안 측면에서는 Podman 도입이 절실하지만, 기능 구현과 빠른 납품이 최우선인 개발사(개발자) 입장에서는 기존에 잘 돌아가던 Docker 방식을 버리고 Podman으로 전환할 기술적 의지와 유인이 부족합니다.

다음 단계를 위한 노력: 우회로(Workaround)를 넘어 표준으로

최근 AI의 급격한 발전은 인프라와 개발 생태계 전반에 믿기지 않을 만큼 거대한 변화를 일으키고 있습니다. 현업에서 AI를 활용하며 느끼는 가장 큰 특징 중 하나는, 어떤 문제든 "일단 어떻게든 돌아가게 만든다"는 점입니다. 이는 당장의 문제를 해결하는 데는 엄청난 장점이지만, 동시에 장기적으로는 잘못된 아키텍처의 길로 빠져드는 입구가 되기도 합니다.

일반적으로 가장 좋은 해결책은 단순하고 명확한 구조에서 나옵니다. 제품 초기, 구조가 단순했을 때 선택한 방식은 당시로서 최선의 결정이었을 것입니다. 하지만 제품의 미래는 누구도 예측할 수 없습니다. 초기에는 작고 단순했던 제품이 시간이 지나며 규모가 커지고 기능이 복잡하게 확장되기도 합니다. 문제는 제품의 덩치는 커졌는데 배포와 운영 구조는 초기의 방식을 그대로 고수할 때 발생합니다.

당장 눈앞의 납기와 기능 추가가 급한 상황에서, AI는 기존의 낡은 구조를 유지한 채 문제를 우회하는 방법을 무서울 정도로 효과적으로 찾아냅니다. 그 결과, 근본적인 구조 개선 대신 온갖 임시방편(Workaround)이 겹겹이 결합된, 이제는 사람의 손으로는 건드리기조차 두려운 거대한 배포·래퍼(Wrapper) 스크립트 덩어리가 탄생합니다.

물론 이것은 누구의 잘못도 아닙니다. 초기에는 완벽히 동작했던 방법이었고, 제품이 이렇게까지 비대해질 줄은 아무도 몰랐으며, 한시가 바쁜 현업에서 아키텍처를 통째로 갈아엎자고 주장하기는 쉽지 않기 때문입니다. 당장의 위기를 돌파하기 위해 AI의 힘을 빌려 최선을 다한 결과물인 셈입니다. 다만 그 대가로 현재의 시스템이 제품의 규모에 맞지 않는 기형적인 구조가 되었고, 이제는 AI 없이는 유지보수조차 불가능하며, 아주 작은 예외 상황만 발생해도 전체가 멈춰버리는 아슬아슬한 상태에 놓인 것 또한 엄연한 사실입니다.

가령 과거 Docker로 문제없이 배포하던 환경에서, 최근 고도화된 보안 위협과 강화된 보안 규정으로 인해 Podman 도입이 필수 불가결해졌다고 가정해 봅시다. 이제 기존 Docker 기반으로 동작하던 배포 코드들을 Podman에 맞게 전환해야 합니다. 앞서 살펴봤듯 podman-compose는 어디까지나 개발이나 테스트 목적의 과도기적 도구일 뿐이며, 중앙 데몬이 없는 Podman의 특성상 Compose만으로는 서버 재부팅 시 컨테이너의 자동 기동조차 온전히 보장하지 못합니다.

이런 상황이라면 어떻게든 기존의 Compose 체계를 살려보겠다고 AI를 동원해 복잡한 재시작 스크립트나 권한 우회 로직을 덧대는 식의 임시방편을 써서는 안 됩니다. 정공법으로 Podman의 네이티브 방식인 Quadlet을 도입해 배포 구조를 근본적으로 전환해야 합니다.

다행히 AI를 올바른 방향으로 활용하면, 기존 docker-compose.yml 문법을 Quadlet 설정 파일로 번역하는 작업 자체는 그리 어렵지 않습니다. 복잡한 비즈니스 로직을 코딩하는 것보다 훨씬 수월한 일입니다. 진짜 넘어야 할 산은 문법 변환이 아니라 엔지니어의 관점 전환입니다. 기존의 익숙한 Docker 데몬과 Compose 방식에서 벗어나, 리눅스의 systemd unit 체계와 Quadlet의 유닛 자동 생성(Generator) 원리, 그리고 journalctl과 systemctl을 활용한 실행 및 디버깅 방식 등 새롭게 학습하고 적응해야 할 영역이 달라지기 때문입니다. 하지만 이 학습의 문턱만 넘어서면, 비로소 Podman과 리눅스 OS의 기본 체계가 결합된 가장 정석적이고 견고한 배포 환경을 손에 넣을 수 있습니다.

이렇게 한 번 '표준의 트랙'에 올라타면, 이후에는 표준이 주는 다양한 기술적 이점을 온전히 누릴 수 있습니다.

  • 확장성: 추후 컨테이너 오케스트레이션이 필요해질 경우, 쿠버네티스(Kubernetes) YAML 규격을 기본 지원하는 Podman과 Quadlet의 특성을 살려 경량 K8s(k3s 등) 환경으로 손쉽게 이관할 수 있습니다.
  • 단순성: 억지로 환경을 맞추기 위해 덕지덕지 붙였던 거대하고 복잡한 빌드·배포 스크립트를 걷어내고 구조를 대폭 단순화할 수 있습니다.
  • 유지보수성: 나만이 아는 독자적인 커스텀 스크립트가 아니라 리눅스와 컨테이너의 공식 표준을 따르기 때문에, 문제가 발생해도 구글링이나 AI를 통해 즉각적으로 원인을 추적하고 해결책을 찾을 수 있습니다. 밑바닥부터 자체 설계한 거대한 우회 스크립트는 장애가 날 때마다 그 방대한 맥락을 다시 AI에게 이해시켜야 하므로 갈수록 관리 비용이 기하급수적으로 늘어납니다.

분명 Docker에서 Podman으로 넘어가는 과정은 단순히 컨테이너 명령어 하나를 새로 배우는 수준의 일이 아닙니다. 그 밑바탕에 깔린 보안 구조를 이해하고, 리눅스라는 OS의 표준 관리 체계를 깊이 있게 다루어야 하는, 생각보다 배울 것이 많은 고된 작업임은 분명합니다. 그러나 AI가 당장 눈앞에 제시하는 달콤한 우회로(Workaround)의 유혹을 뿌리치고 표준에 입각한 정석적인 길을 선택한다면, 당장은 돌아가는 것처럼 보여도 결국 가장 빠르고 안전하게 목적지에 도달하는 길이 열릴 것입니다.