速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

Docker:利用 Linux 容器实现可移植的应用部署

  • 2014-02-12
  • 本文字数:7489 字

    阅读完需:约 25 分钟

Docker 是一种在 Linux容器里运行应用的开源工具,一种轻量级的虚拟机。除了运行应用,Docker 还提供了一些工具,借助 Docker Index 或自己托管的 Docker 注册表对进行了集装箱化处理的应用进行分发,从而简化复杂应用的部署过程。

我将在本文介绍如今在部署复杂系统时公司所面临的挑战,Docker 怎样有效地解决这个问题,以及 Docker 的其他用例。

部署的挑战

服务器应用的部署已经越来越复杂了。把几个 Perl 脚本拷贝到正确目录就完成服务器应用的安装,这种时代已经一去不复返了。如今的软件有很多类型的需求:

  • 对已安装软件和库的依赖(“Python 版本高于 2.6.3,使用 Django 1.2”)
  • 依赖于正在运行的服务(“需要一个 MySQL 5.5 数据库和一个 RabbitMQ 队列”)
  • 依赖于特定的操作系统(“在 64 位的 Ubuntu Linux 12.04 上构建、测试”)
  • 资源需求:
    • 最小的可用内存(“需要 1GB 的可用内存”)
    • 能绑定特定的端口(“绑定 80 和 443 端口”)

我们来看一个相对简单的应用的部署: Wordpress 。Wordpress 的安装通常要求:

  • Apache 2
  • PHP 5
  • MySQL
  • Wordpress 源码
  • 一个 Wordpress MySQL 数据库,配置 Wordpress 使用该数据库
  • Apache 的配置:
    • 加载 PHP 模块
    • 支持 URL 重写和.htaccess 文件
    • 指向 WordPress 源码的 DocumentRoot

在服务器上部署、运行这样一个系统,我们可能会遇到下面的问题和挑战:

  1. 隔离性:如果我们已经在这个服务器上部署了不同的网站,已有的网站只能在 nginx 上运行,而 Wordpress 依赖于 Apache,这时我们就会有麻烦:它们都监听 80 端口。同时运行两个网站是可以的,但需要调整配置(修改监听端口),设置反向代理等。库级别也会出现类似的冲突,如果还要运行一个仍然依赖 PHP4 的老应用就会出问题,因为 Wordpress 不再支持 PHP4,同时运行 PHP4 和 PHP5 则非常困难。运行在同一个服务器上的应用没有互相隔离(在文件系统级别和网络级别),所以它们可能会互相冲突。
  2. 安全性:****Wordpress的安全记录并不是非常好。所以还是给它创建个沙箱,至少黑客入侵时不会影响其他运行的应用。
  3. 升级、降级:升级应用一般会覆盖现有文件。升级过程中会发生什么?系统要关闭么?如果升级失败,或者不对该怎么办?我们怎样快速回退到先前的版本?
  4. 快照、备份:一旦所有的内容都设置好,就给系统创建一个“快照”,以便能备份快照,甚至能移到另一个服务器上再次启动,或者拷贝到多个服务器上以备不时之需。
  5. 重复性:系统出新版本之后,比较好的做法是先在测试基础设施上自动部署并测试,然后再发布到生产系统。通常会利用诸如 Chef Puppet 等工具在服务器上自动安装一堆包,等一切内容都就绪后,再在生产系统上运行相同的部署脚本。这在百分之九十九的情况下都没有问题。但有百分之一的例外,在部署到测试环境和生产环境之间的时间跨度里,你依赖的包在包仓库里有了更新,而新版本并不兼容。结果生产环境的设置和测试环境不同,还有可能破坏生产系统。假如没有控制部署的每一个方面(例如托管自己的 APT 或 YUM 仓库),持续在多个阶段(比如测试、预演、生产环境)重复搭建出完全相同的系统就很困难。
  6. 资源限制:如果我们的 Wordpress 耗费 CPU 资源,并占用了所有的 CPU 周期,导致其他应用无法做任何事情怎么办?如果它用尽了全部可用的内存呢?或者疯狂写日志阻塞磁盘呢?要是能限制应用的可用资源,比如 CPU、内存和磁盘空间,就会非常方便。
  7. 易于安装:也许有 Debian 或 CentOS 包,抑或是能自动执行所有复杂步骤并安装 Wordpress 的 Chef 菜谱。但这些菜谱很难稳定下来,因为它们需要考虑目标系统上可能的系统配置。很多情况下,这些菜谱只能在干净的系统上运行。因此,你不太可能更换成自己的包或 Chef 菜谱。这样的话,安装就是个复杂的系统工程,而不是午休期间就能搞定的事情。
  8. 易于移除:软件应该能轻松、干净地移除,不留痕迹。但部署应用通常要调整已有的配置文件、设置状态(MySQL 数据库的数据,日志),完全移除应用也变得不那么容易。

