NetApp + Ansible


Меня уже несколько раз на различных мероприятиях спрашивали по поводу первоначального конфигурирования массивов NetApp при помощи Ansible, да и статьи по Ansible, судя по статистике посещаемости моего блога, крайне популярны. Поэтому не будем отставать от трендов перехода к infrastructure as a code и поговорим о том, как быстро и просто сконфигурировать с нуля систему на Ontap.

Главная сложность массивов NetApp (но в том же и их прелесть), что вам для конфигурирования доступно практически всё, что вашей душе угодно. Такого количества параметров нет, пожалуй, ни у одного другого вендора СХД. Но в этом же и сложность, особенно учитывая «многослойность» системы и большое количество абстракций.
Настраивать всё это через GUI удовольствия действительно мало, да и порой это крайне неудобно. Можно использовать консоль — она понятна и читабельна, все команды в целом интуитивно понятны, но они длинные, их много и вам всё равно необходимо иметь некий чек-лист того, что вам необходимо настроить на системе, для того чтобы это было во-первых единообразно с остальными вашими системами, а во-вторых чтобы в большом количестве настроек не упустить и не забыть что-либо.
Но если вам всё равно необходимо хотя бы однажды описать всю конфигурацию, то почему бы не сделать это сразу в эталонном конфигурационном файле и далее уже по необходимости туда вносить изменения, необходимые для конкретных систем? Если вы не один занимаетесь поддержкой этих систем, то можно дополнить эту схему при помощи Git репозитория, куда можно складывать конфиги под каждую систему и каждый из администраторов сможет вносить в них изменения, и при этом всем будет понятно когда и по какой причине в конфиг для конкретной системы вносились изменения (при условии, что всё это документируется при коммитах, конечно же).
Да и вспомнить через пару лет как была сконфигурирована система намного быстрее и нагляднее при помощи одного конфигурационного файла, нежели проводить инвентаризацию системы через GUI или консоль.

Но немного о самих модулях NetApp для Ansible. NetApp пожалуй активнее всего развивает их среди остальных стораджовых вендоров. И лишь Pure дышит им в затылок. Поддержка NetApp не ограничивается только системами под управлением ONTAP, также есть модули как для SolidFire, так и для E-series. Есть даже модули для поддержки 7-mode в старых версиях Ansible (были удалены в 2.11).

И так, расскажу, как на мой взгляд лучше построить этот процесс, а вы уже сможете применить его под себя, внеся необходимые правки. Но прежде чем переходить к написанию конфигурационного файла, надо всё-таки подготовить нашу рабочую станцию к работе. Как разворачивается сам Ansible я писал в одной из своих старых статей «Ansible знакомимся с системой на примерах». Дополнительно к этому нам понадобится модуль для работы с системами NetApp. Когда вы работаете с Ansible, крайне важным является использование самой свежей версии модулей. Очень часто проблемы с плейбуками возникают из-за того, что написаны они, например, для версии 2.8, а в версии 2.9 уже многое что изменилось и надо заглянуть в документацию на предмет изменений, которых обычно довольно много, в том числе и новые возможности в модулях. Поэтому ставить будем из Git.

