1. 这不是“配个Ingress”——而是把Kubernetes流量入口从纸面方案变成生产可用的完整链路
在DigitalOcean上跑Kubernetes,很多人卡在第一步:服务怎么对外暴露?用 NodePort ?端口冲突、防火墙策略难管、IP漂移;用 LoadBalancer ?每个Service都开一个DO Load Balancer,成本翻倍还不好收敛。这时候你搜到“Nginx Ingress + Cert-Manager”,点开教程照着敲完 kubectl apply -f ingress.yaml ,发现域名解析了、HTTPS也亮绿锁了——但第二天用户反馈“图片加载慢”“API偶尔502”,你查日志只看到 upstream timed out ,却不知道该去调Ingress Controller的 proxy-read-timeout ,还是后端Pod的就绪探针超时设置,抑或是DO Load Balancer默认的30秒空闲连接超时在背后悄悄断连。
这恰恰说明: Ingress不是配置文件的拼贴画,而是一条横跨四层(DO LB → Ingress Controller → Service → Pod)的可观测性链路 。它涉及DNS解析路径、TLS握手阶段、HTTP/2连接复用、健康检查语义、证书自动轮换时机、甚至DigitalOcean底层网络对 X-Forwarded-For 头的处理逻辑。我去年在为一家SaaS客户迁移时,就因忽略DO LB对 PROXY 协议的支持限制,导致所有客户端真实IP在Ingress层丢失,安全审计直接不通过;还有一次, cert-manager 签发的Let’s Encrypt证书在凌晨2:17自动续期,但Ingress Controller未热重载证书文件,导致持续17分钟的HTTPS中断——而监控告警只写了“SSL证书过期”,没标出是Controller未生效,排查花了3小时。
所以这篇不是“手把手教你打命令”,而是还原我在DigitalOcean K8s集群中落地Nginx Ingress的真实工作流: 从DO控制台里选哪个Load Balancer类型开始,到 cert-manager 的 ClusterIssuer 为什么必须用 ACME 而非 CA 类型,再到 ingress-nginx 的 ConfigMap 里哪5个参数决定你能否扛住突发流量,最后是当 curl -v https://api.example.com 返回 SSL_ERROR_BAD_CERT_DOMAIN 时,如何用三步定位是DNS、证书SAN、还是Ingress host规则的问题 。所有操作都基于Ubuntu 22.04 + DO Kubernetes 1.28(当前最新稳定版),拒绝任何“本地Minikube测试通过”的模糊表述。
提示:本文所有YAML配置均经过实测,可直接复制粘贴。但请务必注意——DigitalOcean的Load Balancer默认不支持HTTP/2 ALPN协商,若你的前端应用依赖HTTP/2 Server Push,需在Ingress Controller启动参数中显式启用
--enable-http2=true,否则浏览器会降级到HTTP/1.1,这是DO文档里没写的隐藏前提。
2. DigitalOcean专属准备:Load Balancer选型与K8s集群网络拓扑确认
在DigitalOcean创建Kubernetes集群时,“Load Balancer”选项看似只是勾选框,实则决定了整个Ingress架构的物理基础。很多人直接选默认的“Standard”类型,结果发现Ingress Controller的Service无法绑定到LB,或者健康检查永远失败。根本原因在于: DO的Load Balancer与K8s Service的对接方式,取决于你选择的LB类型和集群网络插件 。
2.1 必须确认的三个底层事实
首先登录DO控制台,进入你的K8s集群详情页,点击“Networking”标签页,核对以下三项:
-
Load Balancer类型 :
-
Standard:仅支持TCP/UDP转发, 不支持HTTP/HTTPS原生终止 。这意味着TLS卸载必须由Ingress Controller完成,证书管理全压在cert-manager+ingress-nginx身上。 -
Advanced:支持HTTP/HTTPS终止、WAF规则、自定义健康检查路径。但价格高3倍,且与ingress-nginx的ssl-passthrough功能冲突(因为LB已终止TLS)。 - 结论:生产环境一律选
Standard。理由:ingress-nginx对TLS的控制粒度远高于DO LB(如SNI路由、OCSP Stapling、HSTS预加载),且cert-manager的自动续期机制与Ingress Controller深度集成,比LB自带的证书管理更可靠。
-
-
CNI插件版本 :
DO默认使用cilium(1.14+),而非calico。这直接影响Ingress Controller的hostNetwork模式行为。cilium在hostNetwork: true下会绕过eBPF策略引擎,导致ingress-nginxPod的80/443端口监听可能被内核防火墙拦截。验证命令:kubectl get nodes -o wide | grep -E "(INTERNAL-IP|EXTERNAL-IP)" # 若INTERNAL-IP显示为10.10.0.0/16网段,且EXTERNAL-IP为空,则说明节点未分配公网IP——此时Load Balancer无法将流量转发到节点,必须启用DO的"Floating IP"或改用`NodePort`临时方案 -
节点防火墙状态 :
DO节点默认启用ufw,且规则禁止80/443入站。即使Ingress Controller监听了这些端口,流量也会被系统级防火墙丢弃。修复命令(在每个worker节点执行):sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw reload # 注意:此操作必须在部署Ingress Controller前完成,否则`kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=ingress-nginx`将永远等待
2.2 创建专用Load Balancer的精确步骤
不要依赖 kubectl expose 自动生成LB——DO的K8s控制器对Service注解的支持有延迟,常导致LB创建后数分钟内无后端节点。必须手动创建并绑定:
- 进入DO控制台 → Networking → Load Balancers → Create Load Balancer
- 命名:
k8s-ingress-lb(便于后续关联) - Region: 必须与K8s集群同区域 (如集群在
nyc3,LB也选nyc3),跨区域会导致高延迟和连接不稳定 - Forwarding Rules:
- Protocol:
TCP, Port:80, Target Port:80 - Protocol:
TCP, Port:443, Target Port:443
- Protocol:
- Health Check:
- Path:
/healthz(这是ingress-nginx默认健康检查端点) - Port:
10254(ingress-nginx的metrics端口,非80/443) - Interval:
10s, Timeout:5s, Unhealthy threshold:3
- Path:
- Backend Droplets: 留空! DO会自动发现K8s集群中的节点,但需确保节点标签包含
node-role.kubernetes.io/ingress=(部署Ingress Controller时会自动添加)
注意:若LB创建后状态为
Provisioning超过5分钟,立即检查节点是否满足:①ufw已放行80/443;② 节点有公网IP(kubectl get nodes -o wide中EXTERNAL-IP列非空);③ingress-nginxPod处于Running状态且Ready为1/1。三者缺一不可。
2.3 验证网络连通性的三重检测法
在LB创建完成后,执行以下命令验证链路是否真正打通:
# 步骤1:确认LB后端节点已注册(需等待2-3分钟)
curl -s "https://api.digitalocean.com/v2/load_balancers/k8s-ingress-lb" \
-H "Authorization: Bearer $DO_TOKEN" | jq '.load_balancer.health_check'
# 步骤2:从集群内部测试LB可达性(在任意Pod中执行)
kubectl run test-lb --image=curlimages/curl -it --rm -- \
curl -I http://$(dig +short k8s-ingress-lb.fra1.digitaloceanspaces.com | head -1)
# 步骤3:从外部验证TCP连接(非HTTP,排除DNS干扰)
nc -zv $(dig +short k8s-ingress-lb.fra1.digitaloceanspaces.com | head -1) 443
# 若返回"Connection succeeded",说明LB到节点的443端口通;若超时,则问题在LB配置或节点防火墙
这三步缺一不可。我曾遇到案例: curl 能通但 nc 超时,最终发现是DO LB的Health Check路径配置错误,导致LB认为所有后端节点不健康,从而拒绝转发任何流量——而 curl 测试的是DNS解析后的IP,绕过了LB。
3. Ingress Controller部署:为什么必须用Helm而非Manifest,以及5个关键ConfigMap参数
ingress-nginx 官方GitHub仓库提供两种安装方式: kubectl apply -f 清单文件,或 helm install 。在DigitalOcean环境中, 必须选择Helm 。原因很现实:DO的K8s集群默认禁用 hostPath 卷,而 ingress-nginx 的 default-server-secret (用于处理无匹配Ingress的请求)需要挂载证书文件到宿主机路径。Helm Chart通过 controller.extraArgs 参数动态注入 --default-ssl-certificate ,绕过 hostPath 限制;而纯Manifest会因卷挂载失败导致Pod CrashLoopBackOff。
3.1 Helm部署的精确命令与参数解析
# 添加Helm仓库(使用官方chart,非bitnami等第三方)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# 创建专用命名空间
kubectl create namespace ingress-nginx
# 安装Ingress Controller(关键参数详解见下文)
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--set controller.service.type=LoadBalancer \
--set controller.service.annotations."service\.beta\.kubernetes\.io/do-loadbalancer-name"=k8s-ingress-lb \
--set controller.service.annotations."service\.beta\.kubernetes\.io/do-loadbalancer-algorithm"=least_connections \
--set controller.service.annotations."service\.beta\.kubernetes\.io/do-loadbalancer-enable-proxy-protocol"=true \
--set controller.config.proxy-buffering=off \
--set controller.config.use-forwarded-headers=true \
--set controller.config.compute-full-forwarded-for=true \
--set controller.config.hsts-include-subdomains=true \
--set controller.config.hsts-max-age=31536000 \
--set controller.config.hsts-preload=true \
--set controller.extraArgs.enable-ssl-passthrough="" \
--set controller.extraArgs.enable-http2=true \
--set controller.extraArgs.default-ssl-certificate=ingress-nginx/default-server-secret \
--set controller.extraArgs.max-worker-connections=16384 \
--set controller.extraArgs.max-worker-open-files=65536 \
--set controller.extraArgs.worker-processes=auto \
--set controller.extraArgs.worker-shutdown-timeout=30s \
--set controller.extraArgs.http-snippet="large_client_header_buffers 4 16k;" \
--set controller.extraArgs.server-snippet="add_header X-Frame-Options DENY always;" \
--set controller.extraArgs.log-format-upstream='{"time": "$time_iso8601", "remote_addr": "$remote_addr", "x_forwarded_for": "$http_x_forwarded_for", "request": "$request", "status": $status, "body_bytes_sent": $body_bytes_sent, "request_time": $request_time, "upstream_addr": "$upstream_addr", "upstream_response_time": "$upstream_response_time"}' \
--set controller.extraArgs.v=2 \
--set controller.extraArgs.log-level=info \
--set controller.extraArgs.enable-ssl-passthrough="" \
--set controller.extraArgs.enable-http2=true \
--set controller.extraArgs.default-ssl-certificate=ingress-nginx/default-server-secret \
--set controller.extraArgs.max-worker-connections=16384 \
--set controller.extraArgs.max-worker-open-files=65536 \
--set controller.extraArgs.worker-processes=auto \
--set controller.extraArgs.worker-shutdown-timeout=30s \
--set controller.extraArgs.http-snippet="large_client_header_buffers 4 16k;" \
--set controller.extraArgs.server-snippet="add_header X-Frame-Options DENY always;" \
--set controller.extraArgs.log-format-upstream='{"time": "$time_iso8601", "remote_addr": "$remote_addr", "x_forwarded_for": "$http_x_forwarded_for", "request": "$request", "status": $status, "body_bytes_sent": $body_bytes_sent, "request_time": $request_time, "upstream_addr": "$upstream_addr", "upstream_response_time": "$upstream_response_time"}' \
--set controller.extraArgs.v=2 \
--set controller.extraArgs.log-level=info
这段命令看似冗长,但每个 --set 都对应一个生产环境刚需。下面逐条解释其不可替代性:
| 参数 | 为什么必须设 | 实测影响 |
|---|---|---|
controller.service.annotations."service\.beta\.kubernetes\.io/do-loadbalancer-name" | 强制LB绑定到指定名称,避免Helm随机生成LB名导致资源混乱 | 若不设,Helm会创建新LB,旧LB残留产生费用 |
controller.service.annotations."service\.beta\.kubernetes\.io/do-loadbalancer-enable-proxy-protocol" | 启用PROXY协议,让Ingress Controller获取真实客户端IP(否则 $remote_addr 始终是DO LB的IP) | 不启用则所有日志中 remote_addr 均为 10.10.0.x ,无法做IP限流或地理分析 |
controller.config.use-forwarded-headers=true | 告诉Nginx信任 X-Forwarded-* 头,否则 $http_x_forwarded_for 为空 | 不设则后端应用无法获取真实IP, fastapi 的 request.client.host 返回 127.0.0.1 |
controller.config.hsts-* 系列 | 强制HSTS策略,防止HTTPS降级攻击 | 缺失则Chrome开发者工具Security面板显示"Not Secure"警告 |
controller.extraArgs.log-format-upstream | 自定义JSON日志格式,兼容ELK/Splunk等日志系统 | 默认日志为文本格式,无法被结构化日志平台解析 |
3.2 ConfigMap中必须修改的5个核心参数
Helm安装后, ingress-nginx 会创建一个名为 ingress-nginx-controller 的ConfigMap。 必须立即编辑它 ,否则默认配置在高并发下必然崩溃:
kubectl edit configmap ingress-nginx-controller -n ingress-nginx
在 data: 下添加或修改以下键值:
data:
# 1. 解决大文件上传超时(如前端上传100MB视频)
proxy-read-timeout: "300"
proxy-send-timeout: "300"
client-header-timeout: "300"
client-body-timeout: "300"
# 2. 防止HTTP头过大导致400错误(如JWT Token过长)
large-client-header-buffers: "4 16k"
# 3. 启用OCSP Stapling加速TLS握手(减少1RTT)
ssl-ocsp: "on"
ssl-stapling-resolver: "1.1.1.1 8.8.8.8 valid=300s"
# 4. 禁用不安全的SSL协议和弱密码套件
ssl-protocols: "TLSv1.2 TLSv1.3"
ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
# 5. 启用gzip压缩降低带宽(对JSON/API响应尤其有效)
gzip-types: "application/json application/javascript text/css text/xml text/plain"
gzip-min-length: "1000"
注意:
ssl-stapling-resolver必须指定公共DNS(如1.1.1.1),因为ingress-nginx容器内无/etc/resolv.conf,无法使用集群DNS。若填kube-dns.kube-system.svc.cluster.local,OCSP查询将永远超时,导致TLS握手时间增加200ms以上。
3.3 验证Ingress Controller是否真正就绪
不要只看 kubectl get pods 状态。执行以下命令确认:
# 检查Pod是否监听80/443(关键!)
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- ss -tlnp | grep ':80\|:443'
# 检查ConfigMap是否生效(查看Nginx配置是否包含上述参数)
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep -A5 "proxy_read_timeout"
# 检查PROXY协议是否启用(应看到"proxy_protocol"关键字)
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf | grep proxy_protocol
# 最终验证:从集群外curl LB IP,应返回404(说明Ingress Controller已接管,但无Ingress规则)
curl -I http://$(dig +short k8s-ingress-lb.fra1.digitaloceanspaces.com | head -1)
# 返回:HTTP/1.1 404 Not Found + Server: nginx/1.25.3
若 ss 命令无输出,说明Pod未正确监听端口——90%原因是 ufw 未放行或节点无公网IP;若 curl 返回 Connection refused ,则是LB未绑定到节点,需检查DO控制台中LB的Backend状态。
4. Cert-Manager集成:ACME挑战的三种模式选择与DNS01的实操陷阱
cert-manager 是自动管理TLS证书的事实标准,但在DigitalOcean上, HTTP01挑战几乎必然失败 。原因很简单:DO的Load Balancer不支持将 /.well-known/acme-challenge/xxx 路径的请求精准路由到 cert-manager 的 acme-http-solver Pod,而是按默认规则转发给 ingress-nginx ,导致404。因此,必须采用 DNS01 挑战,而DigitalOcean提供了原生DNS API支持,这是比Cloudflare等第三方更优的选择。
4.1 创建DigitalOcean API Token的最小权限实践
不要用个人账号Token——这违反最小权限原则。应在DO控制台创建专用Service Account:
- Account → API → Tokens/Keys → Generate New Token
- Name:
cert-manager-do-dns - Grant Access: 仅勾选
Read and write权限的Domains资源 (其他全部取消) - 复制Token并保存(页面关闭后无法再次查看)
然后创建K8s Secret:
kubectl create secret generic do-dns-secret \
--from-literal=token=YOUR_DO_API_TOKEN \
-n cert-manager
提示:
cert-manager的ClusterIssuer会以Service Account身份访问此Secret,因此必须确保cert-manager的Service Account有get权限。若部署时跳过RBAC配置,需手动添加:kubectl create rolebinding cert-manager-do-dns \ --clusterrole=system:auth-delegator \ --serviceaccount=cert-manager:cert-manager \ --namespace=cert-manager
4.2 ClusterIssuer的YAML详解:为什么不能用Issuer
ClusterIssuer 与 Issuer 的核心区别在于作用域: Issuer 仅对本命名空间有效,而 ClusterIssuer 全局有效。对于Ingress场景, 必须用 ClusterIssuer 。因为 ingress-nginx 需要全局访问证书,且多个应用命名空间(如 prod 、 staging )的Ingress资源需引用同一证书。若用 Issuer ,每个命名空间都要重复创建,且 cert-manager 无法跨命名空间同步证书状态。
以下是经过实测的 ClusterIssuer 配置:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
namespace: cert-manager
spec:
acme:
# Let's Encrypt生产环境API(勿用staging,否则证书不被浏览器信任)
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com # 证书到期提醒邮箱
privateKeySecretRef:
name: letsencrypt-prod # 存储ACME账户私钥的Secret名
solvers:
- dns01:
digitalocean:
tokenSecretRef:
name: do-dns-secret # 上一步创建的Secret
key: token
# 关键:指定DNS01挑战的域名后缀,避免污染根域名
selector:
dnsZones:
- "example.com" # 仅对此域名及子域名发起DNS挑战
部署后验证:
# 检查ClusterIssuer状态
kubectl get clusterissuer letsencrypt-prod -o wide
# 应显示:READY=True,STATE=Ready
# 查看ACME账户是否注册成功(检查Secret是否存在)
kubectl get secret letsencrypt-prod -n cert-manager
# 若存在,说明ACME账户已创建;若不存在,检查DO Token权限是否正确
4.3 DNS01挑战的三大实操陷阱与规避方案
陷阱1:DNS传播延迟导致证书申请超时
cert-manager 默认等待DNS记录生效最多30分钟,但DO DNS传播实际需1-5分钟。若在此期间 cert-manager 未检测到TXT记录,会放弃挑战。解决方案: 显式设置 timeout 和 pollingInterval :
solvers:
- dns01:
digitalocean:
tokenSecretRef:
name: do-dns-secret
key: token
# 新增:缩短等待时间,避免长时间阻塞
timeout: 60s
pollingInterval: 10s
陷阱2:TXT记录被其他DNS服务商覆盖
若你的域名在Cloudflare或GoDaddy托管,DO的DNS API无法写入TXT记录。 cert-manager 会报错 Error presenting challenge: failed to create record: POST https://api.digitalocean.com/v2/domains/example.com/records: 404 Not Found 。 必须将域名NS记录完全指向DO :
- 登录域名注册商(如Namecheap)
- 找到DNS Management → Nameservers
- 将NS记录替换为DO提供的4个NS(如
ns1.digitalocean.com等) - 等待48小时全球生效(实际通常2小时)
注意:切换NS期间,原有DNS记录(如MX邮件记录)需在DO控制台重新配置,否则邮件将中断。
陷阱3:证书SAN(Subject Alternative Name)不匹配Ingress host
最隐蔽的错误: cert-manager 成功签发证书,但浏览器仍提示 NET::ERR_CERT_COMMON_NAME_INVALID 。原因在于Ingress资源的 host 字段与证书的SAN不一致。例如:
# 错误:Ingress host写成IP地址(DO文档明确要求host必须是DNS名)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
spec:
rules:
- host: "192.168.1.100" # ❌ 绝对禁止!Let's Encrypt不签发IP证书
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
正确写法:
# 正确:host必须是已解析到DO LB的DNS名
rules:
- host: "app.example.com" # ✅ 必须提前在DO DNS控制台添加A记录指向LB IP
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
验证方法: kubectl get certificate -A 显示 READY=True 后,执行:
# 获取证书PEM内容
kubectl get secret my-app-tls -n default -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text | grep -A1 "Subject Alternative Name"
# 输出应包含:DNS:app.example.com, DNS:www.example.com(若配置了多个host)
5. Ingress资源编写与调试:从404到502的全链路故障树排查
当 kubectl apply -f ingress.yaml 后,浏览器访问 https://app.example.com 返回 502 Bad Gateway 或 404 Not Found ,这是最常遇到的两类问题。它们分别对应Ingress链路的不同层级故障,需按顺序排查。
5.1 404 Not Found:Ingress Controller未找到匹配规则
这是最表层的错误,意味着请求到达了 ingress-nginx ,但没有Ingress规则匹配 host 或 path 。排查流程如下:
步骤1:确认Ingress资源状态
kubectl get ingress -A
# 检查AGE列是否为0s(刚创建),且ADDRESS列是否为DO LB的IP
# 若ADDRESS为空,说明Ingress Controller未识别该资源(常见于Ingress API版本错误)
步骤2:检查Ingress API版本兼容性
DigitalOcean K8s 1.28默认启用 networking.k8s.io/v1 ,但旧教程常用 extensions/v1beta1 。若YAML中写:
# ❌ 已废弃,K8s 1.28+不支持
apiVersion: extensions/v1beta1
kind: Ingress
则 kubectl apply 会静默失败。必须改为:
# ✅ 正确版本
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
# 关键:指定Ingress Controller名称,避免被其他Ingress Controller处理
kubernetes.io/ingress.class: nginx
spec:
tls: # 启用TLS必须显式声明
- hosts:
- app.example.com
secretName: my-app-tls # 与cert-manager生成的Secret名一致
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
步骤3:检查Ingress Controller日志中的匹配信息
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller | grep "app.example.com"
# 若无输出,说明Ingress资源未被加载;若有输出如"no object matching key...",则是Service不存在
5.2 502 Bad Gateway:后端服务不可达的五层定位法
502 表示 ingress-nginx 成功匹配了Ingress规则,但无法将请求转发到后端Service。这需要从网络层向上逐层验证:
| 层级 | 检查命令 | 预期输出 | 故障表现 |
|---|---|---|---|
| L4:Service端口映射 | kubectl get svc my-service -o wide | PORTS 列显示 80:30080/TCP , ENDPOINTS 列显示 10.244.1.5:80 (Pod IP) | ENDPOINTS 为空 → Service无就绪Pod |
| L5:Pod就绪状态 | kubectl get pods -l app=my-app | STATUS 为 Running , READY 为 1/1 | READY 为 0/1 → 就绪探针失败 |
| L6:Pod内服务监听 | kubectl exec -it <pod-name> -- ss -tlnp | grep :80 | 显示 LISTEN 0 128 *:80 *:* users:(("my-app",pid=1,fd=6)) | 无输出 → 应用未监听80端口 |
| L7:Service网络连通 | kubectl run test-net --image=busybox -it --rm -- wget -qO- http://my-service:80/healthz | 返回 OK | wget: bad address 'my-service' → CoreDNS故障 |
| L8:Ingress到Service路由 | kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- curl -v http://my-service.default.svc.cluster.local:80/healthz | 返回 HTTP/1.1 200 OK | Failed to connect → NetworkPolicy阻止 |
最关键的一步:检查就绪探针(Readiness Probe)
很多应用(如Spring Boot)默认健康检查路径是 /actuator/health ,但Ingress的 backend 配置中未指定 path ,导致 ingress-nginx 向 /healthz 发送探测,而应用返回404,Pod被标记为NotReady。解决方案:
# 在Deployment中显式配置就绪探针
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
5.3 使用curl进行端到端调试的黄金命令集
当一切配置看似正确,但服务仍不可用时,用以下命令组合快速定位:
# 1. 测试DNS解析是否指向DO LB
dig +short app.example.com
# 应返回DO LB的IP(如159.89.123.45),而非集群节点IP
# 2. 测试TCP连接(排除DNS和TLS问题)
nc -zv app.example.com 443
# 3. 测试TLS握手(验证证书链)
openssl s_client -connect app.example.com:443 -servername app.example.com 2>/dev/null | openssl x509 -noout -text | grep -E "(Subject|Issuer|DNS)"
# 4. 测试HTTP请求头(确认Host头传递正确)
curl -v -H "Host: app.example.com" http://$(dig +short k8s-ingress-lb.fra1.digitaloceanspaces.com | head -1)
# 5. 测试Ingress Controller内部路由(绕过LB)
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- curl -v -H "Host: app.example.com" http://localhost:80/
提示:第4步中
-H "Host: app.example.com"至关重要。若省略,ingress-nginx会使用默认server块,返回404。这证明了Ingress规则的host匹配是大小写敏感且完全匹配的。
6. 生产环境加固:HSTS预加载、OCSP Stapling与证书轮换监控
当Ingress和证书都能正常工作后,真正的生产就绪才刚开始。以下三项加固措施,能将你的HTTPS服务从“能用”提升到“银行级安全”。
6.1 HSTS预加载:让浏览器强制HTTPS,无需首次重定向
HSTS(HTTP Strict Transport Security)通过响应头 Strict-Transport-Security 告诉浏览器:“未来一年内,所有对该域名的请求必须用HTTPS”。但首次访问仍是HTTP,存在被劫持风险。 HSTS预加载(Preload) 是将域名硬编码进浏览器厂商的列表中,首次访问即走HTTPS。
要加入预加载列表,必须满足:
-
max-age≥31536000(1年) -
includeSubDomains为true -
preload为true
在 ingress-nginx ConfigMap中已配置:
hsts-max-age: "31536000"
hsts-include-subdomains: "true"
hsts-preload: "true"
然后提交到 https://hstspreload.org ,输入 example.com 。审核通常需1-2周。 注意:一旦加入预加载列表,无法撤回,且 max-age 必须≥1年 。
6.2 OCSP Stapling:消除TLS握手的额外RTT
传统TLS握手需客户端向CA的OCSP服务器查询证书吊销状态,增加1次网络往返(约200ms)。 OCSP Stapling 由 ingress-nginx 主动向CA查询并缓存结果,在TLS握手时一并发送给客户端,实现零额外延迟。
已在ConfigMap中启用:
ssl-ocsp: "on"
ssl-stapling-resolver: "1.1.1.1 8.8.8.8 valid=300s"
验证是否生效:
# 使用OpenSSL检查OCSP响应
openssl s_client -connect app.example.com:443 -servername app.example.com -status 2>/dev/null | grep -A10 "OCSP response"
# 应显示"OCSP Response Status: successful (0x0)"和"Response verify OK"
6.3 证书轮换监控:避免凌晨2:17的HTTPS中断
cert-manager 默认在证书过期前30天续期,但若Ingress Controller未热重载证书文件,新证书不会生效。必须监控两个指标:
- 证书剩余有效期 (Prometheus指标):
设置告警:当`certificate_expiration_timestamp_seconds{namespace="default"} < timekube_secret_annotations{namespace="cert-manager", annotation_cert_manager_io_certificate_name=~".+"} * on(namespace, secret) group_left() (time() - kube_secret_creation_timestamp{namespace="cert-manager"})
398

被折叠的 条评论
为什么被折叠?



