Tổng quan

AWS VPC CNI là CNI plugin mặc định trên Amazon EKS. Điểm đặc biệt: mỗi Pod nhận một IP thực từ VPC - không overlay, không encapsulation. Điều này mang lại native VPC networking cho Pod nhưng cũng đi kèm những ràng buộc riêng.
Bài viết này đi sâu vào cách VPC CNI hoạt động bên trong một EKS node, từ ENI management đến trunk networking và iptables chains.
ENI và Secondary IPs
Kiến trúc tổng thể

Cấu trúc network interfaces trên node
Một EKS node điển hình có nhiều ENI (Elastic Network Interface), mỗi ENI mang nhiều secondary private IPs:
# Xem tất cả interfaces trên node
ip link show
1: lo: <LOOPBACK,UP>
2: eth0: <BROADCAST,MULTICAST,UP> # Primary ENI - 172.31.23.106/20
3: dummy0: <BROADCAST,NOARP>
5: eni4c9bab731ed@if3: <BROADCAST,UP> # veth cho pod
6: eth1: <BROADCAST,MULTICAST,UP> # Secondary ENI - 172.31.23.93/20
7: eth2: <BROADCAST,MULTICAST,UP> # Secondary ENI - 172.31.18.110/20
8: eni2bc5393b053@if3: <BROADCAST,UP> # veth cho pod
Mỗi ENI có nhiều secondary IPs:
- eth0: 172.31.19.13, 172.31.16.74, 172.31.26.201, 172.31.27.54, 172.31.29.4
- eth1: 172.31.20.235, 172.31.20.89, 172.31.31.116, 172.31.23.163, 172.31.21.208
Pods chạy trên node này nhận secondary IPs từ các ENI.
ipamd - IP Address Management Daemon
ipamd (chạy trong aws-node DaemonSet) quản lý toàn bộ lifecycle của ENI và IPs:
- Khởi động: attach ENIs, lấy secondary IPs
- Warm Pool: giữ sẵn N IPs chưa dùng
WARM_IP_TARGET(default: 1)WARM_ENI_TARGET(default: 1)MINIMUM_IP_TARGET
- Khi kubelet gọi CNI ADD: ipamd cấp IP từ warm pool
- Khi kubelet gọi CNI DEL: IP trả về warm pool
- Scale: tự động request thêm ENI khi cần
Pod networking chi tiết
Khi một Pod được tạo, CNI binary thực hiện:

Bên trong mỗi Pod network namespace:
Pod Network Namespace:
+--------------------------------------+
| eth0: 172.31.23.163/32 |
| |
| Route table: |
| default via 169.254.1.1 dev eth0 |
| 169.254.1.1 dev eth0 scope link |
| |
| ARP: |
| 169.254.1.1 -> MAC(veth_host_side) |
+--------------------------------------+
Các điểm quan trọng:
- Pod nhận IP
/32(không phải subnet) - đây là secondary IP từ một ENI - Default gateway là
169.254.1.1(link-local) với static ARP trỏ đến veth host side - Host có route
/32trỏ traffic đến đúng veth interface của Pod
Pattern này giống hệt kỹ thuật "Secondary IP với Link-Local Gateway" trong bài Container Networking from Scratch.
Network namespaces trên node
# Liệt kê các CNI network namespaces
ip netns list
cni-29b37092-bcad-2228-1130-8b9945f6bb30
cni-b3466bf5-4367-e10f-09dd-99abc561194e
cni-55ae0531-a760-9e14-a192-fdc6d6c82970
cni-da53182a-dd25-596a-fae3-2d0556c2692a
Mỗi Pod có namespace riêng, được đặt tên theo format cni-<uuid>.
# Inspect một Pod namespace
ip netns exec cni-29b37092-bcad-2228-1130-8b9945f6bb30 ip a
ip netns exec cni-29b37092-bcad-2228-1130-8b9945f6bb30 ip route
Trunk Networking - Security Groups cho Pods
Vấn đề
Mặc định, tất cả Pods trên một node chia sẻ security groups của node. Nhưng trong thực tế:
- Pod backend cần access database
- Pod frontend không nên access database
- Khi autoscale, security rules phải theo Pod, không theo node
Giải pháp: Trunk ENI + VLAN
Trước khi có trunk networking, mỗi network cần một vNIC riêng - không scale được:


Kích hoạt bằng ENABLE_POD_ENI=true. VPC CNI sẽ tạo trunk ENI - một ENI đặc biệt hỗ trợ VLAN tagging:

Network interfaces thực tế trên node
ip link show
1: lo: <LOOPBACK,UP>
2: eth0: <BROADCAST,MULTICAST,UP> # Primary ENI
3: dummy0: <BROADCAST,NOARP>
5: eni4c9bab731ed@if3: <BROADCAST,UP> # veth cho regular pod
6: eth1: <BROADCAST,MULTICAST,UP> # Trunk ENI
7: eth2: <BROADCAST,MULTICAST,UP> # Secondary ENI
8: eni2bc5393b053@if3: <BROADCAST,UP> # veth cho regular pod
11: vlan14d01641e41@eth1: <BROADCAST,UP> # VLAN subport (pod với SG)
12: vlan.eth.3@eth1: <BROADCAST,UP> # VLAN subport
17: eni37cc5983ec1@if3: <BROADCAST,UP> # veth cho regular pod
Chú ý sự khác biệt:
eniXXX@if3: veth pair thông thường cho regular podsvlanXXX@eth1: VLAN interface trên trunk ENI cho pods với custom security group
Routing cho Trunk Pods
Pods với security group riêng có routing khác. Regular pod traffic đi qua host iptables rules, trong khi trunk pod traffic đi trực tiếp qua trunk ENI ra VPC.
SecurityGroupPolicy CRD
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
name: database-access
namespace: backend
spec:
podSelector:
matchLabels:
role: api-server
securityGroups:
groupIds:
- sg-0abc123def456789 # Cho phép access tới RDS
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: backend
spec:
replicas: 4
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
role: api-server # Match với SecurityGroupPolicy
spec:
terminationGracePeriodSeconds: 120
containers:
- name: api
image: my-api:latest
ports:
- containerPort: 8080
Policy Routing trên EKS Node
Route tables
EKS node sử dụng policy-based routing với nhiều route tables:
# Main route table
ip route show table main
default via 172.31.16.1 dev eth0
172.31.16.0/20 dev eth0 proto kernel scope link src 172.31.0.225
# Per-pod routes
172.31.20.89 dev eni1e3dd7d66d7 scope link # Regular pod
172.31.19.50 dev vlan.eth.1 scope link # Trunk pod
# Route tables cho VLAN interfaces (trunk pods)
ip route show table 101
default via 172.31.16.1 dev vlan.eth.1
ip route show table 102
default via 172.31.16.1 dev vlan.eth.2
IP Rules
Policy routing rules quyết định traffic đi qua table nào:
ip rule show
0: from all lookup local
512: from all to 172.31.19.50 lookup main # Đến trunk pod: dùng main
1024: from all fwmark 0x80/0x80 lookup main # Marked traffic: dùng main
1536: from 172.31.19.50 lookup 101 # Từ trunk pod: dùng table 101
32766: from all lookup main
32767: from all lookup default
ip rulekiểm soát routing policy - quyết định dùng route table nào dựa trên source IP, destination IP, hoặc fwmark. Đây là cách VPC CNI đảm bảo traffic từ trunk pod đi đúng VLAN interface thay vì primary ENI.
iptables Chains trên EKS Node
EKS node có một hệ thống iptables phức tạp với nhiều custom chains:
NAT Table Flow
PREROUTING
+-- KUBE-SERVICES
+-- Match Service ClusterIP:port
+-- DNAT -> Pod IP:port
OUTPUT
+-- KUBE-SERVICES (cho local traffic)
POSTROUTING
+-- KUBE-POSTROUTING
| +-- MASQUERADE cho traffic từ Service
|
+-- AWS-SNAT-CHAIN-0
+-- Không SNAT traffic trong VPC CIDR
+-- SNAT traffic ra ngoài VPC
+-- AWS-CONNMARK-CHAIN-0
+-- Đánh dấu connection cho return traffic
AWS-specific chains
AWS-SNAT-CHAIN: Xử lý Source NAT cho traffic ra ngoài VPC
iptables -t nat -S AWS-SNAT-CHAIN-0
# Không SNAT traffic nội bộ VPC
-A AWS-SNAT-CHAIN-0 ! -d 172.31.0.0/16 -m comment --comment "AWS SNAT CHAIN" \
-j AWS-SNAT-CHAIN-1
# SNAT traffic ra internet
-A AWS-SNAT-CHAIN-1 -m comment --comment "AWS, SNAT" \
-m addrtype ! --dst-type LOCAL -j SNAT --to-source 172.31.0.225 \
--random-fully
AWS-CONNMARK-CHAIN: Đánh dấu connections để xử lý return traffic đúng interface
iptables -t nat -S AWS-CONNMARK-CHAIN-0
# Mark traffic đi ra ngoài VPC
-A AWS-CONNMARK-CHAIN-0 ! -d 172.31.0.0/16 \
-m comment --comment "AWS CONNMARK CHAIN, VPC CIDR" \
-j AWS-CONNMARK-CHAIN-1
# Set connmark
-A AWS-CONNMARK-CHAIN-1 -m comment --comment "AWS, CONNMARK" \
-j CONNMARK --set-xmark 0x80/0x80
KUBE-SERVICES: Service routing
Ví dụ: CoreDNS Service (ClusterIP: 10.100.0.10:53)
PREROUTING -> KUBE-SERVICES
+-- -d 10.100.0.10/32 -p udp --dport 53
+-- KUBE-SVC-xxx (Service chain)
+-- 50% -> KUBE-SEP-aaa -> DNAT 172.31.20.89:53 (Pod 1)
+-- 50% -> KUBE-SEP-bbb -> DNAT 172.31.23.45:53 (Pod 2)
kube-proxy tạo các iptables rules này dựa trên Service và Endpoints. Đây là lý do tại sao Cilium với eBPF có performance tốt hơn - nó bypass hoàn toàn iptables.
Debugging VPC CNI
Kiểm tra ENI allocation
# Xem aws-node logs
kubectl logs -n kube-system -l k8s-app=aws-node
# Kiểm tra IP allocation trên node
kubectl exec -n kube-system <aws-node-pod> -- /app/grpc-health-probe \
-addr :50051 -service=""
Network debug trên node
# Xem tất cả interfaces
ip link show
# Xem route tables
ip route show table all
# Xem policy rules
ip rule show
# Inspect specific Pod namespace
POD_NS=$(ip netns list | head -1)
ip netns exec $POD_NS ip a
ip netns exec $POD_NS ip route
ip netns exec $POD_NS arp -n
# Capture traffic
tcpdump -nn -i any icmp -l
ip netns exec $POD_NS tcpdump -nn -i eth0 -l
Các lỗi thường gặp
| Lỗi | Nguyên nhân | Cách khắc phục |
|---|---|---|
| Pod stuck Pending (no IP available) | Hết IP trong subnet hoặc hết ENI slots | Mở rộng subnet |
| Pod-to-Pod timeout cross-node | Security group chặn traffic | Kiểm tra SG rules |
| SNAT không hoạt động (pod không ra internet) | AWS-SNAT-CHAIN cấu hình sai | Kiểm tra VPC CIDR |
| Trunk pod không có custom SG | ENABLE_POD_ENI chưa bật | Set =true |
Tổng kết
| Component | Chức năng |
|---|---|
| CNI Binary | Tạo veth pair, gán /32 IP, setup 169.254.1.1 gateway, static ARP |
| ipamd | Quản lý ENI, warm IP pool, IP allocation |
| Trunk ENI | VLAN per pod, per-pod security groups, policy routing |
| AWS-SNAT-CHAIN | Source NAT cho traffic ra ngoài VPC |
| AWS-CONNMARK-CHAIN | Đánh dấu connections để route return traffic đúng interface |
| KUBE-SERVICES | DNAT traffic từ Service ClusterIP đến Pod IP |
Bài viết liên quan
- Container Networking from Scratch - Xây dựng networking từ đầu, hiểu các building blocks
- Tổng quan Kubernetes CNI - So sánh các CNI approach: GKE, EKS, Cilium, Calico
