Pull to refresh

Поняв Docker

Reading time 14 min
Views 221K

Если вы еще никогда не поддерживали чужие приложения, или пусть даже свои, но таких размеров, что уже не помещаются в одной голове, то прошу вас расслабиться, откинуться на спинку кресла и воспринимать прочитанное как поучительную сказку с надуманными проблемами, забавным сюжетом и очевидным счастливым концом. В противном случае, если реальный боевой опыт у вас имеется, добро пожаловать в ад, но с IDDQD и IDKFA.


К вашему сведению! В этой статье мы рассматриваем само явление docker-контейнеров, а не составляем список микросервисов, которые гнездятся внутри. Этим мы займемся в следующей серии, во имя справедливости!


UPDATE: пришлось заменить «докер» на «docker», иначе статья не ищется. Заранее прошу прощения за все «docker'ы» в тексте. Селяви.


Что мы имеем сегодня


  • Зоопарк дубовых VPS-хостингов.
  • Дорогие IaaS и PaaS с гарантированным vendor lock in.
  • Уникальные сервера-снежинки.
  • Ворох устаревших зависимостей на неподдерживаемой операционке.
  • Скрытые связи частей приложения.
  • Незаменимый админ полубог на скейтборде.
  • Радуга окружений: development, testing, integration, staging, production.
  • Генерация конфигов для системы управления конфигами.
  • Feature flagging.

Удивительно то, что корень всех этих проблем только в одном: сервер дорого поднимать, а потом еще и дорого гасить. Дорогим может оказаться время потраченное на художественное плетение по конфигам, или энтерпрайз железяка просто долго стартует, или состояние серверов надо хитро бекапить и потом восстанавливать, или всё просто и быстро, но работает только на какой-то уникально дорогущей платформе, с которой не сбежишь.


Конечно, это капитанское вступление. И цель вступления как раз в капитанстве — показать, что эти проблемы уже настолько банальны, что даже простое их перечисление заставляет кое-где болеть.


Если представить, что на холодный запуск приложения на новой машине уйдет минута, и при этом все данные в сохранности, то дышать сразу становится легче, а на лице появляется предвкушение написания полезного кода заместо возни с сервером.


Если ваши уши владеют английским, то прошу сначала послушать восторжженное Введение в docker от основателя.


Назад к основам


Как часто бывает, в какой-то момент состояние некой системы становится неуправляемым и требует новых технологий. В программировании, правда, часто выходит наоборот — требуется вспомнить старые технологии. Вот и в случае с docker'ом не случилось ничего нового: взяли принципы функционального программирования (другие видят ООП) и микроядерности, применили к инфраструктурному слою приложения (серверам, сети и т.п.) и получили stateless-иммутабельные-изолированные-микросервисы. Все прелести docker'а вырастают именно из этого. Как известно, функциональщина не так проста, как хотел бы Рич Хикки. Чтобы можно было каждый день пользоваться мудростью предков, нужно иметь хорошие инструменты. Docker как раз и есть такой инструмент.


Вот базовые отличия docker-контейнера (не микросервиса, см. выше) от простого сервера.


Стейтлес


Конфигурация контейнера не может и не должна меняться после запуска. Все предварительные работы делаются на этапе создания образа или старта контейнера: конфиги, порты, общие папки, переменные окружения, всё это должно быть известно к моменту старта контейнера. Конечно, docker позволяет запущенному внутри контейнера процессу делать со своей памятью и файловой системой всё что тому заблагорассудится, но трогать что-то, что можно было потрогать до запуска считается плохим тоном.


Чистый (pure)


Контейнер ничего не знает о хост системе и не может мешать другим контейнерам: ни влезть в чужую файловую систему, ни послать сигнал чужому процессу, ни даже в случайный порт стукнуться. На то он и контейнер. Само собой, docker позволят контейнерам общаться, но только строго задекларированными способами. Еще можно запускать контейнеры наделенные какими-нибудь суперсилами, например, доступом к реальной сети или физическим девайсам.


Ленивый


