Docker-故障排除手册(全)

Docker 故障排除手册(全)

原文:zh.annas-archive.org/md5/26C3652580332746A9E26A30363AEFD3

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Docker 是一个开源的基于容器的平台,可以让任何人在任何地方一致地开发和部署稳定的应用程序。Docker 在创建可扩展和可移植环境方面提供了速度、简单性和安全性。随着 Docker 在现代微服务和 N 层应用的容器化中的出现和普及,有效地解决生产级部署的自动化工作流程是明智且必要的。

本书内容

第一章,理解容器场景和 Docker 概述,介绍了基本的容器化概念,以应用程序和基于操作系统的容器为例。我们将介绍 Docker 技术、其优势以及 Docker 容器的生命周期。

第二章,Docker 安装,将介绍在各种 Linux 发行版上安装 Docker 的步骤 - Ubuntu,CoreOS,CentOS,Red Hat Linux,Fedora 和 SUSE Linux。

第三章,构建基础和分层镜像,教授了在生产就绪的应用容器化中构建镜像的重要任务。我们还将讨论如何手动从头开始构建镜像。接下来,我们将详细探讨使用 Dockerfile 构建分层镜像,并列出 Dockerfile 命令。

第四章,设计微服务和 N 层应用,将探讨从开发到测试无缝设计的示例环境,消除了手动和容易出错的资源配置和配置的需求。在这样做的过程中,我们将简要介绍微服务应用程序如何进行测试、自动化、部署和管理。

第五章,移动容器化应用,将介绍 Docker 注册表。我们将从使用 Docker Hub 的 Docker 公共存储库的基本概念开始,以及与更大观众共享容器的用例。Docker 还提供了部署私有 Docker 注册表的选项,我们将探讨这一点,该注册表可用于在组织内部推送、拉取和共享 Docker 容器。

第六章,“使容器工作”,将教你关于特权容器,它们可以访问所有主机设备,以及超级特权容器,它们表明容器可以运行后台服务,用于在 Docker 容器中运行服务以管理底层主机。

第七章,“管理 Docker 容器的网络堆栈”,将解释 Docker 网络是如何通过 Docker0 桥接进行配置和故障排除的。我们还将探讨 Docker 网络与外部网络之间的通信问题的故障排除。我们将研究使用不同网络选项(如 Weave、OVS、Flannel 和 Docker 的最新覆盖网络)在多个主机之间进行容器通信,并比较它们以及它们配置中涉及的故障排除问题。

第八章,“使用 Kubernetes 管理 Docker 容器”,解释了如何借助 Kubernetes 管理 Docker 容器。我们将涵盖许多部署场景和在裸机、AWS、vSphere 或使用 minikube 部署 Kubernetes 时的故障排除问题。我们还将探讨有效部署 Kubernetes pods 和调试 Kubernetes 问题。

第九章,“挂载卷包”,将深入探讨与 Docker 相关的数据卷和存储驱动器概念。我们将讨论使用四种方法来故障排除数据卷,并研究它们的优缺点。存储数据在 Docker 容器内部的第一种情况是最基本的情况,但它不提供在生产环境中管理和处理数据的灵活性。第二和第三种情况是使用仅存储数据的容器或直接存储在主机上的情况。第四种情况是使用第三方卷插件,如 Flocker 或 Convoy,它将数据存储在单独的块中,即使容器从一个主机转移到另一个主机,或者容器死亡,也能提供数据的可靠性。

第十章,“在公共云中部署 Docker - AWS 和 Azure”,概述了在 Microsoft Azure 和 AWS 公共云上部署 Docker。

本书所需内容

您需要在 Windows、Mac OS 或 Linux 机器上安装 Docker 1.12+。可能需要 AWS、Azure 和 GCE 的公共云账户,这些在各章节的相应部分中提到。

本书适合对象

本书旨在帮助经验丰富的解决方案架构师、开发人员、程序员、系统工程师和管理员解决 Docker 容器化的常见问题。如果您希望构建用于自动部署的生产就绪的 Docker 容器,您将能够掌握和解决 Docker 的基本功能和高级功能。熟悉 Linux 命令行语法、单元测试、Docker 注册表、GitHub 以及领先的容器托管平台和云服务提供商(CSP)是先决条件。在本书中,您还将了解避免首次进行故障排除的方法和手段。

约定

在本书中,您将找到一些文本样式,用于区分不同类型的信息。以下是一些样式的示例及其含义解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:“使用start_k8s.sh shell 脚本重新启动集群。”

代码块设置如下:

ENTRYPOINT /usr/sbin/sshd -D 
VOLUME ["/home"] 
EXPOSE 22 
EXPOSE 8080

任何命令行输入或输出都以以下形式书写:

Docker build -t username/my-imagename -f /path/Dockerfile

新术语重要单词以粗体显示。您在屏幕上看到的单词,例如菜单或对话框中的单词,会在文本中显示为:“指定堆栈名称密钥对和集群 3。”

注意

警告或重要说明显示在这样的框中。

提示

提示和技巧显示如下。

第一章:理解容器场景和 Docker 概述

Docker 是最近最成功的开源项目之一,它提供了任何应用程序的打包、运输和运行,作为轻量级容器。我们实际上可以将 Docker 容器比作提供标准、一致的运输任何应用程序的集装箱。Docker 是一个相当新的项目,借助本书的帮助,将很容易解决 Docker 用户在安装和使用 Docker 容器时遇到的一些常见问题。

本章重点将放在以下主题上:

  • 解码容器

  • 深入 Docker

  • Docker 容器的优势

  • Docker 生命周期

  • Docker 设计模式

  • 单内核

解码容器

容器化是虚拟机的一种替代方案,它涉及封装应用程序并为其提供自己的操作环境。容器的基本基础是 Linux 容器(LXC),它是 Linux 内核封装特性的用户空间接口。借助强大的 API 和简单的工具,它让 Linux 用户创建和管理应用程序容器。LXC 容器介于chroot和完整的虚拟机之间。容器化和传统的虚拟化程序之间的另一个关键区别是,容器共享主机机器上运行的操作系统使用的 Linux 内核,因此在同一台机器上运行的多个容器使用相同的 Linux 内核。与虚拟机相比,它具有快速的优势,几乎没有性能开销。

容器的主要用例列在以下各节中。

操作系统容器

操作系统容器可以很容易地想象成一个虚拟机(VM),但与 VM 不同的是,它们共享主机操作系统的内核,但提供用户空间隔离。与 VM 类似,可以为容器分配专用资源,并且可以安装、配置和运行不同的应用程序、库等,就像在任何 VM 上运行一样。在可伸缩性测试的情况下,操作系统容器非常有用,可以轻松部署一系列具有不同发行版的容器,与部署 VM 相比成本要低得多。容器是从模板或镜像创建的,这些模板或镜像确定了容器的结构和内容。它允许您在所有容器中创建具有相同环境、相同软件包版本和配置的容器,主要用于开发环境设置的情况。

有各种容器技术,如 LXC、OpenVZ、Docker 和 BSD jails,适用于操作系统容器:

操作系统容器

基于操作系统的容器

应用容器

应用容器旨在在一个包中运行单个服务,而先前解释过的操作系统容器可以支持多个进程。自 Docker 和 Rocket 推出后,应用容器受到了很多关注。

每当启动一个容器时,它都会运行一个进程。这个进程运行一个应用程序进程,但在操作系统容器的情况下,它在同一个操作系统上运行多个服务。容器通常采用分层方法,就像 Docker 容器一样,这有助于减少重复和增加重用。容器可以从所有组件共同的基本镜像开始启动,然后我们可以在文件系统中添加特定于组件的层。分层文件系统有助于回滚更改,因为如果需要,我们可以简单地切换到旧层。在 Dockerfile 中指定的run命令为容器添加了一个新层。

应用容器的主要目的是将应用程序的不同组件打包到单独的容器中。应用程序的不同组件被单独打包到容器中,然后它们通过 API 和服务进行交互。分布式多组件系统部署是微服务架构的基本实现。在前述方法中,开发人员可以根据自己的需求打包应用程序,IT 团队可以在多个平台上部署容器,以实现系统的水平和垂直扩展:

注意

Hypervisor 是一个虚拟机监视器VMM),用于允许多个操作系统在主机上运行和共享硬件资源。每个虚拟机被称为一个客户机。

应用容器

Docker 层

以下简单示例解释了应用容器和操作系统容器之间的区别:

让我们考虑一下 Web 三层架构的例子。我们有一个数据库层,比如MySQLNginx用于负载均衡,应用层是Node.js

应用容器

一个操作系统容器

在操作系统容器的情况下,我们可以默认选择 Ubuntu 作为基本容器,并使用 Dockerfile 安装服务 MySQL,nginx 和 Node.js。这种打包适用于测试或开发设置,其中所有服务都打包在一起,并可以在开发人员之间共享和传送。但是,将此架构部署到生产环境中不能使用操作系统容器,因为没有考虑数据可扩展性和隔离性。应用容器有助于满足这种用例,因为我们可以通过部署更多的应用程序特定容器来扩展所需的组件,并且还有助于满足负载均衡和恢复用例。对于前述的三层架构,每个服务将被打包到单独的容器中,以满足架构部署的用例:

应用容器

应用容器的扩展

操作系统和应用容器之间的主要区别是:

操作系统容器 应用容器
旨在在同一操作系统容器上运行多个服务 旨在运行单个服务
本地,没有分层文件系统 分层文件系统
示例:LXC,OpenVZ,BSD Jails 示例:Docker,Rocket

深入 Docker

Docker 是一个容器实现,在近年来引起了巨大的兴趣。它整齐地捆绑了各种 Linux 内核特性和服务,如命名空间、cgroups、SELinux、AppArmor 配置文件等,以及 Union 文件系统,如 AUFS 和 BTRFS,以制作模块化的镜像。这些镜像为应用程序提供了高度可配置的虚拟化环境,并遵循一次编写,随处运行的原则。一个应用程序可以简单到运行一个进程,也可以是高度可扩展和分布式的进程共同工作。

Docker 因其性能敏锐和普遍可复制的架构而在行业中获得了很多关注,同时提供了现代应用开发的以下四个基石:

  • 自治

  • 去中心化

  • 并行性

  • 隔离

此外,Thoughtworks 的微服务架构或大量小应用(LOSA)的广泛采用进一步为 Docker 技术带来潜力。因此,谷歌、VMware 和微软等大公司已经将 Docker 移植到他们的基础设施上,并且随着 Tutum、Flocker、Giantswarm 等众多 Docker 初创公司的推出,这种势头还在持续。

由于 Docker 容器可以在任何地方复制其行为,无论是在开发机器、裸机服务器、虚拟机还是数据中心,应用程序设计者可以将注意力集中在开发上,而操作语义留给 DevOps。这使得团队工作流程模块化、高效和高产。Docker 不应与 VM 混淆,尽管它们都是虚拟化技术。Docker 共享操作系统,同时为运行在容器中的应用程序提供足够的隔离和安全性,然后完全抽象出操作系统并提供强大的隔离和安全性保证。但是与 VM 相比,Docker 的资源占用量微不足道,因此更受经济和性能的青睐。然而,它仍然不能完全取代 VM,容器的使用是 VM 技术的补充:

深入 Docker

VM 和 Docker 架构

Docker 容器的优势

以下是在微服务架构中使用 Docker 容器的一些优势:

  • 快速应用部署:由于尺寸减小,容器可以快速部署,因为只有应用程序被打包。

  • 可移植性:一个应用及其操作环境(依赖项)可以捆绑到一个单独的 Docker 容器中,独立于操作系统版本或部署模型。Docker 容器可以轻松地转移到另一台运行 Docker 容器的机器上,并且在没有任何兼容性问题的情况下执行。Windows 支持也将成为未来 Docker 版本的一部分。

  • 易共享:预构建的容器镜像可以通过公共存储库以及用于内部使用的托管私有存储库轻松共享。

  • 轻量级占用空间:即使 Docker 镜像非常小,也具有最小的占用空间,可以使用容器轻松部署新应用程序。

  • 可重用性:Docker 容器的连续版本可以轻松构建,并且可以在需要时轻松回滚到先前的版本。它们因为可以重用来自现有层的组件而变得明显轻量级。

Docker 生命周期

这些是 Docker 容器生命周期中涉及的一些基本步骤:

  1. 使用包含打包所需的所有命令的 Dockerfile 构建 Docker 镜像。可以以以下方式运行:
Docker build

标签名称可以以以下方式添加:

Docker build -t username/my-imagename .

如果 Dockerfile 存在于不同的路径,则可以通过提供-f标志来执行 Docker build命令:

Docker build -t username/my-imagename -f /path/Dockerfile

  1. 在创建镜像之后,可以使用Docker run来部署容器。可以使用Docker ps命令来检查正在运行的容器,该命令列出当前活动的容器。还有两个要讨论的命令:
  • Docker pause:此命令使用 cgroups 冻结器来暂停容器中运行的所有进程。在内部,它使用 SIGSTOP 信号。使用此命令,进程可以在需要时轻松暂停和恢复。

  • Docker start:此命令用于启动一个或多个已停止的容器。

  1. 在使用容器后,可以将其停止或杀死;Docker stop命令将通过发送 SIGTERM 然后 SIGKILL 命令优雅地停止运行的容器。在这种情况下,仍然可以使用Docker ps -a命令列出容器。Docker kill将通过向容器内部运行的主进程发送 SIGKILL 来杀死运行的容器。

  2. 如果在容器运行时对容器进行了一些更改,这些更改可能会被保留,可以在容器停止后使用Docker commit将容器转换回镜像:

Docker 生命周期

Docker 设计模式

这里列出了八个 Docker 设计模式及其示例。Dockerfile 是我们定义 Docker 镜像的基本结构,它包含了组装镜像的所有命令。使用Docker build命令,我们可以创建一个自动化构建,执行所有前面提到的命令行指令来创建一个镜像:

$ Docker build
Sending build context to Docker daemon 6.51 MB
...

这里列出的设计模式可以帮助创建在卷中持久存在的 Docker 镜像,并提供各种灵活性,以便可以随时轻松地重新创建或替换它们。

基础镜像共享

为了创建基于 web 的应用程序或博客,我们可以创建一个基础镜像,可以共享并帮助轻松部署应用程序。这种模式有助于将所有所需的服务打包到一个基础镜像之上,以便这个 web 应用程序博客镜像可以在任何地方重复使用:

    FROM debian:wheezy 
    RUN apt-get update 
    RUN apt-get -y install ruby ruby-dev build-essential git 
    # For debugging 
    RUN apt-get install -y gdb strace 
    # Set up my user 
    RUN useradd -u 1000 -ms /bin/bash vkohli 
       RUN gem install -n /usr/bin bundler 
    RUN gem install -n /usr/bin rake 
    WORKDIR /home/vkohli/ 
    ENV HOME /home/vkohli 
    VOLUME ["/home"] 
    USER vkohli 
    EXPOSE 8080 

前面的 Dockerfile 显示了创建基于应用程序的镜像的标准方式。

Docker 镜像是一个压缩文件,是基础镜像中所有配置参数以及所做更改的快照(操作系统的内核)。

它在 Debian 基础镜像上安装了一些特定工具(Ruby 工具 rake 和 bundler)。它创建了一个新用户,将其添加到容器镜像中,并通过从主机挂载"/home"目录来指定工作目录,这在下一节中有详细说明。

共享卷

在主机级别共享卷允许其他容器获取它们所需的共享内容。这有助于更快地重建 Docker 镜像,或者在添加、修改或删除依赖项时。例如,如果我们正在创建前面提到的博客的主页部署,唯一需要共享的目录是/home/vkohli/src/repos/homepage目录,通过以下方式通过 Dockerfile 与这个 web 应用容器共享:

  FROM vkohli/devbase 
          WORKDIR /home/vkohli/src/repos/homepage 
          ENTRYPOINT bin/homepage web 

为了创建博客的开发版本,我们可以共享/home/vkohli/src/repos/blog文件夹,其中所有相关的开发者文件可以驻留。并且为了创建开发版本镜像,我们可以从预先创建的devbase中获取基础镜像:

FROM vkohli/devbase 
WORKDIR / 
USER root 
# For Graphivz integration 
RUN apt-get update 
RUN apt-get -y install graphviz xsltproc imagemagick 
       USER vkohli 
         WORKDIR /home/vkohli/src/repos/blog 
         ENTRYPOINT bundle exec rackup -p 8080 

开发工具容器

为了开发目的,我们在开发和生产环境中有不同的依赖关系,这些依赖关系很容易在某个时候混合在一起。容器可以通过将它们分开打包来帮助区分依赖关系。如下所示,我们可以从基本映像中派生开发工具容器映像,并在其上安装开发依赖,甚至允许ssh连接,以便我们可以处理代码:

FROM vkohli/devbase 
RUN apt-get update 
RUN apt-get -y install openssh-server emacs23-nox htop screen 

# For debugging 
RUN apt-get -y install sudo wget curl telnet tcpdump 
# For 32-bit experiments 
RUN apt-get -y install gcc-multilib  
# Man pages and "most" viewer: 
RUN apt-get install -y man most 
RUN mkdir /var/run/sshd 
ENTRYPOINT /usr/sbin/sshd -D 
VOLUME ["/home"] 
EXPOSE 22 
EXPOSE 8080 

如前面的代码所示,安装了基本工具,如wgetcurltcpdump,这些工具在开发过程中是必需的。甚至安装了 SSHD 服务,允许在开发容器中进行ssh连接。

测试环境容器

在不同的环境中测试代码总是有助于简化流程,并有助于在隔离中发现更多的错误。我们可以创建一个 Ruby 环境在一个单独的容器中生成一个新的 Ruby shell,并用它来测试代码库。

FROM vkohli/devbase 
RUN apt-get update 
RUN apt-get -y install ruby1.8 git ruby1.8-dev 

在列出的 Dockerfile 中,我们使用devbase作为基本映像,并借助一个docker run命令,可以轻松地使用从该 Dockerfile 创建的映像创建一个新的环境来测试代码。

构建容器

我们的应用程序中涉及一些耗费时间的构建步骤。为了克服这一点,我们可以创建一个单独的构建容器,该容器可以使用构建过程中所需的依赖项。以下 Dockerfile 可用于运行单独的构建过程:

FROM sampleapp 
RUN apt-get update 
RUN apt-get install -y build-essential [assorted dev packages for libraries] 
VOLUME ["/build"] 
WORKDIR /build 
CMD ["bundler", "install","--path","vendor","--standalone"] 

/build目录是共享目录,可用于提供已编译的二进制文件,还可以将容器中的/build/source目录挂载到提供更新的依赖项。因此,通过使用构建容器,我们可以将构建过程和最终打包过程分离开来。它仍然通过将前面的过程分解为单独的容器来封装过程和依赖关系。

安装容器

该容器的目的是将安装步骤打包到单独的容器中。基本上,这是为了在生产环境中部署容器。

显示了将安装脚本打包到 Docker 映像中的示例 Dockerfile:

ADD installer /installer 
CMD /installer.sh 

installer.sh 可以包含特定的安装命令,在生产环境中部署容器,并提供代理设置和 DNS 条目,以便部署一致的环境。

服务容器

为了在一个容器中部署完整的应用程序,我们可以捆绑多个服务以提供完整的部署容器。在这种情况下,我们将 Web 应用程序、API 服务和数据库捆绑在一个容器中。这有助于简化各种独立容器之间的互联的痛苦。

services: 
  web: 
    git_url: git@github.com:vkohli/sampleapp.git 
    git_branch: test 
    command: rackup -p 3000 
    build_command: rake db:migrate 
    deploy_command: rake db:migrate 
    log_folder: /usr/src/app/log 
    ports: ["3000:80:443", "4000"] 
    volumes: ["/tmp:/tmp/mnt_folder"] 
    health: default 
  api: 
    image: quay.io/john/node 
    command: node test.js 
    ports: ["1337:8080"] 
    requires: ["web"] 
databases: 
  - "mysql" 
  - "redis" 

基础设施容器

正如我们在开发环境中讨论过的容器使用,还有一个重要的类别缺失-用于基础设施服务的容器的使用,比如代理设置,它提供了一个连贯的环境,以便提供对应用程序的访问。在下面提到的 Dockerfile 示例中,我们可以看到安装了haproxy并提供了其配置文件的链接:

FROM debian:wheezy 
ADD wheezy-backports.list /etc/apt/sources.list.d/ 
RUN apt-get update 
RUN apt-get -y install haproxy 
ADD haproxy.cfg /etc/haproxy/haproxy.cfg 
CMD ["haproxy", "-db", "-f", "/etc/haproxy/haproxy.cfg"] 
EXPOSE 80 
EXPOSE 443 

haproxy.cfg文件是负责对用户进行身份验证的配置文件:

backend test 
    acl authok http_auth(adminusers) 
    http-request auth realm vkohli if !authok 
    server s1 192.168.0.44:8084 

Unikernels

Unikernels 将源代码编译成一个包括应用逻辑所需功能的自定义操作系统,生成一个专门的单地址空间机器映像,消除了不必要的代码。Unikernels 是使用库操作系统构建的,与传统操作系统相比具有以下优点:

  • 快速启动时间:Unikernels 使得配置高度动态化,并且可以在不到一秒的时间内启动

  • 小的占地面积:Unikernel 代码库比传统的操作系统等效代码库要小,而且管理起来也同样容易

  • 提高安全性:由于不部署不必要的代码,攻击面大大减少

  • 精细化优化:Unikernels 是使用编译工具链构建的,并且针对设备驱动程序和应用逻辑进行了优化

Unikernels 与微服务架构非常匹配,因为源代码和生成的二进制文件都可以很容易地进行版本控制,并且足够紧凑,可以重新构建。而另一方面,修改虚拟机是不允许的,只能对源代码进行修改,这是耗时且繁琐的。例如,如果应用程序不需要磁盘访问和显示功能。Unikernels 可以帮助从内核中删除这些不必要的设备驱动程序和显示功能。因此,生产系统变得极简,只打包应用代码、运行时环境和操作系统设施,这是不可变应用部署的基本概念,如果在生产服务器上需要进行任何应用程序更改,则会构建一个新的映像:

Unikernels

从传统容器过渡到基于 Unikernel 的容器

容器和 Unikernels 是彼此的最佳选择。最近,Unikernel 系统已成为 Docker 的一部分,这两种技术的合作很快将在下一个 Docker 版本中看到。如前图所示,第一个显示了支持多个 Docker 容器的传统打包方式。下一步显示了一对一的映射(一个容器对应一个 VM),这允许每个应用程序是自包含的,并且能更好地利用资源,但为每个容器创建一个单独的 VM 会增加开销。在最后一步中,我们可以看到 Unikernels 与当前现有的 Docker 工具和生态系统的合作,其中一个容器将获得特定于其需求的内核低库环境。

Unikernels 在 Docker 工具链中的采用将加速 Unikernels 的进展,并且它将被广泛使用和理解为一种打包模型和运行时框架,使 Unikernels 成为另一种类型的容器。在为 Docker 开发人员提供 Unikernels 抽象之后,我们将能够选择是使用传统的 Docker 容器还是 Unikernel 容器来创建生产环境。

摘要

在本章中,我们通过应用程序和基于操作系统的容器的帮助下学习了基本的容器化概念。本章中解释的它们之间的区别将清楚地帮助开发人员选择适合其系统的容器化方法。我们对 Docker 技术、其优势以及 Docker 容器的生命周期进行了一些介绍。本章中解释的八种 Docker 设计模式清楚地展示了在生产环境中实现 Docker 容器的方法。在本章结束时,介绍了 Unikernels 的概念,这是容器化领域未来发展的方向。在下一章中,我们将开始讨论 Docker 安装故障排除问题及其深入解决方案。

第二章:Docker 安装

大多数操作系统中 Docker 安装非常顺利,很少出错的机会。Docker 引擎安装在大多数 Linux、云、Windows 和 Mac OS X 环境中都得到支持。如果 Linux 版本不受支持,那么可以使用二进制文件安装 Docker 引擎。Docker 二进制安装主要面向那些想在各种操作系统上尝试 Docker 的黑客。通常涉及检查运行时依赖关系、内核依赖关系,并使用 Docker 特定于平台的二进制文件以便继续安装。

Docker Toolbox 是一个安装程序,可以快速在 Windows 或 Mac 机器上安装和设置 Docker 环境。Docker 工具箱还安装了:

  • Docker 客户端:它通过与 Docker 守护程序通信执行命令,如构建和运行,并发送容器

  • Docker Machine:它是一个用于在虚拟主机上安装 Docker 引擎并使用 Docker Machine 命令管理它们的工具

  • Docker Compose:它是一个用于定义和运行多容器 Docker 应用程序的工具

  • Kitematic:在 Mac OS X 和 Windows 操作系统上运行的 Docker GUI

使用工具箱安装 Docker 以及在各种支持的操作系统上的安装都非常简单,但我们列出了可能的陷阱和涉及的故障排除步骤。

在本章中,我们将探讨如何在各种 Linux 发行版上安装 Docker,例如以下内容:

  • Ubuntu

  • 红帽 Linux

  • CentOS

  • CoreOS

  • Fedora

  • SUSE Linux

上述所有操作系统都可以部署在裸机上,但在某些情况下我们使用了 AWS 进行部署,因为这是一个理想的生产环境。此外,在 AWS 上快速启动环境也更快。我们在本章的各个部分中解释了相同的步骤,这将帮助您解决问题并加快在 AWS 上的部署速度。

在 Ubuntu 上安装 Docker

让我们开始在 Ubuntu 14.04 LTS 64 位上安装 Docker。我们可以使用 AWS AMI 来创建我们的设置。可以通过以下链接直接在 AMI 上启动镜像:

thecloudmarket.com/image/ami-a21529cc--ubuntu-images-hvm-ssd-ubuntu-trusty-14-04-amd64-server-20160114-5

以下图表说明了在 Ubuntu 14.04 LTS 上安装 Docker 所需的安装步骤:

在 Ubuntu 上安装 Docker

先决条件

Docker 需要 64 位安装,无论 Ubuntu 版本如何。内核必须至少为 3.10。

让我们使用以下命令检查我们的内核版本:

$ uname -r

输出是 3.13.x 的内核版本,这很好:

3.13.0-74-generic

更新软件包信息

执行以下步骤来更新 APT 存储库并安装必要的证书:

  1. Docker 的 APT 存储库包含 Docker 1.7.x 或更高版本。要设置 APT 以使用新存储库中的软件包:
$ sudo apt-get update

  1. 运行以下命令以确保 APT 使用 HTTPS 方法并安装 CA 证书:
$ sudo apt-get install apt-transport-https ca-certificates

apt-transport-https软件包使我们能够在/etc/apt/sources.list中使用deb https://foo distro main行,以便使用libapt-pkg库的软件包管理器可以访问通过 HTTPS 可访问的源中的元数据和软件包。

ca-certificates是容器的 CA 证书的 PEM 文件,允许基于 SSL 的应用程序检查 SSL 连接的真实性。

添加新的 GPG 密钥

GNU 隐私保护(称为GPGGnuPG)是一款符合 OpenPGP(RFC4880)标准的免费加密软件:

$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

输出将类似于以下清单:

Executing: gpg --ignore-time-conflict --no-options --no-default-keyring --homedir /tmp/tmp.SaGDv5OvNN --no-auto-check-trustdb --trust-model always --keyring /etc/apt/trusted.gpg --primary-keyring /etc/apt/trusted.gpg --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D 
gpg: requesting key 2C52609D from hkp server p80.pool.sks-keyservers.net 
gpg: key 2C52609D: public key "Docker Release Tool (releasedocker) <docker@docker.com>" imported 
gpg: Total number processed: 1 
gpg:               imported: 1  (RSA: 1)

故障排除

如果您发现sks-keyservers不可用,可以尝试以下命令:

$ sudo apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D

为 Docker 添加新的软件包源

可以通过以下方式将 Docker 存储库添加到 APT 存储库中:

  1. 使用新的源更新/etc/apt/sources.list.d作为 Docker 存储库。

  2. 打开/etc/apt/sources.list.d/docker.list文件,并使用以下条目进行更新:

deb https://apt.dockerproject.org/repo ubuntu-trusty main

更新 Ubuntu 软件包

在添加 Docker 存储库后,可以更新 Ubuntu 软件包,如下所示:

$ sudo apt-get update

安装 linux-image-extra

对于 Ubuntu Trusty,建议安装linux-image-extra内核包;linux-image-extra包允许使用 AUFS 存储驱动程序:

$ sudo apt-get install linux-image-extra-$(uname -r)

输出将类似于以下清单:

Reading package lists... Done 
Building dependency tree        
Reading state information... Done 
The following extra packages will be installed: 
  crda iw libnl-3-200 libnl-genl-3-200 wireless-regdb 
The following NEW packages will be installed: 
  crda iw libnl-3-200 libnl-genl-3-200 linux-image-extra-3.13.0-74-generic 
  wireless-regdb 
0 upgraded, 6 newly installed, 0 to remove and 70 not upgraded. 
Need to get 36.9 MB of archives. 
After this operation, 152 MB of additional disk space will be used. 
Do you want to continue? [Y/n] Y 
Get:1 http://ap-northeast-1.ec2.archive.ubuntu.com/ubuntu/ trusty/main libnl-3-200 amd64 3.2.21-1 44 ..
Updating /boot/grub/menu.lst ... done 
run-parts: executing /etc/kernel/postinst.d/zz-update-grub 3.13.0-74-generic /boot/vmlinuz-3.13.0-74-generic 
Generating grub configuration file ... 
Found linux image: /boot/vmlinuz-3.13.0-74-generic 
Found initrd image: /boot/initrd.img-3.13.0-74-generic 
done 
Processing triggers for libc-bin (2.19-0ubuntu6.6) ...

可选 - 安装 AppArmor

如果尚未安装,使用以下命令安装 AppArmor:

$ apt-get install apparmor

输出将类似于以下清单:

sudo: unable to resolve host ip-172-30-0-227 
Reading package lists... Done 
Building dependency tree        
Reading state information... Done 
apparmor is already the newest version. 
0 upgraded, 0 newly installed, 0 to remove and 70 not upgraded.

Docker 安装

让我们开始使用官方 APT 软件包在 Ubuntu 上安装 Docker Engine:

  1. 更新 APT 软件包索引:
$ sudo apt-get update

  1. 安装 Docker Engine:
$ sudo apt-get install docker-engine

  1. 启动 Docker 守护程序:
$ sudo service docker start

  1. 验证 Docker 是否正确安装:
$ sudo docker run hello-world

  1. 输出将如下所示:
Latest: Pulling from library/hello-world 
        03f4658f8b78: Pull complete  
        a3ed95caeb02: Pull complete  
        Digest: sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8b
        c72074cc1ca36966a7 
        Status: Downloaded newer image for hello-world:latest 
        Hello from Docker. 
        This message shows that your installation appears to be working 
        correctly.

在 Red Hat Linux 上安装 Docker

Docker 在 Red Hat Enterprise Linux 7.x 上受支持。本节概述了使用 Docker 管理的发行包和安装机制安装 Docker。使用这些软件包可以确保您能够获得最新版本的 Docker。

