Identificando e Corrigindo Gargalos em Playbooks Lentos do Ansible

Acelere drasticamente suas implantações Ansible identificando e eliminando gargalos de desempenho. Este guia fornece etapas práticas, exemplos de configuração e melhores práticas para perfilagem de playbooks lentos, otimização de coleta de fatos, gerenciamento de conexões e ajuste de execução de tarefas. Aprenda a aproveitar os recursos do Ansible para automação de infraestrutura eficiente e rápida.

Identificando e Corrigindo Gargalos em Playbooks Lentos do Ansible

Playbooks lentos do Ansible são frustrantes porque o atraso raramente está em um lugar óbvio. Uma execução pode gastar alguns segundos coletando fatos, mais alguns abrindo conexões SSH e, em seguida, minutos copiando arquivos um host de cada vez. Se você apenas adivinhar, geralmente ajusta a coisa errada.

Comece medindo onde o tempo é gasto. Em seguida, corrija a maior fonte de atraso primeiro. Em um ambiente pequeno, isso pode ser uma única tarefa de shell que executa um gerenciador de pacotes toda vez. Em um ambiente maior, geralmente é a configuração de conexão, coleta de fatos, baixo forks ou um playbook que serializa o trabalho mais do que o pretendido.

Compreendendo as Métricas de Desempenho do Ansible

Antes de mergulhar em técnicas de otimização específicas, é crucial entender como medir e interpretar o desempenho do Ansible. O Ansible fornece informações de tempo integradas que podem ser inestimáveis para diagnósticos.

Use a Saída de Tempo Antes de Logs Detalhados

Uma saída muito detalhada pode ajudar com problemas de conexão, mas é ruidosa para o trabalho de desempenho. Uma primeira passagem mais limpa é o callback profile_tasks, que mostra as durações das tarefas no final da execução.

No ansible.cfg:

[defaults]
callbacks_enabled = profile_tasks

Em seguida, execute o playbook normalmente:

ansible-playbook my_playbook.yml

Observe as tarefas mais lentas primeiro. Se uma tarefa leva a maior parte da execução, não passe a manhã debatendo forks.

Controlando a Verbosidade da Saída

Use -vvv quando precisar ver detalhes do SSH, comportamento de transferência de módulo, novas tentativas ou descoberta de interpretador. Para tempo de rotina, isso pode esconder o sinal sob páginas de saída de log.

Gargalos Comuns e Estratégias de Otimização

Vários fatores podem contribuir para playbooks lentos do Ansible. Aqui, exploraremos gargalos comuns e forneceremos estratégias acionáveis para resolvê-los.

1. Coleta Excessiva de Fatos

Por padrão, o Ansible coleta fatos (informações do sistema) dos hosts gerenciados no início de cada play. Embora útil, isso pode ser demorado, especialmente em um grande número de hosts ou redes lentas. Se o seu playbook não requer todos os fatos coletados, você pode desabilitar ou limitar a coleta de fatos.

Desabilitando a Coleta de Fatos

Para desabilitar completamente a coleta de fatos para um play, use a diretiva gather_facts: no:

- name: Meu Playbook
  hosts: webservers
  gather_facts: no
  tasks:
    - name: Garantir que o Apache esteja instalado
      apt: name=apache2 state=present

Limitando a Coleta de Fatos

Se você precisar de alguns fatos, mas não de todos, pode especificar quais fatos coletar usando gather_subset.

- name: Meu Playbook
  hosts: webservers
  gather_facts: yes
  gather_subset:
    - '!all'
    - '!any'
    - hardware
    - network
  tasks:
    - name: Usar fatos de rede
      debug: var=ansible_default_ipv4.address

Armazenando Fatos em Cache

Para ambientes onde os fatos não mudam com frequência, armazená-los em cache pode acelerar drasticamente as execuções subsequentes do playbook. O Ansible suporta vários plugins de cache de fatos (por exemplo, jsonfile, redis, memcached).

Para habilitar o cache de fatos, configure-o no seu arquivo ansible.cfg:

[defaults]
fact_caching = jsonfile
fact_caching_connection = /caminho/para/ansible/facts_cache
fact_caching_timeout = 86400 # Cache por 24 horas

Em seguida, seu playbook usará automaticamente os fatos em cache quando disponíveis.

2. Execução Ineficiente de Tarefas

Algumas tarefas podem ser inerentemente lentas, ou podem ser executadas de maneira ineficiente.

Execução Paralela (Forking)

O comportamento padrão do Ansible é executar tarefas em hosts sequencialmente dentro de um play. Você pode aumentar o número de processos paralelos (forks) que o Ansible usa para gerenciar hosts simultaneamente. Isso é controlado pela configuração forks no ansible.cfg ou através da opção de linha de comando -f.

ansible.cfg:

[defaults]
forks = 10

Linha de comando:

ansible-playbook my_playbook.yml -f 10

Dica: Comece com um número moderado de forks e aumente gradualmente enquanto observa o nó de controle, a rede e o serviço de destino. Mais forks podem tornar uma implantação mais rápida, mas também podem sobrecarregar um repositório de pacotes, balanceador de carga ou etapa de migração de banco de dados.

