Как рефакторить Ansible-плейбуки с помощью ChatGPT - Академия Selectel

Как рефакторить Ansible-плейбуки с помощью ChatGPT

Мария Портнова
Мария Портнова Младший сисадмин
19 января 2024

В большом Ansible-плейбуке бывает сложно читать и дебажить код. Я поделюсь опытом рефакторинга такого плейбука с помощью ChatGPT. Расскажу, как нейросети справляются с задачами дебага: разберу кейс отладки проблем с проверками дисков, ошибок с Линтером и шаблоном Jinja.

Изображение записи

Привет, я Маша, младший системный администратор облачной инфраструктуры Selectel. Статья будет полезна коллегам, которые интересуются Ansible и изучают его, плюс я немного поделюсь нашей внутренней кухней.

Фото Маши Портовой.

Небольшое предупреждение: я не буду углубляться в основы Ansible, а скорее опишу свой опыт. 

В чем была проблема

Наша команда обслуживает «железную» инфраструктуру облака, мы деплоим серверы для смежных команд и тестируем новое оборудование. В работе используем разные инструменты, в том числе Ansible. Вот более подробное описание наших команд.

Ansible в основном используем для проверок серверов на этапе тестирования. Для этих целей у нас есть несколько плейбуков. Один проверяет, правильно ли подключены порты сервера. Другой сверяет информацию с сервера — правильно ли она занесена в систему документирования оборудования Netbox, и исправляет ошибки. 

Один из наших главных плейбуков — основные проверки железа и ПО сервера, мы называем его «чек-листом». Он проверяет прошивки дисков и сетевых карт, версии ОС и количество алертов на хосте, но на самом деле проверок гораздо больше. Чек-лист используем не только мы, но и дежурные из Cloud Duty при обслуживании аварийных серверов — эта команда занимается авариями и инцидентами. Он применяется буквально во всем продакшене, особенно часто при вводе новых хостов.

Изначально этот плейбук был структурирован, но постепенно в него добавляли новые и новые проверки, в итоге он начал занимать тысячи (!) строк кода. Его стало тяжело обслуживать и дебажить, было сложно отследить зависимости. Например, переменная могла объявляться в одном месте, а использоваться в другом. 

Есть негласное правило: не писать плейбуки на такое количество задач и строк, поэтому один из наших ведущих системных администраторов решил «распилить» чек-лист.

Что предстояло сделать:

  • создать структуру;
  • разделить проверки по иерархии в зависимости от того, что они проверяют;
  • убрать легаси в коде (например, связки Bash + Ansible);
  • убрать все сообщения об ошибках в конец плейбука и перенести его в роль.

Это должно было «освежить» плейбук: упростить дебаг и обслуживание, улучшить структуру. А еще такой «распил» генерирует большое количество разных задач на Ansible для джунов — таких сотрудников было много, и не все их них сталкивались с Ансиблом.

Проверки я писала сама. Пробовала использовать ChatGPT, но отказалась от этой идеи. Нейросеть не всегда может понять, что конкретно тебе нужно и что у тебя есть из наработок, поэтому мне было легче написать задачу самой. Но зато я использовала ChatGPT для дебага — об этом дальше. 

Важный момент в работе с ChatGPT — безопасность. Все, что вы пишете нейросети, идет на серверы Microsoft и потенциально может быть слито и использовано против вас. Чувствительными данными разбрасываться точно не стоит.

Проверка дисков на хосте

Одной из задач была проверка количества дисков на хосте. Она достаточно простая. Сначала отдельной переменной задаем допустимые значения количества дисков для конкретного типа хостов, например ss. Потом смотрим, соответствует ли количество дисков допустимому значению Если нет, в массив сообщений об ошибках добавляем описание проблемы:

Проверка количества дисков на хосте.

    - name: Set disks for ss
  become: true
  ansible.builtin.set_fact:
    allowed_disk_amount: [26, 27, 28, 38, 40]
  when:
    - "'device_roles_ss' in group_names"

*** 

