Docker 完全指南

Docker 作为新瓶装旧酒的一门技术,用简单便捷的操作极大改变了软件开发的流程与生态环境,本文我们就来了解一下。注:Docker 目前已改名为 Moby。


更新历史

  • 2017.05.01: 完成初稿

快速入门

  • Docker 最初 dotCloud 公司内部的一个业余项目
  • Docker 基于 Go 语言
  • Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案
  • Docker 的基础是 Linux 容器(LXC)等技术
  • Docker 容器的启动可以在秒级实现,这相比传统的虚拟机方式要快得多
  • Docker 对系统资源的利用率很高,一台主机上可以同时运行数千个 Docker 容器

下面的图片比较了 Docker 和传统虚拟化方式的不同之处,可见容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统方式则是在硬件层面实现。

容器除了运行其中应用外,基本不消耗额外的系统资源,使得应用的性能很高,同时系统的开销尽量小。传统虚拟机方式运行 10 个不同的应用就要起 10 个虚拟机,而Docker 只需要启动 10 个隔离的应用即可。

主要优势为:

  • 更快速的交付和部署 - 容器成为了最小单位
  • 更高效的虚拟化 - 内核级虚拟化
  • 更轻松的迁移和拓展
  • 更简单的管理

安装

官方网站提供了 Mac, LinuxWindows 版本的安装教程。我们只要跟着官方文档即可,这里不再赘述。

不过需要提一下 Kitematic 这个图形化工具(官方给出的定义是 Visual Docker Container Management on Mac & Windows),对于熟悉和了解 Docker 是很好的帮助,大家可以体验一下。

守护进程

运行 Docker 守护进程时,可以用 -H 来改变绑定接口的方式,比如 sudo /usr/bin/docker -d -H tcp://0.0.0.0:2375,如果不想每次都输入这么长的命令,需要加入以下环境变量 export DOCKER_HOST="tcp://0.0.0.0:2375"

图形用户界面

虽然我们可以用命令来控制 docker,但是如果能有一个 web 管理界面,操作什么的会方便很多,比较常见的有

基本概念

基本概念主要有三个:

  • 镜像(Image)
    • 一个只读的模板,镜像可以用来创建 Docker 容器
    • 用户基于镜像来运行自己的容器。镜像是基于 Union 文件系统的层式结构
    • 可以简单创建或更新现有镜像,或者直接下载使用其他人的。可以理解为生成容器的『源代码』
  • 容器(Container)
    • 容器是从镜像创建的运行实例,在启动的时候创建一层可写层作为最上层(因为镜像是只读的)
    • 可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台
    • 可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序
  • 仓库(Registry)
    • 集中存放镜像文件的场所,可以是公有的,也可以是私有的
    • 最大的公开仓库是 Docker Hub
    • 国内的公开仓库包括 Docker Pool
    • 当用户创建了自己的镜像之后就可以使用 push 命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上 pull 下来就可以了
    • Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务

另外 Docker 采用的是客户端/服务器架构,客户端只需要向 Docker 服务器或守护进程发出请求即可完成各类操作。那么问题来了,我们能用 Docker 来做什么呢?我们可以:

  • 统一、优化和加速本地开发和构建流程
  • 保证不同的环境中可以得到相同的运行结果
  • 创建隔离环境用于测试

Docker 可以提供的隔离有:

  • 文件系统隔离:每个容器都有自己的 root 文件系统
  • 进程隔离:每个容器都运行在自己的进程环境中
  • 网络隔离:容器间的虚拟网络接口和 IP 地址都是分开的
  • 资源隔离和分组:使用 cgroups 将 CPU 和内存之类的资源独立分配给每个 Docker 容器

常用命令

  • 查看 docker 状态 sudo docker info
  • 查看系统中正在运行的容器的列表 docker ps
    • 加上 -a 可以列出所有容器
    • 加上 -l 可以列出最后一次运行的容器

