За мной, за мной, читатель, и я проведу тебя в чарующий мир автоматизации разворачивания окружения на серверах под управлением 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 сертификатов, заведение необходимых системных пользователей, создание баз данных и т.д.
Как результат: после всех манипуляций мы получили рабочее окружение для нашего приложения. Теперь можно приступать к процедуре деплоймента приложения, но это уже совсем другая история, которой мы поделимся в следующей статье.
Комментарии (14)
ALexhha
09.02.2018 15:24Я так и не понял, что хотели сказать статьей — «Смотрите мы умеем использовать ansible»? Или я просто не заметил скрытый смысл? Так как кроме банальных тасков/ролей не увидил ничего интересного, может просто не туда смотрел
hamnsk
09.02.2018 20:17а что вы хотели увидеть? ну так для примера?
ALexhha
09.02.2018 20:55Какие то уникальные «трудности» и пути их решения, а в итоге получаем стандартную установку nginx/haproxy/mysql.
Просто не понятно — для кого адресована статья. Если для новичков, то они все равно ничего не поймут, так как нет никакой вводной по ansible. Ну а более опытным пользователем она будет не интересна, имхо
scor2k
09.02.2018 17:39А почему не используете docker? В вашем случае настройка сервера свелась бы к установке docker, docker-compose, ctop :) и пару ролей на деплой продукта…
hamnsk
09.02.2018 20:17тогда уже лучше kubernetes, считаю что необходимо писать по с заделкой на контейнеризацию его и возможность скейлиться.
Думаю этот плейбук используется для раскатки софта разработчиком, чтобы посмотреть как оно все получилось. Либо в очень маленьком проектеALexhha
09.02.2018 20:58тогда уже лучше kubernetes
очень спорное утверждение. Порог вхождения у кубернетиса намного больше. Да и чем вы собрались управлять собственно
scor2k
12.02.2018 06:40Kubernetes для двух хостов — сильно избыточно. Если хватает обычных плейбуков — то кластер тут точно не нужен.
eastbanctech Автор
12.02.2018 06:47Спасибо за комментарий. Да, мы собираемся переходить на kubernetes.
relgames
11.02.2018 21:07reload haproxy
Это сработает только для frontend записей. Сами столкнулись с этой проблемой. Нужно делать restart.
0x12ee705
плохая практика даже в тестовом окружении, т.к со временем формируется привычка и отключаешь в Prod
navion
Это будет справедливо, когда разработчики начнут массово писать правила для SELinux.
Сейчас же из всех серверов приложений они есть лишь для WAS, а сам Red Hat для JBoss их написать не удосужился.
0x12ee705
пример: накатываете случайно роль на prod, в итоге получаете дырявое решето смотрящее наружу, Вас ломают, а Вы, а мы не виноваты это разработчики во всем виноваты.
navion
Дырявое решето — это если не ставить патчи, а так лишь отключена дополнительная фича безопасности.