[Docker] 도커 옵션 쉽게 알아보고 제대로 활용하기

Docker를 프로젝트에 적용하면서 아주 기본적인 사용법은 알고 있었지만

특정 옵션을 왜 사용하는지? 또한 발생했던 문제 상황을 해결하기 위한 옵션들을 포스팅해보려 한다.

 

Docker에 대한 이해가 필요하다면 아래 포스팅을 참고하자

2023.06.21 - [◼ 오픈소스] - Docker(도커)란? 왜 사용하는가?

 

Docker(도커)란? 왜 사용하는가?

Docker란? Docker는 컨테이너 기반의 오픈 소스 가상화 플랫폼으로, 애플리케이션 및 그에 따른 환경을 격리된 컨테이너에 패키징하여 개발, 배포, 실행을 쉽게 지원하는 툴이다. 먼저 Docker를 이해

hstory0208.tistory.com

2023.06.23 - [◼ 오픈소스] - [Docker] 도커 컴포즈(Docker Compose)란? 왜 사용하는가?

 

[Docker] 도커 컴포즈(Docker Compose)란? 왜 사용하는가?

📌 도커 컴포즈란?여러 개의 Docker 컨테이너들을 하나의 서비스로 정의하고 구성해 하나의 묶음으로 관리할 수 있는 하나의 애플리케이션을 만드는 것이다. 도커 컴포즈 설치# 도커 컴포즈 V2

hstory0208.tistory.com


플랫폼 (Plaform)

위 dockerfile을 사용해 docker 이미지를 build 하면 기본적으로 Host 시스템 아키텍처에 맞는 플랫폼으로 이미지가 빌드된다.

현재 필자는 Mac으로 arm64로 이미지가 빌드 된다.

 

아래 명령어를 실행해 docker hub에 이미지를 배포하고 어떠한 아키텍처를 갖는지 확인해보자.

// dockerfile로 이미지 빌드후 해당 이미지 태그를 guswns0208/docker-study 변경 후 해당 레포지토리에 Push
docker build --push --tag guswns0208/docker-study .

 

다음과 같이 빌드를 실행한 내 컴퓨터의 운영체제와 동일하게 이미지가 build 된 것을 볼 수 있다.


문제 상황 - exec format error

필자는 Mac OS인 로컬에서 이미지(arm64 아키텍처)를 빌드하고 docker hub에 배포한 후

64비트(x86) 아키텍처를 갖는 EC2에서 이미지를 Pull받을 것이다. 

  • 64비트(x86) : amd64라고도 하며 고성능 컴퓨팅에 최적화되어 있어, 서버와 데스크톱 환경에서 사용되는 아키텍처 (intel 기반인 Windows에서 많이 보는 아키텍처)
  • 64비트(Arm) : arm64라고도 하며 낮은 전력 소비로 모바일 기기 및 임베디드 시스템에 많이 사용되는 아키텍처 (Mac에서 Moterey 이상인 Mac OS에서 많이 보는 아키텍처)

필자의 EC2 아키텍처는 위에서 64비트(x86)을 선택해 생성했으므로 다음과 같은 아키텍처를 갖는다.

 

하지만 이미지의 아키텍처와 EC2의 아키텍처가 맞지 않아 Pull을 받고 실행하면 다음과 같은 에러가 발생한다.

이를 플랫폼 설정을 통해 해결해보자.


--platform 옵션으로 build

--platform 으로 아키텍처를 설정해 build하는 방법이다.

docker build --platform linux/amd64 --push --tag guswns0208/docker-study .

 

설정한 amd64 아키텍처로 이미지가 빌드 된 것을 볼 수 있다.


멀티 플랫폼 빌드

여러 아키텍처 조합을 빌드하는 것으로

이를 사용하면 linux/amd64, linux/arm64, window/amd64와 같은 여러 플랫폼에서 실행할 수 있는 단일 이미지를 만들 수 있다.

