腾讯云 Kubernetes 一键部署实践


很多人在实际工作中都使用过Kubernetes,我们的容器服务在2016年年底开始提供全托管的Kubernetes服务,主要提供了四个方面的功能。首先是提供了一键部署的Kubernetes,与其他容器服务的提供商不一样,我们的Kubernetes是完全隔离的,每个用户都会独享所有的计算节点和控制节点,集群网络也在用户自己的VPC中。我们在这个基础上提供了集群的全生命周期管理,包括集群的创建、销毁,还有计算节点的添加、删除,还有一些类似Kubernetes原有组件的初始化以及证书的初始化工作。为了大家更方便地使用Kubernetes,我们在控制台包装了一些界面,使大家可以通过可视化的方式创建一些负载来暴露自己的服务,避免了大家手工编码的烦琐。第三,我们提供了周边的监控能力,包括集群本身Pod内存的使用率以及一些Kubernetes事件。这些能力都与腾讯云的云监控产品进行了打通,大家可以直接在云监控产品界面使用这些能力。为了方便大家将自己的一些比较传统的应用部署到云上,我们在Kubernetes集群之外还提供了Docker镜像仓库、TencentHub、CICD的功能,为大家提供了一站式应用的云解决方案。

我今天所讲的内容,一方面是介绍Kubernetes相关的知识,另一方面是介绍如何将Kubernetes和腾讯云进行集成。首先带大家了解一下Kubernetes集群需要哪些组件,将Kubernetes部署好之后还要做什么工作使它正常运行。之后会介绍云上Kubernetes上的网络存储和日志与监控,这是将Kubernetes和腾讯云集成时进行的工作。最后介绍Kubernetes的两种部署方案,一种是容器服务产品发展早期所采用的一种基于CVM的方案,另一种是最近逐渐部署的使用Kubernetes集群来管理Kubernetes集群组件的方案。

随着容器服务产品以及Kubernetes社区的发展,在我们提供托管的CCS服务通过了CNCF的Kubernetes一致性验证之后,为了大家更好地认知腾讯的容器服务产品,产品的名字从之前的CCS正式改名为TKE。

Kubernetes 组件

接下来我们看一下想要让Kubernetes集群运行起来我们要做哪些工作。如果对Kubernetes有了解的同学,可能知道使Kubernetes跑起来,无非是初始化一些Master组件和Node组件。首先给大家介绍Master组件,一个最简单的部署要包括Kube-apiserver、Kube-controller-manager 、kube-scheduler这些组件。Kube-apiserver可以理解为整个集群的大脑或者是集中存储,所有组件与Kubernetes的交互都是通过Kube-apiserver来完成的。其中存储着我们定义的一些工作负载以及我们对于存储的一些需求,这些数据都存储在Kube-apiserver中。

第二个组件Kube-controller-manager主要负责将声明的一些工作负载在实际的集群中运行起来,举个最简单的例子,当我们在Kubernetes上创建了一个Deployment之后,Kube-controller-manager就会去创建相应的Replicaset、Pod这些。根据需求将Pod创建出来,在创建Pod后,Kube-scheduler负责对这些Pod进行调度,比如这个Pod实际应该被运行在哪台机器,比如GPU的结点做这样的调度工作。当一个集群的Master组件被完全部署好之后,我们会部署一些Node。

在它的上面部署两个组件,一个是kubelet,负责在这些Node上创建出来我们需要的Pod。另一个是kube-proxy,它在集群中负责的工作是:当一个deployment需要以服务的形式对外暴露的时候,Kude-proxy会负责配置这些iptables规则,让集群内不管是宿主机上的程序还是容器里的程序,都能够按照Service的名字去做一个自动的发现和访问。
k8s组件.jpg

