Docker

seohan·2022년 3월 4일
0

1. 시작하기 전에

기존 앱을 도커로 migration

Docker로 이주한 애플리케이션은 비용 이득이 크며 이식성이 좋아집니다.

새로운 클라우드-네이티브 앱을 구축

CNCF에서는 애플리케이션을 마이크로서비스로 배포, 각 파트를 컨테이너로 패키징, 자원 이용을 최적화하는 컨테이너 오케스트레이션을 지원하는 오픈소스 스택 아키텍처가 특징입니다.

다음은 마이크로서비스에 대한 전형적인 샘플입니다. 모든 컴포넌트가 컨테이너에서 실행합니다.

Demo 소스코드

  • 각 컴포넌트는 자신의 데이터가 있으며 API로 노출합니다.
  • 언어도 다르고 데이터베이스도 다르지만 Dockerfile로 패키징하고 전체 애플리케이션은 Docker Compose 파일에서 정의합니다.

서버리스 함수

서버리스는 모두 컨테이너에 관한 것으로서 개발자가 함수 코드를 작성하고 서비스에 푸시하면 서비스가 코드를 빌드하고 패키징하는 것이 목적입니다.

컨슈머가 함수를 사용할 때 서비스가 함수의 인스턴스를 시작해서 요청을 처리합니다. 빌드 서버, 파이프라인, 프로덕션 서버도 없으며 플랫폼이 모두 관리합니다.

이면에서 모든 클라우드 서버리스 옵션은 도커로 코드를 패키징하고 컨테이너에서 함수를 실행합니다. 다만 서버리스는 개방 표준이 아니라서 AWS lamda 함수가 Azure 클라우드에서 호환되지 않습니다.

도커를 실행하는 서버들의 클러스터는 모든 타입의 애플리케이션을 아키텍처와 기술 스택과 상관없이 빌드, 배포, 관리 실행할 수 있습니다.

2. Hello world

Hello World 컨테이너 실행

docker container run diamol/ch02-hello-diamol

컨테이너는 앱이 들어있는 박스로서 자체의 이름, IP 주소, 디스크 등이 있습니다. 이들은 도커가 만든 가상 리소스이며 애플리케이션이 실행한 환경과 함께 하는 논리적 객체들입니다.

컨테이너 내부의 앱은 외부를 볼 수 없지만 컨테이너는 한 컴퓨터에서 실행하며 컴퓨터는 여러 개의 컨테이너를 실행 할 수 있습니다.

컨테이너에 연결

docker container run --interactive --tty diamol/base
  • --interactive는 컨테이너와 연결을 설정
  • --tty는 컨테이너에 터미널로 연결

실행중인 컨테이너 리스트

docker container ls

컨테이너 id 이름은 컨테이너 내부에서 hostname과 같습니다.

실행 중인 프로세스

docker container top f1

로그

docker container logs f1345def

상세 정보

docker container inspect f1345def

컨테이너로 웹사이트 운영

모든 컨테이너 목록

docker container ls --all

컨테이너에서 웹사이트를 실행

docker container run --detach --publish 8088:80 diamol/ch02-hello- diamol-web

docker container ls
  • --detach 백그라운드에서 컨테이너를 시작
  • --publish 사용할 포트를 공개

분리된 컨테이너는 백그라운드에서 데몬처럼 실행을 시작합니다.
컨테이너에서 공개한 포트로 들어오는 요청을 내부의 포트로 보낼 수 있습니다.

브라우저에서 확인

http://localhost:8088

컨테이너가 사용 중인 리소스를 확인

docker container stats e35

모든 컨테이너를 삭제

docker container rm --force $(docker container ls --aq)

도커가 컨테이너를 실행하는 방법

  • 도커 엔진은 로컬 이미지 캐시 역할을 하며 컨테이너, 가상 네트워크와 모든 도커 리소스를 만듭니다.

  • 도커 엔진의 모든 기능은 도커 API로 접근할 수 있으며 도커 CLI는 도커 API의 클라이언트입니다.

3. 이미지 빌드

3.1 docker hub의 이미지 사용하기

ping을 하는 이미지 내려받기

docker image pull diamol/ch03-web-ping

이미지 실행하기

docker container run --detach ---name web-ping diamol/ch03-web-ping
docker container logs web-ping

컨테이너 삭제 후 새로운 환경변수로 재 실행

--detach 플래그 없이 환경변수를 설정하고 실행합니다. ctrl+c를 누르면 실행을 멈춥니다.

docker container rm -f web-ping
docker container run --env TARGET=google.com diamol/ch03-web-ping

3.2 Dockerfile 작성하기

Dockerfile은 애플리케이션을 패키징하는 스크립트 파일로서 이미지를 만듭니다.

 FROM diamol/node
 ENV TARGET="blog.sixeyed.com"
 ENV METHOD="HEAD"
 ENV INTERVAL="3000"
 WORKDIR /web-ping
 COPY app.js .
 CMD ["node", "/web-ping/app.js"]
  • FROM 모든 이미지는 다른 이미지로부터 시작해야 합니다.
  • ENV 환경 변수
  • WORKDIR 이미지 파일시스템에서 디렉토리를 만들고 현재 디렉토리로 설정
  • COPY 로컬 파일시스템에서 이미지로 파일을 복사
  • CMD 컨테이너가 시작될 때 실행할 명령

ch03/exercises/web-ping에 다음의 파일이 존재합니다.

  • Dockerfile

  • app.js

  • README.md

    컨테이너 이미지 빌드

    docker image build --tag web-ping .
    docker image ls 'w*'
  • --tag는 이미지 이름이고

  • .은 Dockerfile이 있는 디렉토리를 지정합니다. 이 디렉토리를 context라고 합니다.

    5초마다 ping하도록 실행

docker container run -e TARGET=docker.com -e INTERVAL=5000 web-ping

3.3 이미지 레이어

이미지는 패키징한 모든 파일과 이미지 자체에 대한 메타데이터를 포함합니다. 여기에는 이미지가 어떻게 빌드되었는지 간단한 히스토리도 들어있습니다.

web-ping 이미지 히스토리

docker image history web-ping

이미지는 이미지 레이어 컬렉션입니다. 이미지 레이어들은 도커 엔진의 캐시에 저장되어 있으며 다른 이미지와 컨테이너들을 서로 공유할 수 있습니다.

이미지 리스트와 실제 디스크 용량

docker image ls

여기에는 3개의 diamol/node가 있습니다.
모든 node.js 이미지의 크기는 대략 75MB 정도됩니다. 이들은 베이스 이미지를 공유하지만 각자 75MB씩 차지하고 있는것을 보여줍니다.

docker system df

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          3         0         130.1MB   130.1MB (100%)
Containers      0         0         0B        0B
Local Volumes   0         0         0B        0B
Build Cache     310       0         7.566GB   7.566GB

도커가 실제 사용하는 디스크 공간은 훨씬 적습니다.

이미지 레이어가 공유되고 있다면 변경될 수 없습니다. 한 이미지가 수정되면 이를 공유하는 다른 이미지 레이어에도 연쇄적인 수정이 일어나기 때문입니다, 도커는 이미지 레이어를 읽기 전용으로 강제합니다. 여러분이 이미지를 빌드하여 레이어를 만들면 그 레이어는 다른 이미지가가 공유할 수 있지만 변경될 수 없습니다.

3.4 이미지 레이어 캐시로 Dockerfile 최적화

web-ping 이미지의 레이어에는 자바스크립트 파일이 들어있습니다.
파일을 수정하여 빌드하면 새로운 이미지 레이어가 생깁니다. 도커는 이미지에 있는 레이어는 정의된 순서를 따른다고 가정하므로 중간의 레이어가 바뀌면 그 이후의 레이어를 재사용할 수 있다고 가정하지 않습니다.

app.js 파일을 수정하고 이미지를 새로 빌드

docker image build -t web-ping:v2 .

매번의 Dockerfile 명령마다 이미지 레이어를 만들지만 빌드마다 명령이 수정이 없고 명령으로 들어가는 콘텐츠가 같다면 도커는 캐시에 있는 이전 레이어를 사용할 수 있다는 것을 알고 있습니다. 이것은 Dockerfile 명령을 다시 실행하고 중복된 레이어를 생성하는 것을 막아줍니다. 입력이 같으면 출력도 같습니다.

