本文首先介绍了 Istio 的基础知识,然后结合实际的样例阐释了 Istio 是如何将 sidecar 容器注入到 Kubernetes 集群中,并实现流量拦截的。
本文最初发表于Solo官方博客,经原作者Kasun Talwatta授权,由 InfoQ 中文站翻译分享。
像Istio这样的服务网格项目会为我们的架构引入很多的特性和收益,包括更安全地管理集群中微服务之间的流量、服务发现、请求路由以及服务之前可靠的通信。
尽管 Istio 是平台中立的服务网格,但是它更受欢迎的使用场景是与Kubernetes协作。虽然它如此流行,但对于刚接触服务网格的人来说,理解 Istio 的网络和核心机制可能会很复杂和困难,例如:
在本系列的博客文章的第一篇中,我们将会分析 Istio 的架构和实现原理,从而解释这些机制是如何运行的,我们将会介绍 Istio 的网络基础知识、数据平面和控制平面、网络、以及使用 Envoy 代理的 sidecar 注入。借助一个演示环境,我们将会看到 Istio 如何注入 init 和 sidecar 容器,以及这些容器在 pod 模板中的配置。
Istio 概览已在官方文档中进行了详尽的介绍,但在继续后面的内容之前,我们着重再看一下它的几个核心组件。
Istio 主要由两部分组成,分别是数据平面和控制平面。
在介绍下面的内容之前,我们创建一个本地的沙箱环境。这能确保我们会有一个部署在 Kubernetes 中的 Istio 服务网格以及运行在网格中的示例应用。
所需的工具包括:
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.11.4 sh
安装)部署 Istio 服务网格的步骤如下:
1.使用 hyperkit 驱动在本地创建一个 1.22.2 版本的 Kubernetes 集群,如果你使用非 Mac OS X 的机器的话,那么需要使用 virtualbox 来代替。
minikube start --memory=4096 --cpus=2 --disk-size='20gb' --kubernetes-version=1.22.2 --driver=hyperkit -p istio-demo
2.集群启动之后,执行如下的命令来搭建 Istio
# Deploy Istio operator
istioctl operator init
# Inject operator configuration
cat << EOF | kubectl apply -f -
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: istio-control-plane
namespace: istio-system
spec:
profile: minimal
meshConfig:
accessLogFile: /dev/stdout
enableAutoMtls: true
defaultConfig:
proxyMetadata:
# Enable basic DNS proxying
ISTIO_META_DNS_CAPTURE: 'true'
# Enable automatic address allocation
ISTIO_META_DNS_AUTO_ALLOCATE: 'true'
EOF
3.部署示例应用
# Create apps namespace
kubectl create ns apps
# Label apps namespace for sidecar auto injection
kubectl label ns apps istio-injection=enabled
# Deploy a unprivileged sleep application
cat << EOF | kubectl apply -n apps -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: sleep
---
apiVersion: v1
kind: Service
metadata:
name: sleep
labels:
app: sleep
service: sleep
spec:
ports:
- name: http
port: 80
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
terminationGracePeriodSeconds: 0
serviceAccountName: sleep
containers:
- name: sleep
image: curlimages/curl
command: ["/bin/sleep", "3650d"]
imagePullPolicy: IfNotPresent
volumeMounts:
- name: secret-volume
mountPath: /etc/sleep/tls
volumes:
- name: secret-volume
secret:
secretName: sleep-secret
optional: true
EOF
4.验证 istio-init 和 istio-proxy 容器已经就绪并处于运行状态
kubectl get po -l app=sleep -n apps -o jsonpath='{range .items[*]}{range @.status.containerStatuses[*]}{.name},{"ready="}{.ready},{"started="}{.started}{"\n"}{end}{range @.status.initContainerStatuses[*]}{.name},{"ready="}{.ready},{"terminated="}{.state.terminated.reason}{end}' | sort
该命令将会输出:
istio-init,ready=true,terminated=Completed
istio-proxy,ready=true,started=true
在 Istio 中,sidecar 注入是关键功能之一,它简化了以 pod 模板的形式添加和运行额外容器的过程。在这个注入过程中,会提供两个额外的容器,分别是:
我们首先看一下在之前部署的应用 pod 中,这两个容器的 YAML 清单(manifest)。
kubectl get po -l app=sleep -n apps -o yaml
我们来看一下 istio-init 和 istio-proxy 容器的片段。
istio-init 容器:
initContainers:
- name: istio-init
image: docker.io/istio/proxyv2:1.11.4
imagePullPolicy: IfNotPresent
args:
- istio-iptables
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- 15090,15021,15020
env:
- name: ISTIO_META_DNS_AUTO_ALLOCATE
value: "true"
- name: ISTIO_META_DNS_CAPTURE
value: "true"
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: 100m
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
istio-proxy 容器:
containers:
- name: istio-proxy
image: docker.io/istio/proxyv2:1.11.4
imagePullPolicy: IfNotPresent
args:
- proxy
- sidecar
- --domain
- $(POD_NAMESPACE).svc.cluster.local
- --proxyLogLevel=warning
- --proxyComponentLogLevel=misc:error
- --log_output_level=default:info
- --concurrency
- "2"
ports:
- name: http-envoy-prom
containerPort: 15090
protocol: TCP
readinessProbe:
httpGet:
path: /healthz/ready
port: 15021
scheme: HTTP
failureThreshold: 30
initialDelaySeconds: 1
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 1337
runAsNonRoot: true
runAsUser: 1337
env:
- name: PROXY_CONFIG
value: |
{"proxyMetadata":{"ISTIO_META_DNS_AUTO_ALLOCATE":"true","ISTIO_META_DNS_CAPTURE":"true"}}
- name: ISTIO_META_DNS_AUTO_ALLOCATE
value: "true"
- name: ISTIO_META_DNS_CAPTURE
value: "true"
...
在这些片段中有一些有意思的事情:
kubectl exec $(kubectl get po -l app=sleep -n apps -o jsonpath="{.items[0].metadata.name}") -n apps -c istio-proxy -- pilot-agent
输出如下所示:
Istio Pilot agent runs in the sidecar or gateway container and bootstraps Envoy.
Usage:
pilot-agent [command]
Available Commands:
completion generate the autocompletion script for the specified shell
help Help about any command
istio-clean-iptables Clean up iptables rules for Istio Sidecar
istio-iptables Set up iptables rules for Istio Sidecar
proxy XDS proxy agent
request Makes an HTTP request to the Envoy admin API
version Prints out build version information
wait Waits until the Envoy proxy is ready
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
另一方面,istio-proxy 容器以 1337 用户在限制权限下运行。因为这是一个保留用户,所以应用工作负载的 UID(User ID)必须要与之不同,不能与 1337 冲突。1337 UID 是由 Istio 团队任意选择的,以便于将流量重定向到 istio-proxy 容器。我们可以看到在初始化 iptables 的时候,1337 也作为了 istio-iptables 的参数。由于这个容器会与应用工作负载一起运行,Istio 还确保它对根文件系统只有读的权限。
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 1337
runAsNonRoot: true
runAsUser: 1337
readinessProbe:
httpGet:
path: /healthz/ready
port: 15021
scheme: HTTP
initialDelaySeconds: 1
failureThreshold: 30
periodSeconds: 2
successThreshold: 1
timeoutSeconds: 3
Istio 采用了两种不同的方式将 sidecar 代理注入应用的工作负载中,分别是手动和自动方式。这两种方法都遵循相同的注入原则,那就是指定的“某些”应用工作负载(这能够以更高级的 Kubernetes 资源的形式来进行定义,如 Deployment、Statefulset、DaemonSet,甚至可以作为 Pod)允许 Kubernetes 使用 sidecar 注入模板和配置参数(istio-sidecar-injector configmap)注入 sidecar 容器。
在这两种方法中,手动方式更易于理解。手动注入是通过 istioctl 命令并借助 kube-inject 参数完成的。你可以使用下面的任何一种格式来注入:
istioctl kube-inject -f application.yaml | kubectl apply -f -
或者
kubectl apply -f <(istioctl kube-inject -f application.yaml)
当使用 istioctl kube-inject 来注入 sidecar 的时候,默认它会使用集群中名为 istio-sidecar-injector Kubernetes configmap。它是以一组标记的形式提供的,我们可以声明它们以自定义这种行为:
--injectConfigFile string Injection configuration filename. Cannot be used with --injectConfigMapName
--meshConfigFile string Mesh configuration filename. Takes precedence over --meshConfigMapName if set
--meshConfigMapName string ConfigMap name for Istio mesh configuration, key should be "mesh" (default "istio")
--injectConfigMapNam string ConfigMap name for Istio sidecar injection, key should be "config" (default "istio-sidecar-injector")
注意,在 istioctl kube-inject 中,--injectConfigMapNam 是一个隐藏标记,它允许我们重写集群中 sidecar 的注入配置。
另外,注入也可以通过配置的本地副本和上述标记来实现:
kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml
kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.values}' > inject-values.yaml
kubectl -n istio-system get configmap istio -o=jsonpath='{.data.mesh}' > mesh-config.yaml
istioctl kube-inject \
--injectConfigFile inject-config.yaml \
--meshConfigFile mesh-config.yaml \
--valuesFile inject-values.yaml \
--filename application.yaml \
| kubectl apply -f -
必须要注意的是,在手动注入的时候,不要破坏 sidecar,尤其是使用自定义配置的时候。
这种方式被认为是 Istio 中注入 sidecar 的标准方法。与手动方式相比,它涉及的配置步骤更少,但是它取决于底层的 Kubernetes 分发版本是否启用了对admission控制器的支持。Istio 使用了一个mutating webhook admission控制器来实现这一点。
点击查看大图
如下是 Kubernetes mutating admission 在 sidecar 注入时的处理过程:
关于完整的配置,请使用如下的命令 kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml 进行查阅。为了简洁起见,下面的片段中仅包含了四个 webhook 配置中的两个:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: istio-sidecar-injector
webhooks:
- admissionReviewVersions:
- v1beta1
- v1
clientConfig:
caBundle: cert
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: namespace.sidecar-injector.istio.io
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: In
values:
- enabled
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values:
- "false"
reinvocationPolicy: Never
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
timeoutSeconds: 10
- admissionReviewVersions:
- v1beta1
- v1
clientConfig:
caBundle: cert
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: namespace.sidecar-injector.istio.io
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: In
values:
- enabled
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values:
- "false"
reinvocationPolicy: Never
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
timeoutSeconds: 10
- admissionReviewVersions:
- v1beta1
- v1
clientConfig:
caBundle: cert
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: object.sidecar-injector.istio.io
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: DoesNotExist
- key: istio.io/rev
operator: DoesNotExist
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: In
values:
- "true"
- key: istio.io/rev
operator: DoesNotExist
reinvocationPolicy: Never
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
timeoutSeconds: 10
这个配置会告诉 Kubernetes mutating 控制器在 HTTPS 端口上安全地将请求发送到 istiod 服务的“/inject”端点。在调用 mutating webhook 之前,Kubernetes 会检查发送请求的用户是否允许发起该请求。在 Istio 中,webhook是作为istiod二进制文件的一部分实现的。
注入可以使用命名空间级别的标签(istio-injection=enabled),也可以使用对象级别的注解(sidecar.istio.io/inject="true")来触发。每个 webhook 配置在 namespaceSelector 和 objectSelector 中为这些触发器定义了匹配规则。当注入基于命名空间级别定义的标签触发时,在命名空间中创建的任何部署对象(Deployment、StatefulSet、DaemonSet)都将注入 sidecar 代理的变更。下面是对匹配规则的小结。
在注入 Pod 清单时,也可以直接变更 pod 对象(如果命名空间还没有标签的话)。Pod 清单必须要有一个 sidecar.istio.io/inject="true"标签。举例来说:
apiVersion: v1
kind: Pod
metadata:
name: sleep
namespace: apps
labels:
app: sleep
sidecar.istio.io/inject: "true"
...
到目前为止,我们已经了解了 Istio 的基础知识、数据平面和控制平面、网络,以及 Envoy 代理的 sidecar 注入,并且展示了 Istio 如何使用演示环境在 pod 模板中注入 init 和 sidecar 容器以及这些容器的配置。在后续的博客文章中,我们将分析如何配置和管理 iptables。
领取专属 10元无门槛券
私享最新 技术干货