AWS Thai Blog
กำหนดหมายเลขไอพีสาธารณะแบบคงที่ให้กับเครื่อง worker ของ Amazon EKS ใน Local Zones ด้วย KubeIP v2
ลูกค้า AWS ใช้งาน Amazon EKS ใน AWS Local Zones เพื่อให้ลูกค้าของตนเข้าถึงบริการด้วยความหน่วงที่ต่ำ และปฏิบัติตามนโยบายด้านการประมวลผลข้อมูลในประเทศ รวมถึงในบางกรณีมีความจำเป็นต้องใช้งานหมายเลขไอพีสาธารณะแบบคงที่ เพื่อสื่อสารกับระบบของคู่ค้าที่มีข้อกำหนดดังกล่าว อย่างไรก็ตาม ทรัพยากร Kubernetes (k8s) เช่น เครื่อง worker อาจมีการปิดเปิดเครื่องตามสถานการณ์ต่างๆ ทำให้เกิดการเปลี่ยนแปลงที่อยู่ไอพีของเครื่องได้ ยกตัวอย่างเช่น การอัปเกรดเวอร์ชันของ EKS คลัสเตอร์ โดยเราสามารถนำ KubeIP v2 มาช่วยในการกำหนดหมายเลขไอพีสาธารณะแบบคงที่ได้ โดยใช้ประโยชน์จากความสามารถของคลาวด์ เพื่อให้แน่ใจว่าการกำหนดที่อยู่ไอพีจะเป็นหมายเลขเดิม แม้ว่าเครื่อง worker จะถูกปิดและสร้างขึ้นใหม่ก็ตาม
โซลูชันโดยรวม
บทความนี้แสดงวิธีการติดตั้ง Amazon EKS cluster ที่ประกอบด้วย managed node group ที่อยู่ใน AWS region และ self-managed node group ที่อยู่ใน Local Zone รวมทั้งสาธิตการกำหนด Elastic IP Address ของ AWS ให้กับ EKS worker ใน Local Zone โดยใช้ KubeIP v2
แผนภาพสถาปัตยกรรมด้านล่างแสดงถึงแอปพลิเคชัน edge ทำงานอยู่บนเครื่อง EC2 ที่มีหมายเลขไอพีสาธารณะแบบคงที่กำหนดอยู่ใน Local Zone และรับทราฟฟิคจากผู้ใช้งาน จากนั้นส่งต่อทราฟฟิคไปยังแอปพลิเคชัน backend ที่อยู่ใน AWS region
ข้อกำหนดเบื้องต้น
- เปิดใช้งาน Local Zones บน AWS region ที่รันแอปพลิเคชัน
- กำหนดสิทธิ์ในบัญชี AWS ที่จำเป็นสำหรับใช้งาน AWS CLI
- ติดตั้งเครื่องมือ CLI ที่ใช้ในการสร้างทรัพยากรตามตัวอย่างดังนี้ AWS CLI, Terraform และ kubectl
มาลงมือสร้างกัน
1) ในบทความนี้ เราจะทำการดีพอยตามตัวอย่างซอร์สโค๊ด ซึ่งประกอบไปด้วย 4 ไดเรกทอรี ได้แก่ 01-vpc 02-eks 03-kubeip และ 04-app โดยจะเริ่มดีพอยจาก 01-vpc ไป 04-app
เริ่มแรก ให้ทำการคัดลอกตัวอย่างจากบน github มายังไดเรกทอรีปัจจุบัน
git clone https://github.com/duoh/kubeip-eks-localzone
cd kubeip-eks-localzone
2) เราจะทำการสร้าง VPC และ subnet ใน AWS region และ Local Zone ตามในไฟล์ main.tf โดยใช้โมดูล vpc เพื่อสร้าง subnet ใน AWS region และใช้ aws_subnet resources ในการสร้าง subnet ใน Local Zone
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" {
...
}
...
กำหนดค่าตัวแปรอินพุต จากตัวอย่าง name (VPC) เป็นค่า `kubeip-lz-eks-vpc` ส่วน region (AWS region) เป็น ‘ap-southeast-1’ และ lzs (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"
ทำการดีพอย VPC subnet และ route table ด้วย terraform
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"
3) ถัดมา ให้เราทำการสร้าง EKS คลัสเตอร์ที่มี managed node group ใน AWS region และสร้าง self-managed node group ใน Local Zone สำหรับ self-managed node group นั้น จะมีแท็ก ‘eks.amazonaws.com/nodegroup=public-lz-ng’ และ ‘kubeip=use’ กำหนดอยู่ด้วย นอกจากนี้เรายังใช้โมดูล kubeip_role เพื่อสร้าง IRSA (IAM Role for Service Accounts) สำหรับ KubeIP daemonSet
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"
4) จากนั้น สร้าง Elastic IP Address และทรัพยากร k8s เช่น DaemonSet และ service account โดย Elastic IP Address ถูกสร้างตามที่กำหนดไว้ใน aws_eip resource ซึ่งแยกตามโซนผ่าน network_border_group argument สำหรับ KubeIP DaemonSet นั้น จะรันบนเครื่อง worker ใน Local Zone ซึ่งถูกกำหนดโดย node_selector field ของ k8s ส่วน KubeIP เองก็สามารถเลือก EIP โดยใช้แท็กของ AWS ได้ ที่สำคัญคือเราต้องทำการสร้าง IRSA และกำหนด RBAC ให้กับ service account สำหรับ KubeIP DaemonSet
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"
ติดตั้ง Elastic IP Address (EIP) และทรัพยากร Kubernetes (k8s) โดยใช้ Terraform
terraform init
terraform apply -auto-approve
บันทึกค่า IP ไว้เพื่อใช้ในการทดสอบ หลังจากดีพอยแอปพลิเคชันเสร็จ
elastic_ips = [
"15.220.243.225",
]
5) ท้ายสุด เราจะทำการทดสอบระบบโดยดีพอยแอปพลิเคขันบน EKS คลัสเตอร์
ไฟล์ที่มีชื่อว่า app_a.yaml และ app_b.yaml นั้นไว้สำหรับสร้างแอปพลิเคชัน backend ที่ทำงานใน AWS 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
...
ไฟล์ที่มีชื่อว่า edge_svc.yaml นั้น ไว้สร้างแอปพลิเคชัน edge ที่รันใน 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 ตามด้วย AWS region ที่ใช้งาน เพื่ออัปเดตไฟล์ kubeconfig สำหรับใช้ยืนยันตัวตนกับ EKS คลัสเตอร์
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
ตรวจสอบผลลัพธ์ว่าแอปพลิเคชันทำงานได้ โดยใช้ที่อยู่ไอพีสาธารณะจากผลลัพธ์ก่อนหน้า
ทำการทดสอบผ่าน 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 มีข้อจำกัดจากการใช้งานได้แค่ Application Load Balancer (ALB) จากบริการของ Elastic Load Balancing (ELB) เท่านั้น นอกจากนี้หมายเลขไอพีสาธารณะที่กำหนดให้กับ ALB นั้น สามารถเปลี่ยนแปลงได้ตลอดเวลา สำหรับผู้ดูแล EKS คลัสเตอร์ที่ใช้งานใน Local Zone และจำเป็นต้องมั่นใจว่าค่าไอพีเดิมจะถูกกำหนดให้แก่ระบบอยู่ตลอดเวลา ไม่ว่าเครื่อง worker จะถูกปิดและเปิดขึ้นมาใหม่ก็ตาม ทำให้ต้องสร้างและดูแลโซลูชันเอง ซึ่งในกรณนี้ KubeIP v2 เป็นตัวเลือกที่เหมาะสม ที่สามารถนำมาใช้ตอบโจทย์ดังกล่าวได้
บทความนี้เขียนโดยคุณ Wiriyang Pipatsakulroj, Senior Cloud Architect DoiT
Review โดยคุณ Chatcharoen Chivakanit, Senior Solutions Architect AWS
เกี่ยวกับ DoiT
DoiT จัดหาเทคโนโลยีอัจฉริยะและความเชี่ยวชาญด้านคลาวด์ เพื่อช่วยให้องค์กรเข้าใจและใช้ประโยชน์จาก Public Cloud อย่าง Amazon Web Services (AWS) ในการขับเคลื่อนการเติบโตทางธุรกิจ คุณสามารถดูข้อเสนอและสอบถามได้ที่ doit.com