DevOps & CI/CD

Multi-cloud e Arquitetura Cloud-Agnostic com Terraform e Kubernetes na Prática

15 min de leitura

Multi-cloud e Arquitetura Cloud-Agnostic com Terraform e Kubernetes na Prática

O que é Multi-Cloud e Cloud-Agnostic Multi-cloud refere-se à estratégia de utilizar múltiplos provedores de nuvem (AWS, Azure, Google Cloud, etc.) simultaneamente em uma mesma organização. Cloud-agnostic, por sua vez, significa construir arquiteturas e aplicações que não dependem de serviços específicos de um único provedor, permitindo portabilidade e flexibilidade. A combinação dessas duas abordagens reduz riscos de vendor lock-in, melhora a disponibilidade e oferece oportunidades de otimização de custos. Quando você constrói uma aplicação cloud-agnostic, você se afasta de serviços gerenciados proprietários (como AWS Lambda, Azure Functions ou Google Cloud Run) e prefere abstrações que funcionam igualmente bem em qualquer nuvem. Isso significa usar contêineres, orquestração padrão e infraestrutura como código agnóstica. O resultado é uma organização mais resiliente, capaz de migrar workloads entre provedores sem reescrever toda a aplicação. Por que isso importa na prática Empresas que adotam multi-cloud conseguem negociar melhores preços, distribuir risco de outages e selecionar os melhores serviços de cada provedor sem medo de aprisionamento.

<h2>O que é Multi-Cloud e Cloud-Agnostic</h2>

<p>Multi-cloud refere-se à estratégia de utilizar múltiplos provedores de nuvem (AWS, Azure, Google Cloud, etc.) simultaneamente em uma mesma organização. Cloud-agnostic, por sua vez, significa construir arquiteturas e aplicações que não dependem de serviços específicos de um único provedor, permitindo portabilidade e flexibilidade. A combinação dessas duas abordagens reduz riscos de vendor lock-in, melhora a disponibilidade e oferece oportunidades de otimização de custos.</p>

<p>Quando você constrói uma aplicação cloud-agnostic, você se afasta de serviços gerenciados proprietários (como AWS Lambda, Azure Functions ou Google Cloud Run) e prefere abstrações que funcionam igualmente bem em qualquer nuvem. Isso significa usar contêineres, orquestração padrão e infraestrutura como código agnóstica. O resultado é uma organização mais resiliente, capaz de migrar workloads entre provedores sem reescrever toda a aplicação.</p>

<h3>Por que isso importa na prática</h3>

<p>Empresas que adotam multi-cloud conseguem negociar melhores preços, distribuir risco de outages e selecionar os melhores serviços de cada provedor sem medo de aprisionamento. Um exemplo real: se sua aplicação roda em Kubernetes, ela funciona identicamente em EKS (AWS), AKS (Azure) ou GKE (Google Cloud), desde que você use apenas recursos nativos do Kubernetes.</p>

<h2>Terraform para Orquestração Multi-Cloud</h2>

<p>Terraform é uma ferramenta de Infrastructure as Code (IaC) que permite descrever sua infraestrutura de forma declarativa. O grande diferencial é seu suporte a múltiplos provedores através de providers, permitindo gerenciar recursos em AWS, Azure, Google Cloud e outras plataformas com uma sintaxe única e consistente.</p>

<p>A linguagem do Terraform (HCL — HashiCorp Configuration Language) é agnóstica por design. Você escreve uma vez e provisiona em diferentes provedores alterando apenas algumas variáveis e a seleção do provider. Isso é fundamental para arquiteturas multi-cloud, pois centraliza o controle da infraestrutura em um único lugar.</p>

<h3>Estrutura básica de um projeto Terraform multi-cloud</h3>

<pre><code class="language-hcl"># providers.tf - Define os provedores que serão usados

terraform {

required_version = &quot;&gt;= 1.0&quot;

required_providers {

aws = {

source = &quot;hashicorp/aws&quot;

version = &quot;~&gt; 5.0&quot;

}

azurerm = {

source = &quot;hashicorp/azurerm&quot;

version = &quot;~&gt; 3.0&quot;

}

google = {

source = &quot;hashicorp/google&quot;

version = &quot;~&gt; 5.0&quot;

}

}

}

provider &quot;aws&quot; {

region = var.aws_region

}

provider &quot;azurerm&quot; {

features {}

subscription_id = var.azure_subscription_id

}

