乐趣区

关于云计算:Cloudpods容器化经验分享

Cloudpods 是一个开源的多云混合云治理平台。Cloudpods 首先是一个公有云云平台,具备将计算节点应用开源 QEMU/KVM 虚拟化技术虚构出虚拟机,实现公有云的性能。其次,Cloudpods 可能纳管其余的云平台,包含支流公有云和私有云,实现云管的性能。Cloudpods 的指标是帮忙用户基于本地根底设置以及已有云根底设置,构建一个对立交融的云上之云,达到升高复杂度,进步管理效率的成果。Cloudpods 从 3.0 开始全面拥抱 Kubernetes,基于 Kubernetes 部署运行云平台的服务组件,采纳 Kubernetes Operator,基于 Kubernetes 集群自动化部署服务,实现了云平台的服务的容器化分布式部署。本文总结了 Cloudpods 在过来 3 年云平台底层容器化革新的教训。目前,将 Kubernetes 作为 IAAS 平台的底层服务治理平台是一个趋势,例如 OpenStack 的 Kolla 我的项目,VMware 的 Tanzu,以及基于 Kubernetes 的虚拟化计划 KubeVirt。Cloudpods 适应此趋势,早在 2019 年下半年开始基于 Kubernetes 构建 Cloudpods 的服务组件基础设施。实践上,Cloudpods 站在了伟人的肩膀上。有了 Kubernetes 的加持,咱们基于 Operator 治理 CRD(Custom Resource Definition) 机制做到了更优雅的服务自动化部署,合乎 IaC(Infrastructure as code)实际的服务降级和回滚,服务的主动高可用部署等等。但在实际效果上,咱们基于 Kubernetes,取得了一些便当,但也遇到了不少未曾预料到的问题。本文介绍自从 2019 年 3.0 容器化革新以来,因为引入 Kubernetes 遇到的问题,咱们的一些解决方案,以及未来的布局。

1、容器化带来了哪些益处

1)不便对散布于多个节点上的服务的治理

管理员能够在管制节点对立地查看运行在各个节点的服务状态,查看日志,启停和公布回滚服务,甚至 exec 进入服务容器排查问题。同时咱们引入 Loki 收集所有容器的日志,能够对立地查看各个服务的日志。对分布式集群的运维和排障都变得绝对简略。采纳 Kubernetes 之后,间接登录各个节点排障的机会大大降低了。

2)集群配置变更更不便及可控

整个集群的状态能够保留为一个 OnecloudCluster yaml 文件。能够不便地变更集群的配置,包含集群的版本,实现版本的降级和回退,以及集群服务的开启和敞开,镜像版本等要害参数的变更等。更进一步地,能够通过 git 进行配置 yaml 的版本控制,做到变更的历史记录审计,并且能够随时复原到任意指定的配置。

3)便于适配不同的 CPU 架构和操作系统

Kubernetes 作为一层中间层,从肯定水平上屏蔽了底层的差别。采纳 Kubernetes 后,对 CPU 和操作系统的适配大略分为三局部工作:

  1. Kubernetes 对 CPU 和操作系统的适配;
  2. 不同 CPU 架构下服务容器镜像的构建;
  3. Kubernetes 之外的组件的适配,例如平台依赖的 rpm 包等。基于 Kubernetes 本身弱小的生态,根本都有现成的解决方案,只须要做相应的集成工作。只需通过 docker buildx 工具生成异构 CPU 架构的镜像。因而,整个适配工作复杂度大大降低了。

4)部署的便利性减少

引入 Kubernetes 之后,整个部署流程分为几个阶段:

Kubernetes 的部署,这个步骤通过基于 kubeadm 革新的 ocadm 实现。
Cloudpods 服务容器的部署,这个步骤通过 ocadm 在容器内部署 operator,通过 operator 实现相应 configmaps,deployments 和 daemonsets 等资源的创立,进而主动创立服务集群。
Kubernetes 之外依赖组件的装置部署。这个步骤通过 ocboot,集成 ansible 实现。每个阶段都是基于成熟的开源计划扩大实现,可靠性高。同时,各个组件分工明确,模块化清晰,易于保护和扩大。

5)可复用 Kubernetes 自身自带的弱小性能

如 coredns 能够自定义域名,甚至能够做泛域名解析。Ingress 自带反向代理的性能。service+deployment 提供的多正本冗余机制。daemonset 提供的在新增加节点主动拉起服务的能力。对服务的资源限度(CPU,内存,过程号等)。这些都使得云平台服务性能个性的实现变得更加容易。

2、容器化遇到了哪些问题,如何解决

上面总结一些遇到的问题。这些问题是咱们在采纳 Kubernetes 治理和运行云平台组件中陆续发现的。有些曾经彻底解决,但很大一部分还只是局部解决,彻底解决的计划还在继续摸索中。