在Kubernetes这些标准化的组件之外,我们还提供了额外的组件,主要给大家介绍一下。包括三个,一个是hpa-metrics-server,它是我们为了使用Kubernetes提供本身的Pod横向扩展控制器而去自研的一个组件,这个组件相比于Kubernetes社区方案的优点,是Kubernetes社区方案只提供了基于CPU和内存的扩展,而我们在这个基础上更加拓展了Pod的入带宽和出带宽的指标,方便大家去适应更多的扩缩容场景。第二个组件是cbs-provisioner,这个组件提供了去让Kubernetes里面的Pod去消费我们腾讯云一个叫做Cbs块存储服务,我们后面会详细讲到。第三是Ccs-log-collector,这个组件主要是负责收集容器里Pod运行的日志,后面也会讲到。
k8s组件2.jpg

容器网络

当我们把Kubernetes和腾讯云进行集成的时候,网络方案是怎么做的呢?在你将一些控制组件搭建起来之后,Kubernetes对于网络提出了三点的要求。第一是在不使用NAT的情况下,集群内所有容器都可以和其他的容器进行通讯。第二是所有的节点都可以和所有的容器进行通信,反向也可以通信,同样要求不能使用NAT。第三是容器看到的自己的IP与其他人看到的IP是一样的。

总的来说,它实现了Node和container之间的扁平化网络。同时为了应对服务发现的需求,降低网络复杂度,还要求不能使用NAT。Kubernetes并没有内置的网络解决方案,所以在社区其实有很多不同的解决方案,例如flannel,它是通过Tun/tap设备,再通过内核用户态的转化程序,实现一个类似于overlay的网络能力。Callco不需要进行overlay封装,直接通过路由的方案可以完成Kubernetes的网络需求。

接下来为大家介绍一下腾讯云Kubernetes使用的方案,我们的网络方案主要是直接使用了VPC提供的路由能力,叫做global route。简单介绍一下Kubernetes结点加入到一个集群中配置网络的过程,当我们将一个结点加到集群中的时候,Kubernetes的Kube-controller-manager会为这个结点分配一个网端。

例如,集群网络是172.16.1.0/16掩码,这时加进去一个结点。按照我们的逻辑,我们会给它赋172.16.1.0/24位掩码的CIDR。也就是所有在这个主机上创建的Pod,它的ID都在这个CIDR的范围内。然后我们会去VPC那里做一个动作,将172.16.1.0/24掩码到目的地址的流量去注册一条路由规则,使所有的包都发往被赋予这个Pod CIDR的主机。这样我们就可以完成之前提到的Pod和Node之间的扁平化网络。

具体过程如下:假设现在有一个位于172.16.1.0/24网端机上的Pod,向另一个机器的Pod发送了这样一个包,这个包首先从容器出来,到cbr0 bridge然后出了主机。这时就会进入VPC的路由表进行匹配,当它发现这个Pod的目的IP在172.16.2.0/24时,这个包就会被VPC转发到10.1.1.3,这样我们就可以完成一个Pod的跨主机通信。Pod在本地的通信比较简单,这里就不多讲了。通过这种方式,我们实现了一个Pod和Node之间的扁平化网络,这里Docker的网络模式是用的bridge模式,Pod IP直接由cni插件进行分配。
容器网络.jpg

容器存储

接下来给大家介绍我们的Kubernetes和腾讯云容器集成的实现。Kubernetes集群集成了腾讯云的CBS和CFS两个能力,一个是块存储,一个是基于NFS的文件系统存储。当我们在一个Pod中声明需要一个volume时,Kubernetes如何将volume最终挂载到Pod里面?其实是这样一个过程:首先,Kube-controller-manager会去provisnon这样一个volume。

也就是说,实际去创建一个云盘,当云盘创建好之后会做一个Attach的动作,相当于把刚刚创建好的云盘插到对应主机上,这时,主机上的Kubelet会做一个mount动作,也就是将插进来的这个设备去mount到一个Kubernetes指定的目录,Kubelet在创建这个Pod的时候,通过mount的形式把mount到的目录实际挂载到容器的namespace里面。然后当我们这个Pod销毁,这个volume不再被需要的时候,它就反向去执行,先从主机上把对应的块设备先mount掉,再将它detach掉,相当于把这块磁盘从主机上拔下来,然后会由Kube-controller-manager根据对应的plugin设置销毁或者是保留。
容器存储.jpg

