Создание контейнеризированного приложения Docker

В предыдущем мануале, Установка и настройка Docker, вы узнали, как преобразовать контейнеры Docker в образы. Описанный там метод вполне подходит для работы, но он не всегда является оптимальным.

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

Dockerfile отвечает всем этим требованиям и предоставляет последовательный способ создания образов Docker.

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

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

Данный мануал поможет создать образ приложения Docker из Dockerfile на примере простого веб-приложения на основе Node.js и MongoDB, создать настраиваемую сеть, которая позволяет контейнерам Docker взаимодействовать, и научит использовать Docker Compose для запуска и масштабирования контейнерного приложения.

Требования

1: Создание образа с помощью Dockerfile

Перейдите в домашний каталог и с помощью Git клонируйте тестовое приложение из официального репозитория GitHub.

cd ~
git clone https://github.com/janakiramm/todo-app.git

Эта команда скопирует тестовое приложение в новый каталог todo-app.

Перейдите в этот каталог и просмотрите его содержимое:

cd todo-app
ls

В нем будет два подкаталога и два файла:

  • app – каталог, который содержит исходный код приложения.
  • compose – каталог, в котором находятся конфигурации Docker Compose.
  • Dockerfile – файл, в котором находятся инструкции по сборке образов Docker.
  • README.md – файл, в котором находится краткое описание приложения.

Запустите команду cat Dockerfile, и на экране появится:

FROM node:slim
LABEL maintainer = "jani@janakiram.com"
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY ./app/ ./
RUN npm install
CMD ["node", "app.js"]

Рассмотрим содержимое файла подробнее:

  • FROM определяет базовый образ, на основе которого будет создан пользовательский образ. В данном случае образ основан на node:slim, публичном образе Node.js, которому необходимо минимальное количество пакетов для запуска node.
  • LABEL – пара «ключ-значение», которая предоставляет описательную информацию. В этом случае здесь содержится адрес электронной почты разработчика.
  • RUN выполняет команды внутри контейнера. Сюда входят такие задачи, как создание каталогов и инициализация контейнера путем запуска основных команд Linux. Первая команда RUN в этом файле используется для создания каталога /usr/src/app, содержащего исходный код.
  • WORKDIR определяет каталог, в котором выполняются все команды. Обычно это каталог, в котором копируется код.
  • COPY копирует файлы с главной машины в образ контейнера. В этом случае копируется весь каталог app.
  • Вторая команда RUN выполняет npm install, чтобы установить зависимости приложения, определенные в файле package.json.
  • CMD запускает процесс, который будет поддерживать контейнер в рабочем состоянии. В данном случае это node с параметром app.js.

Теперь пора собрать образ на основе Dockerfile. Используйте флаг –t, чтобы добавить имя реестра, имя образа и опциональный тег.

docker build -t 8host/todo-web .

Вывод сообщит, что образ был успешно создан и правильно помечен.

Sending build context to Docker daemon  8.238MB
Step 1/7 : FROM node:slim
---> 286b1e0e7d3f
Step 2/7 : LABEL maintainer = "jani@janakiram.com"
---> Using cache
---> ab0e049cf6f8
Step 3/7 : RUN mkdir -p /usr/src/app
---> Using cache
---> 897176832f4d
Step 4/7 : WORKDIR /usr/src/app
---> Using cache
---> 3670f0147bed
Step 5/7 : COPY ./app/ ./
---> Using cache
---> e28c7c1be1a0
Step 6/7 : RUN npm install
---> Using cache
---> 7ce5b1d0aa65
Step 7/7 : CMD node app.js
---> Using cache
---> 2cef2238de24
Successfully built 2cef2238de24
Successfully tagged 8host/todo-web:latest

Убедитесь, что образ создан, запустив команду docker images:

docker images

Команда выведет размер образа и время, прошедшее с момента его создания.

