Docker客户端
Docker客户端(见图2-4)是Docker架构中最简单的部件。在主机上输入docker run或docker pull这类命令时运行的便是它。它的任务是通过HTTP请求与Docker守护进程进行通信。
在本节中,读者将看到如何监听Docker客户端与服务器之间的信息,还将看到一些与端口映射有关的基本技巧,这是向本书后续的编排章节迈进的一小步,也是使用浏览器作为Docker客户端的一种方式。
使用socat监控Docker API流量
有时docker命令可能会不按预期工作。多数时候是因为没有理解命令行参数的某些部分,不过偶尔也存在更严重的安装问题,如Docker的二进制文件过时了。为了诊断问题,查看与之通信的Docker守护进程来往的数据流是十分有用的。
Docker不是不稳定的 不用惊慌!本技巧的存在不表示Docker需要经常调试,或者有任何的不稳定!
这条技巧在此是为了理解Docker架构的一个工具,同时也是为了介绍socat这个强大的工具。
如果读者像我们一样,在众多不同的地方使用Docker,所使用的Docker版本将会有差异。与任何软件一样,不同的版本将具有不同的功能和标志,这可能会让读者无所适从。
问题
想要调试一个Docker命令的问题。
解决方案
使用流量监听器(traffic snooper)来检查API调用,并自行解决。
讨论在本技巧中,用户将在自己的请求与服务器套接字之间插入一个代理Unix域套接字,并查看通过它的内容(如图2-5所示)。注意,要完成这一步需要root或sudo权限。
要创建这个代理,会用到socat。
$ sudo socat -v UNIX-LISTEN:/tmp/dockerapi.sock UNIX-CONNECT:/var/run/docker.sock &
socat是一个强大的命令,能让用户在两个几乎任意类型的数据通道之间中继数据。如果熟悉netcat,可以将其看作是加强版的netcat。
在这条命令中,-v用于提高输出的可读性,带有数据流的指示。
UNIX-LISTEN部分是让socat在一个Unix套接字上进行监听,而UNIX-CONNECT是让socat连接到Docker的Unix套接字。“&”符号指定在后台运行该命令。
发往守护进程的请求所经过的新路由如图2-6所示。所有双向流量都会被socat看到,并与Docker客户端所提供的任何输出一起记录到终端日志中。
图2-6 插入socat作为代理的Docker客户端与服务器
现在一个简单的docker命令的输出看起来将类似下面这样:
$ docker -H unix:///tmp/dockerapi.sock ps –a (1)
> 2015/01/12 04:34:38.790706 length=105
from=0 to=104 (2)
GET /v1.16/containers/json?all=1 HTTP/1.1r
Host: /tmp/dockerapi.sockr
User-Agent: Docker-Client/1.4.1r r
< 2015/01/12 04:34:38.792516 length=544
from=0 to=543 (3)
HTTP/1.1 200 OKr
Content-Type: application/jsonr Date: Mon, 12 Jan 2015 09:34:38 GMTr Content-Length: 435r
r
[{"Command":"/bin/bash","Created":1420731043,"Id": ➥ "
4eec1b50dc6db7901d3b3c5a8d607f2576829fd6902c7f658735c3bc0a09a39c",
➥ "Image":"debian:jessie","Names":["/lonely_mclean"],"Ports":[],
➥ "Status":"Exited (0) 3 days ago"} (4)
,{"Command":"/bin/bash","Created":1420729129,"Id":
➥ "029851aeccc887ecf9152de97f524d30659b3fa4b0dcc3c3fe09467cd0164da5",
➥ "Image":"debian:jessie","Names":["/suspicious_torvalds"],"Ports":[],
➥ "Status":"Exited (130) 3 days ago"} ]CONTAINER ID IMAGE COMMAND CREATED
➥ STATUS PORTS
NAMES (5)
4eec1b50dc6d debian:jessie "/
bin/bash" 3 days ago
➥ Exited (0) 3 days ago lonely_mclean 029851aeccc8 debian:jessie "/bin/bash" 3 days ago
➥ Exited (130) 3 days ago
suspicious_torvalds
(1)用于查看请求与响应所发送的命令
(2)HTTP请求从此处开始,左侧带有右尖括号
(3)HTTP响应从此处开始,左侧带有左尖括号
(4)来自Docker服务器的响应的JSON内容
(5)用户正常看到的输出,由Docker客户端从前面的JSON解释而来。
小心 如果在前面的示例中以root身份运行socat,需要使用sudo来运行docker -H命令。这是因为dockerapi.sock文件的所有者是root。
使用socat不仅对Docker来说是一种强大的调试方式,对工作过程中可能碰到的任何其他网络服务也是如此。
使用端口连接容器
Docker容器从一开始就被设计用于运行服务。在大多数情况下,都是这样或那样的 HTTP服务。其中很大一部分是可以使用浏览器访问的Web服务。
这会造成一个问题。如果有多个Docker容器运行在其内部环境中的80端口上,它们将无法全部通过宿主机的80端口进行访问。接下来的技巧将展示如何通过暴露和映射容器的端口来管理这一常见场景。
问题
想要将多个运行在同一个端口的Docker容器服务暴露到宿主机上。
解决方案
使用Docker的-p标志将容器的端口映射到宿主机上。
讨论
在这个示例中,我们将使用tutum-wordpress镜像。假设想在宿主机上运行两个实例来服务不同的博客。由于此前有很多人想这么做,已经有人准备了任何人都可以获取并启动的镜像。要从外部地址获取镜像,可以使用docker pull命令。在默认情况下,镜像将从Docker Hub下载:
$ docker pull tutum/wordpress
要运行第一个博客,可使用如下命令:
$ docker run -d -p 10001:80 --name blog1 tutum/wordpress
这里的docker run命令以守护进程方式(-d)及发布标志(-p)运行容器。它指定将宿主机端口(10001)映射到容器端口(80)上,并赋予该容器一个名称用于识别它(--name blog1 tutum/wordpress)。
可以对第二个博客做相同操作:
$ docker run -d -p 10002:80 --name blog2 tutum/wordpress
如果现在运行这个命令:
$ docker ps -a | grep blog
将看到列出的两个博客容器及其端口映射,看起来像下面这样:
9afb95ad3617 tutum/wordpress:latest "/run.sh"
9 seconds ago Up 9 seconds ➥ 3306/tcp, 0.0.0.0:10001->80/tcp
blog1 31ddc8a7a2fd tutum/wordpress:latest "/run.sh" 17 seconds ago Up 16 seconds ➥ 3306/tcp, 0.0.0.0:10002->80/tcp blog2
现在可以通过浏览http://localhost:10001和http://localhost:10002来访问自己的容器。
要在完成后删除这些容器(假设不想保留它们),可运行下面这个命令:
$ docker rm -f blog1 blog2
如果需要的话,现在就可以通过管理端口分配在宿主机上运行多个相同的镜像和服务了。
牢记-p标志的参数顺序 在使用-p标志时,很容易忘记哪个端口属于宿主机,哪个端口属于容器。
我们可以将它看作是在从左向右读一个句子。用户连接到宿主机(-p),并从宿主机的端口传递到容器的端口(宿主机端口:容器端口)。
如果熟悉SSH的端口转发命令的话,会发现它们的格式是一样的。
链接容器实现端口隔离
上面展示的是如何通过暴露端口将容器开放给宿主机网络。
用户不会总想将服务暴露给宿主机或外界,但是会希望容器彼此相连。本技巧展示的是如何使用Docker的链接标志来实现这一点,并确保外人无法访问内部服务。
问题出于内部目的,想要让容器间实现通信。
解决方案
使用Docker的链接功能可以让容器彼此通信。讨论继续安装WordPress的任务,我们将把mysql数据库层从wordpress容器中分离出来,并将它们链接在一起,且不需要进行端口配置。图2-7展示了最终状态的概览。
为什么这一点很有用?
既然已经可以将端口暴露给宿主机来使用,为什么还要用链接?链接可以让用户封装并定义容器间的关系,而无须将服务暴露给宿主机网络(即可能暴露给外界)。用户可能会因为安全因素而这么做。
要像这样运行容器,可按照以下顺序执行,并在第一条和第二条命令之间暂停大约一分钟:
$ docker run --name wp-mysql
-e MYSQL_ROOT_PASSWORD=yoursecretpassword -d mysql (1)
$ docker run --name wordpress
--link wp-mysql:mysql -p 10003:80 -d wordpress (2)
首先将mysql容器命名为wp-mysql,用于在后面引用它❶。还需要提供一个环境变量以便mysql容器可以初始化数据库(-e MYSQL_ROOT_PASSWORD=yoursecretpassword)。两个容器都以守护进程方式运行(-d),同时使用了Docker Hub上官方mysql镜像的引用。
在第二个命令❷ 中,将wordpress容器命名为wordpress,以备后面要引用它。同时将wp-mysql容器链接到wordpress容器中(--link wp-mysql:mysql)。在wordpress容器内对mysql服务器的引用将被发送到名为wp-mysql的容器中。有如技巧5所述,使用了一个本地端口映射(-p 10003:80),并添加了Docker Hub上官方wordpress镜像(wordpress)的引用。
请注意,链接不会等待被链接容器启动,因此才有在命令之间暂停的指示。完成这一步更精确的方法是,在运行wordpress容器之前,在docker logs wp-mysql的输出中查找mysqid: ready for connections。
如果现在浏览http://localhost:10003,将会看到wordpress介绍画面,并可设置这个wordpress实例。这个示例的关键在于第二条命令里的--link标志。
这个标志会设置容器的host文件以便wordpress容器能够引用mysql服务器,这将被路由到具有“wp-mysql”名称的容器。这有很大的好处,即无须对wordpress容器做任何改动,就可以将不同的mysql容器交换进来,使不同服务的配置管理变得更简单。
启动顺序至关重要 容器必须以正确的顺序启动,以便能对已经存在的容器名称做映射。截至编写本书时,Docker不具备动态解析链接的功能。
为了使用这种方式链接容器,在构建镜像时必须指定暴露容器的端口。这可以通过在镜像构建的Dockerfile中使用EXPOSE命令来达成。现在,已经见识了Docker编排的一个简单示例,并朝着微服务构架前进了一步。在这个例子中,可以在不影响wordpress容器的同时对mysql容器进行操作,反之亦然。这种对运行中服务的细粒度控制是微服务构架的关键的运维优势之一。
在浏览器中使用Docker
销售新技术可能很艰难,因此简单而有效的演示是非常有价值的。让演示可操作则效果更佳,这也是为什么我们发现,为了以易于达成的方式给新手带来Docker的初体验,创建一个能在浏览器中与容器进行交互的网页是一个非常棒的技巧。这种让人眼前一亮的体验没有坏处!
问题
想要演示Docker的强大威力,用户无须自己安装Docker或运行自己不理解的命令。
解决方案
使用一个开放端口启动Docker守护进解决方案使用一个开放端口启动Docker守护进程,并启用CORS[1]。然后使用所选择的Web服务器为Docker终端仓库提供服务。
讨论
REST API最常见的用法是在一台服务器上暴露它,并在一个网页上使用JavaScript来调用。由于Docker正巧是通过REST API来执行所有交互的,因此可以使用相同方式来控制Docker。
尽管一开始看起来有点儿令人惊讶,但这种控制一直延伸到能通过浏览器里的终端与容器进行交互。我们在技巧1中已经讨论过如何在2375端口上启动守护进程,因而不再赘述。
此外,CORS太庞大,这里无法深入讲述(可以参考Monsur Hossain所著的CORS in Action[Manning Publications, 2014])——简言之,它是小心地绕过限制JavaScript只能访问当前域这一常规限制的一种机制。在这个例子中,它将允许守护进程监听一个与提供Docker终端页面不同的端口上。要启用它,需要使用--api-enable-cors选项和用于监听端口的选项一起来启动Docker守护进程。现在,先决条件已经梳理好,我们将它运行起来。首先,需要获取代码:
cd Docker-Terminal
然后需要提供文件服务:
python2 -m SimpleHTTPServer 8000
上述命令使用Python内置的一个模块为目录中的静态文件服务。用户可以使用任何自己喜欢的等效服务。现在可以在浏览器中访问http://localhost:8000并启动一个容器。
图2-8展示了Docker终端是如何连接起来的。页面托管在本地计算机中,并连接到本地计算机上的Docker守护进程,以执行所有操作。
如果想把链接发送给其他人,以下几点值得注意。其他人不能使用任何类型的代理。
这是我们见到的最常见的错误根源——Docker终端使用Websocket,后者目前无法通过代理工作。
给出指向localhost的链接明显无法工作——需要给出外部IP地址。Docker终端需要知道上哪儿找到Docker API——它应该可以根据浏览器访问的地址自动完成这一点,不过这一点需要留意。
这里为什么不使用Docker 如果读者的Docker经验更丰富,会奇怪为什么我们没在这个技巧中使用Docker。原因是,我们还在介绍Docker,不想给刚接触Docker的读者增加复杂度。“Docker化”这个技巧将作为一个练习留给读者。
Docker注册中心
一旦创建了镜像,读者可能就想与其他用户分享它。这是Docker注册中心概念的所在。图2-9中的3个注册中心差别在于它们的可达性。一个处于私有网络上,一个开放在公共网络中,而另一个是公共的但只有注册用户才能使用Docker访问。它们全部使用相同的API完成相同的功能,这就是Docker守护进程如何知道怎样与它们进行相互通信。
Docker注册中心允许多个用户使用REST风格API将镜像推送到一个中央存储中,也可以从中拉取镜像。与Docker自身一样,注册中心代码也是开源的。
很多公司(如我们公司)建立了私有注册中心在内部存储和共享专有的镜像。这是在进一步说明Docker公司的注册中心之前,我们这里将要讨论的东西。
建立一个本地Docker注册中心
读者已经看到Docker公司具有一项服务,人们可以在其上公开地共享他们的镜像(如果想私下进行,可以付费实现)。不过存在一些不想通过Hub来共享镜像的原因——有些商业组织想尽可能把东西保留在内部,或者镜像可能很大,通过互联网传输太慢,或者也许想在试验时保持镜像私有化,同时又不想付费。不管出于什么原因,幸运的是有一个简单的解决方案。
问题
想要一个在本地托管镜像的方法。
解决方案
在本地网络上建立一个注册中心服务器。
讨论
要让注册中心运行起来,可在一台具有大量磁盘空间的机器上发起以下命令:
$ docker run -d -p 5000:5000 -v $HOME/registry:/var/lib/registry registry:2
这条命令让注册中心运行于Docker宿主机的5000端口上(-p 5000:5000),并使用主目录下的registry作为容器的/var/lib/registry目录,后者是容器里的registry默认存储文件的位置。它同时指定了容器内的registry将文件存储在/registry目录下(STORAGE_PATH=/registry)。
在所有想访问这个注册中心的机器上,将下列内容添加到守护进程选项中(HOSTNAME是新的注册中心服务器的主机名或IP地址):--insecure-registry HOSTNAME。
现在可以执行docker push HOSTNAME:5000/image:tag。正如所见,一个本地注册中心最基础层次的配置很简单,所有数据都存储在$HOME/registry目录中。
如果要扩容或让它变得更健壮,GitHub上的仓库(https://github.com/ docker/distribution/ blob/v2.2.1/docs/storagedrivers.md )罗列了一些可选项,例如,在Amazon S3里存储数据。
读者可能会对--insecure-registry选项感到好奇。为了帮助用户保持安全,Docker只允许使用签名HTTPS证书从注册中心上拉取。因为对本地网络相当信任,我们覆盖了这个选项。不过,毫无疑问的是,在互联网上这么做必须慎之又慎!
注册中心路线图 与Docker生态系统里的其他事物一样,注册中心也在发生变化。尽管注册中心镜像将保持可用及稳定,但它最终将被一个名为distribution(见https://github.com/docker/distribution)的新工具取代。
Docker Hub
HubDocker Hub(见图2-10)是由Docker公司维护的一个注册中心。它拥有成千上万个镜像可供下载和运行。任何Docker用户都可以在上面创建免费账号及公共Docker镜像。除了用户提供的镜像,上面还维护着一些作为参考的官方镜像。
镜像受用户认证的保护,同时具有一个与GitHub类似的支持率打星系统。
这些官方镜像的表现形式可能是Linux发行版,如Ubuntu或Cent OS,或是预装软件包,如Node.js,或是完整的软件栈,如WordPress。
查找并运行一个Docker镜像
Docker注册中心造就的是与GitHub相似的社交编码文化。如果读者有兴趣尝试一个新的软件应用程序,或正在找寻服务于某个特定用途的新的应用程序,那么Docker镜像将是一个简单的实验手段,它不会对宿主机造成干扰,不需要配备一个虚拟机,也不必担心安装步骤。
问题
想要查找一个Docker镜像形式的应用程序或工具,并进行尝试。
解决方案
使用docker search命令来查找要拉取的镜像,然后运行它。
讨论
假设读者对Node.js有兴趣。在下面的代码中,我们使用docker search命令搜索出匹配“node”的镜像:
$ docker search node
NAME
DESCRIPTION
➥ STARS OFFICIAL AUTOMATED
node Node.js is a JavaScript-based platform for...
➥ 432 [OK] (1)
dockerfile/nodejs Trusted automated Node.js (http://nodejs.o...
➥ 57 [OK] (2)
dockerfile/nodejs-bower-grunt Trusted automated Node.js (http://nodejs.o...
➥ 17 [OK] (3)
nodesource/node
➥ 9 [OK] (4) selenium/node-firefox
➥ 5 [OK]
selenium/node-chrome
➥ 5 [OK]
selenium/node-base
➥ 3 [OK]
strongloop/node StrongLoop, Node.js, and tools.
➥ 3 [OK]
selenium/node-chrome-debug
➥ 3 [OK]
dockerfile/nodejs-runtime Trusted automated Node.js runtime Build ..
➥ 3 [OK] jprjr/stackbrew-node
A stackbrew/ubuntu-based image for Docker,...
➥ 2 [OK]
selenium/node-firefox-debug
➥ 2 [OK]
maccam912/tahoe-node Follow "The Easy Way" in the description t...
➥ 1 [OK]
homme/node-mapserv The latest checkouts of Mapserver and its ...
➥ 1 [OK]
maxexcloo/nodejs Docker framework container with Node.js an...
➥ 1 [OK]
brownman/node-0.10
➥ 0 [OK]
kivra/node Image with build dependencies for frontend...
➥ 0 [OK]
thenativeweb/node
➥ 0 [OK]
thomaswelton/node
➥ 0 [OK]
siomiz/node-opencv _/node + node-opencv
➥ 0 [OK]
bradegler/node
➥ 0 [OK] t
cnksm/centos-node Dockerfile for CentOS packaging node
➥ 0 [OK]
azukiapp/node ➥ 0 [OK]
onesysadmin/node-imagetools ➥ 0 [OK]
fishead/node ➥ 0 [OK]
(1)docker search的输出是按评星数量排序的
(2)描述是上传者对镜像用途的解释
(3)官方镜像是指受Docker Hub信任的镜像
(4)自动化镜像是指使用Docker Hub自动化构建功能构建的镜像一旦选择了一个镜像,就可以通过对其名称执行docker pull命令来下载它:
$ docker pull node (1)
node:latest: The image you are pulling has been verified
81c86d8c1e0c: Downloading 81c86d8c1e0c: Pull complete 3a20d8faf171: Pull complete c7a7a01d634e: Pull complete 2a13c2a76de1: Pull complete 4cc808131c54: Pull complete bf2afba3f5e4: Pull complete 0cba665db8d0: Pull complete 322af6f234b2: Pull complete 9787c55efe92: Pull complete 511136ea3c5a: Already exists bce696e097dc: Already exists 58052b122b60: Already exists Status: Downloaded newer image for node:latest (2)
(1)从Docker Hub拉取名为node的镜像
(2)如果Docker拉取了一个新的镜像(与之相对的是说明没有比已有镜像更新的版本),会显示这条信息。读者看到的输出可能会有所不同。
接着,可以使用-t和-i标志以交互方式运行它。
-t标志指明创建一个tty设备(一个终端),而-i标志指明该Docker会话是交互式的:
$ docker run -t -i node /bin/bash
root@c267ae999646:/# node
> process.version 'v0.12.0'
>
-ti标志惯用法
可以在上述docker run调用中用-ti取代-t -i来减少输入。从这里开始,本书将使用这种用法。
常常会有来自镜像维护人员的有关如何运行镜像的建议。在http://hub.docker.com网站上搜索镜像将引导到该镜像的页面。其描述标签页可提供更多信息。
这个镜像可信吗
如果用户下载并运行了一个镜像,运行的将是自己无法充分验证的代码。虽然使用受信任的镜像具有相对的安全性,但是通过互联网下载和运行软件时,没有什么是能保证100%安全的。
有了这方面的知识和经验,现在可以对Docker Hub提供的大量资源进行挖掘了。毫不夸张地说,要试用这成千上万的镜像,有很多东西要学。请慢慢享受!