Docker 가상화를 통한 이미지와 컨테이너 이해하기
Fundamentals of Docker Images and Containers
Docker는 애플리케이션과 그의 종속성을 패키징하여 표준화된 환경에서 독립적으로 실행할 수 있게 해주는 컨테이너 기술을 제공하는 플랫폼입니다. 이를 통해 개발, 테스트, 운영 등 다양한 단계에서 동일한 작업 결과를 보장함으로써, 환경 차이로 인한 문제를 방지합니다. 또한, Docker를 이용하면 애플리케이션의 배포와 스케일링 과정을 단순화시키고, 일련의 작업 흐름을 자동화함으로써 개발자의 생산성을 크게 향상시킵니다. 이러한 이유로 Docker는 소프트웨어 개발 및 배포 환경에서 점점 더 중요한 위치를 차지하고 있습니다.
Prerequisites
이번 포스팅은 macOS에 Docker가 설치되어 있는 환경을 기반으로 하고 있으며, 간단하게 구성된 Spring Boot 프로젝트를 바탕으로 Docker 이미지 및 컨테이너 구성 과정에 대해 살펴볼 예정입니다.
Hypervisor vs. Containers
가상화는 하나의 물리적 하드웨어 시스템 위에 가상 환경을 생성하여 여러 작업 수행을 가능하게 하는 컴퓨팅 기술입니다. 이로 인해 시스템 자원의 효율적인 분배 및 활용이 가능하며, 이를 통해 시스템의 격리성을 보장하고, 안정적인 환경을 제공하게 됩니다.
가상화 기술은 크게 Hypervisor 기반의 가상머신과 Container 기반의 가상화로 나뉩니다.
- Hypervisor(하이퍼바이저): 가상 머신의 경우, Hypervisor라는 소프트웨어를 사용하여 하드웨어를 가상화하며, 각 가상 머신은 자체 운영 체제와 필요한 자원을 포함하는 독립적인 환경을 제공합니다 . 이 덕분에 각 가상 머신은 완전히 독립적으로 작동할 수 있으며, 다른 가상 머신에 영향을 미치지 않습니다. 하지만 이런 구조는 많은 시스템 자원을 차지하게 되며, 부팅 시간 또한 더 걸립니다.
- Containers(컨테이너): OS 수준에서 가상화를 제공하는 방식으로, 하나의 Host 시스템에서 여러 컨테이너를 운영하게 합니다. 각 컨테이너에는 애플리케이션이 필요로 하는 라이브러리 및 종속성이 포함되어 있으며, 각각 독립적으로 작동하지만 Host OS 커널을 공유합니다. 이 방식은 각각의 컨테이너를 경량화하며, 빠른 시작과 정지를 가능하게 합니다.
Docker는 현재 가장 인기 있는 Container 플랫폼 중 하나입니다. Docker가 이렇게 인기를 누리게 된 이유는 Hypervisor와 비교했을 때 컨테이너의 다양한 장점 때문입니다.
- Docker 이미지는 가볍고 유연하여, 빠른 구동 시간 및 이미지 배포시 빠른 반응성을 보장합니다.
- Docker는 애플리케이션과 그에 필요한 모든 종속성을 패키지화하여 개발부터 배포, 그리고 확장에 이르기까지의 프로세스를 단순화합니다.
- 개발, 테스트, 프로덕션 환경에서의 일관성을 보장합니다.
- 컨테이너는 CPU, 메모리, I/O 등의 자원 사용을 격리시켜 작업의 안정성을 제공하고, 운영 체제를 안전하게 보호합니다.
- Docker Hub 중앙 저장소를 이용하면, 손쉽게 다양한 이미지를 찾아 사용하거나 공유할 수 있습니다.
- Docker는 macOS, Linux, Windows 등 다양한 운영 체제를 지원합니다.
Docker Image
Docker 이미지는 소프트웨어를 실행하는 데 필요한 모든 것을 포함하는 가볍고 독립적이며, 실행 가능한 소프트웨어 패키지입니다. 이는 코드, 런타임, 라이브러리, 환경 변수, 설정 파일 등을 포함합니다.
Docker 이미지는 여러 개의 레이어(Layer)로 구성되며, 독립적인 파일 시스템과 컨테이너 간의 변경사항을 저장하는 역할을 합니다. 이 레이어 형식이 Docker 이미지의 크기를 작게 하고, 빌드와 배포를 빠르게 진행하면서, 코드의 재사용을 촉진하는 등 많은 이점을 제공합니다.
Docker 이미지는 건축의 설계도라고 볼 수 있습니다. 설계도는 방의 개수, 위치, 사용할 재료와 같은 건물의 핵심 구조를 결정합니다. 설계도는 일반적으로 건물을 건축하는데 있어 필요한 모든 정보를 제공합니다, 건축가는 이를 토대로 동일한 구조의 건물을 여러 개 만들어낼 수 있습니다. 여기서 중요한 것은 이런 건물이라도 내부 인테리어, 예를 들어 자재 선택, 조명, 가구 배치 등을 다르게 함으로써 각 건물을 독특하게 꾸밀 수 있다는 점입니다.
이와 유사하게 Docker 이미지는 컨테이너를 실행하는데 필요한 모든 정보, 즉 운영 체제, 라이브러리, 애플리케이션 코드 등을 담고 있습니다. 이 이미지를 기반으로 동일한 환경을 가진 컨테이너를 여러 개 생성할 수 있습니다. 하지만, 마치 건물 인테리어를 커스터마이징하는 것처럼, 컨테이너도 실행시 추가적인 설정을 통해 각 컨테이너가 다르게 동작하도록 만들 수 있습니다. 예를 들어, 환경 변수를 변경하거나 파일을 다르게 마운트하는 등의 작업을 통해, 동일한 이미지라도 각각 다른 동작을 하는 컨테이너를 만들 수 있습니다.
즉, Docker를 사용하면 설계도인 이미지에 기반을 두되, 그 위에 사용자의 요구사항에 부합하는 다양한 컨테이너를 쉽게 만들어낼 수 있게 됩니다. 이를 통해 Docker는 소프트웨어 개발에 있어 큰 유연성과 효율성을 제공하게 됩니다.
Docker Image Commands
Docker는 이미지의 생성, 배포 및 관리를 위한 다양한 명령어를 제공합니다. 이를 통해 이미지를 만드는 것부터 저장소에 푸시하고, 로컬에서 조회하거나 삭제하는 작업까지 편리하게 수행할 수 있습니다.
Docker Image Naming Convention
Docker 이미지는 일반적으로 username/repository:tag 형태로 네이밍합니다. 여기서 username은 Docker 계정명, repository는 이미지 명, 그리고 tag는 해당 이미지의 버전을 나타냅니다. 예를 들어, catsriding/ongs:1.0.0은 catsriding 이라는 Docker 계정의 ongs라는 이미지 리포지토리의 1.0.0 버전을 나타냅니다. 그러나 공식 이미지의 경우 계정명 없이 사용될 수 있습니다. 이는 Docker Hub의 기본 라이브러리 계정인 library가 생략된 것으로, 공식 이미지들은 이 library 계정 아래에 속하게 됩니다. 따라서 ubuntu라는 공식 이미지는 사실 library/ubuntu를 의미하며,docker pull ubuntu
같은 명령어를 실행할 때는 이 library 계정명이 생략될 수 있습니다. 이런 명령어를 실행하면, Docker는 Docker Hub에서 공식 ubuntu 이미지를 찾아 자동으로 내려받게 됩니다.
docker images
docker images
명령어는 로컬에 저장되어 있는 Docker 이미지의 목록을 조회합니다. 이미지 ID, 리포지토리 이름, 태그, 생성 시간, 크기 등의 정보를 확인할 수 있습니다.
$ docker images [OPTIONS] [REPOSITORY[:TAG]]
- OPTIONS:
-a, --all
: 중간 이미지도 모두 출력합니다.-q, --quiet
: 이미지 ID만 출력합니다.-f, --filter
: 특정 조건으로 출력을 필터링합니다.
다음과 같이 사용해 볼 수 있습니다:
# 모든 이미지의 목록 출력
$ docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
catsriding/hello-docker 1.0.0 7071df080f72 46 seconds ago 832MB
catsriding/ongs 3.0.0 7de500000301 23 hours ago 476MB
catsriding/mongs 2.0.0 6a2d35bc038a 5 days ago 476MB
# 특정 이미지 검색
$ docker images catsriding/hello-docker
REPOSITORY TAG IMAGE ID CREATED SIZE
catsriding/hello-docker 1.0.0 7071df080f72 15 minutes ago 832MB
docker build
docker build
명령어는 Dockerfile
의 지시문을 순서대로 실행하여 Docker 이미지를 생성합니다.
$ docker build [OPTIONS] PATH | URL | -
- OPTIONS:
-t, --tag
: 이름과 태그를 붙여 생성하는 이미지를 지정합니다. 형식은name:tag
입니다.--platform
: 빌드할 떄 이미지 타겟 플랫폼을 지정합니다. 예를 들어,linux/amd64
또는linux/arm64
등을 지정할 수 있습니다.-f
,--file
:Dockerfile
의 이름이 'Dockerfile'이 아닌 경우, 또는 다른 경로에 있는Dockerfile
을 지정할 때 사용합니다.--add-host
: 호스트에서 IP 주소로 매핑을 추가합니다.--allow
: 추가 특권 활성화를 허용합니다.--annotation
: 이미지에 어노테이션 추가를 허용합니다.--attest
:type=sbom
,generator=image
형식의 증명 매개 변수가 가능합니다.--build-arg
: 빌드 시간에 변수를 설정합니다.--build-context
: 추가 빌드 컨텍스트를 제공합니다.--builder
: 구성된 빌더 인스턴스를 재정의합니다. 기본적으로desktop-linux
를 사용합니다.--cache-from
: 외부 캐시 소스를 지정합니다.--cache-to
: 캐시 내보내기 목적지를 지정합니다.--cgroup-parent
: 빌드 중run
명령에 대한 부모cgroup
을 설정합니다.--iidfile
: 이미지 ID를 해당 파일에 작성합니다.--label
: 이미지에 메타데이터를 설정합니다.--load
:--output=type=docker
에 대한 단축표현입니다.--metadata-file
: 빌드 결과 메타데이터를 해당 파일에 작성합니다.--network
: 빌드 중run
명령에 대한 네트워킹 모드를 설정합니다. 기본적으로default
값을 사용합니다.--no-cache
: 이미지를 빌드할 때 캐시를 사용하지 않습니다.--no-cache-filter
: 캐시하지 않을 특정 단계를 지정합니다.-o
,--output
: 출력 경로를 지정합니다.--progress
: 진행상황 출력 형태를 설정합니다.auto
,plain
,tty
중 선택이 가능합니다.plain
은 컨테이너 출력을 표시합니다.--pull
: 참조된 모든 이미지를 반드시 가져옵니다.--push
:--output=type=registry
에 대한 단축표현입니다.-q
,--quiet
: 빌드 출력을 억제하고 성공 시 이미지 ID를 출력합니다.--sbom
:--attest=type=sbom
의 단축 표현입니다.--secret
: 빌드에 공개할 비밀 정보입니다.--shm-size
: 빌드 컨테이너의 공유 메모리 크기를 설정합니다.--ssh
: 빌드에 공개할 SSH 에이전트 소켓 또는 키를 설정합니다.--target
: 빌드할 대상 빌드 단계를 설정합니다.--ulimit
: Ulimit 옵션을 설정합니다.
다음과 같은 방법으로 사용할 수 있습니다:
# Dockerfile 위치에 이미지 빌드하기
$ docker build -t catsriding/waves:1.0.0 .
# Dockerfile 이름을 지정하여 이미지 빌드하기
$ docker build -t catsriding/waves:1.0.0 -f Dockerfile.dev .
# Dockerfile 경로를 지정하여 이미지 빌드하기
$ docker build -t catsriding/waves:1.0.0 -f ./app/Dockerfile.prod .
# 특정 플랫폼을 명시하여 Docker image 빌드하기
$ docker build --platform linux/amd64 -t catsriding/waves:1.0.0 .
docker buildx
docker buildx
명령어는 Docker 19.03 버전부터 실험적으로 도입된 기능으로, Docker 이미지를 여러 플랫폼에 대해 동시에 빌드하고 푸시하는 작업을 지원합니다.
$ docker buildx build [OPTIONS] PATH | URL | -
- OPTIONS:
-t, --tag
: 이름과 태그를 붙여 생성하는 이미지를 지정합니다. 형식은name:tag
입니다.-f, --file
:Dockerfile
의 이름이 'Dockerfile'이 아닌 경우, 또는 다른 경로에 있는Dockerfile
을 지정할 때 사용합니다.--platform
: 빌드할 이미지의 플랫폼을 지정합니다. 지원하려는 모든 플랫폼을 쉼표로 구분하여 명시합니다. 예를 들어linux/amd64
,linux/arm64
등이 있을 수 있습니다.--push
: 이미지를 빌드하는 동시에 Docker Hub 또는 지정된 저장소에 푸시합니다.
다음과 같이 사용할 수 있습니다:
# Dockerfile 위치에 여러 플랫폼에 대해 이미지 빌드하고 푸시하기
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t waves:1.0.0 --push .
# Dockerfile 이름을 지정하여 여러 플랫폼에 대해 이미지 빌드하고 푸시하기
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t waves:1.0.0 -f Dockerfile.dev --push .
# Dockerfile 경로를 지정하여 여러 플랫폼에 대해 이미지 빌드하고 푸시하기
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t waves:1.0.0 -f ./app/Dockerfile.prod --push .
docker pull
docker pull
명령어는 Docker Hub 등의 원격 리포지토리에서 특정 Docker 이미지를 내려받습니다. :tag
를 이용해 이미지의 특정 버전을 지정할 수 있습니다.
$ docker pull [OPTIONS] NAME[:TAG|@DIGEST]
- OPTIONS:
-a, --all-tags
: 저장소의 모든 태그가 붙은 이미지를 다운로드합니다.--disable-content-trust
: 이미지의 검증 절차를 생략합니다.--platform
: 서버가 다중 플랫폼을 지원하는 경우, 특정 플랫폼을 설정합니다.-q, --quiet
: 상세한 출력 메시지를 보여주지 않습니다.
- @DIGEST:
- 다이제스트는 Docker에 의해 이미지 콘텐츠의 유일한 해시를 생성하는데 사용됩니다. 이를 통해 특정 버전의 Docker 이미지를 완벽하게 식별하게 됩니다.
- 다이제스트는 일반적으로
sha256:
로 시작하는 긴 해시 값으로 표현되며, 각 Docker 이미지는 고유한 다이제스트를 가집니다. 이는 이미지의 내용이 변경될 때마다 다이제스트도 함께 변경되므로, 다이제스트를 사용하면 이미지의 특정 스냅샷을 정확히 참조할 수 있습니다. - 예를 들어, docker pull 명령어에 다이제스트를 사용하면, Docker는 특정 해시를 가진 이미지를 Docker 저장소로부터 정확히 가져옵니다. 이렇게 하면 이미지의 다른 버전이 아닌, 특정 버전의 이미지만을 가져오는 것이 보장됩니다.
실제 사용 예는 아래와 같습니다:
# Ubuntu 18.04 이미지 다운로드하기
$ docker pull ubuntu:18.04
# 다이제스트를 사용한 이미지 다운로드하기
$ docker pull ubuntu@sha256:b385fccb12e2a220b01f413bb3aad810c4137b861c2122b93683c8ff8f12f4bb
docker push
docker push
명령어는 로컬에서 생성한 Docker 이미지를 Docker Hub에 업로드합니다. 먼저 Docker에 로그인한 후, 해당 이미지를 푸시할 수 있습니다. :tag
를 이용해 이미지의 특정 버전을 지정할 수 있습니다.
- OPTIONS:
--disable-content-trust
: 콘텐츠 신뢰성 검증을 비활성화합니다.-q
,--quiet
: 상세한 출력 메시지를 생략합니다.
로그인 및 이미지 푸시 예시는 아래와 같습니다:
# Docker Hub에 로그인하기
$ docker login
# 로컬에서 생성한 이미지 Docker Hub에 업로드하기
$ docker push catsriding/waves-server:1.0.0
docker push
를 사용하여 업로드한 이미지는 해당 명령을 실행한 동일한 Docker Hub 계정으로 로그인하면 확인할 수 있습니다.
docker rmi
docker rmi
명령어는 로컬에 저장된 특정 Docker 이미지를 삭제합니다.
$ docker rmi [OPTIONS] IMAGE [IMAGE...]
- OPTIONS:
-f, --force
: 강제로 이미지를 삭제합니다. 삭제하려는 이미지가 하나 이상의 컨테이너에서 사용되고 있어도 삭제할 수 있습니다.
다음과 같이 사용할 수 있습니다:
# Ubuntu 18.04 이미지 삭제
$ docker rmi ubuntu:18.04
# 모든 이미지 삭제
$ docker rmi -f $(docker images)
docker image inspect
docker image inspect
명령어는 특정 Docker 이미지에 대한 상세 정보를 출력합니다.
$ docker image inspect [OPTIONS] IMAGE [IMAGE...]
- OPTIONS:
-f
,--format
: 출력 형식을 지정하는 Go 템플릿 문자열입니다.
다음과 같이 사용할 수 있습니다:
# Ubuntu 18.04 이미지의 정보 출력
$ docker image inspect ubuntu:18.04
이 명령어를 실행하면, 이미지 ID, 생성 시간, 크기, 컨테이너에서 실행될 명령어, 작성자, 라벨, 환경 변수 등에 대한 JSON 형식의 상세 정보가 출력됩니다. 따라서 Docker 이미지의 세부 정보를 확인하거나 디버깅 등의 작업에 도움이 됩니다.
Docker Image Build
Docker 이미지를 만들기 위해서는 현재 실행중인 컨테이너의 상태를 이미지로 저장하는 방법이 있기는 하지만, 일반적으로 Dockerfile
이라는 특수한 파일을 활용합니다. Dockerfile
은 이미지를 생성하기 위한 명령어들이 순차적으로 기록된 명세서입니다.
Dockerfile
은 일반적으로 프로젝트의 루트 디렉토리에 위치시킵니다. 프로젝트의 모든 구성요소에 쉽게 접근할 수 있게 하기 위함입니다. 물론, 이것은 공식적인 규칙이 아니라 일반적인 표준이며 필요에 따라 Dockerfile
을 다른 위치에 배치할 수도 있습니다.
Dockerfile
은 다음과 같은 형식으로 구성되어 있습니다:
# Comment
INSTRUCTION arguments
지시어(INSTRUCTION)는 대소문자를 구분하지 않습니다. 그러나 명령어를 인식하기 쉽게 하기 위한 관례로 대문자를 사용합니다. 인자(arguments)는 이 명령어에 전달될 값들을 의미합니다.
Dockerfile
에서 사용되는 주요 지시어는 다음과 같습니다:
Instruction | Description |
---|---|
ADD | Add local or remote files and directories. |
ARG | Use build-time variables. |
CMD | Specify default commands. |
COPY | Copy files and directories. |
ENTRYPOINT | Specify default executable. |
ENV | Set environment variables. |
EXPOSE | Describe which ports your application is listening on. |
FROM | Create a new build stage from a base image. |
HEALTHCHECK | Check a container's health on startup. |
LABEL | Add metadata to an image. |
MAINTAINER | Specify the author of an image. |
ONBUILD | Specify instructions for when the image is used in a build. |
RUN | Execute build commands. |
SHELL | Set the default shell of an image. |
STOPSIGNAL | Specify the system call signal for exiting a container. |
USER | Set user and group ID. |
VOLUME | Create volume mounts. |
WORKDIR | Change working directory. |
이 지시어들을 활용하여 Dockerfile
을 작성하면 다음과 같은 형식이 될 것입니다:
# 베이스 이미지 지정: Docker 이미지 생성의 기반이 되는 부모 이미지를 설정합니다.
FROM 이미지명
# 레이블 설정: Docker 이미지에 메타데이터를 추가합니다. 키-값 쌍으로 이루어져 있습니다.
LABEL 키=값
# 작업 디렉토리 설정: Dockerfile의 모든 이후 명령어들이 실행될 작업 디렉토리를 설정합니다.
WORKDIR 폴더명
# 파일 복사: 로컬 시스템의 파일 또는 디렉토리를 Docker 이미지에 복사합니다. 이미지 내부의 워킹 디렉터리로 설정 됩니다.
COPY 파일경로 복사할경로
# 사용자 설정: 이후의 RUN, CMD, ENTRYPOINT 명령어가 실행 될 유닉스 사용자를 설정합니다. 보안상의 목적으로 사용됩니다.
USER 유저명
# 포트 노출: Docker 컨테이너가 실행될 때 노출 할 네트워크 포트 번호를 설정합니다.
EXPOSE 포트번호
# 빌드 시 변수 설정: Docker 이미지 빌드 중에 사용할 변수를 설정합니다. Dockerfile 내부 어디에서나 사용 가능합니다.
ARG 변수명=변수값
# 환경변수 설정: 컨테이너가 실행될 때 사용할 환경변수를 설정합니다.
ENV 변수명=변수값
# 컨테이너 실행 시점의 명령어 또는 파라미터 설정: 컨테이너가 시작될 때 실행될 명령어 또는 파라미터를 설정합니다. 여기서 정의된 명령어는 후에 정의되는 CMD의 파라미터로 사용될 수 있습니다.
ENTRYPOINT ["명령어"]
# 컨테이너 실행 시점의 기본 명령어 설정: 컨테이너가 시작될 때 실행될 기본 명령어를 설정합니다. 여기서 설정된 값은 docker run 명령어 또는 ENTRYPOINT에서 정의한 파라미터를 재정의하는데 사용됩니다.
CMD ["명령어"]
이렇게 작성된 Dockerfile
을 사용하면, 빌드 과정에서 Docker 클라이언트는 이 Dockerfile
의 명령어들을 순서대로 실행하여 Docker 이미지를 생성합니다. 이 이미지는 나중에 Docker 컨테이너를 실행하는 데 사용됩니다.
지시어에 대한 추가적인 정보는 Dockerfile reference에서 확인할 수 있습니다.
Single Stage Build
이제 이러한 지시어들을 통해 실제 Dockerfile
을 작성하는 방법에 대해 살펴보겠습니다. 먼저, Spring Boot 프로젝트의 루트 디렉토리에 Dockerfile
을 추가합니다. 추가된 파일의 구조는 아래와 같습니다:
.
├── .git
├── .gitignore
├── .gradle
├── .idea
├── build.gradle
├── Dockerfile
├── gradle
├── gradlew
├── gradlew.bat
├── HELP.md
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── app
│ │ └── catsriding
│ │ └── docker
│ │ ├── api
│ │ │ └── DockerController.java
│ │ └── Application.java
│ └── resources
│ └── application.yml
└── test
루트 디렉토리에 Dockerfile
을 추가했다면, 아래와 같이 작성합니다:
FROM gradle:8.6.0-jdk17
LABEL version="1.0.0" description="Hello, Docker!" vendor="catsriding" maintainer="Jynn"
WORKDIR /app
COPY . .
RUN gradle clean build --no-daemon
RUN cp build/libs/*[^.plain].jar application.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "application.jar"]
CMD ["--spring.profiles.active=test"]
위 Dockerfile
을 간략히 살펴보면, gradle:7.2-jdk17
컨테이너를 베이스로 생성하고 모든 소스파일을 워킹 디렉토리 /app
에 복사합니다. 이후에, Gradle을 이용하여 Spring Boot 프로젝트를 빌드하고 생성된 JAR 파일을 application.jar
로 복사합니다. 마지막으로, 실행할 명령어를 정의하고 8080 포트를 노출합니다.
작성된 Dockerfile
로 Docker 이미지를 빌드하려면, 터미널을 Dockerfile
이 위치한 디렉토리로 이동한 후 아래 명령어를 실행하면 됩니다.
$ docker build -t catsriding/hello-docker:1.0.0 .
[+] Building 19.6s (10/10) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 328B 0.0s
=> [internal] load metadata for docker.io/library/gradle:8.6.0-jdk17 0.8s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/5] FROM docker.io/library/gradle:8.6.0-jdk17@sha256:27ed98487dd9c155d555955084dfd33f32d9f7ac5a90a79b1323ab002a1a8b6e 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 8.92kB 0.0s
=> CACHED [2/5] WORKDIR /app 0.0s
=> CACHED [3/5] COPY . . 0.0s
=> [4/5] RUN gradle clean build --no-daemon 18.4s
=> [5/5] RUN cp build/libs/*[^.plain].jar application.jar 0.2s
=> exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:7071df080f72b5dfa3b7fa8c54b4322c8081e547d906cb0c4c5cc28c4ada3f34 0.0s
=> => naming to docker.io/catsriding/hello-docker:1.0.0 0.0s
View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/y2n6ecq79kacv802s6nc7u21v
What's Next?
View a summary of image vulnerabilities and recommendations → docker scout quickview
정상적으로 Docker 이미지가 빌드되면, docker images
명령어를 통해 생성된 이미지를 확인할 수 있습니다.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
catsriding/hello-docker 1.0.0 7071df080f72 46 seconds ago 832MB
이렇게 생성된 이미지를 이용해서 Docker 컨테이너를 실행하면 됩니다. 이것이 Dockerfile
을 작성하고 Docker 이미지를 빌드하고 실행하는 과정입니다.
Multi Stage Builds
일반적으로 Docker 이미지를 구성할 때는 Dockerfile
을 사용하여 단일 스테이지 빌드(Single Stage Build)를 진행합니다. 이는 FROM
지시어를 한 번만 사용하여 모든 빌드 과정을 한 이미지에서 완료합니다. 이 방식은 가장 간단한 방법이지만, 이미지가 복잡해질수록 재사용성이 줄어들고, 불필요한 빌드 도구 및 라이브러리가 이미지에 포함되게 되어 크기를 증가시키는 단점이 있습니다.
대안으로 Docker는 멀티 스테이지 빌드(Multi Stage Builds) 기능을 제공합니다. 이 경우 Dockerfile
에서 FROM
지시어를 여러 번 사용하여 빌드 프로세스를 여러 단계로 나눕니다. 이렇게 하면 각 스테이지에서 생성된 결과물만을 다음 스테이지로 전달하므로 이미지 크기를 줄일 수 있습니다. 이를 통해 배포를 위한 Docker 이미지에는 애플리케이션을 실행하는 데 필요한 최소한의 파일만 포함됩니다.
예를 들어, 위 Java 애플리케이션의 멀티 스테이지 Dockerfile
은 다음과 같이 작성할 수 있습니다:
# Build stage
FROM gradle:8.6.0-jdk17 as build
WORKDIR /app
COPY . .
RUN gradle clean build --no-daemon
# Package stage
FROM openjdk:17
LABEL version="1.0.0" description="Hello, Docker!" vendor="catsriding" maintainer="Jynn"
WORKDIR /app
COPY /app/build/libs/*.jar /app/application.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "application.jar"]
CMD ["--spring.profiles.active=test"]
이처럼 이 빌드 스테이지와 패키지 스테이지를 명확하게 분리함으로써, 최종 이미지에는 애플리케이션 실행에 필요한 요소만 포함되고, 불필요한 툴이나 라이브러리 등은 제외시킬 수 있습니다.
태그를 변경하여 새로운 Docker 이미지를 빌드 한 후에 이미지의 크기를 비교해 보겠습니다:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
catsriding/hello-docker 2.0.0 f19f9785c066 6 seconds ago 521MB # Multi-stage builds
catsriding/hello-docker 1.0.0 7071df080f72 3 hours ago 832MB # Single-stage build
단일 스테이지 빌드에서 생성된 이미지와 비교할 때, 불필요한 Gradle 빌드 도구는 제거되고, 애플리케이션 실행에만 필요한 핵심 환경만이 구성되었습니다. 결과적으로, 이미지 크기가 상당히 줄어든 것을 확인할 수 있습니다. 이처럼 멀티 스테이지 빌드 방법은 효율적인 이미지 구축을 위해 반드시 고려해야 할 사항입니다.
Docker Container
Docker 컨테이너는 Docker 이미지를 실행한 상태를 말합니다. Docker 이미지는 코드, 런타임, 라이브러리, 환경 변수, 설정 파일 등을 소프트웨어를 실행하는 데 필요한 모든 것을 포함하는 정적인 상태라면, 컨테이너는 이러한 이미지를 실행한 동적인 상태를 의미합니다.
즉, Docker 컨테이너는 배포 가능한 소프트웨어의 단위로, Host 시스템의 리소스를 격리된 환경에서 활용하여 다양한 작업을 수행할 수 있습니다. 하나의 컨테이너 안에서는 하나의 애플리케이션 프로세스가 동작하며, 각 컨테이너는 완전히 독립된 실행환경을 가지므로 다른 컨테이너에는 영향을 주지 않습니다.
Docker 컨테이너는 도커 이미지와 마찬가지로 레이어 구조를 가지며, 가장 하단에 베이스 이미지 레이어가 존재하고 그 위에 여러 레이어가 쌓여있습니다. 이미지가 변경되면 새로운 레이어가 추가되며, 컨테이너는 여기에 읽기-쓰기 레이어를 추가하여 실행 상태를 유지합니다.
Docker Container Commands
Docker는 컨테이너의 시작, 멈춤, 삭제, 로그 조회 등을 다루는 다양한 명령을 제공합니다.
docker run
docker run
명령어는 Docker 이미지를 바탕으로 새로운 컨테이너를 실행합니다.
$ docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
- OPTIONS:
-p
,--publish
: 호스트와 컨테이너의 포트를 연결합니다.-d
,--detach
: 백그라운드 모드, 즉 터미널에 붙지 않고 실행합니다.-e
,--env
: 컨테이너 내부 환경 변수를 설정합니다.--name
: 컨테이너에 이름을 지정합니다.--rm
: 컨테이너가 종료된 후 자동으로 컨테이너를 제거합니다.-v
,--volume
: 호스트와 컨테이너 사이에 볼륨을 공유합니다.-it
:docker run
을 인터랙티브 모드로 실행하며 터미널에서 입력을 가능하게 합니다. 보통 bash 쉘을 실행할 때 주로 사용되며,-i
(Interactive)와-t
(pseudo-TTY) 옵션의 조합입니다.--net
: 네트워크 모드를 설정합니다.-a
,--attach
:stdin
,stdout
또는stderr
와 같은 파일 디스크립터에 연결합니다.--add-host
: 컨테이너의/etc/hosts
에 호스트를 추가합니다.--blkio-weight
: 블록 IO weight 조절합니다.--cpu-shares
: CPU 사용량을 제한합니다.--device
: 호스트 시스템의 장치를 컨테이너에 추가합니다.--dns
: 컨테이너를 위한 DNS 서버를 설정합니다.--expose
: 네트워크 포트를 추가합니다.--group-add
: 컨테이너 프로세스가 추가할 그룹을 설정합니다.--health-cmd
: 헬스 체크를 위한 커맨드를 설정합니다.
- COMMAND, ARG:
- 컨테이너가 시작되면 실행할 커맨드와 그 커맨드에 전달할 인자입니다.
저장소에서 이미지를 가져와 컨테이너 실행을 동시에 하려면 다음과 같이 실행합니다:
$ docker run -d -p 8080:8080 --name waves catsriding/hello-docker:1.0.0
위 명령은 catsriding/hello-docker:1.0.0 이미지를 바탕으로 새로운 컨테이너를 만들고, 해당 컨테이너의 8080 포트를 호스트의 8080 포트에 연결한 뒤, 백그라운드에서 실행합니다.
docker ps
docker ps
명령어는 현재 실행 중인 모든 컨테이너의 목록을 출력합니다.
$ docker ps [OPTIONS]
- OPTIONS:
-a
,--all
: 모든 컨테이너를 보여줍니다.-q
,--quiet
: 컨테이너 ID만 출력합니다.-f
,--filter
: 특정 조건으로 출력을 필터링합니다.
예시로, 모든 컨테이너와 해당 컨테이너 정보를 확인하는 방법은 다음과 같습니다:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
123456789abc catsriding/hello-docker:1.0.0 "java -jar applic…" 5 minutes ago Up 5 minutes 8080/tcp waves
docker stop
docker stop
명령어는 실행 중인 컨테이너를 중지합니다.
$ docker stop [OPTIONS] CONTAINER [CONTAINER...]
- OPTIONS:
-t
,--time
: 이 옵션은 Docker에게 얼마나 오랫동안 실행 중인 컨테이너를 그레이스풀하게 중지하려고 시도할 것인지를 초 단위로 지시합니다. 이 시간이 지나면 Docker는 컨테이너에 SIGKILL을 보내 중지를 강제합니다. 기본값은 10초입니다.
예를 들어, waves 라는 이름의 컨테이너를 정지시키는 명령어는 다음과 같습니다:
# 컨테이너에 SIGTERM 신호를 보내서 종료 요청
# 일정 대기 시간(기본값 10초) 후, 컨테이너가 여전히 종료되지 않았다면 SIGKILL 신호를 보내 강제 종료
$ docker stop waves
# 종료 대기 시간을 직접 지정하여 종료 요청
$ docker stop --time 5 waves
위의 명령어를 실행하면, Docker는 waves 컨테이너에 SIGTERM 신호를 보내서 종료를 요청하고, 일정 시간(기본값은 10초) 후에 컨테이너가 여전히 종료되지 않았다면 SIGKILL 신호를 보내서 강제로 종료합니다.
docker rm
docker rm
명령어는 컨테이너를 제거합니다.
$ docker rm [OPTIONS] CONTAINER [CONTAINER...]
- OPTIONS:
-f
,--force
: 실행 중인 컨테이너도 강제로 제거합니다.-l
,--link
: 컨테이너의 네트워크 링크를 제거합니다, 컨테이너는 남아있습니다.-v
,--volumes
: 컨테이너가 사용하던 볼륨을 함께 제거합니다.
예시로, waves 컨테이너를 제거하는 방법은 다음과 같습니다:
# 종료된 컨테이너 제거
$ docker rm waves
# 실행 중인 컨테이너 강제 제거
$ docker rm -f waves
waves 컨테이너를 지우기 전에는 반드시 docker stop
명령어를 사용해 컨테이너를 중지하거나 -f
옵션을 추가해야 합니다.
docker logs
docker logs
명령어는 컨테이너의 로그를 출력합니다.
$ docker logs [OPTIONS] CONTAINER
- OPTIONS:
-f
,--follow
: 실시간으로 로그를 확인합니다.--since
: 특정 시각 이후의 로그만 조회합니다.--tail
: 가장 최근의 일부 로그만 조회합니다.
예시로, waves에서 가장 최근의 100줄 로그만 확인하는 방법은 다음과 같습니다:
$ docker logs --tail 100 waves
docker exec
docker exec
명령어는 실행 중인 컨테이너에서 명령을 실행합니다.
$ docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
- OPTIONS:
-i
,--interactive
: interactive 모드입니다.-t
,--tty
: pseudo-TTY 를 할당합니다.
- COMMAND, ARG:
- 컨테이너에서 실행할 명령과 그 인자입니다.
예를 들어, waves 컨테이너에서 셸을 실행하는 방법은 다음과 같습니다:
$ docker exec -it waves /bin/bash
root@717942d91b98:/app#
이제 셸을 통해 컨테이너 안의 파일시스템을 탐색하거나 명령을 실행할 수 있습니다.
docker container inspect
docker container inspect
명령어는 특정 Docker 컨테이너에 대한 상세 정보를 출력합니다.
$ docker container inspect [OPTIONS] CONTAINER [CONTAINER...]
- OPTIONS:
-f
,--format
: 출력 형식을 지정하는 Go 템플릿 문자열입니다.-s
,--size
: 컨테이너의 총 파일 크기 및 가상 크기를 출력합니다.
다음과 같이 사용할 수 있습니다:
# 컨테이너 이름이 waves인 컨테이너 정보 출력
$ docker container inspect waves
# 모든 실행중인 컨테이너 정보 출력
$ docker container inspect $(docker container ls -q)
이 명령어를 실행하면, 컨테이너의 ID, 생성 시간, 상태, 네트워크 설정 등에 대한 JSON 형식의 상세 정보가 출력됩니다. 이 정보를 통해 컨테이너의 네트워크 인터페이스 설정, 마운트된 볼륨, 환경 변수 등 많은 세부 정보를 확인할 수 있습니다.
Docker Network
Docker 네트워크는 Docker 컨테이너가 네트워크에 접근하거나 다른 컨테이너와 통신하는 방법을 관리합니다. Docker는 여러 가지 네트워크 모드를 제공하며, 각 모드는 컨테이너의 네트워킹 동작을 다르게 설정합니다.
Docker의 주요 네트워크 모드에는 다음과 같은 것들이 있습니다:
- Bridge: 이 모드는 컨테이너가 별도의 네트워크 네임스페이스를 가지고 있으며, Docker 호스트의 네트워크와는 독립적으로 운영됩니다. 각 컨테이너는 브릿지 네트워크를 통해 외부와 통신하며, 고유한 IP 주소를 가집니다. 이 모드는 Docker의 기본 네트워크 모드입니다.
- Host: 이 모드는 컨테이너가 Docker 호스트의 네트워크 네임스페이스를 사용하게 합니다. 즉, 컨테이너는 호스트의 네트워크 인터페이스와 IP 주소를 공유하며, 네트워크를 공유하는 다른 컨테이너와의 격리가 없습니다.
- None: 이 모드는 네트워크가 필요하지 않은 컨테이너에 사용됩니다. 컨테이너는 네트워크 네임스페이스를 가지지만, 네트워크 인터페이스는 설정되지 않습니다.
- Overlay: 이 모드는 멀티 호스트 네트워킹을 지원하며, 여러 Docker 호스트의 컨테이너가 동일한 네트워크에 연결될 수 있습니다. 이는 Swarm 모드에서 서비스 간 통신을 위해 사용됩니다.
Docker는 컨테이너를 생성할 때 각 컨테이너에 가상 에더넷(Virtual Ethernet) 인터페이스를 생성하고, 이 인터페이스를 통해 컨테이너가 네트워크에 접속하도록 합니다. 이 가상 인터페이스는 일반적으로 Veth*
로 이름지어지며, 각 컨테이너는 자신의 네트워크 네임스페이스(Network namespace) 내에서 이 인터페이스를 통해 네트워크에 접근합니다.
Virtual Network
가상 네트워크는 물리적 네트워크 위에서 구현되는 독립적인 네트워크입니다. 가상 네트워크는 가상 머신 또는 컨테이너와 같은 다양한 컴퓨팅 리소스를 마치 실제 네트워크에 연결된 것처럼 동일 네트워크에서 운용할 수 있게 합니다. 가상 네트워크의 한 예로 Docker의 네트워크 시스템이 있습니다. Docker는 각 컨테이너가 고유한 IP 주소를 가질 수 있도록 가상 네트워크를 제공하며, 이를 통해 컨테이너 간의 손쉬운 통신이 가능합니다. 또한, 클라우드 환경에서도 가상 네트워크가 널리 쓰이고 있습니다. AWS의 VPC(Virtual Private Cloud)는 사용자가 클라우드 상에서 자신만의 가상 네트워크 환경을 구성하고 관리할 수 있게 해줍니다. VPC를 통해, 사용자는 EC2 인스턴스나 RDS같은 AWS 리소스를 자신의 가상 네트워크에 배치하고, 각 리소스 간의 네트워크 통신을 관리할 수 있습니다.
기본적으로, Docker는 docker0
이름의 브릿지 네트워크를 생성하여 모든 컨테이너가 이 네트워크에 연결되도록 합니다. 브릿지 네트워크는 물리적 네트워크와 가상 네트워크를 연결하는 역할을 하며, Docker 컨테이너는 이 브릿지 네트워크를 통해 외부 네트워크에 접속할 수 있습니다.
Docker 컨테이너가 외부 네트워크와 통신하는 메커니즘으로, Docker 데몬은 Linux 커널의 iptables
기능을 사용하여 Network Address Translation(NAT)를 실행합니다. NAT는 브릿지 네트워크의 주소와 포트를 외부 네트워크의 주소와 포트에 매핑하며, 이를 통해 컨테이너는 외부 네트워크와 통신할 수 있게 됩니다.
또한, 사용자가 직접 Docker 네트워크 설정을 커스터마이즈 할 수 있습니다. 사용자는 Docker 명령어를 통해 네트워크 드라이버를 선택하거나, 서브넷을 지정하거나, IP 주소 할당 정책을 설정하는 등의 세부적인 네트워크 구성을 할 수 있습니다.
위 이미지는 Docker의 네트워크 메커니즘을 설명합니다. 여기서, Container 1 ~ 3은 같은 브리지 네트워크에 있어서 서로 통신이 가능합니다. 하지만 다른 브리지 네트워크에 위치한 Container 4는 Docker의 보안 정책으로 인해 이들 컨테이너와 직접 통신할 수 없습니다. 이 정책은 컨테이너들 사이의 격리를 보장하기 위한 것입니다.
그러나 컨테이너가 서로 다른 브리지 네트워크에 위치하더라도, 명시적으로 연결을 설정하면 통신이 가능합니다. 이는 컨테이너가 여러 네트워크에 연결될 수 있기 때문입니다. docker network connect
명령어를 사용하면 이를 구현할 수 있습니다.
결과적으로, 적절한 설정을 통해 서로 다른 네트워크에 위치한 컨테이너들 간의 통신을 가능하게 할 수 있습니다. 통신 규칙은 개발자의 필요에 따라 매우 유연하게 조정될 수 있으므로, 프로젝트의 필요성에 따라 네트워크 구성을 변경하면서 최적의 구조를 만들 수 있습니다.
Docker Network Commands
Docker는 네트워킹 작업을 편리하게 수행할 수 있도록 다양한 명령어를 제공합니다. 이 명령들은 컨테이너 간의 통신, 포트 매핑, 네트워크 분리 및 보안, 다른 네트워크 인프라에 대한 통합 등 복잡한 네트워크 관리 작업을 단순화하는데 도움을 줍니다.
docker network ls
docker network ls
명령어는 현재 정의된 모든 네트워크를 나열합니다.
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
0a350a6107de bridge bridge local
af4e13892c91 host host local
919eabc335c1 none null local
23adcabd9a3b waves-network bridge local
이 기본적으로 생성된 bridge
, host
, none
이외에 사용자가 직접 생성한 네트워크도 표시됩니다.
docker network create
docker network create
명령어는 새로운 네트워크를 생성합니다. 사용자는 네트워크의 드라이버와 서브넷 등의 옵션을 지정할 수 있습니다. 네트워크 드라이버의 기본값은 bridge
가 할당됩니다.
$ docker network create --driver bridge waves-network
위의 명령어는 waves-network라는 이름의 bridge 네트워크를 생성합니다.
docker network rm
docker network rm
명령어는 네트워크를 삭제합니다.
$ docker network rm waves-network
docker network connect
docker network connect
명령어는 컨테이너를 네트워크에 연결합니다.
$ docker network connect waves-network waves
이 예제에서는 waves 컨테이너가 waves-network 네트워크에 연결됩니다.
추가로, Docker 컨테이너는 하나 이상의 네트워크에 연결될 수 있으며, 이런 경우 각 네트워크에서 통신할 수 있게 됩니다. 여러 네트워크에 연결되어야 하는 경우, 아래와 같이 동일 명령어를 네트워크마다 반복적으로 실행할 수 있습니다.
$ docker network connect another-network waves
이 명령어를 실행하면 waves 컨테이너는 이제 waves-network와 another-network 모두에 연결되게 됩니다. 즉, 컨테이너는 연결된 모든 네트워크에서 통신이 가능해진다는 것을 의미합니다.
docker network disconnect
docker network disconnect
명령어는 컨테이너를 네트워크에서 분리합니다.
$ docker network disconnect waves-network waves-container
이 스크립트는 Docker 네트워크에 대한 기본 개념과 사용법을 설명할 뿐이며, 보다 상세한 정보는 Docker 공식 문서를 참조하는 것을 권장합니다.
docker network inspect
docker network inspect
명령어는 특정 네트워크의 상세 정보를 조회합니다.
$ docker network inspect NETWORK
위의 명령어는 네트워크의 구성, 연결된 컨테이너, IP 할당 정보 등의 상세 정보를 JSON 형식으로 출력합니다.
예를 들어, waves-network라는 네트워크의 상세 정보를 조회하는 명령어는 다음과 같습니다:
$ docker network inspect waves-network
출력 결과는 다음과 같은 형태로 표시됩니다:
[
{
"Name": "waves-network",
"Id": "d7b9c780de8a2cd17a52c924745cece8dd4b5adceb32553b66e54a49364dc426",
"Created": "2024-04-06T03:37:31.749671326Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
Docker Volume
Docker 컨테이너는 Stateless라는 특성을 가지고 있습니다. 이는 각 컨테이너가 독립적으로 동작하여 요청을 처리하고, 그 결과를 보존하지 않는다는 의미입니다. 이러한 특징 덕분에 효율적으로 컨테이너를 생성 및 제거할 수 있으며, 이는 시스템의 확장성과 유연성을 증대시킵니다.
그러나, 또한 Stateless 특성으로 인해 데이터 관리에 어려움이 동반됩니다. 컨테이너 내에서 발생하거나 수정된 데이터는 컨테이너가 종료되는 즉시 사라지기 때문입니다.
이 문제를 극복하기 위해 Docker는 다음과 같은 데이터 관리 기능을 제공합니다:
- 볼륨(Volume): 볼륨은 Docker 호스트 파일 시스템의 일부를 컨테이너에 마운트하는 방식입니다. 볼륨은 독립적으로 생성, 유지 및 삭제할 수 있으며, 한 개 이상의 컨테이너에서 동시에 사용될 수 있습니다. Docker가 직접 볼륨을 관리하기 때문에, 사용자는 볼륨이 저장되는 실제 위치를 알 필요가 없습니다.
- 바인드 마운트(Bind mounts): 바인드 마운트는 Docker 호스트 상의 임의의 위치를 컨테이너에 마운트하는 방식입니다. 바인드 마운트를 사용하면, 컨테이너는 Docker 호스트의 파일 시스템에 직접 접근할 수 있습니다. 바인드 마운트는 개발, 테스팅, 디버깅과 같은 시나리오에서 매우 유용합니다.
- tmpfs 마운트(tmpfs mounts): tmpfs 마운트는 Docker 호스트의 메모리에 데이터를 저장하는 방식입니다. 이러한 마운트는 컨테이너의 비휘발성 스토리지에 데이터를 유지하고 싶지 않은 경우에만 사용되며, 호스트 또는 다른 컨테이너와는 공유되지 않습니다.
이러한 방식 중에서, 볼륨을 선택하는 경우 Docker가 데이터 관리를 주도하게 됩니다. 이를 통해 데이터 백업, 복제 등의 고급 기능을 활용하는 것이 가능해지며, 이는 컨테이너 간에 데이터를 효과적으로 공유하는 데에 도움이 됩니다. 또한, 이 방식은 컨테이너의 생명주기와는 별개로 데이터의 지속성을 보장하게 해 주는 큰 장점이 있습니다.
Docker Volume vs Bind Mount
볼륨과 바인드 마운트는 모두 컨테이너에 데이터를 저장하고 공유하는 데 사용되지만, 용도와 운용 방식에 큰 차이가 있습니다. 볼륨은 Docker에 의해 관리되며, Docker 기능 및 API를 사용하여 백업, 복제, 마이그레이션 등을 할 수 있습니다. 데이터의 실제 위치는 Docker에 의해 숨겨져 있으며, 사용자가 접근하거나 이동시키는 것이 힘듭니다. 반면, 바인드 마운트는 Docker 호스트의 특정 블록을 직접 컨테이너에 연결합니다. 사용자는 호스트의 파일 시스템에서 마운트 지점을 선택하며, 마운트 지점의 내용은 컨테이너와 호스트 모두에서 수정될 수 있습니다.
Docker Volume Commands
Docker는 볼륨을 효과적으로 관리하기 위한 다양한 기능과 함께 강력한 명령어 세트를 제공합니다. 이를 통해 데이터의 영구 저장, 복제, 마이그레이션, 백업 등 복잡한 데이터 관리 작업을 훨씬 간단하게 처리할 수 있습니다.
docker volume ls
docker volume ls
명령어는 모든 볼륨을 나열합니다.
$ docker volume ls [OPTIONS]
- OPTIONS:
-f
,--filter
: 특정 조건을 만족하는 볼륨만 표시합니다.--format
: 출력 형식을 지정합니다.-q
,--quiet
: 볼륨 이름만 출력합니다.
다음과 같이 사용할 수 있습니다:
$ docker volume ls
DRIVER VOLUME NAME
local ocean-volume
local waves-volume
docker volume create
docker volume create
명령어는 새 볼륨을 생성합니다.
$ docker volume create
- OPTIONS:
-d
,--driver
: 볼륨 드라이버를 지정합니다.--label
: 볼륨에 메타데이터를 부여합니다.--name
: 볼륨의 이름을 지정합니다.--opt
: 드라이버별 옵션을 설정합니다.
예시로, 새로운 볼륨을 생성하는 방법은 다음과 같습니다:
$ docker volume create ocean-volume
ocean-volume
docker volume rm
docker volume rm
명령어는 하나 이상의 볼륨을 삭제합니다.
$ docker volume rm [OPTIONS] VOLUME [VOLUME...]
- OPTIONS:
-f
,--force
: 활성 상태의 볼륨도 강제로 삭제합니다.
다음과 같이 ocean-volume 볼륨을 삭제할 수 있습니다:
$ docker volume rm ocean-volume
ocean-volume
docker volume prune
docker volume prune
명령어는 사용하지 않는 볼륨을 모두 삭제합니다.
$ docker volume prune [OPTIONS]
- OPTIONS:
-f
,--force
: 묻지 않고 바로 삭제를 수행합니다.--filter
: 특정 조건을 만족하는 볼륨만 삭제합니다.
docker volume inspect
docker volume inspect
명령어는 하나 이상의 볼륨에 대한 자세한 정보를 출력합니다.
$ docker volume inspect VOLUME
다음과 같이 사용할 수 있습니다:
$ docker volume inspect ocean-volume
출력 결과는 다음과 같은 형태로 표시됩니다:
[
{
"CreatedAt": "2024-03-30T05:20:40Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/ocean-volume/_data",
"Name": "waves-volume",
"Options": null,
"Scope": "local"
}
]
Docker Storage Connection
Docker 볼륨과 바인드 마운트 두 가지 방식을 사용하여 컨테이너 내부의 파일과 디렉토리를 어떻게 연결하는지 알아보도록 하겠습니다.
Volumes
Docker 호스트에 waves-volume
라는 이름의 볼륨을 생성하고, 이를 컨테이너의 /app/logs
디렉토리에 연결하는 방법을 살펴보겠습니다. 아래 명령을 실행하면 컨테이너 내에서 생성되거나 변경되는 /app/logs
디렉토리의 모든 데이터는 waves-volume
에도 자동적으로 저장되게 됩니다.
$ docker run -d --name waves -v waves-volume:/app/logs catsriding/waves-server:1.0.1
실제로 볼륨에 데이터가 저장되고 있는지 확인하기 위해 컨테이너를 삭제한 후, 새 컨테이너를 생성하여 동일한 볼륨에 연결하면, 이전 컨테이너에서 생성된 로그 파일이 여전히 존재함을 확인할 수 있습니다.
Bind Mounts
바인드 마운트 방법을 사용하면, 컨테이너는 호스트의 특정 디렉토리를 직접 사용하게 됩니다. 즉, 컨테이너에서 생성되거나 변경되는 데이터는 호스트 디렉토리에 바로 반영됩니다. 아래 예제를 보면, docker run
명령을 통해 호스트의 /home/ec2-user/app/log
디렉토리를 컨테이너의 디렉토리에 바인드 마운트하는 과정을 확인할 수 있습니다.
$ docker run -d --name waves -v /home/ec2-user/app/log:/app/logs catsriding/waves-server:1.0.1
바인드 마운트는 호스트 시스템의 특정 위치를을 참조하기 때문에, 그러한 위치에 쓰기 권한이 있는 경우에만 사용할 수 있습니다. 또한, 데이터의 백업 또는 마이그레이션이 볼륨보다는 복잡할 수 있습니다.
여기까지, Docker에 대한 기본 개념을 살펴보았습니다. Docker의 이점에 대해 듣고 실제로 활용해 보려 했을 때, 처음에는 매우 어렵게 느껴졌습니다. 그러나 이것은 Docker 자체가 복잡했기 때문이 아니라, CS 지식이 미흡했기 때문이었습니다. CS 기반 지식을 점차 강화하면서 Docker의 여러 기능과 그 기능들이 어떻게 작동하는지가 더욱 분명해지기 시작했습니다. 역시 어느 분야든지 기본기가 정말 중요한 것 같습니다. 🐳
- Docker