REPOSITORY                                       TAG                 IMAGE ID            CREATED             SIZE
8host/todo-web                                   latest              81f5f605d1ca        9 minutes ago       236MB

Поскольку для запуска тестового веб-приложения также нужен контейнер MongoDB, добавьте его на свою машину:

docker pull mongo:latest

Вывод сообщит, какой образ был загружен, и покажет статус загрузки.

latest: Pulling from library/mongo
Digest: sha256:18b239b996e0d10f4ce2b0f64db6f410c17ad337e2cecb6210a3dcf2f732ed82
Status: Downloaded newer image for mongo:latest

Теперь у вас есть все необходимое для запуска тестового приложения. Создайте настраиваемую сеть, которая позволит контейнерам общаться друг с другом.

2: Создание сети для подключения контейнеров

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

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

cat app/db.js

После импорта Mongoose (библиотеки объектного моделирования MongoDB для Node.js) и определения схемы БД веб-приложение пытается подключиться к БД по имени хоста db, которого пока что не существует.

Читайте также: SQL, NoSQL и другие модели баз данных

var mongoose = require( 'mongoose' );
var Schema   = mongoose.Schema;
var Todo = new Schema({
user_id    : String,
content    : String,
updated_at : Date
});
mongoose.model( 'Todo', Todo );
mongoose.connect( 'mongodb://db/express-todo' );

Чтобы контейнеры, принадлежащие одному и тому же приложению, могли найти друг друга, необходимо запустить их в одной сети.

Docker предоставляет возможность создавать собственные сети в дополнение к стандартным сетям, созданным во время установки.

Вы можете проверить имеющиеся в настоящее время сети с помощью следующей команды:

docker network ls

Каждая сеть, созданная Docker, основана на драйвере. В следующем выводе можно увидеть, что сеть с именем bridge основана на драйвере bridge. Область local указывает, что сеть доступна только на этом хосте.

NETWORK ID          NAME                DRIVER              SCOPE
5029df19d0cf        bridge              bridge              local
367330960d5c        host                host                local
f280c1593b89        none                null                local

Теперь создайте пользовательскую сеть todo_net, чтобы затем запустить в ней контейнеры приложения.

docker network create todo_net

В выводе появится хэш созданной сети:

C09f199809ccb9928dd9a93408612bb99ae08bb5a65833fefd6db2181bfe17ac

Снова просмотрите список доступных сетей:

docker network ls

Как видите, пользовательская сеть готова к работе:

NETWORK ID          NAME                DRIVER              SCOPE
c51377a045ff        bridge              bridge              local
2e4106b07544        host                host                local
7a8b4801a712        none                null                local
bc992f0b2be6        todo_net            bridge              local

Теперь вы можете сослаться на сеть в команде docker run с помощью флага —network. Запустите контейнеры приложения и БД с правильными именами хостов. Так контейнеры смогут взаимодействовать. Сначала запустите контейнер БД MongoDB:

docker run -d \
--name=db \
--hostname=db \
--network=todo_net \
mongo

Команда состоит из таких компонентов:

  • Флаг -d запускает контейнер в раздельном режиме.
  • Флаги —name и —hostname назначают определенное пользователем имя контейнера. Флаг —hostname также добавляет запись в DNS Docker. Это помогает в разрешении контейнера по имени хоста.
  • Флаг —network указывает, что Docker Engine запускает контейнер в пользовательской сети вместо сети bridge по умолчанию.

Когда в выводе docker run вы видите длинную строку, вы можете решить, что контейнер запущен успешно, но это не всегда так.

aa56250f2421c5112cf8e383b68faefea91cd4b6da846cbc56cf3a0f04ff4295

Чтобы убедиться, что контейнер db успешно запущен и работает, используйте команду docker logs.

docker logs db

Она выводит логи контейнера в stdout. Последняя строка лога говорит, что контейнер MongoDB готов и ожидает соединений.