Kubernetes目前通过与cloud provider进行volume集成的方面主要是三种。一种是比较早期,所有要和kubernetes进行集成的Volume代码都需要写在kubernetes自己的代码仓库中。这样的缺点在于,假设我是一个存储提供商,我写的代码有些bug,这样不仅影响存储功能的正常使用,可能也会影响整个集群的稳定性。所以后来为了更好的扩展性和稳定性,Kubernetes提供了一种叫做Flex volume的形式。具体是将mount和umount这两个形式由Flex volume实现,Flex volume是一个实现了特定接口的二进制文件,实际上在需要mount、amount的时候,Kubelet会执行这个二进制文件,做一个mount、umount的动作。

这种方式其实也有一个问题,在kubernetes的部署环境中,如果要往主机上放一个二进制的动作,看起来不是那么的容器化。另外二进制文件执行的环境也有一定的要求,所以后来Kubernetes提供了第三种方式,也就是利用社区的CSI接口实现了基于CSI的插件。之前由Flex volume这个二进制文件完成的工作全部放到了容器里面,然后通过unix socket的形式,使Kubelet和实现对应功能的插件做一个通信,最后一个mount、umount的动作。我们现在也与腾讯云的CBS块存储做了集成,使用第一种方式,这是因为我们在早期使用的就是这种方式。后期我们也会将这部分独立出来,计划通过CSI的方式去提供存储的能力。这部分代码我们将会开源,但目前这个工作还正在进行中。

容器日志与监控

接下来为大家介绍一下日志和监控的方案,首先介绍一下日志的方案。在Kubernetes中没有提供默认的日志方案,Kubernetes社区提供的这种方式可以运行一个sidecar容器,在你自己的容器中运行一个这样的进程并将它输出到标准输出,然后再用你的sidecar容器做一个收集。这样的问题在于,每跑一个Pod,就要跑一个类似的sidecar容器,对于资源的消耗不太可以接受。腾讯容器服务基于Fluentd+Kubernetes custom resource definition,实现了日志收集的控制器。这个控制器可以支持收集容器的标准输出,也可以支持收集我的Pod所在的Node上主机上文件路径的文件内容。

主要的实现原理是:我们会在kube-apiserver上注册一个LogCollector的custom resource definition,里面声明了我要收集namespaces下面某一个deployment对应的Pod资源。还有另外一个进程就是LogCollector,去监听Kubernetes apiserver资源,然后去生成对应的Fluentd的配置文件,再通过sighup信号的形式去触发Fluentd的重载,然后去收集我们想要的日志文件。因为Kubernetes Pod对应的日志文件是存储在主机的/var/log/containers的路径规则下来,直接配置Fluentd去收集这个规则,再根据我们的实际需要做一个日志的路由。这样就可以把不同的日志发往用户指定的不同后端,比如Kafka或腾讯云的CIS的日志服务。这是我们目前对于日志收集的方案实现。
容器日志与监控.jpg

在监控方面,我们将Kubernetes中Pod的性能信息和云监控做了对接,主要的实现方法是在用户的每台kubernete结点上运行一个agent,这个agent在kubelet中内置的cadvisor收集Pod运行的性能信息,然后再到apiserver获取这些Pod对应的元数据。

我们将这些性能信息和元数据进行打包,再将它上传到腾讯云的监控服务上。另外,基于腾讯云存储的监控指标,我们实现了hpa-metrics-server。Kubernetes提供的HPA能力,是它在kube-controller-manager里面实现了horizontal pod autoscaler,它会定期请求hpa metrics server去获取这个Pod目前的CPU Usage或者是入带宽、出带宽这些指标的数量,根据我们的定义,比如当CPU使用率超过80%的时候,我们要去进行扩容。它也会定期拉取数据,比对当前的Pod负载,直接去修改deployment中的replica字段,实现自动扩缩容。目前日志和监控的方案,是将Kubernetes和腾讯云的基础设施进行对接时候所需要做的一部分工作,也是我们的主要工作。
容器日志与监控2.jpg

在 CVM 上部署 Kubernetes