![在 Red Hat Linux 上安装 Docker

检查内核版本

可以使用以下命令检查 Linux 内核版本:

$ uname -r

在我们的情况下,输出是内核版本 3.10.x,这将很好地工作:

3.10.0-327.el7.x86 _64

更新 YUM 软件包

可以使用以下命令更新 YUM 存储库:

$ sudo yum update

给出输出列表;确保最后显示Complete!,如下所示:

Loaded plugins: amazon-id, rhui-lb, search-disabled-repos 
rhui-REGION-client-config-server-7       | 2.9 kB   
.... 
Running transaction check 
Running transaction test 
Transaction test succeeded 
Running transaction 
  Installing : linux-firmware-20150904-43.git6ebf5d5.el7.noarch      1/138  
  Updating   : tzdata-2016c-1.el7.noarch                             2/138  
  ....                              
Complete!

添加 YUM 存储库

让我们将 Docker 存储库添加到 YUM 存储库列表中:

$ sudo tee /etc/yum.repos.d/docker.repo <<-EOF 
[dockerrepo] 
name=Docker Repository 
baseurl=https://yum.dockerproject.org/repo/main/centos/7 
enabled=1 
gpgcheck=1 
gpgkey=https://yum.dockerproject.org/gpg 
EOF

安装 Docker 软件包

Docker 引擎可以使用 YUM 存储库进行安装,如下所示:

$ sudo yum install docker-engine

启动 Docker 服务

可以使用以下命令启动 Docker 服务:

$ sudo service docker start
Redirecting to /bin/systemctl start docker.service

测试 Docker 安装

使用以下命令列出 Docker 引擎中的所有进程可以验证 Docker 服务的安装是否成功:

$ sudo docker ps -a

以下是前述命令的输出:

CONTAINER   ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES

检查 Docker 版本以确保它是最新的:

$ docker --version
Docker version 1.11.0, build 4dc5990

检查安装参数

让我们运行 Docker 信息以查看默认安装参数:

$ sudo docker info

输出列表如下;请注意存储驱动程序devicemapper

Containers: 0 
 Running: 0 
 Paused: 0 
 Stopped: 0 
Images: 0 
Server Version: 1.11.0 
Storage Driver: devicemapper 
 Pool Name: docker-202:2-33659684-pool 
 Pool Blocksize: 65.54 kB 
 Base Device Size: 10.74 GB 
 Backing Filesystem: xfs 
 Data file: /dev/loop0 
 Metadata file: /dev/loop1 
... 
Cgroup Driver: cgroupfs 
Plugins:  
 Volume: local 
 Network: null host bridge 
Kernel Version: 3.10.0-327.el7.x86_64 
Operating System: Red Hat Enterprise Linux Server 7.2 (Maipo) 
OSType: linux 
Architecture: x86_64 
CPUs: 1 
Total Memory: 991.7 MiB 
Name: ip-172-30-0-16.ap-northeast-1.compute.internal 
ID: VW2U:FFSB:A2VP:DL5I:QEUF:JY6D:4SSC:LG75:IPKU:HTOK:63HD:7X5H 
Docker Root Dir: /var/lib/docker 
Debug mode (client): false 
Debug mode (server): false 
Registry: https://index.docker.io/v1/

故障排除提示

确保您使用最新版本的 Red Hat Linux 以便部署 Docker 1.11。另一个重要的事情要记住是内核版本必须是 3.10 或更高。其余的安装过程都很顺利。

部署 CentOS VM 在 AWS 上运行 Docker 容器

我们正在使用 AWS 作为环境来展示 Docker 安装的便利性。如果需要测试操作系统是否支持其 Docker 版本,AWS 是部署和测试的最简单和最快速的方式。

如果您不是在 AWS 环境中使用,请随意跳过涉及在 AWS 上启动 VM 的步骤。

在本节中,我们将看看在 AWS 上部署 CentOS VM 以快速启动环境并部署 Docker 容器。CentOS 类似于 Red Hat 的发行版,并使用与 YUM 相同的打包工具。我们将使用官方支持 Docker 的 CentOS 7.x:

首先,让我们在 AWS 上启动基于 CentOS 的 VM:

在 AWS 上部署 CentOS VM 来运行 Docker 容器

我们使用一键启动和预先存在的密钥对进行启动。SSH 默认启用:

在 AWS 上部署 CentOS VM 来运行 Docker 容器

一旦实例启动,从 AWS EC2 控制台获取公共 IP 地址。

SSH 进入实例并按照以下步骤进行安装:

$ ssh -i "ubuntu-1404-1.pem" centos@54.238.154.134

在 AWS 上部署 CentOS VM 来运行 Docker 容器

检查内核版本

可以使用以下命令检查 Linux 操作系统的内核版本:

$ uname -r

在我们的情况下,输出是内核版本 3.10.x,这将很好地工作:

3.10.0-327.10.1.el7.x86_64

注意它与 Red Hat 内核版本 3.10.0-327.el7.x86_64 有多相似。

更新 YUM 包

YUM 包和存储库可以更新,如下所示:

$ sudo yum update 
Output listing is given, make sure it shows complete at the end 

Loaded plugins: fastestmirror 
base                                                     | 3.6 kB     00:00      
extras                                                   | 3.4 kB     00:00      
updates                                                  | 3.4 kB     00:00      
(1/4): base/7/x86_64/group_gz                            | 155 kB   00:00      
(2/4): extras/7/x86_64/primary_db                        | 117 kB   00:00      
(3/4): updates/7/x86_64/primary_db                       | 4.1 MB   00:00      
(4/4): base/7/x86_64/primary_db                          | 5.3 MB   00:00      
Determining fastest mirrors 
 * base: ftp.riken.jp 
 * extras: ftp.riken.jp 
 * updates: ftp.riken.jp 
Resolving Dependencies 
--> Running transaction check 
---> Package bind-libs-lite.x86_64 32:9.9.4-29.el7_2.2 will be updated 
---> Package bind-libs-lite.x86_64 32:9.9.4-29.el7_2.3 will be an update 
---> Package bind-license.noarch 32:9.9.4-29.el7_2.2 will be updated 
---> Package bind-license.noarch 32:9.9.4-29.el7_2.3 will be an update 
.... 
  teamd.x86_64 0:1.17-6.el7_2                                                    
  tuned.noarch 0:2.5.1-4.el7_2.3                                                 
  tzdata.noarch 0:2016c-1.el7                                                    
  util-linux.x86_64 0:2.23.2-26.el7_2.2                                          
Complete!

添加 YUM 存储库

让我们将 Docker 存储库添加到 YUM 存储库中:

$ sudo tee /etc/yum.repos.d/docker.repo <<-EOF 
[dockerrepo] 
name=Docker Repository 
baseurl=https://yum.dockerproject.org/repo/main/centos/7 
enabled=1 
gpgcheck=1 
gpgkey=https://yum.dockerproject.org/gpg 
EOF

安装 Docker 包

以下命令可用于使用 YUM 存储库安装 Docker Engine:

$ sudo yum install docker-engine

启动 Docker 服务

Docker 服务可以通过以下方式启动:

$ sudo service docker start
Redirecting to /bin/systemctl start docker.service

测试 Docker 安装

$ sudo docker ps -a

输出:

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

检查 Docker 版本以确保它是最新的:

$ docker --version
Docker version 1.11.0, build 4dc5990

检查安装参数

让我们运行 Docker 信息来查看默认安装参数:

$ sudo docker info

输出如下;请注意Storage Driverdevicemapper

Server Version: 1.11.0 
Storage Driver: devicemapper 
 ... 
Kernel Version: 3.10.0-327.10.1.el7.x86_64 
Operating System: CentOS Linux 7 (Core) 
OSType: linux 
Architecture: x86_64 
CPUs: 1 
Total Memory: 991.7 MiB 
Name: ip-172-30-0-236 
ID: EG2K:G4ZR:YHJ4:APYL:WV3S:EODM:MHKT:UVPE:A2BE:NONM:A7E2:LNED 
Docker Root Dir: /var/lib/docker 
Registry: https://index.docker.io/v1/

在 CoreOS 上安装 Docker

CoreOS 是为云构建的轻量级操作系统。它预先打包了 Docker,但版本比最新版本落后一些。由于它预先构建了 Docker,因此几乎不需要故障排除。我们只需要确保选择了正确的 CoreOS 版本。

CoreOS 可以在各种平台上运行,包括 Vagrant、Amazon EC2、QEMU/KVM、VMware 和 OpenStack,以及自定义硬件。CoreOS 使用 fleet 来管理容器集群以及 etcd(键值数据存储)。

CoreOS 的安装通道

在我们的情况下,我们将使用稳定的发布通道

CoreOS 的安装通道

首先,我们将使用 CloudFormation 模板在 AWS 上安装 CoreOS。您可以在以下链接找到此模板:

s3.amazonaws.com/coreos.com/dist/aws/coreos-stable-pv.template

此模板提供以下参数:

  • 实例类型

  • 集群大小

  • 发现 URL

  • 广告 IP 地址

  • 允许 SSH 来自

  • 密钥对

这些参数可以在默认模板中设置如下:

{ 
  "Parameters": { 
    "InstanceType": { 
      "Description": "EC2 PV instance type (m3.medium, etc).", 
      "Type": "String", 
      "Default": "m3.medium", 
      "ConstraintDescription": "Must be a valid EC2 PV instance type." 
    }, 
    "ClusterSize": { 
      "Default": "3", 
      "MinValue": "3", 
      "MaxValue": "12", 
      "Description": "Number of nodes in cluster (3-12).", 
      "Type": "Number" 
    }, 
    "DiscoveryURL": { 
      "Description": "An unique etcd cluster discovery URL. Grab a new token from https://discovery.etcd.io/new?size=<your cluster size>", 
      "Type": "String" 
    }, 
    "AdvertisedIPAddress": { 
      "Description": "Use 'private' if your etcd cluster is within one region or 'public' if it spans regions or cloud providers.", 
      "Default": "private", 
      "AllowedValues": [ 
        "private", 
        "public" 
      ], 
      "Type": "String" 
    }, 
    "AllowSSHFrom": { 
      "Description": "The net block (CIDR) that SSH is available to.", 
      "Default": "0.0.0.0/0", 
      "Type": "String" 
    }, 
    "KeyPair": { 
      "Description": "The name of an EC2 Key Pair to allow SSH access to the instance.", 
      "Type": "String" 
    } 
  } 
} 

以下步骤将提供在 AWS 上使用截图进行 CoreOS 安装的完整步骤:

  1. 选择 S3 模板进行启动:CoreOS 的安装通道

  2. 指定堆栈名称密钥对和集群 3:CoreOS 的安装通道

故障排除

以下是在之前安装过程中应遵循的一些故障排除提示和指南:

  • 堆栈名称不应重复

  • ClusterSize不能低于 3,最大为 12

  • 建议的InstanceTypem3.medium

  • 密钥对应存在;如果不存在,集群将无法启动

SSH 进入实例并检查 Docker 版本:

core@ip-10-184-155-153 ~ $ docker --version
Docker version 1.9.1, build 9894698

在 Fedora 上安装 Docker

Docker 支持 Fedora 22 和 23 版本。以下是在 Fedora 23 上安装 Docker 的步骤。它可以部署在裸机上或作为虚拟机。

检查 Linux 内核版本

Docker 需要 64 位安装,无论 Fedora 版本如何。此外,内核版本应至少为 3.10。使用以下命令在安装之前检查内核版本:

$ uname -r
4.4.7-300.fc23.x86_64
Switch to root user
[os@osboxes ~]# su -
Password:
[root@vkohli ~]#

使用 DNF 安装

使用以下命令更新现有的 DNF 软件包:

$ sudo dnf update

添加到 YUM 存储库

让我们将 Docker 存储库添加到 YUM 存储库中:

$ sudo tee /etc/yum.repos.d/docker.repo <<-'EOF' 
> [dockerrepo] 
> name=Docker Repository 
> baseurl=https://yum.dockerproject.org/repo/main/fedora/$releasever/ 
> enabled=1 
> gpgcheck=1 
> gpgkey=https://yum.dockerproject.org/gpg 
> EOF 
[dockerrepo] 
name=Docker Repository 
baseurl=https://yum.dockerproject.org/repo/main/fedora/$releasever/ 
enabled=1 
gpgcheck=1 
gpgkey=https://yum.dockerproject.org/gpg

安装 Docker 软件包

可以使用 DNF 软件包安装 Docker 引擎:

$ sudo dnf install docker-engine

输出将类似于以下列表(此列表已被截断):

Docker Repository                                32 kB/s | 7.8 kB     00:00 
Last metadata expiration check: 0:00:01 ago on Thu Apr 21 15:45:25 2016\. 
Dependencies resolved. 
Install  7 Packages 
... 
Running transaction test 
Transaction test succeeded. 
Running transaction 
  Installing: python-IPy-0.81-13.fc23.noarch                                                                     .... 
Installed: 
... 
Complete!

使用systemctl启动 Docker 服务:

$ sudo systemctl start docker

使用 Docker 的 hello-world 示例来验证 Docker 是否成功安装:

[root@osboxes ~]# docker run hello-world

输出将类似于以下列表:

Unable to find image 'hello-world:last' locally 
latest: Pulling from library/hello-world 
03f4658f8b78: Pull complete 
a3ed95caeb02: Pull complete 
Digest: sha256:8be990ef2aeb16dbcb9271ddfe2610fa6658d13f6dfb8bc72074cc1ca36966a7 
Status: Downloaded newer image for hello-world:latest 

Hello from Docker. 
This message shows that your installation appears to be working correctly.

为了生成这条消息,Docker 采取了以下步骤:

  1. Docker 客户端联系了 Docker 守护程序。

  2. Docker 守护程序从 Docker Hub 拉取了hello-world镜像。

  3. Docker 守护程序从该镜像创建了一个新的容器,该容器运行生成您当前正在阅读的输出的可执行文件。

  4. Docker 守护程序将输出流式传输到 Docker 客户端,然后发送到您的终端。

要尝试更雄心勃勃的事情,您可以使用以下命令运行 Ubuntu 容器:

$ docker run -it ubuntu bash

通过免费的 Docker Hub 帐户hub.docker.com共享图像,自动化工作流程等。

有关更多示例和想法,请访问docs.docker.com/userguide/md64-server-20160114.5 (ami-a21529cc)

使用脚本安装 Docker

更新您的 DNF 包,如下所示:

$ sudo dnf update

运行 Docker 安装脚本

Docker 安装也可以通过执行 shell 脚本并从官方 Docker 网站获取来快速简便地完成:

$ curl -fsSL https://get.docker.com/ | sh
+ sh -c 'sleep 3; dnf -y -q install docker-engine'

启动 Docker 守护程序:

$ sudo systemctl start docker

Docker 运行hello-world

$ sudo docker run hello-world

要创建 Docker 组并添加用户,请按照以下步骤进行操作:

$ sudo groupadd docker
$ sudo usermod -aG docker your_username

注销并使用用户登录以确保您的用户已成功创建:

$ docker run hello-world

要卸载 Docker,请按照以下步骤进行操作:

# sudo dnf -y remove docker-engine.x86_64

上述命令的截断输出如下所示:

Dependencies resolved. 
Transaction Summary 
================================================================ 
Remove  7 Packages 
Installed size: 57 M 
Running transaction check 
Transaction check succeeded. 
Running transaction test 
Transaction test succeeded. 
Running transaction 
... 
Complete! 
[root@osboxes ~]# rm -rf /var/lib/docker

在 SUSE Linux 上安装 Docker

在本节中,我们将在 SUSE Linux Enterprise Server 12 SP1 x86_64(64 位)上安装 Docker。我们还将看一下在安装过程中遇到的一些问题。

在 AWS 上启动 SUSE Linux VM

选择适当的 AMI 并从 EC2 控制台启动实例:

在 AWS 上启动 SUSE Linux VM

下一步显示了以下参数;请查看然后启动它们:

在 AWS 上启动 SUSE Linux VM

我们选择了一个现有的密钥对以 SSH 进入实例:

在 AWS 上启动 SUSE Linux VM

VM 启动后,请从终端登录到 VM:

$ ssh -i "ubuntu-1404-1.pem" ec2-user@54.199.222.91

截断的输出如下所示:

The authenticity of host '54.199.222.91 (54.199.222.91)' can't be established. 
... 
Management and Config: https://www.suse.com/suse-in-the-cloud-basics 
Documentation: http://www.suse.com/documentation/sles12/ 
Forum: https://forums.suse.com/forumdisplay.php?93-SUSE-Public-Cloud 
Have a lot of fun...  
ec2-user@ip-172-30-0-104:~>

由于我们已经启动了 VM,让我们专注于安装 docker。以下图表概述了在 SUSE Linux 上安装 docker 的步骤:

在 AWS 上启动 SUSE Linux VM

检查 Linux 内核版本

内核版本应至少为 3.10。在继续安装之前,请使用以下命令检查内核版本:

$ uname -r

添加 Containers-Module

在安装 docker 之前,需要更新本地软件包中的以下 Containers-Module。您可以在以下链接找到有关 Containers-Module 的更多详细信息:

www.suse.com/support/update/announcement/2015/suse-ru-20151158-1.html

执行以下命令:

ec2-user@ip-172-30-0-104:~> sudo SUSEConnect -p sle-module-containers/12/x86_64 -r ''

输出将类似于此:

Registered sle-module-containers 12 x86_64
To server: https://smt-ec2.susecloud.net
ec2-user@ip-172-30-0-104:~>

安装 Docker

执行以下命令:

ec2-user@ip-172-30-0-104:~> sudo zypper in Docker

截断的输出如下所示:

... 
 (2/2) Installing: docker-1.10.3-66.1 ...........................................................[done] 
Additional rpm output: 
creating group docker... 
Updating /etc/sysconfig/docker...

启动 Docker 服务

Docker 服务可以启动,如下所示:

ec2-user@ip-172-30-0-104:~> sudo systemctl start docker

检查 Docker 安装

执行 Docker 运行,如下所示,以测试安装:

ec2-user@ip-172-30-0-104:~> sudo docker run hello-world

输出将类似于这样:

Unable to find image 'hello-world:latest' locally 
latest: Pulling from library/hello-world 
4276590986f6: Pull complete  
a3ed95caeb02: Pull complete  
Digest: sha256:4f32210e234b4ad5cac92efacc0a3d602b02476c754f13d517e1ada048e5a8ba 
Status: Downloaded newer image for hello-world:latest 
Hello from Docker. 
This message shows that your installation appears to be working correctly. 
.... 
For more examples and ideas, visit: 
 https://docs.docker.com/engine/userguide/ 
ec2-user@ip-172-30-0-104:~>

故障排除

请注意,SUSE Linux 11 上的 Docker 安装并不是一次顺利的体验,因为 SUSE Connect 不可用。

总结

在本章中,我们介绍了如何在各种 Linux 发行版(Ubuntu,CoreOS,CentOS,Red Hat Linux,Fedora 和 SUSE Linux)上安装 Docker 的步骤。我们注意到在 Linux 上的步骤和常见先决条件的相似之处,而 Docker 模块需要从远程存储库下载和 Docker 模块的软件包管理在每个 Linux 操作系统上都有所不同。在下一章中,我们将探讨构建镜像的使命关键任务,了解基本和分层镜像,并探索故障排除方面。

第三章:构建基础和分层图像

在本章中,我们将学习如何为生产就绪的容器构建基础和分层图像。正如我们所见,Docker 容器为我们提供了理想的环境,我们可以在其中构建、测试、自动化和部署。这些确切环境的再现性为我们提供了更高效和更有信心的效果,目前可用的基于脚本的部署系统无法轻易复制。开发人员在本地构建、测试和调试的图像可以直接推送到分段和生产环境中,因为测试环境几乎是应用程序代码运行的镜像。

图像是容器的字面基础组件,定义了部署的 Linux 版本和要包含和提供给容器内部运行的代码的默认工具。因此,图像构建是应用程序容器化生命周期中最关键的任务之一;正确构建图像对于容器化应用程序的有效、可重复和安全功能至关重要。

容器镜像由一组应用程序容器的运行时变量组成。理想情况下,容器镜像应尽可能精简,仅提供所需的功能,这有助于高效处理容器镜像,显著减少了从注册表上传和下载镜像的时间,并在主机上占用空间最小。

我们的重点、意图和方向是为您的 Docker 容器构建、调试和自动化图像。

本章我们将涵盖以下主题:

  • 构建容器镜像

  • 从头开始构建基础镜像

  • 来自 Docker 注册表的官方基础镜像

  • 从 Dockerfile 构建分层图像

  • 通过测试调试图像

  • 带有测试的自动化图像构建

构建容器镜像

由于本书试图解决 Docker的问题,减少我们需要解决的错误的机会不是很有益吗?幸运的是,Docker 社区(以及开源社区)提供了一个健康的基础(或)镜像注册表,大大减少了错误并提供了更可重复的过程。在Docker Registry中搜索,我们可以找到广泛且不断增长的容器镜像的官方和自动化构建状态。 Docker 官方仓库(docs.docker.com/docker-hub/official_repos/))是由 Docker Inc.支持的仔细组织的镜像集合-自动化仓库,允许您验证特定镜像的源和内容也存在。

本章的一个主要重点和主题将是基本的 Docker 基础知识;虽然对于经验丰富的容器用户来说可能看起来微不足道,但遵循一些最佳实践和标准化水平将有助于我们避免麻烦,并增强我们解决问题的能力。

Docker Registry 的官方图像

标准化是可重复过程的重要组成部分。因此,无论何时何地,都应选择Docker Hub中提供的标准基础镜像,用于不同的 Linux 发行版(例如 CentOS、Debian、Fedora、RHEL、Ubuntu 等)或特定用例(例如 WordPress 应用程序)。这些基础镜像源自各自的 Linux 平台镜像,并专门用于容器中使用。此外,标准化的基础镜像经过良好维护,并经常更新以解决安全公告和关键错误修复。

这些基础镜像由 Docker Inc.构建、验证和支持,并通过它们的单词名称(例如centos)轻松识别。此外,Docker 社区的用户成员还提供和维护预构建的镜像以解决特定用例。这些用户镜像以创建它们的 Docker Hub 用户名为前缀,后缀为镜像名称(例如tutum/centos)。

Docker Registry 的官方图像

对我们来说,这些标准基础镜像仍然是准备就绪的,并且可以在 Docker Registry 上公开获取;可以使用docker searchdocker pull终端命令简单地搜索和检索镜像。这将下载任何尚未位于 Docker 主机上的镜像。Docker Registry 在提供官方基础镜像方面变得越来越强大,可以直接使用,或者至少作为解决容器构建需求的一个可用的起点。

注意

虽然本书假设您熟悉 Docker Hub/Registry 和 GitHub/Bitbucket,但我们将首先介绍这些内容,作为您构建容器的高效镜像的首要参考线。您可以访问 Docker 镜像的官方注册表registry.hub.docker.com/

来自 Docker Registry 的官方镜像

Docker Registry 可以从您的 Docker Hub 帐户或直接从终端进行搜索,如下所示:

$ sudo docker search centos

可以对搜索条件应用标志来过滤星级评分、自动构建等图像。要使用来自注册表的官方centos镜像,请从终端执行:

  • $ sudo docker pull centos:这将把centos镜像下载到您的主机上。

  • $ sudo docker run centos:这将首先在主机上查找此镜像,如果找不到,将会将镜像下载到主机上。镜像的运行参数将在其 Dockerfile 中定义。

用户存储库

此外,正如我们所见,我们不仅仅局限于官方 Docker 镜像的存储库。事实上,社区用户(无论是个人还是来自公司企业)已经准备好了满足某些需求的镜像。例如,创建了一个ubuntu镜像,用于在运行在 Apache、MySql 和 PHP 上的容器中运行joomla内容管理系统。

在这里,我们有一个用户存储库,其中有这样的镜像(命名空间/存储库名称):

用户存储库

注意

试一下: 从终端练习从 Docker Registry 拉取和运行图像。

$ sudo docker pull cloudconsulted/joomla

从容器中拉取我们的基础镜像,并且$ sudo docker run -d -p 80:80 cloudconsulted/joomla 运行我们的容器镜像,并将主机的端口80映射到容器的端口80

将你的浏览器指向http://localhost,你将会看到一个新的 Joomla 网站的构建页面!

构建我们自己的基础镜像

然而,可能会有情况需要创建定制的镜像以适应我们自己的开发和部署环境。如果你的使用情况要求使用非标准化的基础镜像,你将需要自己创建镜像。与任何方法一样,事先适当的规划是必要的。在构建镜像之前,你应该花足够的时间充分了解你的容器所要解决的使用情况。没有必要运行不符合预期应用程序的容器。其他考虑因素可能包括你在镜像中包含的库或二进制文件是否可重用,等等。一旦你觉得完成了,再次审查你的需求和要求,并过滤掉不必要的部分;我们不希望毫无理由地膨胀我们的容器。

使用 Docker Registry,你可以找到自动构建。这些构建是从 GitHub/Bitbucket 的仓库中拉取的,因此可以被 fork 并根据你自己的规格进行修改。然后,你新 fork 的仓库可以同步到 Docker Registry,生成你的新镜像,然后可以根据需要被拉取和运行到你的容器中。

注意

试一下:从以下仓库中拉取 ubuntu minimal 镜像,并将其放入你的 Dockerfile 目录中,以创建你自己的镜像:

$ sudo docker pull cloudconsulted/ubuntu-dockerbase $ mkdir dockerbuilder $ cd dockerbuilder

打开一个编辑器(vi/vim 或 nano)并创建一个新的 Dockerfile:

$ sudo nano Dockerfile

稍后我们将深入讨论如何创建良好的 Dockerfile,以及分层和自动化的镜像构建。现在,我们只想创建我们自己的新基础镜像,只是象征性地通过创建 Dockerfile 的过程和位置。为了简单起见,我们只是从我们想要构建新镜像的基础镜像中调用:

FROM cloudconsulted/ubuntu-dockerbase:latest 

保存并关闭这个 Dockerfile。现在我们在本地构建我们的新镜像:

$ sudo docker build -t mynew-ubuntu

让我们检查一下确保我们的新镜像已列出:

$ sudo docker images

注意我们的IMAGE IDmynew-ubuntu,因为我们很快会需要它:

在 Docker Hub 用户名下创建一个新的公共/私有仓库。我在这里添加了新的仓库,命名为<namespace><reponame>,如cloudconsulted/mynew-ubuntu

构建我们自己的基础镜像

接下来,返回到终端,这样我们就可以标记我们的新镜像以推送到我们的<namespace>下的新 Docker Hub 仓库:

$ sudo docker tag 1d4bf9f2c9c0 cloudconsulted/mynew-ubuntu:latest

确保我们的新镜像在我们的镜像列表中正确标记为<namespace><repository>

$ sudo docker images

此外,我们将找到我们新创建的标记为推送到我们的 Docker Hub 仓库的镜像。

现在,让我们将镜像推送到我们的 Docker Hub 仓库:

$ sudo docker push cloudconsulted/mynew-ubuntu

然后,检查我们的新镜像是否在 Hub 上:

构建我们自己的基础镜像

构建自己的 Docker 镜像基本上有两种方法:

  • 通过 bash shell 手动交互式构建层来安装必要的应用程序

  • 通过 Dockerfile 自动化构建带有所有必要应用程序的镜像

使用 scratch 仓库构建镜像

构建自己的 Docker 容器镜像高度依赖于您打算打包的 Linux 发行版。由于这种差异性,以及通过 Docker Registry 已经可用的镜像的盛行和不断增长的注册表,我们不会花太多时间在这样的手动方法上。

在这里,我们可以再次查看 Docker Registry,以提供我们使用的最小镜像。一个scratch仓库已经从一个空的 TAR 文件中创建,可以通过docker pull简单地使用。与以前一样,根据您的参数制作 Dockerfile,然后您就有了新的镜像,从头开始。

通过使用可用工具(例如supermin(Fedora 系统)或debootstrap(Debian 系统)),这个过程甚至可以进一步简化。例如,使用这些工具,构建 Ubuntu 基础镜像的过程可以简单如下:

$ sudo debootstrap raring raring > /dev/null 
$ sudo tar -c raring -c . |  docker import - raring a29c15f1bf7a 
$ sudo docker run raring cat /etc/lsb-release 
DISTRIB_ID=Ubuntu 
DISTRIB_RELEASE=14.04 
DISTRIB_CODENAME=raring 
DISTRIB_DESCRIPTION="Ubuntu 14.04" 

构建分层镜像

Docker 的一个核心概念和特性是分层镜像。Docker 的最重要的特性之一是镜像分层和镜像内容的管理。容器镜像的分层方法非常高效,因为您可以引用镜像中的内容,识别分层镜像中的层。在构建多个镜像时,使用 Docker Registry 来推送和拉取镜像非常强大。

构建分层镜像

[镜像版权 © Docker, Inc.]

使用 Dockerfile 构建分层镜像

分层图像主要是使用Dockerfile构建的。实质上,Dockerfile 是一个脚本,它可以按照您需要的顺序从源(基础)镜像自动构建我们的容器,逐步、一层叠一层地由 Docker 守护程序执行。这些是在文件中列出的连续命令(指令)和参数,它们在基础镜像上执行一组规定的操作,每个命令构成一个新层,以构建一个新的镜像。这不仅有助于组织我们的镜像构建,而且通过简化大大增强了从头到尾的部署。Dockerfile 中的脚本可以以各种方式呈现给 Docker 守护程序,以为我们的容器构建新的镜像。

Dockerfile 构建

Dockerfile 的第一个命令通常是FROM命令。FROM指定要拉取的基础镜像。这个基础镜像可以位于公共 Docker 注册表(www.docker.com/)中,在私有注册表中,甚至可以是主机上的本地化 Docker 镜像。

Docker 镜像中的附加层根据 Dockerfile 中定义的指令进行填充。Dockerfile 具有非常方便的指令。在 Dockerfile 中定义的每个新指令都构成了分层图像中的一个。通过RUN指令,我们可以指定要运行的命令,并将命令的结果作为图像中的附加层。

建议

强烈建议将图像中执行的操作逻辑分组,并将层的数量保持在最低限度。例如,在尝试为应用程序安装依赖项时,可以在一个RUN指令中安装所有依赖项,而不是使用每个依赖项的N个指令。

我们将在后面的章节“自动化镜像构建”中更仔细地检查 Dockerfile 的方面。现在,我们需要确保我们理解 Dockerfile 本身的概念和构造。让我们特别看一下可以使用的一系列简单命令。正如我们之前所看到的,我们的 Dockerfile 应该在包含我们现有代码(和/或其他依赖项、脚本和其他内容)的工作目录中创建。

建议

注意:避免使用根目录 [/] 作为源代码库的根目录。docker build 命令使用包含您的 Dockerfile 的目录作为构建上下文(包括其所有子目录)。构建上下文将在构建镜像之前发送到 Docker 守护程序,这意味着如果您使用 / 作为源代码库,您硬盘的整个内容将被发送到守护程序(因此发送到运行守护程序的机器)。在大多数情况下,最好将每个 Dockerfile 放在一个空目录中。然后,只向目录添加构建 Dockerfile 所需的文件。为了提高构建的性能,可以向上下文目录添加一个 .dockerignore 文件,以正确排除文件和目录。

Dockerfile 命令和语法

虽然简单,但我们的 Dockerfile 命令的顺序和语法非常重要。在这里正确关注细节和最佳实践不仅有助于确保成功的自动部署,还有助于任何故障排除工作。

让我们勾画一些基本命令,并直接用一个工作的 Dockerfile 来说明它们;我们之前的joomla镜像是一个基本的分层镜像构建的好例子。

注意

我们的示例 joomla 基本镜像位于公共 Docker 索引中

cloudconsulted/joomla

来自

一个正确的 Dockerfile 从定义一个 FROM 镜像开始,构建过程从这里开始。这个指令指定要使用的基本镜像。它应该是 Dockerfile 中的第一个指令,对于通过 Dockerfile 构建镜像是必须的。您可以指定本地镜像、Docker 公共注册表中的镜像,或者私有注册表中的镜像。

常见结构

FROM <image> 
FROM <image>:<tag> 
FROM <image>@<digest> 

<tag><digest> 是可选的;如果您不指定它们,它默认为 latest

我们的 Joomla 镜像的示例 Dockerfile

在这里,我们定义要用于容器的基本镜像:

# Image for container base 
FROM ubuntu 

维护者

这一行指定了构建镜像的作者。这是 Dockerfile 中的一个可选指令;然而,应该指定此指令与作者的姓名和/或电子邮件地址。维护者的详细信息可以放在您的 Dockerfile 中任何您喜欢的地方,只要它总是在您的 FROM 命令之后,因为它们不构成任何执行,而是一个定义的值(也就是一些额外的信息)。

常见结构

MAINTAINER <name><email> 

我们的 Joomla 镜像的示例 Dockerfile

在这里,我们为此容器和镜像定义了作者:

# Add name of image author 
MAINTAINER John Wooten <jwooten@cloudconsulted.com> 

ENV

此指令在 Dockerfile 中设置环境变量。设置的环境变量可以在后续指令中使用。

常见结构

ENV <key> <value> 

上述代码设置了一个环境变量<key><value>

ENV <key1>=<value1> <key2>=<value2> 

上述指令设置了两个环境变量。使用=符号在环境变量的键和值之间,并用空格分隔两个环境键值来定义多个环境变量:

ENV key1="env value with space" 

对于具有空格值的环境变量,请使用引号。

以下是关于ENV指令的要点:

  • 使用单个指令定义多个环境变量

  • 创建容器时环境变量可用

  • 可以使用docker inspect <image>从镜像中查看环境变量

  • 环境变量的值可以通过向docker run命令传递--env <key>=<value>选项在运行时进行更改

我们的 Joomla 镜像的示例 Dockerfile

在这里,我们为 Joomla 和 Docker 镜像设置环境变量,而不使用交互式终端:

# Set the environment variables 
ENV DEBIAN_FRONTEND noninteractive 
ENV JOOMLA_VERSION 3.4.1 

RUN

此指令允许您运行命令并生成一个层。RUN指令的输出将是在进程中为镜像构建的一个层。传递给RUN指令的命令在此指令之前构建的层上运行;需要注意顺序。

常见结构

RUN <command> 

<command>在 shell 中执行-/bin/sh -c shell 形式。

RUN ["executable", "parameter1", "parameter2"] 

在这种特殊形式中,您可以在可执行形式中指定可执行文件参数。确保在命令中传递可执行文件的绝对路径。这对于基础镜像没有/bin/sh的情况很有用。您可以指定一个可执行文件,它可以是基础镜像中的唯一可执行文件,并在其上构建层。

如果您不想使用/bin/sh shell,这也很有用。考虑一下:

RUN ["/bin/bash", "-c", "echo True!"] 
RUN <command1>;<command2> 

实际上,这是一个特殊形式的示例,您可以在其中指定多个由;分隔的命令。RUN指令一起执行这些命令,并为所有指定的命令构建一个单独的层。

我们的 Joomla 镜像的示例 Dockerfile

在这里,我们更新软件包管理器并安装所需的依赖项:

# Update package manager and install required dependencies 
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 
    mysql-server \ 
    apache2 \ 
    php5 \ 
    php5-imap \ 
    php5-mcrypt \ 
    php5-gd \ 
    php5-curl \ 
    php5-apcu \ 
    php5-mysqlnd \ 
    supervisor 

请注意,我们特意这样写,以便将新软件包作为它们自己的 apt-get install 行添加,遵循初始安装命令。

这样做是为了,如果我们需要添加或删除一个软件包,我们可以在 Dockerfile 中不需要重新安装所有其他软件包。显然,如果有这样的需要,这将节省大量的构建时间。

注意

Docker 缓存: Docker 首先会检查主机的镜像缓存,查找以前构建的任何匹配层。如果找到,Dockerfile 中的给定构建步骤将被跳过,以利用缓存中的上一层。因此,最佳实践是将 Dockerfile 的每个apt-get -y install命令单独列出。

正如我们所讨论的,Dockerfile 中的RUN命令将在 Docker 容器的上下文和文件系统下执行任何给定的命令,并生成具有任何文件系统更改的新镜像层。我们首先运行apt-get update以确保软件包的存储库和 PPA 已更新。然后,在单独的调用中,我们指示软件包管理器安装 MySQL、Apache、PHP 和 Supervisor。-y标志跳过交互式确认。

安装了我们所有必要的依赖项来运行我们的服务后,我们应该整理一下,以获得一个更干净的 Docker 镜像:

# Clean up any files used by apt-get 
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 

ADD

这些信息用于将文件和目录从本地文件系统或远程 URL 中的文件复制到镜像中。源和目标必须在ADD指令中指定。

常见结构

ADD  <source_file>  <destination_directory> 

这里的<source_file>的路径是相对于构建上下文的。另外,<destination_directory>的路径可以是绝对的,也可以是相对于WORKDIR的:

ADD  <file1> <file2> <file3> <destination_directory> 

多个文件,例如<file1><file2><file3>,被复制到<destination_directory>中。请注意,这些源文件的路径应该相对于构建上下文,如下所示:

ADD <source_directory> <destination_directory> 

<source_directory>的内容与文件系统元数据一起复制到<destination_directory>中;目录本身不会被复制:

ADD text_* /text_files

在构建上下文目录中以text_开头的所有文件都会被复制到容器镜像中的/text_files目录中:

ADD ["filename with space",...,  "<dest>"] 

文件名中带有空格的情况可以在引号中指定;在这种情况下,需要使用 JSON 数组来指定 ADD 指令。

以下是关于ADD指令的要点:

  • 所有复制到容器镜像中的新文件和目录的 UID 和 GID 都为0

  • 在源文件是远程 URL 的情况下,目标文件将具有600的权限

  • ADD指令的源中引用的所有本地文件应位于构建上下文目录或其子目录中

  • 如果本地源文件是受支持的 tar 存档,则它将被解压缩为目录

  • 如果指定了多个源文件,则目标必须是一个目录,并以斜杠/结尾

  • 如果目标不存在,它将与路径中的所有父目录一起创建,如果需要的话

我们的 Joomla 镜像的示例 Dockerfile

在这里,我们将joomla下载到 Apache web 根目录:

