作者简介:刘超,[网易云][ https://c.163.com/ ]计算解决方案首席架构师。10年云计算领域研发及架构经验,Open DC/OS贡献者。长期专注于kubernetes, OpenStack、Hadoop、Docker、Lucene、Mesos等开源软件的企业级应用及产品化。曾出版《Lucene应用开发揭秘》。
以下为正文:
[云计算的前世今生(上)][ https://segmentfault.com/a/1190000008091499 ]中提到:云计算解决了基础资源层的弹性伸缩,却没有解决PaaS层应用随基础资源层弹性伸缩而带来的批量、快速部署问题。于是容器应运而生。
容器是Container,Container另一个意思是集装箱,其实容器的思想就是要变成软件交付的集装箱。集装箱的特点,一是打包,二是标准。
在没有集装箱的时代,假设将货物从A运到B,中间要经过三个码头、换三次船。每次都要将货物卸下船来,摆的七零八落,然后搬上船重新整齐摆好。因此在没有集装箱的时候,每次换船,船员们都要在岸上待几天才能走。
有了集装箱以后,所有的货物都打包在一起了,并且集装箱的尺寸全部一致,所以每次换船的时候,一个箱子整体搬过去就行了,小时级别就能完成,船员再也不能上岸长时间耽搁了。
这是集装箱“打包”、“标准”两大特点在生活中的应用。下面用一个简单的案例来看看容器在开发部署中的实际应用。
假设有一个简单的Java网站需要上线,代码的运行环境如下:
看,一个简单的Java网站,就有这么多零零散散的东西!这就像很多零碎地货物,如果不打包,就需要在开发、测试、生产的每个环境上重新查看以保证环境的一致,有时甚至要将这些环境重新搭建一遍,就像每次将货物卸载、重装一样麻烦。中间稍有差池,比如开发环境用了JDK 1.8,而线上是JDK 1.7;比如开发环境用了root用户,线上需要使用hadoop用户,都可能导致程序的运行失败。
那么容器如何对应用打包呢?还是要学习集装箱,首先要有个封闭的环境,将货物封装起来,让货物之间互不干扰,互相隔离,这样装货卸货才方便。好在ubuntu中的lxc技术早就能做到这一点。
封闭的环境主要使用了两种技术,一种是看起来是隔离的技术,称为namespace,也即每个namespace中的应用看到的是不同的IP地址、用户空间、程号等。另一种是用起来是隔离的技术,称为cgroup,也即明明整台机器有很多的CPU、内存,而一个应用只能用其中的一部分。
有了这两项技术,集装箱的铁盒子我们是焊好了,接下来是决定往里面放什么。
最简单粗暴的方法,就是将上面列表中所有的都放到集装箱里面。但是这样太大了!因为即使你安装一个干干静静的ubuntu操作系统,什么都不装,就很大了。把操作系统装进容器相当于把船也放到了集装箱里面!传统的虚拟机镜像就是这样的,动辄几十G。答案当然是NO!所以第一项操作系统不能装进容器。
撇下第一项操作系统,剩下的所有的加起来,也就几百M,就轻便多了。因此一台服务器上的容器是共享操作系统内核的,容器在不同机器之间的迁移不带内核,这也是很多人声称容器是轻量级的虚拟机的原因。轻不白轻,自然隔离性就差了,一个容器让操作系统崩溃了,其他容器也就跟着崩溃了,这相当于一个集装箱把船压漏水了,所有的集装箱一起沉。
另一个需要撇下的就是随着应用的运行而产生并保存在本地的数据。这些数据多以文件的形式存在,例如数据库文件、文本文件。这些文件会随着应用的运行,越来越大,如果这些数据也放在容器里面,会让容器变得很大,影响容器在不同环境的迁移。而且这些数据在开发、测试、线上环境之间的迁移是没有意义的,生产环境不可能用测试环境的文件,所以往往这些数据也是保存在容器外面的存储设备上。也是为什么人们称容器是无状态的。
至此集装箱焊好了,货物也装进去了,接下来就是如何将这个集装箱标准化,从而在哪艘船上都能运输。这里的标准一个是镜像,一个是容器的运行环境。
所谓的镜像,就是将你焊好集装箱的那个时刻,将集装箱的状态保存下来,就像孙悟空说定,集装箱里面就定在了那一刻,然后将这一刻的状态保存成一系列文件。这些文件的格式是标准的,谁看到这些文件,都能还原当时定住的那个时刻。将镜像还原成运行时的过程(就是读取镜像文件,还原那个时刻的过程)就是容器的运行的过程。除了大名鼎鼎的Docker,还有其他的容器,例如AppC、Mesos Container,都能运行容器镜像。所以说容器不等于Docker。
总而言之,容器是轻量级的、隔离差的、适用于无状态的,可以基于镜像标准实现跨主机、跨环境的随意迁移。
有了容器,使得PaaS层对于用户自身应用的自动部署变得快速而优雅。容器快,快在了两方面,第一是虚拟机启动的时候要先启动操作系统,容器不用启动操作系统,因为是共享内核的。第二是虚拟机启动后使用脚本安装应用,容器不用安装应用,因为已经打包在镜像里面了。所以最终虚拟机的启动是分钟级别,而容器的启动是秒级。容器咋这么神奇。其实一点都不神奇,第一是偷懒少干活了,第二是提前把活干好了。
因为容器的启动快,人们往往不会创建一个个小的虚拟机来部署应用,因为这样太费时间了,而是创建一个大的虚拟机,然后在大的虚拟机里面再划分容器,而不同的用户不共享大的虚拟机,可以实现操作系统内核的隔离。这又是一次合久必分的过程。由IaaS层的虚拟机池,划分为更细粒度的容器池。
有了容器的管理平台,又是一次分久必合的过程。
容器的粒度更加细,管理起来更难管,甚至是手动操作难以应对的。假设你有100台物理机,其实规模不是太大,用Excel人工管理是没问题的,但是一台上面开10台虚拟机,虚拟机的个数就是1000台,人工管理已经很困难了,但是一台虚拟机里面开10个容器,就是10000个容器,你是不是已经彻底放弃人工运维的想法了。
所以容器层面的管理平台是一个新的挑战,关键字就是自动化:
当前火热的容器管理平台有三大流派:
三大容器门派,到底鹿死谁手,谁能一统江湖,尚未可知。
网易之所以选型Kubernetes作为自己的容器管理平台,是因为基于 Borg 成熟的经验打造的 Kubernetes,为容器编排管理提供了完整的开源方案,并且社区活跃,生态完善,积累了大量分布式、服务化系统架构的最佳实践。
想不想尝试一下最先进的容器管理平台呢?我们先了解一下Docker的生命周期。如图所示。
图中最中间就是最核心的两个部分,一个是镜像Images,一个是容器Containers。镜像运行起来就是容器。容器运行的过程中,基于原始镜像做了改变,比如安装了程序,添加了文件,也可以提交回去(commit)成为镜像。如果大家安装过系统,镜像有点像GHOST镜像,从GHOST镜像安装一个系统,运行起来,就相当于容器;容器里面自带应用,就像GHOST镜像安装的系统里面不是裸的操作系统,里面可能安装了微信,QQ,视频播放软件等。安装好的系统使用的过程中又安装了其他的软件,或者下载了文件,还可以将这个系统重新GHOST成一个镜像,当其他人通过这个镜像再安装系统的时候,则其他的软件也就自带了。
普通的GHOST镜像就是一个文件,但是管理不方便,比如如果有十个GHOST镜像的话,你可能已经记不清楚哪个镜像里面安装了哪个版本的软件了。所以容器镜像有tag的概念,就是一个标签,比如dev-1.0,dev-1.1,production-1.1等,凡是能够帮助你区分不同镜像的,都可以。
为了镜像的统一管理,有一个镜像库的东西,可以通过push将本地的镜像放到统一的镜像库中保存,可以通过pull将镜像库中的镜像拉到本地来。
从镜像运行一个容器可使用下面的命令,如果初步使用Docker,记下下面这一个命令就可以了。
这行命令会启动一个里面安装了mysql的容器。其中docker run就是运行一个容器;--name就是给这个容器起个名字;-v 就是挂数据盘,将外面的一个目录/my/own/datadir挂载到容器里面的一个目录/var/lib/mysql作为数据盘,外面的目录是在容器所运行的主机上的,也可以是远程的一个云盘;-e 是设置容器运行环境的环境变量,环境变量是最常使用的设置参数的方式,例如这里设置mysql的密码。mysql:tag就是镜像的名字和标签。
docker stop可以停止这个容器,start可以再启动这个容器,restart可以重启这个容器。在容器内部做了改变,例如安装了新的软件,产生了新的文件,则调用docker commit变成新的镜像。
镜像生产过程,除了可以通过启动一个docker,手动修改,然后调用docker commit形成新镜像之外,还可以通过书写Dockerfile,通过docker build来编译这个Dockerfile来形成新镜像。为什么要这样做呢?前面的方式太不自动化了,需要手工干预,而且还经常会忘了手工都做了什么。用Dockerfile可以很好的解决这个问题。
Dockerfile的一个简单的例子如下:
这其实是一个镜像的生产说明书,Docker build的过程就是根据这个生产说明书来生产镜像:
1. FROM基础镜像,先下载这个基础镜像,然后从这个镜像启动一个容器,并且登陆到容器里面;
2. RUN运行一个命令,在容器里面运行这个命令;
3. COPY/ADD将一些文件添加到容器里面;
4. 最终给容器设置启动命令 ENTRYPOINT,这个命令不在镜像生成过程中执行,而是在容器运行的时候作为主程序执行;
5. 将所有的修改commit成镜像。
这里需要说明一下的就是主程序,是Docker里面一个重要的概念,虽然镜像里面可以安装很多的程序,但是必须有一个主程序,主程序和容器的生命周期完全一致,主程序在则容器在,主程序亡则容器亡。
就像图中展示的一样,容器是一个资源限制的框,但是这个框没有底,全靠主进程撑着,主进程挂了,衣服架子倒了,衣服也就垮了。
了解了如何运行一个独立的容器,接下来介绍如何使用容器管理平台。
容器管理平台会对容器做更高的抽象,容器不再是单打独斗,而且组成集团军共同战斗。
多个容器组成一个Pod,这几个容器亲如兄弟,干的也是相关性很强的活,能够通过localhost访问彼此,真是兄弟齐心,力可断金。有的任务一帮兄弟还刚不住,就需要多个Pod合力完成,这个由ReplicationController进行控制,可以将一个Pod复制N个副本,同时承载任务,众人拾柴火焰高。
N个Pod如果对外散兵作战,一是无法合力,二是给人很乱的感觉,因而需要有一个老大,作为代言人,将大家团结起来,一致对外,这就是Service。老大对外提供统一的虚拟IP和端口,并将这个IP和服务名关联起来,访问服务名,则自动映射为虚拟IP。老大的意思就是,如果外面要访问我这个团队,喊一声名字就可以,例如”雷锋班,帮敬老院打扫卫生!”,你不用管雷锋班的那个人去打扫卫生,每个人打扫哪一部分,班长会统一分配。
最上层通过namespace分隔完全隔离的环境,例如生产环境,测试环境,开发环境等。就像军队分华北野战军,东北野战军一样。野战军立正,出发,部署一个Tomcat的Java应用。
有个blog的war,可以从这里下载
http://demo.nos-eastchina1.126.net/springblog.war
还需要一个jar,从这里下载
http://demo.nos-eastchina1.126 ... E.jar
登陆 网易蜂巢 ,点击对象存储,创建桶。上传springblog.war到这个桶里面。
对于spring,还需要spring-instrument-tomcat-4.2.2.RELEASE.jar放到这个桶里面。
由于这是基于Tomcat的,所以我们使用hub.c.163.com/library/tomcat:8.0-jre7作为基础镜像。这个镜像可以在镜像仓库里面搜到。
通过ADD命令,将对象存储中的war,jar放入容器镜像中,并且修改权限。注意URL地址不是本例子中的地址,而是您自己的地址,可以在对象存储界面上通过复制地址得到这个地址。
将catalina.sh run作为ENTRYPOINT。
在镜像仓库里面,创建镜像仓库,起个名字
点击镜像仓库,点击构建镜像
在本地选择写好的Dockerfile开始构建。
构建成功则显示如下:
在服务管理中,点击创建服务。
填写名称testtomcat,选择无状态
镜像选择前面构建的镜像。
选择默认的规格,端口映射为8080映射为8080。
点击立即创建。等待创建成功。
在负载均衡页面,点击创建负载均衡,取名testtomcat。
点击创建监听。
取名testtomcat,监听端口8080,映射为testtomcat服务的8080.
点击立即创建。
使用负载均衡的IP地址加上8080端口访问tomcat。
使用user/user登陆,创建一个博客