자습서: VS Code에서 볼륨을 사용하여 컨테이너 앱에 데이터 유지

이 자습서에서는 컨테이너 애플리케이션에 데이터를 유지하는 방법을 알아봅니다. 컨테이너 애플리케이션을 실행하거나 업데이트할 때 데이터를 계속 사용할 수 있습니다. 데이터를 유지하는 데 사용되는 두 가지 주요 볼륨 유형이 있습니다. 이 자습서에서는 명명된 볼륨에 중점을 둡니다.

호스트의 정확한 탑재 지점을 제어하는 바인드 탑재에 대해서도 알아봅니다. 바인드 탑재를 사용하여 데이터를 유지할 수 있지만, 컨테이너에 더 많은 데이터를 추가할 수도 있습니다. 애플리케이션 작업 시 바인드 탑재를 사용하여 소스 코드를 컨테이너에 탑재하면 변경 내용을 즉시 확인할 수 있으며, 컨테이너가 코드 변경을 확인하고 응답하게 할 수 있습니다.

이 자습서에서는 이미지 계층화, 계층 캐싱, 다단계 빌드도 소개합니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • 컨테이너 간 데이터를 이해합니다.
  • 명명된 볼륨을 사용하여 데이터를 유지합니다.
  • 바인드 탑재를 사용합니다.
  • 이미지 계층을 봅니다.
  • 종속성을 캐시합니다.
  • 다단계 빌드를 이해합니다.

필수 조건

이 자습서에서는 이전 자습서인 Visual Studio Code를 사용하여 Docker 앱 만들기 및 공유를 계속 진행합니다. 필수 구성 요소가 포함된 해당 자습서로 시작합니다.

컨테이너 간 데이터 이해

이 섹션에서는 두 개의 컨테이너를 시작하고 각각에 파일을 만듭니다. 한 컨테이너에서 만들어진 파일은 다른 컨테이너에서 사용할 수 없습니다.

  1. 다음 명령을 사용하여 ubuntu 컨테이너를 시작합니다.

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    

    이 명령은 &&를 사용하여 두 개의 명령 호출을 시작합니다. 첫 번째 명령은 난수 1개를 선택하여 /data.txt에 씁니다. 두 번째 명령은 컨테이너가 계속 실행되는지 파일을 감시합니다.

  2. VS Code의 Docker 영역에서 ubuntu 컨테이너를 마우스 오른쪽 단추로 클릭하고 셸 연결을 선택합니다.

    Screenshot shows the Docker extension with a container selected and a context menu with Attach Shell selected.

    Ubuntu 컨테이너의 셸을 실행하는 터미널이 열립니다.

  3. 다음 명령을 실행하여 /data.txt 파일 내용을 확인합니다.

    cat /data.txt
    

    터미널에 1에서 10000 사이의 숫자가 표시됩니다.

    명령줄을 사용하여 이 결과를 확인하려면 docker ps 명령을 사용하여 컨테이너 ID를 가져오고 다음 명령을 실행합니다.

    docker exec <container-id> cat /data.txt
    
  4. 다른 ubuntu 컨테이너를 시작합니다.

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    
  5. 다음 명령을 사용하여 폴더 내용을 확인합니다.

    docker run -it ubuntu ls /
    

    data.txt 파일이 없는데 첫 번째 컨테이너의 스크래치 공간에만 작성되었기 때문입니다.

  6. 이러한 두 개의 Ubuntu 컨테이너를 선택합니다. 마우스 오른쪽 단추를 클릭하고 제거를 선택합니다. 명령줄에서 docker rm -f 명령을 사용하여 제거할 수 있습니다.

명명된 볼륨을 사용하여 todo 데이터 유지

기본적으로 todo 앱은 /etc/todos/todo.db에 있는 SQLite 데이터베이스에 데이터를 저장합니다. SQLite 데이터베이스는 데이터를 단일 파일로 저장하는 관계형 데이터베이스입니다. 이 접근 방식은 소규모 프로젝트에 적합합니다.