https://docs.docker.com/build/building/multi-platform/

 

Multi-platform builds

Introduction to what multi-platform builds are and how to execute them using Docker Buildx.

docs.docker.com

필자는 위 공식문서와 동일한 방법으로 적용했다.

 

커스텀 빌더 생성

공식문서에 나와있는데로  Docker의 공식적인 멀티플랫폼 빌드 도구인 buildx를 이용해 빌드를 할 예정이다.

buildx는 docker의 빌드 엔진인 BuildKit을 기반으로 작동한다.

즉, buildx는 BuildKit 엔진을 쉽게 사용할 수 있도록 돕는 도구이다.

Docker 엔진이 19.03이상 버전이라면 buildx가 기본적으로 설치되어 있을 것이다.

아래 명령어로 buildx를 생성해보자.

 docker buildx create \
  --name container-builder \
  --driver docker-container \
  --use --bootstrap

container-builder 이름을 가지고 docker의 기본 드라이버인 docker-container를 사용하는

  • --name : 생성할 빌드 인스턴스 이름을 지정한다.
  • --driver : 빌드 인스턴스가 사용할 드라이버를 지정한다. (docker의 기본 드라이버인 docker-container를 사용)
  • --use : 생성할 빌드 인스턴스를 기본 빌드 인스턴스로 지정한다. (이후 buildx 명령이 자동으로 이 인스턴스를 사용)
  • --bootstrap : BuildKit 이미지를 가져와 빌드 컨테이너를 시작한다.

 

buildx로 이미지를 빌드하는 명령어로 추가하고자하는 아키텍처를 "쉼표"로 구분해 입력해주면 된다.

docker buildx build --platform linux/amd64,linux/arm64 --push --tag guswns0208/docker-study .

 

위 buildx로 build시 다음과 같이 buildkit 컨테이너가 실행되는 것을 볼 수 있다.

 

docker hub의 레포지토리로 들어가보면 선택한 두 개의 아키텍처를 가진 이미지가 빌드된 것을 볼 수 있다.

 

이제 EC2에서 해당 이미지를 Pull 받으면 되는데 특정 플랫폼을 지정하지 않아도 된다.

pull 받는 Host의 아키텍처 맞춰 pull을 받는 것이 docker의 기본 동작이기 때문이다.


docker file의 FROM 절에 플랫폼 지정

다음과 같이 dockerfile의 FROM절에서 빌드하길 원하는 하나의 아키텍처를 지정해 원하는 플랫폼으로 빌드할 수도 있다.


환경변수 (Environment)

필자는 mysql 이미지를 사용해 환경변수를 설정에 대해 설명할 것이다.

 

dockfile에 환경 변수 지정 (ARG와 ENV)

mysql 이미지를 기반으로하는 dockerfile 작성하였다. 

해당 파일을 기반으로 설명할 예정이다.

 

ARG를 사용한 환경변수 주입

ARG는 Dockerfile로 이미지가 빌드되는 시점에만 유효한 환경변수이다.

이미지 생성시 베이스가 될 이미지인 mysql의 태그를 ARG를 사용해 환경변수로 넣어 빌드해보자.

이미지 빌드 시에 아래와 같이 --build-arg 옵션으로 값을 주입할 수 있다.

로그를 통해서 mysql:latest라는 base 이미지를 가져온 것을 볼 수 있다.

 

주의할점으로 ARG는 빌드 히스토리에 값이 남아 민감한 정보를 담아선 안된다.

도커 공식문서에서도 아래와 같이 경고를 주고 있다.

 

경고에 설명한 것과 같이 아래 명령어를 쳐보면 아래와 같이 모든 기록들이 나오는 것을 볼 수 있다.

docker history 이미지ID_또는_이미지명

 

ENV를 사용한 환경변수 주입

ENV는 Dockerfile로 생성된 이미지로 실행  시킨 컨테이너에서 사용될 환경변수이다.

 

