Docker, GitLab, бесплатные SSL-сертификаты и другие плюшки современной веб-разработки

И снова здравствуйте!


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


Начинать написание нового материала после столь продолжительного отдыха от этого дела — труднее всего. Но раз цель поставлена — надо идти до конца. Начну немного издалека.


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


Под хабракатом вы найдете Quick Start по использованию Docker на уровне, необходимом для решения конкретных задач, обозначенных ниже, без углубления в «дебри» виртуализации и прочих сопутствующих тем. Если вы до сих пор хотите начать успешно использовать эту современную технологию, тем самым значительно упростив целый ряд процесов: от разработки веб-продуктов и до разворачивания и переноса оных под какое-либо современное оборудование — прошу под кат!


Opening Illustration - Docker


Преамбула


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


С самого начала я заинтерисовался Docker’ом с целью быстрого создания небольшого, но довольно универсального кластера под собственные проекты (рабочие, учебные, etc). Так как системным администрированием я профессионально заниматься не собирался — я решил, что должен обучиться основам кластеризации ровно до того момента, когда я смог бы без особых затруднений разворачивать любой популярный программный стек для веб-проекта. Далее я рассмотрю разворачивание на Docker следующих конфигураций:


  • LAMP;
  • LEMP;
  • MEAN.

Первые две в представлении, думаю, не нуждаются. Третяя же состоит из MongoDB,  Express.js,  Node.js. MEAN я чаще всего использовал для написания RESTful API, например, для дальнейшего разрабатывания на его основе мобильного приложения.


После этого я сам себе немного усложнил задачу, добавив следующие требования:


  1. Возможность без затруднений использовать различные домены (или, зачастую, поддомены) для каждого отдельного контейнера (по принципу виртуальных хостов).
  2. Использование HTTPS-протокола по-умолчанию. Более того, хотелось бы организовать бесплатное генерирование SSL-сертификатов, не уступающих при этом платным аналогам.
  3. Разворачивание на одном и том же сервере 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 Engine


Базовые принципы:


Docker представляет из себя дополнительный уровень абстракции; систему, автоматизирующую виртуализацию на уровне операционной системы.


»Виртуализация на уровне операционной системы — метод виртуализации, при котором ядро операционной системы поддерживает несколько изолированных экземпляров пространства пользователя, вместо одного. Эти экземпляры (часто называемые контейнерами или зонами) с точки зрения пользователя полностью идентичны реальному серверу. Ядро обеспечивает полную изолированность контейнеров, поэтому программы из разных контейнеров не могут воздействовать друг на друга.»

From Wikipedia

Основные преимущества использования Docker:


  • Изолированность отдельных кластеров друг от друга;
  • Один и тот же Docker-контейнер может работать без изменений на множестве различных машин (с разными конфигурациями);
  • Docker оптимизирован для развертывания приложений (application-centric);
  • Автоматизация сборки приложений;
  • Повторное использование одних и тех же компонентов;
  • Open Source реестр готовых контейнеров (Docker Hub);
  • Единый API для автоматизации пользовательской настройки контейнера и его разворачивания.

Далее расмотрим основные команды, которые нам понадобятся для создания кластера:


$ docker run


По сути основная команда, выполняющая запуск нового контейнера.


Основные параметры:


  1. --name: UUID идентификатор: уникальное имя контейнера;
  2. --volume (-v): том, связанный с контейнером: задается в форме абсолютного пути к директории;
  3. --env (-e): переменная окружения: позволяет дополнительного настраивать запускаемый контейнер;
  4. --publish (-p): настройка определенных портов, необходимых для работы контейнера (например, 80 для http, 443 для https).

$ docker ps


Команда, с помощью которой можно получить список запущенных контейнеров.


$ docker stop container-name


Команда, останавливающая работу контейнера.


$ docker rm container-name


Удаление определенного контейнера.


Внимание: прежде, чем удалить контейнер, его необходимо остановить (docker stop)!


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


Конкретные примеры использования docker run вы увидите в этой статье немного далее.


Настройка виртуальных хостов


Nginx Reverse Proxy


Проблема: определенная сложности реализации кластера с использованием виртуальных хостов в различных контейнерах заключается в том, что один порт может «прослушиваться» только одним контейнером (настраивается через --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

Рассмотрим эту команду более подробно:


  1. -p 80:80 -p 443:443 — привязываем к контейнеру порты 80 и 443. При чем 80-ый порт сервера отвечает 80-ому порту внутри контейнера и аналогично с портом 443. В формате PORT: PORT2 выполняется создание соответствий между реальным портом всей машины и портами внутри отдельного виртуального контейнера.
  2. -v /full/path/to/ssl-keys:/etc/nginx/certs — первый volume, необходимый для настройки данного контейнера. Здесь мы выполняем связь стандартной директории /etc/nginx/certs внутри самого контейнера с директорией, в которую мы вручную поместили символьные ссылки на файлы сертификатов и приватных ключей для наших доменов (на предыдущем этапе).
  3. 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 необходимо:


  1. Загрузить в отдельную директорию исходники tutum-docker-lamp (удобнее всего это сделать с помощью git clone);


  2. Создать в этой рабочей директории файл docker-compose.yml с примерно таким содержанием:

web:
    ​build: .
    volumes:
        - ./www:/var/www/html
    environment:
        - MYSQL_PASS=yourmysqlpassword
        - VIRTUAL_HOST=yourdomain.com   

  1. Выполнить запуск с помощью 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


GitLab CE on Docker Engine


Некоторые, более сложные контейнеры, требуют дополнительной настройки для запуска с использованием 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 упрощается до нескольких команд:


  1. docker stop custom-gitlab — останавливаем работающий контейнер;


  2. docker rm custom-gitlab — удаляем контейнер GitLab CE.


    Важный момент: удаление контейнера не означает удаление данных, которые были созданы в процесе использования системы. Поэтому вы можете выполнять эту команду без каких-либо опасений.


  3. docker pull gitlab/gitlab-ce — собственно обновление изображения контейнера;


  4. выполняем длинную команду (пример выше), с помощью которой мы изначально запускали контейнер.

Вот и все. Выполнив эти 4 команды, GitLab автоматически обновится до последний версии и запустится через Docker Engine.


Итоги


Итак, в результате выполнения этого гайда должен получится Docker-кластер, в основе которого лежит NGINX Reverse Proxy; каждое веб-приложение имеет свой виртуальный хост, который при этом поддерживает защищенный HTTPS-протокол.


Вместе с веб-приложениями функционирует GitLab-кластер, полностью настроенный, вплоть до доступа к SMTP-серверу.


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


Спасибо за внимание!

Комментарии (1)

  • 15 декабря 2016 в 13:11

    0

    Я так понял что всё запускалось на одном сервере. Как насчёт сценария с тремя серверами — на одном nginx-proxy, на двух других — контейнер с веб-сервисом. Как это сконфигурировать? Как сделать так, чтобы nginx видел что один контейнер с веб-сервисом умер, и на него не надо перенаправлять веб-запросы?

© Habrahabr.ru