Istio Sidecar注入原理
概念
简单来说,Sidecar 注入会将额外容器的配置添加到 Pod 模板中。这里特指将Envoy容器注应用所在Pod中。
Istio 服务网格目前所需的容器有:
istio-init
用于设置 iptables 规则,以便将入站/出站流量通过 Sidecar 代理。
初始化容器与应用程序容器在以下方面有所不同:
- 它在启动应用容器之前运行,并一直运行直至完成。
- 如果有多个初始化容器,则每个容器都应在启动下一个容器之前成功完成。
因此,您可以看到,对于不需要成为实际应用容器一部分的设置或初始化作业来说,这种容器是多么的完美。在这种情况下,istio-init
就是这样做并设置了 iptables
规则。
istio-proxy
这个容器是真正的 Sidecar 代理(基于 Envoy)。
下面的内容描述了向 pod 中注入 Istio Sidecar 的两种方法:
- 使用
istioctl
手动注入 - 启用 pod 所属命名空间的 Istio Sidecar 注入器自动注入。
手动注入直接修改配置,如 deployment,并将代理配置注入其中。
当 pod 所属namespace
启用自动注入后,自动注入器会使用准入控制器在创建 Pod 时自动注入代理配置。
通过应用 istio-sidecar-injector
ConfigMap 中定义的模版进行注入。
自动注入
当你在一个namespace
中设置了 istio-injection=enabled
标签,且 injection webhook 被启用后,任何新的 pod 都有将在创建时自动添加 Sidecar. 请注意,区别于手动注入,自动注入发生在 pod 层面。你将看不到 deployment 本身有任何更改 。
kubectl label namespace default istio-inhection=enabled
kubectl get namespace -L istio-injection
NAME STATUS AGE ISTIO-INJECTION
default Active 1h enabled
istio-system Active 1h
kube-public Active 1h
kube-system Active 1h
注入发生在 pod 创建时。杀死正在运行的 pod 并验证新创建的 pod 是否注入 sidecar。原来的 pod 具有 READY 为 1/1 的容器,注入 sidecar 后的 pod 则具有 READY 为 2/2 的容器 。
自动注入原理
自动注入是利用了k8s Admission webhook 实现的。 Admission webhook 是一种用于接收准入请求并对其进行处理的 HTTP 回调机制, 它可以更改发送到 API 服务器的对象以执行自定义的设置默认值操作。 具体细节可以查阅 Admission webhook文档。
istio 对应的istio-sidecar-injector webhook配置,默认会回调istio-sidecar-injector service的/inject
地址。
apiVersion: admissionregistration.k8s.io/v1beta1
kind: MutatingWebhookConfiguration
metadata:
name: istio-sidecar-injector
webhooks:
- name: sidecar-injector.istio.io
clientConfig:
service:
name: istio-sidecar-injector
namespace: istio-system
path: "/inject"
caBundle: ${CA_BUNDLE}
rules:
- operations: [ "CREATE" ]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
namespaceSelector:
matchLabels:
istio-injection: enabled
回调API入口代码在 pkg/kube/inject/webhook.go
中
// 创建一个用于自动注入sidecar的新实例
func NewWebhook(p WebhookParameters) (*Webhook, error) {
// ...省略一万字...
wh := &Webhook{
Config: sidecarConfig,
sidecarTemplateVersion: sidecarTemplateVersionHash(sidecarConfig.Template),
meshConfig: p.Env.Mesh(),
configFile: p.ConfigFile,
valuesFile: p.ValuesFile,
valuesConfig: valuesConfig,
watcher: watcher,
healthCheckInterval: p.HealthCheckInterval,
healthCheckFile: p.HealthCheckFile,
env: p.Env,
revision: p.Revision,
}
//api server 回调函数,监听/inject回调
p.Mux.HandleFunc("/inject", wh.serveInject)
p.Mux.HandleFunc("/inject/", wh.serveInject)
// ...省略一万字...
return wh, nil
}
serveInject
逻辑
func (wh *Webhook) serveInject(w http.ResponseWriter, r *http.Request) {
// ...省略一万字...
var reviewResponse *v1beta1.AdmissionResponse
ar := v1beta1.AdmissionReview{}
if _, _, err := deserializer.Decode(body, nil, &ar); err != nil {
handleError(fmt.Sprintf("Could not decode body: %v", err))
reviewResponse = toAdmissionResponse(err)
} else {
//执行具体的inject逻辑
reviewResponse = wh.inject(&ar, path)
}
// 响应inject sidecar后的内容给k8s api server
response := v1beta1.AdmissionReview{}
if reviewResponse != nil {
response.Response = reviewResponse
if ar.Request != nil {
response.Response.UID = ar.Request.UID
}
}
// ...省略一万字...
}
// 注入逻辑实现
func (wh *Webhook) inject(ar *v1beta1.AdmissionReview, path string) *v1beta1.AdmissionResponse {
// ...省略一万字...
// injectRequired判断是否有设置自动注入
if !injectRequired(ignoredNamespaces, wh.Config, &pod.Spec, &pod.ObjectMeta) {
log.Infof("Skipping %s/%s due to policy check", pod.ObjectMeta.Namespace, podName)
totalSkippedInjections.Increment()
return &v1beta1.AdmissionResponse{
Allowed: true,
}
}
// ...省略一万字...
// 返回需要注入Pod的对象
spec, iStatus, err := InjectionData(wh.Config.Template, wh.valuesConfig, wh.sidecarTemplateVersion, typeMetadata, deployMeta, &pod.Spec, &pod.ObjectMeta, wh.meshConfig, path) // nolint: lll
if err != nil {
handleError(fmt.Sprintf("Injection data: err=%v spec=%vn", err, iStatus))
return toAdmissionResponse(err)
}
// 执行容器注入逻辑
patchBytes, err := createPatch(&pod, injectionStatus(&pod), wh.revision, annotations, spec, deployMeta.Name, wh.meshConfig)
if err != nil {
handleError(fmt.Sprintf("AdmissionResponse: err=%v spec=%vn", err, spec))
return toAdmissionResponse(err)
}
reviewResponse := v1beta1.AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: func() *v1beta1.PatchType {
pt := v1beta1.PatchTypeJSONPatch
return &pt
}(),
}
return &reviewResponse
}
injectRequired
函数
func injectRequired(ignored []string, config *Config, podSpec *corev1.PodSpec, metadata *metav1.ObjectMeta) bool {
// HostNetwork模式直接跳过注入
if podSpec.HostNetwork {
return false
}
// k8s系统命名空间(kube-system/kube-public)跳过注入
for _, namespace := range ignored {
if metadata.Namespace == namespace {
return false
}
}
annos := metadata.GetAnnotations()
if annos == nil {
annos = map[string]string{}
}
var useDefault bool
var inject bool
// 优先判断是否申明了`sidecar.istio.io/inject` 注解,会覆盖命名配置
switch strings.ToLower(annos[annotation.SidecarInject.Name]) {
case "y", "yes", "true", "on":
inject = true
case "":
// 使用命名空间配置
useDefault = true
}
// 指定Pod不需要注入Sidecar的标签选择器
if useDefault {
for _, neverSelector := range config.NeverInjectSelector {
selector, err := metav1.LabelSelectorAsSelector(&neverSelector)
if err != nil {
} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels))
// 设置不需要注入
inject = false
useDefault = false
break
}
}
}
// 总是将 sidecar 注入匹配标签选择器的 pod 中,而忽略全局策略
if useDefault {
for _, alwaysSelector := range config.AlwaysInjectSelector {
selector, err := metav1.LabelSelectorAsSelector(&alwaysSelector)
if err != nil {
log.Warnf("Invalid selector for AlwaysInjectSelector: %v (%v)", alwaysSelector, err)
} else if !selector.Empty() && selector.Matches(labels.Set(metadata.Labels)){ // 设置需要注入
inject = true
useDefault = false
break
}
}
}
// 如果都没有配置则使用默认注入策略
var required bool
switch config.Policy {
default: // InjectionPolicyOff
log.Errorf("Illegal value for autoInject:%s, must be one of [%s,%s]. Auto injection disabled!",
config.Policy, InjectionPolicyDisabled, InjectionPolicyEnabled)
required = false
case InjectionPolicyDisabled:
if useDefault {
required = false
} else {
required = inject
}
case InjectionPolicyEnabled:
if useDefault {
required = true
} else {
required = inject
}
}
return required
}
从上面我们可以看出,是否注入Sidecar的优先级为
Pod Annotations → NeverInjectSelector → AlwaysInjectSelector → Default Policy
createPath
函数
func createPatch(pod *corev1.Pod, prevStatus *SidecarInjectionStatus, revision string, annotations map[string]string,
sic *SidecarInjectionSpec, workloadName string, mesh *meshconfig.MeshConfig) ([]byte, error) {
var patch []rfc6902PatchOperation
// ...省略一万字...
// 注入初始化启动容器
patch = append(patch, addContainer(pod.Spec.InitContainers, sic.InitContainers, "/spec/initContainers")...)
// 注入Sidecar容器
patch = append(patch, addContainer(pod.Spec.Containers, sic.Containers, "/spec/containers")...)
// 注入挂载卷
patch = append(patch, addVolume(pod.Spec.Volumes, sic.Volumes, "/spec/volumes")...)
patch = append(patch, addImagePullSecrets(pod.Spec.ImagePullSecrets, sic.ImagePullSecrets, "/spec/imagePullSecrets")...)
// 注入新注解
patch = append(patch, updateAnnotation(pod.Annotations, annotations)...)
// ...省略一万字...
return json.Marshal(patch)
}
总结:可以看到,整个注入过程实际就是原本的Pod配置反解析成Pod对象,把需要注入的Yaml内容(如:Sidecar)反序列成对象然后append到对应Pod (如:Container)上,然后再把修改后的Pod重新解析成yaml 内容返回给k8s的api server,然后k8s 拿着修改后内容再将这两个容器调度到同一台机器进行部署,至此就完成了对应Sidecar的注入。
卸载 sidecar 自动注入器
kubectl delete mutatingwebhookconfiguration istio-sidecar-injector
kubectl -n istio-system delete service istio-sidecar-injector
kubectl -n istio-system delete deployment istio-sidecar-injector
kubectl -n istio-system delete serviceaccount istio-sidecar-injector-service-account
kubectl delete clusterrole istio-sidecar-injector-istio-system
kubectl delete clusterrolebinding istio-sidecar-injector-admin-role-binding-istio-system
上面的命令不会从 pod 中移除注入的 sidecar。需要进行滚动更新或者直接删除对应的pod,并强制 deployment 重新创建新pod。
手动注入 sidecar
手动注入 deployment ,需要使用 使用 istioctl kube-inject
使用手动注入前先关闭自动注入
kubectl label namespace default istio-injection=disabled
使用istioctl手动注入
istioctl kube-inject -f samples/sleep/sleep.yaml | kubectl apply -f -
我们可以查看对应的deployment
明细
describe deployment sleep
Name: sleep
Namespace: default
CreationTimestamp: Wed, 27 May 2020 10:45:23 +0800
Annotations: deployment.kubernetes.io/revision: 1
Selector: app=sleep
Pod Template:
Labels: app=sleep
istio.io/rev=
security.istio.io/tlsMode=istio
Annotations: sidecar.istio.io/interceptionMode: REDIRECT
sidecar.istio.io/status:
{"version":"d36ff46d2def0caba37f639f09514b17c4e80078f749a46aae84439790d2b560","initContainers":["istio-init"],"containers":["istio-proxy"]...
traffic.sidecar.istio.io/excludeInboundPorts: 15020
traffic.sidecar.istio.io/includeOutboundIPRanges: *
Service Account: sleep
Init Containers:
istio-init:
Image: docker.io/istio/proxyv2:1.6.0
Port: <none>
Host Port: <none>
Args:
istio-iptables
-p
15001
-z
15006
-u
1337
-m
REDIRECT
-i
*
-x
-b
*
-d
15090,15021,15020
Containers:
sleep:
Image: governmentpaas/curl-ssl
Port: <none>
Host Port: <none>
Command:
/bin/sleep
3650d
Environment: <none>
Mounts:
/etc/sleep/tls from secret-volume (rw)
istio-proxy:
Image: docker.io/istio/proxyv2:1.6.0
Port: 15090/TCP
Host Port: 0/TCP
Args:
proxy
sidecar
--domain
$(POD_NAMESPACE).svc.cluster.local
--serviceCluster
sleep.$(POD_NAMESPACE)
--proxyLogLevel=warning
--proxyComponentLogLevel=misc:error
--trust-domain=cluster.local
--concurrency
2
可以看到,相比原始的deployment.yaml文件多出了两个容器,这两个容器的作用后面单独写一篇文章来分析:
Init Containers
下的istio-init
Containers
下的istio-proxy
上面两个容器的注入,默认情况下将使用集群内的配置,或者使用该配置的本地副本来完成注入。
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
指定输入文件,运行 kube-inject
并部署
istioctl kube-inject \
--injectConfigFile inject-config.yaml \
--meshConfigFile mesh-config.yaml \
--valuesFile inject-values.yaml \
--filename samples/sleep/sleep.yaml \
| kubectl apply -f -
验证 sidecar 已经被注入到 READY 列下 2/2
的 sleep pod 中
kubectl get pod -l app=sleep
NAME READY STATUS RESTARTS AGE
sleep-64c6f57bc8-f5n4x 2/2 Running 0 24s
查看对应pod中的容器
kubectl get pods -o jsonpath="{.items[*].spec.containers[*].image}" | tr -s '[[:space:]]' '\n' | sort
docker.io/istio/proxyv2:1.6.0
governmentpaas/curl-ssl
手动注入的代码入口在 istioctl/cmd/kubeinject.go
手工注入跟自动注入还是有些差异的。手动注入是改变了Deployment
。我们可以看下它具体做了哪些动作:
Deployment
注入前配置:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
serviceAccountName: sleep
containers:
- name: sleep
image: governmentpaas/curl-ssl
command: ["/bin/sleep", "3650d"]
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /etc/sleep/tls
name: secret-volume
volumes:
- name: secret-volume
secret:
secretName: sleep-secret
optional: true
Deployment
注入后配置:
kubectl get deployment sleep -o yaml > sleep.yaml
less sleep.yaml
这里只保留跟注入容器有关的部分内容
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
generation: 1
name: sleep
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
annotations:
sidecar.istio.io/interceptionMode: REDIRECT
labels:
app: sleep
istio.io/rev: ""
security.istio.io/tlsMode: istio
spec:
containers:
- command:
- /bin/sleep
- 3650d
image: governmentpaas/curl-ssl
imagePullPolicy: IfNotPresent
name: sleep
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/sleep/tls
name: secret-volume
- args:
- proxy
- sidecar
- --domain
- $(POD_NAMESPACE).svc.cluster.local
- --serviceCluster
- sleep.$(POD_NAMESPACE)
- --proxyLogLevel=warning
- --proxyComponentLogLevel=misc:error
- --trust-domain=cluster.local
- --concurrency
- "2"
image: docker.io/istio/proxyv2:1.6.0
imagePullPolicy: Always
name: istio-proxy
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
dnsPolicy: ClusterFirst
initContainers:
- args:
- istio-iptables
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- 15090,15021,15020
env:
- name: DNS_AGENT
image: docker.io/istio/proxyv2:1.6.0
imagePullPolicy: Always
name: istio-init
restartPolicy: Always
可见新增了一个容器镜像
image: docker.io/istio/proxyv2:1.6.0
那么注入的内容模板从哪里获取,这里有两个选项。
- —injectConfigFile 指定对应的注入文件
- —injectConfigMapName 注入配置的 ConfigMap 名称
如果在操作时发现Sidecar没有注入成功可以根据注入的方式查看上面的注入流程来查找问题。
参考文献
https://kubernetes.io/zh/docs/reference/access-authn-authz/admission-controllers/
https://istio.io/zh/docs/reference/commands/istioctl/#istioctl-kube-inject
Istio Sidecar注入原理的更多相关文章
- istio sidecar流量处理机制及配置
sidecar 介绍 在istio的流量管理等功能,都需要通过下发的配置应用到应用运行环境执行后生效,负责执行配置规则的组件在service mesh中承载应用代理的实体被称为side-car Ist ...
- istio sidecar自动注入过程分析
目录 istio sidecar自动注入过程分析 sidecar自动注入检查 检查kube-apiserver 检查sidecar-injector的configmap 检查namespace标签 s ...
- istio部署-sidecar注入
参考 fleeto/sleep fleeto/flaskapp 1. Sidecar注入 1.1 对工作负载的一些要求 支持的工作负载类型:Job,DaemonSet,ReplicaSet,Pod,D ...
- 注入 Istio sidecar
注入 Istio sidecar 网格中的每个 Pod 都必须伴随一个 Istio 兼容的 Sidecar 一同运行. 下文中将会介绍两种把 Sidecar 注入到 Pod 中的方法:使用 istio ...
- Istio Sidecar
概念及示例 Sidecar描述了sidecar代理的配置.默认情况下,Istio 让每个 Envoy 代理都可以访问来自和它关联的工作负载的所有端口的请求,然后转发到对应的工作负载.您可以使用 sid ...
- istio sidecar使用自定义镜像源
Istio 和 sidecar 配置保存在 istio 和 istio-sidecar-injector 这两个 ConfigMap 中,其中包含了 Go template,所谓自动 sidecar ...
- 十种MYSQL显错注入原理讲解(二)
上一篇讲过,三种MYSQL显错注入原理.下面我继续讲解. 1.geometrycollection() and geometrycollection((select * from(select * f ...
- Java程序员从笨鸟到菜鸟之(一百)sql注入攻击详解(一)sql注入原理详解
前段时间,在很多博客和微博中暴漏出了12306铁道部网站的一些漏洞,作为这么大的一个项目,要说有漏洞也不是没可能,但其漏洞确是一些菜鸟级程序员才会犯的错误.其实sql注入漏洞就是一个.作为一个菜鸟小程 ...
- SQL注入原理
随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多.但是由于这个行业的入门门槛不高,程序员的水平及经验也参差不齐,相当大一 部分程序员在编写代码的时候,没有对用户输入数据的合法性 ...
随机推荐
- Tomcat 8 Host-Manager配置访问的方法,全网唯一正确配置
2019独角兽企业重金招聘Python工程师标准>>> 环境: 操作系统: Linux version 2.6.32-696.10.1.el6.x86_64 (moc ...
- WordPress发布文章/页面时自动添加默认的自定义字段
如果你每篇文章或页面都需要插入同一个自定义字段和值,可以考虑在WordPress发布文章/页面时,自动添加默认的自定义字段.将下面的代码添加到当前主题的 functions.php 即可: 1 2 3 ...
- hdu2544最短路
在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线,你可以帮助 ...
- Web 跨域请求问题的解决方案- CORS 方案
1.什么是跨域 跨域是指跨域名的访问,以下情况都属于跨域: 跨域现象 实例 域名不相同 www.baidu.com与www.taobao 一级域名相同,但是端口不相同 www.baidu.com:80 ...
- spring学习笔记(七)HttpMessageConverter
spring学习笔记(七)HttpMessageConverter 1. HttpMessageConverter的加载 2. 从StringMessageConverter探究消息转换器的原理 1. ...
- PHP导出excel文件,第二步先实现自写二维数组加入模板excel文件后导出
今天主要研究数据加入EXCEL并导出的问题,先不从数据库提取数据导出,自己先写一个二维数组,然后遍历二维数组写入excel模板中导出,首先根据模板excel的内容书写对应的二维数组 $arr=arra ...
- python重试次数装饰器
目录 重试次数装饰器 重试次数装饰器 前言, 最近在使用tornado框架写Restful API时遇到很多的问题. 有框架的问题, 有异步的问题. 虽然tornado 被公认为当前python语言最 ...
- Algorithms - Priority Queue - 优先队列
Priority queue - 优先队列 相关概念 Priority queue优先队列是一种用来维护由一组元素构成的集合S的数据结构, 其中的每一种元素都有一个相关的值,称为关键字(key). 一 ...
- 白话马尔科夫链蒙特卡罗方法(MCMC)
前言 你清茶园不是人待的地方! 里面的个个都是人才,说话又好听--就是我太菜了啥也听不懂,这次期中还考的贼**烂,太让人郁闷了. 最近课上讲这个马尔科夫链蒙特卡罗方法,我也学得一塌糊涂.这时我猛然想起 ...
- Linux 面试最常问的十个问题
如果你要去面试一个Linux系统运维工程师的职位,下面这十个最常见的问题一定要会,否则你的面试可能就危险了.这些都是比较基本的问题,大家要理解,不能光死记硬背. 1.如何查看系统内核的版本 这里有两种 ...