Ian's Archive 🏃🏻

thumbnail
Docker를 톺아보자
CI/CD
2025.01.13.

여러 기업들은 자사에서 데이터센터나 기계실을 보유하여 온프레미스 환경에서 가동시키던 부분을

효율적인 자원 활용을 위해 클라우드 상의 가상 인스턴스로 옮기고 시스템을 구축한다

또한 확장성을 고려해 MSA(Micro Service Architecture) 아키텍처를 적용하며

여러개의 나뉘어진 App을 하나의 Application 처럼 동작할 수 있게 시스템을 구축한다.

이와같은 분산환경을 적용하면 아래와 같은 장점이 있다.

  1. 개별 서비스가 다른 서비스에 영향을 주지 않으면서 작동 (높은 가용성)

  2. 향후 확장 및 새로운 기능 추가 용이 (높은 확장성)

  3. 개발자들이 각각의 서비스를 파악하고 개선하기에 용이해짐 (편리한 엑세스)

  4. 기존 모놀로식 방법에 비해 배포에 따른 우려사항들이 적어짐

기존 VM (Virtual Machine)으로도 MSA환경을 구축할 수 있지만

컴퓨터를 가상화하는 기술이 발전하고 점점 가벼워지면서 컨테이어 기술은 더욱더 인기가 많아지고 있다.

VM은 하이퍼바이저를 사용해 각 머신별로 독립적인 하드웨어 자원을 제공하고

그 자원위에 별도의 OS를 띄운다.

컨테이너는 동일한 OS커널(호스트)을 사용하여 운영된다.

그럼 효율적인 인프라 구성을 위해 컨테이너의 대표 기술인 도커에 대해 알아보자

Container

소프트웨어 서비스를 실행하는 데 필요한 1. 특정 버전의 프로그래밍 언어 런타임 및 2. 라이브러리와 같은 종속 항목과 3. 애플리케이션 코드를 함께 포함하는 경량 패키지

  • 이미지를 기반으로 하는 격리된 소프트웨어 유닛, 이미지를 실행중인 인스턴스
  • 중요한 특징으로 컨테이너는 서로 분리되어 있으며, 디폴트로 공유 데이터나 상태가 없다.

VMvsDocker

Container와 VM을 비교해보면 다음과 같은 특성이 있다.

  • 컨테이너는 VM보다 경량화 되어있다.
  • 컨테이너는 OS수준에서 가상화 되고, VM은 하드웨어 수준에서 가상화 된다.
    -> 메모리, CPU 등 효율적인 자원 사용
  • Linux커널과 Cgroups 및 네임스페이스 등 커널의 기능을 사용해 프로세스를
    분리함으로써 독립적으로 실행할 수 있다.
  • 컨테이너는 OS커널을 공유하며 VM에 필요한 것보다 적은 메모리 사용
  • 컨테이너는 무상태 (Volume에 할당되어 있으면 로컬 호스트 머신의 폴더로 미러링 하거나 복사 가능)

이러한 컨테이너의 장점 때문에 Linux Container기술이 발전 했지만

Linux Container 컨테이너 기술은 사용하기 어렵고 대중화하기에는 한계가 있었다.

이러한 기술을 도입하기 편리하게 개발된 기술이 Docker이다.

LXC vs Docker

LXCvsDocker

Linux Container(LXC)는 단일 컨트롤 호스트 상에서 여러개의 고립된 리눅스 시스템 (컨테이너)들을 실행하기 위한 OS 레벨 가상화 기술이다.

Docker는 단일 애플리케이션을 실행하는데 중점을 둔 컨테이너 기술이다.

세분화된 접근 방식에 의해 크게 2가지 차이점이 나타난다.

  • Docker는 네트워크, 스토리지, 로깅 등 머신별 설정에 대한 추상화 제공한다.
  • Docker 컨테이너는 컨테이너당 단일 프로세스를 실행하도록 만들어 졌다.

추가로 더 자세히 정리된 링크 남긴다.

Linex컨테이너란? - Red Hat
Table of contents LXC vs Docker: Which Container Platform Is Right for You?
Docker 그리고 Linux 컨테이너 기술들

Docker

그럼 이제부터 Docker에 대해 자세히 알아보자

Docker Inc.에 의해 개발 된 Go 언어로 작성된 소프트웨어로 컨테이너를 생성하고 관리하기 위한 도구이다.

Docker를 실행 하면 Docker Engine이라는

컨테이너를 구축 및 실행하는 오픈 소스 호스트 소프트웨어가 실행되며

이 위에 Libraries, Dependencies, Tools, App이 실행 된다.

도커를 사용하면 아래와 같은 장점을 가진다.

  • Docker Engine을 이용하기 때문에 호스트 운영체제의 영향을 받지 않음
  • 설정 파일(Dockerfile)로 동일한 환경을 가지기 때문에 공유, 재구축 및 배포가 매우 쉬움
  • 설정 파일을 통한 이미지 버전 관리
  • 이미지가 레이어 구조를 가지기 때문에 차분 빌드가 가능
  • 다양한 기능의 API (Docker API를 이용하여 관리 App 개발 가능)
  • Docker registry를 사용해 이미지 관리(저장, 배포) 가능 (public, private)

Images & Containers

Docker의 핵심 요소인 Image와 Container에 대해 정리해보자

