接下來的篇章將踏入 Cilium Service Mesh,後面會想體驗 GatewayAPI 和 Cluster Mesh,但是實際踏進去會發現 Cilium Service Mesh 範疇很大,有一些東西其實我們以前就已經玩到了
這篇我希望先定義好 Cilium Service Mesh 是什麼,然後就會開始從一些好入手的進階功能玩起
先假設我們的系統現在是微服務架構,所以各個微服務都可以獨立部署和運作,各個微服務之間的要溝通就是靠網路在溝通,不像是傳統單體式架構所有的 function call 都在同一個 process 裡面。
如果微服務變得越來越多,在每個 Application 裡面的「對於網路通訊姿勢」都不太一樣就會開始有點麻煩,像是 Service A 和 Service B 的 Retry 邏輯 Timeout 都不太一樣。另一方面就是企業基於安全性會希望微服務之間的通訊要加密,所以會想導入 mTLS
另外我自己比較想玩的其實是金絲雀部署和流量分流,主要是我目前公司在 Disaster Recovery (DR ) 有一些的藍圖想完成以及想提升部署穩定性
我直接舉一個情境好了,來讓讀者感受一下我們有什麼藍圖想達成?現況有遭遇什麼問題:
如果能做到上面這畫面簡直美如畫啊…
這邊是真實的體悟,因為最近 AWS Singapore Region 的 Global Accelerator 其實有發生問題過,導致我們某些 Workload 有中斷…
Cilium 在官方文件有定義 Service Mesh
如果要用一段簡單的話來說,其實 Service Mesh 就是:
👉 將複雜的網路通訊邏輯從應用程式程式碼中抽離出來,下沉到基礎設施層進行統一管理,而不是散落在應用程式裡 。
老實說,我覺得在 Cilium 的世界裡,「Service Mesh」這個詞定義有點模糊,而且 Scope 很大。Cilium 已經跨出我原本認為的單純的 Service-to-Service 通訊範疇。
看完定義,Service Mesh 就是一種架構、一種設計模式,鐵人賽的學習過程中,其實我們都有在做 Service Mesh 定義中想做的事,因為我們確實有把網路通訊的邏輯抽出來統一管理!最直接的例子是 L7 CiliumNetworkPolicy,我們寫好 L7 CNP,真的做到不用改應用程式,就能控制 client Pod 必須帶特定 Request Header 才可以成功把流量送進 server Pod
如果將 Service Mesh 這種架構,拆成「功能面」來看,一個現代的 Service Mesh 通常會包含這幾個重點:
講完了定義,我們就來多玩一些好入手的進階功能,這裡來玩「流量分流」
我們現在要來實踐「網路通訊邏輯從應用程式程式碼中抽離出來,下沉到基礎設施層進行統一管理」這句話裡面的「基礎設施層進行統一管理」
至於在 Infra 層為什麼 Pod (也就是我們的 Application) 流量為什麼會在無形之中被 Redirec to Cilium Envoy 請回顧 [Day 17] 為什麼 Cilium 能支援 L7? Cilium Envoy 解密
我們將用到 EnvoyConfig 來管理流量,但是在這之前會需要安裝 CRD CiliumEnvoyConfig ,具體的 Config 是會被寫進去 CiliumEnvoyConfig
另外,也可以看一下目前有哪些 Cilium 的 API Resources:
$ kubectl api-resources | grep cilium
ciliumcidrgroups                    ccg                                 cilium.io/v2                      false        CiliumCIDRGroup
ciliumclusterwidenetworkpolicies    ccnp                                cilium.io/v2                      false        CiliumClusterwideNetworkPolicy
ciliumendpoints                     cep,ciliumep                        cilium.io/v2                      true         CiliumEndpoint
ciliumidentities                    ciliumid                            cilium.io/v2                      false        CiliumIdentity
ciliuml2announcementpolicies        l2announcement                      cilium.io/v2alpha1                false        CiliumL2AnnouncementPolicy
ciliumloadbalancerippools           ippools,ippool,lbippool,lbippools   cilium.io/v2                      false        CiliumLoadBalancerIPPool
ciliumnetworkpolicies               cnp,ciliumnp                        cilium.io/v2                      true         CiliumNetworkPolicy
ciliumnodeconfigs                                                       cilium.io/v2                      true         CiliumNodeConfig
ciliumnodes                         cn,ciliumn                          cilium.io/v2                      false        CiliumNode
ciliumpodippools                    cpip                                cilium.io/v2alpha1                false        CiliumPodIPPool
可以發現 CiliumEnvoyConfig 還尚未被註冊進去
現在就來透過 envoyConfig.enabled=true 來安裝 CiliumEnvoyConfig CRD,可以透過以下指令進行升級和安裝:
$ helm upgrade cilium cilium/cilium --version 1.18.1 \
    --namespace kube-system \
    --reuse-values \
    --set envoyConfig.enabled=true
