DevOps Admin

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

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.

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. 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.

Terraform para Orquestração Multi-Cloud

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.

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.

Estrutura básica de um projeto Terraform multi-cloud

# providers.tf - Define os provedores que serão usados
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

provider "azurerm" {
  features {}
  subscription_id = var.azure_subscription_id
}

provider "google" {
  project = var.gcp_project_id
  region  = var.gcp_region
}
# variables.tf - Variáveis agnósticas
variable "cluster_name" {
  description = "Nome do cluster Kubernetes"
  type        = string
  default     = "my-app-cluster"
}

variable "cluster_version" {
  description = "Versão do Kubernetes"
  type        = string
  default     = "1.27"
}

variable "node_count" {
  description = "Número de nós no cluster"
  type        = number
  default     = 3
}

variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "azure_subscription_id" {
  type      = string
  sensitive = true
}

variable "gcp_project_id" {
  type = string
}

variable "gcp_region" {
  type    = string
  default = "us-central1"
}
# aws_cluster.tf - Cluster EKS na AWS
resource "aws_eks_cluster" "main" {
  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 "aws_eks_node_group" "main" {
  cluster_name    = aws_eks_cluster.main.name
  node_group_name = "${var.cluster_name}-nodes"
  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 = ["t3.medium"]
}

resource "aws_iam_role" "eks_cluster_role" {
  name = "${var.cluster_name}-cluster-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "eks.amazonaws.com"
      }
    }]
  })
}

resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
  role       = aws_iam_role.eks_cluster_role.name
}

resource "aws_iam_role" "eks_node_role" {
  name = "${var.cluster_name}-node-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
    }]
  })
}

resource "aws_iam_role_policy_attachment" "eks_worker_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
  role       = aws_iam_role.eks_node_role.name
}
# azure_cluster.tf - Cluster AKS no Azure
resource "azurerm_resource_group" "main" {
  name     = "${var.cluster_name}-rg"
  location = "East US"
}

resource "azurerm_kubernetes_cluster" "main" {
  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       = "default"
    node_count = var.node_count
    vm_size    = "Standard_B2s"
  }

  identity {
    type = "SystemAssigned"
  }
}
# gcp_cluster.tf - Cluster GKE no Google Cloud
resource "google_container_cluster" "main" {
  name               = var.cluster_name
  location           = var.gcp_region
  initial_node_count = var.node_count

  node_config {
    machine_type = "e2-medium"
    oauth_scopes = [
      "https://www.googleapis.com/auth/cloud-platform"
    ]
  }

  min_master_version = var.cluster_version
}

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.

Kubernetes como Camada de Abstração

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.

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.

Aplicação agnóstica rodando em múltiplos clusters

# app-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: production
# app-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  LOG_LEVEL: "info"
  DATABASE_HOST: "db.internal"
  ENVIRONMENT: "production"
# 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=
# 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: "128Mi"
            cpu: "100m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: http
          initialDelaySeconds: 10
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: http
          initialDelaySeconds: 5
          periodSeconds: 5
# 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
# 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: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
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

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 kubectl apply -f .. A aplicação não conhece qual provedor a hospeda.

Instalando componentes agnósticos com Helm

# 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
# 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

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.

Sincronização de Estado entre Clusters

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.

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.

GitOps com ArgoCD

# 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
# 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
# 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: '{{cluster}}'
        namespace: production
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

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.

Secretos seguros entre clusters com Sealed Secrets

# 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 > public-cert.pem
# 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 > secret.yaml

# Selar o secret usando kubeseal
kubeseal -f secret.yaml -w sealed-secret.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

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.

Conclusion

Multi-cloud com Terraform e Kubernetes é viável porque abstrai as diferenças específicas de cada provedor. Primeiro: use Terraform para provisionar infraestrutura agnóstica, 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: Kubernetes torna suas aplicações portáveis, funcionando como um denominador comum que permite que contêineres rodem identicamente em qualquer nuvem. Terceiro: GitOps com ArgoCD sincroniza estado entre clusters de forma automática e auditável, transformando Git em fonte única da verdade para toda a infraestrutura.

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.

Referências


Artigos relacionados