<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <docs>https://blogs.law.harvard.edu/tech/rss</docs>
    <title>Automação on FXShell - DevOps &amp; Sec</title>
    <link>https://fxshell.com.br/tags/automa%C3%A7%C3%A3o/</link>
    <description>Recent content in Automação on FXShell - DevOps &amp; Sec</description>
    <image>
      <title>Automação on FXShell - DevOps &amp; Sec</title>
      <link>https://fxshell.com.br/tags/automa%C3%A7%C3%A3o/</link>
      <url>fxshell.png</url>
    </image>
    <ttl>1440</ttl>
    <generator>Hugo 0.152.2</generator>
    <language>pt-br</language>
    <lastBuildDate>Thu, 14 May 2026 21:17:58 UT</lastBuildDate>
    <atom:link href="https://fxshell.com.br/tags/automa%C3%A7%C3%A3o/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Migração S3 - Azure Blob Storage com Ansible e Azure Data Factory</title>
      <link>https://fxshell.com.br/posts/ansible-adf-migrate-s3-azure/</link>
      <pubDate>Fri, 08 May 2026 00:00:00 UT</pubDate>
      <dc:creator>Felipe da Matta</dc:creator>
      <guid>https://fxshell.com.br/posts/ansible-adf-migrate-s3-azure/</guid>
      <description>Em um projeto de migração real, precisamos mover os dados de dezenas de buckets S3 de clientes para o Azure Blob Storage. O processo manual - criar o ADF no portal, configurar cada linked service, montar a pipeline, disparar e monitorar - era repetitivo e propenso a erro. A solução foi um único playbook Ansible que provisiona toda a infraestrutura e executa a cópia de ponta a ponta.
