第 1 章 Docker 入门

第 1 章 Docker 入门

1.0 简介

入门 Docker 很简单。Docker 的核心是一个被称作 Docker 引擎的基于单主机运行的守护进程,我们可以通过这个守护进程来创建和管理容器。在深入使用 Docker 之前,你需要先在一台主机(比如台式机、笔记本电脑或者服务器)上安装 Docker 引擎。

本章中的前几个范例将会介绍在服务器上运行 Docker 所需的安装步骤。官方文档差不多涵盖了所有操作系统,这里我们会对 Ubuntu 14.04(参见范例 1.1)、CentOS 6.5(参见范例 1.2)和 CentOS 7(参见范例 1.3)进行介绍。如果你想使用 Vagrant,可以参见范例 1.4。

我们也会以树莓派为例来介绍如何在 ARM CPU 上安装 Docker(参见范例 1.5)。如果是 Windows 或者 OS X 系统的主机,你可以使用 Docker Toolbox(参见范例 1.6)。Docker Toolbox 除了包括 Docker 引擎之外,还包括其他一些实用工具。Docker Toolbox 使用 VirtualBox 将一个虚拟机作为 Docker 主机运行,这个虚拟机也就是 Boot2Docker。现在已经不再推荐使用 Boot2Docker 了,不过我们还是会在范例 1.7 中介绍一下如何使用 Boot2Docker 安装 Docker。

在这些安装范例之后,我们会介绍一下 docker-machine。这是一个 Docker 工具,可以通过它在公有云上创建云主机并安装 Docker,自动配置本地 Docker 客户端来使用远程 Docker 主机。范例 1.9 中会介绍如何在 DigitalOcean 云中使用 docker-machine

一旦在自己的环境中安装好了 Docker,就可以浏览一下创建和管理容器所需的基本命令。范例 1.11 将会展示运行容器的第一步,范例 1.13 将会带你了解一个容器的标准生命周期:创建、启动、停止、终止和移除。

介绍完这些基本概念之后,我们将会直接深入到 Dockerfile(参见范例 1.14)。Dockerfile 是一个用于描述如何构建容器镜像的文件。它是 Docker 中的一个核心概念,第 2 章会详细展开讨论,这里只介绍其最简单的用法。我们通过 Dockerfile 来学习一个更为复杂的例子,即运行 WordPress 服务。

首先,我们会从头开始构建一个 Docker 镜像,在单一容器中运行多个进程(参见范例 1.15)。Docker 会改变我们的应用程序设计思想,不再将所有一切打包在一起,而是创建多个独立的服务,然后将这些独立的服务连接起来。然而,这并不意味着一个容器中不能运行多个服务。使用 supervisord 可以在一个容器中运行多个服务,范例 1.15 将会讲述如何去做。但是 Docker 的强大之处在于通过组合服务来运行应用程序。因此,在范例 1.16 中,我们会介绍如何将单一容器示例拆分为两个容器,并使用容器链接进行互连。这将是你的第一个分布式应用程序,尽管它也只是运行在同一台主机之上。

我们在本章介绍的最后一个概念是数据管理。在容器中访问数据是一个关键组件。你可以通过它来加载配置变量或数据集,或在容器之间共享数据。我们将再次使用 WordPress 的例子,告诉你如何备份数据库(参见范例 1.17),如何将宿主机的数据挂载到容器中(参见范例 1.18),以及如何创建数据容器(参见范例 1.19)。

总之,在本章中,你将会快速学到如何在一台主机上安装 Docker 引擎,并运行一个由两个容器组成的 WordPress 网站。

1.1 在Ubuntu 14.04上安装Docker

1.1.1 问题

你想在 Ubuntu 14.04 上运行 Docker。

1.1.2 解决方案

