В этом посте мы увидим несколько вещей:
- Работа с контейнерами
- докер-Compose
- Создание изображений
- тома
- Делая наш имидж более гибким с ARG
- точки входа
- Докер-хаб и контейнерный реестр
- источники
Докер: Что это?
Я мог бы попытаться объяснить, что такое контейнер, но объяснение, найденное на веб-сайте Docker, вероятно, поможет лучше. Раздел Что такое контейнер? с веб-сайта Docker описывает контейнеры как единицу программного обеспечения, которая упаковывает код и все его зависимости. Таким образом, приложения становятся более надежными при переходе из одной среды в другую. Контейнеры — это легкий, автономный исполняемый пакет программного обеспечения.
Какую проблему решает Docker?
Почему я должен использовать докер?
Контейнеры помогут вам справиться со многими проблемами:
- В отличие от полной виртуализации вам не нужны гипервизор и гостевая ОС, все эти издержки исключены, и они намного легче, чем виртуальная машина.
- Контейнеры являются детерминированными, это означает, что если контейнер работает на вашей машине, он будет работать везде. Нет проблем с людьми, добавляющими или изменяющими зависимость от виртуальной машины вручную и не информирующими других людей.
- Легко распространять, с помощью реестра вы можете загружать образ контейнера и легко распространять, так что вы можете иметь свой конвейер для создания нового образа каждый раз, когда что-то объединяется с веткой, и это будет доступно всем в QA, или вы можете у вас есть вся среда разработки внутри контейнера, поэтому, когда кто-то присоединяется к новому, он может просто получить последний образ разработчика и начать работу.
- С помощью инструмента оркестровки очень легко вывести внешние зависимости, такие как PostgreSQL, Redis. Поэтому, если вам нужно запустить интеграционные или сквозные тесты, для которых требуется пустая база данных или что-то вроде wiremock, с docker-compose это действительно просто.
Что это не так?
Прежде чем начать, мне нужно уточнить некоторые вещи:
- Контейнеры Docker — это контейнеры Linux, это означает, что когда вы запускаете Docker на Windows или Mac, он запускает виртуальную машину под капотом.
- Докер не решит все ваши проблемы DevOps
- Вам все еще нужны знания о вашей среде для развертывания в производство
- Как и любой инструмент, это не серебряная пуля
Почему все прояснилось, мы можем начать.
Работа с контейнерами
Docker создает контейнеры на основе изображений, которые содержат все файлы, необходимые для запуска приложения, Docker запустит приложение, и после этого контейнер будет удален, внутри контейнера ничего не будет сохранено.
Мы можем запустить наш первый контейнер с:
1
|
$ docker run hello-world |
Первое, что он покажет, это то, что у вас нет образа hello-world
поэтому он скачает его. Но откуда это изображение? В Docker есть сервис Docker Hub
который хранит и редактирует образы Docker Hub
. Это в основном GitHub для докера.
1
2
3
4
5
|
Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535 Status: Downloaded newer image for hello-world:latest |
После загрузки образа Docker создаст новый контейнер, запустит приложение и остановит контейнер, когда все будет сделано:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1 . The Docker client contacted the Docker daemon. 2 . The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3 . The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4 . The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https: //hub.docker.com/ For more examples and ideas, visit: https: //docs.docker.com/get-started/ |
Это очень простая вещь, и я думаю, что мы можем добиться большего. Как насчет запуска веб-сервера? Запускаем docker run -p 8080:80 nginx
и ждем загрузки образа.
И тогда мы можем получить доступ к http://localhost:8080
и мы увидим страницу приветствия Nginx. Но подождите: на этот раз мы передали новый флаг ( -p
) с некоторыми числами. Что они имеют в виду?
порт
При работе с контейнерами, которые работают через порты, мы должны привязать локальный порт к контейнеру. Как и все веб-серверы, Nginx всегда работает на порту 80. Если мы хотим получить к нему доступ, нам нужно привязать этот порт к localhost. Идея связывания порта заключается в следующем:
1
|
docker run -p <local port>:<container port> <image> |
Это, вероятно, одна из наиболее часто используемых команд, с которыми вы столкнетесь.
Управление контейнерами и изображениями
Мы хотим иметь возможность удалить изображение, остановить работающий контейнер и удалить предыдущий контейнер. Для этого у нас есть набор команд:
-
docker ps
: Показать запущенные контейнеры. Добавьте флаг-a
чтобы показать остановленные контейнеры. -
docker rm <contaier id or name>
: удалить контейнер. -
docker images
: Показать загруженные изображения. -
docker rmi <image id>
: удалить изображение.
Теперь мы можем вести домашнюю работу на наших изображениях.
докер-Compose
Запуск контейнеров вручную работает, но это не помогает, как мы хотим. Мы хотим иметь возможность легко создавать контейнеры с одинаковой конфигурацией каждый раз.
Вот куда входит docker-compose
. docker-compose
— это инструмент оркестровки, который может одновременно управлять несколькими контейнерами. Он основан на файле YAML, в котором мы указываем нужный контейнер, и он делает всю работу за нас.
Основной вариант использования docker-compose
— это помощь в управлении внешними зависимостями вашего приложения в среде разработки. Вы можете просто настроить файл docker-compose
с вашей базой данных, кешем и SMTP-сервером, и любой, кто запускает приложение, может легко запустить контейнеры с этими зависимостями.
Пример из реального мира
Лучший способ чему-то научиться — это сломать производство, и теперь идея состоит в том, чтобы использовать docker-compose
в реальной жизни.
У нас есть Java-приложение, где мы можем добавлять и извлекать пользователей через RESTful API.
Эти пользователи хранятся в базе данных PostgreSQL, но мы не хотим устанавливать Postgres. Поэтому мы собираемся использовать docker-compose
для предоставления Postgres всем, кто клонирует приложение.
Анатомия docker-compose скрипта
01
02
03
04
05
06
07
08
09
10
11
|
version: '3.1' services: db: image: postgres: 10 ports: - "5432:5432" environment: POSTGRES_PASSWORD: postgres POSTGRES_DB: realworld |
- Тег
version
указывает минимальную версиюdocker-compose
которую можно использовать со скриптом -
services
: здесь будут объявлены наши контейнеры. Мы даем название нашему сервису и указываем, что мы хотим для этого сервиса. В этом случае у нас есть образ PostgreSQL 10.postgres:10
— имя изображения и версия, разделенные двоеточием. -
ports
: мы связываем порт5432
в контейнере с5432
в нашем локальном хосте, как мы делали это ранее с контейнером nginx. -
environment
: установите переменные окружения, такие какPOSTGRES_PASSWORD
иPOSTGRES_DB
чтобы определить пароль и создать базу данных с именемrealworld
. Обычно вы можете увидеть возможные переменные в документации к образу Docker в Docker Hub.
Теперь, если мы запустим docker-compose up -d
(флаг -d
означает отсоединение, поэтому процесс продолжается в фоновом режиме), мы сможем увидеть запущенный экземпляр Postgres.
Поэтому, если мы создадим флягу приложения, можно будет без проблем запустить приложение.
1
2
3
4
5
|
# This will build a jar with all the dependencies $ ./gradlew shadowJar # You can run the application $ java -jar build\libs\realworldkata- 1.0 -SNAPSHOT-all.jar |
Теперь мы можем получить доступ к http://localhost.com:4321/database
и должны увидеть Tables created!
если все работает. Мы можем дважды проверить доступ к контейнеру и проверить, видим ли мы таблицу в базе данных.
Сначала мы проверяем идентификатор контейнера:
1
2
3
|
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1180df37d9f1 postgres: 10 "docker-entrypoint.s…" About an hour ago Up About an hour 0.0 . 0.0 : 5432 -> 5432 /tcp realworld_db_1 |
Затем мы получаем доступ к контейнеру и используем приложение psql
для подключения к базе данных, и мы можем перечислить таблицы.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
$ docker exec -it 118 /bin/bash $ root @1180df37d9f1 :/# psql -U postgres psql ( 10.7 (Debian 10.7 - 1 .pgdg90+ 1 )) Type "help" for help. $ postgres=# \c realworld You are now connected to database "realworld" as user "postgres" . $ realworld=# \dt List of relations Schema | Name | Type | Owner --------+-------+-------+---------- public | users | table | postgres ( 1 row) |
Создание изображений
Теперь мы можем использовать изображения, созданные другими людьми, но что, если мы хотим использовать наши собственные изображения? Можем ли мы использовать Docker для распространения нашего приложения? Конечно; это не любительский час.
Вы можете создавать образы через Dockerfile
где мы собираемся указать наши зависимости и то, как создать и запустить приложение.
Сначала мы начинаем создавать наш Dockerfile
, указав базовый образ. Этот базовый образ может быть Ubuntu или образом Java. Для нашего приложения мы будем использовать adoptopenjdk/openjdk11-openj9
, который является реализацией OpenJ9 Eclipse Foundation.
1
|
FROM adoptopenjdk/openjdk11-openj9 |
Имея базовое изображение в руках, мы можем перейти к сбору исходного кода для создания приложения. Для этого нам нужно установить WORKDIR
и использовать команду COPY
для получения наших исходных файлов.
1
2
3
|
FROM adoptopenjdk/openjdk11-openj9 WORKDIR /realworld COPY . /realworld |
У нас есть источники; нам нужно создать наше приложение сейчас, поэтому нам нужно RUN
команду, чтобы сгенерировать толстый jar со всеми зависимостями.
1
2
3
4
|
FROM adoptopenjdk/openjdk11-openj9 WORKDIR /realworld COPY . /realworld RUN ./gradlew shadowJar |
Это веб-приложение, которое получает запросы через порт TCP. Чтобы иметь возможность получать запросы в контейнере, мы EXPOSE
порт, который нам нужен. EXPOSE
скажет, какой порт должен предоставить контейнер для сети docker-compose
, которую создаст docker-compose
, а также будет работать как документация, чтобы увидеть, какой порт необходимо связать при запуске контейнера.
1
2
3
4
5
|
FROM adoptopenjdk/openjdk11-openj9 WORKDIR /realworld COPY . /realworld RUN ./gradlew shadowJar EXPOSE 4321 |
Наконец, мы можем запустить приложение, передав CMD
для запуска
1
2
3
4
5
|
FROM adoptopenjdk/openjdk11-openj9 WORKDIR /realworldCOPY . /realworld RUN ./gradlew shadowJar EXPOSE 4321 CMD [ "java" , "-jar" , "build/libs/realworldkata-1.0-SNAPSHOT-all.jar" ] |
Когда готов Dockerfile
, мы можем создать образ и создать из него контейнер.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
$ docker build . --tag "realworld" $ docker run realworld SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" . SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http: //www.slf4j.org/codes.html#StaticLoggerBinder for further details. Exception in thread "main" java.lang.RuntimeException at realworld.infrastructure.ConnectionFactory.build(ConnectionFactory.java: 14 ) at realworld.persistence.daos.UserDAO.<init>(UserDAO.java: 18 ) at realworld.Application.buildUserService(Application.java: 41 ) at realworld.Application.setRoutes(Application.java: 28 ) at realworld.Application.start(Application.java: 24 ) at realworld.Application.main(Application.java: 65 ) |
Флаг --tag
должен дать изображению имя. Вы также можете указать тег как dev
или staging
.
Приложение работает как надо; ошибка происходит, потому что у контейнера нет доступа к другому контейнеру, который выполняет postgres. Теперь, когда мы знаем, что все работает, мы можем начать улучшать некоторые части сборки контейнера.
.dockerignore
Как и в git с .gitignore
, в docker есть .dockerignore
, файл, который исключает файлы из копии вашего контейнера. Давайте создадим одну, чтобы не копировать специфичные для IDE и выходные папки, чтобы мы быстрее собирались.
1
2
3
4
|
.gradle/ .idea/ build/ out/ |
Многоступенчатая сборка
Мы создаем образ докера со всем нашим исходным кодом, и, поскольку мы просто хотим запустить наше приложение, нам не нужно распространять их с нашим последним jar-файлом. Исходный код приложения находится внутри контейнера, его может увидеть любой, у кого есть доступ к изображению, и это увеличит наше изображение без необходимости. Чтобы решить эту проблему, мы собираемся сделать multi-stage
сборку.
Что такое multi-stage
сборка?
Многоэтапная сборка — это способ разделения процесса построения изображения между несколькими контейнерами с различными этапами, что-то вроде сборки CI. Преобразовать вашу обычную сборку в multi-stage
легко, вам просто нужно добавить еще один FROM
в ваш Dockerfile
.
В нашем случае мы хотим разделить среду, которая создает наше приложение, и легкую среду, которая будет работать.
Во-первых, мы собираемся разобраться с контейнером, который у нас уже есть. Есть несколько вещей, которые мы должны сделать:
- Дать имя для стадии сборки. Мы делаем это, добавляя
as <name>
перед базовым образом. - Затем мы должны удалить вещи, которые должны запускать приложение. Для этого мы удаляем
EXPOSE
иCMD
из файла, но не удаляем, мы собираемся использовать его позже.
Теперь мы можем начать создавать наш образ, который будет запускать приложение:
- Определение изображения. Нам не нужно полное изображение с Gradle или Maven. На самом деле, нам даже не нужен JDK, нам просто нужен JRE, поэтому мы можем использовать
adoptopenjdk/openjdk11:jre-11.0.2.9-alpine
изображениеadoptopenjdk/openjdk11:jre-11.0.2.9-alpine
которое имеет только среду выполнения Java. Он основан на легком дистрибутиве Linux под названием Alpine. - У нас может быть тот же самый
WORKDIR
из предыдущего этапа. - Теперь мы собираемся скопировать jar, который мы создаем. В этом случае мы собираемся скопировать jar из контейнера на предыдущем этапе, используя
--from=build
перед тем как файлы, которые мы хотим скопировать. - Теперь нам осталось только выставить
EXPOSE
иCMD
которые мы сохранили ранее.
Теперь, когда мы собираем наш контейнерный докер, мы ускоряем контейнер, создаем приложение и создаем другое изображение, используя эти файлы, они удаляют что-либо из предыдущих шагов, поэтому не нужно писать скрипт очистки.
01
02
03
04
05
06
07
08
09
10
|
FROM adoptopenjdk/openjdk11-openj9 as build WORKDIR /realworld COPY . /realworld RUN ./gradlew shadowJar FROM adoptopenjdk/openjdk11:jre- 11.0 . 2.9 -alpine WORKDIR /realworld COPY --from=build /realworld/build/libs/realworldkata- 1.0 -SNAPSHOT-all.jar . EXPOSE 4321 CMD [ "java" , "-jar" , "realworldkata-1.0-SNAPSHOT-all.jar" ] |
Добавьте изображение в docker-compose.yml
Теперь у нас есть правильно построенный образ, который мы можем добавить в наш docker-compose.yml
, но в отличие от уже имеющегося образа postgres, мы хотим построить образ на основе Dockerfile
а также нам нужно настроить некоторые переменные окружения, чтобы подключиться к базе данных.
Поэтому мы добавляем новый файл в файл. Вместо использования image
мы собираемся использовать build
и передавать относительный путь к Dockerfile
который мы хотим построить.
Мы сопоставляем порт и добавляем переменную окружения для DB_HOST
указывающую на наш сервис db
и порт postgres. И, наконец, мы добавляем depends_on
говоря, что мы зависим от службы db
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
version: '3.1' services: db: image: postgres: 10 ports: - "5432:5432" environment: POSTGRES_PASSWORD: postgres POSTGRES_DB: realworld realworld: build: . ports: - "4321:4321" environment: DB_HOST: "db" depends_on: - db |
Когда мы пытаемся создать экземпляр контейнера, он еще не работает: почему? Я пока не знаю, давайте проверим логи.
Это важная часть наших журналов: depends_on
ожидает готовности контейнера, но не ожидает запуска объектов после инициализации.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
db_1 | fixing permissions on existing directory /var/lib/postgresql/data ... ok db_1 | creating subdirectories ... ok db_1 | selecting default max_connections ... 100 db_1 | selecting default shared_buffers ... 128MB db_1 | selecting dynamic shared memory implementation ... posix realworld_1 | Picked up JAVA_TOOL_OPTIONS: db_1 | creating configuration files ... ok db_1 | running bootstrap script ... ok realworld_1 | SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" . realworld_1 | SLF4J: Defaulting to no-operation (NOP) logger implementation realworld_1 | SLF4J: See http: //www.slf4j.org/codes.html#StaticLoggerBinder for further details. db_1 | performing post-bootstrap initialization ... ok db_1 | syncing data to disk ... ok |
Основное устранение неполадок
В этом случае у нас есть «теория», что приложение пытается подключиться к базе данных, прежде чем все будет готово. Мы должны проверить, что если мы запустим приложение после того, как база данных будет готова, все будет работать.
Самое основное, что мы можем сделать, чтобы убедиться, что это порождать контейнеры и запускать приложение вручную, но как мы можем подключиться к контейнеру с помощью docker-compose
?
Так же, как запуск одного контейнера в Docker, docker-compose
предоставляет метод run
который мы можем использовать для доступа к оболочке внутри нашего контейнера.
1
2
|
# docker-compose run <service> <command> docker-compose run realworld /bin/sh |
Это даст доступ к контейнеру с приложением, позволяющим нам запустить:
1
2
3
4
|
$ java -jar realworldkata- 1.0 -SNAPSHOT-all.jar SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder" . SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http: //www.slf4j.org/codes.html#StaticLoggerBinder for further details. |
если приложение не выдает никакой ошибки, связанной с базой данных, то оно работает, и мы можем приступить к поиску решения проблемы.
Взломать наш путь через
Как решить эту проблему? Добавление скрипта, который проверяет, работает ли база данных, и запускает ли приложение только тогда, когда оно готово, выполнит эту работу.
Современные проблемы, требующие современных решений.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
#! /bin/sh # Set to exit on error set -e # We need to install the psql application to connect to the db apk add --no-cache postgresql-client # Keep in a loop until connects to the postgres database until PGPASSWORD= "postgres" psql -h "${DB_HOST}" -U "postgres" -c '\q' ; do >& 2 echo "Postgres is unavailable - sleeping" sleep 1 done >& 2 echo "Postgres is up - executing command" # Exec the command with the arguments that were passed exec $@ |
У нас уже есть готовое изображение, поэтому мы могли бы перестроить его с помощью скрипта и изменить команду запуска, но тогда мы будем выполнять эту проверку везде, где мы используем изображение, и мы не хотим этого, потому что в других местах мы можем используя базу данных, которая не находится в контейнере Docker.
Таким образом, мы можем переопределить команду из нашего образа и запустить скрипт.
Первое, что нам нужно сделать, это поместить скрипт в контейнер, мы уже создали изображение, поэтому мы не можем снова использовать COPY
, в этом случае мы можем создать том внутри нашего контейнера.
тома
Том — это способ монтирования папки с хост-машины в контейнер. Все внутри этой папки будет отражено в контейнер. Это хорошо, когда вы хотите сохранить такие вещи, как журналы или сохранить в базе данных, которую вы запускаете в контейнере.
Мы изменили docker-compose.yml
чтобы добавить наши новые функции:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
version: '3.1' services: db: image: postgres: 10 ports: - "5432:5432" environment: POSTGRES_PASSWORD: postgres POSTGRES_DB: realworld realworld: build: . ports: - "4321:4321" environment: DB_HOST: "db" depends_on: - db volumes: - "./scripts:/scripts" command: [ "/scripts/wait-for-db.sh" , "java" , "-jar" , "realworldkata-1.0-SNAPSHOT-all.jar" ] |
Мы добавили тег volumes
связывающий папку scripts внутри папки нашего приложения с папкой scripts в корне нашего контейнера, и у нас есть новая команда для запуска со сценарием и команда для запуска приложения.
Делая наш имидж более гибким с ARG
Прямо сейчас приложение выставляет порт «4321», который очень негибкий. Если требуется какое-либо изменение, единственным способом сделать это было бы сопоставление в файле docker-compose
для сопоставления с другим портом. Это можно сделать более гибким, используя ARG
в Dockerfile
.
Какие изменения необходимы для этого?
Установите ключевое слово ARG
в Dockerfile
. Это получит имя аргумента и значение по умолчанию. Хорошо установить значение по умолчанию на тот случай, если вы не хотите каждый раз передавать значение во время сборки.
1
2
|
# ARG NAME=<value> ARG PORT= 4321 |
При использовании ARG
следует позаботиться о другом. Вы не можете использовать ARG
который объявлен в FROM
после того, с которым вы работаете. Думайте как переменные во время кода: вы не можете использовать переменную до ее объявления или использовать переменную, которая была объявлена внутри другой функции.
После создания ARG
пришло время установить переменную среды PORT, чтобы приложение знало, какой порт использовать. Это можно сделать с помощью ключевого слова ENV
.
1
2
|
# ENV NAME $arg ENV PORT $PORT |
Наконец, мы должны изменить ключевое слово EXPOSE
чтобы использовать ARG
вместо жестко заданного значения.
1
|
EXPOSE $PORT |
Окончательный результат будет:
01
02
03
04
05
06
07
08
09
10
11
12
|
FROM adoptopenjdk/openjdk11:jdk- 11.0 . 2.9 as build WORKDIR /realworld COPY . /realworld RUN ./gradlew shadowJar FROM adoptopenjdk/openjdk11:jre- 11.0 . 2.9 -alpine ARG PORT= 4321 WORKDIR /realworld COPY --from=build /realworld/build/libs/realworldkata- 1.0 -SNAPSHOT-all.jar . ENV PORT $PORT EXPOSE $PORT CMD [ "java" , "-jar" , "realworldkata-1.0-SNAPSHOT-all.jar" ] |
И построить контейнер, передавая аргумент:
1
2
|
# docker build . --build-arg ARG=<value> $ docker build . -t realworld:ports --build-arg PORT= 4332 |
Чтобы проверить, работает ли контейнер с правильным портом, вы можете увидеть открытые порты с помощью docker ps
1
2
3
4
5
|
$ docker run -it realworld:args /bin/sh $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 10b6105d1db2 realworld:args "/bin/sh" 5 seconds ago Up 4 seconds 4332 /tcp gravel_pit |
Вы можете увидеть в столбце PORTS
что он представляет 4332/tcp
как это было передано в аргументах сборки, но что если вы захотите сделать это с помощью docker-compose
? Есть ли способ передать build-arg
через файл yml? Конечно.
Измените docker-compose.yaml
чтобы передать аргумент в части сборки, используя тег args
. Теперь эта build
будет иметь несколько значений. Ключевой context
должен быть добавлен, чтобы указать место, где будет находиться ваш Dockerfile
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
# ... realworld: build: context: . args: PORT: 4332 ports: - "4332:4332" environment: DB_HOST: "db" depends_on: - db volumes: - "./scripts:/scripts" command: [ "/scripts/wait-for-db.sh" , "java" , "-jar" , "realworldkata-1.0-SNAPSHOT-all.jar" ] |
После внесения всех изменений просто запустите docker-compose up
и можно проверить, все ли работает в нужном порту, пытаясь создать таблицы.
1
2
|
$ curl localhost: 4332 /database Tables created! |
точки входа
CMD
— не единственный способ запуска контейнера в Docker, фактически до запуска CMD
контейнер имеет ENTRYPOINT
. Иногда вы хотите, чтобы ваш контейнер выполнял более сложный запуск и выполнял несколько команд или сценариев перед запуском вашего приложения. Docker объединит ENTRYPOINT
с CMD
переданным в контейнер, поэтому в случае, если мы имеем в docker-compose.yml
, команда, которая только что wait-for-db.sh
могла быть отделена от команды java
.
Итак, если Dockerfile выглядел так:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
FROM adoptopenjdk/openjdk11:jdk- 11.0 . 2.9 as build WORKDIR /realworld COPY . /realworld RUN ./gradlew shadowJar FROM adoptopenjdk/openjdk11:jre- 11.0 . 2.9 -alpine ARG PORT= 4321 WORKDIR /realworld COPY --from=build /realworld/build/libs/realworldkata- 1.0 -SNAPSHOT-all.jar . ENV PORT $PORT EXPOSE $PORT ENTRYPOINT [ "wait-for-db.sh" ] CMD [ "java" , "-jar" , "realworldkata-1.0-SNAPSHOT-all.jar" ] |
Это будет выполнено как wait-for-db.sh java -jar realworldkata-1.0-SNAPSHOT-all.jar
. Сценарий может сделать несколько вещей и в конце выполнить приложение.
Postgres делает что-то вроде этого. Вместо того, чтобы использовать команду postgres
в качестве точки запуска, он выполняет сценарий, который устанавливает папку, в которой будут храниться данные, устанавливает пароль в правильной переменной среды и проверяет, есть ли .sql
либо файл .sql
или .sh
который нужно запустить до запуск базы данных.
Не запускайте приложение с помощью ENTRYPOINT
. Используйте CMD
чтобы вы могли переопределить команду при docker run
.
Докер-хаб и контейнерный реестр
Построение одинакового образа докера на каждой машине — не самая интуитивно понятная вещь. Возможно, вы захотите использовать образ на другом компьютере, не имея всего исходного кода, а просто с помощью docker-compose.yml
.
Прежде всего, необходимо зарегистрировать учетную запись в Docker Hub и войти в систему с этой учетной записью в командной строке:
1
|
$ docker login |
После входа в систему вы можете отправить изображения в репозитории. Репозиторий основан на имени тега, которое вы даете при создании изображения. При создании первого изображения был флаг --tag
для присвоения имени изображению; хранилище в Docker Hub будет использовать то же имя. Вы можете иметь несколько версий одного и того же изображения в репозитории, добавив в него версию.
1
2
|
# docker build . --tag <repository>/<image-name>:<version> $ docker build . --tag "andretorrescodurance/realworld:0.1" |
Теперь мы можем создать изображение, и когда все будет установлено, вы можете нажать
1
2
|
# docker push <repository>:<version> $ docker push andretorrescodurance/realworld: 0.1 |
Если вы не добавите репозиторий перед именем образа, у вас могут возникнуть проблемы с переносом изображения в Docker Hub.
Теперь вместо создания с нуля, вы можете просто использовать образ из Docker Hub в вашем docker-compose.yml
и запускать, docker-compose.yml
не отправляя исходные файлы.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
version: '3.1' services: db: image: postgres: 10 ports: - "5432:5432" environment: POSTGRES_PASSWORD: postgres POSTGRES_DB: realworld realworld: image: andretorrescodurance/realworld: 0.1 ports: - "4321:4321" environment: DB_HOST: "db" depends_on: - db volumes: - "./scripts:/scripts" command: [ "/scripts/wait-for-db.sh" , "java" , "-jar" , "realworldkata-1.0-SNAPSHOT-all.jar" ] |
И с этим изменением и работающими контейнерами мы можем закончить этот пост.
Источники:
- https://docs.docker.com/engine/reference/builder/
- https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
- https://docs.docker.com/compose/compose-file/
Смотреть оригинальную статью здесь: Контейнеры с Docker Мнения, высказанные участниками Java Code Geeks, являются их собственными. |