- name: Check disks amount
  vars:
    disks_amount: "{{ facter_disks|length }}"
  ansible.builtin.set_fact:
    failed_messages: "{{ failed_messages + ['Check amount of disks in host, got {{ disks_amount|string }} , but required for this type of host: {{ allowed_disk_amount|string }}' ] }}"
  when: disks_amount not in allowed_disk_amount

У нас Ansible обернут в Линтер. Когда я загрузила код в GitLab, Линтер выдал мне стену сообщений. Как обычный человек, я прочитала то, что было в самом начале, и только после этого обратила внимание на саму суть:


    WARNING  Listing 8 violation(s) that are fatal
***
roles/cops/checklist/tasks/hardware/disks.yaml:166 Task/Handler: Check disks amount
package-latest: Package installs should not use latest.
roles/cops/netbox_sd/tasks/install.yml:16 Task/Handler: install netbox_sd
package-latest: Package installs should not use latest.
roles/legacy/libvirt-exporter/tasks/install.yml:16 Task/Handler: install libvirt-exporter
package-latest: Package installs should not use latest.
roles/legacy/udping_agent/tasks/install.yml:16 Task/Handler: install udping-agent
package-latest: Package installs should not use latest.
roles/legacy/udping_collector/tasks/install.yml:16 Task/Handler: install udping-collector
You can skip specific rules or tags by adding them to your configuration file:
# .config/ansible-lint.yml
warn_list:  # or 'skip_list' to silence them completely
  - experimental  # all rules tagged as experimental
  - no-jinja-nesting  # Nested jinja pattern.
Finished with 1 failure(s), 7 warning(s) on 684 files.
Cleaning up project directory and file based variables
00:00
ERROR: Job failed: command terminated with exit code 1

Сообщение об ошибке.
Зеленые строки — на что жалуется Линтер.

Я закинула кусок кода, на который жаловался Линтер, и само сообщение об ошибке. Вот каким вышел мой диалог с нейросетью:

Masha: Помоги, пожалуйста, с ансиблом, Линтер пишет:

roles/cops/checklist/tasks/hardware/disks.yaml:166 Task/Handler: Check disks amount package-latest: Package installs should not use latest. 

кусок кода:
- name: Check amount  ansible.builtin.set_fact:    failed_messages: "{{ failed_messages + ['Check amount, got {{ facter_disks|length|string }}, but required: {{ allowed_disk_amount|string }}' ] }}"  when: facter_disks|length not in allowed_disk_amount

ChatGPT: Ошибка, указанная Линтером, связана с использованием ключевого слова "latest" при установке пакетов. Вместо этого рекомендуется указывать конкретную версию пакета. Чтобы исправить ошибку, вам необходимо изменить код …***- name: Install package  ansible.builtin.package:    name: package-name    state: present    version: specific-version

ChatGPT предложил мне скачать дополнительный пакет и просто добавить лишнюю задачу в плейбук, что никак не решило бы проблему. Об этом я и написала нейросети:

Masha: Но тут же нет установки пакетов?

ChatGPT: Извините за недоразумение. Похоже, я неправильно понял контекст вашего вопроса. В данном случае ошибка Линтера связана с установкой пакета `package-latest` (пишет то же самое и предлагает тот же код)

***здесь я заметила, что скинула не ту ошибку, и отправила нужную***

Masha: Неправильно увидела, он жалуется на no-jinja-nesting: Nested jinja pattern.

*** ещё несколько попыток ChatGPT, но по итогу он обнаружил и решил проблему ***

ChatGPT: К сожалению, ошибка "no-jinja-nesting: Nested jinja pattern" остается в коде, потому что в данном случае используется вложенный шаблон Jinja внутри ключа `failed_messages`. Линтер ansible-lint ругается на такую структуру, поскольку вложенные шаблоны Jinja не поддерживаются.Один из способов исправить это — использовать фильтр `format` для форматирования строки. Вот обновленная версия кода без вложенных шаблонов Jinja

