YuMingzhe's blog

Writing and Teaching is my way of Learning

Docker 入门

Docker 是最近比较火的一种新型轻量级虚拟化技术,看了其官网的介绍后,其中的一些特性对我来说还是比较有吸引力的,所以在通读了入门教程后试用了一把,感觉还不错,所以写了这篇文章简单记录下。

首先,引用一下网上对 docker 的介绍——PaaS 供应商 dotCloud 开源了自有平台上的关键组件 Docker。Docker 是一种增加了高级 API 的 LinuX Container(LXC)技术,提供了能够独立运行 Unix 进程的轻量级虚拟化解决方案。它提供了一种在安全、可重复的环境中自动部署软件的方式”。从名字上就可以看出 dotCloud 是一家云计算公司,为了方便应用的部署,节省成本,开发出了 docker 这门技术。

下面说一下自己对 docker 的理解:docker 底层依靠的 Linux 原生 LXC 技术(lxc技术类似于浏览器中的沙盒,由内核直接负责其中应用的资源分配),只是在其上面又添加了一层 API,用户只需简单的调用某个 api 就能完成 lxc 的多个繁琐的步骤。docker 是一种轻量级的虚拟化技术,它不同于普通的虚拟机技术,通常虚拟机技术需要有一个 Hypervisor 也就是虚拟机监视器,它主要负责创建虚拟机、为虚拟机分配资源、回收资源等,而虚拟机运行的指令都要先转换为 hypervisor 的指令然后再由 hypervisor 调用操作系统 api 执行相应的功能,因此效率不高。而 docker 这是轻量级的,将文件系统和应用程序打包成一个镜像,运行时只载入文件系统,而其中的应用程序则单独运行,开多个“虚拟机”时,只运行其中的程序,多个程序间共享这个文件系统,每个“虚拟机”都作为一个进程运行,这样就好像一个本地程序一样,由操作系统负责资源的分配与释放,这样极大的降低了系统资源的消耗,并且启动、运行速度也得到了巨大的提高。下面就通过实际的操作来演示下docker的具体用法:

实验环境: CentOS 6.5, docker-io-0.8.1

需要注意的是CentOS需要安装EPEL源才能安装 docker,如果你使用的是其他 Linux 发行版,请直接到官网查找相应的安装方法,此处不再赘述。

第1步

和其他虚拟机类似,docker 也需要一个镜像文件才能启动虚拟机,所以首先我们要下载相应的镜像,例如你想跑个 Ubuntu,就要下载 Ubuntu 的镜像,想要跑 Fedora 系统就得下载 Fedora 的镜像,docker 官网为我们提供了诸多发行版的镜像,并且也有许多用户将他们自己制作的镜像上传到仓库中,通常这些镜像文件中都安装了不同用途的软件或运行环境,便于用户使用。当然,我们也可以制作自己的镜像并上传到官网上。我们可以访问http://index.docker.io 来查找需要的镜像,假如我们想要运行的环境是 CentOS,我们只需要搜索 ‘centos’ 即可列出所有符合条件的镜像,如下图所示:

从图中可以看出结果中有官方的镜像也有普通用户上传的镜像,用户自己上传的镜像的名字通常都以repository/image的格式显示,而官方的镜像则只有镜像名,所以上图中第一条结果是官方的镜像,第二条结果是用户的镜像。当然我们安装 docker 后也可以通过 docker 提供的命令来搜索镜像,如下图所示:

第2步 下载镜像。找到符合需求的镜像后我们就需要把它给下载下来,使用如下命令进行下载:

注意到图中最后一行包含了一串特殊的字符串:’539c0211cd76′,它代表了当前下载的这个镜像的唯一标识,并且以后我们对镜像所做的任何修改都会返回唯一的一个标识用于区分以前的镜像。

第3步 查看镜像。如果我们经常需要运行不同的 Linux 发行版,那么我们就会下载许多的镜像,这时候我们就需要查看到底机器中存在哪些镜像,有些过时或不用的镜像就可以进行删除以节省空间,所以使用下面的命令来查看、删除系统中存在的镜像:

