前言
最近这段时间一直想要把工作上的一些收获和成果沉淀下来,想主要讲讲gitlab与kubernetes包括在k8s平台上更往上一层service mesh这些东西。然而这段时间一直在负责Coohom项目组基础技术架构的建设和三个后端服务的需求与维护,抽空还要带一个实习生,所以一直都找不到比较空的时间可以写一下博客。本着再忙不能忙娱乐的精神,在补完番,神界原罪1代游戏通关以后,恰逢这周五也要在公司内部开展一个kubernetes最佳实践的分享,所以干脆就以此为机会写一篇博客,正好一石二鸟。
正文
这篇博客讲什么
这篇文章的目的并不是为了说服为什么要去使用Kubernetes,我相信对于这方面有许许多多的文章和博客或者观点讲述了为什么我们最终会选择kubernetes。对于容器编排平台来说,虽然从来没有官方钦定过Kubernetes是最终的胜利者,然而kubernetes却已经是事实胜利了,这点已经无需辩驳。所以这个博客的主要讲述的聚焦点将在于我作为一个应用开发者,是如何在Kubernetes平台上帮助我更好、更效率、更方便的进行我的工作。
关于Kubernetes
这个单词写起来太麻烦了,我还是喜欢用k8s作为简写。所以后面的k8s都用来指kubernetes.
服务与被服务
在提供服务的企业内经常会有这么一句话,叫做顾客就是上帝。对于写应用的程序员来说,使用你应用的人就是你的顾客,换言之你就是为这些人服务的。所以如果你让你的顾客不爽了,那你的好日子基本上也到头了。假设在工作中,你不可置否的需要研究k8s平台并且做出一些改动从而让使用这个平台的人的应用能跑的更爽,这种情况下多半你可能会是在一个类似基础架构组这样小组中工作。套用上面的理论,那些使用我们平台的程序员们就是我们的客户,换言之基础架构组就是要为业务开发所服务的,一个傻逼的业务开发可能会让这个应用的某个功能非常难受。然而一个傻逼的基础架构建设者就可能让整个产品变得傻逼。当我们k8s上做出一些改动或者新的方案时希望去推动业务开发程序员们一起推进时,一定要学会站在他人的角度去考虑,思考这项改动对他们侵入度会有多大。如果由于基础架构的改变使得业务开发的程序员们都疲于配合你的改动而不能进行业务开发时,这就是一个非常傻逼的事情。所以不管是否是k8s平台也好,作为基础架构建设,一个最明智的举动就是润物细无声的改动。底层的变化对上层是无感知,这是最好的。
关于Docker
虽然k8s对于底层的容器技术并没有指定,除了大家熟悉的Docker,我们同样其他容器技术作为k8s的底层容器。不过还是那句话,虽然Docker没有被官方钦定,但是使用Docker作为k8s的底层容器几乎已经成为了事实标准,包括我们的k8s平台也是同样基于Docker作为底层容器。
我们是如何使用k8s的
对于我们的国际站项目,我们的所有应用、所有环境已经完全上了K8s平台,并且对于某些环境我们还上了最新的ServiceMesh技术,即Istio。不过这个东西如果要做整理分享的话也可以再拿出来写好几篇博客了,所以打算放在以后再说。作为一个后端开发者,我们主要使用Java作为我们的主力开发语言。所以下面我提到的一些实践虽然有语言无关的,但也有一部分是语言相关的,当然说白了就是JAVA服务和K8S。作为后端开发人员,我也非常清楚目前这个时代不少程序员对JAVA的看法有着不少意见,可能会更喜欢Python Golang之类的语言。而我的看法是Java作为一个后端语言确实有他不足的地方,但是和他庞大而稳定的生态圈而言同样也带来了很多便利。我们可以不喜欢JAVA,但他值得我们去尊重与了解。不过既然这里是我的博客,所以我想写啥就写啥嘻嘻。
语言无关
代码与配置分离
在我们的开发过程中,我们的程序会同时面临好几个环境。比如本地开发环境,内网的alpha,beta,gamma环境,外网的正式生产环境。当产品达到一定的规模时,我们希望有全球部署的能力,那么很可能对于生产环境来说也会有比如美东环境,欧洲环境,华东环境,东南亚环境等等等等。由于环境的不同,不同环境我们的代码表现是一定有可能存在不同的。一个通用并且科学的做法是对于我们的程序永远只保持一份代码,但是有多份配置,应用会根据我们的环境加载不同的配置从而有不同的表现。在我们的配置中,有些是敏感配置,有些是正常的配置。什么是敏感配置呢,比如数据库连接,数据库用户名和密码啥的。对于内网的测试数据库来说,应用开发程序员是可以接触到的,所以会写在项目本身的配置文件中。但是对于生产环境的敏感配置该怎么办?如果我们用的是虚拟机,可能这些配置文件通常会交付给运维组来处理,提前写到虚拟机内的环境变量之类。
那么假设我们用容器呢?Docker容器比起虚拟机而言在管理上确实会更加方便,但是假设我们内网镜像和外网镜像用的是同一个镜像,那么这意味着镜像内部就要带上所有配置文件,考虑到镜像泄露的可能性,这同样也是非常危险的。对于K8S而言,他非常好的解决了这个问题。在K8S平台内,有专门成为配置
与密钥
的两种资源。K8S会根据我们的定义将镜像或者镜像们包装成为一个更为抽象的容器,即POD,这个POD的内部环境变量都可以通过配置
与密钥
两种资源获取。这就带来了以下几个好处:
- 代码仓库脱敏,代码仓库中没有任何敏感信息与配置,开发程序员只要知道最终运行时配置项会被相应填上,而不用关心这个配置项是如何被注入的。
- 镜像脱敏,对于容器而言,我们的镜像内配置文件资源永远只有非敏感的资源,而对于敏感资源只有在K8S拉取镜像后创建POD时才会被自动注入。
- 配置即代码,由于
配置
与密钥
对K8S而言就是一种资源文件,而K8S的资源文件是可以通过文本定义的,这也就是意味着我们可以通过Git来管理我们的配置文件。
确立K8S平台权限
就像上文我所讲的,我们可以通过K8S平台来进行敏感配置的管理。这就意味着能够操作K8S平台的人就能接触到敏感信息。所以确立K8S的平台操作权限是非常关键的。在我们早期使用K8S时,我们所有开发的K8S配置都是相同的一份。这意味着从刚进组的实习生到整个项目的技术负责人而言,他们对于操纵K8S平台的权限是一样的。这是非常致命的,这意味着很可能一个并不了解K8S平台或者项目业务的人很可能因为好奇或者是别的什么原因修改或者查看我们的所有在K8S平台上的资源、配置等一切敏感或者不敏感的信息,而我们对此一无所知。在某一次我们对于Mysql数据库监控的时候,我让一个开发人员只要去操作我们的内网K8s环境即可,结果在第二天的站会上他告诉我同样也给我们的生产环境接上了监控,当我问他是从哪里获得我们的敏感链接时,他告诉我就是直接在K8S平台上查到的。这让我当时就感到很无语。另外一方面,过多的人同时修改K8S平台内的资源配置非常容易造成双方都意想不到的后果。所以目前在我们项目组而言,所有对于K8S的修改都是经过一个专门的人审核过后,通过我们的CICD系统来自动部署到K8S平台上,而并非在某个人的开发机上去做这件事情。当然审核的人需要对整个业务环境的资源配置非常熟悉才行,而这个苦逼的工作目前就交给我负责了。
总而言之,让很多人都有同时修改查看K8S的能力是一个非常危险的事情,确立K8S的操作权限是一件非常重要的事情,这点从技术上和流程上来说都能实现。
滚动升级与探针策略
在我们之前的开发流程中,每当我们的后端服务进行部署时,对于前端开发以及我们平台内的应用监控而言,都会有一小段不可用时间。从而导致我们的客户端开发人员发现后端服务出现了闪断现象,我们的监控服务发现后端服务出现了一段时间的不可用从而发出报警。 究其原因,假设我们一个服务的实例副本是2个,那么我们之前的更新其实就是起两个新的实例的同时再将两个旧的实例关闭,在旧的实例被关闭以及新的实例完全启动的这当中的时间就会造成服务的不可用。一个比较好的做法是,我们先将新的服务实例启动起来,但是并不接入流量,等到服务实例完全启动完毕后再将旧的服务实例关闭,并且将流量接入新的服务实例。那么如何判断服务实例完全启动以及新老服务实例交替该遵循如何的规则,在这方面上K8S都给出了非常棒的解决方案,即滚动升级与探针策略。使用K8S的滚动升级与探针策略以后将会将我上文提到的所有操作完全自动化进行,不会出现任何一刻的服务不可用,这对于后端服务来说尤其重要。
镜像预热
对于我们的业务环境来说,我们的项目在现在以及未来将会进行全球化的部署,比如美国,欧洲,中国香港等地进行部署。那么一个问题是当我们定义K8S的deployment资源进行部署时,K8S会新建POD以后再通过这个POD进行镜像的拉取。由于我们的业务部署环境的网络情况各不相同,在我们真正部署到生产环境时,很可能会遇见因为网络原因从而拉取不到镜像的情况。虽然这个风险会被我们上文的滚动升级策略所抵消,但是直到部署时才发现我们的镜像网络拉取问题这件事情同样是非常可怕的。一个我所实施的办法是进行镜像预热,所谓镜像预热,即在进行部署之前事先让K8S的每个Node进行镜像的拉取,这个拉取会直接存放在K8S每个NODE的文件系统中,而每个NODE都拉取完我们所需要的镜像这个过程即是镜像预热。等镜像预热结束完以后,再进行部署以后,我们的所有应用将会在拉取镜像环节秒起。虽然对比上面直接拉取的方案,也许在网络时间消耗上并没有减少,但是我们将网络问题的风险提前转移到了镜像预热环节中,从而避免了生产环境部署时出现网络问题拉取镜像失败的问题。
另外一方面,从工作环节的角度去考虑,部署生产环境这个过程总是让人神经紧绷,而镜像预热这个过程可以有效减少生产环境部署的这个流程。假设我们预定晚上6点开始进行生产环境的部署,那么我们在当天下午3点或者4点就能进行镜像预热,在镜像预热的过程中我们不必要像部署生产环境时一直盯着,可以说是非常节约,提高工作效率了。
监控报警的垂直领域切割
对于K8S平台上的监控与报警,我个人非常推荐使用Prometheus搭配Grafana进行使用。一方面是prometheus与grafana在K8S平台上集成的已经非常成熟,运用这些开源组件我们不仅可以看到集群的情况,我们更可以对K8S内每个POD以及每个Container进行监控与报警,比如CPU使用率、内存使用率和网络输入输出的流量等。一个我比较推崇的做法是,对于集群而言,他有一套自己的Prometheus来监控集群的情况以及集群内每个POD和Container的使用情况。而对于业务开发组,每个组有自己的prometheus和alertmanager来对自己的应用健康状况进行监控与报警。这是一个我认为比较科学的从垂直方向上去切割监控与报警的作用域。
语言相关
所谓的语言相关,说白了就是我写JAVA服务时如何更加爽的跟K8S平台集成使用。关于这一点我觉得这篇文章再写下去就太长了,准备放下次写。