Skip to content

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:

bash
# Xem webhook config
kubectl get mutatingwebhookconfigurations istio-sidecar-injector -o yaml
yaml
apiVersion: 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: 30

Webhook Injection Process

Khi Pod được submit:

  1. API Server nhận Pod spec
  2. API Server gọi webhook /inject endpoint trên istiod
  3. Istiod nhận Pod spec, trả về mutated Pod spec với:
    • initContainers: thêm istio-init
    • containers: thêm istio-proxy
    • volumes: thêm certificate volumes
    • annotations: ghi lại injection metadata
bash
# 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

yaml
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:

yaml
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: 10Mi

iptables Rules được tạo ra

istio-iptables script tạo ra các rules sau trong network namespace của Pod:

bash
# 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

COMMIT

Tạ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:

yaml
containers:
- name: istio-proxy
  securityContext:
    runAsUser: 1337  # Envoy chạy với UID 1337
    runAsGroup: 1337

Khi 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_DST socket option để Envoy biết original destination
  • Hoạt động với hầu hết use cases
  • Không support --transparent proxy (original source IP không preserved trong mTLS context)

TPROXY mode (advanced):

bash
# Enable TPROXY mode
traffic.sidecar.istio.io/interceptionMode: TPROXY
  • Envoy thấy original source IP
  • Yêu cầu NET_ADMIN trong 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:

bash
# 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
EOF

Với CNI plugin:

  • Không có istio-init container trong Pod
  • istio-cni-node DaemonSet 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_ADMIN capability

Ưu điểm CNI approach:

  • Không cần privileged init container
  • Compatible với PSA restricted profile
  • 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

bash
# 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-injection

Revision-based injection (cho upgrades)

bash
# 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 production

Selective opt-out

yaml
# 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

bash
# 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 initContainers

iptables rules không đúng

bash
# 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

bash
# 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 Istiod

Traffic không đi qua Envoy

bash
# 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_OUTPUT

Init 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 readyapplication container start:

yaml
# Giải pháp: holdApplicationUntilProxyStarts
annotations:
  proxy.istio.io/config: |
    holdApplicationUntilProxyStarts: true

Khi 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í:

  1. Thêm init container: Tăng Pod startup time (50-200ms)
  2. Thêm sidecar container: Tăng resource usage (50m CPU, 128Mi memory baseline)
  3. iptables complexity: Có thêm layer indirection, debug khó hơn
  4. 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

References