Image

  1. 모든 설정 명령과 2. 모든 코드가 포함된 공유 가능한 패키지

docker의 이미지를 registry를 통해 검색할 수 있는데 default 주소는 dockerhub이다.
(docker search 명령어를 통해 검색)

copyButtonText
$ docker search {검색 할 레지스트리 명} --filter=stars=10

# --filter=stars=n : star 수가 n개 이상인 결과만 표시

Image는 name : tag로 나뉘어 있는데

name 부분은 image의 이름을 나타내고

tag는 이미지의 버전을 기록한다.

Container

이미지의 구체적인 실행 인스턴스

Container에서 구동시킬 Image는 Container Repository에서 docker pull로 다운로드 되거나

DockerFile을 참조해 docker build 명령어로 커스텀 이미지를 생성한다.

DockerFile엔 자체 이미지를 빌드할 때 실행하려는 도커에 대한 명령이 포함한다.

copyButtonText
FROM {image name} # 이름 지정

WORKDIR # 명령어를 수행 할 디렉토리 지정

COPY {이미지의 외부 경로} {이미지 내부 경로} # 어떤 파일이 이미지에 들어가는지 설정 (절대 경로가 좋다)

EXPOSE # 로컬 시스템에 노출 시킬 포트 설정

RUN # 실행 할 명령어

DockerFile에서 RUN, CMD, ENTRYPOINT 3가지 명령어가 비슷한 역할을 해 헷갈릴 수 있는데 TODO:

RUN 명령어는 새로운 layer를 생성하거나, 생성된 layer 위에서 command를 실행 하고

주로 라이브러리 설치 하는데 사용

CMD 명령어는 docker가 실행될때 실행되는 명령어 정의한다.

만약 docker run 으로 실행될 때 사용자가 직접 실행할 명령어를 입력하면 CMD의 명령어는 실행되지 않는다.

여러번 사용 가능하지만 가장 마지막 CMD만 남는다 (오버라이딩)

[docker] RUN vs CMD vs ENTRYPOINT - freepsw

위의 글을 참고해서 한줄 요약헤보면

docker run 명령어로 실행시

Docker는 모든 명령어를 기반으로 하는 이미지 레이어 생성 -> 모든 명령 결과를 캐시

다시 빌드할 때 변경사항이 없으면 캐시 된 내용을 사용해 실행한다.
-> 레이어 기반 아키텍처 라 한다.


레이어 기반 아키텍처를 자세히 이해하기 전에 UFS에 대해 잠깐 알아보고 가자

Union File System

Docker의 이미지는 Union File System기반으로 동작하는

Storage Driver에 의해 파일 시스템으로 복수의 Layer가 구현된다.
(Storage Driver가 파일 I/O 수행)

UFS란, 여러 개의 파일 시스템을 merge된 하나의 파일 시스템에 마운트 하는 기능이다.

OverlayDriver

각 layer 간의 파일들을 취합해서 하나의 파일시스템으로 만들고 중복되는 파일의 경우, “상위” layer 가 “하위” layer 의 있는 파일을 대체하는 방식이다

UFS 를 통해, Dockerfile 명령어를 실행한 결과 파일 시스템을 하나의 docker layer 로 만들고, 이 layer 들을 겹겹이 쌓아서 merge 된 최종 결과물이 docker 이미지가 된다.

inspectGraphDriver

추가로 inspect명령어로 GraphDriver부분을 살펴보면 위 그림처럼 각 레이어에 매칭되는 디렉토리 경로를 확인할 수 있다.
(jq 명령어는 JSON포맷 데이터를 다루는 커맨드라인 유틸리티)

docker diff 명령어를 통해 lowerdir을 기준으로 upperdir에서 변경된 사항을 확인 가능
(C : 변경, A : 추가, D : 삭제)


Docker는 UFS의 CoW(Copy on Write)전략을 활용해 레이어의 역할을 나눠

동일한 파일 시스템을 공유하며 독립적으로 실행한다

DockerLayer

도커의 컨테이너 레이어 구조는 컨테이너 레이어이미지 레이어로 분류한다.

각각의 특징을 살펴보면 아래와 같다.

컨테이너 레이어

  • 쓰기 가능한 레이어
  • 컨테이너가 생성된 후 변경작업은 이 레이어에서 이루어 진다.
  • 각 컨테이너마다 최상단 레이어에 생성되어 컨테이너마다 고유한 상태를 가진다.

이미지 레이어

  • 읽기 전용 레이어
  • 다른 컨테이너와 공유

DockerImageLayer

다시 Docker의 레이어 동작방식을 정리해보면

하위 레이어(base layer) 위에 새로운 Layer가 쌓일 경우, 하위 layer는 READ ONLY 상태로 변경되고

상위 layer에서 하위 layer를 복사하여 사용(CoW)하기 때문에, 상위 layer는 하위 layer에 아무런 영향을 주지 않는다.

이미지 레이어를 통해 동일한 이미지가 생성되고

컨테이너 레이어를 통해 이미지에 대한 생성을 공유 하면서도 고유한 데이터 상태를 가질 수 있다.


Docker에서 특정 iamge를 이용해 container를 기동하는 것은

모든 Layer를 결합하여 사용자에게 하나의 File system으로 제공하는 것이다

추가로

