개발하다 보면 환경 차이 때문에 예상치 못한 오류를 자주 만나게 됩니다.
특히 여러 사람이 서버를 관리하는 환경에서는 각자 다른 방식으로 설정하다 보니 일관성이 무너지곤 합니다.
컨테이너를 활용하면 이런 문제들을 깔끔하게 해결할 수 있다는 걸 알게 되었고, 제대로 이해하기 위해 정리한 내용들을 공유합니다.
컨테이너를 실행하기 위해서는 먼저 Docker가 필요합니다.
그렇다면 Docker란 무엇이고, 왜 애플리케이션을 컨테이너화하여 사용하는 걸까요?
애플리케이션을 왜 컨테이너화 해야할까?
컨테이너를 사용하면 주변 환경과의 격리가 가능하고 애플리케이션이 실행할 때 사용하는 모든 것이 컨테이너 안에 준비됩니다.
예를 들어, 우리가 만든 Spring Boot 애플리케이션의 경우 대부분의 의존성은 Gradle에 의해 관리되고 JAR 파일로 패키징되지만, 애플리케이션을 배포하는 서버에는 Java 런타임이 설치되어 있어야 합니다.
컨테이너화를 하면 어떤 종류의 클라우드 환경이라도 애플리케이션이 독립적이고 이식 가능하다는 것을 의미합니다.
또한 애플리케이션을 구현하는 데 사용되는 언어나 프레임워크에 상관없이 표준적인 방식으로 애플리케이션을 관리할 수 있습니다.
컨테이너와 가상머신의 차이점
처음에는 "컨테이너가 가상머신과 뭐가 다른가?"라는 의문이 있었는데, 호스트 운영체제에서 격리된 환경을 구현하는 방식에 차이가 있었습니다.
가상화 환경과 컨테이너의 구성을 보면 아래와 같습니다.

