Ansible знакомимся с системой на примерах.

К вопросу управления серверами можно подходить по разному. Можно управлять ими в ручную, можно делать это при помощи самописных скриптов, а можно при помощи системы управления. Мне кажется, что когда количество серверов под вашем контролем переваливает за десяток, а работать с ними приходится часто, то система управления это больше необходимость, чем каприз. Кто то может работать и со скриптами, кому то достаточно и однострочников в консоли, конечно все эти решения работают, но не все они красивы и универсальны. Я лично к этому шёл очень долго, и не так давно понял что «пора». Уже даже поставил к себе в лаборатории Puppet, но тут на новой работе оказалось что пришли к использованию Ansible. Беглый поиск показал одно, крайне интересное преимущество над другими системами — отсутствие необходимости устанавливать клиент на управляемые серверы. Сам Ansible написан на Python, так же на Python пишутся и модули для него. Сам проект достаточно молод, его разработка началась 2012 году. Ну а сегодня, на примерах, я постараюсь показать на сколько удобно и быстро можно управлять огромным парком серверов.

Для начала нам нужно установить Ansible на «управляющий» сервер. В общем то это может быть любая машина, но у меня есть возможность выделить под неё отдельную.
Ansible есть в репозитории EPEL и нам понадобится для нормальной работы ещё sshpass

root # rpm -ihv http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-2.noarch.rpm
root # yum install ansible sshpass

Первое что нам нужно сделать после установки, настроить файл hosts для Ansible. Он у него собственный и находится в /etc/ansible/hosts. Туда мы должны занести все наши машинки, которыми мы будем управлять. Можно добавлять в кучу, но я предпочитаю их группировать:

[webservers]
client1 ansible_ssh_user=korp ansible_ssh_pass=XXX
client2 ansible_ssh_user=korp ansible_ssh_pass=XXX

А в начале файла мы ещё определим рутовый пароль, для тех случаев, когда нужно будет из под обычного пользователя подниматься до рута, что бы выполнить необходимые действия. Вы можете и вводить пароль каждый раз руками, но для меня это не так удобно. Такая реализация мне нравится, тк на все своих серверах я сразу же запрещаю рутовый логин по ssh. На мой взгляд это достаточно усиливает безопасность системы, но у вас может быть и другое мнение на этот счёт.

[all:vars]
ansible_sudo_pass=YYY

И так, давайте проверим что всё работает и немного поиграем в Ansible из консоли, прежде чем приступим к написанию playbook`а.

root # ansible webservers -m command -a "uname -a"
client1 | success | rc=0 >>
Linux localhost.localdomain 3.10.0-123.9.3.el7.x86_64 #1 SMP Thu Nov 6 15:06:03 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

client2 | success | rc=0 >>
Linux localhost.localdomain 3.10.0-123.9.3.el7.x86_64 #1 SMP Thu Nov 6 15:06:03 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

Здесь мы просто говорим Ansible выполнить комманду uname -a для серверов группы webservers. Вы можете подставить своё название группы или all для всех серверов в файле hosts.

А вот и ещё кое что интересное

root # ansible webservers -m setup

Вывод этой команды я положил на pastebin, ибо он слишком длинный. Здесь мы при помощи модуля setup получаем кучу информации о наших серверах в формате JSON.
Но, помимо большой портянки, мы можем и отфильтровать полученный результат

root # ansible webservers -m setup -a 'filter=ansible_distribution'
client1 | success >> {
    "ansible_facts": {
        "ansible_distribution": "CentOS"
    },
    "changed": false
}

client2 | success >> {
    "ansible_facts": {
        "ansible_distribution": "CentOS"
    },
    "changed": false
}

Но, как указано в справке к модулю, Ansible фильтрует только по первому уровню дерева, так же как и не получится использовать одновременно несколько фильтров.

Но работа из консоли это не так интересно, ввиду ограничения возможностей. Можно конечно лепить команды в кучу, но это уже не так эстетично, по этому пора приступить к написанию нашего первого playbook`а. Пожалуй первое, что хочется сделать со всеми своими серверами — обновление.
тк я использую CentOS я буду пользоваться модулем yum, но в комплекте есть всё необходимое для различных платформ, и apt и pkgng и portage.

