Add-ons do EKS com Terraform: Versões, Configuração e EBS CSI com gp3

Os add-ons do EKS são os componentes que fazem o cluster funcionar de verdade: rede dos pods, DNS, regras de serviço e, quando você precisa de disco, o driver do EBS. Instalados como add-ons gerenciados, eles têm versão compatível com o cluster, atualização pela API do EKS e configuração validada por um schema. Instalados na mão, viram um YAML que ninguém lembra de atualizar.

Este é o quinto artigo da série artigos-eks. O primeiro artigo já instalou os add-ons básicos sem entrar em detalhes. Agora o foco é escolher versões, passar configuração e entender o que acontece em conflito. Depois, vamos instalar o aws-ebs-csi-driver com a permissão vinda do EKS Pod Identity, fechando com uma StorageClass gp3 como padrão do cluster.

Segurança: os comandos deste guia usam o perfil do AWS CLI para autenticar. Nunca coloque access keys no código ou em .tfvars versionado; prefira aws sso login ou um perfil com credenciais temporárias.

Sumário

Pré-requisitos

Os mesmos do primeiro artigo: AWS CLI v2 autenticado, Terraform >= 1.5, kubectl e o provider hashicorp/aws ~> 6.0. O ambiente deste artigo recria a VPC, o cluster (agora na 1.35) e o node group, e acrescenta o driver do EBS. Assim ele roda sozinho, sem depender do state dos anteriores.

Os módulos usados são os mesmos da série: terraform-vpc-modules, terraform-eks-modules, terraform-eks-node-group-modules, terraform-eks-pod-identity-modules e terraform-eks-addon-modules.

Add-ons do EKS: como escolher a versão

Cada versão de add-on é publicada para um conjunto de versões do Kubernetes, e uma delas é marcada como padrão. Antes de fixar qualquer número, pergunte à própria API qual é a compatível com o seu cluster:

# versão padrão de um add-on para o Kubernetes 1.35
aws eks describe-addon-versions \
  --kubernetes-version 1.35 \
  --addon-name aws-ebs-csi-driver \
  --query 'addons[0].addonVersions[?compatibilities[0].defaultVersion==`true`].addonVersion' \
  --output text

# todas as versões disponíveis, da mais nova para a mais antiga
aws eks describe-addon-versions \
  --kubernetes-version 1.35 \
  --addon-name vpc-cni \
  --query 'addons[0].addonVersions[].addonVersion' --output text

No Terraform, o módulo terraform-eks-addon-modules faz essa consulta sozinho quando você não informa versão: usa o data source aws_eks_addon_version com most_recent = true. É prático para lab. Em produção, prefira fixar a versão dos add-ons que mexem com rede, como o vpc-cni, e subir de propósito, depois de ler o changelog:

addons = {
  vpc-cni = {
    version = var.vpc_cni_version   # null = mais recente compatível
  }
  coredns                = {}
  kube-proxy             = {}
  eks-pod-identity-agent = {}
}

Um cuidado: most_recent = true pega a versão mais nova, que pode não ser a marcada como padrão. Se você quiser exatamente a padrão, rode o primeiro comando acima e fixe o valor retornado.

Configurando add-ons do EKS com configuration_values

Add-on gerenciado não aceita qualquer valor de Helm. Cada versão publica um schema JSON com o que pode ser configurado, e o EKS rejeita o que estiver fora dele. Para ver o schema:

aws eks describe-addon-configuration \
  --addon-name aws-ebs-csi-driver \
  --addon-version <versão> \
  --query configurationSchema --output text | jq '.properties | keys'

No Terraform, a configuração vai em configuration_values como JSON. Use jsonencode para não brigar com aspas. Neste ambiente, o driver do EBS acrescenta tags em todo volume que criar, o que ajuda a achar volumes órfãos e a separar custo por cluster:

aws-ebs-csi-driver = {
  pod_identity_role_arn = module.pi_ebs_csi.role_arn
  service_account       = "ebs-csi-controller-sa"

  configuration_values = jsonencode({
    controller = {
      extraVolumeTags = {
        cluster    = var.cluster_name
        managed_by = "ebs-csi"
      }
    }
  })
}

Conflitos: OVERWRITE, PRESERVE e NONE