# Download joomla and put it default apache web root 
ADD https://github.com/joomla/joomla-cms/releases/download/$JOOMLA_VERSION/Joomla_$JOOMLA_VERSION-Stable-Full_Package.tar.gz /tmp/joomla/ 
RUN tar -zxvf /tmp/joomla/Joomla_$JOOMLA_VERSION-Stable-Full_Package.tar.gz -C /tmp/joomla/ 
RUN rm -rf /var/www/html/* 
RUN cp -r /tmp/joomla/* /var/www/html/ 

# Put default htaccess in place 
RUN mv /var/www/html/htaccess.txt /var/www/html/.htaccess 

RUN chown -R www-data:www-data /var/www 

# Expose HTTP and MySQL 
EXPOSE 80 3306 

COPY

COPY命令指定应将位于输入路径的文件从与 Dockerfile 相同的目录复制到容器内部的输出路径。

CMD

CMD指令有三种形式-作为ENTRYPOINT的默认参数的 shell 形式和首选可执行形式。CMD的主要目的是为执行容器提供默认值。这些默认值可以包括或省略可执行文件,后者必须指定ENTRYPOINT指令。如果用户在 Docker run中指定参数,则它们将覆盖CMD中指定的默认值。如果您希望容器每次运行相同的可执行文件,则应考虑结合使用ENTRYPOINTCMD

以下是要记住的要点:

  • 不要将CMDRUN混淆-RUN实际上会执行命令并提交结果,而CMD不会在构建过程中执行命令,而是指定图像的预期命令

  • Dockerfile 只能执行一个CMD;如果列出多个,则只会执行最后一个CMD

我们的 Joomla 镜像的示例 Dockerfile

在这里,我们设置 Apache 以启动:

# Use supervisord to start apache / mysql 
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf 
CMD ["/usr/bin/supervisord", "-n"] 

以下是我们完成的 Joomla Dockerfile 的内容:

FROM ubuntu 
MAINTAINER John Wooten <jwooten@cloudconsulted.com> 

ENV DEBIAN_FRONTEND noninteractive 
ENV JOOMLA_VERSION 3.4.1 

RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ 
    mysql-server \ 
    apache2 \ 
    php5 \ 
    php5-imap \ 
    php5-mcrypt \ 
    php5-gd \ 
    php5-curl \ 
    php5-apcu \ 
    php5-mysqlnd \ 
    supervisor 

# Clean up any files used by apt-get 
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 

# Download joomla and put it default apache web root 
ADD https://github.com/joomla/joomla-cms/releases/download/$JOOMLA_VERSION/Joomla_$JOOMLA_VERSION-Stable-Full_Package.tar.gz /tmp/joomla/ 
RUN tar -zxvf /tmp/joomla/Joomla_$JOOMLA_VERSION-Stable-Full_Package.tar.gz -C /tmp/joomla/ 
RUN rm -rf /var/www/html/* 
RUN cp -r /tmp/joomla/* /var/www/html/ 

# Put default htaccess in place 
RUN mv /var/www/html/htaccess.txt /var/www/html/.htaccess 

RUN chown -R www-data:www-data /var/www 

# Expose HTTP and MySQL 
EXPOSE 80 3306 

# Use supervisord to start apache / mysql 
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf 
CMD ["/usr/bin/supervisord", "-n"] 

其他常见的 Dockerfile 命令如下:ENTRYPOINT

ENTRYPOINT允许您配置将作为可执行文件运行的容器。根据 Docker 的文档,我们将使用提供的示例;以下将启动nginx,并使用其默认内容,在端口80上进行侦听:

docker run -i -t --rm -p 80:80 nginx 

docker run <image>的命令行参数将在可执行形式的ENTRYPOINT中的所有元素之后追加,并将覆盖使用CMD指定的所有元素。这允许将参数传递给入口点,即docker run <image> -d将向入口点传递-d参数。您可以使用docker run --entrypoint标志覆盖ENTRYPOINT指令。

LABEL

该指令指定了图像的元数据。稍后可以使用docker inspect <image>命令来检查这些图像元数据。这里的想法是在图像元数据中添加关于图像的信息,以便轻松检索。为了从图像中获取元数据,不需要从图像创建容器(或将图像挂载到本地文件系统),Docker 将元数据与每个 Docker 图像关联,并为其定义了预定义的结构;使用LABEL,可以添加描述图像的附加关联元数据。

图像的标签是键值对。以下是在 Dockerfile 中使用LABEL的示例:

LABEL <key>=<value>  <key>=<value>  <key>=<value> 

此指令将向图像添加三个标签。还要注意,它将创建一个新层,因为所有标签都是在单个LABEL指令中添加的:

LABEL  "key"="value with spaces" 

如果标签值中有空格,请在标签中使用引号:

LABEL LongDescription="This label value extends over new \ 
line." 

如果标签的值很长,请使用反斜杠将标签值扩展到新行。

LABEL key1=value1 
LABEL key2=value2 

可以通过行尾EOL)分隔它们来定义图像的多个标签。请注意,在这种情况下,将为两个不同的LABEL指令创建两个图像层。

关于LABEL指令的注意事项:

  • 标签按照 Dockerfile 中描述的方式汇总在一起,并与FROM指令中指定的基本图像中的标签一起使用

  • 如果标签中的key重复,后面的值将覆盖先前定义的键的值。

  • 尝试在单个LABEL指令中指定所有标签,以生成高效的图像,从而避免不必要的图像层计数

  • 要查看构建图像的标签,请使用docker inspect <image>命令

WORKDIR

此指令用于为 Dockerfile 中的后续RUNADDCOPYCMDENTRYPOINT指令设置工作目录。

在 Dockerfile 中定义工作目录,容器中引用的所有后续相对路径将相对于指定的工作目录。

以下是使用WORKDIR指令的示例:

WORKDIR /opt/myapp 

前面的指令将/opt/myapp指定为后续指令的工作目录,如下所示:

WORKDIR /opt/ 
WORKDIR myapp 
RUN pwd 

前面的指令两次定义了工作目录。请注意,第二个WORKDIR将相对于第一个WORKDIRpwd命令的结果将是/opt/myapp

ENV SOURCEDIR /opt/src 
WORKDIR $SOURCEDIR/myapp 

工作目录可以解析之前定义的环境变量。在这个例子中,WORKDIR指令可以评估SOURCEDIR环境变量,结果的工作目录将是/opt/src/myapp

USER

这将为后续的RUNCMDENTRYPOINT指令设置用户。当从镜像创建和运行容器时,也会设置用户。

以下指令为镜像和容器设置了用户myappuser

USER myappuser 

关于USER指令的注意事项:

  • 可以使用docker run命令中的--user=name|uid[:<group|gid>]来覆盖用户容器的用户

镜像测试和调试

虽然我们可以赞赏容器的好处,但目前对其进行故障排除和有效监控会带来一些复杂性。由于容器设计上的隔离性,它们的环境可能会变得模糊不清。有效的故障排除通常需要进入容器本身的 shell,并且需要安装额外的 Linux 工具来查看信息,这使得调查变得更加困难。

通常,对我们的容器和镜像进行有意义的故障排除所需的工具、方法和途径需要在每个容器中安装额外的软件包。这导致以下结果:

  • 连接或直接附加到容器的要求并非总是微不足道的

  • 一次只能检查一个容器的限制

增加这些困难的是,使用这些工具给我们的容器增加了不必要的臃肿,这是我们最初在规划中试图避免的;极简主义是我们在使用容器时寻求的优势之一。让我们看看如何可以合理地利用一些基本命令获取有用的容器镜像信息,以及调查新出现的应用程序,使我们能够从外部监视和排除容器。

用于故障排除的 Docker 详细信息

现在您已经有了您的镜像(无论构建方法如何)并且 Docker 正在运行,让我们进行一些测试,以确保我们的构建一切正常。虽然这些可能看起来很常规和乏味,但作为故障排除的自上而下方法来运行以下任何或所有内容是一个很好的做法。

这里的前两个命令非常简单,看起来似乎太通用了,但它们将提供基本级别的细节,以便开始任何下游的故障排除工作--$ docker version$ docker info

Docker 版本

首先确保我们知道我们正在运行的 Docker、Go 和 Git 的版本:

$ sudo docker version

Docker 信息

此外,我们还应该了解我们的主机操作系统和内核版本,以及存储、执行和日志记录驱动程序。了解这些东西可以帮助我们从自上而下的角度进行故障排除:

$ sudo docker info

Debian / Ubuntu 的故障排除说明

通过$ sudo docker info命令,您可能会收到以下警告中的一个或两个:

WARNING: No memory limit support 
WARNING: No swap limit support

您需要添加以下命令行参数到内核中,以启用内存和交换空间记账:

cgroup_enable=memory swapaccount=1

对于这些 Debian 或 Ubuntu 系统,如果使用默认的 GRUB 引导加载程序,则可以通过编辑/etc/default/grub并扩展GRUB_CMDLINE_LINUX来添加这些参数。找到以下行:

GRUB_CMDLINE_LINUX="" 

然后,用以下内容替换它:

GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" 

然后,运行update-grub并重新启动主机。

列出已安装的 Docker 镜像

我们还需要确保容器实例实际上已经在本地安装了您的镜像。SSH 进入 docker 主机并执行docker images命令。您应该看到您的 docker 镜像列在其中,如下所示:

$ sudo docker images

如果我的镜像没有出现怎么办?检查代理日志,并确保您的容器实例能够通过 curl 访问您的 docker 注册表并打印出可用的标签:

curl [need to add in path to registry!]

注意

$ sudo docker images 告诉我们什么:我们的容器镜像已成功安装在主机上。

手动启动您的 Docker 镜像

既然我们知道我们的镜像已安装在主机上,我们需要知道它是否对 Docker 守护程序可访问。测试确保您的镜像可以在容器实例上运行的简单方法是尝试从命令行运行您的镜像。这里还有一个额外的好处:我们现在有机会进一步检查应用程序日志以进行进一步的故障排除。

让我们看一下以下示例:

$ sudo docker run -it [need to add in path to registry/latest bin!]

注意

$ sudo docker run <imagename>告诉我们什么:我们可以从 docker 守护程序访问容器镜像,并且还提供可访问的输出日志以进行进一步的故障排除。

如果我的镜像无法运行?检查是否有任何正在运行的容器。如果预期的容器没有在主机上运行,可能会有阻止它启动的问题:

$ sudo docker ps

当容器启动失败时,它不会记录任何内容。容器启动过程的日志输出位于主机上的/var/log/containers中。在这里,您会找到遵循<service>_start_errors.log命名约定的文件。在这些日志中,您会找到我们的RUN命令生成的任何输出,并且这是故障排除的推荐起点,以了解为什么您的容器启动失败。

提示

提示: Logspout (github.com/gliderlabs/logspout) 是 Docker 容器的日志路由器,运行在 Docker 内部。Logspout 附加到主机上的所有容器,然后将它们的日志路由到您想要的位置。

虽然我们也可以查看/var/log/messages中的输出来尝试故障排除,但我们还有一些其他途径可以追求,尽管可能需要更多的工作量。

从缓存中检查文件系统状态

正如我们讨论过的,每次成功的RUN命令在我们的 Dockerfile 中,Docker 都会缓存整个文件系统状态。我们可以利用这个缓存来检查失败的RUN命令之前的最新状态。

完成任务的方法:

  • 访问 Dockerfile 并注释掉失败的RUN命令,以及任何后续的RUN命令

  • 重新保存 Dockerfile

  • 重新执行$ sudo docker build$ sudo docker run

图像层 ID 作为调试容器

每次 Docker 成功执行 Dockerfile 中的RUN命令时,图像文件系统中都会提交一个新的层。方便起见,您可以使用这些层 ID 作为图像来启动一个新的容器。

考虑以下 Dockerfile 作为示例:

FROM centos 
RUN echo 'trouble' > /tmp/trouble.txt 
RUN echo 'shoot' >> /tmp/shoot.txt 

如果我们从这个 Dockerfile 构建:

$ docker build -force-rm -t so26220957 .

我们将获得类似以下的输出:

Sending build context to Docker daemon 3.584 kB 
Sending build context to Docker daemon 
Step 0 : FROM ubuntu 
   ---> b750fe79269d 
Step 1 : RUN echo 'trouble' > /tmp/trouble.txt 
   ---> Running in d37d756f6e55 
   ---> de1d48805de2 
Removing intermediate container d37d756f6e55 
Step 2 : RUN echo 'bar' >> /tmp/shoot.txt 
Removing intermediate container a180fdacd268 
Successfully built 40fd00ee38e1

然后,我们可以使用前面的图像层 ID 从b750fe79269dde1d48805de240fd00ee38e1开始新的容器:

$ docker run -rm b750fe79269d cat /tmp/trouble.txt 
cat: /tmp/trouble.txt No such file or directory 
$ docker run -rm de1d48805de2 cat /tmp/trouble.txt 
trouble 
$ docker run -rm 40fd00ee38e1 cat /tmp/trouble.txt 
trouble 
shoot

注意

我们使用--rm来删除所有调试容器,因为没有理由让它们在运行后继续存在。

如果我的容器构建失败会发生什么?由于构建失败时不会创建任何映像,我们将无法获得容器的哈希 ID。相反,我们可以记录前一层的 ID,并使用该 ID 运行一个带有该 ID 的 shell 的容器:

$ sudo docker run --rm -it <id_last_working_layer> bash -il

进入容器后,执行失败的命令以重现问题,修复命令并进行测试,最后使用修复后的命令更新 Dockerfile。

您可能还想启动一个 shell 并浏览文件系统,尝试命令等等:

$ docker run -rm -it de1d48805de2 bash -il 
root@ecd3ab97cad4:/# ls -l /tmp 
total 4 
-rw-r-r-- 1 root root 4 Jul 3 12:14 trouble.txt 
root@ecd3ab97cad4:/# cat /tmp/trouble.txt 
trouble 
root@ecd3ab97cad4:/#

其他示例

最后一个示例是注释掉以下 Dockerfile 中的内容,包括有问题的行。然后我们可以手动运行容器和 docker 命令,并以正常方式查看日志。在这个 Dockerfile 示例中:

RUN trouble 
RUN shoot 
RUN debug 

此外,如果失败是在射击,那么注释如下:

RUN trouble 
# RUN shoot 
# RUN debug 

然后,构建和运行:

$ docker build -t trouble . 
$ docker run -it trouble bash 
container# shoot 
...grep logs...

检查失败的容器进程

即使您的容器成功从命令行运行,检查任何失败的容器进程,不再运行的容器,并检查我们的容器配置也是有益的。

运行以下命令来检查失败或不再运行的容器,并注意CONTAINER ID以检查特定容器的配置:

$ sudo docker ps -a

注意容器的状态。如果您的任何容器的状态显示除0之外的退出代码,可能存在容器配置的问题。举个例子,一个错误的命令会导致退出代码为127。有了这些信息,您可以调试任务定义CMD字段。

虽然有些有限,但我们可以进一步检查容器以获取额外的故障排除细节:

$ **sudo docker inspect <containerId>

最后,让我们也分析一下容器的应用程序日志。容器启动失败的错误消息将在这里输出:

$ sudo docker logs <containerId>

其他潜在有用的资源

$ sudo docker top 给出了容器内运行的进程列表。

当您需要比top提供的更多细节时,可以使用$ sudo docker htop,它提供了一个方便的、光标控制的界面。htoptop启动更快,您可以垂直和水平滚动列表以查看所有进程和完整的命令行,您不需要输入进程号来终止进程或优先级值来接收进程。

当本书付印时,排除容器和镜像的机制可能已经得到了显著改善。Docker 社区正在致力于内置报告和监控解决方案,市场力量也必将带来更多的选择。

使用 sysdig 进行调试

与任何新技术一样,一些最初固有的复杂性会随着时间的推移而被排除,新的工具和应用程序也会被开发出来以增强它们的使用。正如我们所讨论的,容器目前确实属于这一类别。虽然我们已经看到 Docker Registry 中官方标准化镜像的可用性有所改善,但我们现在也看到了新出现的工具,这些工具可以帮助我们有效地管理、监视和排除我们的容器。

使用 sysdig 进行调试

Sysdig 为容器提供应用程序监控[图片版权© 2014 Draios, Inc.]

Sysdig (www.sysdig.org/ ) 就是这样的一个工具。作为一个用于系统级探索和排除容器化环境可见性的au courant应用程序,sysdig的美妙之处在于我们能够从外部访问容器数据(尽管sysdig实际上也可以安装在容器内部)。从高层来看,sysdig为我们的容器管理带来了以下功能:

  • 能够访问和审查每个容器中的进程(包括内部和外部 PID)

  • 能够深入到特定容器中

  • 能够轻松过滤一组容器以进行进程审查和分析

Sysdig 提供有关 CPU 使用、I/O、日志、网络、性能、安全和系统状态的数据。重申一遍,这一切都可以从外部完成,而无需在我们的容器中安装任何东西。

我们将在本书中继续有效地使用sysdig来监视和排除与我们的容器相关的特定进程,但现在我们将提供一些示例来排除我们基本的容器进程和日志问题。

让我们安装sysdig到我们的主机上,以展示它对我们和我们的容器可以做什么!

单步安装

通过以 root 或sudo执行以下命令,可以在一步中完成sysdig的安装:

curl -s https://s3.amazonaws.com/download.draios.com/stable/install-sysdig | sudo bash

注意

注意:sysdig目前已在最新的 Debian 和 Ubuntu 版本中本地包含;但建议更新/运行安装以获取最新的软件包。

高级安装

根据sysdig维基百科,高级安装方法可能对脚本化部署或容器化环境有用。它也很容易;高级安装方法已列入 RHEL 和 Debian 系统。

什么是凿子?

要开始使用sysdig,我们应该了解一些专业术语,特别是凿子。在sysdig中,凿子是一些小脚本(用 Lua 编写),用于分析sysdig事件流以执行有用的操作。事件被有效地带到用户级别,附加上下文,然后可以应用脚本。凿子在活动系统上运行良好,但也可以与跟踪文件一起用于离线分析。您可以同时运行尽可能多的凿子。例如:

topcontainers_error chisel 将按错误数量显示顶部容器。

有关 sysdig 凿子的列表:

$ sysdig -cl(使用-i标志获取有关特定凿子的详细信息)

单容器进程分析

使用topprocs_cpu凿子的示例,我们可以应用过滤器:

$ sudo sysdig -pc -c topprocs_cpu container.name=zany_torvalds

这些是示例结果:

CPU%          Process       container.name   
------------------------------------------ 
02.49%        bash          zany_torvalds 
37.06%        curl          zany_torvalds 
0.82%         sleep         zany_torvalds

与使用$ sudo docker top(以及类似)不同,我们可以确定我们想要查看进程的确切容器;例如,以下示例仅显示来自wordpress容器的进程:

$ sudo sysdig -pc -c topprocs_cpu container.name contains wordpress 

CPU%           Process         container.name   
-------------------------------------------------- 
5.38%          apache2         wordpress3 
4.37%          apache2         wordpress2 
6.89%          apache2         wordpress4 
7.96%          apache2         wordpress1

其他有用的 Sysdig 凿子和语法

  • topprocs_cpu按 CPU 使用率显示顶部进程

  • topcontainers_file按 R+W 磁盘字节显示顶部容器

  • topcontainers_net按网络 I/O 显示顶部容器

  • lscontainers将列出正在运行的容器

  • $ sudo sysdig -pc -cspy_logs分析每个屏幕的所有日志

  • $ sudo sysdig -pc -cspy_logs container.name=zany_torvalds打印容器zany_torvalds的日志

故障排除-一个开放的社区等待您

一般来说,你可能遇到的大多数问题在其他地方和其他时间已经有人经历过。Docker 和开源社区、IRC 频道和各种搜索引擎都可以提供高度可访问的信息,并可能为你提供解决困扰的情况和条件的答案。充分利用开源社区(特别是 Docker 社区)来获取你所寻找的答案。就像任何新兴技术一样,在开始阶段,我们都在一起学习!

自动化镜像构建

有许多方法可以自动化构建容器镜像的过程;在一本书中无法合理地提供所有方法。在本书的后面章节中,我们将更深入地探讨一系列自动化选项和工具。在这种特定情况下,我们只讨论使用我们的 Dockerfile 进行自动化。我们已经讨论过 Dockerfile 可以用于自动化镜像构建,所以让我们更专门地研究 Dockerfile 自动化。

单元测试部署

在构建过程中,Docker 允许我们运行任何命令。让我们利用这一点,在构建镜像的同时启用单元测试。这些单元测试可以帮助我们在将镜像推送到分阶段或部署之前识别生产镜像中的问题,并且至少部分验证镜像的功能是否符合我们的意图和期望。如果单元测试成功运行,我们就有了一定程度的信心,我们有一个有效的服务运行环境。这也意味着,如果测试失败,我们的构建也会失败,有效地阻止了一个不工作的镜像进入生产环境。

使用我们之前的cloudconsulted/joomla仓库镜像,我们将建立一个自动构建的示例工作流程,并进行测试。我们将使用PHPUnit,因为它是 Joomla 项目开发团队正式使用的工具,它可以方便地针对整个堆栈(Joomla 代码、Apache、MySQL 和 PHP)运行单元测试。

进入cloudconsulted/joomla的 Dockerfile 目录(在我们的例子中是dockerbuilder),并进行以下更新。

执行以下命令安装 PHPUnit:

[# install composer to a specific directory 
curl -sS https://getcomposer.org/installer | php -- --install-dir=bin 
# use composer to install phpunit 
composer global require "phpunit/phpunit=4.1.*"]

PHPUnit 也可以通过执行以下命令进行安装:

[# install phpunit 
wget https://phar.phpunit.de/phpunit.phar 
chmod +x phpunit.phar 
mv phpunit.phar /usr/local/bin/phpunit 
# might also need to put the phpunit executable placed here? test this: 
cp /usr/local/bin/phpunit /usr/bin/phpunit]

现在,让我们用phpunit运行我们的单元测试:

# discover and run any tests within the source code 
RUN phpunit 

我们还需要确保将我们的单元测试COPY到镜像内的资产中:

# copy unit tests to assets 
COPY test /root/test 

最后,让我们做一些清理工作。为了确保我们的生产代码不能依赖(无论是意外还是其他原因)测试代码,一旦单元测试完成,我们应该删除那些测试文件:

# clean up test files 
RUN rm -rf test 

我们对 Dockerfile 的总更新包括:

wget https://phar.phpunit.de/phpunit.phar 
chmod +x phpunit.phar 
mv phpunit.phar /usr/local/bin/phpunit 

RUN phpunit   
COPY test /root/test 
RUN rm -rf test 

现在,我们有一个脚本化的 Dockerfile,每次构建此镜像时,都将完全测试我们的 Joomla 代码、Apache、MySQL 和 PHP 依赖项,作为构建过程的一个文字部分。结果是一个经过测试的、可重现的生产环境!

自动化测试部署

在我们对部署可行图像的信心增强之后,这个构建过程仍然需要开发人员或 DevOps 工程师在每次生产推送之前重新构建镜像。相反,我们将依赖于来自我们的 Docker 和 GitHub 存储库的自动构建。

我们的 GitHub 和 Docker Hub 存储库将用于自动化我们的构建。通过在 GitHub 上维护我们的 Dockerfile、依赖项、相关脚本等,对存储库进行任何推送或提交将自动强制将更新的推送到同步的 Docker Hub 存储库。我们在 Docker Hub 上拉取的生产图像会自动更新任何新的构建信息。

Docker Cloud 是最新的应用程序生命周期的一部分,它提供了一个托管的注册服务,具有构建和测试设施。Docker Cloud 扩展了 Tutum 的功能,并与 Docker Hub 更紧密地集成。借助 Docker Cloud 系统,管理员可以仅需点击几下即可在云中部署和扩展应用程序。持续交付代码集成和自动化构建、测试和部署工作流程。它还提供了对整个基础架构容器的可见性,并访问面向开发人员友好的 CLI 工具的程序化 RESTful API。因此,Docker Cloud 可用于自动化构建过程和测试部署。

以下是 Docker Cloud 的重要特性:

  • 允许构建 Docker 镜像,并将云存储库链接到源代码,以便简化镜像构建过程

  • 它允许将您的基础架构和云服务链接起来,以自动提供新节点

  • 一旦镜像构建完成,它可以用于部署服务,并可以与 Docker Cloud 的服务和微服务集合进行链接

  • 在 Docker Cloud 中,beta 模式下的 Swarm 管理可用于在 Docker Cloud 中创建 swarm 或将现有的 swarm 注册到 Docker Cloud 中使用 Docker ID

总结

Docker 和 Dockerfiles 为应用程序开发周期提供了可重复的流程,为开发人员和 DevOps 工程师提供了独特的便利-生产就绪的部署,注入了经过测试的镜像的信心和自动化的便利。这为最需要的人提供了高度的赋权,并导致了经过测试和生产就绪的图像构建的持续交付,我们可以完全自动化,延伸到我们的云端。

在本章中,我们了解到生产就绪的应用程序容器化中的一项关键任务是图像构建。构建基本和分层镜像以及避免故障排除的领域是我们涵盖的主要主题。在构建我们的基本镜像时,我们看到 Docker Registry 提供了丰富和经过验证的图像,我们可以自由地用于可重复的流程。我们还讨论了手动构建图像,从头开始。前进的时候,我们探讨了使用 Dockerfile 构建分层图像,并详细列出了 Dockerfile 命令。最后,一个示例工作流程说明了自动化图像构建以及镜像和容器的测试。在整个过程中,我们强调了故障排除领域和选项的方法和手段。

为您的应用程序容器构建简洁的 Docker 镜像对于应用程序的功能和可维护性至关重要。现在我们已经了解了构建基本和分层镜像以及基本的故障排除方法,我们将期待构建真实的应用程序镜像。在下一章中,我们将学习使用一组合适的镜像规划和构建多层应用程序。

第四章:设计微服务和 N 层应用程序

让我们扩展上一章中所看到和学到的关于微服务和 N 层应用程序更高级的开发和部署。本章将讨论这些设计方法的基础架构,以及在构建这些类型的应用程序时遇到的典型问题。本章将涵盖以下主题:

  • 单片架构模式

  • N 层应用程序架构

  • 构建、测试和自动化 N 层应用程序

  • 微服务架构模式

  • 构建、测试和自动化微服务

  • 将多层应用程序解耦为多个图像

  • 使不同层的应用程序运行

如今,作为服务构建的现代软件正在引发应用程序设计方式的转变。如今,应用程序不再使用 Web 框架来调用服务和生成网页,而是通过消费和生成 API 来构建。在业务应用程序的开发和部署方面发生了许多变化,其中一些变化是戏剧性的,另一些变化是根据过去的设计方法进行修订或扩展的,这取决于您的观点。存在几种架构设计方法,它们可以通过为企业构建的应用程序与为 Web 构建的应用程序与云构建的应用程序进行区分。

在过去几年的发展趋势中,充斥着诸如微服务架构(MSA)之类的术语,这些术语适用于一种特定的应用程序设计和开发方式,即独立部署的服务套件。微服务架构风格的迅猛崛起显然是当今开发部署中不可否认的力量;从单片架构到 N 层应用程序和微服务的转变是相当大的,但这究竟有多少是炒作,有多少可以被磨练?

炒作还是自负

在我们开始深入研究故障排除之前,我们应该对现代应用程序以及 N 层和微服务架构风格进行基本的上下文概述。了解这些架构风格的优势和局限将有助于我们规划潜在的故障排除领域,以及我们如何避免它们。容器非常适合这两种架构方法,我们将分别讨论每种方法,以便给予它们适当的重视。

在所有的噪音中,我们有时会忘记,要在这些领域部署系统,仍然需要创建服务,并在工作的分布式应用程序中组合多个服务。在这里,重要的是要理解术语“应用程序”的现代含义。应用程序现在主要是构建为异步消息流或同步请求调用(如果不是两者兼而有之),这些消息流或请求调用用于形成由这些连接联合的组件或服务的集合。参与的服务高度分布在不同的机器和不同的云(私有、公共和混合)之间。

关于建筑风格,我们不会过多比较或进行过于详细的讨论,关于微服务到底是什么,以及它们是否与面向服务的架构(SOA)有任何不同-在其他地方肯定有很多论坛和相关的辩论可供选择。以 Unix 至少根植的设计原则为基础,我们在本书中不会提出任何权威观点,即当前的微服务趋势是概念上独特的或完全巧妙的。相反,我们将提出实施这种架构方法的主要考虑因素以及现代应用程序可以获得的好处。

用例仍然驱动和决定架构方法(或者,在我看来,应该如此),因此在所有主要的架构风格之间进行一定程度的比较分析是有价值的:单体、N 层和微服务。

单体架构

单体应用本质上是一个部署单元,包含所有服务和依赖关系,使其易于开发、易于测试、相对容易部署,并且最初易于扩展。然而,这种风格不符合大多数现代企业应用程序(N 层)和大规模 Web 开发的必要需求,当然也不适用于部署到云端的微服务应用程序。变更周期紧密耦合-对应用程序的任何更改,甚至是最小的部分,都需要对整个单体进行全面重建和重新部署。随着单体的成熟,任何尝试扩展都需要扩展整个应用程序而不是单个部分,这特别需要更多的资源,变得非常困难,甚至不可能。在这一点上,单体应用程序变得过于复杂,充斥着越来越难以解读的大量代码,以至于像错误修复或实施新功能这样的业务关键项目变得太耗时,根本无法尝试。随着代码库变得难以理解,可以合理地预期任何更改可能会出现错误。应用程序的不断增长不仅减缓了开发速度,而且完全阻碍了持续开发;要更新单体的任何部分,必须重新部署整个应用程序。

单体架构

单体架构模式

单体应用程序的其他问题也很多,资源无法更好地满足需求,例如 CPU 或内存需求。由于所有模块都在运行相同的进程,任何错误都有可能导致整个进程停止。最后,更难以采用新的框架或语言,这给采用新技术带来了巨大障碍-您可能会被困在项目开始时所做的技术选择中。不用说,自项目开始以来,您的需求可能已经发生了相当大的变化。使用过时的、低效的技术使得留住和引进新人才变得更加困难。应用程序现在变得非常难以扩展和不可靠,使得敏捷开发和交付应用程序变得不可能。单体应用程序最初的简单和便利很快变成了它自己的致命弱点。

由于这些单片架构基本上是一个执行单元,可以完成所有任务,N 层和微服务架构已经出现,以解决现代化应用程序,主要是云和移动应用程序的专门服务需求。

N 层应用架构

为了理解 N 层应用程序及其分解为微服务的潜力,我们将其与单片样式进行比较,因为 N 层应用程序的开发和微服务的普及都是为了解决单片架构所带来的过时条件中发现的许多问题。

N 层应用架构,也称为分布式应用多层,提供了一个模型,开发人员可以创建灵活和可重用的应用程序。由于应用程序被分为多个层,开发人员可以选择修改或添加特定的层或层,而不需要对整个应用程序进行重新设计,这在单片应用程序下是必要的。多层应用程序是指分布在多个层之间的任何应用程序。它在逻辑上分离了不同的应用程序特定和操作层。层的数量根据业务和应用程序要求而变化,但三层是最常用的架构。多层应用程序用于将企业应用程序划分为两个或多个可以分别开发、测试和部署的组件。

N 层应用程序本质上是 SOA,试图解决过时的单片设计架构的一些问题。正如我们在之前的章节中所看到的,Docker 容器非常适合 N 层应用程序开发。

N 层应用架构

N 层应用架构

一个常见的 N 层应用程序由三层组成:表示层(提供基本用户界面和应用程序服务访问)、领域逻辑层(提供用于访问和处理数据的机制)和数据存储层(保存和管理静态数据)。

注意

虽然层和层经常可以互换使用,但一个相当普遍的观点是实际上存在差异。这个观点认为是构成软件解决方案的元素的逻辑结构机制,而是系统基础设施的物理结构机制。除非在我们的书中另有特别说明,否则我们将互换使用层和层。

将各种层在 N 层应用程序中分开的最简单方法是为您的应用程序中要包含的每个层创建单独的项目。例如,表示层可能是一个 Windows 表单应用程序,而数据访问逻辑可能是位于中间层的类库。此外,表示层可能通过服务与中间层的数据访问逻辑进行通信。将应用程序组件分离到单独的层中可以增加应用程序的可维护性和可扩展性。它可以通过使新技术更容易地应用于单个层而无需重新设计整个解决方案来实现这一点。此外,N 层应用程序通常将敏感信息存储在中间层中,以保持与表示层的隔离。

N 层应用程序开发的最常见示例可能是网站;在我们上一章中使用的cloudconsulted/joomla镜像中可以看到这样的示例,其中 Joomla、Apache、MySQL 和 PHP 都被分层为单个容器。

对我们来说,很容易简单地递归使用我们之前的cloudconsulted/joomla镜像,但让我们构建一个经典的三层 Web 应用程序,以暴露自己于一些其他应用潜力,并为我们的开发团队引入另一个单元测试工具。

构建一个三层 Web 应用程序

让我们借助以下容器开发和部署一个真实的三层 Web 应用程序:

NGINX > Ruby on Rails > PostgreSQL:

NGINX Docker 容器(Dockerfile)如下:

## AngularJS Container build  
FROM nginx:latest 

# Download packages 
RUN apt-get update 
RUN apt-get install -y curl   \ 
                   git    \ 
                   ruby \ 
                   ruby-dev \     
                   build-essential 

# Copy angular files 
COPY . /usr/share/nginx 

# Installation 
RUN curl -sL https://deb.nodesource.com/setup | bash - 
RUN apt-get install -y nodejs \ 
                  rubygems 
RUN apt-get clean 
WORKDIR /usr/share/nginx 
RUN npm install npm -g 
RUN npm install -g bower 
RUN npm install  -g grunt-cli 
RUN gem install sass 
RUN gem install compass 
RUN npm cache clean 
RUN npm install 
RUN bower -allow-root install -g 

# Building 
RUN grunt build 

# Open port and start nginx 
EXPOSE 80 
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]

如图所示的 Ruby on Rails Docker 容器(Dockerfile):

## Ruby-on-Rails Container build 
FROM rails:onbuild 

# Create and migrate DB 
RUN bundle exec rake db:create 
RUN bundle exec rake db:migrate 

# Start rails server 
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]

如图所示的 PostgreSQL Docker 容器:

## PostgreSQL Containers build 
# cloudconsulted/postgres is a Postgres setup that accepts remote connections from Docker IP (172.17.0.1/16).  We can therefore make use of this image directory so there is no need to create a new Docker file here.

上述 Dockerfile 可用于部署三层 Web 应用程序,并帮助我们开始使用微服务。

微服务架构

要开始解释微服务架构风格,将有利于再次与单片进行比较,就像我们在 N 层中所做的那样。您可能还记得,单片应用是作为一个单一单位构建的。还要记住,单片企业应用通常围绕三个主要层构建:客户端用户界面(包括在用户机器上的浏览器中运行的 HTML 页面和 JavaScript)、数据库(包括插入到一个常见且通常是关系型数据库管理系统中的许多表)和服务器端应用程序(处理 HTTP 请求,执行领域逻辑,从数据库中检索和更新数据,并选择和填充要发送到浏览器的 HTML 视图)。这种经典版本的单片企业应用是一个单一的逻辑可执行文件。对系统的任何更改都涉及构建和部署服务器端应用程序的新版本,并且更改底层技术可能是不明智的。

通往现代化的道路

微服务代表了现代云和现代应用开发的融合,围绕以下结构:

  • 组件化服务

  • 围绕业务能力的组织

  • 产品,而不是项目

  • 智能端点和愚蠢的管道

  • 分散式治理和数据管理

  • 基础设施自动化

在这里,单片通常侧重于用于集成单片应用的企业服务总线(ESB),现代应用设计是 API 驱动的。这些现代应用在各个方面都采用 API:在前端用于连接富客户端,在后端用于与内部系统集成,并在侧面允许其他应用访问其内部数据和流程。许多开发人员发现,与更复杂的传统企业机制相比,那些已被证明对前端、后端和应用程序之间的场景具有弹性、可扩展性和敏捷性的轻量级 API 服务也可以用于应用程序组装。同样引人注目的是,容器,尤其是在微服务架构方法中,缓解了开发人员被阻止参与架构决策的永恒问题,同时仍然实现了可重复性的好处。使用经过预先批准的容器配置。

微服务架构模式

在这里,我们说明了,我们没有一个单一的庞大的单片应用程序,而是将应用程序分割成更小、相互连接的服务(即微服务),每个功能区域实现一个。这使我们能够直接部署以满足专用用例或特定设备或用户的需求,或者微服务方法,简而言之,规定了我们不是拥有所有开发人员都接触的一个巨大的代码库,这通常变得难以管理,而是由小而敏捷的团队管理的许多较小的代码库。这些代码库之间唯一的依赖是它们的 API:

微服务架构模式

微服务架构模式

注意

围绕微服务的一个常见讨论是关于这是否只是 SOA。在这一点上存在一些有效性,因为微服务风格确实分享了 SOA 的一些主张。实际上,SOA 意味着许多不同的事情。因此,我们提出并将尝试表明,虽然存在共同的相似之处,但 SOA 与此处所呈现的微服务架构风格仍然存在显着差异。

微服务的共同特征

虽然我们不会尝试对微服务架构风格进行正式定义,但有一些共同的特征我们当然可以用来识别它。微服务通常围绕业务能力和优先级进行设计,并包括多个组件服务,可以独立自动化部署,而不会影响应用程序、智能端点和语言和数据的分散控制。

为了提供一些基础,如果不是共同的基础,以下是一个可以被视为符合微服务标签的架构的共同特征的概述。应该理解的是,并非所有的微服务架构都会始终展现所有的特征。然而,我们期望大多数微服务架构将展现大部分这些特征,让我们列举一下:

  • 独立

  • 无状态

  • 异步

  • 单一职责

  • 松散耦合

  • 可互换

微服务的优势

我们刚刚列出的微服务的共同特征也用于列举它们的优势。而不是要过多地重复,让我们至少审视一下主要的优势点:

  • 微服务强制实施一定程度的模块化:这在单片架构中实际上非常难以实现。微服务的优势在于单个服务开发速度更快,更容易理解,更容易维护。

  • 微服务使每个服务能够独立开发:这是由专门专注于该服务的团队完成的。微服务的优势在于赋予开发人员选择最适合或更合理的技术的自由,只要该服务遵守 API 合同。这也意味着开发人员不再被困在项目开始时或开始新项目时可能过时的技术中。不仅存在使用当前技术的选项,而且由于服务规模相对较小,现在还可以使用更相关和可靠的技术重写旧服务。

  • 微服务使每个服务能够持续部署:开发人员无需协调局部更改的部署。微服务的优势在于持续部署-只要更改成功测试,部署就会立即进行。

  • 微服务使每个服务能够独立扩展:您只需部署每个服务实例以满足容量和可用性约束。此外,我们还可以简洁地匹配硬件以满足服务的资源需求(例如,为 CPU 和内存密集型服务优化的计算或内存优化硬件)。微服务的优势在于不仅匹配容量和可用性,而且利用为服务优化的用户特定硬件。

所有这些优势都非常有利,但接下来让我们详细阐述可伸缩性的观点。正如我们在单片架构中所看到的,虽然易于初始化扩展,但在随着时间的推移执行扩展时显然存在不足;瓶颈随处可见,最终,其扩展方法是极不可行的。幸运的是,作为一种架构风格,微服务在扩展方面表现出色。一本典型的书,《可伸缩性的艺术》(theartofscalability.com/)展示了一个非常有用的三维可伸缩性模型,即可伸缩性立方体microservices.io/articles/scalecube.html)。

可伸缩的微服务

在提供的模型中,沿着 X 轴进行扩展(即,单体应用程序),我们可以看到常见的水平复制方法,通过在负载平衡器后运行应用程序的多个克隆副本来扩展应用程序。这将提高应用程序的容量和可用性。

可扩展的微服务

可扩展的微服务

在 Z 轴上进行扩展(即 N 层/SOA),每个服务器运行代码的相同副本(类似于 X 轴)。这里的区别在于每个服务器仅负责严格的数据子集(即数据分区或通过将数据拆分为相似的内容进行扩展)。因此,系统的某个组件负责将特定请求路由到适当的服务器。

注意

分片是一种常用的路由标准,其中使用请求的属性将请求路由到特定服务器(例如,行的主键或客户的身份)。

与 X 轴扩展一样,Z 轴扩展旨在提高应用程序的容量和可用性。然而,正如我们在本章中所了解的,单体或 N 层方法(X 和 Y 轴扩展)都无法解决我们不断增加的开发和应用程序复杂性的固有问题。要有效地解决这些问题,我们需要应用 Y 轴扩展(即,微服务)。

扩展的第三个维度(Y 轴)涉及功能分解,或通过将不同的内容拆分来进行扩展。在应用程序层发生的 Y 轴扩展将把单体应用程序分解为不同的服务集,其中每个服务实现一组相关功能(例如,客户管理,订单管理等)。在本章后面,我们将直接探讨服务的分解。

我们通常可以看到的是利用了扩展立方体的三个轴的应用程序。Y 轴扩展将应用程序分解为微服务;在运行时,X 轴扩展在负载平衡器后执行每个服务的多个实例,以增强输出和可用性,一些应用程序可能还会使用 Z 轴扩展来分区服务。

微服务的缺点

让我们通过了解一些微服务的缺点来全面尽职调查:

  • 基于微服务的应用部署要复杂得多:与单片应用相比,微服务应用通常由大量服务组成。事实上,我们在部署它们时会面临更大的复杂性。

  • 管理和编排微服务要复杂得多:在大量服务中,每个服务都将有多个运行时实例。随着更多需要配置、部署、扩展和监控的移动部件的指数级增加。因此,任何成功的微服务部署都需要开发人员对部署方法进行更细粒度的控制,同时结合高水平的自动化。

  • 测试微服务应用要复杂得多:为微服务应用编写测试类不仅需要启动该服务,还需要启动其依赖服务。

一旦理解,我们就可以制定策略和设计来减轻这些缺点,并更好地规划故障排除领域。

制定微服务的考虑

我们已经审查了从单一交付到多层到容器化微服务的违规行为,并了解到每种应用都有其自己的功能位置。每种架构都有其自己的有效程度;适当的设计策略和这些架构的应用对于您的部署成功是必要的。通过学习了解了单片、N 层和微服务的基本原则,我们更有能力根据每种情况来战略性地实施最合适的架构。

制定微服务的考虑

从单一到微服务

尽管存在缺点和实施挑战,微服务架构模式是复杂、不断发展的应用的更好选择。为了利用微服务进行现代云和 Web 应用程序的设计和部署,我们如何最好地利用微服务的优势,同时减轻潜在的缺点?

无论是开发新应用还是重振旧应用,这些考虑因素都必须考虑到微服务:

  • 构建和维护高可用的分布式系统是复杂的

  • 更多的移动部件意味着需要跟踪更多的组件

  • 松散耦合的服务意味着需要采取步骤来保持数据一致

  • 分布式异步进程会产生网络延迟和更多的 API 流量

  • 测试和监控单个服务是具有挑战性的

减轻缺点

这可能是整本书中提供的最简单的指导;然而,我们一次又一次地看到明显的事情要么完全被忽视,要么被忽视,要么被忽视。我们在这里提交的观点是,尽管已知的缺点相对较少,但存在着当前和不断发展的机制来解决几乎所有这些问题;人们强烈期望容器市场将发展出大量解决当前问题的解决方案。

再次,让我们从这里开始,作为需要更少故障排除的成功微服务应用程序的基础:

  • 全面负责:如果不全面负责并知道最终的成功直接取决于你和你的团队,你的项目及其产生的应用程序将受到影响。承诺、奉献和坚持会带来丰厚的成果。

  • 全面理解:充分理解业务目标以及最适合解决这些目标的技术,更不用说你使用它们的“如何”和“为什么”。始终在学习!

  • 进行详尽协调的规划:战略性地规划,与其他应用程序利益相关者一起规划,为失败做规划,然后再做更多规划;衡量你的结果并修订计划,不断重新评估计划。始终在衡量,始终在规划!

  • 利用当前技术:在当今的技术环境中,充分利用最稳定和功能齐全的工具和应用程序至关重要;因此,寻找它们。

  • 随着应用程序的发展:你必须像你正在使用的容器技术一样灵活和适应;变化必须成为你详尽协调规划的一部分!

太好了!我们知道我们不仅必须承认,而且要积极参与我们应用项目过程的最基本要素。我们也知道并理解微服务架构方法的优缺点,以及这些优点可能远远超过任何负面影响。除了前面提到的五个强大的要素之外,我们如何减轻这些缺点,以利用微服务为我们带来的积极影响呢?

管理微服务

此时,你可能会问自己“那么,Docker 在这场对话中的位置在哪里?”我们的第一个半开玩笑的答案是,它完全合适!

Docker 非常适合微服务,因为它将容器隔离到一个进程或服务中。这种有意的单个服务或进程的容器化使得管理和更新这些服务变得非常简单。因此,毫不奇怪,在 Docker 之上的下一个浪潮导致了专门用于管理更复杂场景的框架的出现,包括以下内容:

  • 如何在集群中管理单个服务?

  • 如何在主机上跨多个实例中管理一个服务?

  • 如何在部署和管理层面协调多个服务?

正如在不断成熟的容器市场中所预期的那样,我们看到了更多的辅助工具出现,以配合开源项目,例如 Kubernetes、MaestroNG 和 Mesos 等等,所有这些都是为了解决 Docker 容器化应用程序的管理、编排和自动化需求。例如,Kubernetes 是专门为微服务构建的项目,并且与 Docker 非常配合。Kubernetes 的关键特性直接迎合了微服务架构中至关重要的特征-通过 Docker 轻松部署新服务、独立扩展服务、终端客户端对故障的透明性以及简单的、临时的基于名称的服务端点发现。此外,Docker 自己的原生项目-Machine、Swarm、Compose 和 Orca,虽然在撰写本文时仍处于测试阶段,但看起来非常有前途-很可能很快就会被添加到 Docker 核心内核中。

由于我们稍后将专门讨论 Kubernetes、其他第三方应用程序以及整个章节的 Docker Machine、Swarm 和 Compose,让我们在这里看一个例子,利用我们之前使用过的服务(NGINX、Node.js)以及 Redis 和 Docker Compose。

真实世界的例子

NGINX > Node.js > Redis > Docker Compose

# Directly create and run the Redis image 
docker run -d -name redis -p 6379:6379 redis 

## Node Container 
# Set the base image to Ubuntu 
FROM ubuntu 

# File Author / Maintainer 
MAINTAINER John Wooten @CONSULTED <jwooten@cloudconsulted.com> 

# Install Node.js and other dependencies 
RUN apt-get update && \ 
        apt-get -y install curl && \ 
        curl -sL https://deb.nodesource.com/setup | sudo bash - && \ 
        apt-get -y install python build-essential nodejs 

# Install nodemon 
RUN npm install -g nodemon 

# Provides cached layer for node_modules 
ADD package.json /tmp/package.json 
RUN cd /tmp && npm install 
RUN mkdir -p /src && cp -a /tmp/node_modules /src/ 

# Define working directory 
WORKDIR /src 
ADD . /src 

# Expose portability 
EXPOSE 8080 

# Run app using nodemon 
CMD ["nodemon", "/src/index.js"] 

## Nginx Containers build 
# Set nginx base image 
FROM nginx 

# File Author / Maintainer 
MAINTAINER John Wooten @CONSULTED <jwooten@cloudconsulted.com> 

# Copy custom configuration file from the current directory 
COPY nginx.conf /etc/nginx/nginx.conf 

## Docker Compose 
nginx: 
build: ./nginx 
links: 
 - node1:node1 
 - node2:node2 
 - node3:node3 
ports: 
- "80:80" 
node1: 
build: ./node 
links: 
 - redis 
ports: 
 - "8080" 
node2: 
build: ./node 
links: 
 - redis 
ports: 
- "8080" 
node3: 
build: ./node 
links: 
 - redis 
ports: 
- "8080" 
redis: 
image: redis 
ports: 
 - "6379"

我们将在第十章中更深入地探讨 Docker Compose,Docker Machine、Compose 和 Swarm。此外,我们还需要实现一个服务发现机制(在后面的章节中讨论),使服务能够发现其需要与之通信的任何其他服务的位置(主机和端口)。

自动化测试和部署

我们希望尽可能多地确信我们的应用程序正在运行;这始于自动化测试,以促进我们的自动化部署。不用说,我们的自动化测试是至关重要的。推动工作软件管道意味着我们自动化部署到每个新环境。

目前,微服务的测试仍然相对复杂;正如我们讨论过的,对于一个服务的测试类将需要启动该服务,以及它所依赖的任何服务。我们至少需要为这些服务配置存根。所有这些都可以做到,但让我们来研究如何减少其复杂性。

自动化测试

从战略上讲,我们需要规划我们的设计流程,包括测试,以验证我们的应用程序是否可以部署到生产环境。以下是我们希望通过自动化测试实现的示例工作流程:

自动化测试

上述图表代表了一个 DevOps 管道,从代码编译开始,经过集成测试、性能测试,最终在生产环境中部署应用程序。

设计以应对故障

为了成功,我们必须接受故障是非常真实的可能性。事实上,我们确实应该有目的地将故障插入到我们的应用程序设计流程中,以测试当它们发生时我们如何成功地处理它们。这种在生产中的自动化测试最初需要钢铁般的神经;然而,通过重复和熟悉,我们可以得到自我修复的自动化。故障是必然的;因此,我们必须计划和测试我们的自动化,以减轻这种必然带来的损害。

成功的应用程序设计涉及内置的容错能力;这对于微服务尤为重要,因为使用服务作为组件的结果。由于服务随时可能失败,能够快速检测到故障并且在可能的情况下自动恢复服务是非常重要的。对我们的应用程序进行实时监控在微服务应用程序中至关重要,提供了一个早期警报系统,可以提前发现问题或潜在的错误或问题。这为开发团队提供了更早的响应和调查;由于微服务架构中存在这样的协作和事件协同,我们追踪新出现的行为变得非常重要。

因此,微服务团队应该设计包括一些最低限度的监控和日志设置,用于每个单独的服务:具有上/下状态的仪表板,断路器状态的元数据,当前吞吐量和延迟以及各种操作和业务相关的指标。

在应用程序构建结束时,如果我们的组件不能清晰地组合在一起,我们所做的不过是将复杂性从组件内部转移到它们之间的连接。这使得事情变得更难定义和更难控制。最终,我们应该设计以应对失败的必然性才能取得成功。

Dockunit 用于单元测试

为了增强我们的单元测试能力,我们还将安装和使用 Dockunit 来进行单元测试。对于我们的单元测试,有很多选项可供选择。在过去的单元测试中,我发现通过将 Dockunit 部署为我的开发工具包中的一个标准应用程序,我几乎可以满足任何单元测试需求。为了不显得太重复,让我们继续设置使用 Dockunit 进行自动化测试。

Dockunit 的要求是 Node.js、npm 和 Docker。

如果尚未安装,安装 npm(我们将假设已安装 Docker 和 Node.js):

npm install -g dockunit

现在我们可以使用 Dockunit 轻松测试我们的 Node.js 应用程序。这可以通过一个Dockunit.json文件来完成;以下是一个示例,测试了一个使用mocha的 Node.js 0.10.x 和 0.12.0 应用程序:

{ 
  "containers": [ 
    { 
      "prettyName": "Node 0.10.x", 
      "image": "google/nodejs:latest", 
      "beforeScripts": [ 
        "npm install -g mocha" 
      ], 
      "testCommand": "mocha" 
    }, 
    { 
      "prettyName": "Node 0.12", 
      "image": "tlovett1/nodejs:0.12", 
      "beforeScripts": [ 
        "npm install -g mocha" 
      ], 
      "testCommand": "mocha" 
    } 
  ] 
} 

上面的代码片段显示了一个应用程序如何在 docker 容器内进行单元测试。

自动化部署

自动化的一种方法是使用现成的 PaaS(例如 Cloud Foundry 或 Tutum 等)。PaaS 为开发人员提供了一种简单的方式来部署和管理他们的微服务。它使他们免受采购和配置 IT 资源等问题的困扰。与此同时,配置 PaaS 的系统和网络专业人员可以确保符合最佳实践和公司政策。

自动化部署微服务的另一种方法是开发基本上是自己的 PaaS。一个典型的起点是使用集群解决方案,如 Mesos 或 Kubernetes,结合使用 Docker 等技术。本书的后面部分将介绍像 NGINX 这样的软件应用交付方法,它可以轻松处理缓存、访问控制、API 计量和微服务级别的监控,从而帮助解决这个问题。

将 N 层应用程序解耦为多个镜像

分解应用程序可以提高部署能力和可伸缩性,并简化对新技术的采用。要实现这种抽象级别,应用程序必须与基础设施完全解耦。应用程序容器,如 Docker,提供了一种将应用程序组件与基础设施解耦的方法。在这个级别上,每个应用服务必须是弹性的(即,它可以独立于其他服务进行扩展和缩减)和具有弹性(即,它具有多个实例并且可以在实例故障时继续运行)。应用程序还应该设计成一个服务的故障不会级联到其他服务。

我们已经说了太多,做得太少。让我们来看看我们真正需要知道的东西——如何构建它!我们可以在这里轻松使用我们的cloudconsulted/wordpress镜像来展示我们将其解耦为独立容器的示例:一个用于 WordPress,PHP 和 MySQL。相反,让我们探索其他应用程序,继续展示我们可以使用 Docker 进行应用程序部署的能力和潜力;例如,一个简单的 LEMP 堆栈

构建 N 层 Web 应用程序

LEMP 堆栈(NGINX > MySQL > PHP)

为了简化,我们将把这个 LEMP 堆栈分成两个容器:一个用于 MySQL,另一个用于 NGINX 和 PHP,每个都使用 Ubuntu 基础:

# LEMP stack decoupled as separate docker container s 
FROM ubuntu:14.04 
MAINTAINER John Wooten @CONSULTED <jwooten@cloudconsulted.com> 

RUN apt-get update 
RUN apt-get -y upgrade 

# seed database password 
COPY mysqlpwdseed /root/mysqlpwdseed 
RUN debconf-set-selections /root/mysqlpwdseed 

RUN apt-get -y install mysql-server 

RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf 

RUN /usr/sbin/mysqld & \ 
    sleep 10s &&\ 
    echo "GRANT ALL ON *.* TO admin@'%' IDENTIFIED BY 'secret' WITH GRANT OPTION; FLUSH PRIVILEGES" | mysql -u root --password=secret &&\ 
    echo "create database test" | mysql -u root --password=secret 

# persistence: http://txt.fliglio.com/2013/11/creating-a-mysql-docker-container/ 

EXPOSE 3306 

CMD ["/usr/bin/mysqld_safe"]

第二个容器将安装和存储 NGINX 和 PHP:

# LEMP stack decoupled as separate docker container s 
FROM ubuntu:14.04 
MAINTAINER John Wooten @CONSULTED <jwooten@cloudconsulted.com> 

## install nginx 
RUN apt-get update 
RUN apt-get -y upgrade 
RUN apt-get -y install nginx 
RUN echo "daemon off;" >> /etc/nginx/nginx.conf 
RUN mv /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak 
COPY default /etc/nginx/sites-available/default 

## install PHP 
RUN apt-get -y install php5-fpm php5-mysql 
RUN sed -i s/\;cgi\.fix_pathinfo\s*\=\s*1/cgi.fix_pathinfo\=0/ /etc/php5/fpm/php.ini 

# prepare php test scripts 
RUN echo "<?php phpinfo(); ?>" > /usr/share/nginx/html/info.php 
ADD wall.php /usr/share/nginx/html/wall.php 

# add volumes for debug and file manipulation 
VOLUME ["/var/log/", "/usr/share/nginx/html/"] 

EXPOSE 80 

CMD service php5-fpm start && nginx

将不同层次的应用程序工作起来

从我们的实际生产示例中,我们已经看到了几种不同的方法,可以使不同的应用程序层一起工作。由于讨论使应用程序层在应用程序内部可互操作的方式都取决于应用程序层的部署,我们可以继续无限地讨论如何做到这一点;一个例子引出另一个例子,依此类推。相反,我们将在第六章中更深入地探讨这个领域,使容器工作

总结

容器是现代微服务架构的载体;与微服务和 N 层架构风格结合使用容器不仅提供了一些狂野和富有想象力的优势,而且还提供了可行的生产就绪解决方案。在许多方面,使用容器来实现微服务架构与过去 20 年在 Web 开发中观察到的演变非常相似。这种演变的很大一部分是由于需要更好地利用计算资源和维护日益复杂的基于 Web 的应用程序的需求驱动的。对于现代应用程序开发来说,Docker 是一种确凿而有力的武器。

正如我们所看到的,使用 Docker 容器的微服务架构解决了这两个需求。我们探讨了从开发到测试无缝设计的示例环境,消除了手动和容易出错的资源配置和配置的需求。在这样做的过程中,我们简要介绍了微服务应用程序如何进行测试、自动化部署和管理,但在分布式系统中使用容器远不止微服务。越来越多地,容器正在成为所有分布式系统中的“一等公民”,在接下来的章节中,我们将讨论诸如 Docker Compose 和 Kubernetes 这样的工具对于管理基于容器的计算是至关重要的。

第五章:在容器化应用程序之间移动

在上一章中,我们介绍了使用 Docker 容器部署微服务应用程序架构。在本章中,我们将探讨 Docker 注册表以及如何在公共和私有模式下使用它。我们还将深入探讨在使用公共和私有 Docker 注册表时出现问题时的故障排除。

我们将讨论以下主题:

  • 通过 Docker 注册表重新分发

  • 公共 Docker 注册表

  • 私有 Docker 注册表

  • 确保镜像的完整性-签名镜像

  • Docker Trusted RegistryDTR

  • Docker 通用控制平面

通过 Docker 注册表重新分发

Docker 注册表是服务器端应用程序,允许用户存储和分发 Docker 镜像。默认情况下,公共 Docker 注册表(Docker Hub)可用于托管多个 Docker 镜像,提供免费使用、零维护和自动构建等附加功能。让我们详细看看公共和私有 Docker 注册表。

Docker 公共存储库(Docker Hub)

正如前面解释的那样,Docker Hub 允许个人和组织与内部团队和客户共享 Docker 镜像,而无需维护基于云的公共存储库。它提供了集中的资源镜像发现和管理。它还为开发流水线提供了团队协作和工作流自动化。除了镜像存储库管理之外,Docker Hub 的一些附加功能如下:

  • 自动构建:它帮助在 GitHub 或 Bitbucket 存储库中的代码更改时创建新的镜像

  • WebHooks:这是一个新功能,允许在成功将镜像推送到存储库后触发操作

  • 用户管理:它允许创建工作组来管理组织对镜像存储库的用户访问

可以使用 Docker Hub 登录页面创建帐户以托管 Docker 镜像;每个帐户将与唯一的基于用户的 Docker ID 相关联。可以在不创建 Docker Hub 帐户的情况下执行基本功能,例如从 Docker Hub 进行 Docker 镜像搜索和拉取。可以使用以下命令浏览 Docker Hub 中存在的镜像:

$ docker search centos

它将根据匹配的关键字显示 Docker Hub 中存在的镜像。

也可以使用docker login命令创建 Docker ID。以下命令将提示创建一个 Docker ID,该 ID 将成为用户公共存储库的公共命名空间。它将提示输入用户名,还将提示输入密码电子邮件以完成注册过程:

$ sudo docker login 

Username: username 
Password: 
Email: email@blank.com 
WARNING:login credentials saved in /home/username/.dockercfg. 
Account created. Please use the confirmation link we sent to your e-mail to activate it.

为了注销,可以使用以下命令:

$ docker logout

私有 Docker 注册表

私有 Docker 注册表可以部署在本地组织内;它是 Apache 许可下的开源软件,并且易于部署。

使用私有 Docker 注册表,您有以下优势:

  • 组织可以控制并监视 Docker 图像存储的位置

  • 完整的图像分发流程将由组织拥有

  • 图像存储和分发对于内部开发工作流程以及与其他 DevOps 组件(如 Jenkins)的集成将非常有用

将图像推送到 Docker Hub

我们可以创建一个定制的图像,然后使用标记将其推送到 Docker Hub。让我们创建一个带有小型基于终端的应用程序的简单图像。创建一个包含以下内容的 Dockerfile:

FROM debian:wheezy 
RUN apt-get update && apt-get install -y cowsay fortune 

转到包含 Dockerfile 的目录并执行以下命令来构建图像:

$ docker build -t test/cowsay-dockerfile . 
Sending build context to Docker daemon 2.048 kB 
Sending build context to Docker daemon 
Step 0 : FROM debian:wheezy 
wheezy: Pulling from debian 
048f0abd8cfb: Pull complete 
fbe34672ed6a: Pull complete 
Digest: sha256:50d16f4e4ca7ed24aca211446a2ed1b788ab5e3e3302e7fcc11590039c3ab445 
Status: Downloaded newer image for debian:wheezy 
 ---> fbe34672ed6a 
Step 1 : RUN apt-get update && apt-get install -y cowsay fortune 
 ---> Running in ece42dc9cffe

或者,如下图所示,我们可以首先创建一个容器并对其进行测试,然后创建一个带有标记的Docker 图像,可以轻松地推送到Docker Hub

将图像推送到 Docker Hub

从 Docker 容器创建 Docker 图像并将其推送到公共 Docker Hub 的步骤

我们可以使用以下命令检查图像是否已创建。如您所见,test/cowsay-dockerfile图像已创建:

$ docker images
REPOSITORY                  TAG                 IMAGE ID
CREATED             VIRTUAL SIZE
test/cowsay-dockerfile      latest              c1014a025b02        33
seconds ago      126.9 MB
debian                      wheezy              fbe34672ed6a        2
weeks ago         84.92 MB
vkohli/vca-iot-deployment   latest              35c98aa8a51f        8
months ago        501.3 MB
vkohli/vca-cli              latest              d718bbdc304b        9
months ago        536.6 MB

为了将图像推送到 Docker Hub 帐户,我们将不得不使用图像 ID 对其进行标记,标记为 Docker 标记/Docker ID,方法如下:

$ docker tag c1014a025b02 username/cowsay-dockerfile

由于标记的用户名将与 Docker Hub ID 帐户匹配,因此我们可以轻松地推送图像:

$ sudo docker push username/cowsay-dockerfile 
The push refers to a repository [username/cowsay-dockerfile] (len: 1) 
d94fdd926b02: Image already exists 
accbaf2f09a4: Image successfully pushed 
aa354fc0b2b2: Image successfully pushed 
3a94f42115fb: Image successfully pushed 
7771ee293830: Image successfully pushed 
fa81ed084842: Image successfully pushed 
e04c66a223c4: Image successfully pushed 
7e2c5c55ef2c: Image successfully pushed

将图像推送到 Docker Hub

Docker Hub 的屏幕截图

提示

可以预先检查的故障排除问题之一是,自定义 Docker 镜像上标记的用户名应与 Docker Hub 帐户的用户名匹配,以便成功推送镜像。推送到 Docker Hub 的自定义镜像将公开可用。Docker 免费提供一个私有仓库,应该用于推送私有镜像。Docker 客户端版本 1.5 及更早版本将无法将镜像推送到 Docker Hub 帐户,但仍然可以拉取镜像。只支持 1.6 或更高版本。因此,建议始终保持 Docker 版本最新。

如果向 Docker Hub 推送失败并出现500 内部服务器错误,则问题与 Docker Hub 基础设施有关,重新推送可能有帮助。如果在推送 Docker 镜像时问题仍然存在,则应参考/var/log/docker.log中的 Docker 日志以进行详细调试。

安装私有本地 Docker 注册表

可以使用存在于 Docker Hub 上的镜像部署私有 Docker 注册表。映射到访问私有 Docker 注册表的端口将是5000

$ docker run -p 5000:5000 registry

现在,我们将在前面教程中创建的相同镜像标记为localhost:5000/cowsay-dockerfile,以便可以轻松地将匹配的仓库名称和镜像名称推送到私有 Docker 注册表:

$ docker tag username/cowsay-dockerfile localhost:5000/cowsay-dockerfile

将镜像推送到私有 Docker 注册表:

$ docker push localhost:5000/cowsay-dockerfile

推送是指一个仓库(localhost:5000/cowsay-dockerfile)(长度:1):

Sending image list 
Pushing repository localhost:5000/cowsay-dockerfile (1 tags) 
e118faab2e16: Image successfully pushed 
7e2c5c55ef2c: Image successfully pushed 
e04c66a223c4: Image successfully pushed 
fa81ed084842: Image successfully pushed 
7771ee293830: Image successfully pushed 
3a94f42115fb: Image successfully pushed 
aa354fc0b2b2: Image successfully pushed 
accbaf2f09a4: Image successfully pushed 
d94fdd926b02: Image successfully pushed 
Pushing tag for rev [d94fdd926b02] on {http://localhost:5000/v1/repositories/ cowsay-dockerfile/tags/latest}

可以通过访问浏览器中的链接或使用curl命令来查看镜像 ID,该命令在推送镜像后会出现。

在主机之间移动镜像

将一个镜像从一个注册表移动到另一个注册表需要从互联网上推送和拉取镜像。如果需要将镜像从一个主机移动到另一个主机,那么可以简单地通过docker save命令来实现,而不需要上传和下载镜像。Docker 提供了两种不同的方法来将容器镜像保存为 tar 包:

  • docker export:这将一个容器的运行或暂停状态保存到一个 tar 文件中

  • docker save:这将一个非运行的容器镜像保存到一个文件中

让我们通过以下教程来比较docker exportdocker save命令:

使用 export,从 Docker Hub 拉取一个基本的镜像:

$ docker pull Ubuntu 
latest: Pulling from ubuntu 
dd25ab30afb3: Pull complete 
a83540abf000: Pull complete 
630aff59a5d5: Pull complete 
cdc870605343: Pull complete

在从上述镜像运行 Docker 容器后,让我们创建一个示例文件:

$ docker run -t -i ubuntu /bin/bash 
root@3fa633c2e9e6:/# ls 
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root 
run  sbin  srv  sys  tmp  usr  var 
root@3fa633c2e9e6:/# touch sample 
root@3fa633c2e9e6:/# ls 
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root 
run  sample  sbin  srv  sys  tmp  usr  var

在另一个 shell 中,我们可以看到正在运行的 Docker 容器,然后可以使用以下命令将其导出到 tar 文件中:

$  docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED
         STATUS              PORTS               NAMES
3fa633c2e9e6        ubuntu              "/bin/bash"         45 seconds
ago      Up 44 seconds                           prickly_sammet
$ docker export prickly_sammet | gzip > ubuntu.tar.gz

然后可以将 tar 文件导出到另一台机器,然后使用以下命令导入:

$ gunzip -c ubuntu.tar.gz | docker import - ubuntu-sample 
4411d1d3001702b2304d5ebf87f122ef80b463fd6287f3de4e631c50efa01369

在另一台机器上从 Ubuntu-sample 图像运行容器后,我们可以发现示例文件完好无损。

$ docker images
REPOSITORY                   TAG                 IMAGE ID  CREATED
IRTUAL SIZE
ubuntu-sample                    latest               4411d1d30017      20 seconds
go    108.8 MB
$ docker run -i -t ubuntu-sample /bin/bash
root@7fa063bcc0f4:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root run  sample
bin  srv  sys  tmp  usr  var

使用 save 命令,以便在运行 Docker 容器的情况下传输图像,我们可以使用docker save命令将图像转换为 tar 文件:

$ docker save ubuntu | gzip > ubuntu-bundle.tar.gz

ubuntu-bundle.tar.gz文件现在可以使用docker load命令在另一台机器上提取并使用:

$ gunzip -c ubuntu-bundle.tar.gz | docker load

在另一台机器上从ubuntu-bundle图像运行容器,我们会发现示例文件不存在,因为docker load命令将存储图像而不会有任何投诉:

$ docker run -i -t ubuntu /bin/bash 
root@9cdb362f7561:/# ls 
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root 
run  sbin  srv  sys  tmp  usr  var 
root@9cdb362f7561:/#

前面的例子都展示了导出和保存命令之间的区别,以及它们在不使用 Docker 注册表的情况下在本地主机之间传输图像的用法。

确保图像的完整性-签名图像

从 Docker 版本 1.8 开始,包含的功能是 Docker 容器信任,它将The Update FrameworkTUF)集成到 Docker 中,使用开源工具 Notary 提供对任何内容或数据的信任。它允许验证发布者-Docker 引擎使用发布者密钥来验证,并且用户即将运行的图像确实是发布者创建的;它没有被篡改并且是最新的。因此,这是一个允许验证图像发布者的选择性功能。 Docker 中央命令-pushpullbuildcreaterun-将对具有内容签名或显式内容哈希的图像进行操作。图像在推送到存储库之前由内容发布者使用私钥进行签名。当用户第一次与图像交互时,与发布者建立了信任,然后所有后续交互只需要来自同一发布者的有效签名。该模型类似于我们熟悉的 SSH 的第一个模型。 Docker 内容信任使用两个密钥-离线密钥标记密钥-当发布者推送图像时,它们在第一次生成。每个存储库都有自己的标记密钥。当用户第一次运行docker pull命令时,使用离线密钥建立了对存储库的信任:

  • 离线密钥:它是您存储库的信任根源;不同的存储库使用相同的离线密钥。由于具有针对某些攻击类别的优势,应将此密钥保持离线。基本上,在创建新存储库时需要此密钥。

  • 标记密钥:为发布者拥有的每个新存储库生成。它可以导出并与需要为特定存储库签署内容的人共享。

以下是按照信任密钥结构提供的保护列表:

  • 防止图像伪造:Docker 内容信任可防止中间人攻击。如果注册表遭到破坏,恶意攻击者无法篡改内容并向用户提供,因为每次运行命令都会失败,显示无法验证内容的消息。

  • 防止重放攻击:在重放攻击的情况下,攻击者使用先前的有效负载来欺骗系统。Docker 内容信任在发布图像时使用时间戳密钥,从而提供对重放攻击的保护,并确保用户接收到最新的内容。

  • 防止密钥被破坏:由于标记密钥的在线特性,可能会遭到破坏,并且每次将新内容推送到存储库时都需要它。Docker 内容信任允许发布者透明地旋转受损的密钥,以便用户有效地将其从系统中删除。

Docker 内容信任是通过将 Notary 集成到 Docker 引擎中实现的。任何希望数字签名和验证任意内容集合的人都可以下载并实施 Notary。基本上,这是用于在分布式不安全网络上安全发布和验证内容的实用程序。在以下序列图中,我们可以看到 Notary 服务器用于验证元数据文件及其与 Docker 客户端的集成的流程。受信任的集合将存储在 Notary 服务器中,一旦 Docker 客户端具有命名哈希(标记)的受信任列表,它就可以利用客户端到守护程序的 Docker 远程 API。一旦拉取成功,我们就可以信任注册表拉取中的所有内容和层。

确保图像的完整性-签名图像

Docker 受信任运行的序列图

在内部,Notary 使用 TUF,这是一个用于软件分发和更新的安全通用设计,通常容易受到攻击。TUF 通过提供一个全面的、灵活的安全框架来解决这个普遍的问题,开发人员可以将其与软件更新系统集成。通常,软件更新系统是在客户端系统上运行的应用程序,用于获取和安装软件。

让我们开始安装 Notary;在 Ubuntu 16.04 上,可以直接使用以下命令安装 Notary:

$ sudo apt install notary 
Reading package lists... Done 
Building dependency tree        
Reading state information... Done 

The following NEW packages will be installed: 
  Notary 
upgraded, 1 newly installed, 0 to remove and 83 not upgraded. 
Need to get 4,894 kB of archives. 
After this operation, 22.9 MB of additional disk space will be used. 
...

否则,该项目可以从 GitHub 下载并手动构建和安装;构建该项目需要安装 Docker Compose:

$ git clone https://github.com/docker/notary.git 
Cloning into 'notary'... 
remote: Counting objects: 15827, done. 
remote: Compressing objects: 100% (15/15), done. 

$ docker-compose build 
mysql uses an image, skipping 
Building signer 
Step 1 : FROM golang:1.6.1-alpine 

  $ docker-compose up -d 
$ mkdir -p ~/.notary && cp cmd/notary/config.json cmd/notary/root-ca.crt ~/.notary

在上述步骤之后,将127.0.0.1 Notary 服务器添加到/etc/hosts中,或者如果使用 Docker 机器,则将$(docker-machine ip)添加到 Notary 服务器。

现在,我们将推送之前创建的docker-cowsay镜像。默认情况下,内容信任是禁用的;可以使用DOCKER_CONTENT_TRUST环境变量来启用它,这将在本教程中稍后完成。目前,操作内容信任的命令如下所示:

  • push

  • build

  • create

  • pull

  • run

我们将使用仓库名称标记该镜像:

$ docker images
REPOSITORY                  TAG                 IMAGE ID
CREATED             VIRTUAL SIZE
test/cowsay-dockerfile      latest              c1014a025b02        33
seconds ago      126.9 MB
debian                      wheezy              fbe34672ed6a        2
weeks ago         84.92 MB
vkohli/vca-iot-deployment   latest              35c98aa8a51f        8
months ago        501.3 MB
vkohli/vca-cli              latest              d718bbdc304b        9
months ago        536.6 MB
$ docker tag test/cowsay-dockerfile username/cowsay-dockerfile
$ docker push username/cowsay-dockerfile:latest
The push refers to a repository [docker.io/username/cowsay-dockerfile]
bbb8723d16e2: Pushing 24.08 MB/42.01 MB

现在,让我们检查 notary 是否有这个镜像的数据:

$ notary -s https://notary.docker.io -d ~/.docker/trust list docker.io/vkohli/cowsay-dockerfile:latest 
* fatal: no trust data available

正如我们在这里所看到的,没有信任数据可以让我们启用DOCKER_CONTENT_TRUST标志,然后尝试推送该镜像:

$ docker push vkohli/cowsay-dockerfile:latest 
The push refers to a repository [docker.io/vkohli/cowsay-dockerfile] 
bbb8723d16e2: Layer already exists  
5f70bf18a086: Layer already exists  
a25721716984: Layer already exists  
latest: digest: sha256:0fe0af6e0d34217b40aee42bc21766f9841f4dc7a341d2edd5ba0c5d8e45d81c size: 2609 
Signing and pushing trust metadata 
You are about to create a new root signing key passphrase. This passphrase 
will be used to protect the most sensitive key in your signing system. Please 
choose a long, complex passphrase and be careful to keep the password and the 
key file itself secure and backed up. It is highly recommended that you use a 
password manager to generate the passphrase and keep it safe. There will be no 
way to recover this key. You can find the key in your config directory. 
Enter passphrase for new root key with ID f94af29:

正如我们在这里所看到的,第一次推送时,它将要求输入密码来签署标记的镜像。

现在,我们将从 Notary 获取先前推送的最新镜像的信任数据:

$ notary -s https://notary.docker.io -d ~/.docker/trust list docker.io/vkohli/cowsay-dockerfile:latest
NAME                                 DIGEST                                SIZE
BYTES)    ROLE
----------------------------------------------------------------------------------
-------------------
latest     0fe0af6e0d34217b40aee42bc21766f9841f4dc7a341d2edd5ba0c5d8e45d81c
1374           targets

借助上面的例子,我们清楚地了解了 Notary 和 Docker 内容信任的工作原理。

Docker Trusted Registry (DTR)

DTR 提供企业级的 Docker 镜像存储,可以在本地以及虚拟私有云中提供安全性并满足监管合规要求。DTR 在 Docker Universal Control Plane (UCP)之上运行,UCP 可以在本地或虚拟私有云中安装,借助它我们可以将 Docker 镜像安全地存储在防火墙后面。

Docker Trusted Registry (DTR)

DTR 在 UCP 节点上运行

DTR 的两个最重要的特性如下:

  • 镜像管理:它允许用户在防火墙后安全存储 Docker 镜像,并且 DTR 可以轻松地作为持续集成和交付过程的一部分,以构建、运行和交付应用程序。Docker 受信任的注册表(DTR)

DTR 的屏幕截图

  • 访问控制和内置安全性:DTR 提供身份验证机制,以添加用户,并集成了轻量级目录访问协议LDAP)和 Active Directory。它还支持基于角色的身份验证RBAC),允许您为每个用户分配访问控制策略。Docker 受信任的注册表(DTR)

DTR 中的用户身份验证选项

Docker 通用控制平面

Docker UCP 是企业级集群管理解决方案,允许您从单个平台管理 Docker 容器。它还允许您管理数千个节点,并可以通过图形用户界面进行管理和监控。

UCP 有两个重要组件:

  • 控制器:管理集群并保留集群配置

  • 节点:可以添加多个节点到集群中以运行容器

可以使用 Mac OS X 或 Windows 系统上的Docker Toolbox进行沙盒安装 UCP。安装包括一个 UCP 控制器和一个或多个主机,这些主机将作为节点添加到 UCP 集群中,使用 Docker Toolbox。

Docker Toolbox 的先决条件是必须在 Mac OS X 和 Windows 系统上安装,使用官方 Docker 网站提供的安装程序。

Docker 通用控制平面

Docker Toolbox 安装

让我们开始部署 Docker UCP:

  1. 安装完成后,启动 Docker Toolbox 终端:Docker 通用控制平面

Docker Quickstart 终端

  1. 使用docker-machine命令和virtualbox创建一个名为node1的虚拟机,该虚拟机将充当 UCP 控制器:
$ docker-machine create -d virtualbox --virtualbox-memory 
        "2000" --virtualbox-disk-size "5000" node1 
        Running pre-create checks... 
        Creating machine... 
        (node1) Copying /Users/vkohli/.docker/machine/cache/
        boot2docker.iso to /Users/vkohli/.docker/machine/
        machines/node1/boot2docker.iso... 
        (node1) Creating VirtualBox VM... 
        (node1) Creating SSH key... 
        (node1) Starting the VM... 
        (node1) Check network to re-create if needed... 
        (node1) Waiting for an IP... 
        Waiting for machine to be running, this may take a few minutes... 
        Detecting operating system of created instance... 
        Waiting for SSH to be available... 
        Detecting the provisioner... 
        Provisioning with boot2docker... 
        Copying certs to the local machine directory... 
        Copying certs to the remote machine... 
        Setting Docker configuration on the remote daemon... 
        Checking connection to Docker... 
        Docker is up and running! 
        To see how to connect your Docker Client to the 
        Docker Engine running on this virtual machine, run: 
        docker-machine env node1

  1. 还要创建一个名为node2的虚拟机,稍后将其配置为 UCP 节点:
        $ docker-machine create -d virtualbox --virtualbox-memory 
        "2000" node2 
        Running pre-create checks... 

        Creating machine... 
        (node2) Copying /Users/vkohli/.docker/machine/cache/boot2docker.iso 
        to /Users/vkohli/.docker/machine/machines/node2/
        boot2docker.iso... 
        (node2) Creating VirtualBox VM... 
        (node2) Creating SSH key... 
        (node2) Starting the VM... 
        (node2) Check network to re-create if needed... 
        (node2) Waiting for an IP... 
        Waiting for machine to be running, this may take a few minutes... 
        Detecting operating system of created instance... 
        Waiting for SSH to be available... 
        Detecting the provisioner... 
        Provisioning with boot2docker... 
        Copying certs to the local machine directory... 
        Copying certs to the remote machine... 
        Setting Docker configuration on the remote daemon... 
        Checking connection to Docker... 
        Docker is up and running! 
        To see how to connect your Docker Client to the 
        Docker Engine running on this virtual machine, 
        run: docker-machine env node2

  1. node1配置为 UCP 控制器,负责提供 UCP 应用程序并运行管理 Docker 对象安装的过程。在此之前,设置环境以将node1配置为 UCP 控制器:
        $ docker-machine env node1
        export DOCKER_TLS_VERIFY="1"
        export DOCKER_HOST="tcp://192.168.99.100:2376"
        export DOCKER_CERT_PATH="/Users/vkohli/.docker/machine/machines/node1"
        export DOCKER_MACHINE_NAME="node1"
        # Run this command to configure your shell:
        # eval $(docker-machine env node1)
        $ eval $(docker-machine env node1)
        $ docker-machine ls
NAME    ACTIVE   DRIVER       STATE    URL            SWARM
        DOCKER  ERRORS
node1   *        virtualbox   Running  tcp://192.168.99.100:2376
        1.11.1  
        node2   -        virtualbox   Running  tcp://192.168.99.101:2376                   v1.11.1  

  1. 在将node1设置为 UCP 控制器时,它将要求输入 UCP 管理员帐户的密码,并且还将要求输入其他别名,可以使用 enter 命令添加或跳过:
$ docker run --rm -it -v /var/run/docker.sock:/var/run
        /docker.sock --name ucp docker/ucp install -i --swarm-port 
        3376 --host-address $(docker-machine ip node1) 

        Unable to find image 'docker/ucp:latest' locally 
        latest: Pulling from docker/ucp 
        ... 
        Please choose your initial UCP admin password:  
        Confirm your initial password:  
        INFO[0023] Pulling required images... (this may take a while)  
        WARN[0646] None of the hostnames we'll be using in the UCP 
        certificates [node1 127.0.0.1 172.17.0.1 192.168.99.100] 
        contain a domain component.  Your generated certs may fail 
        TLS validation unless you only use one of these shortnames 
        or IPs to connect.  You can use the --san flag to add more aliases  

        You may enter additional aliases (SANs) now or press enter to 
        proceed with the above list. 
        Additional aliases: INFO[0646] Installing UCP with host address 
        192.168.99.100 - If this is incorrect, please specify an 
        alternative address with the '--host-address' flag  
        INFO[0000] Checking that required ports are available and accessible  

        INFO[0002] Generating UCP Cluster Root CA                
        INFO[0039] Generating UCP Client Root CA                 
        INFO[0043] Deploying UCP Containers                      
        INFO[0052] New configuration established.  Signalling the daemon
        to load it...  
        INFO[0053] Successfully delivered signal to daemon       
        INFO[0053] UCP instance ID:            
        KLIE:IHVL:PIDW:ZMVJ:Z4AC:JWEX:RZL5:U56Y:GRMM:FAOI:PPV7:5TZZ  
        INFO[0053] UCP Server SSL: SHA-256       
        Fingerprint=17:39:13:4A:B0:D9:E8:CC:31:AD:65:5D:
        52:1F:ED:72:F0:81:51:CF:07:74:85:F3:4A:66:F1:C0:A1:CC:7E:C6  
        INFO[0053] Login as "admin"/(your admin password) to UCP at         
        https://192.168.99.100:443

  1. UCP 控制台可以使用安装结束时提供的 URL 访问;使用admin作为用户名和之前安装时设置的密码登录。Docker Universal Control Plane

Docker UCP 许可证页面

  1. 登录后,可以添加或跳过试用许可证。可以通过在 Docker 网站上的 UCP 仪表板上的链接下载试用许可证。UCP 控制台具有多个选项,如列出应用程序、容器和节点:Docker Universal Control Plane

Docker UCP 管理仪表板

  1. 首先通过设置环境将 UCP 的node2加入控制器:
        $ docker-machine env node2 
        export DOCKER_TLS_VERIFY="1" 
        export DOCKER_HOST="tcp://192.168.99.102:2376" 
        export DOCKER_CERT_PATH="/Users/vkohli/.docker/machine/machines/node2" 
        export DOCKER_MACHINE_NAME="node2" 
        # Run this command to configure your shell:  
        # eval $(docker-machine env node2) 
        $ eval $(docker-machine env node2)

  1. 使用以下命令将节点添加到 UCP 控制器。将要求输入 UCP 控制器 URL、用户名和密码,如图所示:
$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock
         --name ucp docker/ucp join -i --host-address 
        $(docker-machine ip node2) 

        Unable to find image 'docker/ucp:latest' locally 
        latest: Pulling from docker/ucp 
        ... 

        Please enter the URL to your UCP server: https://192.168.99.101:443 
        UCP server https://192.168.99.101:443 
        CA Subject: UCP Client Root CA 
        Serial Number: 4c826182c994a42f 
        SHA-256 Fingerprint=F3:15:5C:DF:D9:78:61:5B:DF:5F:39:1C:D6:
        CF:93:E4:3E:78:58:AC:43:B9:CE:53:43:76:50:
        00:F8:D7:22:37 
        Do you want to trust this server and proceed with the join? 
        (y/n): y 
        Please enter your UCP Admin username: admin 
        Please enter your UCP Admin password:  
        INFO[0028] Pulling required images... (this may take a while)  
        WARN[0176] None of the hostnames we'll be using in the UCP 
        certificates [node2 127.0.0.1 172.17.0.1 192.168.99.102] 
        contain a domain component.  Your generated certs may fail 
        TLS validation unless you only use one of these shortnames 
        or IPs to connect.  You can use the --san flag to add more aliases  

        You may enter additional aliases (SANs) now or press enter 
        to proceed with the above list. 
        Additional aliases:  
        INFO[0000] This engine will join UCP and advertise itself
        with host address 192.168.99.102 - If this is incorrect, 
        please specify an alternative address with the '--host-address' flag  
        INFO[0000] Verifying your system is compatible with UCP  
        INFO[0007] Starting local swarm containers               
        INFO[0007] New configuration established.  Signalling the 
        daemon to load it...  
        INFO[0008] Successfully delivered signal to daemon

  1. UCP 的安装已完成;现在可以通过从 Docker Hub 拉取官方 DTR 镜像来在node2上安装 DTR。为了完成 DTR 的安装,还需要 UCP URL、用户名、密码和证书:
        $ curl -k https://192.168.99.101:443/ca > ucp-ca.pem 

        $ docker run -it --rm docker/dtr install --ucp-url https://
        192.168.99.101:443/ --ucp-node node2 --dtr-load-balancer 
        192.168.99.102 --ucp-username admin --ucp-password 123456 
        --ucp-ca "$(cat ucp-ca.pem)" 

        INFO[0000] Beginning Docker Trusted Registry installation  
        INFO[0000] Connecting to network: node2/dtr-br           
        INFO[0000] Waiting for phase2 container to be known to the 
        Docker daemon  
        INFO[0000] Connecting to network: dtr-ol                 
        ... 

        INFO[0011] Installation is complete                      
        INFO[0011] Replica ID is set to: 7a9b6eb67065            
        INFO[0011] You can use flag '--existing-replica-id 7a9b6eb67065' 
        when joining other replicas to your Docker Trusted Registry Cluster

  1. 安装成功后,DTR 可以在 UCP UI 中列为一个应用程序:Docker Universal Control Plane

Docker UCP 列出所有应用程序

  1. DTR UI 可以使用http://node2 URL 访问。单击新存储库按钮即可创建新存储库:Docker Universal Control Plane

在 DTR 中创建新的私有注册表

  1. 可以从之前创建的安全 DTR 中推送和拉取镜像,并且还可以将存储库设置为私有,以便保护公司内部的容器。Docker Universal Control Plane

在 DTR 中创建新的私有注册表

  1. 可以使用设置选项从菜单中配置 DTR,该选项允许设置 Docker 镜像的域名、TLS 证书和存储后端。Docker Universal Control Plane

DTR 中的设置选项

摘要

在本章中,我们深入探讨了 Docker 注册表。我们从使用 Docker Hub 的 Docker 公共存储库的基本概念开始,并讨论了与更大观众共享容器的用例。Docker 还提供了部署私有 Docker 注册表的选项,我们研究了这一点,并可以用于在组织内部推送、拉取和共享 Docker 容器。然后,我们研究了通过使用 Notary 服务器对 Docker 容器进行签名来标记和确保其完整性,该服务器可以与 Docker Engine 集成。DTR 提供了更强大的解决方案,它在本地以及虚拟私有云中提供企业级 Docker 镜像存储,以提供安全性并满足监管合规要求。它在 Docker UCP 之上运行,如前面详细的安装步骤所示。我希望本章能帮助您解决问题并了解 Docker 注册表的最新趋势。在下一章中,我们将探讨如何通过特权容器和资源共享使容器正常工作。

第六章:使容器工作

在本章中,我们将探索使用特权模式和超级特权模式容器创建 Docker 容器的各种选项。我们还将探索这些模式的各种故障排除问题。

我们将深入研究各种部署管理工具,如ChefPuppetAnsible,它们与 Docker 集成,以便为生产环境部署数千个容器减轻痛苦。

在本章中,我们将涵盖以下主题:

  • 特权容器和超级特权容器

  • 解决使用不同设置选项创建容器时遇到的问题

  • 使 Docker 容器与 Puppet、Ansible 和 Chef 配合工作

  • 使用 Puppet 创建 Docker 容器并部署应用程序

  • 使用 Ansible 管理 Docker 容器

  • 将 Docker 和 Ansible 一起构建

  • 用于 Docker 的 Chef

利用前述管理工具自动化 Docker 容器的部署具有以下优势:

  • 灵活性:它们为您提供了在云实例或您选择的裸机上复制基于 Docker 的应用程序以及 Docker 应用程序所需的环境的灵活性。这有助于管理和测试,以及根据需要提供开发环境。

  • 可审计性:这些工具还提供了审计功能,因为它们提供了隔离,并帮助跟踪任何潜在的漏洞以及在哪个环境中部署了什么类型的容器。

  • 普遍性:它们帮助您管理容器周围的整个环境,即管理容器以及非容器环境,如存储、数据库和容器应用程序周围的网络模型。

特权容器

默认情况下,容器以非特权模式运行,也就是说,我们不能在 Docker 容器内运行 Docker 守护程序。但是,特权 Docker 容器被赋予对所有设备的访问权限。Docker 特权模式允许访问主机上的所有设备,并在App ArmorSELinux中设置系统配置,以允许容器与主机上运行的进程具有相同的访问权限:

特权容器

突出显示的特权容器

特权容器可以使用以下命令启动:

 $
 docker run -it --privileged ubuntu /bin/bash
 root@9ab706a6a95c:/# cd /dev/
 root@9ab706a6a95c:/dev# ls
 agpgart          hdb6                psaux   sg1       tty32  tty7
 atibm            hdb7                ptmx    shm       tty33  tty8
 audio            hdb8                pts     snapshot  tty34  tty9
 beep             hdb9                ram0    sr0       tty35  ttyS0

正如我们所看到的,启动特权模式容器后,我们可以列出连接到主机机器的所有设备。

故障排除提示

Docker 允许您通过支持添加和删除功能来使用非默认配置文件。最好删除容器进程不需要的功能,这样可以使其更安全。

如果您的主机系统上运行的容器面临安全威胁,通常建议检查是否有任何容器以特权模式运行,这可能会通过运行安全威胁应用程序来影响主机系统的安全。

如下例所示,当我们以非特权模式运行容器时,无法更改内核参数,但当我们使用--privileged标志以特权模式运行容器时,它可以轻松更改内核参数,这可能会在主机系统上造成安全漏洞:

 $ docker run -it centos /bin/bash
 [root@7e1b1fa4fb89 /]#  sysctl -w net.ipv4.ip_forward=0
 sysctl: setting key "net.ipv4.ip_forward": Read-only file system
 $ docker run --privileged -it centos /bin/bash
 [root@930aaa93b4e4 /]#  sysctl -a | wc -l
 sysctl: reading key "net.ipv6.conf.all.stable_secret"
 sysctl: reading key "net.ipv6.conf.default.stable_secret"
 sysctl: reading key "net.ipv6.conf.eth0.stable_secret"
 sysctl: reading key "net.ipv6.conf.lo.stable_secret"
 638
 [root@930aaa93b4e4 /]# sysctl -w net.ipv4.ip_forward=0
 net.ipv4.ip_forward = 0

因此,在审核时,您应确保主机系统上运行的所有容器的特权模式未设置为true,除非某些特定应用程序在 Docker 容器中运行时需要:

 $ docker ps -q | xargs docker inspect --format '{{ .Id }}: 
    Privileged={{ 
    .HostConfig.Privileged }}'
 930aaa93b4e44c0f647b53b3e934ce162fbd9ef1fd4ec82b826f55357f6fdf3a: 
    Privileged=true

超级特权容器

这个概念是在 Redhat 的 Project Atomic 博客中介绍的。它提供了使用特殊/特权容器作为代理来控制底层主机的能力。如果我们只发布应用程序代码,我们就有将容器变成黑匣子的风险。将代理打包为具有正确访问权限的 Docker 容器对主机有许多好处。我们可以通过-v /dev:/dev绑定设备,这将帮助在容器内部挂载设备而无需超级特权访问。

使用nsenter技巧,允许您在另一个命名空间中运行命令,也就是说,如果 Docker 有自己的私有挂载命名空间,通过nsenter和正确的模式,我们可以到达主机并在其命名空间中挂载东西。

我们可以以特权模式运行,将整个主机系统挂载到某个路径(/media/host)上:

 $ docker run -it -v /:/media/host --privileged fedora 
nsenter --mount=/media/host/proc/1/ns/mnt --mount /dev/xvdf /home/mic

然后我们可以在容器内部使用nsenter--mount告诉nsenter查看/media/host,然后选择 proc 编号 1 的挂载命名空间。然后,运行常规挂载命令将设备链接到挂载点。如前所述,此功能允许我们挂载主机套接字和设备,例如文件,因此所有这些都可以绑定到容器中供使用:

超级特权容器

作为超级特权容器运行的 nsenter 监视主机

基本上,超级特权容器不仅提供安全隔离、资源和进程隔离,还提供了一种容器的运输机制。允许软件以容器镜像的形式进行运输,也允许我们管理主机操作系统和管理其他容器进程,就像之前解释的那样。

让我们考虑一个例子,目前,我们正在加载应用程序所需的内核模块,这些模块是主机操作系统中不包括的 RPM 软件包,并在应用程序启动时运行它们。这个模块可以通过超级特权容器的帮助进行运输,好处是这个自定义内核模块可以与当前内核非常好地配合,而不是将内核模块作为特权容器的一部分进行运输。在这种方法中,不需要将应用程序作为特权容器运行;它们可以分开运行,内核模块可以作为不同镜像的一部分加载,如下所示:

 $ sudo docker run --rm --privileged foobar /sbin/modprobe PATHTO/foobar-kmod 
$ sudo docker run -d foobar

故障排除 - 大规模的 Docker 容器

在生产环境中工作意味着持续部署。当基础设施是分散的和基于云的时,我们经常在相同的系统上管理相同服务的部署。自动化整个配置和管理这个系统的过程将是一个福音。部署管理工具就是为此目的而设计的。它们提供配方、剧本和模板,简化编排和自动化,提供标准和一致的部署。在接下来的章节中,我们将探讨三种常见的配置自动化工具:Chef、Puppet 和 Ansible,以及它们在大规模部署 Docker 容器时提供的便利。

木偶

Puppet 是一个自动化引擎,执行自动化的管理任务,如更新配置、添加用户和根据用户规范安装软件包。 Puppet 是一个众所周知的开源配置管理工具,可在各种系统上运行,如 Microsoft Windows、Unix 和 Linux。用户可以使用 Puppet 的声明性语言或特定领域语言(Ruby)描述配置。 Puppet 是模型驱动的,使用时需要有限的编程知识。 Puppet 提供了一个用于管理 Docker 容器的模块。 Puppet 和 Docker 集成可以帮助轻松实现复杂的用例。 Puppet 管理文件、软件包和服务,而 Docker 将二进制文件和配置封装在容器中,以便部署为应用程序。

Puppet 的一个潜在用例是,它可以用于为 Jenkins 构建所需的 Docker 容器进行配置,并且可以根据开发人员的需求进行规模化,即在触发构建时。构建过程完成后,二进制文件可以交付给各自的所有者,并且每次构建后都可以销毁容器。在这种用例中,Puppet 扮演着非常重要的角色,因为代码只需使用 Puppet 模板编写一次,然后可以根据需要触发:

Puppet

将 Puppet 和 Jenkins 集成以部署构建 Docker 容器

可以根据garethr-docker GitHub 项目安装用于管理 Docker 的 Puppet 模块。该模块只需要包含一个类:

    include 'docker'

它设置了一个 Docker 托管的存储库,并安装了 Docker 软件包和任何所需的内核扩展。 Docker 守护程序将绑定到unix socket /var/run/docker.sock;根据需求,可以更改此配置:

    class { 'docker':
      tcp_bind        => ['tcp://127.0.0.1:4245','tcp://10.0.0.1:4244'],
      socket_bind     => 'unix:///var/run/docker.sock',
      ip_forward      => true,
      iptables        => true,
      ip_masq         => true,
      bridge          => br0,
      fixed_cidr      => '10.21.1.0/24',
      default_gateway => '10.21.0.1',
    }

如前面的代码所示,Docker 的默认配置可以根据此模块提供的配置进行更改。

图像

可以使用此处详细说明的配置语法来拉取 Docker 镜像。

ubuntu:trusty docker命令的替代方法如下:

 $ docker pull -t="trusty" ubuntu
 docker::image { 'ubuntu':
 image_tag => 'trusty'
    }

甚至配置允许链接到 Dockerfile 以构建镜像。也可以通过订阅外部事件(如 Dockerfile 中的更改)来触发镜像的重建。我们订阅vkohli/Dockerfile文件夹中的更改,如下所示:

    docker::image { 'ubuntu':
      docker_file => '/vkohli/Dockerfile'
      subscribe => File['/vkohli/Dockerfile'],
    }

    file { '/vkohli/Dockerfile':
      ensure => file,
      source => 'puppet:///modules/someModule/Dockerfile',
    }

容器

创建图像后,可以使用多个可选参数启动容器。我们可以使用基本的docker run命令获得类似的功能:

    docker::run { 'sampleapplication':
      image           => 'base',
      command         => '/bin/sh -c "while true; do echo hello world; sleep 1; 
                         done"',
      ports           => ['4445', '4555'],
      expose          => ['4665', '4777'],
      links           => ['mysql:db'],
      net             => 'my-user-def',
      volumes         => ['/var/lib/couchdb', '/var/log'],
      volumes_from    => '6446ea52fbc9',
      memory_limit    => '20m', # (format: '<number><unit>', where unit = b, k, m 
                         or g)
      cpuset          => ['0', '4'],
      username        => 'sample',
      hostname        => 'sample.com',
      dns             => ['8.8.8.8', '8.8.4.4'],
      restart_service => true,
      privileged      => false,
      pull_on_start   => false,
      before_stop     => 'echo "The sample application completed"',
      after           => [ 'container_b', 'mysql' ],
      depends         => [ 'container_a', 'postgres' ],
      extra_parameters => [ '--restart=always' ],
    }

如下所示,我们还可以传递一些更多的参数,例如以下内容:

  • pull_on_start:在启动图像之前,每次都会重新拉取它

  • before_stop:在停止容器之前将执行所述命令

  • extra_parameters:传递给docker run命令所需的附加数组参数,例如--restart=always

  • after:此选项允许表达需要首先启动的容器

可以设置的其他参数包括portsexposeenv_filesvolumes。可以传递单个值或值数组。

网络

最新的 Docker 版本已经官方支持网络:该模块现在公开了一种类型,Docker 网络,可以用来管理它们:

    docker_network { 'sample-net':
      ensure   => present,
      driver   => 'overlay',
      subnet   => '192.168.1.0/24',
      gateway  => '192.168.1.1',
      ip_range => '192.168.1.4/32',
    }

正如前面的代码所示,可以创建一个新的覆盖网络sample-net,并配置 Docker 守护程序以使用它。

Docker compose

Compose 是一个用于运行多个 Docker 容器应用程序的工具。使用 compose 文件,我们可以配置应用程序的服务并启动它们。提供了docker_compose模块类型,允许 Puppet 轻松运行 compose 应用程序。

还可以添加一个 compose 文件,例如运行四个容器的缩放规则,如下面的代码片段所示。我们还可以提供网络和其他配置所需的附加参数:

    docker_compose { '/vkohli/docker-compose.yml':
      ensure  => present,
      scale   => {
        'compose_test' => 4,
      },
      options => '--x-networking'
    }

  1. 如果 Puppet 程序未安装在您的计算机上,可以按以下方式进行安装:
 $ puppet module install garethr-docker
 The program 'puppet' is currently not installed. On Ubuntu 14.04 the 
        puppet program 
        can be installed as shown below;
 $ apt-get install puppet-common
 Reading package lists... Done
 Building dependency tree
 Reading state information... Done
 ...
 The following extra packages will be installed:
 Unpacking puppet-common (3.4.3-1ubuntu1.1) ...
 Selecting previously unselected package ruby-rgen.
 Preparing to unpack .../ruby-rgen_0.6.6-1_all.deb ...
 ...

  1. 在安装 Puppet 模块之后,可以按照所示安装garethr-docker模块:
 $ puppet module install garethr-docker
 Notice: Preparing to install into /etc/puppet/modules ...
 Notice: Downloading from https://forge.puppetlabs.com ...
 Notice: Installing -- do not interrupt ...
 /etc/puppet/modules
        |__ **garethr-docker (v5.3.0)
 |__ puppetlabs-apt (v2.2.2)
 |__ puppetlabs-stdlib (v4.12.0)
 |__ stahnma-epel (v1.2.2)

  1. 我们将创建一个示例 hello world 应用程序,将使用 Puppet 进行部署:
 $ nano sample.pp 
        include 'docker' 
        docker::image { 'ubuntu': 
          image_tag => 'precise' 
        } 
        docker::run { 'helloworld': 
          image => 'ubuntu', 
          command => '/bin/sh -c "while true; do echo hello world; sleep 1; 
                     done"',  
        }

  1. 创建文件后,我们应用(运行)它:
 $ puppet apply sample.pp
 Warning: Config file /etc/puppet/hiera.yaml not found, using Hiera 
        defaults 
        Warning: Scope(Apt::Source[docker]): $include_src is deprecated and 
        will be removed in the next major release, please use $include => { 
        'src' => false } instead 
        ... 
        Notice: /Stage[main]/Main/Docker::Run[helloworld]/Service[docker-
        helloworld]/ensure: 
        ensure changed 'stopped' to 'running' 
        Notice: Finished catalog run in 0.80 seconds 
        Post installation it can be listed as running container: 
        $ docker ps 
        CONTAINER ID        IMAGE               COMMAND 
        CREATED             STATUS              PORTS               NAMES   
        bd73536c7f64        ubuntu:trusty       "/bin/sh -c 'while tr"   5 
        seconds ago       Up 5 seconds        helloworld

  1. 我们可以将其附加到容器并查看输出:
 $ docker attach bd7
 hello world
 hello world
 hello world
 hello world

如前所示,容器可以部署在多个主机上,并且整个集群可以通过单个 Puppet 配置文件创建。

故障排除提示

如果即使 Puppet apply命令成功运行后,仍无法列出 Docker 镜像,请检查语法和是否在示例文件中放置了正确的镜像名称。

Ansible

Ansible 是一个工作流编排工具,通过一个易于使用的平台提供配置管理、供应和应用程序部署的帮助。Ansible 的一些强大功能如下:

  • 供应:应用程序在不同的环境中开发和部署。可以是裸金属服务器、虚拟机或 Docker 容器,在本地或云上。Ansible 可以通过 Ansible tower 和 playbooks 来简化供应步骤。

  • 配置管理:保持一个通用的配置文件是 Ansible 的主要用例之一,有助于在所需的环境中进行管理和部署。

  • 应用程序部署:Ansible 有助于管理应用程序的整个生命周期,从部署到生产。

  • 持续交付:管理持续交付流水线需要来自各个团队的资源。这不能仅靠简单的平台实现,因此,Ansible playbooks 在部署和管理应用程序的整个生命周期中发挥着重要作用。

  • 安全和合规性:安全性可以作为部署阶段的一个组成部分,通过将各种安全策略作为自动化流程的一部分,而不是作为事后的思考过程或稍后合并。

  • 编排:如前所述,Ansible 可以定义管理多个配置的方式,与它们交互,并管理部署脚本的各个部分。

使用 Ansible 自动化 Docker

Ansible 还提供了一种自动化 Docker 容器的方式;它使我们能够将 Docker 容器构建和自动化流程进行通道化和操作化,这个过程目前大多数情况下是手动处理的。Ansible 为编排 Docker 容器提供了以下模块:

  • Docker_service:现有的 Docker compose 文件可以用于通过 Ansible 的 Docker 服务部分在单个 Docker 守护程序或集群上编排容器。Docker compose 文件与 Ansible playbook 具有相同的语法,因为它们都是Yaml文件,语法几乎相同。Ansible 也是用 Python 编写的,Docker 模块使用的是 docker compose 在内部使用的确切 docker-py API 客户端。

这是一个简单的 Docker compose 文件:

        wordpress:
        image: wordpress
        links:
           - db:mysql
        ports:
           - 8080:80
        db:
        image: mariadb
        environment:
              MYSQL_ROOT_PASSWORD: sample

前面的 Docker compose 文件的 Ansible playbook 看起来很相似:

        # tasks file for ansible-dockerized-wordpress
        - name: "Launching DB container"
         docker:
           name: db
           image: mariadb
           env:
             MYSQL_ROOT_PASSWORD: esample
        - name: "Launching wordpress container"
         docker:
           name: wordpress
           image: wordpress
           links:
           - db:mysql
           ports: 
           - 8081:80
  • docker_container:通过提供启动、停止、创建和销毁 Docker 容器的能力,来管理 Docker 容器的生命周期。

  • docker_image:这提供了帮助来管理 Docker 容器的镜像,包括构建、推送、标记和删除 Docker 镜像的命令。

  • docker_login:这将与 Docker hub 或任何 Docker 注册表进行身份验证,并提供从注册表推送和拉取 Docker 镜像的功能。

Ansible Container

Ansible Container 是一个工具,仅使用 Ansible playbooks 来编排和构建 Docker 镜像。可以通过创建 virtualenv 并使用 pip 安装的方式来安装 Ansible Container:

 $ virtualenv ansible-container
 New python executable in /Users/vkohli/ansible-container/bin/python
 Installing setuptools, pip, wheel...done.
 vkohli-m01:~ vkohli$ source ansible-container/bin/activate
 (ansible-container) vkohli-m01:~ vkohli$ pip install ansible-container
 Collecting ansible-container
 Using cached ansible-container-0.1.0.tar.gz
 Collecting docker-compose==1.7.0 (from ansible-container)
 Downloading docker-compose-1.7.0.tar.gz (141kB)
 100% |=============================| 143kB 1.1MB/s
 Collecting docker-py==1.8.0 (from ansible-container)
 ...
 Downloading docker_py-1.8.0-py2.py3-none-any.whl (41kB)
 Collecting cached-property<2,>=1.2.0 (from docker-compose==1.7.0->ansible-
     container)

故障排除提示

如果您在安装 Ansible Container 方面遇到问题,可以通过从 GitHub 下载源代码来进行安装:

 $ git clone https://github.com/ansible/ansible-container.git
 Cloning into 'ansible-container'...
 remote: Counting objects: 2032, done.
 remote: Total 2032 (delta 0), reused 0 (delta 0), pack-reused 2032
 Receiving objects: 100% (2032/2032), 725.29 KiB | 124.00 KiB/s, done.
 Resolving deltas: 100% (1277/1277), done.
 Checking connectivity... done.
 $ cd ansible-container/
 $ ls
 AUTHORS      container        docs     EXAMPLES.md  LICENSE
 README.md         setup.py  update-authors.py
 codecov.yml  CONTRIBUTORS.md  example  INSTALL.md   MANIFEST.in
 requirements.txt  test
 $ sudo python setup.py install
 running install
 running bdist_egg
 running egg_info
 creating ansible_container.egg-info
 writing requirements to ansible_container.egg-info/requires.txt

Ansible Container 有以下命令可供开始使用:

  • ansible_container init:此命令创建一个用于开始的 Ansible 文件目录。
 $ ansible-container init
 Ansible Container initialized.
 $ cd ansible
 $ ls
 container.yml    main.yml    requirements.tx

  • ansible-container build:这将从 Ansible 目录中的 playbooks 创建镜像

  • ansible-container run:这将启动 container.yml 文件中定义的容器

  • ansible-container push:这将根据用户选择将项目的镜像推送到私有或公共仓库

  • ansible-container shipit:这将导出必要的 playbooks 和角色以部署容器到支持的云提供商

如在 GitHub 上的示例中所示,可以在 container.yml 文件中以以下方式定义 Django 服务:

    version: "1"
    services:
      django:
        image: centos:7
        expose:
          - "8080"
        working_dir: '/django'

Chef

Chef 有一些重要的组件,如 cookbook 和 recipes。Cookbook 定义了一个场景并包含了一切;其中第一个是 recipes,它是组织中的一个基本配置元素,使用 Ruby 语言编写。它主要是使用模式定义的资源集合。Cookbook 还包含属性值、文件分发和模板。Chef 允许以可版本控制、可测试和可重复的方式管理 Docker 容器。它为基于容器的开发提供了构建高效工作流和管理发布流水线的能力。Chef delivery 允许您自动化并使用可扩展的工作流来测试、开发和发布 Docker 容器。

Docker cookbook 可在 GitHub 上找到(github.com/chef-cookbooks/docker),并提供自定义资源以在配方中使用。它提供了各种选项,例如以下内容:

  • docker_service:这些是用于 docker_installationdocker_service 管理器的复合资源

  • docker_image: 这个用于从仓库中拉取 Docker 镜像

  • docker_container: 这个处理所有 Docker 容器操作

  • docker_registry: 这个处理所有 Docker 注册操作

  • docker_volume: 这个管理 Docker 容器所有卷相关的操作

以下是一个样本 Chef Docker 配方,可用作参考以使用 Chef 配方部署容器:

    # Pull latest nginx image
    docker_image 'nginx' do
      tag 'latest'
      action :pull
      notifies :redeploy, 'docker_container[sample_nginx]'
    end

    # Run container by exposing the ports
    docker_container 'sample_nginx' do
      repo 'nginx'
      tag 'latest'
      port '80:80'
      host_name 'www'
      domain_name 'computers.biz'
      env 'FOO=bar'
      volumes [ '/some/local/files/:/etc/nginx/conf.d' ]
    end

总结

在本章中,我们首先深入研究了特权容器,它可以访问所有主机设备以及超级特权容器,展示了容器管理在后台运行服务的能力,这可以用于在 Docker 容器中运行服务以管理底层主机。然后,我们研究了 Puppet,一个重要的编排工具,以及它如何借助 garethr-docker GitHub 项目来处理容器管理。我们还研究了 Ansible 和 Chef,它们提供了类似的能力,可以规模化地管理 Docker 容器。在下一章中,我们将探索 Docker 网络堆栈。

第七章:管理 Docker 容器的网络堆栈

在本章中,我们将涵盖以下主题:

  • docker0 桥

  • 故障排除 Docker 桥接配置

  • 配置 DNS

  • 排除容器之间和外部网络之间的通信故障

  • ibnetwork 和容器网络模型

  • 基于覆盖和底层网络的 Docker 网络工具

  • Docker 网络工具的比较

  • 配置OpenvSwitch(OVS)以与 Docker 一起工作

Docker 网络

每个 Docker 容器都有自己的网络堆栈,这是由于 Linux 内核的net命名空间,为每个容器实例化了一个新的net命名空间,外部容器或其他容器无法看到。

Docker 网络由以下网络组件和服务提供支持:

  • Linux 桥接器:内核中内置的 L2/MAC 学习交换机,用于转发

  • Open vSwitch:可编程的高级桥接器,支持隧道

  • 网络地址转换器(NAT):这些是立即实体,用于转换 IP 地址+端口(SNAT,DNAT)

  • IPtables:内核中的策略引擎,用于管理数据包转发、防火墙和 NAT 功能

  • Apparmor/SElinux:可以为每个应用程序定义防火墙策略

可以使用各种网络组件来与 Docker 一起工作,提供了访问和使用基于 Docker 的服务的新方法。因此,我们看到了许多遵循不同网络方法的库。一些著名的库包括 Docker Compose、Weave、Kubernetes、Pipework 和 libnetwork。以下图表描述了 Docker 网络的根本思想:

Docker 网络

Docker 网络模式

docker0 桥

docker0 桥是默认网络的核心。启动 Docker 服务时,在主机上创建一个 Linux 桥接器。容器上的接口与桥接器通信,桥接器代理到外部世界。同一主机上的多个容器可以通过 Linux 桥接器相互通信。

docker0 可以通过--net标志进行配置,通常有四种模式:

  • --net default:在此模式下,默认桥用作容器相互连接的桥

  • --net=none:使用此标志,创建的容器是真正隔离的,无法连接到网络

  • --net=container:$container2:使用此标志,创建的容器与名为$container2的容器共享其网络命名空间

  • --net=host:在此模式下,创建的容器与主机共享其网络命名空间

故障排除 Docker 桥接配置

在本节中,我们将看看容器端口是如何映射到主机端口的,以及我们如何解决连接容器到外部世界的问题。这种映射可以由 Docker 引擎隐式完成,也可以被指定。

如果我们创建两个容器-容器 1容器 2-它们都被分配了来自私有 IP 地址空间的 IP 地址,并且也连接到docker0 桥,如下图所示:

故障排除 Docker 桥接配置

两个容器通过 Docker0 桥进行通信

前述的两个容器将能够相互 ping 通,也能够访问外部世界。对于外部访问,它们的端口将被映射到主机端口。正如前一节中提到的,容器使用网络命名空间。当第一个容器被创建时,为该容器创建了一个新的网络命名空间。

在容器和 Linux 桥之间创建了一个虚拟以太网vEthernetvEth)链接。从容器的eth0端口发送的流量通过 vEth 接口到达桥接,然后进行切换:

# show linux bridges 
$ sudo brctl show 

上述命令的输出将类似于以下内容,其中包括桥接名称和容器上的 vEth 接口:

$ bridge name  bridge            id    STP       enabled interfaces 
docker0        8000.56847afe9799 no    veth44cb727    veth98c3700 

将容器连接到外部世界

主机上的iptables NAT表用于伪装所有外部连接,如下所示:

$ sudo iptables -t nat -L -n 
... 
Chain POSTROUTING (policy ACCEPT) target prot opt 
source destination MASQUERADE all -- 172.17.0.0/16 
!172.17.0.0/16 
... 

从外部世界访问容器

端口映射再次使用主机上的 iptables NAT 选项进行,如下图所示,其中容器 1 的端口映射用于与外部世界通信。我们将在本章的后面部分详细讨论。

从外部世界访问容器

容器 1 的端口映射,以与外部世界通信

Docker 服务器默认在 Linux 内核中创建了一个docker0桥,可以在其他物理或虚拟网络接口之间传递数据包,使它们表现为单个以太网网络:

root@ubuntu:~# ifconfig 
docker0   Link encap:Ethernet  HWaddr 56:84:7a:fe:97:99 
inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0 
inet6 addr: fe80::5484:7aff:fefe:9799/64 Scope:Link 
inet6 addr: fe80::1/64 Scope:Link 
... 
collisions:0 txqueuelen:0 
RX bytes:516868 (516.8 KB)  TX bytes:46460483 (46.4 MB) 
eth0      Link encap:Ethernet  HWaddr 00:0c:29:0d:f4:2c 
inet addr:192.168.186.129  Bcast:192.168.186.255  
    Mask:255.255.255.0 

一旦我们有一个或多个容器正在运行,我们可以通过在主机上运行 brctl 命令并查看输出的接口列来确认 Docker 是否已将它们正确连接到 docker0 桥接。首先,使用以下命令安装桥接实用程序:

$ apt-get install bridge-utils 

这里有一个主机,连接了两个不同的容器:

root@ubuntu:~# brctl show 
bridge name     bridge id           STP enabled   interfaces
docker0         8000.56847afe9799   no            veth21b2e16
                                                  veth7092a45 

Docker 在创建容器时使用 docker0 桥接设置。每当创建新容器时,它会从桥接可用的范围中分配一个新的 IP 地址:

root@ubuntu:~# docker run -t -i --name container1 ubuntu:latest /bin/bash 
root@e54e9312dc04:/# ifconfig 
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:07 
inet addr:172.17.0.7 Bcast:0.0.0.0 Mask:255.255.0.0 
inet6 addr: 2001:db8:1::242:ac11:7/64 Scope:Global 
inet6 addr: fe80::42:acff:fe11:7/64 Scope:Link 
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 
... 
root@e54e9312dc04:/# ip route 
default via 172.17.42.1 dev eth0 
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.7 

注意

默认情况下,Docker 提供了一个名为 vnet docker0 的桥接,其 IP 地址为 172.17.42.1。Docker 容器的 IP 地址在 172.17.0.0/16 范围内。

要更改 Docker 中的默认设置,请修改 /etc/default/docker 文件。

将默认的桥接从 docker0 更改为 br0

# sudo service docker stop 
# sudo ip link set dev docker0 down 
# sudo brctl delbr docker0 
# sudo iptables -t nat -F POSTROUTING 
# echo 'DOCKER_OPTS="-b=br0"' >> /etc/default/docker 
# sudo brctl addbr br0 
# sudo ip addr add 192.168.10.1/24 dev br0 
# sudo ip link set dev br0 up 
# sudo service docker start 

以下命令显示了 Docker 服务的新桥接名称和 IP 地址范围:

root@ubuntu:~# ifconfig 
br0       Link encap:Ethernet  HWaddr ae:b2:dc:ed:e6:af 
inet addr:192.168.10.1  Bcast:0.0.0.0  Mask:255.255.255.0 
inet6 addr: fe80::acb2:dcff:feed:e6af/64 Scope:Link 
UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1 
RX packets:0 errors:0 dropped:0 overruns:0 frame:0 
TX packets:7 errors:0 dropped:0 overruns:0 carrier:0 
collisions:0 txqueuelen:0 
RX bytes:0 (0.0 B)  TX bytes:738 (738.0 B) 
eth0      Link encap:Ethernet  HWaddr 00:0c:29:0d:f4:2c 
inet addr:192.168.186.129  Bcast:192.168.186.255  Mask:255.255.255.0 
inet6 addr: fe80::20c:29ff:fe0d:f42c/64 Scope:Link 
... 

配置 DNS

Docker 为每个容器提供主机名和 DNS 配置,而无需构建自定义镜像。它通过虚拟文件覆盖容器内的 /etc 文件,可以在其中写入新信息。

可以通过在容器内运行 mount 命令来查看。容器在初始创建时会接收与主机机器相同的 /resolv.conf。如果主机的 /resolv.conf 文件被修改,只有在容器重新启动时,容器的 /resolv.conf 文件才会反映这些修改。

在 Docker 中,可以通过两种方式设置 dns 选项:

  • 使用 docker run --dns=<ip-address>

  • 在 Docker 守护程序文件中,添加 DOCKER_OPTS="--dns ip-address"

提示

您还可以使用 --dns-search=<DOMAIN> 指定搜索域。

以下图表显示了在容器中使用 Docker 守护程序文件中的 DOCKER_OPTS 设置配置名称服务器:

配置 DNS

使用 DOCKER_OPTS 来设置 Docker 容器的名称服务器设置

主要的 DNS 文件如下:

/etc/hostname 
/etc/resolv.conf 
/etc/hosts 

以下是添加 DNS 服务器的命令:

# docker run --dns=8.8.8.8 --net="bridge" -t -i  ubuntu:latest /bin/bash 

以下是添加主机名的命令:

#docker run --dns=8.8.8.8 --hostname=docker-vm1  -t -i  ubuntu:latest 
    /bin/bash 

解决容器与外部网络之间的通信问题

只有在将 ip_forward 参数设置为 1 时,数据包才能在容器之间传递。通常情况下,您会将 Docker 服务器保留在默认设置 --ip-forward=true,并且当服务器启动时,Docker 会为您设置 ip_forward1。要检查设置,请使用以下命令:

# cat /proc/sys/net/ipv4/ip_forward 
0 
# echo 1 > /proc/sys/net/ipv4/ip_forward 
# cat /proc/sys/net/ipv4/ip_forward 
1 

通过启用ip-forward,用户可以使容器与外部世界之间的通信成为可能;如果您处于多个桥接设置中,还需要进行容器间通信:

容器和外部网络之间通信的故障排除

ip-forward = true 将所有数据包转发到/从容器到外部网络

Docker 不会删除或修改 Docker 过滤链中的任何现有规则。这允许用户创建规则来限制对容器的访问。Docker 使用 docker0 桥来在单个主机中的所有容器之间进行数据包流动。它在 iptables 的FORWARD链中添加了一个规则(空白接受策略),以便两个容器之间的数据包流动。--icc=false选项将DROP所有数据包。

当 Docker 守护程序配置为--icc=false--iptables=true,并且使用--link=选项调用 Docker 运行时,Docker 服务器将为新容器插入一对 iptables ACCEPT规则,以便连接到其他容器公开的端口-这些端口在其 Dockerfile 的EXPOSE行中提到:

容器和外部网络之间通信的故障排除

ip-forward = false 将所有数据包转发到/从容器到外部网络

默认情况下,Docker 的转发规则允许所有外部 IP。要仅允许特定 IP 或网络访问容器,请在 Docker 过滤链的顶部插入一个否定规则。

例如,您可以限制外部访问,使只有源 IP10.10.10.10可以使用以下命令访问容器:

#iptables -I DOCKER -i ext_if ! -s 10.10.10.10 -j DROP 

注意

参考:

docs.docker.com/v1.5/articles/networking/

docs.docker.com/engine/userguide/networking/

containerops.org/

限制一个容器对另一个容器的 SSH 访问

要限制一个容器对另一个容器的 SSH 访问,请执行以下步骤:

  1. 创建两个容器,c1 和 c2:
# docker run -i -t --name c1 ubuntu:latest /bin/bash 
root@7bc2b6cb1025:/# ifconfig 
eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:05 
inet addr:172.17.0.5 Bcast:0.0.0.0 Mask:255.255.0.0 
inet6 addr: 2001:db8:1::242:ac11:5/64 Scope:Global 
inet6 addr: fe80::42:acff:fe11:5/64 Scope:Link 
... 
# docker run -i -t --name c2 ubuntu:latest /bin/bash 
root@e58a9bf7120b:/# ifconfig
        eth0 Link encap:Ethernet HWaddr 02:42:ac:11:00:06
         inet addr:172.17.0.6 Bcast:0.0.0.0 Mask:255.255.0.0
         inet6 addr: 2001:db8:1::242:ac11:6/64 Scope:Global
         inet6 addr: fe80::42:acff:fe11:6/64 Scope:Link 

  1. 我们可以使用刚刚发现的 IP 地址测试容器之间的连通性。现在让我们使用ping工具来看一下。

  2. 让我们进入另一个容器 c1,并尝试 ping c2:

root@7bc2b6cb1025:/# ping 172.17.0.6
        PING 172.17.0.6 (172.17.0.6) 56(84) bytes of data.
        64 bytes from 172.17.0.6: icmp_seq=1 ttl=64 time=0.139 ms
        64 bytes from 172.17.0.6: icmp_seq=2 ttl=64 time=0.110 ms
        ^C
        --- 172.17.0.6 ping statistics ---
        2 packets transmitted, 2 received, 0% packet loss, time 999ms
        rtt min/avg/max/mdev = 0.110/0.124/0.139/0.018 ms
        root@7bc2b6cb1025:/#
        root@e58a9bf7120b:/# ping 172.17.0.5
        PING 172.17.0.5 (172.17.0.5) 56(84) bytes of data.
        64 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.270 ms
        64 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.107 ms
        ^C
        --- 172.17.0.5 ping statistics ---

        2 packets transmitted, 2 received, 0% packet loss, time 1002ms
        rtt min/avg/max/mdev = 0.107/0.188/0.270/0.082 ms
        root@e58a9bf7120b:/# 

  1. 在两个容器上安装openssh-server
#apt-get install openssh-server 

  1. 在主机上启用 iptables。最初,您将能够从一个容器 SSH 到另一个容器。

  2. 停止 Docker 服务,并在主机机器的default docker文件中添加DOCKER_OPTS="--icc=false --iptables=true"。此选项将启用 iptables 防火墙并在容器之间关闭所有端口。默认情况下,主机上未启用 iptables:

root@ubuntu:~# iptables -L -n
        Chain INPUT (policy ACCEPT)
        target prot opt source destination
        Chain FORWARD (policy ACCEPT)
        target prot opt source destination
        DOCKER all -- 0.0.0.0/0 0.0.0.0/0
        ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
        ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
        DOCKER all -- 0.0.0.0/0 0.0.0.0/0
        ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
        ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
        ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 
#service docker stop 
#vi /etc/default/docker 

  1. Docker Upstart 和 SysVinit 配置文件,自定义 Docker 二进制文件的位置(特别是用于开发测试):
#DOCKER="/usr/local/bin/docker" 

  1. 使用DOCKER_OPTS来修改守护程序的启动选项:
#DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4" 
#DOCKER_OPTS="--icc=false --iptables=true" 

  1. 重启 Docker 服务:
# service docker start 

  1. 检查 iptables:
root@ubuntu:~# iptables -L -n 
Chain INPUT (policy ACCEPT) 
target prot opt source destination 
Chain FORWARD (policy ACCEPT) 
target prot opt source destination 
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED, ESTABLISHED 
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED, ESTABLISHED 
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 
DROP all -- 0.0.0.0/0 0.0.0.0/0 

DROP规则已添加到主机机器的 iptables 中,这会中断容器之间的连接。现在您将无法在容器之间进行 SSH 连接。

链接容器

我们可以使用--link参数来通信或连接传统容器。

  1. 创建将充当服务器的第一个容器-sshserver
root@ubuntu:~# docker run -i -t -p 2222:22 --name sshserver ubuntu bash 
root@9770be5acbab:/# 
Execute the iptables command and you can find a Docker chain rule added. 
#root@ubuntu:~# iptables -L -n 
Chain INPUT (policy ACCEPT) 
target     prot opt source               destination 
Chain FORWARD (policy ACCEPT) 
target     prot opt source               destination 
Chain OUTPUT (policy ACCEPT) 
target     prot opt source               destination 
Chain DOCKER (0 references) 
target     prot opt source               destination 
ACCEPT     tcp  --  0.0.0.0/0            172.17.0.3           tcp dpt:22 

  1. 创建一个充当 SSH 客户端的第二个容器:
root@ubuntu:~# docker run -i -t --name sshclient --link 
        sshserver:sshserver 
        ubuntu bash 
root@979d46c5c6a5:/# 

  1. 我们可以看到 Docker 链规则中添加了更多规则:
root@ubuntu:~# iptables -L -n 
Chain INPUT (policy ACCEPT) 
target     prot opt source               destination 
Chain FORWARD (policy ACCEPT) 
target     prot opt source               destination 
Chain OUTPUT (policy ACCEPT) 
target     prot opt source               destination 
Chain DOCKER (0 references) 
target     prot opt source               destination 
ACCEPT     tcp  --  0.0.0.0/0            172.17.0.3           tcp dpt:22 
ACCEPT     tcp  --  172.17.0.4           172.17.0.3           tcp dpt:22 
ACCEPT     tcp  --  172.17.0.3           172.17.0.4           tcp spt:22 
root@ubuntu:~# 

以下图解释了使用--link标志的容器之间的通信:

链接容器

Docker--link 在容器之间创建私有通道

  1. 您可以使用docker inspect检查您的链接容器:
root@ubuntu:~# docker inspect -f "{{ .HostConfig.Links }}" sshclient 
[/sshserver:/sshclient/sshserver] 

  1. 现在您可以成功地通过 SSH 连接到 SSH 服务器:
**#ssh root@172.17.0.3 -p 22** 

使用--link参数,Docker 在容器之间创建了一个安全通道,无需在容器上外部公开任何端口。

libnetwork 和容器网络模型

libnetwork 是用 Go 实现的,用于连接 Docker 容器。其目标是提供一个容器网络模型CNM),帮助程序员提供网络库的抽象。libnetwork 的长期目标是遵循 Docker 和 Linux 的理念,提供独立工作的模块。libnetwork 的目标是提供容器网络的可组合需求。它还旨在通过以下方式将 Docker Engine 和 libcontainer 中的网络逻辑模块化为单一可重用库:

  • 用 libnetwork 替换 Docker Engine 的网络模块

  • 允许本地和远程驱动程序为容器提供网络

  • 提供一个用于管理和测试 libnetwork 的dnet工具-但是,这仍然是一个正在进行中的工作

注意

参考: github.com/docker/libnetwork/issues/45

libnetwork 实现了 CNM。它规范了为容器提供网络的步骤,同时提供了一个抽象,可用于支持多个网络驱动程序。其端点 API 主要用于管理相应的对象,并对其进行簿记,以提供 CNM 所需的抽象级别。

CNM 对象

CNM 建立在三个主要组件上,如下图所示:

CNM 对象

libnetwork 的网络沙盒模型

注意

参考:www.docker.com

沙盒

沙盒包含容器的网络堆栈配置,包括路由表的管理、容器的接口和 DNS 设置。沙盒的实现可以是 Linux 网络命名空间、FreeBSD 监狱或类似的概念。

一个沙盒可以包含来自多个网络的许多端点。它还表示容器的网络配置,如 IP 地址、MAC 地址和 DNS 条目。

libnetwork 利用特定于操作系统的参数来填充由沙盒表示的网络配置。它提供了一个框架来在多个操作系统中实现沙盒。

Netlink用于管理命名空间中的路由表,目前存在两种沙盒的实现-namespace_linux.goconfigure_linux.go-以唯一标识主机文件系统上的路径。一个沙盒与一个 Docker 容器关联。

以下数据结构显示了沙盒的运行时元素:

    type sandbox struct {
          id            string
           containerID   string
          config        containerConfig
          osSbox        osl.Sandbox
          controller    *controller
          refCnt        int
          endpoints     epHeap
          epPriority    map[string]int
          joinLeaveDone chan struct{}
          dbIndex       uint64
          dbExists      bool
          isStub        bool
          inDelete      bool
          sync.Mutex
    }

一个新的沙盒是从网络控制器实例化的(稍后将详细解释):

    func (c *controller) NewSandbox(containerID string, options ...SandboxOption) 
     (Sandbox, error) {
        .....
    }

端点

一个端点将一个沙盒连接到一个网络,并为容器公开的服务提供与部署在同一网络中的其他容器的连接。它可以是 Open vSwitch 的内部端口或类似的 vEth 对。

一个端点只能属于一个网络,也只能属于一个沙盒。它表示一个服务,并提供各种 API 来创建和管理端点。它具有全局范围,但只附加到一个网络。

一个端点由以下结构指定:

    type endpoint struct { 
       name          string 
       id            string 
       network       *network 
       iface         *endpointInterface 
       joinInfo      *endpointJoinInfo 
       sandboxID     string 
       exposedPorts  []types.TransportPort 
       anonymous     bool 
       generic      map[string]interface{} 
       joinLeaveDone chan struct{} 
       prefAddress   net.IP 
       prefAddressV6 net.IP 
       ipamOptions   map[string]string 
       dbIndex       uint64 
       dbExists      bool 
       sync.Mutex 
    }

一个端点与唯一的 ID 和名称相关联。它附加到一个网络和一个沙盒 ID。它还与 IPv4 和 IPv6 地址空间相关联。每个端点与一个端点接口相关联。

网络

能够直接相互通信的一组端点称为网络。它在同一主机或多个主机之间提供所需的连接,并在创建或更新网络时通知相应的驱动程序。例如,VLAN 或 Linux 桥在集群中具有全局范围。

网络是从网络控制器中控制的,我们将在下一节中讨论。每个网络都有名称、地址空间、ID 和网络类型:

    type network struct { 
       ctrlr        *controller 
       name         string 
       networkType  string 
       id           string 
       ipamType     string 
       addrSpace    string 
       ipamV4Config []*IpamConf 
       ipamV6Config []*IpamConf 
       ipamV4Info   []*IpamInfo 
       ipamV6Info   []*IpamInfo 
       enableIPv6   bool 
       postIPv6     bool 
       epCnt        *endpointCnt 
       generic      options.Generic 
       dbIndex      uint64 
       svcRecords   svcMap 
       dbExists     bool 
       persist      bool 
       stopWatchCh  chan struct{} 
       drvOnce      *sync.Once 
       internal     bool 
       sync.Mutex   
    }

网络控制器

网络控制器对象提供 API 来创建和管理网络对象。它是通过将特定驱动程序绑定到给定网络来绑定到 libnetwork 的入口点,并支持多个活动驱动程序,包括内置和远程驱动程序。网络控制器允许用户将特定驱动程序绑定到给定网络:

    type controller struct { 
       id             string 
       drivers        driverTable 
       ipamDrivers    ipamTable 
       sandboxes      sandboxTable 
       cfg            *config.Config 
       stores         []datastore.DataStore 
       discovery     hostdiscovery.HostDiscovery 
       extKeyListener net.Listener 
       watchCh        chan *endpoint 
       unWatchCh      chan *endpoint 
       svcDb          map[string]svcMap 
       nmap           map[string]*netWatch 
       defOsSbox      osl.Sandbox 
       sboxOnce       sync.Once 
       sync.Mutex 
    }   

每个网络控制器都引用以下内容:

  • 一个或多个数据结构驱动程序表中的驱动程序

  • 一个或多个数据结构中的沙盒

  • 数据存储

  • 一个 ipamTable网络控制器

网络控制器处理 Docker 容器和 Docker 引擎之间的网络

上图显示了网络控制器如何位于 Docker 引擎、容器和它们连接的网络之间。

CNM 属性

以下是 CNM 属性:

  • 选项:这些对终端用户不可见,但是数据的键值对,提供了一种灵活的机制,可以直接从用户传递到驱动程序。只有当键与已知标签匹配时,libnetwork 才会处理选项,结果是选择了一个由通用对象表示的值。

  • 标签:这些是在 UI 中使用--labels选项表示的终端用户可变的选项子集。它们的主要功能是执行特定于驱动程序的操作,并从 UI 传递。

CNM 生命周期

CNM 的使用者通过 CNM 对象及其 API 与其管理的容器进行网络交互;驱动程序向网络控制器注册。

内置驱动程序在 libnetwork 内注册,而远程驱动程序使用插件机制向 libnetwork 注册。

每个驱动程序处理特定的网络类型,如下所述:

  • 使用libnetwork.New() API 创建一个网络控制器对象,以管理网络的分配,并可选择使用特定于驱动程序的选项进行配置。使用控制器的NewNetwork() API 创建网络对象,作为参数添加了nameNetworkType

  • NetworkType参数有助于选择驱动程序并将创建的网络绑定到该驱动程序。对网络的所有操作都将由使用前面的 API 创建的驱动程序处理。

  • Controller.NewNetwork() API 接受一个可选的选项参数,其中包含驱动程序特定的选项和标签,驱动程序可以用于其目的。

  • 调用Network.CreateEndpoint()在给定网络中创建一个新的端点。此 API 还接受可选的选项参数,这些参数随驱动程序而异。

  • CreateEndpoint()在创建网络中的端点时可以选择保留 IPv4/IPv6 地址。驱动程序使用driverapi中定义的InterfaceInfo接口分配这些地址。IPv4/IPv6 地址是完成端点作为服务定义所需的,还有端点暴露的端口。服务端点是应用程序容器正在侦听的网络地址和端口号。

  • Endpoint.Join()用于将容器附加到端点。如果不存在该容器的沙盒,Join操作将创建一个沙盒。驱动程序利用沙盒密钥来标识附加到同一容器的多个端点。

有一个单独的 API 用于创建端点,另一个用于加入端点。

端点表示与容器无关的服务。创建端点时,为容器保留了资源,以便稍后附加到端点。它提供了一致的网络行为。

  • 当容器停止时,将调用Endpoint.Leave()。驱动程序可以清理在Join()调用期间分配的状态。当最后一个引用端点离开网络时,libnetwork 将删除沙盒。

  • 只要端点仍然存在,libnetwork 就会持有 IP 地址。当容器(或任何容器)再次加入时,这些地址将被重用。它确保了在停止和重新启动时重用容器的资源。

  • Endpoint.Delete()从网络中删除一个端点。这将导致删除端点并清理缓存的sandbox.Info

  • Network.Delete() 用于删除网络。如果没有端点连接到网络,则允许删除。

基于覆盖和底层网络的 Docker 网络工具

覆盖是建立在底层网络基础设施(底层)之上的虚拟网络。其目的是实现在物理网络中不可用的网络服务。

网络覆盖大大增加了可以在物理网络之上创建的虚拟子网的数量,从而支持多租户和虚拟化功能。

Docker 中的每个容器都分配了一个用于与其他容器通信的 IP 地址。如果容器需要与外部网络通信,您需要在主机系统中设置网络并将容器的端口暴露或映射到主机。在容器内运行此应用程序时,容器将无法广告其外部 IP 和端口,因为它们无法获取此信息。

解决方案是在所有主机上为每个 Docker 容器分配唯一的 IP,并有一些网络产品在主机之间路由流量。

有不同的项目和工具可帮助处理 Docker 网络,如下所示:

  • Flannel

  • Weave

  • Project Calico

Flannel

Flannel 为每个容器分配一个 IP,可用于容器之间的通信。通过数据包封装,它在主机网络上创建一个虚拟覆盖网络。默认情况下,flannel 为主机提供一个/24子网,Docker 守护程序将从中为容器分配 IP。

Flannel

使用 Flannel 进行容器之间的通信

Flannel 在每个主机上运行一个代理flanneld,负责从预配置的地址空间中分配子网租约。Flannel 使用etcd (github.com/coreos/etcd)存储网络配置、分配的子网和辅助数据(如主机的 IP)。

为了提供封装,Flannel 使用Universal TUN/TAP设备,并使用 UDP 创建覆盖网络以封装 IP 数据包。子网分配是通过etcd完成的,它维护覆盖子网到主机的映射。

Weave

Weave 创建一个虚拟网络,连接部署在主机/虚拟机上的 Docker 容器,并实现它们的自动发现。

Weave

Weave 网络

Weave 可以穿越防火墙,在部分连接的网络中运行。流量可以选择加密,允许主机/虚拟机在不受信任的网络中连接。

Weave 增强了 Docker 现有(单个主机)的网络功能,如 docker0 桥,以便这些功能可以继续被容器使用。

Calico 项目

Calico 项目为连接容器、虚拟机或裸机提供可扩展的网络解决方案。Calico 使用可扩展的 IP 网络原则作为第 3 层方法提供连接。Calico 可以在不使用覆盖或封装的情况下部署。Calico 服务应该作为每个节点上的一个容器部署。它为每个容器提供自己的 IP 地址,并处理所有必要的 IP 路由、安全策略规则和在节点集群中分发路由的工作。

Calico 架构包含四个重要组件,以提供更好的网络解决方案:

  • Felix,Calico 工作进程,是 Calico 网络的核心,主要路由并提供所需的与主机上工作负载之间的连接。它还为传出端点流量提供与内核的接口。

  • BIRD,路由 ic。BIRD,路由分发开源 BGP,交换主机之间的路由信息。BIRD 捡起的内核端点被分发给 BGP 对等体,以提供主机之间的路由。在calico-node容器中运行两个 BIRD 进程,一个用于 IPv4(bird),另一个用于 IPv6(bird6)。

  • confd,一个用于自动生成 BIRD 配置的模板处理过程,监视etcd存储中对 BGP 配置的任何更改,如日志级别和 IPAM 信息。confd还根据来自etcd的数据动态生成 BIRD 配置文件,并在数据应用更新时自动触发。confd在配置文件更改时触发 BIRD 加载新文件。

  • calicoctl是用于配置和启动 Calico 服务的命令行。它甚至允许使用数据存储(etcd)定义和应用安全策略。该工具还提供了通用管理 Calico 配置的简单界面,无论 Calico 是在虚拟机、容器还是裸机上运行,都支持以下命令在calicoctl上。

$ calicoctl 
Override the host:port of the ETCD server by setting the 
         environment 
        variable 
ETCD_AUTHORITY [default: 127.0.0.1:2379] 
Usage: calicoctl <command> [<args>...] 
status            Print current status information 
node              Configure the main calico/node container and 
         establish 
                          Calico 
networking 
container         Configure containers and their addresses 
profile           Configure endpoint profiles 
endpoint          Configure the endpoints assigned to existing 
         containers 
pool              Configure ip-pools 
bgp               Configure global bgp 
ipam              Configure IP address management 
checksystem       Check for incompatibilities on the host 
         system 
diags             Save diagnostic information 
version           Display the version of calicoctl 
config            Configure low-level component configuration 
        See 'calicoctl <command> --help' to read about a specific 
         subcommand. 

根据 Calico 存储库的官方 GitHub 页面(github.com/projectcalico/calico-containers),存在以下 Calico 集成:

  • Calico 作为 Docker 网络插件

  • 没有 Docker 网络的 Calico

  • Calico 与 Kubernetes

  • Calico 与 Mesos

  • Calico 与 Docker SwarmProject Calico

Calico 架构

使用 Docker 引擎 swarm 节点配置覆盖网络

随着 Docker 1.9 的发布,多主机和覆盖网络已成为其主要功能之一。它可以建立私有网络,以连接多个容器。我们将在 swarm 集群中运行的管理器节点上创建覆盖网络,而无需外部键值存储。swarm 网络将使需要该服务的 swarm 节点可用于网络。

当部署使用覆盖网络的服务时,管理器会自动将网络扩展到运行服务任务的节点。多主机网络需要一个用于服务发现的存储,所以现在我们将创建一个 Docker 机器来运行这项服务。

使用 Docker 引擎 swarm 节点配置覆盖网络

跨多个主机的覆盖网络

对于以下部署,我们将使用 Docker 机器应用程序在虚拟化或云平台上创建 Docker 守护程序。对于虚拟化平台,我们将使用 VMware Fusion 作为提供者。

Docker-machine 的安装如下:

$ curl -L https://github.com/docker/machine/releases/download/
    v0.7.0/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/
    docker-machine && \ 
> chmod +x /usr/local/bin/docker-machine 
% Total    % Received % Xferd  Average Speed   Time    Time    Time  Current 
                                     Dload  Upload   Total   Spent   Left  Speed 
100   601    0   601    0     0    266      0 --:--:--  0:00:02 --:--:--   266 
100 38.8M  100 38.8M    0     0  1420k      0  0:00:28  0:00:28 --:--:-- 1989k 
$ docker-machine version 
docker-machine version 0.7.0, build a650a40 

多主机网络需要一个用于服务发现的存储,因此我们将创建一个 Docker 机器来运行该服务,创建新的 Docker 守护程序:

$ docker-machine create \ 
>   -d vmwarefusion \ 
>   swarm-consul 
Running pre-create checks... 
(swarm-consul) Default Boot2Docker ISO is out-of-date, downloading the latest 
    release... 
(swarm-consul) Latest release for github.com/boot2docker/boot2docker is 
    v1.12.1 
(swarm-consul) Downloading 
... 

提示

要查看如何将 Docker 客户端连接到在此虚拟机上运行的 Docker 引擎,请运行docker-machine env swarm-consul

我们将启动 consul 容器进行服务发现:

$(docker-machine config swarm-consul) run \ 
>         -d \ 
>         --restart=always \ 
>         -p "8500:8500" \ 
>         -h "consul" \ 
>         progrium/consul -server -bootstrap 
Unable to find image 'progrium/consul:latest' locally 
latest: Pulling from progrium/consul 
... 
Digest: 
    sha256:8cc8023462905929df9a79ff67ee435a36848ce7a10f18d6d0faba9306b97274 
Status: Downloaded newer image for progrium/consul:latest 
d482c88d6a1ab3792aa4d6a3eb5e304733ff4d622956f40d6c792610ea3ed312 

创建两个 Docker 守护程序来运行 Docker 集群,第一个守护程序是 swarm 节点,将自动运行用于协调集群的 Swarm 容器:

$ docker-machine create \ 
>   -d vmwarefusion \ 
>   --swarm \ 
>   --swarm-master \ 
>   --swarm-discovery="consul://$(docker-machine ip swarm-
     consul):8500" \ 
>   --engine-opt="cluster-store=consul://$(docker-machine ip swarm-
    consul):8500" \ 
>   --engine-opt="cluster-advertise=eth0:2376" \ 
>   swarm-0 
Running pre-create checks... 
Creating machine... 
(swarm-0) Copying 
     /Users/vkohli/.docker/machine/cache/boot2docker.iso to 
    /Users/vkohli/.docker/machine/machines/swarm-0/boot2docker.iso... 
(swarm-0) Creating SSH key... 
(swarm-0) Creating VM... 
... 

Docker 已经启动运行!

提示

要查看如何将 Docker 客户端连接到在此虚拟机上运行的 Docker 引擎,请运行docker-machine env swarm-0

第二个守护程序是 Swarm 的secondary节点,将自动运行一个 Swarm 容器并将状态报告给master节点:

$ docker-machine create \ 
>   -d vmwarefusion \ 
>   --swarm \ 
>   --swarm-discovery="consul://$(docker-machine ip swarm-
     consul):8500" \ 
>   --engine-opt="cluster-store=consul://$(docker-machine ip swarm-
    consul):8500" \ 
>   --engine-opt="cluster-advertise=eth0:2376" \ 
>   swarm-1 
Running pre-create checks... 
Creating machine... 
(swarm-1) Copying 
     /Users/vkohli/.docker/machine/cache/boot2docker.iso to 
    /Users/vkohli/.docker/machine/machines/swarm-1/boot2docker.iso... 
(swarm-1) Creating SSH key... 
(swarm-1) Creating VM... 
... 

Docker 已经启动运行!

提示

要查看如何将 Docker 客户端连接到在此虚拟机上运行的 Docker Engine,请运行docker-machine env swarm-1

Docker 可执行文件将与一个 Docker 守护程序通信。由于我们在一个集群中,我们将通过运行以下命令来确保 Docker 守护程序与集群的通信:

$ eval $(docker-machine env --swarm swarm-0) 

之后,我们将使用覆盖驱动程序创建一个私有的prod网络:

$ docker $(docker-machine config swarm-0) network create --driver 
    overlay prod 

我们将使用--net 参数启动两个虚拟的ubuntu:12.04容器:

$ docker run -d -it --net prod --name dev-vm-1 ubuntu:12.04 
426f39dbcb87b35c977706c3484bee20ae3296ec83100926160a39190451e57a 

在以下代码片段中,我们可以看到这个 Docker 容器有两个网络接口:一个连接到私有覆盖网络,另一个连接到 Docker 桥接口:

$ docker attach 426 
root@426f39dbcb87:/# ip address 
23: eth0@if24: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc 
     noqueue state 
    UP 
link/ether 02:42:0a:00:00:02 brd ff:ff:ff:ff:ff:ff 
inet 10.0.0.2/24 scope global eth0 
valid_lft forever preferred_lft forever 
inet6 fe80::42:aff:fe00:2/64 scope link 
valid_lft forever preferred_lft forever 
25: eth1@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc 
     noqueue state 
    UP 
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff 
inet 172.18.0.2/16 scope global eth1 
valid_lft forever preferred_lft forever 
inet6 fe80::42:acff:fe12:2/64 scope link 
valid_lft forever preferred_lft forever 

另一个容器也将连接到另一个主机上现有的prod网络接口:

$ docker run -d -it --net prod --name dev-vm-7 ubuntu:12.04 
d073f52a7eaacc0e0cb925b65abffd17a588e6178c87183ae5e35b98b36c0c25 
$ docker attach d073 
root@d073f52a7eaa:/# ip address 
26: eth0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc 
     noqueue state 
    UP 
link/ether 02:42:0a:00:00:03 brd ff:ff:ff:ff:ff:ff 
inet 10.0.0.3/24 scope global eth0 
valid_lft forever preferred_lft forever 
inet6 fe80::42:aff:fe00:3/64 scope link 
valid_lft forever preferred_lft forever 
28: eth1@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc 
     noqueue state 
    UP 
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff 
inet 172.18.0.2/16 scope global eth1 
valid_lft forever preferred_lft forever 
inet6 fe80::42:acff:fe12:2/64 scope link 
valid_lft forever preferred_lft forever 
root@d073f52a7eaa:/# 

这是在 Docker Swarm 集群中跨主机配置私有网络的方法。

所有多主机 Docker 网络解决方案的比较

Calico Flannel Weave Docker Overlay N/W
网络模型 第 3 层解决方案 VxLAN 或 UDP VxLAN 或 UDP VxLAN
名称服务
协议支持 TCP,UDP,ICMP 和 ICMPv6 全部 全部 全部
分布式存储
加密通道 TLS NaCI 库

配置 OpenvSwitch(OVS)以与 Docker 一起工作

Open vSwitchOVS)是一个开源的OpenFlow能力虚拟交换机,通常与虚拟化程序一起使用,以在主机内部和跨网络的不同主机之间连接虚拟机。覆盖网络需要使用支持的隧道封装来创建虚拟数据路径,例如 VXLAN 或 GRE。

覆盖数据路径是在 Docker 主机中的隧道端点之间进行配置的,这使得给定提供者段中的所有主机看起来直接连接在一起。

当新容器上线时,前缀将在路由协议中更新,通过隧道端点宣布其位置。当其他 Docker 主机接收到更新时,转发将安装到 OVS 中,以确定主机所在的隧道端点。当主机取消配置时,类似的过程发生,隧道端点 Docker 主机将删除取消配置容器的转发条目:

配置 OpenvSwitch(OVS)以与 Docker 一起工作

通过基于 OVS 的 VXLAN 隧道在多个主机上运行的容器之间的通信

注意

默认情况下,Docker 使用 Linux docker0 桥;但是,在某些情况下,可能需要使用 OVS 而不是 Linux 桥。单个 Linux 桥只能处理 1,024 个端口;这限制了 Docker 的可扩展性,因为我们只能创建 1,024 个容器,每个容器只有一个网络接口。

故障排除 OVS 单主机设置

在单个主机上安装 OVS,创建两个容器,并将它们连接到 OVS 桥:

  1. 安装 OVS:
$ sudo apt-get install openvswitch-switch 

  1. 安装ovs-docker实用程序:
$ cd /usr/bin 
$ wget https://raw.githubusercontent.com/openvswitch/ovs/master 
/utilities/ovs-docker 
$ chmod a+rwx ovs-docker 

故障排除 OVS 单主机设置

单主机 OVS

  1. 创建一个 OVS 桥。

  2. 在这里,我们将添加一个新的 OVS 桥并对其进行配置,以便我们可以将容器连接到不同的网络上:

$ ovs-vsctl add-br ovs-br1 
$ ifconfig ovs-br1 173.16.1.1 netmask 255.255.255.0 up 

  1. 从 OVS 桥添加端口到 Docker 容器。

  2. 创建两个ubuntu Docker 容器:

$ docker run -i-t --name container1 ubuntu /bin/bash 
$ docker run -i-t --name container2 ubuntu /bin/bash 

  1. 将容器连接到 OVS 桥:
# ovs-docker add-port ovs-br1 eth1 container1 --
         ipaddress=173.16.1.2/24 
# ovs-docker add-port ovs-br1 eth1 container2 --
         ipaddress=173.16.1.3/24 

  1. 使用ping命令测试使用 OVS 桥连接的两个容器之间的连接。首先找出它们的 IP 地址:
# docker exec container1 ifconfig 
eth0      Link encap:Ethernet  HWaddr 02:42:ac:10:11:02 
inet addr:172.16.17.2  Bcast:0.0.0.0  Mask:255.255.255.0 
inet6 addr: fe80::42:acff:fe10:1102/64 Scope:Link 
... 
# docker exec container2 ifconfig 
eth0      Link encap:Ethernet  HWaddr 02:42:ac:10:11:03 
inet addr:172.16.17.3  Bcast:0.0.0.0  Mask:255.255.255.0 
inet6 addr: fe80::42:acff:fe10:1103/64 Scope:Link 
... 

  1. 由于我们知道container1container2的 IP 地址,我们可以运行以下命令:
# docker exec container2 ping 172.16.17.2 
PING 172.16.17.2 (172.16.17.2) 56(84) bytes of data. 
64 bytes from 172.16.17.2: icmp_seq=1 ttl=64 time=0.257 ms 
64 bytes from 172.16.17.2: icmp_seq=2 ttl=64 time=0.048 ms 
64 bytes from 172.16.17.2: icmp_seq=3 ttl=64 time=0.052 ms 
# docker exec container1 ping 172.16.17.2 
PING 172.16.17.2 (172.16.17.2) 56(84) bytes of data. 
64 bytes from 172.16.17.2: icmp_seq=1 ttl=64 time=0.060 ms 
64 bytes from 172.16.17.2: icmp_seq=2 ttl=64 time=0.035 ms 
64 bytes from 172.16.17.2: icmp_seq=3 ttl=64 time=0.031 ms 

故障排除 OVS 多主机设置

首先,我们将使用 OVS 在多个主机上连接 Docker 容器:

让我们考虑我们的设置,如下图所示,其中包含两个运行 Ubuntu 14.04 的主机-Host1Host2

  1. 在两台主机上安装 Docker 和 OVS:
# wget -qO- https://get.docker.com/ | sh 
# sudo apt-get install openvswitch-switch 

  1. 安装ovs-docker实用程序:
# cd /usr/bin 
# wget https://raw.githubusercontent.com/openvswitch/ovs
        /master/utilities/ovs-docker 
# chmod a+rwx ovs-docker 

故障排除 OVS 多主机设置

使用 OVS 进行多主机容器通信

  1. 默认情况下,Docker 选择随机网络来运行其容器。它创建一个 docker0 桥,并为其分配一个 IP 地址(172.17.42.1)。因此,Host1Host2的 docker0 桥 IP 地址相同,这使得两个主机中的容器难以通信。为了克服这一点,让我们为网络分配静态 IP 地址(192.168.10.0/24)。

更改默认的 Docker 子网:

  1. Host1上执行以下命令:
$ service docker stop 
$ ip link set dev docker0 down 
$ ip addr del 172.17.42.1/16 dev docker0 
$ ip addr add 192.168.10.1/24 dev docker0 
$ ip link set dev docker0 up 
$ ip addr show docker0 
$ service docker start 

  1. 添加br0 OVS 桥:
$ ovs-vsctl add-br br0 

  1. 创建到另一个主机的隧道:
$ ovs-vsctl add-port br0 gre0 -- set interface gre0 type=gre 
        options:remote_ip=30.30.30.8 

  1. br0桥添加到docker0桥:
$ brctl addif docker0 br0 

  1. 在 Host2 上执行以下命令:
$ service docker stop 
$ iptables -t nat -F POSTROUTING 
$ ip link set dev docker0 down 
$ ip addr del 172.17.42.1/16 dev docker0 
$ ip addr add 192.168.10.2/24 dev docker0 
$ ip link set dev docker0 up 
$ ip addr show docker0 
$ service docker start 

  1. 添加br0 OVS 桥:
$ ip link set br0 up 
$ ovs-vsctl add-br br0 

  1. 创建到另一个主机的隧道并将其附加到:
# br0 bridge  
        $ ovs-vsctl add-port br0 gre0 -- set interface gre0 type=gre 
        options:remote_ip=30.30.30.7 

  1. br0桥添加到docker0桥:
$ brctl addif docker0 br0 

docker0 桥连接到另一个桥-br0。这次,它是一个 OVS 桥,这意味着容器之间的所有流量也通过br0路由。此外,我们需要连接两个主机上的网络,容器正在其中运行。为此目的使用了 GRE 隧道。这个隧道连接到br0 OVS 桥,结果也连接到docker0。在两个主机上执行上述命令后,您应该能够从两个主机上 ping 通docker0桥的地址。

在 Host1 上:

$ ping 192.168.10.2 
PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data. 
64 bytes from 192.168.10.2: icmp_seq=1 ttl=64 time=0.088 ms 
64 bytes from 192.168.10.2: icmp_seq=2 ttl=64 time=0.032 ms 
^C 
--- 192.168.10.2 ping statistics --- 
2 packets transmitted, 2 received, 0% packet loss, time 999ms 
rtt min/avg/max/mdev = 0.032/0.060/0.088/0.028 ms 

在 Host2 上:

$ ping 192.168.10.1 
PING 192.168.10.1 (192.168.10.1) 56(84) bytes of data. 
64 bytes from 192.168.10.1: icmp_seq=1 ttl=64 time=0.088 ms 
64 bytes from 192.168.10.1: icmp_seq=2 ttl=64 time=0.032 ms 
^C 
--- 192.168.10.1 ping statistics --- 
2 packets transmitted, 2 received, 0% packet loss, time 999ms 
rtt min/avg/max/mdev = 0.032/0.060/0.088/0.028 ms 

  1. 在主机上创建容器。

在 Host1 上,使用以下命令:

$ docker run -t -i --name container1 ubuntu:latest /bin/bash 

在 Host2 上,使用以下命令:

$ docker run -t -i --name container2 ubuntu:latest /bin/bash 

现在我们可以从container1 ping container2。这样,我们使用 OVS 在多个主机上连接 Docker 容器。

总结

在本章中,我们学习了 Docker 网络是如何通过 docker0 桥进行连接的,以及它的故障排除问题和配置。我们还研究了 Docker 网络和外部网络之间的通信问题的故障排除。在此之后,我们深入研究了 libnetwork 和 CNM 及其生命周期。然后,我们研究了使用不同的网络选项在多个主机上跨容器进行通信,例如 Weave、OVS、Flannel 和 Docker 的最新 overlay 网络,以及它们配置中涉及的故障排除问题。

我们看到 Weave 创建了一个虚拟网络,OVS 使用了 GRE 隧道技术,Flannel 提供了一个独立的子网,Docker overlay 设置了每个主机以连接多个主机上的容器。之后,我们研究了使用 OVS 进行 Docker 网络配置以及单个主机和多个主机设置的故障排除。

第八章:使用 Kubernetes 管理 Docker 容器

在上一章中,我们学习了 Docker 网络和如何解决网络问题。在本章中,我们将介绍 Kubernetes。

Kubernetes 是一个容器集群管理工具。目前,它支持 Docker 和 Rocket。这是谷歌的一个开源项目,于 2014 年 6 月在 Google I/O 上发布。它支持在各种云提供商上部署,如 GCE、Azure、AWS、vSphere 和裸机。Kubernetes 管理器是精简的、可移植的、可扩展的和自愈的。

在本章中,我们将涵盖以下内容:

  • Kubernetes 简介

  • 在裸机上部署 Kubernetes

  • 在 Minikube 上部署 Kubernetes

  • 在 AWS 和 vSphere 上部署 Kubernetes

  • 部署一个 pod

  • 在生产环境中部署 Kubernetes

  • 调试 Kubernetes 问题

Kubernetes 有各种重要组件,如下:

  • 节点:这是 Kubernetes 集群的一部分的物理或虚拟机,运行 Kubernetes 和 Docker 服务,可以在其上调度 pod。

  • 主节点:这个节点维护 Kubernetes 服务器运行时的运行状态。这是所有客户端调用配置和管理 Kubernetes 组件的入口点。

  • Kubectl:这是用于与 Kubernetes 集群交互以提供对 Kubernetes API 的主访问权限的命令行工具。通过它,用户可以部署、删除和列出 pod。

  • Pod:这是 Kubernetes 中最小的调度单元。它是一组共享卷并且没有端口冲突的 Docker 容器集合。可以通过定义一个简单的 JSON 文件来创建它。

  • 复制控制器:这个控制 pod 的生命周期,并确保在任何给定时间运行指定数量的 pod,通过根据需要创建或销毁 pod。

  • 标签:标签用于基于键值对标识和组织 pod 和服务:使用 Kubernetes 管理 Docker 容器

Kubernetes 主/从流程

在裸机上部署 Kubernetes

Kubernetes 可以部署在裸机 Fedora 或 Ubuntu 机器上。甚至 Fedora 和 Ubuntu 虚拟机可以部署在 vSphere、工作站或 VirtualBox 上。在以下教程中,我们将介绍在单个 Fedora 24 机器上部署 Kubernetes,该机器将充当主节点,以及部署k8s pod 的节点:

  1. 启用 Kubernetes 测试 YUM 仓库:
 yum -y install --enablerepo=updates-testing kubernetes

  1. 安装etcdiptables-services
 yum -y install etcd iptables-services

  1. /etcd/hosts中,设置 Fedora 主节点和 Fedora 节点:
 echo "192.168.121.9  fed-master 
        192.168.121.65  fed-node" >> /etc/hosts

  1. 禁用防火墙和iptables-services
 systemctl disable iptables-services firewalld 
        systemctl stop iptables-services firewalld

  1. 编辑/etcd/kubernetes/config文件:
 # Comma separated list of nodes in the etcd cluster
        KUBE_MASTER="--master=http://fed-master:8080"
        # logging to stderr means we get it in the systemd journal
        KUBE_LOGTOSTDERR="--logtostderr=true"
        # journal message level, 0 is debug
        KUBE_LOG_LEVEL="--v=0"
        # Should this cluster be allowed to run privileged docker 
        containers
        KUBE_ALLOW_PRIV="--allow-privileged=false"

  1. 编辑/etc/kubernetes/apiserver文件的内容:
 # The address on the local server to listen to. 
        KUBE_API_ADDRESS="--address=0.0.0.0" 

        # Comma separated list of nodes in the etcd cluster 
        KUBE_ETCD_SERVERS="--etcd-servers=http://127.0.0.1:2379" 

        # Address range to use for services         
        KUBE_SERVICE_ADDRESSES="--service-cluster-ip-
        range=10.254.0.0/16" 

        # Add your own! 
        KUBE_API_ARGS=""

  1. /etc/etcd/etcd.conf文件应该取消注释以下行,以便在端口2379上进行监听,因为 Fedora 24 使用 etcd 2.0:
 ETCD_LISTEN_CLIENT_URLS="http://0.0.0.0:2379"

  1. Kubernetes 节点设置可以在单独的主机上完成,但我们将在当前机器上设置它们,以便在同一台机器上配置 Kubernetes 主节点和节点:

  2. 编辑/etcd/kubernetes/kubelet文件如下:

 ### 
        # Kubernetes kubelet (node) config 

        # The address for the info server to serve on (set to 0.0.0.0 
        or "" for 
        all interfaces) 
        KUBELET_ADDRESS="--address=0.0.0.0" 

        # You may leave this blank to use the actual hostname 
        KUBELET_HOSTNAME="--hostname-override=fed-node" 

        # location of the api-server 
        KUBELET_API_SERVER="--api-servers=http://fed-master:8080" 

        # Add your own! 
        #KUBELET_ARGS=""

  1. 创建一个 shell 脚本在同一台机器上启动所有 Kubernetes 主节点和节点服务:
 $ nano start-k8s.sh 
        for SERVICES in etcd kube-apiserver kube-controller-manager 
        kube-scheduler 
        kube-proxy kubelet docker; do  
            systemctl restart $SERVICES 
            systemctl enable $SERVICES 
            systemctl status $SERVICES  
        done

  1. 在 Kubernetes 机器上创建一个node.json文件来配置它:
        { 
            "apiVersion": "v1", 
            "kind": "Node", 
            "metadata": { 
                "name": "fed-node", 
                "labels":{ "name": "fed-node-label"} 
            }, 
            "spec": { 
                "externalID": "fed-node" 
            } 
        } 

  1. 使用以下命令创建一个节点对象:
 $ kubectl create -f ./node.json 

        $ kubectl get nodes 
        NAME               LABELS                  STATUS 
        fed-node           name=fed-node-label     Unknown

  1. 一段时间后,节点应该准备好部署 pod:
 kubectl get nodes 
        NAME                LABELS                  STATUS 
        fed-node            name=fed-node-label     Ready

故障排除 Kubernetes Fedora 手动设置

如果 kube-apiserver 启动失败,可能是由于服务账户准入控制,需要在允许调度 pod 之前提供服务账户和令牌。它会被控制器自动生成。默认情况下,API 服务器使用 TLS 服务密钥,但由于我们不是通过 HTTPS 发送数据,也没有 TLS 服务器密钥,我们可以提供相同的密钥文件给 API 服务器,以便 API 服务器验证生成的服务账户令牌。

使用以下内容生成密钥并将其添加到k8s集群中:

 openssl genrsa -out /tmp/serviceaccount.key 2048

要启动 API 服务器,在/etc/kubernetes/apiserver文件的末尾添加以下选项:

 KUBE_API_ARGS="--
         service_account_key_file=/tmp/serviceaccount.key"

/etc/kubernetes/kube-controller-manager文件的末尾添加以下选项:

 KUBE_CONTROLLER_MANAGER_ARGS=" -
 service_account_private_key_file
        =/tmp/serviceaccount.key"

使用start_k8s.sh shell 脚本重新启动集群。

使用 Minikube 部署 Kubernetes

Minikube 仍在开发中;它是一个工具,可以方便地在本地运行 Kubernetes,针对底层操作系统进行了优化(MAC/Linux)。它在虚拟机内运行单节点 Kubernetes 集群。Minikube 帮助开发人员学习 Kubernetes,并轻松进行日常开发和测试。

以下设置将涵盖 Mac OS X 上的 Minikube 设置,因为很少有指南可以部署 Kubernetes 在 Mac 上:

  1. 下载 Minikube 二进制文件:
 $ curl -Lo minikube
 https://storage.googleapis.com/minikube/releases/v0.12.2/minikube-darwin-amd64
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
 100 79.7M 100 79.7M 0 0 1857k 0 0:00:43 0:00:43 --:--:-- 1863k

  1. 授予二进制文件执行权限:
 $ chmod +x minikube

  1. 将 Minikube 二进制文件移动到/usr/local/bin,以便将其添加到路径并可以直接在终端上执行:
 $ sudo mv minikube /usr/local/bin

  1. 之后,我们将需要kubectl客户端二进制文件来针对 Mac OS X 运行命令单节点 Kubernetes 集群:
 $ curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.3.0/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/

        https://storage.googleapis.com/kubernetes-release/release/v1.3.0/bin/darwin/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
          % Total % Received % Xferd Average Speed Time Time Time Current
                                     Dload Upload Total Spent Left Speed
        100 53.2M 100 53.2M 0 0 709k 0 0:01:16 0:01:16 --:--:-- 1723k

现在已配置 kubectl 以与集群一起使用。

  1. 设置 Minikube 以在本地部署 VM 并配置 Kubernetes 集群:
 $ minikube start

        Starting local Kubernetes cluster...

        Downloading Minikube ISO

        36.00 MB / 36.00 MB

		[==============================================] 
        100.00% 0s

  1. 我们可以设置 kubectl 以使用 Minikube 上下文,并在需要时进行切换:
 $ kubectl config use-context minikube 
        switched to context "minikube".

  1. 我们将能够列出 Kubernetes 集群的节点:
 $ kubectl get nodes

        NAME       STATUS    AGE
        minikube   Ready     39m

  1. 创建hello-minikube pod 并将其公开为服务:
 $ kubectl run hello-minikube --
          image=gcr.io/google_containers/echoserver:1.4 --port=8080

        deployment "hello-minikube" created

        $ kubectl expose deployment hello-minikube --type=NodePort

        service "hello-minikube" exposed

  1. 我们可以使用以下命令获取hello-minikube pod 的状态:
 $  kubectl get pod
     NAME                           READY   STATUS    RESTARTS   AGE          hello-minikube-3015430129-otr7u   1/1    running       0          36s
        vkohli-m01:~ vkohli$ curl $(minikube service hello-minikube --url)
        CLIENT VALUES:
        client_address=172.17.0.1
        command=GET
        real path=/
        query=nil
        request_version=1.1
        request_uri=http://192.168.99.100:8080/

        SERVER VALUES:
        server_version=nginx: 1.10.0 - lua: 10001

        HEADERS RECEIVED:
        accept=*/*
        host=192.168.99.100:30167
        user-agent=curl/7.43.0

  1. 我们可以使用以下命令打开 Kubernetes 仪表板并查看部署的 pod 的详细信息:
 $ minikube dashboard

        Opening kubernetes dashboard in default browser...

