第2章 理解Docker——深入引擎室

    本章主要内容

    • Docker的架构
    • 在用户的宿主机上追溯Docker的内部结构
    • 使用Docker Hub查找和下载镜像
    • 设置自己的Docker注册中心(registry)
    • 实现容器间的相互通信

    掌握Docker的架构是更全面地理解Docker的关键。在本章中,读者将在自己的主机和网络上对Docker的主要组件进行大致了解,并学习一些有助于增进这种理解的技巧。

    在这个过程中,将学习一些有助于更有效地使用Docker(及Linux)的小窍门。后续的更高级的很多技巧都是基于这里所见的部分,因此请特别留意以下内容。

    图2-1展示了Docker的架构,这将是本章的核心内容。我们将从高层次视角入手,然后聚焦到每个部分,使用设计好的技巧来巩固理解。

    宿主机上的Docker(在编写本书时)分成两个部分:一个具有REST风格API的守护进程,以及一个与守护进程通信的客户端。图2-1展示的是运行着Docker客户端和守护进程的宿主机。

    REST风格 一个REST风格API是指使用标准HTTP请求类型,如GETPOSTDELETE等,来执行通常符合HTTP设计者预想的功能的API。

    调用Docker客户端可以从守护进程获取信息或给它发送指令。守护进程是一个服务器,它使用HTTP协议接收来自客户端的请求并返回响应。相应地,它会向其他服务发起请求来发送和接收镜像,使用的同样是HTTP协议。该服务器将接收来自命令行客户端或被授权连接的任何人的请求。守护进程还负责在幕后处理用户的镜像和容器,而客户端充当的是用户与REST风格API之间的媒介。

    2

    图2-1 Docker架构概览

    私有Docker注册中心是存储Docker镜像的一项服务,可以从任何有相应权限的Docker守护进程向其发送请求。这个注册中心处于内部网络中,不能公开访问,因此被视为是私有的。

    宿主机一般坐落在一个私有网络上。在收到请求时,Docker守护进程将连接互联网来获取镜像。

    Docker Hub是由Docker公司运营的一个公共的注册中心。互联网上也存在其他公共的注册中心,且Docker守护进程可与之进行交互。

    在第1章中我们说可以将Docker容器分发到任何能运行Docker的地方——这并不完全正确。实际上,只有当守护进程可以被安装到机器上时,容器才能在这台机器上运行。最明显的事实是,Docker客户端可以运行在Windows上,但守护进程(还)不行。

    理解这张图的关键在于,当用户在自己的机器上运行Docker时,与其进行交互的可能是自己机器上的另一个进程,或者甚至是运行在内部网络或互联网上的服务。

    现在,对Docker的结构有了大致的印象,我们来介绍几个与图中不同部分有关的技巧。

    Docker守护进程(见图2-2)是用户与Docker交互的枢纽,因而它是理解所有相关部分的最佳切入点。它控制着用户机器上的Docker访问权限,管理着容器与镜像的状态,同时代理着与外界的交互。

    2

    图2-2 Docker守护进程

    守护进程与服务器 守护进程是运行在后台的一个进程,不在用户的直接控制之下。服务器是负责接受客户端请求,并执行用于满足该请求所需的操作的一个进程。守护进程通常也是服务器,接收来自客户端的请求,为其执行操作。docker命令是一个客户端,而Docker守护进程则作为服务器对Docker容器和镜像进行操作。

    我们来看几个技巧,这些技巧用于展示Docker作为守护进程高效运行,同时使用docker命令与其进行的交互被限制为执行操作的简单请求,就像与Web服务器进行交互一样。第一个技巧允许其他人连接你的Docker守护进程,并执行与你在宿主机上所能执行的相同操作,第二个技巧说明的是Docker容器是由守护进程管理的,而不是你的shell会话。

    虽然默认情况下Docker的守护进程只能在宿主机上访问,但是有些情况下还是需要允许其他人访问它。读者可能遇到了一个问题,需要其他人来远程调试,或者可能想让DevOps工作流中的某一部分在宿主机上启动一个进程。

    不安全! 尽管这个技巧很强大也很有用,但它被认为是不安全的。开放的Docker守护进程可能被别有用心的人所利用,并获得提升的权限。

    问题

    想要将Docker服务器开放给其他人访问。

    解决方案

    使用开放的TCP地址启动Docker守护进程。

    讨论

    图2-3给出了这个技巧的工作概览。

    2

    图2-3 Docker可访问性:正常情况与公开情况

    在开放Docker守护进程之前,必须先停止正在运行的实例。操作的方式因操作系统而异。如果不清楚怎么做,可以首先试试这个命令:

    1. $ sudo service docker stop

    如果得到一个类似下面这样的消息,说明这是一个基于systemctl的启动系统:

    1. The service command supports only basic LSB actions (start, stop, restart,
    2. try-restart, reload, force-reload, status). For other actions, please try
    3. to use systemctl.

    可以试试这个命令:

    1. $ systemctl stop docker

    如果这个方法有效,以下命令将看不到任何输出:

    1. ps -ef | grep -E docker (-d|daemon)\b | grep -v grep

    一旦Docker守护进程停止,就可以使用以下命令手工重启并向外界用户开放它:

    1. docker daemon -H tcp://0.0.0.0:2375

    这个命令以守护进程方式启动Docker(docker daemon),使用-H标志定义宿主机服务器,使用TCP协议,绑定到所有IP地址上(使用0.0.0.0),并以标准的Docker服务器端口(2375)开放。如果Docker提示daemon不是一个有效的子命令,请尝试使用旧版的-d参数。

    可以从外部使用如下命令进行连接:

    1. $ docker -H tcp://<宿主机IP>:2375

    需要注意的是,在本地机器内部也需要这么做,因为Docker已经不再在其默认位置进行监听了。

    如果想在宿主机上让这项变更永久生效,需要对启动系统进行配置。其操作方式参见附录B。

    使用IP限制 如果开放了守护进程,请务必只向特定IP范围开放,同时不要绑定到0.0.0.0上,这样非常不安全!

    在熟悉了Docker之后,(与我们一样)读者会开始思考Docker的其他使用场景,首先想到的使用场景之一是以运行服务的方式来运行Docker容器。

    以服务方式运行Docker容器,通过软件隔离实现可预测行为,是Docker的主要使用场景之一。本技巧将让读者使用适合自己的操作的方式来管理服务。

    问题

    想要以服务方式在后台运行一个Docker容器。

    解决方案

    docker run命令中使用-d标志,并使用相关的容器管理标志定义此服务特性。

    讨论

    Docker容器与多数进程一样,默认在前台运行。在后台运行Docker容器最常见的方式是使用标准的&控制操作。虽然这行得通,但如果用户注销终端会话就可能出现问题,用户被迫使用nohup标志,而这将在本地目录中创建一个不得不管理的输出文件……是的,使用Docker守护进程的功能完成这一点将简洁得多。

    要做到这一点,可使用-d标志。

    1. $ docker run -d -i -p 1234:1234 name daemon ubuntu nc -l 1234

    docker run一起使用的-d标志将以守护进程方式运行容器。-i标志则赋予容器与Telnet会话交互的能力。使用-p将容器的1234端口公布到宿主机上。通过—name标志赋予容器一个名称,以便后期用来对它进行引用。最后,使用netcat(nc)在1234端口上运行一个简单的监听应答(echo)服务器。

    如果现在使用Telnet连接它并发送消息,就可以使用docker logs命令看到容器已经接收到该消息,如代码清单2-1所示。

    代码清单2-1 使用Telnet连接容器netcat服务器

    1. $ telnet localhost 1234 1
    2. Trying ::1
    3. Connected to localhost.
    4. Escape character is ‘^]’.
    5. hello daemon 2
    6. ^] 3)    
    7. telnet> q 4
    8. Connection closed.
    9. $ docker logs daemon 5
    10. hello daemon
    11. $ docker rm daemon 6) 
    12. daemon
    13. $

    (1)使用telnet命令连接到容器的netcat服务器

    (2)输入发送给netcat服务器的一行文本

    (3)按Ctrl+]然后按回车键退出Telnet会话

    (4)输入q然后按回车键退出Telnet程序

    (5)运行docker logs命令查看容器的输出

    (6)使用rm命令清除容器

    由此可见,以守护进程方式运行一个容器是相当简单的,但在实际操作中,还有一些问题有待解答:

    • 如果服务失败了会发生什么?
    • 在服务结束时会发生什么?
    • 如果服务不断失败会发生什么?

    幸运的是,Docker为每个问题都提供了相应标志!

    非必需的标志 尽管重启标志经常会与守护进程标志(-d)一起使用,但从技术角度来说,与-d一起运行这些标志并不是必需的。

    重启标志

    docker run —restart标志允许用户应用一组容器终止时需要遵循的规则(就是所谓的“重启策略”,见表2-1)。

    表2-1 重启策略

    策  略

    描  述

    no

    容器退出时不重启

    always

    容器退出时总是自动重启

    on-failure[:max-retry]

    只在失败时重启

    no策略很简单:当容器退出时,它不会被重启。这是默认值。

    always策略也很简单,不过还是值得简要讨论一下:

    1. $ docker run -d restart=always ubuntu echo done

    这个命令以守护进程方式(-d)运行容器,并总是在容器终止时自动重启(—restart=always)。它发送了一个简单的快速完成的echo命令,然后退出容器。

    如果运行了上述命令,然后运行docker ps命令,就会看到类似下面这样的输出:

    1. $ docker ps
    2. CONTAINER ID    IMAGE     COMMAND     CREATED   
    3. STATUS     PORTS            NAMES
    4. 69828b118ec3    ubuntu:14.04  “echo done”   4 seconds ago
    5. Restarting (0) Less than a second ago    sickbrattain

    docker ps命令列出了所有运行中的容器及其信息,包括以下内容:

    • 容器什么时候被创建的(CREATED);
    • 容器的当前状态——通常将是Restarting,因为它只运行了很短的时间(STATUS);
    • 容器上一次运行的退出码(也在STATUS下面)。0代表运行成功;
    • 容器名称。默认情况下,Docker会通过连接两个随机单词为容器命名。有时这会造成一些奇怪的结果!

    注意,STATUS一栏还告诉我们,容器在不到一秒前退出并正在重启。这是因为echo done命令会立即退出,而Docker必须不断地重启这个容器。

    需要特别说明的是,Docker复用了容器ID。这个ID在重启时不会改变,并且对于这个Docker调用来说,ps表里永远只会有一条。

    最后,on-failure策略只在容器从它的主进程返回一个非0(一般表示失败)退出码时重启:

    1. $ docker run -d restart=on-failure:10 ubuntu /bin/false

    这条命令以守护进程(-d)形式运行容器,并设置了重启的尝试次数限制(—restart=on- failure:10),如果超出限制则退出。它运行了一个快速完成并肯定失败的简单的命令(/bin/ false)。

    如果运行上述命令并等待一分钟,然后运行docker ps -a,就会看到类似下面这样的输出:

    1. $ docker ps -a
    2. CONTAINER ID    IMAGE      COMMAND     CREATED 
    3. STATUS          PORTS       NAMES
    4. b0f40c410fe3    ubuntu:14.04  “/bin/false”   2 minutes ago
    5. Exited (1) 25 seconds ago         loving_rosalind

    Docker把所有与容器和镜像有关的数据都存储在一个目录下。由于它可能会存储大量不同的镜像,这个目录可能会迅速变大!

    如果宿主机具有不同分区(这在企业Linux工作站上很常见),用户可能会更快遭遇空间限制。在这种情况下,用户会想移动Docker所操作的目录。

    问题

    想要移动Docker存储数据的位置。

    解决方案

    停止Docker守护进程,并使用-g标志指定新的位置来启动。

    讨论

    首先必须将Docker守护进程停止(有关这个问题的讨论参见附录B)。

    假设想在/home/dockeruser/mydocker运行Docker。运行下列命令将在这个目录中创建一组新的目录和文件:

    1. docker daemon -g /home/dockeruser/mydocker

    这些目录是Docker内部使用的,对其进行操作风险自担(因为我们已经尝过滋味了!)。

    请注意,这看起来像是把容器和镜像从之前的Docker守护进程清除了。不过不用担心。如果杀掉刚才运行的Docker进程,并重启Docker服务,Docker客户端就会指回它原来的位置,容器和镜像也将回归。

    如果想让这个移动永久有效,需要对宿主机系统的启动进程进行相应配置。

    Docker客户端(见图2-4)是Docker架构中最简单的部件。在主机上输入docker rundocker pull这类命令时运行的便是它。它的任务是通过HTTP请求与Docker守护进程进行通信。

    2

    图2-4 Docker客户端

    在本节中,读者将看到如何监听Docker客户端与服务器之间的信息,还将看到一些与端口映射有关的基本技巧,这是向本书后续的编排章节迈进的一小步,也是使用浏览器作为Docker客户端的一种方式。

    有时docker命令可能会不按预期工作。多数时候是因为没有理解命令行参数的某些部分,不过偶尔也存在更严重的安装问题,如Docker的二进制文件过时了。为了诊断问题,查看与之通信的Docker守护进程来往的数据流是十分有用的。

    Docker不是不稳定的 不用惊慌!本技巧的存在不表示Docker需要经常调试,或者有任何的不稳定!这条技巧在此是为了理解Docker架构的一个工具,同时也是为了介绍socat这个强大的工具。如果读者像我们一样,在众多不同的地方使用Docker,所使用的Docker版本将会有差异。与任何软件一样,不同的版本将具有不同的功能和标志,这可能会让读者无所适从。

    问题

    想要调试一个Docker命令的问题。

    解决方案

    使用流量监听器(traffic snooper)来检查API调用,并自行解决。

    讨论

    在本技巧中,用户将在自己的请求与服务器套接字之间插入一个代理Unix域套接字,并查看通过它的内容(如图2-5所示)。注意,要完成这一步需要root或sudo权限。

    2

    图2-5 宿主机上的Docker客户/服务器架构

    要创建这个代理,会用到socat

    1. $ sudo socat -v UNIX-LISTEN:/tmp/dockerapi.sock \
    2.   UNIX-CONNECT:/var/run/docker.sock &

    socat socat是一个强大的命令,能让用户在两个几乎任意类型的数据通道之间中继数据。如果熟悉netcat,可以将其看作是加强版的netcat。

    在这条命令中,-v用于提高输出的可读性,带有数据流的指示。UNIX-LISTEN部分是让socat在一个Unix套接字上进行监听,而UNIX-CONNECT是让socat连接到Docker的Unix套接字。“&”符号指定在后台运行该命令。

    发往守护进程的请求所经过的新路由如图2-6所示。所有双向流量都会被socat看到,并与Docker客户端所提供的任何输出一起记录到终端日志中。

    2

    图2-6 插入socat作为代理的Docker客户端与服务器

    现在一个简单的docker命令的输出看起来将类似下面这样:

    1. $ docker -H unix:///tmp/dockerapi.sock ps –a (1)
    2. > 2015/01/12 04:34:38.790706 length=105 from=0 to=104 2
    3. GET /v1.16/containers/json?all=1 HTTP/1.1\r
    4. Host: /tmp/dockerapi.sock\r
    5. User-Agent: Docker-Client/1.4.1\r
    6. \r
    7. < 2015/01/12 04:34:38.792516 length=544 from=0 to=543 3
    8. HTTP/1.1 200 OK\r
    9. Content-Type: application/json\r
    10. Date: Mon, 12 Jan 2015 09:34:38 GMT\r
    11. Content-Length: 435\r
    12. \r
    13. [{“Command”:”/bin/bash”,”Created”:1420731043,”Id”:
    14. 4eec1b50dc6db7901d3b3c5a8d607f2576829fd6902c7f658735c3bc0a09a39c”,
    15. Image”:”debian:jessie”,”Names”:[“/lonely_mclean”],”Ports”:[],
    16. Status”:”Exited (0) 3 days ago”} 4
    17. ,{“Command”:”/bin/bash”,”Created”:1420729129,”Id”:
    18. 029851aeccc887ecf9152de97f524d30659b3fa4b0dcc3c3fe09467cd0164da5”,
    19. Image”:”debian:jessie”,”Names”:[“/suspicious_torvalds”],”Ports”:[],
    20. Status”:”Exited (130) 3 days ago”}
    21. ]CONTAINER ID    IMAGE        COMMAND        CREATED  
    22. STATUS         PORTS         NAMES 5)           
    23. 4eec1b50dc6d    debian:jessie    “/bin/bash”      3 days ago 
    24. Exited (0) 3 days ago             lonely_mclean
    25. 029851aeccc8    debian:jessie    “/bin/bash”      3 days ago 
    26. 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下载:

    1. $ docker pull tutum/wordpress

    要运行第一个博客,可使用如下命令:

    1. $ docker run -d -p 10001:80 name blog1 tutum/wordpress

    这里的docker run命令以守护进程方式(-d)及发布标志(-p)运行容器。它指定将宿主机端口(10001)映射到容器端口(80)上,并赋予该容器一个名称用于识别它(—name blog1 tutum/wordpress)。

    可以对第二个博客做相同操作:

    1. $ docker run -d -p 10002:80 name blog2 tutum/wordpress

    如果现在运行这个命令:

    1. $ docker ps -a | grep blog

    将看到列出的两个博客容器及其端口映射,看起来像下面这样:

    1. 9afb95ad3617 tutum/wordpress:latest “/run.sh 9 seconds ago Up 9 seconds
    2. 3306/tcp, 0.0.0.0:10001->80/tcp blog1
    3. 31ddc8a7a2fd tutum/wordpress:latest “/run.sh 17 seconds ago Up 16 seconds
    4. 3306/tcp, 0.0.0.0:10002->80/tcp blog2

    现在可以通过浏览http://localhost:10001和http://localhost:10002来访问自己的容器。

    要在完成后删除这些容器(假设不想保留它们),可运行下面这个命令:

    1. $ docker rm -f blog1 blog2

    如果需要的话,现在就可以通过管理端口分配在宿主机上运行多个相同的镜像和服务了。

    牢记-p标志的参数顺序 在使用-p标志时,很容易忘记哪个端口属于宿主机,哪个端口属于容器。我们可以将它看作是在从左向右读一个句子。用户连接到宿主机(-p),并从宿主机的端口传递到容器的端口(宿主机端口:容器端口)。如果熟悉SSH的端口转发命令的话,会发现它们的格式是一样的。

    技巧5展示的是如何通过暴露端口将容器开放给宿主机网络。用户不会总想将服务暴露给宿主机或外界,但是会希望容器彼此相连。

    本技巧展示的是如何使用Docker的链接标志来实现这一点,并确保外人无法访问内部服务。

    问题

    出于内部目的,想要让容器间实现通信。

    解决方案

    使用Docker的链接功能可以让容器彼此通信。

    讨论

    继续安装WordPress的任务,我们将把mysql数据库层从wordpress容器中分离出来,并将它们链接在一起,且不需要进行端口配置。图2-7展示了最终状态的概览。

    为什么这一点很有用 既然已经可以将端口暴露给宿主机来使用,为什么还要用链接?链接可以让用户封装并定义容器间的关系,而无须将服务暴露给宿主机网络(即可能暴露给外界)。用户可能会因为安全因素而这么做。

    2

    图2-7 使用链接容器设置WordPress

    要像这样运行容器,可按照以下顺序执行,并在第一条和第二条命令之间暂停大约一分钟:

    1. $ docker run name wp-mysql \
    2.   -e MYSQL_ROOT_PASSWORD=yoursecretpassword -d mysql 1
    3. $ docker run name wordpress \
    4.   —link wp-mysql:mysql -p 10003:80 -d wordpress 2

    (1)❶

    (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守护进程,并启用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守护进程。

    现在,先决条件已经梳理好,我们将它运行起来。首先,需要获取代码:

    1. git clone https://github.com/aidanhs/Docker-Terminal.git
    2. cd Docker-Terminal

    然后需要提供文件服务:

    1. python2 -m SimpleHTTPServer 8000

    上述命令使用Python内置的一个模块为目录中的静态文件服务。用户可以使用任何自己喜欢的等效服务。

    现在可以在浏览器中访问http://localhost:8000并启动一个容器。

    图2-8展示了Docker终端是如何连接起来的。页面托管在本地计算机中,并连接到本地计算机上的Docker守护进程,以执行所有操作。

    2

    图2-8 Docker终端是如何工作的

    如果想把链接发送给其他人,以下几点值得注意。

    • 其他人不能使用任何类型的代理。这是我们见到的最常见的错误根源——Docker终端使用Websocket,后者目前无法通过代理工作。
    • 给出指向localhost的链接明显无法工作——需要给出外部IP地址。
    • Docker终端需要知道上哪儿找到Docker API——它应该可以根据浏览器访问的地址自动完成这一点,不过这一点需要留意。

    这里为什么不使用Docker 如果读者的Docker经验更丰富,会奇怪为什么我们没在这个技巧中使用Docker。原因是,我们还在介绍Docker,不想给刚接触Docker的读者增加复杂度。“Docker化”这个技巧将作为一个练习留给读者。

    一旦创建了镜像,读者可能就想与其他用户分享它。这是Docker注册中心概念的所在。

    图2-9中的3个注册中心差别在于它们的可达性。一个处于私有网络上,一个开放在公共网络中,而另一个是公共的但只有注册用户才能使用Docker访问。它们全部使用相同的API完成相同的功能,这就是Docker守护进程如何知道怎样与它们进行相互通信。

    2

    图2-9 一个Docker注册中心

    Docker注册中心允许多个用户使用REST风格API将镜像推送到一个中央存储中,也可以从中拉取镜像。

    与Docker自身一样,注册中心代码也是开源的。很多公司(如我们公司)建立了私有注册中心在内部存储和共享专有的镜像。这是在进一步说明Docker公司的注册中心之前,我们这里将要讨论的东西。

    读者已经看到Docker公司具有一项服务,人们可以在其上公开地共享他们的镜像(如果想私下进行,可以付费实现)。不过存在一些不想通过Hub来共享镜像的原因——有些商业组织想尽可能把东西保留在内部,或者镜像可能很大,通过互联网传输太慢,或者也许想在试验时保持镜像私有化,同时又不想付费。不管出于什么原因,幸运的是有一个简单的解决方案。

    问题

    想要一个在本地托管镜像的方法。

    解决方案

    在本地网络上建立一个注册中心服务器。

    讨论

    要让注册中心运行起来,可在一台具有大量磁盘空间的机器上发起以下命令:

    1. $ 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(见图2-10)是由Docker公司维护的一个注册中心。它拥有成千上万个镜像可供下载和运行。任何Docker用户都可以在上面创建免费账号及公共Docker镜像。除了用户提供的镜像,上面还维护着一些作为参考的官方镜像。

    2

    图2-10 Docker Hub

    镜像受用户认证的保护,同时具有一个与GitHub类似的支持率打星系统。

    这些官方镜像的表现形式可能是Linux发行版,如Ubuntu或Cent OS,或是预装软件包,如Node.js,或是完整的软件栈,如WordPress。

    Docker注册中心造就的是与GitHub相似的社交编码文化。如果读者有兴趣尝试一个新的软件应用程序,或正在找寻服务于某个特定用途的新的应用程序,那么Docker镜像将是一个简单的实验手段,它不会对宿主机造成干扰,不需要配备一个虚拟机,也不必担心安装步骤。

    问题

    想要查找一个Docker镜像形式的应用程序或工具,并进行尝试。

    解决方案

    使用docker search命令来查找要拉取的镜像,然后运行它。

    讨论

    假设读者对Node.js有兴趣。在下面的代码中,我们使用docker search命令搜索出匹配“node”的镜像:

    1. $ docker search node
    2. NAME               DESCRIPTION 
    3. STARS     OFFICIAL  AUTOMATED
    4. node               Node.js is a JavaScript-based platform for… 
    5. 432      [OK] (1)                                   
    6. dockerfile/nodejs       Trusted automated Node.js (http://nodejs.o… 
    7. 57      [OK] (2)    
    8. dockerfile/nodejs-bower-grunt Trusted automated Node.js (http://nodejs.o… 
    9. 17      [OK] (3
    10. nodesource/node       
    11. 9       [OK] (4
    12. selenium/node-firefox          
    13. 5       [OK]
    14. selenium/node-chrome                   
    15. 5       [OK]
    16. selenium/node-base        
    17. 3       [OK]
    18. strongloop/node        StrongLoop, Node.js, and tools.     
    19. 3       [OK]
    20. selenium/node-chrome-debug   
    21. 3       [OK]
    22. dockerfile/nodejs-runtime   Trusted automated Node.js runtime Build ..  
    23. 3       [OK]
    24. jprjr/stackbrew-node      A stackbrew/ubuntu-based image for Docker,… 
    25. 2       [OK]
    26. selenium/node-firefox-debug           
    27. 2       [OK]
    28. maccam912/tahoe-node      Follow The Easy Way in the description t… 
    29. 1       [OK]
    30. homme/node-mapserv       The latest checkouts of Mapserver and its … 
    31. 1       [OK]
    32. maxexcloo/nodejs        Docker framework container with Node.js an… 
    33. 1       [OK]
    34. brownman/node-0.10      
    35. 0       [OK]
    36. kivra/node           Image with build dependencies for frontend
    37. 0       [OK]
    38. thenativeweb/node    
    39. 0       [OK]
    40. thomaswelton/node    
    41. 0       [OK]
    42. siomiz/node-opencv      /node + node-opencv  
    43. 0       [OK]
    44. bradegler/node     
    45. 0       [OK]
    46. tcnksm/centos-node      Dockerfile for CentOS packaging node     
    47. 0       [OK]
    48. azukiapp/node     
    49. 0       [OK]
    50. onesysadmin/node-imagetools  
    51. 0       [OK]
    52. fishead/node  
    53. 0       [OK]

    (1)docker search的输出是按评星数量排序的

    (2)描述是上传者对镜像用途的解释

    (3)官方镜像是指受Docker Hub信任的镜像

    (4)自动化镜像是指使用Docker Hub自动化构建功能构建的镜像

    一旦选择了一个镜像,就可以通过对其名称执行docker pull命令来下载它:

    1. $ docker pull node 1
    2. node:latest: The image you are pulling has been verified
    3. 81c86d8c1e0c: Downloading
    4. 81c86d8c1e0c: Pull complete
    5. 3a20d8faf171: Pull complete
    6. c7a7a01d634e: Pull complete
    7. 2a13c2a76de1: Pull complete
    8. 4cc808131c54: Pull complete
    9. bf2afba3f5e4: Pull complete
    10. 0cba665db8d0: Pull complete
    11. 322af6f234b2: Pull complete
    12. 9787c55efe92: Pull complete
    13. 511136ea3c5a: Already exists
    14. bce696e097dc: Already exists
    15. 58052b122b60: Already exists
    16. Status: Downloaded newer image for node:latest 2

    (1)从Docker Hub拉取名为node的镜像

    (2)如果Docker拉取了一个新的镜像(与之相对的是说明没有比已有镜像更新的版本),会显示这条信息。读者看到的输出可能会有所不同

    接着,可以使用-t-i标志以交互方式运行它。-t标志指明创建一个tty设备(一个终端),而-i标志指明该Docker会话是交互式的:

    1. $ docker run -t -i node /bin/bash
    2. root@c267ae999646:/# node
    3. > process.version
    4. v0.12.0
    5. >

    -ti标志惯用法 可以在上述docker run调用中用-ti取代-t -i来减少输入。从这里开始,本书将使用这种用法。

    常常会有来自镜像维护人员的有关如何运行镜像的建议。在http://hub.docker.com网站上搜索镜像将引导到该镜像的页面。其描述标签页可提供更多信息。

    这个镜像可信吗 如果用户下载并运行了一个镜像,运行的将是自己无法充分验证的代码。虽然使用受信任的镜像具有相对的安全性,但是通过互联网下载和运行软件时,没有什么是能保证100%安全的。

    有了这方面的知识和经验,现在可以对Docker Hub提供的大量资源进行挖掘了。毫不夸张地说,要试用这成千上万的镜像,有很多东西要学。请慢慢享受!

    在本章中,我们学习了Docker是如何结合在一起的,并且使用这一认知对不同组件进行了操作。

    下面是涉及的主要领域:

    • 通过TCP或Web浏览器向外界开放Docker守护进程;
    • 以服务守护进程方式运行容器;
    • 通过Docker守护进程将容器链接在一起;
    • 监听Docker守护进程API;
    • 设置自己的注册中心;
    • 使用Docker Hub来查找和下载镜像。

    前两章已经涵盖了基础知识(但仍希望读者学到了一些新东西,即使对Docker已经比较熟悉)。现在我们继续学习第二部分,看一看Docker在软件开发世界中扮演的角色。


    [1]  Cross-Origin Resource Sharing,跨域资源共享。——译者注