Istio Sidecar 注入:例外和除错
原文:Istio sidecar injection: enabling automatic injection, adding exceptions and debugging
作者:Jonh Wendell
Kubernetes 环境下的 Istio 使用了 Sidecar 模型进行部署,把一个辅助容器(也就是 Sidecar)附加到业务 Pod 之中。这一过程让 Sidecar 容器和业务容器共享同样的网络栈,可以视为同一主机上的两个进程。这样一来,Istio 就能够接管业务应用的所有网络调用,就有了增强服务间通信能力的基础。
这个 Sidecar 容器命名为 istio-proxy
,能够用手工或者自动方式进行注入。其实这个手工注入也不是 100% 徒手完成的。
手工注入
Istio 的发行版本中会带有一个 istioctl
工具。看名字就知道这工具很棒:)。它的一个能力就是把 istio-proxy
Sidecar 注入到业务容器之中。我们可以用一个简单的 busybox
Pod 来看看它的功能:
$ cat busybox.yaml
apiVersion: v1
kind: Pod
metadata:
name: busybox-test
spec:
containers:
- name: busybox-container
image: busybox
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep infinity']
$ istioctl kube-inject -f busybox.yaml > busybox-injected.yaml
$ cat busybox-injected.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
name: busybox-test
spec:
containers:
- command:
- sh
- -c
- echo Hello Kubernetes! && sleep infinity
image: busybox
name: busybox-container
- image: docker.io/istio/proxyv2:1.0.2
imagePullPolicy: IfNotPresent
name: istio-proxy
args:
- proxy
- sidecar
...
上文可见,这个命令生成了一个 YAML 文件,和原始文件有点像,但是 Pod 中加入了一个 Sidecar(istio-proxy)。这的确不是纯手工吧——至少让我们少打了不少字。然后就可以使用 kubectl apply
命令把这个修改后的文件提交给 Kubernetes 集群了:
$ kubectl apply -f busybox-injected.yaml
# 如果不想生成中间文件,可以直接用管道完成全部操作。
$ kubectl apply -f <(istioctl kube-inject -f busybox.yaml)
随之而来的一个问题就是:这些数据是哪来的?它怎么知道 Sidecar 镜像是 docker.io/istio/proxyv2:1.0.2
的?答案很简单:所有数据都来自于一个 ConfigMap,这个对象保存在 istio-system
命名空间:
$ kubectl -n istio-system describe configmap istio-sidecar-injector
Name: istio-sidecar-injector
Namespace: istio-system
Data
====
config:
----
policy: enabled
template: |-
initContainers:
- name: istio-init
image: "docker.io/istio/proxy_init:1.0.2"
...
可以对这个 ConfigMap 进行编辑,修改一些你需要的值,用来进行注入。不难发现,这主要是一个用于向 Pod 加入内容的模板。如果想要为 istio-proxy
容器使用其它对象,只要修改这个字段的内容就可以了,或者还可以调整任何其它要注入的东西。这个 ConfigMap 是用来完成网格中所有 Pod 的注入工作的,所以要小心从事。
因为 istioctl
要根据 ConfigMap 来获知注入内容,也就是说执行 istioctl
的用户必须能够访问到安装了 Istio 的 Kubernetes 集群的这一对象。如果因为某些原因无法访问到这一 ConfigMap,还可以在 istioctl
中使用一个本地的配置文件。
# 首先使用有权限的用户运行这一命令
$ kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml
# 这个文件可以随时进行任意修改。
$ istioctl kube-inject --injectConfigFile inject-config.yaml ...
自动注入
注入 istio-proxy
还有另一个方式,就是要求 Istio 进行自动注入。为命名空间设置标签 istio-injection=enabled 就能满足这一需要了。如果命名空间加上了这个标签,所有其中的 Pod 都会被注入 istio-proxy
Sidecar,也就无需手工执行 istioctl
处理 YAML 文件了。
工作方式很简单:它使用了 Kubernetes 的 MutatingWebhook
,新 Pod 创建之前,这一功能会通知 Istio,让 Istio 有机会对新 Pod 进行就地修改,stio 会使用在 ConfigMap 中的模板把 istio-proxy
注入到 Pod 中。
看起来不错?
自动注入过程有很大的弹性:
istio-sidecar-injector
ConfigMap 中有一个布尔值用来指定自动注入是否启用。- 只有使用标签进行标注的命名空间才会进行自动注入,也就是说用命名空间标签能够对自动注入进行控制。
- 还可以使用
kubectl -n istio-system edit MutatingWebhookConfiguration istio-sidecar-injector
命令,修改其中的namespaceSelector
字段来调整这个标签的用法,甚至移除这个限制(也就是为所有命名空间使用自动注入,慎用)。 - 可以禁用特定 Pod 的自动注入。如果 Pod 包含注解
sidecar.istio.io/inject: "false"
,Istio 就不会为在这一 Pod 中注入 Sidecar。 - 还有更保守的方式:只对选择的 Pod 进行注入——也就是白名单方式。设置策略为 false(
kubectl -n istio-system edit configmap istio-sidecar-injector
),只对包含注解sidecar.istio.io/inject: "true"
进行注入。
想要更大弹性?
还有些上面提到的方法无法满足的弹性要求:
如果不熟悉 Openshift 的用户,它有一个功能叫做 source-to-image
(代码到镜像,s2i
),这一功能会将代码构建为容器镜像。提供一个 git 仓库作为输入(支持多种语言),就会输出镜像并运行到 Openshift 集群上。
这是一个神奇的功能。这里不会介绍很多细节,我只会告诉你本文中需要了解的事情:在这一过程中 Openshift 会创建一或更多个的用于进行构建的中间、辅助 Pod。构建过程结束之后,二进制工件被构建为容器镜像,而辅助 Pod 会被销毁。
如果我们为指定命名空间启用了自动注入,并使用 Openshift 的 s2i 功能,会让这些辅助 Pod 也被注入 Sidecar,这些 Pod 是 Openshift 创建的,我们无法对其进行注解以阻止 Sidecar 的注入。辅助 Pod 无需进行注入,怎么办呢?
一个可能的解决方法就是用上面提到的保守方案,只对特定 Pod 启用注入,但是就要对 Pod 进行特别的注解。还有别的方式。
新方案
1.1.0 中,Istio 自动注入可以根据标签进行例外设置:不管命名空间标签如何,策略如何设置,对符合标签选择器要求的 Pod 都不进行注入。可以在 istio-sidecar-injector
中加入例外设置。
$ kubectl -n istio-system describe configmap istio-sidecar-injector
apiVersion: v1
kind: ConfigMap
metadata:
name: istio-sidecar-injector
data:
config: |-
policy: enabled
neverInjectSelector:
- matchExpressions:
- {key: openshift.io/build.name, operator: Exists}
- matchExpressions:
- {key: openshift.io/deployer-pod-for.name, operator: Exists}
template: |-
initContainers:
...
可以看到上面的 neverInjectSelector
字段,它是一个 Kubernetes 标签选择器的数组。不同元素之间是“或”关系,第一次发现有符合条件的标签之后就会跳过其它判断。上面的语句意味着:包含 openshift.io/build.name
或者 openshift.io/deployer-pod-for.name
标签的 Pod,不管标签值如何,都不会进行注入。
为了完整性起见,可以使用 alwaysInjectSelector
字段,这个字段会无视全局策略,向符合条件的 Pod 进行注入。
标签选择器方式产生了很大的弹性,能够处理更多的例外情况。阅读 Kubernetes 文档可以了解更多这方面的内容。
值得注意的是,Pod 注解还是有更高的优先级,如果 Pod 注解包含了 sidecar.istio.io/inject: "true/false"
,会被优先处理,所以自动注入的评估顺序是:
Pod 注解 → NeverInjectSelector → AlwaysInjectSelector → 命名空间策略。
注入选择器是新特性,这方面的文档还在更新,但是其它部分的文档和例子,都可以在官方文档中查看。
Pod 为什么没注入?
这是个常见问题。按照前面的介绍(例如给命名空间打标签)进行操作,结果 Pod 还没有被注入。
或者刚好相反,Pod 明明注解为 sidecar.istio.io/inject: "false
,还是被注入了,为什么?
可以看看 sidecar-injector
Pod 的日志:
$ pod=$(kubectl -n istio-system get pods -l istio=sidecar-injector -o jsonpath='{.items[0].metadata.name}')
$ kubectl -n istio-system logs -f $pod
然后可以创建业务 Pod,看看日志输出的具体内容。要看到更详细的日志(经常会很有用),可以编辑 sidecar-injector
Deployment 对象,给它加上参数 --log_output_level=default:debug
:
$ kubectl -n istio-system edit deployment istio-sidecar-injector
...
containers:
- args:
- --caCertFile=/etc/istio/certs/root-cert.pem
- --tlsCertFile=/etc/istio/certs/cert-chain.pem
- --tlsKeyFile=/etc/istio/certs/key.pem
- --injectConfig=/etc/istio/inject/config
- --meshConfig=/etc/istio/config/mesh
- --healthCheckInterval=2s
- --healthCheckFile=/health
- --log_output_level=default:debug
image: docker.io/istio/sidecar_injector:1.0.2
imagePullPolicy: IfNotPresent
...
编辑成功之后 Pod 会重启,完成之后就可以重新查看日志了:
$ pod=$(kubectl -n istio-system get pods -l istio=sidecar-injector -o jsonpath='{.items[0].metadata.name}')
$ kubectl -n istio-system logs -f $pod
提示
如果在日志中还是找不到问题原因,就代表 sidecar-injector
Pod 没有收到 Pod 创建的通知,也就不会触发自动注入的操作。这可能是因为命名空间没有正确标签导致的,因此需要检查一下命名空间的标签以及 MutatingWebhookConfiguration
中的配置。缺省情况下,命名空间应该设置 istio-injection=enabled
标签。可以使用 kubectl -n istio-system edit MutatingWebhookConfiguration istio-sidecar-injector
命令检查其中的 namespaceSelector
字段内容。
完成排查之后,可以再次编辑 sidecar-injector
Deployment 对象,清除新加入的参数。