1)容器内运行零碎级服务

Cloudpods 在计算节点运行的服务都是零碎级的服务,如计算节点的外围服务 hostagent,需具备几个特权:

需启动零碎的 daemon 服务过程,如 qemu 虚拟机过程,vswitchd 等零碎过程,这些过程由 hostagent 启动,但需独立于 hostagent 运行;
需拜访计算节点的任意目录文件。
在容器化之前,这些服务由 systemd 治理,以 root 身份运行。这些特权都天然具备。容器化后,服务需运行在容器内。尽管能够通过配置给与容器零碎级的 root 权限,然而一些特权操作在容器内仍然无奈执行。

首先,容器内无奈启动零碎级 daemon 服务过程。如果通过容器内的程序启动过程,则该过程只能运行在容器内的 PID 空间 (pid namespace),只能追随容器的生命周期启停。为了解决这个问题,咱们将零碎服务的二进制程序安装在计算节点的底层操作系统,并且开发了一个命令执行代理 executor-server。该代理装置在底层操作系统,并作为一个零碎服务运行。容器内的 hostagent 通过该代理执行零碎级命令,例如启动这些 daemon 服务,设置内核参数等,从而取得了执行零碎级命令的特权。

其次,每个容器具备本人独立的文件系统命名空间 (mount namespace)。为了容许容器内服务拜访计算节点底层零碎的特定门路文件,须要将该门路显式地挂载到容器的文件系统命名空间。例如,虚拟机的配置文件和本地磁盘文件都存储在 /opt/cloud/workspace 目录下。容器内的 hostagent 在虚拟机筹备和配置阶段须要可能拜访这个目录的文件,同时,启动虚拟机后,在底层操作系统运行的虚拟机 qemu 过程也须要可能拜访对应的文件。并且,因为上述命令执行代理的机制,为了简化和放弃向后兼容的目标,须要确保尽量以统一的门路在容器内和容器外拜访这些文件。为此,咱们将一些特定的系统目录以同样的门路挂载到 hostagent 的容器内,例如零碎设施文件门路 /dev,云平台的配置文件门路 /etc/yunion,虚拟机系统文件门路 /opt/cloud/workspace 等。然而,这个机制还无奈解决容器内服务拜访底层零碎任意门路的问题。例如,用户能够将底层零碎的任意目录设置为虚拟机磁盘的存储目录,然而该目录其实并未通过容器的 spec 挂载到 hostagent 容器内,从而导致 hostagent 在容器内无法访问该目录。为了解决这个问题,咱们对 hostagent 进行了革新。当 hostagent 检测到用户增加了新的本地目录作为虚构磁盘文件的存储门路,会主动地执行底层系统命令,将该门路挂载到底层操作系统的 /opt/cloud/workspace 目录下。因该目录曾经挂载到 hostagent 容器内,这样 hostagent 就能够在容器内拜访这个目录下的文件。

总之,相比将一个一般应用程序容器化,将零碎级的服务程序从 systemd 托管变为在 Kubernetes 容器中运行,不是仅仅简略地打一个容器镜像,其实还须要做一系列比较复杂和繁冗的革新工作。

2)日志长久化

容器化之前,服务日志会记录到 journald 中,并被长久化到 /var/log/messages。依照 CentOS 的默认策略,保留最近一段时间的日志。遇到问题的时候,能够到对应服务器查找到对应的日志,排查谬误起因。然而,不不便的中央是须要登录到服务运行的节点查看日志。在一个事变波及多个节点的时候,就须要同时登录多个节点进行日志排查。

容器化之后,能够不便地在一个中央,通过 kubectl log 命令查看指定容器的日志,不须要登录到服务运行的节点。

然而,如果没有做非凡设置,K8s 里的容器的日志都是不长久保留的,并且只保留以后正在运行的容器的最近一段时间的日志。而容器往往十分动静,很容易删除。这就导致遇到问题须要排查曾经被删除的容器时候,容易遇到找不到对应的日志。这就使得追溯问题变得比拟艰难。

咱们的解决方案是从 3.7 开始,会默认在 k8s 集群里部署 Loki 套件来收集容器的日志,日志最初存在 minio 的 S3 Bucket 外面。这样做可能长久化容器的日志。解决上述问题。然而,保留 Loki 日志有肯定的零碎负载,并且须要较大容量的存储空间。在集群容量缓和的状况下成为平台的额外负担,可能造成平台的不稳固。

3)节点 Eviction 机制

