在之前的系列文章中,我们花了大量篇幅,从记录后端Pod真实IP开始,逐步引入Envoy,并解决了诸如配置自动重载、流量劫持、Sidecar自动注入等需求。同时,我们也探索了Envoy的多种能力,包括熔断、流控、分流、透明代理和可观测性等,这些已足以支撑起一个完整的服务治理框架。
而今天要介绍的Istio,正是上述所有功能的集大成者。从本文开始,我们将详细介绍Istio,并与之前手动实现的方案进行对比,为大家在未来选择服务治理工具时提供参考。
Istio架构
我们先来看看Istio的核心架构。Istio采用控制面与数据面分离的设计,这也是现代Service Mesh的典型架构。
┌──────────────┐
│ istiod │ ← 控制面
│ (Pilot+CA) │
└──────┬───────┘
│ xDS (gRPC / TLS)
│
┌────────────┐ │ ┌────────────┐
│ Envoy │◄───┼───►│ Envoy │ ← 数据面
│ (Sidecar) │ │ (Sidecar) │
└─────▲──────┘ └─────▲──────┘
│ iptables │
│ │
App Pod App Pod
- 数据面就是我们之前深入研究的Envoy代理,它负责处理四层和七层的网络流量,执行熔断、限流、观测等具体策略。简单说,Envoy就是执行控制面下发指令的“士兵”。
- 控制面主要是
istiod组件,它的核心职责是将配置下发到每一个Envoy。在Istio中,配置以Kubernetes自定义资源(CRD)的形式存在,因此istiod需要持续监听Kubernetes API Server,将这些资源的变化“翻译”成Envoy能理解的配置(即xDS协议),并动态下发。
至于Istio的其他资源对象,我们将在后续文章中详细介绍。
安装Istio
理论先行,实践紧随。我们先动手把Istio安装到K8s集群中。
首先,你需要一个可用的Kubernetes集群。然后,下载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
安装完成后,检查istio-system命名空间下的Pod状态。
▶ 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
看到istiod和istio-ingressgateway都处于Running状态,说明安装成功。那么,接下来从哪里开始体验呢?
体验Sidecar自动注入
Istio一个非常便捷的功能就是Sidecar的自动注入。只需为命名空间打上标签,该命名空间下新建的Pod就会自动注入Sidecar容器。
kubectl label namespace default istio-injection=enabled
这和之前我们手动管理Envoy Sidecar的方式类似。打上标签后,重启现有的Deployment即可生效。
kubectl rollout restart deploy nginx-test
重启后,Pod中应该已经注入了Sidecar。我们来观察一下Istio究竟做了什么。首先查看Pod的事件记录。
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
从事件可以看出,Pod内现在有三个容器:一个istio-init初始化容器,一个业务容器nginx-test,以及Sidecar容器istio-proxy。
其中,istio-init容器的配置尤为关键:
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
...
可以看到,Istio和我们之前手动操作时一样,也是利用istio-iptables命令在Pod网络空间内设置iptables规则,从而将进出业务容器的流量劫持并转发到Sidecar代理(Envoy)中处理。
现在,尝试访问这个已经注入Sidecar的服务。
▶ curl 10.22.12.178:30785/test
i am backend in backend-6d76f54494-g6srz
访问成功!但查看Sidecar的日志,却发现是空的。为了方便调试,我们开启访问日志并输出到标准输出。
kubectl -n istio-system edit cm istio
在ConfigMap中找到mesh配置,添加或修改accessLogFile字段:
apiVersion: v1
data:
mesh: |-
accessLogFile: /dev/stdout
...
至此,我们完成了对Istio第一个核心功能——Sidecar自动注入及流量劫持的探索。
遭遇“Upgrade Required” (HTTP 426) 错误
接下来,我们要模拟一个更真实的微服务场景:让一个注入了Sidecar的Nginx,去访问另一个同样注入了Sidecar的后端服务(Backend)。当前的架构是下图左侧,目标是演进到右侧的架构。

