<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>Aws on FXShell - DevOps &amp; Sec</title>
    <link>https://fxshell.com.br/tags/aws/</link>
    <description>Recent content in Aws on FXShell - DevOps &amp; Sec</description>
    <image>
      <title>Aws on FXShell - DevOps &amp; Sec</title>
      <link>https://fxshell.com.br/tags/aws/</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/aws/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>Terraform AWS — Provisionando uma VPC Segura com ALB, EC2 e RDS</title>
      <link>https://fxshell.com.br/posts/terraform-aws-vpc/</link>
      <pubDate>Tue, 21 Apr 2026 00:00:00 UT</pubDate>
      <dc:creator>Felipe da Matta</dc:creator>
      <guid>https://fxshell.com.br/posts/terraform-aws-vpc/</guid>
      <description>Toda vez que um time precisa criar um novo ambiente — seja para testes, staging ou produção — o processo manual de clicar no console da AWS abre espaço para erros, inconsistências e ambientes que ninguém sabe exatamente como foram configurados. Infraestrutura como Código resolve isso: você escreve a infraestrutura em arquivos de texto, versiona no Git e aplica com um comando.
Neste lab, construo uma VPC completa na AWS usando Terraform, com balanceador de carga, servidores web em subnets privadas, banco de dados MySQL e state remoto no S3.
</description>
      <content:encoded><![CDATA[Toda vez que um time precisa criar um novo ambiente — seja para testes, staging ou produção — o processo manual de clicar no console da AWS abre espaço para erros, inconsistências e ambientes que ninguém sabe exatamente como foram configurados. Infraestrutura como Código resolve isso: você escreve a infraestrutura em arquivos de texto, versiona no Git e aplica com um comando.
Neste lab, construo uma VPC completa na AWS usando Terraform, com balanceador de carga, servidores web em subnets privadas, banco de dados MySQL e state remoto no S3.
Objetivo do Lab Provisionar, via Terraform, uma infraestrutura web segura na AWS contendo:
VPC com subnets públicas e privadas em duas zonas de disponibilidade Application Load Balancer nas subnets públicas Duas instâncias EC2 com Nginx nas subnets privadas RDS MySQL 8 em subnet privada, sem acesso público Bastion host para acesso SSH operacional NAT Gateway para que as instâncias privadas acessem a internet State remoto no S3 com lock via DynamoDB Tecnologias Utilizadas Terraform é uma ferramenta de infraestrutura como código criada pela HashiCorp. Você descreve os recursos que quer criar (VMs, redes, bancos de dados) em arquivos .tf e o Terraform se encarrega de criar, modificar ou destruir esses recursos na cloud. É amplamente usada em times de DevOps e SRE para gerenciar infraestrutura de forma consistente e reproduzível.
AWS VPC (Virtual Private Cloud) é uma rede virtual isolada dentro da AWS onde você controla o espaço de endereçamento IP, as rotas e as regras de acesso. Tudo que você cria na AWS precisa estar dentro de uma VPC.
Subnets públicas e privadas dividem a VPC em camadas. Recursos em subnets públicas têm IP público e acesso direto à internet via Internet Gateway. Recursos em subnets privadas ficam isolados da internet — só se comunicam com o mundo externo via NAT Gateway, sem exposição direta.
Application Load Balancer (ALB) distribui o tráfego HTTP/HTTPS entre as instâncias de destino. Fica nas subnets públicas e repassa as requisições para os servidores web nas subnets privadas, aplicando health checks automaticamente.
EC2 (Elastic Compute Cloud) são as máquinas virtuais da AWS. Neste lab uso instâncias t3.micro com Amazon Linux 2023 e Nginx.
RDS (Relational Database Service) é o serviço gerenciado de banco de dados da AWS. A AWS cuida de backups, patches e failover. Uso MySQL 8 em instância db.t3.micro isolada em subnet privada.
S3 backend armazena o terraform.tfstate — arquivo que registra o estado atual da infraestrutura — em um bucket S3 com criptografia ativada. O DynamoDB garante que dois operadores não apliquem mudanças ao mesmo tempo (locking).
Arquitetura INTERNET │ ▼ [Internet Gateway] │ ▼ ┌─────────────────────────────────────────────┐ │ VPC — 10.0.0.0/16 │ │ │ │ ┌── us-east-1a ──┐ ┌── us-east-1b ──┐ │ │ │ public subnet │ │ public subnet │ │ │ │ 10.0.1.0/24 │ │ 10.0.2.0/24 │ │ │ │ [bastion] │ │ [nat-gw] │ │ │ └────────────────┘ └────────────────┘ │ │ │ │ │ │ [Application Load Balancer] │ │ │ │ │ │ ┌── private ─────┐ ┌── private ─────┐ │ │ │ 10.0.10.0/24 │ │ 10.0.20.0/24 │ │ │ │ [web-01 EC2] │ │ [web-02 EC2] │ │ │ └────────────────┘ └────────────────┘ │ │ │ │ │ [RDS MySQL] │ │ 10.0.10.0/24 (private) │ └─────────────────────────────────────────────┘ │ [S3 — tfstate] Recurso Tipo Subnet Acesso Internet Gateway aws_internet_gateway — Internet ALB aws_lb Pública Internet → EC2 Bastion EC2 t3.nano Pública SSH operador NAT Gateway aws_nat_gateway Pública Saída privada web-01 / web-02 EC2 t3.micro Privada Via ALB RDS MySQL 8 db.t3.micro Privada Via EC2 S3 (tfstate) aws_s3_bucket — Terraform Estrutura dos Arquivos terraform-aws-vpc/ ├── main.tf ← provider, backend S3 ├── variables.tf ← todas as variáveis com descrição e validação ├── outputs.tf ← DNS do ALB, IPs, endpoint RDS ├── vpc.tf ← VPC, subnets, IGW, NAT, route tables ├── security_groups.tf ← SGs separados por recurso ├── ec2.tf ← instâncias web e bastion ├── alb.tf ← ALB, target group, listener, S3 de logs └── rds.tf ← RDS MySQL, subnet group Separar em arquivos por responsabilidade facilita a leitura e evita um main.tf com centenas de linhas.
Backend S3 — State Remoto O state do Terraform registra exatamente o que foi criado na AWS. Sem state remoto, cada pessoa da equipe teria seu próprio estado local — o que é um desastre em times. O backend S3 centraliza o state com criptografia e o DynamoDB evita que dois apply aconteçam ao mesmo tempo.
Antes de qualquer coisa, crie o bucket e a tabela DynamoDB (só precisa fazer uma vez):
aws s3api create-bucket \ --bucket fxshell-terraform-state \ --region us-east-1 aws s3api put-bucket-versioning \ --bucket fxshell-terraform-state \ --versioning-configuration Status=Enabled aws dynamodb create-table \ --table-name terraform-locks \ --attribute-definitions AttributeName=LockID,AttributeType=S \ --key-schema AttributeName=LockID,KeyType=HASH \ --billing-mode PAY_PER_REQUEST VPC — Rede e Roteamento A VPC cria o espaço de rede isolado. As subnets públicas têm rota para o Internet Gateway; as privadas têm rota para o NAT Gateway — assim o tráfego de saída das instâncias privadas passa pelo NAT sem expor as máquinas diretamente.
resource &#34;aws_vpc&#34; &#34;main&#34; { cidr_block = var.vpc_cidr enable_dns_hostnames = true enable_dns_support = true tags = { Name = &#34;${var.project_name}-vpc&#34; } } O enable_dns_hostnames = true é necessário para que o RDS receba um hostname DNS resolvível dentro da VPC.
Security Groups — Princípio do Menor Privilégio Cada recurso tem seu próprio Security Group com regras mínimas. O EC2 aceita HTTP apenas do ALB, não da internet. O RDS aceita MySQL apenas do EC2. O bastion aceita SSH apenas do IP configurado.
# EC2 só aceita tráfego do ALB ingress { description = &#34;HTTP do ALB&#34; from_port = 80 to_port = 80 protocol = &#34;tcp&#34; security_groups = [aws_security_group.alb.id] } # RDS só aceita do EC2 ingress { description = &#34;MySQL das EC2&#34; from_port = 3306 to_port = 3306 protocol = &#34;tcp&#34; security_groups = [aws_security_group.ec2.id] } Referenciar o ID de outro Security Group como origem (em vez de um CIDR) é mais seguro: se uma instância for substituída e ganhar novo IP, a regra continua válida.
EC2 — Instâncias com IMDSv2 As instâncias usam user_data para instalar e configurar o Nginx automaticamente na inicialização. O http_tokens = &quot;required&quot; força o uso do IMDSv2 — a versão mais segura do serviço de metadados das instâncias, que exige um token de sessão antes de responder.
metadata_options { http_tokens = &#34;required&#34; } ALB — Balanceamento com Health Check O ALB distribui requisições entre as instâncias usando o algoritmo Round Robin. O health check testa a rota / a cada 30 segundos: se uma instância não responder com HTTP 200 por 3 verificações consecutivas, ela é removida do pool até se recuperar.
health_check { healthy_threshold = 2 unhealthy_threshold = 3 interval = 30 path = &#34;/&#34; matcher = &#34;200&#34; } Executando Antes de aplicar, sempre revise o plan:
# Inicializa o provider e o backend terraform init # Mostra o que será criado — sem alterar nada terraform plan -out=tfplan # Aplica o plan aprovado terraform apply tfplan A senha do banco nunca deve estar no código. Passe via variável de ambiente:
export TF_VAR_db_password=&#34;sua-senha-segura&#34; terraform apply Após o apply, os outputs mostram os dados importantes:
terraform output alb_dns_name # DNS para acessar a aplicação terraform output bastion_public_ip # IP para SSH de manutenção terraform output rds_endpoint # Endpoint do banco (sensitive) Para Destruir o Lab terraform destroy O Terraform remove todos os recursos na ordem correta, respeitando as dependências.
Para Que Serve no Mercado Times de cloud usam esse padrão (VPC + ALB + EC2 + RDS em subnets privadas) como base para praticamente qualquer aplicação web na AWS. O que muda é o tamanho das instâncias, o número de AZs e os serviços adicionais.
Com Terraform, essa infraestrutura vira um módulo reutilizável. Um time pode criar um ambiente de staging idêntico ao de produção em minutos, com certeza de que as configurações são as mesmas — algo impossível de garantir clicando manualmente no console.
Conclusão Infraestrutura como código não é só sobre automação — é sobre confiança. Quando a infra está em arquivos .tf versionados no Git, qualquer pessoa do time pode entender o que existe, revisar mudanças em pull requests e reverter para um estado anterior se algo der errado. O terraform plan funciona como um diff da infraestrutura: você vê exatamente o que vai mudar antes de mudar.
Referências Terraform AWS Provider Docs AWS VPC User Guide Terraform Best Practices ]]></content:encoded>
    </item>
  </channel>
</rss>
