EKS private worker
Giới thiệu
Là bài tiếp theo của Tôi bắt đầu học EKS.
Bài trước mình đã dựng một cụm EKS với worker node là public (có ip public và có thể truy cập được từ internet) nên không được bảo mật cho lắm, chưa kể ip public còn bị tính tiền nên mình đang trả phí cho tài nguyên không sử dụng đến. Nên là bài này mình sẽ dựng một cụm EKS private worker.
Nếu các bạn chưa xem bài trước cũng không sao, tuy nhiên những thông tin mình đã đề cập trong bài trước thì bài này mình sẽ nêu qua và reference đến bài trước, và mình khuyên là các bạn nên xem cả bài trước để có cái hiểu hoàn chỉnh về EKS.
Kiến trúc của cụm lần này trông như sau: có public, private subnets, worker nằm trong private subnet truy cập internet qua nat gateway nằm ở public subnet.
[UPDATE] trong quá trình triển khai mình nhận ra việc đặt control plane ở public hay private subnet không ảnh hưởng tới việc truy cập cụm, chứng tỏ việc truy cập được quản lý độc lập bởi aws.
Các bước dựng cụm lần này cũng tương tự như bài trước, khác mỗi bước dựng network
- Tạo network (*): vpc, subnets, internet gateway, nat gateway, route table.
- Khởi tạo EKS cluster
- Cài đặt addon: vpc-cni, kube-proxy
- Khởi tạo worker node
- Cài đặt addon: coredns
- Test nginx deployment
- Dọn dẹp
link GitHub cho bài này nè aws-eks-example/one
Khởi tạo project
Phần này các bạn làm tương tự bài trước hoặc tự setup sao cho giống yêu cầu của bạn nhé
terraform.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.24"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "ap-southeast-1"
}
Sau đó khởi tạo provider bằng lệnh
terraform init
Khởi tạo xong provider thì bắt đầu triển
1. Tạo network (*)
[UPDATE]
khi mình sử dụng load balancer trong cụm EKS mình phát hiện ra setup network hiện tại của mình làm load balancer khó hoạt động đúng, các bạn có thể xem thêm ở bài này Mình gặp phải vấn đề khi sử dụng Load Balancer trong EKS do vậy mình đã sửa lại phần setup network phía dưới
Network cho cụm EKS lần này sẽ trông như sau:
- 1 vpc có dải ip là
10.0.0.0/16
có tên làone
- 1 internet gateway để cho phép truy cập internet từ trong ra ngoài và từ ngoài vào trong. - có 2 availability zone, mỗi availability zone có
- 1 public subnet: các instance trong subnet này sẽ được gán ip public và truy cập được từ public internet
- 1 private subnet: các instance trong subnet này sẽ ko được gán ip public và không thể truy cập các ec2 instance này từ internet
- 1 nat gateway: cho phép các ec2 instance trong private subnet truy cập được internet và control plane.
- mỗi nat gateway cần một ip public (
eip
) để có thể thực hiện NAT
- mỗi nat gateway cần một ip public (
- 1 route table public:
public
cho public subnet publicone_one
- 1 route table nat:
nat
dùng chung cho 2 private subnet - các route rule cho 2 route table trên
Toàn bộ source code cho network mình sẽ để ở file network.tf
link GitHub nè https://github.com/tuana9a/aws-eks-example/blob/main/one/network.tf
1.1 Tạo vpc
vpc có dải ip là 10.0.0.0/16
có tên là one
network.tf
resource "aws_vpc" "one" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "one"
}
}
1.2 Tạo subnets
Do mình sẽ dựng trên 2 availability zones là: ap-southeast-1a
, ap-southeast-1b
và mỗi một zone lại có 1 public, 1 private do vậy cần tạo 4 subnet.
Public subnet sẽ có trường map_public_ip_on_launch = true
, private subnet sẽ là map_public_ip_on_launch = false
network.tf
resource "aws_subnet" "one_public_a" {
vpc_id = aws_vpc.one.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-southeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "one_public_a"
}
}
resource "aws_subnet" "one_private_a" {
vpc_id = aws_vpc.one.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-southeast-1a"
map_public_ip_on_launch = false
tags = {
Name = "one_private_a"
}
}
resource "aws_subnet" "one_public_b" {
vpc_id = aws_vpc.one.id
cidr_block = "10.0.3.0/24"
availability_zone = "ap-southeast-1b"
map_public_ip_on_launch = true
tags = {
Name = "one_public_b"
}
}
resource "aws_subnet" "one_private_b" {
vpc_id = aws_vpc.one.id
cidr_block = "10.0.4.0/24"
availability_zone = "ap-southeast-1b"
map_public_ip_on_launch = false
tags = {
Name = "one_private_b"
}
}
1.3 Tạo internet gateway và route public
1 internet gateway để cho phép truy cập internet từ trong ra ngoài và từ ngoài vào trong.
network.tf
resource "aws_internet_gateway" "public" {
vpc_id = aws_vpc.one.id
tags = {
Name = "public"
}
}
1 route table public: public
sử dụng chung cho các public subnet
network.tf
resource "aws_route_table" "public" {
vpc_id = aws_vpc.one.id
tags = {
Name = "public"
}
}
Route table public
sẽ có một rule là: sẽ route toàn bộ traffic qua internet gateway: (cidr 0.0.0.0/0
sẽ match với mọi destination nếu ko có cidr match hơn: cái này trong môn mạng máy tính), ý định là để traffic từ private subnet qua nat gateway nằm trong public subnet, rồi nat gateway ở public subnet sẽ mặc định gửi tiếp traffic ra ngoài internet với internet gateway.
network.tf
resource "aws_route" "public" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.public.id
}
Tạo xong route rồi thì gắn route table đó cho các public subnet
network.tf
resource "aws_route_table_association" "one_public_a" {
route_table_id = aws_route_table.public.id
subnet_id = aws_subnet.one_public_a.id
}
resource "aws_route_table_association" "one_public_b" {
route_table_id = aws_route_table.public.id
subnet_id = aws_subnet.one_public_b.id
}
1.4 Tạo nat gateway và nat route
Mỗi zone mình sẽ tạo một nat gateway, tương ứng 2 zone sẽ tạo 2 nat gateway.
Lưu ý:
- Câu hỏi ở đây là có thể dùng chung nat gateway không? Có thể dùng nhưng bạn sẽ bị charge tiền traffic giữa 2 zone. Và nếu một zone bị down thì zone còn lại sẽ không truy cập public internet được nữa.
- worker trong private subnet sẽ sử dụng nat để có thể truy cập public internet, nếu không có nat hoặc thứ khác tương đương thì nếu worker của bạn cần truy cập public internet thì sẽ toang: VD: kéo docker image từ public registry.
- một nat gateway cần một ip public (
eip
) để có thể đóng vai trò đứng trước public internet và internal subnet của mình - nat gateway cho private subnet nhưng lại nằm ở public subnet. Giải thích: các bạn có thể xem ở đây hoặc các bạn hiểu đơn giản như sau: thằng nat này cần truy cập public internet và traffic từ public internet về cũng phải vào được nat gateway, do vậy nó cần được nằm ở public subnet.
Ok hiểu rùi thì tạo nhé.
network.tf
resource "aws_eip" "nat_a" {
domain = "vpc"
tags = {
Name = "nat_a"
}
}
resource "aws_eip" "nat_b" {
domain = "vpc"
tags = {
Name = "nat_b"
}
}
resource "aws_nat_gateway" "nat_a" {
allocation_id = aws_eip.nat_a.allocation_id
subnet_id = aws_subnet.one_public_a.id
tags = {
Name = "nat_a"
}
}
resource "aws_nat_gateway" "nat_b" {
allocation_id = aws_eip.nat_b.allocation_id
subnet_id = aws_subnet.one_public_b.id
tags = {
Name = "nat_b"
}
}
Giờ đến phần routing: do có 2 nat gateway nên tương ứng mình sẽ tạo 2 route table:
network.tf
resource "aws_route_table" "nat_a" {
vpc_id = aws_vpc.one.id
tags = {
Name = "nat_a"
}
}
resource "aws_route_table" "nat_b" {
vpc_id = aws_vpc.one.id
tags = {
Name = "nat_b"
}
}
Các route table này chỉ một route rule: mặc định route toàn bộ traffic qua nat gateway
network.tf
resource "aws_route" "nat_a" {
route_table_id = aws_route_table.nat_a.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat_a.id
}
resource "aws_route" "nat_b" {
route_table_id = aws_route_table.nat_b.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat_b.id
}
Xong rồi thì gắn vào private subnet tương ứng
network.tf
resource "aws_route_table_association" "one_private_a" {
route_table_id = aws_route_table.nat_a.id
subnet_id = aws_subnet.one_private_a.id
}
resource "aws_route_table_association" "one_private_b" {
route_table_id = aws_route_table.nat_b.id
subnet_id = aws_subnet.one_private_b.id
}
Setup xong rule rồi thì gắn route table nat
cho 2 private subnet one_two
và one_three
network.tf
resource "aws_route_table_association" "one_two" {
route_table_id = aws_route_table.nat.id
subnet_id = aws_subnet.one_two.id
}
resource "aws_route_table_association" "one_three" {
route_table_id = aws_route_table.nat.id
subnet_id = aws_subnet.one_three.id
}
Như vậy traffic từ private subnet mặc định sẽ qua nat gateway, mà nat gateway nằm trên public subnet và traffic mặc định trên public subnet sẽ qua internet gateway và ra public internet. Từ đó private worker trong private subnet có thể truy cập được public internet.
cuối cùng cũng xong network, các bạn có thể apply thử.
terraform apply # yes
Yay xong r, tốn não dã man - networking vẫn là một thứ gì đó. Sau khi dựng xong network thì có thể bắt đầu dựng cụm EKS rùi
2. Khởi tạo EKS Cluster
Phần này mình sẽ đi nhanh vì nó giống hệt bài trước
2. Tạo quyền cho control plane
eks-cluster-iam.tf
resource "aws_iam_role" "eks_cluster_role" {
name = "EKSClusterRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "eks.amazonaws.com"
}
},
]
})
managed_policy_arns = [
"arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
]
}
Ở đây mình đặt tên là cluster role thì các bạn cũng hiểu là tương đương với control plane role nhé
terraform apply # yes
Đến lúc dựng cụm rồi.
Cụm mình tạo như sau:
- tên cụm là
one
- role là role vừa tạo, role có tên là
EKSClusterRole
subnet là public subnet:,one_one
mục đích là cho phép mình tiếp cập control plane từ public internet,các worker cũng chọc tới control plane thông qua public internet cho đơn giản
UPDATE: EKS yêu cầu ít nhất 2 az cho việc tạo cluster
UPDATE Vì lười tạo lại network nên mình test luôn tạo cluster với 2 private subnets xem tạo được không? Rồi có thể truy cập control plane từ máy mình được không?
Câu trả lời là: có - chứng tỏ việc truy cập được từ public hay không, không bị ảnh hưởng bởi việc đặt cluster (control plane ở đâu)
eks-cluster.tf
resource "aws_eks_cluster" "one" {
name = "one"
role_arn = aws_iam_role.eks_cluster_role.arn
version = "1.29"
vpc_config {
subnet_ids = [
aws_subnet.one_private_a.id,
aws_subnet.one_private_b.id,
]
}
# Ensure that IAM Role permissions are created before and deleted after EKS Cluster handling.
# Otherwise, EKS will not be able to properly delete EKS managed EC2 infrastructure such as Security Groups.
depends_on = [
aws_iam_role.eks_cluster_role,
]
}
terraform apply # yes
Bình thường sẽ mất khoảng 7-8 phút, lần này mất tận 12 phút (khác mỗi cho vào private subnets). Các thông tin bên lề các bạn có thể xem thêm ở mục 3. Khởi tạo control plane trong bài viết trước. Kết quả khi kiểm tra trên giao diện trông sẽ như sau.
3. eks addon: vpc-cni, kube-proxy, coredns
Phần này cũng tương tự bài trước, tuy nhiên mình đã thêm code để đảm bảo là addon sẽ chạy một phát lên luôn
Cách tìm eks addon version các bạn tìm trên mạng hoặc dùng script của mình ở README nhé
eks-addon.tf
# WARN: apply BEFORE node_group
resource "aws_eks_addon" "one_vpccni" {
cluster_name = aws_eks_cluster.one.name
addon_name = "vpc-cni"
addon_version = "v1.19.0-eksbuild.1"
}
# WARN: apply BEFORE node_group
resource "aws_eks_addon" "one_kubeproxy" {
cluster_name = aws_eks_cluster.one.name
addon_name = "kube-proxy"
addon_version = "v1.29.10-eksbuild.3"
}
# WARN: apply AFTER node_group
resource "aws_eks_addon" "one_coredns" {
cluster_name = aws_eks_cluster.one.name
addon_name = "coredns"
addon_version = "v1.11.1-eksbuild.4"
depends_on = [aws_eks_node_group.one]
}
Lưu ý là thằng addon coredns depends_on aws_eks_node_group.one
4. worker node
Phần này cũng tương tự bài trước nhưng mình đã sửa một chút để tránh các lỗi thường gặp ở bài trước Tổng hợp lỗi
eks-node-iam.tf
resource "aws_iam_role" "eks_node_role" {
name = "EKSNodeRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
managed_policy_arns = [
"arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy",
"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
]
}
eks-node-group.tf
# WARN: apply AFTER vpc-cni, kube-proxy
# WARN: apply BEFORE coredns
resource "aws_eks_node_group" "one" {
cluster_name = aws_eks_cluster.one.name
node_group_name = "one"
version = "1.29"
node_role_arn = aws_iam_role.eks_node_role.arn
capacity_type = "SPOT"
instance_types = [
"t3.medium"
]
subnet_ids = [
aws_subnet.one_private_a.id,
aws_subnet.one_private_b.id,
]
scaling_config {
desired_size = 1
max_size = 3
min_size = 1
}
update_config {
max_unavailable = 1
}
# Ensure that IAM Role permissions are created before and deleted after EKS Node Group handling.
# Otherwise, EKS will not be able to properly delete EC2 Instances and Elastic Network Interfaces.
depends_on = [
aws_iam_role.eks_node_role,
aws_eks_addon.one_vpccni,
aws_eks_addon.one_kubeproxy,
]
}
terraform apply # yes
Check trên aws console
Và khi click vào chi tiết instance thì ta không thấy có ip public được gắn vào worker node
Ok vậy đã dựng xong worker node, các thông tin khác các bạn có thể xem thêm bài trước 6. Khởi tạo worker node
Vậy là đã dựng xong cụm EKS, tiếp theo mình sẽ test một vài deployment để xem cụm của mình có hoạt động hay không
6. Test nginx deployment
kéo kubeconfig về bằng lệnh
aws eks update-kubeconfig --name one
sau đó tạo file nginx-deployment.yml
với nội dung như sau
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 10
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "64m"
limits:
memory: "128Mi"
cpu: "128m"
cùng chạy thử và xem kết quả nhé
OK vậy là đã thành công tạo một cụm EKS private worker: an toàn, bảo mật và chuẩn chỉ, theo best practice hơn. Cảm ơn ae đã dành thời gian ra để đọc đến đây.
7. Dọn dẹp
terraform destroy # yes
Kết
Vậy là đã setup xong cụm EKS với worker node nằm ở private subnet, không bị expose ra ngoài internet.
Sau đó các e có thể quay lại bài đầu tiên phần Tôi bắt đầu học EKS#TODO của bài trước để xem các trò tiếp theo mình sẽ làm với EKS.
All rights reserved