자주 변경되는 명령은 뒤쪽에 배치하는 것이 좋습니다.

FROM diamol/node
CMD ["node", "/web-ping/app.js"]
ENV TARGET="blog.sixeyed.com" METHOD="HEAD" INTERVAL="3000"
WORKDIR /web-ping
COPY app.js .

web-ping-optimized 폴더에서 이미지 빌드

cd ../web-ping-optimized
docker image build -t web-ping:v3 .

Lab

https://github.com/sixeyed/diamol/tree/master/ch03/lab .

  • Remember that the -it flags let you run to a container interactively.

  • The filesystem for a container still exists when it is exited.

  • There are lots of commands you haven’t used yet. docker container --help will show you two that could help you solve the lab.

4. 패키징

빌드 동안에 실행되는 명령과 파일시스템의 변경 사항은 이미지 레이어에 저장됩니다. 이것이 도커 파일을 가장 유연한 패키징 포맷으로 만든 것입니다. 압축 파일을 풀거나 윈도우즈 인스톨러를 실행하는 등 많은 일들을 할 수 있습니다.

4.1 빌드 서버

프로그래밍 언어는 프로젝트 빌드를 위한 수많은 툴이 필요합니다.

빌드 도구를 패키징하여 공유하는 것이 가장 깔끔합니다. 이것이 도커가 하는 일입니다. Dockerfile에 모든 툴의 배포 스크립트를 작성한 다음 이미지를 빌드합니다. 그 이미지를 사용하여 소스코드를 컴파일한 출력이 패키징된 애플리케이션입니다.

다음의 Dockerfile 파일에는 build, test 등 3개의 스테이지가 있습니다. 각 스테이지는 FROM으로 시작하며 AS로 스테이지 이름을 정할 수 있습니다.

출력은 최종 스테이지가 만든 도커 이미지입니다.

FROM diamol/base AS build-stage
RUN echo 'Building...' > /build.txt

FROM diamol/base AS test-stage 
COPY --from=build-stage /build.txt /build.txt
RUN echo 'Testing...' >> /build.txt
 
FROM diamol/base
COPY --from=test-stage /build.txt /build.txt
CMD cat /build.txt

각 스테이지는 독립적으로 실행하지만 이전 스테이지의 파일과 디렉토리를 복사합니다. COPY는 앞에 스테이지의 파일들을 복사합니다. 각 스테이지에서 파일을 만들고 이를 최종 스테이지에 복사하였습니다.

RUN은 파일을 작성합니다. 이 명령은 컨테이너 내부에서 실행하고 출력을 이미지 레이어에 저장합니다. RUN에서 사용하는 명령은 FROM에서 가져온 이미지에서 사용 가능해야 합니다.

각 스테이지는 격리됩니다. 스테이지마다 다른 이미지를 사용할 수 있으며 최종 스테이지의 출력만 앞 스테이지에서 복사한 것을 포함합니다.

멀티 스테이지로 이미지 빌드

docker image build -t multi-stage .

스프링 애플리케이션 구축

SpringBoot 애플리케이션을 maven, openjdk를 사용하여 빌드하고 실행합니다.

이미지 빌드

cd ch04/exercises/image-of-the-day
docker image build -t image-of-the-day .

Dockerfile입니다.

FROM diamol/maven AS builder
 
WORKDIR /usr/src/iotd
COPY pom.xml .
RUN mvn -B dependency:go-offline
 
COPY . .
RUN mvn package

builder 스테이지가 완료되면 컴파일된 애플리케이션은 builder 스테이지의 파일 시스템에 존재합니다. builder 스테이지가 성공하면 최종 스테이지에서 애플리케이션 이미지를 생성합니다.

# app
FROM diamol/openjdk
 
WORKDIR /app
COPY --from=builder /usr/src/iotd/target/iotd-service-0.1.0.jar .
 
EXPOSE 80
ENTRYPOINT ["java", "-jar", "/app/iotd-service-0.1.0.jar"]

앱은 NASA로부터 오늘의 이미지에 관한 정보를 가져와 캐싱하여 제공합니다.

컨테이너는 가상 네트워크를 통해 접근하므로 도커가 컨테이너를 만들 때 할당하는 가상 IP 주소를 사용합니다.

도커 네트워크 만들기

docker network create nat

이제는 컨테이너를 실행하고 --network로 도커 네트워크에 연결할 수 있습니다. 네크워크에 있는 어떤 컨테이너와도 서로 접근이 가능합니다.

컨테이너를 실행하고 도커 네트워크에 연결

docker container run --name iotd -d -p 8000:80 --network nat image-of-the-day

NASA의 사진에 대한 정보를 얻을 수 있습니다.

http://localhost:8000/images

빌드 도구는 최종의 애플리케이션 이미지의 일부가 아닙니다. 최종 스테이지의 컨텐츠만 애플리케이션 이미지로 만들어집니다.

Nodejs 소스 코드를 빌드

The source code for this app is at the folder path ch04/exercises/access-log.

 FROM diamol/node AS builder
 
 WORKDIR /src
 COPY src/package.json .
 
 RUN npm install
 
 # app
 FROM diamol/node
 
 EXPOSE 80
 CMD ["node", "server.js"]
 
 WORKDIR /app
 COPY --from=builder /src/node_modules/ /app/node_modules/
 COPY src/ .

이미지 빌드

cd ch04/exercises/access-log
docker image build -t access-log .

컨테이너 실행하고 도커 네트워크에 연결

docker container run --name accesslog -d -p 8001:80 --network nat access-log
http://localhost:8001/stats

멀티 스테이지 Dockerfile

멀티 스테이지 Dockerfile이 컨테이너에서 앱을 빌드할 때 분명한 장점은 다음과 같습니다.

  • 표준화입니다.
  • 각 스테이지 마다 자체의 캐싱을 사용하므로 성능이 우수합니다.
  • 빌드를 상세 조정하여 이미지를 가능한 작게 만들 수 있습니다.

연습 lab

  • 기존의 Dockerfile을 사용하여 이미지를 빌드한 다음 이 파일을 최적화하여 새로운 이미지를 만듭니다

  • 현재의 리눅스 상의 이미지 800MB를 15MB 정도로 만듭니다

  • Dockerfile을 최적화하면 HTML을 수정하더라도 한 스텝만 실행되어야 합니다.

https://github.com/sixeyed/diamol/blob/master/ch04/lab/Dockerfile.optimized


5. Docker Hub

5.1 레지스트리, 저장소와 이미지 태그

소프트웨어 배포는 도커 플랫폼에서 구축됩니다. 이미지를 저장한 서버를 도커 레지스트리라고 합니다. 도커 허브는 일종의 이미지 레지스트리입니다.

도커 이미지 이름은 네 가지 부분이 있습니다.

  • 레지스트리의 도메인
  • 소유자 계정
  • 저장소 이름. 애플리케이션 이름으로 사용
  • 태그. 디폴트는 latest

5.2 Docker Hub에 이미지 푸시

dockerId를 변수에 설정

# using Bash on Linux or Mac
export dockerId="<your-docker-id-goes-here>"

로그인

docker login --username $dockerId

Password: 
Login Succeeded

빌드 이미지

docker image build -t image-gallery .
docker image tag image-gallery $dockerId/image-gallery:v1

docker image ls --filter reference=image-gallery --filter reference='*/image-gallery'
REPOSITORY             TAG       IMAGE ID       CREATED         SIZE
image-gallery          latest    99fabd0edb31   3 minutes ago   26.2MB
goalps/image-gallery   v1        99fabd0edb31   3 minutes ago   26.2MB

이미지 레퍼런스를 푸시

docker image push $dokcerId/image-gallery

도커 레지스트리는 로컬 도커 엔진과 같은 방식으로 이미지 레이어 수준에서 작동합니다. 이미지를 푸시하면 도커는 실제로 이미지 레이어들을 업로드합니다.

푸시한 이미지의 위치

https://hub.docker.com/r/$dockerId/image-gallery/tags

5.3 도커 레지스트리를 사용