호스트에 단일 파일을 유지할 수 있습니다. 다음 컨테이너에서 이 파일을 사용할 수 있도록 설정할 때 애플리케이션이 중단된 위치를 선택할 수 있습니다. 볼륨을 만들고 데이터를 저장하는 폴더에 볼륨을 연결하거나 탑재하면 데이터를 유지할 수 있습니다. 컨테이너는 todo.db 파일에 쓰고 해당 데이터는 볼륨의 호스트에 유지됩니다.

이 섹션에서는 명명된 볼륨을 사용합니다. Docker는 디스크에서 볼륨의 실제 위치를 유지 관리합니다. 볼륨의 이름을 참조하면 Docker가 올바른 데이터를 제공합니다.

  1. docker volume create 명령을 사용하여 볼륨을 만듭니다.

    docker volume create todo-db
    
  2. 컨테이너 아래에서 시작 및 마우스 오른쪽 단추를 클릭합니다. 중지를 선택하여 앱 컨테이너를 중지합니다.

    명령줄에서 컨테이너를 중지하려면 docker stop 명령을 사용합니다.

  3. 다음 명령을 사용하여 getting-started 컨테이너를 시작합니다.

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    볼륨 매개 변수는 탑재할 볼륨과 위치 /etc/todos를 지정합니다.

  4. 브라우저를 새로 고쳐 앱을 다시 로드합니다. 브라우저 창을 닫은 경우 http://localhost:3000/로 이동합니다. todo 목록에 몇 가지 항목을 추가합니다.

    Screenshot shows the sample app with several items added to the list.

  5. todo 앱의 getting-started 컨테이너를 제거합니다. Docker 영역에서 컨테이너를 마우스 오른쪽 단추로 클릭하고 제거를 선택하거나 docker stopdocker rm 명령을 사용합니다.

  6. 같은 명령을 사용하여 새 컨테이너를 시작합니다.

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    이 명령은 이전과 같은 드라이브를 탑재합니다. 브라우저를 새로 고칩니다. 추가한 항목은 여전히 목록에 있습니다.

  7. getting-started 컨테이너를 다시 제거합니다.

아래에 설명된 명명된 볼륨 및 바인드 탑재는 기본 Docker 엔진 설치에서 지원하는 주요 볼륨 유형입니다.

속성 명명된 볼륨 탑재 바인딩
호스트 위치 Docker 선택 사용자 제어
탑재 예(-v 사용) my-volume:/usr/local/data /path/to/data:/usr/local/data
새 볼륨에 컨테이너 내용을 채웁니다. 아니요
볼륨 드라이버 지원 아니요

NFS, SFTP, NetApp 등을 지원하는 데 사용할 수 있는 많은 볼륨 드라이버 플러그 인이 있습니다. 이러한 플러그 인은 Swarm 또는 Kubernetes와 같은 클러스터형 환경의 여러 호스트에서 컨테이너를 실행하는 데 특히 중요합니다.

Docker 에서 실제로 데이터를 저장하는 위치가 궁금한 경우 다음 명령을 실행합니다.

docker volume inspect todo-db

이 결과와 유사한 출력을 확인합니다.