provider &quot;google&quot; {

project = var.gcp_project_id

region = var.gcp_region

}</code></pre>

<pre><code class="language-hcl"># variables.tf - Variáveis agnósticas

variable &quot;cluster_name&quot; {

description = &quot;Nome do cluster Kubernetes&quot;

type = string

default = &quot;my-app-cluster&quot;

}

variable &quot;cluster_version&quot; {

description = &quot;Versão do Kubernetes&quot;

type = string

default = &quot;1.27&quot;

}

variable &quot;node_count&quot; {

description = &quot;Número de nós no cluster&quot;

type = number

default = 3

}

variable &quot;aws_region&quot; {

type = string

default = &quot;us-east-1&quot;

}

variable &quot;azure_subscription_id&quot; {

type = string

sensitive = true

}

variable &quot;gcp_project_id&quot; {

type = string

}

variable &quot;gcp_region&quot; {

type = string

default = &quot;us-central1&quot;

}</code></pre>

<pre><code class="language-hcl"># aws_cluster.tf - Cluster EKS na AWS

resource &quot;aws_eks_cluster&quot; &quot;main&quot; {

name = var.cluster_name

role_arn = aws_iam_role.eks_cluster_role.arn

version = var.cluster_version

vpc_config {

subnet_ids = aws_subnet.private[*].id

}

depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]

}

resource &quot;aws_eks_node_group&quot; &quot;main&quot; {

cluster_name = aws_eks_cluster.main.name

node_group_name = &quot;${var.cluster_name}-nodes&quot;

node_role_arn = aws_iam_role.eks_node_role.arn

subnet_ids = aws_subnet.private[*].id

version = var.cluster_version

scaling_config {

desired_size = var.node_count

max_size = var.node_count + 2

min_size = var.node_count

}

instance_types = [&quot;t3.medium&quot;]

}

resource &quot;aws_iam_role&quot; &quot;eks_cluster_role&quot; {

name = &quot;${var.cluster_name}-cluster-role&quot;

assume_role_policy = jsonencode({

Version = &quot;2012-10-17&quot;

Statement = [{

Action = &quot;sts:AssumeRole&quot;

Effect = &quot;Allow&quot;

Principal = {

Service = &quot;eks.amazonaws.com&quot;

}

}]

})

}

resource &quot;aws_iam_role_policy_attachment&quot; &quot;eks_cluster_policy&quot; {

policy_arn = &quot;arn:aws:iam::aws:policy/AmazonEKSClusterPolicy&quot;

role = aws_iam_role.eks_cluster_role.name

}

resource &quot;aws_iam_role&quot; &quot;eks_node_role&quot; {

name = &quot;${var.cluster_name}-node-role&quot;

assume_role_policy = jsonencode({

Version = &quot;2012-10-17&quot;

Statement = [{

Action = &quot;sts:AssumeRole&quot;

Effect = &quot;Allow&quot;

Principal = {

Service = &quot;ec2.amazonaws.com&quot;

}

}]

})

}

resource &quot;aws_iam_role_policy_attachment&quot; &quot;eks_worker_policy&quot; {

policy_arn = &quot;arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy&quot;

role = aws_iam_role.eks_node_role.name

}</code></pre>

<pre><code class="language-hcl"># azure_cluster.tf - Cluster AKS no Azure

resource &quot;azurerm_resource_group&quot; &quot;main&quot; {

name = &quot;${var.cluster_name}-rg&quot;

location = &quot;East US&quot;

}

resource &quot;azurerm_kubernetes_cluster&quot; &quot;main&quot; {

name = var.cluster_name

location = azurerm_resource_group.main.location

resource_group_name = azurerm_resource_group.main.name

dns_prefix = var.cluster_name

kubernetes_version = var.cluster_version

default_node_pool {

name = &quot;default&quot;

node_count = var.node_count

vm_size = &quot;Standard_B2s&quot;

}

identity {

type = &quot;SystemAssigned&quot;

}

}</code></pre>

<pre><code class="language-hcl"># gcp_cluster.tf - Cluster GKE no Google Cloud

resource &quot;google_container_cluster&quot; &quot;main&quot; {

name = var.cluster_name

location = var.gcp_region

initial_node_count = var.node_count

node_config {

machine_type = &quot;e2-medium&quot;

oauth_scopes = [

&quot;https://www.googleapis.com/auth/cloud-platform&quot;

]

}

min_master_version = var.cluster_version

}</code></pre>