使用 Minikube 部署 Kubernetes

展示 hello-minikube pod 的 Kubernetes UI

在 AWS 上部署 Kubernetes

让我们开始在 AWS 上部署 Kubernetes 集群,可以使用 Kubernetes 代码库中已经存在的配置文件来完成。

  1. 登录到 AWS 控制台(aws.amazon.com/console/

  2. 打开 IAM 控制台(console.aws.amazon.com/iam/home?#home)

  3. 选择 IAM 用户名,选择安全凭据选项卡,然后单击创建访问密钥选项。

  4. 创建密钥后,下载并保存在安全的位置。下载的 CSV 文件将包含访问密钥 ID 和秘密访问密钥,这将用于配置 AWS CLI。

  5. 安装和配置 AWS 命令行界面。在本例中,我们使用以下命令在 Linux 上安装了 AWS CLI:

 $ sudo pip install awscli

  1. 为了配置 AWS-CLI,请使用以下命令:
 $ aws configure
 AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
 AWS Secret Access Key [None]: YYYYYYYYYYYYYYYYYYYYYYYYYYYY
 Default region name [None]: us-east-1
 Default output format [None]: text

  1. 配置 AWS CLI 后,我们将创建一个配置文件,并附加一个具有对 S3 和 EC2 的完全访问权限的角色。
 $ aws iam create-instance-profile --instance-profile-name Kube

  1. 角色可以附加到上述配置文件,该配置文件将具有完全的 EC2 和 S3 访问权限,如下面的屏幕截图所示。可以使用控制台或 AWS CLI 单独创建角色,并使用 JSON 文件定义角色可以具有的权限:
 $ aws iam create-role --role-name Test-Role --assume-role-policy-
          document /root/kubernetes/Test-Role-Trust-Policy.json

在 AWS 上部署 Kubernetes

在 Kubernetes 部署期间在 AWS 中附加策略

  1. 创建角色后,可以使用以下命令将其附加到策略:
 $ aws iam add-role-to-instance-profile --role-name Test-Role --
          instance-profile-name Kube

  1. 脚本使用默认配置文件;我们可以按以下方式更改它:
 $ export AWS_DEFAULT_PROFILE=Kube

  1. Kubernetes 集群可以使用一个命令轻松部署,如下所示;
 $ export KUBERNETES_PROVIDER=aws; wget -q -O - https://get.k8s.io | bash
 Downloading kubernetes release v1.1.1 to /home/vkohli/kubernetes.tar.gz
 --2015-11-22 10:39:18--  https://storage.googleapis.com/kubernetes-
        release/release/v1.1.1/kubernetes.tar.gz
 Resolving storage.googleapis.com (storage.googleapis.com)... 
        216.58.220.48, 2404:6800:4007:805::2010
 Connecting to storage.googleapis.com 
        (storage.googleapis.com)|216.58.220.48|:443... connected.
 HTTP request sent, awaiting response... 200 OK
 Length: 191385739 (183M) [application/x-tar]
 Saving to: 'kubernetes.tar.gz'
 100%[======================================>] 191,385,739 1002KB/s   
        in 3m 7s
 2015-11-22 10:42:25 (1002 KB/s) - 'kubernetes.tar.gz' saved 
        [191385739/191385739]
 Unpacking kubernetes release v1.1.1
 Creating a kubernetes on aws...
 ... Starting cluster using provider: aws
 ... calling verify-prereqs
 ... calling kube-up
 Starting cluster using os distro: vivid
 Uploading to Amazon S3
 Creating kubernetes-staging-e458a611546dc9dc0f2a2ff2322e724a
 make_bucket: s3://kubernetes-staging-e458a611546dc9dc0f2a2ff2322e724a/
 +++ Staging server tars to S3 Storage: kubernetes-staging-
        e458a611546dc9dc0f2a2ff2322e724a/devel
 upload: ../../../tmp/kubernetes.6B8Fmm/s3/kubernetes-salt.tar.gz to 
        s3://kubernetes-staging-e458a611546dc9dc0f2a2ff2322e724a/devel/kubernetes-
        salt.tar.gz
 Completed 1 of 19 part(s) with 1 file(s) remaining

  1. 上述命令将调用kube-up.sh,然后使用config-default.sh脚本调用utils.sh,该脚本包含具有四个节点的k8s集群的基本配置,如下所示:
 ZONE=${KUBE_AWS_ZONE:-us-west-2a}
 MASTER_SIZE=${MASTER_SIZE:-t2.micro}
 MINION_SIZE=${MINION_SIZE:-t2.micro}
 NUM_MINIONS=${NUM_MINIONS:-4}
 AWS_S3_REGION=${AWS_S3_REGION:-us-east-1}

  1. 这些实例是在 Ubuntu 上运行的“t2.micro”。该过程需要五到十分钟,之后主节点和从节点的 IP 地址将被列出,并可用于访问 Kubernetes 集群。

在 vSphere 上部署 Kubernetes

可以使用govc(基于 govmomi 构建的 vSphere CLI)在 vSphere 上安装 Kubernetes:

  1. 在开始设置之前,我们需要在 Linux 机器上安装 golang,可以按以下方式进行:
 $ wget https://storage.googleapis.com/golang/go1.7.3.linux-
 amd64.tar.gz

        $ tar -C /usr/local -xzf go1.7.3.linux-amd64.tar.gz

        $ go

        Go is a tool for managing Go source code.
        Usage:
          go command [arguments]

  1. 设置 go 路径:
 $ export GOPATH=/usr/local/go
 $ export PATH=$PATH:$GOPATH/bin

  1. 下载预构建的 Debian VMDK,该 VMDK 将用于在 vSphere 上创建 Kubernetes 集群:
         $ curl --remote-name-all https://storage.googleapis.com/
        govmomi/vmdk/2016-01-08/kube.vmdk.gz{,.md5}
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  
        Current
 Dload  Upload   Total   Spent    Left  
        Speed
         100  663M  100  663M   0   0  14.4M      0  0:00:45  0:00:45 --:--:-- 
        17.4M
         100    47  100    47   0   0     70      0 --:--:-- --:--:-- --:--:--   
        0
         $ md5sum -c kube.vmdk.gz.md5
         kube.vmdk.gz: OK
         $ gzip -d kube.vmdk.gz

Kubernetes 设置故障排除

我们需要设置适当的环境变量以远程连接到 ESX 服务器以部署 Kubernetes 集群。为了在 vSphere 上进行 Kubernetes 设置,应设置以下环境变量:

 export GOVC_URL='https://[USERNAME]:[PASSWORD]@[ESXI-HOSTNAME-IP]/sdk'
 export GOVC_DATASTORE='[DATASTORE-NAME]'
 export GOVC_DATACENTER='[DATACENTER-NAME]'
 #username & password used to login to the deployed kube VM
 export GOVC_RESOURCE_POOL='*/Resources'
 export GOVC_GUEST_LOGIN='kube:kube'
 export GOVC_INSECURE=true

注意

本教程使用 ESX 和 vSphere 版本 v5.5。

kube.vmdk上传到 ESX 数据存储。VMDK 将存储在由以下命令创建的kube目录中:

 $ govc datastore.import kube.vmdk kube

将 Kubernetes 提供程序设置为 vSphere,同时在 ESX 上部署 Kubernetes 集群。这将包含一个 Kubernetes 主节点和四个 Kubernetes 从节点,这些从节点是从上传到数据存储中的扩展的kube.vmdk派生出来的:

 $ cd kubernetes
 $ KUBERNETES_PROVIDER=vsphere cluster/kube-up.sh

这将显示四个 VM 的 IP 地址列表。如果您目前正在开发 Kubernetes,可以使用此集群部署机制以以下方式测试新代码:

 $ cd kubernetes
 $ make release
 $ KUBERNETES_PROVIDER=vsphere cluster/kube-up.sh

可以使用以下命令关闭集群:

 $ cluster/kube-down.sh

Kubernetes 设置故障排除

在 vSphere 上部署的 Kubernetes 主节点/从节点

Kubernetes pod 部署

现在,在以下示例中,我们将部署两个 NGINX 复制 pod(rc-pod)并通过服务公开它们。要了解 Kubernetes 网络,请参考以下图表以获取更多详细信息。在这里,应用程序可以通过虚拟 IP 地址公开,并且服务会代理请求,负载均衡到 pod 的副本:

Kubernetes pod deployment

使用 OVS 桥的 Kubernetes 网络

  1. 在 Kubernetes 主节点上,创建一个新文件夹:
 $ mkdir nginx_kube_example
 $ cd nginx_kube_example

  1. 在您选择的编辑器中创建 YAML 文件,该文件将用于部署 NGINX pod:
 $ vi nginx_pod.yaml
 apiVersion: v1
 kind: ReplicationController
 metadata:
 name: nginx
 spec:
 replicas: 2
 selector:
 app: nginx
 template:
 metadata:
 name: nginx
 labels:
 app: nginx
 spec:
 containers:
 - name: nginx
 image: nginx
 ports:
 - containerPort: 80

  1. 使用kubectl创建 NGINX pod:
 $ kubectl create -f nginx_pod.yaml

  1. 在前面的 pod 创建过程中,我们创建了两个 NGINX pod 的副本,其详细信息如下所示:
 $ kubectl get pods
 NAME          READY     REASON    RESTARTS   AGE
 nginx-karne   1/1       Running   0          14s
 nginx-mo5ug   1/1       Running   0          14s
 $ kubectl get rc
 CONTROLLER   CONTAINER(S)   IMAGE(S)   SELECTOR    REPLICAS
 nginx        nginx          nginx      app=nginx   2

  1. 可以列出部署的 minion 上的容器如下:
         $ docker ps
         CONTAINER ID        IMAGE                                   COMMAND
        CREATED             STATUS              PORTS               NAMES
         1d3f9cedff1d        nginx:latest                            "nginx -g 
        'daemon of   41 seconds ago      Up 40 seconds
        k8s_nginx.6171169d_nginx-karne_default_5d5bc813-3166-11e5-8256-
        ecf4bb2bbd90_886ddf56
         0b2b03b05a8d        nginx:latest                            "nginx -g 
        'daemon of   41 seconds ago      Up 40 seconds

  1. 使用 YAML 文件部署 NGINX 服务,以便在主机端口82上暴露 NGINX pod:
 $ vi nginx_service.yaml
 apiVersion: v1
 kind: Service
 metadata:
 labels:
 name: nginxservice
 name: nginxservice
 spec:
 ports:
 # The port that this service should serve on.
 - port: 82
 # Label keys and values that must match in order to receive traffic for 
        this service.
 selector:
 app: nginx
 type: LoadBalancer

  1. 使用kubectl创建 NGINX 服务:
 $kubectl create -f nginx_service.yaml
 services/nginxservice

  1. 可以列出 NGINX 服务如下:
         $ kubectl get services
         NAME           LABELS                                   SELECTOR    IP(S)
        PORT(S)
         kubernetes     component=apiserver,provider=kubernetes  <none>      
        192.168.3.1    443/TCP
         nginxservice   name=nginxservice                        app=nginx   
        192.168.3.43   82/TCP

  1. 现在可以通过以下 URL 访问通过服务访问的 NGINX 服务器测试页面:http://192.168.3.43:82

在生产环境中部署 Kubernetes

在本节中,我们将介绍一些可以用于在生产环境中部署 Kubernetes 的重要要点和概念。

  • 暴露 Kubernetes 服务:一旦我们部署了 Kubernetes pod,我们就使用服务来暴露它们。Kubernetes 服务是一个抽象,它定义了一组 pod 和一种将它们作为微服务暴露的策略。服务有自己的 IP 地址,但问题是这个地址只存在于 Kubernetes 集群内,这意味着服务不会暴露到互联网上。可以直接在主机机器端口上暴露服务,但一旦在主机机器上暴露服务,就会出现端口冲突。这也会使 Kubernetes 的优势失效,并且使部署的服务难以扩展:在生产环境中部署 Kubernetes

Kubernetes 服务通过外部负载均衡器暴露

一个解决方案是添加外部负载均衡器,如 HAProxy 或 NGINX。这是为每个 Kubernetes 服务配置一个后端,并将流量代理到各个 pod。类似于 AWS 部署,可以在 VPN 内部部署 Kubernetes 集群,并使用 AWS 外部负载均衡器来暴露每个 Kubernetes 服务:

  • 支持 Kubernetes 中的升级场景:在升级场景中,我们需要实现零停机。Kubernetes 的外部负载均衡器有助于在通过 Kubernetes 部署服务的情况下实现这种功能。我们可以启动一个运行新版本服务的副本集群,旧的集群版本将为实时请求提供服务。一旦新服务准备就绪,负载均衡器可以配置为将负载切换到新版本。通过使用这种方法,我们可以支持企业产品的零运行时升级场景:在生产环境中部署 Kubernetes

Kubernetes 部署中支持的升级场景

  • 使基于 Kubernetes 的应用部署自动化:借助部署工具,我们可以自动化测试和在生产环境中部署 Docker 容器的过程。为此,我们需要构建流水线和部署工具,在成功构建后将 Docker 镜像推送到 Docker Hub 这样的注册表。然后,部署工具将负责部署测试环境并调用测试脚本。在成功测试后,部署工具还可以负责在 Kubernetes 生产环境中部署服务。

Kubernetes 应用部署流水线

  • 了解资源约束:在启动 Kubernetes 集群时了解资源约束,配置每个 pod 的资源请求和 CPU/内存限制。大多数容器在生产环境中崩溃是由于资源不足或内存不足。容器应经过充分测试,并在生产环境中为 pod 分配适当的资源,以成功部署微服务。

  • 监控 Kubernetes 集群:应该通过日志持续监控 Kubernetes 集群。应使用诸如 Graylog、Logcheck 或 Logwatch 等日志工具与 Apache Kafka 这样的消息系统一起收集容器的日志并将其传送到日志工具中。借助 Kafka,可以轻松索引日志,并处理大量流。Kubernetes 副本运行无误。如果任何 pod 崩溃,Kubernetes 服务会重新启动它们,并根据配置始终保持副本数量正常运行。用户想要了解的一个方面是失败背后的真正原因。Kubernetes 指标和应用指标可以发布到诸如 InfluxDB 这样的时间序列存储中,用于跟踪应用程序错误,并测量负载、吞吐量和其他统计数据,以进行失败后分析。

  • Kubernetes 中的持久存储:Kubernetes 具有卷的概念来处理持久数据。在 Kubernetes 的生产部署中,我们希望有持久存储,因为容器在重新启动时会丢失数据。卷由各种实现支持,例如主机机器、NFS 或使用云提供商的卷服务。Kubernetes 还提供了两个 API 来处理持久存储。

  • 持久卷(PV):这是在集群中预配的资源,其行为就像节点是集群资源一样。pod 根据需要从持久卷请求资源(CPU 和内存)。通常由管理员进行预配。

  • 持久卷索赔(PVC):PVC 消耗 PV 资源。这是用户对存储的请求,类似于 pod。pod 可以根据需要请求资源(CPU 和内存)的级别。

调试 Kubernetes 问题

在本节中,我们将讨论一些 Kubernetes 故障排除方面的问题:

  1. 调试 Kubernetes 集群的第一步是列出节点的数量,使用以下命令:
 $ kubetl get nodes

还要验证所有节点是否处于就绪状态。

  1. 查看日志以找出部署的 Kubernetes 集群中的问题
 master:
 var/log/kube-apiserver.log - API Server, responsible for serving the API
        /var/log/kube-scheduler.log - Scheduler, responsible for making scheduling 
    decisions
        /var/log/kube-controller-manager.log - Controller that manages replication 
    controllers
 Worker nodes:
 /var/log/kubelet.log - Kubelet, responsible for running containers on the 
    node
        /var/log/kube-proxy.log - Kube Proxy, responsible for service load 
    balancing

  1. 如果 pod 保持在挂起状态,请使用以下命令:
 $ cluster/kubectl.sh describe pod podname

这将列出事件,并可能描述发生在 pod 上的最后一件事情。

  1. 要查看所有集群事件,请使用以下命令:
 $ cluster/kubectl.sh get events

如果kubectl命令行无法到达apiserver进程,请确保Kubernetes_masterKube_Master_IP已设置。确保apiserver进程在主节点上运行,并检查其日志:

  • 如果您能够创建复制控制器但看不到 pod:如果复制控制器没有创建 pod,请检查控制器是否正在运行,并查看日志。

  • 如果kubectl永远挂起或 pod 处于等待状态:

  • 检查主机是否被分配给了 pod,如果没有,那么它们目前正在为某些任务进行调度。

  • 检查 kubelet 是否指向etcd中 pod 的正确位置,apiserver是否使用相同的名称或 minion 的 IP。

  • 如果出现问题,请检查 Docker 守护程序是否正在运行。还要检查 Docker 日志,并确保防火墙没有阻止从 Docker Hub 获取镜像。

  • apiserver进程报告:

  • 错误同步容器:Get http://:10250/podInfo?podID=foo: dial tcp :10250:连接被拒绝

  • 这意味着 pod 尚未被调度

  • 检查调度器日志,看看它是否正常运行

  • 无法连接到容器

  • 尝试 Telnet 到服务端口或 pod 的 IP 地址的 minion

  • 使用以下命令检查 Docker 中是否创建了容器:

 $ sudo docker ps -a

  • 如果您看不到容器,则问题可能出在 pod 配置、镜像、Docker 或 kubelet 上。如果您看到容器每 10 秒创建一次,则问题可能出在容器的创建或容器的进程失败。

  • X.509 证书已过期或尚未生效。

检查当前时间是否与客户端和服务器上的时间匹配。使用ntpdate进行一次性时钟同步。

总结

在本章中,我们学习了如何借助 Kubernetes 管理 Docker 容器。Kubernetes 在 Docker 编排工具中有不同的视角,其中每个 pod 将获得一个唯一的 IP 地址,并且可以借助服务进行 pod 之间的通信。我们涵盖了许多部署场景,以及在裸机、AWS、vSphere 或使用 Minikube 部署 Kubernetes 时的故障排除问题。我们还研究了有效部署 Kubernetes pods 和调试 Kubernetes 问题。最后一部分介绍了在生产环境中部署 Kubernetes 所需的负载均衡器、Kubernetes 服务、监控工具和持久存储。在下一章中,我们将介绍 Docker 卷以及如何在生产环境中有效使用它们。

第九章:挂载卷行李

本章介绍了数据卷和存储驱动程序的概念,在 Docker 中被广泛用于管理持久或共享数据。我们还将深入研究 Docker 支持的各种存储驱动程序,以及与其相关的基本命令进行管理。Docker 数据卷的三个主要用例如下:

  • 在容器被删除后保持数据持久

  • 在主机和 Docker 容器之间共享数据

  • 用于在 Docker 容器之间共享数据

为了理解 Docker 卷,我们需要了解 Docker 文件系统的工作原理。Docker 镜像存储为一系列只读层。当容器启动时,只读镜像在顶部添加一个读写层。如果需要修改当前文件,则将其从只读层复制到读写层,然后应用更改。读写层中的文件版本隐藏了底层文件,但并未销毁它。因此,当删除 Docker 容器时,重新启动镜像将启动一个带有全新读写层的全新容器,并且所有更改都将丢失。位于只读层之上的读写层的组合称为联合文件系统UFS)。为了持久保存数据并能够与主机和其他容器共享,Docker 提出了卷的概念。基本上,卷是存在于 UFS 之外的目录,并在主机文件系统上表现为普通目录或文件。

Docker 卷的一些重要特性如下:

  • 在创建容器时可以初始化卷

  • 数据卷可以被重用并在其他数据容器之间共享

  • 数据卷即使容器被删除也可以保留数据

  • 数据卷的更改是直接进行的,绕过了 UFS

在本章中,我们将涵盖以下内容:

  • 仅数据容器

  • 托管由共享存储支持的映射卷

  • Docker 存储驱动程序性能

通过理解 Docker 卷来避免故障排除

在本节中,我们将探讨处理数据和 Docker 容器的四种方法,这将帮助我们理解并实现前面提到的 Docker 卷的用例。

默认情况下将数据存储在 Docker 容器内部

在这种情况下,数据只能在 Docker 容器内部可见,而不是来自主机系统。如果容器关闭或 Docker 主机死机,数据将丢失。这种情况主要适用于打包在 Docker 容器中的服务,并且在它们返回时不依赖于持久数据:

$ docker run -it ubuntu:14.04 
root@358b511effb0:/# cd /tmp/ 
root@358b511effb0:/tmp# cat > hello.txt 
hii 
root@358b511effb0:/tmp# ls 
hello.txt 

如前面的例子所示,hello.txt文件只存在于容器内部,一旦容器关闭,它将不会被保存。

默认情况下将数据存储在 Docker 容器内部

存储在 Docker 容器内部的数据

数据专用容器

数据可以存储在 Docker UFS 之外的数据专用容器中。数据将在数据专用容器的挂载命名空间内可见。由于数据持久保存在容器之外,即使容器被删除,数据仍然存在。如果任何其他容器想要连接到这个数据专用容器,只需使用--volumes-from选项来获取容器并将其应用到当前容器。让我们尝试使用数据卷容器:

数据专用容器

使用数据专用容器

创建数据专用容器

$ docker create -v /tmp --name ubuntuvolume Ubuntu:14.04 

在前面的命令中,我们创建了一个 Ubuntu 容器并附加了/tmp。它是基于 Ubuntu 镜像的数据专用容器,并存在于/tmp目录中。如果新的 Ubuntu 容器需要向我们的数据专用容器的/tmp目录写入一些数据,可以通过--volumes-from选项实现。现在,我们在新容器的/tmp目录中写入的任何内容都将保存在 Ubuntu 数据容器的/tmp卷中:

$ docker create -v /tmp --name ubuntuvolume ubuntu:14.04 
d694752455f7351e95d1563ed921257654a1867c467a2813ae25e7d99c067234 

在容器-1 中使用数据卷容器:

$ docker run -t -i --volumes-from ubuntuvolume ubuntu:14.04 /bin/bash 
root@127eba0504cd:/# echo "testing data container" > /tmp/hello 
root@127eba0504cd:/# exit 
exit 

在容器-2 中使用数据卷容器来获取容器-1 共享的数据:

$ docker run -t -i --volumes-from ubuntuvolume ubuntu:14.04 /bin/bash 
root@5dd8152155de:/# cd tmp/ 
root@5dd8152155de:/tmp# ls 
hello 
root@5dd8152155de:/tmp# cat hello 
testing data container 

正如我们所看到的,容器-2 获得了容器-1 在/tmp空间中写入的数据。这些示例演示了数据专用容器的基本用法。

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

这是一个常见的用例,需要在主机和 Docker 容器之间共享文件。在这种情况下,我们不需要创建一个数据专用容器;我们可以简单地运行任何 Docker 镜像的容器,并简单地用主机系统目录的内容覆盖其中一个目录。

让我们考虑一个例子,我们想要从主机系统访问 Docker NGINX 的日志。目前,它们在主机外部不可用,但可以通过简单地将容器内的/var/log/nginx映射到主机系统上的一个目录来实现。在这种情况下,我们将使用来自主机系统的共享卷运行 NGINX 镜像的副本,如下所示:

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

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

在主机系统中创建一个serverlogs目录:

$ mkdir /home/serverlogs 

运行 NGINX 容器,并将/home/serverlogs映射到 Docker 容器内的/var/log/nginx目录:

$ docker run -d -v /home/serverlogs:/var/log/nginx -p 5000:80 nginx 
Unable to find image 'nginx:latest' locally 
latest: Pulling from library/nginx 
5040bd298390: Pull complete 
... 

从主机系统访问http://localhost:5000,之后将生成日志,并且可以在主机系统中的/home/serverlogs目录中访问这些日志,该目录映射到 Docker 容器内的/var/log/nginx,如下所示:

$ cd serverlogs/ 
$ ls 
access.log  error.log 
$ cat access.log  
172.17.42.1 - - [20/Jan/2017:14:57:41 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0" "-" 

由共享存储支持的主机映射卷

Docker 卷插件允许我们挂载共享存储后端。这样做的主要优势是用户在主机故障的情况下不会遭受数据丢失,因为它由共享存储支持。在先前的方法中,如果我们迁移容器,卷不会被迁移。可以借助外部 Docker 卷插件实现这一点,例如FlockerConvy,它们使卷可移植,并有助于轻松迁移带有卷的容器,同时保护数据,因为它不依赖于主机文件系统。

Flocker

Flocker 被广泛用于运行容器化的有状态服务和需要持久存储的应用程序。Docker 提供了卷管理的基本视图,但 Flocker 通过提供卷的耐久性、故障转移和高可用性来增强它。Flocker 可以手动部署到 Docker Swarm 和 compose 中,或者可以借助 CloudFormation 模板在 AWS 上轻松设置,如果备份存储必须在生产设置中使用。

Flocker 可以通过以下步骤轻松部署到 AWS 上:

  1. 登录到您的 AWS 帐户并在 Amazon EC2 中创建一个密钥对。

  2. 从 AWS 的主页中选择CloudFormation

  3. Flocker 云形成堆栈可以使用 AWS S3 存储中的模板启动,链接如下:https://s3.amazonaws.com/installer.downloads.clusterhq.com/flocker-cluster.cloudformation.json

  4. 选择创建堆栈;然后选择第二个选项并指定 Amazon S3 模板 URL:Flocker

  5. 在下一个屏幕上,指定堆栈名称AmazonAccessKeyIDAmazonSecretAccessKeyFlocker

  6. 提供键值对以标记此 Flocker 堆栈,并在必要时提供此堆栈的IAM 角色Flocker

  7. 审查详细信息并启动 Flocker 云形成堆栈:Flocker

  8. 一旦从输出选项卡完成堆栈部署,获取客户端节点和控制节点的 IP 地址。使用在 Flocker 堆栈部署开始时生成的键值对 SSH 进入客户端节点。

设置以下参数:

$ export FLOCKER_CERTS_PATH=/etc/flocker 
$ export FLOCKER_USER=user1 
$ export FLOCKER_CONTROL_SERVICE=<ControlNodeIP> # not ClientNodeIP! 
$ export DOCKER_TLS_VERIFY=1 
$ export DOCKER_HOST=tcp://<ControlNodeIP>:2376 
$ flockerctl status # should list two servers (nodes) running 
$ flockerctl ls # should display no datasets yet 
$ docker info |grep Nodes # should output "Nodes: 2" 

如果 Flocker 的statusls命令成功运行,这意味着 Docker Swarm 和 Flocker 已成功在 AWS 上设置。

Flocker 卷可以轻松设置,并允许您创建一个将超出容器或容器主机生命周期的容器:

$ docker run --volume-driver flocker -v flocker-volume:/cont-dir --name=testing-container 

将创建并挂载外部存储块到我们的主机上,并将容器目录绑定到它。如果容器被删除或主机崩溃,数据仍然受到保护。可以使用相同的命令在第二个主机上启动备用容器,并且我们将能够访问我们的共享存储。前面的教程是为了在 AWS 上为生产用例设置 Flocker,但我们也可以通过 Docker Swarm 设置在本地测试 Flocker。让我们考虑一个使用情况,您有两个 Docker Swarm 节点和一个 Flocker 客户端节点。

在 Flocker 客户端节点

创建一个docker-compose.yml文件,并定义容器redisclusterhq/flask。提供相应的配置 Docker 镜像、名称、端口和数据卷:

$ nano docker-compose.yml 
web: 
  image: clusterhq/flask 
  links: 
   - "redis:redis" 
  ports: 
   - "80:80" 
redis: 
  image: redis:latest 
  ports: 
   - "6379:6379" 
  volumes: ["/data"] 

创建一个名为flocker-deploy.yml的文件,在其中我们将定义将部署在相同节点node-1上的两个容器;暂时将node-2留空作为 Swarm 集群的一部分:

$ nano flocker-deploy.yml 
"version": 1 
"nodes": 
  "node-1": ["web", "redis"] 
  "node-2": [] 

使用前述的.yml文件部署容器;我们只需要运行以下命令即可:

$ flocker-deploy control-service flocker-deploy.yml docker-compose.yml 

集群配置已更新。可能需要一段时间才能生效,特别是如果需要拉取 Docker 镜像。

两个容器都可以在node-1上运行。一旦设置完成,我们可以在http://node-1上访问应用程序。它将显示此网页的访问计数:

"Hello... I have been seen 8 times" 

重新创建部署文件以将容器移动到node-2

$ nano flocker-deply-alt.yml 
"version": 1\. 
"nodes": 
  "node-1": ["web"] 
  "node-2": ["redis"] 

现在,我们将把容器从node-1迁移到node-2,我们将看到 Flocker 将自动处理卷管理。当 Redis 容器在node-2上启动时,它将连接到现有的卷:

$ flocker-deploy control-service flocker-deploy-alt.yml docker-compose.yml 

集群配置已更新。这可能需要一段时间才能生效,特别是如果需要拉取 Docker 镜像。

我们可以 SSH 进入node-2并列出正在运行的 Redis 容器。尝试访问http://node2上的应用程序;我们将能够看到计数仍然保持在node-1中,并且当从node-2访问应用程序时,计数会增加1

"Hello... I have been seen 9 times" 

这个例子演示了我们如何在 Flocker 集群中轻松地将容器及其数据卷从一个节点迁移到另一个节点。

Convoy Docker 卷插件

Convoy 是另一个广泛使用的 Docker 卷插件,用于提供存储后端。它是用 Go 语言编写的,其主要优势是可以以独立模式部署。Convoy 将作为 Docker 卷扩展运行,并且会像一个中间容器一样运行。Convoy 的初始实现利用 Linux 设备,并为卷提供以下四个 Docker 存储功能:

  • 薄配置卷

  • 在主机之间恢复卷

  • 对卷进行快照

  • 将卷备份到外部对象存储,如Amazon EBS虚拟文件系统VFS)和网络文件系统NFS):Convoy Docker 卷插件