- name: Check amount  vars:    disks_amount: "{{ facter_disks|length }}"    failure_message: "Check amount {{ disks_amount|string }}, but required: {{ allowed_disk_amount|string }}"  ansible.builtin.set_fact:    failed_messages: "{{ failed_messages + [failure_message] }}"  when: disks_amount not in allowed_disk_amount

ChatGPT: В этом коде мы создаем переменную `failure_message`, которая содержит отформатированное сообщение об ошибке. Затем мы добавляем это сообщение в список `failed_messages` без использования вложенных шаблонов Jinja.

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


    - name: Check disks amount
  vars:
    disks_amount: "{{ facter_disks|length }}"
    failed_disks_message: "Check amount of disks in host, got {{ disks_amount|string }} , but required for this type of host: {{ allowed_disk_amount|string }}"
  ansible.builtin.set_fact:
    failed_messages: "{{ failed_messages + [ failed_disks_message ] }}"
  when: disks_amount not in allowed_disk_amount

Ошибка простая и решение тоже, но даже здесь я сэкономила время на дебаге. Стоит отметить, что проблема в большей степени завязана именно на Python, с которым я до этого практически не работала. Неизвестно, как долго я бы решала проблему сама. Можно было, например, почитать документацию по Jinja, которая занимает не одну страницу текста. 

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


    - name: Check disks amount
  vars:
    disks_amount: "{{ facter_disks|length }}"
  ansible.builtin.set_fact:
    failed_messages: "{{ failed_messages + ['Check amount of disks in host, got’ + {{ disks_amount|string }} + ’, but required for this type of host: ‘ + {{ allowed_disk_amount|string }}' ] }}"
  when: disks_amount not in allowed_disk_amount

Такой способ тоже сработал бы, но первый вариант удобнее читать.

Итог: ChatGPT сэкономил мне время на решение проблем с Python и помог разобраться с шаблонами Jinja, с которыми я раньше не сталкивалась.

Проверка процессоров

ChatGPT не всегда решает проблему замыленных глаз. Я писала проверку на разные процессоры и память — там было пять разных условий, но при этом ошибки ссылались на конкретные строки кода. 


    - name: Check for 2 processors
  become: True
  ansible.builtin.set_fact:
    failed_messages: "{{ failed_messages + ['Decrease memory, there are more than 256Gb on host with 2 processors, don't forget SILENCE for 2 days!'] }}"
  when:
    - ansible_processor_count == 2
    - ansible_memtotal_mb > 258000
    - ansible_processor is not search("AMD")
    - ansible_fqdn is search('zone-{1,2,3,7,9}')
    - "'device_roles_cmp' in group_names"

- name: Check for 4 processors
  become: True

    #Ошибки

yaml: truthy value should be one of [false, true] (truthy)
roles/cops/checklist/tasks/hardware/memory.yaml:2
yaml: truthy value should be one of [false, true] (truthy)
roles/cops/checklist/tasks/hardware/memory.yaml:13 

Код работал, но Линтер снова вывел ошибку, с которой я и пошла к ChatGPT:

Masha: Мой кусок кода выглядит так:

*** отправляю код ***

Линтер фейлится:

yaml: truthy value should be one of [false, true] (truthy)
roles/cops/checklist/tasks/hardware/memory.yaml:2
yaml: truthy value should be one of [false, true] (truthy)
roles/cops/checklist/tasks/hardware/memory.yaml:13 
как это можно исправить?

В этот раз у него не особо получилось что-то исправить

ChatGPT: Ошибки указывают на то, что ваши условия в блоках `when` должны возвращать либо `true`, либо `false`. Пожалуйста, убедитесь, что ваши условия возвращают одно из этих значений…

*** говорю что не в ту сторону пошёл ***

ChatGPT: Извините за путаницу. Ошибка может быть связана с тем, что условие `ansible_fqdn is search('zone-{1,2,3,7,9}')` не является булевым выражением, и Линтер считает это неверным. Давайте попробуем…

***также предлагает исправить условия ***