도커에서 관리되는 모든 레이어와 관련된 정보는 호스트 파일 시스템의 /var/lib/docker 폴더에 저장되며

이 영역을 Docker Area 또는 Backing Filesystem이라 부른다.

이러한 레이어 기반 아키텍처 덕분에 크게 2가지 이점이 생긴다.


첫번째는 빌드 시간 단축이다.

Dockerfile에 정의된 명령어 중

RUN, ADD, COPY 3단계가 레이어로 저장이 된다.
(CMD, LABEL, ENV, EXPOSE 등 메타 정보를 다루는 부분은 임시 레이어로 생성되지만 저장되지 않는다.)

레이어 기반 아키텍처를 이해하면 다음과 같은 최적화가 가능하다.

copyButtonText
# node가 설치된 docker 이미지를 가져온다.
From node

# /app 폴더 내부에서 실행될 것임을 설정
WORKDIR /app

# 현재 Dockerfile 경로에 있는 파일들 전부를 도커 이미지 내부 /app 경로에 복사
COPY . /app

# package.json 파일을 설치
RUN npm install

# 만약 node 앱에 80포트로 서버를 열어놨다면 container의 외부 포트를 설정
EXPOSE 80

# 마지막 server.js 파일을 실행
RUN node server.js

# CMD의 경우 이미지 기반으로 컨테이너가 실행 될 때 실행
# CMD ["node", "server.js"]

위의 설정파일의 경우엔 소스를 수정해 파일을 하나라도 변경 할 경우

npm install이 실행되어 종속된 파일을 전부 다시 받게되는데

copyButtonText
# node가 설치된 docker 이미지를 가져온다.
From node

# /app 폴더 내부에서 실행될 것임을 설정
WORKDIR /app

# 현재 package.json 파일만 /app 경로에 복사
COPY package.json /app

# package.json 파일을 설치
RUN npm install

# 현재 Dockerfile 경로에 있는 파일들 전부를 도커 이미지 내부 /app 경로에 복사
COPY . /app

# 만약 node 앱에 80포트로 서버를 열어놨다면 container의 외부 포트를 설정
EXPOSE 80

# 마지막 server.js 파일을 실행
RUN node server.js

package.json을 app경로에 복사 한 후 나머지 실행파일을 /app경로로 복사한다면

package.json파일이 바뀌지 않아서 package를 다시 받지 않고 실행된다.

즉, 이미지를 빌드할 때 필요한 부분만 다시 실행하여 이미지 생성 속도를 높일 수 있다
(빌드 시간 단축)


두번째는 이러한 레이어 기반 아키텍처 덕분에 롤백 기능을 활용할 수 있고

Layer를 활용해 **지속적 통합 및 배포(CI/CD)**를 수행하는 데 도움을 준다.


추가로 base가 되는 image layer(read-only layer)에 있는 내용을 변경하고 싶을 땐

해당 data 를 Writable layer 로 먼저 Copy 한 후 이를 변경하는 기법을 사용해야 한다.

CoW기법은 동작 구조 상 다음과 같은 단점을 가질 수 있다는 부분을 알고있어야 한다.

  • data 를 먼저 복제(Copy)한 후 변경을 수행해야 하므로 성능 하락(Performance Overhead)이 존재한다.
  • 하위 layer 에 있는 원본 data와 상위 layer 에 변경된 data 도 유지해야 하므로 disk space를 많이 사용할 수 있다.

Docker Command

Docker의 동작방식을 살펴봤으니 다시 돌아와 사용하기 위한 명령어를 살펴보자

리눅스에서 도커 엔진을 제어하기 위한 명령어는 아래와 같다.

copyButtonText
# Docker 데몬 상태 확인
$ sudo systemctl status docker

# Docker demon 시작/중지
$ sudo systemctl start/stop docker

# Docker 데몬 사용/미사용 설정
$ sudo systemctl enabloe/disable docker

Docker 명령어의 기본적인 형태는 아래와 같다.

copyButtonText
$ docker [command] (option) [target] (arguments)
  • target : container (생략 가능), image, volume, network 등
  • command : ls, inspect, start, run 등

start vs run -> attached 모드 vs Detached 모드

1. build

Dockerfile로 부터 image 생성

copyButtonText
$ docker build [options] . -t "app/container_name"    # name

2. run

image를 기반으로 컨테이너를 만들어 실행 (생성, 실행)

copyButtonText
$ docker run {options} {이미지 명}

  # -a, --attach               # attach stdout/err
  # -i, --interactive          # attach stdin (interactive)
  # -t, --tty                  # pseudo-tty
  #     --name NAME            # name your image
  # -p, --publish 5000:5000    # port map
  #     --expose 5432          # expose a port to linked containers
  # -P, --publish-all          # publish all ports
  #     --link container:alias # linking
  # -v, --volume `pwd`:/app    # mount (absolute paths needed)
  # -e, --env NAME=hello       # env vars

3. start

컨테이너 실행

copyButtonText
$ docker start [options] CONTAINER
  # -a, --attach        # attach stdout/err
  # -i, --interactive   # attach stdin

4. create

컨테이너 생성

copyButtonText
$ docker create [options] IMAGE

# ex
$ docker create --name app_redis_1 \
  --expose 6379 \
  redis:3.0.2