那我们应该如何解决这些问题呢?

虚拟机!

我们决定在单独的虚拟机上运行独立的应用,例如 Amazon 的 EC2 ,大部分问题这时会迎刃而解:

  1. 隔离性:在一个 VM 上安装一个应用,应用是完全独立的,除非它们攻入了对方的防火墙。
  2. 重复性:用你喜欢的方式准备系统,然后创建一个 AMI。你可以随意实例化多个 AMI 实例。完全是可重现的。
  3. 安全性:由于我们完全隔离,如果 Wordpress 遭到攻击,其余的基础设施并不会受到影响——除非你没有保管好 SSH 密钥或者在哪里都使用同一个密码,但你应该不会这么做吧?
  4. 资源限制:VM 会分配特定的 CPU 周期、可用内存和磁盘空间,没有加价的话就不能超额。
  5. 易于安装:越来越多的应用能够在 EC2 上运行,只要在 AWS marketplace 上点击一个按钮就能实例化应用。启动只需要几分钟,就是这样。
  6. 易于移除:不需要某个应用了?销毁 VM。干净又方便。
  7. 升级、降级: Netflix 如何部署代码里提到,只需要在新 VM 上部署新版本,然后让负载均衡器指向部署了新版本的 VM。不过应用如果需要在本地保存状态,这种方法就不是很好用了。
  8. 快照、备份:点击一个按钮(或者调用一下 API)就能获得 EBS 磁盘的快照,快照会备份到 S3 中。

完美!

不过……我们有个新问题:虚拟机在两个方面比较昂贵

  • 金钱:你真的有那么多钱为每个应用启动一个 EC2 实例?另外你能预测到需要多少个实例么?如果你以后需要更多的资源,你需要停止 VM 进行升级——否则就要为闲置资源白白付钱,直到真正用起来(除非你用能动态调整大小的 Solaris Zones,比如 Joyent 上的)。
  • 时间:虚拟机相关的操作大多都很慢:启动要几分钟,捕捉快照要几分钟,创建镜像也需要几分钟。世界不停转动,我们可没有这种时间!

我们能做得更好吗?

进入 Docker 的世界吧。

Docker 是由公共 PaaS 提供商 dotCloud 的人发起的开源项目,于去年初发起。从技术角度来说,Docker(主要用 Go 语言编写)试图简化两种已有技术的使用:

  • LXC:Linux 容器,允许独立进程在比普通 Unix 进程更高的隔离级别上运行。使用的技术术语是集装箱化:一个容器里运行一个进程。容器支持的隔离级别有:
    • 文件系统:容器只能访问自己的沙箱文件系统(类似于 chroot ),否则要专门挂载到容器的文件系统中才能访问。
    • 用户名字空间:容器有自己的用户数据库(也就是容器的 root 不等于主机的 root 账户)。
    • 进程名字空间:只有容器里的进程才是可见的(ps aux 的输出会非常简洁)。
    • 网络名字空间:每个容器都有自己的虚拟网络设备和虚拟 IP(因此它可以绑定任意端口,不用占用主机端口)。
  • AUFS:高级多层的统一文件系统,可用来创建联合、写时拷贝的文件系统。

