Учебник. Сохранение данных в приложении-контейнере с помощью томов в VS Code

Из этого учебника вы узнаете, как сохранять данные в приложении-контейнере. При запуске или обновлении данные по-прежнему остаются доступными. Существует два основных типа томов, используемых для хранения данных. В этом учебнике основное внимание уделяется именованным томам.

Кроме того, вы узнаете о подключениях привязок, которые контролируют точку подключения на узле. Подключение привязки можно использовать для хранения данных, однако оно также может привести к добавлению дополнительных данных в контейнеры. При работе с приложением можно использовать подключение BIND для подключения исходного кода к контейнеру, чтобы он мог видеть изменения в коде, реагировать и сразу отображать изменения.

В этом учебнике также рассматриваются слои в образе, кэширование слоев и многоэтапные сборки.

В этом руководстве описано следующее:

  • Общие сведения о данных в контейнерах.
  • Сохранение данных с помощью именованных томов.
  • Использование подключений привязок.
  • Просмотр слоя образа.
  • Кэширование зависимостей.
  • Общие сведения о многоэтапных сборках.

Необходимые компоненты

Этот учебник является продолжением предыдущего — Создание приложения Docker и предоставление к нему общего доступа с помощью Visual Studio Code. Начните с первого учебника, поскольку в нем приводятся предварительные требования.

Общие сведения о данных в контейнерах

В этом разделе вы запустите два контейнера и создайте файл в каждом из них. Файлы, созданные в одном контейнере, недоступны в другом.

  1. Запустите контейнер ubuntu с помощью следующей команды:

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

    Эта команда запускает две команды с помощью &&. Первая часть выбирает одно случайное число и записывает его в файл /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 и выполните следующую команду.

    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 сохраняет данные в базе данных SQLite в /etc/todos/todo.db. База данных 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
    

    Параметр volume указывает том для подключения и расположение (/etc/todos).

  4. Обновите браузер, чтобы перезагрузить приложение. Если вы закрыли окно браузера, перейдите по адресу http://localhost:3000/. Добавьте несколько элементов в список дел.

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

  5. Удалите контейнер getting-started для приложения Todo. Щелкните правой кнопкой мыши контейнер в области Docker и выберите Удалить либо используйте команды docker stop и docker 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 — это фактическое расположение, в котором хранятся данные. На большинстве компьютеров для доступа к этому каталогу с узла необходим доступ с правами root.

Использование подключения BIND

Подключения BIND позволяют указать точную точку подключения на узле. Этот подход сохраняет данные, но часто используется для добавления большого объема данных в контейнеры. Подключение привязки можно использовать для подключения исходного кода к контейнеру, чтобы он мог видеть изменения в коде, реагировать и сразу отображать изменения.

Чтобы запустить контейнер для поддержки рабочего процесса разработки, выполните приведенные ниже действия.

  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.

Подход заключается в том, чтобы сначала скопировать только этот файл, установить зависимости и затем скопировать все остальное. Повторное создавать зависимости Yarn нужно будет только в том случае, если было внесено изменение в файл package.json.

  1. Сначала измените Dockerfile, чтобы выполнить копирование в package.json, установите зависимости, а затем скопируйте все остальное. Вот новый файл:

    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" (Прекрасное приложение Todo).

  4. Снова создайте образ Docker, используя docker build. На этот раз выходные данные должны выглядеть немного иначе.

    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) используется для выполнения фактической сборки Java с помощью Maven. Второй этап (начиная с "FROM tomcat") копирует в файлы с этапа build. Окончательный образ создается только на последнем этапе (что можно переопределить с помощью параметра --target).

Пример React

При создании приложений React требуется среда Node для компиляции кода JavaScript, таблиц стилей SASS и др. в статический HTML, JavaScript и CSS. Если вы не выполняете отрисовку на стороне сервера, вам даже не нужна среда узла для рабочей сборки.

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.

Очистка ресурсов

Чтобы продолжить работу с этой серией учебников, оставьте все настройки, сделанные на данный момент.

Следующие шаги

Вы узнали о вариантах сохранения данных для приложений-контейнеров.

Что вы хотите сделать дальше?