如何簡化 Kubernetes 出入向流量管理

NGINX Plus同時整合了NGINX Ingress Controller 和 NGINX Service Mesh,也就是將Service Mesh和Ingress Controller兩個功能在一個元件上完成,通過這個整合成功能可以同時控制出入向的mTLS流量,這樣不僅可以避免分開配置引起的問題,也會節省時間。


Kubernetes出入向的流量管理可以通過Service Mesh和Ingress Controller兩個功能來實現,這兩個功能通常是分開進行配置的,這樣Kubernetes 環境的管理就會變得非常複雜。

這樣的配置不僅會增加延遲,同時還會增加配置錯誤的概率,進而阻礙適當的流量路由,甚至導致出現安全漏洞(例如攻擊者獲得過多的應用訪問許可權)和不好的使用體驗(例如客戶無法訪問其授權訪問的應用)。 另外分開配置也會消耗更多的配置時間,出現錯誤的時候還需要更多的時間來處理。

NGINX Plus同時整合了NGINX Ingress Controller 和 NGINX Service Mesh,也就是將Service Mesh和Ingress Controller兩個功能在一個元件上完成,通過這個整合成功能可以同時控制出入向的mTLS流量,這樣不僅可以避免分開配置引起的問題,也會節省時間。

下面通過幾個章節進行說明:

  • 先決條件
  • 使用 NGINX Service Mesh 部署 NGINX Ingress Controller
  • 使用標準 Kubernetes Ingress 資源發佈應用
  • 使用 NGINX Virtual Server 資源發佈應用
  • 使用 NGINX Ingress Controller 配置安全出向路由

先決條件

在開始演示之前,我們需要具備以下前提條件

・在 Kubernetes 集群中安裝 NGINX Server Mesh 控制平面,併為Service Mesh設置 mTLS 和strict 策略。
・在 Kubernetes 集群中安裝基於 NGINX Plus 的 NGINX Ingress Controller(使用 Deployment ,而不要使用DaemonSet),啟用出向 (egress) 功能,並將其作為LoadBalancer 類型的服務進行發佈。
注:本演示不適用於 NGINX 開源版NGINX Ingress Controller。 為方便閱讀,我們在後文把基於 NGINX Plus 的 NGINX Ingress Controller 簡稱為“NGINX Ingress Controller”。
・根據我們的部署文檔完成三個內容:下載bookinfo 示例應用,注入 NGINX Service Mesh sidecar,部署該應用。

由於我們在第 1 步中設置了strict 策略,sidecar 會拒絕來自Service Mesh外用戶端的bookinfo 應用請求。 為了解決這個問題,我們需要在演示中設置埠轉發,具體命令如下:

> kubectl port-forward svc/product-page 9080
Forwarding from 127.0.0.1:9080 -> 9080
Forwarding from [::1]:9080 -> 9080
Handling connection for 9080

當我們試圖訪問該應用時,會得到狀態碼503,因為我們的本地設備不在Service Mesh中:

> curl localhost:9080
503

使用 NGINX Service Mesh 
部署 NGINX Ingress Controller

在發佈應用的第一階段,我們需要部署一個 NGINX Ingress Controller 實例。

NGINX 為此提供了 Deployment 和 Daemonset的manifest檔。 本演示中使用的是Deployment manifest,檔為:nginx-plus-ingress.yaml。 

它包括了下面的annotations,配置這個annotations的NGINX Ingress Controller會同時實現入向流量和出向流量的管理:

nsm.nginx.com/enable-ingress: “true”
nsm.nginx.com/enable-egress: “true”
查看在 GitHub 托管的nginx-plus-ingress.yaml

該 manifest 直接整合了 NGINX Ingress Controller 和 Spire,Spire是NGINX Service Mesh 的證書頒發機構 (CA),因此NGINX Service Mesh sidecar不需要注入 NGINX Ingress Controller。

NGINX Ingress Controller 將直接從 Spire CA 獲取證書和秘鑰,用於Service Mesh中 pod 的 mTLS。 該 manifest 指定了 Spire 的代理位址:

args:
     – -spire-agent-address=/run/spire/sockets/agent.sock
查看在 GitHub 托管的nginx-plus-ingress.yaml

並將 Spire 代理的 UNIX 套接字安裝到 NGINX Ingress Controller pod:

volumes:
     – hostPath:
          path: /run/spire/sockets
          type: DirectoryOrCreate
         name: spire-agent-socket
查看在 GitHub 托管的nginx-plus-ingress.yaml

關於 manifest,最後需要注意的一點是-enable-internal-routes CLI 參數,該參數用於出向路由服務:

args:
     – -enable-internal-routes
查看在 GitHub 托管的nginx-plus-ingress.yaml

在開始演示之前,我們通過運行kubectl apply -f nginx-plus-ingress.yaml 命令來安裝 NGINX Ingress Controller,然後檢查一下 nginx-ingress 命名空間中的部署情況。 

如下面輸出結果中的READY 列所示,NGINX Ingress Controller pod 中只有一個容器,是因為我們還沒有為其注入 NGINX Service Mesh sidecar。 

此外,我們還部署了LoadBalancer 類型的服務,以便將 NGINX Ingress Controller 的外部 IP 位址(此處為 35.233.133.188)發佈到整合外部。 後續我們將訪問位於該IP位址的bookinfo示例應用。 

> kubectl get pods --namespace=nginx-ingress
NAME   READY   STATUS    RESTARTS   AGE
pod/nginx-ingress-867f954b8f0fzdrm   1/1   Running 0 3d3hNAME 
TYPE CLUSTER-IP  EXTERNAL-IP      ...
service-nginx-ingress   LoadBalancer   10.31.245.207   35.233.133.188   ...      
... PORT(S) AGE      
... 80:31469/TCP,443:32481/TCP   4d2h
...

使用標準 Kubernetes Ingress 資源
發佈應用

現在,我們使用 bookinfo-ingress.yaml 中定義的標準 Kubernetes Ingress 資源在Service Mesh中發佈bookinfo 應用。 

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: bookinfo-ingress
spec:  
ingressClassName: nginx # use only with k8s version >= 1.18.0  
tls:  
- hosts:  
  - bookinfo.example.com  
  secretName: bookinfo-secret  
rules:
  - host: bookinfo.example.com
    http: 
     paths: 
     - path: / 
       backend:  
        serviceName: productpage  
        servicePort: 9080
查看在 GitHub 托管的bookinfo-ingress.yaml

在bookinfo-ingress.yaml的第10行引用了Kubernetes Secret,這個配置提供了路由規則,該規則將對bookinfo.example.com 的請求轉發給productpage 服務(該配置在第11–18 行)。

該Kubernetes Secret 在bookinfo-secret.yaml 中進行了定義:

apiVersion: v1
kind: Secret
metadata:
name: bookinfo-secret
type: kubernetes.io/tls
data:
tls.crt:LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMl
查看在 GitHub 托管的bookinfo-ingress.yaml

我們執行以下命令,可以載入金鑰和證書(本演示中為自簽名證書):

> kubectl apply -f bookinfo-secret.yaml
secret/bookinfo-secret unchanged

然後我們啟動 Ingress 資源:

> kubectl apply -f bookinfo-ingress.yaml
ingress.networking.k8s.io/bookinfo-ingress deleted

並驗證 Ingress Controller 是否添加了 Ingress 資源中定義的路由,輸出結果的最後一行的event就說明已經確認了這個資訊:

> kubectl describe ingress bookinfo-ingress
...
Events:
  Type  Reason   Age   From   Message
  ----    ------  ----  ----       -------
  Normal  AddedOrUpdated  5s    nginx-ingress-controller  Configuration for ...
        ...default/bookinfo-ingress was added or updated

在本演示中,使用瀏覽器通過訪問 https://bookinfo.example.com/ 來查看bookinfo 應用(在本機的/etc/hosts檔中,已經添加了域名和IP的映射,這裡的域名為bookinfo.example.com,IP為35.233.133.188)。 

頁面中“Book Reviews”部分的信息會根據對 bookinfo.yaml(下載)中定義的三個reviews服務版本的依次請求而發生週期性改變。 

接下來,我們開始檢查進入集群的流量。 首先運行 generate-traffic.sh 腳本,然後通過訪問 NGINX Ingress Controller 發佈的外網 IP 位址來請求productpage 服務,再運行nginx-meshctl top 命令來監控流量:

> nginxmesh-ctl top deploy/productpage-v1
Deployment  Direction  Resource  Success Rate P99 P90 
P50   ...
productpage-v1
                  To         details-v1     100.00%       3ms    3ms    2ms                   