nano update.yml
- hosts: webservers
  user: korp
  sudo: yes

  tasks:
    - name: Update system
      yum: name='*' state=latest

    - name: Reboot
      command: shutdown -r 1

Расскажу немного подробнее, построчно:
Для хостов группы webservers мы говорим залогиниться под пользователем korp, а потом при помощи sudo выполнить команды yum update и shutdown -r 1. Тут сразу стоит сказать — почему shutdown -r 1, а не now. Дело в том, что при shutdown -r now или reboot происходит ошибка, в баги её уже записали, писали о ней так же не однократно и пока приходится довольствоваться обходным решением.

Перейдём к следующему этапу. У нас есть сервер установки ПО и его настройки и возникает ситуация — в нашей сети появляется новый сервер. Пока не важно — какую задачу он будет выполнять, пока перед нами стоит задача его первоначальной настройки — подключить нужные репозитории, создать пользователей, поставить необходимый минимум ПО, в общем привести сервер к некому стандартно-базавому состоянию. Пишем ещё один playbook:

nano new.yml
- hosts: all
  user: root

  tasks:
    - name: Add user
      action: user name=korp password=XXX groups=wheel

    - name: Add EPEL repo
      yum: name=http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-2.noarch.rpm

    - name: Add RPMForge repo
      yum: name=http://apt.sw.be/redhat/el7/en/x86_64/rpmforge/RPMS/rpmforge-release-0.5.3-1.el7.rf.x86_64.rpm

    - name: Add Remi repo
      yum: name=http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
      
    - name: Software install
      yum: name={{item}} state=latest
      with_items:
        - yum-priorities 
        - htop 
        - mc 
        - nano 
        - ntsysv 
        - wget 
        - vim 
        - ntpdate 
        - openssh-clients 
        - yum-utils 
        - yum-remove-with-leaves
        
    - name: NTP daemon enable
      service: name=ntpdate enabled=yes
      
    - name: Setup NTP daemon
      template: src=ntpdate.j2 dest=/etc/ntp.conf owner=root group=root mode=0644
      
    - name: NTP daemon restart
      service: name=ntpdate state=restarted
      
    - name: Disable SELinux 
      selinux: state=disabled
      
    - name: PermitRootLoginSSH
      lineinfile: dest=/etc/ssh/sshd_config regexp="^MaxSessions 10" insertafter="^#MaxSessions 10" line="PermitRootLogin no"

Пожалуй сразу оговорюсь о шаблонах, раз уж в этом скрипте я его использовал. Модуль template. Дело в том, что модуль lineinfile не умеет создавать файл, соответственно что бы создать файл и занести в него данные, нам нужно пользоваться шаблонами. Шаблоны пишутся в формате Jinja2 и он в общем то совсем не сложен. Я создал простой файл ntpdate.j2, но в данном случае, мне нужно внести всего одну запись и нет смысла городить целый шаблон, по этому я просто захордкоил в нём строку server ntp1.stratum2.ru.

Следующим этапом мы заносим новый сервер в /etc/ansible/hosts, как мы это делали уже с нашими серверами, а запускать развёртывание мы будем следующей командой:

ansible-playbook -l client3 new.yml -u root -k

ключ l говорит о том, что мы «ограничиваемся» хостом/группой с именем client3, ну и тк пока у нас нет пользователя в новой системе, мы логинимся туда в качестве пользователя root, а ключ k говорит о том, что бы ansible запросил у нас пароль от учётной записи root в интерактивном режиме.
После успешного завершения, нам нужно новую систему тоже обновить, по этому натравим на неё наш playbook update.yml

ansible-playbook -l client3 update.yml