참고로 공식적으로 등록된 이미지들은 각 이미지마다 어떤 환경변수를 가지고 있는지 아래와 같이 설명이 나와 있다.

 

위 dockerfile로 이미지를 빌드하고 컨테이너를 실행했을 때 환경변수가 입력한데로 잘 설정되었는지 확인해보자.

// 컨테이너 접속 명령어
docker exec -it 컨테이너이름_또는_ID bash

 

echo로 환경변수를 확인해보면 설정한데로 잘 들어가 있는 것을 확인 할 수 있다.

 

못 미더우니 실행된 mysql도 잘 적용됐는지 확인해보자.

// mysql 접속 (해당 명령어 입력후 설정한 비밀번호 입력)
mysql -u root -p

// 생성된 User 확인
use mysql;
select Host, User from user;

 

ENV와 ARG의 차이 ?

이미지 빌드 시점에 ARG값을 아래처럼 ENV로 설정할 순 있지만 반대는 불가능하다.

또한 빌드 시에는 ENV 값을 설정할 build 명령어 옵션도 없다.

 

이미지라는 것은 읽기 전용 파일이다.

ARG는 Dockerfile로 빌드되는 시점에만 유효해 이미지 파일 내부의 값(환경변수)을 설정하는데 사용되는 옵션이고

ENV는 Dockerfile로 생성된 컨테이너에서 사용될 환경변수를 설정하는 옵션이다.

즉,  환경변수 생명주기를 보면 ARG는 이미지 빌드시에만 ENV는 컨테이너 실행동안 유지되는 것이다.


docker-compose.yml에 환경 변수 지정

이번에는 docker-compose 파일로 mysql 컨테이너를 실행해보자.

변수는 위 dockerfile과 동일하게 작성했고, 아래처럼 yml 작성법대로 설정해주면 된다.

위 dockerfile과 동일하게 해당 컨테이너로 접속해 환경변수를 확인해보면 설정한데로 잘 적용된것을 볼 수 있다.

 

추가로 환경변수를 다음과 같이 파일로 관리하고 이 파일로 환경변수를 설정할 수도 있다.

env파일의 경로는 docker-compose.yml 파일의 위치를 기준으로 한다.

 


CLI로 환경 변수 지정

파일에서 환경변수를 지정하지 않고 CLI 명령어에 환경변수 설정을 적용할 수 있다.

이 때 이미 이미지에 환경변수가 설정되어 있는데 CLI로 환경변수를 설정했다면 재정의된다.

docker run -d --name db -e MYSQL_ROOT_PASSWORD=4321 mysql:latest

 

해당 컨테이너로 접속해 해당 환경변수를 확인해보면 설정한데로 4321이 나오는 것을 볼 수 있다.

 

다음과 같이 환경변수 파일을 적용시킬 수도 있다.

docker run -d --name db --env-file .env mysql:latest

해당 컨테이너로 접속해 환경변수를 확인해보면 잘 설정된 것을 볼 수 있다.


Docker Compose 옵션인 depends_on 그리고 condition

depends_on 옵션은 컨테이너의 시작 순서를 보장해주는 옵션이다.

위 상황에서 spring-web 서비스는 mysql-db 서비스가 먼저 시작되고 나서 연결되어야만 한다.

그렇기 때문에 depends_on 옵션으로 spring-web 서비스가 mysql-db 서비스가 시작 된 후 시작하도록 의도했다.

하지만 depends_on은 해당 mysql-db 서비스가 시작은 됐지만, 완전히 연결 준비된 후에 spring-web이 시작되도록 보장해주진 않는다.

그렇기 때문에 위 대로 docker compose up을 실행하면 spring-web 서비스가 mysql-db에 연결 요청을 보내지 못해 다음과 같은 에러가 발생한다.

 

Unable to open JDBC Connection for DDL execution [Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.] 

이 문제를 해결하기 위해 검색한 결과 아래 공식 문서에서 해답을 찾을 수 있었고

