Kubernetes和GitOps的血与泪


Kubernetes能够帮助管理你的计算工作负载,而你无需关心它们运行在哪台物理服务器上。

GitOps能够让你以版本控制的方式去管理这些工作任务,通常是把它们放在中心化的代码仓库中。比起把nginx.con这样的配置文件置于某些服务器的available-sites目录,把它们纳入Git仓库中会更容易发现其中隐藏的细微的错误。

考虑到这一点,我开始把各种分散的、各异的Docker Compose文件进行迁移。这是我一路上犯的一些错误,以及困扰我的问题。

我写这篇文章的目的是希望让你避免一些痛苦和挫败感,也许还可以让你对你将要面对的问题有一定的了解。

经验法则:一次只修改一处

当你正在尝试建立一个复杂的系统并使其运行,你可能会同时在许多组件上进行大量的变更,这个工作本身可能听上去十分有趣且诱人。但是,随后出现了故障,并且你不知道具体是什么导致了问题。

以我的经验,有时会有许多东西可能会导致这个问题,如果你所作的改动是其中的一种可能,那么调试起来会很麻烦。

我建议每次提交一个小的变更,然后不断地增量式更新。编码,发布然后测试,再重复以上步骤。

留意版本号

如果你正在使用GitOps,并且需要手动指定Chart所依赖的版本,请确保该版本符合你的想法,并通过以下方式获取最新版本:helm show chart repo / chart-name | grep版本。对此,我会用一个脚本来做到这一点。

我曾经很多次遇到过这个问题。我花了一个小时的时间对照一个Helm Chart的源码和我的values.yaml文件,冥思苦想到底为什么这个会不起作用,按理说绝对没有问题啊。最后发现是这个Chart不是最新的版本。

在进行深入的调试之前,请检查一些最基本的东西:比如,这个Chart肯定是你认为的那个版本吗?

echo、base64以及换行符

作者注:我发现使用stringData,能够以明文的方式创建secrets,这能够完全避免这个问题。详情可以参考文档

在Kubernetes中,secrets都必须以base64进行编码。这能够有效防止站在你身背后的人偷窥你的密码。

那么问题就来了:在命令行中,你必须使用诸如echo 'MY SUPER SECRET PASSWORD' | base64 | pbcopy这样的命令来生成base64编码的secret。 这条命令看上去没什么问题,它的输出确实是base64的格式。

但你实际上并不能在脑子里直接解码base64字符串,你可能并没有意识到U0VDUkVUIFBBU1NXT1JECg==U0VDUkVUIFBBU1NXT1JE存在差别。

这个差别来源于echo会默认在最后加上一个换行符。这会造成灾难性的后果,因为大部分程序,尤其是在比较密文的时候,会比较精确值。它不会替你先做trim的操作,所以你的密码匹配不上。

你甚至可能像我一样以前就知道这一点,但仍然会犯这个错误。 使用echo -n能避免在行尾打印换行符,你就不会不小心"踢"到你自己。或者更好的做法是:如果你的编辑器带有内置的"encode base64"和"decode base64”命令,请改用该命令。 这个VS Code扩展能够让VS Code支持这些命令。

PVCs 和 PVs

我遇到很多PVCs没有绑定到正确的PV的问题,或者说是由于不匹配而没有绑定成功。

检查PV和PVC之间的accessModes是否匹配,并使用volumeName,尤其是当你仍然有多个未绑定的PV时。你当然不希望PVC绑定到错误的Volume。

如果你正在使用的Helm Chart,但这个Chart不允许你指定volumeName,那么它很可能会让你使用existingClaim字段,因此你可以预先使用一个volumeName来定义自己的PVC。

Ingress的路径

如果你使用的是带有ssl/httpsIngress,而它们的后端服务却不同,则你可能无法使用paths。

我决定使用WebSocket协议来访问我的MQTT broker,同时也需要它能够支持MQTT协议,于是我"无辜地"写了以下的ingress.yaml文件:
rules:
- host: mqtt.private.channings.me
  http:
    paths:
      - path: /
        backend:
          serviceName: mosquitto
          servicePort: mqtts
      - path: /ws
        backend:
          serviceName: mosquitto
          servicePort: wsmqtts

这个定义看起来没什么问题,而且我的Ingress Controller也没有输出任何错误信息。然而,这会转译成以下的规则:
use_backend mosquitto-mosquitto-wsmqtts if { req_ssl_sni -i mqtt.private.channings.me
use_backend mosquitto-mosquitto-mqtts if { req_ssl_sni -i mqtt.private.channings.me

如你所见,mqtts后端将永远不会被命中。当然我们也可以说这是Ingress Controller的bug,但这在其他的Ingress Controller中也会产生bug。所以如果你想要实现以上的逻辑,那么你可能需要定义另一个子域名。

解决这些问题

语法检查

当然,其中的一些问题是我的设置所特有的。 但是,你也有可能遇到对你而言非常具体的问题,那么你就需要能够诊断出此类问题。

如果你熟悉在Pod中实际运行的软件,那么它肯定会有所帮助。像之前那个HAProxy的问题,我能够发现是因为我对HAProxy配置很有经验。选择那些你比较熟悉并能够调试的软件。

你可以通过对你的代码仓库进行检查来避免愚蠢的错误从而节省时间。使用yamllint来检查YAML,kubeval可以检查你的Kubernetes定义是否正确,helm lint可以检查你的Helm Chart。

如果你正在编写Shell脚本,则可能还需要使用shellcheck

编写工具也可以防止你犯错误。其中一些问题是比较小众的,但是没有什么可以阻止你编写一个小的脚本来在提交之前检查这些事情。现在有了GitHub Actions,你可以轻松地在GitHub上运行这些检查工具

如果你对我描述的带语法检查的代码仓库感兴趣,可以看看我写的Kubeconfig这个项目。

调试

以我的经验,使用kubectl exec进入那些无法正常工作的Pod进行查看非常方便。但是,如果在你找到任何有用的信息之前,由于livenessProbe发生故障导致这个Pod被停止怎么办? 可以使用kubectl编辑并删除livenessProbe来阻止其被关闭。

虽然kubectl日志在大多数情况下都是非常好的,但是kailkubetail还可以监听一组Pod的日志。

如果你正在查看以父子关系链接的资源(例如 Certificate,CertificateRequest,Challange等),则kube tree对于查看这些关系和状态很有用。

以下是一些输出的样例:
λ k tree certificate haproxy-internal-tls -n haproxy-ingress
NAMESPACE        NAME                                                  READY  REASON  AGE
haproxy-ingress  Certificate/haproxy-internal-tls                      True   Ready   100m
haproxy-ingress  └─CertificateRequest/haproxy-internal-tls-3343126354  True   Issued  95m
haproxy-ingress    └─Order/haproxy-internal-tls-3343126354-920263207   -              95m

如果你在使用VS Code,KubernetesKubernetes Support扩展对验证你正在编写的Kubernetes资源文件非常有用。

原文链接:Mistakes made and lessons learned with Kubernetes and GitOps(翻译:小灰灰)

0 个评论

要回复文章请先登录注册