2015 年 11 月 12 日 ,又拍云架构运维大会上海站在沪举行,Coding CTO 孙宇聪在会上作题为《浅谈架构升级》的分享,以下是演讲实录:
今天标题是浅谈架构升级,我主要分析一下 Coding 作为新成立的公司,2014年成立上线,到现在是一年半的时间。Coding 2014年是什么样的?一台服务机,几个人,我们就把 Coding 网站搭起来,后面经过一段时间的发展,服务慢慢变得多起来,管理上也遇到一些困难,架构做过很多调整,然后变成了这个样子。 我们希望 Coding 未来的机房也跟谷歌一样,也可以作为一个旅游景点去参观。
昨天我到代码库里面看一下,Coding 上线了一年半的时间,第一个版本在2014年8月,到现在一共有三百多个版本,我自己都不太敢相信了。三百多个版本,每周至少发布两次,多的时候甚至一周七八次也是有。同时我又不觉得奇怪,任何一个创业公司可能都经历了相同的成长历程,版本数量的飙升,也带来了架构的一点点更新。
回首 Coding 开始的时候,一共三五个工程师。整个网站架构就是一个很土的 java 程序,后面接一个数据库,现在慢慢变成了现在这个复杂的架构。这个图上的字大家看不清楚不要紧,因为这个架构图已经过时了(笑)。
公司网站的架构每周,每个月都有变化。所谓的架构升级就是在飞机飞行的过程更换引擎。大家可以想一下如果你来的话,要怎么换。当然这一定是还要继续保证飞机一直在天上(笑)。
另外一种说法是在行使过程给火车换轮子,这个就听起来容易多了。不管怎么说,架构升级就是干这些事情,一定想办法不影响业务运行的情况下,逐渐替换老的架构,换上新的东西,飞的更高,跑的更快。
我总结了一下,Coding 在一年半的时间内,主要做了三件事情。
第一件事情就是研发体系,软件架构升级。这个大家听比较多,所谓无状态化、微服务化、数据逻辑分离等等。听起来像是一些比较空的理念,但是具体落地执行起来还是有很多值得讲的地方。
第二就是生产环境管理理念的升级:容器化、代码化、自动化。 说到生产环境管理:一开始就靠人脑,后来好一点的公司有一个表格,再后来升级为 CMDB 之类的。我们公司之前写在即时贴上,贴在墙上,或者显示器上。这个东西怎么推动进步?容器化就是让软件封装起来,有了这一点之后,就可以搞代码化,为将来的自动化做基础。
第三点就是资源管理概念的升级。
我们先说第一点,就是微服务架构。关于这个,大家可能都有点听腻了。以前的应用程序把所有的代码写在一起,微服务架构就是把它们拆分成了右边这个一堆小服务的状态。各个微服务有自己生命周期。注意这里没有绝对的好坏之分,但是相对来说微服务发展的更容易,如果把基础环境做好之后,微服务可以更快,更大规模的部署。
Coding 分了几个功能性的小组,大家共同维护一个大体的程序。右下角还有一个 OPS 团队,负责整个版本的构建,测试,然后做一个发布。很多公司都是这样的,这样的架构持续了很久都没什么问题。后来我们逐渐引入了微服务,由各个小组来维护。 但是统一的发布和更新还是一体的,这样会造成什么问题?是运维团队实在处理不了这样的情况了。因为每周都有一个业务团队跟 OPS 来说,近期我写了小东西,用了个 XXX 技术,可牛了等等。
但是从 OPS 的角度来说,这个没有办法管理啊。因为微服务维护上完全不能统一,包括监控,文件管理等等都做不到统一,管理难度直线上升。很多公司在这个时候就选择做一个平台,让小组上来自己做发布,什么东西都是你们管。 其实这个有点过,我们 Coding 真正选择的就是就是搞容器化。
提到容器化,现在很火的就是 docker,它实际引入了几点,第一点重要就是做了 packaging,作为容器来说是重要的概念。有了这个基本属性之后,第二点给物理机和应用程序做了一个 Universal API,我只要把容器跑起来,内部我就不再过问了。第三就是为进一步做自动化做了一些基础,包括运维的自动化,监控的自动化,所以引入了容器化之后,我们看一下,刚才这个图变成什么样子。
现在运维部门不再真正关心业务造出来的东西,只要搭起一个容器流水线,可以接纳容器,那么所有的容器都可以在这个平台上进行部署。我觉得这个图更加清晰描绘出在一个公司内部 PAAS 的位置,运维团队不应该太操心应用团队做出来的东西,而是想办法不同部门产生出来的程序管理起来,跑在一起,这个是对容器化的理解。
再具体一点,Coding 究竟在 Docker 上做了什么事情呢?
第一:Coding 所有的代码都打包成了 Docker image.
第二:Coding 自己搭建了私有 registr.
第三:Coding 所有程序都在 docker container 里运行.
但是 Coding 在用的过程中,排了无数的雷,我们用法也与一般的公司不大一样。
要说明这个问题我们先来看一下 docker 究竟是什么东西?
Docker 大概分为四个部分,首先 docker 有 image/Distribution,还有一个 runtime,一个编排系统。
首先看一下 Docker Image,docker image 是用 dockerfile 来写的,这个 Dockerfile 就是个半成品:
1. 第一行的 From 给 Image 指定了一个基础的镜像,但是基础镜像没有一个是非常靠谱的, 一般人从 docker HUB 上找来的镜像要么是奇奇怪怪的基础镜像(例如有的是基于 Debian 的,有的是基于 CentOS 的),要么是早就没人维护的 Abandonware。 我把这称为新一代的 copy & poste 大法,以前程序员不会写程序去网上搜一段回来抄到代码里。 现在你让他们去包装容器,那么肯定还是在网上搜回来一段,抄进 Dockefile 自己给自己埋坑。
2. 你写了完之后 from,就要写 run。 RUN 也有问题,第一, 如果你的镜像里写了 apt—get update && apt-get upgrade –y ,那就是错误的用法。首先是慢,你用国内的源会稍微好一点。再一个每句加一个 layer,轻松来个几 G 的镜像。结果是不可重现,你经常就会发现某个某个地方下的东西不见了,经常发现 docker 编译失败。 一个镜像如果每次编译出来的结果都不一样,那到底哪个才是正确的?
3. 然后是 Cmd/Entrypoint 的问题,这两个居然也占 Layer,实在是很无语啊。有必要把运行命令写死在镜像里面吗?如果只是简单的参数有点区别,难道要编好几个镜像?
4. 第四是 Dockerfile 造成了以前没有的问题:编译产生的垃圾谁负责?代码谁负责清理?生产环境里的镜像总不能带着源代码吧!
我们来看看 Coding 怎么做的这个事情的, 我们将这个步骤拆成两步,Build & Package。所有主流编程语言都已然实现了编译和打包工具,依赖管理压根就不是一个问题,所有的主流语言都有现成的方案,为什么不直接用而要自己发明呢?
Coding 更关注的是接口统一,我不是很关心每个项目怎么 bulld 出来,其中可能涉及到执行一些第三方的命令,我只要一个接口就可以产生一个可执行任务的包。
Package 也是一个接口。目前因为我们都还是跑在 Docker 平台上,暂时还是先用 docker image 格式,随时也可以改为 Tar 包,其他什么的格式。Coding 里正确的 Dockerfile只有三行,FROM, ADD, RUN. 就这三行,你如果写了其他的,那都是错误的用法。
我们说一下 Docker Registrty,其实他的 API Library 都很不完善,只是大概提出来一个这么一个概念。现在还有人提出搞 P2P 传输,加密,CDN 什么的,我听了之后感觉都很牛,但是我一个都用不上。目前是在排不上优先级去折腾,先凑合着用就行了。他对我来说就是一个 FTP,随时可以改掉。
Docker runtime 我对它的评语就是坑爹,它代码质量、架构太差劲了。当然我们用它用的规模也很大,压力也很高,暴露出许多问题,第一 dockercontainer 在 stdout 有大量的数据传输会导致内存泄露,直至 package daemon oom。
docker daemon 在频繁创建 container 后,会在文件系统中遗留很多垃圾文件不情理,导致磁盘 inode 被耗尽。docker 里面没有 init,daemon 也没有 reap 子进程 fork 很多进程,会在系统中出现很多僵尸进程,导致 docker daemon 出现问题。
其实 docker 的问题海量无数,我们自己也提交了一些 PATCH。未来的计划是打算把 docker daemon 抛弃了,我们准备写一个,RPC 服务器 + libcontainer(runc) 直接管理自己的容器。
Conding 的容器化方案里,没有任何花哨的东西。我们是严格按照单进程容器,微服务容器,没有必要上升到平台层去做事情,所以我们用的都是host网络模式。SDN 方面我们实在没有任何的应用位置,性能损耗至少在30%左右。为了追求这一种无畏的隔离,搞这种性能损耗这么严重的东西是为什么,我一直也没有想清楚。第三点我们的数据都是写在 host 上,因为 host 上数据可以持久化,我们短时间内也无法抛弃这种做法。
接下来说一下 docker 编排的系统,你可以发现所有真正大规模使用 docker 的人,他们都是采用了自研编排系统。大家都知道 docker 编排系统几个神仙正在打架,我不知道他们在争什么。他们提出的方案没有一个可以很好的解决我们目前的问题,我不知道是我们的问题有问题,还是他们的解决方案有问题(笑)。
第一很多厂商强调他们的东西可以支持动态伸缩。 这个需求有,但是没有那么大,在国内的公有云上你给我应用一个试试,不省钱啊!
第二这些方案对docker容器的直接接管能力都不行,除了 swarm 之外,其他都不好用,但是 swarm 自己的发展方向也有问题。
从这点出发,Coding 认为对我们来说编排系统重要是工具,实用,半自动,因为我们的精力是有限的,包括研发的精力也是有限的,不可能浪费在搞一个完整的 PAAS 平台上,只要我们自己够用就行了。
第一、 我们提出了所谓代码化生产环境环境,说白了很简单,就是搞一个配置文件,里面写的东西很简单,告诉我们什么业务运行在什么机器上面,不要小看这个需求,这个需求是重要的,第二有一些常用的属性,这个业务需要什么端口,主机需要什么目录,第三出来提出自己的抽象,我们认为一个业务应该是一个 Job,下面有很多 task。 这个配置文件达到了可以复制的地步,一个东西拷到另外一个集群, 就可以把一模一样的东西跑起来。
第二、 基于代码化的配置管理我们做到了半自动化操作,比如说把 Job 抽象出来,我们只需要跑出来,我们发一个 UP 指令给他。半自动化操作就是自动使用标准配置启动一个容器,自动监控容器的状态,内存等。还有一些高级化的操作,比如说 update,操作,会列出当前的 image 列表,选择后就可以因为全自动更新。所以对我们来说,这个就是我们需要的东西,我们要知道哪些版本可用,要更新什么版本,中间怎么发生的,没有人关心,除了写这个工具的人关心之外,其他没有人关心。
第三、 未来还准备做的功能就是 DIFF在真的更新之前,先看看改了什么,第二 job/tasks 抽象层可以批量操作一个服务。第三个方便网页观看,可以看 stotus log 甚至远程 shell功能,甚至在容器里面执行任务。
给大家讲一下所谓资源环境管理的理念升级。首先跟大家介绍一下 pet vs catlte。
宠物性管理把一台服务器当成宠物去管理,给机器起个特殊的名字,有病了还要给宠物治病。 这个小公司都是这样的。但是公司大了,就做不好了,因为一个人要管理很多台机器,有时候还要换别人来管,就不太记得宠物是什么状态。 第二种管理方式,也是我们现在推崇的,叫放牛式的管理。你在添加新服务器的时候,就不能起给他们特殊名,只能按照地理位置,或者是逻辑顺序起,基本上必须是起数字名。
一头牛如果得疯牛病了,你肯定不是花大价钱给他治好,而是干掉再买一头。同样的道理,机器出问题如果不能修,我们当然是直接干掉。所以如果采用了这个理念管理生产环境,你强调可重现性,所有的东西都是代码,所有服务器管理也是代码化,自动化的,那么你把这个服务器干掉之后可以重现出来,在几十秒之内可以开启一个新的服务器。
第二是静态、手动的资源调配,多留点富余余量,迁移能力比压榨能力更加重要。与其追求自动化的分布,我们更加需要一个手动、静态的分布系统,只要被完美的执行下去就可以了。我不想和编排系统去搏斗。
第三是就是 Redundancy 更加重要,资源不是重要的,我们不是资源特别紧张的场景。
今天讲的差不多了,我再有一个总结。
第一是开发环境要定死接口,放手实现,至于怎么实践,各个公司有不同的选择,把这三个部分分清楚,你对这个问题思考会上一个层次。第二个生产环境容器化,要关注三个重点,就是打成包,挪的动,可复制。还有就是 DEVOPS 的概念,主要是讲迪马化,自动化,工具化,首先要把生产环境变成代码,工具才能操作这个代码实现自动化,取决于具体场景的需要。