Сергей Коба

Сергей Коба

Веб, блокчейн, мобильная разработка и интернет вещей

Веб тимлид в MobiDev. Цель: изучать и учить чему-то новому без остановки. Основные языки: PHP и Ruby. Также интересно: блокчейн, мобильная разработка, IoT и DevOps. Жизненное кредо: я жив, пока я учу что-то новое.

Docker в Production с помощью Rancher

30 декабря 2016 00:47

Многие разработчики, с которыми я общался, признают пользу Docker для локальной разработки. Но с опаской смотрят на его использование в Production. Сегодня я надеюсь развеять часть опасений и поделиться своим опытом деплоя Docker приложений. 

Кстати этот блог, который Вы сейчас читаете, работает в Production и локально на Docker Fork me

Для менеджмента контейнеров на сервере я использую Rancher

Кратко о чем пойдет речь дальше:

  • подготовка Docker образов приложения;
  • установка Rancher на сервер;
  • добавление новых хостов;
  • создание нового Stack'а и деплой приложения.


Подготовка образов

Одной из главных идей деплоя с Docker является то, что для обновления Вашего приложения должно быть достаточно скачать новый образ из DockerHub.

Это централизованный репозиторий Docker-образов. Там ты найдешь огромное количество уже готовых к использованию образов на продакшене. Нужен образ, запускающий Redis? Нужен образ, запускающий Tomcat? Просто ищешь их в Docker Hub, запускаешь, и готовы работающие приложения.

Помимо официальных образов популярного ПО, каждый желающий может хранить там свои персональные приватные образы. Docker образы создаются и конфигурируются на основании Dockerfile-ов.

Это что-то типа Makefile, только вместо инструкций для сборки он содержит инструкции для конфигурирования контейнеров. Просто пишешь докерфайл, и Docker по нему создает и настраивает контейнер. Докерфайл позволяет разработчику создать повторяемое окружение (по своему сценарию).

Что нужно делать в Dockerfile? Устанавливать необходимое ПО (например, Ruby, nodejs, npm), копировать код приложения и файлы конфигурации внутрь образа, устанавливать необходимые библиотеки (rubygems, composer, npm, bower).

Что НЕ надо делать в Dockerfile? Не выполняйте никаких команд работы с базой данных, например не запускайте миграции.

Когда у вас есть готовый Dockerfile (в качесвте примера подойдет Dockerfile блога) можно начать загрузку образа на DockerHub. О том как создавать Dockerfile подробнее можете почитать в предыдущем посте. Предварительно зарегистрируйтесь там и создайте свой репозиторий, я свой назвал "blog", а мой пользователь "sergkoba"

#!/bin/bash
echo "Заходим 1 раз в DockerHub"
docker login
echo "Переходим в папку с проектом"
cd PATH_TO_YOUR_PROJECT
echo "Собираем образ приложения"
docker build -f Dockerfile -t sergkoba/blog:latest .
echo "Загружаем образ на DockerHub"
docker push sergkoba/blog:latest

Флаг -t используется, чтобы поставить метку ("latest") на образ и затем использовать её для быстрого и понятного поиска образа. Флаг -f в данном случае можно опустить, он задает путь к Dockerfile.

С этого момента наш образ загружен на DockerHub и готов к использованию.


Установка Rancher

Для использования Rancher  нужен сервер с Docker и минимум 1GB RAM. Причем требование по оперативке жесткое и установить на более слабый сервер Rancher не выйдет.

Чтобы установить Rancher подключитесь по ssh к серверу и запустите команду

$ sudo docker run -d --restart=unless-stopped -p 8080:8080 rancher/server

Она скачает образ Rancher (rancher/server) и запустит его на 8080 порту. Через минуту или две после запуска можете заходить в браузере на 8080 порт вашего сервера с Rancher.

По-умолчанию Rancher запускается в режиме "доступен всем", поэтому сразу после входа идем в Admin -> Accounts и создаем аккаунт для доступа к Rancher.