从图中可以看出,首先使用docker images命令列出系统中所有的镜像,图中显示有 2 个不同的镜像(id 分别为 4535…和 539c02…),然后通过docker rmi image_id命令来删除镜像文件,会返回镜像的 id,最后再列一遍镜像,发现文件确实删除了。

需要注意的是,有时我们在移除镜像的时候通常会发生如下所示的错误:

从图中可以看出,当我们要移除 Image ID 为’d472a2307d5b’的镜像时,会报错’Error: image_delete: Conflict, d472a…. wasn’t delete’。这是什么原因呢?使用docker images –tree命令会列出每个镜像的依赖关系,从图中可以看出 ID 为1c0dc1cf…的镜像依赖于 ID 为d472a…的镜像,前面我们说过 Docker 是轻量级的,我们对镜像所做的每个修改都会以增量的形式保存起来,这里的增量就是一个个新的镜像,所以如果直接删除父镜像,那么无法保证子镜像能正常的跑起来,因此这种情况下只有先删除子镜像才行。

第4步 运行虚拟机。下载完镜像后我们就可以通过 Docker 来跑虚拟机了。这里用到命令是docker run,可以通过使用docker run -h来列出命令的详细使用方法,下面我们先简单的运行一下,如下图所示:

从图中可以看出,docker run命令后接 IMAGE ID 和命令参数。我们这个例子中就运行了 ID 为’1c0dc1cf8fc9’的镜像,镜像启动后执行命令echo “hello world。执行完后我们可以看到返回了’hello world’ 字符串。注意的是当命令运行完成后我们的“虚拟机”就停止退出了,此时的虚拟机称为“容器”,但容器的状态仍然被保存起来以备再启动,我们可以通过docker ps -a命令来查看所有的容器,如图所示:

从图中可以看出容器的状态:ID 为1db2…e30,使用的镜像是 CentOS,容器运行的命令是echo hello world等等。如果我们想重新运行下这个容器,则需运行命令docker start -i containerID即可,这里的 -i 选项指的是 interactive,把容器运行的结果返回到当前的 tty 中显示,此处不再演示。每调用一次docker run命令都会生成唯一ID的容器,所以一个镜像可以运行出多个容器,当容器运行完,对我们没有用时,就需要删除容器以节省空间,调用docker rm containerID来删除容器,此处不再赘述。还需要注意的是,如果想要删除某个镜像,而依赖此镜像的容器没有先删除的话也会出现错误。

有人可能会疑问,难道 Docker 的功能仅限于运行单条命令吗?命令一运行完容器就停止了,无法持久化运行程序吗?答案当然是肯定的,Docker 和别的虚拟机一样,别人能做的事 Docker 也能完成。下面就来看一下如何持久化运行某个程序,这对于像 web、email 等网络服务来说是最非常有用的。

