博客 / 詳情

返回

istio初探以及解決http-426的問題

前言

在之前的文章中,我們花了大量的篇幅,從記錄後端pod真實ip開始説起,然後引入envoy,再解決了各種各樣的需求:配置自動重載、流量劫持、sidecar自動注入,到envoy的各種能力:熔斷、流控、分流、透明代理、可觀測性等等,已經可以支撐起一個完整的服務治理框架了

而今天介紹的istio,正是前面提到的這些所有功能的集大成者,從本文開始,我們將詳細介紹istio,並且與之前手搓的功能做一個詳細的對比,為大家以後選擇服務治理的某個功能提供參考

istio架構

           ┌──────────────┐
           │   istiod     │   ← 控制面
           │ (Pilot+CA)   │
           └──────┬───────┘
                  │ xDS (gRPC / TLS)
                  │
┌────────────┐    │    ┌────────────┐
│  Envoy     │◄───┼───►│   Envoy    │  ← 數據面
│ (Sidecar)  │         │ (Sidecar)  │
└─────▲──────┘         └─────▲──────┘
      │ iptables             │
      │                      │
   App Pod                App Pod

  • 數據面就是之前一直在研究的envoy,包括4/7代理、熔斷、限流、可觀測性等等,envoy就是執行由控制面下發的配置
  • 控制面istiod主要的職責:將配置下發到每一個envoy去。由於istio中配置以crd的形式成為了k8s的資源,所以要不斷的監聽k8s apiserver,將資源的變化翻譯成envoy看得懂的配置,並且下發到envoy去

至於其餘istio的資源,我們後面詳細介紹

istio安裝

不説廢話,先把istio安裝上去再説

首先準備好k8s集羣,其次下載istio(這一步有可能需要上網)

curl -L https://istio.io/downloadIstio | sh -
cd istio-*
sudo ln -s $PWD/istioctl /usr/local/bin/istioctl

驗證兼容性

istioctl x precheck

開始安裝

istioctl install --set profile=default -y

由於鏡像倉庫沒法直接使用,所以需要一些特殊的方法,具體可以看這篇文章: 快速拉取docker鏡像

需要的鏡像有:

docker.io/istio/pilot:1.28.2
docker.io/istio/proxyv2:1.28.2

安裝完成:

▶ kubectl -n istio-system get pod
NAME                                    READY   STATUS    RESTARTS   AGE
istio-ingressgateway-865c448856-qs8s2   1/1     Running   0          8s
istiod-86c75775bb-j7qbg                 1/1     Running   0          12s

安裝完成,要從哪兒開始呢?

istio的自動注入

kubectl label namespace default istio-injection=enabled

同之前envoy一樣,給namespace打上標籤之後,重啓服務即可

kubectl rollout restart deploy nginx-test

重啓之後sidecar已經注入進去了,我們來觀察一下istio注入到底做了什麼事情

先describe看看events

Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  8s    default-scheduler  Successfully assigned default/nginx-test-6f855b9bb9-9phsv to wilson
  Normal  Pulled     8s    kubelet            Container image "docker.io/istio/proxyv2:1.28.2" already present on machine
  Normal  Created    8s    kubelet            Created container: istio-init
  Normal  Started    8s    kubelet            Started container istio-init
  Normal  Pulled     8s    kubelet            Container image "docker.io/istio/proxyv2:1.28.2" already present on machine
  Normal  Created    8s    kubelet            Created container: istio-proxy
  Normal  Started    8s    kubelet            Started container istio-proxy
  Normal  Pulled     6s    kubelet            Container image "registry.cn-beijing.aliyuncs.com/wilsonchai/nginx:latest" already present on machine
  Normal  Created    6s    kubelet            Created container: nginx-test
  Normal  Started    5s    kubelet            Started container nginx-test

1個initContainer,1個業務container和1個sidecar

其中initContainer:

Init Containers:
  istio-init:
    Container ID:  containerd://2bf56cd37703d82a2a43e94e8c8d683ed66b0afe22bf7148a597d67b89a727a8
    Image:         docker.io/istio/proxyv2:1.28.2
    Image ID:      docker.m.daocloud.io/istio/proxyv2@sha256:39065152d6bd3e7fbf6bb04be43c7a8bbd16b5c7181c84e3d78fa164a945ae7f
    Port:          <none>
    Host Port:     <none>
    Args:
      istio-iptables
      -p
      15001
      -z
      15006
      -u
      1337
      -m
      REDIRECT
      -i
      *
      -x

      -b
      *
      -d
      15090,15021,15020
      --log_output_level=default:info
...

和之前envoy中劫持流量的做法一樣,istio依然是使用iptables將端口流量導入到代理之中處理

嘗試訪問一下:

▶ curl 10.22.12.178:30785/test
i am backend in backend-6d76f54494-g6srz

成功,再次查看istio-proxy日誌。空的?為了調試方便,將其打開並且輸出至控制枱

kubectl -n istio-system edit cm istio

apiVersion: v1
data:
  mesh: |-
    accessLogFile: /dev/stdout
  ...

至此,istio的第一個功能探索完畢,自動注入sidecar container並且完成了流量劫持

Upgrade Required 426 的問題

當前的架構是左圖,現在要前進到右圖

watermarked-istio_1

其實就是在backend注入istio-proxy,直接重啓就好

▶ kubectl get pod -owide
NAME                          READY   STATUS        RESTARTS   AGE     IP            NODE     NOMINATED NODE   READINESS GATES
backend-5d4d7b598c-f7852      2/2     Running       0          13s     10.244.0.49   wilson   <none>           <none>
nginx-test-6f855b9bb9-9phsv   2/2     Running       0          58m     10.244.0.48   wilson   <none>           <none>

