Maximizando o Desempenho do Ansible com ControlPersist e Pipelining
Aumente significativamente o desempenho dos seus playbooks Ansible habilitando a reutilização de conexão SSH com ControlPersist e otimizando a execução de módulos via Pipelining. Este guia fornece insights essenciais e configurações práticas para reduzir os tempos de execução, especialmente em ambientes de grande escala. Aprenda a ajustar seu `ansible.cfg` para uma automação de TI mais rápida e eficiente.
Maximizando o Desempenho do Ansible com ControlPersist e Pipelining
Execuções lentas do Ansible geralmente parecem misteriosas até que você observe o que está acontecendo na rede. Um playbook com vinte tarefas pequenas em cem hosts pode gastar uma quantidade surpreendente de tempo abrindo sessões SSH, copiando arquivos de módulo temporários, executando Python, coletando saída e fechando conexões. O trabalho no host remoto pode levar milissegundos, mas a sobrecarga de conexão se repete várias vezes.
Duas configurações geralmente ajudam: reutilização de conexão SSH através do ControlPersist e pipelining do Ansible. Elas não são interruptores mágicos e não corrigirão mirrors de pacotes lentos, bancos de dados sobrecarregados ou tarefas que realizam trabalho pesado. Elas reduzem a sobrecarga de comunicação evitável, que é exatamente onde muitos playbooks com tarefas pequenas perdem tempo.
Primeiro, meça a dor atual
Antes de alterar a configuração, execute o playbook uma vez com a temporização ativada:
ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks ansible-playbook site.yml
Se esse callback não estiver instalado, use a saída de temporização integrada mais simples do seu sistema de CI ou envolva o comando com time. O objetivo não é um benchmark perfeito. Você quer uma linha de base e uma noção de se as tarefas lentas são trabalho real ou tarefas minúsculas repetidas em muitos hosts.
Um teste de fumaça útil é um ping ad-hoc em um inventário representativo:
time ansible all -m ping
Execute-o duas vezes. Se a segunda execução for muito mais rápida após a reutilização de conexão ser configurada, você confirmou que o custo de configuração do SSH era parte do problema.
O que o ControlPersist muda
ControlPersist é um recurso do OpenSSH. Ele mantém uma conexão SSH mestre aberta por um período de tempo para que comandos SSH posteriores para o mesmo host, usuário e porta possam reutilizá-la. O Ansible comumente usa SSH para cada tarefa, então a multiplexação de conexão remove handshakes repetidos.
Um ansible.cfg prático em nível de projeto se parece com isso:
[defaults]
forks = 20
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
Crie o diretório do socket com permissões privadas:
mkdir -p ~/.ansible/cp
chmod 700 ~/.ansible ~/.ansible/cp
ControlMaster=auto diz ao SSH para usar uma conexão mestre existente quando disponível e criar uma quando não estiver. ControlPersist=600s mantém o mestre por dez minutos após a última sessão sair. Esse valor não é sagrado. Para jobs curtos de CI, alguns minutos podem ser suficientes. Para um operador executando playbooks repetidamente durante uma janela de manutenção, dez ou quinze minutos podem ser confortáveis.
O ControlPath importa mais do que as pessoas esperam. Os caminhos de socket Unix têm limites de comprimento em muitos sistemas. Um nome de host de inventário longo dentro de um caminho de workspace profundo pode quebrar a multiplexação com um erro confuso. Manter o caminho curto, como em ~/.ansible/cp, evita essa classe de falha.
Versões recentes do Ansible já habilitam a multiplexação SSH por padrão em muitas configurações normais, mas configurações explícitas em um ansible.cfg de projeto tornam o comportamento mais fácil de auditar. Verifique a configuração ativa com:
ansible-config dump --only-changed
O que o pipelining muda
Sem pipelining, o Ansible geralmente copia um módulo para um diretório temporário no host remoto, executa e depois limpa. Com o pipelining ativado, o Ansible pode passar o código do módulo através da conexão SSH em vez de escrever tantos arquivos temporários. Isso economiza viagens de ida e volta e trabalho no sistema de arquivos remoto.
Ative-o no mesmo arquivo:
[ssh_connection]
pipelining = True
Ou teste-o para uma execução:
ANSIBLE_PIPELINING=True ansible-playbook site.yml
A configuração é mais perceptível quando o playbook tem muitos módulos pequenos: file, lineinfile, template, user, service e tarefas de comando curtas. Importa menos quando uma tarefa passa a maior parte do tempo instalando pacotes, compilando software, transferindo artefatos grandes ou aguardando um serviço externo.
A armadilha do sudo requiretty
O problema clássico do pipelining é requiretty no sudoers. Algumas configurações mais antigas de Linux empresarial exigiam um TTY para sudo. O pipelining não funciona bem com esse requisito porque o Ansible está tentando transmitir trabalho através do SSH de forma não interativa.
Verifique o sudoers cuidadosamente. Não edite /etc/sudoers com um editor normal; use visudo:
sudo visudo
Se você vir uma linha global como esta:
Defaults requiretty
Você pode precisar removê-la ou substituí-la para o usuário de automação do Ansible:
Defaults:ansible !requiretty
Faça essa alteração apenas se corresponder à política de segurança da sua organização. Em muitas distribuições modernas, requiretty não está ativado por padrão.
Uma configuração combinada mais segura
Para muitas equipes, este é um ponto de partida razoável:
[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 controla o paralelismo. Está relacionado ao desempenho, mas não é a mesma otimização. Aumentar forks de 5 para 20 pode ajudar se o seu nó de controle e rede puderem suportar. Aumentá-lo demais pode sobrecarregar hosts bastiões, repositórios de pacotes ou os próprios nós gerenciados.
O cache de fatos ajuda um tipo diferente de lentidão. Se seus playbooks coletam fatos em cada execução e os fatos não precisam estar atualizados a cada minuto, o cache pode remover o trabalho de configuração repetido. Use-o deliberadamente; fatos desatualizados podem ser confusos se você depender de dados atuais de memória, disco ou interface.
Como testar sem se enganar
Teste em um subconjunto que se pareça com a produção. Cinco VMs de teste ociosas na mesma sub-rede não lhe dirão muito sobre cem hosts mistos atrás de um bastião.
Execute o mesmo playbook antes e depois da alteração. Limpe os sockets de controle existentes entre as execuções de teste se precisar de uma comparação a frio:
rm -f ~/.ansible/cp/*
time ansible-playbook site.yml --limit web
Em seguida, execute uma comparação a quente:
time ansible-playbook site.yml --limit web
Procure por menos segundos gastos em pequenas tarefas repetidas. Observe também os modos de falha. Se tarefas que usam become começarem a falhar, investigue a configuração do sudo antes de culpar o pipelining em si.
Quando essas configurações não ajudarão muito
ControlPersist e pipelining não tornam o trabalho remoto lento rápido. Se apt update espera em um mirror, a reutilização de conexão não vai te salvar. Se uma reinicialização de serviço espera trinta segundos porque o aplicativo drena conexões, o pipelining é irrelevante. Se seu playbook copia um artefato de 2 GB para cada host, concentre-se na distribuição de artefatos, cache ou repositórios de pacotes locais.
Eles também não substituem o design de playbook idempotente. Um playbook que executa comandos shell incondicionalmente ainda perderá tempo. Use módulos que podem detectar o estado atual, adicione creates ou removes a tarefas de comando quando apropriado e evite coletar fatos quando o play não os usar.
A abordagem prática é simples: ative o ControlPersist com um caminho de controle curto e privado; teste o pipelining com sua política de sudo; ajuste forks gradualmente; e meça com o mesmo inventário e carga de trabalho a cada vez. Em muitos ambientes Ansible reais, essas mudanças transformam uma execução lenta em uma tolerável porque removem a sobrecarga repetida em vez de escondê-la.
Hosts bastiões e hosts de salto
Muitos inventários não se conectam diretamente aos nós gerenciados. Eles passam por um bastião. O ControlPersist ainda ajuda, mas você precisa pensar em ambas as conexões: nó de controle para bastião e bastião para destino.
Uma variável de inventário comum se parece com isso:
[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'
Se cada tarefa constrói repetidamente uma conexão de salto, o bastião se torna parte da sobrecarga. Mantenha o caminho de controle curto e privado, e observe os limites de conexão SSH do bastião. Um playbook que funciona bem em dez hosts pode sobrecarregar um bastião pequeno quando forks é aumentado para cinquenta.
Para clientes SSH mais antigos, você pode ver ProxyCommand em vez de ProxyJump:
ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p bastion.example.com"'
Teste uma conexão SSH simples antes de culpar o Ansible:
ssh -o ProxyJump=bastion.example.com [email protected] hostname
Se isso for lento, o Ansible também será.
Pipelining e become em playbooks reais
O conselho antigo de que o pipelining não funciona com escalonamento de privilégios é muito amplo. O pipelining pode funcionar com become; o bloqueador comum é o sudo exigindo um TTY ou uma política que interfira na execução não interativa. A única resposta confiável é testar com as mesmas configurações de become que seus playbooks usam.
Um pequeno play de teste é suficiente:
- hosts: all
become: true
gather_facts: false
tasks:
- name: Confirm privileged command works
ansible.builtin.command: id
changed_when: false
Execute-o com pipelining ativado. Se falhar, verifique o sudoers, prompts de autenticação e se o usuário de automação pode executar os comandos necessários sem um prompt de senha. Prompts de senha em execuções de automação grandes são frágeis; eles também tornam as medições de temporização ruidosas.
Ajuste forks com respeito às dependências
Aumentar forks é tentador porque produz uma mudança visível imediata. Também pode criar um novo gargalo. Se um play atualiza caches de pacotes em todos os hosts de uma vez, uma contagem alta de forks pode punir o mirror de pacotes. Se cada host reinicia e se reconecta ao mesmo banco de dados de uma vez, o banco de dados vê o impacto.
Uma abordagem medida é melhor:
[defaults]
forks = 10
Execute o play. Tente 20. Depois 30. Observe a CPU do nó de controle, contagem de processos SSH, carga do bastião, saturação da rede, repositório de pacotes e o serviço que está sendo alterado. A configuração mais rápida nem sempre é a com o maior paralelismo. Para implantações de aplicativos contínuas, você também pode querer serial no playbook para proteger a disponibilidade:
- hosts: web
serial: 10
forks controla quanto o Ansible pode fazer de uma vez. serial controla quantos hosts o play deve processar de cada vez. Eles resolvem problemas diferentes.
Limpe sockets de controle obsoletos
Os sockets de controle geralmente se limpam sozinhos, mas laptops dormem, jobs de CI são mortos e caminhos de rede mudam. Se o SSH começar a relatar erros de multiplexação, remova sockets obsoletos:
rm -f ~/.ansible/cp/*
Isso é seguro quando nenhuma execução do Ansible está ativa para esses sockets. Em runners de automação compartilhados, evite colocar sockets de controle em um diretório gravável compartilhado. Cada usuário de automação deve ter seu próprio caminho privado.
O design do inventário pode apagar ganhos de desempenho
A otimização de conexão ajuda, mas o design do inventário ainda pode desacelerar tudo. Scripts de inventário dinâmico que chamam APIs de nuvem lentamente, vars de grupo que executam lookups caros e playbooks que coletam fatos para hosts que nunca tocam podem adicionar atraso antes mesmo do SSH começar.
Se uma execução pausar antes da primeira tarefa, perfile o carregamento do inventário. Armazene em cache o inventário dinâmico onde o plugin suportar. Evite lookups de variáveis caros no momento da análise. Mantenha os padrões de host restritos:
ansible-playbook site.yml --limit web:&prod
Esse comando visa hosts em ambos web e prod. Executar um play amplo contra all e depois pular a maioria das tarefas com condições when perde tempo e torna a saída mais difícil de ler.
Prefira tarefas menos numerosas e mais claras quando o estado estiver relacionado
A legibilidade do Ansible importa, mas tarefas excessivamente pequenas podem tornar a sobrecarga de conexão mais visível. Se você definir dez linhas relacionadas em um arquivo de configuração com dez tarefas lineinfile separadas, você paga a sobrecarga da tarefa dez vezes e torna o raciocínio de reversão mais difícil. Um template pode ser mais claro e rápido:
- name: Render application config
ansible.builtin.template:
src: app.conf.j2
dest: /etc/app/app.conf
mode: '0644'
notify: restart app
Isso não é um argumento para scripts shell gigantes dentro do Ansible. É um lembrete para modelar o estado desejado no nível certo. Um template para um arquivo de configuração é muitas vezes melhor do que muitas micro-edições.
Evite mudanças globais prematuras
Coloque as configurações de desempenho no ansible.cfg do projeto quando possível. Alterar /etc/ansible/ansible.cfg em um nó de controle compartilhado pode surpreender outras equipes. Um arquivo em nível de projeto faz o comportamento viajar com o repositório e mantém CI, laptops e runners de automação mais próximos da mesma configuração.
Confirme qual arquivo de configuração o Ansible está usando:
ansible --version
A saída inclui o caminho da configuração ativa. Isso pega um erro comum: editar um ansible.cfg enquanto o Ansible está lendo outro.
Saiba quando parar de otimizar o Ansible
Se a sobrecarga de conexão não for mais uma parte importante do tempo de execução, pare de otimizar o SSH e olhe para o trabalho. Atualizações de cache de pacotes, pulls de contêineres, migrações de banco de dados, verificações de saúde de serviço e chamadas de API de nuvem geralmente dominam playbooks maduros. Nesse ponto, a melhor otimização pode ser um mirror de pacotes local, cache de artefatos, lotes de implantação menores ou mover o provisionamento único para fora de cada execução de deploy.