Непрерывная доставка и поэтапные обновления
Введение
Непрерывная доставка — это концепция частой поставки обновлений для вашего программного приложения.
Идея заключается в том, что, обновляясь чаще, вам не нужно ждать определенного периода времени, и ваша организация становится лучше в процессе реагирования на изменения.
Некоторые пользователи Ansible развёртывают обновления для конечных пользователей ежечасно или даже чаще — иногда каждый раз после одобренного изменения кода. Для достижения этого вам нужны инструменты, которые смогут быстро применять эти обновления без простоев.
В данном документе подробно описано, как достичь этой цели, используя один из наиболее полных примерных файлов Ansible: lamp_haproxy. Этот пример использует множество функций Ansible: роли, шаблоны и переменные групп, а также включает скрипт оркестрации, который может выполнять поэтапные обновления веб-приложения без простоев.
Скрипты развёртывают Apache, PHP, MySQL, Nagios и HAProxy на наборе серверов на базе CentOS.
Мы не будем рассматривать здесь, как запускать эти скрипты. Прочитайте прилагаемый файл README в проекте github вместе с примером для получения этой информации. Вместо этого, мы подробно рассмотрим каждую часть скрипта и опишем его действия.
Развертывание сайта
Начнём с site.yml. Это наш скрипт развёртывания сайта. Он может использоваться для первоначального развёртывания сайта, а также для внесения обновлений на все серверы:
--- # This playbook deploys the whole application stack in this site. # Apply common configuration to all hosts - hosts: all roles: - common # Configure and deploy database servers. - hosts: dbservers roles: - db # Configure and deploy the web servers. Note that we include two roles # here, the 'base-apache' role which simply sets up Apache, and 'web' # which includes our example web application. - hosts: webservers roles: - base-apache - web # Configure and deploy the load balancer(s). - hosts: lbservers roles: - haproxy # Configure and deploy the Nagios monitoring node(s). - hosts: monitoring roles: - base-apache - nagios
Примечание
Если вы не знакомы с такими понятиями, как скрипты и задачи, ознакомьтесь со статьёй Скрипты.
В этом скрипте у нас 5 задач. Первая задача направлена на all хосты и применяет роль common ко всем хостам. Это делается для общих настроек, таких как настройка репозитория yum, настройка брандмауэра и всего остального, что необходимо применить ко всем серверам.
Следующие четыре задачи выполняются для определённых групп хостов и применяют к ним определённые роли. Наряду с ролями для мониторинга Nagios, базы данных и веб-приложения, мы реализовали роль base-apache, которая устанавливает и настраивает базовый Apache. Она используется как для примера веб-приложения, так и для хостов Nagios.
Переиспользуемые компоненты: Роли
К этому моменту вы должны понимать роли и как они работают в Ansible. Роли — это способ организации контента: задач, обработчиков, шаблонов и файлов в переиспользуемые компоненты.
В этом примере есть шесть ролей: common, base-apache, db, haproxy, nagios, и web. Способ организации ролей зависит от вас и вашего приложения, но большинство сайтов будут иметь одну или несколько общих ролей, применяемых ко всем системам, и ряд ролей, специфичных для приложения, которые устанавливают и настраивают определённые части сайта.
Роли могут иметь переменные и зависимости, и вы можете передавать параметры ролям, чтобы изменить их поведение. Более подробную информацию о ролях можно найти в разделе Роли.
Настройка: Переменные групп
Переменные групп — это переменные, которые применяются к группам серверов. Они могут использоваться в шаблонах и скриптах для настройки поведения, а также для предоставления легко изменяемых настроек и параметров. Они хранятся в каталоге group_vars в том же месте, что и ваш инвентарь. Вот файл group_vars/all из lamp_haproxy. Как можно ожидать, эти переменные применяются ко всем машинам в вашем инвентаре:
--- httpd_port: 80 ntpserver: 192.0.2.23
Это файл YAML, и вы можете создавать списки и словари для более сложных структур переменных. В данном случае мы просто задаём две переменные: одну для порта веб-сервера и одну для NTP-сервера, который должны использовать наши машины для синхронизации времени.
Вот ещё один файл переменных групп. Это group_vars/dbservers, который применяется к хостам в группе dbservers:
--- mysqlservice: mysqld mysql_port: 3306 dbuser: root dbname: foodb upassword: usersecret
Если вы посмотрите на пример, есть переменные групп для группы webservers и группы lbservers, аналогичным образом.
Эти переменные используются в различных местах. Вы можете использовать их в скриптах, как в этом примере roles/db/tasks/main.yml:
- name: Create Application Database
mysql_db: name={{ dbname }} state=present
- name: Create Application DB User
mysql_user: name={{ dbuser }} password={{ upassword }}
priv=*.*:ALL host='%' state=present
Вы также можете использовать эти переменные в шаблонах, как в этом примере roles/common/templates/ntp.conf.j2:
driftfile /var/lib/ntp/drift
restrict 127.0.0.1
restrict -6 ::1
server {{ ntpserver }}
includefile /etc/ntp/crypto/pw
keys /etc/ntp/keys
Вы можете видеть, что синтаксис подстановки переменных {{ и }} одинаков как для шаблонов, так и для переменных. Синтаксис внутри фигурных скобок — Jinja2, и вы можете выполнять всевозможные операции и применять различные фильтры к данным внутри. В шаблонах вы также можете использовать циклы for и условные операторы if для обработки более сложных ситуаций, как в этом примере roles/common/templates/iptables.j2:
{% if inventory_hostname in groups['dbservers'] %}
-A INPUT -p tcp --dport 3306 -j ACCEPT
{% endif %}
Это проверяет, существует ли имя инвентаря машины, на которой мы в настоящее время работаем (inventory_hostname), в группе инвентаря dbservers. Если да, то эта машина получит строку iptables ACCEPT для порта 3306.
Вот ещё один пример из того же шаблона:
{% for host in groups['monitoring'] %}
-A INPUT -p tcp -s {{ hostvars[host].ansible_default_ipv4.address }} --dport 5666 -j ACCEPT
{% endfor %}
Этот цикл перебирает все хосты в группе под названием monitoring, и добавляет строку ACCEPT для каждого хоста мониторинга с его стандартным IPv4 адресом в конфигурацию iptables текущей машины, чтобы Nagios мог мониторить эти хосты.
Вы можете узнать больше о Jinja2 и его возможностях здесь, а более подробную информацию о переменных Ansible можно найти в разделе Переменные.
Поэтапное обновление
Теперь у вас есть полностью развёрнутый сайт с веб-серверами, балансировщиком нагрузки и мониторингом. Как его обновить? Вот где вступают в игру функции оркестрации Ansible. В то время как некоторые приложения используют термин «оркестрация» для обозначения базового упорядочения или командного удаления, Ansible рассматривает оркестрацию как «ведение машин, как оркестра» и имеет довольно сложный для этого движок.
Ansible способен выполнять операции на многоуровневых приложениях в координированном порядке, что упрощает оркестрацию сложного поэтапного обновления веб-приложения без простоев. Это реализовано в отдельном скрипте под названием rolling_update.yml.
Посмотрев на скрипт, вы можете увидеть, что он состоит из двух задач. Первая задача очень проста и выглядит так:
- hosts: monitoring tasks: []
Что здесь происходит и почему нет задач? Возможно, вы знаете, что Ansible собирает «факты» с серверов перед их использованием. Эти факты полезны для многих вещей: информация о сети, версии ОС/дистрибутива и т.д. В нашем случае, нам нужна информация обо всех серверах мониторинга в нашей среде перед выполнением обновления, поэтому эта простая задача выполняет сбор фактов на серверах мониторинга. Вы иногда увидите этот шаблон, и это полезный приём.
Следующая часть — это задача обновления. Первая часть выглядит так:
- hosts: webservers user: root serial: 1
Это просто обычное определение задачи, работающей с группой webservers. Ключевое слово serial указывает Ansible, сколько серверов обрабатывать одновременно. Если оно не указано, Ansible будет параллелизировать эти операции до предела «вилок» по умолчанию, заданного в файле конфигурации. Но для поэтапного обновления без простоев вы можете не захотеть обрабатывать так много хостов одновременно. Если у вас всего несколько веб-серверов, вы можете установить serial в значение 1, для обработки по одному хосту за раз. Если у вас 100, то, возможно, вы установите serial в значение 10, для обработки по 10 хостов за раз.
Вот следующая часть задачи обновления:
pre_tasks:
- name: disable nagios alerts for this host webserver service
nagios: action=disable_alerts host={{ inventory_hostname }} services=webserver
delegate_to: "{{ item }}"
with_items: "{{ groups.monitoring }}"
- name: disable the server in haproxy
shell: echo "disable server myapplb/{{ inventory_hostname }}" | socat stdio /var/lib/haproxy/stats
delegate_to: "{{ item }}"
with_items: "{{ groups.lbservers }}"
Примечание
- Ключевое слово
serialзаставляет задачу выполняться в «пакетах». Каждый пакет считается полной задачей с подвыборкой хостов. Это имеет некоторые последствия для поведения задачи. Например, если все хосты в пакете завершатся неудачно, задача завершится неудачей, что, в свою очередь, приведёт к завершению всего запуска неудачно. Вы должны учесть это при использовании сmax_fail_percentage.
Ключевое слово pre_tasks позволяет вам перечислить задачи, которые нужно выполнить до вызова ролей. Это станет понятнее через минуту. Если вы посмотрите на имена этих задач, вы увидите, что мы отключаем оповещения Nagios и затем удаляем веб-сервер, который мы в настоящее время обновляем, из пула балансировки HAProxy.
Аргументы delegate_to и with_items вместе заставляют Ansible пройтись по каждому серверу мониторинга и балансировщику нагрузки и выполнить эту операцию (делегировать эту операцию) на сервере мониторинга или балансировки нагрузки «от имени» веб-сервера. В программировании внешним циклом является список веб-серверов, а внутренним циклом — список серверов мониторинга.
Обратите внимание, что шаг HAProxy выглядит немного сложным. В этом примере мы используем HAProxy, так как он свободно доступен, но если у вас (например) F5 или Netscaler в вашей инфраструктуре (или, возможно, у вас есть настроенный AWS Elastic IP?), вы можете использовать модули, включенные в основной Ansible, для связи с ними вместо этого. Возможно, вы захотите использовать другие модули мониторинга вместо nagios, но это просто показывает основную цель раздела «предварительных задач» — вывести сервер из мониторинга и из вращения.
Следующий шаг просто повторно применяет соответствующие роли к веб-серверам. Это вызовет применение всех объявлений управления конфигурацией в ролей web и base-apache, включая обновление кода самого веб-приложения. Нам не обязательно делать это таким образом — мы могли бы просто обновить веб-приложение, но это хороший пример того, как роли могут использоваться для повторного использования задач:
roles: - common - base-apache - web
Наконец, в разделе post_tasks мы отменяем изменения в конфигурации Nagios и возвращаем веб-сервер в пул балансировки:
post_tasks:
- name: Enable the server in haproxy
shell: echo "enable server myapplb/{{ inventory_hostname }}" | socat stdio /var/lib/haproxy/stats
delegate_to: "{{ item }}"
with_items: "{{ groups.lbservers }}"
- name: re-enable nagios alerts
nagios: action=enable_alerts host={{ inventory_hostname }} services=webserver
delegate_to: "{{ item }}"
with_items: "{{ groups.monitoring }}"
Опять же, если вы использовали Netscaler или F5 или Elastic Load Balancer, вы просто подставите соответствующие модули вместо этого.
Управление другими балансировщиками нагрузки
В этом примере мы используем простой балансировщик нагрузки HAProxy для работы с веб-серверами. Он легко настраивается и управляется. Как мы упоминали, Ansible имеет встроенную поддержку различных других балансировщиков нагрузки, таких как Citrix NetScaler, F5 BigIP, Amazon Elastic Load Balancers и многое другое. Более подробную информацию см. в документации О модулях.
Для других балансировщиков нагрузки, возможно, потребуется отправлять им командные строки оболочки (как мы делаем для HAProxy выше) или вызывать API, если ваш балансировщик нагрузки предоставляет его. Для балансировщиков нагрузки, для которых Ansible имеет модули, вы можете запускать их как local_action если они обращаются к API. Вы можете узнать больше о локальных действиях в разделе Делегирование, отказоустойчивые обновления и локальные действия. Если вы разработаете что-то интересное для какого-либо оборудования, где нет основного модуля, это может стать хорошим модулем для основного включения!
Непрерывная доставка по всем этапам
Теперь, когда у вас есть автоматизированный способ развертывания обновлений для вашего приложения, как вы всё это свяжете вместе? Многие организации используют инструмент непрерывной интеграции, такой как Jenkins или Atlassian Bamboo, для связывания этапов разработки, тестирования, выпуска и развертывания. Также вы можете использовать инструмент, такой как Gerrit, чтобы добавить шаг проверки кода к коммитам как в сам код приложения, так и в ваши Ansible-сценарии, или и в то, и в другое.
В зависимости от вашей среды, вы можете непрерывно развертывать в тестовую среду, запускать набор интеграционных тестов в этой среде и затем автоматически развертывать в производственную. Или вы можете упростить процесс и просто использовать отказоустойчивое обновление для по запросу развертывания в тестовую или производственную среду. Всё зависит от вас.
Для интеграции с системами непрерывной интеграции вы можете легко запускать выполнение сценариев с помощью инструмента командной строки ansible-playbook, или, если вы используете Ansible Tower, инструмент tower-cli или встроенный API REST. (Команда tower-cli «joblaunch» запустит удаленную задачу через API REST и довольно удобна).
Это должно дать вам хорошее представление о том, как структурировать многоуровневое приложение с помощью Ansible и оркестрировать операции над этим приложением с конечной целью непрерывной доставки вашим клиентам. Вы можете расширить идею отказоустойчивого обновления на многие части приложения; например, добавить веб-серверы front-end вместе с серверами приложений или заменить базу данных SQL чем-то вроде MongoDB или Riak. Ansible предоставляет вам возможность легко управлять сложными средами и автоматизировать общие операции.
См. также
- lamp_haproxy пример
- Здесь обсуждается пример lamp_haproxy.
- Сценарии
- Введение в сценарии
- Роли
- Введение в роли сценариев
- Переменные
- Введение в переменные Ansible
- Ansible.com: Непрерывная доставка
- Введение в непрерывную доставку с Ansible
© 2012–2018 Michael DeHaan
© 2018–2019 Red Hat, Inc.
Licensed under the GNU General Public License version 3.
https://docs.ansible.com/ansible/2.4/guide_rolling_upgrade.html