При запуске контейнер не копирует файловую систему образа, из которого создан. Контейнер всего-лишь создает пустую файловую систему поверх образа. В docker'е это называется слоем. Так же устроены и образы. Образ состоит из списка (геологических) слоев, накладывающихся друг на друга. Отсюда такая высокая скорость запуска нового контейнера: меньше секунды.


Декларативный


Все свойства контейнера хранятся в декларативном виде. Этапы создания образа тоже описаны строго разделенными шагами. Параметры сети, содержимое файловой системы, объем памяти, публичные порты и так далее и тому подобное задается в Dockerfile или статичными ключами при запуске. Всё это легко читается на паре экранов текста даже для очень сложной системы.


Функциональный


Контейнер делает только одно дело, но делает его хорошо. Предполагается, что в контейнере будет жить всего один процесс (можно с семьёй), выполняющий всего одну функцию в приложении. За счет того, что в контейнере нет своего ядра, загрузочного раздела, init-процесса и, чаще всего, даже пользователь всего один (псевдо-root) — то есть в контейнере нет полноценной операционной системы — за счет отсутствия всего этого контейнер стартует так быстро, как стартовал бы ваш сервис будь операционная система уже полностью загружена. Это узкая специализация делает функцию реализуемую контейнером предсказуемой и масштабируемой. Так как процесс только один, ему неоткуда ждать (если только снаружи) переполнения логов, попадания в своп и тому подобного.


Строгий


По-умолчанию, docker запрещает контейнеру всё, кроме доступа в сеть (которую тоже можно запретить). Однако, при необходимости позволяется нарушать любые эти правила, когда нарушить правила бывает логичнее. И что интересно, разрешать доступ также просто, как и запрещать. Соединять или разъединять контейнеры в docker'е с каждой версией становится всё проще и проще, особенно радует работа с сетью, распределенной по датацентрам.


Чем docker не является


Docker часто путают с Vagrant’ом или OpenVZ, или даже VirtualBox’ом. Docker это всего-лишь user-space демон на языке Go, который умело жонглирует уже существующими технологиями ядра Linux. Думаю, стоит объяснить подробнее, так как сам начал знакомиться с темой с запроса «docker vs. vagrant».


Не вагрант


Вагрант занимается тем, что управляет виртуальными машинами (или, более приближенно к теме хостинга, виртуальными серверами). Так же как и у docker'а, у вагранта есть целая библиотека образов виртуальных машин, и он как и docker умеет их снепшотить, скачивать и заливать, настраивать и запускать. Но повторюсь, Vagrant управляет полноценными тяжелыми виртуальными машинами запущенными, например, в VirtualBox, VMWare, DigitalOcean, AWS, да где только он их не запускает. Шикарная вещь, этот вагрант, если вам нужна полноценная виртуальная машина с состоянием внутри.


Не виртуальная машина


И конечно, docker вовсе не является новой технологией (пара)виртуализации, как например KVM/Qemu, VirtualBox/VMWare/Parallels, XEN, etc. Docker-демон (не путать с консольной командой docker) работает только в Линуксе (и скоро в винде) поверх линуксовых же контейнеров. А контейнеры, в свою очередь, не являются полноценной виртуальной средой, так как они делят общее запущенное ядро одной физической машины. Если вы работали с OpenVZ или щупали Linux Containers то вы должны знать, как они устроены, чем хороши и плохи.


А что же тогда docker?


Docker состоит из трех частей:


  1. docker daemon — сердце docker'а. Это демон работающий на хост-машине и умеющий скачивать и заливать образы, запускать из них контейнеры, следить за запущенными контейнерами, собирать логи и настраивать сеть между контейнерами (а с версии 0.8 и между машинами). А еще именно демон создает образы контейнеров, хоть и может показаться, что это делает docker-client.


  2. docker это консольная утилита для управления docker-демоном по HTTP. Она устроена очень просто и работает предельно быстро. Вопреки заблуждению, управлять демоном docker'а можно откуда угодно, а не только с той же машины. В сборке нового образа консольная утилита docker принимает пассивное участие: архивирует локальную папку в tar.gz и передает по сети docker-daemon, который и делает всё работу. Именно из-за передачи контекста демону по сети лучше собирать тяжелые образы локально.


  3. Docker Hub централизованно хранит образы контейнеров. Когда вы пишете docker run ruby docker скачивает самый свежий образ с руби именно из публичного репозитория. Изначально хаба не было, его добавили уже после очевидного успеха первых двух частей.

