Чому вам не потрібен 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 ----pid net


Ви отримаєте консольний доступ всередину контейнера. Якщо ви хочете відразу запустити скрипт або програму — додайте їх аргументом до 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», переконайтеся, що це правда необхідно.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.