[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

Mountpoint가 데이터가 저장되는 실제 위치입니다. 대부분 컴퓨터에서 호스트에서 이 디렉터리에 액세스하려면 루트 액세스 권한이 필요합니다.

바인드 탑재 사용

바인드 탑재를 사용하면 호스트의 탑재 지점을 정확하게 제어할 수 있습니다. 이 접근 방식은 데이터를 유지하지만, 컨테이너에 더 많은 데이터를 제공하는 데 자주 사용됩니다. 바인드 탑재를 사용하여 소스 코드를 컨테이너에 탑재하면 코드 변경 내용을 확인하고 대응할 수 있으며 변경 내용을 즉시 확인할 수 있습니다.

컨테이너를 실행하여 개발 워크플로를 지원하려면 다음 단계를 수행합니다.

  1. 모든 getting-started 컨테이너를 제거합니다.

  2. app 폴더에서 다음 명령을 실행합니다.

    docker run -dp 3000:3000 -w /app -v ${PWD}:/app node:20-alpine sh -c "yarn install && yarn run dev"
    

    이 명령에는 다음 매개 변수가 포함됩니다.

    • -dp 3000:3000 이전과 같습니다. 분리 모드에서 실행하고 포트 매핑을 만듭니다.
    • -w /app 컨테이너 내의 작업 디렉터리
    • -v ${PWD}:/app" 컨테이너에 있는 호스트의 현재 디렉터리를 /app 디렉터리에 바인드 탑재합니다.
    • node:20-alpine 사용할 이미지. 이 이미지는 Dockerfile에 있는 앱의 기본 이미지입니다.
    • sh -c "yarn install && yarn run dev" 명령. sh를 사용하여 셸을 시작하고 yarn install을 실행하여 모든 종속성을 설치합니다. 그런 다음, yarn run dev를 실행합니다. package.json을 살펴보면 dev 스크립트에서 nodemon이 시작되는 것을 확인할 수 있습니다.
  3. docker logs를 사용하여 로그를 확인할 수 있습니다.

    docker logs -f <container-id>
    
    $ nodemon src/index.js
    [nodemon] 2.0.20
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching path(s): *.*
    [nodemon] watching extensions: js,mjs,json
    [nodemon] starting `node src/index.js`
    Using sqlite database at /etc/todos/todo.db
    Listening on port 3000
    

    이 목록에 최종 항목이 표시되면 앱이 실행되고 있는 것입니다.

    로그 확인을 마쳤으면 터미널 창에서 아무 키나 선택하거나 외부 창에서 Ctrl+C를 선택합니다.

  4. VS Code에서 src/static/js/app.js를 엽니다. 줄 109에서 항목 추가 단추의 텍스트를 변경합니다.

    - {submitting ? 'Adding...' : 'Add Item'}
    + {submitting ? 'Adding...' : 'Add'}
    

    변경 내용을 저장합니다.

  5. 브라우저를 새로 고칩니다. 변경 내용이 표시됩니다.

    Screenshot shows the sample app with the new text on the button.

이미지 계층 보기

이미지를 구성하는 계층을 확인할 수 있습니다. docker image history 명령을 실행하여 이미지 내의 각 계층을 만드는 데 사용된 명령을 확인합니다.

  1. docker image history를 사용하여, 자습서의 앞부분에서 만든 getting-started 이미지의 계층을 확인합니다.

    docker image history getting-started
    

    결과는 이 출력과 유사합니다.

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "/app/src/ind…   0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    각 선은 이미지 계층을 나타냅니다. 출력은 맨 아래에 기준이 표시되고 맨 위에 최신 계층이 있습니다. 이 정보를 사용하여 각 계층의 크기를 확인할 수 있어 큰 이미지를 진단하는 데 도움이 됩니다.

  2. 여러 줄이 잘렸습니다. --no-trunc 매개 변수를 추가하면 전체 출력이 표시됩니다.

    docker image history --no-trunc getting-started
    

캐시 종속성

계층이 변경되면 다운스트림 계층도 모두 다시 만들어야 합니다. 다시 Dockerfile은 다음과 같습니다.

FROM node:20-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "/app/src/index.js"]

Dockerfile의 각 명령은 이미지의 새 계층이 됩니다. 계층 수를 최소화하기 위해 종속성 캐싱을 지원하도록 Dockerfile을 재구성할 수 있습니다. 노드 기반 애플리케이션의 경우 package.json 파일에서 해당 종속성이 정의됩니다.

