34

平安证券Kubernetes落地实践

 4 years ago
source link: http://dockone.io/article/10618
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

2020年7月18日,由信也科技集团(NYSE:FINV)旗下科技布道师FTE(FINV technology evangelist)在上海举办了第二届技术沙龙。本次沙龙的主题为《Kubernetes在大型互联网公司落地》,在本次沙龙上,本人分享了《平安证券Kubernetes落地实践》的技术主题。

在本次沙龙中,其它几位技术大神的技术分享,让我受益良多。eBay的技术专家潘野的《eBay大规模化Kubernetes集群管理实践》中,自研扩展Kubernetes API的功能,让超大超多节点的Kubernetes集群管理不再麻烦。美国电商iHerb公司中国研发部DevOps团队负责人李洋的《多Kubernetes集群生产环境中的服务部署与监控实践》,借助于国际云提供商的资源和自研的CI/CD平台,完成了一套高效的基于Kubernetes的DevOps流程体系。信也科技集团架构师、布道师张轶丛的《Kubernetes在信也科技落地分享》,深耕细作于Kubernetes集群各组件的高效运行,并且开源了自家研发的CI/CD交付源代码,让人起敬!

这篇文章,即基于这次的讲演PPT写成(有少量增删)。在此文中,主要讲述了我公司在进行Kubernetes的落地实践中,涉及到的几点小小的经验分享:

  • 镜像制作过程中让JAVA应用感知容器资源;
  • 制作支持GPU的AI项目镜像;
  • 使用Operator部署有状态应用;
  • Kubernetes应用指标的收集和监控;

一、Java应用中的容器配置感知

Kubernetes部署中,我们一般会定义一个容器使用CPU,内存资源的上下限。如下一个yaml文件片断:

9930ebf848948ebf64305ca0618f8ced.png

这表示tomcat-demo:v8.55这个容器运行在Kubernetes集群中时,最低能拥有0.5核CPU,512M内存,最高不超过2核CPU,2G内存。

假想当我们把这个yaml资源声明应用到Kubernetes集群时,Kubernetes将它调度一个拥有64核CPU,128G内存的宿主机节点上。Docker容器本质是是宿主机上的一个进程,它与宿主机共享一个/proc目录,也就是说我们在容器内看到的/proc/meminfo,/proc/cpuinfo与直接在宿主机上看到的一致。也即,此容器认为自己的系统资源同样是64核CPU,128G内存。

此处,就出现了运行的容器感知的资源,与Kubernetes分配给它的资源的偏差。在早期的Java版本(JDK 8u131之前)中,这种偏差可能会导致Java应用不断的重启。

那么,为什么Pod会不断重启呢?

以刚才这个配置为例,Java应用以为这自身拥有128G内存,默认情况下,JVM的Max Heap Size是系统内存的1/4,也就是32G作为最大JVM堆内存。但是,当应用运行的总内存,超过Kubernetes的声明限制的2G时,应用就会触发Kubernetes集群定义的资源超配额限制,自动重启此应用POD。于是,我们看到的现象,就是这个应用有越来越多的restart。

如果用户运行的JDK8版本低于131,解决这个问题的办法,是需要在容器启动JAVA程序时,自行定义好与资源配额一致的-Xmx等参数。后来,随着JDK版本的升级,容器感知的功能就内置于Java应用中了,如果JDK8的版本高于191,我们就可以使用MaxRAMPercentage这种更智能化的参数来解决这个问题了。

下图是我综合网上的文档,制作的容器内的JVM参数进化史。

058ed96900ac0f4839d6edfe9693714a.png

二、AI项目镜像对GPU的支持

随着现在AI人工智能的高速发布,深度学习,神经网络的热门,公司越来越多的项目,开始使用PyTorch、TensorFlow作为项目开发的支持库。

众所周知,深度学习库,在CPU和GPU上运行效率有巨大差异。为了更快的运行速度,我们也从去年开始了这部份的实践。本节,主要讲述的就是将一个GPU机器加入Kubernetes集群的过程。

因为我公司用的是Nvidia的Tesla系列GPU,所以我们的方案,主要是参照Nvidia的构架思路。在Docker中调用Nvidia的GPU,可以通过nvidia-docker调用,nvidia-docker是一个可以使用GPU的Docker。

6b6d5951eb04d51684df3b3527b90cac.png

这里且放过Nvidia驱动,cuda-toolkit这些不表,整个安装过程分两大步:

替换docker-runtime

1、安装nvidia-docker(CentOS)

yum install nvidia-docker2-2.0.3-3.docker18.09.6.ce.noarch.rpm

2、修改/etc/docker/daemon.json

4359a813bc63cd33af983e9342314ccc.png

如果这时我们使用docker info命令,会发现docker runtime已替换成了Nvidia。

f34a19079cd5a7de2fdf4f2ba83bdfd0.png

将此节点加入Kubernetes集群之后,在集群内安装Nvidia Device Plugin

1、Nvidia Device Plugin项目地址: https://github.com/NVIDIA/k8s-device-plugin

Nvidia Device Plugin的工作机制如下图(图片来自于网络):

