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 ready và application 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 ​