Что docker заменил


Для пользы дела стоит перечислить технологии, ставшие лишними при работе с инфраструктурой после появления docker'а. Хочу заметить, что для других применений эти технологии до сих пор незаменимы (вот как, например, заменить SSH или Git используемые по назначению?).


Chef / Puppet / Ansible


Очевидный кандидат на вылет — системы управления конфигурацией stateful сервера. Им на смену приходит лаконичный и быстрый Dockerfile. Так как docker умеет собирать образ контейнера за секунды, а запускать сам контейнер за миллисекунды, больше не надо громоздить скрипты, поддерживающие тяжелый и дорогой сервер в актуальном состоянии. Достаточно запустить новый контейнер и погасить старый. Если вы и до прихода docker'а использовали Chef для быстрого развертывания stateless серверов, то вы уже любите docker, а статью читаете ради удовольствия и сбора аргументов против админа-слоупока, сидящего на FreeBSD 4.11.


Upstart / SystemD / Supervisor / launchd / God.rb / SysVinit, тысячи их


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


Ubuntu_14.04.iso / AMI-W7FIS1T / apt-get update


Так же как и вагрант, docker позволяет, наконец, забыть про скачивание давно устаревших установочных DVD-образов (совсем недавно пришлось качать для Dell блейда), помнить панели управления разных облачных платформ и сокрушаться, что самой свежей убунты у них никогда нет. А главное, запустить свой собственный кастомный образ со всем необходимым можно за пару минут, собрав его на локальной машине и загрузив в бесплатное облако Docker Hub. И никаких больше apt-get update после установки — ваши образы всегда будут готовы к работе сразу после сборки.


RUBY_ENV, database.dev.yml, testing vs. staging vs. backup


Так как docker позволяет запускать контейнеры со всеми зависимостями и окружением бит-в-бит совпадающим с тем, что вы сконфигурировали, можно за пару секунд поднять точную копию продакшна прямо на своём ноутбуке. Все операционные системы, все сервера баз данных и кеширования, само приложение и версии библиотек им используемые, всё это будет точно воспроизведено на девелоперской машине или на машине тестера. Всегда мечтали иметь резервный продакшн сервер готовый к бою в случае падения главного — docker-compose up и у вас два боевых сервера являющихся копией друг друга с точность до бита. Начиная с версии docker'а 1.10 идентификаторы контейнеров являются их же собственной SHA256 подписью гарантирующей идентичность.


Xcode, brew, port install, ./configure && make && sudo make install, mysql --version


Всё, больше никаких мук с установкой серверного софта локально, никаких несовпадений версий мускуля для разных проектов, и возни с удалением всего этого добра после завершения проекта. Это еще ничего, если вы маковод, а вот если у вас виндоус… Docker позволит запускать почти всё, что угодно, лишь бы это работало на линуксе. Можно и Хром в контейнере запускать.


SSH (sic!), VPN, Capistrano, Jenkins-slave


Для того чтобы запустить контейнер не нужно получать root на сервере, морочиться с ключами и настраивать систему деплоя. С docker'ом деплой выглядит как сборка и настройка физического сервера дома, дальнейшего его всестороннего тестирования, а затем магического перемещения в произвольное количество датацентров одной командой. Для этого достаточно запустить docker run на своём ноутбуке и docker сам секьюрно подключится к серверу по TLS и всё запустит. Внутрь контейнера тоже попасть проще простого: docker exec -it %containername% bash и у вас в руках консоль для отладки. Можно даже rsync через docker'овский туннель гонять вот так: rsync -e 'docker exec -i' --blocking-io -rv CONTAINER_NAME:/data ., главное не забыть добавить --blocking-io.