# 重啟 operator 和 agent
$ kubectl -n kube-system rollout restart deployment/cilium-operator
$ kubectl -n kube-system rollout restart ds/cilium
安裝好之後,我們再次檢查看看有沒有 CiliumEnvoyConfig CRD,成功範例如下:
$ kubectl api-resources | grep cilium
ciliumcidrgroups                    ccg                                 cilium.io/v2                      false        CiliumCIDRGroup
ciliumclusterwideenvoyconfigs       ccec                                cilium.io/v2                      false        CiliumClusterwideEnvoyConfig
ciliumclusterwidenetworkpolicies    ccnp                                cilium.io/v2                      false        CiliumClusterwideNetworkPolicy
ciliumendpoints                     cep,ciliumep                        cilium.io/v2                      true         CiliumEndpoint
ciliumenvoyconfigs                  cec                                 cilium.io/v2                      true         CiliumEnvoyConfig
ciliumidentities                    ciliumid                            cilium.io/v2                      false        CiliumIdentity
ciliuml2announcementpolicies        l2announcement                      cilium.io/v2alpha1                false        CiliumL2AnnouncementPolicy
ciliumloadbalancerippools           ippools,ippool,lbippool,lbippools   cilium.io/v2                      false        CiliumLoadBalancerIPPool
ciliumnetworkpolicies               cnp,ciliumnp                        cilium.io/v2                      true         CiliumNetworkPolicy
ciliumnodeconfigs                                                       cilium.io/v2                      true         CiliumNodeConfig
ciliumnodes                         cn,ciliumn                          cilium.io/v2                      false        CiliumNode
ciliumpodippools                    cpip                                cilium.io/v2alpha1                false        CiliumPodIPPool
接下來,我們透過一個簡單的 canary 流量分流示範來體驗 Cilium Mesh 的運作。
先準備一個 bookstore 範例,包含兩個版本:v1 與 v2。
# bookstore-v1-v2.yaml
apiVersion: v1
kind: Service
metadata:
  name: bookstore
spec:
  selector:
    app: bookstore # 注意:Service 同時選擇了 v1 和 v2
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bookstore-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bookstore
      version: v1
  template:
    metadata:
      labels:
        app: bookstore
        version: v1
    spec:
      containers:
      - name: bookstore
        image: busybox:1.36
        ports:
        - containerPort: 8080
        # 使用 command 直接啟動一個極簡 web server
        command:
          - /bin/sh
          - -c
          - "while true; do { echo -e 'HTTP/1.1 200 OK\\n\\nResponse from v1'; } | nc -l -p 8080; done"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bookstore-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bookstore
      version: v2
  template:
    metadata:
      labels:
        app: bookstore
        version: v2
    spec:
      containers:
      - name: bookstore
        image: busybox:1.36
        ports:
        - containerPort: 8080
        command:
          - /bin/sh
          - -c
          - "while true; do { echo -e 'HTTP/1.1 200 OK\\n\\nResponse from v2'; } | nc -l -p 8080; done"
這裡會建立:
bookstore service,一定要注意這個 Service 直接同時選擇了 v1 和 v2接著請起一個 netshoot pod,並執行以下指令,這會連續調用 bookstore service:
for i in {1..20}; do kubectl exec netshoot -- curl -s http://bookstore:8080; done
正常來說執行上面指令後會看到 v1 和 v2 的 Response 是 50:50,因為現在我們還沒有實作流量分流
# bookstore-v1-v2.yaml
apiVersion: v1
kind: Service
metadata:
  name: bookstore
spec:
  selector:
    app: bookstore
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bookstore-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bookstore
      version: v1
  template:
    metadata:
      labels:
        app: bookstore
        version: v1
    spec:
      containers:
      - name: bookstore
        image: busybox:1.36
        ports:
        - containerPort: 8080
        command:
          - /bin/sh
          - -c
          - "while true; do { echo -e 'HTTP/1.1 200 OK\\n\\nResponse from v1'; } | nc -l -p 8080; done"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bookstore-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: bookstore
      version: v2
  template:
    metadata:
      labels:
        app: bookstore
        version: v2
    spec:
      containers:
      - name: bookstore
        image: busybox:1.36
        ports:
        - containerPort: 8080
        command:
          - /bin/sh
          - -c
          - "while true; do { echo -e 'HTTP/1.1 200 OK\\n\\nResponse from v2'; } | nc -l -p 8080; done"
