Почему вам не нужен sshd в Docker-контейнере

в 7:56, , рубрики: docker, linux, виртуализация, Серверное администрирование

Почему вам не нужен sshd в Docker контейнереКогда люди запускают своей первый Docker-контейнер, они часто спрашивают: «А как мне попасть внуть контейнера?» и ответ «в лоб» на этот вопрос, конечно: «Так запустите в нём SSH-сервер и приконнектитесь!». Цель этого топика — показать, что на самом деле вам не нужен sshd внутри вашего контейнера (ну, конечно, кроме случая, когда ваш контейнер собственно и предназначен для инкапсуляции SSH-сервера).

Запустить SSH-сервер — заманчивая идея, поскольку это даёт быстрый и простой доступ «внутрь» контейнера. Все умеют пользоваться SSH-клиентами, мы делаем это каждый день, мы знакомы с доступами по паролям и по ключам, перенаправлением портов, ну и вообще доступ по SSH — хорошо знакомая вещь, точно будет работать.

Но давайте подумаем ещё.

Давайте представим себе, что вы собираете Docker-образ для Redis или веб-сервиса на Java. Я хотел бы задать вам несколько вопросов:

Зачем Вам ssh?
Скорее всего вы хотите делать бекапы, проверять логи, может быть перезапускать процессы, править настройки, отлаживать что-то с помощь gdb, strace или подобных утилит. Так вот, это можно делать и без SSH.

Как вы будете управлять ключами и паролями?
Вариантов не много — либо вы их «намертво» зашьёте в образ, либо положите на внешний том. Подумайте, что нужно будет сделать для обновления ключей или паролей. Если они будут вшиты — придётся пересобирать образ, передеплоивать его, перезапускать контейнеры. Не конец света, но как-то не элегантно. Значительно лучшим решением будет положить данные на внешний том и управлять доступом к нему. Это работает, но важно проверить, чтобы контейнер не имел доступа на запись в данный том. Ведь если доступ будет — контейнер может повредить данные, и тогда вы не сможете подсоединиться по SSH. Что ещё хуже — если один том будет использоваться в качестве средства хранения данных для аутентификации в несколько контейнеров — вы потеряете доступ сразу ко всем. Но это только если вы везде будете использовать доступ по SSH.

Как вы будете управлять обновлениями безопасности?
SSH-сервер это вообще-то достаточно надёжная штука. Но всё-же это окно во внешний мир. А значит нам нужно будет устанавливать обновления, следить за безопасностью. Т.е. в любом самом что ни на есть безобидном контейнере у нас теперь будет область, потенциально уязвимая ко взлому извне и требующая внимания. Мы своими руками создали себе проблему.

Достаточно ли «просто добавить SSH-сервер» чтобы всё работало?
Нет. Докер управляет и следит за одним процессом. Если вы хотите управлять несколькими процессами внутри контейнера — вам понадобится что-то типа Monit или Supervisor. Их тоже нужно добавить в контейнер. Таким образом мы превращаем простую концепцию «один контейнер для одной задачи» во что-то сложное, что нужно строить, обновлять, управлять, поддерживать.

Вы ответственны за создание образа контейнера, но вы также ответственны и за управление политиками доступа к контейнеру?
В маленьких компаниях это не имеет значения — скорее всего вы будете выполнять обе функции. Но при построении большой инфраструктуры скорее всего один человек будет создавать образы, и совсем другие люди будут заниматься управлением правами доступа. А значит «вшивание» SSH-сервера в контейнер — не лучший путь.

Но как же мне ...

Делать бекапы?

Ваши данные должны храниться на внешнем томе. После этого вы можете запустить другой контейнер с опцией --volumes-from, который будет иметь доступ к тому же тому. Этот новый контейнер будет специально для выполнения задач бекапа данных. Отдельный профит: в случае обновлениязамены инструментов бекапа и восстановления данных вам не нужно обновлять все контейнеры, а только тот один, который предназначен для выполнения этих задач.

Проверять логи?

Используйте внешний том! Да, снова то же самое решение. Ну а что поделаешь, если оно подходит? Если вы будете писать все логи в определённую папку, а она будет на внешнем томе, вы сможете создать отдельный контейнер («инспектор логов») и делать в нём всё, что вам нужно. Опять таки, если вам нужны какие-то специальные инструменты для анализа логов — их можно установить в этот отдельный контейнер, не замусоривая исходный.

Перезапустить мой сервис?

Любой правильно спроектированный сервис может быть перезапущен с помощью сигналов. Когда вы выполняете команду foo restart — она практически всегда посылает процессу определённый сигнал. Вы можете послать сигнал с помощь команды docker kill -s . Некоторые сервисы не реагируют на сигналы, а принимают команды, например из TCP-сокета или UNIX-сокета. К TCP-сокету вы можете приконнектиться извне, а для UNIX-сокета — опять-таки используйте внешний том.

«Но это всё сложно!» — да нет, не очень. Давайте представим, что ваш сервис foo создаёт сокет в /var/run/foo.sock и требует от вас запуска fooctl restart для корректного перезапуска. Просто запустите сервис с -v /var/run (или добавьте том /var/run в Dockerfile). Когда вы хотите перезапустить сервис, запустите тот же образ, но с ключом --volumes-from. Это будет выглядеть как-то так:

# запуск сервиса
CID=$(docker run -d -v /var/run fooservice)
# перезапуск сервиса с помощью внешнего контейнера
docker run --volumes-from $CID fooservice fooctl restart
Отредактировать конфигурацию?

Во-первых следует отличать оперативные изменения конфигурации от фундаментальных. Если вы хотите изменить что-то существенное, что должно отразиться на всех будущих контейнерах, запущенных на основе данного образа — изменение должно быть вшито в сам образ. Т.е. в этом случае SSH-сервер вам не нужен, вам нужна правка образа. «Но как же оперативные изменения?» — спросите вы. «Ведь мне может быть нужно менять конфигурацию по ходу работы моего сервиса, к примеру, добавить виртуальные хосты в конфиг веб-сервера?». В этом случае вам нужно использовать… подождите-подождите… внешний том! Конфигурация должна быть на нём. Вы даже можете поднять специальный контейнер с ролью «редактор конфигов», если хотите, установить там любимый редактор, плагины к нему, да что угодно. И это всё никак не будет влиять на базовый контейнер.
«Но я делаю всего лишь временные правки, экспериментирую с разными значениями и смотрю на результат!». Ок, для получения ответа на этот вопрос читайте следующий раздел.

Отлаживать мой сервис?

И вот мы добрались до случая, когда вам действительно нужен настоящий консольный доступ «внутрь» вашего контейнера. Вам ведь нужно где-то запускать gdb, strace, править конфигурацию, и т.д. И в этом случае вам понадобиться nsenter.

Что такое nsenter

nsenter это маленькая утилита, позволяющая вам попадать внутрь пространств имён (namespaces). Строго говоря, она может как входить в уже существующие пространства имён, так и запускать процессы в новых пространствах имён. «Что это вообще за пространства имён, о которых мы тут говорим?». Это важная концепция, связанная с Docker-контейнерами, позволяющая им быть независимыми друг от друга и от родительской операционной системы. Если не углубляться в детали: с помощью nsenter вы можете получить консольный доступ к существующему контейнеру, даже если внутри него нет SSH-сервера.

Где взять nsenter?

С Гитхаба: jpetazzo/nsenter. Можете запустить

docker run -v /usr/local/bin:/target jpetazzo/nsenter

Это установит nsenter в /usr/local/bin и вы сразу сможете его использовать. Кроме того, в некоторых дистрибутивах nsenter уже встроен.

Как его использовать?

Сначала выясните PID контейнера, внутрь которого хотите попасть:

PID=$(docker inspect --format {{.State.Pid}} <container_name_or_ID>)

Теперь зайдите в контейнер:

nsenter --target $PID --mount --uts --ipc --net --pid

Вы получите консольный доступ «внутрь» контейнера. Если вы хотите сразу запустить скрипт или программу — добавьте их аргументом к nsenter. Работает слегка похоже на chroot, с той лишь разницей, что касаемо контейнеров, а не просто директорий.

Как на счёт удалённого доступа?

Если вам нужен удалённый доступ к докер-контейнеру, у вас есть как минимум два способа сделать это:

  • SSH на хост-машину, а дальше использование nsenter
  • SSH на хост-машину со специальным ключом, дающим возможность запустить определённую команду (в нашему случае — nsenter)

Первый путь достаточно прост, но он требует прав рута на хостовой машине (что с точки безопасности не очень хорошо). Второй путь предполагает использование специальной возможности "command" авторизационных ключей SSH. Вы наверняка видел «классический» authorized_keys типа вот такого:

ssh-rsa AAAAB3N…QOID== jpetazzo@tarrasque

(Конечно, реальный ключ намного длиннее.) Вот в нём вы и можете указать определённую команду. Если вы хотите дать определённому пользователю проверять количество свободной ОЗУ на вашей машине, используя SSH-доступ, но не хотите давать ему полный доступ к консоли, вы можете написать в authorized_keys следующее:

command="free" ssh-rsa AAAAB3N…QOID== jpetazzo@tarrasque

Теперь, когда пользователь приконнектиться с использованием этого ключа, сразу будет запущена команда free. И ничего другого не может быть запущено. (Технически, вы возможно захотите добавить no-port-forwarding, смотрите детали в manpage по authorized_keys). Идея этого механизма в разделении полномочий и ответственности. Алиса создаёт образы контейнеров, но не имеет доступа к продакшн-серверам. Бетти имеет право на удалённый доступ для отладки. Шарлотта — только на просмотр логов. И т.д.

Выводы

Действительно ли это ну вот прямо УЖАСНО запускать SSH-сервер в каждом Docker-контейнере? Давайте будем честными — это не катастрофа. Более того, это может быть даже единственным вариантом, когда у вас нет доступа к хостовой системе, но непременно нужен доступ к самому контейнеру. Но, как мы увидели из статьи, есть много способов обойтись без SSH-сервера в контейнере, имея доступ ко всему необходимому функционалу и получив в то же время весьма более элегантную архитектуру системы. Да, в докере можно сделать и так, и так. Но перед тем как превращать свой Docker-контейнер в такой себе «мини-VPS», убедитесь, что это правда необходимо.

Автор: tangro

Источник

Поделиться

* - обязательные к заполнению поля