Git (!!!)


Используете Git как способ выкатки приложения на сервер? Отлично! Docker это буквально новый Git, только для контейнеров. Вместо git push (и настройки кучи дополнительного софта и сопряжения с Github’ом) вы получаете docker push. Вместо запуска скрипта деплоя, docker-compose up. И все контейнеры обновятся и перезапустятся. Конечно, если вы хотите zero-downtime деплой, надо будет немного покумекать. Но даже без этого, ваши пользователи увидят MAINTENANCE.HTML не более чем на несколько секунд. Вдобавок, теперь вы можете отправлять тестру не хеш коммита гита и ждать, пока он (или дженкинс) воссоздаст окружение, а просто шлёте ему хеш контейнера и он его сразу запускает.


Datacenter lock-in


Всё, больше его нет. Хотите переезжайте всем приложением за пару минут на другой континент, а хотите — размазывайте своё приложение по всему земному шару. Контейнерам всё равно где крутиться, они объединены виртуальной сетью, видят и слышат друг друга отовсюду, включая девелоперкую машину в случае живой отладки.


bundler, rvm, dotenv, /opt


Нужна старая версия руби для запуска старого скрипта раз в месяц — упакуйте его в docker со старым руби. Нужны разные версии библиотек для разных частей приложения — разбейте по контейнерам и не заморачивайтесь с bundle exec.


*.log, *.pid


И логами, и процессами заведует docker. Есть много драйверов сборки логов (kudos ➞ Fesor) и отправки их на анализ. Можно грабить корованы.


useradd www


Можно забыть детские кошмары о том, что веб-сервер кто-то взломает и получит доступ ко всей системе, если его запустить от рута. А еще не нужно больше искать следы взлома в страхе, что теперь надо всё переустанавливать. Просто обновите софт в образе, убейте контейнер (или всю машину целиком, только базу забекапьте, пожалуйста) и перезапустите сервис.


chroot, CGroups, LXC


Docker объединяет и абстрагирует сложные и разрозненные технологии в своих библиотеках libcontainer, runC, libnetwork. Если вы пробовали настроить LXC, то уже любите docker. Пока эти библиотеки как отдельные проекты еще свежи, но уже используется в самом docker'е.


Вкусности docker'а


Dockerfile


Может показаться, что Dockerfile это старый добрый Chef-конфиг, только на новый лад. А вот и нет: от конфигурации сервера в нем осталась только одна строка — имя базового образа операционной системы. Остальное же — часть архитектуры приложения. И это стоит воспринимать, как декларацию API и зависимостей именно сервиса, а не сервера. Эта часть пишется проектирующим приложение программистом по ходу дела естественным путём прямо в процессе разработки. Такой подход дает не только поразительную гибкость конфигурации, но и позволяет избежать испорченного телефона между разработчиком и админом. Больше про это ждите в статье про микросервисы.


Слоёные имаджи


Имаджи в docker не монолитны, а состоят из copy-on-write слоев. Это позволяет переиспользовать файлы базового readonly имаджа во всех контейнерах бесплатно, запускать контейнер, не копируя файловую систему имаджа, делать контейнеры readonly, а также кешировать разные этапы сборки имаджа. Сильно похоже на коммиты гита, если вы знакомы с его архитектурой.


Плагины и драйверы


Fesor:


Для него есть и драйвера логгеров, и плагины для волумов и сетей… словом… и это все еще будет развиваться но и уже немало.

Самое приятное, что никакой магии в плагинах нет. Простые, как выражаются разработчики докера, «водопроводные работы».


docker-daemon — это асинхронный HTTP-сервер


Да, ребята решили не играть в параллельность, а замутить Single Actor архитектуру через асинхронность, как в nginx. Только нам на радость выбрали язык попроще и запилили docker на Go, так что не нужно становиться Игорем Сысоевым, чтобы читать исходники docker'а.


docker in docker


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