Um add-on gerenciado é um conjunto de objetos do Kubernetes (Deployment, DaemonSet, ConfigMap…). Se alguém editar esses objetos com kubectl, o EKS precisa decidir o que fazer na próxima atualização. É isso que resolve_conflicts_on_update controla:

  • OVERWRITE: os valores do add-on vencem. Edição manual é descartada. É o padrão do módulo, porque mantém o Terraform como fonte da verdade.
  • PRESERVE: mantém os valores alterados no cluster. Útil durante uma migração, quando alguém ajustou algo à mão e você ainda não passou para o código.
  • NONE: não resolve nada. Se houver conflito, a atualização falha e o add-on fica em estado degradado até alguém intervir.

Na criação, o módulo usa sempre OVERWRITE. Isso resolve o caso clássico de clusters antigos em que vpc-cni, coredns e kube-proxy já existiam como componentes autogerenciados: o add-on assume os objetos em vez de falhar.

EBS CSI Driver com Pod Identity

Sem o aws-ebs-csi-driver, um PersistentVolumeClaim fica pendente para sempre: o cluster não tem quem crie o volume EBS. O driver precisa de permissão na AWS para criar, anexar, redimensionar e apagar volumes. A política gerenciada AmazonEBSCSIDriverPolicy cobre isso.

module "pi_ebs_csi" {
  source = "../../terraform-eks-pod-identity-modules"

  cluster_name = module.eks.name
  role_name    = "${var.cluster_name}-ebs-csi"

  # A associação é feita pelo próprio add-on (pod_identity_association),
  # por isso nenhuma service account é listada aqui.
  service_accounts = []

  policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"]
}

Repare que a role só é criada pelo módulo de Pod Identity. Quem liga a role à service account ebs-csi-controller-sa é o bloco pod_identity_association do próprio aws_eks_addon, preenchido pelos campos pod_identity_role_arn e service_account. Assim a associação nasce e morre junto com o add-on.

Isso só funciona porque o add-on eks-pod-identity-agent está instalado desde o primeiro artigo. Sem o agente, o controller sobe sem credencial e os logs mostram erro de autorização ao criar o primeiro volume.

StorageClass gp3 como padrão

O gp3 entrega 3.000 IOPS e 125 MiB/s de base independentemente do tamanho do volume e custa menos por GB que o gp2. Em clusters novos, a StorageClass gp2 que aparece no cluster não vem marcada como padrão, então um PVC sem storageClassName fica pendente. A saída é criar a sua:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gp3
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
parameters:
  type: gp3
  encrypted: "true"
  fsType: ext4
  • WaitForFirstConsumer: o volume só é criado quando o pod é agendado. Como um volume EBS existe em uma única AZ, isso garante que ele nasça na AZ do nó que vai usá-lo.
  • allowVolumeExpansion: permite aumentar o PVC sem recriar o volume.
  • encrypted: "true": criptografa com a chave padrão do EBS. Para usar uma chave KMS própria, acrescente kmsKeyId.
  • reclaimPolicy: Delete: apagar o PVC apaga o volume. Para dados que não podem sumir junto, use Retain.

O schema do add-on também tem a opção defaultStorageClass.enabled, que cria uma StorageClass padrão pelo próprio driver. Prefiro o YAML acima, porque deixa explícitos o tipo, a criptografia e a política de retenção. Salve-o como storageclass-gp3.yaml para aplicar depois do apply.

terraform plan

terraform init
terraform validate
terraform plan -var 'public_access_cidrs=["SEU_IP/32"]' -out=tfplan

O código deste artigo foi validado com terraform validate e terraform plan contra uma conta AWS real, sem apply. O plan fecha em 31 recursos: os 28 do cluster base, mais o add-on do EBS CSI, a role dele e o anexo da política:

  # module.addons.aws_eks_addon.this["aws-ebs-csi-driver"] will be created
  # module.addons.aws_eks_addon.this["coredns"] will be created
  # module.addons.aws_eks_addon.this["eks-pod-identity-agent"] will be created
  # module.addons.aws_eks_addon.this["kube-proxy"] will be created
  # module.addons.aws_eks_addon.this["vpc-cni"] will be created
  # module.pi_ebs_csi.aws_iam_role.this will be created
  # module.pi_ebs_csi.aws_iam_role_policy_attachment.managed["arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"] will be created
  ...
Plan: 31 to add, 0 to change, 0 to destroy.

Outputs do Terraform

O ambiente expõe cinco outputs. Este é o bloco Changes to Outputs do plan; o que depende de recurso ainda não criado aparece como (known after apply):

