Repository Reading Site
本轮操作记录:HTTPS、TLS、自签证书与 Ingress 实验
这一轮我要把下面这些问题做成真实证据: 1. 为什么没有 `tls:` 的 Ingress,HTTPS 也可能访问成功 2. 默认假证书到底长什么样 3. `spec.tls.secretName` 到底如何影响证书选择 4. 自签证书为什么能加密但默认不被信任 5. `Host` 和 `SNI` 到底有什么区别 6. 为什么错误主机名时常见表现是“假证书
本轮操作记录:HTTPS、TLS、自签证书与 Ingress 实验
本轮目标
这一轮我要把下面这些问题做成真实证据:
- 为什么没有
tls:的 Ingress,HTTPS 也可能访问成功 - 默认假证书到底长什么样
spec.tls.secretName到底如何影响证书选择- 自签证书为什么能加密但默认不被信任
Host和SNI到底有什么区别- 为什么错误主机名时常见表现是“假证书 + 404”
- 为什么 HTTPS 进来之后,后端仍然可能只是 HTTP
Step 1:先核对现有 TLS / 证书底座
实际命令
rg -n "TLS|HTTPS|证书|cert-manager|SNI|Ingress TLS|ssl|https" README.md phase-* 00-学习总纲-仓库评估与专家路线图.md -g '*.md'
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ns | grep cert-manager || true
KUBECONFIG=~/.kube/config-k8s-lab kubectl get certificates,issuers,clusterissuers -A
KUBECONFIG=~/.kube/config-k8s-lab kubectl get ingress -A -o yaml | sed -n '1,260p'
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n ingress-nginx get secret
为什么要先查这一层
因为我需要先知道:
- 集群里有没有
cert-manager - 有没有现成 TLS Ingress 样本
- ingress-nginx 自己有没有单独管理的 TLS Secret
我看到的结果
仓库材料
仓库里已经提到:
cert-managerIngress TLS- 控制面证书
但还没有一课把入口 HTTPS 这条线单独讲透。
cert-manager
没有 cert-manager namespace。
而 kubectl get certificates,issuers,clusterissuers -A 直接报:
error: the server doesn't have a resource type "certificates"
说明当前集群并没有安装 cert-manager CRD。
ingress-nginx namespace
我看到只有 Helm release Secret,没有额外 TLS 业务 Secret。
原理解释
这一步很关键,因为它决定了本课实验策略。
既然当前集群:
- 没有 cert-manager
- 也没有现成 TLS 业务 Secret
那我就选择:
- 用手工自签证书
- 从零做出一个最小 HTTPS 闭环
这反而更适合初学者理解底层。
Step 2:准备实验清单,并故意分成“前状态”和“后状态”
本课清单
我新增了:
- 00-namespace-tls-lab.yaml
- 10-web.yaml
- 20-service.yaml
- 30-ingress-http-only.yaml
- 31-ingress-with-tls.yaml
- 40-generate-selfsigned-secret.sh
为什么要拆成两个 Ingress 版本
30-ingress-http-only.yaml
只写:
hostpath- backend Service
故意不写:
spec.tls
作用:
- 证明“HTTPS 也许能通,但证书不一定对”
31-ingress-with-tls.yaml
在同一个对象上额外增加:
spec.tls.hostsspec.tls.secretName
作用:
- 证明证书选择是如何从默认假证书切换到自定义 TLS Secret 的
为什么我没有把私钥写进仓库
这是一个故意的工程习惯训练。
自签证书当然可以直接写进 Secret YAML,但那样等于:
- 把私钥静态留在仓库
这不符合好习惯。
所以我选择:
- 写一个生成脚本
- 运行时生成
- Secret 在集群里创建
这比把私钥硬编码进 repo 更接近正确工程实践。
Step 3:先应用 HTTP-only 版本,建立“前状态”
实际命令
chmod +x manifests/15-tls/40-generate-selfsigned-secret.sh
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply \
-f manifests/15-tls/00-namespace-tls-lab.yaml \
-f manifests/15-tls/10-web.yaml \
-f manifests/15-tls/20-service.yaml \
-f manifests/15-tls/30-ingress-http-only.yaml
我创建了什么
命名空间
tls-lab
后端 Deployment
secure-web
它返回一个静态页面,页面里会写出:
- pod 名称
- pod IP
- node
- ingress host
这样后面你看到响应时,就能明确知道到底命中了哪个后端。
Service
secure-web-svc
Ingress
- 名字:
secure-web - host:
secure.k8s-lab.local - class:
nginx
但没有:
tls:
原理解释
这一阶段的核心目的不是直接做 HTTPS 成功,而是先做出:
- 路由已存在
- 但 TLS 证书还没显式绑定
这样我们才能看清 ingress-nginx 的默认行为。
Step 4:核对对象是否就绪
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n tls-lab get deploy,pod,svc,ingress,endpoints,endpointslices -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get deploy,pod,svc,ingress,endpoints,endpointslices -A | grep tls-lab || true
我看到的关键结果
Deployment
secure-web:1/1
Pod
secure-web-6d88765f88-z4tpb- Pod IP:
10.244.147.112 - 节点:
wk-1
Service
secure-web-svc- ClusterIP:
10.103.169.56
Ingress
secure-webCLASS: nginxHOSTS: secure.k8s-lab.localADDRESS: 10.109.198.120
Endpoints
10.244.147.112:8080
原理解释
这说明:
- 路由对象已经存在
- 后端成员表已经正常
- 还没涉及 TLS Secret,但入口路由层已经成立
这正是我们想要的前状态。
Step 5:验证 HTTP 正常
实际命令
ssh root@107.148.176.193 \
"curl -sS -D - http://127.0.0.1:30080/ -H 'Host: secure.k8s-lab.local'"
我看到的结果
HTTP/1.1 200 OK
...
app=secure-web
pod=secure-web-6d88765f88-z4tpb
pod_ip=10.244.147.112
node=wk-1
ingress_host=secure.k8s-lab.local
ingress_path=/
原理解释
这一步先证明:
- Ingress 路由规则本身没有问题
secure.k8s-lab.local的 HTTP 入口已经能命中正确后端
这为后面“HTTPS 证书问题”和“HTTP 路由问题”分层打基础。
Step 6:验证没有 tls: 时,HTTPS 也可能返回 200
实际命令
ssh root@107.148.176.193 \
"curl -k --resolve secure.k8s-lab.local:30443:127.0.0.1 -sS -D - https://secure.k8s-lab.local:30443/"
命令解释
这里的 curl --resolve 很关键。
它会:
- 把
secure.k8s-lab.local:30443临时解析到127.0.0.1 - URL 主机名仍然保持
secure.k8s-lab.local - 因此 curl 会把这个名字同时用于:
- TLS 的 SNI
- HTTP 的
Host
这比单纯 -H 'Host: ...' 更接近真实 HTTPS 行为。
我看到的结果
HTTP/2 200
...
app=secure-web
pod=secure-web-6d88765f88-z4tpb
...
原理解释
这一步非常容易让新手误解。
因为很多人会以为:
- 没有
tls:,HTTPS 就应该完全失败
但 ingress-nginx 的默认行为不是这样。
它可以:
- 用默认假证书先完成 TLS
- 然后再根据 HTTP Host 路由到正确后端
所以:
- 路由成功
- 不等于证书正确
Step 7:直接抓服务端证书,确认它是默认假证书
实际命令
ssh root@107.148.176.193 \
"echo | openssl s_client -connect 127.0.0.1:30443 -servername secure.k8s-lab.local 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName"
为什么要用 openssl s_client
因为证书问题不能靠猜。
openssl s_client 能直接告诉你:
- 服务器实际回了哪张证书
subject是什么issuer是什么- SAN 是什么
- 有效期是什么
我看到的结果
subject=O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
issuer=O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
notBefore=Apr 9 11:44:56 2026 GMT
notAfter=Apr 9 11:44:56 2027 GMT
X509v3 Subject Alternative Name:
DNS:ingress.local
原理解释
这说明:
- 入口加密已经发生
- 但服务端回的不是我们业务域名对应证书
- 它回的是 ingress-nginx 默认假证书
这就是“HTTPS 内容能通,但证书是错的”的真实样本。
Step 8:生成自签证书 Secret
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
manifests/15-tls/40-generate-selfsigned-secret.sh \
tls-lab secure-web-tls secure.k8s-lab.local 365
我看到的结果
secret/secure-web-tls created
subject=CN=secure.k8s-lab.local
issuer=CN=secure.k8s-lab.local
notBefore=Apr 10 07:09:58 2026 GMT
notAfter=Apr 10 07:09:58 2027 GMT
X509v3 Subject Alternative Name:
DNS:secure.k8s-lab.local
原理解释
subject = issuer
说明这是一张:
- 自签证书
SAN 包含 secure.k8s-lab.local
说明它的主机名身份声明是正确的。
也就是说,这张证书解决了:
- 证书名字是否匹配
但还没有解决:
- 客户端是否默认信任它
Secret 结构验证
我又执行了:
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n tls-lab get secret secure-web-tls \
-o go-template='{{.type}}{{"\n"}}{{range $k, $v := .data}}{{printf "%s\n" $k}}{{end}}'
结果是:
kubernetes.io/tls
tls.crt
tls.key
这说明 Ingress TLS Secret 的结构本质就是:
tls.crttls.key
Step 9:把 Ingress 切换到 TLS 版本
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply -f manifests/15-tls/31-ingress-with-tls.yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n tls-lab get ingress secure-web -o yaml | sed -n '1,220p'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n tls-lab describe ingress secure-web
我看到的关键结果
Ingress 里明确出现:
tls.hosts: [secure.k8s-lab.local]secretName: secure-web-tls
原理解释
这一配置告诉 ingress controller:
当 TLS 握手里的 SNI 是
secure.k8s-lab.local时,优先使用secure-web-tls这张证书。
这一步不是在改后端路由,而是在改:
- 证书选择逻辑
Step 10:验证自定义证书已经被选中
实际命令
ssh root@107.148.176.193 \
"echo | openssl s_client -connect 127.0.0.1:30443 -servername secure.k8s-lab.local 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName"
我看到的结果
subject=CN = secure.k8s-lab.local
issuer=CN = secure.k8s-lab.local
notBefore=Apr 10 07:09:58 2026 GMT
notAfter=Apr 10 07:09:58 2027 GMT
X509v3 Subject Alternative Name:
DNS:secure.k8s-lab.local
原理解释
这说明证书选择已经从:
- 默认假证书
成功切到了:
- 我们自己在 Secret 里提供的证书
也就是说:
spec.tls.hostssecretName- ingress controller 本地证书存储
这条链已经生效。
Step 11:验证“证书对了,但客户端仍然不信”
实际命令
ssh root@107.148.176.193 \
"curl --resolve secure.k8s-lab.local:30443:127.0.0.1 -sS -D - https://secure.k8s-lab.local:30443/"
我看到的结果
curl: (60) SSL certificate problem: self-signed certificate
原理解释
这一点是整节课最重要的概念分离之一。
现在证书已经:
- 名字正确
- SAN 正确
- 服务端也选对了
但客户端仍然失败。
为什么?
因为它仍然是:
- 自签证书
客户端默认 trust store 不信它。
这说明:
证书身份正确,不等于客户端信任它。
Step 12:验证“忽略信任校验后,请求可以成功”
实际命令
ssh root@107.148.176.193 \
"curl -k --resolve secure.k8s-lab.local:30443:127.0.0.1 -sS -D - https://secure.k8s-lab.local:30443/"
我看到的结果
HTTP/2 200
...
app=secure-web
pod=secure-web-6d88765f88-z4tpb
...
原理解释
-k 的含义不是“关闭加密”,而是:
- 不校验证书信任链
- 不校验证书合法性
所以这一步实际上说明:
- TLS 握手本身没有问题
- 加密也没有问题
- 出问题的是默认信任校验
这一点在生产现场特别重要。
你以后要分清:
- “握手没成”
- “证书不受信”
- “路由失败”
这三类问题。
Step 13:验证错误主机名时的行为
实际命令
ssh root@107.148.176.193 \
"echo | openssl s_client -connect 127.0.0.1:30443 -servername wrong.k8s-lab.local 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName"
ssh root@107.148.176.193 \
"curl -k --resolve wrong.k8s-lab.local:30443:127.0.0.1 -sS -D - https://wrong.k8s-lab.local:30443/"
我看到的结果
证书
又变回:
subject=O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
HTTP 响应
HTTP/2 404
原理解释
这正好把 TLS 和 HTTP 两层彻底拆开。
TLS 层
因为:
wrong.k8s-lab.local不在tls.hosts里
所以 controller 回退到:
- 默认假证书
HTTP 层
因为:
- 也没有任何 Ingress 规则匹配
wrong.k8s-lab.local
所以最终得到:
- 默认
404
这就是为什么错误主机名时,你会经常同时看到:
- 证书不对
- 404
Step 14:看 controller 日志,理解控制循环的收敛过程
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=80 | grep 'tls-lab/'
我看到的关键结果
日志里出现了几段非常有价值的信息。
1. Ingress 被认领
Found valid IngressClass" ingress="tls-lab/secure-web"
2. 刚切 TLS 时,controller 先没在本地 store 里找到 Secret
Error getting SSL certificate "tls-lab/secure-web-tls": local SSL certificate tls-lab/secure-web-tls was not found. Using default certificate
3. 随后 Secret 被 watch 到
Secret was added ...
Adding secret to local store
原理解释
这一段很有平台工程意义。
它说明:
- controller 是基于 watch + 本地缓存工作的
- Secret 和 Ingress 的收敛不是“绝对同步瞬时完成”
- 在很短窗口里,Ingress 可能已经引用了 Secret,但 controller 本地缓存尚未拿到它
所以会先短暂地:
- 用默认证书
等 Secret 缓存到位后再切换。
这就是典型的:
- 最终一致
- 控制器收敛
Step 15:看访问日志,证明 TLS 在入口终止,后端仍然是 HTTP
实际命令
ssh root@107.148.176.193 \
"curl -k --resolve secure.k8s-lab.local:30443:127.0.0.1 -sS https://secure.k8s-lab.local:30443/ >/dev/null"
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=40
我看到的结果
访问日志里对应 HTTPS 请求的行包含:
- 客户端请求:
GET / HTTP/2.0 - upstream name:
[tls-lab-secure-web-svc-80] - upstream addr:
10.244.147.112:8080
原理解释
这说明:
- 客户端到 ingress-nginx 用的是 HTTPS,而且协商到了 HTTP/2
- 但 ingress-nginx 向后端 Pod 转发时,目标仍然是:
10.244.147.112:8080
也就是说:
- TLS 在 ingress-nginx 终止了
- 后端仍然是普通 HTTP
这就是:
- TLS termination
本轮最重要的结论
结论 1:没有 tls: 时,HTTPS 也可能返回业务内容
因为 ingress-nginx 可以用默认假证书先完成 TLS,再按 HTTP Host 路由。
结论 2:证书正确,不等于客户端默认信任
自签证书就是最典型的样本。
结论 3:spec.tls 决定的是证书选择,不是路由本身
路由仍然主要由:
HostPath
决定。
结论 4:错误主机名时常见的组合是“假证书 + 404”
前者是 TLS 层后果,后者是 HTTP 层后果。
结论 5:Ingress 最常见的模式是 TLS termination
客户端到入口加密,入口到后端不一定加密。
本轮最值得记住的命令与用途
openssl s_client
直接取服务端实际返回的证书。
curl --resolve
同时模拟:
- DNS 解析覆盖
- TLS SNI
- HTTP Host
kubectl get ingress -o yaml
确认 spec.tls 是否真的存在。
kubectl get secret -o go-template
看 Secret 类型和数据键名,不必把大段 base64 人肉阅读。
kubectl logs deploy/ingress-nginx-controller
看证书收敛、默认证书回退、以及上游访问日志。
这一课给你的专家能力提升点
如果你把这一轮实验真正吃透,你已经具备了下面这些关键能力:
- 能把 TLS、证书、信任、HTTP 路由彻底分层
- 能解释 SNI 与
Host的区别 - 能判断“证书错但路由对”这类复杂入口现象
- 能用
openssl s_client和curl --resolve做 HTTPS 入口排障 - 能解释为什么 TLS termination 后后端仍然可以只跑 HTTP
这已经是做平台入口、安全接入和生产故障排查非常核心的一块能力。