在 Ubuntu 14.04 上,可以通过至多三条 bash 命令来安装 Docker。Docker 项目推荐的安装方式是从网上下载并运行一个 bash 脚本。需要注意的是,在 Ubuntu 的软件包仓库中已经有一个 docker 软件包,不过这个软件与 Docker(http://www.docker.com)并没有任何关系。执行推荐的安装,如下所示。

$ sudo apt-get update
$ sudo apt-get install -y wget
$ sudo wget -qO- https://get.docker.com/ | sh

可以通过查看 Docker 软件的版本来确认是否已正确安装了 Docker,如下所示。

$ sudo docker --version
Docker version 1.7.1, build 786b29d

可以停止、启动和重启 Docker 服务。比如,可以像下面这样重启 Docker 服务。

$ sudo service docker restart

如果你想直接以一个非 root 用户的身份来运行 docker 命令,可以将该用户添加到 docker 用户组,如下所示。

$ sudo gpasswd -a <user> docker

退出当前 shell 然后重新登录,或者重新启动一个新 shell,就能使用上面的配置了。

1.1.3 讨论

你可以按照 https://get.docker.com 的安装脚本,一步一步地手动安装或者进行自定义安装。在 Ubuntu 14.04(代号 trusty)上,最精简的安装步骤如下所示。

$ sudo apt-get update
$ sudo apt-get install -y linux-image-extra-$(uname -r) linux-image-extra-virtual
$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80
                   --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
$ sudo su
# echo deb https://apt.dockerproject.org/repo ubuntu-trusty main > \
/etc/apt/sources.list.d/docker.list
# apt-get -y install docker-engine

1.1.4 参考

1.2 在CentOS 6.5上安装Docker

1.2.1 问题

你想在 CentOS 6.5 上安装 Docker。

1.2.2 解决方案

在 CentOS 6.5 上,可以通过添加 EPEL(Extra Packages for Enterprise Linux)仓库使用 docker-io 包安装 Docker,如下所示。

$ sudo yum -y update
$ sudo yum -y install epel-release
$ sudo yum -y install docker-io
$ sudo service docker start
$ sudo chkconfig docker on

在 CentOS 6.5 上,Docker 的版本应该是 1.6.2,如下所示。

# docker --version
Docker version 1.6.2, build 7c8fca2/1.6.2

1.2.3 讨论

Docker 将不会继续提供对 CentOS 6.x 的支持。如果你想使用最新版 Docker,那么应该选择 CentOS 7(参见范例 1.3)。

1.3 在CentOS 7上安装Docker

1.3.1 问题

你想在 CentOS 7 上使用 Docker。

1.3.2 解决方案

通过 yum 管理器安装 Docker 软件包。CentOS 采用了 systemd,因此你需要使用 systemctl 命令来管理 docker 服务,如下所示。

$ sudo yum update
$ sudo yum -y install docker
$ sudo systemctl start docker

也可以使用 Docker 官方的安装脚本来安装,这也会使用 Docker 仓库中的软件包,如下所示。

$ sudo yum update
$ sudo curl -sSL https://get.docker.com/ | sh

1.4 使用Vagrant创建本地Docker主机

1.4.1 问题

你的本地计算机的操作系统和你想要运行 Docker 的操作系统不同。比如,你现在运行的是 OS X,但是你想在 Ubuntu 上运行 Docker。

1.4.2 解决方案

在本地通过 Vagrant(http://vagrantup.com)启动一个虚拟机(virtual machine,VM),并使用 Vagrantfile 中的 shell 配置程序初始化 VM。

如果已经安装了 VirtualBox(http://virtualbox.org)和 Vagrant(http://vagrantup.com),那么你只需要创建一个名为 Vagrantfile 的文本文件,其内容如下所示。

VAGRANTFILE_API_VERSION = "2"

$bootstrap=<<SCRIPT
apt-get update
apt-get -y install wget
wget -qO- https://get.docker.com/ | sh
gpasswd -a vagrant docker
service docker restart
SCRIPT

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "ubuntu/trusty64"

  config.vm.network "private_network", ip: "192.168.33.10"

  config.vm.provider "virtualbox" do |vb|
     vb.customize ["modifyvm", :id, "--memory", "1024"]
  end

  config.vm.provision :shell, inline: $bootstrap

end

之后就可以启动虚拟机了。Vagrant 将会从 Vagrant cloud[http://vagrantcloud.com,现在是 Atlas(https://atlas.hashicorp.com)的一部分]下载 ubuntu/trusty64 镜像(box),通过 VirtualBox 创建该镜像的一个实例,再运行在 Vagrantfile 中定义的初始化脚本。这个虚拟机实例将会有 1GB 的内存和两个网络接口:一个用于与外部网络进行通信的网络地址转换(Network Address Translation,NAT)接口,一个本地网络接口 192.168.33.10。虚拟机启动后,就可以通过 ssh 来登录到虚拟机并使用 Docker。

$ vagrant up
$ vagrant ssh
vagrant@vagrant-ubuntu-trusty-64:~$ docker ps
CONTAINER ID    IMAGE     COMMAND     CREATED     STATUS     PORTS     NAMES

 在上面的 Vagrant 配置中,vagrant 用户被添加到了 Docker 用户组。这样一来,即使你不是 root 用户,也能运行 Docker 命令。你可以在 how2doc 仓库(https://github.com/how2dock/docbook.git)的 ch01 文件夹中找到上面的脚本。

1.4.3 讨论

如果从未使用过 Vagrant,你需要先安装它。Vagrant 官方下载页面(https://www.vagrantup.com/downloads)上列出了主流的安装包类型。比如对于基于 Debian 的系统,可以选择下载 .deb 包,并像下面这样安装。

$ wget https://dl.bintray.com/mitchellh/vagrant/vagrant_1.7.4_x86_64.deb
$ sudo dpkg -i vagrant_1.7.4_x86_64.deb
$ sudo vagrant --version
Vagrant 1.7.4

1.5 在树莓派上安装Docker

1.5.1 问题

假定你在公司大量使用树莓派(https://www.raspberrypi.org),或者作为个人兴趣在业余时间喜欢自己钻研一下。不管是哪种情况,你都想知道如何在自己的树莓派上安装 Docker。

1.5.2 解决方案

你可以从 Hypriot(http://blog.hypriot.com)下载预配置好的 SD 卡镜像。你需要按照下面的步骤来操作。

(1) 下载 SD 卡镜像(http://blog.hypriot.com/downloads)。

(2) 将镜像写入 SD 卡。

(3) 将 SD 卡插入树莓派并启动。

(4) 登录到树莓派,开始使用 Docker。

1.5.3 讨论

比如在一台 OS X 主机上,你可以按照 Hypriot 的指南(http://blog.hypriot.com/getting-started-with-docker-and-mac-on-the-raspberry-pi/)来操作。

下载并解压 SD 镜像,如下所示。

$ curl -sOL http://downloads.hypriot.com/hypriot-rpi-20150416-201537.img.zip
$ unzip hypriot-rpi-20150416-201537.img.zip

然后将 SD 卡插入到主机上的读卡器中,查看所有可用磁盘,找到哪个是你所使用的 SD 卡。之后你需要卸载这个磁盘,并使用 dd 命令将镜像复制到这张 SD 卡上。这里我们假定 SD 卡设备名为 disk1

$ diskutil list
...
$ diskutil unmountdisk /dev/disk1
$ sudo dd if=hypriot-rpi-20150416-201537.img of=/dev/rdisk1 bs=1m
$ diskutil unmountdisk /dev/disk1

将镜像复制到 SD 卡之后,对 SD 卡执行弹出操作,将 SD 卡从读卡器中移除,然后插入到树莓派中。你需要知道树莓派的 IP 地址,这样就可以通过 ssh 使用密码 hypriot 登录到树莓派了,如下所示。

$ ssh root@<IP_OF_RPI>
...
HypriotOS: root@black-pearl in ~
$ docker ps
CONTAINER ID     IMAGE     COMMAND     CREATED     STATUS     PORTS     NAMES
HypriotOS: root@black-pearl in ~
$ uname -a
Linux black-pearl 3.18.11-hypriotos-v7+ #2 SMP PREEMPT Sun Apr 12 16:34:20 UTC \
2015 armv7l GNU/Linux
HypriotOS: root@black-pearl in ~

你已经拥有了一个可以在 ARM 系统上运行的 Docker 了。

因为容器会使用 Docker 主机的内核,所以你需要拉取为基于 ARM 的架构准备的镜像。访问 Docker Hub 可以查找为基于 ARM 的系统准备的镜像,选择一个 Hyperiot 提供的镜像是不错的开始。

1.5.4 参考

1.6 在OS X上通过Docker Toolbox安装Docker

1.6.1 问题

Docker 守护进程并不支持 OS X 系统,但是你想在 OS X 上使用 Docker。

1.6.2 解决方案

可以使用 Docker Toolbox(https://https://www.docker.com/toolbox),这是一个包含了 Docker 客户端、Docker Machine、Docker Compose、Docker Kitematic 和 VirtualBox 的安装包。Docker Toolbox 允许你在 VirtualBox 中启动一个运行着 Docker 守护进程的微型虚拟机。安装到你的 OS X 操作系统上的 Docker 客户端也会被配置为连接到这个虚拟机中的 Docker 守护进程。安装 Docker Toolbox 的同时也会安装 Docker Machine(参见范例 1.9)、 Docker Compose(参见范例 7.1)和 Kitematic(参见范例 7.5)。

你可以从 Docker Toolbox 的下载页面(https://https://www.docker.com/toolbox)下载它的安装包。下载完成之后(参见图 1-1),打开安装文件并按照提示步骤进行安装即可。安装完成之后,finder 应用会自动打开,你将会看到一个指向 Docker quickstart terminal 的链接。单击这个链接会打开一个终端,同时会自动在 VirtualBox 中启动一个虚拟机。Docker 客户端也会自动被配置为连接到在这个虚拟机中运行的 Docker 守护进程。

{%}

图 1-1:在 OS X 上的 Docker Toolbox

Toolbox 终端显示 Docker 客户端被配置为使用默认虚拟机。可以像下面这样试用一些 docker 命令。

                       ##         .
                 ## ## ##        ==
              ## ## ## ## ##    ===
          /"""""""""""""""""\___/ ===
     ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ /  ===- ~~~
          \______ o           __/
            \    \         __/
             \____\_______/


docker is configured to use the default machine with IP 192.168.99.100
For help getting started, check out the docs at https://docs.docker.com

bash-4.3$ docker ps
CONTAINER ID        IMAGE   COMMAND     CREATED     STATUS      PORTS      NAMES
bash-4.3$ docker images
REPOSITORY          TAG     IMAGE ID    CREATED     VIRTUAL SIZE
bash-4.3$ docker
docker          docker-compose  docker-machine

可以看到,现在你也可以使用 docker-machinedocker-compose 二进制文件了。

1.6.3 讨论

Docker Toolbox 是从 Docker 1.8 开始提供的,并且在 OS X 和 Windows 主机上,你也应该使用它作为默认的安装方式。在 Docker Toolbox 之前的版本中,你可以使用 Boot2Docker(参见范例 1.7 和范例 1.8)。虽然本书中也为 Boot2Docker 准备了一些范例,但是你还是应该优先使用 Docker Toolbox。实际上,Boot2Docker 仍然用在 Toolbox 中。你可以像下面这样通过 docker-machine ssh 登录到 VM 来确认这一点。

bash-4.3$ docker-machine ssh default
                       ##         .
                 ## ## ##        ==
              ## ## ## ## ##    ===
          /"""""""""""""""""\___/ ===
     ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ /  ===- ~~~
          \______ o           __/
            \    \         __/
             \____\_______/
_                 _   ____     _            _
| |__   ___   ___ | |_|___ \ __| | ___   ___| | _____ _ __
| '_ \ / _ \ / _ \| __| __) / _` |/ _ \ / __| |/ / _ \ '__|
| |_) | (_) | (_) | |_ / __/ (_| | (_) | (__|   <  __/ |
|_.__/ \___/ \___/ \__|_____\__,_|\___/ \___|_|\_\___|_|
Boot2Docker version 1.8.1, build master : 7f12e95 - Thu Aug 13 03:24:56 UTC 2015
Docker version 1.8.1, build d12ea79

1.7 在OS X上通过Boot2Docker安装Docker

1.7.1 问题

Docker 守护进程并不支持 OS X 系统,但是你想在 OS X 上无缝地使用 Docker。

1.7.2 解决方案

使用轻量 Linux 发行版 Boot2Docker(http://boot2docker.io)。Boot2Docker 基于微内核 Linux,并且为运行 Docker 进行了专门配置。安装完之后,你会得到一个 boot2docker 命令。你将会使用这个命令来与通过 VirtualBox 启动的虚拟机进行交互,这个虚拟机将会作为 Docker 主机。运行于 OS X 之上的 Docker 客户端和 Docker 守护进程不一样,需要在本地的 OS X 上进行安装。

让我们这就开始下载并安装 Boot2Docker。打开 Boot2Docker 网站(http://boot2docker.io)将会看到一些下载链接。从它的发布页面中(https://github.com/boot2docker/osx-installer/releases)选择最新的版本下载。下载完成之后,打开安装文件,如图 1-2 所示。

{%}

图 1-2:Boot2Docker 安装向导

安装完成后(参见图 1-3),你就可以开始使用 Boot2Docker 了。

{%}

图 1-3:Boot2Docker 安装完毕

终端窗口中,在出现提示时键入 boot2docker,此时应该能看到这个命令的用法选项。也可以查看所安装的 Boot2Docker 版本号,如下所示。

$ boot2docker
Usage: boot2docker [<options>] {help|init|up|ssh|save|down|poweroff|reset|
                                restart|config|status|info|ip|shellinit|delete|
                                download|upgrade|version} [<args>]
$ boot2docker version
Boot2Docker-cli version: v1.3.2
Git commit: e41a9ae

在安装完 Boot2Docker 之后,第一步是需要对它进行初始化。如果还没有下载 Boot2Docker 的 ISO 镜像,那么这一步将会下载这个镜像并在 VirtualBox 中创建一个虚拟机,如下所示。

$ boot2docker init
Latest release for boot2docker/boot2docker is v1.3.2
Downloading boot2docker ISO image...
Success:
    downloaded https://github.com/boot2docker/boot2docker/releases/download/\
  v1.3.2/boot2docker.iso
    to /Users/sebgoa/.boot2docker/boot2docker.iso

可以看到,ISO 镜像被保存到了用户主文件夹下的 .boot2docker/boot2docker.iso 文件夹中。如果打开 VirtualBox 界面,将会看到 boot2docker 这个虚拟主机处于关机状态(参见图 1-4 )。

{%}

图 1-4:boot2docker VirualBox 虚拟机

 你并不需要保持 VirtualBox 界面为打开状态,上面的截图只是为了方便说明。Boot2Docker 会在后台运行,使用 VBoxManage 命令对 boot2docker 虚拟机进行管理。

你现在已经可以使用 Boot2Docker 了。下面的命令将会启动这个虚拟机,然后返回一些操作指令,你可以按照这些指令对环境变量进行设置,以正确地连接到虚拟机中的 Docker 守护进程。

$ boot2docker start
Waiting for VM and Docker daemon to start...
.........................ooooooooooooooooooooo
Started.
Writing /Users/sebgoa/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/sebgoa/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/sebgoa/.boot2docker/certs/boot2docker-vm/key.pem

To connect the Docker client to the Docker daemon, please set:
    export DOCKER_CERT_PATH=/Users/sebgoa/.boot2docker/certs/boot2docker-vm
    export DOCKER_TLS_VERIFY=1
    export DOCKER_HOST=tcp://192.168.59.103:2376

虽然我们可以自己手动来设置这些环境变量,但是 Boot2Docker 提供了一个方便的命令:shellinit。可以使用该命令设置连接到 Docker 守护进程所需要的传输层安全(Transport Layer Security,TLS)信息,之后就可以在本地 OS X 计算机上访问虚拟机中的 Docker 主机了,如下所示。

$ $(boot2docker shellinit)
Writing /Users/sebgoa/.boot2docker/certs/boot2docker-vm/ca.pem
Writing /Users/sebgoa/.boot2docker/certs/boot2docker-vm/cert.pem
Writing /Users/sebgoa/.boot2docker/certs/boot2docker-vm/key.pem
$ docker ps
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

1.7.3 讨论

当有新版本的 Boot2Docker 可用时,你非常容易就能升级。只需要使用 download 命令下载最新的 Boot2Docker 安装包和新的 ISO 镜像就可以了。

请确保通过 $ boot2docker stop 命令停止 boot2docker 虚拟机之后,再运行安装脚本:

$ boot2docker stop
$ boot2docker upgrade
$ boot2docker start

1.8 在Windows 8.1台式机上运行Boot2Docker

1.8.1 问题

你有一台安装了 Windows 8.1 的台式机,你想在这台计算机上使用 Boot2Docker 来测试一下 Docker。

1.8.2 解决方案

使用 Boot2Docker 的 Windows 安装程序(https://github.com/boot2docker/windows-installer/releases/tag/v1.8.0),参见图 1-5。

{%}

图 1-5:Boot2Docker Windows 8.1 安装程序

下载完最新版本的 Windows 安装程序(一个 .exe 二进制文件)之后,通过命令提示符或者资源管理器(参见图 1-5)运行这个安装程序。这将会自动安装 VirtualBox、MSysGit 和 Boot2Docker 的 ISO 镜像。要想在 Windows 主机上使用 ssk-keygen 二进制文件,需要 MSysGit 这个软件。按照安装向导的提示进行安装,在此过程中,你需要同意一些来自 Oracle 关于 VirtualBox 的许可证信息。这个安装程序会在桌面上创建 VirtualBox 和启动 Boot2Docker 的快捷方式。

当安装完成之后,双击 Boot2Docker 的快捷方式,就可以在 VirtualBox 中启动虚拟机,并打开一个命令提示符(参见图 1-6)。这时就可以开始在 Windows 主机中使用 Docker 了。

{%}

图 1-6:Boot2Docker Windows 8.1 命令

1.8.3 讨论

Docker Machine(参见范例 1.9)也自带了一个 Hyper-V 驱动程序。如果你的计算机上已安装 Hyper-V,那么也可以通过 Docker Machine 来启动 Boot2Docker 实例。

1.8.4 参考

1.9 使用Docker Machine在云中创建Docker主机

1.9.1 问题

你不想在本地通过 Vagrant 来安装 Docker 守护进程(参见范例 1.4),也不想使用 Boot2Docker(参见范例 1.7)。事实上,你想在云上使用 Docker 主机(比如 AWS、 DigitalOcean、Azure 或 Google Compute Engine),并从本地 Docker 客户端无缝地连接到云上的 Docker 主机。

1.9.2 解决方案

使用 Docker Machine 在你选择的公有云上启动一个云主机实例。Docker Machine 是一个在本地主机上运行的客户端工具,你可以使用这个工具在远端的公有云中启动一台服务器,并像使用本地 Docker 主机一样来使用远程 Docker 主机。Machine 将会自动安装 Docker 并配置使用 TLS 进行安全传输。这之后就可以将远程的云主机实例作为 Docker 主机,从本地 Docker 客户端进行操作。可以参考第 8 章,那里有更多专门为在云中使用 Docker 而准备的范例。

 Docker Machine 的 beta 版是在 2015 年 2 月 26 日发布的(http://blog.docker.com/2015/02/announcing-docker-machine-beta/),官方文档(https://docs.docker.com/machine/)可以在 Docker 的网站上看到。它的源代码也在 GitHub 上(https://github.com/docker/machine)。

让我们这就开始吧。Machine 现在支持 VirtualBox、DigitalOcean(https://www.digitalocean.com)、Amazon Web Services(https://aws.amazon.com)、Azure(https://azure.microsoft.com)、Google Compute Engine(GCE,http://cloud.google.com)以及其他一些云服务提供商。也有一些驱动程序正在开发或审查中,你可以从这里(https://github.com/docker/machine/pulls)查看详细情况,我们可以期待很快会有更多的驱动程序出现。本范例将使用 DigitalOcean,如果你也想一步一步跟着操作,需要先到 DigitalOcean(https://cloud.digitalocean.com/registrations/new)注册一个账号。

账号注册完成之后,先不要通过 DigitalOcean 的界面来创建云主机(droplet)。取而代之的是,创建一个 API 访问令牌,以供 Docker Machine 使用。这个令牌是一个同时具备 read 和 write 权限的令牌,这样 Machine 才能上传 SSH 公钥(图 1-7)。之后,在本地电脑的命令行中设置一个名为 DIGITALOCEAN_ACCESS_TOKEN 的环境变量,其值即为刚才创建的令牌的内容。

 Machine 会将 SSH 公钥上传到你的云账号上。请确认你的访问令牌或 API 密钥具有创建公钥的必要权限。

{%}

图 1-7:DigitalOcean 为 Machine 准备的访问令牌

一切准备就绪。你现在需要去下载 docker-machine 二进制文件。打开 Docker Machine 文档网站(https://docs.docker.com/machine/),并选择与本地主机架构一致的二进制文件。比如,在 OS X 上像下面这样操作。

$ curl -sOL https://github.com/docker/machine/releases/download/v0.3.0/ \
docker-machine_darwin-amd64
$ mv docker-machine_darwin-amd64 docker-machine
$ chmod +x docker-machine
$ ./docker-machine --version
docker-machine version 0.3.0

由于已经设置了 DIGITALOCEAN_ACCESS_TOKEN 环境变量,现在就可以创建远程 Docker 主机了,如下所示。

$ ./docker-machine create -d digitalocean foobar
Creating SSH key...
Creating Digital Ocean droplet...
To see how to connect Docker to this machine, run: docker-machine env foobar

回到 DigitalOcean 的控制面板,你将会看到一个新创建的 SSH 密钥,以及一个新创建的云主机(参见图 1-8 和图 1-9)。

{%}

图 1-8:DigitalOcean 上由 Machine 生成的 SSH 密钥

{%}

图 1-9:由 Machine 创建的 DigitalOcean 云主机

要想将本地 Docker 客户端配置为使用这台远程 Docker 主机,可以执行下面的命令,根据该命令的输出来进行设置。

$ ./docker-machine env foobar
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://104.131.92.15:2376"
export DOCKER_CERT_PATH="/Users/sebastiengoasguen/.docker/machine/machines/foobar"
export DOCKER_MACHINE_NAME="foobar"
# Run this command to configure your shell:
# eval "$(docker-machine env foobar)"
$ eval "$(./docker-machine env foobar)"
$ docker ps
CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES

现在就可以享受一个由 Docker Machine 创建的位于远程 DigitalOcean 主机上的 Docker 了。

1.9.3 讨论

 如果在命令行上没有指定参数,Machine 就会去查找 DIGITALOCEAN_IMAGEDIGITALOCEAN_REGIONDIGITALOCEAN_SIZE 等环境变量。默认情况下,这些环境变量会被分别设置为 docker、nyc3 和 512mb。

docker-machine 二进制文件允许你在多个提供程序上创建多台主机。它也提供了一些基本的管理功能,比如 startstoprm 等,如下所示。

$ ./docker-machine
...
COMMANDS:
   active Get or set the active machine
   create Create a machine
   config Print the connection config for machine
   inspect  Inspect information about a machine
   ip   Get the IP address of a machine
   kill   Kill a machine
   ls   List machines
   restart  Restart a machine
   rm   Remove a machine
   env    Display the commands to set up the environment for the Docker client
   ssh    Log into or run a command on a machine with SSH
   start  Start a machine
   stop   Stop a machine
   upgrade  Upgrade a machine to the latest version of Docker
   url    Get the URL of a machine
   help, h  Shows a list of commands or help for one command

比如,可以列出刚才创建的主机,获得该主机的 IP 地址,甚至可以通过 SSH 连接到该主机,如下所示。

$ ./docker-machine ls
NAME     ACTIVE   DRIVER         STATE     URL                        SWARM
foobar   *        digitalocean   Running   tcp://104.131.92.15:2376
$ ./docker-machine ip foobar
104.131.92.15
$ ./docker-machine ssh foobar
Welcome to Ubuntu 14.04.2 LTS (GNU/Linux 3.13.0-57-generic x86_64)
...

Last login: Mon Mar 16 09:02:13 2015 from ...
root@foobar:~#

在本范例结束之前,别忘了删除刚才创建的云主机,如下所示。

$ ./docker-machine rm foobar

1.9.4 参考

1.10 使用Docker实验版二进制文件

1.10.1 问题

你想使用 Docker 的实验性功能,或者想使用提交给 Docker 上游代码的补丁。

1.10.2 解决方案

使用 Docker 实验版二进制文件。你可以下载实验版二进制文件或者使用每天晚上更新的实验版通道。

以 Linux 发行版中软件包的形式获取 Docker 的每日构建,如下所示。

$ wget -qO- https://experimental.docker.com/ | sh
$ docker version | grep Version
 Version:      1.8.0-dev
 Version:      1.8.0-dev

或者也可以直接下载每天晚上构建出的二进制文件,比如在 64 位系统上,如下所示。

$ wget https://experimental.docker.com/builds/Linux/x86_64/docker-latest
$ chmod +x docker-latest
$ ./docker-latest version | grep Version
 Version:      1.8.0-dev
 Version:      1.8.0-dev

如果你想默认使用这个二进制文件,请参考范例 4.4 中的说明。

1.10.3 参考

1.11 在Docker中运行Hello World

1.11.1 问题

你已经拥有一台 Docker 主机,想运行你的第一个容器。你想学习容器的不同生命周期。比如,你想运行一个容器并在其中打印 hello world

1.11.2 解决方案

在命令行提示符下键入 docker,将会显示 docker 命令的使用方法,如下所示。

$ docker
Usage: docker [OPTIONS] COMMAND [arg...]

A self-sufficient runtime for linux containers.

...

Commands:
    attach    Attach to a running container
    build     Build an image from a Dockerfile
    commit    Create a new image from a container's changes
...
    rm        Remove one or more containers
    rmi       Remove one or more images
    run       Run a command in a new container
    save      Save an image to a tar archive
    search    Search for an image on the Docker Hub
    start     Start a stopped container
    stop      Stop a running container
    tag       Tag an image into a repository
    top       Lookup the running processes of a container
    unpause   Unpause a paused container
    version   Show the Docker version information
    wait      Block until a container stops, then print its exit code

你已经见过了 docker ps 命令,该命令用于列出所有运行中的容器。在本书的其他范例中,你将会看到更多的命令。首先,你想启动一个容器。让我们立即开始,执行 docker run 命令,如下所示。

$ docker run busybox echo hello world
Unable to find image 'busybox' locally
busybox:latest: The image you are pulling has been verified
511136ea3c5a: Pull complete
df7546f9f060: Pull complete
e433a6c5b276: Pull complete
e72ac664f4f0: Pull complete
Status: Downloaded newer image for busybox:latest
hello world

容器来源于镜像。docker run 命令也需要一个参数来指定使用哪个镜像。在上面的例子中,使用了名为 busybox 的镜像。在本地 Docker 中还没有这个镜像,所以需要先从公开 registry 中将这个镜像拖到本地。registry 是一个 Docker 镜像的仓库,Docker 客户端可以与这个仓库进行交互并从中下载镜像。镜像下载完毕后,Docker 就会启动容器,并在容器内执行 echo hello world 命令。恭喜你成功运行自己的第一个容器。

1.11.3 讨论

如果列出运行中的容器,你会发现没有容器正在运行。这是因为容器一旦完成了它的工作(输出 hello world),就停止了。但是容器并没有完全消失,你可以通过 docker ps -a 命令看到这个容器,如下所示。

$ docker ps
CONTAINER ID    IMAGE    COMMAND      CREATED   STATUS     PORTS        NAMES
$ docker ps -a
CONTAINER ID    IMAGE           COMMAND            ...    PORTS    NAMES
8f7089b187e8    busybox:latest  "echo hello world" ...          thirsty_morse

可以看到,输出信息中包括该容器的 ID(8f7089b187e8)和镜像(busybox:latest),还有容器的名称以及容器所运行的命令。你可以通过 docker rm 8f7089b187e8 命令将这个容器永久删除。该容器使用的镜像已经被下载到了本地,docker images 命令将会输出这个镜像的信息,如下所示。

$ docker images
REPOSITORY       TAG             IMAGE ID        CREATED         VIRTUAL SIZE
busybox          latest          e72ac664f4f0    9 weeks ago     2.433 MB

如果任何运行中或者已经停止的容器都没有使用这个镜像,你就可以通过 docker rmi busybox 命令来删除这个镜像。

运行 echo 命令虽然很有趣,但是获得一个终端会话会更好。要想在容器中运行 /bin/bash,你需要使用 -t-i 参数来获得一个交互式会话,下面以使用 Ubuntu 镜像为例进行说明。

$ docker run -t -i ubuntu:14.04 /bin/bash
Unable to find image 'ubuntu:14.04' locally
ubuntu:14.04: The image you are pulling has been verified
01bf15a18638: Pull complete
30541f8f3062: Pull complete
e1cdf371fbde: Pull complete
9bd07e480c5b: Pull complete
511136ea3c5a: Already exists
Status: Downloaded newer image for ubuntu:14.04
root@6f1050d21b41:/#

你会看到 Docker 下载的 ubuntu:14.04 镜像由多个层组成,然后你得到了一个容器中 root 权限的会话。提示符也显示了这个容器的 ID。一旦你退出这个终端,该容器就会停止运行,就像我们前面的 hello world 例子一样。

 如果你错过了最开始关于如何安装 Docker 的范例,也可以试试网页版的模拟器(https://www.docker.com/tryit/)。这个工具提供了一个 10 分钟的 Docker 教程,你可以通过这个网页初步了解一下 Docker。

1.12 以后台方式运行Docker容器

1.12.1 问题

你已经知道如何以交互方式启动一个容器,但是你想以后台方式运行一个服务。

1.12.2 解决方案

使用 docker run-d 选项。

运行下面的命令,将会在容器中启动一个简单的基于 Python 的 HTTP 服务器。这个容器使用了 python:2.7 镜像,该镜像下载自 Docker Hub(https://registry.hub.docker.com/_/python/,参见范例 2.9)。

$ docker run -d -p 1234:1234 python:2.7 python -m SimpleHTTPServer 1234
$ docker ps
CONTAINER ID  IMAGE       COMMAND                ...   NAMES
0fae2d2e8674  python:2.7  "python -m SimpleHTT   ...   suspicious_pike

如果你在浏览器上访问该容器所在主机的 IP 地址以及 1234 端口,将会看到该容器中根目录下的所有文件列表。Docker 会根据 -p 1234:1234 参数自动在容器和宿主机之间进行端口映射。在范例 3.2 中,你将会详细了解 Docker 的这一网络功能。

1.12.3 讨论

这个 -d 参数会让容器在后台运行。你可以通过运行 exec 命令来启动一个 bash shell,再次进入到该容器中,如下所示。

$ docker exec -ti 9d7cebd75dcf /bin/bash
root@9d7cebd75dcf:/# ps -ef | grep python
root         1     0  0 15:42 ?        00:00:00 python -m SimpleHTTPServer 1234

在官方文档(https://docs.docker.com/reference/run/)中还有 docker run 的其他很多选项。你可以实验一下给容器指定名称,修改容器中的工作目录,设置一个环境变量,等等。

1.12.4 参考

1.13 创建、启动、停止和移除容器

1.13.1 问题

你已经知道如何启动一个容器并让它在后台运行。你希望学习基本命令来管理容器的整个生命周期。

1.13.2 解决方案

使用 Docker 命令行的 createstartstopkillrm 命令。你可以在这些命令后面加上 -h 或者 --h 选项来查看它们的使用方法,或者只输入命令而不指定任何参数(比如 docker create)。

1.13.3 讨论

在范例 1.12 中,你通过 docker run 自动启动了一个容器。你也可以通过 docker create 命令来创建一个容器。继续使用上面简单的 HTTP 服务器的例子,唯一的区别就是这里没有指定 -d 选项。当创建容器之后,你需要运行 docker start 来启动这个容器,如下所示。

$ docker create -P --expose=1234 python:2.7 python -m SimpleHTTPServer 1234
a842945e2414132011ae704b0c4a4184acc4016d199dfd4e7181c9b89092de13
$ docker ps -a
CONTAINER ID  IMAGE       COMMAND              CREATED       ... NAMES
a842945e2414  python:2.7  "python -m SimpleHTT 8 seconds ago ... fervent_hodgkin
$ docker start a842945e2414
a842945e2414
$ docker ps
CONTAINER ID  IMAGE       COMMAND              ...   NAMES
a842945e2414  python:2.7  "python -m SimpleHTT ...   fervent_hodgkin

要想停止一个正在运行中的容器,可以选择使用 docker kill(这个命令会发送 SIGKILL 信号到容器)或者 docker stop(这个命令会发送 SIGTERM 到容器,如果在一定时间内容器还没有停止,则会再发送 SIGKILL 信号强制停止)。这两个命令最终的结果是停止容器的运行,该容器将不会出现在 docker ps 返回的运行中容器列表中。但是,容器还没有完全消失(比如容器的文件系统还在)。你可以通过 docker restart 来重启这个容器,或者通过 docker rm 移除这个容器,如下所示。

$ docker restart a842945e2414
a842945e2414
$ docker ps
CONTAINER ID    IMAGE       COMMAND              ...   NAMES
a842945e2414    python:2.7  "python -m SimpleHTT ...   fervent_hodgkin
$ docker kill a842945e2414
a842945e2414
$ docker rm a842945e2414
a842945e2414
$ docker ps -a
CONTAINER ID    IMAGE       COMMAND     CREATED     STATUS      PORTS       NAMES

如果你有很多停止中的容器待删除,可以在一条命令中使用嵌套的 shell 来删除所有容器。docker ps-q 选项只会返回容器的 ID 信息,如下所示。

$ docker rm $(docker ps -a -q)

1.14 使用Dockerfile构建Docker镜像

1.14.1 问题

你知道了如何从公有的 Docker registry 下载镜像,但是你想构建自己的 Docker 镜像。

1.14.2 解决方案

使用 Dockerfile 构建镜像。Dockerfile 是一个文本文件,它记述了 Docker 构建一个镜像所需要的过程,包括安装软件包、创建文件夹、定义环境变量以及其他一些操作。在第 2 章中我们会对 Dockerfile 和构建镜像做更深入的说明。本范例中只会涉及构建镜像的基本概念。

作为一个简单例子,我们假设你要基于 busybox 镜像创建一个新镜像,并定义一个环境变量。busybox 镜像是一个包含了 busybox(http://www.busybox.net/about.html)二进制文件的 Docker 镜像,这个二进制文件将很多 Unix 实用工具打包到了一个单一的二进制文件中。在一个空文件夹下创建一个名为 Dockerfile 的文件,如下所示。

FROM busybox

ENV foo=bar

可以通过 docker build 命令来构建一个新镜像,并命名为 busybox2,如下所示。

$ docker build -t busybox2 .
Sending build context to Docker daemon 2.048 kB
Step 0 : FROM busybox
latest: Pulling from library/busybox
cf2616975b4a: Pull complete
6ce2e90b0bc7: Pull complete
8c2e06607696: Pull complete
Digest: sha256:df9e13f36d2d5b30c16bfbf2a6110c45ebed0bfa1ea42d357651bc6c736d5322
Status: Downloaded newer image for busybox:latest
 ---> 8c2e06607696
Step 1 : ENV foo bar
 ---> Running in f46c59e9bdd6
 ---> 582bacbe7aaa

构建结束之后,你就能通过 docker images 命令看到新构建的镜像了。可以基于这个新镜像启动一个容器,检查一下其中是否有一个名为 foo 的环境变量,并且其值被设置为了 bar,如下所示。

$ docker images
REPOSITORY      TAG        IMAGE ID        CREATED         VIRTUAL SIZE
busybox2        latest     582bacbe7aaa    6 seconds ago   2.433 MB
busybox         latest     8c2e06607696    3 months ago    2.433 MB
$ docker run busybox2 env | grep foo
foo=bar

1.14.3 参考

1.15 在单一容器中使用Supervisor运行WordPress

1.15.1 问题

你已经知道了如何将两个容器链接到一起(参见范例 1.16),不过你希望在一个容器中运行应用程序所需的所有服务。以运行 WordPress 为例,你想在一个容器中同时运行 MySQL 和 HTTPD 服务。由于 Docker 运行的是前台进程,所以你需要找到一种同时运行多个“前台”进程的方式。

1.15.2 解决方案

使用 Supervisor(http://supervisord.org/index.html)来监控并运行 MySQL 和 HTTPD。 Supervisor 不是一个 init 系统,而是一个用来控制多个进程的普通程序。

 本范例是一个在容器中使用 Supervisor 同时运行多个进程的例子。你可以以此为基础在一个 Docker 镜像中运行多个服务(比如 SSH、Nginx)。本范例中,WordPress 的配置是一个最精简的可行配置,并不适用于生产环境。

示例中的文件可以在 GitHub(https://github.com/how2dock/docbook/tree/master/ch01/supervisor)下载。这些文件中包括一个用于启动虚拟机的 Vagrantfile,Docker 运行在该虚拟机中,还包含一个 Dockerfile 来定义要创建的镜像,此外还有一个 Supervisor 的配置文件(supervisord.conf)和一个 WordPress 的配置文件(wp-config.php)。

 如果你不想使用 Vagrant,也可以使用其中的 Dockerfile、supervisord 和 WordPress 的配置文件,在自己的 Docker 主机上来安装。

为了运行 WordPress,你需要安装 MySQL、Apache 2(即 httpd)、PHP 以及最新版本的 WordPress。你将需要创建一个用于 WordPress 的数据库。在该范例的配置文件中,WordPress 数据库用户名为 root,密码也是 root,数据库名为 wordpress。如果你想对数据库的配置进行修改,需要同时修改 wp-config.php 和 Dockerfile 这两个文件,并让它们的设置保持一致。

Dockerfile 文件用来描述一个 Docker 镜像是如何构建的,后面章节会有关于 Dockerfile 的详细说明。如果这是你第一次使用 Dockerfile 文件,那么你可以直接使用下面的文件,以后再学习 Dockerfile(参见范例 2.3 对 Dockerfile 的介绍)。

FROM ubuntu:14.04

RUN apt-get update && apt-get -y install \
    apache2 \
    php5 \
    php5-mysql \
    supervisor \
    wget

RUN echo 'mysql-server mysql-server/root_password password root' | \
    debconf-set-selections && \
    echo 'mysql-server mysql-server/root_password_again password root' | \
    debconf-set-selections

RUN apt-get install -qqy mysql-server

RUN wget http://wordpress.org/latest.tar.gz && \
    tar xzvf latest.tar.gz && \
    cp -R ./wordpress/* /var/www/html && \
    rm /var/www/html/index.html

RUN (/usr/bin/mysqld_safe &); sleep 5; mysqladmin -u root -proot create wordpress

COPY wp-config.php /var/www/html/wp-config.php
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

EXPOSE 80

CMD ["/usr/bin/supervisord"]

Supervisor 的配置文件 supervisord.conf 如下所示。

[supervisord]
nodaemon=true

[program:mysqld]
command=/usr/bin/mysqld_safe
autostart=true
autorestart=true
user=root

[program:httpd]
command=/bin/bash -c "rm -rf /run/httpd/* && /usr/sbin/apachectl -D FOREGROUND"

这里定义了两个被监控和运行的服务:mysqldhttpd。每个程序都可以使用诸如 autorestartautostart 等选项。最重要的指令是 command,其定义了如何运行每个程序。在这个例子中,Docker 容器只需要运行一个前台进程:supervisord。从 Dockerfile 中的 CMD ["/usr/bin/supervisord"] 这一行也能看出来。

在你的 Docker 主机上,构建该镜像并启动一个后台容器。如果按照例子中的配置文件使用了基于 Vagrant 的虚拟机,可以执行如下命令。

$ cd /vagrant
$ docker build -t wordpress .
$ docker run -d -p 80:80 wordpress

容器启动后还会在宿主机和 Docker 容器之间为 80 端口进行端口映射。现在只需要在浏览器中打开 http://<ip_of_docker_host>,就可以进入到 WordPress 的配置页面了。

1.15.3 讨论

尽管通过 Supervisor 在一个容器内同时运行多个应用服务工作起来非常完美,但是你最好还是使用多个容器来运行不同的服务。这能充分利用容器的隔离优势,也能帮助你创建基于微服务设计思想的应用(参见《微服务设计》1)。最终这也将会使你的应用更具弹性和可扩展性。

1此书已由人民邮电出版社出版。——编者注

1.15.4 参考

1.16 使用两个链接在一起的容器运行 WordPress 博客程序

1.16.1 问题

你希望通过容器来运行一个 WordPress 网站(http://wordpress.com/),但是你不想让 MySQL 和 WordPress 在同一个容器中运行。你时刻谨记对关注点进行分离的原则,并尽可能地对应用程序的不同组件进行解耦。

1.16.2 解决方案

启动两个容器:一个运行来自 Docker Hub(http://hub.docker.com/)的官方 WordPress,另一个运行 MySQL 数据库。这两个容器通过 Docker 命令行工具的 --link 选项链接在一起。

开始下载最新的 WordPress(https://hub.docker.com/_/wordpress/)和 MySQL(https://.hub.docker.com/_/mysql/)镜像,如下所示。

$ docker pull wordpress:latest
$ docker pull mysql:latest
$ docker images
REPOSITORY      TAG             IMAGE ID        CREATED         VIRTUAL SIZE
mysql           latest          9def920de0a2    4 days ago      282.9 MB
wordpress       latest          93acfaf85c71    8 days ago      472.8 MB

启动一个 MySQL 容器,并通过命令行工具的 --name 选项为这个容器设置一个名称,通过 MYSQL_ROOT_PASSWORD 环境变量来设置 MySQL 的密码,如下所示。

$ docker run --name mysqlwp -e MYSQL_ROOT_PASSWORD=wordpressdocker -d mysql

 如果在使用 mysql 镜像时没有指定标签,Docker 会自动使用 latest 标签,这也是前面刚刚下载的镜像。容器通过 -d 选项以守护式的方式开始运行。

现在就可以基于 wordpress:latest 镜像启动 WordPress 容器了。这个容器将会通过 --link 选项链接到 MySQL 容器,这样 Docker 会自动进行网络配置,让 WordPress 容器能访问到 MySQL 容器中暴露出来的端口,如下所示。

$ docker run --name wordpress --link mysqlwp:mysql -p 80:80 -d wordpress

两个容器都会以后台方式运行,WordPress 容器的 80 端口会被映射到宿主机的 80 端口上,如下所示。

$ docker ps
CONTAINER ID    IMAGE             COMMAND                CREATED              ...
e1593e7a20df    wordpress:latest  "/entrypoint.sh apac   About a minute ago   ...
d4be18e33153    mysql:latest      "/entrypoint.sh mysq   5 minutes ago        ...

...             STATUS              PORTS                NAMES
...             Up About a minute   0.0.0.0:80->80/tcp   wordpress
...             Up 5 minutes        3306/tcp             mysqlwp

在浏览器中打开 http://<ip_of_host> 就会看到 WordPress 的安装界面,里面有选择语言的窗口,如图 1-10 所示。完成了 WordPress 的安装过程,你将会得到一个在两个链接到一起的容器之上运行的 WordPress 网站。

{%}

图 1-10:在容器中运行的 WordPress 站点

1.16.3 讨论

这两个 WordPress 和 MySQL 镜像都是官方镜像,分别由 WordPress 和 MySQL 的社区来维护。Docker Hub 这些镜像的页面都有关于如何进行配置以从这些镜像创建容器的详细文档。

 别忘了阅读 WordPress 镜像文档(https://hub.docker.com/_/wordpress/)和 MySQL 镜像文档(https://hub.docker.com/_/mysql/)。

令人感兴趣的是,你可以通过设置几个环境变量来创建一个数据库,并且只有具有相应权限的用户才能操作数据库:MYSQL_DATABASEMYSQL_USERMYSQL_PASSWORD。在前面的例子中,WordPress 使用了 MySQL 的 root 用户,这并不是一个好实践。最好是创建一个名为 wordpress 的数据库,并为其创建一个用户,像下面这样。

$ docker run --name mysqlwp -e MYSQL_ROOT_PASSWORD=wordpressdocker \
                            -e MYSQL_DATABASE=wordpress \
                            -e MYSQL_USER=wordpress \
                            -e MYSQL_PASSWORD=wordpresspwd \
                            -d mysql

如果你需要删除所有容器,可以使用下面这种嵌套 shell 的快捷方式。

$ docker stop $(docker ps -q)
$ docker rm -v $(docker ps -aq)

docker rm 命令的 -v 选项用来删除 MySQL 镜像中定义的数据卷。

数据库容器启动之后,可以启动 WordPress 容器并指定你设置好的数据库表,如下所示。

$ docker run --name wordpress --link mysqlwp:mysql -p 80:80 \
                              -e WORDPRESS_DB_NAME=wordpress \
                              -e WORDPRESS_DB_USER=wordpress \
                              -e WORDPRESS_DB_PASSWORD=wordpresspwd \
                              -d wordpress

1.17 备份在容器中运行的数据库

1.17.1 问题

你使用 MySQL 镜像对外提供数据库服务。为了对数据进行持久化,你需要备份该数据库。

1.17.2 解决方案

你可以使用一种或几种备份策略结合起来使用。适用于容器的主要有两种方式:一是可以在一个以后台方式运行的容器中执行一条命令,二是挂载一个宿主机卷(即一个在宿主机上能访问的存储区域)到容器中。在本范例中,我们将会看到如何去做下面两件事:

  • 将 Docker 主机上的卷挂载到 MySQL 容器中

  • 使用 docker exec 命令执行 mysqldump

在范例 1.16 的例子中,我们通过两个链接在一起的容器安装了一个 WordPress 站点。在该范例中,我们将会修改启动 MySQL 容器的方式。容器启动之后,一个功能齐全的 WordPress 网站已经安装完成,你可以停止容器,这也将会停止你的应用程序。这时容器还没有被完全删除,数据库中的数据仍然可以访问到。不过,如果你删除容器(docker rm $(docker ps -aq)),那么其中所有的数据都会丢失。

有一种方式可以在容器被 docker rm -v 命令删除之后也能保留数据,就是将宿主机的卷挂载到容器中。如果你只是使用 docker rm 命令来删除容器,那么这个容器的镜像所定义的卷会在磁盘上保留,尽管这时容器已经不存在了。如果看一下构建这个 MySQL 镜像的 Dockerfile 文件(https://github.com/docker-library/mysql/blob/d6268ace61047c74468d7c59b4d8da6be5dec16a/5.6/Dockerfile),将会看到 VOLUME /var/lib/mysql 这一行。这一行的意思是,当你基于该镜像启动一个容器时,可以将宿主机的文件夹绑定到容器中的这个挂载点上,比如下面这样。

$ docker run --name mysqlwp -e MYSQL_ROOT_PASSWORD=wordpressdocker \
                            -e MYSQL_DATABASE=wordpress \
                            -e MYSQL_USER=wordpress \
                            -e MYSQL_PASSWORD=wordpresspwd \
                            -v /home/docker/mysql:/var/lib/mysql \
                            -d mysql

上面命令中 -v /home/docker/mysql:/var/lib/mysql 这一行进行了宿主机和容器中卷的绑定。当完成 WordPress 的设置之后,在宿主机 /home/docker/mysql 文件夹下就能看到这些文件变动。

$ ls mysql/
auto.cnf  ibdata1  ib_logfile0  ib_logfile1  mysql  performance_schema  wordpress

为了对整个 MySQL 数据库进行备份,可以使用 docker exec 命令在容器内执行 mysqldump,如下所示。

$ docker exec mysqlwp mysqldump --all-databases \
                                --password=wordpressdocker > wordpress.backup

现在你可以使用传统方式来进行数据库的备份和恢复了。比如,在云环境中,你可能会将 Elastic Block Store(例如 AWS EBS)挂载到一个主机实例,再挂载到容器中。你也可以将你的 MySQL 备份保存到 Elastic Storage(比如 AWS S3)中。

1.17.3 讨论

尽管本例中使用的是 MySQL 数据库,不过这种方式也适用于 Postgres 和其他数据库。如果你用了 Docker Hub 上的 Postgres(https://registry.hub.docker.com/_/postgres/)镜像,那么也可以从它的 Dockerfile(https://github.com/docker-library/postgres/blob/5f401753715311752f2346cdbd32bd55e7251ddd/9.4/Dockerfile)中看到其中也定义了一个卷(VOLUME /var/lib/postgresql/data)。

1.18 在宿主机和容器之间共享数据

1.18.1 问题

你在宿主机上有一些数据,想在容器中也能访问到这些数据。

1.18.2 解决方案

在运行 docker run 命令时,通过设置 -v 选项将宿主机的卷挂载到容器中。

比如,你想将宿主机 /cookbook 目录下的工作目录与容器共享,可以执行以下指令。

$ ls
data
$ docker run -ti -v "$PWD":/cookbook ubuntu:14.04 /bin/bash
root@11769701f6f7:/# ls /cookbook
data

在这个例子中,宿主机上的当前工作目录会挂载到容器中的 /cookbook 目录上。如果你在容器内创建了文件或者文件夹,那么这些修改会直接反映到宿主机上,如下所示。

$ docker run -ti -v "$PWD":/cookbook ubuntu:14.04 /bin/bash
root@44d71a605b5b:/# touch /cookbook/foobar
root@44d71a605b5b:/# exit
exit
$ ls -l foobar
-rw-r--r-- 1 root root 0 Mar 11 11:42 foobar

默认情况下,Docker 会以读写模式挂载数据卷。如果想以只读方式挂载数据卷,可以在卷名称后通过冒号设置相应的权限。比如在前面的例子中,如果想以只读方式将工作目录挂载到 /cookbook,可以使用 -v "$PWD":/cookbook:ro。可以通过 docker inspect 命令来查看数据卷的挂载映射情况。参考范例 9.1 可以获取更多有关 inspect 的介绍。

$ docker inspect -f {{.Mounts}} 44d71a605b5b
[{ /Users/sebastiengoasguen/Desktop /cookbook   true}]

1.18.3 参考

1.19 在容器之间共享数据

1.19.1 问题

你已经知道了如何将宿主机中的数据卷挂载到运行中的容器上,但是你想和其他容器共享在一个容器中定义的卷。这样做的优点是让 Docker 去负责管理卷,并且遵循了单一职责这一原则。

1.19.2 解决方案

使用数据容器。在范例 1.18 中,你已经知道了如何将宿主机中的卷挂载到容器中,可以使用 docker run 命令的 -v 选项指定宿主机中的卷,以及该卷在容器中要挂载的路径。如果省略了宿主机中的路径,那么你就创建了一个称为数据容器的容器。

容器启动后会在内部创建参数中指定的卷,这是一个具备读写权限的文件系统,它不在容器镜像最顶层的只读层之上。Docker 会负责管理这个文件系统,但你也可以在宿主机上对其进行读写。下面我们就来看看它是如何工作的。(为了方便说明,这里对卷的 ID 进行了截断。)

$ docker run -ti -v /cookbook ubuntu:14.04 /bin/bash
root@b5835d2b951e:/# touch /cookbook/foobar
root@b5835d2b951e:/# ls cookbook/
foobar
root@b5835d2b951e:/# exit
exit
bash-4.3$ docker inspect -f {{.Mounts}} b5835d2b951e
[{dbba7caf8d07b862b61b39... /var/lib/docker/volumes/dbba7caf8d07b862b61b39... \
/_data /cookbook local  true}]
$ sudo ls /var/lib/docker/volumes/dbba7caf8d07b862b61b39...
foobar

 由 Docker 引擎创建的用于存储卷数据的目录位于 Docker 主机之上。如果你是通过 Docker Machine 创建的远程 Docker 主机,就需要连接到该远程 Docker 主机来查看 Docker volume 数据路径的详情。

这个容器启动之后,Docker 会创建 /cookbook 目录。在容器中,你可以对这个目录进行读写操作。在你退出这个容器之后,可以通过 inspect 命令(参见范例 9.1)来查看这个数据卷被保存到了宿主机的什么位置。Docker 会在 /var/lib/docker/volumes/ 下为这个卷创建对应的文件夹。你可以在宿主机上对这个文件夹进行读写。任何对这个文件夹的修改都会被保存下来,如果你重启了这个容器,那么在容器内也能看到修改后的内容,如下所示。

$ sudo touch /var/lib/docker/volumes/dbba7caf8d07b862b61b39.../foobar2
$ docker start b5835d2b951e
$ docker exec -ti b5835d2b951e /bin/bash
root@b5835d2b951e:/# ls /cookbook
foobar  foobar2

为了将这个容器中的卷共享给其他容器,可以使用 --volumes-from 选项。让我们重新开始来创建一个数据容器,然后再创建另一个容器来挂载源数据容器中共享的卷,如下所示。

$ docker run -v /data --name data ubuntu:14.04
$ docker ps
CONTAINER ID    IMAGE   COMMAND     CREATED   STATUS   PORTS    NAMES
$ docker inspect -f {{.Mounts}} data
[{4ee1d9e3d453e843819c6ff... /var/lib/docker/volumes/4ee1d9e3d453e843819c6ff... \
/_data /data local true]

 这个容器并没有处于运行状态。但是它的卷映射关系已经存在,并且卷被持久化到了 /var/lib/docker/vfs/dir 下面。你可以通过 docker rm -v data 命令来删除容器和它的卷。如果你没有使用 rm -v 选项来删除容器和它的卷,那么系统中将会遗留很多没有被使用的卷。

即使这个数据容器没有运行,你也可以通过 --volumes-from 来挂载其中的卷,如下所示。

$ docker run -ti --volumes-from data ubuntu:14.04 /bin/bash
root@b94a006377c1:/# touch /data/foobar
root@b94a006377c1:/# exit
exit
$ sudo ls /var/lib/docker/volumes/4ee1d9e3d453e843819c6ff...
foobar

1.19.3 参考

1.20 对容器进行数据复制

1.20.1 问题

你有一个运行中的容器,没有设置任何卷映射信息,但是你想从容器内复制数据出来或者将数据复制到容器里。

1.20.2 解决方案

使用 docker cp 命令将文件从正在运行的容器复制到 Docker 主机。docker cp 命令支持在 Docker 主机与容器之间进行文件复制。其用法很简单,如下所示。

$ docker cp
docker: "cp" requires 2 arguments.

See 'docker cp --help'.

Usage:  docker cp [OPTIONS] CONTAINER:PATH LOCALPATH|-
  docker cp [OPTIONS] LOCALPATH|- CONTAINER:PATH

Copy files/folders between a container and your host.
...

为了讲解它是如何工作的,我们先启动一个容器,并让它执行睡眠操作。然后你可以进入到这个容器,并手动创建一个文件,如下所示。

$ docker run -d --name testcopy ubuntu:14.04 sleep 360
$ docker exec -ti testcopy /bin/bash
root@b81793e9eb3e:/# cd /root
root@b81793e9eb3e:~# echo 'I am in the container' > file.txt
root@b81793e9eb3e:~# exit

要将在容器中创建的这个文件复制到宿主机上,使用 docker cp 即可,如下所示。

$ docker cp testcopy:/root/file.txt .
$ cat file.txt
I am in the container

要想将文件从宿主机复制到容器,仍然可以使用 docker cp 命令,只不过源和目的文件的参数要调换一下位置,如下所示。

$ echo 'I am in the host' > host.txt
$ docker cp host.txt testcopy:/root/host.txt

一个比较好的使用场景是将一个容器中的文件复制到另一个容器中,这可以通过临时先将主机作为文件的中转站,执行两次 docker cp 命令来实现。比如,如果想将 /root/file.txt 从两个运行容器中的 c1 复制到 c2,可以使用下面的命令。

$ docker cp c1:/root/file.txt .
$ docker file.txt c2:/root/file.txt

1.20.3 讨论

如果是 1.8 版本之前的 Docker,docker cp 还不支持将文件从宿主机复制到容器中。不过你可以组合使用 docker exec 和一些 shell 重定向,如下所示。

$ echo 'I am in the host' > host.txt
$ docker exec -i testcopy sh -c 'cat > /root/host.txt' < host.txt
$ docker exec -i testcopy sh -c 'cat /root/host.txt'
I am in the host

现在,新版本的 Docker 已经不需要再像上面那样麻烦了,但是上面的例子很好地展示了 docker exec 命令的强大之处。

1.20.4 参考

目录