防火牆的詛咒3: 打穿好多個防火牆…
最近看了Netflix的咒術迴戰劇場版,發現原來咒術師原來不止要擺平宿儺手指,還有另一線劇情是要搞定可怕的理香。繼之前的防火牆詛咒2之後,我們IT咒術師通過GCE可以搭建SNAT快速拔除詛咒,我們又遇到了一個新的需求:有多個詛咒在地端,需要一次性的拔除。
需求:
地端有多個詛咒(防火牆通道),GKE內僅允許紫色的名稱空間的服務,連線到紫色的地端Backend服務1,深藍色的名稱空間連到深藍色Backend服務2,淺藍色的名稱空間僅能連到淺藍色的Backend服務3。這個該如何實現呢?
方案1:
如果知道GKE on-premise (a.k.a Anthos)的捧油或是使用過Cilium 的捧油可能很直覺的就會說,不是有Egress NAT Gateway可以達成這個功能嗎?是的,這也是為何我把這個當成方案1,在這個方案底下通過定義EgressNATPolicy,我們可以快速定義由哪些Namespace,甚至哪些帶著Label的Pod,請求哪個服務位置時,可以使用對應的NetworkGatewayGroup,在NetworkGatewayGroup中,定義對應的Floating IP,完成SNAT的設定。
kind: EgressNATPolicy
apiVersion: networking.gke.io/v1
metadata:
name: egress
spec:
sources:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: frontend
- namespaceSelector:
matchLabels:
user: paul
podSelector:
matchLabels:
role: frontend
action: SNAT
destinations:
- cidr: 8.8.8.0/24
gatewayRef:
name: default
namespace: kube-system
---
kind: NetworkGatewayGroup
apiVersion: networking.gke.io/v1
metadata:
namespace: kube-system
name: default
spec:
floatingIPs:
- 192.168.1.100
- 192.168.1.101
- 192.168.1.102
status:
nodes:
worker1: Up
worker2: Up // Or Down
floatingIPs:
192.168.1.100: worker1
192.168.1.101: worker2
192.168.1.102: worker1
這個直覺且簡易的架構圖說明如下,在設定完EgressNATPolicy之後ebpf會將Pod出來的封包forward到帶著NAT Gateway Pod的Worker Node節點,通過對應NetworkGatewayGroup的設定,將原始封包的SNAT為對應的IP (e.g. 192.168.1.100如下圖)。
方案2: (for GKE on GCP)
這麼棒的方案1,在筆者寫blog的今天(2022–12–10)在GKE on GCP無法使用的,主要是Dataplane-v2 不支援EgressNATPolicy的(喂,說好的功能切齊呢?)。所以我們來延伸一下之前的那一篇,再加上分Namespace的SNAT能力。這個工作主要分為三個部分:
(1) SNAT部分:由於我們有三個詛咒要拔除,我們要設定三組SNAT 伺服器Pair,並設定Static Route將三個網段的Worker Node Pool,各自導入一組SNAT Server Pair,詳細設定請參考上一篇單詛咒的拔除。
(2) 部署Istio Egress Gateway Pod:雖然說我們可以將Namespace Pod通過NodeSelector限制在對應的Worker Node Pool中,通過SNAT完成對防火牆的聯通,但要對應Namespace的Pod數量過多,或是Node個數分配不均,都會造成不便。因此我們設計,在不影響部署服務的YAML檔之下,通過istio的sidecar設定,更改名稱空間內的服務封包路由,強迫通過對應的Istio Egress Gateway Pod回到地端,而每個Istio Egress Pod會住在不同的Worker NodePool上,這個動作需先依照範例部署ASM (我是使用Managed ASM),然後再依序部署Egress Gateway Pod如下(特別注意,每個Deployment需使用NodeSelector部署在對應的WorkerNodePool上),本範例部署了2個:
apiVersion: v1
kind: ServiceAccount
metadata:
name: istio-egressgateway
namespace: mb
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: istio-egressgateway-mb
namespace: mb
spec:
selector:
matchLabels:
app: istio-egressgateway-mb
istio: egressgateway-mb
template:
metadata:
annotations:
# This is required to tell Anthos Service Mesh to inject the gateway with the
# required configuration.
inject.istio.io/templates: gateway
labels:
app: istio-egressgateway-mb
istio: egressgateway-mb
spec:
containers:
- name: istio-proxy
image: auto # The image will automatically update each time the pod starts.
resources:
limits:
cpu: 2000m
memory: 1024Mi
requests:
cpu: 100m
memory: 128Mi
serviceAccountName: istio-egressgateway
nodeSelector:
cloud.google.com/gke-nodepool: default-node-pool
---
apiVersion: v1
kind: Service
metadata:
name: istio-egressgateway-mb
namespace: mb
labels:
app: istio-egressgateway-mb
istio: egressgateway-mb
spec:
ports:
# Any ports exposed in Gateway resources should be exposed here.
- name: http2
port: 80
- name: https
port: 443
selector:
istio: egressgateway-mb
app: istio-egressgateway-mb
(3) 強制SideCar服務導流:通過指定外部服務(ServiceEntry),與對應的VirtualService與DestinationRule,我們可以強制將地端服務的流量,導入上一步的Pod中,詳細設定如下:
# 加上Auto-Injection的Label (istio.io/rev: asm-managed) 在名稱空間
apiVersion: v1
kind: Namespace
metadata:
name: mb
labels:
istio.io/rev: asm-managed
# Pod 我們使用一個ubuntu pod模擬前台服務。
apiVersion: v1
kind: Pod
metadata:
name: mobilebank
namespace: mb
labels:
name: mobilebank
spec:
containers:
- name: mobilebank
image: ubuntu:latest
command: ["/bin/bash"]
args: ["-c", "apt-get update && apt-get install -y curl jq && sleep infinity"]
resources:
limits:
memory: "128Mi"
cpu: "500m"
nodeSelector:
cloud.google.com/gke-nodepool: default-node-pool-2
---
# 設定外部服務的Domain Name
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: mobilebank-db
namespace: mb
spec:
hosts:
- api.myip.com
ports:
- number: 443
name: https
protocol: HTTPS
resolution: DNS
---
# 設定EgressGateway, 選擇Label為istio: egressgateway-nb的Pod導出
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-egressgateway-mb
namespace: mb
spec:
selector:
istio: egressgateway-mb
servers:
- port:
number: 443
name: tls
protocol: TLS
tls:
mode: PASSTHROUGH
hosts:
- '*'
---
#VirtualService 主要提供流量規則,在這裡使用match: gateway=mesh意指所有sidecar進入的流量,
#都應被導入istio-egressgateway-mb。通過exportTo 限制本規則僅施行於mb namespace
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: mb-from-egress
namespace: mb
spec:
hosts:
- api.myip.com
gateways:
- mesh #all sidecar traffic inside mesh
- istio-egressgateway-mb
exportTo: #限制virtualservice scope
- mb
tls:
- match:
- gateways:
- mesh
port: 443
sniHosts:
- "api.myip.com"
route:
- destination:
host: istio-egressgateway-mb
port:
number: 443
- match:
- gateways:
- istio-egressgateway-mb
port: 443
sniHosts:
- "api.myip.com"
route:
- destination:
host: api.myip.com
port:
number: 443
weight: 100
展示
實際展示部分,我們主要測試方法2的第(2)(3)部分,我們產生了兩個Worker Pools,並在每個Pool各創建了一個Istio-EgressGateway Pod。再通過(3)的VirtualService, ServiceEntry與Gateway,強迫同個namespace的Pod流量通過同一個Istio-EgressGateway連出,為了驗證實際是由對應的Node流出的,我們故意以 https://api.myip.com 為destination,其回應的訊息中會帶著呼叫者的來源IP位置。
後記
最近ChatGPT爆紅,想到來試試看看ChatGPT懂不懂Istio EgressGateway。
好險短期內還沒有失業的風險。OpenAI告訴我們的結果,看起來還是Ingress gateway的範例寫法,不過已經看到有重要的Gateway跟VirtualService的成分了,如果連電腦都能寫跟解釋Gateway, VirtualService,我們大家都要加油囉!