Перестраивать контейнер Docker при изменении файла

86

Для запуска приложения ASP.NET Core я создал файл dockerfile, который создает приложение и копирует исходный код в контейнер, который Git получает с помощью Jenkins. Итак, в моем рабочем пространстве в файле dockerfile я делаю следующее:

WORKDIR /app
COPY src src

Хотя Дженкинс правильно обновляет файлы на моем хосте с помощью Git, Docker не применяет это к моему изображению.

Мой базовый скрипт для сборки:

#!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

containerRunning=$(docker inspect --format="{{ .State.Running }}" $containerName 2> /dev/null)

if [ "$containerRunning" == "true" ]; then
        docker stop $containerName
        docker start $containerName
else
        docker run -d -p 5000:5000 --name $containerName $imageName
fi

Я пробовал разные вещи, такие как --rmи --no-cacheпараметр, docker runа также остановку / удаление контейнера до создания нового. Я не уверен, что я здесь делаю не так. Кажется, что докер правильно обновляет изображение, так как вызов COPY src srcприведет к идентификатору уровня и отсутствию вызова кеша:

Step 6 : COPY src src
 ---> 382ef210d8fd

Каков рекомендуемый способ обновления контейнера?

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

Лев
источник
Можете ли вы перечислить точные шаги, которые вы предприняли для создания контейнера, включая вашу команду сборки и весь вывод каждой команды?
BMitch

Ответы:

136

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

Почему требуется удаление

Следовательно, перестройки и перезапуска недостаточно. Я думал, что контейнеры работают как служба: остановите службу, внесите изменения, перезапустите ее, и они будут применены. Это была моя самая большая ошибка.

Поскольку контейнеры являются постоянными, вы должны docker rm <ContainerName>сначала удалить их, используя . После того, как контейнер удален, вы не можете просто запустить его docker start. Это нужно сделать с помощью docker run, который сам использует последний образ для создания нового экземпляра контейнера.

Контейнеры должны быть максимально независимыми

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

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

Почему -rfможет тебе не помочь

docker runКоманда имеет Очистите переключатель под названием -rf. Это остановит поведение докер-контейнеров навсегда. Используя -rf, Docker уничтожит контейнер после его выхода. Но у этого переключателя есть две проблемы:

  1. Docker также удаляет тома без имени, связанного с контейнером, что может убить ваши данные.
  2. Используя этот параметр, невозможно запускать контейнеры в фоновом режиме с помощью -dпереключателя

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

Как снять контейнер

Мы можем обойти эти ограничения, просто удалив контейнер:

docker rm --force <ContainerName>

Переключатель --force(или -f), который использует SIGKILL для запущенных контейнеров. Вместо этого вы также можете остановить контейнер до:

docker stop <ContainerName>
docker rm <ContainerName>

Оба равны. docker stopтакже использует SIGTERM . Но использование --forceпереключателя сократит ваш сценарий, особенно при использовании серверов CI: docker stopвыдает ошибку, если контейнер не запущен. Это приведет к тому, что Jenkins и многие другие серверы CI ошибочно будут считать сборку неудачной. Чтобы исправить это, вы должны сначала проверить, работает ли контейнер, как я сделал в вопросе (см. containerRunningПеременную).

Полный скрипт для восстановления контейнера Docker

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

#!/bin/bash
imageName=xx:my-image
containerName=my-container

docker build -t $imageName -f Dockerfile  .

echo Delete old container...
docker rm -f $containerName

echo Run new container...
docker run -d -p 5000:5000 --name $containerName $imageName

Это отлично работает :)

Лев
источник
4
«Я обнаружил, что у меня есть некоторые недопонимания относительно срока службы контейнеров Docker», - вы выговорили слова прямо из моего рта. Спасибо за столь подробное объяснение. Я бы порекомендовал это новичкам в докере. Это проясняет разницу между виртуальной машиной и контейнером.
Винс Бансон 02
2
После вашего объяснения я обратил внимание на то, что я сделал с моим существующим изображением. Чтобы сохранить изменения, я создал новый файл Dockerfile для создания нового образа, который уже включает изменения, которые я хочу добавить. Таким образом, новый созданный образ (в некоторой степени) обновляется.
Винс Бансон 02
Есть --force-recreateвариант на Docker композе похожа на то , что вы описали здесь? И если да, то не стоит ли использовать это решение вместо этого (извините, если этот вопрос глупый, но я докер-нуб ^^)
cglacet
2
@cglacet Да, похоже, напрямую сравнивать нельзя. Но docker-composeумнее простых dockerкоманд. Я работаю регулярно, docker-composeобнаружение изменений работает хорошо, поэтому пользуюсь --force-recreateочень редко. Просто docker-compose up --buildважно, когда вы создаете собственный образ ( buildдиректива в файле compose) вместо использования образа, например, из концентратора Docker.
Lion
33

Всякий раз, когда в dockerfile, compose или requirements вносятся изменения, повторно запускайте его, используя docker-compose up --build. Чтобы изображения были перестроены и обновлены

Юнус
источник
1
Имея контейнер докеров MySQL в качестве одной службы, будет ли БД пустой после этого, если для этого используется том /opt/mysql/data:/var/lib/mysql?
Мартин Тома
Для меня нет никаких недостатков в том, чтобы просто всегда использовать --buildв локальных средах разработки. Скорость, с которой докер повторно копирует файлы, которые в противном случае могли бы предположить, что копирование не требуется, занимает всего пару миллисекунд и сохраняет большое количество моментов WTF.
Danack
0

Вы можете запустить buildконкретную службу, запустив ее docker-compose up --build <service name>там, где имя службы должно совпадать с тем, как вы ее вызывали, в файле docker-compose.

Пример Предположим, что ваш файл для создания докеров содержит множество служб (приложение .net - база данных - давайте зашифровать ... и т. Д.), И вы хотите обновить только приложение .net, имя которого указано applicationв файле составления докеров. Затем вы можете просто запуститьdocker-compose up --build application

Дополнительные параметры Если вы хотите добавить к своей команде дополнительные параметры, например, -dдля работы в фоновом режиме, параметр должен быть перед именем службы: docker-compose up --build -d application

Aamer
источник