Istio的流量管理(實操三)
涵蓋官方文檔Traffic Management章節中的egress部分。
訪問外部服務
由於啟用了istio的pod的出站流量默認都會被重定向到代理上,因此對集群外部URL的訪問取決於代理的配置。默認情況下,Envoy代理會透傳對未知服務的訪問,雖然這種方式為新手提供了便利,但最好配置更嚴格的訪問控制。
本節展示使用如下三種方式訪問外部服務:
- 允許Envoy代理透傳到網格外部的服務
- 配置service entries來訪問外部訪問
- 透傳某一個IP端的請求
部署
-
部署sleep app,用於發送請求。
$ kubectl apply -f samples/sleep/sleep.yaml
-
設置
SOURCE_POD
為請求源pod名$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
Envoy透傳流量到外部服務
istio有一個安裝選項,meshConfig.outboundTrafficPolicy.mode
,用於配置sidecar處理外部服務(即沒有定義到istio內部服務注冊中心的服務)。如果該選項設置為ALLOW_ANY
,則istio代理會放行到未知服務的請求;如果選項設置為REGISTRY_ONLY
,則istio代理會阻塞沒有在網格中定義HTTP服務或服務表項的主機。默認值為ALLOW_ANY
,允許快速對istio進行評估。
-
首先將
meshConfig.outboundTrafficPolicy.mode
選項設置為ALLOW_ANY
。默認應該就是ALLOW_ANY
,使用如下方式獲取當前的模式:$kubectl get configmap istio -n istio-system -o yaml |grep -o "mode: ALLOW_ANY" |uniq mode: ALLOW_ANY
如果沒有配置模式,可以手動添加:
outboundTrafficPolicy: mode: ALLOW_ANY
-
從網格內向外部服務發送兩個請求,可以看到請求成功,返回200
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -I https://www.baidu.com | grep "HTTP/"; kubectl exec -it $SOURCE_POD -c sleep -- curl -I https://edition.cnn.com | grep "HTTP/" HTTP/1.1 200 OK HTTP/2 200
使用這種方式可以訪問外部服務,但無法對該流量進行監控和控制,下面介紹如何監控和控制網格到外部服務的流量。
控制訪問外部服務
使用ServiceEntry配置可以從istio集群內部訪問公共服務。本節展示如何配置訪問外部HTTP服務,httpbin.org以及www.baidu.com,同時會監控和控制istio流量。
修改默認的阻塞策略
為了展示如何控制訪問外部服務的方式,需要將meshConfig.outboundTrafficPolicy.mode
設置為REGISTRY_ONLY
-
執行如下命令將
meshConfig.outboundTrafficPolicy.mode
選項設置為REGISTRY_ONLY
$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f -
-
從SOURCE_POD訪問外部HTTPS服務,此時請求會被阻塞(可能需要等一段時間來使配置生效)
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -I https://www.baidu.com | grep "HTTP/"; kubectl exec -it $SOURCE_POD -c sleep -- curl -I https://edition.cnn.com | grep "HTTP/" command terminated with exit code 35 command terminated with exit code 35
訪問外部HTTP服務
-
創建一個
ServiceEntry
注冊外部服務,這樣就可以直接訪問外部HTTP服務,可以看到此處並沒有用到virtual service和destination rule下面serviceEntry使用
DNS
作為resolution是一種比較安全的方式,將resolution設置為NONE
將可能導致攻擊。例如,惡意客戶可能會再HOST首部中設置httpbin.org
,但實際上訪問的不同的IP地址。istio sidecar代理會信任HOST首部,並錯誤地允許此次訪問(即使會將流量傳遞到不同於主機的IP地址),該主機可能是一個惡意網站,或是一個被網格安全策略屏蔽的合法網站。使用
DNS
resolution時,sidecar代理會忽略原始目的地址,並將流量傳遞給hosts
字段的主機。在轉發流量前會使用DNS請求hosts
字段的IP地址。serviceEntry包括如下三種resolution:
Name Description NONE
Assume that incoming connections have already been resolved (to a specific destination IP address). Such connections are typically routed via the proxy using mechanisms such as IP table REDIRECT/ eBPF. After performing any routing related transformations, the proxy will forward the connection to the IP address to which the connection was bound. STATIC
Use the static IP addresses specified in endpoints (see below) as the backing instances associated with the service. DNS
Attempt to resolve the IP address by querying the ambient DNS, during request processing. If no endpoints are specified, the proxy will resolve the DNS address specified in the hosts field, if wildcards are not used. If endpoints are specified, the DNS addresses specified in the endpoints will be resolved to determine the destination IP address. DNS resolution cannot be used with Unix domain socket endpoints. $ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: httpbin-ext spec: hosts: - httpbin.org #外部服務URI ports: - number: 80 #外部服務HTTP端口信息 name: http protocol: HTTP resolution: DNS location: MESH_EXTERNAL # 表示一個外部服務,即httpbin.org是網格外部的服務 EOF
-
從SOURCE_POD請求外部HTTP服務
$ kubectl exec -it $SOURCE_POD -c sleep -- curl http://httpbin.org/headers { "headers": { "Accept": "*/*", "Content-Length": "0", "Host": "httpbin.org", "User-Agent": "curl/7.64.0", ... "X-Envoy-Decorator-Operation": "httpbin.org:80/*", } }
注意HTTP添加了istio sidecar代理首部
X-Envoy-Decorator-Operation
。 -
校驗
SOURCE_POD
sidecar代理的日志$ kubectl logs $SOURCE_POD -c istio-proxy | tail
訪問外部HTTPS服務
-
創建ServiceEntry允許訪問外部HTTPS服務
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: baidu spec: hosts: - www.baidu.com ports: - number: 443 # 外部服務HTTPS端口 name: https protocol: HTTPS #指定外部服務為HTTPS協議 resolution: DNS location: MESH_EXTERNAL EOF
-
從
SOURCE_POD
訪問外部服務$ kubectl exec -it $SOURCE_POD -c sleep -- curl -I https://www.baidu.com | grep "HTTP/" HTTP/1.1 200 OK
管理到外部的流量
與管理集群內部的流量類似,istio 的路由規則也可以管理使用ServiceEntry
配置的外部服務。本例將會為httpbin.org
服務設置一個超時規則.
-
從測試的pod向外部服務
httpbin.org
的/delay地址發送一個請求,大概5s后返回200$ kubectl exec -it $SOURCE_POD -c sleep -- time curl -o /dev/null -s -w "%{http_code}\n" http://httpbin.org/delay/5 200 real 0m 5.43s user 0m 0.00s sys 0m 0.00s
-
對外部服務
httpbin.org
設置一個3s的超時時間$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: httpbin-ext spec: hosts: - httpbin.org #此處的hosts與serviceEntry的hosts字段內容對應 http: - timeout: 3s route: - destination: host: httpbin.org weight: 100 EOF
-
幾秒后,重新訪問該服務,可以看到訪問超時
$ kubectl exec -it $SOURCE_POD -c sleep -- time curl -o /dev/null -s -w "%{http_code}\n" http://httpbin.org/delay/5 504 real 0m 3.02s user 0m 0.00s sys 0m 0.00s
卸載
$ kubectl delete serviceentry httpbin-ext google
$ kubectl delete virtualservice httpbin-ext --ignore-not-found=true
直接訪問外部服務
可以配置Envoy sidecar,使其不攔截特定IP段的請求。為了實現該功能,可以修改global.proxy.includeIPRanges
或global.proxy.excludeIPRanges
配置選項(類似白名單和黑名單),並使用kubectl apply
命令更新istio-sidecar-injector
配置。也可以修改annotations traffic.sidecar.istio.io/includeOutboundIPRanges
來達到相同的效果。在更新istio-sidecar-injector
配置后,相應的變動會影響到所有的應用pod。
與使用ALLOW_ANY流量策略配置sidecar放行所有到未知服務的流量不同,上述方式會繞過sidecar的處理,即在特定IP段上不啟用istio功能。使用這種方式不能增量地為特定目的地添加service entry,但使用
ALLOW_ANY
方式是可以的,因此這種方式僅僅建議用於性能測試或其他特殊場景中。
一種不把到外部IP的流量重定向到sidecar代理的方式是將global.proxy.includeIPRanges
設置為集群內部服務使用的一個IP段或多個IP段。
找到平台使用的內部IP段后,就可以使用如下方式配置includeIPRanges
,這樣目的地非10.0.0.1/24
的流量會繞過sidecar的處理。
$ istioctl manifest apply <the flags you used to install Istio> --set values.global.proxy.includeIPRanges="10.0.0.1/24"
總結
本節介紹了三種訪問外部服務的方式:
- 配置Envoy 允許訪問外部服務
- 在網格內部使用service entry注冊可訪問的外部服務,推薦使用這種方式
- 配置istio sidecar排除處理某些IP段的流量
第一種方式的流量會經過istio sidecar代理,當使用這種方式時,無法監控訪問外部服務的流量,無法使用istio的流量控制功能。第二種方法可以在調用集群內部或集群外部的服務時充分使用istio服務網格特性,本章的例子中,在訪問外部服務時設置了超時時間。第三種方式會繞過istio sidecar代理,直接訪問外部服務。然而這種方式需要指定集群的配置,與第一種方式類似,這種方式也無法監控到外部服務的流量,且無法使用istio的功能。
卸載
$ kubectl delete -f samples/sleep/sleep.yaml
環境恢復
檢查當前的模式
$ kubectl get configmap istio -n istio-system -o yaml | grep -o "mode: ALLOW_ANY" | uniq
$ kubectl get configmap istio -n istio-system -o yaml | grep -o "mode: REGISTRY_ONLY" | uniq
將模式從ALLOW_ANY
切換到REGISTRY_ONLY
$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f -
將模式從REGISTRY_ONLY
切換到ALLOW_ANY
$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: REGISTRY_ONLY/mode: ALLOW_ANY/g' | kubectl replace -n istio-system -f -
Egress TLS Origination(Egress TLS源)
本節展示如何通過配置istio來(對到外部服務的流量)初始化TLS。當原始流量為HTTP時,Istio會與外部服務建立HTTPS連接,即istio會加密到外部服務的請求。
創建sleep
應用
$ kubectl apply -f samples/sleep/sleep.yaml
獲取sleep
的pod名
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
創建ServiceEntry
和VirtualService
訪問 edition.cnn.com
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: edition-cnn-com
spec:
hosts:
- edition.cnn.com #外部服務URI
ports:
- number: 80 # HTTP訪問
name: http-port
protocol: HTTP
- number: 443 # HTTPS訪問
name: https-port
protocol: HTTPS #指定外部服務為HTTPS協議
resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: edition-cnn-com
spec:
hosts:
- edition.cnn.com #外部服務URI
tls: #非終結的TLS&HTTPS流量
- match: #將edition.cnn.com:443的流量分發到edition.cnn.com:443
- port: 443
sniHosts:
- edition.cnn.com
route:
- destination:
host: edition.cnn.com
port:
number: 443
weight: 100
EOF
訪問外部服務,下面使用了-L
選項使請求端依照返回的重定向信息重新發起請求。第一個請求會發往http://edition.cnn.com/politics
,服務端會返回重定向信息,第二個請求會按照重定向信息發往https://edition.cnn.com/politics
。可以看到第一次是HTTP
訪問,第二次是HTTPS
訪問。
如果沒有上述VirtualService,也可以通過下面命令進行訪問。此處應該是為了與下面例子結合。
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
HTTP/1.1 301 Moved Permanently
...
location: https://edition.cnn.com/politics
...
HTTP/2 200
...
上述過程會有兩個弊端:上面的第一個HTTP訪問顯然是冗余的;如果在應用和edition.cnn.com
之間存在攻擊者,這樣該攻擊者就可以通過嗅探鏈路獲取請求端執行的操作,存在安全風險。
使用istio的TLS源可以解決如上問題。
為Egress流量配置TLS源
重新定義 ServiceEntry
和VirtualService
,並增加DestinationRule
來發起TLS。此時VirtualService
會將HTTP請求流量從80端口重定向到DestinationRule
的443端口,然后由DestinationRule
來發起TLS。
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry # serviceEntry跟前面配置一樣
metadata:
name: edition-cnn-com
spec:
hosts:
- edition.cnn.com #注冊到注冊中心的host。用於選擇virtualService和DestinationRule
ports:
- number: 80
name: http-port
protocol: HTTP
- number: 443
name: https-port-for-tls-origination
protocol: HTTPS
resolution: DNS
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: edition-cnn-com #請求的hosts字段
spec:
hosts:
- edition.cnn.com #請求中的hosts字段內容
http:
- match:
- port: 80 #后續將http流量通過destinationrule轉換為https流量
route:
- destination:
host: edition.cnn.com #此時定義了DestinationRule,會經過DestinationRule處理
subset: tls-origination
port:
number: 443
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: edition-cnn-com
spec:
host: edition.cnn.com #istio注冊表中的服務
subsets:
- name: tls-origination
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
portLevelSettings: #配置與上游服務edition.cnn.com的連接。即在443端口上使用tls SIMPLE進行連接
- port:
number: 443
tls:
mode: SIMPLE # initiates HTTPS when accessing edition.cnn.com
EOF
向http://edition.cnn.com/politics
發送請求,可以看到此時會返回200,且不會經過重定向,相當於做了一個代理。
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics
HTTP/1.1 200 OK
...
當然直接使用https進行訪問也是可以的,與上面使用http進行訪問的結果相同kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://edition.cnn.com/politics
需要考慮的安全性問題
由於應用和sidecar代理之間是沒有加密。因此滲透到應用所在的node節點的攻擊者仍然能夠看到該節點上未加密的本地通信內容。對於安全性較高的場景,建議應用直接使用HTTPS。
卸載
$ kubectl delete serviceentry edition-cnn-com
$ kubectl delete virtualservice edition-cnn-com
$ kubectl delete destinationrule edition-cnn-com
$ kubectl delete -f samples/sleep/sleep.yaml
Egress 網關
本節描述如何通過一個指定的egress網關訪問外部服務。istio使用ingress和egress網關在服務網格邊界配置負載均衡。一個ingress網關允許定義網格的入站點,egress網關的用法類似,定義了網格內流量的出站點。
使用場景
假設在一個安全要求比較高的組織中,所有離開服務網格的流量都要經過一個指定的節點(前面的egress訪問都是在離開pod之后按照k8s方式訪問,並沒有指定必須經過某個節點),這些節點會運行在指定的機器上,與運行應用的集群的節點分開。這些特定的節點會在出站流量上應用策略,且對這些節點的監控將更加嚴格。
另外一個場景是集群中的應用所在的節點沒有公網IP,因此網格內部的服務無法訪問因特網。定義一個egress網關並為該網關所在的節點分配公網IP,這樣流量就可以通過該節點訪問公網服務。
環境配置
創建sleep應用並獲取Pod名
$ kubectl apply -f samples/sleep/sleep.yaml
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
部署Istio egress網關
校驗是否已經部署istio egress網關
$ kubectl get pod -l istio=egressgateway -n istio-system
如果沒有部署,執行如下步驟部署egress網關
$ istioctl manifest apply -f cni-annotations.yaml --set values.global.istioNamespace=istio-system --set values.gateways.istio-ingressgateway.enabled=true --set values.gateways.istio-egressgateway.enabled=true
注意:apply的時候使用自己定制化的文件,否則系統會使用默認的profile,導致配置丟失!
下面操作關於在
default
命名空間中為egress網關創建destination rule,因此要求sleep
應用也部署在default
命名空間中。如果應用不在default命名空間中,將無法在destination rule查找路徑找到destination rule,客戶端請求將會失敗。
HTTP流量的egress網關
-
上面例子中,當網格內的客戶端可以直接訪問外部服務,此處將會創建一個egress網關,內部流量訪問外部服務時會經過該網關。創建一個
ServiceEntry
允許流量訪問外部服務edition.cnn.com
:$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: cnn spec: hosts: - edition.cnn.com ports: #可以通過HTTP和HTTPS服務外部服務 - number: 80 name: http-port protocol: HTTP - number: 443 name: https protocol: HTTPS resolution: DNS EOF
-
校驗請求能夠發往http://edition.cnn.com/politics,此處的操作與上一節相同。
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics HTTP/1.1 301 Moved Permanently ... HTTP/2 200 ...
-
為
edition.cnn.com
創建一個Gateway
,端口80,監聽來自edition.cnn.com:80
的流量。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: istio-egressgateway spec: selector: istio: egressgateway servers: - port: #監聽來自edition.cnn.com:80的流量, number: 80 name: http protocol: HTTP hosts: - edition.cnn.com --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule #該DestinationRule沒有定義任何規則,實際可以刪除該DestinationRule,並刪除下面VirtualService的"subset: cnn"一行 metadata: name: egressgateway-for-cnn spec: host: istio-egressgateway.istio-system.svc.cluster.local subsets: - name: cnn #下面VirtualService中會用到 EOF
-
定義
VirtualService
,將流量從sidecar定向到egress網關,然后將流量從egress網關定向到外部服務。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: direct-cnn-through-egress-gateway spec: hosts: - edition.cnn.com gateways: #列出應用路由規則的網關 - istio-egressgateway - mesh #istio保留字段,表示網格中的所有sidecar,當忽略gateways字段時,默認會使用mesh,此處表示所有sidecar到edition.cnn.com的請求 http: #采用http路由規則 - match: #各個match是OR關系 - gateways: #處理mesh網關,將來自mesh的edition.cnn.com:80請求發往istio-egressgateway.istio-system.svc.cluster.local:80 - mesh port: 80 route: - destination: host: istio-egressgateway.istio-system.svc.cluster.local subset: cnn #對應DestinationRule中的subset名,由於使用了subset,因此必須使用DestinationRule。刪除該行后就可以不使用上面的DestinationRule port: number: 80 weight: 100 - match: - gateways: - istio-egressgateway #處理istio-egressgateway網關,將來自gateway edition.cnn.com:80的請求發往edition.cnn.com:80 port: 80 route: - destination: host: edition.cnn.com #該host就對應serviceEntry注冊的服務地址 port: number: 80 weight: 100 EOF
-
發送HTTP請求http://edition.cnn.com/politics
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics HTTP/1.1 301 Moved Permanently ... HTTP/2 200 ...
-
校驗egress日志(需要啟用Envoy日志)
$ kubectl logs -l istio=egressgateway -c istio-proxy -n istio-system | tail [2020-08-25T14:55:49.810Z] "GET /politics HTTP/2" 301 - "-" "-" 0 0 1445 1444 "10.80.3.231" "curl/7.64.0" "2151bde2-4382-4e2f-b088-e464943c2a9b" "edition.cnn.com" "151.101.1.67:80" outbound|80||edition.cnn.com 10.80.3.232:51516 10.80.3.232:8080 10.80.3.231:38072 - -
本例中仍然實在sleep pod中執行HTTP請求,通過301重定向重新發送HTTPS請求,而上面規則中並沒有將HTTPs流程轉發給網關,因此從上面網關上看不到到443端口的流量,但可以在sleep的istio-proxy sidecar的日志中可以看到完整的流量信息,如下:
[2020-08-25T14:55:33.114Z] "GET /politics HTTP/1.1" 301 - "-" "-" 0 0 310 310 "-" "curl/7.64.0" "d57ddf5f-985b-431a-8766-7481b75dc486" "edition.cnn.com" "151.101.1.67:80" outbound|80||edition.cnn.com 10.80.3.231:48390 151.101.65.67:80 10.80.3.231:44674 - default
[2020-08-25T14:55:33.439Z] "- - -" 0 - "-" "-" 906 1326852 5490 - "-" "-" "-" "-" "151.101.129.67:443" outbound|443||edition.cnn.com 10.80.3.231:47044 151.101.65.67:443 10.80.3.231:42990 edition.cnn.com -
卸載
$ kubectl delete gateway istio-egressgateway
$ kubectl delete serviceentry cnn
$ kubectl delete virtualservice direct-cnn-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-cnn
HTTPS流量的egress gateway
本節展示通過egress網關定向HTTPS
流量,。會使用到ServiceEntry
,一個egress Gateway
和一個VirtualService
。
-
創建到
edition.cnn.com
的ServiceEntry
,定義外部服務https://edition.cnn.com$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: cnn spec: hosts: - edition.cnn.com ports: - number: 443 name: tls protocol: TLS #protocol為TLS,用於非終結的流量 resolution: DNS EOF
protocol字段可以為
HTTP|HTTPS|GRPC|HTTP2|MONGO|TCP|TLS
其中之一,其中TLS 表示不會終止TLS連接,且連接會基於SNI首部進行路由。 -
校驗可以通過
ServiceEntry
訪問https://edition.cnn.com/politics$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://edition.cnn.com/politics HTTP/2 200 ...
-
為
edition.cnn.com
創建egressGateway
,一個destination rule和一個virtual service。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: istio-egressgateway spec: selector: istio: egressgateway servers: - port: number: 443 name: tls protocol: TLS #該字段與serviceEntry的字段相同 hosts: - edition.cnn.com tls: mode: PASSTHROUGH #透傳模式,不在網關上終止TLS,由sidecar發起TLS --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: egressgateway-for-cnn spec: host: istio-egressgateway.istio-system.svc.cluster.local subsets: - name: cnn --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: direct-cnn-through-egress-gateway spec: hosts: - edition.cnn.com gateways: - mesh - istio-egressgateway tls: #此處由http變為了tls - match: - gateways: - mesh port: 443 sniHosts: - edition.cnn.com #基於SNI的路由 route: - destination: host: istio-egressgateway.istio-system.svc.cluster.local subset: cnn port: number: 443 - match: - gateways: - istio-egressgateway port: 443 sniHosts: - edition.cnn.com #指定tls的SNI route: - destination: host: edition.cnn.com port: number: 443 weight: 100 EOF
由於TLS本身是加密的,無法像HTTP一樣根據host首部字段進行路由管理,因此采用了SNI擴展。SNI位於TLS協商的client-hello階段,作為client-hello的擴展字段存在,基於TLS SNI的路由與基於HTTP host首部字段的路由管理,在邏輯上是相同的。SNI也支持通配符模式。
-
訪問https://edition.cnn.com/politics
$ kubectl exec -it $SOURCE_POD -c sleep -- curl -sL -o /dev/null -D - https://edition.cnn.com/politics HTTP/2 200 ...
-
校驗log
$ kubectl logs -l istio=egressgateway -n istio-system ... [2020-06-02T09:06:43.152Z] "- - -" 0 - "-" "-" 906 1309129 1282 - "-" "-" "-" "-" "151.101.193.67:443" outbound|443||edition.cnn.com 10.83.1.219:39574 10.83.1.219:443 10.80.3.25:35492 edition.cnn.com -
卸載
$ kubectl delete serviceentry cnn
$ kubectl delete gateway istio-egressgateway
$ kubectl delete virtualservice direct-cnn-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-cnn
安全考量
istio不能保證所有通過egress網關出去的流量的安全性,僅能保證通過sidecar代理的流量的安全性。如果攻擊者繞過了sidecar代理,就可以不經過egress網關直接訪問外部服務。此時,攻擊者的行為不受istio的控制和監控。集群管理員或雲供應商必須保證所有的流量都要經過egress網關。例如,集群管理員可以配置一個防火牆,拒絕所有非egress網關的流量。Kubernetes network policies也可以禁止所有非egress網關的流量。此外,集群管理員或雲供應商可以配置網絡來保證應用節點只能通過網關訪問因特網,為了實現這種效果,需要阻止將公共IP分配給網關以外的pod,並配置NAT設備丟棄非egress網關的報文。
使用Kubernetes network policies
本節展示如何創建一個Kubernetes network policy來防止繞過egress網關。為了測試網絡策略,需要創建一個命名空間test-egress
,部署sleep
應用,並嘗試向網關安全的外部服務發送請求。
首先完成中的egress-gateway-for-https-traffic步驟,然后執行如下操作
-
創建
test-egress
命名空間$ kubectl create namespace test-egress
-
將
sleep
部署到test-egress
命名空間中$ kubectl apply -n test-egress -f samples/sleep/sleep.yaml
-
校驗部署的pod不存在istio sidecar
$ kubectl get pod $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress NAME READY STATUS RESTARTS AGE sleep-f8cbf5b76-g2t2l 1/1 Running 0 27s
-
從
test-egress
命名空間中的sleep
pod向https://edition.cnn.com/politics 發送HTTPS請求,返回200成功$ kubectl exec -it $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress -c sleep -- curl -s -o /dev/null -w "%{http_code}\n" https://edition.cnn.com/politics 200
-
在istio組件所在的命名空間創建標簽,如果istio組件部署在
istio-system
命名空間中,則操作方式如下:$ kubectl label namespace istio-system istio=system
-
給
kube-system
命名空間打標簽$ kubectl label ns kube-system kube-system=true
-
部署一個
NetworkPolicy
限制從test-egress
命名空間到istio-system
命名空間和kube-system
DNS服務的egress流量:$ cat <<EOF | kubectl apply -n test-egress -f - apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-egress-to-istio-system-and-kube-dns spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: kube-system: "true" ports: - protocol: UDP port: 53 - to: - namespaceSelector: matchLabels: istio: system EOF
-
重新發送HTTPS請求到https://edition.cnn.com/politics,此時由於network policy阻止了流量,請求會失敗。由於
sleep
Pod無法繞過istio-egressgateway
(需要環境保證,如果環境上即使沒有istio-egressgateway
也能訪問外部服務,則此處可能會與預期不一樣,本人使用的openshift環境無法測試這種場景)訪問外部服務,唯一的方式是將流量定向到istio-egressgateway
上。$ kubectl exec -it $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress -c sleep -- curl -v https://edition.cnn.com/politics Hostname was NOT found in DNS cache Trying 151.101.65.67... Trying 2a04:4e42:200::323... Immediate connect fail for 2a04:4e42:200::323: Cannot assign requested address Trying 2a04:4e42:400::323... Immediate connect fail for 2a04:4e42:400::323: Cannot assign requested address Trying 2a04:4e42:600::323... Immediate connect fail for 2a04:4e42:600::323: Cannot assign requested address Trying 2a04:4e42::323... Immediate connect fail for 2a04:4e42::323: Cannot assign requested address connect to 151.101.65.67 port 443 failed: Connection timed out
-
現在給
test-egress
命名空間的sleep
pod注入istio sidecar代理$ kubectl label namespace test-egress istio-injection=enabled
-
重新在
test-egress
命名空間中部署sleep
deploymentopenshift環境需要首先執行如下步驟:
$ cat <<EOF | oc -n test-egress create -f - apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: istio-cni EOF $ oc adm policy add-scc-to-group privileged system:serviceaccounts:test-egress $ oc adm policy add-scc-to-group anyuid system:serviceaccounts:test-egress
部署
sleep
應用$ kubectl delete deployment sleep -n test-egress $ kubectl apply -f samples/sleep/sleep.yaml -n test-egress
-
校驗
test-egress
命名空間的sleep注入了istio sidecar$ kubectl get pod $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress -o jsonpath='{.spec.containers[*].name}' sleep istio-proxy
-
創建與
default
命名空間中相同的destination rule,將流量定向到egress網關:$ kubectl apply -n test-egress -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: egressgateway-for-cnn spec: host: istio-egressgateway.istio-system.svc.cluster.local #內部服務地址,不需要用serviceEntry subsets: - name: cnn EOF
-
發送HTTPS請求到https://edition.cnn.com/politics:
$ kubectl exec -it $(kubectl get pod -n test-egress -l app=sleep -o jsonpath={.items..metadata.name}) -n test-egress -c sleep -- curl -s -o /dev/null -w "%{http_code}\n" https://edition.cnn.com/politics
-
校驗egress網關代理的日志
$ kubectl logs -l istio=egressgateway -n istio-system ... [2020-06-02T09:04:11.239Z] "- - -" 0 - "-" "-" 906 1309030 1606 - "-" "-" "-" "-" "151.101.1.67:443" outbound|443||edition.cnn.com 10.83.1.219:56116 10.83.1.219:443 10.80.3.25:59032 edition.cnn.com -
卸載網絡策略
$ kubectl delete -f samples/sleep/sleep.yaml -n test-egress
$ kubectl delete destinationrule egressgateway-for-cnn -n test-egress
$ kubectl delete networkpolicy allow-egress-to-istio-system-and-kube-dns -n test-egress
$ kubectl label namespace kube-system kube-system-
$ kubectl label namespace istio-system istio-
$ kubectl delete namespace test-egress
帶TLS源的Egress網關(文件掛載)
在上一節的HTTPS流量的egress gateway中展示了如何配置istio來實現對外部服務的流量發起TLS。HTTP流量的egress網關中展示例子展示了如何配置istio來通過一個特定的egress網格服務來轉發egress流量。本節的例子將結合這兩個例子來描述如何配置一個egress網關來為到外部服務的流量發起TLS。
部署
在default(已啟用sidecar自動注入)命名空間下安裝sleep
$ kubectl apply -f samples/sleep/sleep.yaml
獲取Pod名稱
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
創建egress網關
$ istioctl install -f cni-annotations.yaml --set values.global.istioNamespace=istio-system --set values.gateways.istio-egressgateway.enabled=true --set meshConfig.accessLogFile="/dev/stdout"
使用一個egress網關發起TLS
本節描述如何使用於HTTPS流量的egress gateway相同的方式發起TLS,但此處使用了一個egress網關。注意這種情況下,通過egress網關來發起TLS,而前面的例子中使用了sidecar發起TLS(curl時指定的是https://edition.cnn.com/politics)。
-
為
edition.cnn.com
定義一個ServiceEntry
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: cnn spec: hosts: - edition.cnn.com ports: - number: 80 name: http protocol: HTTP - number: 443 name: https protocol: HTTPS resolution: DNS EOF
-
校驗可以通過創建的
ServiceEntry
向http://edition.cnn.com/politics發送請求# kubectl exec "${SOURCE_POD}" -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics HTTP/1.1 301 Moved Permanently ... location: https://edition.cnn.com/politics ...
-
為
edition.cnn.com
創建一個Gateway
,監聽edition.cnn.com:80
,以及一個destination rule來處理sidecar到egress網關的請求$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: istio-egressgateway spec: selector: istio: egressgateway servers: #配置網關暴露的主機信息 - port: number: 80 name: https-port-for-tls-origination protocol: HTTPS hosts: - edition.cnn.com tls: mode: ISTIO_MUTUAL #與網關的連接使用ISTIO_MUTUAL模式 --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: egressgateway-for-cnn spec: host: istio-egressgateway.istio-system.svc.cluster.local subsets: - name: cnn trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: #基於單個端口的流量策略 - port: number: 80 tls: #與上游服務的連接設置,即到網關的tls配置,使用ISTIO_MUTUAL模式 mode: ISTIO_MUTUAL sni: edition.cnn.com #表示TLS連接的服務端 EOF
-
定義一個
VirtualService
將流量轉移到egress網關,以及一個DestinationRule
來為到edition.cnn.com
的請求發起TLS。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: direct-cnn-through-egress-gateway spec: hosts: - edition.cnn.com gateways: - istio-egressgateway - mesh http: - match: - gateways: #處理來自網格內部所有到edition.cnn.com的流量,發送到egress網關,並使用subset: cnn進行處理 - mesh port: 80 route: - destination: host: istio-egressgateway.istio-system.svc.cluster.local subset: cnn port: number: 80 weight: 100 - match: - gateways: - istio-egressgateway #處理來自網關istio-egressgateway的流量,直接發往edition.cnn.com port: 80 route: - destination: host: edition.cnn.com port: number: 443 weight: 100 --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: originate-tls-for-edition-cnn-com spec: host: edition.cnn.com trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: - port: number: 443 tls: mode: SIMPLE # 網關到edition.cnn.com使用SIMPLE模式,由於edition.cnn.com是網格外部服務,因此不能使用ISTIO_MUTUAL EOF
整個過程為:網格內部HTTP流量->istio-egressgateway(配置TLS)->發起TLS連接
-
發送
HTTP
請求到http://edition.cnn.com/politics# kubectl exec "${SOURCE_POD}" -c sleep -- curl -sL -o /dev/null -D - http://edition.cnn.com/politics HTTP/1.1 200 OK
此時的輸出中不包含301 Moved Permanently 消息
-
校驗
istio-egressgateway
pod的日志$ kubectl logs -l istio=egressgateway -c istio-proxy -n istio-system | tail
可以看到如下輸出:
[2020-08-25T15:16:17.840Z] "GET /politics HTTP/2" 200 - "-" "-" 0 1297688 7518 460 "10.80.3.231" "curl/7.64.0" "2c71707e-3304-418c-840e-c37256c1ad41" "edition.cnn.com" "151.101.193.67:443" outbound|443||edition.cnn.com 10.80.3.232:38522 10.80.3.232:8080 10.80.3.231:46064 edition.cnn.com -
各種資源的tls設置:
資源 描述 virtualService tls字段:用於非終結TLS&HTTPS流量的路由規則。通常使用ClientHello消息中的SNI值進行路由。TLS路由將會應用於端口名為 https -
tls-
的平台服務,使用HTTPS/TLS協議的非終結網關端口(使用passthrough
TLS模式),以及使用HTTPS/TLS協議的serviceEntry端口。注:不關聯virtual service的https-
或tls-
端口的流量將被視為不透明的TCP流量。DestinationRule DestinationRule主要對連接上游服務的tls進行配置,包含網格內的網關以及網格外的服務
ClientTLSSettings字段:連接上游的SSL/TLS相關設置
portLevelSettings字段:按照端口對上游服務進行設置,該字段包含了ClientTLSSettings字段Gateway Gateway主要暴露的服務的tls進行配置,含ingress和egress,前者通常可以使用SIMPLE/MUTUAL模式,后者可以使用SIMPLE/MUTUAL/ISTIO_MUTUAL模式。ServerTLSSettings字段:控制服務端行為的TLS相關選項集。使用這些選項來控制是否應將所有http請求重定向到https,並使用TLS模式
卸載
$ kubectl delete gateway istio-egressgateway
$ kubectl delete serviceentry cnn
$ kubectl delete virtualservice direct-cnn-through-egress-gateway
$ kubectl delete destinationrule originate-tls-for-edition-cnn-com
$ kubectl delete destinationrule egressgateway-for-cnn
使用egress網關發起mutual TLS
與前面章節類似,本節描述如何配置egress網關來向外部服務發起TLS,不同的是這次要使用mutual TLS(上面用的是SIMPLE模式)。
在本例中首先需要:
- 生成client和server證書
- 部署支持mutual TLS協議的外部服務
- 重新部署egress網關,使用mutual TLS證書
然后就是通過egress 網關發起TLS。
生成client和server的證書和key
-
創建根證書和私鑰,用於簽發服務證書
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
-
為
my-nginx.mesh-external.svc.cluster.local
創建證書和私鑰$ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:2048 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization" $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt
-
生成client證書和私鑰
$ openssl req -out client.example.com.csr -newkey rsa:2048 -nodes -keyout client.example.com.key -subj "/CN=client.example.com/O=client organization" $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 1 -in client.example.com.csr -out client.example.com.crt
部署mutual TLS server
為了模擬一個支持mutual TLS協議的外部服務,需要在kubernetes集群中部署一個NGINX服務,但該服務位於istio服務網格外,即位於一個沒有啟用istio sidecar代理注入的命名空間。
-
創建一個唯一istio網格外的命名空間,名為
mesh-external
,該命名空間不啟用sidecar自動注入。$ kubectl create namespace mesh-external
-
創建kubernetes secret,包含服務端的證書和CA證書
$ kubectl create -n mesh-external secret tls nginx-server-certs --key my-nginx.mesh-external.svc.cluster.local.key --cert my-nginx.mesh-external.svc.cluster.local.crt $ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=example.com.crt
-
創建NGINX 服務的配置文件
$ cat <<\EOF > ./nginx.conf events { } http { log_format main '$remote_addr - $remote_user [$time_local] $status ' '"$request" $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log; server { listen 443 ssl; root /usr/share/nginx/html; index index.html; server_name my-nginx.mesh-external.svc.cluster.local; ssl_certificate /etc/nginx-server-certs/tls.crt; ssl_certificate_key /etc/nginx-server-certs/tls.key; ssl_client_certificate /etc/nginx-ca-certs/example.com.crt; ssl_verify_client on; } } EOF
-
創建一個kubernetes ConfigMap來保存NGINX服務的配置信息
$ kubectl apply -f - <<EOF apiVersion: v1 kind: Service metadata: name: my-nginx namespace: mesh-external labels: run: my-nginx spec: ports: - port: 443 protocol: TCP selector: run: my-nginx --- apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx namespace: mesh-external spec: selector: matchLabels: run: my-nginx replicas: 1 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 443 volumeMounts: - name: nginx-config mountPath: /etc/nginx readOnly: true - name: nginx-server-certs mountPath: /etc/nginx-server-certs readOnly: true - name: nginx-ca-certs mountPath: /etc/nginx-ca-certs readOnly: true volumes: - name: nginx-config configMap: name: nginx-configmap - name: nginx-server-certs secret: secretName: nginx-server-certs - name: nginx-ca-certs secret: secretName: nginx-ca-certs EOF
使用client證書重新部署egress網關
-
創建kubernetes secret,包含客戶端證書和CA證書
$ kubectl create -n istio-system secret tls nginx-client-certs --key client.example.com.key --cert client.example.com.crt $ kubectl create -n istio-system secret generic nginx-ca-certs --from-file=example.com.crt
-
更新
istio-egressgateway
deployment來掛載創建的secret。創建如下gateway-patch.json
文件來給istio-egressgateway
deployment打補丁。cat > gateway-patch.json <<EOF [{ "op": "add", "path": "/spec/template/spec/containers/0/volumeMounts/0", "value": { "mountPath": "/etc/istio/nginx-client-certs", "name": "nginx-client-certs", "readOnly": true } }, { "op": "add", "path": "/spec/template/spec/volumes/0", "value": { "name": "nginx-client-certs", "secret": { "secretName": "nginx-client-certs", "optional": true } } }, { "op": "add", "path": "/spec/template/spec/containers/0/volumeMounts/1", "value": { "mountPath": "/etc/istio/nginx-ca-certs", "name": "nginx-ca-certs", "readOnly": true } }, { "op": "add", "path": "/spec/template/spec/volumes/1", "value": { "name": "nginx-ca-certs", "secret": { "secretName": "nginx-ca-certs", "optional": true } } }] EOF
-
使用如下命令使補丁生效
$ kubectl -n istio-system patch --type=json deploy istio-egressgateway -p "$(cat gateway-patch.json)"
-
校驗加載到
istio-egressgateway
pod中的密鑰和證書$ kubectl exec -n istio-system "$(kubectl -n istio-system get pods -l istio=egressgateway -o jsonpath='{.items[0].metadata.name}')" -- ls -al /etc/istio/nginx-client-certs /etc/istio/nginx-ca-certs
tls.crt
和tls.key
應該位於/etc/istio/nginx-client-certs
目錄中,而ca-chain.cert.pem
位於/etc/istio/nginx-ca-certs
目錄中。
配置egress流量的mutual TLS源
-
為
my-nginx.mesh-external.svc.cluster.local:443
創建一個egress Gateway,以及destination rules和virtual service來將流量轉發到egress網關上,並通過該egress網關轉發給外部服務。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: istio-egressgateway spec: selector: istio: egressgateway servers: - port: number: 443 name: https protocol: HTTPS hosts: - my-nginx.mesh-external.svc.cluster.local #暴露給網格內部服務地址,使用ISTIO_MUTUAL進行交互 tls: mode: ISTIO_MUTUAL --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule #處理網格內部pod到網關的流量 metadata: name: egressgateway-for-nginx spec: host: istio-egressgateway.istio-system.svc.cluster.local subsets: - name: nginx trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: #連接的上游服務屬性 - port: number: 443 tls: mode: ISTIO_MUTUAL sni: my-nginx.mesh-external.svc.cluster.local EOF
-
定義一個VirtualService將流量轉移到egress網關
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: direct-nginx-through-egress-gateway spec: hosts: - my-nginx.mesh-external.svc.cluster.local gateways: - istio-egressgateway - mesh http: #內部流量采用http協議,由網關進行mutual tls協商 - match: - gateways: - mesh port: 80 route: - destination: host: istio-egressgateway.istio-system.svc.cluster.local subset: nginx port: number: 443 weight: 100 - match: - gateways: - istio-egressgateway port: 443 route: - destination: host: my-nginx.mesh-external.svc.cluster.local #外部服務地址 port: number: 443 weight: 100 EOF
-
添加一個
DestinationRule
來發起TLS$ kubectl apply -n istio-system -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule #處理網關到外部服務的流量 metadata: name: originate-mtls-for-nginx spec: host: my-nginx.mesh-external.svc.cluster.local trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: - port: number: 443 tls: mode: MUTUAL #使用MUTUAL模式連接外部服務,證書位於網關pod中 clientCertificate: /etc/istio/nginx-client-certs/tls.crt privateKey: /etc/istio/nginx-client-certs/tls.key caCertificates: /etc/istio/nginx-ca-certs/example.com.crt sni: my-nginx.mesh-external.svc.cluster.local EOF
-
發送HTTP請求到
http://my-nginx.mesh-external.svc.cluster.local
:$ kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl -s http://my-nginx.mesh-external.svc.cluster.local
<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...
-
校驗
istio-egressgateway
pod的日志# kubectl logs -l istio=egressgateway -n istio-system | grep 'my-nginx.mesh-external.svc.cluster.local' | grep HTTP [2020-08-26T08:26:15.054Z] "GET / HTTP/1.1" 200 - "-" "-" 0 612 4 4 "10.80.3.231" "curl/7.64.0" "e8bf12bd-9c39-409e-a837-39afc151fc7e" "my-nginx.mesh-external.svc.cluster.local" "10.80.2.14:443" outbound|443||my-nginx.mesh-external.svc.cluster.local 10.80.2.15:56608 10.80.2.15:8443 10.80.3.231:50962 my-nginx.mesh-external.svc.cluster.local -
卸載
$ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
$ kubectl delete secret istio-egressgateway-certs istio-egressgateway-ca-certs -n istio-system
$ kubectl delete configmap nginx-configmap -n mesh-external
$ kubectl delete service my-nginx -n mesh-external
$ kubectl delete deployment my-nginx -n mesh-external
$ kubectl delete namespace mesh-external
$ kubectl delete gateway istio-egressgateway
$ kubectl delete virtualservice direct-nginx-through-egress-gateway
$ kubectl delete destinationrule -n istio-system originate-mtls-for-nginx
$ kubectl delete destinationrule egressgateway-for-nginx
$ rm example.com.crt example.com.key my-nginx.mesh-external.svc.cluster.local.crt my-nginx.mesh-external.svc.cluster.local.key my-nginx.mesh-external.svc.cluster.local.csr client.example.com.crt client.example.com.csr client.example.com.key
$ rm ./nginx.conf
$ rm ./gateway-patch.json
$ kubectl delete service sleep
$ kubectl delete deployment sleep
帶TLS源的Egress網關(SDS)
本節展示如何通過配置一個egress網關來為到外部服務的流量發起TLS。使用Secret Discovery Service (SDS)來配置私鑰,服務證書以及根證書(上一節中使用文件掛載方式來管理證書)。
部署
部署sleep應用,並獲取其Pod名
$ kubectl apply -f samples/sleep/sleep.yaml
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
egress網關使用SDS發起TLS
生成CA和server證書和密鑰
-
創建根證書和私鑰來簽署服務的證書
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
-
為
my-nginx.mesh-external.svc.cluster.local
創建證書和私鑰$ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:2048 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization" $ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt
部署一個simple 模式的TLS服務
下面的操作與使用文件掛載相同,部署一個NGINX服務
-
創建istio網格外的命名空間
mesh-external
$ kubectl create namespace mesh-external
-
創建kubernetes secret來保存服務的證書和CA證書
$ kubectl create -n mesh-external secret tls nginx-server-certs --key my-nginx.mesh-external.svc.cluster.local.key --cert my-nginx.mesh-external.svc.cluster.local.crt $ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=example.com.crt
-
創建NGINX服務的配置文件
$ cat <<\EOF > ./nginx.conf events { } http { log_format main '$remote_addr - $remote_user [$time_local] $status ' '"$request" $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; error_log /var/log/nginx/error.log; server { listen 443 ssl; root /usr/share/nginx/html; index index.html; server_name my-nginx.mesh-external.svc.cluster.local; ssl_certificate /etc/nginx-server-certs/tls.crt; ssl_certificate_key /etc/nginx-server-certs/tls.key; ssl_client_certificate /etc/nginx-ca-certs/example.com.crt; ssl_verify_client off; # simple TLS下server不需要校驗client的證書 } } EOF
-
創建一個kubernetes ConfigMap來保存NGINX服務的配置信息
$ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
-
部署NGINX服務
$ kubectl apply -f - <<EOF apiVersion: v1 kind: Service metadata: name: my-nginx namespace: mesh-external labels: run: my-nginx spec: ports: - port: 443 protocol: TCP selector: run: my-nginx --- apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx namespace: mesh-external spec: selector: matchLabels: run: my-nginx replicas: 1 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 443 volumeMounts: - name: nginx-config mountPath: /etc/nginx readOnly: true - name: nginx-server-certs mountPath: /etc/nginx-server-certs readOnly: true - name: nginx-ca-certs mountPath: /etc/nginx-ca-certs readOnly: true volumes: - name: nginx-config configMap: name: nginx-configmap - name: nginx-server-certs secret: secretName: nginx-server-certs - name: nginx-ca-certs secret: secretName: nginx-ca-certs EOF
為egress流量發起simple TLS
-
創建一個kubernetes Secret來保存egress網格發起TLS使用的CA證書,由於使用的是SIMPLE模式,因此無需客戶端證書,僅對ca證書實現SDS,后續在網關的destinationRule中使用。
$ kubectl create secret generic client-credential-cacert --from-file=ca.crt=example.com.crt -n istio-system
注意,Istio-only-CA證書的secret名稱必須以-cacert結尾,並且必須在與部署的Istio相同的命名空間(默認為
Istio-system
)中創建該secret。secret名稱不應該以
istio
或prometheus
開頭,且secret不能包含token
字段下面的配置除最后一個destinationRule外,其余配置都與上一節相同
-
為
my-nginx.mesh-external.svc.cluster.local:443
創建一個egress Gateway,以及destination rules和virtual service來將流量轉發到egress網關上,並通過該egress網關轉發給外部服務。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: istio-egressgateway spec: selector: istio: egressgateway servers: - port: number: 443 name: https protocol: HTTPS hosts: - my-nginx.mesh-external.svc.cluster.local tls: mode: ISTIO_MUTUAL --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: egressgateway-for-nginx spec: host: istio-egressgateway.istio-system.svc.cluster.local subsets: - name: nginx trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: - port: number: 443 tls: mode: ISTIO_MUTUAL sni: my-nginx.mesh-external.svc.cluster.local EOF
-
定義一個VirtualService將流量轉移到egress網關
kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: direct-nginx-through-egress-gateway spec: hosts: - my-nginx.mesh-external.svc.cluster.local gateways: - istio-egressgateway - mesh http: - match: - gateways: - mesh port: 80 route: - destination: host: istio-egressgateway.istio-system.svc.cluster.local subset: nginx port: number: 443 weight: 100 - match: - gateways: - istio-egressgateway port: 443 route: - destination: host: my-nginx.mesh-external.svc.cluster.local port: number: 443 weight: 100 EOF
-
添加一個
DestinationRule
來發起TLS$ kubectl apply -n istio-system -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: originate-tls-for-nginx spec: host: my-nginx.mesh-external.svc.cluster.local trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: - port: number: 443 tls: mode: SIMPLE credentialName: client-credential # 對應前面創建的包含ca證書的secret client-credential-cacert,但此時不帶"-cacert"后綴 sni: my-nginx.mesh-external.svc.cluster.local #網格外部服務 EOF
-
發送一個HTTP請求到
http://my-nginx.mesh-external.svc.cluster.local
:$ kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl -s http://my-nginx.mesh-external.svc.cluster.local <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ...
-
檢查
istio-egressgateway
中的訪問日志# kubectl logs -l istio=egressgateway -n istio-system | grep 'my-nginx.mesh-external.svc.cluster.local' | grep HTTP [2020-08-26T12:26:09.316Z] "GET / HTTP/1.1" 200 - "-" "-" 0 612 3 3 "10.80.3.231" "curl/7.64.0" "67803676-5617-4e12-a14a-5cef95ea2e87" "my-nginx.mesh-external.svc.cluster.local" "10.80.2.19:443" outbound|443||my-nginx.mesh-external.svc.cluster.local 10.80.2.15:40754 10.80.2.15:8443 10.80.3.231:57626 my-nginx.mesh-external.svc.cluster.local -
卸載
$ kubectl delete destinationrule originate-tls-for-nginx -n istio-system
$ kubectl delete virtualservice direct-nginx-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-nginx
$ kubectl delete gateway istio-egressgateway
$ kubectl delete secret client-credential-cacert -n istio-system
$ kubectl delete service my-nginx -n mesh-external
$ kubectl delete deployment my-nginx -n mesh-external
$ kubectl delete configmap nginx-configmap -n mesh-external
$ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
$ kubectl delete namespace mesh-external
$ rm example.com.crt example.com.key my-nginx.mesh-external.svc.cluster.local.crt my-nginx.mesh-external.svc.cluster.local.key my-nginx.mesh-external.svc.cluster.local.csr
$ rm ./nginx.conf
egress網關使用SDS發起mutual TLS
創建客戶端和服務端證書和密鑰
下面操作跟前面一樣,創建CA和客戶端,服務端證書
$ openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt
$ openssl req -out my-nginx.mesh-external.svc.cluster.local.csr -newkey rsa:2048 -nodes -keyout my-nginx.mesh-external.svc.cluster.local.key -subj "/CN=my-nginx.mesh-external.svc.cluster.local/O=some organization"
$ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in my-nginx.mesh-external.svc.cluster.local.csr -out my-nginx.mesh-external.svc.cluster.local.crt
$ openssl req -out client.example.com.csr -newkey rsa:2048 -nodes -keyout client.example.com.key -subj "/CN=client.example.com/O=client organization"
$ openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 1 -in client.example.com.csr -out client.example.com.crt
部署一個mutual TLS服務端
下面的配置也跟之前一樣
$ kubectl create namespace mesh-external
$ kubectl create -n mesh-external secret tls nginx-server-certs --key my-nginx.mesh-external.svc.cluster.local.key --cert my-nginx.mesh-external.svc.cluster.local.crt
$ kubectl create -n mesh-external secret generic nginx-ca-certs --from-file=example.com.crt
$ cat <<\EOF > ./nginx.conf
events {
}
http {
log_format main '$remote_addr - $remote_user [$time_local] $status '
'"$request" $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
server {
listen 443 ssl;
root /usr/share/nginx/html;
index index.html;
server_name my-nginx.mesh-external.svc.cluster.local;
ssl_certificate /etc/nginx-server-certs/tls.crt;
ssl_certificate_key /etc/nginx-server-certs/tls.key;
ssl_client_certificate /etc/nginx-ca-certs/example.com.crt;
ssl_verify_client on; # mutual TLS下的server會校驗client的證書
}
}
EOF
$ kubectl create configmap nginx-configmap -n mesh-external --from-file=nginx.conf=./nginx.conf
$ kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: my-nginx
namespace: mesh-external
labels:
run: my-nginx
spec:
ports:
- port: 443
protocol: TCP
selector:
run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
namespace: mesh-external
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 443
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx
readOnly: true
- name: nginx-server-certs
mountPath: /etc/nginx-server-certs
readOnly: true
- name: nginx-ca-certs
mountPath: /etc/nginx-ca-certs
readOnly: true
volumes:
- name: nginx-config
configMap:
name: nginx-configmap
- name: nginx-server-certs
secret:
secretName: nginx-server-certs
- name: nginx-ca-certs
secret:
secretName: nginx-ca-certs
EOF
配置egress流量使用SDS發起mutual TLS
-
創建一個kubernetes secret來保存客戶端證書和ca證書
$ kubectl create secret -n istio-system generic client-credential --from-file=tls.key=client.example.com.key \ --from-file=tls.crt=client.example.com.crt --from-file=ca.crt=example.com.crt
使用SDS的secret名稱跟上一節的要求一樣,部署到istio所在的命名空間,且名稱不能以
istio
和prometheus
開頭,不能包含token
字段。 -
為
my-nginx.mesh-external.svc.cluster.local:443
創建Gateway
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: istio-egressgateway spec: selector: istio: egressgateway servers: - port: number: 443 name: https protocol: HTTPS hosts: - my-nginx.mesh-external.svc.cluster.local tls: mode: ISTIO_MUTUAL --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: egressgateway-for-nginx spec: host: istio-egressgateway.istio-system.svc.cluster.local subsets: - name: nginx trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: - port: number: 443 tls: mode: ISTIO_MUTUAL sni: my-nginx.mesh-external.svc.cluster.local EOF
-
創建
VirtualService
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: direct-nginx-through-egress-gateway spec: hosts: - my-nginx.mesh-external.svc.cluster.local gateways: - istio-egressgateway - mesh http: - match: - gateways: - mesh port: 80 route: - destination: host: istio-egressgateway.istio-system.svc.cluster.local subset: nginx port: number: 443 weight: 100 - match: - gateways: - istio-egressgateway port: 443 route: - destination: host: my-nginx.mesh-external.svc.cluster.local port: number: 443 weight: 100 EOF
-
與前面不同點就在該
DestinationRule
中的credentialName
字段,包含了前面創建的證書client-credential
$ kubectl apply -n istio-system -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: originate-mtls-for-nginx spec: host: my-nginx.mesh-external.svc.cluster.local trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: - port: number: 443 tls: mode: MUTUAL credentialName: client-credential # this must match the secret created earlier to hold client certs sni: my-nginx.mesh-external.svc.cluster.local EOF
-
發送請求並校驗egressgateway pod的日志
$ kubectl exec "$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})" -c sleep -- curl -s http://my-nginx.mesh-external.svc.cluster.local $ kubectl logs -l istio=egressgateway -n istio-system | grep 'my-nginx.mesh-external.svc.cluster.local' | grep HTTP
卸載
$ kubectl delete secret nginx-server-certs nginx-ca-certs -n mesh-external
$ kubectl delete secret client-credential -n istio-system
$ kubectl delete configmap nginx-configmap -n mesh-external
$ kubectl delete service my-nginx -n mesh-external
$ kubectl delete deployment my-nginx -n mesh-external
$ kubectl delete namespace mesh-external
$ kubectl delete gateway istio-egressgateway
$ kubectl delete virtualservice direct-nginx-through-egress-gateway
$ kubectl delete destinationrule -n istio-system originate-mtls-for-nginx
$ kubectl delete destinationrule egressgateway-for-nginx
$ rm example.com.crt example.com.key my-nginx.mesh-external.svc.cluster.local.crt my-nginx.mesh-external.svc.cluster.local.key my-nginx.mesh-external.svc.cluster.local.csr client.example.com.crt client.example.com.csr client.example.com.key
$ rm ./nginx.conf
$ rm ./gateway-patch.json
$ kubectl delete service sleep
$ kubectl delete deployment sleep
使用通配符主機的Egress
上兩節中為網關配置了特定的主機名,如 edition.cnn.com
。本節將展示如何為egress流量配置位於同域的一組主機,如*.wikipedia.org
。
背景說明
假設要在istio上為所有語言的wikipedia.org
站點啟用egress流量,每個特定語言的wikipedia.org
站點都有其各自的主機名,如en.wikipedia.org
和de.wikipedia.org
分別表示英文和德文。此時可能會希望為所有的Wikipedia egress流量配置相同的參數,而不需要為每種語言的站點單獨指定。
原文中用於展示的站點為
*.wikipedia.org
,但鑒於這類站點在國內無法訪問,故修改為*.baidu.com
部署
重新安裝Istio,使用--set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY
默認阻塞出站流量
# istioctl install -f cni-annotations.yaml --set values.global.istioNamespace=istio-system --set values.gateways.istio-egressgateway.enabled=true --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY --set meshConfig.accessLogFile="/dev/stdout"
部署sleep應用並獲取POD名稱
$ kubectl apply -f samples/sleep/sleep.yaml
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
配置到通配符主機的直接流量
首先,為了簡化場景,創建一個帶通配符主機的ServiceEntry
,並直接訪問服務。當直接調用服務時(不經過egress網關),通配符主機的配置與其他主機並沒有什么不同(只是對同一域中的主機的服務更加方便)。
為*.baidu.com
定義一個ServiceEntry
和相應的VirtualSevice
:
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: baidu
spec:
hosts:
- "*.baidu.com" #通配符主機
ports:
- number: 443
name: tls
protocol: TLS #HTTPS也是可以的
EOF
發送請求給https://map.baidu.com/和https://fanyi.baidu.com/:
# kubectl exec -it $SOURCE_POD -c sleep -- sh -c 'curl -s https://map.baidu.com/ | grep -o "<title>.*</title>"; curl -s https://fanyi.baidu.com/ | grep -o "<title>.*</title>"'
<title>百度地圖</title>
<title>百度翻譯-200種語言互譯、溝通全世界!</title>
卸載
$ kubectl delete serviceentry baidu
配置到通配符主機的egress網關流量
通過egress網關訪問通配符主機的配置取決於通配符域集是否由一個公共主機來提供服務。例如*.wikipedia.org,所有指定語言的站點都由*wikipedia.org的某一個服務端提供服務,這樣就可以將流量路由到任何*.wikipedia.org站點對應的IP(包括www.wikipedia.org)。
由於map.baidu.com和fanyi.baidu.com的服務並不是由www.baidu.com對應的某個IP服務的(可以使用
nslookup
或dig
命令查看),因此無法用於測試本場景,下面為官網內容。
一般情況下,如果一個通配符的所有域名不是由一個托管服務器提供服務的,則需要更復雜的配置。
單個主機服務器的通配符配置
當一個服務端服務所有的通配符主機時,對使用egress網關訪問通配符主機的配置與訪問非通配符主機的配置類似。
-
為
*.wikipedia.org,
創建一個egressGateway
,destination rule和一個virtual service,將流量導入egress網關,並通過egress網關訪問外部服務$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: istio-egressgateway spec: selector: istio: egressgateway servers: - port: number: 443 name: https protocol: HTTPS hosts: - "*.wikipedia.org" tls: mode: PASSTHROUGH #由網格內部發起https請求,非終結TLS --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: egressgateway-for-wikipedia spec: host: istio-egressgateway.istio-system.svc.cluster.local subsets: - name: wikipedia --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: direct-wikipedia-through-egress-gateway spec: hosts: - "*.wikipedia.org" gateways: - mesh - istio-egressgateway tls: #網格內部的TLS流量處理 - match: - gateways: - mesh port: 443 sniHosts: - "*.wikipedia.org" route: - destination: host: istio-egressgateway.istio-system.svc.cluster.local subset: wikipedia port: number: 443 weight: 100 - match: - gateways: - istio-egressgateway port: 443 sniHosts: - "*.wikipedia.org" route: - destination: host: www.wikipedia.org #將流量從網格傳給外部服務。由於www.wikipedia.org可以處理不同語言的請求,因此在下面才能在用curl請求不同語言的站點時正確返回結果。 port: number: 443 weight: 100 EOF
-
為目的服務www.wikipedia.com創建
ServiceEntry
。由於出站流量被--set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY
阻塞,因此需要配置ServiceEntry指定外部服務,這樣也便於監控流量。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: www-wikipedia spec: hosts: - www.wikipedia.org ports: - number: 443 name: https protocol: HTTPS resolution: DNS #支持DNS和靜態IP EOF
-
發送請求給https://map.baidu.com/和https://fanyi.baidu.com/:
$ kubectl exec "$SOURCE_POD" -c sleep -- sh -c 'curl -s https://en.wikipedia.org/wiki/Main_Page | grep -o "<title>.*</title>"; curl -s https://de.wikipedia.org/wiki/Wikipedia:Hauptseite | grep -o "<title>.*</title>"' <title>Wikipedia, the free encyclopedia</title> <title>Wikipedia – Die freie Enzyklopädie</title>
-
校驗egress gateway的日志
$ kubectl exec "$(kubectl get pod -l istio=egressgateway -n istio-system -o jsonpath='{.items[0].metadata.name}')" -c istio-proxy -n istio-system -- pilot-agent request GET clusters | grep '^outbound|443||www.wikipedia.org.*cx_total:' outbound|443||www.wikipedia.org::208.80.154.224:443::cx_total::2
卸載
$ kubectl delete serviceentry www-wikipedia
$ kubectl delete gateway istio-egressgateway
$ kubectl delete virtualservice direct-wikipedia-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-wikipedia
任意域的通配符配置
上一節中的配置之所以能夠生效,是因為任何一個wikipedia.org服務端都可以服務所有的*.wikipedia.org站點。但情況不總是這樣的,例如有可能希望訪問更加通用的域,如.com
或.org
。
在istio網關上配置到任意通配符的域會帶來挑戰,上一節中直接將流量傳遞給了 www.wikipedia.org(直接配置到了網關上)。受限於Envoy(默認的istio egress網關代理),網關並不知道接收到的請求中的任意主機的IP地址。Envoy會將流量路由到預定義的主機,預定義的IP地址或請求中的原始目的IP地址。在網關場景下,由於請求會首先被路由到egress網關上,因此會丟失請求中的原始目的IP地址,並將目的IP地址替換為網關的IP地址,最終會導致基於Envoy的istio網關無法路由到沒有進行預配置的任意主機,進而導致無法為任意通配符域執行流量控制。
為了給HTTPS和TLS啟用流量控制,需要額外部署一個SNI轉發代理。Envoy會將到通配符域的請求路由到SNI轉發代理,然后將請求轉發到SNI中指定的目的地。
使用SNI代理和相關組件的egress網關架構如下,由於Envoy無法處理任意通配符的主機,因此需要轉發到SNI代理上進行SNI的路由處理。
下面將展示如何重新部署egress網關來使用SNI代理,並配置istio通過網關路由HTTPS流量到任意通配符域。
配置egress網關的SNI代理
本節中將在標准的istio Envoy代理之外部署為egress網關部署一個SNI代理。本例中使用Nginx作為SNI代理,該SNI代理將會監聽8443端口,然后將流量轉發到443端口。
-
為Nginx SNI代理創建配置文件。注意
server
下的listen
指令指定了端口8443
,proxy_pass
指令使用ssl_preread_server_name
,端口443
以及將ssl_preread
設置為on
來啟用SNI
reading。$ cat <<EOF > ./sni-proxy.conf user www-data; events { } stream { log_format log_stream '\$remote_addr [\$time_local] \$protocol [\$ssl_preread_server_name]' '\$status \$bytes_sent \$bytes_received \$session_time'; access_log /var/log/nginx/access.log log_stream; error_log /var/log/nginx/error.log; # tcp forward proxy by SNI server { resolver 8.8.8.8 ipv6=off; listen 127.0.0.1:8443; proxy_pass \$ssl_preread_server_name:443; ssl_preread on; } } EOF
-
創建一個kubernets ConfigMap來保存Nginx SNI代理的配置:
$ kubectl create configmap egress-sni-proxy-configmap -n istio-system --from-file=nginx.conf=./sni-proxy.conf
-
創建一個
IstioOperator
CR來添加帶SNI代理的新的egress網關。# cat egressgateway-with-sni-proxy.yaml apiVersion: install.istio.io/v1alpha1 kind: IstioOperator spec: components: egressGateways: - name: istio-egressgateway-with-sni-proxy enabled: true label: app: istio-egressgateway-with-sni-proxy istio: egressgateway-with-sni-proxy k8s: service: ports: - port: 443 name: https cni: enabled: true namespace: kube-system values: meshConfig: certificates: - secretName: dns.example1-service-account dnsNames: [example1.istio-system.svc, example1.istio-system] - secretName: dns.example2-service-account dnsNames: [example2.istio-system.svc, example2.istio-system] cni: excludeNamespaces: - istio-system - kube-system chained: false cniBinDir: /var/lib/cni/bin cniConfDir: /etc/cni/multus/net.d cniConfFileName: istio-cni.conf sidecarInjectorWebhook: injectedAnnotations: "k8s.v1.cni.cncf.io/networks": istio-cni
-
部署新的網關
# istioctl install -f egressgateway-with-sni-proxy.yaml --set values.global.istioNamespace=istio-system --set values.gateways.istio-egressgateway.enabled=true --set meshConfig.outboundTrafficPolicy.mode=REGISTRY_ONLY --set meshConfig.accessLogFile="/dev/stdout" --set values.gateways.istio-egressgateway.runAsRoot=true
-
給Pod
istio-egressgateway-with-sni-proxy
patch一個SNI代理容器$ cat <<EOF > ./egressgateway-with-sni-proxy-patch.yaml spec: template: spec: volumes: - name: sni-proxy-config configMap: name: egress-sni-proxy-configmap defaultMode: 292 # 0444 containers: - name: sni-proxy image: nginx volumeMounts: - name: sni-proxy-config mountPath: /etc/nginx readOnly: true securityContext: runAsNonRoot: false runAsUser: 0 EOF
# kubectl patch deployment istio-egressgateway-with-sni-proxy -n istio-system --patch "$(cat ./egressgateway-with-sni-proxy-patch.yaml)" deployment.extensions/istio-egressgateway-with-sni-proxy patched
-
校驗新的egress網關已經在運行,且該pod有2個容器(一個是Envoy代理,另一個是SNI代理)
# kubectl get pod -l istio=egressgateway-with-sni-proxy -n istio-system NAME READY STATUS RESTARTS AGE istio-egressgateway-with-sni-proxy-b79df9c8c-brfbw 2/2 Running 0 2m4s
-
創建一個service entry,靜態地址為127.0.0.1,並禁用mutual TLS。
# kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: sni-proxy spec: hosts: - sni-proxy.local location: MESH_EXTERNAL #將SNI代理設置為外部服務,不啟用mTLS ports: - number: 8443 name: tcp protocol: TCP resolution: STATIC endpoints: - address: 127.0.0.1 --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: disable-mtls-for-sni-proxy spec: host: sni-proxy.local #配置到sni-proxy.local的規則,禁用mTLS trafficPolicy: tls: mode: DISABLE EOF
配置流量通過帶SNI代理的Egress網關
-
為
*.baidu.com
定義一個ServiceEntry
$ cat <<EOF | kubectl create -f - apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: baidu spec: hosts: - "*.baidu.com" ports: - number: 443 name: tls protocol: TLS EOF
-
為 *.baidu.com,端口443創建一個egress
Gateway
,以及virtual service將流量通過網關導入*.baidu.com# kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: istio-egressgateway-with-sni-proxy spec: selector: istio: egressgateway-with-sni-proxy servers: #配置監聽"*.baidu.com",並啟用mTLS - port: number: 443 name: tls-egress protocol: TLS hosts: - "*.baidu.com" tls: mode: ISTIO_MUTUAL --- apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: egressgateway-for-baidu spec: host: istio-egressgateway-with-sni-proxy.istio-system.svc.cluster.local #配置在端口443上啟用mTLS subsets: - name: baidu trafficPolicy: loadBalancer: simple: ROUND_ROBIN portLevelSettings: - port: number: 443 tls: mode: ISTIO_MUTUAL --- apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: direct-baidu-through-egress-gateway spec: hosts: - "*.baidu.com" gateways: - mesh - istio-egressgateway-with-sni-proxy tls: - match: #將網格內部發往"*.baidu.com:443"的流量定向到istio-egressgateway-with-sni-proxy.istio-system.svc.cluster.local:443 - gateways: - mesh port: 443 sniHosts: - "*.baidu.com" route: - destination: host: istio-egressgateway-with-sni-proxy.istio-system.svc.cluster.local subset: baidu port: number: 443 weight: 100 tcp: - match: #將網關istio-egressgateway-with-sni-proxy上443端口的流量定向到sni-proxy.local:8443 - gateways: - istio-egressgateway-with-sni-proxy port: 443 route: - destination: host: sni-proxy.local port: number: 8443 weight: 100 --- # The following filter is used to forward the original SNI (sent by the application) as the SNI of the # mutual TLS connection. # The forwarded SNI will be will be used to enforce policies based on the original SNI value. apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: forward-downstream-sni spec: configPatches: - applyTo: NETWORK_FILTER match: context: SIDECAR_OUTBOUND listener: portNumber: 443 filterChain: filter: name: istio.stats patch: operation: INSERT_BEFORE value: name: forward_downstream_sni config: {} EOF
-
給網關添加一個EnvoyFilter,防止欺騙
# kubectl apply -n istio-system -f - <<EOF # The following filter verifies that the SNI of the mutual TLS connection is # identical to the original SNI issued by the client (the SNI used for routing by the SNI proxy). # The filter prevents the gateway from being deceived by a malicious client: routing to one SNI while # reporting some other value of SNI. If the original SNI does not match the SNI of the mutual TLS connection, # the filter will block the connection to the external service. apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: egress-gateway-sni-verifier spec: workloadSelector: labels: app: istio-egressgateway-with-sni-proxy configPatches: - applyTo: NETWORK_FILTER match: context: GATEWAY listener: portNumber: 443 filterChain: filter: name: istio.stats patch: operation: INSERT_BEFORE value: name: sni_verifier config: {} EOF
-
發送HTTPS請求到 https://map.baidu.com和https😕/fanyi.baidu.com:
# kubectl exec "$SOURCE_POD" -c sleep -- sh -c 'curl -s https://map.baidu.com/ | grep -o "<title>.*</title>"; curl -s https://fanyi.baidu.com/ | grep -o "<title>.*</title>"' <title>百度地圖</title> <title>百度翻譯-200種語言互譯、溝通全世界!</title>
-
校驗egress網關的Envoy代理的日志
$ kubectl logs -l istio=egressgateway-with-sni-proxy -c istio-proxy -n istio-system
日志內容如下:
# kubectl logs -l istio=egressgateway-with-sni-proxy -c istio-proxy -n istio-system [2020-09-08T05:32:32.190Z] "- - -" 0 - "-" "-" 932 153941 622 - "-" "-" "-" "-" "127.0.0.1:8443" outbound|8443||sni-proxy.local 127.0.0.1:49018 10.83.0.193:443 10.80.3.61:43114 fanyi.baidu.com - [2020-09-08T05:37:46.853Z] "- - -" 0 - "-" "-" 780 166018 37481 - "-" "-" "-" "-" "127.0.0.1:8443" outbound|8443||sni-proxy.local 127.0.0.1:58212 10.83.0.193:443 10.80.3.61:53980 map.baidu.com -
-
校驗SNI代理的日志
# kubectl logs -l istio=egressgateway-with-sni-proxy -n istio-system -c sni-proxy 127.0.0.1 [08/Sep/2020:05:32:32 +0000] TCP [map.baidu.com]200 51689 232 630.131 127.0.0.1 [08/Sep/2020:05:32:32 +0000] TCP [fanyi.baidu.com]200 153941 415 0.622
卸載
$ kubectl delete serviceentry wikipedia
$ kubectl delete gateway istio-egressgateway-with-sni-proxy
$ kubectl delete virtualservice direct-wikipedia-through-egress-gateway
$ kubectl delete destinationrule egressgateway-for-wikipedia
$ kubectl delete --ignore-not-found=true envoyfilter forward-downstream-sni egress-gateway-sni-verifier
$ kubectl delete serviceentry sni-proxy
$ kubectl delete destinationrule disable-mtls-for-sni-proxy
$ kubectl delete -f ./istio-egressgateway-with-sni-proxy.yaml
$ kubectl delete configmap egress-sni-proxy-configmap -n istio-system
$ rm ./istio-egressgateway-with-sni-proxy.yaml
$ rm ./sni-proxy.conf
卸載
$ kubectl delete -f samples/sleep/sleep.yaml
Egress流量的kubernetes服務
kubernetes ExternalName services 和帶Endpoints的kubernetes services 允許為外部服務創建本地DNS別名,該DNS別名的格式與本地服務的DNS表項的格式相同,即 <service name>.<namespace name>.svc.cluster.local
。DNS別名為工作負載提供了位置透明性:負載可以通過這種方式調用本地和外部服務。如果某個時間需要在集群中部署外部服務,就可以通過更新該kubernetes service來引用本地版本。工作負載將繼續運行,不需要做任何改變。
本任務將展示istio如何使用這些kubernetes機制來訪問外部服務。不過此時必須使用TLS模式來進行訪問,而不是istio的mutual TLS。因為外部服務不是istio服務網格的一部分,因此不能使用istio mutual TLS,必須根據外部服務的需要以及負載訪問外部服務的方式來設置TLS模式。如果負載發起明文HTTP請求,但外部服務需要TLS,此時可能需要istio發起TLS。如果負載已經使用了TLS,那么流量已經經過加密,此時就可以禁用istio的mutual TLS。
本節描述如何將istio集成到現有kubernetes配置中
雖然本例使用了HTTP協議,但使用egress流量的kubernetes Services也可以使用其他協議。
部署
-
部署sleep應用並獲取POD名稱
$ kubectl apply -f samples/sleep/sleep.yaml $ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
-
創建一個不使用istio的命名空間
$ kubectl create namespace without-istio
-
在
without-istio
命名空間下啟用sleep
$ kubectl apply -f samples/sleep/sleep.yaml -n without-istio
-
創建一個環境變量
SOURCE_POD_WITHOUT_ISTIO
來保存without-istio
命名空間下的pod名稱$ export SOURCE_POD_WITHOUT_ISTIO="$(kubectl get pod -n without-istio -l app=sleep -o jsonpath={.items..metadata.name})"
-
校驗該pod沒有istio sidecar
# kubectl get pod "$SOURCE_POD_WITHOUT_ISTIO" -n without-istio NAME READY STATUS RESTARTS AGE sleep-f8cbf5b76-tbptz 1/1 Running 0 53s
通過kubernetes ExternalName service訪問外部服務
-
在
default
命名空間中為httpbin.org
創建一個kubernetes ExternalName service,將外服服務httpbin.org
映射為kubernetes服務my-httpbin
,即可以通過訪問my-httpbin.default.svc.cluster.local
來訪問my-httpbin
。$ kubectl apply -f - <<EOF kind: Service apiVersion: v1 metadata: name: my-httpbin spec: type: ExternalName externalName: httpbin.org ports: - name: http protocol: TCP port: 80 EOF
-
觀察service,可以看到並沒有cluster IP
# kubectl get svc my-httpbin NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-httpbin ExternalName <none> httpbin.org 80/TCP 3s
-
在源pod(不帶istio sidecar)中通過kubernetes的service主機名訪問
httpbin.org
。由於在網格中,因此不需要訪問時禁用istio 的mutual TLS。# kubectl exec "$SOURCE_POD_WITHOUT_ISTIO" -n without-istio -c sleep -- curl my-httpbin.default.svc.cluster.local/headers { "headers": { "Accept": "*/*", "Host": "my-httpbin.default.svc.cluster.local", "User-Agent": "curl/7.64.0", "X-Amzn-Trace-Id": "Root=1-5f485a71-54548d2e5f8b0dc1002e2ce0" } }
-
本例中,使用向
httpbin.org
發送了未加密的HTTP請求。為了簡單例子,下面禁用了TLS模式,允許向外部服務發送未加密的流量。在實際使用時,建議配置Egress TLS源,下面相當於將流量重定向到內部服務my-httpbin.default.svc.cluster.local
上,而my-httpbin.default.svc.cluster.local
映射了外部服務httpbin.org
,這樣就可以通過這種方式通過kubernetes service來訪問外部服務。$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: my-httpbin spec: host: my-httpbin.default.svc.cluster.local trafficPolicy: tls: mode: DISABLE EOF
-
在帶istio sidecar的pod中通過kubernetes service主機名訪問
httpbin.org
,注意isito sidecar添加的首部,如X-Envoy-Decorator-Operation
。同時注意Host
首部字段等於自己的service主機名。# kubectl exec "$SOURCE_POD" -c sleep -- curl my-httpbin.default.svc.cluster.local/headers { "headers": { "Accept": "*/*", "Content-Length": "0", "Host": "my-httpbin.default.svc.cluster.local", "User-Agent": "curl/7.64.0", "X-Amzn-Trace-Id": "Root=1-5f485d05-ac81b19dcee92359b5cae307", "X-B3-Sampled": "0", "X-B3-Spanid": "bee9babd29c28cec", "X-B3-Traceid": "b8260c4ba5390ed0bee9babd29c28cec", "X-Envoy-Attempt-Count": "1", "X-Envoy-Decorator-Operation": "my-httpbin.default.svc.cluster.local:80/*", "X-Envoy-Peer-Metadata": "ChoKCkNMVVNURVJfSUQSDBoKS3ViZXJuZXRlcwo2CgxJTlNUQU5DRV9JUFMSJhokMTAuODAuMi4yNixmZTgwOjozMGI4OmI3ZmY6ZmUxNDpiMjE0Ct4BCgZMQUJFTFMS0wEq0AEKDgoDYXBwEgcaBXNsZWVwChkKDGlzdGlvLmlvL3JldhIJGgdkZWZhdWx0CiAKEXBvZC10ZW1wbGF0ZS1oYXNoEgsaCWY4Y2JmNWI3NgokChlzZWN1cml0eS5pc3Rpby5pby90bHNNb2RlEgcaBWlzdGlvCioKH3NlcnZpY2UuaXN0aW8uaW8vY2Fub25pY2FsLW5hbWUSBxoFc2xlZXAKLwojc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtcmV2aXNpb24SCBoGbGF0ZXN0ChoKB01FU0hfSUQSDxoNY2x1c3Rlci5sb2NhbAofCgROQU1FEhcaFXNsZWVwLWY4Y2JmNWI3Ni13bjlyNwoWCglOQU1FU1BBQ0USCRoHZGVmYXVsdApJCgVPV05FUhJAGj5rdWJlcm5ldGVzOi8vYXBpcy9hcHBzL3YxL25hbWVzcGFjZXMvZGVmYXVsdC9kZXBsb3ltZW50cy9zbGVlcAoaCg9TRVJWSUNFX0FDQ09VTlQSBxoFc2xlZXAKGAoNV09SS0xPQURfTkFNRRIHGgVzbGVlcA==", "X-Envoy-Peer-Metadata-Id": "sidecar~10.80.2.26~sleep-f8cbf5b76-wn9r7.default~default.svc.cluster.local" } }
卸載
$ kubectl delete destinationrule my-httpbin
$ kubectl delete service my-httpbin
使用帶endpoints的kubernetes service訪問一個外部服務
-
為
map.baidu.com
創建一個kubernetes service,不帶selector$ kubectl apply -f - <<EOF kind: Service apiVersion: v1 metadata: name: my-baidu-map spec: ports: - protocol: TCP port: 443 name: tls EOF
-
為外部服務手動創建endpoints,IP來自
map.baidu.com
后端地址。此時可以通過kubernetes service直接訪問外部服務# nslookup map.baidu.com Server: 100.100.2.136 Address: 100.100.2.136#53 Non-authoritative answer: map.baidu.com canonical name = map.n.shifen.com. Name: map.n.shifen.com Address: 180.101.49.69
$ kubectl apply -f - <<EOF kind: Endpoints apiVersion: v1 metadata: name: my-baidu-map subsets: - addresses: - ip: 180.101.49.69 ports: - port: 443 name: tls EOF
-
觀測上述service,可以通過其cluster IP訪問
map.baidu.com
# oc get svc my-baidu-map NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE my-baidu-map ClusterIP 10.84.20.176 <none> 443/TCP 116s
-
從不帶istio sidecar的pod中向
map.baidu.com
發送HTTPS請求。注意下面curl
在訪問map.baidu.com
時使用了--resolve
選項# kubectl exec "$SOURCE_POD_WITHOUT_ISTIO" -n without-istio -c sleep -- curl -s --resolve map.baidu.com:443:"$(kubectl get service my-baidu-map -o jsonpath='{.spec.clusterIP}')" https://map.baidu.com | grep -o "<title>.*</title>" <title>百度地圖</title>
-
這種情況下,負載會直接向
map.baidu.com
發送HTTPS請求,此時可以安全禁用istio的mutual TLS(當然也可以不禁用,此時不需要部署destinationRule)$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: my-baidu-map spec: host: my-baidu-map.default.svc.cluster.local trafficPolicy: tls: mode: DISABLE EOF
-
從帶istio sidecar的pod中訪問
map.baidu.com
# kubectl exec "$SOURCE_POD" -c sleep -- curl -s --resolve map.baidu.com:443:"$(kubectl get service my-baidu-map -o jsonpath='{.spec.clusterIP}')" https://map.baidu.com | grep -o "<title>.*</title>" <title>百度地圖</title>
-
校驗請求確實是通過cluster IP(
10.84.20.176
)進行訪問的。# kubectl exec "$SOURCE_POD" -c sleep -- curl -v --resolve map.baidu.com:443:"$(kubectl get service my-baidu-map -o jsonpath='{.spec.clusterIP}')" https://map.baidu.com -o /dev/null * Expire in 0 ms for 6 (transfer 0x562c95903680) * Added map.baidu.com:443:10.84.20.176 to DNS cache * Hostname map.baidu.com was found in DNS cache * Trying 10.84.20.176... * TCP_NODELAY set
卸載
$ kubectl delete destinationrule my-baidu-map
$ kubectl delete endpoints my-baidu-map
$ kubectl delete service my-baidu-map
卸載
$ kubectl delete -f samples/sleep/sleep.yaml
$ kubectl delete -f samples/sleep/sleep.yaml -n without-istio
$ kubectl delete namespace without-istio
$ unset SOURCE_POD SOURCE_POD_WITHOUT_ISTIO
使用外部HTTPS代理
在前面配置Egress網關的例子中展示了如何通過istio邊界組件Egress網關將流量轉發到外部服務中。然而,但是,有些情況下需要外部的、遺留的(非Istio)HTTPS代理來訪問外部服務。例如,公司可能已經部署了一個代理,所有組織中的應用都必須通過該代理來轉發流量。
本例展示如何通過外部代理轉發流量。由於所有的由於都會使用HTTP CONNECT方法來與HTTPS代理建立連接,配置流量到一個外部代理不同於配置流量到外部HTTP和HTTPS服務。
部署
創建sleep應用並獲取POD名稱
$ kubectl apply -f samples/sleep/sleep.yaml
$ export SOURCE_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
部署一個HTTPS代理
為了模擬一個遺留的代理,需要在集群中部署HTTPS代理。為了模擬在集群外部運行的更真實的代理,需要通過代理的IP地址而不是Kubernetes服務的域名來定位代理的pod。本例使用Squid,但也可以使用其他HTTPS代理來支持HTTP CONNECT。
-
為HTTPS代理創建一個命名空間,不啟用istio sidecar自動注入。使用這種方式來模擬集群外的代理。
$ kubectl create namespace external
-
創建Squid代理的配置文件
$ cat <<EOF > ./proxy.conf http_port 3128 acl SSL_ports port 443 acl CONNECT method CONNECT http_access deny CONNECT !SSL_ports http_access allow localhost manager http_access deny manager http_access allow all coredump_dir /var/spool/squid EOF
-
創建一個kubernetes ConfigMap來保存代理的配置
$ kubectl create configmap proxy-configmap -n external --from-file=squid.conf=./proxy.conf
-
部署Squid容器。注:openshift可能會因為scc導致權限錯誤,為方便測試,將容器設置為privileged權限
# oc adm policy add-scc-to-user privileged -z default
$ kubectl apply -f - <<EOF apiVersion: apps/v1 kind: Deployment metadata: name: squid namespace: external spec: replicas: 1 selector: matchLabels: app: squid template: metadata: labels: app: squid spec: serviceAccount: default volumes: - name: proxy-config configMap: name: proxy-configmap containers: - name: squid image: sameersbn/squid:3.5.27 imagePullPolicy: IfNotPresent securityContext: privileged: true volumeMounts: - name: proxy-config mountPath: /etc/squid readOnly: true EOF
-
在external命名空間中創建sleep應用來測試到代理的流量(不受istio控制)
$ kubectl apply -n external -f samples/sleep/sleep.yaml
-
獲取代理pod的地址並定義在
PROXY_IP
環境變量中$ export PROXY_IP="$(kubectl get pod -n external -l app=squid -o jsonpath={.items..podIP})"
-
定義
PROXY_PORT
環境變量來保存代理的端口,即Squid使用的端口3128$ export PROXY_PORT=3128
-
從external命名空間中的sleep Pod中通過代理向外部服務發送請求:
# kubectl exec "$(kubectl get pod -n external -l app=sleep -o jsonpath={.items..metadata.name})" -n external -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl https://map.baidu.com" | grep -o "<title>.*</title>" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0<title>百度地圖</title> 100 154k 0 154k 0 0 452k 0 --:--:-- --:--:-- --:--:-- 452k
-
檢查代理的訪問日志:
# kubectl exec "$(kubectl get pod -n external -l app=squid -o jsonpath={.items..metadata.name})" -n external -- tail /var/log/squid/access.log 1598596320.477 342 10.80.2.81 TCP_TUNNEL/200 165939 CONNECT map.baidu.com:443 - HIER_DIRECT/180.101.49.69 -
現在,完成了如下兩個於istio無關的任務:
- 部署了HTTPS 代理
- 通過代理訪問
map.baidu.com
下面將配置啟用istio的pod使用HTTPS代理。
配置流量到外部HTTPS代理
-
為HTTPS代理定義一個TCP(非HTTP) Service Entry。雖然應用會使用HTTP CONNECT方法來與HTTPS代理建立連接,但必須為代理配置TCP流量,而非HTTP。一旦建立連接,代理只是充當一個TCP隧道。
$ kubectl apply -f - <<EOF apiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: proxy spec: hosts: - my-company-proxy.com # ignored addresses: - $PROXY_IP/32 #hosts字段的后端IP ports: - number: $PROXY_PORT name: tcp protocol: TCP location: MESH_EXTERNAL EOF
-
從default命名空間中的sleep Pod發送請求,由於該pod帶有sidecar,istio會對流量進行控制
# kubectl exec "$SOURCE_POD" -c sleep -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl https://map.baidu.com" | grep -o "<title>.*</title>" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0<title>百度地圖</title> 100 154k 0 154k 0 0 439k 0 --:--:-- --:--:-- --:--:-- 439k
-
檢查istio sidecar 代理的日志,可以看到對外訪問了my-company-proxy.com
# kubectl exec "$SOURCE_POD" -c sleep -- sh -c "HTTPS_PROXY=$PROXY_IP:$PROXY_PORT curl https://map.baidu.com" | grep -o "<title>.*</title>" [2020-08-28T12:38:10.064Z] "- - -" 0 - "-" "-" 898 166076 354 - "-" "-" "-" "-" "10.80.2.87:3128" outbound|3128||my-company-proxy.com 10.80.2.77:36576 10.80.2.87:3128 10.80.2.77:36574 - -
-
檢查代理的訪問日志,可以看到轉發了HTTP CONNECT請求
# kubectl exec "$(kubectl get pod -n external -l app=squid -o jsonpath={.items..metadata.name})" -n external -- tail /var/log/squid/access.log 1598618290.412 346 10.80.2.77 TCP_TUNNEL/200 166076 CONNECT map.baidu.com:443 - HIER_DIRECT/180.101.49.69 -
過程理解
在本例中完成了如下步驟:
- 部署一個HTTPS代理來模擬外部代理
- 創建TCP service entry來使istio控制的流量轉發到外部代理
注意不能為需要經過外部代理的外部服務(如map.baidu.com)創建service entry。這是因為從istio的角度看,這些請求僅會發送到外部代理,Istio並不知道外部代理會進一步轉發請求的事實。
卸載
$ kubectl delete -f samples/sleep/sleep.yaml
$ kubectl delete -f samples/sleep/sleep.yaml -n external
$ kubectl delete -n external deployment squid
$ kubectl delete -n external configmap proxy-configmap
$ rm ./proxy.conf
$ kubectl delete namespace external
$ kubectl delete serviceentry proxy