Kubernetes 有驱赶机制(Evict)。当节点的资源余量有余时,例如磁盘残余空间低于阈值或残余内存低于阈值(默认根分区磁盘空间低于 85%,闲暇内存低于 500M)等,会触发 Kubernetes 的节点驱赶机制,将该节点设置为不可调度,下面的所有容器都设置为 Evict 状态,进行运行。

该机制对于无状态利用能够动静地躲避有问题的节点,是一个好的个性。然而,在云平台的场景中,甚至对于广泛的有状态服务场景中,Eviction 机制导致节点可用性变得十分动静,进而升高了整体的稳定性。例如,因为用户上传一个大的镜像,导致管制节点根分区利用率超过 Eviction 的阈值 85%,云平台的所有管制服务就会被立刻驱除,导致云平台管制立体齐全不可用。用户在虚拟机磁盘写入大量数据导致宿主机磁盘空间利用率超过阈值,也会引起计算节点上所有服务被驱赶,进而导致这台计算节点上所有的虚拟机失联,无法控制。能够看到,尽管触发 Eviction 机制的问题存在造成服务问题的可能,然而这些问题对服务的影响是延后的,逐渐失效的。Eviction 机制则使得这些潜在危险对服务的影响提前了,并立刻产生,起到了放大的作用。

为了防止 Eviction 机制失效,云平台在计算节点的 agent 启动的时候,会自动检测该节点的 Eviction 阈值,并设置为计算节点的资源申请下限。云平台在调度主机的时候,会思考到 Eviction 的阈值,防止资源分配触发 Eviction。这个机制能从肯定水平躲避 Eviction 的呈现,但云平台只能治理由云平台调配的资源,还是存不在云平台治理范畴内的存储和内存调配导致 Eviction 的状况。因而须要计算节点肯定水平的内存和存储的 over-provisioning。

目前,Eviction 的存在也有肯定的踊跃作用,那就是让节点资源的不足以云平台罢工的形式提出警示。因为云平台的冗余设计,云平台的临时罢工并不会影响虚拟机的运行,因而影响水平还比拟可控。无论如何,以云平台可用性的就义来达到资源有余的警示,代价还是有点大。这样的警示能够其余更柔和的形式来实现。随着云平台本身治理资源容量能力的欠缺,Eviction 机制应该能够去除。

4)容器内过程泄露

Cloudpods 服务次要为 go 开发的应用程序,容器镜像采纳 alpine 根底镜像最小化构建,仅蕴含服务的二进制和 alpine 根底镜像,服务过程作为容器的启动过程(1 号过程)运行。咱们的服务程序没有为作为 1 号过程做专门的优化,因而不具备 systemd/init 等失常操作系统 1 号过程具备的过程治理能力,例如解决孤儿过程,回收 zombie 过程等。然而,一些服务存在 fork 子过程的场景,例如 kubeserver 调用服务的时候会 fork ssh 执行近程命令,cloudmon 则会执行采集监控数据的子过程。当这些子过程遇到异样退出时,因为咱们的服务过程不具备被动回收子过程的性能,导致系统里积压了了大量退出异样未回收的子过程,导致过程泄露。这些子过程占用操作系统过程号,当达到零碎最大过程数时,会呈现零碎 CPU 和内存十分闲暇,然而无奈进一步 fork 新的过程的状况,导致系统服务异样。

为了防止容器内过程泄露问题,咱们在 Cloudpods 服务框架里退出了回收子过程的逻辑,并且增加到每个服务过程中,这样在子过程异样退出后,咱们的服务过程会回收子过程资源,从而防止了这个问题。同时也配置了 kubelet 的 最大过程数的限度参数,限度一个 pod 外面最多能有 1024 个过程。

5)高可用不肯定高可用

咱们基于 Kubernetes 实现了管制节点的 3 节点高可用,基本思路是应用 3 个节点部署高可用的 Kubernetes 的管制服务,包含 apiserver, scheduler, controller, etcd 等。Kubernetes 服务通过 VIP 拜访。采纳 keepalived 实现 VIP 在三个管制节点上的主动漂移。这此高可用 Kubernetes 集群之上,部署云平台管制服务,实现云平台管制立体的高可用。预期成果是将 3 个管制节点中的任意节点宕机后,次要服务不受影响,如果有影响,需可能在短时间内主动复原。

然而,初期测试发现采纳默认参数部署的 Kubernetes 高可用主动复原的工夫高达 15 分钟,不合乎预期。通过调研发现,能够通过给各个组件设置相干的参数来缩小复原工夫(https://github.com/yunionio/o…)。通过参数调整,能够让 Kubernetes 集群高可用切换工夫缩短到 1 分钟以内。

6)服务的启动程序