여러분의 레지스트리를 로컬 네트워크에서 실행하는 것이 실용적입니다. 도커는 Github 상의 docker/distribution에서 코어 레지스트리 서버를 운영합니다. 이미지 push와 pull 기능을 제공하며 도커 허브처럼 레이어 캐시 시스템을 사용하지만 웹 UI는 제공하지 않습니다.

이미지를 사용해서 컨테이너에서 도커 레지스트리를 실행

로컬 시스템에 레지스트리 서버를 만듭니다. 도커 재시작할 때마다 컨테이너가 재시작하도록 레지스트리를 실행합니다.

docker container run -d -p 5000:5000 --restart always diamol/registry

컴퓨터의 별칭으로 registry.name을 추가합니다.

# using Bash on Linux or Mac
echo $'\n127.0.0.1 registry.local' | sudo tee -a /etc/hosts

ping registry.local

레지스트리 도메인으로 image-gallery를 태그

docker image tag image-gallery registry.local:5001/gallery/ui:v1

레지스트리에 이미지 푸시

docker image push registry.local:5000/gallery/ui:v1

5.4 이미지 태그

임의의 문자열을 Docker 이미지 태그에 넣을 수 있으며 동일한 이미지에 대해 여러 태그를 가질 수 있습니다. 이를 사용하여 버전을 지정하고 사용자가 사용하려는 항목에 대해 정보에 입각한 선택을 하도록 하고 다른 사람의 이미지를 사용할 때 정보에 입각한 선택을 할 수 있습니다.

버전 관리 체계를 이미지 태그에 적용할 수 있습니다.

기본 아이디어는 [major].[minor].[patch] 와 같이

  • patch 릴리스에는 버그 수정이 있을 수 있지만 동일한 기능을 가져야 합니다.
  • minor 버전은 기능을 추가할 수 있지만 제거해서는 안 됩니다.
  • major 릴리스는 완전히 다른 기능을 가질 수 있습니다.

이미지 태그에 동일한 접근 방식을 사용하는 경우 사용자가 major 또는 minor 버전을 유지할지 또는 항상 최신 릴리스를 유지할지 선택할 수 있습니다.

이미지에 패키징한 Go 애플리케이션에 대한 몇 가지 새 태그를 만들어 major, minor 및 patch 릴리스 버전을 표시합니다.

docker image tag image-gallery registry.local:5000/gallery/ui:latest
docker image tag image-gallery registry.local:5000/gallery/ui:2
docker image tag image-gallery registry.local:5000/gallery/ui:2.1
docker image tag image-gallery registry.local:5000/gallery/ui:2.1.106

이제 버전이 증가하는 월별 릴리스가 있다고 상상해 보십시오. 그림 5.8은 이미지 태그가 7월부터 10월까지 릴리스에 따라 어떻게 발전할 수 있는지 보여줍니다.

이 이미지 태그 중 일부가 움직이는 대상임을 알 수 있습니다. gallery/ui:2.1은 7월의 2.1.106 릴리스의 별칭이지만 8월의 동일한 2.1 태그는 2.1.114 릴리스의 별칭입니다. gallery/ui:2는 7월에 2.1.106의 별칭이기도 하지만 9월에는 2 태그가 2.2.11 릴리스의 별칭이 됩니다. 최신 태그가 가장 많이 움직입니다. 7월의 gallery/ui는 2.1.106의 별칭이지만 10월의 경우 3.0.42의 별칭입니다.

이것은 Docker 이미지에 대해 볼 수 있는 일반적인 버전 관리 체계입니다. 이미지 사용자가 원하는 최신 정보를 선택할 수 있기 때문에 스스로 채택해야 합니다. image pull 명령 또는 Dockerfile의 FROM에서 특정 패치 버전에 고정할 수 있으며 사용하는 이미지가 항상 동일한지 확인할 수 있습니다. 이 예에서 2.1.106 태그는 7월부터 10월까지의 동일한 이미지입니다. 패치 업데이트를 원하면 2.1 태그를 사용할 수 있고 minor 릴리스를 원하면 2 태그를 사용할 수 있습니다.

그림 5.8 소프트웨어 릴리스 중 이미지 태그의 진화

그 어떤 선택도 괜찮습니다. 특정 patch 버전을 사용하면 애플리케이션을 사용할 때마다 동일하게 유지되지만 보안 수정 사항은 적용되지 않는다는 의미입니다. major 버전을 사용하면 모든 최신 수정 사항을 얻을 수 있지만 예상하지 못한 기능 변경이 있을 수 있습니다.

자체 Dockerfile의 기본 이미지에 대해 특정 이미지 태그를 사용하는 것이 특히 중요합니다. 제품 팀의 빌드 도구 이미지를 사용하여 앱을 빌드하고 런타임 이미지를 사용하여 앱을 패키징하는 것은 좋지만 태그에 버전을 지정하지 않으면 나중에 문제가 발생할 수 있습니다. 빌드 이미지의 새 릴리스는 Docker 빌드를 중단할 수 있습니다. 또는 더 나쁜 것은 런타임의 새 릴리스로 인해 애플리케이션이 중단될 수 있다는 것입니다.

5.5 공식 이미지를 golden 이미지로 전환하기

누구나 Docker Hub에 이미지를 푸시하고 공개적으로 사용할 수 있습니다. 해커에게 이는 악성코드를 배포하는 좋은 방법입니다. 이미지에 순진한 이름과 가짜 설명을 제공하고 사람들이 사용하기 시작할 때까지 기다리기만 하면 됩니다. Docker Hub는 검증된 게시자 및 공식 이미지로 이 문제를 해결합니다.

확인된 게시자는 Docker Hub에 이미지를 게시하는 Microsoft, Oracle 및 IBM과 같은 회사입니다. 그들의 이미지는 취약점에 대한 보안 검색을 포함하는 승인 프로세스를 거칩니다. 그들은 또한 인증을 받았을 수 있습니다. 즉, Docker와 게시자의 지원이 있습니다. 컨테이너에서 기성 소프트웨어를 실행하려면 검증된 게시자의 인증된 이미지가 가장 좋습니다.

공식 이미지는 다릅니다. 일반적으로 프로젝트 팀과 Docker가 공동으로 유지 관리하는 오픈 소스 프로젝트입니다. 보안 검사를 받고 정기적으로 업데이트되며 Dockerfile 모범 사례를 따릅니다. 공식 이미지의 모든 콘텐츠는 오픈 소스이므로 GitHub에서 Dockerfile을 볼 수 있습니다. 대부분의 사람들은 공식 이미지를 자신의 이미지 기반으로 사용하기 시작하지만 어느 시점에서 더 많은 제어가 필요하다는 것을 알게 됩니다. 그런 다음 그들은 골든 이미지라고 하는 기본 이미지를 소개합니다. 그림 5.9는 작동 방식을 보여줍니다.

그림 5.9 공식 이미지를 내장한 golden 이미지

Golden 이미지는 공식 이미지를 기본으로 사용한 다음 보안 인증서 설치 또는 기본 환경 설정 구성과 같이 필요한 사용자 지정 설정을 추가합니다. Golden 이미지는 Docker Hub의 리포지토리 또는 자체 레지스트리에 있으며 모든 애플리케이션 이미지는 golden 이미지를 기반으로 합니다. 이 방식은 프로젝트 팀의 모범 사례 설정과 함께 공식 이미지의 이점을 제공하지만 필요한 추가 구성이 있습니다.

TRY 이 장의 소스 코드에는 .NET Core 앱용 골든 이미지로 빌드할 수 있는 두 개의 Dockerfile이 있습니다. 각 폴더를 찾아 이미지를 빌드합니다.

cd ch05/exercises/dotnet-sdk
docker image build -t golden/dotnetcore-sdk:3.0 .
 
cd ../aspnet-runtime
docker image build -t golden/aspnet-core:3.0 .

golden 이미지는 고유한 참조 및 명명 체계로 이미지를 빌드합니다. 빌드한 Dockerfile을 보면 LABEL을 사용하여 이미지에 일부 메타데이터를 추가하고 몇 가지 공통 구성을 설정하는 것을 볼 수 있습니다. 이제 .NET Core 애플리케이션용 다단계 Dockerfile에서 해당 이미지를 사용할 수 있습니다. 이는 목록 5.1과 비슷합니다.

FROM golden/dotnetcore-sdk:3.0 AS builder
COPY . .
RUN dotnet publish -o /out/app app.csproj
 