使用 Convoy 卷插件

在下面的例子中,我们将运行一个本地的 Convoy 设备映射驱动程序,并展示在两个容器之间使用 Convoy 卷插件共享数据的用法:

  1. 验证 Docker 版本是否高于 1.8。

  2. 通过本地下载插件 tar 文件并解压缩来安装 Convoy 插件:

$ wget https://github.com/rancher/convoy/releases/download
        /v0.5.0/convoy.tar.gz 
        $ tar xvf convoy.tar.gz 
        convoy/ 
        convoy/convoy-pdata_tools 
        convoy/convoy 
        convoy/SHA1SUMS 
        $ sudo cp convoy/convoy convoy/convoy-pdata_tools /usr/local/bin/ 
        $ sudo mkdir -p /etc/docker/plugins/ 
        $ sudo bash -c 'echo "unix:///var/run/convoy/convoy.sock" > 
        /etc/docker/plugins/convoy.spec' 

  1. 我们可以继续使用文件支持的环回设备,它充当伪设备,并使文件可在块设备中访问,以演示 Convoy 设备映射驱动程序:
$ truncate -s 100G data.vol 
        $ truncate -s 1G metadata.vol 
        $ sudo losetup /dev/loop5 data.vol 
        $ sudo losetup /dev/loop6 metadata.vol 

  1. 一旦数据和元数据设备设置好,启动 Convoy 插件守护程序:
sudo convoy daemon --drivers devicemapper --driver-opts 
        dm.datadev=/dev/loop5 --driver-opts dm.metadatadev=/dev/loop6 

  1. 在前面的终端中,Convoy 守护程序将开始运行;打开下一个终端实例并创建一个使用 Convoy 卷test_volume挂载到容器内/sample目录的busybox Docker 容器:
$ sudo docker run -it -v test_volume:/sample --volume-driver=convoy 
        busybox 
        Unable to find image 'busybox:latest' locally 
        latest: Pulling from library/busybox 
        4b0bc1c4050b: Pull complete   
        Digest: sha256:817a12c32a39bbe394944ba49de563e085f1d3c5266eb8
        e9723256bc4448680e 
        Status: Downloaded newer image for busybox:latest 

  1. 在挂载的目录中创建一个示例文件:
/ # cd sample/ 
        / # cat > test 
        testing 
        /sample # exit 

  1. 使用 Convoy 作为卷驱动程序启动不同的容器,并挂载相同的 Convoy 卷:
$ sudo docker run -it -v test_volume:/sample --volume-driver=convoy --
        name=new-container busybox 

  1. 当我们执行ls时,我们将能够看到在先前容器中创建的文件:
/ # cd sample/ 
        /sample # ls 
        lost+found  test 
        /sample # exit 

