도커? 컨테이너? 이제는 정확히 이해하자
항상 컨테이너에 대해 제대로 설명을 못하는데, 이번 기회에 컨테이너에 대해 설명할 수 있게 공부하자!
컨테이너 vs VM
VM은 운영체제까지 따로 설치하는 독립된 컴퓨터를 하나 더 만드는 느낌이라면
컨테이너는 운영체제는 공유하지만 앱만 따로 격리된 가상 공간에 띄운 것이다.
구분 | 가상머신(VM) | 컨테이너(Container) |
---|---|---|
구동 방식 | 하드웨어 위에 OS별 가상화 | OS 위에 프로세스 격리 |
구성 | 전체 OS + 라이브러리 + 앱 | 라이브러리 + 앱 (커널은 호스트 공유) |
무게감 | 무겁다 (수 GB, 느림) | 가볍다 (수 MB~수백 MB, 빠름) |
부팅 시간 | 수 분 | 수 초 또는 수 밀리초 |
자원 격리 | 완전한 하드웨어 수준 격리 (무겁지만 강력) | OS 커널 공유 (가볍지만 상대적 격리 약함) |
운영체제 | 서로 다른 OS 설치 가능 | 동일한 OS 커널을 공유 (보통 Linux 기반) |
사용 사례 | Windows + Linux 같이 돌려야 하는 상황 | 경량 마이크로서비스, 빠른 배포, 이식성 요구 시 |
대표 기술 | VMware, VirtualBox, Hyper-V | Docker, Podman, Kubernetes |
컨테이너는 VM보다 배포가 가볍고 빠르고, 자원 소모가 적고, 빠른 스케일링이 가능하며, 일관성 있다는 장점이 있다!
컨테이너 이미지 내부 구조 확인하기
컨테이너 이미지는 계층(레이어) 구조를 가진다.
최상위 Layer (사용자 커스텀, 소스 코드, 설정파일 등)
│
├── Layer 3 (애플리케이션 실행에 필요한 라이브러리 설치)
│
├── Layer 2 (필요한 패키지 설치 - apt install, pip install 등)
│
└── Layer 1 (베이스 OS - ubuntu, alpine 등)
이런식으로 가장 하단에 첫번째 레이어가 깔리고 그 위에 다음 레이어가 쌓이는 구조이다.
각 레이어는 불변하고, 캐싱되어 재사용된다. 또한 중간에 수정이 발생하면 수정된 그 층부터 다시 생성된다!
구성 요소
- Base Image
- OS 레이어
- Dependency Layer
- 패키지 설치, 라이브러리 설치
- App Layer
- 소스 코드 복사, 환경 설정
- Metadata
- 이미지 이름, 태그, 환경변수 정보 등
- Entrypoint / Command
- 컨테이너 실행 시 동작할 명령어
RUN pip install … 를 여러 번 수정하면, 그 뒤 레이어만 다시 만들어진다. 변경없는 레이어는 캐시로 재사용되어 빌드 속도가 빨라진다.
컨테이너 집중 탐구
컨테이너
컨테이너는 운영체제의 커널을 공유하면서, 독립된 앱 실행 환경을 제공하는 경량 가상화 기술이다.
실제 머신이나 VM처럼 OS 전체를 갖추는 대신, 앱과 필수 라이브러리만 포함하고, OS 커널은 호스트와 공유해서 가볍고 빠르다.
도커
도커는 이러한 컨테이너를 만들고, 실행하고, 배포할 수 있게 도와주는 대표적인 도구이다!
도커를 통해서 아래와 같은 작업을 할 수 있다.
- 컨테이너 이미지 빌드 : Dockerfile 기반으로 앱 실행 환경을 레이어 구조로 만듦
- 컨테이너 실행 및 관리 : 도커 명령어를 통해 컨테이너 실행
- 이미지 배포 : 도커 허브, AWS ECR 같은 저장소에 이미지 업로드/다운로드
- 자원 격리 및 네트워킹 : 각 컨테이너에 독립된 포트/네트워크 설정
컨테이너 생명주기
- Dockerfile 작성
- docker build → 이미지 생성
- docker run → 이미지로 컨테이너 실행
- 컨테이너 운영 (테스트, 서비스 제공)
- docker stop / rm → 종료 및 삭제
컨테이너가 격리된 ‘독립된 공간처럼’ 동작할 수 있는 건 리눅스의 Namespace와 cgroup 덕분이다.
이 두 가지가 컨테이너의 핵심 기술이라고 보면 된다.
컨테이너의 특징
특징 | 설명 |
---|---|
격리성 (Isolation) | 다른 컨테이너나 호스트와 프로세스, 파일, 네트워크 등을 분리 |
경량성 (Lightweight) | OS 전체가 아닌 필요한 실행 환경만 포함 → 가볍고 빠름 |
이식성 (Portability) | 한 번 만든 이미지를 어디서든 동일하게 실행 가능 (로컬, 클라우드 등) |
확장성 (Scalability) | 필요한 만큼 컨테이너 인스턴스 복제/스케일 가능 |
빠른 시작 (Fast Startup) | OS 부팅 필요 없이 바로 실행 → 수 초 이내 동작 |
일관성 (Consistency) | 개발, 테스트, 운영 환경을 동일한 이미지 기반으로 유지 가능 |
기술적 기반
- Namespace
- 컨테이너마다 격리된 자원을 제공하는 기술
- PID Namespace : 컨테이너 프로세스 ID가 호스트와 분리
- Net Namespace : 컨테이너 네트워크 (IP, 포트) 격리
- MNT Namespace : 파일 시스템 격리 (볼륨 등)
- UTS Namespace : 호스트네임 격리
- IPC Namespace : 프로세스 간 통신 격리
- USER Namespace : 사용자/권한 격리 (루트권한처럼 보이지만 실제로는 호스트 제한)
- cgroup
- cpu, 메모리, 네트워크 대역폭 같은 자원 사용량 제한 및 관리 기술
- cpu 코어 사용 제한
- 메모리 용량 제한
- 네트워크 대역폭 제한
여러 컨테이너가 서로 자원을 뺏어먹지 않도록 보장한다.
이는 과도한 자원 사용으로 인한 서버 과부하를 방지한다.
LXC vs runC
LXC
LXC는 리눅스 컨테이너의 약자로, 리눅스 커널의 Namespace + cgroup 기능을 직접 활용해서 컨테이너를 관리하는 초창기 리눅스 컨테이너 기술이다.
- 리눅스 기반 컨테이너의 원조격
- 가벼운 VM처럼 동작
runC
표준화한 경량 컨테이너 런타임이다.
- Docker가 내부적으로 사용하는 실제 컨테이너 실행 엔진
- 저수준 컨테이너 실행 담당
- 컨테이너 스펙(이미지, 파일시스템, 네트워크 등)을 기반으로 진짜 실행
- Docker, containerd, CRI-O 등이 runc를 호출해 컨테이너 생성/실행
나는 Docker 기반으로 주로 쓸 거 같다. (즉 runc를 사용할 것 같음~~)
Docker 컨테이너 실행 구조
[ Docker CLI ]
│
▼
[ Docker Engine (dockerd)] : 도커의 진입점. 도커 전체 운영 관리. containerd를 관리
│ ㄴ 도커 엔진에 이미지 실행을 요청하면 containerd 데몬에 책임을 위임
▼
[ containerd ] : 컨테이너 실행 및 라이프사이클 관리. 컨테이너 이미지 저장/전송
│ ㄴ 컨테이너 생성 시 container-shim 경량 프로세스 생성
│ ㄴ container-shim(= podman common): 개별 컨테이너 관리 및 runC 호출
▼
[ runc (컨테이너 실행 엔진) ] : 표준 OCI 런타임
│ ㄴ namespace, cgroup, 보안 설정 정보 생성
▼
[ 리눅스 커널 (Namespace + CGroup) ]
도커 적용 시나리오 (예시)
-
개발 환경 표준화
- 팀원 간 OS, 라이브러리 버전, 실행 환경이 다르면 에러가 발생
- 이를 해결하기 위해 Dockerfile을 만들어 공통된 실행 환경을 정의함
# 베이스 이미지 FROM python:3.11-slim # 작업 디렉토리 설정 WORKDIR /app # 필요한 파일 복사 COPY requirements.txt . # 패키지 설치 RUN pip install -r requirements.txt # 애플리케이션 복사 COPY . . # 애플리케이션 실행 명령 CMD ["python", "app.py"]
-
이미지 빌드
- 위에서 정의한 Dockerfile을 기반으로 이미지를 만듦
docker build -t myapp:latest .
-
컨테이너 실행
- 빌드된 이미지를 바탕으로 컨테이너를 띄워 실행
docker run -d -p 8000:8000 --name myapp-container myapp:latest
-
테스트 및 디버깅
- 컨테이너에 접속해서 테스트하거나 로그를 확인
docker exec -it myapp-container /bin/bash docker logs myapp-container
-
개발-운영 일관성 유지
- 개발자가 만든 이미지를 운영 서버에 그대로 배포 가능
-
Docker Compose로 다중 컨테이너 관리
- DB, 애플리케이션, 캐시 서버 등 여러 서비스를 한 번에 띄우고 싶을 때 사용
version: '3' services: app: build: . ports: - "8000:8000" db: image: mariadb environment: MYSQL_ROOT_PASSWORD: example
docker-compose up -d
도커는 운영 환경을 표준화하는 도구라고 이해하면 될 듯 하다..
+) 실전 배포 흐름
- Docker로 이미지 빌드
- 배포 인프라에 올리기
- AWS, GCP, Azure 같은 클라우드
- 개인 VPS 서버
- 회사 On-Premise 서버
- 이미지 실행 (컨테이너 실행)
- 도메인 연결 + 보안 인증서 적용
- 실시간 모니터링 및 유지보수
예시: AWS EC2에 배포
- AWS EC2 인스턴스 생성
- 도커 설치 (sudo yum install docker 등)
- 너의 이미지 빌드 또는 Docker Hub에서 pull
- 컨테이너 실행
- AWS 보안 그룹 설정 (포트 오픈)
- 도메인 연결 (Route53, SSL 적용)
Docker = “개발자들이 어디서든 동일하게 실행할 수 있게 해주는 표준 환경 도구”
클라우드 = “그 실행 환경을 24시간 돌아가게 해주는 운영 인프라”
개발 중엔 Docker로 통일된 환경에서 개발하고
운영은 AWS 같은 곳에 올려서 서비스하는 흐름으로 생각하면 돼.
왜 AWS에 도커를 설치하는가?
내가 로컬에서 도커로 만든 이미지는 도커가 설치된 환경에서만 동작함
그래서 운영 서버(AWS EC2)에도 도커를 설치해야 운영 서버에서 도커가 실행되는 것
도커 기반으로 직접 수동 배포할 수 있고, CI/CD 기반 자동 배포를 할 수도 있다.
도커 사용 이유
-
팀원 간 개발 환경 표준화
Node.js, Java, Python 등 개발 언어/라이브러리/OS 환경이 팀원마다 다른 문제를 해결하기 위해
도커 이미지로 실행 환경을 표준화함 -
운영 배포 절차 간소화 및 일관성 확보
로컬 개발 환경 = 운영 서버 환경을 동일한 도커 이미지로 맞춤으로써
배포 시 환경 차이로 인한 문제를 최소화함 -
CI/CD를 통한 배포 자동화
GitHub Actions 같은 CI/CD 툴을 이용해 코드 변경 시
자동으로 도커 이미지 빌드 → 배포 → 실행까지 사람 개입 없이 일괄 처리할 수 있음 -
마이크로서비스, 멀티 서비스 운영 최적화 (추가 추천 포인트)
프론트, 백엔드, DB, 캐시, 메시지 브로커 등 여러 서비스를 컨테이너 단위로 분리해
독립적이면서도 유기적으로 운영할 수 있음
Docker Compose 또는 Kubernetes로 서비스 간 연결 및 일괄 운영 관리가 쉬워짐