开发者学堂课程【ALPD 云架构师系列-云原生 DevOps36计:如何保证软件交付过程的标准化】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/82/detail/1268
如何保证软件交付过程的标准化
内容介绍:
一、开发、运行的基础设施
二、保证软件交付过程的标准化
一、开发、运行的基础设施
要做好云研发的资源交付、工程实践需要做到三大块,一是不可变基础设施,二是资源交付的实现,三是安全可信发布。
今天进入到第一部分学习,基础设施部分,标题中分了 part1。
上次讲的不可变基础设施内容中,举了一个例子,标准化、基于统一标准的产业链,从《集装箱改变世界》书中提到。
上世纪五六十年代,整体货运成本降低了95%,所有的码头工人基本上面临着其他类型的职业,其实很小,就是做了个铁盒子将其装起来,但让整个经济全球化带来了非常大的变化。比如中国制造的东西,美国人也能用,后面中国成为世界工厂,也有非常大的关系,可以向全世界下订单了。这里面提到两个比较重要的点,一是不可变,二是标准化。
1、不可变:
(1)消除不一致带来的不确定性
(2)减少不一致的风险
(3)减少维护成本
2、标准化:
(1)简化部署
(2)降低环境维护成本
(3)降低工具链开发和学习成本
二、保证软件交付过程的标准化
软件开发过程中也会遇到类似的问题,在供应链或集装箱可以改变的世界,那么在软件开发过程中,如何享受产业生态的红利呢?软件交付的“集装箱”应该是什么?
首先回答这个问题,在十几年的时间软件交付中的形态发生了很大的变化,一开始买机器都买的是物理机,后来买虚拟机,现在很多人开始用容器了,那中间为什么会产生这个变化?
其实可以思考,是谁有动力来去促进变化的发生,会发现最早的容器技术本身的底层技术 namespace 和 c group,这两个东西很早就出现了,10几年前快20年前就出现了。
但是最早应用它的其实是名厂商,因为它对资源的利用率和隔离有很明确诉求的厂商。比如阿里云,显然不希望跑在机器上不同的用户进的东西互相串,最好能够限制每个用户的资源,比如 cpu 是多少,内容是多少。
资源是隔离的,是可以限制的,所以就有该诉求,基于这个场景就知道可以用 LXC 这样的方式去隔离和限制资源。但是还没有产生容器,原因是其只是在云场上自己内部去运行,但是不能互相分发。所以 docker 的伟大之处不在于他在底层的基础上做了多大的创新,而是他提供了一个叫做容器镜像的概念。
容器镜像是分发的形式,可以将容器镜像分发给别人,或者让别人继承镜像,这样就形成了一个市场可以分发,同时又提供了 Dockerfile,其可以通过文件的形式去描述镜像,一旦能够定义它,便可发现可以写作了。有这样的能力后,一下子整个的容器都能够被大家所接受。所以容器的接受看起来好像是一个技术范围,其实是随着云原生慢慢的兴起,必然带来的结果。
所以现在讲软件交付的集装箱,基本上大家所认为的集装箱就是容器,而容器很多时候都被认为是 docker 的容器。事实上,在 keybase 中支持多个容器,但大部分情况都会用 docker 容器,其相对于以往的优势就是刚刚提到的,一是镜像,二是 dockerfile。
Docker 镜像提供的如集装箱一样,能够很好的做相应的分发,另外本身容器也提供了一些资源隔离相应的东西。另外,它是在比较小力度的基础上,然后去做相应的隔离,比如虚拟机可能也会做相应的隔离,但相对力度比较大。
容器还提供了一个非常弹性的资源管理方式,对于虚拟机和物理机都有比较大的改善。所以它本身就是物理生命的一个进程,它和虚拟机本质上具有很大的差别。了解后大概知道了软件交付的集装箱是什么,其实很容易了解。
1、软件交付的“集装箱”应该是什么?
(1)容器是一个通过 namespace 和 cgroup 来实现资源隔离和资源限制的沙箱进程。
(2)容器镜像是容器的一种分发形式,里面包含了运行容器所需的组件和资源。
(3)Dockerfile 是描述容器的一份文档。
(4)相对于早先的 LXC,docker 提供了 Dockerfile 这样的容器定义方式,并提供了镜像继承和复用的能力。
容器镜像到底是什么样子的,怎么样组成的。可以看到下方的图片,这个图很形象,一层一层的,这事实上就是 docker 容器镜像中内部的结构。
很多人问,自己去构建一个镜像,会发现出现在日志中很多哈式值,一层层,事实上它的每一层都是有它特定东西,比如最下面的那层 Knernel,其实是运行在虚拟机上,通过 LXC、an inx container 这样的技术,或者现在其他的一些技术,然后通过这个技术去把容器的进程构建出来,进程通过 name space 和 c group 做资源隔离和限制。
那么在容器镜像本身,它有一个叫 basic,就是基础镜像是什么,因为要运行一个程序,它是有操作性的环境要求的,比如依赖一些 library,依赖一些二进制的,比如希尔等,依赖很多东西。这个东西如果是在随便一个物理机上,或者虚拟机上进行部署,它都会随着机器的环境不同而有不同,有可能导致一些风险。
所以容器镜像提供了基本镜像,将这些东西都放入其中并且告诉是什么东西。再向上可以看到 emacs 和 add Apache,这两层其实是会被写的,比如安装软件或者将镜像拷贝进去,这即是这一步,是会向里面加东西。最上面 writable 是在容器 container 运行时,真正可以去写的那一部分东西,所以整个层次就是如此。
其特点首先是分层,即每一层都是可以复用的,假设这一层是一样的,在机器上有很多个容器 container,如果面积是一样的,其实只要下一个就够了。如果资源,可以看到每一层都是基本镜像加所有的层堆起来的,所以如果堆的东西越少,显然镜像就会越少,容器镜像有一个最少的镜像叫 scratch,是最原始的镜像,这里面几乎什么都没有。所以如果想构建一个像 goal,非常小的容器镜像,从 scratch 可以来,可能就几兆大小,但如果基于3OS,可能就是上 G 的大小,这是非常有趣的一个地方。第三点是非常重要的概念,叫 one process per container,即容器的生命周期等于进程的生命周期。
很多时候都听过一句话,容器是 k8s 上的进程,将 k8s 当做操作系统,容器就是上面的进程。进程的生命周期是可以被管理的,可以知道进程的生命周期是什么样子,所以这里就引出了一些问题,大部分人是不是还在用虚拟机或者物理机的方式在用容器。知道容器的特点后,实际在过程当中用容器时,常常也会遇到很多问题,是将用容器的方式当做虚拟机用等等还是其他问题。
2、容器镜像的特点:
(1)镜像是分层的,layer 可以复用。
(2)依赖的资源越少,镜像越小。
(3)One process per container(容器生命周期=进程生命周期)
3、容器镜像的常见问题:
(1)镜像加入了 chromium、elasticsearch、jdk...,大小超过了2个G。
这个问题很简单,是把所有东西都放到一个容器中,这里2个 G 其实都不止,甚至还有3、4个 G,因为做各种各样的业务咨询,装了很多 chromium、elasticsearch、jdk 等。最后发现容器非常大,启动也非常慢,恨不得将所有的东西都加入其中,类似于当做机器用所以将很多东西都放进去。
(2)把镜像的 ENTRYPOINT 设置为 systemd,再添加服务有什么问题?
ENTRYPOINT 在 dockerfile 中会去写,docker run默认 ENTRYPOINT 去起容器镜像,ENTRYPOINT 如果是 systemd,systemd 是 nex 上的 demo 管理器。
当 systemd 作为容器镜像管理的 ENTRYPOINT 的时候,事实上可以在上面添加很多服务,可以当一个虚拟机来用,甚至可以有 IP 地址,可以有防火墙等都可以做得到。这个容器其实就是一个虚拟机,虽然不是用虚拟机运行,但是当虚拟机来用。
因为起点就是 systemd,这有什么后果,大家可以再思考一下,为什么用 systemd 不是一个非常好的情况。systemd 里面的一些进程、运行的结果,单行的状态,其实是不一致的,有可能里面的进程已经死了或者 crush,但是 systemd 还活着,从外部看起来这个容器是好的。按理来说,希望容器子式和本身所提供的服务是绑牢的,它的状态和提供服务的状态是一致的。
(3)私有化部署的时候带一堆导出的镜像 tar 包,U 盘快满了。
因为带着包出去,要去做私有化的部署,去客户现场里做相应的部署,刚刚提到了,容器是分层的,每一层都有复用,但如果直接 dama 出来,一个镜像 dama 一个 tar 包,tar 包是不分层的,它不知道里面有很多层。所以会发现,带出去的镜像特别大,这是其一问题;
其二问题是到客户现场后,要去安装时很麻烦,满大街去分发,整个过程是非常不好的,除了大小之外,分发过程也是非常不好的,所以这个容器其实应该是一个分层复用的概念,但是被用成重复的概念,重复了好多问。可以看到是一层一层的,上面那一层永远可以做,下面这一层也是可以的。
(4)每次把基础镜像下发到整个集群,都会让网络变得特别拥堵。
可能在集群量大的时候会用到,当把基础镜像下发到整个集群时,比如改了个旧电版本,从testing 改到 table,这样更新所有的 Dockerfile 之后,会需要把它们下发到整个集群,这个时候会发现如果业务在高峰期的时候,内部网络会非常拥堵,因为一个容器镜像可能几百兆,可能数以千计的去 dockpro 的时候会占用大量的带宽,其是会影响到业务属性。所以这样,带来后果是因为运维的一个操作,导致了网络拥堵,进而导致服务出现一些问题。
本身应该让服务更稳定的去运行,反而带来了风险,将服务的可用性降低了。可以举一个例子,之前有一个小公司,当时做了安卓的 CK,该 CK 有一个自更新的功能,它会网上去查是否有新版本出来了,出来后就去拉新版本,下载下来更新。
但是,这其中忘了一件事,因为有几百万的终端,万一它同一时间都来查,都来拉包,那么服务是会被打死的。而服务因为有时是有网关在前面,但网关是扛不住这么大的量,在下面给 boss 攻击一样,而要更新该问题,又得先发一个包。所以当时带来了很大的一个挑战,就如现在自己写了一个 bug,导致无法更新。有了这些常见的问题,也给了一些推荐和实践的建议,如果要用逻辑镜像的话,那应该按什么样的一个方式来列据点。
4、容器镜像的实践建议
(1)刚讲到不要用太大的,所以尽量采用轻量级的基础镜像和确定的镜像版本。
(2)通过分层来复用镜像内容,避免重复拉取。因为避免所有东西都放到一起。
(3)避免采用如 systemd、supervisord 这样的进程作为 ENTRYPOINT。类似这样的,不要用其作为 ENTRYPOINT。
(4)采用本地 docker registry 的方式来离线拷贝多个镜像。
(5)避免同时大量的 docker pull,可采用 P2P 的方式提升镜像分发效率(如 dragonfly蜻蜓)。
因为做的不好的话,相当于给自己做了一个 depos,所以这是非常危险的,如果真的遇到这样的错误,它非常难修复。所以像阿里、dragonfly 会做相应的镜像的分发,基本上以 P2P的方式去做,所以它一定程度上可以去加速分发的效果。
这就是从刚才说到不可变基础设施中,要去确定不可变还有标准化, docker 镜像可以做相应的一些标准化的事情。那么另外,就是要考虑标准化其实是一个手段,标准化不是目的,它是帮助更高效的,或者可以复用生态的所有的技术、整个生态的其他一些东西。