yum install git -y
git clone https://github.com/ansible/ansible.git ~/ansible-devel
cd ~/ansible-devel
git pull
cd ~/ansible-devel/lib/ansible
sudo yes|cp -aRv module_utils/netapp* /usr/lib/python2.7/site-packages/ansible/module_utils
sudo yes|cp -aRv modules/storage/netapp/* /usr/lib/python2.7/site-packages/ansible/modules/storage/netapp/

Условно разделим на 2 этапа наше конфигурирование:

  • Настройка параметров массива, которые не привязываются к SVM, а едины для всей системы
  • Создание и настройка SVM

Начну с того, что первоначальную настройку я делаю всё-таки на самом массиве — это настройка менеджмента нод и создание кластера, после чего мы уже, собственно, сможем получить удалённый доступ к системе. И ещё одно действие, которое я предпочитаю всё-таки делать руками, привязка дисков в контроллерам и создание агрегатов. Во-первых потому что в модуле NetApp для Ansible нет поддержки работы с дисками, а создание аггрегатов вручную делается мной уже просто на автоматизме при конфигурировании дисков. Хотя никто не запрещает вам использовать для этого и Ansible, ведь это действительно просто. Начинать нужно с того, что мы скажем нашему плейбуку — где именно он будет запускаться и определим некоторые переменные.

---
- hosts: localhost
  gather_facts: False
  name: System setup
  vars:
    hostname: 192.168.1.130
    username: admin
    password: pass

Если вы уже имели дело с Ansible, то скорее всего заметите, что мы указываем нашему плейбуку, что запускаться он будет на localhost, хотя обычно мы указываем там имена хостов или имена групп хостов. Дело в том, что для работа Ansible с ONTAP (да на самом деле и с ElementOS) идёт не через привычный ssh, а посредствам http/https при помощи модулей, которые мы устанавливали ранее.
А теперь буквально в несколько строк мы создадим 2 агрегата

  - name: Create Aggregates
    na_ontap_aggregate:
      state: present
      service_state: online
      name: "{{ item.aggr }}"
      disk_count: "{{ item.disk_count }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
    loop:
      - { aggr: 'aggr1', disk_count: '13' }
      - { aggr: 'aggr2', disk_count: '13' }

Есть ещё одна задача, которую необходимо выполнить на новой системе — создание VLAN’ов на портах. Если мы используем классическую двухконтроллерную систему, то мы должны как минимум на 4 портах массива настроить пару VLAN’ов. Конечно это не всегда так, их может как больше, так и меньше, или, если вы используете ONTAP Select, у вас может быть вообще один контроллер и один порт. Но я покажу общую концепцию, которую можно будет применить потом к любому количеству портов.

  - name: Create VLANs
    na_ontap_net_vlan:
      state: present
      vlanid: "{{ item.vlanid }}"
      node: "{{ item.node }}"
      parent_interface: "{{ item.parent_interface }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
    loop:
      - { node: 'korphome-01', vlanid: '13', parent_interface: 'e0a' }
      - { node: 'korphome-01', vlanid: '14', parent_interface: 'e0a' }
      - { node: 'korphome-01', vlanid: '15', parent_interface: 'e0a' }
      - { node: 'korphome-01', vlanid: '16', parent_interface: 'e0a' }

Всё в принципе просто. На каждой из нод нашей системы на портах e0a мы создаём интерфейс с 13 VLAN ID, а на e0b с 14. В данном случае я использую loop и метод создания виртуального интерфейса выполнится 4 раза с заданными параметрами. Это не самая сложная схема, чуть более сложный вариант использования циклов я применю при создании вольюмов на агрегатах чуть позже.
Следующий этап — создание IPspaces и Broadcast Domains.

  - name: Сreate broadcast domain
    na_ontap_broadcast_domain:
      state: present
      name: ansible_domain
      mtu: 1000
      ipspace: Default
      ports: ["korphome-01:e0a-13", "korphome-01:e0a-14"]
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false

  - name: Create ipspace
    na_ontap_ipspace:
      state: present
      name: ansibleIpspace
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false

И последним остаётся задать NTP сервер

  - name: Create NTP server
    na_ontap_ntp:
      state: present
      version: auto
      server_name: "3.ru.pool.ntp.org"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false

Теперь определив в первом конфигурационном файле все необходимые нам параметры (а вы, подправив их под свои нужды), можем запустить данный плейбук и получить первоначальные настройки на нашей системе.

Не удивляйтесь что у меня настройки немного расходятся с тем, что я предлагаю делать, просто эксперименты я провожу на ONTAP Select у которого всего один “контроллер” и всего один диск. Чтобы не собирать весь конфиг по тексту статьи, для удобства я выложу полный листинг обоих плейбуков в конце статьи. При помощи na_ontap_user вы возможно захотите добавить ещё и пользователей в систему с различными правами доступа.

С первым плейбуком и предварительной настройкой системы мы закончили, переходим ко второму этапу — создание и конфигурирование SVM. Это как раз одна из тех виртуальных сущностей, которая есть в системах ONTAP. Почему я решил их разделить на 2 части? Потому, что по моему опыту — создавать и модифицировать SVM приходится намного чаще, чем остальные настройки. Раз мы создаём отдельный плейбук, то ещё раз определим как он будет запускаться и с какими переменными, добавив туда сразу и имя нашего SVM.

---
- hosts: localhost
  gather_facts: False
  name: Create SVM
  vars:
    hostname: 192.168.1.130
    username: admin
    password: pass
    vserver: AnsibleSVM

И первым делом, мы создаём саму сущностью SVM, которую будем уже дальше наполнять различными настройками.

  - name: SVM Create
    na_ontap_svm:
      state: present
      name: "{{ vserver }}"
      root_volume: "{{ vserver }}_root"
      root_volume_aggregate: aggr1
      root_volume_security_style: unix
      allowed_protocols: nfs,cifs,iscsi
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false

Используется модуль na_ontap_svm, состояние указано в качестве present, т.е. мы его создаём. На самом деле, чтобы удалить какой-то элемент достаточно сменить present на absent, так что стоит быть внимательнее. Но по логике работы самого Ontap вы не сможете просто удалить SVM, предварительно нужно будет удалить всё его содержимое с данными (луны, вольюмы, шары) и некоторые связанные с ним настройки (igrop’ы, например), а уже потом удалять сам SVM. Но вернёмся к заданию создания SVM. Если вы работали с системами ONTAP, то большинство указанных параметров вам понятны. Имя SVM Create мы объявили в переменной в самом начале — AnsibleSVM, т.к. все наши настройки будут связаны именно с SVM, чтобы нам было достаточно только один раз его указать в конфигурационном файле для большей гибкости в далнейшем использовании этого конфига с другими системами. А вот последние 5 строк задачи создания SVM будут преследовать нас до самого конца. Как я уже говорил ранее, работа Ansible с ONTAP осуществляется посредством http/https и каждая задача — это отдельное обращение к массиву, для чего нам требуется каждый раз авторизовываться, говорить какой протокол мы используемся и что делать с сертификатом. Конечно это не совсем удобно и очень сильно раздувает конфигурационный файл.

Зададим настройки DNS

  - name: Create DNS
    na_ontap_dns:
      state: present
      vserver: "{{ vserver }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      domains: korphome.local
      nameservers: 192.168.1.216
      skip_validation: true
      https: true
      validate_certs: false

С применением loop, который мы использовали при создании VLAN’ов, мы создадим необходимые виртуальные интерфейсы.

  - name: Create interface
    na_ontap_interface:
      state: absent
      interface_name: "{{ item.interface_name }}"
      home_port: "{{ item.home_port }}"
      home_node: "{{ item.node }}"
      role: data
      protocols: "{{ item.protocols }}"
      admin_status: up
      firewall_policy: mgmt
      address: "{{ item.address }}"
      netmask: 255.255.255.0
      vserver: "{{ vserver }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
    loop:
      - { node: 'korphome-01',  interface_name: '{{ vserver }}_iscsi_1', protocols: 'iscsi', home_port: 'e0a', address: '192.168.1.165' }
      - { node: 'korphome-01',  interface_name: '{{ vserver }}_iscsi_2', protocols: 'iscsi', home_port: 'e0a', address: '192.168.1.166' }
      - { node: 'korphome-01',  interface_name: '{{ vserver }}_data_1', protocols: 'nfs,cifs', home_port: 'e0a', address: '192.168.1.167' }
      - { node: 'korphome-01',  interface_name: '{{ vserver }}_data_2', protocols: 'nfs,cifs', home_port: 'e0a', address: '192.168.1.168' }

Как я уже упоминал выше — моя конфигурация будет казаться немного странной. В данном случае у меня весь трафик идёт через один единственный порт, но т.к. мы готовим всё-таки универсальный скрипт для физической системы, я хочу показать как он должен выглядеть в идеальном случае.
Одним из протоколов, с которым мы будем работать, который мы указали при создании SVM, будет iSCSI, мы также создали для него 2 интерфейса, через которые будет ходить трафик и теперь нам необходимо создать на SVM сам сервис iSCSI и запустить его.

  - name: Start iscsi service
    na_ontap_iscsi:
      state: absent
      service_state: started
      vserver: "{{ vserver }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false

Следующим этапом станет создание группы инициаторов, которые будут работать с нашим массивом по iSCSI.

  - name: Create igroup
    na_ontap_igroup:
      state: absent
      name: ansibleIgroup
      initiator_group_type: iscsi
      ostype: vmware
      initiator: iqn.1998-01.com.vmware:esxi6-68316bc6,iqn.1991-05.com.microsoft:veeamv10
      vserver: "{{ vserver }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false

Определяем произвольное имя группы хостов, задаём ostype и через запятую указываем iqn наших серверов. При создании SVM мы указали, что он будет работать ещё и с файловыми протоколами nfs,cifs и для них также необходимо сконфигурировать и запустить сервисы.

  - name: Start nfs service
    na_ontap_nfs:
      state: absent
      service_state: started
      vserver: "{{ vserver }}"
      nfsv3: disabled
      nfsv4: disabled
      nfsv41: enabled
      tcp: disabled
      udp: disabled
      vstorage_state: disabled
      nfsv4_id_domain: korphome.local
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
      
  - name: Create ans start cifs service
    na_ontap_cifs_server:
      state: absent
      cifs_server_name: ontap
      workgroup: korphome
      service_state: started
      vserver: "{{ vserver }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false

Для NFS нам необходимо указать по какой из версий протокола у нас клиенты будут получать доступ, в описании к na_ontap_nfs есть параметры, связанные с правами доступа, которые вам тоже могут понадобится. А для CIFS достаточно указать только workgroup.

Теперь нам осталось организовать луны и шары, на которых собственно и будут храниться наши данные. Но прежде чем переходить к их созданию, немного поговорим об именовании. Дело в том, что в зависимости от проекта, я именую луны и вольюмы по-разному. Где-то это просто порядковые номер vol1, vol2, vol3. А для некоторых проектов мне необходимо указывать какие-то более человеческие имена, типа sevr1_db_2. От этого варианты генериции конфига для Ansible будут немного отличаться. Я покажу оба варианта, а вы уже сможете взять тот вариант, который больше подходит для вас. И так, первый вариант — числовое именование.

  - name: Volume Create
    na_ontap_volume:
      state: present
      name: "vol_{{ item.1.name }}"
      vserver: "{{ vserver }}"
      aggregate_name: "{{ item.0.aggr }}"
      size: 30
      size_unit: gb
      policy: default
      percent_snapshot_space: 0
      efficiency_policy: default      
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
    loop: "{{ lookup('subelements', aggrs, 'volumes', {'skip_missing': True}) }}"
    vars:
      aggrs:
      - { name: "aggr1", volumes: [11,13,15,17] }
      - { name: "aggr2", volumes: [12,14,16,18] }

Здесь мы используем итератор внутри loop. Данный конфиг создаст 4 вольюма vol11, vol13, vol15, vol17 на агрегате aggr1 и 4 вольюма с чётными числами на втором агрегате. Не забываем внести изменения в size и size_unit и поправить percent_snapshot_space и efficiency_policy под свои требования.
Создать луны под этот шаблон именования будет также просто

  - name: Create LUN
    na_ontap_lun:
      state: present
      name: "lun_{{ item }}"
      flexvol_name: "vol_{{ item }}"
      vserver: "{{ vserver }}"
      size: 5
      size_unit: gb
      ostype: vmware
      space_reserve: no
      space_allocation: yes
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
    with_sequence: start=11 end=18

with_sequence это такой же итератор, который создаст нам по одному луну на каждом из наших вольюмов. Не забываем внести изменения в size и size_unit и поправить percent_snapshot_space и efficiency_policy под свои требования. Изменяем ostype, если наши хосты не являются VMware и правим space_reserve и space_allocation, если хотите использовать другие параметры. Таким образом мы получим следующую структуру
aggr1/vol11/lun11
aggr2/vol12/lun12
aggr1/vol13/lun13
aggr2/vol14/lun14
aggr1/vol15/lun15
aggr2/vol16/lun16
aggr1/vol17/lun17
aggr2/vol18/lun18

Быстро и удобно, но не все отвечает требования задачи. Если мы хотим указывать какие-то конкретные имена вольюмов/лунов, то можно воспользоваться другим вариантом

  - name: Volume Create
    na_ontap_volume:
      state: present
      name: "vol_{{ item.1.name }}"
      vserver: "{{ vserver }}"
      aggregate_name: "{{ item.0.aggr }}"
      size: 30
      size_unit: gb
      policy: default
      percent_snapshot_space: 0
      efficiency_policy: default      
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
    with_subelements:
      - "{{ volumes }}"
      - volume
    vars:
      volumes:
        - aggr: aggr1
          volume:
            - name: server1_db1_vol
            - name: server2_db1_vol
        - aggr: aggr2
          volume:
            - name: server1_db2_vol
            - name: server2_db2_vol

Также не забываем про size, size_unit, percent_snapshot_space и efficiency_policy. И по аналогии создадим луны

  - name: Create LUN
    na_ontap_lun:
      state: present
      name: "lun_{{ item }}"
      flexvol_name: "vol_{{ item }}"
      vserver: "{{ vserver }}"
      size: 5
      size_unit: mb
      ostype: vmware
      space_reserve: no
      space_allocation: yes
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
    with_subelements:
      - "{{ volumes }}"
      - lun
    vars:
      volumes:
        - vol: server1_db1_vol
          lun:
            - name: server1_db1_lun
        - vol: server2_db1_vol
          lun:
            - name: server2_db1_lun
        - vol: server1_db2_vol
          lun:
            - name: server1_db2_lun
        - vol: server2_db2_vol
          lun:
            - name: server2_db2_lun

Здесь тоже не забудьте про параметры: size и size_unit, percent_snapshot_space, efficiency_policy, ostype, space_reserve и space_allocation. Честно скажу, этот вариант мне не сильно нравится, но иногда приходится использовать и его. Проблема в том, что это сильно увеличивает конфиг и снижает его читабельность. При этом потом такую же конструкцию придётся использовать при мапинге, т.к. нам нужно будет указывать целиком путь /vol/vol_name/lun_name. Потому что с итератором это делать всё-таки намного проще

  - name: Create LUN mapping
    na_ontap_lun_map:
      state: present
      initiator_group_name: ansibleIgroup
      lun_id: "{{ item }}"
      path: "/vol/vol_{{ item }}/lun_{{ item }}"
      vserver: "{{ vserver }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false
    with_sequence: start=11 end=18

Параметр lun_id в принципе можно не указывать, но удобнее, когда порядковый номер луна совпадает с Lun ID, который видно со стороны сервера. И главный параметр в этом конфиге — initiator_group_name, группа, которую мы создавали уже ранее и к которой мы мапим наши луны.
И последнее, что осталось нам сегодня сделать — создать CIFS и создать доступ по NFS.

  - name: Create CIFS share
    na_ontap_cifs:
      state: present
      share_name: Share
      path: /
      share_properties: browsable,oplocks
      symlink_properties: read_only,enable
      vserver: "{{ vserver }}"
      hostname: "{{ hostname }}"
      username: "{{ username }}"
      password: "{{ password }}"
      https: true
      validate_certs: false

Все share_properties описаны в документации к ONTAP vserver cifs share properties show, а параметры symlink_properties в Configuring UNIX symbolic link support on SMB shares.

Конечно в этой статья я описал далеко не все модули и даже не все возможности модулей, которые использовал для примеров. Но я и не ставил перед собой такой задачи, я лишь хотел показать, как можно удобно построить процесс настройки массивов под управлением ONTAP (в том числе и ONTAP Select). По сути, при помощи Ansible вы можете настроить практически всё тоже самое, что можете настроить из GUI (в CLI возможности конечно шире). Самое главное — сделать это будет удобнее и быстрее. Предварительно собираете необходимую для настройки информацию: ip-адреса для lif’ов, адреса dns, ntp, имена SVM и томов, быстро заполняете это в конфигурационном файле и за одну минуту производите настройку всей системы. Я считаю это отличным решением и потихоньку стараюсь внедрить этот подход у нас. Но а если у вас остались ещё вопросы, или что то не получается, можно написать мне или в канал Netapp.io в Slack.
Как и обещал, в конце полный листинги:
system_setup.yml
create_svm.yml

 

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