https://docs.docker.com/compose/startup-order/

 

Control startup and shutdown order in Compose

How to control service startup and shutdown order in Docker Compose

docs.docker.com

 

다음과 같이 서비스의 준비 상태를 감지하기 위한 조건 옵션(condition)을 사용할 수 있다.

codition 옵션
  • service_started : 의존하는 서비스가 시작되기만 하면 다음 서비스가 시작된다. (서비스가 준비되지 않았더라도 시작될 수 있다.)
  • service_healthy : 의존하는 서비스가 정상적으로 작동 중일 때만 다음 서비스가 시작됩니다. (헬스 체크를 통과해야 시작)
  • service_completed_successfully : 의존하는 서비스가 정상적으로 종료되었는지를 확인한다. (해당 서비스가 성공적으로 실행된 후 종료된 경우에만 다음 서비스가 시작) 주로 데이터 베이스 마이그레이션 같은 일회성 작업에 사용된다.

필자는 위 옵션들 중 mysql-db 서비스가 연결 준비가 완료된 후에 spring-web 서비스가 연결되어야 했기 때문에

service_healthy 옵션을 사용해 연결 문제를 해결하였다.


네트워크 (Network)

docker 네트워크는 컨테이너 간 통신을 관리하고 컨테이너와 외부 네트워크 간의 연결을 제어하는 시스템이다.

이를 통해 격리된 환경에서 실행되는 컨테이너들이 서로 통신하거나 외부와 통신할 수 있게 된다.

 

참고로 네트워크가 아닌 --link 옵션으로 컨테이너들을 연결하는 코드들이 종종 보이는데

아래 도커 공식문서에서는 --link 옵션은 레거시 기능으로 제거 될 수 있어 network를 사용한 연결을 권장한다.


docker 기본 Network

도커 네트워크가 어떤식으로 이뤄져있는지 알기 전에 도커에서 기본적으로 제공해주는 네트워크 드라이버들에 대해 알아보자.

// 도커 네트워크 조회 명령어
docker network ls

기본적으로는 아래와 같이 birdge, host, none 이름의 3가지 네트워가 제공 되고 이름과 동일한 드라이버를 갖고 있다.

 

각 드라이버들에 대해 하나씩 간단하게 알아보자.

 

bridge

컨테이너 시작 시 기본으로 할당되는 네트워크이다.

그리고 사용자 정의 네트워크 생성 시 드라이버를 지정하지 않으면 bridge 드라이버가 자동으로 할당된다.

해당 네트워크에 연결되어 있는 컨테이너마다 IP주소를 할당해주고, 포트 매핑을 통해 외부와 통신을 가능케 해준다.

우리가 일반적으로 컨테이너를 실행할 때 네트워크를 지정하지 않는데 해당 네트워크를 사용하고 있는 것이다.

그렇기 때문에 포트 포워딩을 통해서 외부에서 내부 컨테이너로 접근할 수 있게 되는 것이다.

일반적으로 여러 서비스를 독립적으로 실행하면서도 서로 통신하거나 외부에서 접근이 필요할 때 사용하게 된다.

이름 그대로 "다리" 역할을 한다고 생각하면 이해하기 편할 것 같다.

 

host

이름 그대로 별도의 격리 없이 host의 네트워크 환경을 그대로 사용한다.

때문에 컨테이너 내에서 사용하는 IP 주소는 로컬 컴퓨터의 IP와 동일하고, Port는 실행되는 애플리케이션이 liten하는 포트와 동일하다.

컨테이너 내에서 실행되는 애플리케이션은 별도의 포트포워딩 없이 localhost를 통해 접속할 수 있게 되어

--network 옵션으로 host 네트워크를 지정하면 다음과 같이 PORTS가 할당되지 않는 것을 볼 수 있다.

docker run -d --name web --network host guswns0208/docker-study:latest

Linux 운영체제의 EC2에서 실행시킨 결과

