..

ingress-nginx调试笔记

今天把反向代理从 nginx 搬运到 ingress-nginx 的过程中,出现了几个问题。

minikube addons enable ingress 超时

在国内网络环境不好,下载 ingress-nginx 的镜像很容易超时,这就导致了 minikube addons enable ingress 的时候超时。

解决方案是在 minikube 内配置代理,或者在有代理的本机拉取到镜像后,导入到 minikube 的 docker 里。 另外,建议不要用 minikube addons 启动 ingress-nginx,而是用 helm chart 启动。

  1. 下载 4.8.3 的 helm chart,解压待用
  2. 在目录下创建个 minikube_values.yaml 来覆盖 values.yaml 的值。把 image 的 pullPolicy 都设置为 never,如下代码。
  3. 在“网络环境”好的机器上,手动去 pull values.yaml 里所需要的镜像。导出后 scp 到 minikube 的节点上,再 导入
  4. 这样就可以安装 chart,因为镜像已经导入,并且 pullPolicy=never ,就不会因网络环境不好而超时了。
controller:
  image:
    pullPolicy: Never
  admissionWebhooks:
    patch:
      image:
        pullPolicy: Never
  keda:
    service:
      externalIPs:
        # 这个 ip 是 minikube 节点的ip,通过 minikube ip 命令获取
        - 192.168.49.2

MacOS 无法访问到 ingress-nginx

我在笔记本实验的时候,创建完 ingress,在本机死活访问不到。这其实是老问题了!MacOS 下的 docker ,其实是运行在虚拟机上的。minikube 上的 k8s 节点,跟 MacOS 之间大概隔着两层网络。

可以使用 minikube tunnel 来打通。

服务器上 curl 报证书错误

刚搬运完一个服务通过 ingress 访问之后,就在服务器上试了下 curl https://xxx.xx 却报错,加上 -v 之后看到:

* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: unable to get local issuer certificate
* Closing connection 0
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

但是通过自己的笔记本、手机去访问,都没有报证书错误。其实上面的信息已经说得八九不离十, unable to get local issuer certificate 奈何自己对TLS整个签发、验证过程了解不深。所以一通摸瞎。

先说解决方案,应该用 fullchain.pem 证书,详见这篇 Q&A: Unable to use my cert in the terminal

摸瞎过程,了解几个查看证书信息的命令。

显示证书的发行者

我一开始以为是证书有问题,所以想看看请求拿到的证书是什么样子的。虽然在自己笔记本的浏览器上可以看到证书详情,但是毕竟这个环境没有出问题,出问题的环境是在几台服务器上。

在纯命令行环境下,可以使用 openssl s_client 命令来查看证书,参见 Using openssl to get the certificate from a server

$ openssl s_client -showcerts xxyy.com:443

CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = [...省略]
verify return:1
[...省略]

这样就能看到服务的 TLS 显示服务支持的TLS版本证书,然后简单比较下看跟自己在服务端配置的证书是不是有什么信息出入。

显示服务支持的TLS版本

看完证书,也没发现什么大问题。于是怀疑是不是 ingress-nginx 的 TLS 版本太高了,而服务器上的 curl 不支持。(现在想想这个猜测真的很摸瞎,没太大道理,这里就当学习下命令使用)

参见 Command prompt to check TLS version required by a host:

# 同样是 openssl s_client命令

# 测试服务支不支持TLS1.3
> openssl s_client -connect www.google.com:443 -tls1_3

# 测试服务支不支持TLS1.2
> openssl s_client -connect www.google.com:443 -tls1_2

# 测试服务支不支持TLS1.1
> openssl s_client -connect www.google.com:443 -tls1_1

# 测试服务支不支持TLS1.0
> openssl s_client -connect www.google.com:443 -tls1

如果服务是支持的,则会显示出证书信息来。如果服务是不支持的,而会有报错。

但是很奇怪,我试了下自己个服务,证书其实已经配对了,但显示出来的证书还是之前配错的证书:

CONNECTED(00000005)
depth=0 O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 O = Acme Co, CN = Kubernetes Ingress Controller Fake Certificate
verify error:num=21:unable to verify the first certificate
verify return:1
write W BLOCK
[...省略]

这是 ingress-nginx 自己生成的默认证书。估计是有什么缓存吧!

k8s secret 不支持跨 namespace

搬运到后面,又一个服务的证书就是配置得不对,其它服务的证书倒是没有问题。 后来才发现,是这个服务在另一个 namespace 下,而先前创建的 tls secret 在这个 namespace 下没有。

这个错误还是很好发现的,通过命令行或者浏览器查看证书,就会发现证书是 ingress-nginx 自签发的假证书。 另外查看 ingress-nginx pod 的日志,也会发现这样的报错日志:

W0107 04:21:28.403078       7 backend_ssl.go:47] Error obtaining X.509 certificate: no object matching key "devops-tools/my-tls" in local store
W0107 04:21:28.404208       7 controller.go:1436] Error getting SSL certificate "devops-tools/my-tls": local SSL certificate devops-tools/my-tls was not found. Using default certificate

这个报错很清晰了,就是找不到 devops-tools 这个 namespace 下的名为 my-tls 的secret,所以采用了一个默认的 tls 证书。

解决方案也很简单,把那个 secret 对象搬运一份到 devops-tools 下即可。

css integrity 校验出错

修好证书问题后,访问一些页面发现样式挂了,看了下 console 后,发现 css integrity 校验出错。仔细对比了下迁移前后的请求,发现css文件的内容完全没有变!再对比 Response Headers,发现本该是 text/cssContentType 变成了 text/html

看了这个issue,发现问题所在。我写的 Ingress 都是参照官方的 example-ingress.yaml 例子,实际上我的服务都不需要对请求进行 rewrite

至于为什么 nginx rewrite 会丢失 Content-Type,暂时未深入研究。所以解决方案就是把抄过来的 rewrite 配置去掉,css integrity 检验就没有报错了。

题外话,这个校验会报错也蛮奇怪的,因为两个文件是完全一样的。根据 MDN 的这篇 Subresource Integrity 文档,对文件计算 Integrity,也是匹配的。计算方式是:

$ cat main.css | openssl dgst -sha512 -binary | openssl base64 -A
BDRm0FLwr/dlnE++4ubml+J9+xg86c3azAdr3Wj18S5GQ/BskvkUANyMGugx3A8NKWj69krcG8ugm0XA+HKNxw==

得出的结果也是匹配的。估计浏览器在进行integrity校验之前,会检验下 Content-Type 与预期的资源类型是否匹配吧!

Error: 413 request entity too large

我的某个服务会传输挺大的 body,大于 1mb,需要配置 client_max_body_size。但是在 ingress 里如何配置呢? ingress-nginx 支持从 ingress 的 annotations 里读取配置(所有的配置项在这个文档里)。例如这个 413 报错,这样修复即可:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: xxx-ingress
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: 200m
spec:
  # ... 省略