Максимизация производительности Ansible с помощью ControlPersist и Pipelining

Значительно повысьте производительность ваших плейбуков Ansible, включив повторное использование SSH-соединений с ControlPersist и оптимизировав выполнение модулей с помощью Pipelining. Это руководство содержит важные сведения и практические конфигурации для сокращения времени выполнения, особенно в крупномасштабных средах. Узнайте, как настроить `ansible.cfg` для более быстрой и эффективной ИТ-автоматизации.

Максимизация производительности Ansible с помощью ControlPersist и Pipelining

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

Два параметра часто помогают: повторное использование SSH-соединений через ControlPersist и конвейеризация Ansible. Это не волшебные переключатели, и они не исправят медленные зеркала пакетов, перегруженные базы данных или задачи, выполняющие тяжелую работу. Они уменьшают неизбежные накладные расходы на связь, что как раз и является тем местом, где многие плейбуки с небольшими задачами тратят время впустую.

Сначала измерьте текущую проблему

Перед изменением конфигурации запустите плейбук один раз с включенным хронометражем:

ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks ansible-playbook site.yml

Если этот обратный вызов не установлен, используйте более простой встроенный вывод времени из вашей CI-системы или оберните команду time. Цель — не идеальный бенчмарк. Вам нужен базовый уровень и понимание того, являются ли медленные задачи реальной работой или крошечными задачами, повторяющимися на многих хостах.

Полезный дымовой тест — это ad-hoc ping по репрезентативному инвентарю:

time ansible all -m ping

Запустите его дважды. Если второй запуск будет намного быстрее после настройки повторного использования соединения, вы подтвердили, что затраты на настройку SSH были частью проблемы.

Что меняет ControlPersist

ControlPersist — это функция OpenSSH. Она держит мастер-соединение SSH открытым в течение определенного периода времени, чтобы последующие команды SSH к тому же хосту, пользователю и порту могли его повторно использовать. Ansible обычно использует SSH для каждой задачи, поэтому мультиплексирование соединений устраняет повторные рукопожатия.

Практический ansible.cfg на уровне проекта выглядит так:

[defaults]
forks = 20

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r

Создайте каталог сокетов с частными правами доступа:

mkdir -p ~/.ansible/cp
chmod 700 ~/.ansible ~/.ansible/cp

ControlMaster=auto указывает SSH использовать существующее мастер-соединение, если оно доступно, и создавать новое, если его нет. ControlPersist=600s держит мастер-соединение в течение десяти минут после завершения последнего сеанса. Это значение не является священным. Для коротких CI-задач может быть достаточно нескольких минут. Для оператора, который многократно запускает плейбуки во время окна обслуживания, десять или пятнадцать минут могут быть удобными.

ControlPath важен больше, чем люди ожидают. Пути к Unix-сокетам имеют ограничения по длине во многих системах. Длинное имя хоста инвентаря внутри глубокого пути рабочего пространства может нарушить мультиплексирование с запутанной ошибкой. Хранение пути коротким, например, в ~/.ansible/cp, позволяет избежать этого класса ошибок.

Последние версии Ansible уже включают мультиплексирование SSH по умолчанию во многих обычных конфигурациях, но явные настройки в ansible.cfg проекта упрощают аудит поведения. Проверьте активную конфигурацию с помощью:

ansible-config dump --only-changed

Что меняет конвейеризация

Без конвейеризации Ansible часто копирует модуль во временный каталог на удаленном хосте, выполняет его, а затем очищает. С включенной конвейеризацией Ansible может передавать код модуля через SSH-соединение вместо записи такого количества временных файлов. Это экономит время на обмен данными и работу с удаленной файловой системой.

Включите это в том же файле:

[ssh_connection]
pipelining = True

Или протестируйте для одного запуска:

ANSIBLE_PIPELINING=True ansible-playbook site.yml

Настройка наиболее заметна, когда в плейбуке много маленьких модулей: file, lineinfile, template, user, service и короткие командные задачи. Это менее важно, когда задача тратит большую часть времени на установку пакетов, сборку программного обеспечения, передачу больших артефактов или ожидание внешнего сервиса.

Ловушка sudo requiretty

Классическая проблема конвейеризации — requiretty в sudoers. В некоторых старых корпоративных конфигурациях Linux требовался TTY для sudo. Конвейеризация плохо работает с этим требованием, потому что Ansible пытается передавать работу через SSH в неинтерактивном режиме.