808da5b9fbf49fdfe743018cbb0615c4.png

2、在Kubernetes集群中安装好GPU之后,我们就可以在Yaml文件中,为应用分配GPU资源了。其yaml文件中的resource定义,和Kubernetes调度机制如下图(图片来自于网络):

915a2c91170fd14da62dfbcac7adda02.png

3、经过我实测试,同样的代码,使用GPU和CPU运算的差距,起码在10位以上。

a89119984a0685be64cc35651fb7cac7.png

f0cffe51279f225a26afe500bcefcb00.png

三、使用Operator部署ECK(Elastic Cloud Kubernetes)

在Kubernetes中,对于有状态的应用,有原生的StatefulSet部署支持。StatefulSet原理和ReplicaSet 和 Deployment 资源一样,StatefulSet也使用控制器的方式实现,它主要由StatefulSetController、StatefulSetControl和StatefulPodControl 三个组件协作来完成StatefulSet 的管理,StatefulSetController会同时从PodInformer和ReplicaSetInformer中接受增删改事件并将事件推送到队列中。

但StatefulSet无法解决有状态应用的所有问题,它只能做到创建集群、删除集群、扩缩容等基础操作,但比如备份、恢复等数据库常用操作,则无法实现。

基于此,CoreOS团队提出了Kubernetes Operator概念。Operator是一个自动化的软件管理程序,负责处理部署在Kubernetes有状态应用的安装和生命周期管理。它包含一个Controller和CRD(Custom Resource Definition),CRD扩展了Kubernetes API,可以将一些运维能力,作为代码封装于镜像中。其实现如下图所示(图片来自于网络):

d213772e17d7ac3b3702375a7f96a3c1.png

我们公司遇到一个项目,需要使用Kubernetes集群内的Elasticsearch服务。于是,我们就使用operator部署ECK,后端使用NFS作为数据存储。这里就简要介绍一下其实现思路。

ECK官网: https://www.elastic.co/guide/en/cloud-on-k8s/ ,本节不讲关于ECK的一些部署,因为官网都提供了详细的说明。这里主要讲解一下,如果通过NFS 动态PVC为ECK提供网络存储,这部分内容,我在部署ECK时,并没有在网络上搜索到完全合用的文档。所以在这里作为落地实践的内容,分享给大家。

使用NFS提供动态PVC功能

GitHub项目地址: https://github.com/kubernetes- ... lient

yaml文件地址: https://github.com/kubernetes- ... .yaml

这份配置文件的注意要点,是要将StorageClass定义中的Provisioner与Deployment中的PROVISIONER_NAME这个环境变量一致。

NFS Provisioner是一个自动配置卷程序,它使用现有的和已配置的NFS服务器来支持通过持久卷声明动态配置Kubernetes持久卷。

1c65ed8f8df415f024e7312d666383eb.png

Kubernetes集群有PVC存储之后,就可以将这个StorageClass就用于ECK要求的存储中了

1d54a8acec814fd1d97672d03cbed849.png

使用kube-prometheus收集JVM和Node.js的Metric

在Kubernetes集群里,一般是使用Prometheus作指标的收集,监控和报警。我们在早期,也是使用原生的Prometheus,配合一些Exporter来作Metric的收集。在作过几次集群的迁移和新建之后,发现每一次的部署,都很繁琐,叫苦不迭。

到后来,我们引入了Prometheus Operator项目,再过渡到Kube-prometheus项目,部署监控的流程,才变得规范和轻松起来。

kube-promehteus项目地址: https://github.com/coreos/kube-prometheus

由于时间关系,这里也不展开这个项目的具体细节,只和大家分享一下我们是如何使用Kube-prometheus的ServiceMonitor来监控Node.js应用的。

ServiceMonitor就是一种由Prometheus Operator定义的CRD,Operator会通过监听ServiceMonitor的变化来动态生成Prometheus的配置文件中的Scrape targets,并让这些配置实时生效。Operator通过将生成的Job更新到上面的prometheus-k8s这个Secret的Data的prometheus.yaml字段里,然后Prometheus这个Pod里的Sidecar容器prometheus-config-reloader。当检测到挂载路径的文件发生改变后,自动去执行HTTP Post请求到/api/-reload-路径去reload配置。该自定义资源(CRD)通过labels选取对应的Service,并让Prometheus Server通过选取的Service拉取对应的监控信息(Metric)。

1ad18d5aa8374ea0fb9bbde52f8bde2d.png

在上图中,我们先定义好ServiceMonitor的Endpoint和Selector。然后,在已有的应用Service中,labels和selector对应,port的name和endpoint的port对应,如果此Node.js应用提供了path作为metric的话,Prometheus就能将这些指标定时入库了,极其方便!

JVM的监控也可以类似的方式实现。

d5e0eda1f167009c5c96ebf20b1592f0.png

e5ba8fa757261ac7fc4268fe69167d76.png

当一切部署妥当之后,该应用的Metric就会自动的纳入Promemetheus体系中了。

25fbb577ed3dbaf94d6c53eaa8358ba2.png

作者:陈刚,平安证券运维研发工程师。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK