Последний месяц меня дико прет с Докера. Это настоящий прорыв в индустрии. Я подключаю Докер ко всем репозиториям. Решил поделиться впечатлениями, описать плюсы и рассмотреть реальный пример.

Про Докер написано очень много. Я не собираюсь досканально описывать как он работает. Расскажу лишь кратко о том, что вы, возможно, не слышали.

Докер – утилита для запуска процесса в изолированном окружении. Технически это работает поверх одной из фич ядра Линукса – Cgroups.

Преимущество Докера в том, что стирается грань между отдельными машинами. Если вы собрали образ на Линуксе, то он заведется на Маке. При этом понадобится только Докер. Никаких либ или пакетов выкачивать не нужно.

Этот плюс я уже испытал на практике. Мне пришел из ремонта Мак. За время работы на Линуксе я успел перевести большую часть репозиториев на Докер. На Маке все завелось без проблем.

Докер идеален, когда нужно передать правильно настроенное приложение заказчику. Вы делаете образ, заказчик запускает контейнер. Достаточно лишь, чтобы у вас и у заказчика были близкие версии Докера.

Разработка с Докером напоминает идею виртуальной машины с байткодом. Как Джава – однажды скомпилированное работает везде, был бы только рантайм. Преимущество в том, что теперь речь идет не только о коде, а об утилитах, библиотеках, словом, окружении в целом. Стало возможным сделать Линукс-среду кросплатформенной!

Докер содержит систему в чистоте. Работая нативно, мы ставим сотни и тысячи пакетов, которые оседают в системе как ил. Они занимают место на диске, что особо важно для небольших SSD. Пакеты вызывают коллизии – три версии Руби, два Питона, разные Ноды.

В случае с Докером все файлы ложатся в образ – единый файл. Удалив его, мы достоверно вернем систему в то состояние, в котором она была до билда.

Начав работать на свежей системе, я поставил только Гит и Емакс. База, Редис, различные Питоны, Кложа с Джавой крутятся в Докере. Система ничего о них не знает.

Бытует ошибочное мнение, что Докер – средство вируализации. Это не так. Докер не создает виртуальную машину как это делают Виртуал-Бокс и аналоги. Он запускает процесс через Cgroups с ограничениями в ресурсах и файловой системе.

На Маке и Винде Докер эмулируется через виртуалку. Вы запускаете терминал Докера, а на самом деле проваливаетесь в Виртуал-Бокс, где работает нативный Докер. Но на Маке он тоже скоро будет нативный, уже есть бета-версия.

По этой причине я смеюсь над Микрософтом, которые добавили ps и grep в 10 Виноуз. Кому это нужно? В сети тысячи версий юникс-утилит, скомпилированных под Винду. Скорей делайте нативный Докер!

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

Докер – это отсутствие состояния. Контейнер может удалить все файлы внутри себя, нагадить в конфиги и аварийно завершиться. Но стоит запустить контейнер из образа, и мы вновь в прежнем состоянии: файлы, конфиги на месте. Иммутабельность – это круто.

Докер отлично подходит для билдов всего и вся. Собрать проект, который требует миллион пакетов и библиотек – милое дело.

Докер – это еще и пакетный менеджер. В экосистеме докера выделяют образы и контейнеры. Образ – декларативное описание того, каким будет будущий контейнер. Контейнер – результат запуска из образа. Выражаясь языком ООП, образ это класс, а контейнер – экземпляр.

Существует официальный хаб образов. Оттуда можно скачать образ ЛЮБОЙ версии Питона, Руби, Постгреса и т.д. Помните, какой это геморрой – на систему с Питоном 2.7 ставить 3.4? Или проверить баг на старой версии БД? Теперь c этим покончено.

Хранить приватные образы в хабе Докера разрешено за деньги. Альтернативой может стать официальный сервис Амазона ECR, где все операции над образами бесплатны.

Докер можно использовать и в продакшене для запуска приложений. На работе мы подключили Докер к Дженкинсу для билдов и прогона тестов. Позже мы выкатили приложение на Питоне в Амазон. Оно было разбито на 4 образа. Каждый образ представлял собой микросервис и мог быть заменен независимо от других.

Теперь рассмотрим реальный пример применения Докера.

Бложик, что вы сейчас читаете, работает на движке Jekyll. Это утилита на Руби, которая собирает из маркдаун-файлов статический сайт на голом HTML. Jekyll нативно поддерживается Гитхабом – стоит сделать комит, и твой сайт сам обновляется.

Однако, прожде чем комитить, хочется посмотреть вживую, как выглядит пост. Для этого я ставлю движок и запускаю локальный сервер. При этом Jekyll тянет за собой вагон зависимых пакетов и просит dev-библиотеки для компиляции сишных либ. Тут нам и поможет Докер.

