Статьи

Контейнеры с докером

В этом посте мы увидим несколько вещей:

Докер: Что это?

Я мог бы попытаться объяснить, что такое контейнер, но объяснение, найденное на веб-сайте 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 который нужно запустить до запуск базы данных.

докер-библиотека / Postgres

Не запускайте приложение с помощью 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"]

И с этим изменением и работающими контейнерами мы можем закончить этот пост.

Источники:

Смотреть оригинальную статью здесь: Контейнеры с Docker

Мнения, высказанные участниками Java Code Geeks, являются их собственными.