<p>Esse padrão permite que você provisione a mesma aplicação (um cluster Kubernetes) em três provedores diferentes usando a mesma configuração base. O Terraform gerencia as diferenças específicas de cada provedor internamente.</p>

<h2>Kubernetes como Camada de Abstração</h2>

<p>Kubernetes é o grande nivelador em arquiteturas multi-cloud. Ele funciona de maneira praticamente idêntica em qualquer provedor ou até mesmo on-premise, oferecendo uma API padrão para orquestração de contêineres. Enquanto você usa Terraform para provisionar a infraestrutura dos clusters, usa Kubernetes para gerenciar as aplicações dentro deles.</p>

<p>A chave para o cloud-agnostic é manter suas definições de workload (Deployments, Services, ConfigMaps, etc.) livres de extensões específicas de nuvem. Isso significa evitar NodePort quando possível e, em vez disso, usar abstrações que funcionam em qualquer nuvem. Quando você precisa de um balanceador externo, considere usar um ingress controller neutro como NGINX, em vez de confiar apenas no LoadBalancer service que pode se comportar diferentemente em cada provedor.</p>

<h3>Aplicação agnóstica rodando em múltiplos clusters</h3>

<pre><code class="language-yaml"># app-namespace.yaml

apiVersion: v1

kind: Namespace

metadata:

name: production</code></pre>

<pre><code class="language-yaml"># app-configmap.yaml

apiVersion: v1

kind: ConfigMap

metadata:

name: app-config

namespace: production

data:

LOG_LEVEL: &quot;info&quot;

DATABASE_HOST: &quot;db.internal&quot;

ENVIRONMENT: &quot;production&quot;</code></pre>

<pre><code class="language-yaml"># app-secret.yaml - Use ferramentas como Sealed Secrets para produção

apiVersion: v1

kind: Secret

metadata:

name: app-secrets

namespace: production

type: Opaque

data:

API_KEY: YWJjZGVmZ2hpamtsbW5vcA== # base64

DATABASE_PASSWORD: c3VwZXJzZWNyZXQ=</code></pre>

<pre><code class="language-yaml"># app-deployment.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

name: my-app

namespace: production

labels:

app: my-app

version: v1

spec:

replicas: 3

strategy:

type: RollingUpdate

rollingUpdate:

maxSurge: 1

maxUnavailable: 0

selector:

matchLabels:

app: my-app

template:

metadata:

labels:

app: my-app

spec:

containers:

  • name: app

image: myregistry.azurecr.io/my-app:1.0.0

imagePullPolicy: IfNotPresent

ports:

  • name: http

containerPort: 8080

protocol: TCP

env:

  • name: LOG_LEVEL

valueFrom:

configMapKeyRef:

name: app-config

key: LOG_LEVEL

  • name: DATABASE_PASSWORD

valueFrom:

secretKeyRef:

name: app-secrets

key: DATABASE_PASSWORD

resources:

requests:

memory: &quot;128Mi&quot;

cpu: &quot;100m&quot;

limits:

memory: &quot;512Mi&quot;

cpu: &quot;500m&quot;

livenessProbe:

httpGet:

path: /health

port: http

initialDelaySeconds: 10

periodSeconds: 10

readinessProbe:

httpGet:

path: /ready

port: http

initialDelaySeconds: 5

periodSeconds: 5</code></pre>

<pre><code class="language-yaml"># app-service.yaml

apiVersion: v1

kind: Service

metadata:

name: my-app

namespace: production

labels:

app: my-app

spec:

type: ClusterIP

selector:

app: my-app

ports:

  • name: http

port: 80

targetPort: http

protocol: TCP</code></pre>

<pre><code class="language-yaml"># app-ingress.yaml - Agnóstico com NGINX Ingress Controller

apiVersion: networking.k8s.io/v1

kind: Ingress

metadata:

name: my-app-ingress

namespace: production

annotations:

cert-manager.io/cluster-issuer: &quot;letsencrypt-prod&quot;

nginx.ingress.kubernetes.io/ssl-redirect: &quot;true&quot;

spec:

ingressClassName: nginx

tls:

  • hosts:
  • myapp.example.com

secretName: my-app-tls

rules:

  • host: myapp.example.com

http:

paths:

  • path: /

pathType: Prefix

backend:

service:

name: my-app

port:

number: 80</code></pre>

<p>O ponto crítico aqui é que esse manifesto YAML funciona idêntico em EKS, AKS ou GKE. Você apenas muda a imagem Docker se necessário (registros diferentes) e aplica com <code>kubectl apply -f .</code>. A aplicação não conhece qual provedor a hospeda.</p>

<h3>Instalando componentes agnósticos com Helm</h3>

<pre><code class="language-bash"># Instalando NGINX Ingress Controller (funciona igual em qualquer cloud)

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

helm repo update

helm install nginx-ingress ingress-nginx/ingress-nginx \

--namespace ingress-nginx \

--create-namespace \

--set controller.service.type=LoadBalancer

Instalando Cert-Manager para TLS agnóstico

helm repo add jetstack https://charts.jetstack.io

helm repo update

helm install cert-manager jetstack/cert-manager \

--namespace cert-manager \

--create-namespace \

--set installCRDs=true</code></pre>

<pre><code class="language-yaml"># letsencrypt-issuer.yaml

apiVersion: cert-manager.io/v1

kind: ClusterIssuer

metadata:

name: letsencrypt-prod

spec:

acme:

server: https://acme-v02.api.letsencrypt.org/directory

email: admin@example.com

privateKeySecretRef:

name: letsencrypt-prod

solvers:

  • http01:

ingress:

class: nginx</code></pre>

<p>Aqui estamos instalando Cert-Manager via Helm, que funciona identicamente em qualquer cluster Kubernetes, independentemente do provedor. Isso garante que sua estratégia de TLS é agnóstica.</p>

<h2>Sincronização de Estado entre Clusters</h2>

<p>Um desafio real em ambientes multi-cloud é manter os clusters sincronizados. Uma abordagem eficaz é usar GitOps com ferramentas como ArgoCD, que sincroniza o estado desejado dos seus manifestos Git com todos os clusters.</p>

<p>O fluxo funciona assim: você mantém seus manifestos Kubernetes em um repositório Git, o ArgoCD monitora esse repositório em cada cluster e aplica automaticamente as mudanças. Isso garante consistência entre AWS, Azure e Google Cloud, além de fornecer auditoria completa e rollback simples.</p>

<h3>GitOps com ArgoCD</h3>

<pre><code class="language-bash"># Instalar ArgoCD

kubectl create namespace argocd

kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

Esperar pela disponibilidade

kubectl wait --for=condition=available --timeout=300s deployment/argocd-server -n argocd</code></pre>

<pre><code class="language-yaml"># argocd-app.yaml - Define a aplicação que será sincronizada

apiVersion: argoproj.io/v1alpha1

kind: Application

metadata:

name: my-app

namespace: argocd

spec:

project: default

source:

repoURL: https://github.com/myorg/my-app-manifests

targetRevision: main

path: k8s/

destination:

server: https://kubernetes.default.svc

namespace: production

syncPolicy:

automated:

prune: true

selfHeal: true

syncOptions:

  • CreateNamespace=true

revisionHistoryLimit: 10</code></pre>

<pre><code class="language-yaml"># argocd-multicloud-app.yaml - Sincronizando múltiplos clusters

apiVersion: argoproj.io/v1alpha1

kind: ApplicationSet

metadata:

name: my-app-multicloud

namespace: argocd

spec:

generators:

  • list:

elements:

  • cluster: aws-prod

region: us-east-1

  • cluster: azure-prod

region: East US

  • cluster: gcp-prod

region: us-central1

template:

metadata:

name: my-app-{{cluster}}

spec:

project: default

source:

repoURL: https://github.com/myorg/my-app-manifests

targetRevision: main

path: k8s/

destination:

name: &#039;{{cluster}}&#039;

namespace: production

syncPolicy:

automated:

prune: true

selfHeal: true</code></pre>

<p>Com ArgoCD ApplicationSet, você define uma vez e ele cria automaticamente uma Application para cada cluster listado. Qualquer mudança no Git é automaticamente sincronizada em todos os clusters simultaneamente, mantendo consistência perfeita.</p>

<h3>Secretos seguros entre clusters com Sealed Secrets</h3>

<pre><code class="language-bash"># Instalar Sealed Secrets no cluster

kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml -n kube-system

Gerar uma chave selada (fazer isso uma vez por cluster)