Установка Докера подробно описана на официальном сайте под каждую операционку в отдельности.

В корне проекта создаем файлик Dockerfile с содержимым:

FROM ubuntu:16.04

RUN apt-get update
RUN apt-get install -y jekyll bundler

RUN mkdir /blog
WORKDIR /blog
COPY Gemfile* ./
RUN bundle install
RUN rm Gemfile*

CMD ["jekyll", "serve"]

Разберемся построчно:

  • Образ унаследован от официального образа Убунты версии 16.04.
  • При создании образа обновить информацию о пакетах.
  • Установить из пакетов jekyll (движок) и bundler (установщик пакетов для Руби).
  • Создать внутри образа папку blog в корне.
  • Перейти в нее.
  • Скопировать из хост-системы в образ файлы Gemfile и Gemfile.lock. Это аналог питонячьего pip.requirements.txt.
  • Поставить пакеты, специфичные для проекта.
  • Удалить Gem-файлы.
  • При запуске контейнера стартануть локальный сервер.

Собираем образ Make-командой:

docker-build:
	docker build -t blog .

Параметр -t <foo:bar> задает имя и тег для образа.

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

The following additional packages will be installed:
  ca-certificates cpp cpp-5 dbus file fontconfig-config fonts-dejavu-core
  fonts-lato ifupdown iproute2 isc-dhcp-client isc-dhcp-common
  javascript-common krb5-locales libasn1-8-heimdal libatm1 libauthen-sasl-perl
  libbsd0 libcap-ng0 libdbus-1-3 libdns-export162 libdrm-amdgpu1 libdrm-intel1
  libdrm-nouveau2 libdrm-radeon1 libdrm2 libedit2 libelf1
  libencode-locale-perl libexpat1 libffi6 libfile-basedir-perl
  libfile-desktopentry-perl libfile-listing-perl libfile-mimeinfo-perl
  libfont-afm-perl libfontconfig1 libfontenc1 libfreetype6 libgdbm3
  libgl1-mesa-dri libgl1-mesa-glx libglapi-mesa libglib2.0-0 libglib2.0-data
  libgmp10 libgnutls30 libgssapi-krb5-2 libgssapi3-heimdal libhcrypto4-heimdal
  libheimbase1-heimdal libheimntlm0-heimdal libhogweed4 libhtml-form-perl
  libhtml-format-perl libhtml-parser-perl libhtml-tagset-perl
  libhtml-tree-perl libhttp-cookies-perl libhttp-daemon-perl libhttp-date-perl
  libhttp-message-perl libhttp-negotiate-perl libhx509-5-heimdal libice6
  libicu55 libidn11 libio-html-perl libio-socket-ssl-perl
  libipc-system-simple-perl libisc-export160 libisl15 libjs-coffeescript
  libjs-jquery libk5crypto3 libkeyutils1 libkrb5-26-heimdal libkrb5-3
  libkrb5support0 libldap-2.4-2 libllvm3.8 liblwp-mediatypes-perl
  liblwp-protocol-https-perl libmagic1 libmailtools-perl libmnl0 libmpc3
  libmpfr4 libmysqlclient20 libnet-dbus-perl libnet-http-perl
  libnet-smtp-ssl-perl libnet-ssleay-perl libnettle6 libp11-kit0 libpciaccess0
  libperl5.22 libpng12-0 libpq5 libpython-stdlib libpython2.7-minimal
  libpython2.7-stdlib libroken18-heimdal libruby2.3 libsasl2-2
  libsasl2-modules libsasl2-modules-db libsm6 libsqlite3-0 libssl1.0.0
  libtasn1-6 libtext-iconv-perl libtie-ixhash-perl libtimedate-perl
  libtxc-dxtn-s2tc0 liburi-perl libuv1 libwind0-heimdal libwww-perl
  libwww-robotrules-perl libx11-6 libx11-data libx11-protocol-perl libx11-xcb1
  libxau6 libxaw7 libxcb-dri2-0 libxcb-dri3-0 libxcb-glx0 libxcb-present0
  libxcb-shape0 libxcb-sync1 libxcb1 libxcomposite1 libxcursor1 libxdamage1
  libxdmcp6 libxext6 libxfixes3 libxft2 libxi6 libxinerama1 libxml-parser-perl
  libxml-twig-perl libxml-xpathengine-perl libxml2 libxmu6 libxmuu1 libxpm4
  libxrandr2 libxrender1 libxshmfence1 libxt6 libxtables11 libxtst6 libxv1
  libxxf86dga1 libxxf86vm1 libyaml-0-2 mime-support mysql-common netbase
  nodejs openssl perl perl-modules-5.22 python python-chardet python-minimal
  python-pkg-resources python-pygments python2.7 python2.7-minimal rake rename
  ruby ruby-afm ruby-ascii85 ruby-blankslate ruby-classifier-reborn
  ruby-coderay ruby-coffee-script ruby-coffee-script-source ruby-colorator
  ruby-did-you-mean ruby-execjs ruby-fast-stemmer ruby-ffi ruby-hashery
  ruby-jekyll-coffeescript ruby-jekyll-feed ruby-jekyll-gist
  ruby-jekyll-paginate ruby-jekyll-sass-converter ruby-jekyll-watch ruby-json
  ruby-kramdown ruby-liquid ruby-listen ruby-mercenary ruby-mime-types
  ruby-minitest ruby-multi-json ruby-mysql ruby-net-telnet ruby-oj
  ruby-parslet ruby-pdf-core ruby-pdf-reader ruby-pg ruby-posix-spawn
  ruby-power-assert ruby-prawn ruby-prawn-table ruby-pygments.rb
  ruby-rb-inotify ruby-rc4 ruby-rdiscount ruby-redcarpet ruby-rouge
  ruby-safe-yaml ruby-sass ruby-sequel ruby-sequel-pg ruby-stringex
  ruby-test-unit ruby-toml ruby-ttfunk ruby-yajl ruby2.3 rubygems-integration
  sgml-base shared-mime-info ucf unzip x11-common x11-utils x11-xserver-utils
  xdg-user-dirs xdg-utils xml-core zip