Немного о понятиях, Stack - это группа сервисов, объединенная по какому-либо признаку. Например Stack "blog" содержит 4 сервиса: admin - панель администрирования блога, bloglb - балансировщик нагрузки блога, db - сервис PostgreSQL, front - фронтенд часть блога

Это так называемые "User Stacks" - т.е. группы сервисов, созданные пользователем (Вами). Так же Rancher создает свои "системные" стэки, которые нужны для общения с хостами, мониторинга, выполнения задач по расписанию

У вас список "системных" стеков может отличаться, Rancher добавляет их по мере создания новых хостов. Также по-умолчанию Rancher создаст один пользовательский стэк с названием "default", можете его смело удалить.


Добавление хоста

Чтобы Rancher мог разворачивать наши стэки ему нужны для этого хосты (сервера). Идем в Infrastructure -> Hosts -> Add Host. У Вас есть несколько опций на выбор: создать хост вручную или с помощью API вашего провайдера, например Digital Ocean или Amazon.

При создании вручную (Custom), Вам нужно будет зайти по ssh на хост и запустить там агент Rancher. Команду, которой это нужно сделать Rancher подскажет вам ниже. При этом хост попытается подключится к серверу Rancher и при успешном исходе через минуту Вы должны увидеть Ваш хост в списке.

Признаюсь честно, я предпочел выбрать DigitalOcean и доверить установку Docker, Rancher Agent и всего остального самому Rancher. Вы должны предоставить Rancher только токен от вашего API на Digital Ocean, выбрать параметры сервера (тут зависит от будущего приложения, для блога подойдет и минимальный сервер с 512 оперативы) и всю остальную работу Rancher берет на себя.

Вот мой хост, на который я задеплоил стэк "blog", а также Rancher поставил свои "системные" стэки для мониторинга состояния хоста.


Деплой приложения (Docker Compose)

Теперь мы можем создать свой новый стэк через интерфейс и набросать в него сервисов (контейнеров). Но я предлагаю автоматизировать эту работу и не выполнять её вручную. В локальной работе с Docker я использую Docker Compose. Это инструмент для описания и запуска Docker приложений, состоящих из множества контейнеров (подробнее можете послушать и почитать в предыдущем моем посте о Docker). Docker Compose конфигурируется с помощью docker-compose.yml файла.

Упрощенная версия docker-compose.yml для блога выглядит следующим образом

version: '2'
services:
bloglb:
ports:
- 80:80/tcp
labels:
io.rancher.container.create_agent: 'true'
io.rancher.container.agent.role: environmentAdmin
image: rancher/lb-service-haproxy:v0.4.6
db:
image: postgres:latest
volumes:
- /var/lib/postgresql/data
environment:
POSTGRES_DB: blog
admin:
labels:
io.rancher.container.pull_image: always
image: sergkoba/blog:latest-admin
command: rackup --port 3000 --host 0.0.0.0
links:
- db environment: RACK_ENV: production

front:
labels:
io.rancher.container.pull_image: always
image: sergkoba/blog:latest
command: rackup --port 3001 --host 0.0.0.0
links:
- db environment: RACK_ENV: production

Этот docker-compose.yml уже готов для продакшена, мы используем в качестве образов приложения образы из Docker Hub (sergkoba/blog:latest и sergkoba/blog:latest-admin). При локальной разработки Вы захотите, чтобы Docker собирал образы из локального Dockerfile, добавить код приложения к контейнерам как volume, чтобы не пересобирать образы при кжадом изменении и т.д.

Для решения этой проблемы создается дополнительный файл "docker-compose-dev.yml"

version: '2'
services:
db:
ports:
- "5437:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: development
admin-bundler-cache:
image: ruby:2.3.1
command: echo 'Data Container for Ruby 2.3.1 bundled gems'
volumes:
- /usr/local/bundle
admin:
build: admin
command: rerun -- rackup --port 3000 --host 0.0.0.0
volumes:
- ./admin:/app
volumes_from:
- admin-bundler-cache
ports:
- "3000:3000"
environment:
RACK_ENV: development
stdin_open: true
tty: true
front-bundler-cache:
image: ruby:2.3.1
command: echo 'Data Container for Ruby 2.3.1 bundled gems'
volumes:
- /usr/local/bundle
front:
build: front
command: rerun -- rackup --port 3001 --host 0.0.0.0
volumes:
- ./front:/app
volumes_from:
- front-bundler-cache
ports:
- "3001:3001"
environment:
RACK_ENV: development
stdin_open: true
tty: true