host 네트워크로 실행된 spring-web 컨테이너는 구동되고 있는 서버의 8090 포트로 매핑되어 있고 localhost:8090으로 웹에 접속할 수 있다.

 

host 네트워크는 NAT로 인한 오버헤드가 없어 빠른 통신이 가능하다.

반면에 호스트의 네트워크가 컨테이너와 직접적으로 연결돼있어 보안에 주의가 필요하다.

 

참고로 도커 공식문서에서 다음과 같이 host 네트워크에 대한 전제 조건을 설명하고 있다.

해석하자면 Host 네트워크는 Linux 호스트만 사용할 수 있었지만,

Docker Desktop 4.29 버전부터 Mac, Windows도 베타 버전으로 사용할 수 있다고 한다.

단, Docker Desktop의 설정에서 "Enable host networking"을 활성화 해주어야 한다.

그렇지 않으면 host 네트워크로 실행한 docker 컨테이너가 호스트와 연결이 되지 않는다.

설정을 마치면 Mac, Windows에서도 host 네트워크를 사용할 수 있다.

(참고로 실행시킨 컨테이너의 spring 서버는 8090 포트로 실행되고 있다.)

 

none

컨테이너에 네트워크를 제공하지 않아 컨테이너가 완전히 격리된 네트워크 환경에서 실행된다.

네트워크가 전혀 필요 없거나, 네트워크 격리가 필요할 경우 사용할 수 있다.


bridge 네트워크 통신 과정

bridge 네트워크와 아래 docker-compose.yml 파일으로 어떤식으로 컨테이너가 내/외부로 통신하는지 한번 알아보자.

참고로 아래처럼 compose 파일에 network를 지정하지 않으면 myapp_default 라는 이름의 네트워크가 생성된다.

(myapp은 docker-compose.yml 파일이 있는 디렉토리 명으로 바뀐다.)

그리고 이 네트워크는 기본 드라이버인 bridge를 사용한다.

 

이제 위 compose 파일을 실행해 컨테이너들이 실행되고, 외부로 부터 요청을 받는 과정을 확인해보자.

네트워크 연결

호스트 머신에서 실행된 도커 컨테이너들이 study_default라는 이름의 network에 포함되어 있다.

그리고 연결된 컨테이너들이 이 네트워크로 부터 순차적으로 IP 주소를 할당 받는다.

inspect 명령으로 해당 네트워크 정보를 보면 다음과 같다.

아래는 이해하기 편하도록 나타낸 그림이다.

외부 요청

1. 외부에서 호스트 머신의 IP 주소와 매핑된 포트로 요청을 보낸다.

2. Host가 80포트로 들어온 요청을 80:8090 포트포워딩 되어있는 Docker 네트워크로 전달한다.

3. study_default 네트워크의 Gateway는 80 요청을 수신하고

도커는 설정된 포트 포워딩 규칙에 따라적절한 컨테이너의 포트로 포워딩한다.

그리고 Gateway는 IP 내에서 수신할 컨테이너를 식별해 해당 컨테이너의 IP로 전달한다.


컨테이너 데이터 관리 (volume, bind, tmpfs)

도커 컨테이너는 이미지를 통해 실행할 수 있고 이 이미지를 통해 컨테이너를 실행하면

실행한 컨테이너는 이미지, 컨테이너 2가지 계층을 갖게 된다.

 

이미지는 컨테이너가 실행되기 위해 필요한 모든 파일을 갖는 읽기 전용 계층으로, 변하지 않고 여러 컨테이너에 재사용될 수 있다.

컨테이너는 이미지를 기반으로 실행되는 계층으로, 실행 중에 생성된 데이터는 기본적으로 컨테이너 내부에만 존재한다.

당연하게도 컨테이너가 삭제되면 그 안에 저장된 모든 데이터도 사라진다.

 

컨테이너를 삭제하지 않으면 되지 않을까 ?

