by 何健
容器化技术的动机
随着人类软件活动的日益频繁,早期的瀑布式开发逐渐淡出主流开发模式的视野,敏捷逐渐占据主导。并且衍生出了持续发布模式,微服务架构等。这些对旧的部署模式是一个较大冲击,对部署的快速,敏捷,稳定性都有了较高的要求。
敏捷开发(迭代开发)
那么什么是"迭代开发"呢?迭代的英文是 iterative,直译为"重复",迭代开发其实就是"重复开发"。 对于大型软件项目,传统的开发方式是采用一个大周期(比如一年)进行开发,整个过程就是一次"大开发";迭代开发的方式则不一样,它将开发过程拆分成多个小周期,即一次"大开发"变成多次"小开发",每次小开发都是同样的流程,所以看上去就好像重复在做同样的步骤。 举例来说,SpaceX 公司想造一个大推力火箭,将人类送到火星。但是,它不是一开始就造大火箭,而是先造一个最简陋的小火箭 Falcon 1。结果,第一次发射就爆炸了,直到第四次发射,才成功进入轨道。然后,开发了中型火箭 Falcon 9,九年中发射了70次。最后,才开发 Falcon 重型火箭。如果 SpaceX 不采用迭代开发,它可能直到现在还无法上天。 迭代开发将一个大任务,分解成多次连续的开发,本质就是逐步改进。开发者先快速发布一个有效但不完美的最简版本,然后不断迭代。每一次迭代都包含规划、设计、编码、测试、评估五个步骤,不断改进产品,添加新功能。通过频繁的发布,以及跟踪对前一次迭代的反馈,最终接近较完善的产品形态。
持续部署
持续部署(continuous deployment)是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。 持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。 持续部署的前提是能自动化完成测试、构建、部署等步骤。它与持续交付的区别,可以参考下图。
这里面不讨论如何迭代开发和自动化测试
微服务部署
一个微服务应用包含数十个甚至上百个服务,服务是由多种不同的编程语言和框架写成,每一个服务都是一个有自己独特的部署、资源、扩展性以及 监控需求的mini应用。比如,你需要根据服务的需求对该服务运行一定数量的实例,而且,每一个服务实例必须提供适当的CPU、内存以及I/O资源,更加具有挑战性的是,尽管这很复杂,部署一个服务同时还要求 快速、可靠、高效。
举例
- 开发应用使用oracle jdk,但是目标机器安装的是openjdk环境
- 做c开发,应用需要依赖很多动态库,需要在目标机器上安装
- 用python3开发,但是目标机器是python2
- 应用中需要设置系统环境变量
- 数据库开发需要在目标机器初始化数据元
作为开发者遇到线上问题经常说的一句话就是,“我这没问题啊,是不是你的打开方式不对啊”
解决方案
要解决上述问题,这里面有两转变,一是职责转变,开发与运维职责的融合,二是发布模式的转变,本地发布和远端发布的统一才能解决经典的我这边没问题怎么部署上去就有问题的问题。
devops
DevOps(Development和Operations的组合词)是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。
传统的软件组织将开发、IT运营和质量保障设为各自分离的部门,在这种环境下如何采用新的开发方法(例如敏捷软件开发),是一个重要的课题。按照从前的工作方式,开发和部署,不需要IT支持或者QA深入的跨部门的支持;而现在却需要极其紧密的多部门协作。而DevOps考虑的还不止是软件部署,它是一套针对这几个部门间沟通与协作问题的流程和方法。
需要频繁交付的企业可能更需要对DevOps有一个大致的了解。Flickr发展了自己的DevOps能力,使之能够支撑业务部门“每天部署10次”的要求──如果一个组织要生产面向多种用户、具备多样功能的应用程序,其部署周期必然会很短。这种能力也被称为持续部署,并且经常与精益创业方法联系起来。从2009年起,相关的工作组、专业组织和博客快速涌现。
DevOps的引入能对产品交付、测试、功能开发和维护(包括──曾经罕见但如今已屡见不鲜的──“热补丁”)起到意义深远的影响。在缺乏DevOps能力的组织中,开发与运营之间存在着信息“鸿沟”──例如运营人员要求更好的可靠性和安全性,开发人员则希望基础设施响应更快,而业务用户的需求则是更快地将更多的特性发布给最终用户使用。这种信息鸿沟就是最常出问题的地方。
以下几方面因素可能促使一个组织引入DevOps:
1.使用敏捷或其他软件开发过程与方法
2.业务负责人要求加快产品交付的速率
3.虚拟化和云计算基础设施(可能来自内部或外部供应商)日益普遍
4.数据中心自动化技术和配置管理工具的普及
5.有一种观点认为,当前占主导地位的“传统”美国式管理风格(“斯隆模型 vs 丰田模型”)会导致“烟囱式自动化”,从而造成开发与运营之间的鸿沟,因此需要DevOps能力来克服由此引发的问题。
DevOps经常被描述为“开发团队与运营团队之间更具协作性、更高效的关系”。由于团队间协作关系的改善,整个组织的效率因此得到提升,伴随频繁变化而来的生产环境的风险也能得到降低。
容器技术
职责转变了,还需要工具去解决具体问题,这个工具就是容器技术,先说一下它能达成哪些目标。
简化部署:容器技术可以将应用打包成单一地址访问的、Registry存储的、 仅通过一行命令就可以部署完成的组件。不论将服务部署在哪里,容器都可以从根本上简化服务部署工作。 快速启动:容器技术对操作系统的资源进行再次抽象,而并非对整个物理机资源进虚拟化,通过这种方式,打包好的服务可以快速启动。
服务组合:采用容器的方式进行部署,整个系统会变得易于组合,通过容器技术将不同服务封装在对应的容器中,之后结合一些脚本使这些容器按照要求相互协作,这样操作不仅可以简化部署难度还可以降低操作风险。
易于迁移:容器技术最重要的价值就是为在不同主机上运行服务提供一个轻便的、一致的格式。容器格式的标准化加快交付体验,允许用户方便地对工作负载进行迁移,避免局限于单一的平台提供商。
docker
提到容器技术人们就会联想的docker,可见docker在事实层面已经成为容器化技术的一个标准。下面介绍的一些的docker的特性也可以理解为一般容器特性。
先上一个对比图对docker有一个初步认识,这张图是虚拟机和docker的对比图:
虽然都能做到环境屏蔽但是虚拟机和docker还是有很多区别,假如把程序比作人,虚拟机可以理解为屋子,容器就是衣服。人在屋子里和不在屋子里,屋子始终是屋子不会塌,但是人穿衣服时衣服是立着,不穿衣服时衣服就倒了。也就是说容器一定要依附一个可运行的应用,虚拟机却不一定。
其他容器技术
lxc(Linux Container), libcontainer(Docker0.9.0), runc
容器是否是唯一选择
如果不考虑cpu,内存,磁盘io等限制,只是想发布一个绿色应用,拷贝即可用可不可以呢,答案是肯定的。 ###举例
- Q:前端同事觉得自己的构建结果多无法集中成一个build
- A:可用在java程序打包时作为资源打入jar,例如flink就是典型单jar包结构
- Q: 使用python开发,安装了很多依赖库
- A: virtual env可以完美解决,python都不需要安装
- Q: java程序也要部署jdk啊
- A: java程序也可以编译成native啊,可以参考graal
类似go语言,语言级别只支持源码依赖,产出物是单build,完全绿色。
容器编排
如果仅对于应用外覆一层环境,似乎对于需要组合若干组件提供服务的应用也没有起到太大帮助。
docker-compose
Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 OpenStack 中的 Heat 十分类似。
其代码目前在 https://github.com/docker/compose 上开源。
Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。
通过第一部分中的介绍,我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。
Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
Compose 中有两个重要的概念:
服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose 来进行编排管理。
k8s
k8s的全称是Kubernetes(类似i18n),是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。 k8s主要面向的是集群化设计,这里面因为没有什么和咱们相结合的业务场景就不多做介绍了
业务场景
真实需求
介绍了一些基本概念和背景,回归到业务场景,抛开现有设计,LB的使用场景是一个单机模式下的具有大数据处理能力的web服务。
review一下代码
略
入口部分看起来还挺清晰的,截取一段镜像制作的代码 backend_docker_build.sh
#!/bin/bash
略
replace_conf
build_image
push_image
模块化部署
这个问题被单独拿出来看是因为开发人员很关注,所以一直被作为一个重要不紧急的任务放在部署工作列表中。 重要是因为部署时间长影响开发效率。同时这又是一个内部需求,可能在优先级上也没有排到很靠前的位置。
LB部署架构的机会点
首先先解释一下为啥这是一个架构问题,而不是一个简单的技术问题。技术背景之前已经讲过了,似乎没有什么难点,或者说在LB项目中没有遇到什么新问题,但是LB项目在部署方面也确实有一些需要提升的部分。这部分更多在于协作方式变化,毕竟技术已经在应用了。
工作职责转变
首先要认识到组件开发者交付的不是一个应用而是一个自带运行环境的应用。devops是一个职责转变,程序员从模块功能开发向实施部署延展,集中式的部署工作逐渐弱化。
举例
数据库元表初始化目前放在安装部署阶段,是否可以内置到容器内部?一个简单的判别方式就是数据表结构是否会在运行期变化,目前尚无这种需求。
启动相关优化
既然选择使用微服务的架构就需要遵循一些微服务部署的规则,前面例子里面提到的启动依赖对微服务的部署确实是一个挑战。咱们的工程中大量使用了dubbo这种rpc服务,有服务有客户,似乎就是有依赖关系的。但是这里提到的启动依赖不是那种依赖,而是必须在被依赖服务完全启动后,后续组件才能启动的强依赖,这样造成有时候想做局部服务的重启会变的比较复杂。
从集中向分布转变
前面在现状中已经举例说明了将所有部署细节都集中在部署脚本中的脆弱性。如何让部署过程更健壮,稳定呢。自然是减少耦合。具体做法是由集中式部署向分布式转变。
举例
maven大家都用过,mvn从构建到发布就那几个命令,使用者也不需要关心我的源码式什么,依赖了那些库,一切细节都隐藏在pom.xml中。
前面强调的职责转变具体到实践上来说就是将docker build到push作为组件开发者工作的一部分,对于docker 镜像测试作为工作的一部分 回到之前模块化部署的问题,似乎就不存在了,因为每个开发者都可以自主做镜像发布,在本地或者远端做测试
从分散到集中
刚才介绍了镜像构建的方式希望能够从集中变为分散,反观配置,这部分良好的设计应该是从分散到集中。 前面看到了很多config文件,compose文件,好像大部分人从来没用过也没改过。 start-liebao.sh很多人应该使用过,它的目的是一键启动整个LB服务。 不过从docker使用的角度,这未必是最佳的实践方式。前面介绍过docker-compose就是对多组件部署提供完整服务的方案。
举例
略
这里面包含了两个应用,同时在kafka应用中定义了host作为环境变量,通过这种方式可以将配置集中化
结论
最后借用一张图来表述一种理想的部署过程:
最佳的实践方案是将部署工作向开发侧转移,做到分布化,配置集中化,现场实施做到一键式发布。