Docker 编排指南(一)
原文:
zh.annas-archive.org/md5/1B8FD79C063269548A48D0E2E43C2BF6译者:飞龙
前言
开始使用 Docker,这种彻底改变了应用程序沙盒化的 Linux 容器技术。通过本书,您将学会如何使用 Docker 使开发更快,应用程序部署更简单。
本指南将向您展示如何在沙盒式 Docker 容器中构建应用程序,并使其在任何地方运行 - 您的开发机器,私人服务器,甚至云上,成本仅为虚拟机的一小部分。在您的开发设置中构建 PaaS,部署集群等等。
本书涵盖的内容
第一章 解开 Docker,教您如何在您的环境中运行 Docker。
第二章 Docker CLI 和 Dockerfile,帮助您适应 Docker 命令行工具,并通过编写 Dockerfile 开始构建自己的容器。
第三章 配置 Docker 容器,向您展示如何控制您的容器并配置它们以实现细粒度的资源管理。
第四章 自动化和最佳实践,涵盖了帮助管理容器的各种技术 - 使用监控程序协调多个服务,服务发现,以及有关 Docker 安全性的知识。
第五章 Docker 的朋友,向您展示了围绕 Docker 的世界。您将了解使用 Docker 的开源项目。然后,您可以使用 CoreOS 构建自己的 PaaS 并部署集群。
本书所需内容
本书希望您之前使用过 Linux 和 Git,但新手用户在运行示例中提供的命令时不会遇到困难。您需要在操作系统的用户帐户中具有管理员权限才能安装 Docker。Windows 和 OSX 用户需要安装 VirtualBox。
本书适合谁
无论您是开发人员、系统管理员还是介于两者之间,本书都将为您提供使用 Docker 构建、测试和部署应用程序的指导,使其变得更加简单,甚至令人愉快。
从安装开始,本书将带您了解启动 Docker 容器所需的不同命令。然后它将向您展示如何构建自己的应用程序,并带您完成如何微调这些容器的资源分配的说明,最后以管理一组 Docker 容器的注意事项结束。
通过按照每一章中的步骤顺序进行工作,您将很快掌握 Docker,并准备好在不需要为部署而彻夜不眠的情况下发布您的应用程序。
约定
在这本书中,您将找到许多不同类型信息的文本样式。以下是一些样式的示例,以及它们的含义解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:“我们可以使用ENV指令设置环境变量。”
一块代码设置如下:
WORKDIR code.it
RUN git submodule update --init --recursive
RUN npm install
任何命令行输入或输出都以以下方式编写:
$ docker run --d -p '8000:8000' -e 'NODE_PORT=8000' -v '/var/log/code.it:/var/log/code.it' shrikrishna/code.it .
新术语和重要单词以粗体显示。例如,屏幕上看到的单词,菜单或对话框中出现的单词会在文本中显示为:“转到您的存储库中的设置”。
注意
警告或重要说明会以以下方式出现在方框中。
提示
提示和技巧会以这种方式出现。
第一章:打开 Docker
Docker是一种轻量级的容器化技术,在近年来广受欢迎。它利用了 Linux 内核的一系列特性,如命名空间、cgroups、AppArmor 配置文件等,将进程隔离到可配置的虚拟环境中。
在本章中,您将学习如何在各种系统上安装 Docker,无论是在开发还是生产环境。对于基于 Linux 的系统,由于内核已经可用,安装就像apt-get install或yum install命令一样简单。然而,要在 OSX 和 Windows 等非 Linux 操作系统上运行 Docker,您需要安装 Docker Inc.开发的一个辅助应用程序,称为Boot2Docker。这将在VirtualBox上安装一个轻量级的 Linux 虚拟机,通过Internet Assigned Numbers Authority (IANA)分配的端口 2375,使 Docker 可用。
在本章结束时,您将在您的系统上安装了 Docker,无论是在开发还是生产环境,并进行了验证。
本章将涵盖以下内容:
-
介绍 Docker
-
安装 Docker
-
Ubuntu(14.04 和 12.04)
-
Mac OSX 和 Windows
-
OpenStack
-
Inception:在 Docker 中构建 Docker
-
验证安装:
HelloWorld输出 -
介绍 Docker
Docker 是由 DotCloud Inc.(目前是 Docker Inc.)开发的,作为他们构建的Platform as a Service (PaaS)的框架。当他们发现开发人员对这项技术越来越感兴趣时,他们将其作为开源发布,并自那时起宣布他们将完全专注于 Docker 技术的发展,这是一个好消息,因为这意味着平台将得到持续的支持和改进。
已经有许多旨在使分布式应用程序成为可能,甚至易于设置的工具和技术,但没有一个像 Docker 一样具有如此广泛的吸引力,这主要是因为它的跨平台性和对系统管理员和开发人员的友好性。在任何操作系统上都可以设置 Docker,无论是 Windows、OSX 还是 Linux,Docker 容器在任何地方都可以以相同的方式工作。这是非常强大的,因为它实现了一次编写,到处运行的工作流程。Docker 容器保证在开发桌面、裸机服务器、虚拟机、数据中心或云上以相同的方式运行。不再出现程序在开发人员的笔记本电脑上运行但在服务器上不运行的情况。
Docker 的工作流程的性质使得开发人员可以完全专注于构建应用程序并在容器内运行它们,而系统管理员可以专注于在部署中运行容器。这种角色的分离和一个单一的基础工具的存在使得代码管理和部署过程变得简单。
但是虚拟机不是已经提供了所有这些功能吗?
虚拟机(VMs)是完全虚拟化的。这意味着它们在彼此之间共享最少的资源,每个虚拟机都有其自己分配的资源集。虽然这允许对各个虚拟机进行细粒度配置,但最小的共享也意味着更大的资源使用、冗余的运行进程(需要运行整个操作系统!)和因此性能开销。
另一方面,Docker 建立在容器技术之上,它隔离一个进程并使其相信自己在独立的操作系统上运行。该进程仍然在与其主机相同的操作系统中运行,共享其内核。它使用了一个名为Another Unionfs(AUFS)的分层写时复制文件系统,它在容器之间共享操作系统的公共部分。更大的共享当然只能意味着更少的隔离,但 Linux 进程资源管理解决方案的巨大改进,如命名空间和 cgroups,已经使 Docker 实现了类似虚拟机的进程隔离,同时保持了非常小的资源占用。
让我们来看一下以下图片:
这是一个 Docker 与虚拟机的比较。容器与其他容器和进程共享主机的资源,而虚拟机必须为每个实例运行整个操作系统。
安装 Docker
Docker 在大多数主要 Linux 发行版的标准存储库中都有。我们将看一下 Ubuntu 14.04 和 12.04(Trusty 和 Precise)、Mac OSX 和 Windows 中 Docker 的安装程序。如果您目前使用的操作系统不在上述列表中,您可以在docs.docker.com/installation/#installation上查找您操作系统的说明。
在 Ubuntu 中安装 Docker
Ubuntu 从 Ubuntu 12.04 开始支持 Docker。请记住,您仍然需要 64 位操作系统才能运行 Docker。让我们来看一下 Ubuntu 14.04 的安装说明。
在 Ubuntu Trusty 14.04 LTS 中安装 Docker
Docker 作为一个软件包在 Ubuntu Trusty 版本的软件存储库中以docker.io的名称可用:
$ sudo apt-get update
$ sudo apt-get -y install docker.io
就是这样!您现在已经在系统上安装了 Docker。但是,由于命令已更名为docker.io,您将不得不使用docker.io而不是docker来运行所有 Docker 命令。
注意
该软件包的名称为docker.io,因为它与另一个名为docker的 KDE3/GNOME2 软件包冲突。如果您更愿意以docker运行命令,可以创建一个符号链接到/usr/local/bin目录。第二个命令将自动完成规则添加到 bash:
$ sudo ln -s /usr/bin/docker.io /usr/local/bin/docker
$ sudo sed -i '$acomplete -F _docker docker' \> /etc/bash_completion.d/docker.io
在 Ubuntu Precise 12.04 LTS 中安装 Docker
Ubuntu 12.04 带有较旧的内核(3.2),与 Docker 的一些依赖项不兼容。因此,我们需要升级它:
$ sudo apt-get update
$ sudo apt-get -y install linux-image-generic-lts-raring linux-headers-generic-lts-raring
$ sudo reboot
我们刚刚安装的内核内置了 AUFS,这也是 Docker 的要求。
现在让我们结束安装:
$ curl -s https://get.docker.io/ubuntu/ | sudo sh
这是一个用于简单安装的curl脚本。查看此脚本的各个部分将帮助我们更好地理解该过程:
- 首先,脚本检查我们的高级 软件包 工具(APT)系统是否能处理
httpsURL,并在无法处理时安装apt-transport-https:
# Check that HTTPS transport is available to APT
if [ ! -e /usr/lib/apt/methods/https ]; then apt-get update apt-get install -y apt-transport-https
fi
- 然后它将 Docker 存储库添加到我们的本地密钥链中:
$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
提示
您可能会收到一个警告,表示软件包不受信任。回答yes以继续安装。
- 最后,它将 Docker 存储库添加到 APT 源列表中,并更新并安装
lxc-docker软件包:
$ sudo sh -c "echo deb https://get.docker.io/ubuntu docker main\
> /etc/apt/sources.list.d/docker.list"
$ sudo apt-get update
$ sudo apt-get install lxc-docker
注意
0.9 版本之前的 Docker 对 LXC(Linux 容器)有严格依赖,因此无法安装在 OpenVZ 托管的 VM 上。但自 0.9 版本以来,执行驱动程序已从 Docker 核心中解耦,这使我们可以使用众多隔离工具之一,如 LXC、OpenVZ、systemd-nspawn、libvirt-lxc、libvirt-sandbox、qemu/kvm、BSD Jails、Solaris Zones,甚至 chroot!但是,它默认使用 Docker 自己的容器化引擎的执行驱动程序,称为 libcontainer,这是一个纯 Go 库,可以直接访问内核的容器 API,而无需任何其他依赖关系。
要使用任何其他容器化引擎,比如 LXC,您可以使用-e 标志,如下所示:$ docker -d -e lxc。
现在我们已经安装了 Docker,我们可以全速前进了!不过有一个问题:像 APT 这样的软件仓库通常滞后于时代,经常有较旧的版本。Docker 是一个快速发展的项目,在最近的几个版本中发生了很多变化。因此,建议始终安装最新版本。
升级 Docker
您可以根据 APT 仓库中的更新来升级 Docker。另一种(更好的)方法是从源代码构建。此方法的教程在标题为Inception: Docker in Docker的部分中。建议升级到最新的稳定版本,因为更新的版本可能包含关键的安全更新和错误修复。此外,本书中的示例假定 Docker 版本大于 1.0,而 Ubuntu 的标准仓库中打包了一个更旧的版本。
Mac OSX 和 Windows
Docker 依赖于 Linux 内核,因此我们需要在虚拟机中运行 Linux,并通过它安装和使用 Docker。Boot2Docker 是由 Docker Inc.构建的辅助应用程序,它安装了一个包含轻量级 Linux 发行版的虚拟机,专门用于运行 Docker 容器。它还带有一个客户端,提供与 Docker 相同的应用程序 接口 (API),但与在虚拟机中运行的docker守护程序进行交互,允许我们从 OSX/Windows 终端运行命令。要安装 Boot2Docker,请执行以下步骤:
-
从
boot2docker.io/下载适用于您操作系统的最新版本的 Boot2Docker。 -
安装镜像如下所示:
-
运行安装程序,它将安装 VirtualBox 和 Boot2Docker 管理工具。
运行 Boot2docker。第一次运行时会要求您输入安全 Shell (SSH)密钥密码。脚本的后续运行将连接您到虚拟机中的 shell 会话。如果需要,后续运行将初始化一个新的虚拟机并启动它。
或者,要运行 Boot2Docker,您也可以使用终端命令boot2docker。
$ boot2docker init # First run
$ boot2docker start
$ export DOCKER_HOST=tcp://$(boot2docker ip 2>/dev/null):2375
您只需要运行boot2docker init一次。它会要求您输入 SSH 密钥密码。随后,boot2docker ssh将使用此密码来验证 SSH 访问。
初始化 Boot2Docker 后,您随后可以使用boot2docker start和boot2docker stop命令。
DOCKER_HOST 是一个环境变量,设置后,指示 Docker 客户端 docker 守护程序的位置。端口转发规则设置为 boot2Docker VM 的端口 2375(docker 守护程序运行的位置)。您将需要在每个要在其中使用 Docker 的终端 shell 中设置此变量。
注意
Bash 允许您通过在 `或者$()中包含子命令来插入命令。这些将首先被评估,结果将被替换到外部命令中。
如果您是那种喜欢四处探索的人,Boot2Docker 的默认用户是 docker,密码是 tcuser。
boot2Docker 管理工具提供了几个命令:
$ boot2docker
Usage: boot2docker [<options>] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|delete|download|version} [<args>]
使用 boot2Docker 时,必须在终端会话中使 DOCKER_HOST 环境变量可用,以使 Docker 命令起作用。因此,如果您遇到 Post http:///var/run/docker.sock/v1.12/containers/create: dial unix /var/run/docker.sock: no such file or directory 错误,意味着环境变量未分配。当您打开新终端时很容易忘记设置此环境变量。对于 OSX 用户,为了简化操作,请将以下行添加到您的 .bashrc 或 .bash_profile shell 中:
alias setdockerhost='export DOCKER_HOST=tcp://$(boot2docker ip 2>/dev/null):2375'
现在,每当您打开一个新的终端或出现上述错误时,只需运行以下命令:
$ setdockerhost
此图像显示了当您登录到 Boot2Docker VM 后终端屏幕的外观。
升级 Boot2Docker
-
从
boot2docker.io/下载 OSX 的最新版本 Boot2Docker Installer。 -
运行安装程序,将更新 VirtualBox 和 Boot2Docker 管理工具。
要升级现有的虚拟机,请打开终端并运行以下命令:
$ boot2docker stop
$ boot2docker download
OpenStack
OpenStack** 是一款免费开源软件,可让您建立一个云。它主要用于部署公共和私有 Infrastructure as a Service (IaaS) 解决方案。它由一组相互关联的项目组成,用于云设置的不同组件,如计算调度程序、密钥链管理器、网络管理器、存储管理器、仪表板等等。
Docker 可以作为 OpenStack Nova Compute 的虚拟化驱动程序。Docker 对 OpenStack 的支持是从 Havana 版本开始引入的。
但是... 如何做到呢?
Nova 的 Docker 驱动程序嵌入了一个微型 HTTP 服务器,通过 UNIX TCP socket 与 Docker 引擎的内部 Representational State Transfer (REST) API 通信(稍后您将了解更多)。
Docker 有其自己的镜像仓库系统,称为 Docker-Registry,可以嵌入到 Glance(OpenStack 的镜像仓库)中以推送和拉取 Docker 镜像。Docker-Registry 可以作为 docker 容器或独立模式运行。
使用 DevStack 安装
如果您只是设置 OpenStack 并采用 DevStack 路线,那么配置设置以使用 Docker 很容易。
在运行 DevStack 路线的 stack.sh 脚本之前,请在 localrc 文件中配置 virtual driver 选项以使用 Docker:
VIRT_DRIVER=docker
然后从 devstack 目录运行 Docker 安装脚本。此脚本需要 socat 实用程序(通常由 stack.sh 脚本安装)。如果您尚未安装 socat 实用程序,请运行以下命令:
$ apt-get install socat
$ ./tools/docker/install_docker.sh
最后,从 devstack 目录运行 stack.sh 脚本:
$ ./stack.sh
手动为 OpenStack 安装 Docker
如果您已经设置了 OpenStack,或者 DevStack 方法不起作用,则可以手动安装 Docker:
- 首先,根据 Docker 的一个安装程序安装 Docker。
如果您正在将 docker 注册表与 Glance 服务放置在一起,请运行以下命令:
$ sudo yum -y install docker-registry
在 /etc/sysconfig/docker-registry 文件夹中,设置 REGISTRY_PORT 和 SETTINGS_FLAVOR 注册表如下:
$ export SETTINGS_FLAVOR=openstack
$ export REGISTRY_PORT=5042
在 docker 注册文件中,您还需要指定 OpenStack 认证变量。以下命令完成此操作:
$ source /root/keystonerc_admin
$ export OS_GLANCE_URL=http://localhost:9292
默认情况下,/etc/docker-registry.yml 为 openstack 配置设置了本地或替代 storage_path 路径为 /tmp。您可能希望将路径更改为更永久的位置:
openstack:
storage: glance
storage_alternate: local
storage_path: /var/lib/docker-registry
- 为了使 Nova 能够通过其本地套接字与 Docker 通信,请将
nova添加到docker组,并重新启动compute服务以接收更改:
$ usermod -G docker nova
$ service openstack-nova-compute restart
- 启动 Redis(Docker Registry 使用),如果尚未启动:
$ sudo service redis start
$ sudo chkconfig redis on
- 最后,启动注册表:
$ sudo service docker-registry start
$ sudo chkconfig docker-registry on
Nova 配置
Nova 需要配置为使用 virt Docker 驱动程序。
根据以下选项编辑 /etc/nova/nova.conf 配置文件:
[DEFAULT]
compute_driver = docker.DockerDriver
或者,如果您想使用您自己的 Docker-Registry,并且监听的端口不同于 5042,则可以覆盖以下选项:
docker_registry_default_port = 5042
Glance 配置
Glance 需要配置以支持 Docker 容器格式。只需在 Glance 配置文件中将 Docker 添加到容器格式列表中即可:
[DEFAULT]
container_formats = ami,ari,aki,bare,ovf,docker
提示
为了不破坏现有的 glance 安装,请保留默认格式。
Docker-OpenStack 流程
一旦您配置了 Nova 使用 docker 驱动程序,流程与任何其他驱动程序中的流程相同:
$ docker search hipache
Found 3 results matching your query ("hipache")
NAME DESCRIPTION
samalba/hipache https://github.com/dotcloud/hipache
然后,使用 Docker-Registry 位置标记图像并推送它:
$ docker pull samalba/hipache
$ docker tag samalba/hipache localhost:5042/hipache
$ docker push localhost:5042/hipache
推送引用了一个仓库:
[localhost:5042/hipache] (len: 1)
Sending image list
Pushing repository localhost:5042/hipache (1 tags)
Push 100% complete
在这种情况下,Docker-Registry(在一个端口映射为 5042 的 Docker 容器中运行)将图像推送到 Glance。从那里,Nova 可以访问它们,并且您可以使用 Glance Command-Line Interface(CLI)验证图像:
$ glance image-list
注意
只有具有 Docker 容器格式的图像才能启动。图像基本上包含容器文件系统的 tarball。
您可以使用 nova boot 命令引导实例:
$ nova boot --image "docker-busybox:latest" --flavor m1.tiny test
提示
使用的命令将是在图像中配置的命令。每个容器图像都可以为运行配置一个命令。驱动程序不会覆盖此命令。
一旦实例引导完成,它将在 nova list 中列出:
$ nova list
您还可以在 Docker 中查看相应的容器:
$ docker ps
Inception:构建 Docker 中的 Docker
虽然从标准仓库安装更容易,但它们通常包含较旧的版本,这意味着您可能会错过关键的更新或功能。保持更新的最佳方法是定期从公共 GitHub 仓库获取最新版本。传统上,从源代码构建软件是很痛苦的,只有实际从事项目工作的人才会这样做。但 Docker 不是这样。从 Docker 0.6 开始,就可以在 Docker 中构建 Docker。这意味着升级 Docker 就像在 Docker 中构建新版本并替换二进制文件一样简单。让我们看看如何做到这一点。
依赖关系
您需要在 64 位 Linux 机器(虚拟机或裸机)上安装以下工具才能构建 Docker:
-
**Git
-
**Make
Git** 是一个免费且开放源代码的分布式版本控制系统,旨在以速度和效率处理从小型到非常大型的项目。它在此用于克隆 Docker 公共源代码仓库。有关更多详细信息,请查看 git-scm.org。
make 实用程序是用于管理和维护计算机程序的软件工程工具。Make 在程序由许多组件文件组成时提供了最大的帮助。在这里,使用一个 Makefile 文件以一种可重复和一致的方式启动 Docker 容器。
从源代码构建 Docker
要在 Docker 中构建 Docker,我们首先会获取源代码,然后运行几个 make 命令,最终创建一个 docker 二进制文件,该文件将替换 Docker 安装路径中的当前二进制文件。
在终端中运行以下命令:
$ git clone https://git@github.com/dotcloud/docker
此命令将官方 Docker 源代码仓库从 Github 仓库克隆到名为 docker 的目录中:
$ cd docker
$ sudo make build
这将准备开发环境并安装创建二进制文件所需的所有依赖项。在第一次运行时可能需要一些时间,所以你可以去喝杯咖啡。
提示
如果遇到任何难以调试的错误,您可以随时转到 #docker 上的 freenode IRC。开发人员和 Docker 社区都非常乐意帮助。
现在我们已经准备好编译二进制文件了:
$ sudo make binary
这将编译一个二进制文件,并将其放置在 ./bundles/<version>-dev/binary/ 目录中。然后!您现在有一个准备就绪的 Docker 新版本。
但在替换现有二进制文件之前,请运行测试:
$ sudo make test
如果测试通过,则可以安全地用您刚刚编译的二进制文件替换当前的二进制文件。停止 docker 服务,创建现有二进制文件的备份,然后将新鲜出炉的二进制文件复制到其位置:
$ sudo service docker stop
$ alias wd='which docker'
$ sudo cp $(wd) $(wd)_
$ sudo cp $(pwd)/bundles/<version>-dev/binary/docker-<version>-dev $(wd)
$ sudo service docker start
恭喜!您现在拥有最新版本的 Docker 运行。
提示
OSX 和 Windows 用户可以按照 SSH 进入 boot2Docker VM 的相同步骤进行操作。
验证安装
要验证您的安装是否成功,请在终端控制台中运行以下命令:
$ docker run -i -t ubuntu echo Hello World!
docker run 命令使用ubuntu基础镜像启动容器。由于这是您首次启动ubuntu容器,容器的输出将类似于这样:
Unable to find image 'ubuntu' locally
Pulling repository ubuntu
e54ca5efa2e9: Download complete
511136ea3c5a: Download complete
d7ac5e4f1812: Download complete
2f4b4d6a4a06: Download complete
83ff768040a0: Download complete
6c37f792ddac: Download complete
Hello World!
当您发出docker run ubuntu命令时,Docker 将在本地查找ubuntu镜像,如果找不到,它将从公共docker注册表下载ubuntu镜像。您还将看到它显示正在拉取依赖层。
这意味着它正在下载文件系统层。默认情况下,Docker 使用 AUFS,一种分层的写时复制文件系统,这意味着容器镜像的文件系统是多个只读文件系统层的结合体。而这些层是在运行的容器之间共享的。如果你启动了一个会写入此文件系统的操作,它将创建一个新的层,该层将是底层层和新数据的差异。共享常见层意味着只有第一个容器会占用大量内存,而后续容器将占用微不足道的内存,因为它们将共享只读层。这意味着即使在相对性能较低的笔记本电脑上,你也可以运行数百个容器。
一旦镜像完全下载完成,它将启动容器并在您的控制台中回显Hello``World!。这是 Docker 容器的另一个显著特点。每个容器都与一个命令关联,并且应该运行该命令。请记住,Docker 容器不像虚拟机那样虚拟化整个操作系统。每个docker容器只接受一个单一命令,并在一个独立环境中运行它。
有用的提示
以下是两个有用的提示,以后可能会为您节省大量麻烦。第一个显示了如何为 Docker 客户端提供非根访问权限,第二个显示了如何配置 Ubuntu 防火墙规则以启用转发网络流量。
注意
如果您使用的是 Boot2Docker,则无需遵循这些步骤。
给予非根访问权限
创建一个名为docker的组,并将您的用户添加到该组,以避免每个docker命令都需要添加sudo前缀。默认情况下,您需要使用sudo前缀运行docker命令的原因是docker守护程序需要以root权限运行,但 docker 客户端(您运行的命令)不需要。因此,通过创建一个docker组,您可以在不使用sudo前缀的情况下运行所有客户端命令,而守护程序则以root权限运行:
$ sudo groupadd docker # Adds the docker group
$ sudo gpasswd -a $(whoami) docker # Adds the current user to the group
$ sudo service docker restart
你可能需要退出并重新登录以使更改生效。
UFW 设置
Docker 使用桥接来管理容器中的网络。简化防火墙(UFW)是 Ubuntu 中的默认防火墙工具。它会拒绝所有转发流量。您需要像这样启用转发:
$ sudo vim /etc/default/ufw
# Change:
# DEFAULT_FORWARD_POLICY="DROP"
# to
DEFAULT_FORWARD_POLICY="ACCEPT"
运行以下命令重新加载防火墙:
$ sudo ufw reload
或者,如果你想要能够从其他主机访问你的容器,那么你应该在 Docker 端口(default 2375)上启用入站连接:
$ sudo ufw allow 2375/tcp
提示
下载示例代码
您可以从您在www.packtpub.com的帐户中下载示例代码文件,用于您购买的所有 Packt Publishing 图书。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便文件直接通过电子邮件发送给您
总结
我希望这个介绍性的章节让你着迷于 Docker。接下来的章节将带你进入 Docker 的世界,并试图用它的神奇之处来迷住你。
在本章中,您学习了一些关于 Docker 的历史和基础知识,以及它的工作原理。我们看到了它与虚拟机的不同之处以及优势。
然后,我们继续在我们的开发环境中安装 Docker,无论是 Ubuntu、Mac 还是 Windows。然后我们看到如何用 Docker 替换 OpenStack 的 hypervisor。后来,我们在 Docker 中构建了 Docker 源代码!说到吃自己的狗粮!
最后,我们下载了我们的第一个镜像并运行了我们的第一个容器。现在你可以拍拍自己的背,继续下一章,在那里我们将深入介绍主要的 Docker 命令,并看看我们如何创建自己的镜像。
第二章:Docker CLI 和 Dockerfile
在上一章中,我们在开发环境中设置了 Docker 并运行了我们的第一个容器。在本章中,我们将探索 Docker 命令行界面。在本章后面,我们将看到如何使用 Dockerfiles 创建自己的 Docker 镜像以及如何自动化这个过程。
在本章中,我们将涵盖以下主题:
-
Docker 术语
-
Docker 命令
-
Dockerfiles
-
Docker 工作流程-拉取-使用-修改-提交-推送工作流程
-
自动化构建
Docker 术语
在我们开始激动人心的 Docker 领域之前,让我们更好地了解本书中将使用的 Docker 术语。与 VM 镜像类似,Docker 镜像是系统的快照。VM 镜像和 Docker 镜像之间的区别在于 VM 镜像可以运行服务,而 Docker 镜像只是文件系统的快照,这意味着虽然你可以配置镜像来拥有你喜欢的软件包,但你只能在容器中运行一个命令。不过不要担心,由于限制是一个命令,而不是一个进程,所以有方法让 Docker 容器几乎可以执行任何 VM 实例可以执行的任务。
Docker 还实现了类似 Git 的分布式版本管理系统,用于 Docker 镜像。镜像可以存储在本地和远程的仓库中。其功能和术语大量借鉴自 Git-快照被称为提交,你拉取一个镜像仓库,你将本地镜像推送到仓库,等等。
Docker 容器
一个 Docker 容器可以与虚拟机的实例相关联。它运行沙盒化的进程,这些进程与主机共享相同的内核。术语容器来自于集装箱的概念。其想法是你可以从开发环境将容器运送到部署环境,容器中运行的应用程序无论在哪里运行,都会表现出相同的行为。
以下图片显示了 AUFS 的层次结构:
这与集装箱的情境类似,集装箱在交付之前保持密封,但可以在装卸货物、堆叠和运输之间进行操作。
容器中进程的可见文件系统基于 AUFS(尽管您也可以配置容器以使用不同的文件系统)。AUFS 是一种分层文件系统。这些层都是只读的,这些层的合并是进程可见的。但是,如果进程在文件系统中进行更改,将创建一个新层,该层代表原始状态和新状态之间的差异。当您从此容器创建图像时,这些层将被保留。因此,可以基于现有图像构建新图像,创建一个非常方便的图像层次模型。
Docker 守护程序
docker守护程序是管理容器的进程。很容易将其与 Docker 客户端混淆,因为相同的二进制文件用于运行这两个进程。然而,docker守护程序需要root权限,而客户端不需要。
不幸的是,由于docker守护程序以 root 权限运行,它也引入了一个攻击向量。阅读docs.Docker.com/articles/security/获取更多详细信息。
Docker 客户端
Docker 客户端是与docker守护程序交互以启动或管理容器的工具。Docker 使用 RESTful API 在客户端和守护程序之间进行通信。
注意
REST 是一种架构风格,由一组协调的架构约束应用于分布式超媒体系统中的组件、连接器和数据元素。简而言之,RESTful 服务使用标准的 HTTP 方法,如GET、POST、PUT和DELETE方法。
Dockerfile
Dockerfile 是一个用**特定领域语言(DSL)**编写的文件,其中包含设置 Docker 镜像的指令。可以将其视为 Docker 的 Makefile 等效文件。
Docker 注册表
这是 Docker 社区发布的所有 Docker 镜像的公共存储库。您可以自由地从该注册表中拉取镜像,但要推送镜像,您必须在hub.docker.com注册。Docker 注册表和 Docker Hub 是由 Docker Inc.运营和维护的服务,并提供无限免费的存储库。您也可以购买私人存储库。
Docker 命令
现在让我们在 Docker CLI 上动手。我们将看一下最常用的命令及其用法。Docker 命令是模仿 Linux 和 Git 的,所以如果您使用过其中任何一个,您将发现在 Docker 中也能得心应手。
这里只提到了最常用的选项。要获取完整的参考信息,您可以查看官方文档docs.docker.com/reference/commandline/cli/。
守护程序命令
如果您通过标准存储库安装了docker守护程序,则启动docker守护程序的命令将被添加到init脚本中,以便在启动时自动启动服务。否则,您将需要自己运行docker守护程序,以使客户端命令正常工作。
现在,在启动守护程序时,您可以使用控制域 名 系统(DNS)配置、存储驱动程序和容器的执行驱动程序的参数来运行它:
$ export DOCKER_HOST="tcp://0.0.0.0:2375"
$ Docker -d -D -e lxc -s btrfs –-dns 8.8.8.8 –-dns-search example.com
注意
只有在您想要自己启动守护程序时才需要这些。否则,您可以使用$ sudo service Docker start启动docker守护程序。对于 OSX 和 Windows,您需要运行第一章中提到的命令,安装 Docker。
以下表格描述了各种标志:
| 标志 | 说明 |
|---|
|
-d
| 这以守护程序运行 Docker。 |
|---|
|
-D
| 这以调试模式运行 Docker。 |
|---|
|
-e [option]
这是要使用的执行驱动程序。默认的执行驱动程序是本机,它使用libcontainer。 |
|---|
|
-s [option]
| 这会强制 Docker 使用不同的存储驱动程序。默认值为"",Docker 使用 AUFS。 |
|---|
|
--dns [option(s)]
| 这为所有 Docker 容器设置 DNS 服务器(或服务器)。 |
|---|
|
--dns-search [option(s)]
| 这为所有 Docker 容器设置 DNS 搜索域(或域)。 |
|---|
|
-H [option(s)]
这是要绑定的套接字(或套接字)。可以是一个或多个tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd。 |
|---|
如果同时运行多个docker守护程序,则客户端将遵循DOCKER_HOST参数设置的值。您还可以使用-H标志使其连接到特定的守护程序。
考虑这个命令:
$ docker -H tcp://0.0.0.0:2375 run -it ubuntu /bin/bash
前面的命令与以下命令相同:
$ DOCKER_HOST="tcp://0.0.0.0:2375" docker run -it ubuntu /bin/bash
版本命令
version命令打印版本信息:
$ docker -vDocker version 1.1.1, build bd609d2
信息命令
info命令打印docker守护程序配置的详细信息,例如执行驱动程序、正在使用的存储驱动程序等:
$ docker info # The author is running it in boot2docker on OSX
Containers: 0
Images: 0
Storage Driver: aufs
Root Dir: /mnt/sda1/var/lib/docker/aufs
Dirs: 0
Execution Driver: native-0.2
Kernel Version: 3.15.3-tinycore64
Debug mode (server): true
Debug mode (client): false
Fds: 10
Goroutines: 10
EventsListeners: 0
Init Path: /usr/local/bin/docker
Sockets: [unix:///var/run/docker.sock tcp://0.0.0.0:2375]
run 命令
run 命令是我们将经常使用的命令。它用于运行 Docker 容器:
$ docker run [options] IMAGE [command] [args]
| 标志 | 解释 |
|---|
|
-a, --attach=[]
附加到stdin,stdout或stderr文件(标准输入,输出和错误文件)。 |
|---|
|
-d, --detach
| 在后台运行容器。 |
|---|
|
-i, --interactive
以交互模式运行容器(保持stdin文件打开)。 |
|---|
|
-t, --tty
分配伪tty标志(如果要附加到容器的终端,则需要)。 |
|---|
|
-p, --publish=[]
将容器的端口发布到主机(ip:hostport:containerport)。 |
|---|
|
--rm
退出时自动删除容器(不能与-d标志一起使用)。 |
|---|
|
--privileged
| 这为该容器提供了额外的特权。 |
|---|
|
-v, --volume=[]
绑定挂载卷(从主机=>/host:/container;从 docker=>/container)。 |
|---|
|
--volumes-from=[]
| 从指定的容器中挂载卷。 |
|---|
|
-w, --workdir=""
| 这是容器内的工作目录。 |
|---|
|
--name=""
| 为容器分配一个名称。 |
|---|
|
-h, --hostname=""
| 为容器分配一个主机名。 |
|---|
|
-u, --user=""
| 这是容器应该运行的用户名或 UID。 |
|---|
|
-e, --env=[]
| 设置环境变量。 |
|---|
|
--env-file=[]
| 从新的行分隔文件中读取环境变量。 |
|---|
|
--dns=[]
| 设置自定义 DNS 服务器。 |
|---|
|
--dns-search=[]
| 设置自定义 DNS 搜索域。 |
|---|
|
--link=[]
添加到另一个容器的链接(name:alias)。 |
|---|
|
-c, --cpu-shares=0
| 这是此容器的相对 CPU 份额。 |
|---|
|
--cpuset=""
| 这些是允许执行的 CPU;从 0 开始。(例如,0 到 3)。 |
|---|
|
-m, --memory=""
| 这是此容器的内存限制`(<b | k | m | g>`)。 |
|---|
|
--restart=""
| (v1.2+)指定容器崩溃时的重启策略。 |
|---|
|
--cap-add=""
| (v1.2+)这向容器授予一个功能(参考第四章,“安全最佳实践”)。 |
|---|
|
--cap-drop=""
| (v1.2+)这将把一个功能限制到一个容器中(参考第四章,“安全最佳实践”)。 |
|---|
|
--device=""
| (v1.2+)这在容器上挂载设备。 |
|---|
在运行容器时,重要的是要记住,容器的生命周期与启动容器时运行的命令的生命周期相关联。现在尝试运行这个:
$ docker run -dt ubuntu ps
b1d037dfcff6b076bde360070d3af0d019269e44929df61c93dfcdfaf29492c9
$ docker attach b1d037
2014/07/16 16:01:29 You cannot attach to a stopped container, start it first
发生了什么?当我们运行简单命令ps时,容器运行了该命令并退出。因此,我们得到了一个错误。
注意
attach命令将标准输入和输出附加到正在运行的容器上。
这里还有一条重要的信息,您不需要为所有需要容器 ID 的命令使用完整的 64 字符 ID。前面的几个字符就足够了。使用与以下代码中显示的相同示例:
$ docker attach b1d03
2014/07/16 16:09:39 You cannot attach to a stopped container, start it first
$ docker attach b1d0
2014/07/16 16:09:40 You cannot attach to a stopped container, start it first
$ docker attach b1d
2014/07/16 16:09:42 You cannot attach to a stopped container, start it first
$ docker attach b1
2014/07/16 16:09:44 You cannot attach to a stopped container, start it first
$ docker attach b
2014/07/16 16:09:45 Error: No such container: b
一个更方便的方法是自己为容器命名:
$ docker run -dit --name OD-name-example ubuntu /bin/bash
1b21af96c38836df8a809049fb3a040db571cc0cef000a54ebce978c1b5567ea
$ docker attach OD-name-example
root@1b21af96c388:/#
-i标志是必要的,以便在容器中进行任何交互,-t标志是必要的,以创建一个伪终端。
前面的示例还让我们意识到,即使我们退出容器,它仍处于stopped状态。也就是说,我们可以重新启动容器,并保留其文件系统层。您可以通过运行以下命令来查看:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
eb424f5a9d3f ubuntu:latest ps 1 hour ago Exited OD-name-example
虽然这很方便,但很快您的主机磁盘空间可能会耗尽,因为保存了越来越多的容器。因此,如果您要运行一个一次性容器,可以使用--rm标志运行它,这将在进程退出时删除容器:
$ docker run --rm -it --name OD-rm-example ubuntu /bin/bash
root@0fc99b2e35fb:/# exit
exit
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
运行服务器
现在,对于我们的下一个示例,我们将尝试运行一个 Web 服务器。选择此示例是因为 Docker 容器最常见的实际用例是运行 Web 应用程序:
$ docker run -it –-name OD-pythonserver-1 --rm python:2.7 \
python -m SimpleHTTPServer 8000;
Serving HTTP on 0.0.0.0 port 8000
现在我们知道问题所在;我们在一个容器中运行了一个服务器,但由于 Docker 动态分配了容器的 IP,这使事情变得困难。但是,我们可以将容器的端口绑定到主机的端口,Docker 会负责转发网络流量。现在让我们再次尝试这个命令,加上-p标志:
$ docker run -p 0.0.0.0:8000:8000 -it --rm –-name OD-pythonserver-2 \ python:2.7 python -m SimpleHTTPServer 8000;
Serving HTTP on 0.0.0.0 port 8000 ...
172.17.42.1 - - [18/Jul/2014 14:25:46] "GET / HTTP/1.1" 200 -
现在打开浏览器,转到http://localhost:8000。大功告成!
如果您是 OS X 用户,并且意识到无法访问http://localhost:8000,那是因为 VirtualBox 尚未配置为响应对 boot2Docker VM 的网络地址转换(NAT)请求。将以下函数添加到您的别名文件(bash_profile或.bashrc)将节省很多麻烦:
natboot2docker () { VBoxManage controlvm boot2docker-vm natpf1 \
"$1,tcp,127.0.0.1,$2,,$3"; }
removeDockerNat() {
VBoxManage modifyvm boot2docker-vm \
--natpf1 delete $1;
}
之后,您应该能够使用$ natboot2docker mypythonserver 8000 8000命令来访问 Python 服务器。但是请记住,在完成后运行$ removeDockerDockerNat mypythonserver命令。否则,当您下次运行 boot2Docker VM 时,您将面临一个错误,它将不允许您获取 IP 地址或ssh脚本:
$ boot2docker ssh
ssh_exchange_identification: Connection closed by remote host
2014/07/19 11:55:09 exit status 255
您的浏览器现在显示容器的/root路径。如果您想要提供主机的目录怎么办?让我们尝试挂载设备:
root@eb53f7ec79fd:/# mount -t tmpfs /dev/random /mnt
mount: permission denied
正如您所见,mount命令不起作用。实际上,除非包括--privileged标志,否则大多数潜在危险的内核功能都会被禁用。
但是,除非您知道自己在做什么,否则永远不要使用此标志。Docker 提供了一种更简单的方式来绑定挂载主机卷和使用-v和–volumes选项绑定挂载主机卷。让我们在我们当前所在的目录中再次尝试这个例子:
$ docker run -v $(pwd):$(pwd) -p 0.0.0.0:8000:8000 -it –rm \
--name OD-pythonserver-3 python:2.7 python -m SimpleHTTPServer 8000;
Serving HTTP on 0.0.0.0 port 8000 ...
10.0.2.2 - - [18/Jul/2014 14:40:35] "GET / HTTP/1.1" 200 -
现在,您已经将您从中运行命令的目录绑定到了容器。但是,当您访问容器时,仍然会得到容器根目录的目录列表。为了提供已绑定到容器的目录,让我们使用-w标志将其设置为容器的工作目录(容器化进程运行的目录):
$ docker run -v $(pwd):$(pwd) -w $(pwd) -p 0.0.0.0:8000:8000 -it \ --name OD-pythonserver-4 python:2.7 python -m SimpleHTTPServer 8000;
Serving HTTP on 0.0.0.0 port 8000 ...
10.0.2.2 - - [18/Jul/2014 14:51:35] "GET / HTTP/1.1" 200 -
注意
Boot2Docker 用户目前还无法利用这一功能,除非您使用了增强功能并设置了共享文件夹,可以在medium.com/boot2docker-lightweight-linux-for-docker/boot2docker-together-with-virtualbox-guest-additions-da1e3ab2465c找到相关指南。尽管这种解决方案有效,但这是一种 hack 方法,不建议使用。与此同时,Docker 社区正在积极寻找解决方案(请查看 boot2Docker GitHub 存储库中的问题#64和 Docker 存储库中的问题#4023)。
现在,http://localhost:8000将提供您当前正在运行的目录,但是从 Docker 容器中提供。但要小心,因为您所做的任何更改也会写入主机的文件系统中。
提示
自 v1.1.1 版本开始,您可以使用$ docker run -v /:/my_host:ro ubuntu ls /my_host将主机的根目录绑定到容器,但是禁止在容器的/路径上进行挂载。
卷可以选择地以:ro或:rw命令作为后缀,以只读或读写模式挂载卷。默认情况下,卷以与主机相同的模式(读写或只读)挂载。
此选项主要用于挂载静态资产和写入日志。
但是,如果我想挂载外部设备呢?
在 v1.2 之前,您必须在主机中挂载设备,并在特权容器中使用-v标志进行绑定挂载,但是 v1.2 添加了一个--device标志,您可以使用它来挂载设备,而无需使用--privileged标志。
例如,要在容器中使用网络摄像头,请运行此命令:
$ docker run --device=/dev/video0:/dev/video0
Docker v1.2 还添加了一个--restart标志,用于为容器指定重新启动策略。目前有三种重新启动策略:
-
no:如果容器死掉,则不重新启动(默认)。 -
on-failure:如果以非零退出代码退出,则重新启动容器。它还可以接受一个可选的最大重新启动计数(例如,on-failure:5)。 -
always:无论返回的退出代码是什么,都始终重新启动容器。
以下是一个无限重新启动的示例:
$ docker run --restart=always code.it
下一行用于在放弃之前尝试五次:
$ docker run --restart=on-failure:5 code.it
search 命令
search命令允许我们在公共注册表中搜索 Docker 镜像。让我们搜索与 Python 相关的所有镜像:
$ docker search python | less
pull 命令
pull命令用于从注册表中拉取镜像或仓库。默认情况下,它们从公共 Docker 注册表中拉取,但如果您运行自己的注册表,也可以从中拉取它们:
$ docker pull python # pulls repository from Docker Hub
$ docker pull python:2.7 # pulls the image tagged 2.7
$ docker pull <path_to_registry>/<image_or_repository>
start 命令
我们在讨论docker run时看到,容器状态在退出时会被保留,除非明确删除。docker start命令用于启动已停止的容器:
$ docker start [-i] [-a] <container(s)>
考虑以下start命令的示例:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
e3c4b6b39cff ubuntu:latest python -m 1h ago Exited OD-pythonserver-4
81bb2a92ab0c ubuntu:latest /bin/bash 1h ago Exited evil_rosalind
d52fef570d6e ubuntu:latest /bin/bash 1h ago Exited prickly_morse
eb424f5a9d3f ubuntu:latest /bin/bash 20h ago Exited OD-name-example
$ docker start -ai OD-pythonserver-4
Serving HTTP on 0.0.0.0 port 8000
选项的含义与docker run命令相同。
stop 命令
stop 命令通过发送SIGTERM信号然后在宽限期之后发送SIGKILL信号来停止正在运行的容器:
注意
SIGTERM和SIGKILL是 Unix 信号。信号是 Unix、类 Unix 和其他符合 POSIX 的操作系统中使用的一种进程间通信形式。SIGTERM信号指示进程终止。SIGKILL信号用于强制终止进程。
docker run -dit --name OD-stop-example ubuntu /bin/bash
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
679ece6f2a11 ubuntu:latest /bin/bash 5h ago Up 3s OD-stop-example
$ docker stop OD-stop-example
OD-stop-example
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
您还可以指定-t标志或--time标志,允许您设置等待时间。
restart 命令
restart命令重新启动正在运行的容器:
$ docker run -dit --name OD-restart-example ubuntu /bin/bash
$ sleep 15s # Suspends execution for 15 seconds
$ docker ps
CONTAINER ID IMAGE COMMAND STATUS NAMES
cc5d0ae0b599 ubuntu:latest /bin/bash Up 20s OD-restart-example
$ docker restart OD-restart-example
$ docker ps
CONTAINER ID IMAGE COMMAND STATUS NAMES
cc5d0ae0b599 ubuntu:latest /bin/bash Up 2s OD-restart-example
如果您观察状态,您会注意到容器已经重新启动。
rm 命令
rm命令用于删除 Docker 容器:
$ Docker ps -a # Lists containers including stopped ones
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
cc5d0ae0b599 ubuntu /bin/bash 6h ago Exited OD-restart-example
679ece6f2a11 ubuntu /bin/bash 7h ago Exited OD-stop-example
e3c4b6b39cff ubuntu /bin/bash 9h ago Exited OD-name-example
在我们的冒险之后,似乎有很多容器剩下。让我们移除其中一个:
$ dockerDocker rm OD-restart-example
cc5d0ae0b599
我们还可以组合两个 Docker 命令。让我们将docker ps -a -q命令(打印docker ps -a中容器的 ID 参数)和docker rm命令结合起来,一次性删除所有容器:
$ docker rm $(docker ps -a -q)
679ece6f2a11
e3c4b6b39cff
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES
首先对docker ps -a -q命令进行评估,然后输出由docker rm命令使用。
ps 命令
ps命令用于列出容器。它的使用方式如下:
$ docker ps [option(s)]
| 标志 | 解释 |
|---|
|
-a, --all
| 这显示所有容器,包括已停止的容器。 |
|---|
|
-q, --quiet
| 这仅显示容器 ID 参数。 |
|---|
|
-s, --size
| 这打印出容器的大小。 |
|---|
|
-l, --latest
| 这只显示最新的容器(包括已停止的容器)。 |
|---|
|
-n=""
| 这显示最后n个容器(包括已停止的容器)。其默认值为-1。 |
|---|
|
--before=****""
| 这显示了在指定 ID 或名称之前创建的容器。它包括已停止的容器。 |
|---|
|
--after=""
| 这显示了在指定 ID 或名称之后创建的容器。它包括已停止的容器。 |
|---|
docker ps命令默认只显示正在运行的容器。要查看所有容器,请运行docker ps -a命令。要仅查看容器 ID 参数,请使用-q标志运行它。
logs 命令
logs命令显示容器的日志:
Let us look at the logs of the python server we have been running
$ docker logs OD-pythonserver-4
Serving HTTP on 0.0.0.0 port 8000 ...
10.0.2.2 - - [18/Jul/2014 15:06:39] "GET / HTTP/1.1" 200 -
^CTraceback (most recent call last):
File ...
...
KeyboardInterrupt
你还可以提供一个--tail参数来跟踪容器运行时的输出。
inspect 命令
inspect命令允许你获取容器或镜像的详细信息。它将这些详细信息作为 JSON 数组返回:
$ Docker inspect ubuntu # Running on an image
[{
"Architecture": "amd64",
"Author": "",
"Comment": "",
.......
.......
.......
"DockerVersion": "0.10.0",
"Id": "e54ca5efa2e962582a223ca9810f7f1b62ea9b5c3975d14a5da79d3bf6020f37",
"Os": "linux",
"Parent": "6c37f792ddacad573016e6aea7fc9fb377127b4767ce6104c9f869314a12041e",
"Size": 178365
}]
同样,对于一个容器,我们运行以下命令:
$ Docker inspect OD-pythonserver-4 # Running on a container
[{
"Args": [
"-m",
"SimpleHTTPServer",
"8000"
],
......
......
"Name": "/OD-pythonserver-4",
"NetworkSettings": {
"Bridge": "Docker0",
"Gateway": "172.17.42.1",
"IPAddress": "172.17.0.11",
"IPPrefixLen": 16,
"PortMapping": null,
"Ports": {
"8000/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8000"
}
]
}
},
......
......
"Volumes": {
"/home/Docker": "/home/Docker"
},
"VolumesRW": {
"/home/Docker": true
}
}]
Docker inspect 提供了关于容器或镜像的所有低级信息。在上面的例子中,找出容器的 IP 地址和暴露的端口,并向IP:port发出请求。你会发现你直接访问了在容器中运行的服务器。
然而,手动查看整个 JSON 数组并不是最佳选择。因此,inspect命令提供了一个标志-f(或--follow标志),允许你使用Go模板精确地指定你想要的内容。例如,如果你只想获取容器的 IP 地址,运行以下命令:
$ docker inspect -f '{{.NetworkSettings.IPAddress}}' \
OD-pythonserver-4;
172.17.0.11
{{.NetworkSettings.IPAddress}}是在 JSON 结果上执行的Go模板。Go模板非常强大,你可以在golang.org/pkg/text/template/上列出一些你可以用它们做的事情。
top 命令
top命令显示容器中正在运行的进程及其统计信息,模仿 Unix 的top命令。
让我们下载并运行ghost博客平台,并查看其中运行的进程:
$ docker run -d -p 4000:2368 --name OD-ghost dockerfile/ghost
ece88c79b0793b0a49e3d23e2b0b8e75d89c519e5987172951ea8d30d96a2936
$ docker top OD-ghost-1
PID USER COMMAND
1162 root bash /ghost-start
1180 root npm
1186 root sh -c node index
1187 root node index
是的!我们只需一条命令就设置了我们自己的ghost博客。这带来了另一个微妙的优势,并展示了可能是未来趋势的东西。现在,通过 TCP 端口暴露其服务的每个工具都可以被容器化,并在其自己的沙盒世界中运行。你只需要暴露它的端口并将其绑定到你的主机端口。你不需要担心安装、依赖关系、不兼容性等,卸载将是干净的,因为你只需要停止所有的容器并删除镜像。
注意
Ghost 是一个开源的发布平台,设计精美,易于使用,对所有人免费。它是用 Node.js 编写的,是一个服务器端 JavaScript 执行引擎。
附加命令
attach命令用于附加到正在运行的容器。
让我们启动一个带有 Node.js 的容器,将 node 交互式 shell 作为守护进程运行,然后稍后附加到它。
注意
Node.js 是一个事件驱动的、异步 I/O 的 Web 框架,它在 Google 的 V8 运行环境上运行用 JavaScript 编写的应用程序。
带有 Node.js 的容器如下:
$ docker run -dit --name OD-nodejs shykes/nodejs node
8e0da647200efe33a9dd53d45ea38e3af3892b04aa8b7a6e167b3c093e522754
$ docker attach OD-nodejs
console.log('Docker rocks!');Docker rocks!
杀死命令
kill命令会杀死一个容器,并向容器中运行的进程发送SIGTERM信号:
Let us kill the container running the ghost blog.
$ docker kill OD-ghost-1
OD-ghost-1
$ docker attach OD-ghost-1 # Verification
2014/07/19 18:12:51 You cannot attach to a stopped container, start it first
cp 命令
cp命令将文件或文件夹从容器的文件系统复制到主机路径。路径是相对于文件系统的根目录的。
是时候玩一些游戏了。首先,让我们用/bin/bash命令运行一个 Ubuntu 容器:
$ docker run -it –name OD-cp-bell ubuntu /bin/bash
现在,在容器内部,让我们创建一个带有特殊名称的文件:
# touch $(echo -e '\007')
\ 007字符是 ASCIIBEL字符,当在终端上打印时会响铃系统。你可能已经猜到我们要做什么了。所以让我们打开一个新的终端,并执行以下命令将这个新创建的文件复制到主机:
$ docker cp OD-cp-bell:/$(echo -e '\007') $(pwd)
提示
要使docker cp命令工作,容器路径和主机路径都必须完整,所以不要使用.、,、*等快捷方式。
所以我们在容器中创建了一个文件名为BEL字符的空文件。然后我们将文件复制到主机容器中的当前目录。只剩最后一步了。在执行docker cp命令的主机标签中,运行以下命令:
$ echo *
你会听到系统铃声响起!我们本可以从容器中复制任何文件或目录到主机。但玩一些游戏也无妨!
注意
如果您觉得这很有趣,您可能会喜欢阅读www.dwheeler.com/essays/fixing-unix-linux-filenames.html。这是一篇很棒的文章,讨论了文件名中的边缘情况,这可能会在程序中引起简单到复杂的问题。
端口命令
port命令查找绑定到容器中公开端口的公共端口:
$ docker port CONTAINER PRIVATE_PORT
$ docker port OD-ghost 2368
4000
Ghost 在2368端口运行一个服务器,允许您编写和发布博客文章。在示例中,我们将主机端口绑定到OD-ghost容器的端口2368。
运行您自己的项目
到目前为止,我们已经相当熟悉基本的 Docker 命令。让我们提高赌注。在接下来的几个命令中,我将使用我的一个副业项目。请随意使用您自己的项目。
让我们首先列出我们的要求,以确定我们必须传递给docker run命令的参数。
我们的应用程序将在 Node.js 上运行,因此我们将选择维护良好的dockerfile/nodejs镜像来启动我们的基础容器:
-
我们知道我们的应用程序将绑定到端口
8000,因此我们将将端口暴露给主机的8000端口。 -
我们需要为容器指定一个描述性名称,以便我们可以在将来的命令中引用它。在这种情况下,让我们选择应用程序的名称:
$ docker run -it --name code.it dockerfile/nodejs /bin/bash
[ root@3b0d5a04cdcd:/data ]$ cd /home
[ root@3b0d5a04cdcd:/home ]$
一旦您启动了容器,您需要检查应用程序的依赖项是否已经可用。在我们的情况下,除了 Node.js 之外,我们只需要 Git,它已经安装在dockerfile/nodejs镜像中。
既然我们的容器已经准备好运行我们的应用程序,剩下的就是获取源代码并进行必要的设置来运行应用程序:
$ git clone https://github.com/shrikrishnaholla/code.it.git
$ cd code.it && git submodule update --init --recursive
这将下载应用程序中使用的插件的源代码。
然后运行以下命令:
$ npm install
现在所有运行应用程序所需的节点模块都已安装。
接下来,运行此命令:
$ node app.js
现在您可以转到localhost:8000来使用该应用程序。
差异命令
diff命令显示容器与其基于的镜像之间的差异。在这个例子中,我们正在运行一个带有code.it的容器。在一个单独的标签中,运行此命令:
$ docker diff code.it
C /home
A /home/code.it
...
提交命令
commit命令使用容器的文件系统创建一个新的镜像。就像 Git 的commit命令一样,您可以设置描述镜像的提交消息:
$ docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
| 标志 | 解释 |
|---|
|
-p, --pause
| 这在提交期间暂停容器(从 v1.1.1+开始可用)。 |
|---|
|
-m, --message=""
| 这是提交消息。它可以是对图像功能的描述。 |
|---|
|
-a, --author=""
| 这显示了作者的详细信息。 |
|---|
例如,让我们使用这个命令来提交我们设置的容器:
$ docker commit -m "Code.it – A browser based text editor and interpreter" -a "Shrikrishna Holla <s**a@gmail.com>" code.it shrikrishna/code.it:v1
提示
如果您正在复制这些示例,请替换作者详细信息和图像名称的用户名部分。
输出将是一个冗长的图像 ID。如果您仔细查看命令,我们已经命名了图像shrikrishna/code.it:v1。这是一个约定。图像/存储库名称的第一部分(斜杠之前)是作者的 Docker Hub 用户名。第二部分是预期的应用程序或图像名称。第三部分是一个标签(通常是版本描述),用冒号与第二部分分隔。
注意
Docker Hub是由 Docker,Inc 维护的公共注册表。它托管公共 Docker 图像,并提供帮助您构建和管理 Docker 环境的服务。有关更多详细信息,请访问hub.docker.com。
带有不同版本标签的图像集合是一个存储库。通过运行docker commit命令创建的图像将是本地图像,这意味着您将能够从中运行容器,但它不会公开可用。要使其公开或推送到您的私有 Docker 注册表,请使用docker push命令。
images 命令
images命令列出系统中的所有图像:
$ docker images [OPTIONS] [NAME]
| 标志 | 说明 |
|---|
|
-a, --all
| 这显示所有图像,包括中间层。 |
|---|
|
-f, --filter=[]
| 这提供过滤值。 |
|---|
|
--no-trunc
| 这不会截断输出(显示完整的 ID)。 |
|---|
|
-q, --quiet
| 这只显示图像 ID。 |
|---|
现在让我们看一下image命令的几个用法示例:
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
shrikrishna/code.it v1 a7cb6737a2f6 6m ago 704.4 MB
这列出了所有顶层图像,它们的存储库和标签,以及它们的虚拟大小。
Docker 图像只是一堆只读文件系统层。然后,像 AUFS 这样的联合文件系统合并这些层,它们看起来像是一个文件系统。
在 Docker 术语中,只读层就是一个图像。它永远不会改变。当运行一个容器时,进程会认为整个文件系统是可读写的。但是更改只会发生在最顶层的可写层,这是在容器启动时创建的。图像的只读层保持不变。当你提交一个容器时,它会冻结顶层(底层已经冻结)并将其转换为图像。现在,当一个容器启动这个图像时,图像的所有层(包括之前的可写层)都是只读的。所有的更改现在都是在所有底层的顶部创建一个新的可写层。然而,由于联合文件系统(如 AUFS)的工作方式,进程认为文件系统是可读写的。
我们code.it示例中涉及的层次的大致示意图如下:
注意
在这一点上,可能明智地考虑联合文件系统需要多大的努力来合并所有这些层,并提供一致的性能。在某个时候,事情不可避免地会出错。例如,AUFS 有一个 42 层的限制。当层数超过这个限制时,它就不允许创建更多的层,构建就会失败。阅读github.com/docker/docker/issues/1171获取更多关于这个问题的信息。
以下命令列出了最近创建的图像:
$ docker images | head
-f标志可以给出key=value类型的参数。它经常用于获取悬空图像的列表:
$ docker images -f "dangling=true"
这将显示未标记的图像,也就是说,已经提交或构建而没有标记的图像。
rmi 命令
rmi命令删除图像。删除一个图像也会删除它所依赖的所有底层图像,并在拉取时下载的图像:
$ docker rmi [OPTION] {IMAGE(s)]
| 标志 | 解释 |
|---|
|
-f, --force
| 这将强制删除图像(或图像)。 |
|---|
|
--no-prune
| 这个命令不会删除未标记的父级。 |
|---|
这个命令从你的机器中删除一个图像:
$ docker rmi test
保存命令
save命令将图像或存储库保存在一个 tarball 中,并将其流到stdout文件,保留有关图像的父层和元数据:
$ docker save -o codeit.tar code.it
-o标志允许我们指定一个文件而不是流到stdout文件。它用于创建一个备份,然后可以与docker load命令一起使用。
加载命令
load命令从 tarball 中加载图像,恢复文件系统层和与图像相关的元数据:
$ docker load -i codeit.tar
-i标志允许我们指定一个文件,而不是尝试从stdin文件获取流。
导出命令
export命令将容器的文件系统保存为 tarball 并流式传输到stdout文件。它会展平文件系统层。换句话说,它会合并所有文件系统层。在此过程中,图像历史的所有元数据都会丢失:
$ sudo Docker export red_panda > latest.tar
在这里,red_panda是我其中一个容器的名称。
导入命令
import命令创建一个空的文件系统映像,并将 tarball 的内容导入其中。您可以选择为该图像打标签:
$ docker import URL|- [REPOSITORY[:TAG]]
URL 必须以http开头。
$ docker import http://example.com/test.tar.gz # Sample url
如果您想要从本地目录或存档中导入,可以使用-参数从stdin文件中获取数据:
$ cat sample.tgz | docker import – testimage:imported
标签命令
您可以向图像添加tag命令。它有助于识别图像的特定版本。
例如,python图像名称表示python:latest,即可用的最新版本的 Python,这可能会随时更改。但每当它更新时,旧版本都会用相应的 Python 版本标记。因此,python:2.7命令将安装 Python 2.7。因此,tag命令可用于表示图像的版本,或用于需要识别不同图像版本的任何其他目的:
$ docker tag IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG]
REGISTRYHOST命令仅在您使用自己的私有注册表时才需要。同一图像可以有多个标签:
$ docker tag shrikrishna/code.it:v1 shrikrishna/code.it:latest
提示
每当您给图像打标签时,请遵循username/repository:tag约定。
现在,再次运行docker images命令将显示相同的图像已被标记为v1和latest命令:
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
shrikrishna/code.it v1 a7cb6737a2f6 8 days ago 704.4 MB
shrikrishna/code.it latest a7cb6737a2f6 8 days ago 704.4 MB
登录命令
login命令用于注册或登录到 Docker 注册服务器。如果未指定服务器,默认为index.docker.io/v1/。
$ Docker login [OPTIONS] [SERVER]
| 标志 | 解释 |
|---|
|
-e, --email=""
| 电子邮件 |
|---|
|
-p, --password=""
| 密码 |
|---|
|
-u, --username=""
| 用户名 |
|---|
如果未提供标志,则服务器将提示您提供详细信息。第一次登录后,详细信息将存储在$HOME/.dockercfg路径中。
推送命令
push命令用于将图像推送到公共图像注册表或私有 Docker 注册表:
$ docker push NAME[:TAG]
历史命令
history命令显示图像的历史记录:
$ docker history shykes/nodejs
IMAGE CREATED CREATED BY SIZE
6592508b0790 15 months ago /bin/sh -c wget http://nodejs. 15.07 MB
0a2ff988ae20 15 months ago /bin/sh -c apt-get install ... 25.49 MB
43c5d81f45de 15 months ago /bin/sh -c apt-get update 96.48 MB
b750fe79269d 16 months ago /bin/bash 77 B
27cf78414709 16 months ago 175.3 MB
事件命令
一旦启动,events命令会实时打印docker守护程序处理的所有事件:
$ docker events [OPTIONS]
| 标志 | 解释 |
|---|
|
--since=""
| 这显示自 Unix 时间戳以来创建的所有事件。 |
|---|
|
--until=""
| 这个流事件直到时间戳。 |
|---|
例如,events命令的使用如下:
$ docker events
现在,在另一个标签中,运行以下命令:
$ docker start code.it
然后运行以下命令:
$ docker stop code.it
现在回到运行 Docker 事件的标签并查看输出。它将沿着这些线路进行:
[2014-07-21 21:31:50 +0530 IST] c7f2485863b2c7d0071477e6cb8c8301021ef9036afd4620702a0de08a4b3f7b: (from dockerfile/nodejs:latest) start
[2014-07-21 21:31:57 +0530 IST] c7f2485863b2c7d0071477e6cb8c8301021ef9036afd4620702a0de08a4b3f7b: (from dockerfile/nodejs:latest) stop
[2014-07-21 21:31:57 +0530 IST] c7f2485863b2c7d0071477e6cb8c8301021ef9036afd4620702a0de08a4b3f7b: (from dockerfile/nodejs:latest) die
您可以使用--since和--until等标志来获取特定时间范围内的事件日志。
等待命令
wait命令会阻塞,直到容器停止,然后打印其退出代码:
$ docker wait CONTAINER(s)
构建命令
构建命令从指定路径的源文件构建镜像:
$ Docker build [OPTIONS] PATH | URL | -
| 标志 | 解释 |
|---|
|
-t, --tag=""
| 这是要应用于成功时生成的图像的存储库名称(和可选标签)。 |
|---|
|
-q, --quiet
| 这会抑制默认情况下冗长的输出。 |
|---|
|
--rm=true
| 这会在成功构建后删除中间容器。 |
|---|
|
--force-rm
| 这总是在构建失败后删除中间容器。 |
|---|
|
--no-cache
| 此命令在构建镜像时不使用缓存。 |
|---|
此命令使用 Dockerfile 和上下文来构建 Docker 镜像。
Dockerfile 就像一个 Makefile。它包含了各种配置和命令的指令,需要运行以创建一个镜像。我们将在下一节中讨论编写 Dockerfiles。
提示
最好先阅读关于 Dockerfiles 的部分,然后再回到这里,以更好地理解这个命令以及它是如何工作的。
在PATH或URL路径下的文件被称为构建的上下文。上下文用于指代 Dockerfile 中的文件或文件夹,例如在ADD指令中(这就是为什么诸如ADD ../file.txt这样的指令不起作用。它不在上下文中!)。
当给出 GitHub URL 或带有git://协议的 URL 时,该存储库将被用作上下文。该存储库及其子模块将递归克隆到您的本地机器,然后作为上下文上传到docker守护程序。这允许您在私人 Git 存储库中拥有 Dockerfiles,您可以从本地用户凭据或虚拟私人网络(VPN)访问。
上传到 Docker 守护程序
请记住,Docker 引擎既有docker守护程序又有 Docker 客户端。您作为用户给出的命令是通过 Docker 客户端,然后再与docker守护程序(通过 TCP 或 Unix 套接字)进行通信,它会执行必要的工作。docker守护程序和 Docker 主机可以在不同的主机上(这是 boot2Docker 的前提),并且DOCKER_HOST环境变量设置为远程docker守护程序的位置。
当您为docker build命令提供上下文时,本地目录中的所有文件都会被打包并发送到docker守护程序。PATH变量指定了在docker守护程序中构建上下文的文件的位置。因此,当您运行docker build .时,当前文件夹中的所有文件都会被上传,而不仅仅是 Dockerfile 中列出要添加的文件。
由于这可能会有些问题(因为一些系统如 Git 和一些 IDE 如 Eclipse 会创建隐藏文件夹来存储元数据),Docker 提供了一种机制来忽略某些文件或文件夹,方法是在PATH变量中创建一个名为.dockerignore的文件,并添加必要的排除模式。例如,查看github.com/docker/docker/blob/master/.dockerignore。
如果提供了一个普通的 URL,或者 Dockerfile 通过stdin文件流传输,那么不会设置上下文。在这些情况下,只有当ADD指令引用远程 URL 时才起作用。
现在让我们通过 Dockerfile 构建code.it示例图像。如何创建这个 Dockerfile 的说明在Dockerfile部分提供。
到目前为止,您已经创建了一个目录,并在其中放置了 Dockerfile。现在,在您的终端上,转到该目录并执行docker build命令:
$ docker build -t shrikrishna/code.it:docker Dockerfile .
Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 0 : FROM Dockerfile/nodejs
---> 1535da87b710
Step 1 : MAINTAINER Shrikrishna Holla <s**a@gmail.com>
---> Running in e4be61c08592
---> 4c0eabc44a95
Removing intermediate container e4be61c08592
Step 2 : WORKDIR /home
---> Running in 067e8951cb22
---> 81ead6b62246
Removing intermediate container 067e8951cb22
. . . . .
. . . . .
Step 7 : EXPOSE 8000
---> Running in 201e07ec35d3
---> 1db6830431cd
Removing intermediate container 201e07ec35d3
Step 8 : WORKDIR /home
---> Running in cd128a6f090c
---> ba05b89b9cc1
Removing intermediate container cd128a6f090c
Step 9 : CMD ["/usr/bin/node", "/home/code.it/app.js"]
---> Running in 6da5d364e3e1
---> 031e9ed9352c
Removing intermediate container 6da5d364e3e1
Successfully built 031e9ed9352c
现在,您将能够在 Docker 镜像的输出中查看您新构建的图像
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
shrikrishna/code.it Dockerfile 031e9ed9352c 21 hours ago 1.02 GB
要查看缓存的实际效果,请再次运行相同的命令
$ docker build -t shrikrishna/code.it:dockerfile .
Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 0 : FROM dockerfile/nodejs
---> 1535da87b710
Step 1 : MAINTAINER Shrikrishna Holla <s**a@gmail.com>
---> Using cache
---> 4c0eabc44a95
Step 2 : WORKDIR /home
---> Using cache
---> 81ead6b62246
Step 3 : RUN git clone https://github.com/shrikrishnaholla/code.it.git
---> Using cache
---> adb4843236d4
Step 4 : WORKDIR code.it
---> Using cache
---> 755d248840bb
Step 5 : RUN git submodule update --init --recursive
---> Using cache
---> 2204a519efd3
Step 6 : RUN npm install
---> Using cache
---> 501e028d7945
Step 7 : EXPOSE 8000
---> Using cache
---> 1db6830431cd
Step 8 : WORKDIR /home
---> Using cache
---> ba05b89b9cc1
Step 9 : CMD ["/usr/bin/node", "/home/code.it/app.js"]
---> Using cache
---> 031e9ed9352c
Successfully built 031e9ed9352c
提示
现在尝试使用缓存。更改中间的一行(例如端口号),或者在中间的某个地方添加一个RUN echo "testing cache"行,看看会发生什么。
使用存储库 URL 构建图像的示例如下:
$ docker build -t shrikrishna/optimus:git_url \ git://github.com/shrikrishnaholla/optimus
Sending build context to Docker daemon 1.305 MB
Sending build context to Docker daemon
Step 0 : FROM dockerfile/nodejs
---> 1535da87b710
Step 1 : MAINTAINER Shrikrishna Holla
---> Running in d2aae3dba68c
---> 0e8636eac25b
Removing intermediate container d2aae3dba68c
Step 2 : RUN git clone https://github.com/pesos/optimus.git /home/optimus
---> Running in 0b46e254e90a
. . . . .
. . . . .
. . . . .
Step 5 : CMD ["/usr/local/bin/npm", "start"]
---> Running in 0e01c71faa0b
---> 0f0dd3deae65
Removing intermediate container 0e01c71faa0b
Successfully built 0f0dd3deae65
Dockerfile
我们已经看到了通过提交容器来创建镜像。如果您想要使用依赖项的新版本或您自己应用程序的新版本来更新镜像怎么办?反复启动、设置和提交步骤很快就变得不切实际。我们需要一种可重复的方法来构建镜像。这就是 Dockerfile 的作用,它不过是一个包含指令的文本文件,用于自动化构建镜像的步骤。docker build将按顺序读取这些指令,途中提交它们,并构建一个镜像。
docker build命令使用此 Dockerfile 和上下文来执行指令,并构建 Docker 镜像。上下文是指提供给docker build命令的路径或源代码仓库 URL。
Dockerfile 以这种格式包含指令:
# Comment
INSTRUCTION arguments
任何以#开头的行将被视为注释。如果#符号出现在其他地方,它将被视为参数的一部分。指令不区分大小写,尽管按照惯例,指令应该大写以便与参数区分开。
让我们看看在 Dockerfile 中可以使用的指令。
FROM指令
FROM指令设置了后续指令的基础镜像。有效的 Dockerfile 的第一行非注释行将是一个FROM指令:
FROM <image>:<tag>
镜像可以是任何有效的本地或公共镜像。如果在本地找不到,Docker build命令将尝试从公共注册表中拉取。这里tag命令是可选的。如果没有给出,将假定为latest命令。如果给出了不正确的tag命令,将返回错误。
MAINTAINER指令
MAINTAINER指令允许您为生成的镜像设置作者:
MAINTAINER <name>
RUN指令
RUN指令将在当前镜像的新层上执行任何命令,并提交此镜像。因此提交的镜像将用于 Dockerfile 中的下一条指令。
RUN指令有两种形式:
-
RUN <command>形式 -
RUN ["executable", "arg1", "arg2"...]形式
在第一种形式中,命令在 shell 中运行,具体来说是/bin/sh -c <command> shell。第二种形式在基础镜像没有/bin/sh shell 的情况下很有用。Docker 对这些镜像构建使用缓存。因此,如果您的镜像构建在中间某个地方失败,下一次运行将重用先前成功的部分构建,并从失败的地方继续。
在以下情况下,缓存将被使无效:
-
当使用
--no-cache标志运行docker build命令时。 -
如果给出了诸如
apt-get update之类的不可缓存命令,则所有后续的RUN指令将再次运行。 -
当首次遇到
ADD指令时,如果上下文的内容发生了变化,将使 Dockerfile 中所有后续指令的缓存无效。这也将使RUN指令的缓存无效。
CMD 指令
CMD指令提供了容器执行的默认命令。它有以下形式:
-
CMD ["executable", "arg1", "arg2"...]形式 -
CMD ["arg1", "arg2"...]形式 -
CMD command arg1 arg2 …形式
第一种形式类似于一个 exec,这是首选形式,其中第一个值是可执行文件的路径,后面跟着它的参数。
第二种形式省略了可执行文件,但需要ENTRYPOINT指令来指定可执行文件。
如果您使用CMD指令的 shell 形式,那么<command>命令将在/bin/sh -c shell 中执行。
注意
如果用户在docker run中提供了一个命令,它将覆盖CMD命令。
RUN和CMD指令之间的区别在于,RUN指令实际上运行命令并提交它,而CMD指令在构建时不会被执行。这是一个默认的命令,在用户启动容器时运行,除非用户提供了一个启动命令。
例如,让我们编写一个Dockerfile,将Star Wars的输出带到您的终端:
FROM ubuntu:14.04
MAINTAINER shrikrishna
RUN apt-get -y install telnet
CMD ["/usr/bin/telnet", "towel.blinkenlights.nl"]
将其保存在名为star_wars的文件夹中,并在此位置打开您的终端。然后运行此命令:
$ docker build -t starwars .
现在您可以使用以下命令运行它:
$ docker run -it starwars
以下截图显示了starwars的输出:
因此,您可以在终端上观看星球大战!
注意
这个星球大战致敬是由 Simon Jansen,Sten Spans 和 Mike Edwards 创建的。当您已经看够时,按住Ctrl + ]。您将收到一个提示,您可以在其中输入close以退出。
ENTRYPOINT 指令
ENTRYPOINT指令允许你将 Docker 镜像变成一个可执行文件。换句话说,当你在ENTRYPOINT中指定一个可执行文件时,容器将运行得就像是那个可执行文件一样。
ENTRYPOINT指令有两种形式:
-
ENTRYPOINT ["executable", "arg1", "arg2"...]形式。 -
ENTRYPOINT command arg1 arg2 …形式。
这个指令添加了一个入口命令,当参数传递给docker run命令时,不会被覆盖,不像CMD指令的行为。这允许参数传递给ENTRYPOINT指令。docker run <image> -arg命令将-arg参数传递给ENTRYPOINT指令中指定的命令。
如果在ENTRYPOINT指令中指定了参数,它们不会被docker run的参数覆盖,但是通过CMD指令指定的参数会被覆盖。
例如,让我们编写一个带有cowsay的ENTRYPOINT指令的 Dockerfile:
注意
cowsay是一个生成带有消息的牛的 ASCII 图片的程序。它还可以使用其他动物的预制图片生成图片,比如 Tux 企鹅,Linux 吉祥物。
FROM ubuntu:14.04
RUN apt-get -y install cowsay
ENTRYPOINT ["/usr/games/cowsay"]
CMD ["Docker is so awesomoooooooo!"]
将其保存为名为Dockerfile的文件,放在名为cowsay的文件夹中。然后通过终端,进入该目录,并运行以下命令:
$ docker build -t cowsay .
构建完镜像后,运行以下命令:
$ docker run cowsay
以下截图显示了前面命令的输出:
如果你仔细看截图,第一次运行没有参数,并且使用了我们在 Dockerfile 中配置的参数。然而,当我们在第二次运行中给出自己的参数时,它覆盖了默认值,并将所有参数(-f标志和句子)传递给了cowsay文件夹。
注意
如果你是那种喜欢恶作剧的人,这里有一个提示:应用superuser.com/a/175802中给出的指令来设置一个预执行脚本(每次执行命令时调用的函数),将每个命令传递给这个 Docker 容器,并将其放在.bashrc文件中。现在,cowsay 将打印出它在文本气球中执行的每个命令,由一个 ASCII 牛说出来!
WORKDIR 指令
WORKDIR指令为接下来的RUN,CMD和ENTRYPOINT Dockerfile 命令设置工作目录:
WORKDIR /path/to/working/directory
此指令可以在同一个 Dockerfile 中多次使用。如果提供了相对路径,则WORKDIR指令将相对于先前的WORKDIR指令的路径。
EXPOSE 指令
EXPOSE指令通知 Docker 在启动容器时要公开某个端口:
EXPOSE port1 port2 …
即使在暴露端口之后,在启动容器时,仍然需要使用-p标志来提供端口映射给Docker run。这个指令在链接容器时很有用,我们将在第三章中看到链接容器。
ENV 指令
ENV 命令用于设置环境变量:
ENV <key> <value>
这将把<key>环境变量设置为<value>。这个值将传递给所有未来的RUN指令。这相当于在命令前加上<key>=<value>。
使用ENV命令设置的环境变量将持久存在。这意味着当从生成的镜像运行容器时,环境变量也将对运行的进程可用。docker inspect命令显示了在创建镜像过程中分配的值。但是,可以使用$ docker run –env <key>=<value>命令覆盖这些值。
USER 指令
USER 指令设置在运行镜像和任何后续RUN指令时要使用的用户名或 UID:
USER xyz
VOLUME 指令
VOLUME指令将创建一个具有给定名称的挂载点,并将其标记为保存来自主机或其他容器的外部挂载卷:
VOLUME [path]
以下是VOLUME指令的示例:
VOLUME ["/data"]
以下是此指令的另一个示例:
VOLUME /var/log
两种格式都可以接受。
ADD 指令
ADD指令用于将文件复制到镜像中:
ADD <src> <dest>
ADD指令将文件从<src>复制到<dest>的路径中。
<src>路径必须是相对于正在构建的源目录(也称为构建上下文)的文件或目录的路径,或者是远程文件 URL。
<dest>路径是源将被复制到目标容器内部的绝对路径。
注意
如果通过stdin文件(docker build - < somefile)构建 Dockerfile,则没有构建上下文,因此 Dockerfile 只能包含基于 URL 的ADD语句。您还可以通过stdin文件(docker build - < archive.tar.gz)传递压缩存档。Docker 将在存档的根目录查找 Dockerfile,并且存档的其余部分将用作构建的上下文。
ADD指令遵循以下规则:
-
<src>路径必须在构建的上下文中。您不能使用ADD ../file as ..语法,因为它超出了上下文。 -
如果
<src>是一个 URL,并且<dest>路径不以斜杠结尾(它是一个文件),则将 URL 处的文件复制到<dest>路径。 -
如果
<src>是一个 URL,并且<dest>路径以斜杠结尾(它是一个目录),则会获取 URL 处的内容,并且会从 URL 中推断出一个文件名,并将其保存到<dest>/filename路径中。因此,在这种情况下,URL 不能具有简单的路径,例如example.com。 -
如果
<src>是一个目录,则整个目录将被复制,连同文件系统元数据一起。 -
如果
<src>是本地 tar 存档,则它将被提取到<dest>路径中。<dest>处的结果是: -
<dest>路径处存在的任何内容。 -
提取的 tar 存档的内容,以文件为基础解决冲突,优先考虑
<src>路径。 -
如果
<dest>路径不存在,则将创建该路径以及其路径中的所有缺失目录。
COPY 指令
COPY 指令将文件复制到镜像中:
COPY <src> <dest>
COPY指令类似于ADD指令。不同之处在于COPY指令不允许超出上下文的任何文件。因此,如果您通过stdin文件或 URL(指向源代码存储库的 URL)流式传输 Dockerfile,则无法使用COPY指令。
ONBUILD 指令
ONBUILD指令将触发器添加到镜像中,当镜像用作另一个构建的基础镜像时,将执行该触发器。
ONBUILD [INSTRUCTION]
当源应用程序涉及需要在使用之前编译的生成器时,这是有用的。除了FROM,MAINTAINER和ONBUILD指令之外的任何构建指令都可以注册。
以下是此指令的工作方式:
-
在构建过程中,如果遇到
ONBUILD指令,它会注册一个触发器并将其添加到镜像的元数据中。当前构建不会以任何方式受到影响。 -
所有这些触发器的列表都被添加到镜像清单中,作为一个名为
OnBuild的键,在构建结束时可以通过Docker inspect命令看到。 -
当这个镜像后来被用作新构建的基础镜像时,在处理
FROM指令的过程中,OnBuild key触发器按照注册的顺序被读取和执行。如果其中任何一个失败,FROM指令将中止,导致构建失败。否则,FROM指令完成,构建将继续进行。 -
触发器在执行后会从最终镜像中清除。换句话说,它们不会被grand-child builds继承。
让我们把cowsay带回来!这是一个带有ONBUILD指令的 Dockerfile:
FROM ubuntu:14.04
RUN apt-get -y install cowsay
RUN apt-get -y install fortune
ENTRYPOINT ["/usr/games/cowsay"]
CMD ["Docker is so awesomoooooooo!"]
ONBUILD RUN /usr/games/fortune | /usr/games/cowsay
现在将这个文件保存在一个名为OnBuild的文件夹中,打开该文件夹中的终端,并运行这个命令:
$ Docker build -t shrikrishna/onbuild .
我们需要编写另一个基于这个镜像的 Dockerfile。让我们写一个:
FROM shrikrishna/onbuild
RUN apt-get moo
CMD ['/usr/bin/apt-get', 'moo']
注意
apt-get moo命令是许多开源工具中通常找到的彩蛋的一个例子,只是为了好玩!
构建这个镜像现在将执行我们之前给出的ONBUILD指令:
$ docker build -t shrikrishna/apt-moo apt-moo/
Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 0 : FROM shrikrishna/onbuild
# Executing 1 build triggers
Step onbuild-0 : RUN /usr/games/fortune | /usr/games/cowsay
---> Running in 887592730f3d
________________________________
/ It was all so different before \
\ everything changed. /
--------------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
---> df01e4ca1dc7
---> df01e4ca1dc7
Removing intermediate container 887592730f3d
Step 1 : RUN apt-get moo
---> Running in fc596cb91c2a
(__)
(oo)
/------\/
/ | ||
* /\---/\
~~ ~~
..."Have you mooed today?"...
---> 623cd16a51a7
Removing intermediate container fc596cb91c2a
Step 2 : CMD ['/usr/bin/apt-get', 'moo']
---> Running in 22aa0b415af4
---> 7e03264fbb76
Removing intermediate container 22aa0b415af4
Successfully built 7e03264fbb76
现在让我们利用我们新获得的知识来为我们之前通过手动满足容器中的依赖关系并提交构建的code.it应用程序编写一个 Dockerfile。Dockerfile 看起来会像这样:
# Version 1.0
FROM dockerfile/nodejs
MAINTAINER Shrikrishna Holla <s**a@gmail.com>
WORKDIR /home
RUN git clone \ https://github.com/shrikrishnaholla/code.it.git
WORKDIR code.it
RUN git submodule update --init --recursive
RUN npm install
EXPOSE 8000
WORKDIR /home
CMD ["/usr/bin/node", "/home/code.it/app.js"]
创建一个名为code.it的文件夹,并将这个内容保存为一个名为Dockerfile的文件。
注意
即使不需要上下文,为每个 Dockerfile 创建一个单独的文件夹是一个很好的做法。这样可以在不同的项目之间分离关注点。当你继续前进时,你可能会注意到许多 Dockerfile 作者会将RUN指令合并在一起(例如,查看dockerfile.github.io中的 Dockerfile)。原因是 AUFS 将可能的层的数量限制为 42。更多信息,请查看github.com/docker/docker/issues/1171。
你可以回到Docker build部分,看看如何从这个 Dockerfile 构建一个镜像。
Docker 工作流程 - 拉取-使用-修改-提交-推送
现在,当我们接近本章的结束时,我们可以猜测一个典型的 Docker 工作流程是什么样的:
-
准备运行应用程序的要求清单。
-
确定哪个公共图像(或您自己的图像)可以满足大多数要求,同时也要维护良好(这很重要,因为您需要图像在可用时更新为新版本)。
-
接下来,通过运行容器并执行满足要求的命令(可以是安装依赖项、绑定挂载卷或获取源代码),或者编写 Dockerfile(这更可取,因为您将能够使构建可重复)来满足其余要求。
-
将新图像推送到公共 Docker 注册表,以便社区也可以使用它(或者根据需要推送到私有注册表或存储库)。
自动构建
自动构建自动化了从 GitHub 或 BitBucket 直接在 Docker Hub 上构建和更新图像。它们通过向您选择的 GitHub 或 BitBucket 存储库添加commit挂钩来工作,在您推送提交时触发构建和更新。因此,每次更新时都不需要手动构建和推送图像到 Docker Hub。以下步骤将向您展示如何执行此操作:
-
要设置自动构建,请登录到您的 Docker Hub 帐户。
-
通过链接 帐户菜单链接您的 GitHub 或 BitBucket 帐户。
-
在添加 存储库菜单中选择自动 构建。
-
选择包含您想要构建的 Dockerfile 的 GitHub 或 BitBucket 项目。(您需要授权 Docker Hub 访问您的存储库。)
-
选择包含源代码和 Dockerfile 的分支(默认为主分支)。
-
为自动构建命名。这也将是存储库的名称。
-
为构建分配一个可选的 Docker 标签。默认为
lastest标签。 -
指定 Dockerfile 的位置。默认为
/。
一旦配置完成,自动构建将触发构建,并且您将能够在几分钟内在 Docker Hub 注册表中看到它。它将与您的 GitHub 和 BitBucket 存储库保持同步,直到您自己停用自动构建。
构建状态和历史记录可以在 Docker Hub 中您的个人资料的自动构建页面中查看。
创建自动构建后,您可以停用或删除它。
注意
但是,您不能使用 Docker 的push命令推送到自动构建。您只能通过向 GitHub 或 BitBucket 存储库提交代码来管理它。
您可以为每个存储库创建多个自动构建,并将它们配置为指向特定的 Dockerfile 或 Git 分支。
构建触发器
也可以通过 Docker Hub 上的 URL 触发自动构建。这允许您根据需要重新构建自动构建的图像。
Webhooks
Webhooks 是在成功构建事件发生时调用的触发器。通过 webhook,您可以指定目标 URL(例如通知您的服务)和在推送图像时将传递的 JSON 有效负载。如果您有持续集成工作流程,webhooks 非常有用。
要将 webhook 添加到您的 Github 存储库,请按照以下步骤进行:
-
转到存储库中的Settings。
-
从左侧菜单栏转到Webhooks and Services。
-
单击Add Service。
-
在打开的文本框中,输入Docker并选择该服务。
-
您已经准备就绪!现在每当您提交到存储库时,Docker Hub 都会触发构建。
摘要
在本章中,我们查看了Docker命令行工具并尝试了可用的命令。然后,我们找出如何使用 Dockerfile 使构建可重复。此外,我们使用 Docker Hub 的自动构建服务自动化了此构建过程。
在下一章中,我们将尝试通过查看帮助我们配置它们的各种命令来更好地控制容器的运行方式。我们将研究限制容器可消耗的资源(CPU、RAM 和存储)的数量。