下面介绍一下我们早期在腾讯云上的部署方案。当时为了产品能够快速上线,也为了满足一个完全的隔离的全托管的Kubernetes服务,我们直接把Kubernetes的Master组件部署在了一台CVM上面。同时,为了实现隔离的方式,我们将Master放到用户的VPC中。在Master组件上运行了一些标准化的Kubernetes组件,用户的Node结点以用户的身份直接在CVM那边购买机器,我们在这个基础上做一些kubelet或者kube proxy参数数值化的工作。此外,我们会做一些集群的证书配置、默认拉取镜像的凭证初始化工作。

在这个方案中,我们所有的节点都处于用户的VPC里面,通过Agent初始化的方式将整个集群部署起来。缺点在于难以管理,因为我们早期是通过SSH直接登录到客户的Master结点上进行一些运维操作,对于客户的Node结点是没有办法访问的。当然,现在也没有办法访问。对于Master的一些运维工作比较困难,因为它没有办法去编程化。
部署k8s.jpg

将 Kubernetes 组件部署在 Kubernetes 集群中

虽然我们自己提供的是Kubernetes的服务,但对于整个容器的运维好像和Kubernetes完全没有关系。在Master上一些组件的监控并不是用我们之前提到的方式来实现的,是通过内部的监控组件来收集。这与我们之前提到的用户Node组件信息收集方式不太一样,相当于两种方式并行在跑,人力消耗也比较大。之前我们的ETCD是共享的,也就是每个集群都在ETCD中有一个自己的目录,相当于一个软隔离,没有一个硬隔离的措施。

大家知道ETCD其实并不是为了海量数据存储而服务的,而我们在线上运行了数万个集群,导致遇到了很多和ETCD有关的问题。在这样的大背景下,我们推出了第二种方案,也就是将Kubernetes部署在Kubernetes里面,通过Kubernetes API去管理Master组件,包括我们刚才提到的apiserver、kube-controller-manager和一些自研的组件。这样的好处在于,我们不需要再通过SSH的方式,比如当我们需要做一个apiserver的升级或kube-controller-manager的bug修复的情况,我们不需要再通过SSH的方式去每台机器上进行操作,可以直接通过API Kubernetes提供的deployment的滚动升级的能力来完成这一点。
k8s_in_k8s.jpg

我们也可以充分利用Kubernetes运维的能力,包括健康检查和就绪检查的机制实现故障自愈。基于之前提到的hpa-metrics-server,可以实现apiserver的动态扩容,应对用户的集群结点有一个大规模的上升或者突然下降的情况,更好地满足Kubernetes集群里面的结点对于apiserver性能的需求。在这个基础上,我们还需要解决一个问题:之前基于CVM的部署方案是将所有的组件部署在用户的VPC里面,如果我们将所有组件部署在kubernetes Master中又要怎么做呢?

我们并不能给每个用户部署一个Kubernetes集群,然后再跑它的Master,这样听起来好像和第一种方案没有什么区别。所以我们提供了一个专门的Kubernetes集群,在这个集群里面运行着现在线网所有集群的Master,这个集群运行在我们自己的VPC里面,它又要怎么和用户的VPC结点进行通信呢?我们利用了VPC提供的弹性网卡能力,这个弹性网卡会被直接绑定到运行apiserver的Pod中。大家可以理解为这个Pod既加入了我们用来运行Master组件集群的VPC,又加入了用户的VPC,也就是一个Pod同时在两个网络中,这样就可以很好的去实现和用户Node相关的互通。另外在这个基础上,我们也可以复用之前提到的一些监控和日志设施,更好地去做信息的收集和运维。

值得提到的一点是,这个方案中,我们可以利用一个叫做Etcd operator的组件,也就是coreos其中的一个组件来为每个集群提供独立的Etcd的部署,这样就可以解决在集群数量不断上升的情况下Etcd性能吃紧的问题。通过在Kubernetes集群里面部署Kubernetes Master组件,降低了运维的成本。同时也统一了我们做运维工作的方式,我们都是通过Kubernetes的方式进行运维的。有效降低了Master组件的资源消耗,因为之前Master组件是免费提供给客户的,但有些客户的集群规模非常大,Master配置也非常高,这时集群的Master存在资源浪费的情况。

0 个评论

要回复文章请先登录注册