docker run -v /var/run/docker.sock:/var/run/docker.sock \
       -v $(which docker):/bin/docker \
       -ti ubuntu

Но это не настоящий иерархичный docker-in-docker, а плоский зато стабильный вариант.


VXLAN


В последних версиях docker'а появилась поддержка распределенной сети. В статье это не раз упоминается, и не зря. С самого начала у docker'а была проблема с объединением нескольких машин в единый кластер, или даже нескольких контейнеров в группу. Предлагались кривые решения с проксированием, настройкой хост машины, и прочие некошерные вещи. Теперь же docker умеет сам настраивать на хост машине VXLAN, который объединяет несколько физических машин и docker'ов на них в единую виртуальную IP сеть. В такой сети контейнеры будут видеть друг друга как если бы они работали в одной сети. Само собой, такой кластер упирается в ширину сети между машинами, но и это уже прорыв для рядового девопса. А если еще и NAS запилить, то уже взрослый многофункциональный кластер получается.


Куда дальше?


На передовом крае запуск распределенных кластеров, docker как операционная система, docker-in-docker-in-docker. И, как любая открытая платформа, docker разделяется на составляющие части развиваемые отдельными группами, что очень ободряет коллектив.


Swarm? Machine! Compose!!!


Кроме самого docker'а, есть еще несколько интереснейших проектов внутри сообщества docker'а, которые отлично дополняют контейнеры: Machine, Compose и Swarm. О них — в следующих статьях.


Робота над ашипками


Благодарю покорно за холивар в комментах. Чтобы такое добро не пропадало, постараюсь раскрывать тему глубжее, отвечая прямо в статье.


VasilioRuzanni:


А аргументы Phusion в пользу использования baseimage-docker еще актуальны?

Вот проблемы, которые описаны в статье по ссылке:


  1. Ubuntu не спроектирована для работы в контейнере.


    Базовый образ убунты в docker'е это ее облачный вариант с правильно выключенным init и затюненным dpkg. Получается, что таки допроектировали. Для старых добрых OpenVZ контейнеров убунту тоже приходилось допиливать, и ничего, жили и живем годами в продакшне.


  2. Нет init — будут зомби.


    Да, зомби всё еще копятся. Если запустить fork-бомбу, то через 10 секунд на хост-машину с docker'ом уже не папость по SSH: can't fork. К чести docker'а, запуск docker ps покажет все запущенные контейнеры, а docker kill погасит бомбу и порипает всех зомби.


    Однако, как часто бывает, если хотеть что-то сломать, то что-то таки сломается. Если в вашем приложении воркеры действительно часто внезапно мрут, ожидая завершения дочернего процесса, то у меня для вас плохие новости. Если же вы в таком поведении воркеров не видите ничего плохого, то дались вам эти зомби? Если же частые разрывы в иерархии процессов у вас by design, то да, стоит посмотреть в сторону, например, tini.


  3. Syslog?


    Так как docker предполагает, что микросервис шлет свои логи в STDOUT и STDERR, syslog не нужен. Docker сам управляет логами, и позволяет их сразу слать на анализ без сохранения. Если хотите хранить, то, во-первых, это не по облачному феншую, а во-вторых, логи таки могут занять весь диск.


  4. Cron?


    Что простите? А дженкинс нам на что? Как можно юзать крон в 2016 году? Однако, иногда очень-очень хочется, и, стоит надеяться, что docker инкапсулирует запуск сервисов по времени в самого себя. Но и сейчас никто не мешает запустить контейнер с произвольной реализацией крона и запускать контейнеры из него.


  5. SSH?


    По сути, консольная утилита docker уже и есть SSH. Она связывается с демоном по зашифрованному соединению, аутентифицируется по ключу, запускает шел и эмулирует терминал. Есть даже аналог scp.


  6. Неправда, что docker рассчитан на запуск только одного процесса на контейнер!


    Неправда, что неправда!


  7. А давайте сделаем из непривычного docker'а привычный OpenVZ.


    А давайте без давайте.



powerman:


Безопасность подразумевает необходимость ежедневных обновлений пакетов ОС…

