Имитация systemd в docker-контейнере и применение testinfra

Наши команды активно используют ansible для создания шаблонов и финальной конфигурации виртуальных машин. Поэтому перед нами встаёт вопрос обеспечения работоспособности и корректности сценариев развёртывания.

В этой заметке будет рассказано об имитации systemd внутри docker-контейнера и валидации финального результата с помощью фреймворка testinfra.

Работа с molecule будет рассмотрена в других заметках.

Задача

  • Предоставить команде простой, быстрый и воспроизводимый инструмент для локальной проверки корректности плейбуков и ролей ansbile.
  • Обеспечить доступ к контейнеру по протоколу ssh для запуска ansbile.
  • И, самое важное, дать возможность использовать модуль ansible.builtin.systemd.

Подход к выбору решения

Наша задача — обеспечить быструю проверку на ноутбуке разработчика. Поэтому:

  • virtualbox + vagrant:
    • долгий старт виртуальной машины;
    • не подходит для счастливчиков на M1.
  • multipass:
    • быстрый, но ощутимый старт виртуальной машины;
    • для доступа по ssh требуется несложная дополнительная настройка.
  • docker:
    • молниеносный старт контейнера;
    • возможность один раз “зашить” переиспользуемую конфигурацию sshd;
    • не спроектирован для использования systemd в качестве init-процесса.

В своей работе мы используем два варианта:

  • multipass + multipass-compose, когда нам требуется работа с “настоящими” виртуальными машинами;
  • docker, когда необходимо быстро проверить изменения.

Рассмотрим решение с docker-образом.

Решение в лоб

Очевидное решение — запустить systemd как init-процесс контейнера. И, конечно, это так работать не будет.

# syntax=docker/dockerfile:1

FROM ubuntu:focal

ENV DEBIAN_FRONTEND=noninteractive

RUN <<EOF
apt update

apt install -y --no-install-recommends \
    systemd
EOF


CMD ["/bin/systemd"]

Контейнер будет запущен, но, как только вы обратитесь к systemctl, будет получен следующий результат:

System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down

Решение, удовлетворяющее условиям поставленной задачи

Подготовим образ, внутри которого будет установлены и настроены:

  • пакет systemd (нужен, фактически, только для создания структуры каталогов);
  • заглушка gdraheim/docker-systemctl-replacement для имитации systemctl и journalctl;
  • пользователь, входящий в группу sudo.

Точкой входа будет выступать sshd.

# syntax=docker/dockerfile:1

FROM ubuntu:focal

ENV DEBIAN_FRONTEND=noninteractive

RUN <<EOF
apt update

apt install -y --no-install-recommends \
    openssh-server \
    sudo \
    gnupg \
    systemd \
    python3

useradd -rm -d /home/ubuntu -s /bin/bash -g root -G sudo -u 1000 ubuntu
echo 'ubuntu:ubuntu' | chpasswd

service ssh start
EOF

COPY systemctl3.py /usr/bin/systemctl
COPY journalctl3.py /usr/bin/journalctl

RUN chmod +x /usr/bin/systemctl /usr/bin/journalctl

EXPOSE 22

CMD ["/usr/sbin/sshd","-D"]

При работе sshd будет игнорировать сигнал SIGTERM для остановки контейнера, поэтому запускать его следует либо с опцией --init, либо с указанием сигнала остановки --stop-signal.

После запуска контейнера, в него можно будет зайти по ssh, что нам необходимо для запуска ansible и testinfra.

ssh \
  -o UserKnownHostsFile=/dev/null \
  -o ControlMaster=auto -o \
  ControlPersist=60s \
    ubuntu@localhost

testinfra

Для проверок состояния сервисов с помощью модуля Service testinfra выполняет ряд инспекций, выбирая, какая подсистема инициализации используется на данной машине.

Чтобы явно сообщить об использовании systemd, нужно использовать довольно безобидный и компактный monkeypatch:

import testinfra.modules.service

testinfra.modules.service.Service.get_module_class = classmethod(
  lambda *args, **kwargs: testinfra.modules.service.SystemdService
)

Это явно указывает testinfra опираться на systemctl.

Итого

Что получили в итоге?

  • Быстрый запуск контейнера с имитацией systemd и доступом по ssh.
  • Простой способ проверки плейбуков и ролей ansbile.
  • Возможность прогонять тесты testinfra.
  • Воспроизводимость и переносимость между машинами разработчиков.

Всё в комплексе можно посмотреть и пощупать у меня на github:

Успехов в работе с ansible и тестировании!

Ссылки