Теперь локально мы можем запускать Docker приложение, следующим образом:

docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d

При запуске настройки контейнеров из docker-compose и docker-compose-dev будут объединены и мы получим контейнеры со всеми девелоперскими "тюнингами". А файл docker-compose.yml останется "чистым" (без настроек для локальной разработки) и готовым к Production. Для удобства я создал alias в .bashrc

alias dcg="docker-compose -f docker-compose.yml -f docker-compose.dev.yml"

И запускаю приложение так 

dcg up -d


Деплой приложения (Rancher Cli)

Идея Rancher Cli заключается в том, чтобы управлять стэком вашего приложения на основании docker-compose.yml. Для этого Rancher Cli использует свой файл расширения rancher-compose.yml, в котором мы можем указать количество разворачиваемых контейнеров и настройки балансировщика нагрузки

version: '2'
services:
bloglb:
scale: 1
lb_config:
certs: []
port_rules:
- hostname: '1devblog.org'
path: ''
priority: 1
protocol: http
service: front
source_port: 80
target_port: 3001
- hostname: 'admin.1devblog.org'
path: ''
priority: 2
protocol: http
service: admin
source_port: 80
target_port: 3000
health_check:
port: 42
interval: 2000
unhealthy_threshold: 3
healthy_threshold: 2
response_timeout: 2000
db:
scale: 1
front:
scale: 1
admin:
scale: 1

Перед установкой Rancher Cli необходимо сгенерировать API ключи в нашем сервере Rancher, с помощью которых Rancher Cli получит доступ к стэкам. Переходим в API -> Add Account API Key, генерируем ключи, записываем.

Для установки Rancher Cli достаточно  скачать бинарный файл и положить его в /home/USER_NAME/bin. Далее запускаем

rancher config

и вводим адрес сервера Rancher и сгенерированные API ключи. Теперь достаточно запустить (находясь в папке с проектом)

rancher up -d

чтобы Rancher Cli создал пользовательский стэк, назвал его в соответствии с именем папки, создал указанное количество контейнеров из rancher-compose.yml и запустил их. С этого момента Ваше приложение должно работать в штатном режиме. Обновление с помощью Rancher Cli заключается в запуске команды

rancher up -d -u -c

которая проверяет изменились ли контейнеры, если да, то обновляет измененные (флаг -u) и после успешного обновления заменяет старые контейнера новыми (флаг -c). Во время обновления старые версии контейнеров продолжают работать, поэтому выходит максимально Zero Downtime деплой.

Не забывайте указывать в docker-compose.yml 

labels:
io.rancher.container.pull_image: always

Это сообщает Rancher, что необходимо при обновлении всегда скачивать самый новый образ сервисов из Docker Hub. Альтернативный способ это добавить к команде флаг -p

rancher up -d -u -c -p


Послесловие

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

Глобально я вижу следующую последовательность действий при деплое с помощью  Rancher:

  1. Запустить тесты.
  2. Собрать и запушить образ(ы) приложения на Docker Hub.
  3. Проапдейтить Stack в Rancher.
  4. Запустить миграции БД и другие необходимые действия после деплоя.

Эти 4 действия можно разделить на две группы "Build"(1,2) и "Deploy"(3,4), или "Сборка" билда и "Деплой" билда. В идеале группа "Build" автоматически выполняется при каждом пуше кода в репозиторий, а "Deploy" запускается вручную при необходимости деплоя. 

В целом каждое из этих действий может быть полностью выполнено в автоматическом режиме и запускаться из Вашей Ci системы (Semaphore Ci, Code Ship, Bamboo, Jenkins).

Назад