Как не выводить элементы в ошибках цикла Ansible без no_log

Вопрос или проблема

Когда я выполняю модуль 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)

Ответ Владимира на самом деле работает как обходной путь, но у него есть несколько недостатков:

  1. Вам нужно установить библиотеку jmespath перед выполнением фильтра json_query.
  2. Это требует больше кода и делает код в целом менее читаемым.
  3. Если у вас есть много циклов (в моем конкретном проекте их более 80), код может стать действительно неаккуратным (также из-за пункта 2).
  1. Если у вас есть цикл в родительском файле, вам нужно использовать loop_control с loop_var в отладочном цикле.
  2. Ошибка и место, где отображается сообщение об ошибке, будут в разных местах. То же самое относится к сообщениям об успехе.
  3. Метка цикла label в задаче debug будет отличаться от той, что в основной задаче цикла (что может сделать сложным сопоставление случаев успеха и ошибок с фактическими элементами).
  4. Две задачи будут выводиться излишне, когда все в порядке (при этом сообщения об успехе выводятся отдельно от самих задач, что ухудшает вывод, когда все проходит успешно, что должно быть в большинстве случаев).
  5. Изменение и тестирование существующих задач цикла, а также приведение любых новых циклов к этому формату будет бременем для поддержки (особенно из-за пункта 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 существует несколько способов обработки ошибок в циклах, при этом важно скрыть конфиденциальные данные, не теряя при этом полезную информацию о произошедших ошибках. Вот один из подходов, который позволит вам контролировать журналирование при выполнении задач с циклами.

Решение с использованием блока обработки ошибок

  1. Использование блока: С помощью блока (block) вы можете выполнять задачи и обрабатывать ошибки, сохраняя их вывод для последующего анализа.

  2. Регистрация вывода: Установите no_log: true для задачи, где может содержаться конфиденциальная информация, и зарегистрируйте вывод, чтобы использовать его позже.

  3. Обработка ошибок: Используйте раздел 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 скрытия конфиденциальной информации при возникновении ошибок без использования каких-либо обходных путей. Поэтому предложенное решение является оптимальным вариантом, который сочетает в себе безопасность и возможность отладки. Это подход также позволяет легко поддерживать код, поскольку он остается чистым и читаемым.

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

Капча загружается...