一个简单的例子

接下来我们用一个简单的例子来体验下 docker

容器小介绍

容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。

启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动。因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器(对于初级应用来说后者更方便)。

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

可以利用 docker start 命令,直接将一个已经终止的容器启动运行。

运行容器

现在,我们来创建一个 ubuntu:14.04 的容器 docker run ubuntu:14.04 /bin/echo 'Hello wdx!'(结果如下图所示)

可以看到正确输出了我们的 “Hello wdx!”

接下来,我们用 docker run -t -i ubuntu:14.04 /bin/bash 可以启动一个 bash 终端用来交互。参数的意思是:

  • -t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
  • -i 则让容器的标准输入保持打开

我们可以输入一些命令来测试

容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。我们用 pstop 在伪终端中查看进程信息,可以看到只有我们运行的进程,没有其他花里胡哨的(上图最后一条命令)

试一试如下命令

  • cat /etc/hosts
  • ip a
  • ps -aux
  • cd ~ && echo "hello wdx" > hello.txt && cat hello.txt

(细心的同学可能会发现这里的输出暗藏玄机)

操作完成后,输入 exit 便可以退出这个 ubuntu 容器。退出之后这个容器依然存在,我们可以用 docker ps -l 来看看:

每个容器有一个 Container ID 和 Name,我们一般就是通过这俩来定位一个容器的。


镜像

我们可以使用 docker pull 命令从仓库中获取所需要的镜像。比如说 sudo docker pull ubuntu:12.04,相当于 sudo docker pull registry.hub.docker.com/ubuntu:12.04,即从注册服务器 registry.hub.docker.com 中的 ubuntu 仓库来下载标记为12.04 的镜像。

如果想从其他仓库注册服务器下载,需要输入完成的地址,例如:sudo docker pull dl.dockerpool.com:5000/ubuntu:12.04

下载完成之后就可以使用该镜像了,比如下面的语句就会创建容器,其中运行 bashsudo docker run -t -i ubuntu:12.04 /bin/bash

可以使用 dokcer images 来显示本地已有的镜像,如下

docker images

具体字段的意思一目了然,这里不再赘述。然后我们来运行官方例子 whalesay 镜像。

运行镜像

打开浏览器,进入 Docker Hub

Docker Hub

搜索 whalesay 这个镜像,就可以看到结果,点进去可以看到详细内容(基于 Ubuntu)

whalesay image

然后我们来运行一下,使用命令 docker run docker/whalesay cowsay boo,其中 cowsay 是要运行的命令,后面的 boo 是参数。

Docker 会先在本地查找有没有镜像,如果没有就从仓库中下载,具体的运行结果是:

cowsay boo

也可以让鲸鱼说不同的话,比如:

制作镜像

如果想要制作自己的镜像,需要自己写 Dockerfile。具体步骤如下

  1. 创建一个文件夹 mkdir wdxtub; cd wdxtub,构造镜像所需的所有东西都会放在这个文件夹中
  2. 创建一个名为 Dockerfile 的文件 gedit Dockerfile
  3. 添加第一句话 FROM docker/whalesay:latest,表示我们的镜像以 whalesay 为基础
  4. 添加需要运行的命令,如 RUN apt-get -y update && apt-get install -y fortunesfortunes 这个程序会输出名言警句)
  5. 通过 CMD 指定镜像载入之后需要执行的命令,如 CMD /usr/games/fortune -a | cowsay
  6. 保存并关闭 Dockerfile
  7. 使用 sudo docker build -t wdx-whale . 来构造镜像,简单来说就是用 Dockerfile 中的内容按步骤构造
  8. 使用 docker images 应该就可以看到我们新创建的镜像

wdx-whale image

然后我们就可以运行一下看看 docker run wdx-whale

搞笑句

还有更贱的(感觉可以玩一天)

如果想要把自己的镜像上传到网上,就需要注册一个 Docker Hub 帐号,然后点击 Create Repository,这里我创建了一个名为 wdxtub/demo 的公用仓库。