Docker 可以安装在任何支持 AUFS 和内核版本大于等于 3.8 的 Linux 系统上。但从概念上来说它并不依赖于这些技术,以后也可以和类似的技术一起运行,例如 Solaris 的 Zones BSD jails ,并将 ZFS 作为文件系统。不过目前只能选择 Linux 3.8+ 和 AUFS。

那 Docker 为什么有意思呢?

  • Docker 非常轻量。启动 VM 是个大动作,需要占用大量内存;而启动 Docker 容器只耗费很少的 CPU 和内存,并且非常快。几乎和启动一个常规进程没什么区别。不仅运行容器快,构建镜像、捕获文件系统的快照也很快。
  • 它运行在已经虚拟化过的环境中。也就是说,你可以在 EC2 实例、Rackspace VM 或 VirtualBox 里运行 Docker。事实上,在 Mac 和 Windows 上使用 Docker 的首选方式是使用 Vagrant
  • Docker 容器能移植到任何运行 Docker 的操作系统上。无论是 Ubuntu 还是 CentOS,只要 Docker 运行着,你的容器就能运行。

让我们回到前面的部署、操作问题列表,看看 Docker 是怎么解决的:

  1. 隔离性:Docker 在文件系统和网络级别隔离了应用。从这个意义上来讲很像在运行”真正的“虚拟机。
  2. 重复性:用你喜欢的方式准备系统(登录并在所有软件里执行 apt-get 命令,或者使用 Dockerfile ),然后把修改提交到镜像中。你可以随意实例化若干个实例,或者把镜像传输到另一台机器,完全重现同样的设置。
  • 安全性:Docker 容器比普通的进程隔离更为安全。Docker 团队已经确定了一些安全问题,正在着手解决。
  • 资源约束:Docker 现在能限制 CPU 的使用率和内存用量。目前还不能直接限制磁盘的使用情况。
  • 易于安装:Docker 有一个 Docker Index ,这个仓库存储了现成的 Docker 镜像,你用一条命令就可以完成实例化。比如说,要使用 Clojure REPL 镜像,只要运行 docker run -t -i zefhemel/clojure-repl 命令就能自动获取并运行该镜像。
  • 易于移除:不需要应用了?销毁容器就行。
  • 升级、降级:和 EC2 VM 一样:先启动应用的新版本,然后把负载均衡器切换到新的端口。
  • 快照、备份:Docker 能提交镜像并给镜像打标签,和 EC2 上的快照不同,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
<span>root@78b96377e546:/#</span> 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) ...
<span>root@78b96377e546:/#</span> hello
Hello, world!

现在退出,然后再运行一次相同的 Docker 命令:

复制代码
<span>root@78b96377e546:/#</span> exit
exit
$ docker run -t -i ubuntu /bin/bash
<span>root@e5e9cde16021:/#</span> 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
<span>root@0d7898bbf8cd:/#</span> hello
Hello, world!

我用容器 ID 把容器提交到了仓库中。仓库类似于 Git 仓库,包含一或多个打了标签的镜像。如果像我一样没有指定标签名称,标签会被命名为“latest”。运行 docker images 命令可以查看本地安装的所有镜像。

Docker 提供了一些基础镜像(比如 ubuntu 和 centos),你也可以创建自己的镜像。用户仓库的命名模型和 Github 的类似: Docker 用户名后面跟一个斜线,然后再跟仓库名称。

前面创建 Docker 镜像的方式并不是特别正规,你可以试试。更简洁的方式是使用 Dockerfile。

使用 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.
<span>root@23ee5acf5c91:~#</span>

