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

в 9:31, , рубрики: centos 7, certbot, docker, gitlab, google cloud, lamp, lemp, LetsEncrypt, mean, open source, ssl сертификаты, кластеризация, Разработка веб-сайтов, Разработка под Linux, системное программирование, Системы управления версиями

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

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

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

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

Opening Illustration - Docker

Преамбула

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

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

  • LAMP;
  • LEMP;
  • MEAN.

Первые две в представлении, думаю, не нуждаются. Третяя же состоит из MongoDBExpress.jsNode.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-серверу.

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

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

Автор: Kyborg2011

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js