Changes to Outputs:
  + addon_versions     = {
      + aws-ebs-csi-driver     = (known after apply)
      + coredns                = (known after apply)
      + eks-pod-identity-agent = (known after apply)
      + kube-proxy             = (known after apply)
      + vpc-cni                = (known after apply)
    }
  + cluster_name       = "eks-blog-castilho"
  + ebs_csi_role_arn   = (known after apply)
  + kubeconfig_command = "aws eks update-kubeconfig --region us-east-1 --name eks-blog-castilho"
  + kubernetes_version = "1.35"
  • cluster_name: nome do cluster, para usar em comandos do aws eks.
  • kubernetes_version: versão do control plane. É a referência para escolher versão de add-on.
  • addon_versions: versão instalada de cada add-on. Depois do apply, é o jeito rápido de saber o que o most_recent escolheu: terraform output -json addon_versions.
  • ebs_csi_role_arn: ARN da role do driver do EBS. Depois do apply ele terá o formato arn:aws:iam::111122223333:role/eks-blog-castilho-ebs-csi (exemplo, com a conta fictícia da documentação da AWS). Use para conferir a associação: aws eks list-pod-identity-associations --cluster-name eks-blog-castilho.
  • kubeconfig_command: comando pronto para configurar o kubectl. Rode com $(terraform output -raw kubeconfig_command).

Testando com um PVC

Com o cluster aplicado, crie a StorageClass e um PVC com um pod de teste que grava no volume. Salve o YAML abaixo como pvc-teste.yaml:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: dados-teste
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: app-teste
spec:
  containers:
    - name: app
      image: public.ecr.aws/docker/library/busybox:stable
      command: ["sh", "-c", "date >> /dados/log.txt && sleep 3600"]
      volumeMounts:
        - name: dados
          mountPath: /dados
  volumes:
    - name: dados
      persistentVolumeClaim:
        claimName: dados-teste
$(terraform output -raw kubeconfig_command)
kubectl apply -f storageclass-gp3.yaml
kubectl apply -f pvc-teste.yaml

kubectl get storageclass
kubectl get pvc dados-teste -w

O PVC fica Pending até o pod ser agendado e depois passa para Bound. Se ele ficar preso em Pending mesmo com o pod rodando, olhe os eventos (kubectl describe pvc dados-teste) e os logs do controller (kubectl logs -n kube-system deploy/ebs-csi-controller -c ebs-plugin). Erro de permissão ali quase sempre é a associação do Pod Identity faltando.

Na hora de apagar, a ordem importa: pod, depois PVC. Se o volume ficar preso, o post Disks pvc status is terminating eks aws mostra como destravar. E se a sua aplicação precisa de arquivos no S3 em vez de disco de bloco, veja o add-on Mountpoint para Amazon S3.

Custos e limpeza

  • Add-ons: os add-ons gerenciados não têm cobrança própria. Você paga os nós em que os pods deles rodam.
  • Volumes EBS: cobrança por GB provisionado por mês, mais IOPS e throughput acima da base do gp3. O PVC de teste tem 4 GiB.
  • Cluster base: control plane, NAT Gateway e nós, como no primeiro artigo.

Apague os objetos do Kubernetes antes do destroy. Volume criado pelo driver não está no state do Terraform; se o cluster sumir antes, o volume fica órfão na conta, cobrando. As tags de extraVolumeTags ajudam a achá-los:

kubectl delete -f pvc-teste.yaml
terraform destroy

# confira se sobrou volume do cluster
aws ec2 describe-volumes \
  --filters Name=tag:cluster,Values=eks-blog-castilho \
  --query 'Volumes[].[VolumeId,State,Size]' --output table

Conclusão

Tratar os add-ons do EKS como código resolve três problemas de uma vez: a versão sempre compatível com o cluster, a configuração validada pelo schema e o comportamento previsível quando alguém mexe à mão. O EBS CSI Driver mostra o padrão completo: add-on gerenciado, permissão via Pod Identity sem OIDC e uma StorageClass gp3 explícita como padrão.

No próximo artigo da série, o tráfego passa a entrar no cluster por um Application Load Balancer, criado pelo AWS Load Balancer Controller a partir de um Ingress.

Referências oficiais

Interessado em saber mais sobre esse assunto?

Deixe uma resposta

Descubra mais sobre Blog do Castilho - Tecnologia | FinOps | DevOps | Cloud

Assine agora mesmo para continuar lendo e ter acesso ao arquivo completo.

Continuar lendo