Sidecar 自动注入过程详解

这一节将带大家了解 Istio 为数据平面自动注入 Sidecar 的详细过程。

Sidecar 注入过程概览

如 Istio 官方文档中对 Istio sidecar 注入的描述,你可以使用 istioctl 命令手动注入 Sidecar,也可以为 Kubernetes 集群自动开启 sidecar 注入,这主要用到了 Kubernetes 的准入控制器中的 webhook,参考 Istio 官网中对 Istio sidecar 注入的描述。

Sidecar 注入流程图
Sidecar 注入流程图

手动注入 sidecar 与自动注入 sidecar 的区别

不论是手动注入还是自动注入,sidecar 的注入过程有需要遵循如下步骤:

  1. Kubernetes 需要了解待注入的 sidecar 所连接的 Istio 集群及其配置;
  2. Kubernetes 需要了解待注入的 sidecar 容器本身的配置,如镜像地址、启动参数等;
  3. Kubernetes 根据 sidecar 注入模板和以上配置填充 sidecar 的配置参数,将以上配置注入到应用容器的一侧;

Istio 和 sidecar 配置保存在 istioistio-sidecar-injector 这两个 ConfigMap 中,其中包含了 Go template,所谓自动 sidecar 注入就是将生成 Pod 配置从应用 YAML 文件期间转移到 mutable webhook 中。

Init 容器

Init 容器是一种专用容器,它在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。

一个 Pod 中可以指定多个 Init 容器,如果指定了多个,那么 Init 容器将会按顺序依次运行。只有当前面的 Init 容器必须运行成功后,才可以运行下一个 Init 容器。当所有的 Init 容器运行完成后,Kubernetes 才初始化 Pod 和运行应用容器。

Init 容器使用 Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问 Secret 的权限,而应用程序容器则不能。

在 Pod 启动过程中,Init 容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。如果由于运行时或失败退出,将导致容器启动失败,它会根据 Pod 的 restartPolicy 指定的策略进行重试。然而,如果 Pod 的 restartPolicy 设置为 Always,Init 容器失败时会使用 RestartPolicy 策略。

在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready 状态。Init 容器的端口将不会在 Service 中进行聚集。 正在初始化中的 Pod 处于 Pending 状态,但应该会将 Initializing 状态设置为 true。Init 容器运行完成以后就会自动终止。

关于 Init 容器的详细信息请参考 Init 容器 - Kubernetes 中文指南/云原生应用架构实践手册

Istio 中的 sidecar 注入过程详解

Istio 中提供了以下两种 sidecar 注入方式:

不论是手动注入还是自动注入,sidecar 的注入过程都需要遵循如下步骤:

  1. Kubernetes 需要了解待注入的 sidecar 所连接的 Istio 集群及其配置;
  2. Kubernetes 需要了解待注入的 sidecar 容器本身的配置,如镜像地址、启动参数等;
  3. Kubernetes 根据 sidecar 注入模板和以上配置填充 sidecar 的配置参数,将以上配置注入到应用容器的一侧;

使用下面的命令可以手动注入 sidecar。

istioctl kube-inject -f ${YAML_FILE} | kuebectl apply -f -

该命令会使用 Istio 内置的 sidecar 配置来注入,下面使用 Istio详细配置请参考 Istio 官网

注入完成后您将看到 Istio 为原有 pod template 注入了 initContainer 及 sidecar proxy相关的配置。

Sidecar 注入示例分析

以 Istio 官方提供的 bookinfoproductpage 的 YAML 为例,关于 bookinfo 应用的详细 YAML 配置请参考 bookinfo.yaml

下文将从以下几个方面讲解:

  • Sidecar 容器的注入
  • iptables 规则的创建
  • 路由的详细过程
apiVersion: apps/v1
kind: Deployment
metadata:
  name: productpage-v1
  labels:
    app: productpage
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: productpage
      version: v1
  template:
    metadata:
      labels:
        app: productpage
        version: v1
    spec:
      serviceAccountName: bookinfo-productpage
      containers:
      - name: productpage
        image: docker.io/istio/examples-bookinfo-productpage-v1:1.15.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
        volumeMounts:
        - name: tmp
          mountPath: /tmp
      volumes:
      - name: tmp
        emptyDir: {}

再查看下 productpage 容器的 Dockerfile

FROM python:3.7.4-slim

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY test-requirements.txt ./
RUN pip install --no-cache-dir -r test-requirements.txt

