Este post mostra como atribuir um Elastic IP a nodes do EKS em uma Local Zone usando o KubeIP v2.
Introdução
Clientes da AWS usam o Amazon EKS em AWS Local Zones para ter acesso de baixa latência e atender a exigências de localização de dados. Alguns casos de uso exigem endereços IP públicos estáticos para workloads que se comunicam com parceiros regulados. Só que recursos do Kubernetes (k8s), como worker nodes, são efêmeros, e isso faz o IP mudar a cada evento como uma atualização de versão. O KubeIP atribui endereços IP estáticos aproveitando recursos do provedor de nuvem, garantindo um endereçamento consistente mesmo com as mudanças no ciclo de vida dos nodes.
Visão geral da solução
Este post mostra como configurar um cluster do Amazon EKS com um managed-node group em uma região e um self-managed node group na Local Zone. A ideia é demonstrar como atribuir um Elastic IP a um node do EKS na Local Zone usando o KubeIP v2.
O diagrama de arquitetura abaixo mostra um edge service rodando na Local Zone e dois serviços de backend rodando na região.

Pré-requisitos
- Fazer opt-in da Local Zone onde o workload vai rodar
- Uma conta AWS com as permissões necessárias configurada para o AWS CLI
- Instalação das ferramentas de CLI usadas para provisionar recursos neste exemplo: AWS CLI, Terraform e kubectl
Passo a passo
- O artefato de exemplo tem 4 pastas: 01-vpc, 02-eks, 03-kubeip e 04-app. Vamos aplicá-las em ordem, da 01-vpc até a 04-app.
Clone o artefato de exemplo do github no seu diretório de trabalho.
git clone https://github.com/duoh/kubeip-eks-localzone
cd kubeip-eks-localzone
- Primeiro, provisionamos uma VPC e subnets na região e na Local Zone. No arquivo main.tf, usamos o módulo vpc para criar subnets públicas e privadas em uma região. Já as subnets na Local Zone são definidas em recursos aws_subnet.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = var.name
cidr = var.vpc_cidr
azs = local.azs
public_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k)]
private_subnets = [for k, v in local.azs : cidrsubnet(var.vpc_cidr, 8, k + 10)]
...
public_subnet_tags = {
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"kubernetes.io/cluster/${var.cluster_name}" = "shared"
"kubernetes.io/role/internal-elb" = "1"
}
}
resource "aws_subnet" "public-subnet-lz" {
vpc_id = module.vpc.vpc_id
cidr_block = cidrsubnet(var.vpc_cidr, 8, 5)
availability_zone = local.lzs[0]
map_public_ip_on_launch = true
}
resource "aws_subnet" "private-subnet-lz" {
...
}
...
Defina as variáveis de entrada. No exemplo, o nome da VPC é `kubeip-lz-eks-vpc`, a região é `ap-southeast-1` e a Local Zone é `ap-southeast-1-bkk-1a`.
cd 01-vpc
vi example.auto.tfvars
region = "ap-southeast-1"
lzs = ["ap-southeast-1-bkk-1a"]
name = "kubeip-eks-lz-vpc"
vpc_cidr = "10.0.0.0/16"
cluster_name = "kubeip-eks-lz-cluster"
Faça o deploy da VPC, das subnets e das route tables com o terraform.
terraform init
terraform apply -auto-approve
Anote o output.
Outputs:
private_subnets = [\
"subnet-09139a5ea9dcd335d",\
"subnet-0d7cc25e5066687be",\
"subnet-04bb1e787c9b6e28f",\
]
public_subnets_local_zone = "subnet-0edd1b731c48fb7d7"
vpc_id = "vpc-061f434818a666d8b"
- Em seguida, criamos um cluster EKS com um managed node group na região. Também criamos um self-managed node group na Local Zone. Nesse self-managed node group, os nodes recebem as labels ‘eks.amazonaws.com/nodegroup=public-lz-ng’ e ‘kubeip=use’. Além disso, usamos o módulo kubeip_role para criar uma IRSA (IAM Role for Service Accounts) para o DaemonSet do KubeIP.
module "eks" {
source = "terraform-aws-modules/eks/aws"
cluster_name = var.cluster_name
cluster_version = "1.30"
vpc_id = var.vpc_id
subnet_ids = var.private_subnets
cluster_endpoint_public_access = true
enable_irsa = true
...
eks_managed_node_groups = {
region-ng = {
...
subnet_ids = var.private_subnets
labels = {
region = "true"
}
...
}
}
self_managed_node_groups = {
local-ng = {
...
subnet_ids = [var.public_subnets_local_zone]
bootstrap_extra_args = "--kubelet-extra-args '--node-labels=eks.amazonaws.com/nodegroup=public-lz-ng,kubeip=use'"
...
}
}
enable_cluster_creator_admin_permissions = true
}
resource "aws_iam_policy" "kubeip-policy" {
name = "kubeip-policy"
description = "KubeIP required permissions"
policy = jsonencode({
...
})
}
module "kubeip_role" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = var.kubeip_role_name
role_policy_arns = {
"kubeip-policy" = aws_iam_policy.kubeip-policy.arn
}
...
}
...
Defina as variáveis de entrada. Em vpc_id, private_subnets e public_subnets_local_zone, copie os valores do output anterior.
cd ../02-eks
vi example.auto.tfvars
region = "ap-southeast-1"
vpc_id = "vpc-061f434818a666d8b"
private_subnets = [\
"subnet-09139a5ea9dcd335d",\
"subnet-0d7cc25e5066687be",\
"subnet-04bb1e787c9b6e28f",\
]
public_subnets_local_zone = "subnet-0edd1b731c48fb7d7"
cluster_name = "kubeip-eks-lz-cluster"
kubeip_role_name = "kubeip-agent-role"
kubeip_sa_name = "kubeip-agent-sa"
Faça o deploy dos recursos com o terraform.
terraform init
terraform apply -auto-approve
Anote os outputs.
Outputs:
eks_cluster_name = "kubeip-eks-lz-cluster"
kubeip_role_arn = "arn:aws:iam::xxxxxxxxxxxx:role/kubeip-agent-role"
- Depois, provisionamos um Elastic IP e recursos do k8s, como um DaemonSet e uma service account. O Elastic IP definido no recurso aws_eip é provisionado dentro da Local Zone por meio do argumento network_border_group. Também atribuímos o DaemonSet do KubeIP ao node na Local Zone usando o campo node_selector. Um recurso interessante do KubeIP é a possibilidade de selecionar EIPs filtrados por tags da AWS. Vale destacar que a service account do KubeIP precisa estar vinculada à IRSA e a uma RBAC role.
resource "aws_eip" "kubeip" {
count = 1
tags = {
Name = "kubeip-${count.index}"
environment = "demo"
kubeip = "reserved"
}
network_border_group = var.network_border_group
}
resource "kubernetes_daemonset" "kubeip_daemonset" {
metadata {
name = "kubeip-agent"
...
}
spec {
...
template {
...
spec {
service_account_name = var.kubeip_sa_name
...
container {
name = "kubeip-agent"
image = "doitintl/kubeip-agent"
env {
name = "FILTER"
value = "Name=tag:kubeip,Values=reserved;Name=tag:environment,Values=demo"
}
...
}
node_selector = {
"eks.amazonaws.com/nodegroup" = "public-lz-ng"
kubeip = "use"
}
}
}
}
depends_on = [kubernetes_service_account.kubeip_service_account]
}
resource "kubernetes_service_account" "kubeip_service_account" {
metadata {
name = var.kubeip_sa_name
namespace = "kube-system"
annotations = {
"eks.amazonaws.com/role-arn" = var.kubeip_role_arn
}
}
}
resource "kubernetes_cluster_role" "kubeip_cluster_role" {
...
}
resource "kubernetes_cluster_role_binding" "kubeip_cluster_role_binding" {
...
}
Defina as variáveis de entrada. Em kubeip_role_arn, copie o valor do output anterior.
cd ../03-kubeip
vi example.auto.tfvars
region = "ap-southeast-1"
network_border_group = "ap-southeast-1-bkk-1"
cluster_name = "kubeip-eks-lz-cluster"
kubeip_role_arn = "arn:aws:iam::xxxxxxxxxxxx:role/kubeip-agent-role"
kubeip_sa_name = "kubeip-agent-sa"
Faça o deploy de um Elastic IP (EIP) e dos recursos do Kubernetes (k8s) com o Terraform.
terraform init
terraform apply -auto-approve
Anote o endereço IP do output para usar nos testes mais adiante.
elastic_ips = [\
"15.220.243.225",\
]
- Por fim, mostramos um cenário em que duas aplicações rodam em um node regional e uma aplicação roda em um node na Local Zone. A aplicação na Local Zone funciona como edge service: recebe o tráfego web dos usuários finais e o roteia para as aplicações na região.
Os arquivos app_a.yaml e app_b.yaml definem as aplicações de back-end que rodam na região.
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-a-deployment
...
spec:
...
spec:
containers:
- name: app-a
image: hashicorp/http-echo
ports:
- containerPort: 5678
args: ["-text=<h1>I'm APP <em>A</em></h1>"]
nodeSelector:
region: "true"
---
apiVersion: v1
kind: Service
metadata:
name: app-a-service
spec:
type: ClusterIP
...
O arquivo edge_svc.yaml define uma aplicação edge na Local Zone.
apiVersion: apps/v1
kind: Deployment
metadata:
name: edge-deployment
...
spec:
...
spec:
containers:
- name: edge-svc
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: indexfile
mountPath: /usr/share/nginx/html/
readOnly: true
- name: nginx-conf
mountPath: /etc/nginx/conf.d/
nodeSelector:
kubeip: use
volumes:
...
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-indexfile-configmap
data:
index.html: |
<h1>I am EDGE SERVICE</h1>
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf-configmap
data:
default.conf: |
server {
resolver kube-dns.kube-system.svc.cluster.local valid=1s
...
}
---
apiVersion: v1
kind: Service
metadata:
name: edge-service
spec:
type: NodePort
...
Vá até o diretório 04-app e execute o aws cli conforme a sua região para atualizar o arquivo kubeconfig e autenticar no cluster do EKS.
cd ../04-app
aws eks update-kubeconfig --name kubeip-lz-cluster --region ap-southeast-1
Rode o kubectl para confirmar se o acesso funcionou.
kubectl get no
NAME STATUS ROLES AGE VERSION
ip-10-0-10-213.ap-southeast-1.compute.internal Ready <none> 4m v1.30.0-eks-036c24b
ip-10-0-5-76.ap-southeast-1.compute.internal Ready <none> 3m11s v1.30.0-eks-036c24b
Faça o deploy das aplicações e confira se elas estão rodando.
kubectl apply -f edge_svc.yaml
kubectl apply -f app_a.yaml
kubectl apply -f app_b.yaml
kubectl get po
NAME READY STATUS RESTARTS AGE
app-a-deployment-587b484997-k6f8q 1/1 Running 0 12s
app-b-deployment-78bc6675db-2mk2s 1/1 Running 0 12s
edge-deployment-f6b9f4d5f-5j6f7 1/1 Running 0 13s
Confirme se a aplicação está funcionando usando o endereço IP público do passo anterior.
Teste com curl.
% curl 15.220.243.225:30000
<h1>I am EDGE SERVICE</h1>
% curl 15.220.243.225:30000/app-a
<h1>I'm APP <em>A</em></h1>
% curl 15.220.243.225:30000/app-b
<h1>I'm APP <em>B</em></h1>
Ou teste pelo navegador.



Conclusão
As Local Zones na AWS só permitem o uso de Application Load Balancers (ALBs) do serviço Elastic Load Balancing (ELB). Além disso, os IPs públicos atribuídos aos ALBs podem mudar com o tempo. Clusters EKS rodando em Local Zones precisam de uma automação personalizada para manter IPs públicos estáticos nos worker nodes — e é aí que o KubeIP entra para resolver esse problema.
A DoiT combina tecnologia inteligente e expertise multi-cloud para ajudar empresas a entender e tirar o máximo de nuvens públicas como Amazon Web Services (AWS), Google Cloud (GCP) e Microsoft Azure, impulsionando o crescimento do negócio. Conheça as soluções da DoiT em doit.com.