Sidecar Injection — Init Container, iptables Interception
Cơ chế Traffic Interception
Câu hỏi cơ bản nhất về service mesh: làm thế nào Envoy "chặn" được traffic mà application không cần thay đổi code?
Câu trả lời nằm ở iptables REDIRECT rules được setup bởi istio-init init container. Đây là phép thuật không rõ ràng nhất của Istio, và khi nó fail — hoặc khi bạn debug packet không đi đúng đường — bạn cần hiểu rõ cơ chế này.
Luồng tổng quan
Pod startup:
1. istio-init container chạy → setup iptables rules trong network namespace của Pod
2. istio-init container exit (success)
3. Các containers chính start (app + istio-proxy/Envoy)
Runtime:
4. App gửi traffic → bị iptables redirect đến Envoy port 15001 (outbound)
5. Traffic từ ngoài vào Pod → bị iptables redirect đến Envoy port 15006 (inbound)
6. Envoy xử lý, forward đến App trên localhost (bypass iptables)Sidecar Injection: MutatingWebhook
Istio MutatingWebhookConfiguration
Khi một Pod được create, Kubernetes gọi MutatingAdmissionWebhook của Istio trước khi Pod được accepted:
# Xem webhook config
kubectl get mutatingwebhookconfigurations istio-sidecar-injector -o yamlapiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: istio-sidecar-injector
webhooks:
- name: namespace.sidecar-injector.istio.io
clientConfig:
service:
name: istiod
namespace: istio-system
path: /inject
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: In
values: ["enabled"]
# Hoặc revision-based:
# - key: istio.io/rev
# operator: In
# values: ["asm-1-20"]
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values: ["false"]
failurePolicy: Fail # Quan trọng: nếu webhook fail → Pod bị reject
timeoutSeconds: 30Webhook Injection Process
Khi Pod được submit:
- API Server nhận Pod spec
- API Server gọi webhook
/injectendpoint trên istiod - Istiod nhận Pod spec, trả về mutated Pod spec với:
initContainers: thêmistio-initcontainers: thêmistio-proxyvolumes: thêm certificate volumesannotations: ghi lại injection metadata
# Xem Pod đã được inject ra sao
kubectl get pod my-pod -o json | jq '.spec.initContainers[].name'
# Output: "istio-init"
kubectl get pod my-pod -o json | jq '.spec.containers[].name'
# Output: "my-app", "istio-proxy"Annotations kiểm soát injection
apiVersion: v1
kind: Pod
metadata:
annotations:
# Opt-out injection cho Pod cụ thể
sidecar.istio.io/inject: "false"
# Custom sidecar resources
sidecar.istio.io/proxyCPU: "100m"
sidecar.istio.io/proxyMemory: "128Mi"
sidecar.istio.io/proxyCPULimit: "2000m"
sidecar.istio.io/proxyMemoryLimit: "1024Mi"
# Custom Envoy config
proxy.istio.io/config: |
concurrency: 4
tracing:
sampling: 100.0
# Exclude specific ports từ interception
traffic.sidecar.istio.io/excludeOutboundPorts: "9090,9091"
traffic.sidecar.istio.io/excludeInboundPorts: "9090"
# Include only specific ports
traffic.sidecar.istio.io/includeOutboundPorts: "8080,8443"
# Exclude IP ranges
traffic.sidecar.istio.io/excludeOutboundIPRanges: "10.96.0.0/12"istio-init Container: iptables Rules
Container đặc biệt cần NET_ADMIN capability
istio-init là init container cần NET_ADMIN capability để modify iptables trong Pod's network namespace:
initContainers:
- name: istio-init
image: gcr.io/istio-release/proxyv2:1.20.0
args:
- istio-iptables
- -p "15001" # Outbound port
- -z "15006" # Inbound port
- -u "1337" # Envoy user UID (để skip redirect cho Envoy traffic)
- -m "REDIRECT" # Mode: REDIRECT hoặc TPROXY
- -i "*" # Include all outbound IP ranges
- -x "" # Exclude IP ranges (empty = none)
- -b "*" # Include all inbound ports
- -d "15020,15090,15021" # Exclude these inbound ports (Envoy internal)
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
runAsNonRoot: false
runAsUser: 0 # Root required for iptables
resources:
limits:
cpu: 2000m
memory: 1024Mi
requests:
cpu: 10m
memory: 10Miiptables Rules được tạo ra
istio-iptables script tạo ra các rules sau trong network namespace của Pod:
# Xem rules trong Pod (cần NET_ADMIN hoặc SSH đến node)
kubectl exec -it my-pod -c istio-proxy -- sh
$ iptables-save
# OUTPUT (đã simplify):
*nat
:PREROUTING ACCEPT
:OUTPUT ACCEPT
:POSTROUTING ACCEPT
# ISTIO_INBOUND: Capture inbound traffic
-A PREROUTING -p tcp -j ISTIO_INBOUND
# Skip traffic đến Envoy internal ports
-A ISTIO_INBOUND -p tcp --dport 15008 -j RETURN # HBONE
-A ISTIO_INBOUND -p tcp --dport 15090 -j RETURN # Prometheus
-A ISTIO_INBOUND -p tcp --dport 15021 -j RETURN # Health check
-A ISTIO_INBOUND -p tcp --dport 15020 -j RETURN # Envoy merged
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
# ISTIO_IN_REDIRECT: Redirect đến Envoy inbound port
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
# ISTIO_OUTPUT: Capture outbound traffic
-A OUTPUT -p tcp -j ISTIO_OUTPUT
# Skip loopback traffic
-A ISTIO_OUTPUT -o lo -d 127.0.0.1/32 -j RETURN
# Skip traffic từ Envoy (UID 1337) → prevents loop!
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
# Skip traffic từ Envoy (GID 1337)
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
# Skip loopback
-A ISTIO_OUTPUT -d 127.0.0.0/8 -j RETURN
# Redirect tất cả outbound TCP đến Envoy outbound
-A ISTIO_OUTPUT -p tcp -j ISTIO_REDIRECT
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
COMMITTại sao Envoy không bị redirect loop?
Đây là điểm quan trọng: khi Envoy forward traffic sau khi đã xử lý, nó không được redirect lại lần nữa.
Cơ chế: istio-init setup rule skip traffic từ UID 1337 — đây là UID mà istio-proxy container chạy với:
containers:
- name: istio-proxy
securityContext:
runAsUser: 1337 # Envoy chạy với UID 1337
runAsGroup: 1337Khi Envoy gửi traffic sau processing, kernel thấy UID 1337 và áp dụng rule RETURN → traffic bypasses iptables redirect → đến thẳng destination (localhost:app-port cho inbound, hoặc actual remote IP cho outbound).
REDIRECT vs TPROXY mode
REDIRECT mode (default):
- Sử dụng
SO_ORIGINAL_DSTsocket option để Envoy biết original destination - Hoạt động với hầu hết use cases
- Không support
--transparentproxy (original source IP không preserved trong mTLS context)
TPROXY mode (advanced):
# Enable TPROXY mode
traffic.sidecar.istio.io/interceptionMode: TPROXY- Envoy thấy original source IP
- Yêu cầu
NET_ADMINtrong Envoy container (không chỉ init) - Phức tạp hơn, thường không cần thiết
Privileged vs Non-Privileged Injection
Vấn đề với istio-init cần root
istio-init cần root (UID 0) và NET_ADMIN capability để chạy iptables. Đây là vấn đề trong môi trường security-conscious.
Giải pháp 1: CNI Plugin (Khuyến nghị)
Istio CNI plugin thay thế istio-init container bằng cách inject iptables rules ở node level, không phải trong Pod:
# Enable Istio CNI
istioctl install --set components.cni.enabled=true
# Hoặc trong CSM:
kubectl apply -f - <<EOF
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
components:
cni:
enabled: true
values:
cni:
cniBinDir: /home/kubernetes/bin # GKE CNI directory
cniConfDir: /etc/cni/net.d
EOFVới CNI plugin:
- Không có
istio-initcontainer trong Pod istio-cni-nodeDaemonSet chạy trên mỗi node- Khi Pod mới được create, CNI plugin gọi và setup iptables rules trước khi container start
- Pods không cần
NET_ADMINcapability
Ưu điểm CNI approach:
- Không cần privileged init container
- Compatible với PSA
restrictedprofile - Giảm startup time (không cần run init container)
Giải pháp 2: Ambient Mesh (Sidecarless)
Ambient mesh là evolution của Istio data plane, loại bỏ sidecar hoàn toàn:
- Traffic interception qua eBPF hoặc ztunnel (node-level proxy)
- Không inject vào Pod
- Giảm resource overhead đáng kể
CSM đang preview support ambient mesh. Hiện tại với production workloads, sidecar model vẫn là recommended.
Controlling Injection
Namespace-level control
# Enable injection cho namespace
kubectl label namespace production istio-injection=enabled
# Disable injection
kubectl label namespace kube-system istio-injection=disabled
# List namespaces với injection
kubectl get namespace -L istio-injectionRevision-based injection (cho upgrades)
# Label namespace dùng specific revision
kubectl label namespace production istio.io/rev=asm-1-20
# Để upgrade, label sang revision mới
kubectl label namespace production istio.io/rev=asm-1-21 --overwrite
# Rolling restart để inject sidecar mới
kubectl rollout restart deployment -n productionSelective opt-out
# Opt-out Pod cụ thể
spec:
template:
metadata:
annotations:
sidecar.istio.io/inject: "false"Khi nào opt-out:
- Jobs/CronJobs (thường không cần mesh)
- Monitoring agents (Prometheus node exporter)
- Batch processing với strict resource budget
Debugging Injection Issues
Pod không được inject
# Check namespace label
kubectl get namespace my-ns -o jsonpath='{.metadata.labels}'
# Check webhook exists
kubectl get mutatingwebhookconfigurations | grep istio
# Check webhook logs
kubectl logs -n istio-system deployment/istiod | grep inject
# Analyze injection
istioctl analyze -n my-namespace
# Debug injection cho Pod cụ thể
kubectl get pod my-pod -o yaml | grep -A5 initContainersiptables rules không đúng
# Exec vào istio-proxy container
kubectl exec -it my-pod -c istio-proxy -- sh
# Xem iptables rules
iptables-save -t nat
# Xem Envoy config
curl localhost:15000/config_dump | jq '.configs[] | select(.["@type"] | contains("ListenersConfigDump"))'Envoy startup fail
# Check init container logs
kubectl logs my-pod -c istio-init
# Check Envoy logs
kubectl logs my-pod -c istio-proxy
# Common errors:
# "iptables: No chain/target/match by that name" → Kernel version issue
# "Failed to create listener" → Port conflict
# "Envoy proxy is NOT ready" → xDS connection issue với IstiodTraffic không đi qua Envoy
# Verify bằng cách check Envoy stats
kubectl exec my-pod -c istio-proxy -- curl localhost:15000/stats | grep cx_total
# Nếu counter không tăng → traffic bypass Envoy
# Check iptables rule còn tồn tại
kubectl exec my-pod -c istio-proxy -- iptables -t nat -L ISTIO_OUTPUTInit Container và Container Startup Ordering
Một điểm quan trọng: istio-init phải complete trước khi containers chính start. Kubernetes đảm bảo điều này qua init container semantics.
Nhưng có một edge case: race condition giữa Envoy sidecar ready và application container start:
# Giải pháp: holdApplicationUntilProxyStarts
annotations:
proxy.istio.io/config: |
holdApplicationUntilProxyStarts: trueKhi holdApplicationUntilProxyStarts: true:
- Envoy start trước, kết nối với Istiod
- Sau khi Envoy có đủ xDS config, nó signal lifecycle hook
- Application container bắt đầu sau khi Envoy sẵn sàng
Điều này quan trọng vì: nếu application start trước Envoy sẵn sàng, initial requests có thể fail (không route được) hoặc đi mà không qua mTLS.
Kết luận
Sidecar injection là cơ chế cốt lõi của service mesh, nhưng nó đến với chi phí:
- Thêm init container: Tăng Pod startup time (50-200ms)
- Thêm sidecar container: Tăng resource usage (50m CPU, 128Mi memory baseline)
- iptables complexity: Có thêm layer indirection, debug khó hơn
- NET_ADMIN requirement: Vấn đề với PSA restricted profile (giải quyết bằng CNI plugin)
Hiểu rõ iptables interception flow giúp bạn:
- Debug khi traffic không đi đúng đường
- Exclude specific ports khỏi mesh khi cần
- Troubleshoot startup ordering issues
- Design opt-out strategy cho workloads không cần mesh