블로그
컨테이너 탈출 방어: 멀티 테넌트 호스팅을 위한 Docker 격리의 실용 가이드
멀티 테넌트 환경에서 컨테이너 탈출 취약점 및 격리 실패로부터 Docker 컨테이너를 보호하는 방법을 구체적인 단계와 예제를 통해 알아보세요.

요약
Docker 컨테이너는 호스트 커널을 공유하므로 격리가 중요합니다. 특히 멀티 테넌트 호스팅에서는 단일 컨테이너 탈출이 모든 테넌트를 손상시킬 수 있습니다. 많은 개발자는 컨테이너가 완벽하게 격리된 가상 머신이라고 가정하지만 현실은 다릅니다. 이 글에서는 Docker 격리의 기반이 되는 Linux 커널 기능(네임스페이스, cgroup)과 이를 위협하는 공격 벡터를 설명합니다. 권한 제한, 보안 런타임 사용, 이미지 스캔, 네트워크 분할 구현 등 Docker 설정을 강화하는 실용적인 단계를 배우게 됩니다. 멀티 테넌트 WordPress 호스팅 제공업체의 실제 사례를 통해 이러한 방어를 적용하는 방법을 볼 수 있습니다. 성능 절충 및 seccomp/AppArmor 사용과 같은 주의 사항도 다룹니다. 목표는 컨테이너 탈출을 방지하고 테넌트를 안전하게 유지하는 강력한 격리 전략을 제공하는 것입니다.
소개
공유 WordPress 호스팅, SaaS 애플리케이션 또는 개발 환경 서비스 등 멀티 테넌트 호스팅 플랫폼을 운영한다면 컨테이너 탈출은 악몽 같은 시나리오입니다. 커널 취약점이나 잘못된 구성으로 인해 한 테넌트가 컨테이너를 탈출하여 다른 테넌트의 데이터나 호스트 자체에 접근할 수 있습니다. Docker의 격리는 네임스페이스 및 cgroup과 같은 Linux 커널 기능에 의존하지만, 기본 구성은 강력한 보안에 종종 불충분합니다. 이 글에서는 공격 벡터를 살펴보고 실제 멀티 테넌트 WordPress 예제를 통해 Docker 컨테이너를 잠그는 실행 가능한 단계를 제공합니다. 프로덕션 오케스트레이션에 대한 더 넓은 시각을 보려면 프로덕션 준비 컨테이너화된 애플리케이션 오케스트레이션 가이드를 참조하세요.
Docker 격리 이해
Docker 컨테이너는 프로세스 수준 격리를 위해 Linux 네임스페이스를 사용합니다. PID 네임스페이스는 프로세스 트리를 격리하고, 네트워크 네임스페이스는 네트워크 인터페이스를 분리하며, 마운트 네임스페이스는 파일 시스템 마운트를 격리하고, 사용자 네임스페이스는 컨테이너 루트를 권한 없는 호스트 사용자로 매핑할 수 있게 합니다. 제어 그룹(cgroup)은 CPU, 메모리, 디스크 I/O와 같은 리소스 사용량을 제한합니다. 이러한 기능들이 함께 각 컨테이너 주위에 "샌드박스"를 만듭니다. 그러나 별도의 커널을 실행하는 가상 머신과 달리 컨테이너는 호스트 커널을 공유합니다. 이는 커널의 취약점(예: CVE-2022-0492)을 악용하여 컨테이너의 네임스페이스 격리를 탈출할 수 있음을 의미합니다. 또한 컨테이너 내에서 루트로 컨테이너를 실행하거나, 컨테이너에 모든 권한을 부여하거나, 불필요한 Linux 권한을 해제하지 않는 것과 같은 잘못된 구성은 공격 표면을 넓힐 수 있습니다.
공격 벡터
일반적인 공격 벡터는 다음과 같습니다:
- 커널 익스플로잇: 호스트 커널의 버그를 악용하여 호스트 액세스 권한을 얻습니다.
- 권한 있는 컨테이너:
--privileged로 실행하면 모든 권한이 부여되고 대부분의 격리가 우회됩니다. - 권한 남용: 전체 권한 모드가 없더라도
CAP_SYS_ADMIN또는CAP_NET_ADMIN과 같은 위험한 권한을 가진 컨테이너는 파일 시스템을 마운트하거나 네트워크 설정을 조작할 수 있습니다. - 안전하지 않은 이미지 관행: 알려진 취약점이 있는 기본 이미지를 사용하거나 컴파일러 또는 셸 인터프리터와 같은 불필요한 도구를 포함합니다.
- 공유 마운트 네임스페이스: 호스트 디렉토리를 컨테이너에 마운트하면 읽기 전용이 아닌 경우 탈출을 허용할 수 있습니다.
실용적인 보안 단계
1. 비루트 사용자로 컨테이너 실행
기본적으로 Docker는 컨테이너 내부에서 루트로 컨테이너를 실행합니다. 공격자가 컨테이너 내부에서 루트 권한을 얻으면 더 많은 영향력을 행사할 수 있습니다. Dockerfile에서 사용자를 생성하고 USER 지시문을 사용하세요. 또한 가능한 경우 --user 플래그를 사용하여 임의의 호스트 사용자로 매핑하는 것을 피하세요.
2. 모든 권한을 해제하고 필요한 것만 추가
Linux 권한은 슈퍼유저 권한을 더 작은 단위로 나눕니다. Docker Compose에서는 cap_drop: ALL을 사용한 다음 필요한 권한(예: NET_BIND_SERVICE)만 cap_add로 추가하세요. SYS_ADMIN, NET_ADMIN, SYS_PTRACE와 같은 위험한 권한은 피하세요.
3. 읽기 전용 루트 파일 시스템 사용
컨테이너 정의에서 read_only: true를 설정하세요. 이렇게 하면 공격자가 컨테이너 파일 시스템에 쓸 수 없습니다. 앱이 임시 파일을 써야 하는 경우 해당 위치에 tmpfs 볼륨을 마운트하세요.
4. 사용자 네임스페이스 재매핑 활성화
사용자 네임스페이스 재매핑은 컨테이너의 루트 사용자를 비루트 호스트 사용자로 매핑합니다. 이는 컨테이너 루트가 탈출하더라도 재매핑된 사용자의 권한을 갖게 되므로 격리 계층을 추가합니다. /etc/docker/daemon.json에서 "userns-remap": "default"로 활성화하세요. 이로 인해 볼륨 권한이 복잡해질 수 있습니다. 자세한 내용은 안전하고 효율적인 웹 호스팅을 위한 Docker 격리 마스터링을 참조하세요.
5. Seccomp 및 AppArmor/AppArmor 프로필 적용
Seccomp는 컨테이너가 호출할 수 있는 시스템 호출을 제한합니다. Docker는 위험한 시스템 호출을 차단하는 기본 seccomp 프로필을 제공합니다. 사용자 지정 프로필을 만들 수도 있습니다. 마찬가지로 AppArmor(또는 SELinux)는 강제 액세스 제어를 제공합니다. AppArmor를 사용하여 컨테이너를 최소한의 허용된 작업 집합으로 제한하세요. 보안 프로필은 Docker Compose의 security_opt를 통해 설정할 수 있습니다.
6. 최소 기본 이미지 사용 및 취약점 스캔
공격 표면이 작은 Alpine 또는 Distroless와 같은 작은 이미지를 선택하세요. Docker Scout, Trivy 또는 Clair와 같은 도구로 이미지를 정기적으로 스캔하세요. 취약한 이미지가 배포되지 않도록 CI/CD 파이프라인에 스캔을 통합하세요.
7. 사용자 지정 브리지 네트워크를 사용한 네트워크 분할
각 테넌트 또는 애플리케이션 계층에 대해 별도의 브리지 네트워크를 만드세요. 이렇게 하면 동서 트래픽이 제한됩니다. Docker Compose에서는 네트워크를 정의하고 서비스를 격리하세요. 서비스에 인터넷 아웃바운드 액세스가 필요하지 않은 경우 internal: true를 사용하세요. 호스트의 방화벽 규칙은 컨테이너 간 트래픽을 더욱 제한합니다.
8. Cgroup을 사용한 리소스 제한
Docker Compose에서 deploy.resources.limits를 사용하여 CPU 및 메모리 제한을 설정하세요. 이렇게 하면 손상된 컨테이너가 리소스 고갈 공격을 시작하는 것을 방지할 수 있습니다. 또한 더 세밀한 제어를 위해 kernel_memory 및 memory_reservation을 설정하세요.
실제 사례: Docker Compose를 사용한 멀티 테넌트 WordPress 호스팅
각각 자체 Docker 컨테이너에 있는 여러 WordPress 사이트를 다른 클라이언트를 위해 호스팅하는 시나리오를 고려해 보세요. 안전하지 않은 설정은 다음과 같을 수 있습니다:
version: '3'
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
- ./wp-content:/var/www/html/wp-content
db:
image: mysql:5.7
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_ROOT_PASSWORD: somewordpress
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
이 설정은 취약합니다. WordPress 컨테이너는 내부에서 루트로 실행되고, 모든 권한을 가지고 있으며(아무것도 해제되지 않았으므로), 쓰기 액세스 권한이 있는 호스트 디렉토리를 마운트하고, 제한되지 않은 네트워크 액세스 권한을 가집니다.
이제 이를 강화해 보겠습니다:
version: '3'
services:
wordpress:
image: wordpress:latest
user: www-data
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
read_only: true
tmpfs:
- /var/www/html/wp-content/plugins
security_opt:
- seccomp=seccomp-profile.json
- apparmor=wordpress-profile
networks:
- frontend
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: exampleuser
WORDPRESS_DB_PASSWORD: examplepass
WORDPRESS_DB_NAME: exampledb
volumes:
- wp-uploads:/var/www/html/wp-content/uploads
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
db:
image: mysql:5.7
user: mysql
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
networks:
- backend
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
MYSQL_PASSWORD: examplepass
MYSQL_ROOT_PASSWORD: somewordpress
volumes:
- db_data:/var/lib/mysql
deploy:
resources:
limits:
cpus: '0.25'
memory: 128M
networks:
frontend:
driver: bridge
internal: false
backend:
driver: bridge
internal: true
volumes:
wp-uploads:
db_data:
주요 개선 사항:
- 두 컨테이너 모두 비루트 사용자(
www-data및mysql)로 실행됩니다. - 모든 권한이 해제되고
NET_BIND_SERVICE만 추가되었습니다. - WordPress 파일 시스템은 tmpfs 마운트 및 업로드 볼륨을 제외하고 읽기 전용입니다.
- Seccomp 및 AppArmor 프로필이 적용됩니다(사용자 지정 프로필을 제공해야 함).
- 별도의 네트워크가 웹과 데이터베이스를 격리하며, 데이터베이스 네트워크는 내부입니다.
- 리소스 제한으로 리소스 고갈을 방지합니다.
WordPress별 Docker 강화에 대한 자세한 내용은 Docker for WordPress: 격리된 컨테이너가 모든 것을 바꾸는 이유를 참조하세요.
주의 사항
- 사용자 네임스페이스 재매핑: 강력하지만, 재매핑된 호스트 UID가 컨테이너 UID와 같지 않기 때문에 볼륨 마운트가 실패합니다. 올바른 권한으로 디렉토리를 미리 생성하거나 재매핑 지원이 있는 Docker 볼륨을 사용해야 할 수 있습니다.
- Seccomp/AppArmor 프로필: 사용자 지정 프로필은 애플리케이션의 시스템 호출 및 파일 액세스 패턴에 대한 이해가 필요합니다. 지나치게 제한적인 프로필은 기능을 손상시킬 수 있습니다. 철저히 테스트하세요.
- 성능: seccomp 및 AppArmor와 같은 추가 보안 계층은 최소한의 오버헤드를 갖지만, 리소스 제한 및 읽기 전용 파일 시스템은 쓰기 집약적인 애플리케이션에 영향을 줄 수 있습니다.
- 오케스트레이션 복잡성: 멀티 테넌트 환경에서는 테넌트별 Docker Compose 파일을 관리하는 것이 번거로울 수 있습니다. Kubernetes와 같은 상위 수준 오케스트레이션 도구를 고려할 수 있지만, 이는 자체적인 보안 고려 사항을 도입합니다.
결론
컨테이너 탈출은 멀티 테넌트 Docker 호스팅에서 실제 위협이지만 예방할 수 있습니다. 격리 메커니즘을 이해하고 권한 해제, 비루트 사용자 실행, 사용자 네임스페이스 활성화, seccomp, AppArmor, 네트워크 분할 및 정기적인 이미지 스캔과 같은 심층 방어를 적용함으로써 위험을 크게 줄일 수 있습니다. Docker의 기본 설정은 멀티 테넌트 워크로드에 대해 프로덕션 준비가 되지 않았다는 것을 기억하세요. 이러한 단계를 오늘 구현하여 테넌트와 인프라를 보호하세요. Docker 보안 모범 사례에 대한 포괄적인 개요는 Docker로 웹 애플리케이션 보안 강화: 격리 및 모범 사례 실용 가이드를 참조하세요.
Sources (5)
- Docker and Container Isolation - Medium
- What is container isolation? Mechanisms, limitations, and secure runtimes | Blog - Northflank
- Container Isolation Explained for Kubernetes and Beyond - Edera
- Docker Security: 5 Risks and 12 Best Practices for Securing Your Containers - Tigera.io
- 9 Security Best Practices for Docker Containers - Kinsta®