Kubernetes 无奈指定 pod 启动的程序,同时也要求部署在 K8s 里的服务不要对其余服务的启动先后顺序有依赖。云平台服务在采纳 Kubernetes 部署治理之前是采纳 systemd 治理,systemd 能够明确定义服务之间的启动程序。这导致服务之间有比拟显著的先后秩序依赖。比方,keystone 服务就要求最先启动,其余所有服务都依赖 keystone 服务提供初始化服务账号的认证。容器化革新后,因为这个依赖,导致在 keystone 容器启动之前的服务无奈失常运行。定位到该问题后,咱们将服务因为启动程序导致的谬误降级为致命谬误。这样,该服务程序遇到依赖服务未启动导致的问题就异样退出。进而,通过 Kubernetes 主动重启拉起服务过程。通过这样的革新打消了其余服务对 keystone 的启动程序依赖。然而,咱们无奈找到无效伎俩辨认出所有依赖启动程序而呈现的谬误,因而这样的服务无程序革新还在继续。

7)证书生效问题

Kubernetes 集群节点之间的互相认证和通信依赖 PKI 秘钥体系。如果节点的 PKI 证书过期,则该节点 kubelet 无奈失常和 ApiServer 通信,进而导致节点状态被设置为 NotReady,进而呈现前述的容器驱赶导致节点不可用的重大问题。刚开始,咱们部署的 k8s 集群还是采纳 kubeadm 默认的 1 年有效期的证书,过后还未顾及到证书到期的问题。到 2020 年底,开始陆续呈现多个集群莫名服务不可用的状况,才留神到证书过期的问题。针对这个问题,咱们刚开始采纳 cronjob 装置自动更新证书脚本的计划,并且在客户巡检中,专门查看证书过期问题,以提前发现问题。起初到了 2021 年 3.8 版本,采纳了更糙快猛的办法,间接批改了 kubeadm 的证书签发代码,一次性签发 99 年证书,从而彻底解决了 Kubernetes 的证书过期问题。

8)iptables 批改

Kubernetes 部署后,kubelet、kube-proxy 以及咱们采纳的 calico 等都依赖 iptables,会接管节点的 iptables 规定,在 kubelet 启动之后,对 iptables 规定的批改会被重置,并且会刷新 iptables 规定。如何长久化对 iptables 规定的批改成为问题。目前,针对节点的防火墙规定能够采纳 calico 的网络策略来实现,可参考文章 https://www.cloudpods.org/zh/…。但更简单 iptables 规定,还没找到无效方法。

3、将来布局

1)降级 Kubernetes 版本

目前,云平台底座 kubernetes 的版本是 1.15.12,该版本曾经不再被 Kubernetes 官网反对。目前存在的比拟显著的问题是和较新的采纳 cgroup v2 的操作系统不兼容,导致无奈设置服务的资源 limit。后续思考降级底层 Kubernetes 到更新版本,以期取得更新的性能个性反对。

2)采纳 K3S 等更轻量版本

目前云平台依赖底层 Kubernetes 的性能个性不多,同时 Kubernetes 自身也要耗费肯定的节点资源,前面也打算思考采纳 k3s 等更轻量的 Kubernetes 版本,进一步升高 Kubernetes 的应用老本。

3)移除计算节点对 iptables 的依赖

计算节点网络次要依赖 openvswitch 实现虚拟机的通信,iptables 次要是给 kubelet,kube-proxy 和 cailico-node 等 k8s 服务组件应用,而计算节点上的服务组件次要是用来治理 QEMU/KVM 虚拟机的 host-agent 等服务,这些服务自身具备基于 ovs 的网络管理能,不依赖 k8s 的网络,齐全能够只依赖 host 网络即可失常工作。因而,其实能够去掉计算节点的 kubeproxy,calico 等组件,去除对 iptables 的批改,这样简化组件依赖,进一步提高零碎的可靠性。

4)齐全禁用 Eviction 机制

Eviction 机制在虚拟化云平台或有状态服务场景中,会起到故障放大的作用。在充沛掌控对节点资源耗尽预警的前提下,应思考彻底禁用 Eviction 机制。

5)多数据中心架构的反对

目前云平台所有节点都运行在一个 Kubernetes 集群内。而云平台自身是能够反对多数据中心部署的。然而跨数据中心部署单个 kubernetes 集群不是最佳实际。比拟现实的架构是单个 kubernetes 集群部署在一个数据中心内。因而,应该容许云平台跨多个 K8s 集群部署。例如每个数据中心一个 Kubernetes 集群,其中一个集群部署残缺的云平台,其余 Kubernetes 集群以从可用区的角色退出主集群。每个 Kubernetes 集群之上只运行治理一个数据中心所需的云平台组件。进而形成一个多数据中心的云平台架构。

GitHub:https://github.com/yunionio/c…

查看原文

退出移动版