注入完成,測試一下

▶ curl 10.22.12.178:30785/test
Upgrade Required
▶ kubectl logs -f -l app=nginx-test -c istio-proxy
[2026-01-26T07:54:42.977Z] "GET /test HTTP/1.1" 426 - upstream=10.244.0.48:80 duration=6ms route=default
[2026-01-26T07:54:42.978Z] "- - -" 0 - upstream=10.105.148.194:10000 duration=9ms route=-

在nginx注入istio-proxy,backend沒有注入的時候並沒有報錯。而一旦nginx與backend都注入的時候就會出現Upgrade Required (426)錯誤,Nginx Sidecar 發現目標(Backend)是一個純文本服務,它會回退到“透明代理”模式,簡單地把 Nginx 發出的流量透傳出去

Nginx Sidecar 發現目標也有 Sidecar,它會嘗試建立一個高度優化的、基於 mTLS 的隧道(關於mTLS後面會詳細介紹)。如果此時 Nginx 發出的請求頭(比如缺少 Host 字段,或者使用了 HTTP/1.0)不符合 Envoy 對這種隧道
協議的預期,Envoy 可能會向 Nginx 發送一個特殊的響應,或者 Nginx 在嘗試通過這種隧道通信時,因為某些 Header 衝突(如 Connection: close)自發產生了 426 錯誤

想要解決這個問題有兩種方法

改造nginx中加入標記

        location /test {
            proxy_http_version 1.1; # 必須添加這一行
            proxy_set_header Host $host; # 這一行也是必須的
            proxy_pass http://backend_ups;
        }

Nginx 的 proxy_pass 默認使用 HTTP/1.0。在 Istio 環境中,HTTP/1.0 不支持長連接(Keep-Alive)以及一些現代的協議協商,這與 Istio Sidecar(Envoy)默認的 L7 代理行為衝突,Istio 需要 HTTP/1.1 來支持複雜連接管理問題

改造backend service

如果nginx改造有難度,那也可以嘗試改造backend-service

apiVersion: v1
kind: Service
metadata:
  name: backend-service
  namespace: default
spec:
  ports:
  - name: tcp-80 # 原為 http-80 改為 tcp-80
    port: 10000
    protocol: TCP
    targetPort: 10000
  selector:
    app: backend

Istio 只有在識別到流量是 HTTP 時才會進行深度的協議檢查和轉換。如果你把這個服務聲明為 TCP,Istio 就會將其視為原始字節流進行透傳,不再關心它是 HTTP/1.0 還是 1.1。優點就是徹底解決 426 問題,無需改 Nginx。
缺點則是你會失去 Istio 針對該服務的 HTTP 監控指標(如請求數、4xx/5xx 統計)、分佈式追蹤以及基於路徑的路由功能

http 1.0 與 http 1.1

這裏再簡單介紹一下兩個協議版本的區別

  • 連接管理(最顯著的區別)

    • HTTP 1.0:短連接 (Short-lived),默認情況下,客户端每發起一個請求,都要與服務器建立一次 TCP 三次握手。請求結束並收到響應後,TCP 連接立即關閉。如果頁面有 10 張圖片,瀏覽器就要建立 10 次 TCP 連接。這帶來了極高的延遲和資源開銷。
    • HTTP 1.1:持久連接 (Persistent Connection / Keep-Alive)。默認開啓 Connection: keep-alive。一個 TCP 連接可以被多個請求複用。只有在明確聲明 Connection: close 或連接超時後才會關閉。
    • 在 Istio 中: Envoy 極度依賴持久連接來維持高性能的 Sidecar 間隧道。HTTP 1.0 的頻繁斷開會讓 Envoy 感到“壓力山大”,甚至認為這是一種非標準的協議行為。
  • Host Header

    • HTTP 1.0:人們認為一個 IP 對應一個網站,所以請求頭裏不需要帶域名信息。
    • HTTP 1.1:隨着虛擬主機(一個 IP 跑多個網站)的流行,HTTP 1.1 規定請求頭必須包含 Host 字段。
    • 在 K8s/Istio 中: Istio 的路由決策、Service 的匹配完全依賴 Host 頭。這也是為什麼 Nginx 使用 HTTP 1.0 轉發時,如果不手動補全 Host 頭,後端往往會返回 404 或協議錯誤。

以上是istio必須要求HTTP 1.1最主要的兩個因素,當然還有其他非常重要的區別

特性 HTTP 1.0 HTTP 1.1
連接模型 默認短連接,每次請求新開 TCP 默認持久連接 (Keep-Alive),複用 TCP
Host 頭部 可選 (導致無法支持虛擬主機) 必須 (支持一 IP 多域名)
流水線 (Pipelining) 不支持 支持 (但在實際應用中受限)
斷點續傳 不支持 支持 (通過 Range 頭部)
緩存控制 簡單 (Expires) 複雜且強大 (Cache-Control, ETag)
默認協議版本 許多舊軟件(如 Nginx proxy)的默認值 現代 Web 應用的基石標準

小結

本章內容算是一個開胃小菜,成功安裝了istio,並且解決了一個非常常見的426問題,至於怎麼把之前在envoy的那些最佳實踐搬遷到istio,那就是後面的內容了,敬請期待

後記

如果整個namespace都已經有了注入標籤istio-injection=enabled,但是某個deployment不想讓istio注入

kubectl patch deployment nginx -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/inject":"false"}}}}}'

聯繫我

  • 聯繫我,做深入的交流


至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教...

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.