接下来我们需要打上 tag,目前 docker images 的情况是:

wdx-whale image

记住我们的 IMAGE ID 26ac9649d7da。用以下命令打 tag docker tag 26ac9649d7da wdxtub/wdx-whale:latest,然后再 docker images 一次:

然后用这个命令登录 docker login --username=yourhubusername --email=youremail@company.com,对于我来说就是 docker login --username=wdxtub --email=dacrocodilee@gmail.com

成功之后大概是这样:

登录成功

然后就可以 push 上去了 docker push wdxtub/wdx-whale,像下面这样

push

为了测试 pull 自己的镜像,我们先把本地上的 whale 镜像删掉:docker rmi -f wdxtub/wdx-whale; docker rmi -f wdx-whale(如果有其他的用不着的也都删掉),最后剩下(上课要用的镜像):

接着来运行一下 docker run wdxtub/wdx-whale

管理镜像

我们可以把镜像导出到本地文件,使用 docker save 命令即可,比如针对我现在有的镜像 wdxtub/wdx-whale(id:26ac9649d7da),可以这样:docker save -o wdx-local-whale.tar wdxtub/wdx-whale。如果要载入的话,使用下面的命令即可(会载入相关的元数据信息)

docker load --input wdx-local-whale.tar
# 或者
docker load < wdx-local-whale.tar

在删除镜像之前要先用 docker rm 删掉依赖于这个镜像的所有容器.

sudo docker rmi $(docker images -q -f "dangling=true")

镜像的实现原理

Docker 镜像是怎么实现增量的修改和维护的? 每个镜像都由很多层次构成,Docker 使用 Union FS 将这些不同的层结合到一个镜像中去。

通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。 Docker 在 AUFS 上构建的容器也是利用了类似的原理。

容器

启动

举个例子,

后台运行

更多的时候,需要让 Docker在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。

下面举两个例子来说明一下。

如果不使用 -d 参数运行容器 docker run ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" 容器会把输出的结果(STDOUT)打印到宿主机上面

如果使用了 -d 参数运行容器 docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done",则显示是这样:

使用 docker logs containerid 可以查看输出,如:

使用 -d 参数启动后会返回一个唯一的 id,也可以通过 docker ps 命令来查看容器信息。容器是否会长久运行,是和docker run指定的命令有关,和 -d 参数无关

在使用 -d 参数时,容器启动后会进入后台。 某些时候需要进入容器进行操作,有很多种方法,包括使用 docker attach 命令或 nsenter 工具等。具体参考这里

终止与重新启动

使用 docker stop containerid 来终止容器。终止状态的容器可以用 docker ps -a 命令看到。

另外,docker restart containerid 命令会将一个运行态的容器终止,然后再重新启动它。

导入导出与删除

如果要导出本地某个容器,可以使用 docker export containerid 命令。

可以使用 docker import 从容器快照文件中再导入为镜像,例如

cat ubuntu.tar | sudo docker import - test/ubuntu:v1.0
# 也可以通过指定 URL 或者某个目录来导入,例如
docker import http://example.com/exampleimage.tgz example/imagerepo

用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

可以使用 docker rm 来删除一个处于终止状态的容器。如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。

docker ps -a 命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用 docker rm $(docker ps -a -q) 可以全部清理掉。

注意:这个命令其实会试图删除所有的包括还在运行中的容器,不过就像上面提过的docker rm 默认并不会删除运行中的容器。

仓库

仓库(Repository)是集中存放镜像的地方。

一个容易混淆的概念是注册服务器(Registry)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址dl.dockerpool.com/ubuntu 来说,dl.dockerpool.com 是注册服务器地址,ubuntu是仓库名。

大部分时候,并不需要严格区分这两者的概念。相信信息可以直接看教程,这里不赘述了。

参考链接

捧个钱场?