再次成功了!现在有了一个运行的 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:

  1. 安装 Apache、PHP5 和 MySQL
  2. 下载 Wordpress,解压到文件系统的某个地方
  3. 创建一个 MySQL 数据库
  4. 更新 WordPress 的配置文件,指向 MySQL 数据库
  5. 把 WordPress 设置为 Apache 的 DocumentRoot
  6. 启动 MySQL 和 Apache(比如用 supervisord

幸运的是,很多人已经成功了,比如 John Fink 的 GitHub 库就包括创建这样一个 Wordpress 镜像需要的所有内容。

Docker 用例

除了用可靠、可重复的方式简化复杂应用的部署,Docker 还有很多用途。下面是一些有趣的 Docker 用法和项目:

  • 持续集成和部署:在 Docker 容器里构建软件,确保构建之间的隔离性。构建好的软件镜像可以自动推到私有的 Docker 仓库中,并部署到测试环境或生产环境。
  • Dokku :一个简单的 PaaS,用不到一百行的 Bash 构建而成。
  • Flynn Deis ,两个使用 Docker 的开源 PaaS 项目。
  • 在容器里运行桌面环境
  • CoreOS 验证了 Docker 的合理性,CoreOS 是个非常轻量级的 Linux 发行版,其中的应用都用 Docker 安装、运行,由 systemd 管理。

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

2014-02-12 09:2126961
用户头像

发布了 151 篇内容, 共 62.2 次阅读, 收获喜欢 18 次。

关注

评论

发布
暂无评论
发现更多内容

Vue3中常用的Composition(组合)API-ref(引用)函数

不觉心动

6 月 优质更文活动

万字详解常用设计模式

越长大越悲伤

设计模式

基于STM32的铁路自动围栏系统设计

DS小龙哥

6 月 优质更文活动

Backdata.net 搜索引擎

Larry

搜索引擎 导航网站

WMS 实物库存系统设计

红袖添香

系统设计 系统架构 供应链物流 WMS仓库管理 库存系统

SpringWeb服务应用响应式Web开发组件:响应式编程和SpringBoot

互联网架构师小马

2023-06-18:给定一个长度为N的一维数组scores, 代表0~N-1号员工的初始得分, scores[i] = a, 表示i号员工一开始得分是a, 给定一个长度为M的二维数组operatio

福大大架构师每日一题

golang 算法 rust 福大大架构师每日一题

飞书深诺多系统数据同步方案

飞书深诺技术团队

如何在 Linux 上列出磁盘?

wljslmz

6 月 优质更文活动

Vue3中常用的Composition(组合)API-初识setup

不觉心动

6 月 优质更文活动

C语言编程—语法练习

梦笔生花

C语言 语法 6 月 优质更文活动

做好演讲表达的道法术器(《如何激活你的表达思维》-- 培训收获)(68/100)

hackstoic

领导力 演讲 沟通

SpringWeb服务构建轻量级Web技术体系:SpringGraphQL

互联网架构师小马

在 Go 中使用 sqlx 替代 database/sql 操作数据库

江湖十年

数据库 后端 sql Go 语言

培育开源人才,助力开源生态发展|2023开放原子全球开源峰会校源行分论坛圆满落幕

开放原子开源基金会

开源 开放原子全球开源峰会 开放原子 校源行

数字化扶乩的最佳语言是英语?

FN0

AIGC

华为云CodeArts Build快速上手编译构建-进阶玩家体验

华为云PaaS服务小智

云计算 编译 开发 华为云

xenomai内核解析--实时linux概述

沐多

RTOS 实时linux xenomai

IT知识百科:什么是OTN——光传送网?

wljslmz

6 月 优质更文活动

前端如何处理「并发」问题?

不叫猫先生

并发 axios 6 月 优质更文活动

当 Rokid 遇上函数计算

阿里巴巴云原生

阿里云 云原生 Rokid

Kubernetes集群认证管理

穿过生命散发芬芳

6 月 优质更文活动

Vue3中常用的Composition(组合)API-ref(引用)函数

不觉心动

6 月 优质更文活动

微服务中「组件」集成

Java 架构

CSS小技巧之悬停3D发光效果

南城FE

CSS 前端 动画 3D 交互

MySQL DeadLock -- 二级索引导致的死锁

红袖添香

MySQL 数据库 数据库死锁

Docker:利用Linux容器实现可移植的应用部署_DevOps & 平台工程_Zef Hemel_InfoQ精选文章