이 방법은 먼저 해당 파일만 복사하고 종속성을 설치한 다음 다른 모든 항목을 복사하는 것입니다. 이 프로세스에서는 package.json이 변경된 경우에만 yarn 종속성을 다시 만듭니다.

  1. 먼저 package.json에서 복사할 Dockerfile을 업데이트하고 종속성을 설치한 후 다른 모든 항목을 복사합니다. 새 파일은 다음과 같습니다.

    FROM node:20-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "/app/src/index.js"]
    
  2. docker build를 사용하여 새 이미지를 빌드합니다.

    docker build -t getting-started .
    

    다음 결과와 같은 출력이 표시됩니다.

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Running in d53a06c9e4c2
    yarn install v1.17.3
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    info fsevents@1.2.9: The platform "linux" is incompatible with this module.
    info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    Done in 10.89s.
    Removing intermediate container d53a06c9e4c2
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> a239a11f68d8
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 49999f68df8f
    Removing intermediate container 49999f68df8f
    ---> e709c03bc597
    Successfully built e709c03bc597
    Successfully tagged getting-started:latest
    

    모든 계층이 다시 빌드되었습니다. Dockerfile을 변경했으므로 이 결과가 예상됩니다.

  3. src/static/index.html을 변경합니다. 예를 들어 제목을 “The Awesome Todo App”으로 변경합니다.

  4. 다시 docker build를 사용하여 Docker 이미지를 빌드합니다. 이번에는 출력이 약간 다르게 표시됩니다.

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> Using cache
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Using cache
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> cccde25a3d9a
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 2be75662c150
    Removing intermediate container 2be75662c150
    ---> 458e5c6f080c
    Successfully built 458e5c6f080c
    Successfully tagged getting-started:latest
    

    빌드 캐시를 사용하므로 훨씬 더 빠르게 진행되어야 합니다.

다단계 빌드

다단계 빌드는 여러 단계를 사용하여 이미지를 만드는 데 도움을 주는 매우 강력한 도구입니다. 다단계 빌드에는 다음과 같은 몇 가지 이점이 있습니다.

  • 런타임 종속성과 빌드 시간 종속성 구분
  • 앱을 실행하는 데 필요한 ‘항목’만 제공하여 전체 이미지 크기 축소

이 섹션에서는 간단한 예제를 제공합니다.

Maven/Tomcat 예제

Java 기반 애플리케이션을 빌드하는 경우 소스 코드를 Java 바이트코드로 컴파일하려면 JDK가 필요합니다. 프로덕션 환경에서는 JDK가 필요하지 않습니다. 앱 빌드를 위해 Maven 또는 Gradle과 같은 도구를 사용할 수 있습니다. 해당 도구도 최종 이미지에는 필요하지 않습니다.

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

이 예제에서는 build 한 단계를 사용하여 Maven으로 실제 Java 빌드를 수행합니다. 두 번째 단계(“FROM tomcat”에서 시작)는 build 단계의 파일을 복사합니다. 마지막으로 생성되는 단계만 최종 이미지입니다(--target 매개 변수를 사용하여 재정의할 수 있음).

React 예제

React 애플리케이션을 빌드하는 경우 JavaScript 코드, SASS 스타일시트 등을 정적 HTML, JavaScript, CSS로 컴파일하려면 Node 환경이 필요합니다. 서버 쪽 렌더링을 수행하지 않는 경우 프로덕션 빌드에도 노드 환경이 필요하지 않습니다.

FROM node:20-alpine AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

이 예제에서는 node:20 이미지를 사용하여 계층 캐싱을 최대화한 후 출력을 nginx 컨테이너에 복사하는 빌드를 수행합니다.

리소스 정리

이 자습서 시리즈를 계속 진행하기 위해 지금까지 수행한 모든 작업을 유지합니다.

다음 단계

컨테이너 앱의 데이터를 유지하는 옵션에 대해 알아봤습니다.

다음에 무엇을 하고 싶으신가요?