Вопрос или проблема
Когда я выполняю модуль ansible с циклом, который содержит словари со значениями, используемыми модулем, включая конфиденциальную информацию, я могу скрыть ее, используя loop_control.label
, но не когда возникает ошибка.
- ansible.module:
arg1: "{{ item.name }}"
arg2: "{{ item.some_value }}"
arg3: "{{ item.secret }}"
arg4: "{{ item.throw_error }}"
loop:
- name: "item1"
some_value: "my value"
secret: "p4$$w0rd"
loop_control:
label: "{{ item.name }}"
Я могу скрыть ее, используя no_log: true
, но тогда скрываются все данные, включая успешные сообщения, а также сообщения об ошибках, которые могут быть вполне приемлемыми, и это не дает мне понимания настоящей ошибки, когда я не вижу ее.
Это похоже на полное отключение всех логов в обратном прокси или на логирование всего, включая заголовки авторизации, что совсем не желательно в производственной среде.
Итак, есть ли способ сделать так, чтобы при возникновении ошибки не логировались элементы цикла, но логировалось все остальное? Это может быть либо флаг задачи, подобный no_log
, либо какая-то глобальная конфигурация, которую можно включить в файл ansible.cfg
.
Обновление (2021-04-19)
Ответ Владимира на самом деле работает как обходной путь, но у него есть несколько недостатков:
- Вам нужно установить библиотеку
jmespath
перед выполнением фильтраjson_query
. - Это требует больше кода и делает код в целом менее читаемым.
- Если у вас есть много циклов (в моем конкретном проекте их более 80), код может стать действительно неаккуратным (также из-за пункта 2).
- Если у вас есть цикл в родительском файле, вам нужно использовать
loop_control
сloop_var
в отладочном цикле. - Ошибка и место, где отображается сообщение об ошибке, будут в разных местах. То же самое относится к сообщениям об успехе.
- Метка цикла
label
в задачеdebug
будет отличаться от той, что в основной задаче цикла (что может сделать сложным сопоставление случаев успеха и ошибок с фактическими элементами). - Две задачи будут выводиться излишне, когда все в порядке (при этом сообщения об успехе выводятся отдельно от самих задач, что ухудшает вывод, когда все проходит успешно, что должно быть в большинстве случаев).
- Изменение и тестирование существующих задач цикла, а также приведение любых новых циклов к этому формату будет бременем для поддержки (особенно из-за пункта 2, который заставляет меня думать улучшаю ли я код или ухудшаю его).
(и, вероятно, есть еще больше недостатков)
Так что лучшее, что я могу сказать, это то, что это обходной путь, который решает одну проблему и создает несколько других.
С учетом вышесказанного, я попросил решение, и исходя из того, как работает Ansible в данный момент, предоставленный обходной путь (или какой-то подобный обходной путь) кажется единственным способом достичь того, что я запросил.
Я взвешу плюсы и минусы, чтобы решить, реализую ли я этот обходной путь или продолжу использовать его так, как он есть сейчас.
Существует способ достичь желаемого поведения в нескольких задачах с помощью обработки ошибок ansible. Вы можете register
вывод задачи с no_log
и вывести только неконфиденциальную часть в последующем debug
:
- hosts: localhost
connection: local
tasks:
- block:
- shell: "/bin/false"
loop:
- name: "item1"
some_value: "my value"
secret: "p4$$w0rd"
- name: "item2"
some_value: "my value"
secret: "p4$$w0rd"
loop_control:
label: "{{ item.name }}"
# Захватить вывод задачи
register: my_task
no_log: true
# Запустить это, если предыдущая задача завершилась неудачно
rescue:
# Вывести сообщение, stderr или что-то еще
- debug:
msg: "{{ item }}"
loop: "{{ my_task | json_query('results[*].msg') }}"
# наконец завершить выполнение блока
- fail:
В случае, если вам нужно всегда выводить вывод (не только в случае неудачи задачи), используйте always
вместо rescue
и задачу fail
по условию:
- hosts: localhost
connection: local
tasks:
- block:
- shell: "/bin/true"
loop:
- name: "item1"
some_value: "my value"
secret: "p4$$w0rd"
- name: "item2"
some_value: "my value"
secret: "p4$$w0rd"
loop_control:
label: "{{ item.name }}"
register: my_task
no_log: true
always:
- debug:
msg: "{{ item }}"
loop: "{{ my_task | json_query('results[*].msg') }}"
- fail:
when: my_task['failed'] | default(false)
Обновление (2021-04-20)
Как указал Лукас, код выше имеет ряд недостатков. Основная идея заключалась в том, что вывод может быть зарегистрирован и отфильтрован позже. Другие части кода являются субъективными примерами. Безусловно, есть место для улучшений. Например, вот код, который устраняет проблемы 1, 4, 6 (и частично 2):
- hosts: localhost
connection: local
tasks:
- block:
- shell: "/bin/true"
register: my_task
no_log: true
# Зарегистрировать все ключи, связанные с циклом, чтобы те же настройки цикла могли быть повторно использованы в отладке
<<: &loop
loop:
- name: "item1"
some_value: "my value"
secret: "p4$$w0rd"
- name: "item2"
some_value: "my value"
secret: "p4$$w0rd"
loop_control:
label: "{{ item.name }}"
index_var: index
always:
- debug:
# сопоставить результат с элементом по индексу
msg: "{{ my_task['results'][index]['msg'] | default('ok') }}"
# повторно использовать цикл из основной задачи
<<: *loop
- fail:
when: my_task['failed'] | default(false)
На данный момент, похоже, нет способа реализовать желаемое поведение без обходных путей. Ansible рекомендует записывать логи в безопасное местоположение, если они содержат конфиденциальные данные.
Вы можете контролировать метку каждого элемента, которую Ansible выводит, с помощью loop_control
. Например:
- name: Задача
loop: {{ data | dict2items }}
loop_control:
label: "{{ item.key }}"
ansible.builtin.debug:
msg: "{{ item }}"
Это выведет только ключ каждого элемента вместо потенциально большого или конфиденциального значения каждого элемента.
Ответ или решение
В Ansible существует несколько способов обработки ошибок в циклах, при этом важно скрыть конфиденциальные данные, не теряя при этом полезную информацию о произошедших ошибках. Вот один из подходов, который позволит вам контролировать журналирование при выполнении задач с циклами.
Решение с использованием блока обработки ошибок
-
Использование блока: С помощью блока (
block
) вы можете выполнять задачи и обрабатывать ошибки, сохраняя их вывод для последующего анализа. -
Регистрация вывода: Установите
no_log: true
для задачи, где может содержаться конфиденциальная информация, и зарегистрируйте вывод, чтобы использовать его позже. -
Обработка ошибок: Используйте раздел
rescue
илиalways
, чтобы обработать ошибки и вывод, используяdebug
для отображения только необходимой информации без конфиденциальных данных.
Пример выполнения
- hosts: localhost
connection: local
tasks:
- block:
- ansible.builtin.shell: "/usr/bin/some_command"
loop:
- name: "item1"
some_value: "my value"
secret: "p4$$w0rd"
throw_error: true # Для имитации ошибки
- name: "item2"
some_value: "my value"
secret: "another_secret"
throw_error: false
loop_control:
label: "{{ item.name }}"
register: my_task
no_log: true
rescue:
- name: Показать информацию об ошибке
debug:
msg: "Ошибка при обработке {{ item.name }}: {{ item }}"
loop: "{{ my_task.results }}"
always:
- name: Показать успешные результаты
debug:
msg: "{{ item.name }} завершено с результатом: {{ item.rc | default('успешно') }}"
loop: "{{ my_task.results }}"
Пояснения к коду
- Блок: Задача сгруппирована в блок, что позволяет легче обрабатывать ошибки.
- no_log: Скрывает детали выполнения, включая конфиденциальные данные.
- Регистр: Все результаты сохраняются в переменной
my_task
, что позволяет использовать их в обработке ошибок. - Реску: В этом разделе осуществляется вывод информации об ошибках, где отражается только название элемента, а не секретные данные.
- Альтернативный вывод: В разделе
always
вы можете показывать результаты успешно выполненных задач.
Заключение
К сожалению, на данный момент в Ansible нет полного механизма для selectively скрытия конфиденциальной информации при возникновении ошибок без использования каких-либо обходных путей. Поэтому предложенное решение является оптимальным вариантом, который сочетает в себе безопасность и возможность отладки. Это подход также позволяет легко поддерживать код, поскольку он остается чистым и читаемым.