В итоге я нашла ошибку раньше ChatGPT.

Masha: Была проблема с тем, что я записала true с большой буквы

ChatGPT: Понял, рад, что проблема была решена! Действительно, в YAML true и false должны быть в нижнем регистре. 

Я справилась быстрее ChatGPT, поэтому не стоит на него полагаться во всем. Однако нейросеть предложила мне хорошее исправление, более удобное для чтения, и я воспользовалась этим вариантом.

Итог: хотя ChatGPT не помог мне решить проблему, я посмотрела на свои условия с другой стороны. Иногда при дебаге такой уход в сторону полезен для решения проблемы. Ну и лишний раз напоминание: даже ИИ может не спасти вас от невнимательности.

Проверка на сетевые карты 

Приведу кейс моего коллеги: нужно было достать из большого JSON-файла строку с версией прошивки сетевой карты. Коллега отправил запрос:

Colleague: write an ansible  loop task to iterate over this dict to show only version line ***json example***

ChatGPT: You can use the `with_items` loop in Ansible to iterate over the dictionary and display only the "version" lines. Here's an example playbook to achieve this:

*** представляет не очень рабочий плейбук ***

Colleague: write an ansible  task to iterate over this dict to show only version line using json_query

ChatGPT: You can use the `json_query` filter in Ansible to extract the "version" lines from the provided dictionary. Here's an example task to achieve this:

*** представляет новый плейбук с использованием  json_query, но тоже нерабочий***

Ни один из вариантов не сработал как нужно, поэтому коллега написал:

«Его версия не взлетела, но выглядела очень заманчиво.»

Итог: задачу помог решить наш товарищ по команде. Он написал факт для Ansible, собирающий всю информацию о сетевой карте, в том числе и ее прошивку. Более опытный коллега оказался сильнее нейросети.

Можете заметить, в последнем случае запросы были на английском. Но я не заметила разницы в ответах, хотя пробовала спрашивать и на русском, и на английском. Ради интереса попробовала отправить один и тот же запрос на двух языках, чтобы сравнить время ответа:

На английском нейросеть генерирует ответ быстрее.
На английском.
На русском ответ генерируется медленнее.
На русском.

Ответ на английском приходит быстрее: пять секунд вместо десяти. Но мне кажется, нужно выбирать тот язык, на котором вам удобнее писать запрос и получать ответ.

Проверка на SWAP-раздел

Попросила ChatGPT написать проверку на наличие SWAP-раздела с нуля, без моих наработок. Выбрала самую простую проверку, чтобы точно что-то получилось. Вот как выглядел мой запрос:

Masha: Привет, я хочу написать проверку в ансибл плейбук. Мне нужно проверить, есть ли на хосте swap раздел, и если есть, то вывести сообщение об этом. Также нужно проверить, есть ли на хосте файл /swap.img, и если есть, то вывести сообщение о его существовании, и что его необходимо освободить (сделать swapoff) и удалить.

ChatGPT: Привет! Для проверки наличия swap раздела и файла /swap.img на хосте с использованием Ansible, вы можете воспользоваться модулем `command` или `shell`. Вот пример Ansible-плейбука для выполнения этих проверок.

    - name: Проверка swap раздела и файла /swap.img
  hosts: your_target_hosts
  become: true  # Выполнение команд с правами суперпользователя

  tasks:
    - name: Проверить наличие swap раздела
      command: "swapon --show"
      register: swap_result
      ignore_errors: true  # Игнорировать ошибку, если swap раздел не найден

    - name: Вывести сообщение, если swap раздел существует
      debug:
        msg: "Swap раздел существует на хосте."

      when: swap_result.rc == 0  # Проверить код возврата

    - name: Проверить наличие файла /swap.img
      stat:
        path: /swap.img
      register: swap_file_result

    - name: Вывести сообщение, если файл /swap.img существует
      debug:
        msg: "Файл /swap.img существует на хосте. Освободите его и удалите."

      when: swap_file_result.stat.exists  # Проверить существование файла

