一、背景
我們都知道集群中安裝了istio后,只需要給 namespace 打上istio-injection=enabled?這個(gè)標(biāo)簽,之后這個(gè)namespace下的所有pod都會(huì)注入邊車容器istio-proxy(存量pod需要重啟才能生效,新下發(fā)的pod會(huì)直接注入sidecar容器)。那么1)sidecar 是如何自動(dòng)注入的?2)我們都知道istio-proxy可以攔截流量,具體是如何攔截流量的?3)注入了sidecar之后,不想攔截特定流量如何處理?
二、sidecar容器如何注入
在創(chuàng)建Pod的請(qǐng)求到達(dá)Kube-apiserver后,首先進(jìn)行認(rèn)證鑒權(quán),然后在準(zhǔn)入控制階段 kube-apiserver以REST的方式同步調(diào)用sidecar-injector webhook服務(wù)進(jìn)行init容器與istio-proxy容器的注入,最后將Pod對(duì)象持久化存儲(chǔ)到Etcd中。對(duì)應(yīng)MutatingWebhookConfiguration配置如下:
apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: name: istio-revision-tag-default webhooks: ...... - admissionReviewVersions: - v1beta1 - v1 clientConfig: caBundle: xxx 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 rules: - apiGroups: - "" apiVersions: - v1 operations: - CREATE resources: - pods scope: '*' ... ...
由配置可知,webhook服務(wù)由istiod提供,在istio 1.5 版本之后sidecar-injector 被編譯到istiod進(jìn)程中。sidecar-injector 對(duì)標(biāo)簽匹配 istio-injection : enabled 的命名空間的Pod資源對(duì)象的創(chuàng)建生效。
業(yè)務(wù)pod被自動(dòng)注入istio-init container和 istio-proxy container
# initContainer 配置 ,用于初始化pod網(wǎng)絡(luò),負(fù)責(zé)對(duì)Pod配置定制的iptables規(guī)則,所以也需要被賦予NET_ADMIN權(quán)限 initContainers: - name: istio-init image: docker.io/istio/proxyv2:1.19.0 args: - istio-iptables - '-p' - '15001' - '-z' - '15006' - '-u' - '1337' - '-m' - REDIRECT - '-i' - '*' - '-x' - '' - '-b' - '*' - '-d' - 15090,15021,15020 - '--log_output_level=default:info' securityContext: capabilities: add: - NET_ADMIN - NET_RAW drop: - ALL ...... # istio-proxy container配置, containers: - name: istio-proxy image: docker.io/istio/proxyv2:1.19.0 args: - proxy - sidecar - '--domain' - $(POD_NAMESPACE).svc.cluster.local - '--proxyLogLevel=warning' - '--proxyComponentLogLevel=misc:error' - '--log_output_level=default:info' ports: - name: http-envoy-prom containerPort: 15090 protocol: TCP env: ...... volumeMounts: # istio-proxy 容器掛載的證書及配置文件 - name: workload-socket mountPath: /var/run/secrets/workload-spiffe-uds - name: credential-socket mountPath: /var/run/secrets/credential-uds - name: workload-certs mountPath: /var/run/secrets/workload-spiffe-credentials - name: istiod-ca-cert mountPath: /var/run/secrets/istio - name: istio-data mountPath: /var/lib/istio/data - name: istio-envoy#Envoy的啟動(dòng)配置文件 envoy-rev0.json mountPath: /etc/istio/proxy - name: istio-token# Envoy 訪問(wèn)istiod用的token mountPath: /var/run/secrets/tokens - name: istio-podinfo# 以文件形式保存 Pod自身服務(wù)的信息,包含annotations和labels文件,這兩個(gè)文件將被pilot-agent讀取 mountPath: /etc/istio/pod - name: kube-api-access-xsnpl readOnly: true mountPath: /var/run/secrets/kubernetes.io/serviceaccount securityContext: ...... runAsUser: 1337 # Sidecar運(yùn)行用戶 runAsGroup: 1337
三、istio攔截原理
在完成Sidecar自動(dòng)注入后,業(yè)務(wù)在Pod運(yùn)行期間收發(fā)的網(wǎng)絡(luò)流量將被透明的攔截進(jìn)Sidecar。其流量攔截基于iptables規(guī)則,攔截應(yīng)用容器的Inbound流量或Outbound流量。主要分為兩大部分:
istio-init容器用于設(shè)置pod中的iptables轉(zhuǎn)發(fā)規(guī)則,將流量先導(dǎo)入給istio-proxy(envoy)
Sidecar容器 istio-proxy攔截流量
3.1 istio-init容器分析
istio-init容器會(huì)在pod網(wǎng)絡(luò)協(xié)議棧完成iptables規(guī)則配置操作后退出,該容器的啟動(dòng)命令(istio-iptables命令封裝了一些iptables規(guī)則)
istio-iptables -p 15001 -z 15006 -u 1337 -m REDIRECT -i '*' -x "" -b '*' -d 15090,15021,15020
讓sidecar代理可以攔截所有進(jìn)出Pod的流量,除了15090,15021,15020端口的所有入站流量都被重定向到15006端口(sidecar),還可以攔截應(yīng)用容器的出流量,這些流量經(jīng)過(guò)sidcar(通過(guò)15001端口監(jiān)聽)處理后才能出站。
-z 15006 表示將進(jìn)入應(yīng)用容器的所有流量都轉(zhuǎn)發(fā)到sidecar的15006端口
-u 1337?指定不應(yīng)用重定向的uid,默認(rèn)是1337,即使用istio-proxy用戶身份運(yùn)行
-m REDIRECT 表示使用REDIRECT模式重定向流量
-p 15001 將所有出戰(zhàn)流量都重定向到sidecar的15001端口
-d 15090,15021,15020 表示排除該三個(gè)端口,所有的入流量都會(huì)被重定向處理。15020和15090都是遙測(cè)暴露指標(biāo)的端口,15021監(jiān)控檢查的端口
-i "*" 表示重定向所有的出站流量
-x "" 表示排除指定的網(wǎng)端ip地址,對(duì)出站流量不進(jìn)行重定向處理。為空 沒(méi)有需要排除的ip
-b "*" 表示重定向所有入站流量到Envoy
3.2 sidecar攔截規(guī)則分析
sidecar與用戶進(jìn)程共享同一個(gè)網(wǎng)絡(luò)命名空間,工作在相同的網(wǎng)絡(luò)協(xié)議棧上。sidecar 對(duì)協(xié)議棧iptables規(guī)則的配置,將影響用戶應(yīng)用程序報(bào)文的流向,可以透明的攔截用戶報(bào)文,并進(jìn)行七層處理。進(jìn)入被注入sidecar的容器網(wǎng)絡(luò)命名空間(可以用nsenter命令從宿主機(jī)進(jìn)入,或者直接進(jìn)入有iptables模塊的容器),查看對(duì)應(yīng)iptables規(guī)則
# 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 15008 -j RETURN -A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN -A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN -A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006 -A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN -A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -p tcp -m tcp ! --dport 15008 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT -A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN -A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -p tcp -m tcp ! --dport 15008 -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT -A ISTIO_OUTPUT -o lo -m owner ! --gid-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
-P PREROUTING ACCEPT : 接受進(jìn)入PREROUTING鏈的報(bào)文,其他鏈同理
-N ISTIO_INBOUND : 聲明一個(gè)自定義的鏈 ISTIO_INBOUND
-A PREROUTING -p tcp -j ISTIO_INBOUND : 將進(jìn)入PREROUTING鏈的TCP流量跳轉(zhuǎn)到ISTIO_INBOUND鏈 做進(jìn)一步處理
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN : 對(duì)進(jìn)入ISTIO_INBOUND鏈的目標(biāo)端口為15008的TCP流量不做特殊處理,直接讓其通過(guò)
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006 : 對(duì)進(jìn)入ISTIO_IN_REDIRECT鏈的TCP流量進(jìn)行報(bào)文修改,REDIRECT 對(duì)應(yīng)DNAT修改方式,修改目標(biāo)端口為15006
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN: 對(duì)進(jìn)入ISTIO_OUTPUT鏈的源地址為127.0.0.6的報(bào)文且目標(biāo)網(wǎng)絡(luò)設(shè)備為lo本地設(shè)備的流量,不進(jìn)行特殊處理
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -p tcp -m tcp ! --dport 15008 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT:
對(duì)進(jìn)入ISTIO_OUTPUT鏈且目標(biāo)地址雖然不然127.0.0.1,但判斷目標(biāo)網(wǎng)絡(luò)設(shè)備為本地,即pod自身地址的報(bào)文,若報(bào)文發(fā)送進(jìn)程為uid=1337,則Envoy轉(zhuǎn)到ISTIO_IN_REDIRECT鏈繼續(xù)處理
3.2.1 Inbound場(chǎng)景流量規(guī)則詳細(xì)分析
Inbound流量指從Pod外進(jìn)入Pod內(nèi)的流量。比如客戶端訪問(wèn)服務(wù)端,應(yīng)用數(shù)據(jù)報(bào)文在進(jìn)入服務(wù)端時(shí)就會(huì)被攔截,從而進(jìn)入envoy 15006監(jiān)聽端口來(lái)處理,同時(shí)例如15008 mtls隧道端口,15090,15020遙測(cè)監(jiān)控端口,15021健康檢查端口,均不會(huì)被攔截
此時(shí)服務(wù)端POD接受流量,流量首先會(huì)經(jīng)過(guò)prerouting鏈處理。然后命中iptables規(guī)則: -A PREROUTING -p tcp -j ISTIO_INBOUND ,將進(jìn)入PREROUTING鏈的TCP流量跳轉(zhuǎn)到ISTIO_INBOUND鏈 做進(jìn)一步處理。接著命中iptables規(guī)則: -A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT,如果目標(biāo)端口不是15008,15090,15020,15021,則將流量跳轉(zhuǎn)到ISTIO_IN_REDIRECT鏈進(jìn)行處理。最后命中iptables規(guī)則: -A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006,報(bào)文被攔截并通過(guò)DNAT方式修改目標(biāo)地址,將訪問(wèn)端口置為15006。
此時(shí)流量被攔截至Envoy容器進(jìn)行處理,Envoy容器將流量發(fā)送給業(yè)務(wù)容器進(jìn)行處理。命中規(guī)則: -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN ,表示Envoy發(fā)送給Pod后端backend容器的流量不再被攔截。
業(yè)務(wù)容器接受到報(bào)文后,會(huì)進(jìn)行回包。
存在一些PassthroughCluster場(chǎng)景,例如直接訪問(wèn)podIP,此時(shí)Inbound請(qǐng)求根據(jù)目標(biāo)地址沒(méi)有找到后端服務(wù)時(shí),下游請(qǐng)求將被轉(zhuǎn)發(fā)到Envoy內(nèi)置的PassthroughCluster服務(wù)中,最后按照原始目標(biāo)地址轉(zhuǎn)發(fā)下游請(qǐng)求。
Envoy發(fā)送報(bào)文,命中iptables規(guī)則:?-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN。處理Passthroughcluster流量時(shí),源ip會(huì)被置為127.0.0.6。此時(shí)在output鏈處理階段,被轉(zhuǎn)發(fā)的請(qǐng)求會(huì)被放行。同時(shí)因?yàn)槟繕?biāo)網(wǎng)絡(luò)涉別為lo,可以訪問(wèn)到本pod內(nèi)未注冊(cè)的后端服務(wù)。
3.2.1 Outbound場(chǎng)景流量規(guī)則詳細(xì)分析
上圖模擬了兩個(gè)服務(wù)之間的通信,主要是描述東西向流量客戶端訪問(wèn)服務(wù)端其中outbound流量的過(guò)程。(istio中很多流量治理的功能都是在outbound過(guò)程生效)
客戶端frontend通過(guò)service訪問(wèn)服務(wù)端(svcName:port)。四元組信息:?srcIP: ip1 srcPort: port1 ==> dst:backend ,dscPort:port2。服務(wù)端域名經(jīng)過(guò)DNS解析后,得到clusterIP,然后封裝發(fā)送SYN報(bào)文,隨后報(bào)文被pod內(nèi)的iptables規(guī)則攔截。首先命中該規(guī)則: -A OUTPUT -p tcp -j ISTIO_OUTPUT,將進(jìn)入output鏈的tcp流量轉(zhuǎn)發(fā)到istio_output鏈做進(jìn)一步處理;隨后命中該規(guī)則: -A ISTIO_OUTPUT -j ISTIO_REDIRECT , 將所有出站流量(非本地的)轉(zhuǎn)發(fā)給istio_redirect鏈來(lái)處理;最后命中該規(guī)則: -A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001 ,對(duì)進(jìn)入ISTIO_REDIRECT鏈的TCP流量進(jìn)行報(bào)文修改,目標(biāo)端口重定向到15001。
客戶端Envoy接受到報(bào)文,開始針對(duì)流量進(jìn)行治理。在Envoy中會(huì)根據(jù)配下發(fā)的配置還原目標(biāo)服務(wù)的ClusterIp和port。隨后根據(jù)Envoy內(nèi)配置的負(fù)載均衡策略選擇一個(gè)后端實(shí)例IP作為目的IP,并創(chuàng)建連接
客戶端Envoy準(zhǔn)備發(fā)送報(bào)文。istio-proxy的運(yùn)行用戶和用戶組均為1337。四元組信息: srcIP:ip1, srcPort:隨機(jī)端口(port3) ==> dstIP:ip2,dstPort:port2。命中規(guī)則: -A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN ,對(duì)進(jìn)入istio_output鏈,報(bào)文發(fā)送用戶為1337的流量 放行通過(guò)。
服務(wù)端backend的Envoy接受流量轉(zhuǎn)發(fā)給backend,服務(wù)端backend已接受到報(bào)文,開始回包給客戶端。四元組信息:?scrIP:ip2, srcPort:port2 ==> dstIP:ip1,dstPort:port3
服務(wù)端Envoy發(fā)送流量。此時(shí)envoy會(huì)將報(bào)文轉(zhuǎn)發(fā)給業(yè)務(wù)容器。四元組信息:?scrIP: 127.0.0.1, srcPort:15001 ==> dstIP:ip1,dstPort:port1
3.3 如何放行部分流量不被envoy攔截
在實(shí)際的使用過(guò)程中,如果業(yè)務(wù)容器訪問(wèn)某些應(yīng)用不希望流量被攔截,該怎么才能做到呢?istio中根據(jù)流量攔截的原理是iptables規(guī)則的配置。我們可以給應(yīng)用加上對(duì)應(yīng)的規(guī)則即可,例如出站流量對(duì)端口8080不做攔截,出站流量對(duì)172.16.2.0/24目標(biāo)網(wǎng)段不做攔截。
就需要配置以下規(guī)則: 因?yàn)樗械某稣景紩?huì)轉(zhuǎn)發(fā)到ISTIO_OUTPUT鏈上,所以基于該鏈配置放行規(guī)則
-A ISTIO_OUTPUT -p tcp -m tcp --dport 8080 -j RETURN -A ISTIO_OUTPUT -d 172.16.2.0/24 -j RETURN
istio提供了基于podAnnotation配置的方式控制攔截行為:?https://istio.io/latest/docs/reference/config/annotations/? 為對(duì)應(yīng)工作負(fù)載添加
traffic.sidecar.istio.io/excludeOutboundPorts : 出站流量放行的端口 traffic.sidecar.istio.io/excludeOutboundIPRanges : 出站流量放行的ip地址
登錄pod中查看iptables規(guī)則: iptables -t nat -L -nv ,可以發(fā)現(xiàn)在ISTIO_OUTPUT 新增了兩條放行規(guī)則
審核編輯:黃飛
評(píng)論