Приличный тред получился.


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


А во-вторых, работу с «пакетами ОС», если они на самом деле используются в каком-то вашем микросервисе, надо при обновлении тестировать. А готовить новый релиз к тестированию в мире docker'а принято с помощью — подожди-подожди — пересборки.


И главное, спросите любого владельца маломальски прибыльного сайта, что ему важнее: hardened gcc (с PIE и SSP) и потенцально невзламываемая до следующего shellshock интернет-шаурмятня или мгновенный выкат новой версии приложения без «этого тупого бага с оплатой», уносящего по 100500 денег в час?


Можно взглянуть на безопасность из будущей статьи про микросервисы. Современный сервер состоит из приложения и системы. Обе части надо защищать и обновлять. В предельном случае, к которому стремится сообщество docker'а, в контейнере не будет системы, а только приложение. И это приложение должно делать только одну функцию, например CQRS из базы данных. Тогда паника админов по поводу плохого базового образа убунты бесследно проходит, так как убунты в контейнере больше нет.


grossws:


Оверлейный vxlan, про который пишет автор публикации в предпоследнем абзаце, — без шифрования. Т. е. если использовать --link между контейнерами, то весь трафик внутри должен быть шифрованным и подписанным. Много ли людей будут это делать, а не воспринимать --link, как секьюрный оверлей? Я, например, сейчас использую tinc (p2p vpn), как паллиативное решение.

Прошу всех читателей не воспринимать --link, как секьюрный оверлей, и сам не буду. Если ваше приложение перезрело приватное облако в одном датацентре и дозрело до распределенного по глобусу монстра, пожалуйста, настройте шифрование.


@админы:


Docker админам не нужен, ведь уже же есть «правильная инфраструктура». Пусть всё остается как есть, а кодеры учатся кодить под «правильную инфраструктуру».

Рекомендую почитать «Механическое пианино» Воннегута. За известным исключением, все профессии, однажды, теряют свою маржу.


Scf:


Если вам нужны логи в кибане не в виде текста, а в виде структурированных данных, если у вас ненадежная сеть (привет, амазон), если вас волнуют потенциальные даунтаймы и узкие места в системе, если вы не можете себе позволить потерю логов из-за сетевых проблем, то…
Мы долго думали об этом, но варианта лучше и гибче, чем писать логи в каталог, смапленный наружу, где они подхватываются логсташем, который парсит их в зависимости от их типа и шлет в кибану json-документы, не нашли.

Да, с логами надо быть осторожными. Если лог напрямую слать по сети, то можно залочить процесс, пишущий лог. Полностью поддерживаю описанное решение, особенно в том случае, если логсташ тоже крутится в контейнере ;)


Fesor:


…есть множество кейсов когда это неудобно (shared memory например надо организовать между процессами)

Shared memory подразумевает выполнение процессов на одной машине. Если эти два процесса запущены на одном ядре, как в случае docker'а, то им достаточно иметь доступ к одному и тому же файлу (один inode в файловой системе), чтобы расшарить память. Рецепт для docker'а очень прост: делаете mmap файлу из общего volume'а и всё.


Полезно помнить, что docker всего лишь скрывает процессы друг от друга, но от этого они не перестают быть обычными юниксовыми процессами в обычной юниксовой среде.


JPEG:


Да, последний шаг осталось сделать докеру до гита: мердж имаджей с резолвом --theirs. Я серьёзно :)

Мержа нет потому, что рисковано мержить бинари, зависящие друг от друга. А ребейза нет потому, что нельзя проверить, не зависят ли верхние слои от нижних. В общем, полного гита не получится.

Only registered users can participate in poll. Log in, please.
Что для вас докер?
27.42% Боевой друг 496
6.08% Игрушка для ламеров 110
1.38% Chef rules forever!!!11 25
43.62% Интригующая новинка 789
21.5% Я искал jQuery-плагин, отпустите меня домой… 389
1809 users voted. 594 users abstained.
Tags:
Hubs:
+74
Comments 245
Comments Comments 245

Articles