Установка
Для Ubuntu 24.04
# apt -y update && apt -y install ansible sshpass
Для Centos 7
# yum install epel-release && yum install ansible
Установка через PIP. Тут файл конфигурации не создается по умолчанию.
# pip install ansible && ansible --version | grep file
Инвентори
Для будущего Gitlab CI/CD pipeline будет 4 клиента: Build, Test, Stage, Prod. Главное, чтобы был их IP-адрес и можно к ним подключиться по SSH используя user/password или используя ssh-key. Рекомендую использовать ssh-key. Это более универсально, надёжно и более удобно. Для подключения к серверам-клиентам по закрытому ключу необходимо создать пару ssh-ключей на ansible-мастере и открытый ключ поместить в ~/.ssh/autorized_keys на серверах-клиентах:
$ ssh-keygen
Cделаем директорию
$ mkdir ansible
Первое что нам нужно сделать это создать файл inventory - текстовый файл со списком серверов, к которым мы можем подключиться и с некоторыми параметрами, таких как username/pass и IP адрес, FQDN, ключ и другие разные вещи. Все сервера можно записывать без группы, например, если они ни входят ни в одну, перечислением с новой строки IP-адресов или FQDN. Эти сервера будут входить в группу ungrouped. По умолчанию все сервера входят минимум в две группы. Это "all" и та, которую указали в файле inventory, иначе "ungrouped". В примере ниже будут указаны некоторые vars. Но "best practice" является чистый inventory только с группами и IP-адресами.
$ nano inventory.txt
#[group]
#alias ansible_host=<IP-address> ansible_user=<user> ansible_password=<pass>
1st-build ansible_host=<IP-address> ansible_user=<user> ansible_password=<pass>
[Test]
<IP-address1>
<IP-address2>
[Stage]
3rd ansible_host=<IP-address> ansible_user=<user> ansible_password=<pass>
[Prod]
4th ansible_host=<IP-address>
#Группа с определенными группами, можно создавать "матрешки"
[Release:children]
Stage
Prod
#Указание переменных для группы
[prod:vars]
ansible_port=22
ansible_user=user
ansible_password=<pass>
ansible_connection=ssh
Чтобы запустить этот inventory файл, где -i это указание файла инвентори, а -m это модуль который будет запускаться на удаленных серверах:
$ ansible -i inventory.txt all -m ping
Когда первый раз подключайтесь к любому linux, то просит fingerprint каждого сервера. Чтобы избежать это:
$ nano ansible.cfg
[defaults]
host_key_checking = false #отключение проверки fingerprint
inventory = ./inventory.txt #отключение обязательного указания файла inventory
И теперь когда есть файл конфигурации, то указывать inventory при вызове модулей не обязательно:
$ ansible all -m ping
Команда, которая показывает inventory со всеми группами и переменными серверов в виде дерева:
$ ansible-inventory --list
Vars
Как говорилось ранее, обозначение переменных в inventory не является хорошим тоном, поэтому в больших проектах в корне проекта ansible создается директория group_vars, а внутри создаются файлы с названиями групп, указанных в файле inventory, то есть в нашем случае это prod, в который мы перенесем переменные из inventory:
$ nano ~/ansible/group_vars/prod
Все файлы переменных начинаются с "---", как в файлах формата YAML и вместо "=", как в inventory, ставятся ":"
---
ansible_port:22
ansible_user:user
ansible_password:pass
ansible_connection:ssh
Playbooks
Playbook - это скрипт команд.
$ nano pb.yml
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
--- #YAML начинается с этого, никаких "Tab"-ов в файле
- name: Playbook with loops #Просто имя
hosts: build #На всех хостах или группах через пробел
become: yes #С правами sudo
vars: #Какие то переменные, только для ЭТОГО плейбука
source_file: ./files/index.html
destin_file: ./destination/
source_folder: ./files
destin_folder: ./destination
template_folder:
secret: Supersecret
msg1: one
msg2: two
# roles:
# - deploy_openvpn_client
tasks:
- name: Ping hosts #Имя задачи, писать не обязательно
ping: #Вызываем модуль
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Print #Выводит переменную
debug: var=secret
- debug: #Имя указывать не обязательно
var: secret #Можно выводить вот так
- debug: msg="Slovo{{secret}}" #Выводит переменную в строке
- set_fact: full_message="{{msg1}}{{msg2}}" #Сохраняет знач. двух переменных
- debug: var=full_message
- debug: var=ansible_distribution #Показывает переменную из модуля setup
- shell: uptime #Модуль выполнения shell-команд
register: results #Сохраняем вывод в переменную
- debug: var=results #И вывод сохраненной переменной
- name: Check nix version #Определение установленной ОС на клиенте
debug: var=ansible_os_family #Сразу вывод переменной, не через сохранение
- debug: var=results.stdout #И вывод только stdout из сохраненной переменной
- name: Say Hello in Loop #Вывод сообщения по списку
debug: msg="Hello {{ item }}"
loop: #Используется если ansible>=2.5, если меньше то with_items
- "ca.crt"
- "ca.key"
- "ta.key"
- "make_config.sh"
- "base.conf"
- "ca.srl"
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Install by APT #Модуль пакетного менеджера apt
apt: name=stress state=latest
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Loop until example #Цикл ПОКА-НЕ
shell: echo -n Z >> myfile.txt && cat myfile.txt #Модуль выполнения shell-команд
register: output #
delay: 2 #Задержка в секундах
retries: 10 #Если не указать сколько retries, то всего три
until: output.stdout.find("ZZZZ") == false #Делать пока в выводе нет ZZZZ
- debug: var=output.stdout #Вывод этого цикла
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Install in loop&conditions #Установка пакетов по списку
apt: name={{item}} state=latest #Модуль пакетного менеджера apt
with_items:
- tree
- htop
- block: #Условие block-when ЕСЛИ ОС-клиента RedHat
- name: RedHat
yum: name=httpd state=latest #ТО установка Апач
- name: Service start enabled
service: name=httpd state=started enabled=yes #Включить сервис и поставить а автозагрузку
when: ansible_os_family == "RedHat" #Оператор условия
- block: #Условие block-when ЕСЛИ ОС-клиента НЕ RedHat
- name: Debian
apt: name=nginx state=latest #ТО установка NGINX
- name: Service start enabled
service: name=nginx state=started enabled=yes
when: ansible_os_family != "RedHat"
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Generate template.html #Генерация файла из шаблона
template: src={{template_folder}}/template.j2 dest={{destin_folder}}/template.html mode=0555
notify: #Вызов handler если были изменения
- Restart RH Apache
- Restart D Apache
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Copy in loop #Модуль копирования по списку
#copy: src={{source_file}} dest={{destin_file}} mode=0555 owner=user group=usergroup
#ИЛИ
#copy: src={{source_folder}}/{{item}} dest={{destin_folder}}/{{item}} mode=0555
#ИЛИ ВООБЩЕ
copy: src={{item}} dest={{destin_file}} mode=0555
#with_items:
# - "1.txt"
# - "2.txt"
# - "3.txt"
with_fileglob: "{{source_folder}}/*.*" #Скопировать все без перечисления. Делается без with_items
notify: #Вызов handler если были изменения
- Restart RH Apache
- Restart D Apache
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
handlers: #Выполнять если были изменения после прогона плейбука
- name: Restart RH Apache
service: name=httpd state=restarted
when: ansible_os_family == "RedHat" #Handler вызовется при выполнении условия
- name: Restart D Apache
service: name=nginx state=restarted
when: ansible_os_family != "RedHat" #Handler вызовется при выполнении условия
Шаблоны используются в том случае, если для разных серверов-клиентов необходимы одни и те же файлы, но с индивидуальными отличиями. Файлы шаблонов создаются в формате Jinja с указанием необходимых переменных. Ниже указан пример шаблона для генерации файла template.html:
$ mkdir ~/ansible/templates && nano template.j2
<HTML>
<HEAD>
<TITLE>TEMPLATE</TITLE>
<SCRIPT LANGUAGE="JavaScript">
var sizes = new Array(0,1,2,4,8,10,12);
sizes.pos = 0;
function Elastic()
{var el = document.all.Elastic
if (null==el.direction)el.direction=1
else if (( sizes.pos > sizes.length -2) || (0 == sizes.pos))
el.direction *= -1
el.style.letterSpacing = sizes[sizes.pos += el.direction]
setTimeout('Elastic()',100)
}
</SCRIPT>
<BODY bgcolor="black" onLoad=Elastic()>
<CENTER>
<br><br><br><br>
<br><br><br><br>
<font color="gold">TEMPLATE<br>
<font color="green"><h2>STRING</h2>
<font color="white"><h1 ID="elastic" ALIGN="Center">STRING</h1>
Server Host Name: {{ansible_hostname}}<br>
Server OS Family is: {{ansible_os_family}}<br>
IP Address of this Server is: {{ansible_default_ipv4.address}}<br>
</BODY>
</HTML>
Роли
Для создания ролей необходимо создать директорию roles. Роль инициализируется командой ansible-galaxy init <name>. Создается директория с названием роли и деревом пустых файлов роли. Смысл роли в том, что это тот же самый плейбук, только разбросанный по соответствующим папкам, если проектов много. А из главного плейбука вызывается только необходимая роль.
$ mkdir roles && cd roles && ansible-galaxy init deploy_website_nginx && tree
user@amsiblemaster:~/ansible/roles$ tree
.
└── deploy_website_nginx
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
После переноса файлов "проекта" и шаблонов дерево должно будет выгляжеть примерно так:
.
├── ansible.cfg
├── group_vars
│ ├── build
│ ├── prod
│ ├── stage
│ └── test
├── inventory.txt
├── pb.yml
└── roles
└── deploy_website_nginx
├── defaults
│ └── main.yml
├── files
│ ├── 1.txt
│ ├── 2.txt
│ ├── 3.txt
│ ├── index.html
│ └── zaglushka.html
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
│ └── template.j2
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Файлы:
defaults/main.yml
---
# defaults file for deploy_website_nginx
destin_folder: ./destination
handlers/main.yml
---
# handlers file for deploy_website_nginx
- name: Restart RH Apache
service: name=httpd state=restarted
when: ansible_os_family == "RedHat"
- name: Restart D Apache
service: name=nginx state=restarted
when: ansible_os_family != "RedHat"
tasks/main.yml
---
- name: Ping hosts #Имя задачи, писать не обязательно
ping: #Вызываем модуль
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Print #Выводит переменную
debug: var=secret
- debug: #Имя указывать не обязательно
var: secret #Можно выводить вот так
- debug: msg="Slovo{{secret}}" #Выводит переменную в строке
- set_fact: full_message="{{msg1}}{{msg2}}" #Сохраняет знач. двух переменных
- debug: var=full_message
- debug: var=ansible_distribution #Показывает переменную из модуля setup
- shell: uptime #Модуль выполнения shell-команд
register: results #Сохраняем вывод в переменную
- debug: var=results #И вывод сохраненной переменной
- name: Check nix version #Определение установленной ОС на клиенте
debug: var=ansible_os_family #Сразу вывод переменной, не через сохранение
- debug: var=results.stdout #И вывод только stdout из сохраненной переменной
- name: Say Hello in Loop #Вывод сообщения по списку
debug: msg="Hello {{item}}"
loop: #Используется если ansible>=2.5, если меньше то with_items
- "ca.crt"
- "ca.key"
- "ta.key"
- "make_config.sh"
- "base.conf"
- "ca.srl"
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Install by APT #Модуль пакетного менеджера apt
apt: name=stress state=latest
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Loop until example #Цикл ПОКА-НЕ
shell: echo -n Z >> myfile.txt && cat myfile.txt #Модуль выполнения shell-команд
register: output
delay: 2 #Задержка в секундах
retries: 10 #Если не указать сколько retries, то всего три
until: output.stdout.find("ZZZZ") == false #Делать пока в выводе нет ZZZZ
- debug: var=output.stdout #Вывод этого цикла
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Install in loop&conditions #Установка пакетов по списку
apt: name={{item}} state=latest #Модуль пакетного менеджера apt
with_items:
- tree
- htop
- block: #Условие block-when ЕСЛИ ОС-клиента RedHat
- name: RedHat
yum: name=httpd state=latest #ТО установка Апач
- name: Service start enabled
service: name=httpd state=started enabled=yes #Включить сервис и поставить а автозагрузку
when: ansible_os_family == "RedHat" #Оператор условия
- block: #Условие block-when ЕСЛИ ОС-клиента НЕ RedHat
- name: Debian
apt: name=nginx state=latest #ТО установка NGINX
- name: Service start enabled
service: name=nginx state=started enabled=yes
when: ansible_os_family != "RedHat"
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Generate template.htm #Генерация файла из шаблона
template: src=template.j2 dest={{destin_folder}}/template.html mode=0555
notify: #Вызов handler если были изменения
- Restart RH Apache
- Restart D Apache
#-----------------------------------------------------------------------------------------------------------------------------------------------------#
- name: Copy in loop #Модуль копирования по списку
#copy: src={{item}} dest={{destin_folder}}/{{item}} owner=user group=usergroup mode=0555
#ИЛИ
copy: src={{item}} dest={{destin_folder}} mode=0555
#with_items:
# - "1.txt"
# - "2.txt"
# - "3.txt"
with_fileglob: "*.*" #Скопировать все без перечисления. Делается без with_items
notify: #Вызов handler если были изменения
- Restart RH Apache
- Restart D Apache
pb.yml
--- >
- name: Playbook with loops
hosts: build
become: yes
roles: #Вызов необходимых ролей
- { role: deploy_website_nginx, when: ansible_system == 'Linux' } #Запускать роль, ТОЛЬКО когда ОС сервера-клиента Linux
# - deploy_openvpn_client
Внешние переменные
Чтобы каждый раз не изменять плейбук для разных групп хостов можно сделать следующее:
--- #YAML начинается с этого, никаких "Tab"-ов в файле
- name: Playbook with loops #Просто имя
hosts: "{{myhosts}}" #Внешняя перменная на каких хостах/группах через пробел
become: yes #С правами sudo
roles:
- {role: deploy_website_nginx, when: ansible_system=='Linux'
Внешные переменные имеют наивысший приоритет и переписывают уже объявленные. Опция -K запрашивает пароль перед запуском playbook от пользователя, у которого есть sudo-доступ:
$ ansible-playbook pb.yml -e "myhosts=build" -K
Ad-Hoc команды
Ad-hoc команды - это все команды ansible, которые не указаны в playbook и запускаются из терминала, где -а - это атрибут, -b - быть sudo. Чтобы быть sudo, необходимо чтобы "ansible_user" или пользователь от которого запускаются команды сервере-клиенте был прописан в /etc/sudoers:
# echo "ansible_user ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers
Некоторые команды с комментариями:
$ ansible all -m setup - информация о серверах
$ ansible all -m shell -a 'ssh-keygen -b 2048 -t rsa -f ~/.ssh/sshkey -q -N "" && cat ~/.ssh/sshkey.pub' - запустить команду shell на удаленном сервере для формирования ключей ssh
На выхлопе должны получить пары ssh-ключей для нашего Gitlab CI и не только.
$ ansible all -m command -a "uptime" #тоже что и шелл, но не будет видеть переменные окружающей среды типа $HOME и операторы <>:|& типа ls|grep
$ ansible all -m copy -a "src=file.txt dest=/home/file.txt mode=777" -b #-b быть sudo
$ ansible all -m shell -a 'curl -fsSL https://get.docker.com -o get-docker.sh && sh ./get-docker.sh' -b - установка Docker на удаленные сервера
$ ansible all -m file -a "path=/home/kent/file.txt state=touch/absent" -b #создает/удаляет файл