Suggested packages:
  cpp-doc gcc-5-locales dbus-user-session | dbus-x11 ppp rdnssd iproute2-doc
  resolvconf avahi-autoipd isc-dhcp-client-ddns apparmor apache2 | lighttpd
  | httpd libdigest-hmac-perl libgssapi-perl gnutls-bin krb5-doc krb5-user
  libdata-dump-perl coffeescript libcrypt-ssleay-perl pciutils
  libsasl2-modules-otp libsasl2-modules-ldap libsasl2-modules-sql
  libsasl2-modules-gssapi-mit | libsasl2-modules-gssapi-heimdal
  libauthen-ntlm-perl libunicode-map8-perl libunicode-string-perl
  xml-twig-tools perl-doc libterm-readline-gnu-perl
  | libterm-readline-perl-perl make python-doc python-tk python-setuptools
  ttf-bitstream-vera python2.7-doc binutils binfmt-support ri ruby-dev
  libjs-mathjax ruby-activesupport doc-base coderay fonts-arphic-gkai00mp
  ruby-pdf-inspector ruby-prawn-doc ttf-dejavu-core bundler sgml-base-doc
  mesa-utils nickle cairo-5c xorg-docs-core gvfs-bin debhelper
The following NEW packages will be installed:
  ca-certificates cpp cpp-5 dbus file fontconfig-config fonts-dejavu-core
  fonts-lato ifupdown iproute2 isc-dhcp-client isc-dhcp-common
  javascript-common jekyll krb5-locales libasn1-8-heimdal libatm1
  libauthen-sasl-perl libbsd0 libcap-ng0 libdbus-1-3 libdns-export162
  libdrm-amdgpu1 libdrm-intel1 libdrm-nouveau2 libdrm-radeon1 libdrm2 libedit2
  libelf1 libencode-locale-perl libexpat1 libffi6 libfile-basedir-perl
  libfile-desktopentry-perl libfile-listing-perl libfile-mimeinfo-perl
  libfont-afm-perl libfontconfig1 libfontenc1 libfreetype6 libgdbm3
  libgl1-mesa-dri libgl1-mesa-glx libglapi-mesa libglib2.0-0 libglib2.0-data
  libgmp10 libgnutls30 libgssapi-krb5-2 libgssapi3-heimdal libhcrypto4-heimdal
  libheimbase1-heimdal libheimntlm0-heimdal libhogweed4 libhtml-form-perl
  libhtml-format-perl libhtml-parser-perl libhtml-tagset-perl
  libhtml-tree-perl libhttp-cookies-perl libhttp-daemon-perl libhttp-date-perl
  libhttp-message-perl libhttp-negotiate-perl libhx509-5-heimdal libice6
  libicu55 libidn11 libio-html-perl libio-socket-ssl-perl
  libipc-system-simple-perl libisc-export160 libisl15 libjs-coffeescript
  libjs-jquery libk5crypto3 libkeyutils1 libkrb5-26-heimdal libkrb5-3
  libkrb5support0 libldap-2.4-2 libllvm3.8 liblwp-mediatypes-perl
  liblwp-protocol-https-perl libmagic1 libmailtools-perl libmnl0 libmpc3
  libmpfr4 libmysqlclient20 libnet-dbus-perl libnet-http-perl
  libnet-smtp-ssl-perl libnet-ssleay-perl libnettle6 libp11-kit0 libpciaccess0
  libperl5.22 libpng12-0 libpq5 libpython-stdlib libpython2.7-minimal
  libpython2.7-stdlib libroken18-heimdal libruby2.3 libsasl2-2
  libsasl2-modules libsasl2-modules-db libsm6 libsqlite3-0 libssl1.0.0
  libtasn1-6 libtext-iconv-perl libtie-ixhash-perl libtimedate-perl
  libtxc-dxtn-s2tc0 liburi-perl libuv1 libwind0-heimdal libwww-perl
  libwww-robotrules-perl libx11-6 libx11-data libx11-protocol-perl libx11-xcb1
  libxau6 libxaw7 libxcb-dri2-0 libxcb-dri3-0 libxcb-glx0 libxcb-present0
  libxcb-shape0 libxcb-sync1 libxcb1 libxcomposite1 libxcursor1 libxdamage1
  libxdmcp6 libxext6 libxfixes3 libxft2 libxi6 libxinerama1 libxml-parser-perl
  libxml-twig-perl libxml-xpathengine-perl libxml2 libxmu6 libxmuu1 libxpm4
  libxrandr2 libxrender1 libxshmfence1 libxt6 libxtables11 libxtst6 libxv1
  libxxf86dga1 libxxf86vm1 libyaml-0-2 mime-support mysql-common netbase
  nodejs openssl perl perl-modules-5.22 python python-chardet python-minimal
  python-pkg-resources python-pygments python2.7 python2.7-minimal rake rename
  ruby ruby-afm ruby-ascii85 ruby-blankslate ruby-classifier-reborn
  ruby-coderay ruby-coffee-script ruby-coffee-script-source ruby-colorator
  ruby-did-you-mean ruby-execjs ruby-fast-stemmer ruby-ffi ruby-hashery
  ruby-jekyll-coffeescript ruby-jekyll-feed ruby-jekyll-gist
  ruby-jekyll-paginate ruby-jekyll-sass-converter ruby-jekyll-watch ruby-json
  ruby-kramdown ruby-liquid ruby-listen ruby-mercenary ruby-mime-types
  ruby-minitest ruby-multi-json ruby-mysql ruby-net-telnet ruby-oj
  ruby-parslet ruby-pdf-core ruby-pdf-reader ruby-pg ruby-posix-spawn
  ruby-power-assert ruby-prawn ruby-prawn-table ruby-pygments.rb
  ruby-rb-inotify ruby-rc4 ruby-rdiscount ruby-redcarpet ruby-rouge
  ruby-safe-yaml ruby-sass ruby-sequel ruby-sequel-pg ruby-stringex
  ruby-test-unit ruby-toml ruby-ttfunk ruby-yajl ruby2.3 rubygems-integration
  sgml-base shared-mime-info ucf unzip x11-common x11-utils x11-xserver-utils
  xdg-user-dirs xdg-utils xml-core zip