To         reviews-v2     100.00%       99ms   90ms   20ms                 To         reviews-v3     100.00%       99ms   85ms   18ms                 To         reviews-v1     100.00%       20ms   17ms   9ms                 From       nginx-ingress  100.00%       192ms  120ms  38ms

... NumRequests      
... 14      
... 5      
... 5      
... 12

使用標準 NGINX VirtualServer 資源
發佈應用

下面我們來展示另一種發佈應用的方法,即使用 NGINX VirtualServer 資源發佈應用。 它是一種自定義NGINX Ingress Controller 資源,支援更複雜的流量處理,例如流量分割和基於內容的路由。

首先,我們刪除標準 Ingress 資源:

> kubectl delete -f bookinfo-ingress.yaml
ingress.networking.k8s.io “bookinfo-ingress” deleted

通過bookinfo-vs.yaml 檔使用Secret配置MTLS(第 7-8 行),這個Secret與bookinfo-ingress.yaml 中的Secret相同。 

在bookinfo-vs.yaml的第 9–12 行的配置將productpage 服務定義為上游服務,第 13–24 行的配置將bookinfo.example.com 的所有GET 請求發送到上游。 對於除GET 以外的 HTTP 方法,它將返回狀態代碼405。 

apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: bookinfo-vs
spec:
host: bookinfo.example.com
tls:
secret: bookinfo-secret
upstreams:
 - name: product
service: productpage
port: 9080
routes:
- path: /
matches:
- conditions:    
- variable: $request_method
value: GET
action:
pass: product
action:
return:
code: 405
body: "Method not allowed\n"
查看在 GitHub 托管的 bookinfo-vs.yaml

下面來建立 NGINX VirtualServer 資源:

> kubectl apply -f bookinfo-vs.yaml
virtualserver.kubernetes.nginx.org/bookinfo-vs created

然後執行與 Ingress 資源相同的步驟,即通過運行kubectl describe 命令來確認應用已經部署正常,然後通過瀏覽器訪問該應用。 還可以通過看該應用是否拒絕POST方法來驗證應用的正常運行,具體操作如下:

> curl -k -X POST https://bookinfo.example.com/
Method not allowed

使用NGINX Ingress Controller 
配置安全出向路由

現在我們展示如何通過 NGINX Ingress Controller 處理出向流量。

我們在bash.yaml中定義了一個簡單的bash pod ,並將其部署在default的命名空間中,然後在該命名空間中進行查看。  如下方輸出結果中的READY 列所示,我們已向其注入了 NGINX Service Mesh sidecar。 

> kubectl get all
NAME  READY  STATUS    RESTARTS   AGE
pod/bash-6ccb678958-zsgm7   2/2    Running   0   77s
NAME    TYPE    CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.31.240.1   <none>        443/TCP   4d2h
...

在一些其他案例中,您可能想要從 pod 中請求出向服務,請求的服務不屬於 NGINX Service Mesh提供的物件。 這些部署的服務示例位於:

  • 在整合外
  • 在另一個整合上
  • 在同一整合中,但未注入 NGINX Service Mesh sidecar

在本演示中,我們確定了示例的最終狀態。 我們在legacy 命名空間(不受 NGINX Service Mesh 控制,並禁用 NGINX Service Mesh sidecar 自動注入功能)中部署了一個應用,並只有一個 pod 運行。 

> kubectl get all --namespaces=legacy
NAME    READY  STATUS    RESTARTS   AGE
pod/target-5f7bcb96c6-km9lz   1/1    Running   0     27m
NAME    TYPE   CLUSTER-IP     EXTERNAL-IP  
PORT(S)   AGE
service/target-svc   ClusterIP   10.31.245.213   <none>    80/TCP,443/TCP   27m
...

由於我們為 NGINX Service Mesh 配置了一個strict mTLS 策略,因此,我們無法直接從bash pod 向目標服務發送請求,原因是他們之間沒有互相進行身份驗證。 如果我們堅持進行訪問的話,我們會得到狀態代碼503(如下圖所示):

> kubectl exec -it bash-6ccb678958-zsgm7 -c bash — curl target-svc.legacy
curl: (56) Recv failure: connection reset by peer
503command terminated with exit code 56