Idempotência e Gerenciamento de Estado

Garanta que suas tarefas sejam idempotentes. Isso significa que executar uma tarefa várias vezes deve ter o mesmo efeito que executá-la uma vez. Os módulos do Ansible são geralmente projetados para serem idempotentes, mas scripts ou comandos personalizados podem não ser. Verificações ineficientes dentro das tarefas também podem adicionar sobrecarga.

Por exemplo, em vez de executar um comando que verifica se um serviço está em execução e depois o inicia, use o módulo service dedicado:

Ineficiente:

- name: Iniciar serviço (verificação ineficiente)
  command: systemctl start my_service.service || true
  when: "'inactive' in service_status.stdout"
  register: service_status
  changed_when: false # Esta tarefa não altera o estado

Eficiente (usando o módulo service):

- name: Garantir que my_service esteja em execução
  service:
    name: my_service
    state: started

Usando async e poll para Operações de Longa Duração

Para tarefas que podem levar muito tempo para serem concluídas (por exemplo, atualizações de pacotes, migrações de banco de dados), usar as diretivas async e poll do Ansible pode evitar que seu playbook trave.

  • async: Especifica o tempo máximo que a tarefa deve ser executada em segundo plano.
  • poll: Especifica com que frequência o Ansible deve verificar o status da tarefa assíncrona.
- name: Executar uma operação de longa duração
  command: /usr/local/bin/long_script.sh
  async: 3600 # Executar por no máximo 1 hora
  poll: 60    # Verificar status a cada 60 segundos

3. Otimização de Conexão

Como o Ansible se conecta aos seus nós gerenciados desempenha um papel crucial no desempenho.

Multiplexação de Conexão SSH

A multiplexação SSH (ControlMaster) permite que várias sessões SSH compartilhem uma única conexão de rede. Isso pode acelerar significativamente as conexões subsequentes ao mesmo host.

Habilite no seu ansible.cfg:

[ssh_connection]
control_master = auto
control_path = ~/.ansible/cp/ansible-%%r@%%h:%%p
control_persist = 600 # Manter a conexão de controle aberta por 10 minutos

Tentativas e Tempo Limite SSH

Ajustar os parâmetros de conexão SSH pode evitar atrasos desnecessários quando os hosts estão temporariamente indisponíveis.

[ssh_connection]
sf_retries = 3
sf_delay = 1
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o ConnectionAttempts=5 -o ConnectTimeout=10

Usando pipelining

O pipelining permite que o Ansible execute comandos diretamente no host remoto sem criar uma nova sessão SSH para cada comando. Isso pode reduzir drasticamente a sobrecarga para muitas tarefas.

Habilite no ansible.cfg:

[ssh_connection]
pipelining = True

Aviso: O pipelining pode entrar em conflito com algumas configurações de escalonamento de privilégios, especialmente quando requiretty está habilitado para sudo em distribuições mais antigas. Teste com o mesmo caminho become que seus playbooks de produção usam.

4. Otimizando a Estrutura e Lógica do Playbook

Às vezes, a maneira como um playbook é escrito pode ser a fonte da lentidão.

Usando delegate_to e run_once

Se uma tarefa precisa ser executada apenas em um host, mas afeta vários outros (por exemplo, reiniciar um balanceador de carga), use delegate_to e run_once para executá-la de forma eficiente.

- name: Reiniciar balanceador de carga
  service: name=haproxy state=restarted
  delegate_to: lb_server_1
  run_once: true

Uso Estratégico de Roles e Includes

Embora roles e includes ajudem na organização, includes profundamente aninhados ou estruturados de forma ineficiente podem adicionar uma pequena sobrecarga. Certifique-se de que suas dependências de role e lógica de include sejam limpas.

Palavra-chave serial

A palavra-chave serial limita o número de hosts que podem ser afetados simultaneamente dentro de um play. Embora frequentemente usada para implantações controladas, também pode ser um gargalo se definida muito baixa para o desempenho desejado.

- name: Implantar aplicativo em um subconjunto de servidores
  hosts: appservers
  serial: 2 # Executar apenas em 2 hosts por vez
  tasks:
    - name: Atualizar código do aplicativo
      copy: src=app/ dest=/opt/app/

Se você não está limitando intencionalmente o paralelismo, certifique-se de que serial não esteja definido ou esteja definido com um número alto o suficiente.

Corrija Tarefas Lentas, Não Apenas Transporte Lento

O ajuste de conexão ajuda quando o playbook tem muitas tarefas curtas. Não corrige uma tarefa que faz muito trabalho toda vez.

Um exemplo comum é usar shell para executar um comando de pacote:

- name: Instalar nginx com shell
  shell: apt-get update && apt-get install -y nginx

Essa tarefa é difícil para o Ansible raciocinar. Pode relatar alterado toda vez, pode atualizar metadados do pacote a cada execução e fornece menos informações estruturadas sobre falhas. Prefira módulos que entendem estado:

- name: Atualizar cache apt quando necessário
  apt:
    update_cache: true
    cache_valid_time: 3600