# -d : detached mode (백그라운드 모드)
# -p : 포트 연결
# -v : volume 설정
# -e : 컨테이너 내에서 사용할 환경변수 설정
# --name : 컨테이너 이름 설정
# --it : 터미널 입력 옵션 (컨테이너의 표준 입력과 로컬 컴퓨터 입력 연결)
# --rm : 프로세스 종료 시 컨테이너 자동 제거
# --restart : docker desktop 실행 시 container 자동 실행 여부 (default : no)

5. attach

이미 실행 중인 컨테이너에 연결

copyButtonText
$ docker attach {이미지}:{태그}

6. ps / kill

실행중인 모든 container 노출

copyButtonText
$ docker ps -a
$ docker kill $ID

# -a : 현재 존재하는 모든 컨테이너의 목록 출력

7. cp

copyButtonText
$ docker cp

복사 명령어, 도커 내부의 로그 파일을 꺼내올 때 유용

8. stop

컨테이너 종료

copyButtonText
$ docker stop {Container ID}

9. rm

도커 컨테이너 삭제

copyButtonText
$ docker rm {컨테이너 명}

10. rmi

도커 이미지 삭제

copyButtonText
$ docker rmi {이미지 명}
# -f : 컨테이너도 강제 삭제

10. push / pull

레지스트리에 push / pull

copyButtonText
$ docker pull {이미지 이름}:{태그}
$ docker

11. rename

컨테이너 이름 변경

copyButtonText
$ docker rename {기존 이름} {변경하고자 하는 이름}

12. log

컨테이너 로그 조회

copyButtonText
$ docker container logs -t {컨테이너 식별자}

13. prune

사용하지 않는 Docker 오브젝트 삭제

Prune unused Docker objects

copyButtonText
$ docker container prune # 사용하지 않는 컨테이너 삭제
$ docker image prune # 사용하지 않는 이미지 삭제
$ docker volume prune # 컨테이너와 연결되어 있지 않은 모든 볼륨 삭제
$ docker system prune # 컨테이너, 이미지, 볼륨, 네트워크 전부 삭제

14. image / container

image 관련

command descriptioin
docker image build Dockerfile에서 이미지 빌드
docker image history 이미지의 history 표시
docker image ls 이미지 나열
docker image prune 사용하지 않는 이미지 제거
docker image pull 레지스트리에서 이미지 또는 저장소 가져오기
docker image push 이미지 또는 저장소를 레지스트리에 푸시
docker image rm 하나 이상의 이미지 제거
docker image tag SOURCE_IMAGE를 참조하는 TARGET_IMAGE 태그 생성

command 관련

Command description
docker container attach 실행 중인 컨테이너에 로컬 표준 입력, 출력 및 오류 스트림 연결
docker container commit 컨테이너의 변경 사항에서 새 이미지 만들기
docker container cp 컨테이너와 로컬 파일 시스템 간에 파일/폴더 복사
docker container create 새 컨테이너 만들기
docker container exec 실행 중인 컨테이너에서 명령 실행
docker container inspect 하나 이상의 컨테이너에 대한 자세한 정보 표시
docker container kill 하나 이상의 실행 중인 컨테이너 종료
docker container logs 컨테이너의 로그 가져오기
docker container ls 컨테이너 나열
docker container pause 하나 이상의 컨테이너 내 모든 프로세스 일시 중지
docker container port 컨테이너에 대한 포트 매핑 또는 특정 매핑 나열
docker container prune 중지된 모든 컨테이너 제거
docker container rename 컨테이너 이름 바꾸기
docker container restart 하나 이상의 컨테이너 다시 시작
docker container rm 하나 이상의 컨테이너 제거
docker container run 새 컨테이너에서 명령 실행
docker container start 하나 이상의 중지된 컨테이너 시작
docker container stop 하나 이상의 실행 중인 컨테이너 중지
docker container top 컨테이너의 실행 중인 프로세스 표시
docker container unpause 하나 이상의 컨테이너 내의 모든 프로세스 일시 중지 해제

dockerCheet


Docker의 컨테이너에 쓰여진 데이터는 기본적으로 컨테이너가 삭제될 때 함께 사라진다.

그렇기 때문에 호스트 컴퓨터와 연결하기 위해 Docker는 3가지 마운트 방법을 제공한다.

dockerMount

Volume, Bind Mount, thmpfs Mount

마운트 방식을 항목별로 살펴보자

Volumes

Volume은 Docker에서 관리하는 호스트 파일 시스템과 컨테이너와 매핑해 데이터를 영구적으로 사용하는 방법이다.
-> 볼륨은 Docker에 의해 생성되고 관리되며, 호스트 컴퓨터에서 편집은 불가능하다.

Volume은 Named Volume, Anonymous Volume 두가지 방법으로 지정 가능 하다.

-v <volumeName>:<mount dir>[:rw | ro]

명령어는 다음과 같이 -v or --volume으로 사용하고 :로 3개의 필드를 구분한다.

  • 마지막 rw, ro는 선택사항이며 read only옵션을 설정할 수 있다.

Named Volume

Volume에 이름을 지정해 사용하는 방법은 DockerFile에 명시하거나 명령어를 통해 지정해주는 방식이 있다.

Named Volume은 제거되지 않기 때문에 수동으로 제거시켜 줘야한다.

copyButtonText
# DockerFile 내부

VOLUME [ "/app/log" ]

