За мной, за мной, читатель, и я проведу тебя в чарующий мир автоматизации разворачивания окружения на серверах под управлением Linux семейства RHEL.
Один из наших java-проектов вырос, стал совсем взрослым и сейчас занимает 4 контура:
Dev — контур для команды разработки,
Qa — контур для команды тестирования,
Stage — контур для демонстрации новых фич заказчику,
Production — боевой контур.
Каждый контур содержит два одинаковых сервера с идентичным набором компонентов окружения для нашего приложения:
linux Oracle — операционная система,
jdk — комплект приложений Java,
haproxy — proxy сервер,
nginx — веб-сервер для отдачи статики,
mysql — субд.
Перед командой эксплуатации встал резонный вопрос: как настроить управление окружением на восьми серверах и сохранить оптимистичное отношение к жизни.
После краткого сравнения систем управления конфигурациями был выбран Ansible. В его пользу сыграли простота, гибкость и отсутствие агентов на управляемых серверах.

Теперь немного расскажем об архитектуре ролей Ansible для проекта.
Для установки и первоначальной настройки каждого компонента окружения написана отдельная роль. Последующая кастомизация настроек каждого компонента окружения также выделена в дополнительную роль. Это позволило нам избежать дублирования общих стандартных ролей для остальных проектов.
Playbook c последовательностью выполняемых ролей для подключения новой ноды в контур выглядит так:
- hosts: - project_group user: username become: yes gather_facts: true roles: - rhel_install_new_server - rhel_install_java - rhel_install_haproxy - rhel_install_nginx - rhel_install_mysql
А теперь расскажем о каждой роли поподробнее.
Роль rhel_install_new_server
Выполняет общую настройку операционной системы и установку системных утилит.
Файл roles/rhel_install_new_server/tasks/main.yml
--- # tasks file for rhel_install_new_server # Проверяем и устанавливаем системные обновления: - name: yum update yum: name: "*" state: latest update_cache: yes # Подключаем EPEL repository: - name: install EPEL repository yum: name: https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm state: present update_cache: yes # Устанавливаем стандартные утилиты. Список пакетов задан в переменной tools_packages: - name: install tools yum: name: '{{ item.name }}' state: present update_cache: yes with_items: '{{ tools_packages }}' # Выставляем автозагрузку для сетевого интерфейса по умолчанию: - name: set autoload for default interface lineinfile: path: "/etc/sysconfig/network-scripts/ifcfg-{{ansible_default_ipv4.interface}}" regexp: '^ONBOOT=' line: 'ONBOOT="yes"' # Отключаем IPv6 для интерфейса по умолчанию: - name: disable IPv6 for eth0 replace: path: "/etc/sysconfig/network-scripts/ifcfg-{{ansible_default_ipv4.interface}}" regexp: '^(IPV6.*=).*$' replace: '\1"no"' # Отключаем IPv6: - name: disable IPv6 blockinfile: path: /etc/sysctl.d/disableipv6.conf create: yes marker: no block: | net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 # Отключаем SELinux: - name: disable SELinux selinux: state: disabled # Отключаем локальный firewall: - name: disable firewall systemd: name: firewalld enabled: no state: stopped ignore_errors: yes # Выставляем часовой пояс. Временная зона задана в переменной timezone: - name: set timezone to UTC timezone: name: '{{ timezone }}' # Выставляем синхронизацию времени в крон: - name: set synchronize time in cron cron: name: "set synchronize time by ansible" minute: 1 job: "/usr/sbin/ntpdate -u pool.ntp.org >/dev/null 2>&1" # Чистим старые ядра: - name: Remove old kernels shell: "rpm -q kernel | grep -v `uname -r` | grep -v `/sbin/grubby --default-kernel | sed -r 's#^/boot/vmlinuz-##'` | xargs rpm -e || true"
--- # vars file for rhel_install_new_server tools_packages: - name: vim - name: mc - name: less - name: sysstat - name: iotop - name: strace - name: traceroute - name: screen - name: rsync - name: curl - name: python - name: wget - name: zlib - name: unzip - name: bind-utils - name: ntp - name: ntpdate - name: telnet - name: nmap - name: tcpdump - name: logrotate - name: net-tools - name: bash-completion - name: yum-utils - name: mtr timezone: UTC
Роль rhel_install_java — установка Java
На нашем проекте используется пакет jdk версии 8u60. Дополнительно мы заранее скачали и положили в папку roles/rhel_install_java/files/ файлы JCE: US_export_policy.jar и local_policy.jar.
--- # vars file for roles/rhel_install_java java_dst_path: "/opt/dst/java" download_url: "http://download.oracle.com/otn/java/jdk/8u60-b27/jdk-8u60-linux-x64.rpm" java_cookie: "Cookie:' gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie'"
Файл roles/rhel_install_java/tasks/main.yml
--- # tasks file for roles/rhel_install_java # Создадим папку для дистрибутивов: - name: java | create java dst directory file: path: "{{ java_dst_path }}" state: directory recurse: yes owner: root group: root mode: 0755 # Скачиваем дистрибутив jdk: - name: java | download java get_url: url: "{{ download_url }}" dest: "{{ java_dst_path }}" tmp_dest: "{{ java_dst_path }}" headers: "{{ java_cookie }}" validate_certs: no owner: root group: root mode: 0744 force: yes ignore_errors: True # Находим скачанный jdk в папке дистрибутивов и заносим имя файла в переменную: - name: java | register java rpm find: paths: "{{ java_dst_path }}" patterns: "*.rpm" register: java_rpm # Проверим значение переменной: - debug: msg={{ java_rpm.files.0.path }} # Установим скачанный пакет: - name: java | install java rpm yum: name: "{{ java_rpm.files.0.path }}" state: present # Копируем Java Cryptography Extension: - name: java | copy JCE copy: src: files/{{ item }} dest: /usr/java/default/jre/lib/security/ owner: root group: root mode: 0644 backup: yes with_items: - US_export_policy.jar - local_policy.jar
Роль rhel_install_haproxy — установка haproxy
Файл roles/rhel_install_haproxy/tasks/main.yml
--- # tasks file for install_haproxy # Устанавливаем последнюю версию haproxy: - name: install the latest version of haproxy yum: name: haproxy state: latest # делаем бэкап rsyslog.conf: - name: backup rsyslog.conf copy: src: /etc/rsyslog.conf dest: /etc/rsyslog.conf_orig force: no remote_src: true # Включаем udp в rsyslog: - name: format rsyslog | set UDP options blockinfile: path: /etc/rsyslog.conf block: | $ModLoad imudp $UDPServerAddress 127.0.0.1 $UDPServerRun 514 state: present insertafter: '^#\$UDPServerRun.*$' notify: - restart rsyslog # создаём файл конфигурации haproxy для rsyslog: - name: create rsyslog for haproxy blockinfile: path: /etc/rsyslog.d/haproxy.conf content: | if $programname == 'haproxy' and $syslogseverity <= '4' then /var/log/haproxy/haproxy.out if $programname == 'haproxy' and $syslogseverity > '4' then /var/log/haproxy/haproxy.log & stop state: present create: yes notify: - restart rsyslog # делаем бэкап файла конфигурации logrotate: - name: backup logrotate file haproxy copy: src: /etc/logrotate.d/haproxy dest: /etc/logrotate.d/haproxy_orig force: no remote_src: true # Копируем свой файл конфигурации logrotate для haproxy: - name: copy logrotate file for haproxy copy: src: files/logrotate_haproxy dest: /etc/logrotate.d/haproxy force: yes # добавляем в автозагрузку и запускаем сервис: - name: enable and start haproxy systemd: name: haproxy daemon_reload: yes enabled: yes state: started
Файл rhel_install_haproxy/handlers/main.yml
--- # handlers file for install_haproxy - name: restart rsyslog systemd: name: rsyslog state: restarted - name: reload haproxy systemd: haproxy: name state: reloaded
Роль rhel_install_nginx — установка nginx
Файл rhel_install_nginx/tasks/main.yml
--- # tasks file for roles/rhel_install_nginx # Подключаем официальный репозиторий nginx: - name: add nginx repo yum_repository: name: nginx description: nginx official repo state: present baseurl: "http://nginx.org/packages/rhel/{{ansible_distribution_major_version}}/{{ansible_userspace_architecture}}/" gpgkey: http://nginx.org/keys/nginx_signing.key gpgcheck: yes enabled: yes # Устанавливаем nginx: - name: install nginx yum: name: nginx state: present update_cache: yes # Создаём системные папки: - name: create folders file: path: '/etc/nginx/{{ item }}' state: directory mode: 0755 with_items: - conf-available - sites-available - sites-enabled # Подключаем sites-enabled в конфиг nginx: - name: enable sites-enabled dir lineinfile: dest: /etc/nginx/nginx.conf state: present insertafter: "(.*)include /etc/nginx/(.*)" line: " include /etc/nginx/sites-enabled/*.conf;" # Определяем существует ли default.conf: - name: stat /etc/nginx/conf.d/default.conf stat: path=/etc/nginx/conf.d/default.conf register: defaultconf_stat # Если существует, перемещаем его: - name: Move default.conf command: mv /etc/nginx/conf.d/default.conf /etc/nginx/sites-available/default.conf when: defaultconf_stat.stat.exists # Добавляем в автозагрузку и стартуем вебсервер: - name: enable and started nginx systemd: name: nginx.service enabled: yes state: started
Роль rhel_install_mysql — установка mysql
Здесь пришлось помучиться с первичной авторизацией в mysql.
Файл roles/rhel_install_mysql/vars/main.yml
--- # vars file for roles/rhel_install_mysql mysql_repo_rpm: https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm mysql_packets: - mysql-community-server - mysql-community-common - MySQL-python mysql_log: /var/log/mysqld.log
Файл roles/rhel_install_mysql/handlers/main.yml
--- # handlers file for roles/rhel_install_percona_mysql - name: restarted mysql systemd: name: mysqld state: restarted
Файл roles/rhel_install_mysql/templates/root.cnf.j2
[client] user=root password={{ mysql_root_password }}
Файл roles/rhel_install_mysql/tasks/main.yml
-- - # tasks file for roles/rhel_install_mysql # Устанавливаем репозиторий yum: - name: install mysql repo yum: name: "{{ mysql_repo_rpm }}" state: installed # Устанавливаем пакеты mysql: - name: install mysql yum: name: "{{ mysql_packets }}" state: installed # Добавляем mysql в автозагрузку и запускаем его: - name: enable mysql systemd: daemon_reload: yes name: mysqld.service enabled: yes state: started # Парсим лог файл mysql и получаем из него пароль рута, назначенный при установке и который нам необходимо сменить. Заносим пароль из файла лога в переменную mysql_root_temp_password. - name: take default root password from mysql log file shell: > awk -F': ' '$0 ~ "temporary password"{print $2}' {{ mysql_log }} | tail -1 register: mysql_root_temp_password # Проверяем, что мы действительно получили временный пароль: - name: check mysql_root_temp_password debug: msg={{ mysql_root_temp_password.stdout }} when: mysql_root_temp_password is defined # Назначаем временный пароль как действительный пароль для рута: - name: Set temp root pass as root password set_fact: mysql_root_password: "{{ mysql_root_temp_password.stdout }}" when: mysql_root_temp_password is defined # Копируем шаблон с новым паролем рута: - name: Copy the root credentials as .my.cnf file template: src: root.cnf.j2 dest: "~/.my.cnf" mode: 0600 # Проверяем, валиден ли текущий пароль рута: - name: register password expire shell: mysql --defaults-file=~/.my.cnf -e "SELECT NOW();" register: password_expired ignore_errors: True - name: check password_expired debug: msg={{ password_expired.stdout }} when: password_expired is defined # Наконец-то устанавливаем постоянный пароль для рута на localhost: - name: ALTER USER root@localhost shell: mysql --defaults-file=~/.my.cnf --connect-expired-password -e "ALTER USER root@localhost IDENTIFIED BY '{{ mysql_root_password }}';" when: password_expired.stdout.find("expired") != -1 # Устанавливаем постоянный пароль для остальных учётных записей рута: - name: Update MySQL root password for all root accounts mysql_user: name: root host: "{{ item }}" password: "{{ mysql_root_password }}" state: present priv: "*.*:ALL,GRANT" with_items: - "{{ ansible_hostname }}" - 127.0.0.1 - ::1 - localhost when: mysql_root_temp_password is defined # Удалим анонимного пользователя: - name: Ensure Anonymous user(s) are not in the database mysql_user: name: '' host: "{{ item }}" state: absent with_items: - localhost - "{{ ansible_hostname }}" # Удалим тестовую базу данных: - name: Remove the test database mysql_db: name: test state: absent
На данном этапе мы получили «чистое» окружение, настройки которого необходимо кастомизировать под наш проект. Сделаем это с помощью дополнительной роли project_configuration.
Роль project_configuration — кастомизация окружения
Перед выполнением роли проверим, что все необходимые компоненты установлены в системе.
Файл roles/project_configuration/tasks/main.yml
######### check java - name: register installed java shell: java -version args: warn: no register: java_present failed_when: java_present.rc > 1 changed_when: no tags: check_installed - debug: msg: "{{ java_present.rc }}" tags: check_installed - fail: msg="Please install java first" when: java_present.rc == 1 tags: check_installed ######### check haproxy - name: register installed haproxy shell: rpm -q haproxy args: warn: no register: haproxy_present failed_when: haproxy_present.rc > 1 changed_when: no tags: check_installed - debug: msg: "{{ haproxy_present.rc }}" tags: check_installed - fail: msg="Please install haproxy first" when: haproxy_present.rc == 1 tags: check_installed - include: tasks/project_haproxy.yml when: haproxy_present.rc == 0 tags: check_installed ######### check nginx - name: register installed nginx shell: rpm -q nginx args: warn: no register: nginx_present failed_when: nginx_present.rc > 1 changed_when: no tags: check_installed - debug: msg: "{{ nginx_present.rc }}" tags: check_installed - fail: msg="Please install nginx first" when: nginx_present.rc == 1 tags: check_installed - include: tasks/project_nginx.yml when: nginx_present.rc == 0 tags: check_installed ######### check mysql - name: register installed mysql shell: rpm -q mysql57-community-release args: warn: no register: mysql_present failed_when: mysql_present.rc > 1 changed_when: no tags: check_installed - debug: msg: "{{ mysql_present.rc }}" tags: check_installed - fail: msg="Please install mysql first" when: mysql_present.rc == 1 tags: check_installed - include: tasks/project_mysql.yml when: mysql_present.rc == 0 tags: check_installed
При успешном выполнении проверок подключаются файлы:
tasks/project_haproxy.yml
tasks/project_nginx.yml
tasks/project_mysql.yml
В них мы прописываем установку конфигурационных файлов с помощью шаблонов, установку ssl сертификатов, заведение необходимых системных пользователей, создание баз данных и т.д.
Как результат: после всех манипуляций мы получили рабочее окружение для нашего приложения. Теперь можно приступать к процедуре деплоймента приложения, но это уже совсем другая история, которой мы поделимся в следующей статье.

