其实我挺早就接触Docker和Kubernetes,时间大概在3、4年前吧,但是由于当时所在技术团队的业务模式所限制,还没有真正对容器云的技术需求,所以我更多还是以一种技术玩具的心态接触容器技术。
直到去年开始才正式接触基于容器云平台的技术架构,我从业务运维和DevOps的角度来看,和之前的物理机和虚拟机等IaaS层基础上的运维模式有着非常大的差异。
根据这段时间的运维经验,我尝试总结一下某些容器云的运维方法的共同特性,并将其称为“容器运维模式”,简单百度谷歌了一下,没有这个名词,希望是我的首创~
这个名词灵感来自软件工程的“设计模式”,设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
而“容器运维模式”,指的是由DevOps(题外话:DevOps、SRE、SA、运维 等等,其实都差不多是同一个意思,业界喜欢创一个新的名词来代替运维,主要是为了区分自己和一些低端系统维护人员)在日常运维容器化项目的一些经验总结,为了区别于传统的物理机、虚拟机的运维套路,而归纳出来的容器运维方法。
回顾过去
从大概10年前,大家都是以【自建IDC】+【物理服务器】的形式进行生产环境基础架构的建设;
然后持续到大概5年前,私有云技术和公有云的兴起,让大批中小型企业减少对物理设备资源建设的人力和资金投入,可以专注于业务研发和运营;
后到大概3、4年前,容器技术 Docker 和以 Kubernetes 为代表的容器编排技术的崛起,以及微服务技术的同步普及,宣告了容器云平台的来临;
而事实上,以Kubernetes为首的相关周边项目,已经成为了容器云领域的标准,所以绝大部分技术团队如果现在需要选型容器编排体系,可以无脑选k8s了。
需求的根本--应用交付
在传统裸机(bare metal)或虚拟化的时代,当开发团队将代码交付给运维进行生产环境中部署,但是它却未能正常工作时,挑战就出现了。
“运行环境不一致”、“没有安装相关依赖软件”、“配置文件不一样”等等已经成了开发和运维沟通的惯用语。
在传统的开发场景中,开发和测试团队使用的是与生产环境不同的基础设施,尽管做到了代码和配置解耦,但是在运行环境的转换中,依然会得到像前面所述的团队协作和环境依赖问题
而贯穿软件生命周期共享相同的容器镜像是容器化带来的大好处,它简化了开发与运维团队之间的协作关系
由于本地开发/测试服务器和生产环境的不一致以及应用程序打包部署的过程,一直是让研发和运维纠结的难题,但有了容器之后,由于容器镜像里打包的不仅是应用,而是整个操作系统的文件和目录,即是其运行所需的所有依赖,都能被封装一起。
有了容器镜像的打包能力之后,这些应用程序所需的基础依赖环境,也成为了这个应用沙盒的一部分,这可以给这个应用包赋予这样的能力:无论在开发、测试还是生产环境运行,我们只需要解压这个容器镜像,那么这个应用所需的所有运行依赖都是存在的、一致的。
如果熟悉Docker容器技术原理的话,我们知道它主要由Linux内核的Namespace和CGroups以及rootfs技术隔离出来一种特殊进程。把docker形容为一个房子的话,namespace构成了四面墙,为PID\NET\MNT\UTS\IPC等资源进行隔离;而CGroups形成了它的天花板,限制了对系统资源的占用;而rootfs是其地基,是通过copy-on-write机制构成的分层镜像,也是开发者为关心的应用信息的传递载体。作为开发者,他们可能不关心由前两者构成的容器运行时的环境差异,因为真正承载容器化应用的传递载体,这是这个不变的容器镜像。
在Docker技术的普及后不久,为了整个完整的DevOps链条的打通,包括CI/CD、监控、网络、存储、日志收集等生产环境的刚需,以及整个容器生命周期的管理和调度,以Kubernetes为首的容器编排体系也作为上层建筑也迎来了一波快速的增长。从容器到容器云的蜕变,标志着容器运维时代的来临。
容器运维模式的主要场景分析
1、声明式 vs 命令行
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12.2
ports:
- containerPort: 80
我们知道Kubernetes是通过yaml文件(样例如上所示)来对其API对象,如Deployment、Pod、Service、DaemonSet等进行期望状态的描述,然后k8s的控制器有一套状态调谐的机制让各种API对象按要求所述的状态运行。由于这样一套运行机制的存在,所以使得k8s和过往运维常见的命令行,也包括脚本式的运行方式有着很大的差异。
深度使用过puppet的运维工程师可能会比较清楚两者的区别,puppet也是一套基于声明式机制的配置管理和状态管理的工具。在没有puppet之前,运维工程师喜欢用简单的shell、python脚本对众多服务器进行统一的软件安装、配置管理,但随着服务器数量增多和配置项的递增,命令行式的配置管理往往出现各种缺陷。如状态不一致、历史版本无法回滚、配置没有幂等性、需要很多状态判断才能执行终的操作等等。
而声明式的配置管理方法,可以规避以上弊端,原因如下:
当我们确认了一个版本yaml配置文件后,表示向k8s的Kube-Controller-Manager提交了我们所期望的对象状态信息,然后k8s使用patch的方式对API对象进行修改。而声明式API是k8s项目编排能力的核心所在,它可以在无需干预的情况下对api对象进行增删改查,完成对“期望状态”和“实际状态”的reconcile过程。
以我们常用的deployment对象为例,
方式一:
$ kubectl apply -f deploy-ooxx.yaml
方式二:
创建使用 create ,修改yaml使用edit,然后用replace使之生效。
k8s对这两种机制的处理方法是完全不同的,前者是声明式,后者是命令式。
两者的结果虽然都是触发滚动更新,但是前者是对原有API对象打patch,后者是对象的销毁和替换。前者能一次处理多个yaml配置变更的写操作并具备相同配置项的merge能力,后者只能逐个处理,否则有冲突的可能。
所以,我们只需要确认yaml文件的版本,一律通过 kubectl apply 命令进行执行,无需再考虑步创建、第二步修改、第三部替换之类的命令行。那么我们统一用apply命令,可以通过history命令进行回溯版本,也可以保证apply的结果的幂等性等等。
使用声明式只需要描述终所需的状态,无需用户关心过多的实现流程和细节,没有像命令行式的那么多上下文关系或者运行环境依赖,甚至可以由开发人员直接编写,运维进行code review即可。特别在使用Kubernetes这样的容器编排工具,更加要深刻理解和灵活运用声明式的运维模式。
2、API对象
Hi峰叔:容器运维模式(二)-- API对象3、控制器模式
Hi峰叔:容器运维模式(三)-- 控制器模式4、接口和实现
Hi峰叔:容器运维模式(四)-- 接口与实现5、Master-Node & Api-server
Hi峰叔:容器运维模式(五)-- Master-Node & Api-Server总结
从过去的【单体式应用+物理机】,到现在【微服务应用+容器云】的运行环境的变革,需要运维工程师同步改变以往的运维技术思维。新技术的应用,会引发更深层次的思考,深入了解容器之后,我们会自然而然地会去学习业务主流的编排工具--Kubernetes。
Kubernetes前身是谷歌的 Borg 容器编排管理平台,它充分体现了谷歌公司多年对编排技术的佳实践。而容器云字面意思就是容器的云,实际指的是以容器为单位,封装环境、提供构建、发布、运行分布式应用平台。
而运维工程师在面对业界更新迭代极快的技术潮流下,需要选定一个方向进行深耕,无疑,Kubernetes是值得我们去深入学习的,毕竟它战胜了几乎所有的编排调度工具,成为业内编排标准。我们通过搭建容器云环境下的应用运行平台,并实现运维自动化,快速部署应用、弹性伸缩和动态调整应用环境资源,提高研发运营效率,终实现自身的运维价值。
以上是笔者的一些浅薄的学习体会,希望可以给大家一个参考~