- name: Instalar nginx
  apt:
    name: nginx
    state: present

A mesma ideia se aplica à implantação de arquivos. Copiar um diretório grande com centenas de arquivos pequenos através do módulo copy pode ser lento porque o Ansible verifica e transfere arquivo por arquivo. Para lançamentos de aplicativos, pode ser mais rápido construir um artefato uma vez, fazer upload do arquivo e descompactá-lo no destino:

- name: Fazer upload do artefato de lançamento
  copy:
    src: dist/app.tar.gz
    dest: /tmp/app.tar.gz

- name: Descompactar lançamento
  unarchive:
    src: /tmp/app.tar.gz
    dest: /opt/app
    remote_src: true

Isso nem sempre é o design certo, mas é a pergunta certa: você está pedindo ao Ansible para sincronizar milhares de pequenas decisões quando um artefato seria mais claro?

Verifique o Inventário e o Trabalho de Variáveis

O inventário dinâmico pode ser outro atraso oculto. Se toda execução de playbook chama uma API de nuvem, espera pela paginação e reconstrói toda a lista de hosts, o playbook pode parecer lento antes mesmo da primeira tarefa começar. Armazene dados de inventário em cache quando seu plugin suportar e mantenha padrões de host estreitos. Executar uma implantação web contra all e depois pular a maioria dos hosts com condições when desperdiça tempo.

O carregamento de variáveis também pode ficar confuso. Arquivos grandes group_vars/all.yml, lookups caros e renderização repetida de templates podem se acumular. Se um lookup acessa um gerenciador de segredos ou endpoint HTTP, armazene o resultado em uma variável uma vez por play em vez de chamá-lo em muitas tarefas.

Ferramentas e Técnicas de Perfilagem

Além da saída detalhada do próprio Ansible, a perfilagem dedicada pode oferecer insights mais profundos.

ansible-playbook --syntax-check

Este comando verifica seu playbook em busca de erros de sintaxe, mas não o executa. É uma maneira rápida de validar a estrutura do seu playbook antes de uma execução completa.

Registrando Eventos do Ansible

O Ansible pode registrar seus eventos de execução em um arquivo, que pode ser analisado posteriormente. Isso é particularmente útil para playbooks de longa duração ou para auditoria.

Configure o registro de eventos no ansible.cfg:

[defaults]
log_path = /var/log/ansible.log

Plugins de Callback Personalizados

Para perfilagem avançada, você pode escrever plugins de callback personalizados para capturar métricas específicas ou criar relatórios personalizados sobre a execução do playbook.

Use Async para Esperar, Não para Tudo

Parte do tempo do playbook é espera real: uma reinicialização de serviço, uma compilação de pacote, uma instância de nuvem ficando pronta ou uma migração de banco de dados que leva alguns minutos. Se essas tarefas não precisam bloquear todos os hosts em sincronia, o async e poll do Ansible podem ajudar.

- name: Iniciar geração de relatório de longa duração
  command: /opt/tools/build-report
  async: 1800
  poll: 0
  register: report_job

- name: Verificar trabalho do relatório
  async_status:
    jid: "{{ report_job.ansible_job_id }}"
  register: report_status
  until: report_status.finished
  retries: 60
  delay: 10

Use isso com cuidado. Async não é um atalho para tornar tarefas inseguras paralelas. Se dez hosts iniciarem uma migração de banco de dados ao mesmo tempo, o playbook pode terminar mais rápido e ainda quebrar o ambiente. Async funciona melhor para trabalho independente, onde o destino pode continuar com segurança enquanto o Ansible verifica mais tarde.

Meça do Ponto de Vista do Usuário

Um playbook pode ser tecnicamente mais rápido e ainda parecer lento se o operador esperar muito tempo antes de ver feedback útil. Divida uma implantação grande em fases com nomes de tarefas claros: verificações de pré-voo, upload de artefato, atualização de serviço, verificação de saúde, limpeza. Quando uma fase é lenta, a saída do perfil e o humano lendo o terminal entendem para onde o tempo foi.

Isso também ajuda nas decisões de reversão. Se o playbook gasta 12 minutos antes da primeira verificação de saúde, você pode estar descobrindo falhas tarde demais. Uma pequena tarefa de pré-voo que verifica espaço em disco, acesso ao repositório de pacotes e credenciais de serviço pode economizar muito mais tempo do que reduzir um segundo na configuração do SSH.

O melhor trabalho de desempenho do Ansible é chato de uma boa maneira: habilite o tempo das tarefas, encontre a etapa mais lenta, mude uma coisa e meça novamente. Desabilite fatos apenas quando não precisar deles. Aumente forks apenas quando os destinos e dependências puderem lidar com o paralelismo. Substitua comandos de shell ruidosos por módulos com reconhecimento de estado. Use multiplexação SSH e pipelining depois de confirmar que a sobrecarga de conexão é realmente parte do problema.

Essa disciplina mantém o playbook legível enquanto ainda o torna mais rápido. Uma implantação que termina rapidamente, mas que ninguém entende, é apenas a interrupção de amanhã com uma barra de progresso mais curta.