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

Repository Reading Site

本轮操作记录:HTTPS、TLS、自签证书与 Ingress 实验

这一轮我要把下面这些问题做成真实证据: 1. 为什么没有 `tls:` 的 Ingress,HTTPS 也可能访问成功 2. 默认假证书到底长什么样 3. `spec.tls.secretName` 到底如何影响证书选择 4. 自签证书为什么能加密但默认不被信任 5. `Host` 和 `SNI` 到底有什么区别 6. 为什么错误主机名时常见表现是“假证书

Markdown15-操作记录-HTTPS-TLS-自签证书与Ingress实验.md2026年4月10日 07:18

本轮操作记录:HTTPS、TLS、自签证书与 Ingress 实验

本轮目标

这一轮我要把下面这些问题做成真实证据:

  1. 为什么没有 tls: 的 Ingress,HTTPS 也可能访问成功
  2. 默认假证书到底长什么样
  3. spec.tls.secretName 到底如何影响证书选择
  4. 自签证书为什么能加密但默认不被信任
  5. HostSNI 到底有什么区别
  6. 为什么错误主机名时常见表现是“假证书 + 404”
  7. 为什么 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-manager
  • Ingress 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:准备实验清单,并故意分成“前状态”和“后状态”

本课清单

我新增了:

为什么要拆成两个 Ingress 版本

30-ingress-http-only.yaml

只写:

  • host
  • path
  • backend Service

故意不写:

  • spec.tls

作用:

  • 证明“HTTPS 也许能通,但证书不一定对”

31-ingress-with-tls.yaml

在同一个对象上额外增加:

  • spec.tls.hosts
  • spec.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-web1/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-web
  • CLASS: nginx
  • HOSTS: secure.k8s-lab.local
  • ADDRESS: 10.109.198.120

Endpoints

  • 10.244.147.112:8080

原理解释

这说明:

  1. 路由对象已经存在
  2. 后端成员表已经正常
  3. 还没涉及 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 很关键。

它会:

  1. secure.k8s-lab.local:30443 临时解析到 127.0.0.1
  2. URL 主机名仍然保持 secure.k8s-lab.local
  3. 因此 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

原理解释

这说明:

  1. 入口加密已经发生
  2. 但服务端回的不是我们业务域名对应证书
  3. 它回的是 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.crt
  • tls.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.hosts
  • secretName
  • 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

原理解释

这说明:

  1. 客户端到 ingress-nginx 用的是 HTTPS,而且协商到了 HTTP/2
  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 决定的是证书选择,不是路由本身

路由仍然主要由:

  • Host
  • Path

决定。

结论 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_clientcurl --resolve 做 HTTPS 入口排障
  • 能解释为什么 TLS termination 后后端仍然可以只跑 HTTP

这已经是做平台入口、安全接入和生产故障排查非常核心的一块能力。