两张图理解VM和Docker
VM 的 Hypervisor 需要实现对硬件的虚拟化,并且还要搭载自己的操作系统,自然在启动速度和资源利用率以及性能上有比较大的开销。
Docker 的设计图是这样的:
Docker 几乎就没有什么虚拟化的东西,并且直接复用了 Host 主机的 OS,在 Docker Engine 层面实现了调度和隔离重量一下子就降低了好几个档次。 Docker 的容器利用了 LXC,管理利用了 namespaces 来做权限的控制和隔离, cgroups 来进行资源的配置,并且还通过 aufs 来进一步提高文件系统的资源利用率。
其中的 aufs 是个很有意思的东西,是 UnionFS 的一种。他的思想和 git 有些类似,可以把对文件系统的改动当成一次 commit 一层层的叠加。这样的话多个容器之间就可以共享他们的文件系统层次,每个容器下面都是共享的文件系统层次,上面再是各自对文件系统改动的层次,这样的话极大的节省了对存储的需求,并且也能加速容器的启动。
安装
Mac OS X
下载 Docker Toolbox 并安装
应用
运行Docker容器,你需要
- create a new (or start an existing) Docker virtual machine
- switch your environment to your new VM
- use the docker client to create, load, and manage containers
- 打开Docker CLI
1 | $ docker run hello-world |
第一步
docker run hello-world
这条命令完成了 内核加载完成Docker的工作。命令包括三个部分:
基本命令
启动Docker
如果你使用快速安装并且添加到PATH中,docker就被安装到快捷启动了,当Ubuntu系统启动时就启动了!你只需要运行命令sudo run docker help获得输出.
如果你获得结果是”docker: command not found”或者像” /var/lib/docker/repositories: permission denied”,你需要手动指定路径执行它.
1 | $ # Run docker in daemon mode |
从索引仓库下载一个镜像
这个将从索引仓库中通过名字找到ubuntu镜像,并且从索引仓库顶级中心来下载到本地镜像存储
1 | $ # 下载一个ubuntu镜像 |
注:当镜像下载成功后,你可以看到12位的hash值像539c0211cd76,这是下载完整的镜像的精简ID,这些短的镜像ID是完整镜像ID的前12个字符—可以使用docker inspect 或者 docker images -notrunc=true来获得完整的镜像ID
在Docker中运行应用
Docker 允许你在容器内运行应用程序, 使用 docker run
命令来在容器内运行一个应用程序。
Hello world
现在让我们来试试
$ sudo docker run ubuntu:14.04 /bin/echo 'Hello world'
Hello world
刚才你启动了你的第一个容器!
那么刚才发生了什么? 我们逐步来分析 docker run
命令做了哪些事情。
首先,我们指定了 docker
二进制执行文件和我们想要执行的命令 run
。docker run
组合会运行容器。
接下来,我们指定一个镜像: ubuntu 14.04
。这是我们运行容器的来源。 Docker 称此为镜像。在本例中,我们使用一个 Ubuntu 14.04
操作系统镜像。
当你指定一个镜像,Docker 首先会先从你的 Docker 本地主机上查看镜像是否存在,如果没有,Docker 就会从镜像仓库 Docker Hub 下载公共镜像。
接下来,我们告诉 Docker 在容器内我们需要运行什么命令:
/bin/echo 'Hello world'
当我们的容器启动了 Docker 创建的新的 Ubuntu 14.04 环境,并在容器内执行 /bin/echo
命令后。我们会在命令行看到如下结果:
hello world
那么,我们创建容器之后会发生什么呢? 当命令状态状态处于激活状态的时候 Docker 容器就会一直运行。这里只要 “hello world” 被输出,容器就会停止。
一个交互式的容器
让我们尝试再次运行 docker run
,这次我们指定一个新的命令来运行我们的容器。
$ sudo docker run -t -i ubuntu:14.04 /bin/bash
root@af8bae53bdd3:/#
我们继续指定了 docker run
命令,并启动了 ubuntu:14.04
镜像。但是我们添加了两个新的标识(参数flags): -t
和 -i
。-t
表示在新容器内指定一个伪终端或终端,-i
表示允许我们对容器内的 (STDIN
) 进行交互。
我们在容器内还指定了一个新的命令: /bin/bash
。这将在容器内启动 bash shell
所以当容器(container)启动之后,我们会获取到一个命令提示符:
root@af8bae53bdd3:/#
我们尝试在容器内运行一些命令:
root@af8bae53bdd3:/# pwd
/
root@af8bae53bdd3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
你可以看到我们运行 pwd
来显示当前目录,这时候显示的是我们的根目录。我们还列出了根目录的文件列表,通过目录列表我们看出来这是一个典型的 Linux 文件系统。
你可以在容器内随便的玩耍,你可以使用 exit
命令或者使用 CTRL-D 来退出容器。
root@af8bae53bdd3:/# exit
与我们之前的容器一样,一旦你的 Bash shell 退出之后,你的容器就停止了。
Hello world 守护进程
现在当一个容器运行完一个命令后就会退出,但是这样看起来有时候并不好。让我们创建一个容器以进程的方式运行,就像大多数我们运行在 Docker 中的应用程序一样,这里我们可以使用 docker run
命令:
$ sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147
等等,怎么回事?我们的 “hello world” 输出呢?让我们看看它是怎么运行的。这个命令看起来应该很熟悉.我们运行docker run
,但是我们指定了一个 -d
标识。-d
标识告诉 docker 在容器内以后台进程模式运行。
我们也指定了一个相同的镜像: ubuntu:14.04
最终,我们指定命令行运行:
/bin/sh -c "while true; do echo hello world; sleep 1; done"
这是一个忠实的 hello world 进程:一个脚本会一直输出 “hello world”
为什么我们看不到的一大堆的 “hello world” ? 而是docker返回的一个很长的字符串:
1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147
这个长的字符串叫做容器ID(container ID)。它对于每一个容器来说都是唯一的,所以我们可以使用它。
注意:容器 ID 是有点长并且非常的笨拙,稍后我们会看到一个短点的 ID,某些方面来说它是容器 ID 的简化版。
我们可以根据容器 ID 查看 “hello world” 进程发生了什么
首先,我们要确保容器正在运行。我们可以使用 docker ps
命令来查看。docker ps
命令可以查询 docker 进程的所有容器。
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e5535038e28 ubuntu:14.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute insane_babbage
这里我们看到了以进程模式运行的容器。docker ps
命令会返回一些有用的信息,这里包括一个短的容器 ID:1e5535038e28
。
我们还可以查看到构建容器时使用的镜像, ubuntu:14.04
,当命令运行之后,容器的状态随之改变并且被系统自动分配了名称 insane_babbage
。
注意:当容器启动的时候 Docker 会自动给这些容器命名,稍后我们可以看到我们如何给容器指定名称。
好了,现在我们知道它正在运行。但是我们能要求它做什么呢?做到这,我们需要在我们容器内使用 docker logs
命令。让我们使用容器的名称来填充 docker logs
命令。
$ sudo docker logs insane_babbage
hello world
hello world
hello world
. . .
docker logs
命令会查看容器内的标准输出:这个例子里输出的是我们的命令 hello world
太棒了!我们的 docker 进程是工作的,并且我们创建了我们第一个 docker 应用。
现在我们已经可以创建我们自己的容器了,让我们处理正在运行的进程容器并停止它。我们使用 docker stop
命令来停止容器 。
$ sudo docker stop insane_babbage
insane_babbage
docker stop
命令会通知 Docker 停止正在运行的容器。如果它成功了,它将返回刚刚停止的容器名称。
让我们通过 docker ps
命令来检查它是否还工作。
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
太好了,我们的容器停止了。
现在我们看到了Docker 是多么简单,让我们学习如何做一些更高级的任务。
使用容器工作
在上一节的 Docker 用户指南中,我们启动了我们的第一个容器。而后边的例子中我们使用 docker run
命令启动了两个容器
与前台进行交互的容器
以进程方式在后台运行的容器
在这个过程中,我们学习到了几个 Docker 命令:
docker ps
列出容器docker logs
显示容器的标准输出docker stop
停止正在运行的容器
提示:另一种学习
docker
命令的方式就是查看我们的 交互式教程页面。
docker
客户端非常简单 。Docker 的每一项操作都是通过命令行来实现的,而每一条命令行都可以使用一系列的标识(flags)和参数。
1 | # Usage: [sudo] docker [flags] [command] [arguments] .. |
这个命令不仅返回了您使用的 Docker 客户端和进程的版本信息,还返回了 GO 语言的版本信息( Docker的编程语言 )。
1 | Client: |
查看一下 Docker 客户端都能做什么
我们可以通过只输入不附加任何参数的 docker
命令来运行 docker 二进制文件,这样我们就会查看到 Docker 客户端的所有命令选项。
1 | $ sudo docker |
会看到当前可用的所有命令行列表:
1 | Commands: |
查看 Docker 命令用法
你可以更深入的去了解指定的 Docker 命令使用方法。
试着输入 Docker [command]
,这里会看到 docker 命令的使用方法:
1 | $ sudo docker attach |
或者你可以通过在 docker 命令中使用 --help
标识(flags)
1 | $ sudo docker images --help |
这将返回所有的帮助信息和可用的标识(flags):
1 | Usage: docker attach [OPTIONS] CONTAINER |
注意:你可以点击这里 来查看完整的 Docker 命令行列表和使用方法。
在Docker中运行一个web应用
到这里我们了解了更多关于 docker 客户端的知识,而现在我们需要将学习的焦点转移到重要的部分:运行多个容器。到目前为止我们发现运行的容器并没有一些什么特别的用处。让我们通过使用 docker 构建一个 web 应用程序来运行一个web应用程序来体验一下。
在这个 web 应用中,我们将运行一个 Python Flask 应用。使用 docker run
命令。
1 | $ sudo docker run -d -P training/webapp python app.py |
让我们来回顾一下我们的命令都做了什么。我们指定两个标识(flags) -d
和 -P
。我们已知是 -d
标识是让 docker 容器在后台运行。新的 -P
标识通知 Docker 将容器内部使用的网络端口映射到我们使用的主机上。现在让我们看看我们的 web 应用。
This image is a pre-built image we’ve created that contains a simple Python Flask web application.
我们指定了 training/web
镜像。我们创建容器的时候使用的是这个预先构建好的镜像,并且这个镜像已经包含了简单的 Python Flask web 应用程序。
最后,我们指定了我们容器要运行的命令: python app.py
。这样我们的 web 应用就启动了。
注意:你可以在命令参考和Docker run参考查看更多
docker run
命令细节
查看 WEB 应用容器
现在我们使用 docker ps
来查看我们正在运行的容器。
1 | $ sudo docker ps -l |
你可以看到我们在 docker ps
命令中指定了新的标识 -l
。这样组合的 docker ps
命令会返回最后启动容器的详细信息。
注意:默认情况下,
docker ps
命令只显示运行中的容器。如果你还想看已经停止的容器,请加上-a
标示。
我们这里可以看到一些细节,与我们第一次运行 docker ps
命令的时候相比,这里多了一个 PORTS
列。
1 | PORTS |
我们通过在 docker run
中使用 -P
标示(flags) 来将我们 Docker 镜像内部容器端口暴露给主机。
提示:当我们学习如何构建镜像的时候,我们将了解更多关于如何开放 Docker 镜像端口。
在这种情况下,Docker 开放了 5000 端口(默认 Python Flask 端口)映射到主机端口 49155 上。
Docker 能够很容易的配置和绑定网络端口。在最后一个例子中 -P
标识(flags)是 -p 5000
的缩写,它将会把容器内部的 5000 端口映射到本地 Docker 主机的高位端口上(这个端口的通常范围是 32768 至 61000)。我们也可以指定 -p
标识来绑定指定端口。举例:
1 | $ sudo docker run -d -p 5000:5000 training/webapp python app.py |
这将会把容器内部的 5000 端口映射到我们本地主机的 5000 端口上。你可能现在会问:为什么我们只使用 1对1端口映射的方式将端口映射到 Docker 容器, 而不是采用自动映射高位端口的方式?这里 1:1 映射方式能够保证映射到本地主机端口的唯一性。假设你想要测试两个 Python 应用程序,两个容器内部都绑定了端口5000,这样就没有足够的 Docker 的端口映射,你只能访问其中一个。
所以,现在我们打开浏览器访问端口32768。
我们的应用程序可以访问了!
注意:如果你在 OS X windows或者Linux上使用 boot2docker 虚拟机,你需要获取虚拟机的 ip 来代替localhost 使用,你可以通过运行 boot2docker shell 来获取 ip。
1 | $ boot2docker ip |
1 | #如果使用MAC意见安装包则在启动的`Docker Quickstart Terminal` 时候会分配一个IP地址 |
在这种情况下,你可以通过输入 http://192.168.99.100:32768 来访问上面的例子。
查看网络端口快捷方式
使用 docker ps
命令来会返回端口的映射是一种比较笨拙的方法。为此,Docker 提供了一种快捷方式: docker port
,使用 docker port
可以查看指定 (ID或者名字的)容器的某个确定端口映射到宿主机的端口号。
1 | $ sudo docker port nostalgic_morse 5000 |
在这种情况下,我们看到容器的 5000 端口映射到了宿主机的的 49155 端口。
查看WEB应用程序日志
让我们看看我们的容器中的应用程序都发生了什么,这里我们使用学习到的另一个命令 docker logs
来查看。
1 | $ sudo docker logs -f nostalgic_morse |
这次我们添加了一个 -f
标识。 docker log
命令就像使用 tail -f
一样来输出容器内部的标准输出。这里我们从显示屏上可以看到应用程序使用的是 5000 端口并且能够查看到应用程序的访问日志。
查看WEB应用程序容器的进程
我们除了可以查看容器日志,我们还可以使用 docker top
来查看容器内部运行的进程:
1 | $ sudo docker top nostalgic_morse |
这里我们可以看到 python app.py
在容器里唯一进程。
检查WEB应用程序
最后,我们可以使用 docker inspect
来查看Docker的底层信息。它会返回一个 JSON 文件记录着 Docker 容器的配置和状态信息。
1 | $ sudo docker inspect nostalgic_morse |
来让我们看下JSON的输出。
1 | [{ |
我们也可以针对我们想要的信息进行过滤,例如,返回容器的 IP 地址,如下:
1 | $ sudo docker inspect -f '{{ .NetworkSettings.IPAddress }}' nostalgic_morse |
停止WEB应用容器
现在,我们的WEB应用程序处于工作状态。现在我们通过使用 docker stop
命令来停止名为 nostalgic_morse
的容器:
1 | $ sudo docker stop nostalgic_morse |
现在我们使用 docker ps
命令来检查容器是否停止了。
1 | $ sudo docker ps -l |
重启WEB应用容器
哎呀!刚才你停止了另一个开发人员所使用的容器。这里你现在有两个选择:您可以创建一个新的容器或者重新启动旧的。让我们启动我们之前的容器:
1 | $ sudo docker start nostalgic_morse |
现在再次运行 docker ps -l
来查看正在运行的容器,或者通过URL访问来查看我们的应用程序是否响应。
注意:也可以使用
docker restart
命令来停止容器然后再启动容器。
移除WEB应用容器
你的同事告诉你他们已经完成了在容器上的工作,不在需要容器了。让我们使用 docker rm
命令来删除它:
1 | $ sudo docker rm nostalgic_morse |
发生了什么?实际上,我们不能删除正在运行的容器。这避免你意外删除了正在使用并且运行中的容器。让我们先停止容器,然后再试一试删除容器。
1 | $ sudo docker stop nostalgic_morse |
现在我们停止并删除了容器。
注意:删除容器是最后一步!
直到现在,我们使用的镜像都是从Docker Hub下载的。接下来,我们学习创建和分享镜像。
使用镜像
在了解Docker这部分中,我们知道了 Docker 镜像是容器的基础。在前面的部分我们使用的是已经构建好的 Docker 镜像,例如: ubuntu
镜像和 training/webapp
镜像。
我们还了解了从 Docker 商店下载镜像到本地的 Docker 主机上。如果一个镜像不存在,他就会自动从 Docker 镜像仓库去下载,默认是从 Docker Hub
公共镜像源下载。
在这一节中,我们将探讨更多的关于 Docker 镜像的东西:
- 管理和使用本地 Docker 主机镜像。
- 创建基本镜像
- 上传 Docker 镜像到 Docker Hub Registry。
在主机上列出镜像列表
让我们列出本地主机上的镜像。你可以使用 docker images
来完成这项任务:
1 | $ sudo docker images |
我们可以看到之前使用的镜像。当我们每次要使用镜像来启动一个新容器的时候都会从 Docker Hub 下载对应的镜像。
我们在镜像列表中看到三个至关重要的东西。
- 来自什么镜像源,例如
ubuntu
- 每个镜像都有标签(tags),例如
14.04
- 每个镜像都有镜像ID
镜像源中可能存储着一个镜像源的多个版本。我们会看到 Ubuntu
的多个版本:10.04, 12.04, 12.10, 13.04, 13.10 and 14.04。每个容器有一个唯一的标签(tag),让我们来识别为不同的镜像,例如:
ubuntu:14.04
所以我们可以运行一个带标签镜像的容器:
$ sudo docker run -t -i ubuntu:14.04 /bin/bash
如果我们想要使用 Ubuntu 12.04
的镜像来构建,我们可以这样做
$ sudo docker run -t -i ubuntu:12.04 /bin/bash
如果你不指定一个镜像的版本标签,例如你只使用 Ubuntu
,Docker 将默认使用 Ubuntu:latest
镜像。
提示:我们建议使用镜像时指定一个标签,例如
ubuntu:12.04
。这样你知道你使用的是一个什么版本的镜像。
获取一个新的镜像
现在如何获取一个新的镜像?当我们在本地主机上使用一个不存在的镜像时 Docker 就会自动下载这个镜像。但是这需要一段时间来下载这个镜像。如果我们想预先下载这个镜像,我们可以使用 docker pull
命令来下载它。这里我们下载 centos
镜像。
$ sudo docker pull centos
Pulling repository centos
b7de3133ff98: Pulling dependent layers
5cc9e91966f7: Pulling fs layer
511136ea3c5a: Download complete
ef52fb1fe610: Download complete
. . .
我们看到镜像的每一层都被下载下来了,现在我们可以直接使用这个镜像来运行容器,而不需要在下载这个镜像了。
$ sudo docker run -t -i centos /bin/bash
bash-4.1#
查找镜像
Docker 的特点之一是人们创建了各种各样的 Docker 镜像。而且这些镜像已经被上传到了 Docker Hub
。我们可以从 Docker Hub
网站来搜索镜像。
我们也可以使用 docker search
命令来搜索镜像。譬如说我们的团队需要一个安装了 Ruby 和 Sinatra 的镜像来做我们的 web 应用程序开发。我们可以通过 docker search
命令搜索 sinatra
来寻找适合我们的镜像
$ sudo docker search sinatra
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
training/sinatra Sinatra training image 0 [OK]
marceldegraaf/sinatra Sinatra test app 0
mattwarren/docker-sinatra-demo 0 [OK]
luisbebop/docker-sinatra-hello-world 0 [OK]
bmorearty/handson-sinatra handson-ruby + Sinatra for Hands on with D... 0
subwiz/sinatra 0
bmorearty/sinatra 0
. . .
我们看到返回了大量的 sinatra
镜像。而列表中有镜像名称、描述、Stars(衡量镜像的流行程度-如果用户喜欢这个镜像他就会点击 stars )和 是否是正式版以及构建状态。官方镜像仓库 是官方精心整理出来服务 Docker 的 Docker 镜像库。自动化构建的镜像仓库是允许你验证镜像的内容和来源。
通过检索镜像,我们决定使用 training/sinatra
镜像。到目前为止,我们已经看到了两种类型的镜像,像ubuntu
镜像,我们称它为基础镜像或者根镜像。这些镜像是由 Docker 官方提供构建、验证和支持。这些镜像都可以通过用自己的名字来标记。
我们也可以查找其它用户的公开镜像,像我们选择使用的 training/sinatra
镜像,这属于个人镜像。个人镜像是由 Docker 社区的成员创建和维护的。你可以用用户名称来识别这些镜像,因为这些镜像的前缀都是以用户名来标记的 ,像 training
,就是由 training
用户创建的镜像。
拖取镜像(Pull our image)
我们已经确定了要使用的镜像, training/sinatra
, 现在我们使用 docker pull
命令来下载这个镜像。
$ sudo docker pull training/sinatra
现在团队成员可以在自己的容器内使用这个镜像了。
$ sudo docker run -t -i training/sinatra /bin/bash
root@a8cb6ce02d85:/#
创建我们自己的镜像
团队成员发现 training/sinatra
镜像对于我们来说是非常有用的,但是它不能满足我们的需求,我们需要针对这个镜像做出更改。这里我们有两种方法,更新镜像或者创建一个新的镜像。
- 我们可以从已经创建的容器中更新镜像,并且提交这个镜像。
- 我们可以使用
Dockerfile
指令来创建一个新的镜像。
!!更新并且提交更改
更新镜像之前,我们需要使用镜像来创建一个容器。
$ sudo docker run -t -i training/sinatra /bin/bash
root@0b2616b0e5a8:/#
注意:已创建容器ID
0b2616b0e5a8
,我们在后边还需要使用它。
在运行的容器内使用 gem 来安装 json
root@0b2616b0e5a8:/# gem install json
在完成操作之后,输入 exit
命令来退出这个容器。
现在我们有一个根据我们需求做出更改的容器。我们可以使用 docker commit
来提交容器副本。
$ sudo docker commit -m="Added json gem" -a="Kate Smith" \
0b2616b0e5a8 ouruser/sinatra:v2
4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c
这里我们使用了 docker commit
命令。这里我们指定了两个标识(flags) -m
和 -a
。-m
标识我们指定提交的信息,就像你提交一个版本控制。-a
标识允许对我们的更新来指定一个作者。
我们也指定了想要创建的新镜像容器来源 (我们先前记录的ID) 0b2616b0e5a8
和 我们指定要创建的目标镜像:
ouruser/sinatra:v2
我们分解一下前边的步骤。我们先给这个镜像分配了一个新用户名字 ouruser
,接着,未修改镜像名称,保留了原有镜像名称sinatra
;最后为镜像指定了标签(tag) v2
。
我们可以使用 docker images
命令来查看我们的新镜像 ouruser/sinatra
。
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
training/sinatra latest 5bc342fa0b91 10 hours ago 446.7 MB
ouruser/sinatra v2 3c59e02ddd1a 10 hours ago 446.7 MB
ouruser/sinatra latest 5db5f8471261 10 hours ago 446.7 MB
使用我们的新镜像来创建一个容器:
$ sudo docker run -t -i ouruser/sinatra:v2 /bin/bash
root@78e82f680994:/#
使用 Dockerfile
构建镜像
使用 docker commit
命令能够非常简单的扩展镜像。但是它有点麻烦,并且在一个团队中也不能够轻易的共享它的开发过程。为解决这个问题,我们使用一个新的命令 docker build
, 从零开始来创建一个新的镜像。
为此,我们需要创建一个 Dockerfile
文件,其中包含一组指令来告诉 Docker 如何构建我们的镜像。
现在创建一个目录,并且创建一个 Dockerfile
$ mkdir sinatra
$ cd sinatra
$ touch Dockerfile
如果你是在 Windows 上使用的 Boot2Docker,你可以通过使用 cd
命令来访问你的本地主机目录 /c/Users/your_user_name
每一个指令都会在镜像上创建一个新的层,来看一个简单的例子,我们的开发团建来构建一个自己的 Sinatra
镜像:
# This is a comment
FROM ubuntu:14.04
MAINTAINER Kate Smith <ksmith@example.com>
RUN apt-get update && apt-get install -y ruby ruby-dev
RUN gem install sinatra
让我们看一下我们的 Dockerfile
做了什么。每一个指令的前缀都必须是大写的。
INSTRUCTION statement
提示:我们可以使用 # 来注释
第一个指令 FROM
是告诉 Docker 使用的哪个镜像源,在这个案例中,我们使用的是 Ubuntu 14.04 基础镜像。
下一步,我们使用 MAINTAINER
指令来指定谁在维护这个新镜像。
最后,我们指定了两个 RUN
指令。 RUN
指令在镜像内执行一条命令,例如:安装一个包。这里我们更新了 APT 的缓存,并且安装 Ruby 和 RubyGems ,然后使用 gem 安装 Sinatra 。
注意:我们还提供了更多的 Dockerfile 指令参数。
现在,我们使用 Dockerfile
文件,通过 docker build
命令来构建一个镜像。
$ docker build -t ouruser/sinatra:v2 .
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
---> e54ca5efa2e9
Step 1 : MAINTAINER Kate Smith <ksmith@example.com>
---> Using cache
---> 851baf55332b
Step 2 : RUN apt-get update && apt-get install -y ruby ruby-dev
---> Running in 3a2558904e9b
Selecting previously unselected package libasan0:amd64.
(Reading database ... 11518 files and directories currently installed.)
Preparing to unpack .../libasan0_4.8.2-19ubuntu1_amd64.deb ...
Unpacking libasan0:amd64 (4.8.2-19ubuntu1) ...
Selecting previously unselected package libatomic1:amd64.
Preparing to unpack .../libatomic1_4.8.2-19ubuntu1_amd64.deb ...
Unpacking libatomic1:amd64 (4.8.2-19ubuntu1) ...
Selecting previously unselected package libgmp10:amd64.
Preparing to unpack .../libgmp10_2%3a5.1.3+dfsg-1ubuntu1_amd64.deb ...
Unpacking libgmp10:amd64 (2:5.1.3+dfsg-1ubuntu1) ...
Selecting previously unselected package libisl10:amd64.
Preparing to unpack .../libisl10_0.12.2-1_amd64.deb ...
Unpacking libisl10:amd64 (0.12.2-1) ...
Selecting previously unselected package libcloog-isl4:amd64.
Preparing to unpack .../libcloog-isl4_0.18.2-1_amd64.deb ...
Unpacking libcloog-isl4:amd64 (0.18.2-1) ...
Selecting previously unselected package libgomp1:amd64.
Preparing to unpack .../libgomp1_4.8.2-19ubuntu1_amd64.deb ...
Unpacking libgomp1:amd64 (4.8.2-19ubuntu1) ...
Selecting previously unselected package libitm1:amd64.
Preparing to unpack .../libitm1_4.8.2-19ubuntu1_amd64.deb ...
Unpacking libitm1:amd64 (4.8.2-19ubuntu1) ...
Selecting previously unselected package libmpfr4:amd64.
Preparing to unpack .../libmpfr4_3.1.2-1_amd64.deb ...
Unpacking libmpfr4:amd64 (3.1.2-1) ...
Selecting previously unselected package libquadmath0:amd64.
Preparing to unpack .../libquadmath0_4.8.2-19ubuntu1_amd64.deb ...
Unpacking libquadmath0:amd64 (4.8.2-19ubuntu1) ...
Selecting previously unselected package libtsan0:amd64.
Preparing to unpack .../libtsan0_4.8.2-19ubuntu1_amd64.deb ...
Unpacking libtsan0:amd64 (4.8.2-19ubuntu1) ...
Selecting previously unselected package libyaml-0-2:amd64.
Preparing to unpack .../libyaml-0-2_0.1.4-3ubuntu3_amd64.deb ...
Unpacking libyaml-0-2:amd64 (0.1.4-3ubuntu3) ...
Selecting previously unselected package libmpc3:amd64.
Preparing to unpack .../libmpc3_1.0.1-1ubuntu1_amd64.deb ...
Unpacking libmpc3:amd64 (1.0.1-1ubuntu1) ...
Selecting previously unselected package openssl.
Preparing to unpack .../openssl_1.0.1f-1ubuntu2.4_amd64.deb ...
Unpacking openssl (1.0.1f-1ubuntu2.4) ...
Selecting previously unselected package ca-certificates.
Preparing to unpack .../ca-certificates_20130906ubuntu2_all.deb ...
Unpacking ca-certificates (20130906ubuntu2) ...
Selecting previously unselected package manpages.
Preparing to unpack .../manpages_3.54-1ubuntu1_all.deb ...
Unpacking manpages (3.54-1ubuntu1) ...
Selecting previously unselected package binutils.
Preparing to unpack .../binutils_2.24-5ubuntu3_amd64.deb ...
Unpacking binutils (2.24-5ubuntu3) ...
Selecting previously unselected package cpp-4.8.
Preparing to unpack .../cpp-4.8_4.8.2-19ubuntu1_amd64.deb ...
Unpacking cpp-4.8 (4.8.2-19ubuntu1) ...
Selecting previously unselected package cpp.
Preparing to unpack .../cpp_4%3a4.8.2-1ubuntu6_amd64.deb ...
Unpacking cpp (4:4.8.2-1ubuntu6) ...
Selecting previously unselected package libgcc-4.8-dev:amd64.
Preparing to unpack .../libgcc-4.8-dev_4.8.2-19ubuntu1_amd64.deb ...
Unpacking libgcc-4.8-dev:amd64 (4.8.2-19ubuntu1) ...
Selecting previously unselected package gcc-4.8.
Preparing to unpack .../gcc-4.8_4.8.2-19ubuntu1_amd64.deb ...
Unpacking gcc-4.8 (4.8.2-19ubuntu1) ...
Selecting previously unselected package gcc.
Preparing to unpack .../gcc_4%3a4.8.2-1ubuntu6_amd64.deb ...
Unpacking gcc (4:4.8.2-1ubuntu6) ...
Selecting previously unselected package libc-dev-bin.
Preparing to unpack .../libc-dev-bin_2.19-0ubuntu6_amd64.deb ...
Unpacking libc-dev-bin (2.19-0ubuntu6) ...
Selecting previously unselected package linux-libc-dev:amd64.
Preparing to unpack .../linux-libc-dev_3.13.0-30.55_amd64.deb ...
Unpacking linux-libc-dev:amd64 (3.13.0-30.55) ...
Selecting previously unselected package libc6-dev:amd64.
Preparing to unpack .../libc6-dev_2.19-0ubuntu6_amd64.deb ...
Unpacking libc6-dev:amd64 (2.19-0ubuntu6) ...
Selecting previously unselected package ruby.
Preparing to unpack .../ruby_1%3a1.9.3.4_all.deb ...
Unpacking ruby (1:1.9.3.4) ...
Selecting previously unselected package ruby1.9.1.
Preparing to unpack .../ruby1.9.1_1.9.3.484-2ubuntu1_amd64.deb ...
Unpacking ruby1.9.1 (1.9.3.484-2ubuntu1) ...
Selecting previously unselected package libruby1.9.1.
Preparing to unpack .../libruby1.9.1_1.9.3.484-2ubuntu1_amd64.deb ...
Unpacking libruby1.9.1 (1.9.3.484-2ubuntu1) ...
Selecting previously unselected package manpages-dev.
Preparing to unpack .../manpages-dev_3.54-1ubuntu1_all.deb ...
Unpacking manpages-dev (3.54-1ubuntu1) ...
Selecting previously unselected package ruby1.9.1-dev.
Preparing to unpack .../ruby1.9.1-dev_1.9.3.484-2ubuntu1_amd64.deb ...
Unpacking ruby1.9.1-dev (1.9.3.484-2ubuntu1) ...
Selecting previously unselected package ruby-dev.
Preparing to unpack .../ruby-dev_1%3a1.9.3.4_all.deb ...
Unpacking ruby-dev (1:1.9.3.4) ...
Setting up libasan0:amd64 (4.8.2-19ubuntu1) ...
Setting up libatomic1:amd64 (4.8.2-19ubuntu1) ...
Setting up libgmp10:amd64 (2:5.1.3+dfsg-1ubuntu1) ...
Setting up libisl10:amd64 (0.12.2-1) ...
Setting up libcloog-isl4:amd64 (0.18.2-1) ...
Setting up libgomp1:amd64 (4.8.2-19ubuntu1) ...
Setting up libitm1:amd64 (4.8.2-19ubuntu1) ...
Setting up libmpfr4:amd64 (3.1.2-1) ...
Setting up libquadmath0:amd64 (4.8.2-19ubuntu1) ...
Setting up libtsan0:amd64 (4.8.2-19ubuntu1) ...
Setting up libyaml-0-2:amd64 (0.1.4-3ubuntu3) ...
Setting up libmpc3:amd64 (1.0.1-1ubuntu1) ...
Setting up openssl (1.0.1f-1ubuntu2.4) ...
Setting up ca-certificates (20130906ubuntu2) ...
debconf: unable to initialize frontend: Dialog
debconf: (TERM is not set, so the dialog frontend is not usable.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
Setting up manpages (3.54-1ubuntu1) ...
Setting up binutils (2.24-5ubuntu3) ...
Setting up cpp-4.8 (4.8.2-19ubuntu1) ...
Setting up cpp (4:4.8.2-1ubuntu6) ...
Setting up libgcc-4.8-dev:amd64 (4.8.2-19ubuntu1) ...
Setting up gcc-4.8 (4.8.2-19ubuntu1) ...
Setting up gcc (4:4.8.2-1ubuntu6) ...
Setting up libc-dev-bin (2.19-0ubuntu6) ...
Setting up linux-libc-dev:amd64 (3.13.0-30.55) ...
Setting up libc6-dev:amd64 (2.19-0ubuntu6) ...
Setting up manpages-dev (3.54-1ubuntu1) ...
Setting up libruby1.9.1 (1.9.3.484-2ubuntu1) ...
Setting up ruby1.9.1-dev (1.9.3.484-2ubuntu1) ...
Setting up ruby-dev (1:1.9.3.4) ...
Setting up ruby (1:1.9.3.4) ...
Setting up ruby1.9.1 (1.9.3.484-2ubuntu1) ...
Processing triggers for libc-bin (2.19-0ubuntu6) ...
Processing triggers for ca-certificates (20130906ubuntu2) ...
Updating certificates in /etc/ssl/certs... 164 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....done.
---> c55c31703134
Removing intermediate container 3a2558904e9b
Step 3 : RUN gem install sinatra
---> Running in 6b81cb6313e5
unable to convert "\xC3" to UTF-8 in conversion from ASCII-8BIT to UTF-8 to US-ASCII for README.rdoc, skipping
unable to convert "\xC3" to UTF-8 in conversion from ASCII-8BIT to UTF-8 to US-ASCII for README.rdoc, skipping
Successfully installed rack-1.5.2
Successfully installed tilt-1.4.1
Successfully installed rack-protection-1.5.3
Successfully installed sinatra-1.4.5
4 gems installed
Installing ri documentation for rack-1.5.2...
Installing ri documentation for tilt-1.4.1...
Installing ri documentation for rack-protection-1.5.3...
Installing ri documentation for sinatra-1.4.5...
Installing RDoc documentation for rack-1.5.2...
Installing RDoc documentation for tilt-1.4.1...
Installing RDoc documentation for rack-protection-1.5.3...
Installing RDoc documentation for sinatra-1.4.5...
---> 97feabe5d2ed
Removing intermediate container 6b81cb6313e5
Successfully built 97feabe5d2ed
我们使用 docker build
命令并指定 -t
标识(flag)来标示属于 ouruser
,镜像名称为 sinatra
,标签是 v2
。
如果 Dockerfile
在我们当前目录下,我们可以使用 .
来指定 Dockerfile
提示:你也可以指定
Dockerfile
路径
现在我们可以看到构建过程。Docker 做的第一件事是通过你的上下文进行构建,基本上是目录的内容构建。这样做是因为 Docker 进程构建镜像是实时构建的,并且是需要本地的上下文来做这些工作的。(这里上下文是指Context)
下一步,Dockerfile
中的每一条命令都一步一步的被执行。我们会看到每一步都会创建一个新的容器,在容器内部运行指令并且提交更改 - 就像我们之前使用的 docker commit
一样。当所有的指令执行完成之后,我们会得到97feabe5d2ed
镜像(也帮助标记为 ouruser/sinatra:v2
), 然后所有中间容器会被清除。
注意:与存储程序驱动无关,镜像不能超过127层。这是一种全局设置,为了是从整体上来优化镜像的大小。
我们可以使用新的镜像来创建容器:
$ docker run -t -i ouruser/sinatra:v2 /bin/bash
root@8196968dac35:/#
注意:这里只是简单的介绍一下如何创建镜像。我们跳过了很多你可以使用的其它指令。你会从后边的章节看到更多的指令或者你可以参考
Dockerfile
的详细说明和每一条指令的例子。为了帮助你编写清晰、易读、易维护的Dockerfile
,我们也编写了Dockerfile
最佳实践指南
更多Dockerfile教程
学习更多,请查看 Dockerfile 教程
设置镜像标签
你可以在提交更改和构建之后为镜像来添加标签(tag)。我们可以使用 docker tag
命令。为我们的 ouruser/sinatra
镜像添加一个新的标签。
$ docker tag 5db5f8471261 ouruser/sinatra:devel
docker tag
需要使用镜像ID,这里是 5db5f8471261
,用户名称、镜像源名(repository name)和新的标签名(tag)。
$ docker images ouruser/sinatra
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ouruser/sinatra latest 5db5f8471261 11 hours ago 446.7 MB
ouruser/sinatra devel 5db5f8471261 11 hours ago 446.7 MB
ouruser/sinatra v2 5db5f8471261 11 hours ago 446.7 MB
Image Digests
v2 或后续版本格式的镜像会有内容定位标示符叫做 digest
。只要用于生成镜像的镜像源不更改, digests 值就是可以预料的。使用 --digests
标识来列出镜像 digests 的值
$ docker images --digests | head
REPOSITORY TAG DIGEST IMAGE ID CREATED VIRTUAL SIZE
ouruser/sinatra latest sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 5db5f8471261 11 hours ago 446.7 MB
当我们从 v2 版本的镜像仓库来推送或者拉取镜像的时候,pull
和 push
命令包含了镜像 digests 。 你可以使用 digests
值来拉取镜像。
$ docker pull ouruser/sinatra@cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
你可以参考 digest 的 create
、run
和 rmi
命令,以及 Dockerfile
中的 FROM
镜像。
推送镜像到 Docker Hub
一旦你构建或创建了一个新的镜像,你可以使用 docker push
命令将镜像推送到 Docker Hub 。这样你就可以分享你的镜像了,镜像可以是公开的,或者你可以把镜像添加到你的私有仓库中。
$ docker push ouruser/sinatra
The push refers to a repository [ouruser/sinatra] (len: 1)
Sending image list
Pushing repository ouruser/sinatra (3 tags)
. . .
主机中移除镜像
你可以删除你主机上的镜像,类似于删处容器的方法,这里我们使用 docker rmi
命令。
让我们删除这个不需要使用的容器:training/sinatra
。
$ docker rmi training/sinatra
Untagged: training/sinatra:latest
Deleted: 5bc342fa0b91cabf65246837015197eecfa24b2213ed6a51a8974ae250fedd8d
Deleted: ed0fffdcdae5eb2c3a55549857a8be7fc8bc4241fb19ad714364cbfd7a56b22f
Deleted: 5c58979d73ae448df5af1d8142436d81116187a7633082650549c52c3a2418f0
提示:为了能够从主机上删除镜像,请确保没有基于此镜像的容器。
现在,我们已经看到如何在容器中构建单独的应用程序。接下来,我们要学习如何把多个 Docker 容器连接在一起构建一个完整的应用程序。
连接容器
在使用使用容器工作, 我们谈到了如何通过网络端口来访问运行在 Docker 容器内的服务。这是与docker容器内运行应用程序交互的一种方法。在本节中,我们打算通过端口连接到一个docker容器,并向您介绍容器连接概念。
网络端口映射
在使用容器工作部分,我们创建了一个python应用的容器。
1 | $ sudo docker run -d -P training/webapp python app.py |
注:容器有一个内部网络和IP地址(在使用Docker部分我们使用
docker inspect
命令显示容器的IP地址)。Docker可以有各种网络配置方式。你可以再这里学到更多docker网络信息。
我们使用-P
标记创建一个容器,将容器的内部端口随机映射到主机的高端口49000到49900。这时我们可以使用docker ps
来看到端口5000绑定主机端口49155。
1 | $ sudo docker ps nostalgic_morse |
我们也可以使用-p
标识来指定容器端口绑定到主机端口
1 | $ sudo docker run -d -p 5000:5000 training/webapp python app.py |
我们看这为什么不是一个好的主意呢?因为它限制了我们容器的一个端口。
我们还有很多设置-p
标识的方法。默认-p
标识会绑定本地主机上的指定端口。并且我们可以指定绑定的网络地址。举例设置localhost
1 | $ sudo docker run -d -p 127.0.0.1:5001:5002 training/webapp python app.py |
这将绑定容器内部5002端口到主机的localhost
或者127.0.0.1
的5001端口。
如果要绑定容器端口5002到宿主机动态端口,并且让localhost
访问,我们可以这样做:
1 | $ sudo docker run -d -p 127.0.0.1::5002 training/webapp python app.py |
我们也可以绑定UDP端口,我们可以在后面添加/udp
,举例:
1 | $ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py |
我们可以使用docker port
快捷方式来绑定我们的端口,这有助于向我们展示特定的端口。例如我们绑定localhost
,如下是docker port
输出:
1 | $ docker port nostalgic_morse 5000 |
注:
-p
可以使用多次配置多个端口。
Docker容器连接
端口映射并不是唯一把docker连接到另一个容器的方法。docker有一个连接系统允许将多个容器连接在一起,共享连接信息。docker连接会创建一个父子关系,其中父容器可以看到子容器的信息。
容器命名
执行此连接需要依靠你的docker的名字,这里我们可以看到当我们创建每一个容器的时候,它都会自动被命名。事实上我们已经熟悉了老的nostalgic_morse
指南。你也可以自己命名容器。这种命名提供了两个有用的功能:
- 1.给容器特定的名字使你更容易记住他们,例如:命名web应用程序为web容器。
- 2.它为docker提供一个参考,允许其他容器引用,举例连接web容器到db容器。
你可以使用--name
标识来命名容器,举例:
1 | $ sudo docker run -d -P --name web training/webapp python app.py |
我们可以看到我们启动了的容器,就是我们使用--name
标识命名为web
的容器。我们可以使用docker ps
命令来查看容器名称。
1 | $ sudo docker ps -l |
我们也可以使用docker inspect
来返回容器名字。
1 | $ sudo docker inspect -f "{{ .Name }}" aed84ee21bde |
注:容器的名称必须是唯一的。这意味着你只能调用一个web容器。如果你想使用重复的名称来命名容器,你需要使用
docker rm
命令删除以前的容器。在容器停止后删除。
!!容器连接
连接允许容器之间可见并且安全地进行通信。使用--link
创建连接。我们创建一个新容器,这个容器是数据库。
1 | $ sudo docker run -d --name db training/postgres |
这里我们使用training/postgres
容器创建一个新的容器。容器是PostgreSQL数据库。
现在我们创建一个web
容器来连接db
容器。
1 | $ sudo docker run -d -P --name web --link db:db training/webapp python app.py |
这将使我们的web容器和db容器连接起来。--link
的形式
--link name:alias
name
是我们连接容器的名字,alias
是link的别名。让我们看如何使用alias。
让我们使用docker ps
来查看容器连接.
1 | $ docker ps |
我们可以看到我们命名的容器,db
和web
,我们还在名字列中可以看到web容器也显示db/web
。这告诉我们web容器和db容器是父/子关系。
我们连接容器做什么?我们发现连接的两个容器是父子关系。这里的父容器是db
可以访问子容器web
。为此docker在容器之间打开一个安全连接隧道不需要暴露任何端口在容器外部。你会注意到当你启动db容器的时候我们没有使用-P
或者-p
标识。我们连接容器的时候我们不需要通过网络给PostgreSQL数据库开放端口。
Docker在父容器中开放子容器连接信息有两种方法:
- 环境变量
- 更新
/etc/hosts
文件。
让我们先看看docker的环境变量。我们运行env
命令来查看列表容器的环境变量。
1 | $ sudo docker run --rm --name web2 --link db:db training/webapp env |
注:这些环境变量只设置顶一个进程的容器。同样,一些守护进程(例如sshd)进行shell连接时就会去除。
我们可以看到docker为我们的数据库容器创建了一系列环境变量。每个前缀变量是DB_
填充我们指定的别名。如果我们的别名是db1
,前缀别名就是DB1_
。您可以使用这些环境变量来配置您的应用程序连接到你的数据库db容器。该连接时安全、私有的,只能在web容器和db容器之间通信。
docker除了环境变量,可以添加信息到父主机的/etc/hosts
1 | root@aed84ee21bde:/opt/webapp# cat /etc/hosts |
这里我们可以看到两个主机项。第一项是web容器用容器ID作为主机名字。第二项是使用别名引用IP地址连接数据库容器。现在我们试试ping这个主机:
1 | root@aed84ee21bde:/opt/webapp# apt-get install -yqq inetutils-ping |
注:我们不得不安装
ping
,因为容器内没有它。
我们使用ping
命令来pingdb
容器,使用它解析到127.17.0.5
主机。我们可以利用这个主机项配置应用程序来使用我们的db
容器。
注:你可以使用一个父容器连接多个子容器。例如,我们可以有多个web容器连接到我们的db数据库容器。
现在我们已经学会如何连接容器了,下一步我们学习融合管理数据、卷标和如果在容器内挂载。
管理容器数据
到目前为止,我们已经介绍了docker的一些基本概念,了解了如何使用docker镜像,以及容器之间如何通过网络连接。本节,我们来讨论如何管理容器和容器间的共享数据。
接下来,我们将主要介绍Docker管理数据的两种主要的方法:
- 数据卷
- 数据卷容器
数据卷
数据卷是指在存在于一个或多个容器中的特定目录,此目录能够绕过Union File System提供一些用于持续存储或共享数据的特性。
- 数据卷可在容器之间共享或重用
- 数据卷中的更改可以直接生效
- 数据卷中的更改不会包含在镜像的更新中
- 数据卷的生命周期一直持续到没有容器使用它为止
添加一个数据卷
你可以在docker run
命令中使用-v
标识来给容器内添加一个数据卷,你也可以在一次docker run
命令中多次使用-v
标识挂载多个数据卷。现在我们在web容器应用中创建单个数据卷。
$ sudo docker run -d -P --name web -v /webapp training/webapp python app.py
这会在容器内部创建一个新的卷/webapp
注:类似的,你可以在
Dockerfile
中使用VOLUME
指令来给创建的镜像添加一个或多个数据卷。
挂载一个主机目录作为卷
使用-v
,除了可以创建一个数据卷,还可以挂载本地主机目录到容器中:
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
这将会把本地目录/src/webapp
挂载到容器的/opt/webapp
目录。这在做测试时是非常有用的,例如我们可以挂载宿主机的源代码到容器内部,这样我们就可以看到改变源代码时的应用时如何工作的。宿主机上的目录必须是绝对路径,如果目录不存在docker会自动创建它。
注:出于可移植和分享的考虑,这种方法不能够直接在
Dockerfile
中实现。作为宿主机目录——其性质——是依赖于特定宿主机的,并不能够保证在所有的宿主机上都存在这样的特定目录。
docker默认情况下是对数据卷有读写权限,但是我们通过这样的方式让数据卷只读:
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py
这里我们同样挂载了/src/webapp
目录,只是添加了ro
选项来限制它只读。
将宿主机上的特定文件挂载为数据卷
除了能挂载目录外,-v
标识还可以将宿主机的一个特定文件挂载为数据卷:
$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
上述命令会在容器中运行一个bash shell,当你退出此容器时在主机上也能够看到容器中bash的命令历史。
注:很多文件编辑工具如
vi
和sed --in-place
会导致inode change。Docker v1.1.0之后的版本,会产生一个错误:“sed cannot rename ./sedKdJ9Dy: Device or resource busy”。这种情况下如果想要更改挂载的文件,最好是直接挂载它的父目录。
创建、挂载数据卷容器
如果你想要容器之间数据共享,或者从非持久化容器中使用一些持久化数据,最好创建一个指定名称的数据卷容器,然后用它来挂载数据。
让我们创建一个指定名称的数据卷容器:
$ sudo docker run -d -v /dbdata --name dbdata training/postgres echo Data-only container for postgres
你可以在另外一个容器使用--volumes-from
标识,通过刚刚创建的数据卷容器来挂载对应的数据卷。
$ sudo docker run -d --volumes-from dbdata --name db1 training/postgres
可以将对应的数据卷挂载到更多的容器中:
$ sudo docker run -d --volumes-from dbdata --name db2 training/postgres
当然,您也可以对一个容器使用多个--volumes-from
标识,来将多个数据卷桥接到这个容器中。
数据卷容器是可以进行链式扩展的,之前的dbdata
数据卷依次挂载到了dbdata 、db1和db2容器,我们还可以使用这样的方式来将数据卷挂载到新的容器db3:
$ sudo docker run -d --name db3 --volumes-from db1 training/postgres
即使你删除所有de 挂载了数据卷dbdata的容器(包括最初的dbdata
容器和后续的db1
和db2
),数据卷本身也不会被删除。要删在磁盘上删除这个数据卷,只能针对最后一个挂载了数据卷的容器显式地调用docker rm -v
命令。这种方式可使你在容器之间方便的更新和迁移数据。
备份、恢复或者迁移数据卷
数据卷还可以用来备份、恢复或迁移数据。为此我们使用--volumes-from
参数来创建一个挂载数据卷的容器,像这样:
$ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
这里我们启动了一个挂载dbdata
卷的新容器,并且挂载了一个本地目录作为/backup
卷。最后,我们通过使用tar命令将dbdata
卷的内容备份到容器中的/backup
目录下的backup.tar
文件中。当命令完成或者容器停止,我们会留下我们的dbdata
卷的备份。
然后,你可以在同一容器或在另外的容器中恢复此数据。创建一个新的容器
$ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
然后在新的容器中的数据卷里un-tar此备份文件。
$ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar
你可以对熟悉的目录应用此技术,来测试自动备份、迁移和恢复。
本小节中,我们学习了关于数据卷和数据卷容器使用方法。接下来,我们要看看如何使用Docker Hub服务,包括自动化构建和私人仓库。
使用Docker Hub
现在你已经学习了如何利用命令行在本地运行Docker,还学习了如何拉取镜像用于从现成的镜像中构建容器,并且还学习了如何创建自己的镜像。
接下来,你将会学到如何利用Docker Hub简化和提高你的Docker工作流程。
Docker Hub是一个由Docker公司负责维护的公共注册中心,它包含了超过15,000个可用来下载和构建容器的镜像,并且还提供认证、工作组结构、工作流工具(比如webhooks)、构建触发器以及私有工具(比如私有仓库可用于存储你并不想公开分享的镜像)。
Docker命令和Docker Hub
Docker通过docer search
、pull
、login
和push
等命令提供了连接Docker Hub服务的功能,本页将展示这些命令如何工作的。
账号注册和登陆
一般,你需要先在docker中心创建一个账户(如果您尚未有)。你可以直接在Docker Hub创建你的账户,或通过运行:
$ sudo docker login
这将提示您输入用户名,这个用户名将成为你的公共存储库的命名空间名称。如果你的名字可用,docker会提示您输入一个密码和你的邮箱,然后会自动登录到Docker Hub,你现在可以提交和推送镜像到Docker Hub的你的存储库。
注:你的身份验证凭证将被存储在你本地目录的
.dockercfg
文件中。
搜索镜像
你可以通过使用搜索接口或者通过使用命令行接口在Docker Hub中搜索,可对镜像名称、用户名或者描述等进行搜索:
$ sudo docker search centos
NAME DESCRIPTION STARS OFFICIAL TRUSTED
centos Official CentOS 6 Image as of 12 April 2014 88
tianon/centos CentOS 5 and 6, created using rinse instea... 21
...
这里你可以看到两个搜索的示例结果:centos
和tianon/centos
。第二个结果是从名为tianon/
的用户仓储库搜索到的,而第一个结果centos
没有用户空间这就意味着它是可信的顶级命名空间。/
字符分割用户镜像和存储库的名称。
当你发现你想要的镜像时,便可以用docker pull <imagename>
来下载它。
$ sudo docker pull centos
Pulling repository centos
0b443ba03958: Download complete
539c0211cd76: Download complete
511136ea3c5a: Download complete
7064731afe90: Download complete
现在你有一个镜像,基于它你可以运行容器。
向Docker Hub贡献
任何人都可以从Docker Hub
仓库下载镜像,但是如果你想要分享你的镜像,你就必须先注册,就像你在第一部分的docker用户指南看到的一样。
推送镜像到Docker Hub
为了推送到仓库的公共注册库中,你需要一个命名的镜像或者将你的容器提到为一个命名的镜像,正像这里我们所看到的。
你可以将此仓库推送到公共注册库中,并以镜像名字或者标签来对其进行标记。
$ sudo docker push yourname/newimage
镜像上传之后你的团队或者社区的人都可以使用它。
Docker Hub特征
让我们再进一步看看Docker Hub的特色,这里你可以看到更多的信息。
- 私有仓库
- 组织和团队
- 自动构建
- Webhooks
私有仓库
有时候你不想公开或者分享你的镜像,所以Docker Hub允许你有私有仓库,你可以在这里登录设置它。
组织和机构
私人仓库一个较有用的地方在于你可以将仓库分享给你团队或者你的组织。Docker Hub支持创建组织,这样你可以和你的同事来管理你的私有仓库,在这里你可以学到如何创建和管理一个组织。
自动构建
自动构建功能会自动从Github和BitBucket直接将镜像构建或更新至Docker Hub,通过为Github或Bitbucket的仓库添加一个提交的hook来实现,当你推送提交的时候就会触发构建和更新。
设置一个自动化构建你需要:
- 1.创建一个Docker Hub账户并且登陆
- 2.通过Link Accounts菜单连接你的GitHub或者BitBucket
- 3.配置自动化构建
- 4.选择一个包含
Dockerfile
的Github或BitBucket项目 - 5.选择你想用于构建的分支(默认是
master
分支) - 6.给自动构建创建一个名称
- 7.指定一个Docker标签来构建
- 8.指定
Dockerfile
的路径,默认是/
。
一旦配置好自动构建,在几分钟内就会自动触发构建,你就会在Docker Hub仓库源看到你新的构建,并且它将会和你的Github或者BitBucket保持同步更新直到你解除自动构建。
如果你想看到你自动化构建的状态,你可以去你的Docker Hub自动化构建页面,它将会想你展示你构建的状态和构建历史。
一旦你创建了一个自动化构建,你可以禁用或删除它。但是,你不能通过docker push
推送一个自动化构建,而只能通过在Github或者BitBucket提交你的代码来管理它。
你可以在一个仓库中创建多个自动构建,配置它们只指定的Dockerfile
或Git 分支。
构建触发器
自动构建也可以通过Docker Hub的Url来触发,这样你就可以通过命令重构自动构建镜像。
Webhooks
webhooks属于你的存储库的一部分,当一个镜像更新或者推送到你的存储库时允许你触发一个事件。当你的镜像被推送的时候,webhook可以根据你指定的url和一个有效的Json来递送。
去使用Docker吧!
更多参考 widuu/chinese_docker