从图中可以看出我们在运行ID为1c0d…8fc9的镜像时,使用了’-t -i’ 参数,分别用于分配 tty,将容器的 shell 与当前的 tty 相关联和进入交互模式,这样容器启动后我们就能获得容器的 shell 了(如图中我们看到容器的 shell 提示符’bash-4.1#’),然后我们就可以在容器的 shell 中运行各种程序,最后只需按 Ctrl+p, Ctrl+q 就可切换到宿主机上,此时运行docker ps可以看到容器一直在运行中。

好了,上面讲的是docker的一些基础的操作,还有一些细枝末节的知识,大家只要阅读官网的教程都很简单,比如启动、停止、重启容器用docker start/stop/restart命令等等。

下面讲一下 Docker 最吸引人的地方就是它可以将应用程序打包成镜像,分发到网络上,然后可以随时随地的获取运行。Docker 简化应用程序的部署,会极大提高效率,可以说的上是开箱即用。试想我们将来在服务器上将要部署 java、php、django 应用,这些应用的打包方式和运行环境都各不一样,系统管理员在部署时要耗费许多精力来配置其运行环境,再加上调试,几乎一天就不用干其他的事了,而 Docker 的出现正好解决了这个问题,我们只需把不同的应用分别封装到镜像里,而镜像的环境可以由第三方来配置好,我们只需下载下来简单的配置下环境就可通过 Docker 在服务器上跑起来,并且容器的状态可以随时被保存,对于系统备份和迁移也是及其方便的,所以 Docker这种轻量级虚拟技术将极大地简化部署、维护工作。下面我们就以配置一个 jdk7+tomcat7 的 Java web 运行环境为例,来演示下如何将应用打包,配置好容器的运行环境。

首先我们需要熟悉一下 Dockfile。什么是 Dockfile?在前面的例子中,我们从下载镜像,启动容器,在容器中输入命令来运行程序,这些命令都是手工一条条往里输入的,无法重复利用,而且效率很低。所以就需要一种文件或脚本,我们把想执行的操作以命令的方式写入其中,然后让 Docker 读取并分析、执行,那么重复构建、更新将变得很方便,所以 Dockfile 就此诞生了。

Docker 官网的教程对 Dockfile 的讲解很详细也很简单,这里只简单的介绍下几个常用的命令:

  • FROM 命令。用法:FROM :。FROM 命令告诉 Docker 我们构建的镜像是以哪个(发行版)镜像为基础的?,例如 FROM centos ,Docker 就会先 pull 下官方的 CentOS 镜像,然后在其上运行后续命令。注意,FROM 命令必须是 Dockfile 第一个非注释的命令。

  • RUN 命令。用法:RUN 。RUN 后面接要执行的命令,比如,我们想在镜像中安装 vim,只需在 Dockfile 中写入 RUN yum install -y vim,这样 Docker 在执行这条命令的时候就会在容器中调用 yum 命令从而安装相应的包。

  • ENV命令。用法:ENV 。ENV 命令主要用于设置容器运行时的环境变量,比如,我们将 JDK 安装到了 /home/user/jdk1.7 下,要在 Dockfile 中写入 ENV JAVA_HOME /home/user/jdk1.7,这样容器启动后就能运行 Java 应用了,当然还要把 JAVA_HOME 变量添加到 PATH 环境变量中才行,这在后边的例子中有演示。

  • ADD命令。用法:ADD 。ADD 主要用于将宿主机中的文件添加到镜像中,例如我们将下载好的 JDK 和 Tomcat 放到某一目录中,在 Dockfile 中写入 ADD /some/path/jdk /usr/loacl/jdk,这样 JDK 就被复制到镜像中 /usr/local/jdk 目录中了。

了解完这几个基本的命令后,我们就可以开始构建一套我们自己的 JDK+Tomcat 的运行环境了。例子比较简单,只给出 Dockfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pull down centos image
FROM centos
MAINTAINER myu myu@live.cn

#copy jdk and tomcat into image
ADD ./apache-tomcat-7.0.52.tar.gz /root
ADD ./jdk-7u51-linux-x64.tar.gz /root

#set environment variable
ENV JAVA_HOME /root/jdk1.7.0_51
ENV PATH $JAVA_HOME/bin:$PATH

#define entry point which will be run first when the container starts up
ENTRYPOINT /root/apache-tomcat-7.0.52/bin/startup.sh && tail -F /root/apacher-tomcat-7.0.52/logs/catalina.out

运行结果如下:

-t 选择指定生成镜像的仓库名和 tag

–rm=true 指定在生成镜像过程中删除中间产生的临时容器。

然后我们就可以运行新生成的容器了,如下图所示:

-d 指定容器运行后与当前 tty 分离,后台运行

-p 指定主机 80 端口与容器 8080 端口进行绑定

此时我们只要访问本地的80端口即可看到熟悉的 tomcat 的页面了。

因此按照这个思路,只要把程序一个个地添加到容器中,最后生成一个镜像就可以制作成一个隔绝依赖、开箱即用的应用了。