因此,前面的例子显示了 Convoy 如何允许在同一主机或不同主机上的容器之间共享卷。

基本上,卷驱动程序应该用于持久数据,例如 WordPress MySQL DB:

$ docker run --name wordpressdb --volume-driver=convoy -v test_volume:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=wordpress -d mysql:5.7 
1e7908c60ceb3b286c8fe6a183765c1b81d8132ddda24a6ba8f182f55afa2167 

$ docker run -e WORDPRESS_DB_PASSWORD=password -d --name wordpress --link wordpressdb:mysql  wordpress 
0ef9a9bdad448a6068f33a8d88391b6f30688ec4d3341201b1ddc9c2e641f263 

在前面的例子中,我们使用 Convoy 卷驱动程序启动了 MySQL DB,以便在主机故障时提供持久性。然后我们将 MySQL 数据库链接到 WordPress Docker 容器中。

Docker 存储驱动程序性能

在本节中,我们将研究 Docker 支持的文件系统的性能方面和比较。可插拔的存储驱动程序架构和灵活性插入卷是容器化环境和生产用例的最佳方法。Docker 支持 aufs、btrfs、devicemapper、vfs、zfs 和 overlayfs 文件系统。

UFS 基础知识

如前所述,Docker 使用 UFS 以实现只读的分层方法。