명령어를 통해 Volume을 생성하고 확인하기 위해선 아래 명령어들을 사용한다

copyButtonText
$ docker volume create app_vol
$ docker volume ls

상세한 Volume 정보를 확인하기 위해선 inspect명령어를 사용하는데

Mountpoint 항목을 보면 해당 볼륨이 컴퓨터의 어느 경로에 생성 하였는지 확인 가능하다.

copyButtonText
$ docker volume inspect app_log

volumeInspect

Docker 실행 시 volume지정은 -v 옵션으로 volume의 이름과 경로를 지정한다

copyButtonText
$ docker run -d -p 3000:80 --rm --name exampleApp -v app_log:/app/log first:latest

위의 명령어로 container를 실행 후 inspect 명령어를 통해 container정보를 확인해보면 아래와 같이
mount된 정보를 확인할 수 있다.

copyButtonText
$ docker inspect exampleApp | grep -A 12 "Mounts"

containerVolumeMount

Anonymous Volume

아무 설정없이 컨테이너를 시작하면 자동으로 Anonymous Volume이 생성되는데

–rm 옵션을 넣어 컨테이너 시작하면 종료 시 자동으로 익명 Volume 제거 된다.
(옵션 없이 시작하면 제거되지 않는다.)

–rm 옵션 없이 컨테이너를 재시작 할 경우 새로운 익명 볼륨이 계속 생성된다.

아래 두 명령어를 통해 볼륨 삭제 가능하다

copyButtonText
$ docker volume rm VOL_NAME
$ docker volume prune

주의할 점으로 Volume을 삭제 시 Volume이 마운트 되어있으면 삭제가 되지 않으니
연결된 container를 모두 삭제 하고 제거해야 한다.

docker volume prune 커맨드는 마운트되어 있지 않은 모든 볼륨을 한번에 제거한다.

  • 익명 볼륨은 일부 컨테이너 내부 폴더를 덮어쓰지 않도록

Bind Mount

Volume은 도커에 의해 지정된 위치와 마운트하는 방법이지만

Bind Mount호스트 머신상에 디렉토리를 선언해 매핑할 컨테이너 경로를 마운트한다.
-> 호스트 머신의 어떤 폴더에 연결
-> 항상 최신 코드에 액세스 가능
-> 즉, 바인드 마운트는 컨테이너가 실행되는 동안 변경될 수 있는 데이터를 컨테이너와 공유하는데 사용한다.

Bind Mount는 개발 중에는 유용한 옵션이지만 프로덕션에서는 사용x
(컨테이너는 호스트시스템에서 격리되어 실행하는게 좋은 방향)

바인드 마운트를 사용하는 방법은 docker run 커맨드를 실행할 때

  • -v 옵션의 콜론(:) 앞 부분에 마운트명 대신에 호스트의 경로를 지정한다
  • -v 옵션으로 사용이 가능하지만 --mount도 동일한 기능을 하기 때문에 직관적인 --mount 사용이 좋음.
  • -v--mount의 차이점은 -v옵션은 존재하지 않는 파일 또는 디렉토리를 실행시 파일 존재여부와 상관 없이 생성되는데 --mount는 오류 생성
  • 경로는 절대 경로로 지정해 줘야 한다.
  • 폴더간 마운트를 주로 사용하지만 파일 간 마운트도 가능
  • bind mount는 docker volume ls로 노출되지 않는다.
copyButtonText
$ docker run -d -p 3000:80 --rm --name exampleApp --mount {절대 경로}:/app/log first:latest

-v $(pwd):/app 명령어로 현재 위치에 Bind 가능

docker inspect 커맨드로 컨테이너의 상세 정보를 확인해보면

현재 경로(/root)가 bind 타입으로 마운트되어 있는 것을 확인할 수 있다.


실제로 Node환경 Application의 소스를 bind mount를 활용해 받아올 수 있는데

실행 시 npm install을 받아오게 dockerfile을 구성했으면 에러가 난다.

bind 1

해당 원인은 컨테이너에서 npm install 시 호스트 컴퓨터 파일에 node_modules이 없기 때문인데

이와 같은 상황에선 volume을 추가로 생성해야 한다.
(Bind Mount된 볼륨의 내용을 덮어쓴다.)

volume을 설정하면 도커는 호스트 파일(원본)에 덮어쓰지 않고, 충돌이 있는 경우 긴 경로를 우선시 한다.

이렇게 재 생성하는 경우 Anonymous Volume를 사용하는 것이 유용

Volume vs Bind Mount

한줄정리 해보면

Volumes은 Docker에서 관리하는 호스트 파일 시스템과 컨테이너와 매핑해 데이터를 영구적으로 사용하는 방법

Bind Mount심볼릭 링크처럼 호스트 데이터를 읽을 때 사용 (호스트 폴더를 복사)

name example
Volume - 호스트 컴퓨터와 Docker 구성을 분리 할 경우
- 컨테이너의 데이터를 로컬이 아닌 원격 호스트 또는 클라우드에 저장하는 경우
- Docker 컨테이너 간 데이터를 백업, 복원, 마이그레이션 해야 하는 경우 (중지 후 백업 가능)
- 고성능 I/O가 필요한 경우
- Docker에서 App이 완전한 기본 파일 시스템 동작을 요구하는 경우 (ex - DB)
Bind Mount - 호스트 머신의 구성파일을 컨테이너로 공유 할 경우 (ex - DNS확인 시 /etc/resolv.conf 설정)
- 소스 코드 또는 빌드 아티팩트 공유 시
- Docker의 호스트 파일 or 디렉토리 구조를 일치시킬 때