kubeseal --fetch-cert &gt; public-cert.pem</code></pre>

<pre><code class="language-bash"># Criar um secret normal

kubectl create secret generic app-secrets \

--from-literal=DATABASE_PASSWORD=verysecurepassword \

--from-literal=API_KEY=sk-123456789 \

--dry-run=client -o yaml &gt; secret.yaml

Selar o secret usando kubeseal

kubeseal -f secret.yaml -w sealed-secret.yaml</code></pre>

<pre><code class="language-yaml"># sealed-secret.yaml - Seguro para commitar no Git

apiVersion: bitnami.com/v1alpha1

kind: SealedSecret

metadata:

name: app-secrets

namespace: production

spec:

encryptedData:

DATABASE_PASSWORD: AgBvF5K+2G3H4I5J6K7L8M9N0O1P2Q3R4S5T6U7V8W9X0Y1Z2A3B4C5D6E7F8G9H0I1J2K3L4M5N6O7P8Q9R0S1T2U3V4W5X6Y7Z8

API_KEY: AgCzA9Z8Y7X6W5V4U3T2S1R0Q9P8O7N6M5L4K3J2I1H0G9F8E7D6C5B4A3Z2Y1X0W9V8U7T6S5R4Q3P2O1N0M9L8K7J6I5H4

template:

metadata:

name: app-secrets

namespace: production

type: Opaque</code></pre>

<p>Sealed Secrets permite que você versione secrets criptografados no Git sem comprometer a segurança. Cada cluster tem sua própria chave de descriptografia, então mesmo que alguém acesse seu repositório Git, não consegue ler os dados.</p>

<h2>Conclusion</h2>

<p>Multi-cloud com Terraform e Kubernetes é viável porque abstrai as diferenças específicas de cada provedor. Primeiro: <strong>use Terraform para provisionar infraestrutura agnóstica</strong>, delegando a seleção do provider a variáveis e mantendo o resto da configuração idêntica entre AWS, Azure e Google Cloud. Segundo: <strong>Kubernetes torna suas aplicações portáveis</strong>, funcionando como um denominador comum que permite que contêineres rodem identicamente em qualquer nuvem. Terceiro: <strong>GitOps com ArgoCD sincroniza estado entre clusters</strong> de forma automática e auditável, transformando Git em fonte única da verdade para toda a infraestrutura.</p>

<p>A realidade prática é que você eliminará aproximadamente 80% da complexidade multi-cloud seguindo esses princípios, restando apenas integrações específicas com serviços gerenciados que você conscientemente escolhe por necessidade técnica ou negócio — não por aprisionamento.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://registry.terraform.io/" target="_blank" rel="noopener noreferrer">Terraform Official Documentation - Providers</a></li>

<li><a href="https://kubernetes.io/docs/concepts/" target="_blank" rel="noopener noreferrer">Kubernetes Official Documentation - Concepts</a></li>

<li><a href="https://argo-cd.readthedocs.io/" target="_blank" rel="noopener noreferrer">ArgoCD - Declarative GitOps CD for Kubernetes</a></li>

<li><a href="https://github.com/bitnami-labs/sealed-secrets" target="_blank" rel="noopener noreferrer">Bitnami Sealed Secrets - Encryption for Kubernetes Secrets</a></li>

<li><a href="https://www.cncf.io/" target="_blank" rel="noopener noreferrer">CNCF Cloud Native Computing Foundation - Best Practices</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em DevOps & CI/CD

Como Usar Shell Scripting Avançado: Processos, Sinais, Trap e Scripts Robustos em Produção
Como Usar Shell Scripting Avançado: Processos, Sinais, Trap e Scripts Robustos em Produção

Entendendo Processos em Shell Um processo em Unix/Linux é uma instância de um...

Como Usar GCP para DevOps: GKE, Cloud Run, Cloud SQL e IAM em Produção
Como Usar GCP para DevOps: GKE, Cloud Run, Cloud SQL e IAM em Produção

Google Cloud Platform para DevOps: Uma Introdução Prática Bem-vindo a este gu...

O que Todo Dev Deve Saber sobre Terraform Avançado: Módulos, Workspaces e Remote State
O que Todo Dev Deve Saber sobre Terraform Avançado: Módulos, Workspaces e Remote State

Entendendo Módulos no Terraform Um módulo no Terraform é um diretório contend...