<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>Ansible on FXShell - DevOps &amp; Sec</title>
    <link>https://fxshell.com.br/tags/ansible/</link>
    <description>Recent content in Ansible on FXShell - DevOps &amp; Sec</description>
    <image>
      <title>Ansible on FXShell - DevOps &amp; Sec</title>
      <link>https://fxshell.com.br/tags/ansible/</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/ansible/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 Lab: Provisionando Infraestrutura Web Segura do Zero</title>
      <link>https://fxshell.com.br/posts/ansible-lab-infra-segura/</link>
      <pubDate>Tue, 21 Apr 2026 13:00:00 UT</pubDate>
      <dc:creator>Felipe da Matta</dc:creator>
      <guid>https://fxshell.com.br/posts/ansible-lab-infra-segura/</guid>
      <description>Cenário: você tem 4 VMs vazias e precisa subir uma stack web completa — load balancer, dois servidores de aplicação, banco de dados, tudo com segurança configurada desde o início — em menos de 5 minutos. Só com Ansible.
Este post documenta um lab que montei para praticar exatamente isso: infraestrutura como código, do zero até uma stack funcional e segura, reproduzível com um único comando.
Objetivo do Lab O objetivo é simular, localmente, um ambiente que existe de verdade em qualquer empresa que tenha um sistema web em produção. Em vez de provisionar um servidor por vez — conectando via SSH, instalando pacotes na mão e torcendo para não esquecer nada — o lab demonstra como automatizar todo esse processo com Ansible.
</description>
      <content:encoded><![CDATA[Cenário: você tem 4 VMs vazias e precisa subir uma stack web completa — load balancer, dois servidores de aplicação, banco de dados, tudo com segurança configurada desde o início — em menos de 5 minutos. Só com Ansible.
Este post documenta um lab que montei para praticar exatamente isso: infraestrutura como código, do zero até uma stack funcional e segura, reproduzível com um único comando.
Objetivo do Lab O objetivo é simular, localmente, um ambiente que existe de verdade em qualquer empresa que tenha um sistema web em produção. Em vez de provisionar um servidor por vez — conectando via SSH, instalando pacotes na mão e torcendo para não esquecer nada — o lab demonstra como automatizar todo esse processo com Ansible.
Na prática, esse tipo de automação é usado para:
Subir novos ambientes (staging, produção, DR) de forma idêntica e rápida Garantir que todos os servidores estejam sempre na configuração correta, sem desvios Integrar o provisionamento em pipelines de CI/CD, onde um ambiente novo é criado automaticamente a cada release Padronizar configurações de segurança em toda a frota de servidores Tecnologias Utilizadas Antes de entrar no código, vale entender o papel de cada ferramenta.
Ansible é uma ferramenta de automação de infraestrutura. Você descreve o estado desejado dos seus servidores em arquivos YAML (os chamados playbooks e roles), e o Ansible se conecta via SSH e aplica essas configurações. Sem agente instalado nos servidores, sem banco de dados, sem daemon rodando — é simples por design.
Vagrant é uma ferramenta para criar e gerenciar máquinas virtuais de forma automatizada. Com um único arquivo de configuração (o Vagrantfile), você define quantas VMs quer, qual sistema operacional, qual IP de rede, quanta memória — e o Vagrant cria tudo isso para você. É amplamente usado para criar ambientes de desenvolvimento e laboratório locais, sem precisar pagar por cloud ou configurar VMs manualmente pelo VirtualBox.
VirtualBox é o software de virtualização que o Vagrant usa por baixo dos panos neste lab. É ele quem de fato cria e executa as máquinas virtuais. O Vagrant é a camada de abstração que automatiza a criação delas.
HAProxy é um load balancer de alto desempenho. Ele recebe as requisições dos usuários e as distribui entre os servidores de aplicação disponíveis. Se um servidor cair, o HAProxy para de mandar tráfego para ele automaticamente. É usado em praticamente toda arquitetura web que precisa de escalabilidade e alta disponibilidade.
Nginx é um servidor web e proxy reverso. Neste lab ele serve a aplicação nos dois web servers. No mercado é comum encontrá-lo tanto como servidor de aplicação quanto como proxy na frente de outros serviços.
PHP-FPM é o gerenciador de processos PHP. É ele que executa o código da aplicação e responde às requisições que chegam pelo Nginx.
MySQL é o banco de dados relacional da stack. Armazena os dados da aplicação.
UFW (Uncomplicated Firewall) é a interface de gerenciamento do firewall do Linux. Com ele definimos quais portas e IPs têm permissão de se comunicar com cada servidor. No lab, por exemplo, o MySQL só aceita conexões da rede interna — não há como acessá-lo de fora.
fail2ban monitora os logs do sistema e bane automaticamente IPs que apresentam comportamento suspeito — como várias tentativas de login SSH com falha em sequência. É uma camada de defesa simples e muito efetiva contra ataques de força bruta.
Arquitetura ┌─────────────────────────────────────────────────────────┐ │ ANSIBLE CONTROL NODE │ │ (seu terminal / CI/CD) │ └──────────────────────┬──────────────────────────────────┘ │ SSH (porta 22) ┌─────────────┼─────────────┐ ▼ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ lb-01 │ │ web-01 │ │ web-02 │ │ db-01 │ │ HAProxy │ │ Nginx │ │ Nginx │ │ MySQL │ │:80/:443 │ │ PHP-FPM │ │ PHP-FPM │ │ :3306 │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ Servidor Roles aplicadas lb-01 common, hardening, haproxy web-01 common, hardening, nginx, php web-02 common, hardening, nginx, php db-01 common, hardening, mysql Cada servidor recebe a role common (configurações base) e a role hardening (segurança), além das roles específicas do seu papel na stack.
Preparando o Ambiente com Vagrant O Vagrant cria as 4 VMs localmente com um único vagrant up. Cada máquina recebe um IP fixo na rede privada 192.168.56.0/24, o que permite que o Ansible se comunique com todas elas sem nenhuma configuração extra de rede.
Vagrant.configure(&#34;2&#34;) do |config| config.vm.box = &#34;debian/bookworm64&#34; nodes = { &#34;lb-01&#34; =&gt; &#34;192.168.56.10&#34;, &#34;web-01&#34; =&gt; &#34;192.168.56.11&#34;, &#34;web-02&#34; =&gt; &#34;192.168.56.12&#34;, &#34;db-01&#34; =&gt; &#34;192.168.56.13&#34; } nodes.each do |name, ip| config.vm.define name do |node| node.vm.hostname = name node.vm.network &#34;private_network&#34;, ip: ip node.vm.provider &#34;virtualbox&#34; do |vb| vb.memory = &#34;512&#34; vb.cpus = 1 end end end end vagrant up Quando as VMs estiverem rodando, o Ansible assume o controle.
Estrutura do Projeto O projeto segue a estrutura padrão de roles do Ansible. Cada role é um diretório independente com suas tasks, templates e variáveis. Essa separação facilita reutilizar as roles em outros projetos — a role de hardening, por exemplo, pode ser aplicada em qualquer servidor sem nenhuma modificação.
ansible-lab/ ├── ansible.cfg ├── inventory/ │ └── hosts.ini ├── playbook.yml └── roles/ ├── common/ │ └── tasks/main.yml ├── hardening/ │ ├── tasks/main.yml │ └── templates/ │ └── sshd_config.j2 ├── haproxy/ │ ├── tasks/main.yml │ └── templates/ │ └── haproxy.cfg.j2 ├── nginx/ │ └── tasks/main.yml ├── php/ │ └── tasks/main.yml └── mysql/ ├── tasks/main.yml └── vars/main.yml ansible.cfg O arquivo de configuração do Ansible define o comportamento padrão da ferramenta: onde está o inventário, qual usuário usar para se conectar, se deve escalar privilégios automaticamente, entre outros. Isso evita ter que repetir essas opções em todo comando que você rodar.
[defaults] inventory = inventory/hosts.ini remote_user = vagrant private_key_file = ~/.vagrant.d/insecure_private_key host_key_checking = False forks = 5 log_path = ansible.log [privilege_escalation] become = True become_method = sudo become_user = root become_ask_pass = False inventory/hosts.ini O inventário é onde você diz ao Ansible quais servidores existem e como agrupá-los. Grupos permitem aplicar roles e variáveis diferentes para cada tipo de servidor — tudo o que estiver em [webservers] recebe as configurações de web server, tudo em [database] recebe as de banco de dados, e assim por diante.
[loadbalancer] lb-01 ansible_host=192.168.56.10 [webservers] web-01 ansible_host=192.168.56.11 web-02 ansible_host=192.168.56.12 [database] db-01 ansible_host=192.168.56.13 [all:vars] ansible_python_interpreter=/usr/bin/python3 [webservers:vars] db_host=192.168.56.13 [loadbalancer:vars] web_backends=[&#34;192.168.56.11&#34;, &#34;192.168.56.12&#34;] As Roles common Responsável pelas configurações base que todo servidor precisa ter: pacotes essenciais e sincronização de hora via chrony. Simples, mas garante uma base consistente em todas as máquinas — sem isso, você pode ter servidores com versões diferentes de ferramentas ou relógios dessincronizados, o que causa problemas sutis difíceis de depurar.
# roles/common/tasks/main.yml --- - name: Atualizar cache do apt apt: update_cache: yes cache_valid_time: 3600 - name: Instalar pacotes base apt: name: - vim - curl - wget - htop - net-tools - chrony state: present - name: Garantir chrony rodando service: name: chrony state: started enabled: yes hardening Esta é a role mais importante do ponto de vista de segurança. Ela configura o firewall, restringe o acesso SSH e ativa o bloqueio automático de IPs suspeitos — aplicado em todos os servidores, sem exceção.
# roles/hardening/tasks/main.yml --- - name: Instalar ferramentas de segurança apt: name: - ufw - fail2ban - unattended-upgrades state: present - name: Configurar UFW - política padrão deny ufw: state: enabled policy: deny direction: incoming - name: Permitir SSH ufw: rule: allow port: &#34;22&#34; proto: tcp - name: Aplicar configurações de segurança no SSH template: src: sshd_config.j2 dest: /etc/ssh/sshd_config owner: root group: root mode: &#34;0600&#34; validate: /usr/sbin/sshd -t -f %s notify: Reiniciar SSH - name: Ativar fail2ban service: name: fail2ban state: started enabled: yes - name: Configurar atualizações automáticas de segurança copy: dest: /etc/apt/apt.conf.d/50unattended-upgrades content: | Unattended-Upgrade::Allowed-Origins { &#34;${distro_id}:${distro_codename}-security&#34;; }; Unattended-Upgrade::Automatic-Reboot &#34;false&#34;; O template do SSH — arquivos .j2 são templates Jinja2, que permitem usar variáveis e lógica dentro de arquivos de configuração — desabilita login por senha e acesso direto como root, dois dos vetores de ataque mais comuns em servidores expostos na internet.
{# roles/hardening/templates/sshd_config.j2 #} Port 22 Protocol 2 HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_ecdsa_key HostKey /etc/ssh/ssh_host_ed25519_key # Autenticação PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys # Restrições X11Forwarding no AllowTcpForwarding no MaxAuthTries 3 LoginGraceTime 30 ClientAliveInterval 300 ClientAliveCountMax 2 UsePAM yes PrintLastLog yes Com PasswordAuthentication no, ataques de força bruta por senha simplesmente não funcionam — o servidor nem aceita esse tipo de autenticação. O fail2ban complementa banindo automaticamente IPs que insistem em tentar.
haproxy O load balancer distribui o tráfego entre os dois web servers em round-robin — cada requisição vai alternadamente para web-01 e web-02 — e verifica a saúde de cada servidor antes de repassar requisições. Se um web server cair, o HAProxy detecta pelo health check e para de mandar tráfego para ele até que volte.
# roles/haproxy/tasks/main.yml --- - name: Instalar HAProxy apt: name: haproxy state: present - name: Permitir porta 80 no firewall ufw: rule: allow port: &#34;80&#34; proto: tcp - name: Configurar HAProxy template: src: haproxy.cfg.j2 dest: /etc/haproxy/haproxy.cfg validate: haproxy -c -f %s notify: Reiniciar HAProxy - name: Garantir HAProxy rodando service: name: haproxy state: started enabled: yes Note o validate na task de configuração: o Ansible só aplica o arquivo se o HAProxy confirmar que a sintaxe está correta. Isso evita derrubar o serviço por conta de um erro de configuração.
{# roles/haproxy/templates/haproxy.cfg.j2 #} global log /dev/log local0 chroot /var/lib/haproxy user haproxy group haproxy daemon defaults log global mode http option httplog option dontlognull timeout connect 5000ms timeout client 50000ms timeout server 50000ms frontend web_frontend bind *:80 default_backend web_backends backend web_backends balance roundrobin option httpchk GET /health {% for backend in web_backends %} server web-{{ loop.index }} {{ backend }}:80 check {% endfor %} nginx + php Os web servers aceitam conexões apenas do load balancer — qualquer acesso direto pela porta 80 de outro IP é bloqueado pelo UFW. Isso garante que todo tráfego passe obrigatoriamente pelo HAProxy, sem atalhos.
# roles/nginx/tasks/main.yml --- - name: Instalar Nginx apt: name: nginx state: present - name: Permitir porta 80 apenas do load balancer ufw: rule: allow src: &#34;192.168.56.10&#34; port: &#34;80&#34; proto: tcp - name: Criar endpoint de health check copy: dest: /var/www/html/health content: &#34;OK&#34; owner: www-data group: www-data - name: Criar página de identificação do servidor copy: dest: /var/www/html/index.html content: &#34;&lt;h1&gt;Servidor: {{ inventory_hostname }}&lt;/h1&gt;&#34; owner: www-data group: www-data - name: Garantir Nginx rodando service: name: nginx state: started enabled: yes mysql O MySQL é configurado para escutar apenas no IP interno e aceitar conexões somente da sub-rede 192.168.56.0/24. Usuário de aplicação criado com privilégios mínimos — sem acesso root, sem acesso de fora da rede interna.
# roles/mysql/tasks/main.yml --- - name: Instalar MySQL e dependências Python apt: name: - mysql-server - python3-mysqldb state: present - name: Garantir MySQL rodando service: name: mysql state: started enabled: yes - name: Criar banco de dados da aplicação mysql_db: name: &#34;{{ db_name }}&#34; state: present - name: Criar usuário com acesso restrito por IP mysql_user: name: &#34;{{ db_user }}&#34; password: &#34;{{ db_password }}&#34; priv: &#34;{{ db_name }}.*:ALL&#34; host: &#34;192.168.56.%&#34; state: present - name: Restringir MySQL ao IP interno lineinfile: path: /etc/mysql/mysql.conf.d/mysqld.cnf regexp: &#34;^bind-address&#34; line: &#34;bind-address = 192.168.56.13&#34; notify: Reiniciar MySQL - name: Liberar acesso ao MySQL apenas da rede interna ufw: rule: allow src: &#34;192.168.56.0/24&#34; port: &#34;3306&#34; proto: tcp A senha do banco nunca deve ir em texto plano no repositório. O ansible-vault criptografa valores sensíveis dentro dos arquivos YAML — você commita o arquivo normalmente, mas só quem tem a chave consegue descriptografar.
ansible-vault encrypt_string &#39;SuaSenha&#39; --name &#39;vault_db_password&#39; # roles/mysql/vars/main.yml --- db_name: appdb db_user: appuser db_password: &#34;{{ vault_db_password }}&#34; Playbook Principal O playbook é o arquivo que orquestra tudo — ele define qual conjunto de roles é aplicado em qual grupo de servidores, e em qual ordem. É o ponto de entrada da automação.
# playbook.yml --- - name: Provisionar Load Balancer hosts: loadbalancer roles: - common - hardening - haproxy - name: Provisionar Servidores Web hosts: webservers roles: - common - hardening - nginx - php - name: Provisionar Banco de Dados hosts: database roles: - common - hardening - mysql Executando Antes de rodar de verdade, vale sempre fazer um dry-run com --check --diff para visualizar exatamente o que seria alterado em cada servidor, sem modificar nada:
# Verificar conectividade com todos os nós ansible all -m ping # Dry run — simula a execução sem alterar nada ansible-playbook playbook.yml --check --diff # Execução real ansible-playbook playbook.yml Resultado esperado:
PLAY RECAP lb-01 : ok=14 changed=12 unreachable=0 failed=0 web-01 : ok=17 changed=15 unreachable=0 failed=0 web-02 : ok=17 changed=15 unreachable=0 failed=0 db-01 : ok=16 changed=14 unreachable=0 failed=0 Verificando a Stack # O load balancer deve alternar entre web-01 e web-02 for i in {1..6}; do curl -s http://192.168.56.10; echo; done # Servidor: web-01 # Servidor: web-02 # Servidor: web-01 # Servidor: web-02 # ... # Health check do HAProxy curl http://192.168.56.10/health # OK # Confirmar fail2ban ativo em todos os servidores ansible all -m shell -a &#34;fail2ban-client status&#34; # Confirmar firewall ativo ansible all -m shell -a &#34;ufw status verbose&#34; Idempotência Um conceito central no Ansible — e na automação de infraestrutura em geral — é a idempotência: a capacidade de executar a mesma operação várias vezes e sempre chegar ao mesmo resultado, sem efeitos colaterais.
Rode o playbook novamente sem ter alterado nada:
ansible-playbook playbook.yml PLAY RECAP lb-01 : ok=14 changed=0 unreachable=0 failed=0 web-01 : ok=17 changed=0 unreachable=0 failed=0 web-02 : ok=17 changed=0 unreachable=0 failed=0 db-01 : ok=16 changed=0 unreachable=0 failed=0 changed=0 em todos os servidores. Antes de executar qualquer task, o Ansible verifica se o recurso já está no estado desejado. Se o pacote já está instalado, não instala de novo. Se o arquivo de configuração já está correto, não sobrescreve. Isso significa que você pode rodar o mesmo playbook quantas vezes quiser — em manutenção, em auditoria, em CI/CD — sem risco de quebrar o ambiente.
Comandos Ad-hoc para o Dia a Dia Nem tudo precisa de um playbook. Para tarefas pontuais, o Ansible permite rodar comandos diretamente em um grupo de servidores sem precisar criar um arquivo YAML:
# Ver uso de memória em todos os servidores ansible all -m shell -a &#34;free -h&#34; # Reiniciar nginx nos web servers ansible webservers -m service -a &#34;name=nginx state=restarted&#34; # Coletar informações do sistema (distribuição, versão, etc.) ansible all -m setup -a &#34;filter=ansible_distribution*&#34; # Verificar quais portas estão abertas ansible all -m shell -a &#34;ss -tlnp&#34; Para Que Isso Serve no Mercado Este lab reproduz, em escala reduzida, um padrão que existe em praticamente qualquer empresa com infraestrutura web:
Times de DevOps/SRE usam automação como essa para garantir que todos os ambientes — desenvolvimento, homologação, produção — sejam idênticos. Um bug que aparece só em produção muitas vezes é causado por diferença de configuração entre ambientes. Com IaC, isso deixa de ser um problema.
Onboarding de novos servidores vira um processo de minutos. Quando um servidor novo entra na frota, o Ansible aplica toda a configuração padrão automaticamente — sem checklist manual, sem risco de esquecer um passo.
Auditorias de segurança ficam muito mais simples. Em vez de acessar cada servidor para verificar se as configurações de segurança estão corretas, você roda o playbook e o resultado mostra exatamente o que está diferente do esperado.
Resposta a incidentes ganha velocidade. Se um servidor fica comprometido e precisa ser reconstruído do zero, o processo inteiro leva minutos — não horas ou dias tentando lembrar como aquele servidor foi configurado originalmente.
Conclusão Em menos de 5 minutos e algumas dezenas de linhas de YAML, a stack está de pé:
Load Balancer com HAProxy, round-robin e health check automático 2 Web Servers com Nginx respondendo e identificando o host Banco de dados MySQL com acesso restrito à rede interna Segurança aplicada em todos: SSH por chave, firewall ativo, bloqueio de força bruta e atualizações automáticas O ponto mais importante desse tipo de lab não é a tecnologia em si — é o fato de que a infraestrutura vira código. Ela fica versionada, documentada, reproduzível em qualquer ambiente e auditável linha a linha.
O próximo passo natural é integrar esse playbook em um pipeline CI/CD (GitHub Actions ou GitLab CI) e adicionar testes de infraestrutura com Molecule + Testinfra.
Referências:
Documentação oficial do Ansible Ansible Galaxy CIS Benchmarks para Linux ]]></content:encoded>
    </item>
    <item>
      <title>Automatizando Backups de SQL Server no Azure com Ansible</title>
      <link>https://fxshell.com.br/posts/ansible-mssql-backup-azure/</link>
      <pubDate>Tue, 21 Apr 2026 00:00:00 UT</pubDate>
      <dc:creator>Felipe da Matta</dc:creator>
      <guid>https://fxshell.com.br/posts/ansible-mssql-backup-azure/</guid>
      <description>Manter backups consistentes de múltiplos SQL Servers e armazená-los em nuvem é uma tarefa crítica que, quando feita manualmente, vira fonte de erros, esquecimentos e surpresas na hora de um restore. Este lab mostra como resolver isso com Ansible rodando em uma máquina Linux, acionando servidores Windows via WinRM e fazendo o backup ir direto do SQL Server para o Azure Blob Storage — sem que nenhum arquivo passe pela máquina de controle.
</description>
      <content:encoded><![CDATA[Manter backups consistentes de múltiplos SQL Servers e armazená-los em nuvem é uma tarefa crítica que, quando feita manualmente, vira fonte de erros, esquecimentos e surpresas na hora de um restore. Este lab mostra como resolver isso com Ansible rodando em uma máquina Linux, acionando servidores Windows via WinRM e fazendo o backup ir direto do SQL Server para o Azure Blob Storage — sem que nenhum arquivo passe pela máquina de controle.
Objetivo do Lab Construir uma automação completa que, a partir de uma máquina Linux, conecta em múltiplos SQL Servers Windows via WinRM, cria um SQL Server CREDENTIAL com o SAS Token do Azure, e dispara os três tipos de backup nativos do SQL Server (Full, Differential e Transaction Log) diretamente para o Azure Blob Storage via BACKUP TO URL. O projeto inclui também um playbook de restore com suporte a ponto no tempo (point-in-time recovery), onde o SQL Server lê os arquivos diretamente do Azure via RESTORE FROM URL.
Tecnologias Utilizadas Ansible é a ferramenta de automação de infraestrutura que orquestra todo o fluxo. Ela executa tarefas em hosts remotos sem precisar instalar agentes — usa SSH para Linux e WinRM para Windows. No mercado é amplamente usada por times DevOps e SRE para padronizar operações repetitivas.
WinRM (Windows Remote Management) é o protocolo da Microsoft equivalente ao SSH para Windows. O Ansible usa ele, com autenticação NTLM, para executar comandos em servidores Windows sem precisar de nenhum agente instalado.
sqlcmd é a ferramenta de linha de comando do SQL Server para executar scripts T-SQL. O Ansible gera o script .sql via template Jinja2 e chama o sqlcmd remotamente para executá-lo.
BACKUP TO URL / RESTORE FROM URL é o mecanismo nativo do SQL Server (disponível desde 2012 SP1) para fazer backup e restore diretamente de e para o Azure Blob Storage, sem staging local. O SQL Server autentica via um objeto CREDENTIAL que contém o SAS Token — nenhum dado transita pela máquina Ansible.
SQL Server CREDENTIAL é um objeto de segurança do SQL Server que armazena a identidade e o segredo de autenticação para um recurso externo. Neste projeto, o CREDENTIAL aponta para a URL base da conta de armazenamento e usa o SAS Token como SECRET. O Ansible cria ou atualiza esse CREDENTIAL antes de cada execução de backup.
ansible-vault é o mecanismo de criptografia do Ansible para proteger variáveis sensíveis — senhas, tokens, chaves — dentro dos arquivos de inventário e variáveis do projeto.
Arquitetura ┌─────────────────────────────────────────────────────────────────────┐ │ BACKUP FLOW │ │ │ │ ┌──────────────┐ WinRM/NTLM ┌──────────┐ ┌──────────┐ │ │ │ ops-linux │ ─────────────► │ sql01 │ │ sql02 │ │ │ │ (trigger) │ │ sql03 │ │ ... │ │ │ └──────────────┘ └────┬─────┘ └────┬─────┘ │ │ │ BACKUP TO URL│ │ │ ▼ ▼ │ │ ┌──────────────────────────────────────┐ │ │ │ Azure Blob Storage │ │ │ │ 📦 sql-backup-full │ │ │ │ 📦 sql-backup-diff │ │ │ │ 📦 sql-backup-log │ │ │ └──────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ RESTORE FLOW │ │ │ │ ┌──────────────┐ lista blobs ┌──────────────────────────────┐ │ │ │ ops-linux │ ─────────────► │ Azure Blob Storage │ │ │ │ (orquestra) │ ◄─ URLs ───── │ 📦 containers (full/diff/log│ │ │ └──────┬───────┘ └──────────────────────────────┘ │ │ │ WinRM + URLs ▲ │ │ ▼ │ RESTORE FROM URL │ │ ┌──────────────────┐ ──────────────────────────┘ │ │ │ sql-target │ │ │ │ (RESTORE) │ │ │ └──────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ Componente Função ops-linux Ansible Controller — dispara o backup via WinRM e orquestra o restore (somente metadados) sql01..03 SQL Servers Windows — executam BACKUP TO URL e RESTORE FROM URL diretamente sqlcmd Executa os scripts T-SQL de backup/restore no SQL Server SQL CREDENTIAL Objeto no SQL Server que autentica no Azure via SAS Token Azure Blob Storage Destino e origem dos backups, separados por containers Como Funciona o Backup e Restore Direto Com BACKUP TO URL e RESTORE FROM URL, o SQL Server abre uma conexão TLS direto para o endpoint do Azure Blob Storage e faz o stream do backup sem criar arquivo intermediário em nenhum ponto do caminho. A máquina ops envia apenas o script T-SQL (alguns KB) via WinRM e aguarda a conclusão. Nenhum byte de dados de backup trafega pela máquina de controle — ela funciona exclusivamente como orquestradora.
Atenção ao horário: o stream TLS direto do SQL Server para o Azure consome banda de rede da máquina de banco de dados. Em backups Full de bancos grandes (centenas de GB), o upload pode saturar a interface de rede e impactar a latência de queries em produção. Recomenda-se agendar backups Full e o restore na madrugada — janela de menor carga — e reservar o horário comercial para backups Differential e Log, que são muito menores.
Estrutura do Projeto ansible-mssql-backup-azure/ ├── ansible.cfg ├── inventory/ │ └── hosts.ini ├── group_vars/ │ ├── all.yml # Azure storage + SAS Token (vault) │ └── sqlservers.yml # credenciais SQL + lista de bancos ├── playbook-backup.yml ├── playbook-restore.yml └── roles/ ├── mssql_backup/ │ ├── tasks/ │ │ ├── main.yml # cria CREDENTIAL + direciona por tipo │ │ ├── full.yml │ │ ├── diff.yml │ │ └── log.yml │ └── templates/ │ ├── backup_full.sql.j2 │ ├── backup_diff.sql.j2 │ └── backup_log.sql.j2 └── mssql_restore/ ├── tasks/ │ ├── main.yml # cria CREDENTIAL + chama restore.yml │ └── restore.yml └── templates/ └── restore.sql.j2 Inventário e Variáveis O inventário declara os SQL Servers (Windows, via WinRM). A máquina ops não precisa mais estar no inventário para os backups:
[sqlservers] sql01 ansible_host=192.168.1.20 sql02 ansible_host=192.168.1.21 sql03 ansible_host=192.168.1.22 [sqlservers:vars] ansible_user=ansible_svc ansible_password=&#34;{{ vault_winrm_password }}&#34; ansible_connection=winrm ansible_winrm_transport=ntlm ansible_winrm_server_cert_validation=ignore ansible_port=5985 Em group_vars/sqlservers.yml, cada banco é declarado com seu modelo de recuperação, o que determina se backup de log é possível:
databases: - name: &#34;AppDB&#34; recovery_model: &#34;FULL&#34; - name: &#34;LegacyDB&#34; recovery_model: &#34;SIMPLE&#34; # só aceita full e diff backup_compression: true verify_backup: true Em group_vars/all.yml, a URL base da conta é usada tanto para construir as URLs dos blobs quanto como nome do CREDENTIAL no SQL Server:
azure_storage_account: &#34;minhaconta&#34; azure_container_full: &#34;sql-backup-full&#34; azure_container_diff: &#34;sql-backup-diff&#34; azure_container_log: &#34;sql-backup-log&#34; # SAS Token: permissões read + write + create + list vault_azure_sas_token: !vault | $ANSIBLE_VAULT;1.1;AES256 &lt;criptografado&gt; azure_blob_base_url: &#34;https://{{ azure_storage_account }}.blob.core.windows.net&#34; Para criar o vault:
ansible-vault encrypt_string &#39;sv=2022-11-02&amp;ss=b&amp;...&#39; --name &#39;vault_azure_sas_token&#39; SQL Server CREDENTIAL Antes de executar qualquer backup, a role cria (ou atualiza) um CREDENTIAL no SQL Server com o SAS Token. Esse objeto é o que permite o SQL Server autenticar no Azure sem precisar de credenciais da conta de armazenamento:
CREATE CREDENTIAL [https://minhaconta.blob.core.windows.net] WITH IDENTITY = &#39;SHARED ACCESS SIGNATURE&#39;, SECRET = &#39;sv=2022-11-02&amp;ss=b&amp;srt=co&amp;sp=rwdlc&amp;se=2027-01-01...&amp;sig=...&#39;; O Ansible executa esse script via win_shell + sqlcmd com no_log: true, garantindo que o SAS Token nunca apareça nos logs da execução.
Os Três Tipos de Backup Full — Backup Completo O backup Full captura o banco inteiro. É sempre o ponto de partida para qualquer cadeia de restore.
ansible-playbook playbook-backup.yml -e backup_type=full --ask-vault-pass O script T-SQL gerado pelo template aponta direto para a URL do blob no Azure:
BACKUP DATABASE [AppDB] TO URL = N&#39;https://minhaconta.blob.core.windows.net/sql-backup-full/sql01/2026-04-24/sql01_AppDB_FULL_20260424030000.bak&#39; WITH FORMAT, INIT, NAME = N&#39;AppDB - Full Backup 20260424030000&#39;, COMPRESSION, STATS = 10, CHECKSUM; Após o backup, o SQL Server executa RESTORE VERIFYONLY FROM URL para confirmar a integridade do arquivo diretamente no Azure, antes do Ansible registrar o resultado.
Differential — Backup Diferencial Captura apenas os dados que mudaram desde o último Full. Muito mais rápido e menor em tamanho, ideal para execuções diárias quando o Full é semanal.
ansible-playbook playbook-backup.yml -e backup_type=diff --ask-vault-pass BACKUP DATABASE [AppDB] TO URL = N&#39;https://minhaconta.blob.core.windows.net/sql-backup-diff/sql01/2026-04-24/sql01_AppDB_DIFF_20260424120000.bak&#39; WITH DIFFERENTIAL, COMPRESSION, STATS = 10, CHECKSUM; Transaction Log — Backup de Log de Transações Captura todas as transações registradas no log desde o último backup de log. Permite restaurar o banco para qualquer ponto no tempo (point-in-time recovery). Requer que o banco use o modelo de recuperação FULL ou BULK_LOGGED — o script verifica isso antes de executar.
ansible-playbook playbook-backup.yml -e backup_type=log --ask-vault-pass BACKUP LOG [AppDB] TO URL = N&#39;https://minhaconta.blob.core.windows.net/sql-backup-log/sql01/2026-04-24/sql01_AppDB_LOG_20260424150000.bak&#39; WITH COMPRESSION, STATS = 10, CHECKSUM; Fluxo Interno de Cada Backup Para cada banco, a role mssql_backup executa na sequência:
Cria ou atualiza o SQL Server CREDENTIAL com o SAS Token (via win_shell + sqlcmd, no_log: true) Monta a URL de destino: {conta}/{container}/{host}/{data}/{host}_{banco}_{tipo}_{timestamp}.bak Gera o script .sql via win_template (Jinja2) e salva em C:\Windows\Temp\ Executa o backup com sqlcmd — o SQL Server abre conexão TLS direto para o Azure e faz o stream Verifica integridade com RESTORE VERIFYONLY FROM URL (quando verify_backup: true) Remove o script .sql temporário do SQL Server A máquina ops envia apenas texto (script SQL + comandos WinRM). Nenhum byte de dados de backup trafega por ela.
Playbook de Restore O restore suporta quatro modos:
Modo O que aplica full Somente backup Full full_diff Full + Differential mais recente full_diff_log Full + Diff + todos os Logs em sequência point_in_time Full + Diff + Logs até um STOPAT específico O playbook lista os blobs disponíveis no Azure via API REST (apenas metadados, sem download) usando o módulo uri do Ansible, e passa as URLs para o SQL Server executar RESTORE FROM URL:
# Restore simples ansible-playbook playbook-restore.yml \ -e restore_db=AppDB \ -e restore_target_host=sql01 \ -e restore_date=2026-04-24 \ --ask-vault-pass # Point-in-time recovery ansible-playbook playbook-restore.yml \ -e restore_db=AppDB \ -e restore_target_host=sql01 \ -e restore_date=2026-04-24 \ -e restore_mode=point_in_time \ -e restore_stopat=&#34;2026-04-24T14:30:00&#34; \ --ask-vault-pass # Restaurar com nome diferente (não sobrescreve o banco original) ansible-playbook playbook-restore.yml \ -e restore_db=AppDB \ -e restore_new_name=AppDB_RestoreTest \ -e restore_target_host=sql01 \ -e restore_date=2026-04-24 \ -e restore_mode=full_diff \ --ask-vault-pass O script T-SQL de restore lê os arquivos diretamente do Azure, gerenciando o estado do banco durante a sequência:
-- FULL com NORECOVERY (banco fica em &#34;restaurando&#34; para aceitar diff/log) RESTORE DATABASE [AppDB] FROM URL = N&#39;https://minhaconta.blob.core.windows.net/sql-backup-full/sql01/2026-04-24/sql01_AppDB_FULL_20260424030000.bak&#39; WITH NORECOVERY, REPLACE, STATS = 10; -- DIFF com NORECOVERY RESTORE DATABASE [AppDB] FROM URL = N&#39;https://minhaconta.blob.core.windows.net/sql-backup-diff/sql01/2026-04-24/sql01_AppDB_DIFF_20260424120000.bak&#39; WITH NORECOVERY, STATS = 10; -- LOG final com RECOVERY (encerra a sequência) e STOPAT opcional RESTORE LOG [AppDB] FROM URL = N&#39;https://minhaconta.blob.core.windows.net/sql-backup-log/sql01/2026-04-24/sql01_AppDB_LOG_20260424150000.bak&#39; WITH STOPAT = &#39;2026-04-24 14:30:00&#39;, RECOVERY; -- Volta para multi-user ALTER DATABASE [AppDB] SET MULTI_USER; O uso de NORECOVERY em todos os passos exceto o último é obrigatório — ele mantém o banco em modo de restauração para aceitar os próximos arquivos. Apenas o último comando usa RECOVERY, que finaliza a sequência e coloca o banco online.
Segurança Todos os segredos ficam no vault — nunca em texto plano:
# Criptografar SAS token do Azure (permissões: read + write + create + list) ansible-vault encrypt_string &#39;sv=2022-11-02&amp;ss=b...&#39; --name &#39;vault_azure_sas_token&#39; # Criptografar senha WinRM ansible-vault encrypt_string &#39;SenhaDoServico123!&#39; --name &#39;vault_winrm_password&#39; # Criptografar senha SQL ansible-vault encrypt_string &#39;SenhaSql123!&#39; --name &#39;mssql_password&#39; O SAS Token é passado para o SQL Server dentro do script T-SQL gerado em C:\Windows\Temp\ com no_log: true no Ansible, e o arquivo temporário é removido imediatamente após a execução. O token nunca aparece nos logs do Ansible nem no histórico do shell.
O usuário SQL usado (backup_svc) precisa das permissões mínimas para BACKUP TO URL:
CREATE LOGIN backup_svc WITH PASSWORD = &#39;SenhaSql123!&#39;; GRANT CONNECT SQL TO backup_svc; -- Em cada banco: EXEC sp_addrolemember &#39;db_backupoperator&#39;, &#39;backup_svc&#39;; GRANT VIEW DATABASE STATE TO backup_svc; -- Para criar o CREDENTIAL (necessário para BACKUP TO URL): GRANT ALTER ANY CREDENTIAL TO backup_svc; Executando # Testar conectividade antes de tudo ansible sqlservers -m win_ping --ask-vault-pass # Backup Full de todos os bancos (recomendado: madrugada — ex: 02:00) ansible-playbook playbook-backup.yml -e backup_type=full --ask-vault-pass # Backup Full de um banco específico ansible-playbook playbook-backup.yml -e backup_type=full -e target_db=AppDB --ask-vault-pass # Backup Differential (pode rodar durante o dia — arquivo menor, menos impacto de rede) ansible-playbook playbook-backup.yml -e backup_type=diff --ask-vault-pass # Backup de Log (pode rodar a cada hora — arquivo pequeno, impacto mínimo) ansible-playbook playbook-backup.yml -e backup_type=log --ask-vault-pass # Restore full + diff + log (recomendado: madrugada — evita concorrência com queries ativas) ansible-playbook playbook-restore.yml \ -e restore_db=AppDB \ -e restore_target_host=sql01 \ -e restore_date=2026-04-24 \ -e restore_mode=full_diff_log \ --ask-vault-pass Agendamento sugerido (cron no controller) # Full todo domingo às 02:00 0 2 * * 0 cd /opt/ansible/mssql-backup &amp;&amp; ansible-playbook playbook-backup.yml -e backup_type=full --vault-password-file=.vault_pass # Differential de segunda a sábado às 02:00 0 2 * * 1-6 cd /opt/ansible/mssql-backup &amp;&amp; ansible-playbook playbook-backup.yml -e backup_type=diff --vault-password-file=.vault_pass # Transaction Log a cada hora (bancos com recovery_model FULL) 0 * * * * cd /opt/ansible/mssql-backup &amp;&amp; ansible-playbook playbook-backup.yml -e backup_type=log --vault-password-file=.vault_pass Lifecycle Management — Reduzindo Custo de Armazenamento O BACKUP TO URL grava os arquivos no tier Hot por padrão. Isso faz sentido nos primeiros dias, quando a chance de precisar de um restore rápido é maior. Mas backups são arquivos que você escreve uma vez e só lê em emergência — manter meses de backups no tier Hot é jogar dinheiro fora.
O Azure Blob Storage oferece quatro tiers de armazenamento, cada um com custo e latência de acesso diferentes:
Tier Custo/GB/mês (aprox.) Acesso Uso ideal Hot ~$0.018 Imediato Backups recentes (últimos 7 dias) Cool ~$0.010 Imediato Backups da última semana a um mês Cold ~$0.0036 Imediato Backups de 1 a 3 meses Archive ~$0.002 Horas para reidratar Retenção longa ou compliance A diferença é significativa: um backup de 100 GB no tier Hot custa ~$1.80/mês, no Cold custa ~$0.36/mês. Em um ambiente com vários servidores e meses de retenção, a economia acumula rápido.
Lifecycle Management Policy Em vez de mover blobs manualmente entre tiers, o Azure permite criar uma Lifecycle Management Policy no Storage Account. Essa política é avaliada automaticamente uma vez por dia e move os blobs entre tiers com base na idade — sem script, sem cron, sem custo de operação.
A estratégia recomendada para backups:
0-7 dias → Hot (restore imediato se precisar) 7-30 dias → Cool (metade do custo) 30-90 dias → Cold (1/5 do custo) 90+ dias → Archive ou deletar (conforme retenção exigida) Configurando via Azure CLI Primeiro, crie um arquivo JSON com a política de lifecycle. Este exemplo aplica a movimentação progressiva entre tiers e deleta backups com mais de 365 dias:
{ &#34;rules&#34;: [ { &#34;enabled&#34;: true, &#34;name&#34;: &#34;backup-lifecycle&#34;, &#34;type&#34;: &#34;Lifecycle&#34;, &#34;definition&#34;: { &#34;actions&#34;: { &#34;baseBlob&#34;: { &#34;tierToCool&#34;: { &#34;daysAfterModificationGreaterThan&#34;: 7 }, &#34;tierToCold&#34;: { &#34;daysAfterModificationGreaterThan&#34;: 30 }, &#34;tierToArchive&#34;: { &#34;daysAfterModificationGreaterThan&#34;: 90 }, &#34;delete&#34;: { &#34;daysAfterModificationGreaterThan&#34;: 365 } } }, &#34;filters&#34;: { &#34;blobTypes&#34;: [&#34;blockBlob&#34;], &#34;prefixMatch&#34;: [ &#34;sql-backup-full/&#34;, &#34;sql-backup-diff/&#34;, &#34;sql-backup-log/&#34; ] } } } ] } O filtro prefixMatch garante que a política se aplica apenas aos containers de backup, sem afetar outros blobs na mesma Storage Account.
Agora aplique a política na Storage Account:
# Aplicar a lifecycle policy az storage account management-policy create \ --account-name minhaconta \ --resource-group meu-rg \ --policy @lifecycle-policy.json # Verificar a política aplicada az storage account management-policy show \ --account-name minhaconta \ --resource-group meu-rg Considerações sobre Archive e Restore O tier Archive tem o menor custo de armazenamento, mas a reidratação leva horas (Standard: até 15 horas, High Priority: até 1 hora com custo maior). Se o restore precisa ser rápido, considere usar Cold como tier final em vez de Archive — o custo é ligeiramente maior, mas o acesso é imediato.
Para verificar em qual tier cada blob está:
# Listar blobs com o tier de acesso az storage blob list \ --account-name minhaconta \ --container-name sql-backup-full \ --query &#34;[].{name:name, tier:properties.blobTier, modified:properties.lastModified}&#34; \ --output table \ --auth-mode login A Lifecycle Policy é avaliada uma vez por dia pelo Azure. Após criar a política, os blobs existentes serão movidos gradualmente nas próximas 24-48 horas conforme as regras definidas.
Para Que Serve no Mercado Times de DBA e SRE que gerenciam ambientes com SQL Server Windows enfrentam o desafio de manter backups consistentes sem depender de jobs do SQL Server Agent configurados manualmente em cada instância. Com Ansible, a política de backup fica no código, versionada no Git, aplicável a qualquer número de servidores com um único comando.
O modelo BACKUP TO URL faz o SQL Server enviar o backup diretamente ao Azure via TLS, sem staging intermediário. A máquina de controle funciona apenas como orquestradora — sem impacto de I/O de dados, independente do tamanho dos backups. Isso permite escalar o número de servidores e bancos sem aumentar disco na infraestrutura Ansible.
O suporte a point-in-time recovery é o que diferencia um backup operacional de um backup de compliance — em caso de corrupção de dados, ransomware ou erro humano, a capacidade de restaurar para um momento específico pode ser a diferença entre minutos e horas de downtime.
Conclusão Automatizar backups não é apenas uma questão de conveniência — é uma prática de resiliência. Quando o restore precisa acontecer, não é hora de descobrir que o backup estava corrompido, desatualizado ou mal documentado. Este projeto aplica verificação de integridade no próprio Azure após cada backup (RESTORE VERIFYONLY FROM URL), cadeia de restore estruturada no código, segredos protegidos por vault e zero impacto de disco na máquina de controle — tornando o processo auditável, reproduzível e escalável.
Referências Documentação do Ansible para Windows BACKUP TO URL — SQL Server para Microsoft Azure Criar um SQL Server Credential para autenticação no Azure T-SQL BACKUP DATABASE T-SQL RESTORE DATABASE ansible-vault Lifecycle Management Policy — Azure Blob Storage Access Tiers — Hot, Cool, Cold, Archive ]]></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>