tmpfs Mount

Volume과 bind Mount와 달리 일시적이며 호스트 메모리에 저장된다.

호스트나 컨테이너 쓰기 가능 계층에 유지하고 싶지 않은 민감한 파일을 임시로 저장하는데 유용

  • Linux에서 Docker를 실행하는 경우에만 사용 가능

Docker Setting

.dockerignore

.dockerignre은 Docker CLI가 Docker demon으로 보내기 전에 지정된 파일 및 디렉토리를 제외하도록 컨텍스트를 수정

gitignore와 같이 COPY명령으로 복사하면 안되는 폴더와 파일 지정가능

.dockerignore 파일 - Docker document

.env 설정

Dockerfile 내부의 ENV옵션으로 설정하여 환경변수가 존재함을 Docker에 알린다음
docker run에서 --env옵션을 사용해 구체적인 값 제공

  • key-value 형태로 환경변수 지정 가능
  • ${VAR+} 표현식을 지원하여 매개변수 확장 가능
  • docker run 시에 –e 옵션을 활용하여 오버라이딩 가능
copyButtonText
ENV abc=hello

ENV FOO=/bar
WORKDIR ${FOO}

환경 변수가 포함된 파일 지정 가능 (실행 위치로 부터 상대경로)

--env-file옵션을 사용해 환경 변수가 포함된 파일을 지정

copyButtonText
docker run -d --rm -p 3000:8000 --env-file ./.env

ARG 설정

Build시에만 사용 인수 지정

  • key-value 형태로 환경변수 지정 가능
  • ${VAR+} 표현식을 지원하여 매개변수 확장 가능
  • 빌드 시 특정값 잠금, 변경하지 않고 유연하게 빌드
  • ARG명령어는 FROM는 에서 앞에 올 수 있는 유일한 명령
  • docker build 시에 –build-arg 옵션을 활용하여 오버라이딩 가능
copyButtonText
ARG  CODE_VERSION=latest
FROM base:${CODE_VERSION}

argEnv

보안이 중요한 경우 데이터 종류에 따라 Dockerfile에 포함시키지 않을 수도 있다.

그럴경우에 별도의 환경 변수 파일을 만들어 사용한다.

Networking

통신은 3가지가 이루어지게 된다.

컨테이너 <-> www, 컨테이너 <-> 호스트 컴퓨터, 컨테이너 <->컨테이너

www의 경우 아무 설정 없이도 연결된다.

host Computer와 연결하기 위해선 host.docker.internal도메인을 사용해 http통신한다

도커 컨테이너 내부에서 호스트 머신의 IP주소로 변환된다.

내부 Container간 통신하기 위해서는 IP 주소를 수동으로 찾거나 network를 사용해 통신한다.

docker run 명령에 --netrork 옵션을 추가하면 모든 컨테이너를 하나의 동일한 네트워크에 넣을 수 있다.
-> IP조회 및 해결 작업을 자동으로 수행
-> 컨테이너 격리에 용이

docker는 network경우 자동으로 생성하지 않기 때문에 사용하기 전 docker network create명령어로 생성이 필요

docker network ls를 사용해 네트워크 확인

**동일한 네트워크로 연결 시 **url을 컨테이너 이름으로 지정 가능

ex) mongodb://mongodb:27017/sw

도커 컨테이너 이름또한 도커에 의해 IP주소로 변환된다.

컨테이너 간 통신을 위해선 포트 게시 필요x
-> 로컬 호스트 머신이나 컨테이너 네트워크 외부에서 연결 할 경우만 필요

IP Resolving

컨테이너에서 요청을 전송할 때만 자동 IP 변환

즉, 사용자가 웹 앱 방문하여 Javascript 코드가 브라우저에서 실행 중이고 브라우저에서 요청이 전송되면 도커는 아무 작업도 수행하지 않는다.
(소스 코드 대체x)

Docker-Compose

다중 컨테이너 설정을 더 쉽게 관리할 수 있게 해주고 설정 프로세스를 자동화하는데 도움

다수의 docker build명령과 다수의 docker run을 하나의 구성파일로 가진다.

docker compose가 Dockerfile과 함꼐 작동한다.

하나의 동일한 호스트에서 다중 컨테이너를 관리하는데 좋다.

컴포즈 파일에서 정의할 수 있고, 정의해야 하는 핵심이자 가장 중요한 항목은 서비스

service

시작하기 위해 docker-compose.yml 생성

copyButtonText
version: '3.8'
services:
  mongodb:
    image: 'mongo'
    volumes:
      - data:/data/db
    # enviroment:
    #   MONGO_INITDB_ROOT_USERNAME: max
    #   MONGO_INITDB_ROOT_PASSWORD: secret
    env_file:
      - ./env/mongo.env
    # networks:
    #   - goals-net
  backend:
    # build: ./backend
    build:
      context: ./backend
      dockerfile: Dockerfile
      # args:
      #   some-arg: 1
    ports:
      - '80:80'
    volumes:
      - logs:/app/logs
      - ./backend:/app
      - /app/node_modules
    env_file:
      - ./env/backend.env
    dopends_on:
      - mongodb
  frontend:
    build: ./frontend
    ports:
      - '3000:3000'
    volumes:
      - ./frontend/src:/app/src
    stdin_open: true
    tty: true
    depends_on:
      - backend

