Контейнеры неизменяемы, что означает, что их не следует исправлять на месте, как это делается для виртуальных машин или физических серверов.
Вместо этого обновление контейнера требует повторного развертывания обновленного контейнера и уничтожения старого.
Проблемы, связанные с исправлением контейнеров
Если ваша организация похожа на те, с которыми я работал, не удивительно обнаружить очень старые серверы, операционные системы, приложения и все остальное.
Контейнеры в этом отношении ничем не отличаются – многие команды опасаются уничтожать работающий контейнер, который отлично функционирует.
Если команды не подумали о таких проблемах, как миграция и сохранение данных, может возникнуть повышенная нагрузка, необходимая для запуска цикла исправлений.
Кроме того, я пока не нашел надежного способа выявления контейнеров с уязвимым и устаревшим программным обеспечением для проведения цикла исправлений.
С другой стороны, команды, использующие Docker в полном цикле DevOps, скорее всего, будут постоянно выпускать последние исправления безопасности, что исключает эту проблему.
Давайте рассмотрим обе практики – выполнение правильного цикла DevOps и некоторые потенциальные инструменты для мониторинга!
Команды DevOps устанавливают исправления при каждом развертывании
Если ваши команды разработчиков практикуют DevOps, используют автоматизированные решения интеграции и развертывания и выпускают производственные изменения не реже, чем цикл патчей, то полное обновление контейнеров может быть включено в процесс выпуска без дополнительных усилий.
Все, что нужно, это убедиться, что ваш конвейер CI/CD перестраивает образ развертывания при каждой сборке.
Ниже я покажу очень простой пример с использованием Jenkins.
Автоматическое обновление контейнеров с помощью Jenkins
Давайте создадим простой пример, чтобы увидеть, как это работает.
Если вы еще не знакомы с Jenkins, с этого легко начать.
Сначала мы создадим и запустим контейнер Jenkins, а затем обновим его для работы с Docker.
Создайте следующий Dockerfile в новом каталоге:
# Начните с образа Jenkins
FROM jenkins/jenkins:lts
# Установите Docker в наш образ
USER root
RUN apt update
RUN apt-get install -y apt-transport-https ca-certificates wget software-properties-common
RUN wget https://download.docker.com/linux/debian/gpg
RUN apt-key add gpg
RUN echo "deb https://download.docker.com/linux/debian stretch stable" > /etc/apt/sources.list.d/docker.list
RUN apt update
RUN apt-get install -y docker-ce
# Jenkins запускается под пользователем Jenkins, и ему потребуется
# доступ к unix-сокету docker, который принадлежит root.
# Обычно он должен иметь группу docker, но здесь он
# принадлежит root:root. Мы можем изменить это, но для целей тестирования
# я просто добавлю пользователя jenkins в группу root.
RUN usermod -G root -a jenkins
# Switch back to jenkins user
USER jenkins
Мы должны сделать небольшое изменение, чтобы дать пользователю jenkins доступ к сокету docker (который мы будем монтировать из хост-системы).
Давать групповой root пользователю jenkins – это не то, что я бы хотел делать, но для нашего тестового примера вполне подойдет.
Теперь давайте запустим этот контейнер:
$ docker build . -t jenkins_test
$ mkdir -p ~/jenkins_test
$ docker run \
-p 8080:8080 \
-p 50000:50000 \
--name jenkins \
--privileged=true \
-v /home/nullsweep/jenkins_test:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins_test
Обратите внимание на несколько вещей, которые мы делаем с этим контейнером, чтобы убедиться, что он может создавать и запускать образы docker:
- Запуск в привилегированном режиме, чтобы позволить ему запускать контейнеры братьев и сестер
- Монтирование локального тома для тестовых конфигурационных файлов
- Монтирование сокета docker, что дает контейнеру jenkins контроль над установкой docker на хосте.
Теперь войдите на localhost:8080 и выполните шаги по настройке Jenkins и создайте пайплайн под названием test.
Мы будем использовать это имя позже для размещения файлов проекта. В пайплайн добавьте простой скрипт в раздел “Pipeline”, тип – “Pipeline script”.
node {
stage('Build image') {
sh "docker build -t test --pull ."
}
}
Обратите внимание, что здесь используется команда shell для сборки образа, а не плагин docker.
При использовании плагина можно получить сообщения, связанные с ошибкой Jenkins 31507 java.io.IOException: Cannot retrieve .Id from ‘docker inspect base AS final’.
Замена docker на команду оболочки исправила ситуацию.
# test в приведенном ниже пути - это имя пайплайна, настроенного в Jenkins
$ cd ~/jenkins_test/workspace/test
$ cat Dockerfile
FROM nullsweep/test:latest
CMD sh echo "I ran!"
Вернувшись в Jenkins, запустите сборку, нажав “Build Now”, и вы должны увидеть успешный пайлайн.
Пайплайн запускает скрипт оболочки для вызова docker и передает параметр –pull для принудительной перекомпоновки базовых образов.
Теперь у нас есть зачатки пайплайна, который будет отправлять последние исправления для ОС с каждым выпуском кода (при условии, что базовые образы поддерживаются в актуальном состоянии в своих репозиториях).
Никаких больше переносов патчей!
Мониторинг с помощью WatchTower
Что делать, если вы не выпускаете код хотя бы раз в месяц?
Это часто случается с контейнерами, такими как контейнеры баз данных или полностью готовые контейнеры приложений, такие как контейнеры WordPress.
В этих случаях мы можем рассмотреть возможность использования такого инструмента, как WatchTower, для мониторинга и автоматического обновления наших контейнеров.
Обзор WatchTower
WatchTower – это сопровождающий контейнер, который отслеживает все запущенные контейнеры на хосте на предмет наличия обновлений в их образе.
Если обновление найдено, он может автоматически остановить, обновить и перезапустить контейнер.
Он имеет достаточное количество опций для мониторинга, удаленного мониторинга, чатопсов и уведомлений.
Пример WatchTower
Мы создадим пользовательский базовый образ с файлом версии:
FROM alpine:latest
RUN echo "1.0" >> version.txt
CMD echo "Version: " && cat version.txt
Мы разместим его в публичном репозитории docker под названием watch_base.
$ docker build . -t nullsweep/watch_base
$ docker push nullsweep/watch_base
И мы укажем его в новом образе, который мы хотим развернуть в качестве нашего приложения:
FROM nullsweep/watch_base
RUN printf "while [ 1 ] \n \
do \n \
echo 'Version: ' \n \
cat version.txt \n \
sleep 5s \n \
done \n" >> version.sh
CMD source version.sh
Этот образ создает скрипт оболочки, который выводитт файл version.txt каждые 5 секунд.
Давайте разместим его в новом публичном репозитории.
$ docker build . -t nullsweep/watch
$ docker push nullsweep/watch
# В первом терминале: скачайте и запустите контейнер для контроля:
$ docker pull nullsweep/watch
$ docker run --name watch nullsweep/watch:latest
Version:
1.0
# Во втором терминале запустите WatchTower:
$ docker pull v2tec/watchtower
$ docker run \
-d \
--name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
v2tec/watchtower -i 5 --debug watch
Система запустит watchtower с парой пользовательских флагов: мониторинг только нашего собственного контейнера и проверка обновлений образа каждые 5 секунд вместо стандартных 5 минут.
В любой момент вы можете просмотреть журналы watchtower, выполнив команду:
$ docker logs watchtower
Вернувшись за пределы виртуальной машины, вы можете выполнить два теста:
- Обновите образ watch_base, чтобы увеличить номер версии, и отправьте его в репозиторий. WatchTower не заметит этого изменения.
- Пересоберите образ приложения (тот, который я назвал watch выше) и отправьте его в репозиторий. WatchTower вытащит его и перезапустит виртуальную машину с новой версией. Теперь у нас есть исправленный контейнер!
Заключение
Контейнеры создают некоторые уникальные проблемы, связанные с исправлениями, но могут действительно упростить работу по обеспечению безопасности, если их поместить в конвейер CI/CD, который автоматически собирает новейшие контейнеры.
Хотя я считаю, что на данный момент у нас нет подходящего решения для мониторинга исправлений в контейнерах, WatchTower – это кандидат, который со временем может развиваться в этом направлении.