Внимательно проверьте sudoers. Не редактируйте /etc/sudoers обычным редактором; используйте visudo:

sudo visudo

Если вы видите глобальную строку, подобную этой:

Defaults requiretty

Возможно, вам нужно удалить ее или переопределить для пользователя автоматизации Ansible:

Defaults:ansible !requiretty

Вносите это изменение только в том случае, если оно соответствует политике безопасности вашей организации. Во многих современных дистрибутивах requiretty не включен по умолчанию.

Более безопасная комбинированная конфигурация

Для многих команд это разумная отправная точка:

[defaults]
forks = 20
gathering = smart
fact_caching = jsonfile
fact_caching_connection = .ansible_facts
fact_caching_timeout = 86400

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
pipelining = True

forks управляет параллелизмом. Это связано с производительностью, но это не та же оптимизация. Увеличение forks с 5 до 20 может помочь, если ваш управляющий узел и сеть могут это выдержать. Слишком большое увеличение может перегрузить бастионные хосты, репозитории пакетов или сами управляемые узлы.

Кэширование фактов помогает при другом типе медлительности. Если ваши плейбуки собирают факты при каждом запуске, и факты не должны быть свежими каждую минуту, кэширование может удалить повторную работу по настройке. Используйте его осознанно; устаревшие факты могут сбивать с толку, если вы полагаетесь на текущие данные о памяти, диске или интерфейсе.

Как тестировать, не обманывая себя

Тестируйте на подмножестве, похожем на продакшн. Пять idle тестовых ВМ в одной подсети не скажут вам многого о сотне смешанных хостов за бастионом.

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

