背景
一直以来,我负责维护的K8s都是由SRE搭建的。就操作系统和容器网络接口(CNI)的组合而言,最初我们使用的是CoreOS + Flannel。因为几年前并没有太多可供选择的替代方案,所以我们使用了好几年。后来,CoreOS被RedHat收购,分裂成了FlatCar CoreOS和Fedora CoreOS。而随着市场需求和云服务提供商对K8s的大力支持,CNI也有了越来越多的选择。目前,我们搭建的K8s组合变成了FlatCar CoreOS + Amazon VPC CNI。在这个组合下,我在过去一两年中遇到的一些常见问题也算是通过这篇文章记录下来了。
什么是容器操作系统?
www.netapp.com/blog/contai…
我一开始并没有选择常见的Linux发行版,比如Ubuntu,而是选择了Container OS。这个词是我接触CoreOS时才学到的,它是一种专门为了运行容器而进行优化的操作系统。或许称之为Container Optimized OS更容易理解。后来市场上涌现了越来越多的Container OS,例如Fedora CoreOS(RedHat)、FlatCar CoreOS(Microsoft)、Bottlerocket(AWS)、RancherOS(Rancher)等。或许大家可以发现每一种Container OS背后都有大公司的支持。换句话说,当一家公司要提供K8s托管服务时,现在似乎都会准备好符合自己需求的Container OS。根据我自己使用过CoreOS、FlatCar CoreOS和Fedora CoreOS的经验,来分析一下Container OS的优缺点
😃 优点:
- 操作系统的发布流程变得像一般软件一样:根据功能或安全需求,每个月都会推出多个更新版本,不像传统的Linux发行版,几年才会发布一个大版本更新,有问题时才会进行修复
- 让容器运行的最小需求环境:移除不必要的软件包,并且用户不能在后续随意安装任何东西,必须尽量通过容器的方式满足需求,许多文件都是只读模式无法修改,在安全性方面提升很多
- 从一开始就考虑好配置管理:默认就有内建 Provision 模块以及与各种平台整合的方式,让使用者可以轻松完成操作系统内的各种设置,例如 CoreOS 系统所使用的 Ignition
🥲 缺点:
- 不灵活的更新方式:以前遇到高风险的CVE时,可以选择只修补有问题的部分,但是容器操作系统每次更新都是一大包,有时会变相强迫用户安装其他与安全修补无关的更新,导致出现意料之外的错误。例如以前如果容器运行时需要安全修补,直接更新和测试就可以解决问题,但是容器操作系统每次发布的版本都包含很多东西,除了目标安全更新之外,可能还包含iptables和systemd-networkd的相关修改,导致用户需要测试的范围不小心变大了
- 无法随意定制化:由于无法自行安装软件,如果遇到无法通过容器实现的需求,只能等待官方支持,有时候等待时间很长,比如我希望官方能够支持新版本的Ignition,结果花了一年才实现(参考资料)
当然这些缺点可以通过自己去构建容器操作系统来避免,但这样会增加维护的工作量,时间应该投资在更有价值的事情上;我个人觉得这个领域在过去两年相对较为低调,不过仍在持续发展,可能是因为大部分人都使用托管服务,所以不需要太关注运行K8s底层的操作系统是哪一个
CNI是什么?
CNI的全称是Container Network Interface,它包含了配置Linux容器网络的规范和函数库(参考资料)。其主要功能是在容器运行时创建容器时,先创建好网络命名空间,并调用CNI插件为其配置相关网络设置。在CNI的多个插件中,我认为比较重要的功能有以下两个:
- 负责为容器配置网络:通过实现AddNetwork接口为容器配置网络,然后通过实现DelNetwork接口来删除容器网络
- 负责为容器分配IP地址:就像字面上的意思一样,只是不同的CNI有不同的IP地址配置方式
当 kubelet 在创建 Pod 时,它如何通过 CNI 来成功配置 Pod 的网络呢?实际上,在 kubelet 的参数中可以看到两个与 cni 相关的参数,分别是..
- cni-bin-dir(默认为/opt/cni/bin):是存放各种CNI插件执行文件的位置,例如上面提到的配置网络、删除网络和分配IP地址的功能
- cni-conf-dir(默认为/etc/cni/net.d):存放CNI的配置文件,让kubelet知道在设置网络时,需要通过哪些插件来完成Pod的配置。
如果这两个文件夹里的东西都准备好了,那就可以说成功了一半了XD
亚马逊虚拟私有云容器网络接口(Amazon VPC CNI)
github.com/aws/amazon-…
Kubernetes 对于 Pod 的网络配置有一些规定:
- 所有的容器必须在没有网络地址转换(NAT)的帮助下与其他容器进行通信
- 所有的节点必须在没有网络地址转换(NAT)的帮助下与其他容器进行通信,反之亦然
- 容器本身使用的IP地址和其他人与其进行连接时看到的IP地址必须是相同的
因此,以 Amazon VPC CNI 为例,简单介绍一下当一个 Pod 生成时,需要完成哪些步骤,才能够达到上述 K8s 所要求的目标
- 配置IP地址:首先,Pod所在的Node(EC2)通过ipamd分配一个VPC中的空闲IP地址
- 设置虚拟网络卡接口:在主机命名空间中创建一个 veth 组,另一个 veth 在 Pod 命名空间中
- 设置Pod内网络:在Pod内为eth0配置IP地址,为Pod的路由表添加默认网关和默认路由,最后在默认网关中添加静态ARP
- 设置主机网络:在主机端需要添加主机路由和规则,这样才能成功连接到 Pod 的外部连接
- 设置外部连接:Pod 到 VPC 以外网络的通信连接方式是通过 iptables SNAT,利用主要 ENI 的主要 IP 来实现
虽然这里以 AWS VPC CNI 为例,但其他类型的 CNI 或多或少都会有类似的步骤,所以一旦深入了解一种 CNI,学习其他种类时也会相对容易上手
如何解决CNI的故障?
CNI的开发其实经过了完整的测试,但是为什么我自己使用起来还是遇到了不少问题呢?
- 由于软件必然存在Bug,测试也不可能覆盖所有情况
- CNI 必须与操作系统完美协作才能发挥功能,然而不同的操作系统使用不同的网络管理套件和配置设置,这些在开发测试时很难全面考虑到
但目前整个K8s的升级频率相当高,为了保持系统安全并享受到最新功能,所以自己在升级操作系统或是CNI时就遇到了不少问题。根据上面的知识补充,了解到在创建Pod时要知道如何使用CNI来配置网络,以及CNI如何为Pod配置网络。所以当Pod的网络连接不符合预期时,就有一些方向可以去查找问题
🛠️ kubelet
- 查询 kubelet 日志:kubelet 是负责创建 K8s Pod 的角色,所以如果 K8s Pod 的网络一开始就无法完全设置好,可以去查看 kubelet 的日志,看看是否提到了重要信息
- 检查CNI相关文件:检查一下cni-bin-dir和cni-conf-dir这两个参数所设置的文件夹里面有没有缺少的内容
🛠️ CNI
- 查询CNI插件日志:CNI的主要功能是配置网络、清理网络、分配IP等。如果在执行这些重要步骤时遇到问题,应该可以在日志中找到一些线索,然后可以去该CNI的GitHub存储库查找问题或查看代码,看看如何解决遇到的问题
🛠️ 操作系统网络管理套件
- 确定使用哪种网络管理套件:操作系统内都会有一个管理网络的套件,例如FlatCar CoreOS使用的是systemd-networkd。当CNI在为Pod配置和删除网络时,会新增虚拟网络接口,IP Route/Rule
- 查询网络管理套件的调试日志:只有在网络管理套件的调试日志中才能找到相应的操作,例如我自己曾经踩过一个坑,有些网络套件会删除未在配置中定义的IP路由/规则,因此在CNI完成IP路由/规则的添加后,网络管理套件会将其全部删除,这种情况可以从调试日志中找到罪魁祸首
解决方案有两种(详细信息可以参考我刚好参与的 GitHub 问题)
- 使用全局参数:就拿systemd-networkd来说,有两个参数叫做
ManageForeignRoutes
和ManageForeignRoutingPolicyRules
,当将它们设置为False时,systemd-networkd就不会删除由其他人(CNI)添加的IP路由/规则 - 特定网络接口:如果网络管理套件没有类似上述参数可供设置,那就参考上述GitHub Issue中提到的方法,针对特定的网络接口进行设置,以确保CNI添加的IP路由/规则不会被删除
🛠️ iptables/nftables
- iptables规则有问题:不同的CNI会通过iptables进行不同的设置,可以研究一下是否有遗漏或错误的规则。如果有的话,在kubelet启动之前可以先添加正确的规则作为解决方法。但我个人认为最好在CNI上报一个问题,让他们主动去添加更规范的规则
- iptables被淘汰了:不知道大家有没有听说过nftables?它是用来取代iptables的封包过滤工具。一些操作系统已经开始将iptables替换为nftables。如果CNI仍然使用iptables添加规则,但是主机已经转换为nftables,就会遇到问题。最近我自己也碰到了这种情况再贴一个GitHub Issue
🛠️ sysctl
- 查询 sysctl 的 net 相关参数:最后就是查询操作系统看看有没有将 sysctl 中跟网络相关的设置给打开来,例如:net.bridge.bridge-nf-call-iptables, net.ipv4.ip_forward…等(参考资料)
结论
网络世界广阔而深奥,每次解决了一个问题后,会觉得自己懂了,但下次遇到不同的问题又会感觉好像什么都不懂, 如果以后再遇到与CNI相关的问题,我会再补充到这篇文章中;目前各家云服务提供商都开发了自己的CNI,以便将K8s集群与现有的网络环境完美地整合在一起。而在本地搭建的情况下,之前我使用过Calico,最近经常听到Cilium,等到有需要实际应用时再来研究一下。