Docker 使用 UFS 将多个这样的层合并为单个镜像。本节将深入探讨 UFS 的基础知识以及 Docker 支持的存储驱动程序。

UFS 递归地将多个目录合并为单个虚拟视图。UFS 的基本愿望是拥有一个只读文件系统和一些可写的覆盖层。这会产生一个假象,即文件系统具有读写访问权限,即使它是只读的。UFS 使用写时复制来支持此功能。此外,UFS 操作的是目录而不是驱动器。

底层文件系统并不重要。UFS 可以合并来自不同底层文件系统的目录。由于 UFS 拦截了与这些文件系统绑定的操作,因此可以合并不同的底层文件系统。下图显示了 UFS 位于用户应用程序和文件系统之间。UFS 的示例包括 Union FS、Another Union FS(AUFS)等:

UFS 基础知识

UFS 和分支的底层文件系统

UFS - 术语

UFS 中的分支是合并的文件系统。分支可以具有不同的访问权限,例如只读、读写等。UFS 是可堆叠的文件系统。分支还可以被分配偏好,确定对文件系统执行操作的顺序。如果在多个分支中存在具有相同文件名的目录,则 UFS 中的目录内容似乎被合并,但对这些目录中的文件的操作会被重定向到各自的文件系统。

UFS 允许我们在只读文件系统上创建可写层,并创建新文件/目录。它还允许更新现有文件。通过将文件复制到可写层,然后进行更改来更新现有文件。只读文件系统中的文件保持不变,但 UFS 创建的虚拟视图将显示更新后的文件。将文件复制到可写层以更新它的现象称为复制上升。

使用复制上升后,删除文件变得复杂。在尝试删除文件时,我们必须从底部到顶部删除所有副本。这可能导致只读层上的错误,无法删除文件。在这种情况下,文件会从可写层中删除,但仍然存在于下面的只读层中。

UFS - 问题

UFS 最明显的问题是对底层文件系统的支持。由于 UFS 包装了必要的分支及其文件系统,因此必须在 UFS 源代码中添加文件系统支持。底层文件系统不会改变,但 UFS 必须为每个文件系统添加支持。

删除文件后创建的白出也会造成很多问题。首先,它们会污染文件系统命名空间。可以通过在单个子目录中添加白出来减少这种情况,但这需要特殊处理。此外,由于白出,rmdir的性能会下降。即使一个目录看起来是空的,它可能包含很多白出,因此rmdir无法删除该目录。

在 UFS 中,复制上升是一个很好的功能,但它也有缺点。它会降低第一次更新的性能,因为它必须将完整的文件和目录层次结构复制到可写层。此外,需要决定目录复制的时间。有两种选择:在更新时复制整个目录层次结构,或者在打开目录时进行复制。这两种技术都有各自的权衡。

AuFS

AuFS 是另一种 UFS。AuFS 是从 UFS 文件系统分叉出来的。这引起了开发者的注意,并且现在远远领先于 UFS。事实上,UFS 现在在遵循开发 AuFS 时所做的一些设计决策。像任何 UFS 一样,AuFS 使现有的文件系统和叠加在其上形成一个统一的视图。

AuFS 支持前几节中提到的所有 UFS 功能。您需要在 Ubuntu 上安装aufs-tools软件包才能使用 AuFS 命令。有关 AuFS 及其命令的更多信息,请参阅 AuFS 手册页。

设备映射器

设备映射器是 Linux 内核组件;它提供了将物理块设备映射到虚拟块设备的机制。这些映射设备可以用作逻辑卷。设备映射器提供了创建这种映射的通用方法。

设备映射器维护一个表,该表定义了设备映射。该表指定了如何映射设备的每个逻辑扇区范围。该表包含以下参数的行:

  • 起始

  • 长度

  • 映射

  • 映射参数

第一行的起始值始终为零。对于其他行,起始加上前一行的长度应等于当前行的起始值。设备映射器的大小始终以 512 字节扇区指定。有不同类型的映射目标,例如线性、条带、镜像、快照、快照原点等。

Docker 如何使用设备映射器

Docker 使用设备映射器的薄配置和快照功能。这些功能允许许多虚拟设备存储在同一数据卷上。数据和元数据使用两个单独的设备。数据设备用于池本身,元数据设备包含有关卷、快照、存储池中的块以及每个快照的块之间的映射的信息。因此,Docker 创建了一个单个的大块设备,然后在其上创建了一个薄池。然后创建一个基本块设备。每个镜像和容器都是从此基本设备的快照中形成的。

BTRFS

BTRFS是一个 Linux 文件系统,有潜力取代当前默认的 Linux 文件系统 EXT3/EXT4。BTRFS(也称为butter FS)基本上是一个写时复制文件系统。写时复制CoW)意味着它永远不会更新数据。相反,它会创建数据的新副本,该副本存储在磁盘的其他位置,保留旧部分不变。任何具有良好文件系统知识的人都会理解,写时复制需要更多的空间,因为它也存储了旧数据的副本。此外,它还存在碎片化的问题。那么,写时复制文件系统如何成为默认的 Linux 文件系统呢?这不会降低性能吗?更不用说存储空间的问题了。让我们深入了解 BTRFS,了解为什么它变得如此受欢迎。

BTRFS 的主要设计目标是开发一种通用文件系统,可以在任何用例和工作负载下表现良好。大多数文件系统对于特定的文件系统基准测试表现良好,但在其他情况下性能并不那么好。除此之外,BTRFS 还支持快照、克隆和 RAID(0 级、1 级、5 级、6 级、10 级)。这比以往任何人从文件系统中得到的都要多。人们可以理解设计的复杂性,因为 Linux 文件系统部署在各种设备上,从计算机和智能手机到小型嵌入式设备。BTRFS 布局用 B 树表示,更像是一片 B 树的森林。这些是适合写时复制的 B 树。由于写时复制文件系统通常需要更多的磁盘空间,总的来说,BTRFS 具有非常复杂的空间回收机制。它有一个垃圾收集器,利用引用计数来回收未使用的磁盘空间。为了数据完整性,BTRFS 使用校验和。

存储驱动程序可以通过将--storage-driver选项传递给dockerd命令行,或在/etc/default/docker文件中设置DOCKER_OPTS选项来选择:

$ dockerd --storage-driver=devicemapper & 

我们已经考虑了前面三种广泛使用的文件系统与 Docker,以便使用微基准测试工具对以下 Docker 命令进行性能分析;fio是用于分析文件系统详细信息的工具,比如随机写入:

  • commit:这用于从运行的容器创建 Docker 镜像:BTRFS

图表描述了提交包含单个大文件的大型容器所需的时间

  • build:用于使用包含一系列步骤的 Dockerfile 构建镜像的命令,以便从头开始创建包含单个大文件的镜像:BTRFS

图表描述了在不同文件系统上构建容器所需的时间

  • rm:用于删除已停止的容器的命令:BTRFS

图表描述了使用 rm 命令删除包含成千上万文件的容器所需的时间。

  • rmi:用于删除镜像的命令:BTRFS

图表描述了使用 rmi 命令删除包含单个大文件的大型容器所需的时间

从前面的测试中,我们可以清楚地看到,AuFS 和 BTRFS 在 Docker 命令方面表现非常出色,但是 BTRFS 容器执行许多小写操作会导致 BTRFS 块的使用不佳。这最终可能导致 Docker 主机的空间不足并停止工作。使用 BTRFS 存储驱动程序可以密切监视 BTRFS 文件系统上的可用空间。此外,由于 BTRFS 日志技术,顺序写入受到影响,可能会减半性能。

设备映射器的性能不佳,因为每次容器更新现有数据时,存储驱动程序执行一次 CoW 操作。复制是从镜像快照到容器的快照,可能会对容器性能产生明显影响。

AuFS 看起来是 PaaS 和其他类似用例的不错选择,其中容器密度起着重要作用。AuFS 在运行时有效地共享镜像,实现快速容器启动时间和最小磁盘空间使用。它还非常有效地使用系统页面缓存。OverlayFS 是一种类似于 AuFS 的现代文件系统,但设计更简单,可能更快。但目前,OverlayFS 还不够成熟,无法在生产环境中使用。它可能会在不久的将来成为 AuFS 的继任者。没有单一的驱动程序适用于每种用例。用户应根据用例选择存储驱动程序,并考虑应用程序所需的稳定性,或者使用发行版 Docker 软件包安装的默认驱动程序。如果主机系统是 RHEL 或其变体,则 Device Mapper 是默认的存储驱动程序。对于 Ubuntu,AuFS 是默认驱动程序。

摘要

在本章中,我们深入探讨了与 Docker 相关的数据卷和存储驱动器概念。我们讨论了使用四种方法来排除数据卷故障,以及它们的优缺点。将数据存储在 Docker 容器内的第一种情况是最基本的情况,但在生产环境中无法灵活管理和处理数据。第二种和第三种情况是关于使用仅数据容器或直接在主机上存储数据。这些情况有助于提供可靠性,但仍然依赖于主机的可用性。第四种情况是关于使用第三方卷插件,如 Flocker 或 Convoy,通过将数据存储在单独的块中解决了所有先前的问题,并在容器从一个主机转移到另一个主机或容器死亡时提供了数据的可靠性。在最后一节中,我们讨论了 Docker 存储驱动程序和 Docker 提供的插件架构,以使用所需的文件系统,如 AuFS、BTRFS、Device Mapper、vfs、zfs 和 OverlayFS。我们深入研究了 AuFS、BTRFS 和 Device Mapper,这些是广泛使用的文件系统。通过使用基本的 Docker 命令进行的各种测试表明,AuFS 和 BTRFS 比 Device Mapper 提供更好的性能。用户应根据其应用用例和 Docker 守护程序主机系统选择 Docker 存储驱动程序。

在下一章中,我们将讨论 Docker 在公共云 AWS 和 Azure 中的部署以及故障排除。

第十章:在公共云中部署 Docker - AWS 和 Azure

在本章中,我们将在公共云 AWS 和 Azure 上进行 Docker 部署。 AWS 在 2014 年底推出了弹性计算云EC2)容器服务。当它推出时,该公司强调了基于过去发布的亚马逊服务的高级 API 调用的容器集群管理任务。 AWS 最近发布了 Docker for AWS Beta,允许用户快速在 AWS 和 Azure 上设置和配置 Docker 1.13 swarm 模式。借助这项新服务,我们可以获得以下功能:

  • 它确保团队可以无缝地将应用程序从开发人员的笔记本电脑移动到基于 Docker 的暂存和生产环境

  • 它有助于与底层 AWS 和 Azure 基础设施深度集成,利用主机环境,并向使用公共云的管理员公开熟悉的接口

  • 它部署平台并在各种平台之间轻松迁移,其中 Docker 化的应用程序可以简单高效地移动

  • 它确保应用程序在所选平台、硬件、基础设施和操作系统上以最新和最优秀的 Docker 版本完美运行

在本章的后半部分,我们将涵盖 Azure 容器服务,它可以简单地创建、配置和管理提供支持运行容器化应用程序的虚拟机集群。它允许我们在 Microsoft Azure 上部署和管理容器化应用程序。它还支持各种 Docker 编排工具,如 DC/OS、Docker Swarm 或 Kubernetes,根据用户选择。

在本章中,我们将涵盖以下主题:

  • Amazon EC2 容器服务的架构

  • 故障排除 AWS ECS 部署

  • 更新 ECS 集群中的 Docker 容器

  • Microsoft Azure 容器服务的架构

  • Microsoft Azure 容器服务的故障排除

  • AWS 和 Azure 的 Docker Beta

Amazon ECS 的架构

亚马逊 ECS 的核心架构是集群管理器,这是一个后端服务,负责集群协调和状态管理的任务。在集群管理器的顶部是调度程序管理器。它们彼此解耦,允许客户构建自己的调度程序。资源池包括 Amazon EC2 实例的 CPU、内存和网络资源,这些资源由容器分区。 Amazon ECS 通过在每个 EC2 实例上运行的开源 Amazon ECS 容器代理来协调集群,并根据调度程序的要求启动、停止和监视容器。为了管理单一真相:EC2 实例、运行在它们上面的任务以及使用的容器和资源。我们需要将状态存储在某个地方,这是在集群管理器键/值存储中完成的。为了实现这个键/值存储的并发控制,维护了一个基于事务日志的数据存储,以记录对每个条目的更改。亚马逊 ECS 集群管理器已经开放了一组 API,允许用户访问存储在键/值存储中的所有集群状态信息。通过list命令,客户可以检索管理的集群、运行的任务和 EC2 实例。describe命令可以帮助检索特定 EC2 实例的详细信息以及可用的资源。亚马逊 ECS 架构提供了一个高度可扩展、可用和低延迟的容器管理解决方案。它是完全托管的,并提供运营效率,允许客户构建和部署应用程序,而不必考虑要管理或扩展的集群:

Amazon ECS 架构

亚马逊 ECS 架构

故障排除 - AWS ECS 部署

EC2 实例可以手动部署,并且可以在其上配置 Docker,但 ECS 是由 ECS 管理的一组 EC2 实例。 ECS 将负责在集群中的各个主机上部署 Docker 容器,并与其他 AWS 基础设施服务集成。

在本节中,我们将介绍在 AWS 上设置 ECS 的一些基本步骤,这将有助于解决和绕过基本配置错误:

  • 创建 ECS 集群

  • 创建 ELB 负载均衡器

  • 在 ECS 集群中运行 Docker 容器

  • 更新 ECS 集群中的 Docker 容器

  1. 从 AWS 控制台中的计算下列出的EC2 容器服务启动:故障排除 - AWS ECS 部署

  2. 单击开始按钮:故障排除 - AWS ECS 部署

  3. 在下一个屏幕上,选择两个选项:部署示例应用程序,创建和管理私有仓库。为 EC2 服务创建了一个私有仓库,并由 AWS 进行了安全保护。需要 AWS 登录才能推送镜像:故障排除 - AWS ECS 部署

  4. 提供仓库名称,我们将能够看到生成需要推送容器镜像的仓库地址:故障排除 - AWS ECS 部署

  5. 下一个屏幕显示了一些基本的 Docker 和 AWS CLI 命令,用于将容器镜像推送到私有仓库,如下所示:

使用pip软件包管理器安装 AWS CLI:

 $ pip install awscl

使用aws configure命令并提供 AWS 访问密钥 ID 和 AWS 秘密访问密钥进行登录:

 $ aws configure 
        AWS Access Key ID [None]:  
        AWS Secret Access Key [None]: 
        Default region name [None]:  
        Default output format [None]:

获取docker login命令,以便将本地 Docker 客户端认证到私有 AWS 注册表:

 $ aws ecr get-login --region us-east-1 
        docker login -u AWS -p 
        Key...

使用生成的链接作为前述命令的输出,该链接将配置 Docker 客户端以便与部署在 AWS 中的私有仓库一起工作:

 $ docker login -u AWS -p Key... 
        Flag --email has been deprecated, will be removed in 1.13\. 
        Login Succeeded

现在我们将使用 AWS 私有仓库名称标记 nginx 基本容器镜像,以便将其推送到私有仓库:

 $ docker images 
        REPOSITORY  TAG     IMAGE ID      CREATED     SIZE 
        nginx       latest  19146d5729dc  6 days ago  181.6 MB 

        $ docker tag nginx:latest private-repo.amazonaws.com/sample:latest 

        $ docker push private-repo.amazonaws.com/sample:latest 
        The push refers to a repository [private-repo.amazonaws.com/sample] 
        e03d01018b72: Pushed  
        ee3b1534c826: Pushing [==>] 2.674 MB/58.56 MB 
        b6ca02dfe5e6: Pushing [>] 1.064 MB/123.1 MB  
        ... Image successfully pushed

  1. 将镜像推送到私有 Docker 仓库后,我们将创建一个任务定义,定义以下内容:
  • 要运行的 Docker 镜像

  • 要分配的资源(CPU、内存等)

  • 要挂载的卷

  • 要链接在一起的 Docker 容器

  • 启动时应运行的命令容器

  • 要为容器设置的环境变量

  • 任务应使用的 IAM 角色

  • 特权 Docker 容器与否

  • 要给 Docker 容器的标签

  • 要用于容器的端口映射和网络,以及要用于容器的 Docker 网络模式:故障排除 - AWS ECS 部署

  1. 高级容器配置给我们提供了声明CPU 单位入口点、特权容器与否等选项:故障排除 - AWS ECS 部署

  2. 在下一步中,我们将声明对于运行持续的任务(如 Web 服务)有用的服务。

这使我们能够在 ECS 集群中同时运行和维护指定数量(期望数量)的任务定义。如果任何任务失败,Amazon ECS 服务调度程序将启动另一个实例,并保持服务中所需数量的任务。

我们可以选择在负载均衡器后面的服务中运行所需数量的任务。Amazon ECS 允许我们配置弹性负载均衡,以在服务中定义的任务之间分发流量。负载均衡器可以配置为应用负载均衡器,它可以将请求路由到一个或多个端口,并在应用层(HTTP/HTTPS)做出决策。经典负载均衡器在传输层(TCP/SSL)或应用层(HTTP/HTTPS)做出决策。它需要负载均衡器端口和容器实例端口之间的固定关系:

故障排除 - AWS ECS 部署

  1. 在下一步中,配置集群,这是 EC2 实例的逻辑分组。默认情况下,我们将把t2.micro定义为 EC2 实例类型,并将当前实例数定义为1故障排除 - AWS ECS 部署

  2. 审查配置并部署 ECS 集群。创建集群后,单击查看服务按钮以查看有关服务的详细信息:故障排除 - AWS ECS 部署

  3. 单击 EC2 容器负载均衡器,以获取公开访问的服务 URL:故障排除 - AWS ECS 部署

  4. 在负载均衡器的描述中,DNS 名称是从互联网访问服务的 URL:故障排除 - AWS ECS 部署

  5. 当我们访问负载均衡器的公共 URL 时,可以看到欢迎使用 nginx 页面:故障排除 - AWS ECS 部署

更新 ECS 集群中的 Docker 容器

我们在 ECS 集群中运行 Docker 容器,现在,让我们走一遍这样一个情景,即容器和服务都需要更新。通常,这发生在持续交付模型中,我们有两个生产环境;蓝色环境是服务的旧版本,目前正在运行,以处理用户的请求。新版本环境被称为绿色环境,它处于最后阶段,并将处理未来用户的请求,因为我们从旧版本切换到新版本。

蓝绿部署有助于快速回滚。如果我们在最新的绿色环境中遇到任何问题,我们可以将路由器切换到蓝色环境。现在,由于绿色环境正在运行并处理所有请求,蓝色环境可以用作下一个部署的最终测试步骤的暂存环境。这种情况可以很容易地通过 ECS 中的任务定义来实现:

在 ECS 集群中更新 Docker 容器

蓝绿部署环境

  1. 通过选择创建的 ECS 任务并单击创建新任务定义按钮,可以创建新的修订版本:在 ECS 集群中更新 Docker 容器

  2. 在任务的新定义中,我们可以附加一个新容器或单击容器定义并进行更新。高级容器配置也可以用于设置环境变量在 ECS 集群中更新 Docker 容器

  3. 创建最新任务后,单击操作,然后单击更新服务在 ECS 集群中更新 Docker 容器

  4. console-sample-app-static:2将更新console-sample-app-static:1,并在下一个屏幕上提供了包括任务数量和自动缩放选项在内的各种选项:在 ECS 集群中更新 Docker 容器

自动缩放组将启动,包括 AMI、实例类型、安全组和用于启动 ECS 实例的所有其他细节。使用缩放策略,我们可以扩展集群实例和服务,并在需求减少时安全地缩小它们。可用区感知的 ECS 调度程序管理、分发和扩展集群,从而使架构具有高可用性。

Microsoft Azure 容器服务架构

Azure 是当今市场上增长最快的基础设施服务之一。它支持按需扩展和创建混合环境的能力,并借助 Azure 云服务支持大数据。Azure 容器服务提供了开源容器集群和编排解决方案的部署。借助 Azure 容器服务,我们可以部署基于 DC/OS(Marathon)、Kubernetes 和 Swarm 的容器集群。Azure 门户提供了简单的 UI 和 CLI 支持来实现这种部署。

Microsoft Azure 正式成为第一个支持主流容器编排引擎的公共云。即使 Azure 容器服务引擎也在 GitHub 上开源(github.com/Azure/acs-engine)。

这一步使开发人员能够理解架构并直接在 vSphere Hypervisor、KVM 或 HyperV 上运行多个编排引擎。 Azure 资源管理器ARM)模板为通过 ACS API 部署的集群提供了基础。ACS 引擎是用 Go 构建的,这使用户能够组合不同的配置部件并构建最终模板,用于部署集群。

Azure 容器引擎具有以下功能:

  • 您选择的编排器,如 DC/OS,Kubernetes 或 Swarm

  • 多个代理池(可用性集和虚拟机集)

  • Docker 集群大小最多可达 1,200 个:

  • 支持自定义 vNET

Azure 容器服务主要是以 DC/OS 作为关键组件之一构建的,并且在 Microsoft Azure 上进行了优化以便轻松创建和使用。ACS 架构有三个基本组件:Azure Compute 用于管理 VM 健康,Mesos 用于容器健康管理,Swarm 用于 Docker API 管理:

Microsoft Azure 容器服务架构

Microsoft Azure 容器架构

故障排除-微软 Azure 容器服务

在本节中,我们将看看如何在 Microsoft Azure 中部署 Docker Swarm 集群,并提供编排器配置详细信息:

  1. 我们需要创建一个 RSA 密钥,在部署步骤中将被请求。该密钥将需要用于登录到安装后的部署机器:
 $ ssh-keygen

一旦生成,密钥可以在~/root/id_rsa中找到

  1. 在 Azure 账户门户中单击新建按钮:故障排除-微软 Azure 容器服务

  2. 搜索Azure 容器服务并选择它:故障排除-微软 Azure 容器服务

  3. 完成此步骤后,选择资源管理器作为部署模型,然后单击创建按钮:故障排除-微软 Azure 容器服务

  4. 配置基本设置页面,需要以下细节:用户名,将作为部署在 Docker Swarm 集群中的虚拟机的管理员;第二个字段是提供我们在步骤 1 中创建的SSH 公钥;并通过在资源组字段中指定名称来创建一个新的资源组:故障排除 - Microsoft Azure 容器服务

  5. 根据需要选择编排器配置SwarmDC/OSKubernetes故障排除 - Microsoft Azure 容器服务

  6. 在下一步中,为此部署提供编排器配置、代理计数主服务器计数。还可以根据需要提供 DNS 前缀,如dockerswarm故障排除 - Microsoft Azure 容器服务

  7. 检查摘要,一旦验证通过,点击确定。在下一个屏幕上,点击购买按钮继续部署:故障排除 - Microsoft Azure 容器服务

  8. 一旦部署开始,可以在 Azure 主要仪表板上看到状态:故障排除 - Microsoft Azure 容器服务

  9. 创建 Docker Swarm 集群后,点击仪表板上显示的 Docker Swarm 资源中的 swarm-master:故障排除 - Microsoft Azure 容器服务

  10. 在 swarm-master 的基本信息部分,您将能够找到 DNS 条目,如下面的截图所示:故障排除 - Microsoft Azure 容器服务

以下是连接到 swarm-master 的 SSH 命令:

 ssh <DNS_FROM_FIELD> -A -p 2200 -i <PUB_FILE_LOCATION>

一旦连接到主服务器,可以执行基本的 Docker Swarm 命令,并且可以在部署在 Microsoft Azure 上的 Swarm 集群上部署容器。

AWS 和 Azure 的 Docker Beta

随着这项服务的最新发布,Docker 已经简化了通过与两个云平台的基础设施服务紧密集成,在 AWS 和 Azure 上部署 Docker 引擎的过程。这使开发人员能够将他们的代码捆绑并部署到生产机器中,而不管环境如何。目前,该服务处于 Beta 版本,但我们已经介绍了 AWS 的 Docker 部署的基本教程。该服务还允许您在这些环境中轻松升级 Docker 版本。甚至这些服务中还启用了 Swarm 模式,为单个 Docker 引擎提供了自愈和自组织的 Swarm 模式。它们还分布在可用性区域中。

与先前的方法相比,Docker Beta for AWS and Azure 提供了以下改进:

  • 使用 SSH 密钥进行 IaaS 帐户的访问控制

  • 轻松配置基础设施负载平衡和动态更新,因为应用程序在系统中被配置

  • 可以使用安全组和虚拟网络来进行安全的 Docker 设置

Docker for AWS 使用CloudFormation模板并创建以下对象:

  • 启用自动缩放的 EC2 实例

  • IAM 配置文件

  • DynamoDB 表

  • VPC、子网和安全组

  • ELB

需要部署和访问部署实例的 AWS 区域的 SSH 密钥。安装也可以使用 AWS CLI 使用 CloudFormation 模板完成,但在本教程中,我们将介绍基于 AWS 控制台的方法:

  1. 登录控制台,选择 CloudFormation,然后单击创建堆栈

  2. 指定 Amazon S3 模板 URL 为https://docker-for-aws.s3.amazonaws.com/aws/beta/aws-v1.13.0-rc4-beta14.json,如下所示:Docker Beta for AWS and Azure

  3. 在下一个屏幕上,指定堆栈详细信息,说明需要部署的 Swarm 管理器和节点的数量。还可以指定要使用的 AWS 生成的 SSH 密钥:Docker Beta for AWS and Azure

  4. 在下一个屏幕上,我们将有提供标签以及 IAM 权限角色的选项:Docker Beta for AWS and Azure

  5. 审查详细信息并启动堆栈:Docker Beta for AWS and Azure

  6. 堆栈将显示为状态CREATE_IN_PROGRESS。等待堆栈完全部署:Docker Beta for AWS and Azure

  7. 部署后,堆栈将具有状态CREATE_COMPLETE。单击它,部署的环境详细信息将被列出:Docker Beta for AWS and Azure

AWS 生成的 SSH 密钥可用于 SSH 到管理节点并管理部署的 Docker Swarm 实例:

 $ ssh -i <path-to-ssh-key> docker@<ssh-host> 
Welcome to Docker!

docker info命令将提供有关 Swarm 集群的信息。可以使用以下命令列出 Swarm 节点:

 $ docker info  
Containers: 5 
 Running: 4 
 Paused: 0 
 Stopped: 1 
Images: 5 
Server Version: 1.13.0-rc4 
Storage Driver: overlay2 
 Backing Filesystem: extfs 

$ docker node ls 
ID                           HOSTNAME                       STATUS  AVAILABILITY  MANAGER STATUS 
koewopxooyp5ftf6tn5wypjtd    ip-172-31-37-122.ec2.internal  Ready   Active         
qs9swn3uv67v4vhahxrp4q24g    ip-172-31-2-43.ec2.internal    Ready   Active         
ubkzv527rlr08fjjgvweu0k6t *  ip-172-31-1-137.ec2.internal   Ready   Active        Leader

SSH 连接也可以直接连接到领导节点,并部署基本的 Docker 容器:

 $ ssh docker@ip-172-31-37-122.ec2.internal 

$ docker run hello-world 
Unable to find image 'hello-world:latest' locally 
latest: Pulling from library/hello-world 
c04b14da8d14: Pull complete  
Digest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9 
Status: Downloaded newer image for hello-world:latest 

Hello from Docker!

服务可以按照以下方式为先前部署的容器创建:

 $ docker service create --replicas 1 --name helloworld alpine ping docker.com
 xo7byk0wyx5gim9y7etn3o6kz
 $ docker service ls
 ID            NAME        MODE        REPLICAS   IMAGE
 xo7byk0wyx5g  helloworld  replicated  1/1        alpine:latest
 $ docker service inspect --pretty helloworld
 ID:           xo7byk0wyx5gim9y7etn3o6kz
 Name:         helloworld
 Service Mode: Replicated

可以按照以下方式在 Swarm 集群中扩展和移除服务:

 $ docker service scale helloworld=5 
helloworld scaled to 5 

$ docker service ps helloworld 
ID            NAME          IMAGE          NODE                           DESIRED STATE  CURRENT STATE               ERROR  PORTS 
9qu8q4equobn  helloworld.1  alpine:latest  ip-172-31-37-122.ec2.internal  Running        Running about a minute ago          
tus2snjwqmxm  helloworld.2  alpine:latest  ip-172-31-37-122.ec2.internal  Running        Running 6 seconds ago               
cxnilnwa09tl  helloworld.3  alpine:latest  ip-172-31-2-43.ec2.internal    Running        Running 6 seconds ago               
cegnn648i6b2  helloworld.4  alpine:latest  ip-172-31-1-137.ec2.internal   Running        Running 6 seconds ago               
sisoxrpxxbx5  helloworld.5  alpine:latest  ip-172-31-1-137.ec2.internal   Running        Running 6 seconds ago               

$ docker service rm helloworld 
helloworld

摘要

在本章中,我们已经介绍了在公共云 Microsoft Azure 和 AWS 上部署 Docker。两家云服务提供商为客户提供了有竞争力的容器服务。本章帮助解释了 AWS EC2 和 Microsoft Azure 容器服务架构的详细架构。它还涵盖了容器集群的所有部署步骤的安装和故障排除。本章还涵盖了蓝绿部署场景以及它在 AWS EC2 中的支持情况,这在现代 SaaS 应用程序的情况下通常是必要的。最后,我们介绍了最近推出的 Docker Beta,适用于 AWS 和 Azure,它提供了容器从开发环境迁移到生产环境的简便方法,因为它们是相同的。基于容器的应用程序可以很容易地使用 Docker Beta 进行部署和扩展,因为这项服务与云服务提供商的 IaaS 非常紧密地结合在一起。

posted @ 2024-05-06 18:32  绝不原创的飞龙  阅读(57)  评论(0编辑  收藏  举报