FROM golden/aspnet-core:3.0
COPY --from=builder /out /app
CMD ["dotnet", "/app/app.dll"]

애플리케이션 Dockerfile은 모든 다단계 빌드와 형식이 동일하지만 이제 기본 이미지를 소유하게 되었습니다. 공식 이미지는 매달 새 릴리스가 있을 수 있지만 golden 이미지를 분기별 업데이트로 제한하도록 선택할 수 있습니다.

그리고 golden 이미지는 또 다른 가능성을 열어줍니다. CI 파이프라인의 도구와 함께 사용을 강제할 수 있습니다. Dockerfile을 스캔할 수 있으며 누군가 golden 이미지를 사용하지 않고 앱을 빌드하려고 하면 빌드가 실패합니다. 팀이 사용할 수 있는 소스 이미지를 잠그는 좋은 방법입니다.

5.6 Lab

이 실험실은 약간의 탐정 작업이 필요하지만 결국 가치가 있을 것입니다. REST API가 로컬 Docker 레지스트리와 상호 작용할 수 있는 유일한 방법이기 때문에 [Docker Registry API v2 사양](https://docs .docker.com/registry/spec/api/)을 조사해야 합니다.
(아직은 Docker CLI를 사용하여 이미지를 검색하거나 삭제할 수 없습니다.)

이 실습의 목표는 gallery/ui 이미지에 대한 모든 태그를 로컬 레지스트리에 푸시하고 태그가 모두 있는지 확인한 다음 모두 삭제하고 없어졌는지 확인하는 것입니다. 이 실습은 여러 태그가 있는 이미지에 중점을 두고 있으며 gallery/ui 에 대한 이미지가 있으므로 gallery/api 또는 gallery/logs 이미지는 포함하지 않습니다.

다음은 몇 가지 힌트입니다.

  • 이미지 푸시 명령으로 모든 태그를 푸시할 수 있습니다.
  • 로컬 레지스트리 API의 URL은 http://registry.local:5000/v2 입니다.
  • 저장소의 이미지 태그를 나열하여 시작합니다.
  • 이미지 매니페스트를 가져와야 합니다.
  • API를 통해 이미지를 삭제할 수 있지만 매니페스트를 사용해야 합니다.
  • HEAD 요청에 사용해야 하는 특정 요청 헤더가 있습니다.

해결책은 책의 GitHub 리포지토리에 있으며, 이것은 약간의 속임수를 사용해도 괜찮은 드문 경우입니다.

https://github.com/sixeyed/diamol/tree/ master/ch05/lab .


6 Docker 볼륨 사용

컨테이너는 상태 비저장 애플리케이션을 위한 완벽한 런타임입니다. 모든 컨테이너가 동일한 방식으로 요청을 처리할 것임을 알고 클러스터에서 여러 컨테이너를 실행하여 증가하는 수요를 충족할 수 있습니다. 앱을 항상 온라인 상태로 유지하는 자동 롤링 업그레이드로 업데이트를 출시할 수 있습니다.

그러나 앱의 모든 부분이 상태 비저장이 되는 것은 아닙니다. 성능을 향상시키거나 영구 데이터 저장을 위해 디스크를 사용하는 구성 요소가 있습니다. 그리고 Docker 컨테이너에서도 이러한 구성 요소를 실행할 수 있습니다.

스토리지는 복잡성을 추가하므로 상태 저장 앱을 도커화 하는 방법을 이해해야 합니다. 이 장에서는 Docker 볼륨 및 마운트를 안내하고 컨테이너 파일 시스템이 어떻게 작동하는지 보여줍니다.

6.1 컨테이너의 데이터가 영구적이지 않은 이유

Docker 컨테이너에는 파일 시스템이 있으며 해당 드라이브의 내용은 이미지의 파일로 채워집니다. 도커 이미지는 여러 레이어로 저장되므로 컨테이너의 디스크는 실제로 Docker가 모든 이미지 레이어를 병합하여 구축하는 가상 파일 시스템입니다.

각 컨테이너에는 자체 파일 시스템이 있습니다. 동일한 도커 이미지에서 여러 컨테이너를 실행할 수 있으며 모두 동일한 디스크 내용으로 시작됩니다. 애플리케이션은 한 컨테이너에 있는 파일을 변경할 수 있으며 다른 컨테이너나 이미지에 있는 파일에는 영향을 미치지 않습니다. 데이터를 쓰는 두 개의 컨테이너를 실행한 다음 출력을 보면 쉽게 알 수 있습니다.

TRY 동일한 이미지에서 두 개의 컨테이너를 실행하십시오. 이미지의 앱은 컨테이너의 파일에 임의의 숫자를 씁니다.

docker container run --name rn1 diamol/ch06-random-number

docker container run --name rn2 diamol/ch06-random-number

해당 컨테이너는 시작될 때 스크립트는 임의의 데이터를 텍스트 파일에 쓴 다음 종료합니다. 두 컨테이너는 동일한 이미지에서 시작했지만 파일 내용이 다릅니다.

컨테이너가 종료될 때 컨테이너의 파일 시스템을 삭제하지 않으며 파일과 폴더가 유지됩니다.

Docker CLI에는 컨테이너와 로컬 머신 간에 파일을 복사하는 docker container cp 명령이 있습니다. 컨테이너의 이름과 파일 경로를 지정하고 이를 사용하여 이러한 컨테이너에서 생성된 랜덤 파일을 호스트 컴퓨터로 복사하여 내용을 읽을 수 있습니다.

TRY 각 컨테이너에서 랜덤 파일의 내용을 확인합니다.

docker container cp rn1:/random/number.txt n1.txt
docker container cp rn2:/random/number.txt n2.txt
cat n1.txt
cat n2.txt

각 컨테이너는 /random/number.txt라는 파일을 작성했지만 내용이 다른 것을 알 수 있습니다. 이것은 모든 컨테이너에 독립적인 파일 시스템이 있음을 보여줍니다. 서로 다른 파일이지만 동일한 SQL 엔진을 실행하면서 시작하지만 완전히 다른 데이터를 저장하는 데이터베이스 컨테이너일 수 있습니다.

컨테이너 내부의 파일 시스템은 디스크로 나타납니다. Linux 컨테이너의 경우 /dev/sda1입니다. 그러나 해당 디스크는 Docker가 여러 소스에서 빌드하고 단일 단위로 컨테이너에 제공하는 가상 파일 시스템입니다. 해당 파일 시스템의 기본 소스는 컨테이너 간에 공유할 수 있는 이미지 레이어와 각 컨테이너에 고유한 컨테이너의 쓰기 가능 레이어입니다.

그림 6.2는 난수 이미지와 두 개의 컨테이너를 찾는 방법을 보여줍니다. 이미지 레이어는 공유되므로 읽기 전용이어야 하고, 컨테이너당 쓰기 가능한 레이어가 하나씩 있으며 컨테이너와 수명 주기가 같습니다. 이미지 레이어에는 자체 수명 주기가 있습니다. 가져오는 모든 이미지는 제거할 때까지 로컬 캐시에 유지됩니다. 그러나 컨테이너 쓰기 가능 계층은 컨테이너가 시작될 때 Docker에 의해 생성되고 컨테이너가 제거되면 Docker에 의해 삭제됩니다. (컨테이너를 중지해도 컨테이너의 파일 시스템은 여전히 존재합니다.)

그림 6.2 컨테이너 파일 시스템은 이미지 레이어와 쓰기 가능한 레이어로 구성됩니다.

물론 writeable 계층은 새 파일을 생성하기 위한 것만은 아닙니다. 컨테이너는 이미지 레이어에서 기존 파일을 편집할 수 있습니다. 그러나 이미지 레이어는 읽기 전용이므로 Docker는 이를 가능하게 하는 몇 가지 특별한 마법을 수행합니다. copy-on-write 프로세스를 사용하여 읽기 전용 레이어에서 가져온 파일을 편집할 수 있습니다. 컨테이너가 이미지 계층에서 파일을 편집하려고 하면 Docker는 실제로 해당 파일의 복사본을 쓰기 가능한 계층으로 만들고 편집은 그곳에서 발생합니다. 컨테이너와 애플리케이션에 모두 원활하지만 Docker가 스토리지를 매우 효율적으로 사용하기 위한 초석입니다.

간단한 연습에서는 이미지 레이어에서 파일의 내용을 인쇄하는 컨테이너를 실행할 것입니다. 그런 다음 파일 내용을 업데이트하고 컨테이너를 다시 실행하여 변경된 내용을 확인합니다.

TRY 다음 명령을 실행하여 파일 내용을 인쇄하는 컨테이너를 시작한 다음 파일을 변경하고 컨테이너를 다시 시작하여 새 파일 내용을 확인하십시오.

docker container run --name f1 diamol/ch06-file-display
https://www.manning.com/books/learn-docker-in-a-month-of-lunches

이번에는 Docker를 사용하여 호스트 컴퓨터에서 컨테이너로 파일을 복사하고 대상 경로는 컨테이너가 표시하는 파일입니다. 컨테이너를 다시 시작하면 동일한 스크립트가 실행되지만 이제 다른 내용을 보여줍니다.

echo "http://eltonstoneman.com" > url.txt 
docker container cp url.txt f1:/input.txt 
docker container start --attach f1

그림 6.3에서 내 출력을 볼 수 있습니다.

http://eltonstoneman.com

그림 6.3 컨테이너 상태 수정 및 재실행

컨테이너에서 파일을 수정하면 해당 컨테이너가 실행되는 방식에 영향을 미치지만 이미지나 해당 이미지의 다른 컨테이너에는 영향을 미치지 않습니다. 변경된 파일은 해당 컨테이너의 writeable 레이어에만 있습니다. 새 컨테이너는 이미지의 원래 내용을 사용하고 컨테이너 f1이 제거되면 업데이트된 파일이 사라집니다.

TRY 새 컨테이너를 시작하여 이미지의 파일이 변경되지 않았는지 확인합니다. 그런 다음 원래 컨테이너를 제거하고 데이터가 없어졌는지 확인합니다.

docker container run --name f2 diamol/ch06-file-display
docker container rm -f f1
docker container cp f1:/input.txt .

그림 6.4에서 내 것과 동일한 출력을 볼 수 있습니다. 새 컨테이너는 이미지의 원본 파일을 사용하며 원본 컨테이너를 제거하면 해당 파일 시스템이 제거되고 변경된 파일은 영원히 사라집니다.

Error: No such container:path: f1:/input.txt

그림 6.4 컨테이너의 파일 수정은 이미지에 영향을 미치지 않으며 컨테이너의 데이터는 일시적입니다.

컨테이너 파일 시스템은 컨테이너와 동일한 수명 주기를 가지므로 컨테이너가 제거되면 writeable 계층이 제거되고 컨테이너에서 변경된 모든 데이터가 손실됩니다. 프로덕션에서는 새 이미지를 빌드하고, 이전 컨테이너를 제거하고, 업데이트된 이미지의 새 컨테이너로 교체하여 앱을 업그레이드합니다. 원래 앱 컨테이너에 작성된 모든 데이터는 손실되고 대체 컨테이너는 이미지의 정적 데이터로 시작됩니다.

애플리케이션이 일시적인 데이터만 쓰기 때문에(계산하거나 검색하는 데 비용이 많이 드는 데이터의 로컬 캐시를 유지하기 위해) 대체 컨테이너가 빈 캐시로 시작하는 것이 좋기 때문에 괜찮은 시나리오가 있습니다. 어떤 경우에는 재앙이 될 것입니다. 컨테이너에서 데이터베이스를 실행할 수 있지만 업데이트된 데이터베이스 버전을 출시할 때 모든 데이터가 손실될 것으로 예상하지는 않습니다.

Docker는 이러한 시나리오도 다루었습니다. 컨테이너의 VFS는 항상 이미지 레이어와 쓰기 가능한 레이어로 구성되지만 소스를 추가할 수 있습니다. Docker 볼륨 및 마운트입니다. 컨테이너와 별도의 수명 주기가 있으므로 컨테이너 교체 간에 지속되는 데이터를 저장하는 데 사용할 수 있습니다.

6.2 Docker 볼륨으로 컨테이너 실행

Docker 볼륨은 스토리지 유닛입니다. 컨테이너용 USB 스틱으로 생각할 수 있습니다. 볼륨은 컨테이너와 독립적으로 존재하며 자체 수명 주기가 있지만 컨테이너에 연결할 수 있습니다. 볼륨은 데이터가 지속적이어야 할 때 상태 저장 애플리케이션의 스토리지를 관리하는 방법입니다. 볼륨을 생성하여 애플리케이션 컨테이너에 연결합니다. 컨테이너의 파일 시스템에서 디렉토리로 나타납니다. 컨테이너는 실제로 볼륨에 저장된 디렉터리에 데이터를 씁니다. 앱을 새 버전으로 업데이트하면 동일한 볼륨을 새 컨테이너에 연결하고 모든 원본 데이터를 사용할 수 있습니다.

컨테이너와 함께 볼륨을 사용하는 두 가지 방법이 있습니다. 볼륨을 수동으로 생성하여 컨테이너에 연결하거나 Dockerfile에서 VOLUME 명령을 사용할 수 있습니다. 그러면 컨테이너를 시작할 때 볼륨을 생성할 이미지가 빌드됩니다. 구문은 단순히 VOLUME <target-directory> 입니다. 목록 6.1은 볼륨을 사용하는 상태 저장 앱인 diamol/ch06-todo-list 이미지에 대한 다단계 Dockerfile의 일부를 보여줍니다.

Listing 6.1 Part of a multi-stage Dockerfile using a volume

 FROM diamol/dotnet-aspnet
 WORKDIR /app
 
 ENTRYPOINT ["dotnet", "ToDoList.dll"]
 VOLUME /data
 COPY --from=builder /out/ .

이 이미지에서 컨테이너를 실행하면 Docker가 자동으로 볼륨을 생성하여 컨테이너에 연결합니다. 컨테이너는 /data 디렉터리를 가지며, 정상적으로 읽고 쓸 수 있습니다. 그러나 데이터는 실제로 볼륨에 저장되며 컨테이너가 제거된 후에도 계속 유지됩니다. 이미지에서 컨테이너를 실행한 다음 볼륨을 확인하면 알 수 있습니다.

TRY to-do-list 앱용 컨테이너를 실행하고 Docker가 생성한 볼륨을 살펴보세요.

$ docker container run --name todo1 -d -p 8010:80 diamol/ch06-todo-list
$ docker container inspect --format '{{.Mounts}}' todo1

[{volume 3c25f4652ec4b4b0f6c1c968dd66f0212d9adff6f25bb4d307c3e7553c768ce0 /var/lib/docker/volumes/3c25f4652ec4b4b0f6c1c968dd66f0212d9adff6f25bb4d307c3e7553c768ce0/_data /data local  true }]

Docker는 이 컨테이너에 대한 볼륨을 생성하고 컨테이너가 실행될 때 연결합니다.

$ docker volume ls
DRIVER    VOLUME NAME
local     3c25f4652ec4b4b0f6c1c968dd66f0212d9adff6f25bb4d307c3e7553c768ce0

그림 6.5 Dockerfile에 선언된 볼륨으로 컨테이너 실행

Docker 볼륨은 컨테이너에서 실행되는 앱에 완전히 투명합니다. http://localhost:8010으로 이동하면 to-do-list 앱이 표시됩니다. 앱은 /data 디렉터리의 파일에 데이터를 저장하므로 웹 페이지를 통해 항목을 추가하면 Docker 볼륨에 저장됩니다. 그림 6.6은 실행 중인 앱을 보여줍니다. 이것은 나와 같은 작업 부하를 가진 사람들에게 매우 잘 작동하는 특별한 할 일 목록입니다. 항목을 추가할 수 있지만 제거할 수는 없습니다.


그림 6.6 Docker 볼륨을 사용하여 컨테이너에서 계속 실행되는 할 일 목록

이미지에 선언된 볼륨은 컨테이너별로 별도의 볼륨으로 생성되지만 컨테이너 간에 볼륨을 공유할 수도 있습니다. to-do-list 앱을 실행하는 새 컨테이너를 시작하면 자체 볼륨이 있고 할 일 목록이 비어있는 상태로 시작합니다. 그러나 다른 컨테이너의 볼륨을 연결하는 volume-from 플래그를 사용하여 컨테이너를 실행할 수 있습니다. 이 예에서는 동일한 데이터를 공유하는 두 개의 to-do 앱 컨테이너를 가질 수 있습니다.

TRY 두 번째 to-do-list 컨테이너를 실행하고 데이터 디렉토리의 내용을 확인하십시오. 그런 다음 첫 번째 컨테이너의 볼륨을 공유하는 다른 새 컨테이너와 비교합니다.

# this new container will have its own volume
 docker container run --name todo2 -d diamol/ch06-todo-list
 
 # on Linux:
 docker container exec todo2 ls /data
 
 # on Windows:
 docker container exec todo2 cmd /C "dir C:\data"
 
 # this container will share the volume from todo1
 docker container run -d --name t3 --volumes-from todo1 diamol/ch06-todo-list
 
 # on Linux:
 docker container exec t3 ls /data
 

출력은 그림 6.7과 같을 것입니다(이 예에서는 Linux에서 실행 중입니다). 두 번째 컨테이너는 새 볼륨으로 시작하므로 /data 디렉토리는 비어 있습니다. 세 번째 컨테이너는 첫 번째 컨테이너의 볼륨을 사용하므로 원래 애플리케이션 컨테이너의 데이터를 볼 수 있습니다.

todo-list.db

그림 6.7 전용 및 공유 볼륨으로 컨테이너 실행

컨테이너 간에 볼륨을 공유하는 것은 간단하지만 원하는 것이 아닐 수 있습니다. 데이터를 쓰는 앱은 일반적으로 파일에 대한 독점적인 액세스를 기대하며 다른 컨테이너가 같은 파일을 동시에 읽고 쓰는 경우 제대로 작동하지 않거나 전혀 작동하지 않을 수 있습니다. 볼륨은 애플리케이션 업그레이드 사이의 상태를 유지하는 데 더 잘 사용되며 볼륨을 명시적으로 관리하는 것이 좋습니다. 명명된 볼륨을 생성하고 이를 애플리케이션 컨테이너의 다른 버전에 연결할 수 있습니다.

TRY 볼륨을 생성하고 to-do-list 앱 버전 1용 컨테이너에서 사용하십시오. 그런 다음 UI에 일부 데이터를 추가하고 앱을 버전 2로 업그레이드합니다. 컨테이너의 파일 시스템 경로는 운영 체제와 일치해야 하므로 복사 및 붙여넣기를 더 쉽게 하기 위해 변수를 사용하고 있습니다.

# save the target file path in a variable:
target='/data' # for Linux containers
 
# create a volume to store the data:
docker volume create todo-list
 
# run the v1 app, using the volume for app storage:
docker container run -d -p 8011:80 -v todo-list:$target --name todo-v1 diamol/ch06-todo-list
 
# add some data through the web app at http://localhost:8011
 
# remove the v1 app container:
docker container rm -f todo-v1
 
 # and run a v2 container using the same volume for storage:
docker container run -d -p 8011:80 -v todo-list:$target --name todo-v2 diamol/ch06-todo-list:v2

그림 6.8의 출력은 볼륨에 자체 수명 주기가 있음을 보여줍니다. 컨테이너가 생성되기 전에 존재하며 컨테이너를 사용하는 컨테이너가 제거될 때 유지됩니다. 새 컨테이너가 이전 컨테이너와 동일한 볼륨을 사용하기 때문에 애플리케이션은 업그레이드 간에 데이터를 보존합니다.

그림 6.8 명명된 볼륨 생성 및 이를 사용하여 컨테이너 업데이트 간에 데이터 유지

이제 http://localhost:8011로 이동하면 값비싼 크리에이티브 에이전시에서 UI를 개조한 to-do-list 앱 버전 2를 볼 수 있습니다. 그림 6.9는 이것이 이제 생산 준비가 되었음을 보여줍니다.

그림 6.9 완전히 새로운 to-do-list 앱 UI

계속 진행하기 전에 Docker 볼륨에 대해 명확히 해야 할 것이 있습니다. Dockerfile의 VOLUME 명령어와 컨테이너 실행을 위한 volume 플래그는 별도의 기능입니다. VOLUME 명령으로 빌드된 이미지는 실행 명령에 지정된 볼륨이 없는 경우 항상 컨테이너에 대한 볼륨을 생성합니다. 볼륨에는 임의의 ID가 있으므로 컨테이너가 사라진 후에 사용할 수 있지만 어떤 볼륨에 데이터가 있는지 확인할 수 있는 경우에만 가능합니다.

volume 플래그는 이미지에 지정된 볼륨이 있는지 여부에 관계없이 볼륨을 컨테이너에 탑재합니다. 이미지에 볼륨이 있는 경우 볼륨 플래그는 동일한 대상 경로에 대해 기존 볼륨을 사용하여 컨테이너에 대해 볼륨 플래그를 재정의할 수 있으므로 새 볼륨이 생성되지 않습니다. to-do-list 컨테이너에서 발생한 일입니다.

이미지에 볼륨이 지정되지 않은 컨테이너에 대해 정확히 동일한 구문을 사용하고 동일한 결과를 얻을 수 있습니다. 이미지 작성자는 VOLUME 명령을 상태 저장 응용 프로그램에 대한 안전 장치 옵션으로 사용해야 합니다. 그렇게 하면 사용자가 volume 플래그를 지정하지 않더라도 컨테이너는 항상 영구 볼륨에 데이터를 씁니다. 그러나 이미지 사용자는 기본값에 의존하지 않고 명명된 볼륨으로 작업하는 것이 좋습니다.

6.3 파일 시스템 마운트로 컨테이너 실행

볼륨은 도커가 모든 리소스를 관리하도록 하면서 스토리지의 수명 주기를 분리하는 데 적합합니다. 볼륨은 호스트에 있으므로 컨테이너에서 분리됩니다. 또한 바인딩 마운트로 컨테이너와 호스트 간에 스토리지를 공유하는 보다 직접적인 방법을 제공합니다. 바인딩 마운트는 호스트의 디렉토리를 컨테이너의 경로로 사용할 수 있도록 합니다. 바인딩 마운트는 컨테이너에 투명합니다. 컨테이너 파일 시스템의 일부인 디렉토리일 뿐입니다. 그러나 컨테이너에서 호스트 파일에 액세스할 수 있고 그 반대의 경우도 마찬가지이므로 몇 가지 흥미로운 패턴을 잠금 해제할 수 있습니다.

바인딩 마운트는 컨테이너 데이터에 대해 호스트 시스템의 파일 시스템을 명시적으로 사용할 수 있습니다. 이는 고속 SSD, 디스크 어레이 또는 분산 스토리지 시스템일 수 있습니다. 호스트에서 해당 파일 시스템에 액세스할 수 있으면 컨테이너에 사용할 수 있습니다. 이를 todo-list 앱의 데이터베이스 저장소로 사용할 수 있습니다.

TRY 호스트 컴퓨터에 로컬 디렉토리를 만들고 이를 컨테이너에 바인딩 마운트합니다. 다시 말하지만, 파일 시스템 경로는 호스트 운영 체제와 일치해야 하므로 컴퓨터의 소스 경로와 컨테이너의 대상 경로에 대한 변수를 선언했습니다.

source="$(pwd)/databases" && target='/data' # Linux
mkdir ./databases
 
docker container run --mount type=bind,source=$source,target=$target -d -p 8012:80 diamol/ch06-todo-list
 
curl http://localhost:8012
ls ./databases

이 연습에서는 curl 명령을 사용하여 todo-list 앱에 HTTP 요청을 보냅니다. 그러면 앱이 시작되어 데이터베이스 파일이 생성됩니다. 마지막 명령은 호스트에 있는 databases 디렉토리의 내용을 나열하며 응용 프로그램의 데이터베이스 파일이 실제로 호스트 컴퓨터에 있음을 보여줍니다.

todo-list.db

바인드 마운트는 양방향입니다. 컨테이너에서 파일을 생성하고 호스트에서 편집하거나 호스트에서 파일을 생성하고 컨테이너에서 편집할 수 있습니다. 여기에는 보안 측면이 있습니다. 컨테이너는 일반적으로 시스템을 악용하는 공격자의 위험을 최소화하기 위해 최소 권한 계정으로 실행되어야 하기 때문입니다. 그러나 컨테이너는 호스트에서 파일을 읽고 쓸 수 있는 상승된 권한이 필요하므로 이 이미지는 Dockerfile의 USER 명령으로 빌드되어 컨테이너에 관리 권한을 부여합니다. Linux에서는 기본 제공 루트 사용자를 사용합니다.

파일을 쓸 필요가 없다면 컨테이너 내에서 호스트 디렉터리를 읽기 전용으로 바인딩할 수 있습니다. 이것은 호스트에서 애플리케이션 컨테이너로 구성 설정을 표시하기 위한 한 가지 옵션입니다. 할 일 애플리케이션 이미지는 앱의 로깅 수준을 최소량으로 설정하는 기본 구성 파일과 함께 패키징됩니다. 동일한 이미지에서 컨테이너를 실행할 수 있지만 로컬 구성 디렉토리를 컨테이너에 탑재하고 이미지를 변경하지 않고 앱의 구성을 재정의할 수 있습니다.

TRY todo-list 애플리케이션은 /app/config 경로에서 추가 구성 파일이 있는 경우 이를 로드합니다. 로컬 디렉터리를 해당 위치에 바인딩하는 컨테이너를 실행하면 앱이 호스트의 구성 파일을 사용합니다. diamol 소스 코드의 로컬 복사본으로 이동하여 시작하고 다음 명령을 실행합니다.

cd ./ch06/exercises/todo-list
 
 # save the source path as a variable:
 $source="$(pwd)\config".ToLower(); $target="c:\app\config" # Windows
 source="$(pwd)/config" && target='/app/config'             # Linux
 
 # run the container using the mount:
 docker container run --name todo-configured -d -p 8013:80 --mount type=bind,source=$source,target=$target,readonly diamol/ch06-todo-list
 
 # check the application:
 curl http://localhost:8013
 
 # and the container logs:
 docker container logs todo-configured

그림 6.10 바인드 마운트를 사용하여 컨테이너와 호스트의 디렉토리 공유

호스트의 디렉토리에 있는 구성 파일은 훨씬 더 자세한 로깅을 사용하도록 설정됩니다. 컨테이너가 시작되면 해당 디렉토리를 매핑하고 애플리케이션은 구성 파일을 보고 로깅 구성을 로드합니다. 그림 6.11에 표시된 최종 출력에는 앱이 표준 구성으로 작성하지 않을 디버그 로그 행이 많이 있습니다.

그림 6.11 바인드 마운트를 사용하여 읽기 전용 구성 파일을 컨테이너에 로드

호스트 컴퓨터가 액세스할 수 있는 모든 소스를 바인딩 마운트할 수 있습니다. Linux 호스트에서 /mnt/nfs에 마운트되거나 Windows 호스트에서 X: 드라이브에 매핑된 공유 네트워크 드라이브를 사용할 수 있습니다. 둘 중 하나는 바인드 마운트의 소스가 될 수 있으며 동일한 방식으로 컨테이너에 표시됩니다. 컨테이너에서 실행되는 스테이트풀(Stateful) 앱을 위해 안정적이고 분산된 스토리지를 얻는 데 매우 유용한 방법이지만 이해해야 할 몇 가지 제한 사항이 있습니다.

6.4 파일 시스템 마운트의 제한 사항

바인드 마운트와 볼륨을 효과적으로 사용하려면 몇 가지 주요 시나리오와 제한 사항을 이해해야 합니다. 그 중 일부는 미묘하고 컨테이너와 파일 시스템의 비정상적인 조합에서만 나타납니다.

첫 번째 시나리오는 간단합니다. 마운트가 있는 컨테이너를 실행하고 마운트 대상 디렉토리가 이미 존재하고 이미지 계층의 파일이 있으면 어떻게 됩니까? Docker가 소스를 대상에 병합할 것이라고 생각할 수 있습니다. 컨테이너 내부에서 디렉토리에 이미지의 모든 기존 파일과 마운트의 모든 새 파일이 있는 것을 볼 수 있을 것으로 예상됩니다. 하지만 그렇지 않습니다. 이미 데이터가 있는 대상을 마운트하면 원본 디렉터리가 대상 디렉터리를 대체하므로 이미지의 원본 파일을 사용할 수 없습니다.

실행할 때 디렉토리 내용을 나열하는 이미지를 사용하여 간단한 연습을 통해 이를 확인할 수 있습니다.

TRY 마운트 없이 컨테이너를 실행하면 이미지의 디렉토리 내용이 나열됩니다. 마운트로 다시 실행하면 소스 디렉토리의 내용이 나열됩니다(Windows 및 Linux를 지원하기 위한 변수가 다시 여기에 있음).

cd ./ch06/exercises/bind-mount
 
 $source="$(pwd)\new".ToLower(); $target="c:\init" # Windows
 source="$(pwd)/new" && target='/init'             # Linux
 
 docker container run diamol/ch06-bind-mount
 
 docker container run --mount type=bind,source=$source,target=$target diamol/ch06-bind-mount

첫 번째 실행에서 컨테이너에 abc.txt 및 def.txt라는 두 개의 파일이 나열되는 것을 볼 수 있습니다. 이것들은 이미지 레이어에서 컨테이너로 로드됩니다. 두 번째 컨테이너는 대상 디렉토리를 마운트의 소스로 대체하므로 해당 파일은 나열되지 않습니다. 123.txt 및 456.txt 파일만 표시되며 이는 호스트의 소스 디렉토리에서 가져온 것입니다. 그림 6.12는 내 출력을 보여줍니다.

그림 6.12 바인드 마운트 디렉터리는 대상 디렉터리가 있는 경우 대상 디렉터리를 섀도우합니다.

두 번째 시나리오는 이에 대한 변형입니다. 호스트에서 컨테이너 파일 시스템에 있는 대상 디렉토리로 단일 파일을 마운트하면 어떻게 될까요? 이번에는 디렉터리 내용이 병합되므로 이 기능이 전혀 지원되지 않는 Windows 컨테이너를 실행하지 않는 한 이미지의 원본 파일과 호스트의 새 파일을 볼 수 있습니다.

컨테이너 파일 시스템은 Windows 컨테이너가 Linux 컨테이너와 같지 않은 몇 안 되는 영역 중 하나입니다. 어떤 것들은 같은 방식으로 작동합니다. Dockerfiles 내에서 표준 Linux 스타일 경로를 사용할 수 있으므로 /data는 Windows 컨테이너에서 작동하고 C:\data 의 별칭이 됩니다. 그러나 볼륨 마운트 및 바인드 마운트에서는 작동하지 않습니다. 이것이 이 장의 연습에서 Linux 사용자에게 /data 및 Windows C:\data를 제공하기 위해 변수를 사용하는 이유입니다.

단일 파일 마운트에 대한 제한은 보다 명시적입니다. Windows 및 Linux 시스템을 사용할 수 있거나 Linux 및 Windows 컨테이너를 모두 지원하는 Windows에서 Docker Desktop을 실행하는 경우 직접 시도할 수 있습니다.

TRY. 단일 파일 마운트의 동작은 Linux와 Windows에서 다릅니다. Linux 및 Windows 컨테이너를 사용할 수 있는 경우 작동 중인 것을 볼 수 있습니다.

cd ./ch06/exercises/bind-mount
 
# on Linux:
docker container run --mount type=bind,source="$(pwd)/new/123.txt",target=/init/123.txt diamol/ch06-bind-mount
 
# on Windows:
 
docker container run --mount type=bind,source="$(pwd)/new/123.txt",target=C:\init\123.txt diamol/ch06-bind-mount
 
docker container run diamol/ch06-bind-mount
 
docker container run --mount type=bind,source="$(pwd)/new/123.txt",target=/init/123.txt diamol/ch06-bind-mount

Docker 이미지는 동일하고 명령은 동일하며 대상에 대한 OS별 파일 시스템 경로가 다릅니다. 그러나 이것을 실행하면 Linux 예제가 예상대로 작동하지만 그림 6.13과 같이 Windows의 Docker에서 오류가 발생하는 것을 볼 수 있습니다.

그림 6.13 Bind는 단일 파일을 소스로 사용하여 Linux에서 작동하지만 Windows에서는 작동하지 않는 마운트를 합니다.

세 번째 시나리오는 덜 일반적입니다. 많은 움직이는 조각을 설정하지 않고 재생산하는 것은 매우 어렵습니다. 따라서 이것을 다루는 연습은 없을 것입니다. 제 말을 믿어야 할 것입니다. 시나리오는 분산 파일 시스템을 컨테이너에 바인딩 마운트하면 어떻게 될까요? 컨테이너의 앱이 여전히 올바르게 작동합니까? 보세요, 질문도 복잡합니다.

분산 파일 시스템을 사용하면 네트워크의 모든 시스템에서 데이터에 액세스할 수 있으며 일반적으로 운영 체제의 로컬 파일 시스템과 다른 저장 메커니즘을 사용합니다. 로컬 네트워크의 SMB 파일 공유, Azure Files 또는 클라우드의 AWS S3와 같은 기술일 수 있습니다. 이와 같은 분산 스토리지 시스템의 위치를 컨테이너에 탑재할 수 있습니다. 마운트는 파일 시스템의 정상적인 부분처럼 보이지만 동일한 작업을 지원하지 않으면 앱이 실패할 수 있습니다.

그림 6.14에는 컨테이너 스토리지에 Azure Files를 사용하여 클라우드의 컨테이너에서 Postgres 데이터베이스 시스템을 실행하려는 구체적인 예가 있습니다. Azure Files는 읽기 및 쓰기와 같은 일반적인 파일 시스템 작업을 지원하지만 앱에서 사용할 수 있는 좀 더 특이한 작업은 지원하지 않습니다. 이 경우 Postgres 컨테이너는 파일 링크를 만들려고 하지만 Azure Files는 해당 기능을 지원하지 않으므로 앱이 충돌합니다.

그림 6.14 분산 스토리지 시스템은 일반적인 파일 시스템 기능을 모두 제공하지 않을 수 있습니다.

이 시나리오는 이상값이지만 실제로 발생하면 피할 방법이 없기 때문에 알고 있어야 합니다. 바인드 마운트의 소스는 컨테이너의 앱이 기대하는 모든 파일 시스템 기능을 지원하지 않을 수 있습니다. 이것은 계획할 수 없는 일입니다. 스토리지 시스템으로 앱을 사용해 보기 전에는 알 수 없습니다. 컨테이너에 분산 저장소를 사용하려는 경우 이러한 위험을 알고 있어야 하며 분산 저장소는 로컬 저장소와 매우 다른 성능 특성을 갖는다는 점도 이해해야 합니다. 많은 디스크를 사용하는 애플리케이션은 모든 파일 쓰기가 네트워크를 통해 진행되는 분산 스토리지가 있는 컨테이너에서 실행하면 중단될 수 있습니다.

6.5 컨테이너 파일 시스템

컨테이너 옵션은 물리적 컴퓨터나 가상 머신의 스토리지와 매우 다르기 때문에 스토리지는 중요한 주제입니다. 컨테이너 파일 시스템 사용에 대한 몇 가지 모범 사례 지침과 함께 우리가 다룬 모든 내용을 종합적으로 살펴보고 마무리하겠습니다.

모든 컨테이너에는 Docker가 여러 소스에서 함께 구성하는 가상 디스크인 단일 디스크가 있습니다. Docker는 이것을 통합 파일 시스템이라고 부릅니다. 운영 체제마다 기술이 다르기 때문에 Docker가 통합 파일 시스템을 구현하는 방법은 살펴보지 않겠습니다. Docker를 설치하면 OS에 맞는 선택이 되므로 세부 사항에 대해 걱정할 필요가 없습니다.

통합 파일 시스템을 사용하면 컨테이너가 단일 디스크 드라이브를 볼 수 있고 디스크의 어느 위치에 있든 동일한 방식으로 파일 및 디렉토리를 사용할 수 있습니다. 그러나 디스크의 위치는 그림 6.15와 같이 물리적으로 다른 저장 장치에 저장할 수 있습니다.


그림 6.15 컨테이너 파일 시스템은 여러 소스의 통합으로 생성됩니다.

컨테이너 내부의 애플리케이션에는 단일 디스크가 표시되지만 이미지 작성자 또는 컨테이너 사용자는 해당 디스크의 소스를 선택합니다. 컨테이너에는 여러 이미지 레이어, 여러 볼륨 마운트 및 여러 바인딩 마운트가 있을 수 있지만 항상 쓰기 가능한 단일 레이어가 있습니다. 다음은 저장소 옵션을 사용하는 방법에 대한 몇 가지 일반적인 지침입니다.

  • 쓰기 가능 계층 -- 네트워크 호출이나 계산을 절약하기 위해 디스크에 데이터를 캐싱하는 것과 같은 단기 저장에 적합합니다. 이것들은 각 컨테이너에 고유하지만 컨테이너가 제거되면 영원히 사라집니다.

  • 로컬 바인드 마운트 --호스트와 컨테이너 간에 데이터를 공유하는 데 사용됩니다. 개발자는 바인드 마운트를 사용하여 컴퓨터의 소스 코드를 컨테이너에 로드할 수 있으므로 HTML 또는 JavaScript 파일을 로컬로 편집할 때 새 이미지를 빌드할 필요 없이 변경 내용이 즉시 컨테이너에 적용됩니다.

  • 분산 바인드 마운트 --네트워크 스토리지와 컨테이너 간에 데이터를 공유하는 데 사용됩니다. 이것들은 유용하지만 네트워크 스토리지는 로컬 디스크와 동일한 성능을 갖지 않으며 전체 파일 시스템 기능을 제공하지 않을 수 있음을 알아야 합니다. 구성 데이터 또는 공유 캐시에 대한 읽기 전용 소스로 사용하거나 동일한 네트워크의 모든 시스템에 있는 모든 컨테이너에서 사용할 수 있는 데이터를 저장하기 위해 읽기-쓰기로 사용할 수 있습니다.

  • 볼륨 마운트 -- 컨테이너와 Docker에서 관리하는 스토리지 객체간에 데이터를 공유하는 데 사용됩니다. 이는 애플리케이션이 볼륨에 데이터를 쓰는 영구 저장소에 유용합니다. 새 컨테이너로 앱을 업그레이드하면 이전 버전에서 볼륨에 쓴 데이터가 유지됩니다.

  • 이미지 레이어 -- 컨테이너의 초기 파일 시스템을 나타냅니다. 레이어가 스택되어 최신 레이어가 이전 레이어를 재정의하므로 Dockerfile 시작 부분의 레이어에 작성된 파일은 동일한 경로에 쓰는 후속 레이어에 의해 재정의될 수 있습니다. 레이어는 읽기 전용이며 컨테이너 간에 공유할 수 있습니다.

6.6 Lab

우리는 이 실습에서 그 조각들을 함께 모을 것입니다. 예전의 할 일 목록 앱으로 돌아갔지만 이번에는 반전이 있습니다. 앱은 컨테이너에서 실행되고 이미 생성된 작업 세트로 시작됩니다. 할 일 목록이 비어 있는 상태로 시작하고 항목을 저장할 때 Docker 볼륨에 저장되도록 동일한 이미지를 사용하지만 다른 저장 옵션으로 앱을 실행해야 합니다. 이 장의 연습 문제를 통해 얻을 수 있지만 여기에 몇 가지 힌트가 있습니다.

  • 모든 기존 컨테이너를 제거하는 것은 docker rm -f $(docker ps -aq)라는 것을 기억하십시오.
  • 먼저 diamol/ch06-lab에서 앱을 실행하여 작업을 확인하세요.
  • 그런 다음 일부 마운트가 있는 동일한 이미지에서 컨테이너를 실행해야 합니다.
  • 앱은 구성 파일을 사용합니다. 거기에는 로그 설정보다 더 많은 것이 있습니다.

내 샘플 솔루션은 필요한 경우 책의 GitHub 리포지토리에 있지만 경험이 많지 않은 경우 컨테이너 스토리지에 문제가 생길 수 있으므로 이 솔루션을 통해 작업해야 합니다. 이를 해결하는 몇 가지 방법이 있습니다.

https://github.com/sixeyed/diamol/blob/master/ch06/lab/README.md .

profile
코드코드

0개의 댓글