- PVSM.RU - https://www.pvsm.ru -
В этом руководстве рассмотрим вариант настройки непрерывной интеграции и развертывания Flask приложения на Docker Swarm через GitLab CI.
Сначала мы рассмотрим настройку рабочей среды, включая создание серверов для нодов Docker Swarm. Затем создадим простое приложение Flask с Redis и подготовим GitLab CI для непрерывной доставки.
Управление контейнерами и управление нагрузкой — нетривиальная тема и требует значительной подготовки, особенно если планируется самостоятельная настройка системы оркестрации (например, с помощью Kubernetes). Однако, существуют такие инструменты как Docker Swarm [1] или Rancher [2], которые берут на себя управление контейнерами, внутренними сетями и распределение нагрузок и дают возможность развернуть масштабируемую систему на собственных серверах.
Кроме того, GitLab хорошо поддерживает Docker и позволяет подключать собственные системы хранения образов (GitLab Registry) в несколько простых шагов. А также отслеживать статус выполнения работ по проекту и управлять развернутыми версиями приложения, позволяя при необходимости откатиться на прошлую в один клик.
Перед началом необходимо удостовериться в выполнении следующих условий:
Официальное руководство [3] по настройке HTTPS в GitLab.
Для настройки и запуска приложения в режиме Swarm нам понадобится 2 типа серверов — менеджер и подчиненный. Кроме того, дополнительно один сервер будет выделен под GitLab Runner для выполнения задачи сборки и запуска контейнеров.
Виртуальные сервера мы будем создавать на платформе Vscale [4], однако, если вы пользователь другого сервиса, например, DigitalOcean [5], то действия будут аналогичны изложенным далее.
Создадим три новых сервера из образа Docker:
Полученные IP-адреса мы будем использовать далее.
Примечание. Использование TLS и управление центром сертификации — тема, требующая значительной подготовки. Желательно ознакомиться с технологиями OpenSSL, x509 и TLS перед использованием их в реальных проектах.
На финальном этапе развертывания приложения в рабочей Swarm-среде необходимо защищенное соединение между GitLab Runner и сервисом Docker, запущенном на сервере Manager, что видно на схеме ниже:
Процесс развертывания приложения в рабочей среде.
Для этого необходимо использовать единый сертификационный центр для клиента (GitLab Runner) и сервиса Docker (Manager). После создания сертификата и ключа для клиента, их можно использовать для удаленного подключения к сервису Docker и выполнения различных операций. Следует быть максимально осторожными с хранением клиентских сертификатов и ключей, поскольку они дают полное управление надо сервисом Docker.
Подключимся по SSH к серверу Manager и обновим Docker, поскольку в дальнейшем нам потребуются дополнительные возможности, доступные в старшей версии Docker API. Добавим репозиторий от разработчиков Docker для получения последней версии:
$ apt-get update
$ apt-get install
apt-transport-https
ca-certificates
curl
software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
$ add-apt-repository
"deb [arch=amd64] https://download.docker.com/linux/ubuntu
$(lsb_release -cs)
stable"
$ apt-get update
Установим последнюю версию Docker:
$ apt-get install docker-ce
Для проверки установки выполним команду:
$ docker version
Вывод команды может отличаться, главное, чтобы обе версии API клиента и сервера были больше 1.24:
Client:
Version: 17.09.0-ce
API version: 1.32
Go version: go1.8.3
Git commit: afdb6d4
Built: Tue Sep 26 22:42:18 2017
OS/Arch: linux/amd64
Server:
Version: 17.09.0-ce
API version: 1.32 (minimum version 1.12)
Go version: go1.8.3
Git commit: afdb6d4
Built: Tue Sep 26 22:40:56 2017
OS/Arch: linux/amd64
Experimental: false
Оставаясь в сервере Manager, приступим к созданию сертификационного центра (CA). Перейдем в новую директорию:
$ mkdir certificates
$ cd certificates
Для начала нужно создать приватный и публичный RSA-ключ для CA (потребуется придумать кодовое слово длиной не меньше 4 символов):
$ openssl genrsa -aes256 -out ca-key.pem 4096
Generating RSA private key, 4096 bit long modulus
...............................................................................................................................................................................................................................++
..................................................++
e is 65537 (0x10001)
Enter pass phrase for ca-key.pem:
Verifying - Enter pass phrase for ca-key.pem:
Приступим к созданию локального сертификационного центра. Будут запрошены данные связанные с идентификацией центра. На этапе ввода полного квалифицированного доменного имени (FQDN) требуется ввести доменное имя хоста, по которому доступен сервер Manager, но в целях примера (не используйте подобный метод в рабочих машинах!) используем слово manager для обозначения сервера:
$ openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
Enter pass phrase for ca-key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:manager
Email Address []:your@email.com
Далее создадим приватный ключ для сервера:
$ openssl genrsa -out server-key.pem 4096
Теперь у нас есть сертификационный центр и мы можем создать запрос на подпись SSL сертификата (CSR). Поле CN (Common Name) должно совпадать с использованным на предыдущем шаге значением FQDN:
$ openssl req -subj "/CN=manager" -sha256 -new -key server-key.pem -out server.csr
Дополнительно требуется указать IP-адрес сервера Manager (машина, в которой мы сейчас работаем):
$ echo subjectAltName = DNS:manager,IP:{ваш IP-адрес} >> extfile.cnf
Создадим подписанный ключ для сервера:
$ openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem
-CAcreateserial -out server-cert.pem -extfile extfile.cnf
Создадим клиентский ключ, который будем использовать для доступа к сервису Docker:
$ openssl genrsa -out key.pem 4096
Создадим запрос на подпись и дополнительно укажем тип использования ключа — для авторизации:
$ openssl req -subj '/CN=client' -new -key key.pem -out client.csr
$ echo extendedKeyUsage = clientAuth >> extfile.cnf
Получим подписанный клиентский ключ:
$ openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem
-CAcreateserial -out cert.pem -extfile extfile.cnf
Теперь можно удалить файлы запросов:
$ rm -v client.csr server.csr
В итоге, мы получили следующие файлы:
$ ls
ca-key.pem ca.srl extfile.cnf server-cert.pem
ca.pem cert.pem key.pem server-key.pem
Оставим терминал с сессией на сервере Manager открытым, поскольку он понадобится нам далее.
Сейчас у нас есть всё необходимое для настройки защищенного доступа между GitLab Runner и рабочей Swarm-средой.
Мы не будем хранить данные клиентских ключей на машине Runner из соображений безопасности. Для таких задач в GitLab CI реализована функция секретных переменных среды.
Создадим новый проект в GitLab:
После создания проекта перейдем в настройки CI / CD:
Откроем область секретных переменных (Secret variables) и будем работать с ней:
Нам необходимо добавить и сохранить три переменные со следующими названиями и значениями файлов, которые мы создали на предыдущем шаге:
Вернёмся к терминалу с сессией на сервере Manager и выполним команду:
$ cat ca.pem
(Сокращенный вывод)
-----BEGIN CERTIFICATE-----
MIIFgTCCA2mgAwIBAgIJAMzFvrYTSMoxMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
...
bI9XGs39F+r8Si5y6oHqkZHMpRX631i2KRA6k4jBPrZrS0MH3OwsCobuat5T1ONH
Kx7TFZSuFO25XIut1WucVn5yPWLTKRniMV7dVws9i9x9Sp2Iamk+w2x1GPO6bHtr
BWqdORkUEWMs+DTgX2J989AFh7gnYwHZ2Bo7HKlC6IbOlol7b2E/5p7hWrpe7sf+
oQDn1bhgoauhq2AL4BysJfA3uHoA
-----END CERTIFICATE-----
Скопируем это значение и добавим новую секретную переменную в GitLab:
Теперь по этому примеру добавим оставшиеся значения cert.pem и key.pem.
По-умолчанию доступ к сервису Docker имеет только пользователь-владелец процесса. Для выполнения операций по развертыванию приложения с удаленного хоста, нам необходимо разрешить подключение извне, при этом использовать TLS-протокол. Мы уже получили необходимые сертификаты и ключи, осталось настроить Docker для работы с ними.
Мы создадим отдельный конфигурационный файл явно прописывающий хосты, по которым будет доступен Docker, поэтому для начала нам нужно удалить стандартный параметр -H, прописывающий хост. Для этого откроем файл docker.service:
$ nano /etc/systemd/system/multi-user.target.wants/docker.service
Найдем строку:
ExecStart=/usr/bin/dockerd -H fd://
Удалим флаг и значение, приведя строку к следующему виду:
ExecStart=/usr/bin/dockerd
Создадим новый конфигурационный файл:
$ nano /etc/docker/daemon.json
И запишем следующий текст, определяющим использование протокола TLS для доступа к сервису Docker, а также расположение серверного ключа и сертификата:
{
"hosts": ["tcp://0.0.0.0:2376","fd://"],
"tlsverify": true,
"tlscacert": "/root/certificates/ca.pem",
"tlscert": "/root/certificates/server-cert.pem",
"tlskey": "/root/certificates/server-key.pem"
}
Для вступления изменений в силу, перезапустим сервис Docker:
$ systemctl daemon-reload
$ service docker restart
Теперь мы можем подключиться по TLS к нашему сервису Docker по адресу
[IP адрес сервера]:2376
Для начала рекомендуем изучить материалы на тему режима Swarm, например, на официальном сайте Docker [1]. Swarm — это кластер сервисов Docker, расположенных на различных физических или виртуальных машинах и ведущих себя как единое целое.
Распределение запросов между имеющимися сервисами Docker осуществляется по схеме ingress load balancing, суть которой в том, что любой запрос проходит через внутренний механизм балансировки, а затем перенаправляется на тот сервис, который в данный момент может обслужить запрос.
Масштабирование осуществляется за счет указания количества реплик внутренних сервисов, с которыми мы столкнемся позднее.
Мы активируем режим Docker Swarm на сервере Manager, на котором будет располагаться менеджер этого кластера. Затем мы добавим подчиненный сервис Docker с машины Node1.
В терминале с открытой сессией на сервере Manager выполним команду:
$ docker swarm init
Swarm initialized: current node (r1mbxr2dyuf48zpm5ss0kvwv7) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-5ihkl37kbs13po7htnj9dzzg3gex4i6iuvjho7910crd0hv895-36jw5epwcw3xwpzmqf1mqgod2 {ваш IP-адрес}:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Как видно из сообщения, текущий сервис Docker стал менеджером и готов к добавлению подчиненных хостов через выполнение указанной команды. Скопируем эту команду и подключимся к серверу Node1 по SSH, для добавления его добавления в Swarm:
$ docker swarm join --token SWMTKN-1-1lhmuomvb060rnom4jqj8gxc565f4wgwadjs9ucvqx6huwvbfc-6vt1ljdhldxtetjv2hnct7sh4 {ваш IP-адрес}:2377
Результатом успешного выполнения команды должно стать сообщение:
This node joined a swarm as a worker.
Следующем шагом станет настройка рабочего сервера, который будет выполнять все работы от GitLab CI.
Финальным этапом настройки среды непрерывной интеграции и развертывания с Docker является подключение рабочего сервиса GitLab CI, на котором будут выполняться все работы по сборке и тестированию приложения.
Можно использовать совместные сервисы выполнения работ, но в данном руководстве рассмотрим создание собственного сервиса на созданном ранее сервере Runner.
Подключимся по SSH к серверу Runner. Сперва необходимо установить GitLab Runner и соединить этот сервер с GitLab.
Добавим репозиторий разработчиков GitLab:
$ apt update
$ apt install curl
$ curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | bash
$ apt-get install gitlab-runner
Вернемся к веб-интерфейсу GitLab для получения URL и registration-token. Снова зайдем в настройки проекта в раздел CI / CD, где мы остановились в прошлый раз. Откроем секцию Runner settings:
В разделе Runner settings есть информация по подключенным рабочим хостам: приватным и открытым:
В блоке Specific Runners есть необходимые значения URL и registration token.
Вернемся в терминал с сессией на сервере Runner и заменив значения на свои выполним команду:
$ gitlab-runner register -n
--url http:// {ваш IP-адрес}/
--registration-token _Kof1SxCHzVNcwuZZEwx
--executor docker
--description "Docker Prod Runner"
--docker-image "docker:latest"
--docker-privileged
--tag-list docker
Registering runner... succeeded runner=_Kof1SxC
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
Теперь у нас зарегистрирован рабочий хост, который будет выполнять все работы, посылаемые GitLab CI.
Осталось настроить SSH доступ для Git-репозитория проекта на GitLab.
Создадим приватный и публичный ключи, заменив свой email-адрес и оставив все запрашиваемые значения по-умолчанию:
$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
Далее нужно добавить информацию о сервере GitLab, на котором расположен Git-репозиторий в список известных хостов для предотвращения ошибок во время подключения (IP адрес нужно заменить на IP адрес сервера GitLab, доступный в панели управления):
$ ssh-keyscan -t rsa {ваш IP-адрес} >> .ssh/known_hosts
Затем необходимо добавить публичный ключ в GitLab, чтобы разрешить подключение с сервера Runner. Скопируем значение ключа:
$ cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMY+G6rmx+AZ6Ow1lVr+4ox0HaAaV4xwthCS3ucyI3KsXVV+ltLU0zkFOP8WZoTXCHo38Fpcu5KwYe3V6L/hZ26fNse6WhJ6EvRmVx7wVHyixhpzKj6Jp9zzAf24SbtrjGgNtN4ASKyouU///a3+gtM+eWQYdxavz6wlJ0xgm8MnDqpbCUv1M7IRWsKhejA9vLSXjfdkxQnxVSCOT/FXb/eDsGRTs7WMXYapqeQ0msTXDCFlNDVQaRWZagXpHLRDkeOhRE2rJ6daj8YNKKx0jaatRKIsICqwUljvPgsrnpF9FiUg8n8PTWyYbz3VpwUoIPnFiFXvbgIn8xLb2/4QkFDoZUgyLI+VgrmZmd0HZPWvW5QbMLZ8vwb/Izi0TG/+qoMm8jas0RaUUp18rQAc4GmCLRsFbzN3DsnME31xFa0y/pwA3LK9ptIRivYq82uP5twq0jXpMSji8w+No7kBI5O9VUHmbRYYYWpn+jeKTxmoVORsrCHpAT7Cub0+Ynyq1M7Em0RMqZgdzLsP9rlLwRkc6ZEgqpVQHDZgwJsnQ5qo/6lr18bD9QHSe5t+SSnUbnkmXkp0xb0ivC4XayxCjYVIOoZV2cqyGa+45s7LY+ngPk0Cg+vSMHV8/enEwu1ABdpoGVjaELJOtw1UBr4y9GCyQ0OhKnrzWmqL6+HnEMDQ== your_email@example.com
И добавим его в GitLab:
Перейдем к созданию и докеризации простого Flask приложения, использующего два дополнительных сервиса: Nginx для маршрутизации запросов и Redis для хранения счетчика посещений страницы.
Перед созданием приложения нам необходимо активировать функцию хранения образов Docker в нашем приложении GitLab для эффективного управления развернутыми версиями приложения и обеспечения возможности отката на предыдущие версии.
Подключимся по SSH к серверу GitLab и откроем конфигурационный файл:
$ nano /etc/gitlab/gitlab.rb
Далее добавим с новой строки адрес, по которому будет доступно хранилище Docker, заменив example.com на имя своего хоста:
registry_external_url 'https://gitlab.example.com:4567'
Добавим ещё две строки с указанием места хранения сертификата и ключа. Поскольку мы используем такое же доменное имя, что и для основного приложения GitLab (если вы ещё не настроили HTTPS для GitLab, можно это сделать сейчас [3]):
registry_nginx['ssl_certificate'] = "/etc/letsencrypt/live/gitlab.example.com/fullchain.pem"
registry_nginx['ssl_certificate_key'] = "/etc/letsencrypt/live/gitlab.example.com/privkey.pem"
Перезагрузим сервис GitLab для активации хранилища:
$ gitlab-ctl reconfigure
Перейдем к созданному ранее проекту flask-docker-swarm в GitLab. В случае успешной активации хранилища, в меню проекта станет доступен раздел Registry:
Для дальнейшей работы с внутренним хранилищем образов нам понадобится использовать связку логин-пароль для подключения к нему. Используем возможность добавления секретных переменных в GitLab для добавления пароля. Для этого перейдем в раздел Settings и выберем блок CI / CD:
Раскроем раздел Runner settings и добавим новую переменную HUB_REGISTRY_PASSWORD, значением которой является пароль от учетной записи пользователя GitLab:
Программа будет представлять собой простое веб-приложение, считающее количество посещений и отображающее информацию о контейнере, в котором оно запущено. Для этого нам необходимо создать несколько Dockerfile (файлов конфигурации образа Docker), для каждого используемого сервиса (Nginx, Redis, Flask) и указать, как они должны взаимодействовать между собой.
Откроем страницу проекта, созданного ранее:
Выполним следующую команду для клонирования репозитория и перехода в рабочую директорию, заменив доменное имя:
$ git clone git@gitlab.example.ru:root/flask-docker-swarm.git
$ cd flask-docker-swarm
Создадим внутри три директории для каждого сервиса:
$ mkdir nginx
$ mkdir web
$ mkdir redis
Для сервиса Nginx нам понадобится создать три файла — Dockerfile и два файла настроек. Создадим и запишем общие настройки для всего веб-сервера:
$ nano nginx/nginx.conf
И запишем следующий текст:
# Укажем какой пользователь запускает и выполняет Nginx процесс
user nginx;
# Укажем количество рабочих процессов, рекомендуемое число -
# число процессоров на сервере
worker_processes 1;
# Укажем расположение лога ошибок и уровень значимости записываемых сообщений
error_log /var/log/nginx/error.log warn;
# Укажем имя файла, в котором будет хранится PID главного Nginx-процесса
pid /var/run/nginx.pid;
events {
# Укажем максимальное число одновременных соединений
worker_connections 1024;
}
# http блок определяет как должен вести себя Nginx c http-трафиком
http {
# Включение файла с перечислением всех поддерживаемых типов файлов
include /etc/nginx/mime.types;
# Определение файла возвращаемого по-умолчанию
default_type text/html;
# Определяет формат сообщений-логов
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Указывает путь сохранения логов запросов на доступ к Nginx
access_log /var/log/nginx/access.log main;
# Параметры для оптимизации доставки статических файлов
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Определяет время жизни соединения с клиентом
keepalive_timeout 65;
# Параметр включает компрессирование gzip для экономии трафика
#gzip on;
# Включение дополнительных параметров для виртуальных хостов
include /etc/nginx/conf.d/*.conf;
}
Следующий файл определяет параметры для виртуального сервера, непосредственно здесь указывается связь на сервис web, в котором будет выполняться наше приложение:
$ nano nginx/flask.conf
И запишем текст:
# Блок server определяет параметры виртуального хоста/сервера
server {
# Определение имени сервера, IP адреса и/или порта, на котором слушает сервер
listen 80 default_server;
# server_name xxx.yyy.zzz.aaa
# Определение типа символов для поля “Content-Type” в заголовке ответа
charset utf-8;
# Настройка Nginx для доставки статических файлов через указанную директорию
#location /static {
# alias /usr/src/app/web/static;
#}
# Настройка Nginx в качестве прокси-сервера на внутренний сервер выгрузки данных (Gunicorn (WSGI server))
location / {
# Адрес и порт сервера выгрузки данных
proxy_pass http://web:5000;
# Переопределение заголовков посылаемых серверу выгрузки данных
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Максимальный размер файлов для загрузки
client_max_body_size 5M;
client_body_buffer_size 5M;
}
}
Блок со статическим контентом закомментирован, поскольку в нашем приложении не будет дополнительных файлов, но в дальнейшем можно добавить директорию static в сервисе web и открыть этот блок для эффективной передачи статических файлов.
Создадим Dockerfile, в котором используем готовый образ Nginx в DockerHub и модифицируем его для использования своих файлов настройки:
$ nano nginx/Dockerfile
И запишем следующий текст:
FROM nginx:1.13.6
RUN rm /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/
RUN rm /etc/nginx/conf.d/default.conf
COPY flask.conf /etc/nginx/conf.d
Для сервиса Redis создадим Dockerfile:
$ nano redis/Dockerfile
С простым содержанием:
FROM redis:3.2.11
Мы не вносим дополнительных изменений, но в будущем они вполне возможны, поэтому создадим отдельный сервис.
Сервис с приложением Flask начнём с создания основного исполняемого файла:
$ nano web/main.py
Вставим следующий код:
from flask import Flask
from redis import Redis, RedisError
import os
import socket
# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
app = Flask(__name__)
@app.route("/")
def hello():
try:
visits = redis.incr("counter")
except RedisError:
visits = "<i>cannot connect to Redis, counter disabled</i>"
html = "<h3>Hello {name}!</h3>"
"<b>Hostname:</b> {hostname}<br/>"
"<b>Visits:</b> {visits}"
return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)
if __name__ == "__main__":
app.run()
Создадим отдельный файл с указанием использованных зависимостей:
$ nano web/requirements.txt
И перечислим используемые программные пакеты:
Flask==0.12.2
Redis==2.10.6
Gunicorn==19.7.1
Nose2
Coverage
Настроим простой пример модульного тестирования для демонстрации, в котором проверим доступность страницы. Создадим файл теста:
$ nano web/test_smoke.py
Скопируем и вставим текст:
import os
import unittest
from main import app
class BasicTests(unittest.TestCase):
# executed prior to each test
def setUp(self):
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
app.config['DEBUG'] = False
self.app = app.test_client()
self.assertEqual(app.debug, False)
# executed after each test
def tearDown(self):
pass
def test_main_page(self):
response = self.app.get('/', follow_redirects=True)
self.assertEqual(response.status_code, 200)
if __name__ == "__main__":
unittest.main()
Создадим файл, который будет являться входной точкой нашего приложения:
$ nano web/wsgi.py
В котором укажем имя импортируемого объекта, который будет использовать Gunicorn:
from main import app
if __name__ == "__main__":
app.run(host='0.0.0.0')
Последним файлом в директории web будет Dockerfile, в котором будут перечислены команды для создания образа нашего сервиса:
$ nano web/Dockerfile
Со следующим содержанием:
FROM python:3.6.3
RUN groupadd flaskgroup && useradd -m -g flaskgroup -s /bin/bash flask
WORKDIR /app
ADD . /app
RUN pip install -r requirements.txt
Для создания управляемых сервисов используем инструмент docker-compose, который позволяет указать, на основе какого образа происходит запуск контейнера и определяет поведение сервиса в целом. Для этого создадим файл docker-compose.yml:
$ nano docker-compose.yml
И запишем такой текст, заменив доменное имя:
version: "3.4"
services:
web:
image: gitlab.example.ru:4567/root/flask-docker-swarm/web:${CI_COMMIT_SHA}
deploy:
replicas: 4
restart_policy:
condition: on-failure
command: gunicorn -w 3 --bind 0.0.0.0:5000 wsgi:app
nginx:
image: gitlab.example.ru:4567/root/flask-docker-swarm/nginx:${CI_COMMIT_SHA}
deploy:
mode: global
restart_policy:
condition: on-failure
ports:
- "80:80"
redis:
image: gitlab.example.ru:4567/root/flask-docker-swarm/redis:latest
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
restart_policy:
condition: on-failure
ports:
- "6379"
Рассмотрим подробнее структуру файла:
Для запуска на локальной машине нам понадобится создать дополнительный файл настройки docker-compose для упрощенного тестирования приложения без использования сервиса Nginx:
$ nano docker-compose.override.yml
Вставим следующий текст:
version: "3.4"
services:
web:
image: web
environment:
- FLASK_APP=wsgi.py
- FLASK_DEBUG=1
build:
context: ./web
dockerfile: Dockerfile
command: 'flask run --host=0.0.0.0'
links:
- redis
ports:
- "5000:5000"
volumes:
- ./web/:/usr/src/app/web
redis:
image: redis
build:
context: ./redis
dockerfile: Dockerfile
ports:
- "6379:6379"
По структуре файл напоминает основную версию, но не использует сервис Nginx и есть дополнительный раздел сборки контейнеров build.
Управление GitLab CI осуществляется через файл конфигурации:
$ nano .gitlab-ci.yml
Запишем следующий текст, поменяв example.ru на свое доменное имя:
image: docker:17.09.0-ce
services:
- docker:dind
before_script:
- apk add --update py-pip &&
pip install docker-compose
stages:
- test
- build
- deploy
- stage
unittests:
stage: test
script:
- cd web
- pip install -q -r requirements.txt
- nose2 -v --with-coverage
tags:
- docker
docker-build:
stage: build
script:
- docker login -u root -p $HUB_REGISTRY_PASSWORD https://gitlab.example.ru:4567/
- docker build -t gitlab.example.ru:4567/root/flask-docker-swarm/nginx:$CI_COMMIT_SHA ./nginx
- docker push gitlab.example.ru:4567/root/flask-docker-swarm/nginx:$CI_COMMIT_SHA
- docker build -t gitlab.example.ru:4567/root/flask-docker-swarm/web:$CI_COMMIT_SHA ./web
- docker push gitlab.example.ru:4567/root/flask-docker-swarm/web:$CI_COMMIT_SHA
- docker build -t gitlab.example.ru:4567/root/flask-docker-swarm/redis:latest ./redis
- docker push gitlab.example.ru:4567/root/flask-docker-swarm/redis:latest
tags:
- docker
deploy-to-swarm:
stage: deploy
variables:
DOCKER_HOST: tcp://{manager_ip_address}:2376
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "/certs"
script:
- mkdir -p $DOCKER_CERT_PATH
- echo "$TLSCACERT" > $DOCKER_CERT_PATH/ca.pem
- echo "$TLSCERT" > $DOCKER_CERT_PATH/cert.pem
- echo "$TLSKEY" > $DOCKER_CERT_PATH/key.pem
- docker login -u root -p $HUB_REGISTRY_PASSWORD $CI_REGISTRY
- docker stack deploy -c docker-compose.yml env_name --with-registry-auth
- rm -rf $DOCKER_CERT_PATH
environment:
name: master
url: http://{manager_ip_address}
only:
- master
tags:
- docker
Необходимо заменить значения полей DOCKER_HOST и URL на свои, а также внимательно изменить названия тегов образов Docker. Подробнее о доступных названиях образов можно посмотреть в Registry в GitLab.
В качестве тегов для образов мы использовали переменную окружения $CI_COMMIT_SHA для создания привязки между коммитом и образом. Для образа Redis мы указали тег latest в целях сохранения текущей базы данных.
Рассмотрим файл настроек подробнее:
Мы готовы запустить проект на локальной машине. Подобный метод запуска удобно использовать в целях разработки и тестирования в дальнейшем. Для начала установим Docker (мы приведем пример для Ubuntu 16.04, для Windows существует отдельный инсталлятор). Добавим репозиторий от разработчиков Docker для получения последней версии:
$ sudo apt-get update
$ sudo apt-get install
apt-transport-https
ca-certificates
curl
software-properties-common
$ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository
"deb [arch=amd64] https://download.docker.com/linux/ubuntu
$(lsb_release -cs)
stable"
$ sudo apt-get update
Установим последнюю версию Docker:
$ sudo apt-get install docker-ce
Запустим сборку проекта:
$ sudo docker-compose -f docker-compose.override.yml build
Если всё прошло без ошибок, запустим проект локально:
$ sudo docker-compose -f docker-compose.override.yml up
И перейдем в браузере по адресу:
localhost:5000
В случае успешного запуска всех сервисов, вывод будет примерно следующим:
Если попробовать перезагрузить страницу несколько раз, счётчик посещений будет увеличиваться, а hostname останется прежним, потому что сейчас используется только один контейнер, в котором выполняется сервис web, генерирующей веб-страницу.
Пришло время самого интересного — запуска и обзора непрерывной интеграции. Для этого просто перейдем в директорию нашего проекта на локальной машине и произведем первый коммит и публикацию в удаленный репозиторий:
$ git add --all
$ git commit -m “init”
$ git push origin master
Если на предыдущих шагах не было совершенно ошибки, произойдет “пуш” в наш репозиторий GitLab, пройдут все тесты и сборка образов Docker, а затем развертывание в Docker Swarm.
Для отслеживания процессов перейдем в GitLab в раздел Pipelines и откроем последний:
Если ваш результат отличается от приведенного, перейдите в раздел Jobs и посмотрите логи по неудавшейся операции, часто это бывают опечатки или неверные адреса серверов.
Перейдем в раздел Environments:
Здесь происходит управление развернутыми средами для различных целей. Сейчас мы используем только среду master, но это не мешает вам настроить их под свои процессы.
Если было произведено уже несколько коммитов в одну среду, можно использовать функцию откатывания на предыдущую версию — для этого раскройте среду master, нажатием на название среды:
Для отката на предыдущую версию достаточно нажать кнопку Rollback, которая запустит стадию deploy для выбранного коммита.
Мы использовали развертывание среды на каждый коммит в качестве примера, но в реальности возможно настроить срабатывание такой работы на другие события, например, слияние веток.
Посмотрим на наше приложение. В браузере перейдите по адресу сервера Manager:
Если несколько раз перезагрузить страницу, то заметим, что значение hostname меняется в пределах 4 вариантов, что соответствует количеству реплик, которое мы указали для сервиса web.
Docker Swarm взял на себя контроль по размещению контейнеров между нодами в своей сети. При этом половина контейнеров сервиса web была автоматически размещена в подчиненном ноде. Для просмотра подробной информации можно выполнить следующую команду на сервере Manager, заменив название рабочей среды (если вы его меняли в команде docker stack deploy) на своё:
$ docker stack ps env_name
Мы рассмотрели один из способов применения GitLab CI для настройки непрерывной интеграции и доставки своих Docker-проектов. Следует отметить, что при использовании подобного решения для рабочих проектов следует уделить значительное внимание безопасности — использовать сертификаты доверенных центров, настроить сеть нодов в Docker Swarm для реагирования на перезагрузки серверов и контролировать количество хранимых и используемых образов Docker.
Автор: nzavyalov
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sistemnoe-administrirovanie/270328
Ссылки в тексте:
[1] Docker Swarm: https://docs.docker.com/get-started/part4/#prerequisites
[2] Rancher: https://rancher.com/
[3] руководство: https://about.gitlab.com/2016/04/11/tutorial-securing-your-gitlab-pages-with-tls-and-letsencrypt/
[4] Vscale: https://vscale.io/
[5] DigitalOcean: https://www.digitalocean.com/
[6] Источник: https://habrahabr.ru/post/344324/?utm_source=habrahabr&utm_medium=rss&utm_campaign=sandbox
Нажмите здесь для печати.