컨테이너로 웹 서버를 배포한다 가정했을 때 해당 웹 서버는 배포된 이후에도 계속해서 변경사항이 있을 것이다.

그리고 변경사항이 반영된 이미지로 웹 서버 컨테이너를 실행해야하는데 기존 웹 서버 컨테이너를 계속 띄울 필요도 없고

살려둔다하더라도 새로운 웹 컨테이너가 배포될 때마다 포트 충동을 방지하기 위해 계속해서 포트를 변경해서 배포해야할 것이다.

또한 컨테이너를 삭제할 상황이 절대 없다고 보장하더라도, 개발자는 발생할 수 있는 상황들을 미연에 방지할 수 있어야 한다.

쉽게 삭제 가능한 컨테이너 특성상 중요한 데이터를 영구적으로 보존하고 백업할 필요가 있다.

 

정말로 저장이 안되는지 확인해볼까?

mysql:latest 이미지로 컨테이너를 실행해 docker_study라는 데이터베이스를 생성했다.

컨테이너를 삭제하고 동일한 이미지로 다시 컨테이너를 실행해 확인해보면

아래와 같이 컨테이너 삭제시 데이터도 함께 삭제되어 docker_study라는 데이터베이스를 찾아볼 수 없다.

 

 

 

docker는 위와 같은 문제를 방지하기 위해 데이터를 지속할 수 있도록 다음과 같은  volume, bind, tmpfs  3가지 마운트 타입을 제공해준다.

각 마운트 별로 설명하기 앞서 volume, bind는 컨테이너 간 데이터 공유가 가능하고 tmpfs는 불가능하다.


Volume

볼륨은 호스트 파일 시스템의 일부 /var/lib/docker/volumes에 저장된다. (docker desktop의 경우 VM 내에 저장)

그리고 이 경로에 있는 volumes 디렉토리는 기본적으로 root 권한으로 보호되어 있다.

 

볼륨은 공식문서에서도 데이터를 유지하는 가장 좋은 방법이라고 추천하는 마운트 타입이다.

볼륨은 도커에 의해 생성되고 관리되며 아래 명령으로 명시적으로 볼륨을 생성할 수도 있고

docker volume create my-vol

아래 명령으로 컨테이너를 실행할 때 볼륨을 생성 또는 생성된 볼륨을 연결할 수도 있다.

# -v 데이터를_저장할_볼륨_이름:저장할_데이터가_있는_컨테이너의_디렉토리_경로
docker run -d --name db -v my-vol:/var/lib/mysql mysql:latest

 

이번에는 위 명령어를 통해 데이터베이스를 생성하고 컨테이너를 삭제해도

mysql 이미지로 컨테이너를 다시 실행했을 때 docker_study 데이터베이스가 있는지 확인해보자.

 

볼륨을 생성하고 생성한 볼륨을 연결해 mysql 이미지로 컨테이너를 실행했다.

docker volume create my-vol

docker run -d --name db -v my-vol:/var/lib/mysql mysql:latest

해당 컨테이너가 설정한 대로 볼륨이 잘 연결 된 것을 확인할 수 있고 /var/lib/docker/volumes 경로에 해당 볼륨이 생성되었다.

exec로 mysql에 접속해 docker_study 데이터베이스를 생성하고 이 데이터가 저장된 볼륨으로 컨테이너를 다시 실행하면

아래 처럼 컨테이너가 삭제되고 다시 실행되도 데이터가 유지되고 있는 것을 볼 수 있다.


bind

바인드는 호스트 머신의 지정된 디렉토리에 컨테이너 데이터가 마운트 된다.

아래 명령어로 바인드 마운트를 사용할 수 있다. (만약 호스트 디렉토리가 존재하지 않는다면 생성됨)

# -v 데이터를_저장할_호스트의_디렉토리_경로:저장할_데이터가_있는_컨테이너의_디렉토리_경로
docker run -d --name db -v ~/docker-volume:/var/lib/mysql mysql:latest

 