А ю факин сириес? Миллион пакетов на установку одной тулзы! Представьте теперь, что ставите ее нативно, без Докера. Колоссальное захламление системы. Вы не сможете потом удалить эти пакеты. Не записывать же их на бумажку.

Это точка невозврата. Без Докера вы не сможете сделать систему такой, какой она была до установки.

Ок, образ собрался. Запускаем через Make:

docker-run:
	docker run -it --rm -p 4000:4000 -v $(CURDIR):/blog blog

Параметры:

  • -it значит прикрепить STDIN хост-системы к контейнеру. Иначе говоря, считывать пайпы или ввод с клавиатуры.
  • --rm – удалить контейнер по завершении процесса.
  • -p 4000:4000 – пробросить 4000 порт хост-системы на аналогичный порт контейнера.
  • -v $(CURDIR):/blog – смонтировать текущую папку хост-системы в папку /blog образа.

Make-переменная $(CURDIR) означает текущую папку. К сожалению, при пробросе томов нельзя указать относительный путь, а хардкодить в Гите – моветон.

Если внутри образа я шагну в папку /blog, то увижу содержимое проекта.

При старте образа автоматом запустится сборка блога и сервер. Мне останется открыть в браузере http://127.0.0.1:4000/ и посмотреть на результат. При изменении маркдаун-файлов – не важно где, в образе или на хосте – блог автоматом себя перестроит.

Посмотреть исходники для Make и Докера можно в репозитории блога. Возможно, в следующий раз я расскажу об инструменте Docker Compose. С помощью него мы создадим три контейнера – приложение, база и Редис – и заставим идти в одной упряжке как единое целое.

Используете ли вы Докер, и если да, то как?