其实很简单,为后端服务所在的命名空间打上注入标签后,重启Backend的Deployment即可。
▶ 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
请求失败了,返回了 HTTP 426 (Upgrade Required) 状态码。查看Nginx Sidecar的日志:
▶ 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注入Sidecar而Backend没有时,请求是正常的。一旦双方都注入了Sidecar,就出现了426错误。这是为什么呢?
这涉及到Istio/Envoy的智能协议检测与优化机制:
- 单Sidecar场景:Nginx的Sidecar发现目标(Backend)是一个普通的HTTP服务(无Sidecar),它会退回到“透明代理”模式,简单地将Nginx发出的原始流量透传出去,因此不会出错。
- 双Sidecar场景:Nginx的Sidecar发现目标服务也有Sidecar,它会尝试建立一个高度优化的、基于mTLS的隧道进行通信(mTLS后续文章会详述)。此时,如果Nginx发出的原始请求不符合Envoy对这种隧道协议的预期,就可能触发问题。
一个常见的原因在于 HTTP协议版本。Nginx的proxy_pass指令默认使用HTTP/1.0,而Istio的Sidecar间隧道强烈依赖HTTP/1.1的特性(如持久连接)。当使用HTTP/1.0发起请求时,可能因为缺少必要的头部(如Host)或协议特性不支持,导致通信失败,从而产生426错误。
解决方案
有两种主流方法可以解决这个426错误。
方法一:改造Nginx配置(推荐)
在Nginx的location配置块中,显式指定使用HTTP/1.1协议并添加必要的Host头部。
location /test {
proxy_http_version 1.1; # 必须添加这一行
proxy_set_header Host $host; # 这一行也是必须的
proxy_pass http://backend_ups;
}
- 原理:强制Nginx使用HTTP/1.1协议与上游通信,并补全
Host头部,使其符合现代HTTP协议及Istio Sidecar的预期。
- 优点:保持服务为HTTP类型,可以继续享受Istio提供的HTTP层监控、追踪和高级路由功能。
- 缺点:需要有权修改应用的Nginx配置。
方法二:将Backend Service声明为TCP服务
如果无法修改Nginx配置,可以尝试修改Backend服务的定义,将其端口协议从HTTP改为TCP。
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,从而绕过协议冲突。
- 优点:无需改动应用配置,一劳永逸。
- 缺点:你会失去Istio针对该服务的所有七层能力,包括基于HTTP路径/头部的路由、HTTP指标监控(如QPS、4xx/5xx错误率)以及HTTP层的分布式追踪。
深入理解:HTTP/1.0 与 HTTP/1.1
为什么Istio如此“挑剔”HTTP协议版本?我们简单回顾一下两者的核心区别,这能帮助我们更好地理解各类网络中间件的兼容性问题。
-
连接管理(最显著的区别)
- HTTP 1.0:默认短连接。每个请求完成后,TCP连接立即关闭。如果页面有多个资源,就需要多次握手,效率低下。
- HTTP 1.1:默认持久连接(Keep-Alive)。一个TCP连接可被多个请求复用,极大提升了性能。
- 在Istio中:Envoy Sidecar之间需要维持高性能的持久连接隧道。HTTP 1.0的频繁连接断开行为会被视为非标准或低效,可能引发问题。
-
Host头部(虚拟主机的基石)
- HTTP 1.0:设计时认为一个IP对应一个网站,因此请求头中不需要携带域名信息。
- HTTP 1.1:为支持虚拟主机(一个IP托管多个网站),强制要求请求必须包含
Host头部。
- 在K8s/Istio中:服务发现和路由决策严重依赖
Host头。如果Nginx用HTTP/1.0转发且不补全Host头,后端服务很可能无法正确响应。
除了上述两点,两者还有其他重要差异:
| 特性 |
HTTP 1.0 |
HTTP 1.1 |
| 连接模型 |
默认短连接,每次请求新开TCP |
默认持久连接 (Keep-Alive),复用TCP |
| Host 头部 |
可选 (导致无法支持虚拟主机) |
必须 (支持一IP多域名) |
| 流水线 (Pipelining) |
不支持 |
支持 (但在实际应用中受限) |
| 断点续传 |
不支持 |
支持 (通过 Range 头部) |
| 缓存控制 |
简单 (Expires) |
复杂且强大 (Cache-Control, ETag) |
| 常见场景 |
许多旧软件(如 Nginx proxy_pass)的默认值 |
现代Web应用和中间件的标准 |
小结与后记
本章作为Istio的入门实践,我们成功安装了Istio,体验了其Sidecar自动注入能力,并诊断和修复了一个典型的双Sidecar环境下的HTTP 426协议错误。这仅仅是开始,后续我们将把之前在Envoy中实现的熔断、限流、观测等最佳实践,逐步迁移到Istio的配置模型中来。
附加提示:如果整个命名空间已启用Sidecar注入(istio-injection=enabled),但某个特定的Deployment不希望被注入,可以使用以下命令为其添加注解来禁用注入。
kubectl patch deployment nginx -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/inject":"false"}}}}}'
希望这篇结合实战踩坑经验的介绍,能帮助你更顺畅地迈出使用Istio的第一步。如果你对云原生技术有更多兴趣,欢迎到云栈社区与更多开发者交流探讨。