本記事では、KubeIP v2を用いてLocal Zone上のEKSノードにElastic IPを付与する方法を紹介します。
はじめに
低レイテンシ接続やデータローカライゼーション要件への対応を目的に、AWS Local ZonesでAmazon EKSを利用するケースが増えています。一方で、規制下のパートナーと通信するワークロードでは、静的なパブリックIPアドレスが求められる場面があります。しかし、Kubernetes (k8s) のワーカーノードは本質的にエフェメラルであり、バージョンアップグレードなどのタイミングでIPアドレスが変わってしまいます。KubeIPは、クラウドプロバイダーの機能を利用して静的IPアドレスを割り当てることで、ノードのライフサイクルに左右されない一貫したIP運用を可能にします。
ソリューション概要
本記事では、リージョンにマネージドノードグループ、Local Zoneにセルフマネージドノードグループを配置したAmazon EKSクラスターの構築手順を紹介します。狙いは、KubeIP v2を用いてLocal Zone上のEKSノードにElastic IPを割り当てる方法を示すことです。
以下のアーキテクチャ図は、Local Zoneで稼働するエッジサービスと、リージョンで稼働する2つのバックエンドサービスを表しています。

前提条件
- ワークロードを稼働させるLocal Zoneのオプトインが完了していること
- 必要な権限を持つAWSアカウントがAWS CLI用に設定済みであること
- 本例で使用するCLIツールがインストール済みであること:AWS CLI、Terraform、kubectl
手順
- サンプルリポジトリには、01-vpc、02-eks、03-kubeip、04-appの4つのフォルダが含まれています。01-vpcから04-appの順に適用していきます。
githubにあるサンプルを作業ディレクトリにクローンします。
git clone https://github.com/duoh/kubeip-eks-localzone
cd kubeip-eks-localzone
- まず、リージョンとLocal ZoneにVPCとサブネットをプロビジョニングします。main.tfでは、vpcモジュールでリージョン内にパブリック/プライベートサブネットを作成します。Local Zone内のサブネットは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" {
...
}
...
入力変数を定義します。本例では、VPC名は`kubeip-lz-eks-vpc`、リージョンは`ap-southeast-1`、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"
terraformでVPC、サブネット、ルートテーブルをデプロイします。
terraform init
terraform apply -auto-approve
出力された値を控えておきます。
Outputs:
private_subnets = [\
"subnet-09139a5ea9dcd335d",\
"subnet-0d7cc25e5066687be",\
"subnet-04bb1e787c9b6e28f",\
]
public_subnets_local_zone = "subnet-0edd1b731c48fb7d7"
vpc_id = "vpc-061f434818a666d8b"
- 続いて、リージョンにマネージドノードグループを持つEKSクラスターを作成し、あわせてLocal Zoneにセルフマネージドノードグループを構築します。セルフマネージドノードグループ側のノードには、‘eks.amazonaws.com/nodegroup=public-lz-ng’および‘kubeip=use’というラベルを付与します。さらに、KubeIPのDaemonSet向けにIRSA (IAM Role for Service Accounts) を作成するため、kubeip_roleモジュールを利用します。
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
}
...
}
...
入力変数を定義します。vpc_id、private_subnets、public_subnets_local_zoneには、前のステップで出力された値を貼り付けます。
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"
terraformでリソースをデプロイします。
terraform init
terraform apply -auto-approve
出力された値を控えておきます。
Outputs:
eks_cluster_name = "kubeip-eks-lz-cluster"
kubeip_role_arn = "arn:aws:iam::xxxxxxxxxxxx:role/kubeip-agent-role"
- 次に、Elastic IPアドレスと、DaemonSetやサービスアカウントといったk8sリソースをプロビジョニングします。aws_eipリソースで定義したElastic IPアドレスは、network_border_group引数の指定によりLocal Zone内に確保されます。あわせて、node_selectorフィールドでKubeIP DaemonSetをLocal Zone内のノードにスケジューリングします。KubeIPの便利な点として、AWSタグでフィルタしたEIPを選択できることが挙げられます。なお、KubeIPのサービスアカウントには必ずIRSAと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" {
...
}
入力変数を定義します。kubeip_role_arnには、前のステップで出力された値を貼り付けます。
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"
TerraformでElastic IP (EIP) およびKubernetes (k8s) リソースをデプロイします。
terraform init
terraform apply -auto-approve
後の動作確認で使用するため、出力されたIPアドレスを控えておきます。
elastic_ips = [\
"15.220.243.225",\
]
- 最後に、リージョンノードで2つのアプリケーションを、Local Zoneノードで1つのアプリケーションを稼働させるシナリオを動かしてみます。Local Zone側のアプリケーションは、エンドユーザーからのWebトラフィックを受け、リージョン側のアプリケーションへ振り分けるエッジサービスとして機能します。
app_a.yamlおよびapp_b.yamlは、リージョン内で稼働するバックエンドアプリを定義したファイルです。
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
...
edge_svc.yamlは、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
...
カレントディレクトリを04-appに移し、お使いのリージョンに合わせてaws cliを実行し、EKSクラスター認証用のkubeconfigを更新します。
cd ../04-app
aws eks update-kubeconfig --name kubeip-lz-cluster --region ap-southeast-1
kubectlを実行し、アクセスできるか確認します。
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
アプリケーションをデプロイし、稼働状況を確認します。
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
前のステップで取得したパブリックIPアドレスを使い、アプリケーションが期待どおりに動作するかを確認します。
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>
あるいはブラウザからアクセスして確認します。



まとめ
AWSのLocal Zonesでは、Elastic Load Balancing (ELB) のうちApplication Load Balancer (ALB) のみが利用可能です。さらに、ALBに割り当てられるパブリックIPアドレスは時間とともに変動する可能性があります。Local Zonesで稼働するEKSクラスターのワーカーノードに静的なパブリックIPを維持するには、独自の自動化が欠かせません。こうした課題に対し、KubeIPは有効な解決策となります。
DoiTは、インテリジェントなテクノロジーとマルチクラウドの専門知見により、Amazon Web Services (AWS) 、Google Cloud (GCP) 、Microsoft Azureといったパブリッククラウドの理解と活用を支援し、お客様のビジネス成長を後押しします。DoiTのサービス内容はdoit.comでご覧いただけます。