이 방법도 위와 동일하게 컨테이너를 삭제하고 다시 실행해도 데이터가 남아 있는지 확인해보면 다음과 같이 잘 저장되어 있는 것을 볼 수 있따.

 

또한 -v 옵션으로 지정한 호스트 경로에 mysql의 데이터가 생성된 것을 확인할 수 있다.


volume 마운트 vs bind 마운트

volume 마운트와 bind 마운트는 비슷해보인다.

docker 공식문서에서도 그랬듯이 volume 마운트를 권장하는데 필자가 사용하면서 느낀바로는 이유를 추측해보자면 다음과 같다.

volume 마운트는 호스트의 파일 시스템 내에 특정 경로에 위치하고 docker가 해당 볼륨의 생명 주기를 관리한다.

bind 마운트는 호스트의 파일 시스템 내에 위치해 docker와 독립적으로 관리된다.

docker로 관리되는 volume의 경우에는 docker의 CLI 등 docker의 기능을 활용할 수 있지만

bind는 특정 호스트 경로에 의존하므로 이식성이 떨어질 수 있고, 특정 경로 위치들을 파악해야 한다는 점 때문에

volume 마운트 방식이 권장되지 않을까 생각한다.

 

또한 volume 마운트를 적용했을 때 데이터가 저장되는 volumes 디렉토리는 기본적으로 root 권한으로 보호되어 있다.

그리고 docker의 프로세스는 docker로 부터 생성된 볼륨으로 생긴 volumes에 접근할 수 있다.

하지만 bind 마운트도 호스트의 디렉토리에 root 권한을 주면 되지 않나 생각할 수 있다.

하지만 root 권한을 주게 되면 docker 프로세스는 해당 호스트의 root 권한이 없어 접근할 수 없게 된다.

데이터 관리 안정성 측면에서도 volume 마운트가 더 좋다고 판단된다.

 

하지만 bind 마운트도 적절한 상황이 있다.

여러 상황이 있을 수 있겠지만, 필자가 느끼는 상황 중 하나는

컨테이너의 데이터를 호스트로 마운트 하는 것이 아닌 반대로 마운트를 할 때 유용할 수 있을 것이다.

예를 들어, 설정 파일을 로컬에서 관리하면서 이 설정이 필요한 컨테이너로 마운트할 수 있다.

두 번째로는 로그 같은 특정 파일이나 디렉토리를 호스트의 특정 위치에 저장하고 싶을 때 유용할 수 있다.


tmpfs (Temp File System)

tmpfs 마운트는 일시적인 마운트로, 호스트의 메모리(RAM)에 저장한다.

tmpfs 마운트는 호스트의 메모리를 사용하지만 컨테이너와 동일한 생명주기를 갖고 있어 컨테이너가 종료되거나 중지되면 tmpfs 마운트가 제거되고 데이터도 유지되지 않는다.

또한 volume과 bind와 달리 컨테이너 간에 마운트를 공유할 수도 없다.

주로 컨테이너 생명주기를 넘어 지속될 필요 없는 또는 일시적으로 사용되는 데이터일 경우 tmpfs 마운트를 사용하기 적합하다.

 

그런데 마운트 자체를 설정하지 않으면 컨테이너를 종료했을 때 어차피 데이터가 사라지는데 뭐가 다른걸까?

차이점을 보면 마운트를 설정하지 않았을 때는 컨테이너의 디스크에 데이터가 저장된다.

하지만 tmpfs는 메모리(RAM)에 데이터를 저장하기 때문에 더 빠르게 데이터에 접근할 수 있다.

그렇기 때문에 tmpfs는 캐싱 같이 임시 데이터를 저장하거나 단시간에 대량의 데이터를 처리할 때 유용하게 사용된다고 한다.

 

tmpfs 마운트는 아래와 같이 적용할 수 있다.

(도커 공식문서에 따르면 Linux에서만 tmpfs 마운트를 사용할 수 있다고 한다.)