volumes:
  data:
  logs:
  • 도커 컴포즈를 사용하면 도커가 이 컴포즈 파일에 특정된 모든 서비스에 대해 새 환경을 자동으로 생성하고 모든 서비스를 그 즉시 네트워크에 추가한다.
  • networks 를 통해 지정도 가능하다.
  • volumes엔 services에서 사용중인 명명된 볼륨이 나열되야 한다.
  • 다른 서비스에서 동일한 볼륨 이름을 사용하면 그 볼륨이 공유된다.
    • 따라서 다른 컨테이너가 호스팅 머신 상의 동일한 볼륨, 동일한 폴더를 사용할 수 있다.
  • docker-compose up -ddocker-compose down으로 모든 이미지를 가져온 다음 컨테이너를 시작하고 종료할 수 있다.
  • build를 통해 Dockerfile을 찾고 사용한다.
    • dockerfile로 다른 이름을 가진 Dockerfile도 지정이 가능하다.
  • volume을 지정할 떄 docker-compose.yaml의 상대경로를 이용할 수 있어 편리하다.
  • 익명 볼륨도 지정 가능 (/app/node_modules)
  • depends_on은 의존성을 표기(위의 yaml에서는 backend전에 mongodb를 먼저 불러와야 한다.)
  • stdin_open, tty
    • 이 두가지 옵션을 추가하면 frontend 컨테이너가 인터렉티브 모드에서 시작한다.
  • --build옵션을 up명령어에 추가하면 이미지 리빌드를 강제할 수 있다.

Utility Container

유틸리티 컨테이너란 특정 환경만 포함하는 컨테이너를 의미한다.

도커를 이용하면 시스템에 노드를 설치하지 않고 노드 어플리케이션을 사용할 수 있는데

copyButtonText
docker run -it node

이렇게 입력하면 interactive 모드로 node를 실행할 수 있다.

copyButtonText
docker exec

컨테이너 안에서 dockerfile에서 명시한 명령어들 외에 추가로 execute할 수 있다.


잠깐 정리

  1. Interactive mode
  • Docker를 사용하면 대화형 모드에서 컨테이너를 실행
  • 컨테이너를 대화식으로 사용하면 실행 중인 컨테이너 내부의 명령 프롬프트에 액세스할 수 있습니다.
  • -it명령어로 모드 설정
  1. Attached mode
  • Attached mode에서 Docker는 컨테이너에서 프로세스를 시작하고 콘솔을 프로세스의 표준 입력, 표준 출력 및 표준 오류에 연결
  1. Detached mode
  • 컨테이너가 입력 또는 출력 스트림에 연결되지 않고 백그라운드에서 실행

docker exec

  • ‘docker exec’ 명령을 사용하면 이 컨테이너가 실행하는 기본 명령 외에 실행 중인 컨테이너 내에서 특정 명령을 실행 가능
  • Interactive 모드여야 한다.

copyButtonText
FROM node:14-alpine

WORKDIR /app

CMD npm init

이제 어떤 프로젝트를 빌드한다고 가정

copyButtonText
docker build -t node-util .

기본 설정이 다음과 같을 때 사용자에게 init을 강요하지 않고 더 자유로운 사용성을 주기 위해서 npm init을 제거하고 이미지를 빌드

copyButtonText
docker run -it node-util npm init

이제 컨테이너를 시작한다.

이렇게 시작해주면 npm init 명령어는 위에서 설정한 /app 디렉토리 내부에서 실행되게 된다. 그걸 바인드 마운트로 로컬 폴더메 미러링해보자.

copyButtonText
docker run -it -v /Users/hshoon/Desktop/git_local/docker-study/utility_setup:/app node-util npm init

이렇게 사용하면 로컬 머신에 어떤 프로그램을 설치할 일 없이 도커를 이용해 간단하게 어플리케이션을 제작할 수 있다.

여기에 Dockerfile에 ENTRYPOINT라는 속성을 사용할 수 있는데

Dockerfile을 다음과 같이 작성하고

copyButtonText
FROM node:14-alpine

WORKDIR /app

ENTRYPOINT [ "npm" ]

이미지를 생성해주면 뒤에 적은 npm init 대신 init만 적어줘도 실행 가능하다.

copyButtonText
docker run -it -v /Users/hshoon/Desktop/git_local/docker-study/utility_setup:/app mynpm init

이제 이걸 이용해서 해당 node 프로젝트에 express를 추가해보면

copyButtonText
docker run -it -v /Users/hshoon/docker:/app mynpm install express --save

바인드 마운트를 실행한 폴더에 모듈들이 들어온다.

이제 docker run 뒤에 들어가는 귀찮은 명령어들을 dockerCompose로 이식해보자.

copyButtonText
version: '3.8'
services:
  npm:
    build: ./
    stdin_open: true
    tty: true
    volumes: - ./:/app

명령어는 다음과 같이 적어주면 된다.

단일 컨테이너를 docker compose로 실행해주는 경우

copyButtonText
docker-compose run npm init

이렇게 npm에서 init을 해달라고 요청 가능하다