Но очень часто бывает так, что у нас в сети живут клиенты в различными ОС. по этому этот факт нам так же нужно учесть, ввиду того, что этот скрипт подходит только для CentOS 7, а со всеми другими системами, соответственно будут различные ошибки. Теперь попробуем переписать наш playbook таким образом, что бы он работал как с CentOS 7, так и с 6 версией, но так же поддерживал ещё и работу с Gentoo.
В случае, когда нам нужно установить ПО на разные системы, у нас есть 2 пути:
1. Когда имена пакетов совпадают, тогда можно обойтись достаточно просто:

- name: Install wget
  action: "{{ ansible_pkg_mgr }} name=wget state=present"

2. Когда имена пакетов не совпадают, тогда для каждой системы нужно будет заводить отдельную секцию:

- name: Install ntsysv package (CentOS)
  yum: name=ntsysv state=latest
  when: 'ansible_distribution' == 'CentOS'

- name: Install gentoolkit package (Gentoo)
  emerge: name=gentoolkit state=latest
  when: 'ansible_distribution' == 'Gentoo'

Ну а целиком наш новый файл будет иметь вот такой вид:

- hosts: all
  user: root

  tasks:
    - name: Add user
      action: user name=korp password=XXX groups=wheel

    - name: Add EPEL repo
      yum: name=http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-2.noarch.rpm
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'

    - name: Add RPMForge repo
      yum: name=http://apt.sw.be/redhat/el7/en/x86_64/rpmforge/RPMS/rpmforge-release-0.5.3-1.el7.rf.x86_64.rpm
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'

    - name: Add Remi repo
      yum: name=http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'
      
    - name: Add EPEL repo
      yum: name=http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6'

    - name: Add Remi repo
      yum: name=http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
      when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6'
      
    - name: Software install
      action: "{{ansible_pkg_mgr}} name={{item}} state=present"
      with_items:
        - htop 
        - mc 
        - nano 
        - wget 
        - vim 
        - openssh-clients 

    - name: Install ntsysv package (CentOS)
      yum: name=ntsysv state=latest
      when: ansible_distribution == 'CentOS'
    
    - name: Install gentoolkit package (Gentoo)
      portage: name=gentoolkit state=latest
      when: ansible_distribution == 'Gentoo'
        
    - name: NTP daemon enable
      service: name=ntpdate enabled=yes
      
    - name: Setup NTP daemon
      template: src=ntpdate.j2 dest=/etc/ntp.conf owner=root group=root mode=0644
      
    - name: NTP daemon restart
      service: name=ntpdate state=restarted
      
    - name: Disable SELinux 
      selinux: state=disabled
      
    - name: PermitRootLoginSSH
      lineinfile: dest=/etc/ssh/sshd_config regexp="^MaxSessions 10" insertafter="^#MaxSessions 10" line="PermitRootLogin no"

Посмотреть какие значения могут быть у переменных ansible_distribution, ansible_distribution_major_version, ansible_pkg_mgr можно при помощи модуля setup, возможно вы найдёте для себя иные методы определения принадлежности системы к чему-либо и у вас будут собственные условия.

Пожалуй этого количества примеров, хватит для того, что бы начать писать первые playbook`и в качестве старта. Остался ешё один момент, о котором я не успел рассказать — поддержка Windows.
Поддержка Windows появилась, начиная с версии 1.7 и в качестве средства управления используется powershell remoting, но так же для работы, на Linux-сервере Ansible понадобиться Python-модуль — winrm, а на Windows -сервере необходимо будет включить удалённую поддержку Powershell. Но на сервере версия Powershell должна быть не ниже 3.0. В документации размещены скрипты, для автоматической настройки Windows-сервера для работы с Ansible. На Windows-серверах поддерживает работа почти всех встроенных модулей Ansible (какие именно не работают, к сожалению, не указано), а так же имеется ряд специфичных модулей для Windows.

Пожалуй это всё, что я могу на данный момент рассказать вам об этой системе. Думаю для начала работы, вам этого будет вполне достаточно, а не возникающие вопросы всегда сможет ответить документация. А проекту хочется пожелать удачи и развития, мне он очень понравился.

Добавить комментарий