接著我們來真正實作流量分流
原本我們是: 1 個 bookstore service 直接同時選擇了 v1 和 v2
但是我們現在又拆成兩個 service,所以請再幫我 apply 以下配置,這會創建 bookstore-v1 和 bookstore-v2 Service:
# bookstore-svc-v1-v2.yaml
apiVersion: v1
kind: Service
metadata:
  name: bookstore-v1
spec:
  selector:
    app: bookstore
    version: v1
  ports:
    - port: 8080
      targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: bookstore-v2
spec:
  selector:
    app: bookstore
    version: v2
  ports:
    - port: 8080
      targetPort: 8080
創建好 bookstore-v1 和 bookstore-v2 Services 後,接著需要建立對應的 Envoy 設定,讓流量以 90:10 的比例分配給 v1 / v2,這裡就是會創建 CiliumEnvoyConfig 資源:
apiVersion: cilium.io/v2
kind: CiliumEnvoyConfig   # Cilium 的 CRD,用來下發 Envoy 的 L7 設定
metadata:
  name: bookstore-canary
spec:
  services:
    - name: bookstore
      namespace: default  # 綁定到 Kubernetes Service (bookstore.default)
  backendServices:
    - name: bookstore-v1
      namespace: default  # 後端版本 v1
    - name: bookstore-v2
      namespace: default  # 後端版本 v2
  resources:              # 定義 Envoy 的完整配置(Listener、Route、Cluster)
    # (1) Listener 層:監聽進入 bookstore Service 的 HTTP 流量
    - "@type": type.googleapis.com/envoy.config.listener.v3.Listener
      name: bookstore-listener
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: bookstore-listener
                rds:
                  route_config_name: bookstore-route  # 使用下方定義的 route
                http_filters:
                  - name: envoy.filters.http.router  # 啟用 HTTP Router 過濾器
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
    # (2) Route 層:定義流量在 v1 / v2 之間的分配
    - "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration
      name: bookstore-route
      virtual_hosts:
        - name: bookstore-vh
          domains: ["*"]   # 匹配所有 domain
          routes:
            - match:
                prefix: "/"   # 匹配所有路徑
              route:
                weighted_clusters:  # 加權分流 (Canary Routing)
                  total_weight: 100
                  clusters:
                    - name: "default/bookstore-v1"
                      weight: 90   # 目前 90% 流量導向 v1
                    - name: "default/bookstore-v2"
                      weight: 10    # 10% 比例流量導向 v2,可逐步調整進行 canary rollout
                retry_policy:       # L7 層級的重試策略
                  retry_on: 5xx
                  num_retries: 2
                  per_try_timeout: 1s
    # (3) Cluster 層:定義後端服務連線與健康檢測行為
    - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
      name: "default/bookstore-v1"
      connect_timeout: 5s
      lb_policy: ROUND_ROBIN
      type: EDS  # Endpoints Discovery Service,由 Cilium 自動提供 backend endpoints
      outlier_detection:
        split_external_local_origin_errors: true
        consecutive_local_origin_failure: 2  # 若連續 2 次失敗,暫時移除該 endpoint
    - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
      name: "default/bookstore-v2"
      connect_timeout: 5s
      lb_policy: ROUND_ROBIN
      type: EDS
      outlier_detection:
        split_external_local_origin_errors: true
        consecutive_local_origin_failure: 2
最後測試看看分流結果:
for i in {1..20}; do kubectl exec netshoot -- curl -s http://bookstore:8080; done
你應該會看到 v1 和 v2 交錯出現,大部分流量仍打到 v1,但偶爾會有 v2 的回應,這代表 Mesh 層的流量控制生效了。
如果真的不相信,覺得只是運氣好 XD 可以試著把其中一個 service 的比例改成 100,就可以完完全全相信流量分流有奏效
老實說,Service Mesh 一開始看起來真的有點抽象,因為它不是一個單一功能或指令,而是一整套「理念 + 架構 + 工具組合」。
對我來說,最有感的體悟其實是這句話:「把網路通訊邏輯從應用程式程式碼中抽離出來,交給 Infra 層統一管理。」
在這篇裡,我們先用比較貼近實務的角度去重新定義 Service Mesh,並從真實的場景出發去看它存在的意義──例如跨 Region 流量分流、災難復原 (DR)、重試與加密通訊等需求。
透過這樣的角度,你會發現 Service Mesh 並不是為了「炫技」或「跟風」,而是當系統規模變大、微服務越來越多時,這些「非功能性需求」開始變得難以管理,Mesh 的價值就浮現了。
在技術層面上,我們也實際動手體驗了 Cilium 的一項基礎能力:
透過 CiliumEnvoyConfig 將流量分流 (Traffic Splitting) 與重試策略下沉到 Infra 層實作,不改任何應用程式,就能動態控制流量比例。
接下來的篇章會延伸這個基礎,深入探索 Gateway API 的實踐