Objetivo do Lab Construir um playbook Ansible que automatiza completamente a migração de dados de um bucket AWS S3 para um container no Azure Blob Storage, usando o Azure Data Factory como motor de cópia. O playbook cria todos os recursos necessários, dispara a pipeline e faz polling do status até confirmar o sucesso (ou falha explícita).
</description>
      <content:encoded><![CDATA[Em um projeto de migração real, precisamos mover os dados de dezenas de buckets S3 de clientes para o Azure Blob Storage. O processo manual - criar o ADF no portal, configurar cada linked service, montar a pipeline, disparar e monitorar - era repetitivo e propenso a erro. A solução foi um único playbook Ansible que provisiona toda a infraestrutura e executa a cópia de ponta a ponta.
Objetivo do Lab Construir um playbook Ansible que automatiza completamente a migração de dados de um bucket AWS S3 para um container no Azure Blob Storage, usando o Azure Data Factory como motor de cópia. O playbook cria todos os recursos necessários, dispara a pipeline e faz polling do status até confirmar o sucesso (ou falha explícita).
Tecnologias Utilizadas Ansible é uma ferramenta de automação de infraestrutura sem agente. Ela executa tarefas em sequência, em formato YAML, sem precisar instalar nada nos servidores de destino. É amplamente usada para provisionar recursos em nuvem via CLI, configurar servidores e orquestrar fluxos de deploy.
Azure Data Factory (ADF) é o serviço de integração de dados da Microsoft. Ele permite criar pipelines visuais (ou via API/CLI) que copiam, transformam e movem dados entre fontes heterogêneas - como AWS S3 e Azure Blob Storage. O ADF escala automaticamente, tem retry nativo e gera logs detalhados para cada execução.
AWS S3 (Simple Storage Service) é o serviço de armazenamento de objetos da Amazon. Os dados ficam organizados em buckets, e o acesso externo é controlado por IAM com AccessKey e SecretKey.
Azure Blob Storage é o equivalente ao S3 na Azure. Dados são armazenados em containers dentro de uma Storage Account. Suporta acesso via connection string, Managed Identity ou SAS token.
az CLI é a interface de linha de comando da Azure. Permite criar e gerenciar recursos diretamente do terminal, sem usar o portal. O Ansible chama o az CLI via módulo command, que é a forma mais direta e sem dependências extras.
Arquitetura ┌─────────────────────────────────────────────────────────────────┐ │ AWS Account │ │ ┌─────────────┐ │ │ │ S3 Bucket │ ← s3:GetObject + s3:GetObjectVersion │ │ └──────┬──────┘ │ └─────────┼───────────────────────────────────────────────────────┘ │ │ HTTPS (LinkedService_S3 - AccessKey) ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Azure (Resource Group) │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Azure Data Factory (ADF) │ │ │ │ │ │ │ │ Dataset_S3 ──[CopyActivity]──► Dataset_Blob │ │ │ │ (BinarySource) (BinarySink) │ │ │ │ parallelCopies=4 / DIU=4 │ │ │ └──────────────────────────┬──────────────────────────────┘ │ │ │ │ │ │ LinkedService_BlobStorage │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Storage Account: stdadosmigrados │ │ │ │ └── Container: dados-migrados │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ▲ │ ansible-playbook playbook.yml -e @vars.yml │ ┌─────────┴──────────┐ │ Control Node │ │ Ansible + az CLI │ └────────────────────┘ Componente Função S3 Bucket Origem dos dados. IAM com s3:GetObject e s3:GetObjectVersion Azure Data Factory Orquestra a cópia. Gerencia linked services, datasets e pipeline LinkedService_S3 Credencial do ADF para autenticar no S3 (AccessKey) LinkedService_Blob Credencial do ADF para gravar no Blob (connection string) Dataset_S3 Define o caminho de origem: bucket + prefixo Dataset_Blob Define o destino: storage account + container Pipeline CopyActivity Executa a cópia com parallelCopies=4 e DIU=4 Storage Account Conta de armazenamento Azure que recebe os dados Container Namespace dentro da Storage Account (equivale a um bucket) Como as Partes se Conectam O ADF age como intermediário inteligente: ele autentica no S3 usando as credenciais do IAM, lê os objetos (recursivamente, se configurado), e grava em paralelo no Blob Storage usando a connection string da Storage Account. A pipeline usa formato Binary em ambos os lados - isso preserva os arquivos como estão, sem transformação, o que é exatamente o que queremos em uma migração de dados brutos.
O Ansible não move nenhum dado diretamente. Ele é responsável por orquestrar o provisionamento de todos os recursos no az CLI e, ao final, fazer polling do runId da pipeline até receber Succeeded ou Failed.
Estrutura do Projeto ansible-adf-migrate-s3-azure/ ├── playbook.yml # playbook principal - tudo em um só arquivo ├── vars.yml # variáveis não-sensíveis (pode commitar) ├── vars_vault.yml # credenciais AWS - criptografar com ansible-vault └── diagrama/ ├── gerar_gif.py ├── ansible-adf-migrate-s3-azure.gif └── ansible-adf-migrate-s3-azure.html Pré-requisitos Antes de executar, você precisa:
az CLI instalado e autenticado (az login) Ansible instalado (pip install ansible) IAM user na AWS com permissões: s3:GetObject s3:GetObjectVersion ansible-vault para criptografar as credenciais AWS Preparando as Variáveis vars.yml - configure com os valores do seu ambiente:
resource_group: &#34;rg-migrate-s3-azure&#34; location: &#34;eastus&#34; storage_account_name: &#34;stdadosmigrados&#34; container_name: &#34;dados-migrados&#34; adf_name: &#34;adf-migrate-s3&#34; adf_pipeline_name: &#34;pipeline-s3-to-blob&#34; s3_bucket_name: &#34;meu-bucket-s3&#34; s3_region: &#34;us-east-1&#34; s3_prefix: &#34;&#34; # vazio = copia tudo; ou &#34;pasta/&#34; para subdiretório log_level: verbose vars_vault.yml - nunca commitar sem criptografia:
aws_access_key_id: &#34;AKIAIOSFODNN7EXAMPLE&#34; aws_secret_access_key: &#34;wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY&#34; Criptografar antes de qualquer commit:
ansible-vault encrypt vars_vault.yml Playbook - Explicação por Bloco O playbook está dividido em 9 tasks principais mais pre_tasks e post_tasks.
pre_tasks - Verificação de Ambiente pre_tasks: - name: Verificar az CLI instalado command: az --version changed_when: false failed_when: az_check.rc != 0 - name: Verificar login na Azure command: az account show changed_when: false failed_when: az_account.rc != 0 Falha imediatamente se o az CLI não estiver instalado ou se não houver sessão ativa. Economiza tempo debugando tasks que viriam depois.
Tasks 1-2 - Resource Group e Storage Account - name: &#34;[ 1/9 ] Criar Resource Group&#34; command: &gt; az group create --name {{ resource_group }} --location {{ location }} --output none - name: &#34;[ 2/9 ] Criar Storage Account&#34; command: &gt; az storage account create --name {{ storage_account_name }} --resource-group {{ resource_group }} --sku Standard_LRS --kind StorageV2 --access-tier Hot --output none - name: &#34;[ 2/9 ] Criar container com acesso privado&#34; command: &gt; az storage container create --name {{ container_name }} --account-name {{ storage_account_name }} --public-access off --auth-mode login --output none O container é criado com public-access off - o acesso é controlado pelo ADF via connection string, não por URL pública. Isso é diferente de quando você quer servir arquivos via CDN (nesse caso, --public-access blob).
Tasks 3-5 - ADF e Linked Services - name: &#34;[ 3/9 ] Criar Azure Data Factory&#34; command: &gt; az datafactory factory create --factory-name {{ adf_name }} --resource-group {{ resource_group }} --location {{ location }} - name: &#34;[ 4/9 ] Criar Linked Service - AWS S3&#34; command: &gt; az datafactory linked-service create --factory-name {{ adf_name }} --linked-service-name {{ adf_linked_s3_name }} --properties &#39;{ &#34;type&#34;: &#34;AmazonS3&#34;, &#34;typeProperties&#34;: { &#34;accessKeyId&#34;: &#34;{{ aws_access_key_id }}&#34;, &#34;secretAccessKey&#34;: { &#34;type&#34;: &#34;SecureString&#34;, &#34;value&#34;: &#34;{{ aws_secret_access_key }}&#34; }, &#34;authenticationType&#34;: &#34;AccessKey&#34; } }&#39; no_log: true # nunca logar credenciais O no_log: true garante que a task não apareça em logs de auditoria com as credenciais expostas. Sempre usar em tasks que passam segredos como argumento de CLI.
Tasks 6-7 - Datasets e Pipeline Os datasets definem o &ldquo;onde&rdquo; da cópia: o S3 como AmazonS3Location com bucketName e folderPath, e o Blob como AzureBlobStorageLocation com container. Ambos usam tipo Binary - sem transformação de dados.
A pipeline usa parallelCopies: 4 e dataIntegrationUnits: 4, que é o mínimo para extrair performance razoável em objetos médios. Para migrações de terabytes, aumentar para 32 DIUs.
Tasks 8-9 - Execução e Polling - name: &#34;[ 8/9 ] Executar pipeline&#34; command: &gt; az datafactory pipeline create-run --factory-name {{ adf_name }} --name {{ adf_pipeline_name }} register: pipeline_run - name: &#34;[ 9/9 ] Aguardar conclusão (polling 30s)&#34; command: &gt; az datafactory pipeline-run show --run-id {{ run_id }} --query status --output tsv until: pipeline_status.stdout.strip() in [&#39;Succeeded&#39;, &#39;Failed&#39;, &#39;Cancelled&#39;] retries: 120 delay: 30 O until com retries: 120 e delay: 30 cobre até 1 hora de execução. Para buckets muito grandes, aumentar retries.
Executando # 1. criptografar credenciais AWS ansible-vault encrypt vars_vault.yml # 2. executar o playbook ansible-playbook playbook.yml -e @vars.yml --ask-vault-pass # 3. verificar o resultado no portal # https://adf.azure.com Para testar sem criar recursos (dry-run não funciona com az CLI, mas você pode checar a sintaxe YAML):
ansible-playbook playbook.yml --syntax-check Após a migração, verificar os blobs via CLI:
az storage blob list \ --container-name dados-migrados \ --account-name stdadosmigrados \ --auth-mode login \ --query &#34;[].name&#34; \ --output table Monitoramento e Troubleshooting Sintoma Causa provável Solução AuthorizationFailure no linked service S3 IAM sem s3:GetObject Adicionar a policy no IAM user da AWS StorageErrorCode: AuthorizationFailure no Blob az CLI sem role no storage Atribuir Storage Blob Data Contributor ao usuário Pipeline travada em InProgress DIU insuficiente para o volume Aumentar dataIntegrationUnits na pipeline Pipeline terminated with status: Failed Erro na cópia de objeto específico Verificar logs no ADF Monitor: https://adf.azure.com az: command not found az CLI não instalado curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash retries exceeded no polling Pipeline demorou mais de 1h Aumentar retries no playbook Para Que Serve no Mercado Migrações de cloud são comuns em contextos de aquisição de empresa, mudança de provedor, ou consolidação de infraestrutura. Ter um playbook parametrizado para esse fluxo permite que o time de DevOps ou SRE replique a operação para N buckets com mínima intervenção - só ajusta s3_bucket_name, container_name e roda.
O ADF também é usado para sincronização contínua com triggers agendados, não só migração one-shot. O mesmo playbook pode ser adaptado para provisionar um trigger no ADF que executa a pipeline periodicamente.
Conclusão A combinação Ansible + ADF é mais robusta do que scripts de shell com aws s3 sync + azcopy porque o ADF gerencia o estado da cópia, faz retry automático em falhas transitórias e dá visibilidade detalhada no portal. O Ansible garante que toda a infraestrutura é declarativa, versionada e reproduzível - rodar duas vezes não cria recursos duplicados.
A migração real que deu origem a este lab copiou 10GB em aproximadamente 9 minutos com configuração padrão de 4 DIUs.
Referências Azure Data Factory - documentação oficial Copiar dados do Amazon S3 para o Azure Blob Storage az datafactory - referência CLI Ansible command module Azure Blob Storage - documentação Playbook Completo --- # ============================================================================= # Migração S3 → Azure Blob Storage via Azure Data Factory # Playbook: playbook.yml # Autor: fxshell # Uso: ansible-playbook playbook.yml -e @vars.yml --ask-vault-pass # ============================================================================= - name: Migração S3 → Azure Blob Storage via ADF hosts: localhost connection: local gather_facts: false vars_files: - vars_vault.yml # arquivo criptografado com ansible-vault vars: resource_group: &#34;rg-migrate-s3-azure&#34; location: &#34;eastus&#34; storage_account_name: &#34;stdadosmigrados&#34; container_name: &#34;dados-migrados&#34; adf_name: &#34;adf-migrate-s3&#34; adf_pipeline_name: &#34;pipeline-s3-to-blob&#34; adf_linked_s3_name: &#34;LinkedService_S3&#34; adf_linked_blob_name: &#34;LinkedService_BlobStorage&#34; adf_dataset_s3_name: &#34;Dataset_S3&#34; adf_dataset_blob_name: &#34;Dataset_Blob&#34; s3_bucket_name: &#34;meu-bucket-s3&#34; s3_region: &#34;us-east-1&#34; # Prefixo de objetos a migrar (vazio = tudo) s3_prefix: &#34;&#34; # Nível de log: verbose | normal log_level: normal # --------------------------------------------------------------------------- # PRÉ-REQUISITOS: verificar dependências antes de qualquer task # --------------------------------------------------------------------------- pre_tasks: - name: Verificar az CLI instalado command: az --version register: az_check changed_when: false failed_when: az_check.rc != 0 - name: Verificar login na Azure (az account show) command: az account show register: az_account changed_when: false failed_when: az_account.rc != 0 - name: Exibir subscription ativa debug: msg: &#34;Subscription ativa: {{ (az_account.stdout | from_json).name }}&#34; when: log_level == &#34;verbose&#34; # --------------------------------------------------------------------------- # BLOCO 1 - Resource Group # --------------------------------------------------------------------------- tasks: - name: &#34;[ 1/9 ] Criar Resource Group {{ resource_group }}&#34; command: &gt; az group create --name {{ resource_group }} --location {{ location }} --output none register: rg_result changed_when: &#39;&#34;Succeeded&#34; in rg_result.stdout or rg_result.rc == 0&#39; # --------------------------------------------------------------------------- # BLOCO 2 - Storage Account + Container # --------------------------------------------------------------------------- - name: &#34;[ 2/9 ] Criar Storage Account {{ storage_account_name }}&#34; command: &gt; az storage account create --name {{ storage_account_name }} --resource-group {{ resource_group }} --location {{ location }} --sku Standard_LRS --kind StorageV2 --access-tier Hot --output none register: sa_result changed_when: sa_result.rc == 0 - name: &#34;[ 2/9 ] Obter connection string do Storage Account&#34; command: &gt; az storage account show-connection-string --name {{ storage_account_name }} --resource-group {{ resource_group }} --query connectionString --output tsv register: sa_conn_string changed_when: false no_log: true - name: &#34;[ 2/9 ] Criar container &#39;{{ container_name }}&#39; com acesso privado&#34; command: &gt; az storage container create --name {{ container_name }} --account-name {{ storage_account_name }} --public-access off --auth-mode login --output none register: container_result changed_when: container_result.rc == 0 # --------------------------------------------------------------------------- # BLOCO 3 - Azure Data Factory # --------------------------------------------------------------------------- - name: &#34;[ 3/9 ] Criar Azure Data Factory {{ adf_name }}&#34; command: &gt; az datafactory factory create --factory-name {{ adf_name }} --resource-group {{ resource_group }} --location {{ location }} --output none register: adf_result changed_when: adf_result.rc == 0 - name: &#34;[ 3/9 ] Obter ID do Storage Account para ADF&#34; command: &gt; az storage account show --name {{ storage_account_name }} --resource-group {{ resource_group }} --query id --output tsv register: sa_id changed_when: false # --------------------------------------------------------------------------- # BLOCO 4 - Linked Service: AWS S3 # ADF precisa de access key + secret key com permissões s3:GetObject # e s3:GetObjectVersion no bucket de origem # --------------------------------------------------------------------------- - name: &#34;[ 4/9 ] Criar Linked Service - AWS S3&#34; command: &gt; az datafactory linked-service create --factory-name {{ adf_name }} --resource-group {{ resource_group }} --linked-service-name {{ adf_linked_s3_name }} --properties &#39;{ &#34;type&#34;: &#34;AmazonS3&#34;, &#34;typeProperties&#34;: { &#34;serviceUrl&#34;: &#34;https://s3.amazonaws.com&#34;, &#34;accessKeyId&#34;: &#34;{{ aws_access_key_id }}&#34;, &#34;secretAccessKey&#34;: { &#34;type&#34;: &#34;SecureString&#34;, &#34;value&#34;: &#34;{{ aws_secret_access_key }}&#34; }, &#34;authenticationType&#34;: &#34;AccessKey&#34; } }&#39; register: ls_s3_result changed_when: ls_s3_result.rc == 0 no_log: true # --------------------------------------------------------------------------- # BLOCO 5 - Linked Service: Azure Blob Storage # --------------------------------------------------------------------------- - name: &#34;[ 5/9 ] Criar Linked Service - Azure Blob Storage&#34; command: &gt; az datafactory linked-service create --factory-name {{ adf_name }} --resource-group {{ resource_group }} --linked-service-name {{ adf_linked_blob_name }} --properties &#39;{ &#34;type&#34;: &#34;AzureBlobStorage&#34;, &#34;typeProperties&#34;: { &#34;connectionString&#34;: &#34;{{ sa_conn_string.stdout | trim }}&#34; } }&#39; register: ls_blob_result changed_when: ls_blob_result.rc == 0 no_log: true # --------------------------------------------------------------------------- # BLOCO 6 - Datasets (origem S3 + destino Blob) # --------------------------------------------------------------------------- - name: &#34;[ 6/9 ] Criar Dataset origem - S3 Binary&#34; command: &gt; az datafactory dataset create --factory-name {{ adf_name }} --resource-group {{ resource_group }} --dataset-name {{ adf_dataset_s3_name }} --properties &#39;{ &#34;type&#34;: &#34;Binary&#34;, &#34;linkedServiceName&#34;: { &#34;referenceName&#34;: &#34;{{ adf_linked_s3_name }}&#34;, &#34;type&#34;: &#34;LinkedServiceReference&#34; }, &#34;typeProperties&#34;: { &#34;location&#34;: { &#34;type&#34;: &#34;AmazonS3Location&#34;, &#34;bucketName&#34;: &#34;{{ s3_bucket_name }}&#34;, &#34;folderPath&#34;: &#34;{{ s3_prefix }}&#34; } } }&#39; register: ds_s3_result changed_when: ds_s3_result.rc == 0 - name: &#34;[ 6/9 ] Criar Dataset destino - Blob Binary&#34; command: &gt; az datafactory dataset create --factory-name {{ adf_name }} --resource-group {{ resource_group }} --dataset-name {{ adf_dataset_blob_name }} --properties &#39;{ &#34;type&#34;: &#34;Binary&#34;, &#34;linkedServiceName&#34;: { &#34;referenceName&#34;: &#34;{{ adf_linked_blob_name }}&#34;, &#34;type&#34;: &#34;LinkedServiceReference&#34; }, &#34;typeProperties&#34;: { &#34;location&#34;: { &#34;type&#34;: &#34;AzureBlobStorageLocation&#34;, &#34;container&#34;: &#34;{{ container_name }}&#34; } } }&#39; register: ds_blob_result changed_when: ds_blob_result.rc == 0 # --------------------------------------------------------------------------- # BLOCO 7 - Pipeline de cópia S3 → Blob # --------------------------------------------------------------------------- - name: &#34;[ 7/9 ] Criar Pipeline de migração {{ adf_pipeline_name }}&#34; command: &gt; az datafactory pipeline create --factory-name {{ adf_name }} --resource-group {{ resource_group }} --name {{ adf_pipeline_name }} --pipeline &#39;{ &#34;activities&#34;: [ { &#34;name&#34;: &#34;CopyS3ToBlob&#34;, &#34;type&#34;: &#34;Copy&#34;, &#34;inputs&#34;: [ { &#34;referenceName&#34;: &#34;{{ adf_dataset_s3_name }}&#34;, &#34;type&#34;: &#34;DatasetReference&#34; } ], &#34;outputs&#34;: [ { &#34;referenceName&#34;: &#34;{{ adf_dataset_blob_name }}&#34;, &#34;type&#34;: &#34;DatasetReference&#34; } ], &#34;typeProperties&#34;: { &#34;source&#34;: { &#34;type&#34;: &#34;BinarySource&#34;, &#34;storeSettings&#34;: { &#34;type&#34;: &#34;AmazonS3ReadSettings&#34;, &#34;recursive&#34;: true } }, &#34;sink&#34;: { &#34;type&#34;: &#34;BinarySink&#34;, &#34;storeSettings&#34;: { &#34;type&#34;: &#34;AzureBlobStorageWriteSettings&#34; } }, &#34;parallelCopies&#34;: 4, &#34;dataIntegrationUnits&#34;: 4, &#34;enableStaging&#34;: false } } ] }&#39; register: pipeline_result changed_when: pipeline_result.rc == 0 # --------------------------------------------------------------------------- # BLOCO 8 - Disparar execução da pipeline # --------------------------------------------------------------------------- - name: &#34;[ 8/9 ] Executar pipeline de migração&#34; command: &gt; az datafactory pipeline create-run --factory-name {{ adf_name }} --resource-group {{ resource_group }} --name {{ adf_pipeline_name }} register: pipeline_run changed_when: pipeline_run.rc == 0 - name: &#34;[ 8/9 ] Capturar Run ID&#34; set_fact: run_id: &#34;{{ (pipeline_run.stdout | from_json).runId }}&#34; - name: &#34;[ 8/9 ] Exibir Run ID&#34; debug: msg: &#34;Pipeline iniciada - Run ID: {{ run_id }}&#34; # --------------------------------------------------------------------------- # BLOCO 9 - Aguardar conclusão e verificar status # Polling a cada 30s, timeout de 120 tentativas (≈ 1h) # --------------------------------------------------------------------------- - name: &#34;[ 9/9 ] Aguardar conclusão da pipeline (polling 30s)&#34; command: &gt; az datafactory pipeline-run show --factory-name {{ adf_name }} --resource-group {{ resource_group }} --run-id {{ run_id }} --query status --output tsv register: pipeline_status until: pipeline_status.stdout.strip() in [&#39;Succeeded&#39;, &#39;Failed&#39;, &#39;Cancelled&#39;] retries: 120 delay: 30 changed_when: false - name: &#34;[ 9/9 ] Falhar se a pipeline não completou com sucesso&#34; fail: msg: &gt; Pipeline terminou com status: {{ pipeline_status.stdout.strip() }}. Acesse https://adf.azure.com para detalhes do erro. when: pipeline_status.stdout.strip() != &#39;Succeeded&#39; - name: &#34;[ 9/9 ] Exibir resultado final&#34; debug: msg: | ============================================================ Migração concluída com sucesso! Origem : s3://{{ s3_bucket_name }}/{{ s3_prefix }} Destino : {{ storage_account_name }}/{{ container_name }} Run ID : {{ run_id }} ADF URL : https://adf.azure.com/en-us/home?factory=%2FresourceGroups%2F{{ resource_group }}%2Fproviders%2FMicrosoft.DataFactory%2Ffactories%2F{{ adf_name }} ============================================================ # --------------------------------------------------------------------------- # PÓS-TASKS: limpeza opcional de recursos temporários # --------------------------------------------------------------------------- post_tasks: - name: &#34;[ CLEANUP ] Listar blobs migrados para validação&#34; command: &gt; az storage blob list --container-name {{ container_name }} --account-name {{ storage_account_name }} --auth-mode login --query &#34;[].name&#34; --output table register: blob_list changed_when: false when: log_level == &#34;verbose&#34; - name: &#34;[ CLEANUP ] Exibir blobs migrados&#34; debug: msg: &#34;{{ blob_list.stdout }}&#34; when: log_level == &#34;verbose&#34; and blob_list is defined ]]></content:encoded>
    </item>
    <item>
      <title>Ansible SSL: Certificados Let&#39;s Encrypt Automáticos com AWX</title>
      <link>https://fxshell.com.br/posts/ansible-ssl-letsencrypt/</link>
      <pubDate>Sun, 03 May 2026 13:00:00 UT</pubDate>
      <dc:creator>Felipe da Matta</dc:creator>
      <guid>https://fxshell.com.br/posts/ansible-ssl-letsencrypt/</guid>
      <description>Você já acordou às 3 da manhã com o alerta de que um certificado SSL expirou em produção? Ou descobriu que um cliente tentou acessar o site e viu a tela vermelha do browser dizendo &ldquo;conexão não é segura&rdquo;? Esse é o custo de gerenciar SSL na mão.
Neste lab eu automatizei o ciclo completo de criação e renovação de certificados SSL para três sites diferentes — fxshell1.com.br, fxshell2.com.br e fxshell3.com.br — usando Ansible + Certbot + Let&rsquo;s Encrypt. Cada certificado cobre o domínio raiz e o www no mesmo cert (SAN). O AWX agenda a renovação automática toda terça-feira às 03:00 UTC; quando disparado manualmente, um survey pergunta quais tenants renovar antes de executar. Nenhum clique desnecessário, nenhuma surpresa.
</description>
      <content:encoded><![CDATA[Você já acordou às 3 da manhã com o alerta de que um certificado SSL expirou em produção? Ou descobriu que um cliente tentou acessar o site e viu a tela vermelha do browser dizendo &ldquo;conexão não é segura&rdquo;? Esse é o custo de gerenciar SSL na mão.
Neste lab eu automatizei o ciclo completo de criação e renovação de certificados SSL para três sites diferentes — fxshell1.com.br, fxshell2.com.br e fxshell3.com.br — usando Ansible + Certbot + Let&rsquo;s Encrypt. Cada certificado cobre o domínio raiz e o www no mesmo cert (SAN). O AWX agenda a renovação automática toda terça-feira às 03:00 UTC; quando disparado manualmente, um survey pergunta quais tenants renovar antes de executar. Nenhum clique desnecessário, nenhuma surpresa.
Objetivo do Lab Construir uma automação completa que:
Instala e configura o Certbot em múltiplos servidores Emite um único certificado cobrindo dominio.com.br e www.dominio.com.br (SAN — Subject Alternative Names) Configura o Nginx para servir o desafio ACME e depois ativa HTTPS com TLS 1.3 + HSTS Agenda renovação automática no AWX toda terça-feira às 03:00 UTC Quando executado manualmente, pergunta via survey quais tenants renovar Tecnologias Utilizadas Ansible é a ferramenta de automação que conecta via SSH nos servidores e aplica as configurações descritas em playbooks YAML. Sem agente instalado, sem daemon. Um comando no control node e todos os servidores ficam no estado desejado. Usado massivamente em times DevOps para garantir que a infraestrutura seja reproduzível e auditável.
Certbot é o cliente oficial do protocolo ACME, mantido pela Electronic Frontier Foundation. Ele faz a comunicação com a CA do Let&rsquo;s Encrypt: prova que você controla o domínio (através de um arquivo temporário no servidor), recebe o certificado assinado, e salva tudo no /etc/letsencrypt/live/&lt;domínio&gt;/. No lab ele é instalado como snap — a forma recomendada atualmente, que sempre traz a versão mais recente.
Let&rsquo;s Encrypt é uma Autoridade Certificadora (CA) gratuita e automatizada, mantida por Mozilla, Cisco, EFF e outros. Emite certificados X.509 válidos por 90 dias para qualquer domínio que você consiga provar que controla. A curta validade é intencional — força automação e garante que certificados comprometidos expirem rápido.
ACME v2 é o protocolo que Let&rsquo;s Encrypt usa para emissão automatizada. No método webroot, o Certbot cria um arquivo temporário em /.well-known/acme-challenge/ no seu servidor web. A CA do Let&rsquo;s Encrypt faz uma requisição HTTP para esse arquivo e, se conseguir ler, confirma que você controla o domínio e emite o certificado.
Nginx é o servidor web que fica na frente da aplicação. No lab ele tem duas responsabilidades: servir o diretório do desafio ACME durante a validação, e depois servir HTTPS com o certificado emitido. Configuramos TLS 1.2/1.3, HSTS, OCSP Stapling e headers de segurança.
AWX é a versão open source do Ansible Automation Platform (Red Hat Tower). Ele fornece uma interface web e API para executar playbooks Ansible de forma centralizada, com controle de acesso, logs históricos, e — o que mais importa aqui — schedules (cron jobs) para execução automática de jobs.
Arquitetura ┌─────────────────────────────────────────────────────────────┐ │ AWX / Ansible Tower │ │ Job Template: SSL Create Job Template: SSL Renewal │ │ Schedule: RRULE FREQ=DAILY às 03:00 UTC │ └──────────────────────────┬──────────────────────────────────┘ │ Trigger / SSH ▼ ┌─────────────────────────────────────────────────────────────┐ │ Ansible Control Node │ │ playbook-ssl-create.yml │ │ playbook-ssl-renewal.yml │ │ roles/ssl_certbot roles/ssl_renewal │ └────────┬──────────────────┬────────────────────┬────────────┘ │ SSH │ SSH │ SSH ▼ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ fxshell1.com │ │ fxshell2.com.br │ │ fxshell3.com.br│ │ Nginx + SSL │ │ Nginx + SSL │ │ Nginx + SSL │ │ 192.168.1.10 │ │ 192.168.1.11 │ │ 192.168.1.12 │ └──────┬───────┘ └────────┬─────────┘ └────────┬─────────┘ │ │ │ └───────────────────┴──────────────────────┘ │ ACME HTTP Challenge ▼ ┌────────────────────────┐ │ Let&#39;s Encrypt CA │ │ ACME v2 Protocol │ │ Certificado 90 dias │ └────────────────────────┘ Componente Função AWX Orquestra execução, agenda renovação diária, guarda histórico Ansible Control Node Executa roles, conecta nos servidores via SSH Role ssl_certbot Instala Certbot, configura Nginx HTTP, emite certificado, sobe HTTPS Role ssl_renewal Verifica validade, renova se &lt;30 dias, registra log Nginx Serve ACME challenge, proxy HTTPS com TLS 1.3 + HSTS Let&rsquo;s Encrypt CA Valida domínio via ACME, emite certificado X.509 gratuito Como o Fluxo Funciona O AWX dispara o playbook ssl-create.yml. O Ansible conecta em cada servidor via SSH e executa a role ssl_certbot em sequência:
Instala o Certbot via snap e cria o symlink em /usr/bin/certbot Garante que o Nginx está instalado e rodando Faz o deploy do template nginx-http.conf.j2 — configuração HTTP simples que serve o diretório .well-known/acme-challenge/ e redireciona todo o resto para HTTPS Verifica se o certificado já existe em /etc/letsencrypt/live/&lt;domínio&gt;/ Se não existir, executa certbot certonly --webroot — o Certbot cria o arquivo de desafio, a CA do Let&rsquo;s Encrypt faz o GET HTTP para validar, e o certificado é emitido Faz o deploy do template nginx-ssl.conf.j2 — configuração HTTPS completa com TLS 1.2/1.3, HSTS, OCSP Stapling e security headers Verifica a data de expiração do certificado emitido com openssl x509 -enddate Para renovação, o job ssl-renewal.yml roda diariamente. Para cada servidor, ele lê a data de expiração do certificado atual, calcula os dias restantes, e só executa certbot renew se faltarem menos de 30 dias. Se renovou, notifica o handler para recarregar o Nginx.
Estrutura do Projeto ansible-ssl-letsencrypt/ ├── ansible.cfg # Config: inventory, become, pipelining ├── playbook-ssl-create.yml # Playbook principal: emite certificados ├── playbook-ssl-renewal.yml # Playbook de renovação: usado pelo AWX ├── awx-job-template.yml # Definição dos Job Templates e Schedule ├── inventory/ │ └── hosts.yml # 3 hosts com site_domain, email, webroot ├── group_vars/ │ └── all.yml # Certbot config, Nginx SSL params globais ├── roles/ │ ├── ssl_certbot/ │ │ ├── tasks/main.yml # Instala, configura Nginx, emite cert │ │ ├── handlers/main.yml # restart/reload nginx │ │ └── templates/ │ │ ├── nginx-http.conf.j2 # Config HTTP + ACME challenge │ │ └── nginx-ssl.conf.j2 # Config HTTPS + TLS 1.3 + HSTS │ └── ssl_renewal/ │ ├── tasks/main.yml # Verifica validade, renova, loga │ ├── handlers/main.yml # reload nginx após renovação │ └── templates/ │ └── renewal-report.j2 # Relatório de renovação └── diagrama/ ├── ansible-ssl-letsencrypt.html # Diagrama animado interativo ├── ansible-ssl-letsencrypt.gif # GIF para blog e LinkedIn └── gerar_gif.py # Script Pillow que gerou o GIF Inventário — Os 3 Sites O inventário define os três servidores. Cada host tem site_domain (nome principal, usado como --cert-name pelo Certbot) e site_domains (lista com raiz e www, usada tanto no certbot certonly quanto nos server_name do Nginx).
# inventory/hosts.yml all: children: webservers: hosts: fxshell1_server: ansible_host: 192.168.1.10 ansible_user: ubuntu site_domain: fxshell1.com.br # cert-name no Certbot site_domains: # SAN: raiz + www no mesmo cert - fxshell1.com.br - www.fxshell1.com.br site_email: admin@fxshell1.com.br site_webroot: /var/www/fxshell nginx_config_name: fxshell1.com.br fxshell2_server: ansible_host: 192.168.1.11 ansible_user: ubuntu site_domain: fxshell2.com.br site_domains: - fxshell2.com.br - www.fxshell2.com.br site_email: admin@fxshell2.com.br site_webroot: /var/www/fxshell2 nginx_config_name: fxshell2.com.br fxshell3_server: ansible_host: 192.168.1.12 ansible_user: ubuntu site_domain: fxshell3.com.br site_domains: - fxshell3.com.br - www.fxshell3.com.br site_email: admin@fxshell3.com.br site_webroot: /var/www/fxshell3 nginx_config_name: fxshell3.com.br O Certbot aceita múltiplos --domain num único comando. O primeiro domínio da lista vira o nome do certificado (/etc/letsencrypt/live/fxshell1.com.br/), e os demais entram como SANs. O resultado é um único arquivo .pem válido para fxshell1.com.br e www.fxshell1.com.br — sem precisar de dois certificados separados.
Role ssl_certbot — O Coração da Emissão A task mais importante da role verifica se o certificado já existe antes de tentar emitir. Isso garante idempotência — você pode rodar o playbook quantas vezes quiser sem gerar requisições desnecessárias para a CA (o Let&rsquo;s Encrypt tem rate limits).
O comando usa um loop Jinja2 para gerar múltiplos --domain, cobrindo dominio.com.br e www.dominio.com.br num único certificado SAN:
# roles/ssl_certbot/tasks/main.yml (trecho) - name: Verificar se certificado já existe stat: path: &#34;{{ certbot_certs_dir }}/{{ site_domain }}/fullchain.pem&#34; register: cert_exists - name: Obter certificado SSL via certbot (webroot — raiz + www) command: &gt; certbot certonly --webroot --webroot-path {{ site_webroot }} {% for d in site_domains %}--domain {{ d }} {% endfor %} --email {{ site_email }} --agree-tos --non-interactive {% if certbot_staging %}--staging{% endif %} --rsa-key-size {{ certbot_rsa_key_size }} --keep-until-expiring when: not cert_exists.stat.exists Para fxshell1_server, o comando expandido fica:
certbot certonly --webroot --webroot-path /var/www/fxshell \ --domain fxshell1.com.br --domain www.fxshell1.com.br \ --email admin@fxshell1.com.br --agree-tos --non-interactive \ --rsa-key-size 4096 --keep-until-expiring O flag --keep-until-expiring garante que, mesmo se o certificado já existir, o Certbot não vai renovar antes de 30 dias. O flag --staging permite testar o fluxo completo sem consumir os rate limits de produção do Let&rsquo;s Encrypt.
Template Nginx HTTPS O template nginx-ssl.conf.j2 gera uma config com as melhores práticas de TLS:
server { listen 443 ssl http2; server_name {{ site_domain }} www.{{ site_domain }}; ssl_certificate {{ certbot_certs_dir }}/{{ site_domain }}/fullchain.pem; ssl_certificate_key {{ certbot_certs_dir }}/{{ site_domain }}/privkey.pem; ssl_trusted_certificate {{ certbot_certs_dir }}/{{ site_domain }}/chain.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:...; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:50m; ssl_stapling on; ssl_stapling_verify on; # HSTS: força HTTPS por 1 ano em todos os subdomínios add_header Strict-Transport-Security &#34;max-age=31536000; includeSubDomains; preload&#34; always; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; } O OCSP Stapling (ssl_stapling on) reduz a latência das conexões: em vez de o browser consultar a CA para verificar se o certificado foi revogado, o servidor já faz essa consulta periodicamente e inclui a resposta assinada no handshake TLS.
Role ssl_renewal — Lógica de Renovação A task de renovação calcula os dias restantes com openssl e date, e só chama o certbot renew se a condição for verdadeira:
- name: Calcular dias restantes shell: | end_date=&#34;{{ cert_end_date.stdout }}&#34; today=$(date +%s) expiry=$(date -d &#34;$end_date&#34; +%s) echo $(( (expiry - today) / 86400 )) register: days_remaining - name: Renovar certificado se faltam menos de 30 dias command: &gt; certbot renew --cert-name {{ site_domain }} --non-interactive --quiet when: days_remaining.stdout | int &lt; 30 notify: reload nginx Cada execução registra uma linha em /var/log/certbot-renewal.log com data, domínio, dias restantes e se foi renovado.
AWX — Job Templates e Schedule A configuração do AWX está documentada no arquivo awx-job-template.yml. Os dois jobs principais são:
SSL - Criar Certificado: roda sob demanda com survey para o operador escolher domínio e ambiente (staging/produção).
SSL - Renovar Certificados: tem dois modos de uso:
Schedule automático — toda terça-feira às 03:00 UTC, renova todos os hosts do grupo webservers Execução manual com survey — antes de executar, o AWX exibe um formulário para o operador selecionar quais tenants renovar O survey de seleção de tenants funciona assim: o campo tenants aceita o nome de um ou mais hosts separados por vírgula (ex: fxshell1_server,fxshell2_server) ou o grupo webservers para renovar todos. Esse valor é passado para a diretiva hosts: do playbook, que funciona como um --limit dinâmico.
# O playbook usa o valor do survey como filtro de hosts - name: Renovar certificados SSL Let&#39;s Encrypt hosts: &#34;{{ tenants | default(&#39;webservers&#39;) }}&#34; O schedule usa a sintaxe RFC 5545 (iCalendar RRULE), que é o formato que o AWX aceita:
DTSTART:20240102T030000Z RRULE:FREQ=WEEKLY;BYDAY=TU # BYDAY=TU = Tuesday (terça-feira) # FREQ=WEEKLY = toda semana # Hora: 03:00 UTC Para configurar manualmente na interface do AWX:
Projects → Criar projeto apontando para o repositório git Inventories → Importar hosts.yml com os 3 servidores Credentials → Adicionar chave SSH dos servidores Job Templates → SSL - Criar Certificado (playbook-ssl-create.yml) Job Templates → SSL - Renovar (playbook-ssl-renewal.yml) → Habilitar Survey com campo &#34;tenants&#34; → Habilitar ask_limit_on_launch: true Schedules → Adicionar no job de renovação: → Recurrence: Weekly, Day: Tuesday, Time: 03:00 UTC → Default tenants: webservers (todos) Executando # 1. Testar conectividade com todos os servidores ansible all -m ping # 2. Testar com staging primeiro (não gasta rate limit) ansible-playbook playbook-ssl-create.yml -e &#34;certbot_staging=true&#34; # 3. Verificar o que seria feito (dry-run parcial) ansible-playbook playbook-ssl-create.yml --check # 4. Emitir certificados reais para todos os sites ansible-playbook playbook-ssl-create.yml # 5. Emitir só para um site específico ansible-playbook playbook-ssl-create.yml -l fxshell1_server # 6. Forçar renovação manualmente ansible-playbook playbook-ssl-renewal.yml -e &#34;force_renewal=true&#34; # 7. Testar HTTPS após emissão curl -I https://fxshell1.com.br curl -I https://fxshell2.com.br curl -I https://fxshell3.com.br Para verificar os headers de segurança:
curl -sI https://fxshell1.com.br | grep -E &#34;Strict-Transport|X-Frame|X-Content&#34; Monitoramento e Troubleshooting Sintoma Causa provável Solução certbot: command not found Snap não instalou ou symlink faltando Verificar snap list certbot e recriar symlink Challenge failed: connection refused Nginx não está rodando ou porta 80 bloqueada systemctl status nginx e verificar firewall Too many certificates already issued Rate limit do Let&rsquo;s Encrypt atingido Usar --staging para testes; aguardar 7 dias nginx: configuration file test failed Caminho do certificado errado no template Verificar se /etc/letsencrypt/live/&lt;domínio&gt;/ existe SSL certificate has expired Renovação automática falhou Verificar /var/log/certbot-renewal.log e /var/log/letsencrypt/ AWX job falha com Permission denied Usuário SSH sem sudo configurado Verificar become: true e sudoers no servidor # Verificar logs do certbot cat /var/log/letsencrypt/letsencrypt.log | tail -50 # Verificar log de renovação customizado cat /var/log/certbot-renewal.log # Testar validade do certificado manualmente openssl s_client -connect fxshell1.com.br:443 -servername fxshell1.com.br &lt; /dev/null 2&gt;/dev/null \ | openssl x509 -noout -enddate Para Que Serve no Mercado Times DevOps e SRE que gerenciam dezenas ou centenas de domínios usam exatamente esse padrão. As variações incluem:
Wildcard certificates com validação DNS ao invés de HTTP (necessário quando os servidores não têm porta 80 pública) Integração com Vault para armazenar as credenciais dos provedores DNS e automatizar renovação de wildcards Pipeline de CI/CD que dispara o playbook quando um novo site é adicionado ao inventário Certificados internos usando a mesma lógica com uma CA própria (CFSSL, Vault PKI) para serviços internos que não precisam de CA pública O princípio é o mesmo em qualquer escala: infraestrutura declarativa, idempotente, auditável. Um certificado expirado em produção é a prova de que existe um processo manual em algum lugar esperando para falhar.
Conclusão O que resolve esse lab não é só a automação do certbot — é a combinação de idempotência do Ansible com a orquestração do AWX. Você pode executar esse playbook mil vezes e o resultado é sempre o mesmo: se o certificado existe e está válido, nada muda. Se está para expirar, é renovado. Se não existe, é criado. Esse nível de previsibilidade é o que separa infraestrutura mantida de infraestrutura gerenciada.
Referências Certbot Documentation Let&rsquo;s Encrypt — Rate Limits ACME Protocol RFC 8555 Nginx SSL Configuration AWX Project Ansible Documentation Mozilla SSL Configuration Generator ]]></content:encoded>
    </item>
    <item>
      <title>Ansible</title>
      <link>https://fxshell.com.br/posts/ansible/</link>
      <pubDate>Tue, 30 Mar 2021 00:56:17 UT</pubDate>
      <dc:creator>Felipe da Matta</dc:creator>
      <guid>https://fxshell.com.br/posts/ansible/</guid>
      <description>O Ansible é uma ferramenta de automação de infraestrutura open source que permite gerenciar configurações, provisionar servidores e orquestrar tarefas complexas em dezenas ou centenas de máquinas ao mesmo tempo — tudo a partir de um único ponto de controle, sem instalar nenhum agente nos servidores alvo.
O nome vem da ficção científica: um &ldquo;ansible&rdquo; é um dispositivo de comunicação superlumínica que transmite informação instantaneamente para múltiplos destinos. A metáfora é precisa.
</description>
      <content:encoded><![CDATA[O Ansible é uma ferramenta de automação de infraestrutura open source que permite gerenciar configurações, provisionar servidores e orquestrar tarefas complexas em dezenas ou centenas de máquinas ao mesmo tempo — tudo a partir de um único ponto de controle, sem instalar nenhum agente nos servidores alvo.
O nome vem da ficção científica: um &ldquo;ansible&rdquo; é um dispositivo de comunicação superlumínica que transmite informação instantaneamente para múltiplos destinos. A metáfora é precisa.
Os quatro pilares do Ansible Gerenciamento de Mudança — O Ansible é idempotente: executa uma tarefa somente se for necessário. Se o estado desejado já existe, ele não faz nada. Isso permite rodar o mesmo playbook várias vezes sem efeitos colaterais.
Provisionamento — Instala pacotes, configura serviços, cria usuários, aplica permissões — prepara um servidor do zero para uma função específica.
Automação — Executa tarefas de forma ordenada, permite tomar decisões condicionais e encadear operações complexas com YAML simples.
Orquestração — Coordena múltiplos servidores e aplicações em sequência: atualiza o banco antes dos app servers, drena o load balancer antes de reiniciar um nó.
Por que o Ansible? Sem agente. Não há software para instalar nos servidores gerenciados. O Ansible usa SSH (Linux) e WinRM (Windows) — protocolos que já existem em qualquer servidor.
YAML simples. Os playbooks são legíveis por qualquer pessoa da equipe — dev, ops ou gerência. Não é uma DSL exótica, é YAML com lógica.
Configuração rápida. Sem daemon, sem banco de dados, sem porta extra. Instale o Ansible no control node e comece a usar.
Seguro. SSH é o canal de comunicação. Suporte nativo a chaves SSH, LDAP e Kerberos para autenticação.
Arquitetura O Control Node é a única máquina com Ansible instalado. A partir dele, o Ansible se conecta via SSH aos hosts Linux e via WinRM aos hosts Windows, empurra os módulos Python temporariamente, executa as tarefas e remove tudo ao final. Nenhum rastro fica nos hosts gerenciados.
Como funciona O Ansible é desenvolvido em Python e requer Python ≥ 2.7 ou ≥ 3.5. Ele sempre busca o interpretador em /usr/bin/python por padrão — isso pode ser ajustado com a variável ansible_python_interpreter.
A comunicação acontece via:
SSH para servidores Linux (usuário + senha ou chave SSH) WinRM para servidores Windows A autenticação é descentralizada — pode ser feita com LDAP ou Kerberos para ambientes corporativos.
Fluxo de execução de um Playbook Ao executar ansible-playbook site.yml -i hosts, o Ansible:
Lê o playbook YAML e interpreta as plays Consulta o inventário para descobrir quais hosts executam cada play Determina quais módulos precisam ser executados (apt, copy, service, template&hellip;) Conecta via SSH/WinRM nos hosts alvos Aplica cada task, reporta o estado (ok / changed / failed) e aciona handlers se necessário Instalação O Ansible não adiciona banco de dados, daemon ou software persistente nos hosts. A única dependência é Python.
RHEL / CentOS / Fedora — habilite o EPEL antes:
sudo yum install ansible Ubuntu / Debian:
sudo apt-add-repository ppa:ansible/ansible sudo apt-get update sudo apt-get install ansible Via pip (qualquer distro):
sudo apt-get install python3-pip # se não tiver pip sudo pip3 install ansible sudo pip3 install ansible --upgrade # para atualizar Após instalar, configure o acesso SSH:
ssh-keygen -t rsa # gere sua chave ssh-copy-id user@host # copie para os hosts Configuração — ansible.cfg O arquivo principal de configuração é o ansible.cfg. A ordem de leitura (da menor para a maior precedência):
/etc/ansible/ansible.cfg (global) ~/.ansible.cfg (usuário) ./ansible.cfg (diretório corrente) Variável de ambiente ANSIBLE_CONFIG Um ansible.cfg funcional e comentado:
[defaults] # Execução paralela em até 5 hosts simultaneamente forks = 5 log_path = /var/log/ansible.log module_name = command executable = /bin/bash # Caminhos inventory = /etc/ansible/hosts roles_path = /etc/ansible/roles remote_tmp = ~/.ansible/tmp local_tmp = ~/.ansible/tmp # Autenticação remote_user = root ask_pass = no # SSH remote_port = 22 timeout = 10 host_key_checking = False private_key_file = ~/.ssh/id_rsa [privilege_escalation] become = True become_method = sudo become_user = root become_ask_pass = False [ssh_connection] scp_if_ssh = smart transfer_method = smart retries = 3 Inventário — Hosts, Grupos e Sub-grupos O inventário define quais servidores o Ansible conhece e como eles se organizam. Pode ser um arquivo estático (/etc/ansible/hosts) ou dinâmico (script que consulta cloud providers).
Hosts individuais:
192.168.1.151 192.168.1.234 Grupos:
[servidores_bd] 192.168.1.151 [servidores_web] 192.168.1.234 Sub-grupos (children):
[servidores:children] servidores_web servidores_bd Variáveis por grupo:
[servidores_bd:vars] ansible_ssh_port=22 ansible_ssh_user=osboxes ansible_ssh_pass=osboxes.org ansible_become=yes ansible_become_method=sudo Alias por host:
mysql ansible_ssh_host=192.168.1.234 Comandos Ad-hoc Ad-hoc são comandos pontuais executados sem playbook — ideais para operações rápidas.
Sintaxe base:
ansible &lt;hosts&gt; -u &lt;user&gt; -k [-b] -m &lt;módulo&gt; -a &#34;&lt;args&gt;&#34; Flags:
Flag Função -u usuário remoto -k solicita senha SSH -K solicita senha para sudo -b executa com elevação (become) -m módulo a usar -a argumento do módulo -i inventário alternativo Ping em todos os hosts:
ansible all -m ping -u osboxes -k 192.168.1.151 | SUCCESS =&gt; { &#34;changed&#34;: false, &#34;ping&#34;: &#34;pong&#34; } 192.168.1.234 | SUCCESS =&gt; { &#34;changed&#34;: false, &#34;ping&#34;: &#34;pong&#34; } Coletar facts do sistema:
ansible 192.168.1.234 -u osboxes -k -m setup Reiniciar serviço SSH:
ansible 192.168.1.151 -u osboxes -k -b -m systemd -a &#34;name=ssh state=restarted&#34; Executar comando shell:
ansible 192.168.1.151 -u osboxes -k -b -m shell -a &#34;systemctl status ssh&#34; Comando direto (módulo command por padrão):
ansible 192.168.1.151 -u osboxes -k -a &#34;pwd&#34; Filtrar por grupo:
ansible -i hosts servidores_bd -m ping -u osboxes -k Para ignorar o aviso de Python legado, adicione ao ansible.cfg:
interpreter_python = auto_legacy_silent Roles — Automação Modular e Reutilizável Roles são a forma correta de organizar automação complexa. Em vez de um playbook gigante, você cria unidades independentes e reutilizáveis — uma role para nginx, outra para mysql, outra para hardening — e as compõe no playbook principal.
Estrutura de diretórios padrão:
playbook.yml └── roles/ └── nginx/ ├── tasks/ │ └── main.yml # lista de tarefas (obrigatório) ├── handlers/ │ └── main.yml # acionados por notify nas tasks ├── templates/ │ └── nginx.conf.j2 # Jinja2 com variáveis dinâmicas ├── files/ # arquivos estáticos para cópia ├── vars/ │ └── main.yml # variáveis com prioridade alta ├── defaults/ │ └── main.yml # defaults facilmente sobrescritíveis └── meta/ └── main.yml # dependências entre roles (lido primeiro) Chamando roles no playbook:
--- - hosts: webserver roles: - common # executada primeiro - nginx - php - mysql A execução de uma role é definida pelas tasks em tasks/main.yml. O diretório meta é analisado primeiro para resolver dependências.
Variáveis e Facts As variáveis permitem que o mesmo playbook funcione de forma diferente em cada host ou ambiente.
Ordem de prioridade (menor → maior):
1. role/defaults/main.yml ← mais fácil de sobrescrever 2. inventory file vars 3. group_vars/all 4. group_vars/* 5. host_vars/* 6. host facts (módulo setup) 7. role/vars/main.yml 8. set_fact / registered vars 9. extra vars (-e) ← prioridade máxima Ansible Facts — o módulo setup descobre automaticamente informações de cada host:
ansible hostname -m setup Retorna sistema operacional, IPs, memória, CPUs, discos, variáveis de ambiente e muito mais. Essas informações ficam disponíveis como variáveis nos playbooks: {{ ansible_os_family }}, {{ ansible_default_ipv4.address }}, etc.
Exemplo de estrutura com group_vars:
├── group_vars/ │ └── servidores ← variáveis aplicadas ao grupo ├── hosts ├── host_vars/ └── roles/ Usar variável extra na linha de comando (maior prioridade):
ansible-playbook site.yml -e &#34;nginx_port=8080&#34; Próximos passos Com esse conhecimento base, você está pronto para:
Criar playbooks completos que provisionam stacks inteiras Organizar infraestrutura com roles reutilizáveis (inclusive via Ansible Galaxy) Integrar Ansible em pipelines CI/CD (GitHub Actions, GitLab CI, Jenkins) Gerenciar secrets com Ansible Vault Escalar para centenas de servidores com inventários dinâmicos (AWS, Azure, GCP) Veja os posts relacionados para laboratórios práticos com Ansible em ação:
]]></content:encoded>
    </item>
  </channel>
</rss>
