Docker是一种在Linux容器里运行应用的开源工具,一种轻量级的虚拟机。除了运行应用,Docker还提供了一些工具,借助Docker Index或自己托管的Docker注册表对进行了集装箱化处理的应用进行分发,从而简化复杂应用的部署过程。
我将在本文介绍如今在部署复杂系统时公司所面临的挑战,Docker怎样有效地解决这个问题,以及Docker的其他用例。
服务器应用的部署已经越来越复杂了。把几个Perl脚本拷贝到正确目录就完成服务器应用的安装,这种时代已经一去不复返了。如今的软件有很多类型的需求:
我们来看一个相对简单的应用的部署:Wordpress。Wordpress的安装通常要求:
在服务器上部署、运行这样一个系统,我们可能会遇到下面的问题和挑战:
那我们应该如何解决这些问题呢?
我们决定在单独的虚拟机上运行独立的应用,例如Amazon的EC2,大部分问题这时会迎刃而解:
完美!
不过……我们有个新问题:虚拟机在两个方面比较昂贵:
我们能做得更好吗?
进入Docker的世界吧。
Docker是由公共PaaS提供商dotCloud的人发起的开源项目,于去年初发起。从技术角度来说,Docker(主要用Go语言编写)试图简化两种已有技术的使用:
Docker可以安装在任何支持AUFS和内核版本大于等于3.8的Linux系统上。但从概念上来说它并不依赖于这些技术,以后也可以和类似的技术一起运行,例如Solaris的Zones或BSD jails,并将ZFS作为文件系统。不过目前只能选择Linux 3.8+和AUFS。
那Docker为什么有意思呢?
让我们回到前面的部署、操作问题列表,看看Docker是怎么解决的:
假设你已经安装了Docker。要在Ubuntu容器中运行bash,只要执行:
docker run -t -i ubuntu /bin/bash
根据“ubuntu”镜像的下载情况,Docker会选择下载或者使用本地可用的拷贝,然后在Ubuntu容器里运行/bin/bash。接着你就能在容器里执行几乎所有典型的Ubuntu操作,比如安装新的包。
我们来安装个“hello”:
$ docker run -t -i ubuntu /bin/bash root@78b96377e546:/# apt-get install hello Reading package lists... Done Building dependency tree... Done The following NEW packages will be installed: hello 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 26.1 kB of archives. After this operation, 102 kB of additional disk space will be used. Get:1 http://archive.ubuntu.com/ubuntu/ precise/main hello amd64 2.7-2 [26.1 kB] Fetched 26.1 kB in 0s (390 kB/s) debconf: delaying package configuration, since apt-utils is not installed Selecting previously unselected package hello. (Reading database ... 7545 files and directories currently installed.) Unpacking hello (from .../archives/hello_2.7-2_amd64.deb) ... Setting up hello (2.7-2) ... root@78b96377e546:/# hello Hello, world!
现在退出,然后再运行一次相同的Docker命令:
root@78b96377e546:/# exit exit $ docker run -t -i ubuntu /bin/bash root@e5e9cde16021:/# hello bash: hello: command not found
怎么了?我们美丽的hello命令哪儿去了?事实上我们刚刚根据干净的Ubuntu镜像启动了一个新的容器。要继续先前那个,我们必须把它提交到仓库中。我们退出这个容器,看看先前启动容器的ID是什么:
$ docker ps -a ID IMAGE COMMAND CREATED STATUS PORTS e5e9cde16021 ubuntu:12.04 /bin/bash About a minute ago Exit 127 78b96377e546 ubuntu:12.04 /bin/bash 2 minutes ago Exit 0
docker ps命令能列出当前运行的容器,docker ps -a还会显示已经退出的容器。每个容器都有一个唯一的ID,类似于Git提交哈希值。命令也列出了容器基于的镜像、运行的命令、创建时间、当前状态,以及容器暴露的端口和与主机端口之间的映射。
上面那个是我们第二次启动的容器,不包含“hello”;下面那个是我们想重用的,所以我们提交一下,再创建一个新的容器:
$ docker commit 78b96377e546 zefhemel/ubuntu
356e4d516681
$ docker run -t -i zefhemel/ubuntu /bin/bash
root@0d7898bbf8cd:/# hello
Hello, world!
我用容器ID把容器提交到了仓库中。仓库类似于Git仓库,包含一或多个打了标签的镜像。如果像我一样没有指定标签名称,标签会被命名为“latest”。运行docker images命令可以查看本地安装的所有镜像。
Docker提供了一些基础镜像(比如ubuntu和centos),你也可以创建自己的镜像。用户仓库的命名模型和Github的类似:Docker用户名后面跟一个斜线,然后再跟仓库名称。
前面创建Docker镜像的方式并不是特别正规,你可以试试。更简洁的方式是使用Dockerfile。
Dockerfile是个简单的文本文件,介绍了如何从基础镜像构建镜像。我在Github上提供了几个Dockerfile。下面的文件用来运行、安装SSH服务器:
FROM ubuntu RUN apt-get update RUN apt-get install -y openssh-server RUN mkdir /var/run/sshd RUN echo "root:root" | chpasswd EXPOSE 22
上面的内容一目了然。FROM命令定义了基础镜像,基础镜像可以是官方的,也可以是我们刚刚创建的zefhemel/ubuntu。RUN命令用来配置镜像。在这里,我们更新了APT包仓库,安装了openssh-server,创建了一个目录,然后给我们的root账户设置了一个再简单不过的密码。EXPOSE命令会向外暴露22端口(SSH端口)。接下来看看如何构建并实例化这个Dockerfile。
第一步是构建一个镜像。在包含Dockerfile的目录下运行:
$ docker bui ld -t zefhemel/ssh .
这会创建一个zefhemel/ssh仓库,包含我们新的SSH镜像。如果创建成功,就能进行实例化了:
$ docker run -d zefhemel/ssh /usr/sbin/sshd -D
和前面的命令不一样。-d表示会在后台运行容器,而不是运行bash,所以我们用前台模式(用-D参数指定)运行了sshd守护进程。
让我们检查运行中的容器,看看命令做了些什么:
$ docker ps ID IMAGE COMMAND CREATED STATUS PORTS 23ee5acf5c91 zefhemel/ssh:latest /usr/sbin/sshd -D 3 seconds ago Up 2 seconds 49154->22
可以看到我们的容器启动着。PORTS头下的内容比较有意思。由于我们EXPOSE了22端口,这个端口现在映射到了主机系统的一个端口(这里是49154)。让我们看看它能否运行。
$ ssh root@localhost -p 49154
The authenticity of host '[localhost]:49154 ([127.0.0.1]:49154)' can't be established.
ECDSA key fingerprint is f3:cc:c1:0b:e9:e4:49:f2:98:9a:af:3b:30:59:77:35.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[localhost]:49154' (ECDSA) to the list of known hosts.
root@localhost's password: <I typed in 'root' here>
Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.8.0-27-generic x86_64)
* Documentation: https://help.ubuntu.com/
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
root@23ee5acf5c91:~#
再次成功了!现在有了一个运行的SSH服务器,我们能登录它。在有人猜出密码并攻击容器之前,让我们先从SSH退出,杀掉容器。
$ docker kill 23ee5acf5c91
如你所见,容器的22端口映射到了49154端口,但这是完全随机的。要把它映射到特定端口,运行命令时传入-p参数:
docker run -p 2222:22 -d zefhemel/ssh /usr/sbin/sshd -D
现在,如果2222端口可用,我们的端口就会映射到2222上。我们在Dockerfile的结尾再添加一行内容,以便我们的镜像对用户更加友好:
CMD /usr/sbin/sshd -D
CMD表示构建镜像时并不会运行命令,实例化时才运行。所以不传递其它参数时就会执行/usr/sbin/sshd -D。然后我们可以直接运行:
docker run -p 2222:22 -d zefhemel/ssh
得到的结果和前面一样。要发布新创建的镜像,只要运行docker push就可以了:
docker push zefhemel/ssh
登录之后,镜像就可用了,用先前的docker run命令就能执行命令。
让我们回到Wordpress的例子。怎样在容器里用Docker运行Wordpress呢?要构建一个Wordpress镜像,我们要创建一个Dockerfile:
幸运的是,很多人已经成功了,比如John Fink的GitHub库就包括创建这样一个Wordpress镜像需要的所有内容。
除了用可靠、可重复的方式简化复杂应用的部署,Docker还有很多用途。下面是一些有趣的Docker用法和项目:
尽管Docker有助于系统的可靠部署,但它本身并不是个完全成熟的部署系统。它操作的是容器里运行的应用。哪个容器安装在哪个服务器上,以及如何启动它们,则超出了Docker的范围。
同样的,Docker也不处理跨多个容器(可能在多个物理服务器上,也可能在多个VM上)运行的应用。要让容器互相通信,需要某些发现机制,来找出哪些IP和端口上的其他应用可用。这和跨常规虚拟机的服务发现非常相似。etcd等工具,或者其他的服务发现机制都能用来解决这个问题。
虽然本文描述的所有内容用原始的LXC、cgroups和AUFS也可能实现,但实现起来绝对没有那么容易或简单。Docker提供了一种简单的方式将复杂应用打包到容器中,而且能轻松版本化、可靠分发。进而让轻量级的Linux容器和真正的虚拟机一样灵活、强大,但成本更低、方式更为便捷。即便Vagrant VirtualBox VM在Macbook Pro上,使用运行在其中的Docker创建的Docker镜像也能很好地运行在EC2、Rackspace Cloud或物理硬件上,反之亦然。
Docker可以从它的网站上获取,并免费使用。交互式的入门指南很不错。项目的路线图指出,第一个生产就绪的版本是2013年10月发布的0.8版本,不过此前大家已经在生产环境里使用Docker了。
Zef Hemel是LogicBlox公司的开发者布道师和产品管理团队成员,LogicBlox基于逻辑编程(尤其是Datalog)开发应用服务器和数据库引擎。此前他是Cloud9 IDE的工程副总裁,开发基于浏览器的IDE。Zef工作之初就涉足Web领域,从二十世纪九十年代起就在开发Web应用。他是声明式编程环境的拥趸。
查看英文原文:Docker: Using Linux Containers to Support Portable Application Deployment