解密 Istio 的 Sidecar 注入模型
揭秘 Istio 如何将数据平面组件插入现有部署。
Istio 服务网格架构的简单概述总是从描述控制平面和数据平面开始。
重要的是要理解,尽管手动注入也是可能的,但 sidecar 注入到应用程序 pod 中会自动发生。流量从应用程序服务到这些 sidecar 的方向无需开发人员操心。一旦应用程序连接到 Istio 服务网格,开发人员就可以开始使用和利用服务网格提供的各种好处。但是,数据平面管道是如何实现的,以及真正需要什么才能使其无缝运行?在这篇文章中,我们将深入探讨 sidecar 注入模型的细节,以便清楚地了解 sidecar 注入的工作原理。
Sidecar 注入
简单来说,sidecar 注入就是在 pod 模板中添加附加容器的配置。Istio 服务网格所需的附加容器是
istio-init
这个 初始化容器 用于设置 iptables
规则,以便入站/出站流量将通过 sidecar 代理。初始化容器与应用程序容器的不同之处在于
- 它在应用程序容器启动之前运行,并且始终运行到完成。
- 如果有多个初始化容器,每个容器都应该在下一个容器启动之前成功完成。
因此,您可以看到这种类型的容器非常适合不需要成为实际应用程序容器一部分的设置或初始化作业。在本例中,istio-init
正是这样做的,并设置了 iptables
规则。
istio-proxy
这是实际的 sidecar 代理(基于 Envoy)。
手动注入
在手动注入方法中,您可以使用 istioctl
修改 pod 模板并添加上面提到的两个容器的配置。对于手动和自动注入,Istio 都从 istio-sidecar-injector
配置映射(configmap)和网格的 istio
configmap 中获取配置。
让我们看看 istio-sidecar-injector
configmap 的配置,以便了解发生了什么。
$ kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}'
SNIPPET from the output:
policy: enabled
template: |-
initContainers:
- name: istio-init
image: docker.io/istio/proxy_init:1.0.2
args:
- "-p"
- [[ .MeshConfig.ProxyListenPort ]]
- "-u"
- 1337
.....
imagePullPolicy: IfNotPresent
securityContext:
capabilities:
add:
- NET_ADMIN
restartPolicy: Always
containers:
- name: istio-proxy
image: [[ if (isset .ObjectMeta.Annotations "sidecar.istio.io/proxyImage") -]]
"[[ index .ObjectMeta.Annotations "sidecar.istio.io/proxyImage" ]]"
[[ else -]]
docker.io/istio/proxyv2:1.0.2
[[ end -]]
args:
- proxy
- sidecar
.....
env:
.....
- name: ISTIO_META_INTERCEPTION_MODE
value: [[ or (index .ObjectMeta.Annotations "sidecar.istio.io/interceptionMode") .ProxyConfig.InterceptionMode.String ]]
imagePullPolicy: IfNotPresent
securityContext:
readOnlyRootFilesystem: true
[[ if eq (or (index .ObjectMeta.Annotations "sidecar.istio.io/interceptionMode") .ProxyConfig.InterceptionMode.String) "TPROXY" -]]
capabilities:
add:
- NET_ADMIN
restartPolicy: Always
.....
如您所见,configmap 包含 istio-init
初始化容器和 istio-proxy
代理容器的配置。该配置包括容器镜像的名称以及诸如拦截模式、功能等参数。
从安全角度来看,重要的是要注意,istio-init
需要 NET_ADMIN
功能才能在 pod 的命名空间内修改 iptables
,istio-proxy
在 TPROXY
模式下配置时也是如此。由于这仅限于 pod 的命名空间,因此应该没有问题。但是,我注意到最近的 open-shift 版本可能存在一些问题,需要解决方法。这篇文章的结尾提到了其中一种方法。
要修改当前的 pod 模板以进行 sidecar 注入,您可以
$ istioctl kube-inject -f demo-red.yaml | kubectl apply -f -
或
要使用修改后的 configmap 或本地 configmap
从 configmap 创建
inject-config.yaml
和mesh-config.yaml
$ kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml $ kubectl -n istio-system get configmap istio -o=jsonpath='{.data.mesh}' > mesh-config.yaml
修改现有的 pod 模板,在我的例子中是
demo-red.yaml
$ istioctl kube-inject --injectConfigFile inject-config.yaml --meshConfigFile mesh-config.yaml --filename demo-red.yaml --output demo-red-injected.yaml
应用
demo-red-injected.yaml
$ kubectl apply -f demo-red-injected.yaml
如上所述,我们使用 sidecar-injector
和网格配置创建了一个新的模板,然后使用 kubectl
应用该新模板。如果我们查看注入的 YAML 文件,它包含 Istio 特定容器的配置,正如我们上面讨论的那样。一旦我们应用了注入的 YAML 文件,我们就会看到两个容器在运行。其中一个是实际的应用程序容器,另一个是 istio-proxy
sidecar。
$ kubectl get pods | grep demo-red
demo-red-pod-8b5df99cc-pgnl7 2/2 Running 0 3d
数量不是 3,因为 istio-init
容器是一种初始化类型容器,在完成其工作后退出,即在 pod 内设置 iptable
规则。为了确认初始化容器退出,让我们看看 kubectl describe
的输出
$ kubectl describe pod demo-red-pod-8b5df99cc-pgnl7
SNIPPET from the output:
Name: demo-red-pod-8b5df99cc-pgnl7
Namespace: default
.....
Labels: app=demo-red
pod-template-hash=8b5df99cc
version=version-red
Annotations: sidecar.istio.io/status={"version":"3c0b8d11844e85232bc77ad85365487638ee3134c91edda28def191c086dc23e","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","istio-certs...
Status: Running
IP: 10.32.0.6
Controlled By: ReplicaSet/demo-red-pod-8b5df99cc
Init Containers:
istio-init:
Container ID: docker://bef731eae1eb3b6c9d926cacb497bb39a7d9796db49cd14a63014fc1a177d95b
Image: docker.io/istio/proxy_init:1.0.2
Image ID: docker-pullable://docker.io/istio/proxy_init@sha256:e16a0746f46cd45a9f63c27b9e09daff5432e33a2d80c8cc0956d7d63e2f9185
.....
State: Terminated
Reason: Completed
.....
Ready: True
Containers:
demo-red:
Container ID: docker://8cd9957955ff7e534376eb6f28b56462099af6dfb8b9bc37aaf06e516175495e
Image: chugtum/blue-green-image:v3
Image ID: docker-pullable://docker.io/chugtum/blue-green-image@sha256:274756dbc215a6b2bd089c10de24fcece296f4c940067ac1a9b4aea67cf815db
State: Running
Started: Sun, 09 Dec 2018 18:12:31 -0800
Ready: True
istio-proxy:
Container ID: docker://ca5d690be8cd6557419cc19ec4e76163c14aed2336eaad7ebf17dd46ca188b4a
Image: docker.io/istio/proxyv2:1.0.2
Image ID: docker-pullable://docker.io/istio/proxyv2@sha256:54e206530ba6ca9b3820254454e01b7592e9f986d27a5640b6c03704b3b68332
Args:
proxy
sidecar
.....
State: Running
Started: Sun, 09 Dec 2018 18:12:31 -0800
Ready: True
.....
如输出所示,istio-init
容器的 State
是 Terminated
,Reason
是 Completed
。正在运行的两个容器分别是主应用程序 demo-red
容器和 istio-proxy
容器。
自动注入
大多数情况下,您不希望每次部署应用程序时都手动注入 sidecar,使用 istioctl
命令,而是希望 Istio 自动将 sidecar 注入您的 pod。这是推荐的方法,为了使它起作用,您需要做的就是将您部署应用程序的命名空间标记为 istio-injection=enabled
。
一旦标记,Istio 就会自动为您在该命名空间中部署的任何 pod 注入 sidecar。在下面的示例中,sidecar 会自动注入到 istio-dev
命名空间中部署的 pod 中。
$ kubectl get namespaces --show-labels
NAME STATUS AGE LABELS
default Active 40d <none>
istio-dev Active 19d istio-injection=enabled
istio-system Active 24d <none>
kube-public Active 40d <none>
kube-system Active 40d <none>
但这是如何工作的?为了彻底了解这一点,我们需要了解 Kubernetes 准入控制器。
对于自动 sidecar 注入,Istio 依赖于 Mutating Admission Webhook
。让我们看看 istio-sidecar-injector
变异 Webhook 配置的详细信息。
$ kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml
SNIPPET from the output:
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"admissionregistration.k8s.io/v1beta1","kind":"MutatingWebhookConfiguration","metadata":{"annotations":{},"labels":{"app":"istio-sidecar-injector","chart":"sidecarInjectorWebhook-1.0.1","heritage":"Tiller","release":"istio-remote"},"name":"istio-sidecar-injector","namespace":""},"webhooks":[{"clientConfig":{"caBundle":"","service":{"name":"istio-sidecar-injector","namespace":"istio-system","path":"/inject"}},"failurePolicy":"Fail","name":"sidecar-injector.istio.io","namespaceSelector":{"matchLabels":{"istio-injection":"enabled"}},"rules":[{"apiGroups":[""],"apiVersions":["v1"],"operations":["CREATE"],"resources":["pods"]}]}]}
creationTimestamp: 2018-12-10T08:40:15Z
generation: 2
labels:
app: istio-sidecar-injector
chart: sidecarInjectorWebhook-1.0.1
heritage: Tiller
release: istio-remote
name: istio-sidecar-injector
.....
webhooks:
- clientConfig:
service:
name: istio-sidecar-injector
namespace: istio-system
path: /inject
name: sidecar-injector.istio.io
namespaceSelector:
matchLabels:
istio-injection: enabled
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
在这里您可以看到用于 sidecar 注入的 Webhook namespaceSelector
标签,与标签 istio-injection: enabled
匹配。在本例中,您还可以看到在创建 pod 时执行此操作的操作和资源。当 apiserver
收到与其中一条规则匹配的请求时,apiserver
会向 Webhook 服务发送一个准入审查请求,如 clientConfig:
配置中使用 name: istio-sidecar-injector
键值对指定。我们应该能够看到此服务正在 istio-system
命名空间中运行。
$ kubectl get svc --namespace=istio-system | grep sidecar-injector
istio-sidecar-injector ClusterIP 10.102.70.184 <none> 443/TCP 24d
此配置最终与我们在手动注入中看到的配置基本相同。只是它是在 pod 创建期间自动完成的,所以您不会在部署中看到更改。您需要使用 kubectl describe
来查看 sidecar 代理和初始化代理。
自动 sidecar 注入不仅依赖于 Webhook 的 namespaceSelector
机制,还依赖于默认注入策略和每个 pod 的覆盖注释。
如果您再次查看 istio-sidecar-injector
ConfigMap,它定义了默认注入策略。在本例中,默认情况下它是启用的。
$ kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}'
SNIPPET from the output:
policy: enabled
template: |-
initContainers:
- name: istio-init
image: "gcr.io/istio-release/proxy_init:1.0.2"
args:
- "-p"
- [[ .MeshConfig.ProxyListenPort ]]
您也可以在 pod 模板中使用注释 sidecar.istio.io/inject
来覆盖默认策略。以下示例禁用 Deployment
中 pod 的 sidecar 的自动注入。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ignored
spec:
template:
metadata:
annotations:
sidecar.istio.io/inject: "false"
spec:
containers:
- name: ignored
image: tutum/curl
command: ["/bin/sleep","infinity"]
此示例表明存在许多变量,这些变量取决于自动 sidecar 注入是在您的命名空间、ConfigMap 还是 pod 中控制的,它们是
- webhooks
namespaceSelector
(istio-injection: enabled
) - 默认策略(在 ConfigMap
istio-sidecar-injector
中配置) - 每个 pod 的覆盖注释 (
sidecar.istio.io/inject
)
注入状态表 清楚地显示了基于上述变量值的最终注入状态。
从应用程序容器到 sidecar 代理的流量流
现在我们已经清楚地了解了如何将 sidecar 容器和初始化容器注入应用程序清单,sidecar 代理是如何获取进出容器的入站和出站流量的?我们简要提到了它是通过在 pod 命名空间内设置 iptable
规则来完成的,而这又是由 istio-init
容器完成的。现在,是时候验证命名空间内到底发生了什么更新。
让我们进入我们在上一节中部署的应用程序 pod 命名空间,看看配置的 iptables。我将使用 nsenter
举一个例子。或者,您可以以特权模式进入容器以查看相同的信息。对于无法访问节点的用户,使用 exec
进入 sidecar 并运行 iptables
更加实用。
$ docker inspect b8de099d3510 --format '{{ .State.Pid }}'
4125
$ nsenter -t 4215 -n iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N ISTIO_INBOUND
-N ISTIO_IN_REDIRECT
-N ISTIO_OUTPUT
-N ISTIO_REDIRECT
-A PREROUTING -p tcp -j ISTIO_INBOUND
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp -m tcp --dport 80 -j ISTIO_IN_REDIRECT
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15001
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -j ISTIO_REDIRECT
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
上面的输出清楚地表明,所有进入端口 80 的流量(即我们的 red-demo
应用程序监听的端口)现在被 REDIRECTED
到端口 15001
,即 istio-proxy
(一个 Envoy 代理)正在监听的端口。对出站流量也是如此。
这使我们接近这篇文章的结尾。我希望这有助于揭开 Istio 如何将 sidecar 代理注入现有部署以及 Istio 如何将流量路由到代理的神秘面纱。