요약하자면 Docker 에서는 Utility Container 기능을 통해 로컬에 환경구축 없이 개발, 실행이 가능해진다.

Multi-Container Project example

컴퓨터에 도커 외에 그 어떤 도구를 설치할 필요 없이 Larabel & PHP 프로젝트를 통해 학습한다.

laravel

  • 로컬 머신에서는 코드를 작성한다.
    1. 소스코드 폴더는 PHP인터프리터 컨테이너라는 하나의 컨테이너에 노출된다
    • 이 컨테이너에는 PHP가 설치되어 있다.
    1. Nginx 웹서버가 들어있는 컨테이너가 있다
    • 기본적으로 들어오는 요청을 받은 다음 PHP인터프리터로 이동
    • PHP인터프리터가 응답을 생성해 요청을 전송한 클라이언트에게 요청을 돌려준다.
    1. DB에 저장하기 위해 MySQL DB를 추가한다.
    1. 패키지 관리를 하기 위한 Composer
    1. Laravel Artisan
    1. 프론트 로직을 위한 npm

총 6가지 컨테이너를 구축한다.


copyButtonText
version: "3.8"

services:
  server:
    #image: 'nginx:stable-alpine'
    build:
      context: .
      dockerfile: dockerfiles/nginx.dockerfile
    ports:
      - '8000:80'
    # volumes:
    #   - ./src:/var/www/html
    #   - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php
      - mysql
  php:
    build:
      context: ./dockerfiles
      dockerfile: php.dockerfile
    # volumes:
    #   - ./src:/var/www/html:delegated
  mysql:
    image: mysql:5.7
    env_file:
      - ./env/mysql.env
  composer:
    build:
      context: ./dockerfiles
      dockerfile: composer.dockerfile
    volumes:
      - ./src:/var/www/html
  artisan:
    build:
      context: .
      dockerfile: dockerfiles/php.dockerfile
    volumes:
      - ./src:/var/www/html
    entrypoint: ["php", "/var/www/html/artisan"]
  npm:
    image: node:14
    working_dir: /var/www/html
    entrypoint: ["npm"]
    volumes:
      - ./src:/var/www/html
  • 추가적인 설정 파일이 많지만 Dockerfile만 정리
  • delegated 옵션 사용 시 성능을 약간 상승
    • 컨테이너가 일부 데이터를 기록해야하는 경우에 그에 대한 결과를 호스트 머신에 즉시 반영하지 않고 배치로 기본처리
  • 설정을 할 때 내부통신을 이용하고, 외부 포트로 연결하지 않는다.
  • 필요한 경우 이미지에 리빌드 하도록 하려면 --build옵션 추가
    • docker-compose up -d –build server
  • npm부분은 docker-compose에 설정하였지만, Dockerfile로 분리 가능
  • docker-compose에 bind mount를 하면 소스코드를 즉시 읽고 적용될 수 있지만, 배포시에는 해당하지 않으므로 COPY로 변경 (Dockerfile에 있음)
  • COPY를 하면 도커 내부에서 권한 문제가 발생할 수 있다
    • Dockerfile내부에 RUN chown -R www-data:www-data /var/www/html로 설정

Deploy

도커로 배포 시 주의해야 할 사항

  1. 바인드 마운트를 사용하지 않는다.
  2. 컨테이너화 된 앱의 개발 및 최종 제품 생산을 위해 다른 설정이 필요하다.
  • 예를들어 React앱과 같은 Application은 코드가 변환되고 최적화 되는 빌드가 필요
  1. 여러 컨테이너로 구성된 복잡한 애플리케이션은 단일 머신의 리소스 한계를 넘어설 수 있어, 여러 호스트나 원격 머신에 분산 배포해야 할 수 있습니다.

  2. 제어와 책임 사이의 균형

  • 제어: Docker를 사용하면 애플리케이션과 그 환경을 세밀하게 제어할 수 있습니다. 개발자가 컨테이너의 구성, 리소스 할당, 네트워킹 등을 직접 관리할 수 있습니다.
  • 책임: 하지만 이러한 제어력은 더 많은 책임을 수반합니다. 개발자는 보안, 업데이트, 리소스 관리 등 시스템 관리 업무의 일부를 담당해야 합니다.

배포시에는 bind-mount가 아닌 copy로 코드를 복사해서 컨테이너를 실행해야 한다.

Deploy Summary

추가로 더 알아 볼 내용

  • Docker registry를 사용해 이미지 관리

Reference

【한글자막】 Docker & Kubernetes : 실전 가이드
Storage drivers - Docker docs
Multi-stage builds - Docker docs
도커 컨테이너 까보기(1) - Protocol, Registry
도커 컨테이너 까보기(2) – Container Size, UFS
What are Containers? - Google Cloud
What’s a Linux container? - Red Hat
Docker 그리고 Linux 컨테이너 기술들
Table of contents LXC vs Docker: Which Container Platform Is Right for You?
Docker란? - Red Hat
Docker란 무엇인가? - OCI
What is a hypervisor? - Red Hat
What is a virtual machine (VM)? - Red Hat
Containers vs VMs
Docker Cheatsheet [2023 Updated] - Collabnix
Docker ARG vs ENV - vsupalov blog

Thank You for Visiting My Blog, I hope you have an amazing day 😆
© 2023 Ian, Powered By Gatsby.