2017-12-10T02:55:08.284+0000 I CONTROL  [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=db
. . . .
2017-12-10T02:55:08.366+0000 I NETWORK  [initandlisten] waiting for connections on port 27017

Теперь пора запустить контейнер приложения. В этот раз нужно добавить флаг —publish=3000:3000, который соединит порт хоста 3000 с портом приложения 3000.

docker run -d \
--name=web \
--publish=3000:3000 \
--hostname=web \
--network=todo_net \
8host/todo-web

В выводе вы снова увидите длинную строку.

Убедитесь, что контейнер запущен.

docker logs web

Вывод сообщит, что Express (фреймворк Node.js, на котором основано тестовое веб-приложение) прослушивает порт 3000.

Express server listening on port 3000

Убедитесь, что веб-контейнер может взаимодействовать с контейнером db с помощью команды ping. Для этого запустите команду docker exec в интерактивном (-i) режиме, подключенном к псевдо-TTY (-t).

docker exec -it web ping db

Команда выдает стандартный вывод ping и подтверждает, что два контейнера могут взаимодействовать друг с другом.

PING db (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: icmp_seq=0 ttl=64 time=0.210 ms
64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.095 ms
...

Нажмите CTRL+C, чтобы остановить ping.

Теперь попробуйте получить доступ к тестовому приложению. Для этого направьте браузер на http://your_server_ip:3000. На экране вы увидите страницу с меткой Containers Todo Example.

Чтобы избежать конфликтов наименований, остановите контейнеры и очистите ресурсы с помощью этих команд:

docker rm -f db
docker rm -f web
docker network remove todo_net

Теперь у вас есть контейнеризированное приложение, состоящее из двух отдельных контейнеров.

3: Развертывание мультиконтейнерного приложения

Вы смогли запустить связанные контейнеры, это не самый оптимальный способ работы с мультиконтейнерными приложениями. Нужно найти способ объявить все связанные контейнеры и управлять ими как единым логическим блоком.

Docker Compose – это платформа для работы с мультиконтейнерными приложениями. Подобно Dockefile, она поддерживает декларативный механизм для определения всего стека. Теперь нужно преобразовать приложение Node.js и MongoDB в приложение на основе Docker Compose.

Установите Docker Compose:

sudo apt-get install -y docker-compose

Откройте файл docker-compose.yaml, который находится в каталоге compose тестового приложения.

cat compose/docker-compose.yaml

Файл docker-compose.yaml объединяет все. Он определяет контейнер MongoDB в блоке db:, веб-контейнер Node.js в блоке web: и пользовательскую сеть в блоке networks:.

Обратите внимание, что директива build: ../.  направляет Compose на файл Dockerfile приложения. Так Compose сможет собрать образ перед запуском веб-контейнера.

version: '2'
services:
db:
image: mongo:latest
container_name: db
networks:
- todonet
web:
build: ../.
networks:
- todonet
ports:
- "3000"
networks:
todonet:
driver: bridge

Перейдите в каталог compose и запустите приложение с помощью команды docker-compose up. Как и в команде docker run, флаг -d запустит контейнер в раздельном режиме.

cd compose
docker-compose up -d

Как видите, Docker Compose создал сеть под названием compose_todonet и запустил в ней оба контейнера.

Creating network "compose_todonet" with driver "bridge"
Creating db
Creating compose_web_1

Обратите внимание: отображение портов хоста не указывалось явно. Это заставит Docker Compose назначить случайный порт, чтобы открыть веб-приложение на хосте. Чтобы найти этот порт, выполните следующую команду:

docker ps

Приложение запущено по порту 32782:

CONTAINER ID        IMAGE           COMMAND                  CREATED             STATUS              PORTS                     NAMES
6700761c0a1e        compose_web     "node app.js"            2 minutes ago       Up 2 minutes        0.0.0.0:32782->3000/tcp   compose_web_1
ad7656ef5db7        mongo:latest    "docker-entrypoint..."   2 minutes ago       Up 2 minutes        27017/tcp                 db

Чтобы проверить это, перейдите в веб-браузере по адресу http://your_server_ip:32782. Вы должны увидеть ту же страницу, что и в разделе 2

Теперь давайте рассмотрим управление и масштабирование приложения.

4: Управление и масштабирование приложения

Docker Compose упрощает масштабирование распределенных веб-приложений. Вы можете запустить 10 экземпляров контейнера web  с помощью одной команды.

docker-compose scale web=10

В выводе вы получите информацию об экземплярах в режиме реального времени:

Creating and starting compose_web_2 ... done
Creating and starting compose_web_3 ... done
Creating and starting compose_web_4 ... done
Creating and starting compose_web_5 ... done
Creating and starting compose_web_6 ... done
Creating and starting compose_web_7 ... done
Creating and starting compose_web_8 ... done
Creating and starting compose_web_9 ... done
Creating and starting compose_web_10 ... done

Чтобы убедиться, что приложение запущено в 10 экземплярах, ведите команду:

docker ps

Обратите внимание, что для отображения каждого веб-контейнера на хосте Docker назначает случайный порт. Любой из этих портов может использоваться для доступа к приложению.

CONTAINER ID       IMAGE            COMMAND                CREATED              STATUS              PORTS                     NAMES
cec405db568d       compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32788->3000/tcp   compose_web_9
56adb12640bb      compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32791->3000/tcp   compose_web_10
4a1005d1356a       compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32790->3000/tcp   compose_web_7
869077de9cb1       compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32785->3000/tcp   compose_web_8
eef86c56d16f       compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32783->3000/tcp   compose_web_4
26dbce7f6dab       compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32786->3000/tcp   compose_web_5
0b3abd8eee84       compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32784->3000/tcp   compose_web_3
8f867f60d11d       compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32789->3000/tcp   compose_web_6
36b817c6110b       compose_web      "node app.js"           About a minute ago   Up About a minute   0.0.0.0:32787->3000/tcp   compose_web_2
6700761c0a1e       compose_web      "node app.js"           7 minutes ago        Up 7 minutes        0.0.0.0:32782->3000/tcp   compose_web_1
ad7656ef5db7       mongo:latest     "docker-entrypoint..."   7 minutes ago       Up 7 minutes        27017/tcp                 db

Также можно уменьшить количество копий контейнера с помощью этой же команды:

docker-compose scale web=2

Теперь вы увидите, как дополнительные экземпляры удаляются в режиме реального времени:

Stopping and removing compose_web_3 ... done
Stopping and removing compose_web_4 ... done
Stopping and removing compose_web_5 ... done
Stopping and removing compose_web_6 ... done
Stopping and removing compose_web_7 ... done
Stopping and removing compose_web_8 ... done
Stopping and removing compose_web_9 ... done
Stopping and removing compose_web_10 ... done

Перепроверьте количество экземпляров:

docker ps

Вывод подтверждает, что осталось только два экземпляра:

CONTAINER ID     IMAGE             COMMAND                  CREATED             STATUS              PORTS                     NAMES
36b817c6110b      compose_web       "node app.js"            3 minutes ago       Up 3 minutes        0.0.0.0:32787->3000/tcp compose_web_2
6700761c0a1e      compose_web       "node app.js"            9 minutes ago       Up 9 minutes        0.0.0.0:32782->3000/tcp   compose_web_1
ad7656ef5db7      mongo:latest      "docker-entrypoint..."   9 minutes ago       Up 9 minutes        27017/tcp                 db

Теперь можно остановить приложение, и, как и раньше, очистить ресурсы, чтобы избежать конфликтов имен.

docker-compose stop
docker-compose rm -f
docker network remove compose_todonet

Заключение

Теперь вы знаете основы работы с Dockerfiles и Docker Compose.

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

Tags: ,