In questo articolo vediamo come assegnare un Elastic IP ai nodi EKS in una Local Zone tramite KubeIP v2.
Introduzione
I clienti AWS adottano Amazon EKS nelle AWS Local Zones per ottenere accesso a bassa latenza e rispettare i requisiti di localizzazione dei dati. Alcuni scenari richiedono indirizzi IP pubblici statici per i workloads che comunicano con partner soggetti a vincoli normativi. Le risorse Kubernetes (k8s) come i worker node sono però effimere: l'indirizzo IP cambia in occasione di eventi quali gli aggiornamenti di versione. KubeIP risolve il problema assegnando indirizzi IP statici tramite le funzionalità del cloud provider, garantendo un indirizzamento coerente anche al variare del ciclo di vita dei nodi.
Panoramica della soluzione
L'articolo illustra come configurare un cluster Amazon EKS con un managed-node group in una region e un self-managed node group nella local zone. L'obiettivo è mostrare come assegnare un Elastic IP a un nodo EKS nella local zone con KubeIP v2.
Il diagramma di architettura qui sotto mostra un edge service in esecuzione nella local zone e due servizi backend nella region.

Prerequisiti
- Effettuare l'opt-in alla Local Zone in cui gira il workload
- Un account AWS con i permessi necessari, configurato per la AWS CLI
- Installazione dei tool CLI necessari al provisioning delle risorse di questo esempio: AWS CLI, Terraform e kubectl
Procedura
- L'artefatto di esempio contiene 4 cartelle: 01-vpc, 02-eks, 03-kubeip e 04-app. Le applicheremo nell'ordine, da 01-vpc a 04-app.
Cloni l'artefatto di esempio da github nella directory di lavoro.
git clone https://github.com/duoh/kubeip-eks-localzone
cd kubeip-eks-localzone
- Per prima cosa effettuiamo il provisioning di una VPC e delle subnet nella region e nella local zone. Nel file main.tf utilizziamo il modulo vpc per creare le subnet pubbliche e private nella region; le subnet della local zone sono invece definite nelle risorse 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" {
...
}
...
Definisca le variabili di input. Nell'esempio: il nome della VPC è `kubeip-lz-eks-vpc`, la region è `ap-southeast-1` e la 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"
Faccia il deploy di VPC, subnet e route table con terraform.
terraform init
terraform apply -auto-approve
Prenda nota dell'output.
Outputs:
private_subnets = [\
"subnet-09139a5ea9dcd335d",\
"subnet-0d7cc25e5066687be",\
"subnet-04bb1e787c9b6e28f",\
]
public_subnets_local_zone = "subnet-0edd1b731c48fb7d7"
vpc_id = "vpc-061f434818a666d8b"
- A questo punto creiamo un cluster EKS con un managed node group nella region, oltre a un self-managed node group nella local zone. Per quest'ultimo, ai nodi vengono associate le label 'eks.amazonaws.com/nodegroup=public-lz-ng' e 'kubeip=use'. Utilizziamo inoltre il modulo kubeip_role per creare un IRSA (IAM Role for Service Accounts) destinato al DaemonSet di 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
}
...
}
...
Definisca le variabili di input. Per vpc_id, private_subnets e public_subnets_local_zone, riprenda i valori dall'output precedente.
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"
Faccia il deploy delle risorse con terraform.
terraform init
terraform apply -auto-approve
Prenda nota degli output.
Outputs:
eks_cluster_name = "kubeip-eks-lz-cluster"
kubeip_role_arn = "arn:aws:iam::xxxxxxxxxxxx:role/kubeip-agent-role"
- Successivamente provvediamo al provisioning di un Elastic IP e delle risorse k8s, quali un DaemonSet e un service account. L'Elastic IP definito nella risorsa aws_eip viene allocato all'interno della local zone tramite l'argomento network_border_group. Il DaemonSet di KubeIP viene assegnato al nodo nella local zone usando il campo node_selector. Una caratteristica utile di KubeIP è la capacità di selezionare gli EIP filtrandoli tramite tag AWS. È fondamentale che al service account di KubeIP siano associati l'IRSA e un ruolo RBAC.
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" {
...
}
Definisca le variabili di input. Per kubeip_role_arn, riprenda i valori dall'output precedente.
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"
Faccia il deploy di un Elastic IP (EIP) e delle risorse Kubernetes (k8s) con Terraform.
terraform init
terraform apply -auto-approve
Prenda nota dell'indirizzo IP in output, servirà per i test successivi.
elastic_ips = [\
"15.220.243.225",\
]
- Per concludere, illustriamo uno scenario in cui due applicazioni girano su un nodo regionale e una terza su un nodo della local zone. L'applicazione nella local zone svolge il ruolo di edge service: gestisce il traffico web degli utenti finali e lo instrada verso le applicazioni nella region.
I file app_a.yaml e app_b.yaml definiscono le app di back-end in esecuzione nella region.
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
...
Il file edge_svc.yaml definisce un'app edge nella 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
...
Si sposti nella directory 04-app ed esegua la AWS CLI indicando la propria region per aggiornare il file kubeconfig usato per autenticarsi al cluster EKS.
cd ../04-app
aws eks update-kubeconfig --name kubeip-lz-cluster --region ap-southeast-1
Esegua kubectl per verificare che l'accesso vada a buon fine.
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
Faccia il deploy delle applicazioni e verifichi che siano in esecuzione.
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
Verifichi che l'applicazione funzioni correttamente utilizzando l'indirizzo IP pubblico ottenuto al passaggio precedente.
Test con 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>
In alternativa, può eseguire il test direttamente dal browser.



Conclusioni
Nelle Local Zones di AWS è possibile usare solo gli Application Load Balancer (ALB) del servizio Elastic Load Balancing (ELB) e gli IP pubblici associati agli ALB possono cambiare nel tempo. Per mantenere indirizzi IP pubblici statici sui worker node, i cluster EKS in esecuzione nelle Local Zones richiedono quindi una soluzione di automazione personalizzata: in questi casi KubeIP è la risposta ideale.
DoiT mette a disposizione tecnologia intelligente ed expertise multi-cloud per aiutare le aziende a comprendere e sfruttare al meglio i cloud pubblici come Amazon Web Services (AWS), Google Cloud (GCP) e Microsoft Azure, accelerando la crescita del business. Per scoprire le soluzioni DoiT, visiti doit.com.