Если вы еще никогда не поддерживали чужие приложения, или пусть даже свои, но таких размеров, что уже не помещаются в одной голове, то прошу вас расслабиться, откинуться на спинку кресла и воспринимать прочитанное как поучительную сказку с надуманными проблемами, забавным сюжетом и очевидным счастливым концом. В противном случае, если реальный боевой опыт у вас имеется, добро пожаловать в ад, но с IDDQD и IDKFA.
К вашему сведению! В этой статье мы рассматривается само явление докеровских контейнеров, а не составляем список микросервисов, которые гнездятся внутри. Этим мы займемся в следующей серии, во имя справедливости!
Что мы имеем сегодня
- Зоопарк дубовых VPS-хостингов.
- Дорогие IaaS и PaaS с гарантированным vendor lock in.
- Уникальные сервера-снежинки.
- Ворох устаревших зависимостей на неподдерживаемой операционке.
- Скрытые связи частей приложения.
- Незаменимый админ полубог на скейтборде.
- Радуга окружений: development, testing, integration, staging, production.
- Генерация конфигов для системы управления конфигами.
- Feature flagging.
Удивительно то, что корень всех этих проблем только в одном: сервер дорого поднимать, а потом еще и дорого гасить. Дорогим может оказаться время потраченное на художественное плетение по конфигам, или энтерпрайз железяка просто долго стартует, или состояние серверов надо хитро бекапить и потом восстанавливать, или всё просто и быстро, но работает только на какой-то уникально дорогущей платформе, с которой не сбежишь.
Конечно, это капитанское вступление. И цель вступления как раз в капитанстве — показать, что эти проблемы уже настолько банальны, что даже простое их перечисление заставляет кое-где болеть.
Если представить, что на холодный запуск приложения на новой машине уйдет минута, и при этом все данные в сохранности, то дышать сразу становится легче, а на лице появляется предвкушение написания полезного кода заместо возни с сервером.
Назад к основам
Как часто бывает, в какой-то момент состояние некой системы становится неуправляемым и требует новых технологий. В программировании, правда, часто выходит наоборот — требуется вспомнить старые технологии. Вот и в случае с Докером не случилось ничего нового: взяли принципы функционального программирования (другие видят ООП) и микроядерности, применили к инфраструктурному слою приложения (серверам, сети и т.п.) и получили stateless-иммутабельные-изолированные-микросервисы. Все прелести докера вырастают именно из этого. Как известно, функциональщина не так проста, как хотел бы Рич Хикки. Чтобы можно было каждый день пользоваться мудростью предков, нужно иметь хорошие инструменты. Докер как раз и есть такой инструмент.
Вот базовые отличия докерского контейнера (не микросервиса, см. выше) от простого сервера.
Стейтлес
Конфигурация контейнера не может и не должна меняться после запуска. Все предварительные работы делаются на этапе создания образа или старта контейнера: конфиги, порты, общие папки, переменные окружения, всё это должно быть известно к моменту старта контейнера. Конечно, докер позволяет запущенному внутри контейнера процессу делать со своей памятью и файловой системой всё что тому заблагорассудится, но трогать что-то, что можно было потрогать до запуска считается плохим тоном.
Чистый (pure)
Контейнер ничего не знает о хост системе и не может мешать другим контейнерам: ни влезть в чужую файловую систему, ни послать сигнал чужому процессу, ни даже в случайный порт стукнуться. На то он и контейнер. Само собой, докер позволят контейнерам общаться, но только строго задекларированными способами. Еще можно запускать контейнеры наделенные какими-нибудь суперсилами, например, доступом к реальной сети или физическим девайсам.
Ленивый
При запуске контейнер не копирует файловую систему образа, из которого создан. Контейнер всего-лишь создает пустую файловую систему поверх образа. В докере это называется слоем. Так же устроены и образы. Образ состоит из списка (геологических) слоев, накладывающихся друг на друга. Отсюда такая высокая скорость запуска нового контейнера: меньше секунды.
Декларативный
Все свойства контейнера хранятся в декларативном виде. Этапы создания образа тоже описаны строго разделенными шагами. Параметры сети, содержимое файловой системы, объем памяти, публичные порты и так далее и тому подобное задается в Dockerfile или статичными ключами при запуске. Всё это легко читается на паре экранов текста даже для очень сложной системы.
Функциональный
Контейнер делает только одно дело, но делает его хорошо. Предполагается, что в контейнере будет жить всего один процесс (можно с семьёй), выполняющий всего одну функцию в приложении. За счет того, что в контейнере нет своего ядра, загрузочного раздела, init-процесса и, чаще всего, даже пользователь всего один (псевдо-root) — то есть в контейнере нет полноценной операционной системы — за счет отсутствия всего этого контейнер стартует так быстро, как стартовал бы ваш сервис будь операционная система уже полностью загружена. Это узкая специализация делает функцию реализуемую контейнером предсказуемой и масштабируемой. Так как процесс только один, ему неоткуда ждать (если только снаружи) переполнения логов, попадания в своп и тому подобного.
Строгий
По-умолчанию, докер запрещает контейнеру всё, кроме доступа в сеть (которую тоже можно запретить). Однако, при необходимости позволяется нарушать любые эти правила, когда нарушить правила бывает логичнее. И что интересно, разрешать доступ также просто, как и запрещать. Соединять или разъединять контейнеры в докере с каждой версией становится всё проще и проще, особенно радует работа с сетью, распределенной по датацентрам.
Чем докер не является
Докер часто путают с Vagrant’ом или OpenVZ, или даже VirtualBox’ом. Докер это всего-лишь user-space демон на языке Go, который умело жонглирует уже существующими технологиями ядра Linux. Думаю, стоит объяснить подробнее, так как сам начал знакомиться с темой с запроса «docker vs. vagrant».
Не вагрант
Вагрант занимается тем, что управляет виртуальными машинами (или, более приближенно к теме
Не виртуальная машина
И конечно, докер вовсе не является новой технологией (пара)виртуализации, как например KVM/Qemu, VirtualBox/VMWare/Parallels, XEN, etc. Докер демон (не путать с консольной командой docker
) работает только в Линуксе (и уже в винде, не?) поверх линуксовых же контейнеров. А контейнеры, в свою очередь, не являются полноценной виртуальной средой, так как они делят общее запущенное ядро одной физической машины. Если вы работали с OpenVZ или щупали Linux Containers то вы должны знать, как они устроены, чем хороши и плохи.
А что же тогда докер?
Докер состоит из трех частей:
- docker daemon — сердце докера. Это демон работающий на хост-машине и умеющий скачивать и заливать образы, запускать из них контейнеры, следить за запущенными контейнерами, собирать логи и настраивать сеть между контейнерами (а с версии 0.8 и между машинами). А еще именно демон создает образы контейнеров, хоть и может показаться, что это делает docker-client.
- docker это консольная утилита для управления докер-демоном по HTTP. Она устроена очень просто и работает предельно быстро. Вопреки заблуждению, управлять демоном докера можно откуда угодно, а не только с той же машины. В сборке нового образа консольная утилита
docker
принимает пассивное участие: архивирует локальную папку в tar.gz и передает по сети docker-daemon, который и делает всё работу. Именно из-за передачи контекста демону по сети лучше собирать тяжелые образы локально. - Docker Hub централизованно хранит образы контейнеров. Когда вы пишете
docker run ruby
докер скачивает самый свежий образ с руби именно из публичного репозитория. Изначально хаба не было, его добавили уже после очевидного успеха первых двух частей.
Что докер заменил
Для пользы дела стоит перечислить технологии, ставшие лишними при работе с инфраструктурой после появления докера. Хочу заметить, что для других применений эти технологии до сих пор незаменимы (вот как, например, заменить SSH или Git используемые по назначению?).
Chef / Puppet / Ansible
Очевидный кандидат на вылет — системы управления конфигурацией stateful сервера. Им на смену приходит лаконичный и быстрый Dockerfile. Так как докер умеет собирать образ контейнера за секунды, а запускать сам контейнер за миллисекунды, больше не надо громоздить скрипты, поддерживающие тяжелый и дорогой сервер в актуальном состоянии. Достаточно запустить новый контейнер и погасить старый. Если вы и до прихода докера использовали Chef для быстрого развертывания stateless серверов, то вы уже любите докер, а статью читаете ради удовольствия и сбора аргументов против админа-слоупока, сидящего на FreeBSD 4.11.
Upstart / SystemD / Supervisor / launchd / God.rb / SysVinit, тысячи их
Архаичные системы запуска сервисов, основанные на статичных конфигах идут в сад. На их место приходит docker daemon
, который является init-подобным процессом умеющим следить за запущенными сервисами, перезапускать отвалившиеся, хранить коды выхода, логи, а главное, скачивать любые контейнеры с любыми сервисами на любой машине, не важно какая у машины «роль».
Ubuntu_14.04.iso / AMI-W7FIS1T / apt-get update
Так же как и вагрант, Докер позволяет, наконец, забыть про скачивание давно устаревших установочных DVD-образов (совсем недавно пришлось качать для Dell блейда), помнить панели управления разных облачных платформ и сокрушаться, что самой свежей убунты у них никогда нет. А главное, запустить свой собственный кастомный образ со всем необходимым можно за пару минут, собрав его на локальной машине и загрузив в бесплатное облако Docker Hub. И никаких больше apt-get update
после установки — ваши образы всегда будут готовы к работе сразу после сборки.
RUBY_ENV, database.dev.yml, testing vs. staging vs. backup
Так как докер позволяет запускать контейнеры со всеми зависимостями и окружением бит-в-бит совпадающим с тем, что вы сконфигурировали, можно за пару секунд поднять точную копию продакшна прямо на своём ноутбуке. Все операционные системы, все сервера баз данных и кеширования, само приложение и версии библиотек им используемые, всё это будет точно воспроизведено на девелоперской машине или на машине тестера. Всегда мечтали иметь резервный продакшн сервер готовый к бою в случае падения главного — docker-compose up
и у вас два боевых сервера являющихся копией друг друга с точность до бита. Начиная с версии докера 1.10 идентификаторы контейнеров являются их же собственной SHA256 подписью гарантирующей идентичность.
Xcode, brew, port install, ./configure && make && sudo make install, mysql --version
Всё, больше никаких мук с установкой серверного софта локально, никаких несовпадений версий мускуля для разных проектов, и возни с удалением всего этого добра после завершения проекта. Это еще ничего, если вы маковод, а вот если у вас виндоус… Докер позволит запускать почти всё, что угодно, лишь бы это работало на линуксе. Можно и Хром в контейнере запускать.
SSH (sic!), VPN, Capistrano, Jenkins-slave
Для того чтобы запустить контейнер не нужно получать root на сервере, морочиться с ключами и настраивать систему деплоя. С докером деплой выглядит как сборка и настройка физического сервера дома, дольшейшего его всестороннего тестирования, а затем магического перемещения в произвольное количество детацентров одной командой. Для этого достаточно запустить docker run
на своём ноутбуке и докер сам секьюрно подключится к серверу по TLS и всё запустит. Внутрь контейнера тоже попасть проще простого: docker exec -it %containername% bash
и у вас в руках консоль для отладки.
Git (!!!)
Используете Git как способ выкатки приложения на сервер? Отлично! Docker это буквально новый Git, только для контейнеров. Вместо git push
(и настройки кучи дополнительного софта и сопряжения с Github’ом) вы получаете docker push
. Вместо запуска скрипта деплоя, docker-compose up
. И все контейнеры обновятся и перезапустятся. Конечно, если вы хотите zero-downtime деплой, надо будет немного покумекать. Но даже без этого, ваши пользователи увидят MAINTENANCE.HTML
не более чем на несколько секунд. Вдобавок, теперь вы можете отправлять тестру не хеш коммита гита и ждать, пока он (или дженкинс) воссоздаст окружение, а просто шлёте ему хеш контейнера и он его сразу запускает.
Datacenter lock-in
Всё, больше его нет. Хотите переезжайте всем приложением за пару минут на другой континент, а хотите — размазывайте своё приложение по всему земному шару. Контейнерам всё равно где крутиться, они объединены виртуальной сетью, видят и слышат друг друга отовсюду, включая девелоперкую машину в случае живой отладки.
bundler, rvm, dotenv, /opt
Нужна старая версия руби для запуска старого скрипта раз в месяц — упакуйте его в докер со старым руби. Нужны разные версии библиотек для разных частей приложения — разбейте по контейнерам и не заморачивайтесь с bundle exec.
*.log, *.pid
И логами, и процессами заведует докер. Есть много плагинов сборки логов и отправки их на анализ. Можно грабить корованы.
useradd www
Можно забыть детские кошмары о том, что веб-сервер кто-то взломает и получит доступ ко все системе, если его запустить от рута. А еще не нужно больше искать следы взлома в страхе, что теперь надо всё переустанавливать. Просто обновите софт в образе, убейте контейнер (или всю машину целиком, только базу забекапьте, пожалуйста) и перезапустите сервис.
chroot, CGroups, LXC
Докер объединяет и абстрагирует сложные и разрозненные технологии в своих библиотеках libcontainer, runC, libnetwork. Если вы пробовали настроить LXC, то уже любите докер. Пока эти библиотеки как отдельные проекты еще свежи, но уже используется в самом докере.
Вкусности докера
Dockerfile
Может показаться, что Dockerfile это старый добрый Chef-конфиг, только на новый лад. А вот и нет: от конфигурации сервера в нем осталась только одна строка — имя базового образа операционной системы. Остальное же — часть архитектуры приложения. И это стоит воспринимать, как декларацию API и зависимостей именно сервиса, а не сервера. Эта часть пишется проектирующим приложение программистом по ходу дела естественным путём прямо в процессе разработки. Такой подход дает не только поразительную гибкость конфигурации, но и позволяет избежать испорченного телефона между разработчиком и админом. Больше про это ждите в статье про микросервисы.
docker-daemon — это асинхронный HTTP-сервер
Да, ребята решили не играть в параллельность, а замутить Single Actor архитектуру через асинхронность, как в nginx. Только нам на радость выбрали язык попроще и запилили докер на Go, так что не нужно становиться Игорем Сысоевым, чтобы читать исходники докера.
docker in docker
Можно разрешать одним контейнерам управлять другими контейнерами. Таким образом получается, что на хост машине не будет установлено ничего, кроме докера. Делается это так:
docker run -v /var/run/docker.sock:/var/run/docker.sock
-v $(which docker):/bin/docker
-ti ubuntu
Но это не настоящий иерархичный докер в докере, а плоский зато стабильный вариант.
VXLAN
В последних версиях докера появилась поддержка распределенной сети. В статье это не раз упоминается, и не зря. С самого начала у докера была проблема с объединением нескольких машин в единый кластер, или даже нескольких контейнеров в группу. Предлагались кривые решения с проксированием, настройкой хост машины, и прочие некошерные вещи. Теперь же докер умеет сам настраивать на хост машине VXLAN, который объединяет несколько физических машин и докеров на них в единую виртуальную IP сеть. В такой сети контейнеры будут видеть друг друга как если бы они работали в одной сети. Само собой, такой кластер упирается в ширину сети между машинами, но и это уже прорыв для рядового девопса. А если еще и NAS запилить, то уже взрослый многофункциональный кластер получается.
Куда дальше?
На передовом крае запуск распределенных кластеров, докер как операционная система, докер в докере в докере. И, как любая открытая платформа, докер разделяется на составляющие части развиваемые отдельными группами, что очень ободряет коллектив.
Swarm? Machine! Compose!!!
Кроме самого докера, есть еще несколько интереснейших проектов внутри сообщества докера, которые отлично дополняют контейнеры: Machine, Compose и Swarm. О них — в следующих статьях.
Автор: JPEG