docker run -d --name tmptest --tmpfs 컨테이너_내부_마운트_경로 nginx:latest

 

정말 컨테이너가 종료되면 tmpfs로 저장된 데이터가 사라지는지 확인해보자.

 

아래 명령어로 --tmpfs 마운트 경로를 지정하고 nginx 이미지로 컨테이너를 실행했다.

docker run -d --name tmptest --tmpfs /app nginx:latest

 

해당 컨테이너로 접속해 tmpfs가 마운트된 경로인 /app에 hello.txt라는 파일을 생성했다.

 

이후 컨테이너를 재시작 후 /app 경로를 확인해봤더니 이전에 만들었던 hello.txt가 없는 것을 확인할 수 있다.


라벨 (Labels)

LABEL은 이미지나 컨테이너에 메타데이터를 추가할 수 있는 명령어이다.

LABEL는 키-값 쌍으로 이뤄진 메타데이터로, 이 메타이데이터를 활용해 이미지에 대한 정보를 체계적으로 관리할 수 있다.

말로만 들어서는 와닿지 않을 것이다.

어떻게 활용할 수 있는지 예시를 통해 알아보자.

 

필자는 Spring 애플리케이션을 빌드해 docker hub에 push하려한다.

이 때, ARG와 LABEL을 활용해 이미지가 생성된 시간, 트리거가된 커밋의 해쉬 코드 정보들이 담기도록 할 것이다.

이런 식으로 활용하게 된다면

특정 버전의 이미지를 쉽게 식별, 추적할 수 있을 것이고 문제 발생 시 해당 이미지의 소스 코드인 커밋을 찾아가 디버깅할 수 있을 것이고,

문제가 발생했을 때 특정 시점의 이미지로 쉽게 롤백할 수도 있을 것이다.

 

위 요구사항을 구현하기 위해 다음과 같이 dockerfile과 스크립트를 작성했다.

dockerfile

LABEL은 아래와 같이 key=value 형식으로 작성해주면 된다.

createdAt과 version은 이미지 빌드시 CI/CD 스크립트에서 동적인 값을 주입해주기 위해 ARG를 사용해 주입되도록 했다.

 

 

gitaction ci / cd yml 파일

이미지 빌드 시에 넣어줄 createdAt과 version 값을 생성후 --build-args 옵션을 사용해 환경 변수를 주입해 이미지를 빌드했다.

(스크립트 코드에서 생소할 수 있는 부분에 대해서는 주석을 달아 놓았다.)

 

LABEL 적용 후

이제 이미지를 push 한 저장소를 가보면 아래와 같이 커밋으로 태그가 추가된 것을 볼 수 있다.

 

 

그 다음 해당 이미지를 pull 받고 아래 명령어를 통해 라벨 정보만 확인해 볼 수 있다.

docker inspect -f '{{.Config.Labels}}' 이미지ID_또는_이미지명

 

포맷 형식이 좀 불편하긴 한데 jq 같은 툴을 설치하면 더 깔끔한 형식으로 볼 수 있다.

// mac에서 jq 설치
brew install jq

// 라벨 정보 확인
docker inspect -f '{{.Config.Labels}}' 이미지ID_또는_이미지명 | jq

// 또는
docker inspect 이미지ID_또는_이미지명 | jq '.[0].Config.Labels'

 


 

참고자료

https://seosh817.tistory.com/374

https://docs.docker.com/engine/storage/ https://docs.docker.com/engine/storage/volumes/

https://docs.docker.com/engine/storage/bind-mounts/

https://docs.docker.com/engine/storage/tmpfs/

https://medium.com/@moein.moeinnia/understanding-docker-mounts-volumes-bind-mounts-and-tmpfs-f992185edc27

https://peonyf.tistory.com/204

https://docs.docker.com/engine/network/

https://docs.docker.com/engine/network/links/

https://docs.docker.com/compose/networking/

https://docs.docker.com/build/building/multi-platform/