COPY productpage.py /opt/microservices/
COPY tests/unit/* /opt/microservices/
COPY templates /opt/microservices/templates
COPY static /opt/microservices/static
COPY requirements.txt /opt/microservices/

ARG flood_factor
ENV FLOOD_FACTOR ${flood_factor:-0}

EXPOSE 9080
WORKDIR /opt/microservices
RUN python -m unittest discover

USER 1

CMD ["python", "productpage.py", "9080"]

我们看到 Dockerfile 中没有配置 ENTRYPOINT,所以 CMD 的配置 python productpage.py 9080 将作为默认的 ENTRYPOINT,记住这一点,再看下注入 sidecar 之后的配置。

$ istioctl kube-inject -f samples/bookinfo/platform/kube/bookinfo.yaml

我们只截取其中与 productpage 相关的 Deployment 配置中的部分 YAML 配置。

      containers:
      - image: docker.io/istio/examples-bookinfo-productpage-v1:1.15.0 # 应用镜像
        name: productpage
        ports:
        - containerPort: 9080
      - args:
        - proxy
        - sidecar
        - --domain
        - $(POD_NAMESPACE).svc.cluster.local
        - --configPath
        - /etc/istio/proxy
        - --binaryPath
        - /usr/local/bin/envoy
        - --serviceCluster
        - productpage.$(POD_NAMESPACE)
        - --drainDuration
        - 45s
        - --parentShutdownDuration
        - 1m0s
        - --discoveryAddress
        - istiod.istio-system.svc:15012
        - --zipkinAddress
        - zipkin.istio-system:9411
        - --proxyLogLevel=warning
        - --proxyComponentLogLevel=misc:error
        - --connectTimeout
        - 10s
        - --proxyAdminPort
        - "15000"
        - --concurrency
        - "2"
        - --controlPlaneAuthPolicy
        - NONE
        - --dnsRefreshRate
        - 300s
        - --statusPort
        - "15020"
        - --trust-domain=cluster.local
        - --controlPlaneBootstrap=false
        image: docker.io/istio/proxyv2:1.5.1 # sidecar proxy
        name: istio-proxy
        ports:
        - containerPort: 15090
          name: http-envoy-prom
          protocol: TCP
      initContainers:
      - command:
        - istio-iptables
        - -p
        - "15001"
        - -z
        - "15006"
        - -u
        - "1337"
        - -m
        - REDIRECT
        - -i
        - '*'
        - -x
        - ""
        - -b
        - '*'
        - -d
        - 15090,15020
        image: docker.io/istio/proxyv2:1.5.1 # init 容器
        name: istio-init

Istio 给应用 Pod 注入的配置主要包括:

  • Init 容器 istio-init:用于 pod 中设置 iptables 端口转发
  • Sidecar 容器 istio-proxy:运行 sidecar 代理,如 Envoy 或 MOSN。

接下来将分别解析下这两个容器。

Init 容器解析

Istio 在 pod 中注入的 Init 容器名为 istio-init,我们在上面 Istio 注入完成后的 YAML 文件中看到了该容器的启动命令是:

istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -i '*' -x "" -b '*' -d 15090,15020

我们再检查下该容器的 Dockerfile 看看 ENTRYPOINT 是怎么确定启动时执行的命令。

# 前面的内容省略
# The pilot-agent will bootstrap Envoy.
ENTRYPOINT ["/usr/local/bin/pilot-agent"]

我们看到 istio-init 容器的入口是 /usr/local/bin/istio-iptables 命令行,该命令行工具的代码的位置在 Istio 源码仓库的 tools/istio-iptables 目录。

注意:在 Istio 1.1 版本时还是使用 isito-iptables.sh 命令行来操作 IPtables。

Init 容器启动入口

Init 容器的启动入口是 istio-iptables 命令行,该命令行工具的用法如下:

$ istio-iptables [flags]
  -p: 指定重定向所有 TCP 流量的 sidecar 端口(默认为 $ENVOY_PORT = 15001)
  -m: 指定入站连接重定向到 sidecar 的模式,“REDIRECT” 或 “TPROXY”(默认为 $ISTIO_INBOUND_INTERCEPTION_MODE)
  -b: 逗号分隔的入站端口列表,其流量将重定向到 Envoy(可选)。使用通配符 “*” 表示重定向所有端口。为空时表示禁用所有入站重定向(默认为 $ISTIO_INBOUND_PORTS  -d: 指定要从重定向到 sidecar 中排除的入站端口列表(可选),以逗号格式分隔。使用通配符“*” 表示重定向所有入站流量(默认为 $ISTIO_LOCAL_EXCLUDE_PORTS  -o:逗号分隔的出站端口列表,不包括重定向到 Envoy 的端口。
  -i: 指定重定向到 sidecar 的 IP 地址范围(可选),以逗号分隔的 CIDR 格式列表。使用通配符 “*” 表示重定向所有出站流量。空列表将禁用所有出站重定向(默认为 $ISTIO_SERVICE_CIDR  -x: 指定将从重定向中排除的 IP 地址范围,以逗号分隔的 CIDR 格式列表。使用通配符 “*” 表示重定向所有出站流量(默认为 $ISTIO_SERVICE_EXCLUDE_CIDR)。
  -k:逗号分隔的虚拟接口列表,其入站流量(来自虚拟机的)将被视为出站流量。
  -g:指定不应用重定向的用户的 GID。(默认值与 -u param 相同)
  -u:指定不应用重定向的用户的 UID。通常情况下,这是代理容器的 UID(默认值是 1337,即 istio-proxy 的 UID)。
  -z: 所有进入 pod/VM 的 TCP 流量应被重定向到的端口(默认 $INBOUND_CAPTURE_PORT = 15006)。

以上传入的参数都会重新组装成 iptables 规则,关于该命令的详细用法请访问 tools/istio-iptables/pkg/cmd/root.go

该容器存在的意义就是让 sidecar 代理可以拦截所有的进出 pod 的流量,15090 端口(Mixer 使用)和 15092 端口(Ingress Gateway)除外的所有入站(inbound)流量重定向到 15006 端口(sidecar),再拦截应用容器的出站(outbound)流量经过 sidecar 处理(通过 15001 端口监听)后再出站。关于 Istio 中端口用途请参考 Istio 官方文档

命令解析

这条启动命令的作用是:

  • 将应用容器的所有流量都转发到 sidecar 的 15006 端口。
  • 使用 istio-proxy 用户身份运行, UID 为 1337,即 sidecar 所处的用户空间,这也是 istio-proxy 容器默认使用的用户,见 YAML 配置中的 runAsUser 字段。
  • 使用默认的 REDIRECT 模式来重定向流量。
  • 将所有出站流量都重定向到 sidecar 代理(通过 15001 端口)。

因为 Init 容器初始化完毕后就会自动终止,因为我们无法登陆到容器中查看 iptables 信息,但是 Init 容器初始化结果会保留到应用容器和 sidecar 容器中。

参考