Print this page

Установка и настройка Ansible на Centos 7 и Ubuntu 24.04

19 September 2013
Rate this item
(0 votes)

Установка

Для 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 #создает/удаляет файл