Docker 개요와 이미지, 컨테이너의 이해
1. 도커의 핵심 개념
- 애플리케이션을 컨테이너라는 표준화된 유닛으로 패키징하여, 개발·배포·실행을 자동화하는 오픈소스 플랫폼
- 환경의 차이로 발생하는 ‘내 컴퓨터에서는 되는데 서버에서는 안 돼’라는 고질적인 문제를 해결하는 핵심 기술
- 현대 소프트웨어 엔지니어링에서 인프라를 코드로 관리(IaC)하고, 빠르고 안정적인 배포 체계를 구축하기 위한 필수적인 도구로 자리 잡고 있음
1.1 도커 이미지 (Docker Image)
- 애플리케이션을 실행하기 위해 필요한 모든 파일 시스템과 실행 설정값을 포함하고 있는 읽기 전용(Read-only) 파일
- 컨테이너를 생성하기 위한 읽기 전용 설계도(템플릿)
- 주요 특징
- Immutable (불변성)
- 이미지는 한 번 생성되면 절대로 변하지 않음 🡲 이를 통해 일관된 배포 환경을 보장함
- 설정이 바뀌어야 한다면 새로운 이미지를 빌드해야 함
- Layered Architecture (레이어 구조)
- 도커 이미지는 여러 개의 ‘레이어’가 쌓인 형태
- 예시
Ubuntu 레이어+Python 설치 레이어+소스 코드 레이어가 합쳐져 하나의 이미지가 됨
- 예시
- 이점
- 동일한 레이어를 사용하는 다른 이미지가 있다면
🡲 해당 레이어를 재사용하여 저장 공간과 빌드 시간을 획기적으로 줄일 수 있음 (Copy-on-Write 방식) - 변경된 부분만 업데이트하므로 효율적
- 동일한 레이어를 사용하는 다른 이미지가 있다면
- 도커 이미지는 여러 개의 ‘레이어’가 쌓인 형태
- Snapshot
- 특정 시점의 애플리케이션 상태를 그대로 보존한 스냅샷과 같음
- Immutable (불변성)
1.2 도커 컨테이너 (Docker Container)
- 애플리케이션 코드와 이를 실행하는 데 필요한 모든 종속성(라이브러리, 설정 파일 등)을 하나로 묶은 소프트웨어 유닛
- 컨테이너는 이미지를 실행한 상태
이미지가 정적인 파일이라면, 컨테이너는 메모리 위에서 동작하는 동적인 프로세스
- 특징
- 격리성
- 컨테이너의 핵심 기술 (격리: Isolation)
- 각 컨테이너는 서로 독립된 공간에서 동작함
- 컨테이너는 호스트 OS의 커널(자원)을 공유하면서도, 논리적으로는 분리되어 있어 마치 독립된 서버처럼 보임
- 격리하는 이유
- Namespaces
- 각 컨테이너가 독립된 네트워크, 파일 시스템, 프로세스 ID(PID) 등을 갖게 하여 서로를 볼 수 없게 만듦
- Cgroups (Control Groups) 각 컨테이너가 사용할 수 있는 CPU, 메모리 자원량을 제한하여 특정 컨테이너가 전체 시스템을 점유하지 못하게 차단
- Namespaces
- 이식성
- 도커만 설치되어 있다면 어떤 OS, 어떤 클라우드 환경에서도 동일하게 작동함
- 격리성
- 이미지와 컨테이너의 관계 (Writable Layer)
- 컨테이너가 실행될 때, 도커 엔진은 읽기 전용인 이미지 레이어들 위에 ‘컨테이너 레이어(Writable Layer)’를 한 층 얹음
- 컨테이너 안에서 파일을 생성하거나 수정하는 모든 작업은 이 최상단의 컨테이너 레이어에만 기록됨
- 컨테이너를 삭제하면 이 레이어도 함께 사라지며, 원본 이미지(하단 레이어들)는 전혀 손상되지 않음
- 컨테이너가 실행될 때, 도커 엔진은 읽기 전용인 이미지 레이어들 위에 ‘컨테이너 레이어(Writable Layer)’를 한 층 얹음
1.3 이미지 vs 컨테이너 상세 비교
| 항목 | 도커 이미지 (Image) | 도커 컨테이너 (Container) |
|---|---|---|
| 상태 | 정적 (파일 시스템 저장) | 동적 (메모리 실행 중) |
| 수정 가능 여부 | 읽기 전용 (Read-only) | 읽기/쓰기 가능 (Read/Write) |
| 비유 | 프로그램 설치 파일 (exe), 붕어빵 틀 | 실행된 프로세스, 붕어빵 |
| 생명 주기 | 삭제하기 전까지 보존 | 정지(Stop) 및 삭제(Rm) 가능 |
| 구성 | 여러 개의 읽기 전용 레이어 | 이미지 레이어 + 1개의 쓰기 가능 레이어 |
1.4 실무적 관점에서의 이해
- 왜 이미지를 레이어로 나눌까?
- 만약 1GB짜리 이미지를 수정해서 배포한다면,
- 레이어 구조가 아닐 경우 매번 1GB를 전송해야 함
- 레이어 구조에서는 수정된 레이어(예: 10MB 내외의 소스 코드 레이어)만 전송하면 됨
🡲 배포 속도가 비약적으로 빨라짐
- 만약 1GB짜리 이미지를 수정해서 배포한다면,
- 컨테이너는 ‘휘발성’이다
- 컨테이너 레이어에 쓰인 데이터는 컨테이너 삭제 시 사라짐
🡲 데이터베이스의 데이터처럼 영구적으로 보존해야 하는 값은 컨테이너 내부가 아닌 도커 볼륨(Docker Volume)이라는 외부 저장소에 연결(Mount)해서 관리해야 함
- 컨테이너 레이어에 쓰인 데이터는 컨테이너 삭제 시 사라짐
- 요약
- Dockerfile이라는 레시피를 통해 이미지를 작성
- 이미지는 애플리케이션 실행에 필요한 모든 것이 담긴 압축 파일이며, 결코 변하지 않음
- 이 이미지를 실행(run)하면 컨테이너가 됨
- 컨테이너는 이미지 위에 얇은 ‘쓰기 가능 층’을 얹어 독립된 환경에서 돌아가는 실제 서비스임
2. 도커의 주요 아키텍처
도커는 클라이언트-서버 구조를 따름
- Docker Client
- 사용자가
docker run같은 명령어를 입력하는 인터페이스
- 사용자가
- Docker Host (Daemon)
- 실제로 컨테이너를 관리하고 실행하는 서버 프로세스(
dockerd) - 클라이언트의 요청을 받아 이미지 빌드나 컨테이너 실행을 수행함
- 실제로 컨테이너를 관리하고 실행하는 서버 프로세스(
- Docker Registry
- 도커 이미지를 저장하고 공유하는 장소
- ‘Docker Hub’가 대표적
3. 컨테이너 vs 가상 머신(VM)
- 도커가 기존 가상화 기술보다 가볍고 빠른 이유는 커널 공유 방식에 있음
| 구분 | 가상 머신 (VM) | 도커 컨테이너 |
|---|---|---|
| 구조 | 하이퍼바이저 위에 각자 Guest OS 탑재 | 호스트 OS 위에서 도커 엔진을 통해 커널 공유 |
| 성능 | OS 부팅이 필요해 무겁고 느림 | 프로세스 실행 수준으로 매우 가볍고 빠름 |
| 용량 | 기가바이트(GB) 단위 | 메가바이트(MB) 단위 |
4. 도커를 사용하는 이유 (장점)
- 환경 일관성
- 개발, 테스트, 운영 환경을 100% 동일하게 유지 🡲 배포 사고 감소
- 자원 효율성
- 하드웨어 가상화 없이 OS 커널 공유 🡲 시스템 리소스를 적게 소모
- 마이크로서비스(MSA) 최적화
- 서비스를 작은 단위로 쪼개어 배포하고 확장하기에 용이
- CI/CD 가속화
- 코드 빌드부터 배포까지의 과정을 컨테이너 기반으로 표준화 🡲 자동화 효율 극대화
5. 도커 워크플로우 (Lifecycle)
- 일반적으로 다음과 같은 Build 🡲 Ship 🡲 Run 과정을 거침
- Build
Dockerfile작성docker build명령으로 이미지 생성
- Ship
- 생성된 이미지를
docker push를 통해 레지스트리(Docker Hub 등)에 업로드
- 생성된 이미지를
- Run
- 서버에서
docker pull로 이미지를 다운로드 docker run으로 컨테이너 실행
- 서버에서
- 도커의 핵심인 이미지(Image)와 컨테이너(Container)
- 흔히 붕어빵 틀과 붕어빵, 혹은 설계도와 실제 건물에 비유(객체지향 언어에서의 클래스와 인스턴스의 관계와 비슷)
- 기술적인 깊이에서 이해하려면 레이어(Layer) 구조와 프로세스 격리라는 개념을 명확히 파악해야 함
6. 도커 사용하기
도커의 핵심은 “어디서나 동일하게 실행되는 소프트웨어 패키징”
핵심 명령어
- docker pull [이미지명]: 레지스트리에서 이미지 다운로드
- docker run [옵션] [이미지명]: 이미지를 기반으로 컨테이너 생성 및 실행
- docker ps: 실행 중인 컨테이너 목록 확인
- docker exec -it [컨테이너ID] /bin/bash: 실행 중인 컨테이너 내부에 접속