解決方案是 讓bash pod發送出向流量時,該流量通過NGINX Ingress Controller來發送,操作方式是取消bash.yaml 第 14-15 行的annotations:

#annotations:
#config.nsm.nginx.com/default-egress-allowed: “true” # uncomment to route egress traffic to NGINX Ingress Controller
查看在 GitHub 托管的 bash.yaml

然後應用新設定:

> kubectl apply -f bash.yaml
deployment.apps/bash configured

並驗證新的bash pod 是否已正常啟動:

> kubectl get pods
NAME                    READY  STATUS        RESTARTS   AGE
bash-678c8b4579-7sfml   2/2    Running       0          6s
bash-6ccb678958-zsgm7   2/2    Terminating   0          3m28s

現在,我們運行和前面一樣的kubectl exec 命令,使得bash pod 向目標服務發送請求,這樣會得到狀態代碼404,而不是503。 這表明bash 已經成功將請求發送到 NGINX Ingress Controller,但由於沒有定義路由,所以不知道將它轉發到哪裡。 

我們在 legacy-route.yaml 中使用以下 Ingress 資源創建所需的路由。 第 7 行配置的internal-route ,表示目標服務不會發佈給互聯網,而只發佈給 NGINX Service Mesh 中的工作負載。 

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: target-internal-route
namespace: legacy
annotations: 
nsm.nginx.com/internal-route: "true"
spec:
ingressClassName: nginx # use only with k8s version >= 1.18.0
tls: 
rules: 
- host: target-svc.legacy 
https: 
paths:
- path: /
backend: 
serviceName: target-svc
servicePort: 80
查看在 GitHub 托管的legacy-route.yaml

通過下列命令啟動新資源並確認 NGINX Ingress Controller 已添加相應的路由:

> kubectl apply -f legacy-route.yaml
ingress.networking.k8s.io/target-internal-route created
> kubectl describe ingress target-internal-route -n legacy...
Events:
  Type    Reason          Age   From      Message
  ----    ------          ----  ----    -------  
Normal  AddedOrUpdated  6s    nginx-ingress-controller  Configuration for ...   
     ...legacy/target-internal-route was added or updated

現在,當我們運行kubectl exec 命令時,就可以訪問目標服務了:

{“req”: {“method”: “GET”
                “url”: “/”,
                “host”: “target-svc.legacy”,
                “remoteAddr”: “10.28.2.76:56086”}}

通過 NGINX Ingress Controller 路由出向流量的優勢是,您可以準確控制從集群內部訪問的外部服務(僅指定義了路由的外部服務)。

最後一項演示內容是如何監控出向流量。 通過運行kubectl exec 命令來發送幾個請求,然後運行以下命令:

> nginxmesh-ctl top deploy/nginx-ingress -n nginx-ingress
Deployment      Direction  Resource  Success Rate  P99  P90  P50  NumRequests
nginx-ingress
   To   target    100.00%       1ms  1ms  1ms  9  
From       bash      100.00%       0ms  0ms  0ms  9

拒絕延遲
整合NGINX Service Mesh
與NGINX Ingress Controller

雖然許多服務Service mesh都提供出入向閘道選項,但NGINX還具有低延遲優勢。 大多數服務Service mesh都需要向Ingress Controller 注入 sidecar,因此流量需要額外一跳才能到達目的應用。 雖然延遲很小,但這額外一跳會影響用戶體驗,進而導致客戶流失。

NGINX Service Mesh 不會增加這樣的延遲,因為它不需要將 sidecar 注入到 NGINX Ingress Controller。 通過直接集成 Spire(服務Service mesh的證書授權中心),NGINX Ingress Controller 可以成為NGINX Service Mesh 的一部分。

NGINX Ingress Controller 只從 Spire 代理獲取證書和秘鑰,並使用它們參與 MTLS 與網狀 pod 的證書交換。

Kubernetes NGINX Ingress Controller 有兩個版本:NGINX 開源版和 NGINX Plus 版。

如要使用本文所述的使用 NGINX Service Mesh 部署 NGINX Ingress Controller,您必須使用 NGINX Plus 版(支援 30 天免費試用)。

NGINX Service Mesh 完全免費,您可立即下載並在 10 分鐘內完成部署! 有關使用方法,請查看我們的文檔,並通過 GitHub 告訴我們您的使用體驗。