Также СhatGPT подробно объяснил каждое действие, которое происходит в плейбуке:

ChatGPT: Этот плейбук выполняет следующие действия:

1. Проверяет наличие swap раздела с использованием команды `swapon --show`. Если swap раздел существует, регистрирует результат в переменной `swap_result`.
2. Выводит сообщение, если swap раздел найден (если код возврата равен 0).
3. Проверяет наличие файла /swap.img с использованием модуля `stat`. Регистрирует результат в переменной `swap_file_result`.
4. Выводит сообщение, если файл /swap.img существует.

Вы можете запустить этот плейбук, указав свои целевые хосты вместо `your_target_hosts`. Не забудьте также настроить соединение с хостами (например, использовать модуль `ansible_ssh_user` и другие соответствующие параметры).

Надеюсь, это поможет! Если у вас есть дополнительные вопросы или нужна дополнительная помощь, дайте знать.

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


    TASK [cops/checklist : Check for the existence of a swap partition] *********************************
ok: [cmp15]
ok: [cmp18]

TASK [cops/checklist : Display a message if a swap partition exists] ********************************
ok: [cmp15] => {
    "msg": "Swap partition exists on the host."
}
ok: [cmp18] => {
    "msg": "Swap partition exists on the host."
}

TASK [cops/checklist : Check for the existence of the swap.img file] ********************************
ok: [cmp15]
ok: [cmp18]

TASK [cops/checklist : Display a message if the swap.img file exists] *******************************
ok: [cmp15] => {
    "msg": "The /swap.img file exists on the host. Free it up and delete it."
}
ok: [cmp18] => {
    "msg": "The /swap.img file exists on the host. Free it up and delete it."
}

Из слабых мест был только Линтер, который снова ругался на конкретную ошибку:


    WARNING  Listing 1 violation(s) that are fatal
no-changed-when: Commands should not change things if nothing needs doing.
roles/cops/checklist/tasks/hardware/memory.yaml:1 Task/Handler: Check for the existence of a swap partition

Отправила ошибку СhatGPT, и он ее исправил, просто добавив в проверки changed_when: false. Но куда именно их надо было добавлять, все равно пришлось объяснять: 

ChatGPT: Извините за путаницу. Ваш Линтер предупреждает о том, что задача выполняет команду, но не предпринимает никаких изменений, если условие (`when`) не выполняется. Это может быть проблемой в некоторых случаях, когда Ansible рекомендует избегать выполнения лишних команд.

Для устранения предупреждения Линтера можно попробовать изменить ваш плейбук следующим образом:


- name: Check for the existence of a swap partition
  command: "swapon --show"
  register: swap_result
  ignore_errors: true  # Ignore errors if the swap partition is not found

- name: Display a message if a swap partition exists
  debug:
    msg: "Swap partition exists on the host."
  when: swap_result.rc == 0  # Check the return code

- name: Check for the existence of the /swap.img file
  stat:
    path: /swap.img
  register: swap_file_result
  changed_when: false  # Set changed_when to false to avoid "changed" status

- name: Display a message if the /swap.img file exists
  debug:
    msg: "The /swap.img file exists on the host. Free it up and delete it."
  when: swap_file_result.stat is defined and swap_file_result.stat.exists  # Check file existence

Добавлен параметр `changed_when: false` в задаче `stat`, который указывает Ansible не помечать задачу как измененную, даже если она выполнена. Это может решить предупреждение Линтера.

Попробуйте внести эти изменения и проверьте, решает ли это вашу проблему. Если у вас по-прежнему есть вопросы или проблемы, дайте мне знать!

Masha: Он же ругается именно на первую проверку.

ChatGPT: Понял, извините за путаницу. Попробуйте изменить вашу задачу проверки swap раздела следующим образом:

***добавляет в первую проверку аналогичный changed_when: false***

