K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

第十五课:HTTPS、TLS、SNI、证书信任与 Ingress 终止原理

上一课我们已经把北南向流量的 HTTP 入口讲清楚了: 但只讲 HTTP 还远远不够。 真实生产里你真正面对的通常是: 也就是说,入口层真正难的部分,往往不是“路由到哪里”,而是: 如果这条线不通,你会频繁卡在下面这些问题: 1. 为什么 HTTPS 能访问业务,但证书却是错的 2. 为什么换了 `Host` 头,证书还是没变 3. 为什么 curl 报 `

Markdown15-第十五课-HTTPS-TLS-SNI-证书信任与Ingress终止原理.md2026年4月10日 07:18

第十五课:HTTPS、TLS、SNI、证书信任与 Ingress 终止原理

为什么这一课必须接在 Ingress 之后

上一课我们已经把北南向流量的 HTTP 入口讲清楚了:

  • NodePort 怎样把流量引进集群
  • ingress-nginx 怎样根据 HostPath 做七层路由
  • 404503 大概分别意味着哪一层出了问题
  • Ingress 规则最终怎样落到 Nginx 配置里

但只讲 HTTP 还远远不够。

真实生产里你真正面对的通常是:

  • https://...
  • 证书
  • 浏览器信任
  • TLS 握手
  • SNI
  • 证书续期

也就是说,入口层真正难的部分,往往不是“路由到哪里”,而是:

请求在还没有进入 HTTP 路由之前,TLS 握手时到底发生了什么。

如果这条线不通,你会频繁卡在下面这些问题:

  1. 为什么 HTTPS 能访问业务,但证书却是错的
  2. 为什么换了 Host 头,证书还是没变
  3. 为什么 curl 报 self-signed certificate
  4. 为什么浏览器说“不安全”,但流量其实已经加密了
  5. 为什么错误主机名时常见表现是“假证书 + 404”
  6. 为什么 Ingress 里加了 tls: 之后,后端应用并不一定需要自己支持 HTTPS

这一课的目标,就是把:

  • TLS 是什么
  • 证书是什么
  • 信任链是什么
  • SNI 是什么
  • Ingress 为什么能做 TLS termination

全部讲透。


先把四个最容易混淆的概念拆开

这是整节课最重要的基础。

第一:HTTP

HTTP 是应用层协议。

它默认是明文。

也就是说,如果只有 HTTP:

  • 中间人可以看到请求内容
  • 密码、Cookie、Token 都可能被截获

第二:TLS

TLS 解决的是:

传输过程中的加密、完整性保护、以及身份校验框架。

它不是“证书本身”,也不是“浏览器信任本身”。

TLS 握手里会协商:

  • 使用什么密码套件
  • 使用什么证书
  • 会话密钥是什么

第三:证书

证书的核心作用是:

把某个公钥和某个身份绑定起来。

这个身份通常体现在:

  • CN
  • SAN

尤其现代 TLS 主机名校验,真正更关键的是:

  • SAN,也就是 Subject Alternative Name

第四:信任

很多初学者会把“有证书”和“客户端信任”当成一回事。

不是。

你完全可以有一张证书,但客户端仍然不信任它。

比如:

  • 自签证书

它依然能提供加密,但默认不被系统信任。

这句话你以后必须能脱口而出:

加密不等于信任,证书存在不等于证书被信任。


先建立 HTTPS 的时序认知:TLS 发生在 HTTP 之前

这一点极其关键。

很多人会本能地觉得:

  • 服务器先看到 Host
  • 再决定用哪张证书

真实顺序不是这样。

HTTPS 请求大致是:

  1. TCP 建立连接
  2. TLS ClientHello 发出
  3. 客户端在握手阶段通过 SNI 告诉服务端“我想访问哪个主机名”
  4. 服务端根据 SNI 选择证书
  5. TLS 握手完成
  6. 才开始发送 HTTP 请求
  7. HTTP 请求头里才出现 Host

所以你必须牢牢记住:

证书选择发生在 HTTP 头到来之前。

这也是为什么:

  • 对 HTTPS 来说,只改 Host 头不一定足够
  • 真正影响证书选择的是 SNI

HostSNI 到底有什么区别

这两个词经常被混用,但它们不在同一层。

Host

属于:

  • HTTP 层

作用:

  • 告诉反向代理或应用,我想访问哪个虚拟主机

SNI

属于:

  • TLS 握手层

作用:

  • 告诉服务端,在握手阶段应该返回哪张证书

为什么要分清这两者

因为很多入口问题表面看起来像“同一个域名问题”,但其实层次不同:

  • 证书不对,往往先看 SNI
  • 路由不对,往往再看 Host

所以你以后看到:

  • 证书是默认假证书
  • 但 HTTP 路由其实能到正确服务

不要惊讶,这完全可能发生。


这套集群里 HTTPS 的真实底座是什么

这一课我先验证了几件事实。

1. ingress-nginx-controller 对外暴露了 HTTPS 入口

它的 Service 是:

  • NodePort
  • 80 -> 30080
  • 443 -> 30443

也就是说:

  • 30080 是 HTTP 入口
  • 30443 是 HTTPS 入口

2. controller 自己有默认假证书机制

在上一课查看 nginx.conf 时,我已经看到:

  • default-fake-certificate.pem

这说明 ingress-nginx 在没有找到更合适证书时,会回退到一张默认假证书。

3. 这套集群当前没有 cert-manager

我实际验证了:

  • 没有 cert-manager namespace
  • kubectl get certificates,issuers,clusterissuers -A 直接报资源类型不存在

这意味着:

  • 这套集群现在没有自动证书签发控制面

所以这节课我故意用:

  • 手工自签证书

来做最小可验证闭环。

这反而更利于教学,因为你能先看清:

  • TLS Secret 到底是什么
  • Ingress tls: 到底做了什么

然后再去理解:

  • 为什么生产里还需要 cert-manager

本次实验为什么这样设计

我专门设计了一条很有教学价值的路径:

阶段 1:只创建 HTTP 版 Ingress

对象:

  • tls-lab/secure-web

规则:

  • Host = secure.k8s-lab.local
  • 路由到 secure-web-svc

但故意不写:

  • spec.tls

目的就是验证:

HTTPS 在没有 tls: 时,是否也可能访问成功。

阶段 2:生成一个自签 TLS Secret

我没有把私钥硬编码进仓库,而是写了一个脚本:

它会:

  • 本地临时生成 key/cert
  • 创建 kubernetes.io/tls Secret
  • 打印证书主题、签发者、有效期和 SAN

为什么要这样做?

因为这符合正确工程习惯:

教学可以用自签证书,但不应该把私钥当静态明文材料长期放进仓库。

阶段 3:把同一个 Ingress 切到 TLS 版本

然后我应用带 tls: 的版本:

  • secretName: secure-web-tls
  • hosts: secure.k8s-lab.local

这时我们就可以直接比较:

  1. tls: 前,证书是什么
  2. tls: 后,证书变成什么
  3. 访问行为是否变化
  4. 错误主机名时会发生什么

第一组核心现象:没有 tls:,HTTPS 也可能“能通”

这是本课最反直觉、也最重要的现象。

我在只应用 HTTP 版 Ingress 后,做了两组请求。

HTTP 请求

我在节点上执行:

curl -H 'Host: secure.k8s-lab.local' http://127.0.0.1:30080/

返回:

  • 200 OK
  • 命中了 secure-web

这很正常。

HTTPS 请求

我再执行:

curl -k --resolve secure.k8s-lab.local:30443:127.0.0.1 \
  https://secure.k8s-lab.local:30443/

结果居然也是:

  • HTTP/2 200
  • 同样返回了 secure-web 内容

但证书是什么

我用:

openssl s_client -connect 127.0.0.1:30443 \
  -servername secure.k8s-lab.local

取证书,看到的是:

  • CN = Kubernetes Ingress Controller Fake Certificate
  • SAN = DNS:ingress.local

这说明什么

这说明:

路由成功和证书正确,是两条不同维度的判断。

也就是说:

  • ingress-nginx 能先用默认假证书完成 TLS
  • 握手完成后,HTTP 请求依然可能命中正确的 Host 路由
  • 所以业务内容能返回
  • 但证书身份是错的

这正是很多生产现场最容易误判的地方:

  • “页面都打开了,所以 HTTPS 应该没问题”

不对。

可能只是:

  • 路由没问题
  • 但证书完全不对

第二组核心现象:加了 tls: 后,证书换成自定义 Secret

我用脚本生成了:

  • Secret/tls-lab/secure-web-tls

并确认它的结构是:

  • type: kubernetes.io/tls
  • data keys:
    • tls.crt
    • tls.key

这说明 Kubernetes 里 TLS Secret 的本质就是:

一对证书和私钥的封装。

然后我把 Ingress 更新成:

  • spec.tls.hosts = [secure.k8s-lab.local]
  • spec.tls.secretName = secure-web-tls

之后再次执行:

openssl s_client -connect 127.0.0.1:30443 \
  -servername secure.k8s-lab.local

这次看到的证书变成:

  • subject = CN = secure.k8s-lab.local
  • issuer = CN = secure.k8s-lab.local
  • SAN = DNS:secure.k8s-lab.local

也就是说:

  • ingress-nginx 已经不再回退默认假证书
  • 它成功根据 SNI 和 tls: 配置选中了我们自签的那张证书

但客户端为什么还是报错

我再执行:

curl --resolve secure.k8s-lab.local:30443:127.0.0.1 \
  https://secure.k8s-lab.local:30443/

得到的是:

  • curl: (60) SSL certificate problem: self-signed certificate

这一步就是在给你做一个最核心的概念分离训练:

证书已经对了,不等于客户端就会信任它。

因为它仍然是:

  • 自签证书

所以默认 trust store 不认。

-k 之后为什么又能通

我再执行:

curl -k --resolve secure.k8s-lab.local:30443:127.0.0.1 \
  https://secure.k8s-lab.local:30443/

这次是:

  • HTTP/2 200

这说明:

  • TLS 握手本身是成立的
  • 加密也是成立的
  • 只是默认信任校验没过

这一步对专家思维非常重要。

以后别人说“HTTPS 不通”,你第一反应要追问:

  • 是握手失败?
  • 是证书名不匹配?
  • 是证书不受信任?
  • 还是业务路由失败?

它们不是一回事。


第三组核心现象:错误主机名时,证书和路由会一起退回默认状态

我还做了一个非常关键的反向实验。

我用错误主机名发起 HTTPS:

openssl s_client -connect 127.0.0.1:30443 \
  -servername wrong.k8s-lab.local

拿到的又变回:

  • Kubernetes Ingress Controller Fake Certificate

然后我再请求:

curl -k --resolve wrong.k8s-lab.local:30443:127.0.0.1 \
  https://wrong.k8s-lab.local:30443/

得到的是:

  • HTTP/2 404

这两件事必须连起来理解。

证书为什么回退成假证书

因为在 TLS 层:

  • SNI = wrong.k8s-lab.local
  • 但没有任何 tls.hosts 对它负责

所以 controller 只能回退默认假证书。

为什么后面又是 404

因为 TLS 握手完成后进入 HTTP 层:

  • Host = wrong.k8s-lab.local
  • 也没有任何 Ingress 规则匹配它

所以 HTTP 层返回默认 404

这正好把:

  • SNI 证书选择
  • HTTP Host 路由

两层彻底拆开了。


这节课最重要的一句话:证书选择和路由匹配是相关但独立的两个阶段

你以后必须能把这句话讲得非常顺。

TLS 阶段

服务端根据:

  • SNI
  • spec.tls.hosts
  • secretName

来决定用哪张证书。

HTTP 阶段

服务端根据:

  • Host
  • Path

来决定把请求转给哪个后端。

所以你完全可能遇到:

  1. 证书错,但路由对
  2. 证书对,但路由错
  3. 证书和路由都对
  4. 证书和路由都错

本课里我已经分别做出了真实样本:

证书错,但路由对

  • 没有 tls: 时的 secure.k8s-lab.local

证书对,但默认客户端不信任

  • tls: 后的自签证书

证书错,路由也错

  • wrong.k8s-lab.local

这就是为什么入口 TLS 问题一定不能只看“页面能不能打开”。


Ingress TLS Termination 到底是什么意思

这是生产里非常重要的架构概念。

本课里,从客户端角度看:

  • 我请求的是 https://secure.k8s-lab.local:30443/
  • 客户端拿到的是 HTTP/2 200

但 ingress-nginx 的访问日志里,上游仍然显示:

  • 10.244.147.112:8080

这说明什么?

说明:

TLS 在 ingress-nginx 这一层被终止了。

也就是:

  1. 客户端到 ingress-nginx:HTTPS / TLS
  2. ingress-nginx 到后端 Pod:普通 HTTP

这就叫:

  • TLS termination
  • SSL termination

这有什么好处

好处是:

  • 后端服务不必每个都自己处理证书
  • 证书集中管理
  • 统一入口做 TLS、HSTS、重定向、限流、WAF 等更方便

有什么代价

代价是:

  • ingress 到后端这一段如果需要“全链路加密”,还得额外做 re-encrypt 或 mTLS

所以以后你要分清两种场景:

入口终止 TLS

最常见、最实用。

端到端都加密

更偏高安全要求场景。


为什么我专门用了 curl --resolve

这条命令非常值得你学会。

我用的是:

curl --resolve secure.k8s-lab.local:30443:127.0.0.1 \
  https://secure.k8s-lab.local:30443/

它同时做了三件事:

  1. secure.k8s-lab.local 临时解析到 127.0.0.1
  2. URL 主机名仍然是 secure.k8s-lab.local
  3. 因此 curl 会把这个主机名同时用于:
    • TLS 的 SNI
    • HTTP 的 Host

这就是为什么它特别适合调试 HTTPS 入口。

如果你只是:

curl -H 'Host: secure.k8s-lab.local' https://127.0.0.1:30443/

那 TLS 阶段看到的主机名可能仍然是:

  • 127.0.0.1

从而根本没法准确模拟真实 SNI 场景。

这就是“会用命令”和“理解命令”的区别。


这次实验里还有一个很值得学习的控制器细节

我在并行执行:

  • 创建 TLS Secret
  • 更新带 tls: 的 Ingress

之后,controller 日志里出现了短暂现象:

  • Error getting SSL certificate ... secure-web-tls was not found. Using default certificate

随后又马上出现:

  • Secret was added ...
  • Adding secret to local store

这说明:

  • controller 是基于 watch / 本地缓存工作的
  • 在短时间窗口里,Ingress 可能先被处理,而 Secret 还没进入 controller 本地 store
  • 这时它会先回退默认证书
  • 等 Secret 缓存到位后,再切换到正确证书

这件事很有平台工程味道。

它提醒你:

Kubernetes 控制器不是事务数据库式的“全局同步瞬时完成”,而是多个控制循环逐步收敛。

这也是为什么真实生产里要有“短暂过渡态”的认知。


作为专家,你应该怎样排 HTTPS / 证书问题

建议按下面顺序来。

第一步:先分层判断是 TLS 问题还是 HTTP 路由问题

先问自己:

  • 握手有没有成功
  • 证书是不是对的
  • 客户端是不是信任它
  • 路由是不是命中了

第二步:看 Ingress 是否声明了 TLS

kubectl get ingress -n <ns> <name> -o yaml

重点看:

  • spec.tls.hosts
  • spec.tls.secretName

第三步:看 Secret 是否存在且类型正确

kubectl get secret -n <ns> <name> -o yaml

重点看:

  • type: kubernetes.io/tls
  • 是否包含 tls.crt
  • 是否包含 tls.key

第四步:直接取服务端证书,不猜

openssl s_client -connect <ip>:443 -servername <host>

看:

  • subject
  • issuer
  • SAN
  • 有效期

第五步:用 curl --resolve 测真实 SNI 行为

curl --resolve host:443:ip https://host/

这比只改 Host 头更准确。

第六步:看 controller 日志

kubectl -n ingress-nginx logs deploy/ingress-nginx-controller

重点找:

  • Using default certificate
  • Error getting SSL certificate
  • Secret was added
  • 上游请求日志

学完这一课你必须真正记住的结论

  1. HTTPS 不只是“加一个证书”,它先有 TLS 握手,再有 HTTP 路由。
  2. SNI 决定服务端在握手阶段选哪张证书,Host 决定 HTTP 阶段命中哪条路由。
  3. 没有 tls: 时,Ingress 也可能用默认假证书完成 TLS,然后把请求路由到正确后端。
  4. 证书正确不等于客户端信任它;自签证书默认仍会触发校验失败。
  5. kubernetes.io/tls Secret 本质上就是 tls.crt + tls.key
  6. Ingress TLS termination 的意思是:客户端到入口加密,入口到后端未必加密。
  7. 错误主机名时常见组合现象是:默认假证书 + HTTP 404。
  8. 证书与路由问题必须分层排查,不能混成一句“HTTPS 不通”。

你现在具备了什么能力

学完这一课,你已经不只是“会把证书挂进 Ingress”了。

你开始具备的是:

  • 能把加密、证书、信任、路由四个概念彻底拆开的能力
  • 能解释 SNI 与 Host 的分工
  • 能判断“证书错但路由对”这类复杂入口现象
  • 能直接用 openssl s_clientcurl --resolve 做 HTTPS 实证排障

这已经是从“Kubernetes 使用者”走向“入口与安全架构负责人”的关键一步。