rm -f ~/.ansible/cp/*
time ansible-playbook site.yml --limit web

Затем выполните горячее сравнение:

time ansible-playbook site.yml --limit web

Ищите меньше секунд, потраченных на маленькие повторяющиеся задачи. Также следите за режимами сбоев. Если задачи, использующие become, начинают давать сбой, исследуйте конфигурацию sudo, прежде чем винить саму конвейеризацию.

Когда эти настройки не сильно помогут

ControlPersist и конвейеризация не ускоряют медленную удаленную работу. Если apt update ждет зеркало, повторное использование соединения вас не спасет. Если перезапуск службы ждет тридцать секунд, потому что приложение сбрасывает соединения, конвейеризация не имеет значения. Если ваш плейбук копирует артефакт размером 2 ГБ на каждый хост, сосредоточьтесь на распространении артефактов, кэшировании или локальных репозиториях пакетов.

Они также не заменяют идемпотентный дизайн плейбука. Плейбук, который безусловно выполняет команды оболочки, все равно будет тратить время впустую. Используйте модули, которые могут обнаруживать текущее состояние, добавляйте creates или removes к командным задачам, когда это уместно, и избегайте сбора фактов, когда плейс их не использует.

Практический подход прост: включите ControlPersist с коротким, частным путем управления; протестируйте конвейеризацию с вашей политикой sudo; постепенно настраивайте forks; и измеряйте с одним и тем же инвентарем и рабочей нагрузкой каждый раз. Во многих реальных средах Ansible эти изменения превращают вялый запуск в терпимый, потому что они удаляют повторяющиеся накладные расходы, а не скрывают их.

Бастионные хосты и хосты перехода

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

Общая переменная инвентаря выглядит так:

[private]
app01 ansible_host=10.0.10.11
app02 ansible_host=10.0.10.12

[private:vars]
ansible_user=ansible
ansible_ssh_common_args='-o ProxyJump=bastion.example.com'

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

Для старых SSH-клиентов вы можете увидеть ProxyCommand вместо ProxyJump:

ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p bastion.example.com"'

Протестируйте обычное SSH-соединение, прежде чем винить Ansible:

ssh -o ProxyJump=bastion.example.com [email protected] hostname

Если это медленно, Ansible тоже будет медленным.

Конвейеризация и become в реальных плейбуках

Старый совет о том, что конвейеризация не работает с повышением привилегий, слишком общий. Конвейеризация может работать с become; обычным препятствием является sudo, требующее TTY, или политика, которая мешает неинтерактивному выполнению. Единственный надежный ответ — протестировать с теми же настройками become, которые используются в ваших плейбуках.

Достаточно небольшого тестового плейса:

- hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Confirm privileged command works
      ansible.builtin.command: id
      changed_when: false

Запустите его с включенной конвейеризацией. Если это не удается, проверьте sudoers, запросы аутентификации и то, может ли пользователь автоматизации запускать необходимые команды без запроса пароля. Запросы пароля в крупных автоматизированных запусках ненадежны; они также делают измерения времени зашумленными.

Настройка forks с учетом зависимостей

Увеличение forks заманчиво, потому что это дает немедленный видимый эффект. Это также может создать новое узкое место. Если один плейс обновляет кэши пакетов на всех хостах одновременно, высокое количество forks может нагрузить зеркало пакетов. Если каждый хост перезапускается и подключается к той же базе данных одновременно, база данных видит всплеск.

Лучше использовать измеренный подход:

[defaults]
forks = 10

Запустите плейбук. Попробуйте 20. Затем 30. Следите за загрузкой ЦП управляющего узла, количеством процессов SSH, загрузкой бастиона, насыщением сети, репозиторием пакетов и изменяемым сервисом. Самая быстрая настройка — это не всегда та, которая обеспечивает наибольший параллелизм. Для развертывания приложений поочередно вы также можете использовать serial в плейбуке для защиты доступности:

- hosts: web
  serial: 10

forks контролирует, сколько Ansible может делать одновременно. serial контролирует, сколько хостов плейс должен обрабатывать за раз. Они решают разные проблемы.

Очистка устаревших управляющих сокетов

Управляющие сокеты обычно очищаются сами, но ноутбуки засыпают, CI-задачи убиваются, а сетевые пути меняются. Если SSH начинает сообщать об ошибках мультиплексирования, удалите устаревшие сокеты:

rm -f ~/.ansible/cp/*

Это безопасно, когда для этих сокетов не выполняется ни один запуск Ansible. В общих средствах автоматизации избегайте размещения управляющих сокетов в общем каталоге с возможностью записи. Каждый пользователь автоматизации должен иметь свой собственный частный путь.

Дизайн инвентаря может свести на нет прирост производительности

Настройка соединения помогает, но дизайн инвентаря все еще может все замедлить. Скрипты динамического инвентаря, которые медленно вызывают облачные API, групповые переменные, выполняющие дорогие поиски, и плейбуки, которые собирают факты для хостов, к которым они никогда не обращаются, могут добавить задержку до того, как SSH вообще запустится.

Если запуск приостанавливается перед первой задачей, профилируйте загрузку инвентаря. Кэшируйте динамический инвентарь, где плагин это поддерживает. Избегайте дорогих поисков переменных во время разбора. Держите шаблоны хостов точными:

ansible-playbook site.yml --limit web:&prod

Эта команда нацелена на хосты как в web, так и в prod. Запуск широкого плейбука против all и последующий пропуск большинства задач с условиями when тратит время и усложняет чтение вывода.

Предпочитайте меньшее количество более четких задач, когда состояние связано

Читаемость Ansible важна, но чрезмерное количество крошечных задач может сделать накладные расходы на соединение более заметными. Если вы устанавливаете десять связанных строк в одном файле конфигурации с помощью десяти отдельных задач lineinfile, вы платите накладные расходы на задачу десять раз и усложняете рассуждения об откате. Шаблон может быть понятнее и быстрее:

- name: Render application config
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
    mode: '0644'
  notify: restart app

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

Избегайте преждевременных глобальных изменений

По возможности помещайте настройки производительности в ansible.cfg проекта. Изменение /etc/ansible/ansible.cfg на общем управляющем узле может удивить другие команды. Файл на уровне проекта делает поведение переносимым с репозиторием и приближает конфигурацию CI, ноутбуков и средств автоматизации друг к другу.

Подтвердите, какой файл конфигурации использует Ansible:

ansible --version

Вывод включает активный путь конфигурации. Это ловит распространенную ошибку: редактирование одного ansible.cfg, в то время как Ansible читает другой.

Знайте, когда прекратить настройку Ansible

Если накладные расходы на соединение больше не являются основной частью времени выполнения, прекратите настройку SSH и посмотрите на работу. Обновления кэша пакетов, вытягивание контейнеров, миграции баз данных, проверки работоспособности служб и вызовы облачных API часто доминируют в зрелых плейбуках. На этом этапе лучшей оптимизацией может быть локальное зеркало пакетов, кэширование артефактов, меньшие пакеты развертывания или перемещение одноразовой подготовки из каждого запуска развертывания.