Решила сравнить этот вариант со своей проверкой — я сделала ее немного иначе:


    - name: Check SWAP on ceph
  become: true
  ansible.builtin.set_fact:
    failed_messages: "{{ failed_messages + ['SWAP on ceph hosts should be deleted'] }}"
  when:
    - ansible_swaptotal_mb > 0
    - ('device_roles_fs' in group_names) or ('device_roles_ms' in group_names) or ('device_roles_cs' in group_names) or ('device_roles_meta' in group_names) or ('device_roles_ss' in group_names)

- name: Register swap.img file on ceph
  stat:
    path: "/swap.img"
  register: swapfile

- name: Generate message if exists
  ansible.builtin.set_fact:
    failed_messages: "{{ failed_messages + ['Swapoff and delete swap.img, it exists'] }}"
  when:
    - swapfile.stat.exists

Задача состоит из двух частей: первая проверяет swap раздел на диске, вторая — файл swap.img. И если первая у нас с ИИ реализована по-разному, я иначе смотрю swap раздел и добавила больше условий, то вторая проверка практически идентична моей:

Схожие проверки.

Итог: ChatGPT, хотя и не без изъянов, написал мне заготовку для рабочего плейбука, который я затем доработала. Результат вполне подошел бы в качестве решения. Похоже, нужно уделять запросам особое внимание: чем больше вводных дать, тем качественнее результат. 

Плюсы и минусы работы с СhatGPT

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

Минусы

При использовании СhatGPT всегда нужно помнить о рисках, связанных с конфиденциальностью данных, передачей информации стороннему сервису и возможной устаревшей или неточной информацией.

Риск утечки данных

При работе с моделью можно случайно раскрыть конфиденциальную информацию. Все введенные данные передаются и обрабатываются серверами Microsoft, то есть вы передаете информацию стороннему сервису. Это рискованно, если вы имеете дело с чувствительной информацией, например, с какими-то закрытыми данными, связанными с инфраструктурой дата-центров.

Актуальность информации

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

Плюсы

Решение проблем

ChatGPT действительно помогает решать некоторые задачи, а также взглянуть на проблему с другой стороны. Даже в случае, когда он пошел совсем не туда, он подкинул мне пару идей, как переписать условия, чтобы они стали более легкими для чтения. Когда ты дебажишь, полезно иметь «взгляд со стороны», и нейросеть в этом плане способна помочь.

Экономия времени

Я не знаю, сколько бы времени решала проблемы с Jinja, так как они касаются больше Python, чем Ansible. ChatGPT кажется особенно полезным, если ты не очень хорошо разбираешься в конкретной области. 

Это был один из первых моментов, когда я переписывала плейбуки Я часто спотыкалась об ошибки синтаксиса, неправильный парсинг JSON-файлов и т.д. А когда ты делаешь первые шаги, важно и полезно иметь инструмент, который поможет исправить глупости не за три часа, а за пять минут.

Самообучение

ChatGPT помогает построчно объяснить код. Так, можно разобраться в вещах, которые тебе непонятны, а загуглить их невозможно. Например, без привязки к Ansible, я пробовала разобраться и потренироваться в некоторых запросах в Grafana, и нейросеть подробно пояснила непонятные вещи. Не думаю, что ChatGPT и Google способны полностью заменить ментора, но они точно помогут при его отсутствии.

Заключение

Мы продолжаем переделывать плейбук, но многие задачи уже сделали. Чек-лист приобрел четкую структуру, его стало легче читать. Мы, джуны из отдела облачной инфраструктуры, поработали с Ansible и набрались опыта. Мне понравилось использовать ChatGPT для дебага и стало интересно, как он справится с другими задачами. Надеюсь, что хорошо — узнаю со временем.

Я использовала именно ChatGPT потому что он был самым популярным, а у меня был временный прямой доступ к нему без VPN. Однако есть и другие нейросети, которые теоретически могут помочь с подобными проблемами:

  • Google Bard,
  • Github Copilot,
  • Codeium,
  • YandexGPT,
  • и другие.

Многие из них можно развернуть локально, например LocalAI. Вариантов достаточно, главное — начать погружаться в тему.