И снова здравствуйте! Почти пять лет уже не писал здесь новых статей, хотя, если честно, всегда знал, что рано или поздно начну это делать снова. Не знаю как вам, а мне все таки это дело всегда казалось довольно увлекательным.
Начинать написание нового материала после столь продолжительного отдыха от этого дела — труднее всего. Но раз цель поставлена — надо идти до конца. Начну немного издалека.
Всю свою сознательную жизнь основным родом моей деятельности была и остается по сей день веб-разработка. Именно поэтому, сознаюсь сразу, что данный материал стоит воспринимать именно как попытка построения Docker-кластера от системного администратора любителя, но никак не профессионала. В этой статье я не могу не претендовать на экспертное мнение в кластеризации и даже, более того, сам же хочу проверить достоверность собственного опыта.
Под хабракатом вы найдете Quick Start по использованию Docker на уровне, необходимом для решения конкретных задач, обозначенных ниже, без углубления в "дебри" виртуализации и прочих сопутствующих тем. Если вы до сих пор хотите начать успешно использовать эту современную технологию, тем самым значительно упростив целый ряд процесов: от разработки веб-продуктов и до разворачивания и переноса оных под какое-либо современное оборудование — прошу под кат!
Преамбула
Начнем, конечно же, с постановки задачи и определения основных технологий/методик, используемых в гайде.
С самого начала я заинтерисовался Docker'ом с целью быстрого создания небольшого, но довольно универсального кластера под собственные проекты (рабочие, учебные, etc). Так как системным администрированием я профессионально заниматься не собирался — я решил, что должен обучиться основам кластеризации ровно до того момента, когда я смог бы без особых затруднений разворачивать любой популярный программный стек для веб-проекта. Далее я рассмотрю разворачивание на Docker следующих конфигураций:
- LAMP;
- LEMP;
- MEAN.
Первые две в представлении, думаю, не нуждаются. Третяя же состоит из MongoDB, Express.js, Node.js. MEAN я чаще всего использовал для написания RESTful API, например, для дальнейшего разрабатывания на его основе мобильного приложения.
После этого я сам себе немного усложнил задачу, добавив следующие требования:
- Возможность без затруднений использовать различные домены (или, зачастую, поддомены) для каждого отдельного контейнера (по принципу виртуальных хостов).
- Использование HTTPS-протокола по-умолчанию. Более того, хотелось бы организовать бесплатное генерирование SSL-сертификатов, не уступающих при этом платным аналогам.
- Разворачивание на одном и том же сервере GitLab CE — в качестве основной CVS-системы для работы над проектами не только в одиночку, но и в команде.
Основные определения:
-
Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в среде виртуализации на уровне операционной системы.
-
Letsencrypt — Бесплатный автоматический центр авторизации (CA). Предоставляет бесплатные сертификаты для включения поддержки HTTPS (SSL / TLS) на любом веб-сайте.
- GitLab Community Edition — Open Source аналог GitHub. Обеспечивает управление Git-репозиториями, анализ кода, отслеживание ошибок, работу с каналами активности, создание Wiki и т.д.
Установка и настройка
Проблем с установкой Docker и других пакетов не должно возникнуть. На официальном сайте этот процес расписан довольно подробно. Далее я распишу общий перечень команд, необходимых для начальной настройки.
Сразу уточню, что в этой статье я рассматриваю настройку Docker и всех сопутствующих программ на дистрибютиве CentOS 7, так как на этой ОС я уже давно привык работать, как на основной серверной системе. В целом на любом другом Linux-дистрибютиве действия будут примерно аналогичные, с той лишь разницей, что, например, для Ubuntu вы будете использовать apt-get вместо yum / dnf (для CentOS / Fedora).
Docker + Docker Compose:
Подготовка:
$ sudo yum update
$ sudo tee /etc/yum.repos.d/docker.repo <<-'EOF'
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/7/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF
Установка Docker Engine:
$ sudo yum install docker-engine
$ sudo systemctl enable docker.service
$ sudo systemctl start docker
Создание группы пользователей 'docker' и добавление туда текущего пользователя (это необходимо для того, чтобы работать с Docker без использования 'sudo' или root-доступа):
$ sudo groupadd docker
$ sudo usermod -aG docker your_username
Проверка успешности установки:
$ docker run --rm hello-world
Установка Docker Compose (утилита для объединения нескольких контейнеров в одно веб-приложение):
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.9.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version
Certbot (официальный сайт):
Утилита для автоматического получения/обновления SSL-сертификатов от Letsencrypt:
Перед установкой необходимо включить EPEL-репозиторий, если этого не было сделано ранее.
$ sudo yum install certbot
Основы работы с Docker Engine
Базовые принципы:
Docker представляет из себя дополнительный уровень абстракции; систему, автоматизирующую виртуализацию на уровне операционной системы.
"Виртуализация на уровне операционной системы — метод виртуализации, при котором ядро операционной системы поддерживает несколько изолированных экземпляров пространства пользователя, вместо одного. Эти экземпляры (часто называемые контейнерами или зонами) с точки зрения пользователя полностью идентичны реальному серверу. Ядро обеспечивает полную изолированность контейнеров, поэтому программы из разных контейнеров не могут воздействовать друг на друга."
From Wikipedia
Основные преимущества использования Docker:
- Изолированность отдельных кластеров друг от друга;
- Один и тот же Docker-контейнер может работать без изменений на множестве различных машин (с разными конфигурациями);
- Docker оптимизирован для развертывания приложений (application-centric);
- Автоматизация сборки приложений;
- Повторное использование одних и тех же компонентов;
- Open Source реестр готовых контейнеров (Docker Hub);
- Единый API для автоматизации пользовательской настройки контейнера и его разворачивания.
Далее расмотрим основные команды, которые нам понадобятся для создания кластера:
$ docker run
По сути основная команда, выполняющая запуск нового контейнера.
Основные параметры:
- --name: UUID идентификатор: уникальное имя контейнера;
- --volume (-v): том, связанный с контейнером: задается в форме абсолютного пути к директории;
- --env (-e): переменная окружения: позволяет дополнительного настраивать запускаемый контейнер;
- --publish (-p): настройка определенных портов, необходимых для работы контейнера (например, 80 для http, 443 для https).
$ docker ps
Команда, с помощью которой можно получить список запущенных контейнеров.
$ docker stop container-name
Команда, останавливающая работу контейнера.
$ docker rm container-name
Удаление определенного контейнера.
Внимание: прежде, чем удалить контейнер, его необходимо остановить (docker stop)!
Более подробно разобраться в работе каждой команды вы можете в официальной документации. В этой статье я обратил внимание только на основные команды, необходимые для успешного начала работы с Docker.
Конкретные примеры использования docker run
вы увидите в этой статье немного далее.
Настройка виртуальных хостов
Проблема: определенная сложности реализации кластера с использованием виртуальных хостов в различных контейнерах заключается в том, что один порт может "прослушиваться" только одним контейнером (настраивается через --publish). Получается, что по-умолчанию мы можем создать только один контейнер, который будет отвечать на запросы к серверу через порт 80 и/или 443 (http и https протоколы, соответственно).
Решение: в принципе, довольно очевидно использовать для решения этой проблемы Reverse Proxy, инкапсулированный в один контейнер, который и будет "прослушивать" порты 80 и 443. Функционал данного контейнера будет заключаться в автоматическом перенаправлении запросов, в соответствии с используемыми виртуальными хостами.
Такой контейнер существует в открытом доступе в Docker Hub — nginx-proxy.
Кроме решения проблемы с виртуальными хостами, он по-умолчанию поддерживает работу с SSL-сертификатами, что позволяет развернуть поддержку защищенного HTTPS-доступа к сайту.
Прежде, чем запустить данный Reverse Proxy контейнер, давайте получим SSL-сертификаты для доменов, которые мы хотим использовать в качестве виртуальных хостов.
Получение бесплатного SSL-сертификата
Для получения SSL-сертификата будем использовать бесплатный сервис letsencrypt. Для этого на предыдущих этапах мы уже установили утилиту certbot. Я не буду останавливаться на подробностях использования этой утилиты (это все есть в официальной документации).
Приведу лишь готовую команду для автоматического получения бесплатного SSL-сертификата для вашего домена:
$ sudo certbot certonly -n -d yourdomain.com --email your@email.com --standalone --noninteractive --agree-tos
--standalone --noninteractive --agree-tos
— данные параметры необходимы для того, чтобы certbot с одной стороны отработал в фоновом режиме, а с другой — сгенерировал сертификат без конкретной привязки к определенному веб-серверу.
В результате успешного выполнения данной команды будут сгенерированы два файла:
/etc/letsencrypt/live/yourdomain.com/fullchain.pem
/etc/letsencrypt/live/yourdomain.com/privkey.pem
Для корректной работы nginx-proxy нам необходимо поместить все файлы сертификатов в одну директорию, при этом используя для каждого доменного имени по два файла в формате: yourdomain.com.crt (файл сертификата) и yourdomain.com.key (приватный ключ).
В данном случае логично воспользоваться символьными ссылками. Пример:
$ mkdir ssl-certs
$ cd ssl-certs
$ ln -s /etc/letsencrypt/live/yourdomain.com/fullchain.pem ./yourdomain.com.crt
$ ln -s /etc/letsencrypt/live/yourdomain.com/privkey.pem ./yourdomain.com.key
На расширение .pem не стоит обращать особого внимания — суть файлов от этого не меняется.
Аналогичным образом мы можем получить сертификаты для любых, принадлежащих нам, доменных имен и далее использовать их в качестве виртуальных хостов. Единственное требование заключается в том, что A-записи этих доменных имен должны быть направленны на внешний IP-адрес сервера, на котором вы и выполняете certbot certonly ...
Сгенерировав сертификаты для каждого домена, мы готовы к запуску nginx-proxy контейнера.
$ docker run -d -p 80:80 -p 443:443
-v /full/path/to/ssl-keys:/etc/nginx/certs
-v /var/run/docker.sock:/tmp/docker.sock:ro
jwilder/nginx-proxy
Рассмотрим эту команду более подробно:
-p 80:80 -p 443:443
— привязываем к контейнеру порты 80 и 443. При чем 80-ый порт сервера отвечает 80-ому порту внутри контейнера и аналогично с портом 443. В формате PORT:PORT2 выполняется создание соответствий между реальным портом всей машины и портами внутри отдельного виртуального контейнера.-v /full/path/to/ssl-keys:/etc/nginx/certs
— первый volume, необходимый для настройки данного контейнера. Здесь мы выполняем связь стандартной директории /etc/nginx/certs внутри самого контейнера с директорией, в которую мы вручную поместили символьные ссылки на файлы сертификатов и приватных ключей для наших доменов (на предыдущем этапе).jwilder/nginx-proxy
— идентификатор контейнера внутри Docker Hub. Docker Engine автоматически скачает изображение данного контейнера, если оно еще не было загруженно ранее.
Вот и все — первый контейнер запущен! И этим контейнером является Reverse Proxy, через который мы сможем далее задать любому контейнеру-приложению VIRTUAL_HOST.
Примеры работы с различными стеками
LAMP
Итак, наконец-то мы можем перейти к запуску контейнеров, в которых мы уже сможем разрабатывать наши веб-приложения.
В базе Docker Hub довольно много разных вариантов LAMP-контейнеров. Лично я использовал этот: tutum-docker-lamp.
Ранее, помимо Docker Engine, мы установили утилиту Docker Compose. И вот только с этого момента мы начинаем ее использовать. Docker Compose удобен для создания приложений, в которых несколько контейнеров объединены и именно совместно представляют собой разрабатываемое приложение.
Для того, чтобы запустить этот контейнер в связке с нашим nginx-proxy необходимо:
-
Загрузить в отдельную директорию исходники tutum-docker-lamp (удобнее всего это сделать с помощью
git clone
); - Создать в этой рабочей директории файл docker-compose.yml с примерно таким содержанием:
web:
build: .
volumes:
- ./www:/var/www/html
environment:
- MYSQL_PASS=yourmysqlpassword
- VIRTUAL_HOST=yourdomain.com
-
Выполнить запуск с помощью docker-compose:
$ docker-compose up
Как видите в данном примере, управление виртуальными хостами при использовании nginx-proxy выполняется с помощью всего одной переменной окружения VIRTUAL_HOST.
Внимание на связку ./www:/var/www/html
. Очевидно, что рабочей директорией вашего сайта становится папка www (ее необходимо создать вручную). Все файлы в этой директории автоматически попадают и в /var/www/html
внутри запущеного контейнера.
Разобраться более детально с синтаксисом файла настроек docker-compose.yml вы можете в официальной документации.
LEMP
Запуск LEMP-контейнера принципиально ничем не отличается от примера выше.
Сначала находим контейнер в Docker Hub. Например: docker-lemp.
Скачиваем исходники контейнера и добавляем docker-compose.yml. Внутри этого файла настроек уже нашего кастомного контейнера вы можете не только задавать переменную окружения VIRTUAL_HOST, но и настраивать всё, что позволяет файл Dockerfile. Например в Dockerfile определено:
VOLUME /var/www/
Следовательно вы можете в docker-compose.yml выполнить связку с этим томом примерно так:
volumes:
- ./www:/var/www
NodeJS + ExpressJS + MongoDB
Пример такой конфигурации: docker-nodejs-mongodb-example.
Файл docker-compose.yml выглядит следующим образом:
web:
build: .
volumes:
- "./api:/src/app"
environment:
- VIRTUAL_HOST=yourdomain.com
links:
- "db:mongo"
db:
image: mongo
ports:
- "27017:27017"
volumes:
- ./data/db:/data/db
В данном случае будет создано два связанных контейнера. Один — для базы (mongoDB), второй собственно для NodeJS приложения.
Для запуска этой связки контейнеров используется все та же команда docker-compose up
.
Тонкости в работе с gitlab/gitlab-ce
Некоторые, более сложные контейнеры, требуют дополнительной настройки для запуска с использованием nginx-proxy. К таким контейнерам относится и gitlab-ce.
Я сначала приведу полностью рабочую версию команды для запуска этого контейнера, с учетом той конфигурации, которая обсуждается в этой статье, а далее поясню ниже некоторые детали из этой команды.
Итак:
$ docker run --detach
--hostname gitlab.yourdomain.com
--publish 2289:22
--restart always
--name custom-gitlab
--env GITLAB_OMNIBUS_CONFIG="nginx['listen_port'] = 80; nginx['listen_https'] = false; nginx['proxy_set_headers'] = { "X-Forwarded-Proto" => "https", "X-Forwarded-Ssl" => "on" }; gitlab_rails['gitlab_shell_ssh_port'] = 2289; external_url 'https://gitlab.yourdomain.com'; gitlab_rails['smtp_enable'] = true; gitlab_rails['smtp_address'] = 'smtp.mailgun.org'; gitlab_rails['smtp_port'] = 2525; gitlab_rails['smtp_authentication'] = 'plain'; gitlab_rails['smtp_enable_starttls_auto'] = true; gitlab_rails['smtp_user_name'] = 'postmaster@mg.yourdomain.com'; gitlab_rails['smtp_password'] = 'password'; gitlab_rails['smtp_domain'] = 'mg.yourdomain.com';"
--env VIRTUAL_HOST="gitlab.yourdomain.com"
--volume /srv/gitlab/config:/etc/gitlab
--volume /srv/gitlab/logs:/var/log/gitlab
--volume /srv/gitlab/data:/var/opt/gitlab
gitlab/gitlab-ce:latest
Запуск через NGINX Reverse Proxy + HTTPS
Для того, чтобы схема с Reverse Proxy работала в данном случае, необходимо добавить:
nginx['listen_port'] = 80;
nginx['listen_https'] = false;
nginx['proxy_set_headers'] = { "X-Forwarded-Proto" => "https", "X-Forwarded-Ssl" => "on" };
Причина заключается в том, что nginx-proxy при работе с контейнерами обращается внутри них к порту 80, а не 443. Без дополнительных заголовков в настройке nginx внутри контейнера gitlab-ce (proxy_set_headers) запрос проходить не будет (ошибка 502 "Bad Gateway").
Кроме этого важно добавить:
external_url 'https://gitlab.yourdomain.com';
Порт 22
Суть в данных строках:
--publish 2289:22
Если работа с рабочей машиной производится через SSH-протокол, то мы не можем создавать связку напрямую "22:22", так как порт 22 уже занят сервисом sshd.
Решение этой проблемы описано в официальной документации gitlab-ce. Все просто: мы привязываем любой другой (кроме 22) порт внутри сервера к 22 порту внутри контейнера. В данном примере используется порт 2289.
Параллельно с этим важно не забыть добавить
gitlab_rails['gitlab_shell_ssh_port'] = 2289;
В настройки самого GitLab.
Таким образом после запуска gitlab-ce и создания в нем самом какого-либо репозитория работа с ним будет производится по адресу в стиле:
ssh://git@gitlab.yourdomain.com:2289/username/repository_name.git
Настройка SMTP-сервера
Здесь тоже необходимо использовать специальные переменные окружения самого GitLab.
В моем случае (я использую Google Cloud Engine) по-умолчанию закрыты порты 25, 465 (т.е. стандартные порты SMTP-протокола). Одним из вариантов решения этой проблемы является использование стороннего сервиса (как например MailGun) в качестве SMTP-сервера. Для этого используем настройки:
gitlab_rails['smtp_enable'] = true;
gitlab_rails['smtp_address'] = 'smtp.mailgun.org';
gitlab_rails['smtp_port'] = 2525;
gitlab_rails['smtp_authentication'] = 'plain';
gitlab_rails['smtp_enable_starttls_auto'] = true;
gitlab_rails['smtp_user_name'] = 'postmaster@mg.yourdomain.com';
gitlab_rails['smtp_password'] = 'password';
gitlab_rails['smtp_domain'] = 'mg.yourdomain.com';
Ну и наконец не забываем о --env VIRTUAL_HOST="gitlab.yourdomain.com"
— переменной окружения для самого nginx-proxy.
Вот и все. После выполнения этой инструкции Docker запустит полностью функционирующий контейнер с GitLab CE.
Стандартный процес обновления gitlab-ce
Это последний момент, который я хочу отдельно осветить в этом гайде.
Процесс обновления GitLab с помощью Docker упрощается до нескольких команд:
-
docker stop custom-gitlab
— останавливаем работающий контейнер; -
docker rm custom-gitlab
— удаляем контейнер GitLab CE.Важный момент: удаление контейнера не означает удаление данных, которые были созданы в процесе использования системы. Поэтому вы можете выполнять эту команду без каких-либо опасений.
-
docker pull gitlab/gitlab-ce
— собственно обновление изображения контейнера; - выполняем длинную команду (пример выше), с помощью которой мы изначально запускали контейнер.
Вот и все. Выполнив эти 4 команды, GitLab автоматически обновится до последний версии и запустится через Docker Engine.
Итоги
Итак, в результате выполнения этого гайда должен получится Docker-кластер, в основе которого лежит NGINX Reverse Proxy; каждое веб-приложение имеет свой виртуальный хост, который при этом поддерживает защищенный HTTPS-протокол.
Вместе с веб-приложениями функционирует GitLab-кластер, полностью настроенный, вплоть до доступа к SMTP-серверу.
Я очень надеюсь, что это моё небольшое исследование окажется полезным или, хотя бы, интересным, для многих читателей ХабраХабра. Конечно же, буду рад выслушать критику профессионалов, дополнения или усовершенствования к статье!
Спасибо за внимание!
Автор: Kyborg2011