가상머신(Virtual Machine)
하이퍼바이저(Hypervisor)를 이용하여 하나의 물리적 서버 위에 여러 개의 독립적인 운영체제(OS)를 실행하는 방식입니다.
- 특징 : 각 VM 마다 독립적인 Guest OS를 가짐
- 단점 : OS가 중복 설치되므로, 자원소모(CPU, 메모리 등)가 크고, 미리 자원을 할당하므로 유동적인 관리가 어려움
컨테이너
컨테이너는 호스트 OS의 커널을 공유하면서, 애플리케이션 실행에 필요한 라이브러리와 바이너리만 격리하여 실행하는 기술입니다.
- 특징 : 불필요한 OS 설치 없이 프로세스 단위로 격리
- 장점 : VM 보다 메모리를 훨씬 적게 사용하고, 용량이 작아 백업, 전송, 마이그레이션이 쉬움.
하드웨어와의 통신이 직접적이라 성능 우수 - 단점 : 호스트 OS 커널 의존 (Linux 커널은 Linux 컨테이너만 사용 가능)
컨테이너는 애플리케이션의 독립성을 보장하면서도 컴퓨터 자원을 낭비하지 않기 때문에,
서비스 규모를 유연하게 늘리거나 줄여야 하는 오늘날의 인프라 환경에 적합한 선택일 수 있습니다.
도커(Docker)란?
Docker는 Linux 컨테이너를 기반으로 하는 오픈소스 가상화 플랫폼 입니다.
컨테이너가 기술이라면, 도커는 그 기술을 누구나 쉽게 사용할 수 있게 만든 도구(플랫폼)입니다.
Docker를 사용하기 이전에 구성요소를 간단하게 알아봅시다.
- 도커 서버 (Docker Server)
Docker가 설치되어 있는 호스트 시스템으로, Docker daemon(dockerd)이 실행되는 곳입니다. - 도커 데몬 (Docker Daemon)
백그라운드에서 실행되면서 컨테이너 실행, 이미지 관리 등을 담당하는 핵심 엔진입니다. - 도커 클라이언트 (Docker Client)
우리가 docker run, docker build 같은 명령어를 입력하는 CLI 인터페이스입니다. - 컨테이너 이미지 (Container Image)
컨테이너를 만들기 위한 불변한 실행 패키지로, 애플리케이션과 필요한 모든 것들이 포함되어 있습니다. - 컨테이너 (Container)
이미지로부터 생성된 격리된 실행 환경으로, 하나의 이미지로 여러 개의 컨테이너를 실행할 수 있습니다.
컨테이너는 어떻게 작동할까?
컨테이너는 다음과 같은 기술들로 구성됩니다
1. 파일 시스템 계층 (Union File System)
Docker는 여러 계층으로 구성된 파일 시스템을 사용합니다.
- 이미지 레이어: 변경 불가능한 읽기 전용 파일 시스템
- 컨테이너 레이어: 실행 중 변경된 내용을 저장하는 쓰기 가능 계층
이 구조 덕분에 여러 컨테이너가 같은 베이스 이미지를 공유하면서도 각자의 변경사항을 독립적으로 관리할 수 있습니다.
2. 네임스페이스 (Namespaces)
각 컨테이너에 별도의 리소스 공간을 부여해서 격리된 환경을 만듭니다.
- PID: 프로세스 ID 격리 (호스트나 다른 컨테이너의 PID 안보임)
- NET: 네트워크 스택 격리 (네트워크 인터페이스, 라우팅 테이블, 포트 번호)
- MNT: 마운트된 파일 시스템 격리
- IPC : 프로세스 간 통신 리소스 격리
- UTS : 호스트 이름, 도메인 격리
- USER : UID, GID 등의 사용자 권한 격리
3. CGroups (Control Groups)
CPU, 메모리, 디스크 등의 리소스 사용량을 제한하고 모니터링합니다.
아래의 명령은 컨테이너가 CPU 2, 메모리 4GB만 사용하도록 제한합니다. 컨테이너가 더 많은 리소스를 요구해도 CGroups가 막아줍니다.
docker run --cpus="2.0" --memory="4g" nginx
CGroups는 각 컨테이너가 실제로 사용중인 CPU, 메모리, 디스크 I/O, 네트워크를 사용하는지 실시간을 측정합니다.
docker stats 명령으로 이 정보를 확인할 수 있습니다.
4. 도커 네트워크
컨테이너는 서로 격리되어 있지만, 필요에 따라 서로 통신하고 외부와 연결되어야 합니다.
도커는 이를 위해 여러 네트워크 모드를 제공합니다.
Bridge 네트워크 (기본값)
같은 호스트의 컨테이너들이 서로 통신할 때 사용합니다.
- 원리 : 도커를 설치하면 호스트에 docker0 이라는 가상 브릿지가 자동으로 생성됩니다. 각 컨테이너는 가상 이더넷 인터페이스(veth)을 통해 이 브릿지에 연결되며, 172.17.0.0/16 대역의 사설 IP를 할당받습니다.
- 특징 : 외부에서 컨테이너에 접속하려면 아래와 같이 포트포워딩이 필요합니다.
docker run -d -p 80:80 --name web nginx
Host 네트워크
컨테이너가 호스트의 네트워크를 그대로 사용합니다.
docker run --network host nginx
- 원리: 별도의 네트워크 격리 없이 호스트의 IP와 포트를 직접 사용합니다.
- 특징: 포트 포워딩이 필요 없으며, 네트워크 오버헤드가 없어 성능이 매우 빠릅니다. 하지만 포트 충돌 가능성이 큽니다.
Overlay 네트워크
여러 대의 호스트 서버에 흩어져 있는 컨테이너들을 하나의 네트워크로 묶어줍니다.
예를 들어, 아래와 같이 web과 mysql 컨테이너가 있을 때, Overlay 네트워크를 사용하면 두 컨테이너가 같은 스위치에 연결된 것처럼 통신할 수 있습니다.
docker network create -d overlay my-network
docker service create --network my-network --name web nginx
docker service create --network my-network --name db mysql
- 원리 : VXLAN 기술을 사용하여 서로 다른 호스트의 컨테이너들을 가상 네트워크로 연결합니다. 도커 스웜이나 쿠버네티스 같은 오케스트레이션 도구에서 필수적으로 사용됩니다.
- 특징 : IP 대신 컨테이너 이름으로 통신할 수 있음 (ping db), 다른 네트워크의 컨테이너와 분리하여 유연하게 관리 가능
5. 도커의 데이터 관리
도커 컨테이너에 사용된 파일들은 컨테이너가 삭제될 떄 함께 삭제됩니다.
컨테이너의 데이터를 영구적으로 보관하기 위해서는 바인드마운트와 볼륨을 사용할 수 있습니다.
두 방식 모두 컨테이너 외부에 데이터를 저장하지만, 작동 방식과 사용 목적이 다릅니다.
바인드 마운트(Bind Mount)
바인드 마운트는 호스트 파일 시스템의 특정 경로를 컨테이너 내부로 직접 연결하는 방식입니다.
가장 직관적이고 이해하기 쉬운 방법입니다.
# 전체 경로 지정 방식
docker run -v /home/user/data:/app/data nginx
# 읽기 전용 마운트
docker run -v /host/config:/app/config:ro nginx
# mount 옵션 사용
docker run --mount type=bind,source=/host/data,target=/app/data nginx
바인드 마운트는 호스트 디렉터리의 내용을 그대로 컨테이너 디렉터리에 덮어쓴다는 특징이 있습니다.
만약 컨테이너의 /usr/share/nginx/html 디렉터리에 원래 파일이 있었다면, 바인드 마운트를 연결하는 순간 호스트의 /root/html 내용으로 완전히 대체됩니다.
이러한 특징으로 바인드마운트는 주로 개발 환경에서 사용됩니다.
볼륨(Volume)
볼륨은 도커가 관리하는 전용 저장 공간입니다.
호스트 파일 시스템과 분리되어 있으며, 도커가 생명주기를 관리하기 때문에,
컨테이너 내부와 연결할 때, 전체 디렉터리 경로를 사용하지 않고 볼륨 이름만으로 간편하게 연결할 수 있습니다.
# 볼륨 생성
docker volume create my-data
# 볼륨 목록 확인
docker volume ls
# 볼륨 상세 정보
docker volume inspect my-data
# 볼륨 연결하여 컨테이너 시작
docker run -v my-data:/app/data nginx
이렇게 생성된 볼륨은 호스트 서버의 /var/lib/docker/volumes/ 아래에 저장됩니다.
컨테이너에는 여러개의 볼륨을 적용할 수 있는데, 나중에 세팅을 바꾸기가 어렵기 때문에 초기에 설정하는 것이 좋습니다.
볼륨의 특징
바인드 마운트와 달리 볼륨은 컨테이너의 빈 디렉터리를 덮어쓰지 않고, 내부의 파일을 보존합니다.
그러나 볼륨에 컨테이너와 동일한 파일이 존재한 상태로 연결하는 경우에는 컨테이너 디렉터리에 있는 파일로 덮어쓰기가 됩니다.
- 장점 : 도커가 생성/삭제/백업을 관리하므로 수동으로 디렉터리를 만들거나 권한을 설정할 필요가 없음, 호스트 경로에 의존하지 않아 이식성이 좋고, 여러 컨테이너가 안전하게 공유할 수 있음
- 단점 : 호스트에서 파일을 직접 열어보거나 수정하기 어렵기 때문에 디버깅 시 추가 단계가 필요함
어떤 방식을 선택할까?
볼륨과 바인드 마운트는 사용하려는 상황에 맞게 적합한 방법을 사용하는 것이 좋습니다.
바인드 마운트가 적합한 경우:
- 개발 환경에서 코드를 실시간으로 수정하며 테스트할 때
- 설정 파일을 호스트에서 직접 관리하고 싶을 때
- 로그 파일을 호스트에서 바로 확인하고 싶을 때
볼륨이 적합한 경우:
- 프로덕션 환경의 데이터베이스 데이터
- 컨테이너를 삭제해도 반드시 보존해야 하는 데이터
- 여러 컨테이너가 데이터를 공유해야 할 때
- 데이터 백업과 마이그레이션이 중요한 경우
주의사항
컨테이너가 호스트의 같은 파일 시스템을 공유하는 경우, 동시 접근시 발생할 수 있는 문제가 있습니다.
여러 컨테이너가 같은 파일을 동시에 수정하려고 하면, 파일 시스템 수준에서는 동시 쓰기를 막지 않기 때문에 데이터가 섞이거나 덮어써질 수 있습니다.
안전하게 공유 디렉터리를 설정하기 위한 방법은 나중에 다뤄보도록 하겠습니다.
macOS/Windows에서의 Docker
개인적인 궁금증이었는데, macOS나 Windows에서 Docker는 어떻게 실행될까 궁금했었습니다.
컨테이너는 본래 Linux 커널 기능에 의존하기 때문에, macOS/Windows용 Docker Desktop을 설치하면 실제로는 Linux 가상머신 위에서 Docker가 실행됩니다.
사용자 입장에서는 차이점을 느낄 수 없지만, 실제로는 로컬 컴퓨터가 아닌 Linux 가상머신의 Docker 서버와 상호작용하게 됩니다.
# 예를 들어, MacOS의 경우
도커 클라이언트 : darwin/amd64
도커 서버 : linux/amd64
지금까지 컨테이너와 도커가 무엇인지에 대해 알아봤습니다.
대략적인 이론을 이해했으니, 다음 글에서는 도커를 설치하고 사용하는 과정들을 정리하려고 합니다.
참고자료
[도서]컨테이너 인프라 환경 구축을 위한 쿠버네티스/도커
'DevOps > docker' 카테고리의 다른 글
| [Docker] 컨테이너 실행 과정 파헤치기 (0) | 2025.12.20 |
|---|