Docker-网络学习手册(全)

Docker 网络学习手册(全)

原文:zh.annas-archive.org/md5/EA91D8E763780FFC629216A68518897B

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

本书帮助读者学习、创建、部署和提供 Docker 网络的管理步骤。Docker 是一个 Linux 容器实现,可以创建轻量级便携的开发和生产环境。这些环境可以进行增量更新。Docker 通过利用 cgroups 和 Linux 命名空间等封装原则以及基于覆盖文件系统的便携式镜像来实现这一点。

Docker 提供了网络原语,允许管理员指定不同容器如何与每个应用程序进行网络连接,连接到它们的各个组件,然后将它们分布在大量服务器上,并确保它们之间的协调,无论它们运行在哪个主机或虚拟机上。本书汇集了所有最新的 Docker 网络技术,并提供了深入的设置详细说明。

第一章《Docker 网络入门》解释了 Docker 网络的基本组件,这些组件是从简单的 Docker 抽象和强大的网络组件(如 Linux 桥接、Open vSwitch 等)演变而来。本章还解释了 Docker 容器可以以各种模式创建。在默认模式下,端口映射通过使用 iptables NAT 规则帮助我们,允许到达主机的流量到达容器。本章后面还涵盖了容器的基本链接以及下一代 Docker 网络——libnetwork。

本书内容

第二章《Docker 网络内部》讨论了 Docker 的内部网络架构。我们将了解 Docker 中的 IPv4、IPv6 和 DNS 配置。本章后面还涵盖了 Docker 桥接和单主机和多主机之间容器之间的通信。本章还解释了覆盖隧道和 Docker 网络上实现的不同方法,如 OVS、Flannel 和 Weave。

第三章,“构建您的第一个 Docker 网络”,展示了 Docker 容器如何使用不同的网络选项从多个主机进行通信,例如 Weave、OVS 和 Flannel。Pipework 使用传统的 Linux 桥接,Weave 创建虚拟网络,OVS 使用 GRE 隧道技术,Flannel 为每个主机提供单独的子网,以连接多个主机上的容器。一些实现,如 Pipework,是传统的,并将在一段时间内变得过时,而其他一些则设计用于特定操作系统的上下文中,例如 Flannel 与 CoreOS。本章还涵盖了 Docker 网络选项的基本比较。

第四章,“Docker 集群中的网络”,深入解释了 Docker 网络,使用各种框架,如原生 Docker Swarm,使用 libnetwork 或开箱即用的覆盖网络,Swarm 提供了多主机网络功能。另一方面,Kubernetes 与 Docker 有不同的观点,其中每个 pod 将获得一个唯一的 IP 地址,并且可以借助服务在 pod 之间进行通信。使用 Open vSwitch 或 IP 转发高级路由规则,可以增强 Kubernetes 网络,以在不同子网的主机之间提供连接,并将 pod 暴露给外部世界。在 Mesosphere 的情况下,我们可以看到 Marathon 被用作部署容器的网络后端。在 Mesosphere 的 DCOS 情况下,整个部署的机器堆栈被视为一个机器,以在部署的容器服务之间提供丰富的网络体验。

第五章,“Docker 容器的安全性和 QoS”,通过引用内核和 cgroups 命名空间,深入探讨 Docker 安全性。我们还将讨论文件系统和各种 Linux 功能的一些方面,容器利用这些功能来提供更多功能,例如特权容器,但以暴露更多威胁为代价。我们还将看到如何在 AWS ECS 中部署容器在安全环境中使用代理容器来限制易受攻击的流量。我们还将讨论 AppArmor 提供了丰富的强制访问控制(MAC)系统,提供内核增强功能,以限制应用程序对有限资源的访问。利用它们对 Docker 容器的好处有助于在安全环境中部署它们。在最后一节中,我们将快速深入研究 Docker 安全基准和一些在审核和在生产环境中部署 Docker 时可以遵循的重要建议。

第六章,“Docker 的下一代网络堆栈:libnetwork”,将深入探讨 Docker 网络的一些更深层次和概念性方面。其中之一是 libnetworking——Docker 网络模型的未来,随着 Docker 1.9 版本的发布已经初具规模。在解释 libnetworking 概念的同时,我们还将研究 CNM 模型,以及其各种对象和组件,以及其实现代码片段。接下来,我们将详细研究 CNM 的驱动程序,主要是覆盖驱动程序,并作为 Vagrant 设置的一部分进行部署。我们还将研究容器与覆盖网络的独立集成,以及 Docker Swarm 和 Docker Machine。在接下来的部分中,我们将解释 CNI 接口,其可执行插件,并提供使用 CNI 插件配置 Docker 网络的教程。在最后一节中,将详细解释 Project Calico,该项目提供基于 libnetwork 的可扩展网络解决方案,并与 Docker、Kubernetes、Mesos、裸机和虚拟机集成。

本书所需内容

基本上所有的设置都需要 Ubuntu 14.04(安装在物理机器上或作为虚拟机)和 Docker 1.9,这是截止目前的最新版本。如果需要,会在每个设置之前提到特定的操作系统和软件要求(开源 Git 项目)。

这本书适合谁

如果您是一名 Linux 管理员,想要通过使用 Docker 来学习网络知识,以确保对核心元素和应用程序进行高效管理,那么这本书适合您。假定您具有 LXC/Docker 的基础知识。

约定

您还会发现一些文本样式,用于区分不同类型的信息。以下是一些这些样式的示例和它们的含义解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名会显示如下:“您可能会注意到,我们使用了 Unix 命令rm来删除Drush目录,而不是 DOS 的del命令。”

代码块设置如下:

# * Fine Tuning
#
key_buffer = 16M
key_buffer_size = 32M
max_allowed_packet = 16M
thread_stack = 512K
thread_cache_size = 8
max_connections = 300

当我们希望引起您对代码块的特定部分的注意时,相关的行或项目会以粗体显示:

# * Fine Tuning
#
key_buffer = 16M
key_buffer_size = 32M
max_allowed_packet = 16M
thread_stack = 512K
thread_cache_size = 8
max_connections = 300

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

cd /ProgramData/Propeople
rm -r Drush
git clone --branch master http://git.drupal.org/project/drush.git

新术语重要单词会以粗体显示。您在屏幕上看到的单词,比如菜单或对话框中的单词,会以这样的形式出现在文本中:“在选择目标位置屏幕上,点击下一步以接受默认目标。”

注意

警告或重要提示会出现在这样的框中。

提示

技巧和窍门会以这样的形式出现。

第一章:Docker 网络入门

Docker 是一种轻量级的容器技术,近年来引起了巨大的兴趣。它巧妙地捆绑了各种 Linux 内核特性和服务,如命名空间、cgroups、SELinux 和 AppArmor 配置文件,以及 AUFS 和 BTRFS 等联合文件系统,以创建模块化的镜像。这些镜像为应用程序提供了高度可配置的虚拟化环境,并遵循“一次编写,随处运行”的工作流程。一个应用可以由在 Docker 容器中运行的单个进程组成,也可以由在它们自己的容器中运行的多个进程组成,并随着负载的增加而复制。因此,有必要拥有强大的网络元素来支持各种复杂的用例。

在本章中,您将了解 Docker 网络的基本组件以及如何构建和运行简单的容器示例。

本章涵盖以下主题:

  • 网络和 Docker

  • docker0 桥接网络

  • Docker OVS 网络

  • Unix 域网络

  • 链接 Docker 容器

  • Docker 网络的新特性

Docker 在行业中备受关注,因为其性能敏锐和通用可复制的架构,同时提供现代应用开发的以下四个基石:

  • 自治

  • 去中心化

  • 并行性

  • 隔离

此外,Thoughtworks 的微服务架构或 LOSA(许多小应用程序)的广泛采用进一步为 Docker 技术带来潜力。因此,谷歌、VMware 和微软等大公司已经将 Docker 移植到他们的基础设施上,而无数的 Docker 初创公司的推出,如 Tutum、Flocker、Giantswarm 等,也在推动这股势头。

由于 Docker 容器可以在开发机器、裸金属服务器、虚拟机或数据中心中复制其行为,因此应用程序设计人员可以专注于开发,而操作语义留给了 DevOps。这使团队工作流程模块化、高效和高产。 Docker 不应与虚拟机(VM)混淆,尽管它们都是虚拟化技术。虽然 Docker 与提供足够隔离和安全性的应用程序共享操作系统,但它后来完全抽象了操作系统并提供了强大的隔离和安全性保证。但是,与 VM 相比,Docker 的资源占用量微不足道,因此更受经济和性能的青睐。但是,它仍然无法完全取代 VM,因此是 VM 技术的补充。以下图表显示了 VM 和 Docker 的架构:

Docker Networking Primer

网络和 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 网络的根本思想:

AppArmor/SELinux

docker0 桥

docker0桥是默认网络的核心。当 Docker 服务启动时,在主机上创建一个 Linux 桥。容器上的接口与桥进行通信,桥代理到外部世界。同一主机上的多个容器可以通过 Linux 桥相互通信。

docker0可以通过--net标志进行配置,并且通常有四种模式:

  • --net default

  • --net=none

  • --net=container:$container2

  • --net=host

--net default 模式

在此模式下,默认桥被用作容器相互连接的桥。

--net=none 模式

使用此模式,创建的容器是真正隔离的,无法连接到网络。

--net=container:$container2 模式

使用此标志,创建的容器与名为$container2的容器共享其网络命名空间。

--net=host 模式

使用此模式,创建的容器与主机共享其网络命名空间。

Docker 容器中的端口映射

在本节中,我们将看看容器端口是如何映射到主机端口的。这种映射可以由 Docker 引擎隐式完成,也可以被指定。

如果我们创建两个名为Container1Container2的容器,它们都被分配了来自私有 IP 地址空间的 IP 地址,并连接到docker0桥上,如下图所示:

Docker 容器中的端口映射

前述的两个容器都能够相互 ping 通,也能够访问外部世界。

为了外部访问,它们的端口将被映射到主机端口。

如前一节所述,容器使用网络命名空间。当创建第一个容器时,为容器创建了一个新的网络命名空间。在容器和 Linux 桥之间创建了一个 vEthernet 链接。从容器的eth0发送的流量通过 vEthernet 接口到达桥,然后进行切换。以下代码可用于显示 Linux 桥的列表:

# 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选项完成。

Docker 容器中的端口映射

Docker OVS

Open vSwitch 是一个强大的网络抽象。下图显示了 OVS 如何与VMHypervisorPhysical Switch交互。每个VM都有一个与之关联的vNIC。每个vNIC通过VIF(也称为虚拟接口)与虚拟交换机连接:

Docker OVS

OVS 使用隧道机制,如 GRE、VXLAN 或 STT 来创建虚拟覆盖,而不是使用物理网络拓扑和以太网组件。下图显示了 OVS 如何配置为使用 GRE 隧道在多个主机之间进行容器通信:

Docker OVS

Unix 域套接字

在单个主机内,UNIX IPC 机制,特别是 UNIX 域套接字或管道,也可以用于容器之间的通信:

$  docker run  --name c1 –v /var/run/foo:/var/run/foo –d –I –t base /bin/bash
$  docker run  --name c2 –v /var/run/foo:/var/run/foo –d –I –t base /bin/bash

c1c2上的应用程序可以通过以下 Unix 套接字地址进行通信:

struct  sockaddr_un address;
address.sun_family = AF_UNIX;
snprintf(address.sun_path, UNIX_PATH_MAX, "/var/run/foo/bar" );

C1: Server.c C2: Client.c

|

bind(socket_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un));
listen(socket_fd, 5);
while((connection_fd = accept(socket_fd, (struct sockaddr *) &address, &address_length)) > -1)
nbytes = read(connection_fd, buffer, 256);

|

connect(socket_fd, (struct sockaddr *) &address, sizeof(struct sockaddr_un));
write(socket_fd, buffer, nbytes);

|

链接 Docker 容器

在本节中,我们介绍了链接两个容器的概念。Docker 在容器之间创建了一个隧道,不需要在容器上外部公开任何端口。它使用环境变量作为从父容器传递信息到子容器的机制之一。

除了环境变量env之外,Docker 还将源容器的主机条目添加到/etc/hosts文件中。以下是主机文件的示例:

$ docker run -t -i --name c2 --rm --link c1:c1alias training/webapp /bin/bash
root@<container_id>:/opt/webapp# cat /etc/hosts
172.17.0.1  aed84ee21bde
...
172.17.0.2  c1alaias 6e5cdeb2d300 c1

有两个条目:

  • 第一个是用 Docker 容器 ID 作为主机名的c2容器的条目

  • 第二个条目,172.17.0.2 c1alaias 6e5cdeb2d300 c1,使用link别名来引用c1容器的 IP 地址

下图显示了两个容器容器 1容器 2使用 veth 对连接到docker0桥接器,带有--icc=true。这意味着这两个容器可以通过桥接器相互访问:

Linking Docker containers

链接

链接为 Docker 提供了服务发现。它们允许容器通过使用标志-link name:alias来发现并安全地相互通信。通过使用守护程序标志-icc=false可以禁用容器之间的相互通信。设置为false后,容器 1除非通过链接明确允许,否则无法访问容器 2。这对于保护容器来说是一个巨大的优势。当两个容器被链接在一起时,Docker 会在它们之间创建父子关系,如下图所示:

链接

从外部看,它看起来像这样:

# start the database
$  sudo docker run -dp 3306:3306 --name todomvcdb \
-v /data/mysql:/var/lib/mysql cpswan/todomvc.mysql

# start the app server
$  sudo docker run -dp 4567:4567 --name todomvcapp \
--link todomvcdb:db cpswan/todomvc.sinatra

在内部,它看起来像这样:

$  dburl = ''mysql://root:pa55Word@'' + \ ENV[''DB_PORT_3306_TCP_ADDR''] + ''/todomvc''
$  DataMapper.setup(:default, dburl)

Docker 网络的新特性是什么?

Docker 网络处于非常初期阶段,开发者社区有许多有趣的贡献,如 Pipework、Weave、Clocker 和 Kubernetes。每个都反映了 Docker 网络的不同方面。我们将在后面的章节中了解它们。Docker, Inc.还建立了一个新项目,网络将被标准化。它被称为libnetwork

libnetwork 实现了容器网络模型CNM),它规范了为容器提供网络所需的步骤,同时提供了一个抽象,可以支持多个网络驱动程序。CNM 建立在三个主要组件上——沙盒、端点和网络。

沙盒

沙盒包含容器网络栈的配置。这包括容器的接口管理、路由表和 DNS 设置。沙盒的实现可以是 Linux 网络命名空间、FreeBSD 监狱或类似的概念。一个沙盒可以包含来自多个网络的许多端点。

端点

端点将沙盒连接到网络。端点的实现可以是 veth 对、Open vSwitch 内部端口或类似的东西。一个端点只能属于一个网络,但只能属于一个沙盒。

网络

网络是一组能够直接相互通信的端点。网络的实现可以是 Linux 桥接、VLAN 等。网络由许多端点组成,如下图所示:

网络

Docker CNM 模型

CNM 提供了网络和容器之间的以下约定:

  • 同一网络上的所有容器可以自由通信

  • 多个网络是在容器之间分段流量的方式,并且所有驱动程序都应该支持它

  • 每个容器可以有多个端点,这是将容器连接到多个网络的方法。

  • 端点被添加到网络沙箱中,以提供网络连接。

我们将在第六章中讨论 CNM 的实现细节,Docker 的下一代网络堆栈:libnetwork

总结

在本章中,我们学习了 Docker 网络的基本组件,这些组件是从简单的 Docker 抽象和强大的网络组件(如 Linux 桥和 Open vSwitch)的耦合中发展而来的。

我们学习了 Docker 容器可以以各种模式创建。在默认模式下,通过端口映射可以帮助使用 iptables NAT 规则,使得到达主机的流量可以到达容器。在本章后面,我们介绍了容器的基本链接。我们还讨论了下一代 Docker 网络,称为 libnetwork。

第二章:Docker 网络内部

本章详细讨论了 Docker 网络的语义和语法,揭示了当前 Docker 网络范式的优势和劣势。

它涵盖以下主题:

  • 为 Docker 配置 IP 堆栈

  • IPv4 支持

  • IPv4 地址管理问题

  • IPv6 支持

  • 配置 DNS

  • DNS 基础知识

  • 多播 DNS

  • 配置 Docker 桥

  • 覆盖网络和底层网络

  • 它们是什么?

  • Docker 如何使用它们?

  • 它们有哪些优势?

为 Docker 配置 IP 堆栈

Docker 使用 IP 堆栈通过 TCP 或 UDP 与外部世界进行交互。它支持 IPv4 和 IPv6 寻址基础设施,这些将在以下小节中解释。

IPv4 支持

默认情况下,Docker 为每个容器提供 IPv4 地址,这些地址附加到默认的docker0桥上。可以在启动 Docker 守护程序时使用--fixed-cidr标志指定 IP 地址范围,如下面的代码所示:

$ sudo docker –d --fixed-cidr=192.168.1.0/25

我们将在配置 Docker 桥部分中更多讨论这个问题。

Docker 守护程序可以在 IPv4 TCP 端点上列出,还可以在 Unix 套接字上列出:

$ sudo docker -H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock -d &

IPv6 支持

IPv4 和 IPv6 可以一起运行;这被称为双栈。通过使用--ipv6标志运行 Docker 守护程序来启用此双栈支持。Docker 将使用 IPv6 链路本地地址fe80::1设置docker0桥。所有容器之间共享的数据包都通过此桥流动。

要为您的容器分配全局可路由的 IPv6 地址,必须指定一个 IPv6 子网以选择地址。

以下命令通过--fixed-cidr-v6参数在启动 Docker 时设置 IPv6 子网,并向路由表添加新路由:

# docker –d --ipv6 --fixed-cidr-v6="1553:ba3:2::/64"
# docker run -t -i --name c0 ubuntu:latest /bin/bash

下图显示了配置了 IPv6 地址范围的 Docker 桥:

IPv6 支持

如果在容器内部使用ifconfig检查 IP 地址范围,您会注意到适当的子网已分配给eth0接口,如下面的代码所示:

#ifconfig
eth0      Link encap:Ethernet HWaddr 02:42:ac:11:00:01
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:1/64 Scope:Link
          inet6 addr: 1553:ba3:2::242:ac11:1/64 Scope:Global
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:7 errors:0 dropped:0 overruns:0 frame:0
          TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:738 (738.0 B)  TX bytes:836 (836.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

所有流向1553:ba3:2::/64子网的流量将通过docker0接口路由。

前面的容器使用fe80::42:acff:fe11:1/64作为链路本地地址和1553:ba3:2::242:ac11:1/64作为全局可路由的 IPv6 地址。

注意

链路本地和环回地址具有链路本地范围,这意味着它们应该在直接连接的网络(链路)中使用。所有其他地址具有全局(或通用)范围,这意味着它们在全球范围内可路由,并且可以用于连接到任何具有全局范围的地址。

配置 DNS 服务器

Docker 为每个容器提供主机名和 DNS 配置,而无需我们构建自定义镜像。它在容器内部覆盖/etc文件夹,其中可以写入新信息。

通过在容器内运行mount命令可以看到这一点。容器在初始创建时会接收与主机机器相同的resolv.conf文件。如果主机的resolv.conf文件被修改,只有当容器重新启动时,这将反映在容器的/resolv.conf文件中。

在 Docker 中,您可以通过两种方式设置 DNS 选项:

  • 使用docker run --dns=<ip-address>

  • DOCKER_OPTS="--dns ip-address"添加到 Docker 守护程序文件中

您还可以使用--dns-search=<DOMAIN>指定搜索域。

下图显示了在 Docker 守护程序文件中使用DOCKER_OPTS设置在容器中配置nameserver

配置 DNS 服务器

主 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_forward设置为1

要检查设置或手动打开 IP 转发,请使用以下命令:

# 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 = false如何将所有数据包转发到/从容器到/从外部网络:

容器与外部网络之间的通信

Docker 不会删除或修改 Docker 过滤链中的任何现有规则。这允许用户创建规则以限制对容器的访问。

Docker 使用docker0桥来在单个主机上的所有容器之间进行数据包流动。它添加了一个规则,使用 IPTables 转发链,以便数据包在两个容器之间流动。设置--icc=false将丢弃所有数据包。

当 Docker 守护程序配置为--icc=false--iptables=true,并且使用--link选项调用docker run时,Docker 服务器将为新容器插入一对 IPTables 接受规则,以便连接到其他容器暴露的端口,这些端口是在其 Dockerfile 的暴露行中提到的端口。以下图显示了ip_forward = false如何丢弃所有来自/到达外部网络的容器的数据包:

容器与外部网络之间的通信

默认情况下,Docker 的forward规则允许所有外部 IP。要允许只有特定 IP 或网络访问这些容器,插入一个否定规则到 Docker 过滤链的顶部。

例如,使用以下命令,您可以限制外部访问,只有源 IP10.10.10.10可以访问这些容器:

#iptables –I DOCKER –i ext_if ! –s 10.10.10.10 –j DROP

限制一个容器到另一个容器的 SSH 访问

按照以下步骤限制一个容器到另一个容器的 SSH 访问:

  1. 创建两个容器,c1c2

对于c1,使用以下命令:

# 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
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:7 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:738 (738.0 B)  TX bytes:696 (696.0 B)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

对于c2,使用以下命令:

# 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
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:6 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:648 (648.0 B)  TX bytes:696 (696.0 B)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

我们可以使用刚刚发现的 IP 地址测试容器之间的连接。现在让我们使用ping工具来看一下:

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:

  2. 最初,您可以从一个容器 SSH 到另一个容器。

  3. 停止 Docker 服务,并在主机机器的默认 Dockerfile 中添加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。

  1. 我们可以使用--link参数进行容器之间的通信或连接,以下是使用的步骤:

  2. 创建第一个充当服务器的容器sshserver

root@ubuntu:~# docker run -i -t -p 2222:22 --name sshserver ubuntu bash
root@9770be5acbab:/#

  1. 执行iptables命令,您会发现添加了一个 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

  1. 创建第二个充当客户端的容器sshclient
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标志之间容器之间的通信:

限制一个容器到另一个容器的 SSH 访问

  1. 您可以使用docker inspect命令检查已连接的容器:
root@ubuntu:~# docker inspect -f "{{ .HostConfig.Links }}" sshclient
[/sshserver:/sshclient/sshserver]

现在您可以使用其 IP 成功 ssh 到 sshserver。

#ssh root@172.17.0.3 –p 22

使用--link参数,Docker 在容器之间创建一个安全通道,不需要在容器上外部公开任何端口。

配置 Docker 桥

Docker 服务器默认在 Linux 内核中创建一个名为docker0的桥,并且可以在其他物理或虚拟网络接口之间来回传递数据包,使它们表现为单个以太网网络。运行以下命令以查找 VM 中接口的列表以及它们连接到的 IP 地址:

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
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:11909 errors:0 dropped:0 overruns:0 frame:0
 TX packets:14826 errors:0 dropped:0 overruns:0 carrier:0
 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
 inet6 addr: fe80::20c:29ff:fe0d:f42c/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:108865 errors:0 dropped:0 overruns:0 frame:0
 TX packets:31708 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:59902195 (59.9 MB)  TX bytes:3916180 (3.9 MB)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:4 errors:0 dropped:0 overruns:0 frame:0
 TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:336 (336.0 B)  TX bytes:336 (336.0 B)

一旦您有一个或多个容器正在运行,您可以通过在主机上运行brctl命令并查看输出的interfaces列来确认 Docker 已将它们正确连接到docker0桥。

在配置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
 RX packets:7 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:738 (738.0 B)  TX bytes:696 (696.0 B)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
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 提供名为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
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:110823 errors:0 dropped:0 overruns:0 frame:0
 TX packets:33148 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:60081009 (60.0 MB)  TX bytes:4176982 (4.1 MB)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:4 errors:0 dropped:0 overruns:0 frame:0
 TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:336 (336.0 B)  TX bytes:336 (336.0 B)

覆盖网络和底层网络

覆盖是建立在底层网络基础设施(底层)之上的虚拟网络。其目的是实现在物理网络中不可用的网络服务。

网络覆盖大大增加了可以在物理网络之上创建的虚拟子网的数量,从而支持多租户和虚拟化。

Docker 中的每个容器都被分配一个 IP 地址,用于与其他容器通信。如果容器需要与外部网络通信,您可以在主机系统中设置网络,并将容器的端口暴露或映射到主机上。通过这种方式,容器内运行的应用程序将无法广告其外部 IP 和端口,因为这些信息对它们不可用。

解决方案是在所有主机上为每个 Docker 容器分配唯一的 IP,并且有一些网络产品来路由主机之间的流量。

有不同的项目来处理 Docker 网络,如下所示:

  • Flannel

  • Weave

  • Open vSwitch

Flannel 通过为每个容器分配一个 IP 来提供解决方案,用于容器之间的通信。它使用数据包封装,在主机网络上创建一个虚拟覆盖网络。默认情况下,Flannel 为主机提供一个/24子网,Docker 守护程序从中为容器分配 IP。以下图显示了使用 Flannel 进行容器之间通信:

覆盖网络和底层网络

Flannel 在每个主机上运行一个代理flanneld,负责从预配置的地址空间中分配子网租约。Flannel 使用 etcd 存储网络配置、分配的子网和辅助数据(如主机的 IP)。

Flannel 使用通用的 TUN/TAP 设备,并使用 UDP 创建覆盖网络来封装 IP 数据包。子网分配是通过 etcd 的帮助完成的,它维护覆盖子网到主机的映射。

Weave 创建了一个虚拟网络,连接了部署在主机/虚拟机上的 Docker 容器,并实现它们的自动发现。以下图显示了 Weave 网络:

覆盖网络和底层网络

Weave 可以穿越防火墙,在部分连接的网络中运行。流量可以选择加密,允许主机/虚拟机在不受信任的网络中连接。

Weave 增强了 Docker 现有(单个主机)的网络功能,比如docker0桥,因此这些功能可以继续被容器使用。

Open vSwitch 是一个开源的支持 OpenFlow 的虚拟交换机,通常与虚拟化程序一起使用,在主机内部和跨网络的不同主机之间连接虚拟机。覆盖网络需要使用支持的隧道封装来创建虚拟数据路径,例如 VXLAN 和 GRE。

覆盖数据路径是在 Docker 主机中的隧道端点之间进行配置的,这使得在给定提供者段内的所有主机看起来直接连接在一起。

当新容器上线时,前缀会在路由协议中更新,通过隧道端点宣布其位置。当其他 Docker 主机接收到更新时,转发规则会被安装到 OVS 中,用于主机所在的隧道端点。当主机取消配置时,类似的过程会发生,隧道端点 Docker 主机会移除取消配置容器的转发条目。下图显示了通过基于 OVS 的 VXLAN 隧道在多个主机上运行的容器之间的通信:

覆盖网络和底层网络

总结

在本章中,我们讨论了 Docker 的内部网络架构。我们了解了 Docker 中的 IPv4、IPv6 和 DNS 配置。在本章的后面,我们涵盖了 Docker 桥接和单个主机内以及多个主机之间容器之间的通信。

我们还讨论了在 Docker 网络中实施的覆盖隧道和不同的方法,例如 OVS、Flannel 和 Weave。

在下一章中,我们将学习 Docker 网络的实际操作,结合各种框架。

第三章:构建您的第一个 Docker 网络

本章描述了 Docker 网络的实际示例,跨多个主机连接多个容器。我们将涵盖以下主题:

  • Pipework 简介

  • 在多个主机上的多个容器

  • 朝着扩展网络-介绍 Open vSwitch

  • 使用覆盖网络进行网络连接-Flannel

  • Docker 网络选项的比较

Pipework 简介

Pipework 让您在任意复杂的场景中连接容器。

在实际操作中,它创建了一个传统的 Linux 桥接,向容器添加一个新的接口,然后将接口连接到该桥接;容器获得了一个网络段,可以在其中相互通信。

在单个主机上的多个容器

Pipework 是一个 shell 脚本,安装它很简单:

#sudo wget -O /usr/local/bin/pipework https://raw.githubusercontent.com/jpetazzo/pipework/master/pipework && sudo chmod +x /usr/local/bin/pipework

以下图显示了使用 Pipework 进行容器通信:

在单个主机上的多个容器

首先,创建两个容器:

#docker run -i -t --name c1 ubuntu:latest /bin/bash
root@5afb44195a69:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:10
 inet addr:172.17.0.16  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe11:10/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:13 errors:0 dropped:0 overruns:0 frame:0
 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:1038 (1.0 KB)  TX bytes:738 (738.0 B)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

#docker run -i -t --name c2 ubuntu:latest /bin/bash
root@c94d53a76a9b:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:11
 inet addr:172.17.0.17  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe11:11/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:8 errors:0 dropped:0 overruns:0 frame:0
 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:648 (648.0 B)  TX bytes:738 (738.0 B)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

现在让我们使用 Pipework 来连接它们:

#sudo pipework brpipe c1 192.168.1.1/24

此命令在主机上创建一个桥接brpipe。它向容器c1添加一个eth1接口,IP 地址为192.168.1.1,并将接口连接到桥接如下:

root@5afb44195a69:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:10
 inet addr:172.17.0.16  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe11:10/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:13 errors:0 dropped:0 overruns:0 frame:0
 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:1038 (1.0 KB)  TX bytes:738 (738.0 B)
eth1      Link encap:Ethernet  HWaddr ce:72:c5:12:4a:1a
 inet addr:192.168.1.1  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::cc72:c5ff:fe12:4a1a/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:23 errors:0 dropped:0 overruns:0 frame:0
 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:1806 (1.8 KB)  TX bytes:690 (690.0 B)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
#sudo pipework brpipe c2 192.168.1.2/24

此命令不会创建桥接brpipe,因为它已经存在。它将向容器c2添加一个eth1接口,并将其连接到桥接如下:

root@c94d53a76a9b:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:11
 inet addr:172.17.0.17  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe11:11/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:8 errors:0 dropped:0 overruns:0 frame:0
 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:648 (648.0 B)  TX bytes:738 (738.0 B)
eth1      Link encap:Ethernet  HWaddr 36:86:fb:9e:88:ba
 inet addr:192.168.1.2  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::3486:fbff:fe9e:88ba/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:8 errors:0 dropped:0 overruns:0 frame:0
 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:648 (648.0 B)  TX bytes:690 (690.0 B)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

现在容器已连接,将能够相互 ping 通,因为它们在同一个子网192.168.1.0/24上。Pipework 提供了向容器添加静态 IP 地址的优势。

编织您的容器

编织创建了一个虚拟网络,可以连接 Docker 容器跨多个主机,就像它们都连接到一个单一的交换机上一样。编织路由器本身作为一个 Docker 容器运行,并且可以加密路由的流量以通过互联网进行传输。在编织网络上由应用容器提供的服务可以被外部世界访问,无论这些容器在哪里运行。

使用以下代码安装 Weave:

#sudo curl -L git.io/weave -o /usr/local/bin/weave
#sudo chmod a+x /usr/local/bin/weave

以下图显示了使用 Weave 进行多主机通信:

编织您的容器

$HOST1上,我们运行以下命令:

# weave launch
# eval $(weave proxy-env)
# docker run --name c1 -ti ubuntu

接下来,我们在$HOST2上重复类似的步骤:

# weave launch $HOST1
# eval $(weave proxy-env)
# docker run --name c2 -ti ubuntu

$HOST1上启动的容器中,生成以下输出:

root@c1:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:21
 inet addr:172.17.0.33  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe11:21/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:38 errors:0 dropped:0 overruns:0 frame:0
 TX packets:34 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:3166 (3.1 KB)  TX bytes:2299 (2.2 KB)
ethwe     Link encap:Ethernet  HWaddr aa:99:8a:d5:4d:d4
 inet addr:10.128.0.3  Bcast:0.0.0.0  Mask:255.192.0.0
 inet6 addr: fe80::a899:8aff:fed5:4dd4/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:65535  Metric:1
 RX packets:130 errors:0 dropped:0 overruns:0 frame:0
 TX packets:74 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:11028 (11.0 KB)  TX bytes:6108 (6.1 KB)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

您可以使用ifconfig命令查看编织网络接口ethwe

root@c2:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:04
 inet addr:172.17.0.4  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe11:4/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:28 errors:0 dropped:0 overruns:0 frame:0
 TX packets:29 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:2412 (2.4 KB)  TX bytes:2016 (2.0 KB)
ethwe     Link encap:Ethernet  HWaddr 8e:7c:17:0d:0e:03
 inet addr:10.160.0.1  Bcast:0.0.0.0  Mask:255.192.0.0
 inet6 addr: fe80::8c7c:17ff:fe0d:e03/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:65535  Metric:1
 RX packets:139 errors:0 dropped:0 overruns:0 frame:0
 TX packets:74 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:11718 (11.7 KB)  TX bytes:6108 (6.1 KB)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

#root@c1:/# ping -c 1 -q c2
PING c2.weave.local (10.160.0.1) 56(84) bytes of data.
--- c2.weave.local ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.317/1.317/1.317/0.000 ms

同样,在$HOST2上启动的容器中,生成以下输出:

#root@c2:/# ping -c 1 -q c1
PING c1.weave.local (10.128.0.3) 56(84) bytes of data.
--- c1.weave.local ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.658/1.658/1.658/0.000 ms

所以我们有了—两个容器在不同的主机上愉快地交流。

Open vSwitch

Docker 默认使用 Linux 桥docker0。但是,在某些情况下,可能需要使用Open vSwitchOVS)而不是 Linux 桥。单个 Linux 桥只能处理 1024 个端口-这限制了 Docker 的可扩展性,因为我们只能创建 1024 个容器,每个容器只有一个网络接口。

单主机 OVS

现在我们将在单个主机上安装 OVS,创建两个容器,并将它们连接到 OVS 桥。

使用此命令安装 OVS:

# sudo apt-get install openvswitch-switch

使用以下命令安装ovs-docker实用程序:

# cd /usr/bin
# wget https://raw.githubusercontent.com/openvswitch/ovs/master/utilities/ovs-docker
# chmod a+rwx ovs-docker

以下图显示了单主机 OVS:

单主机 OVS

创建 OVS 桥

在这里,我们将添加一个新的 OVS 桥并对其进行配置,以便我们可以在不同的网络上连接容器,如下所示:

# ovs-vsctl add-br ovs-br1
# ifconfig ovs-br1 173.16.1.1 netmask 255.255.255.0 up

将一个端口从 OVS 桥添加到 Docker 容器,使用以下步骤:

  1. 创建两个 Ubuntu Docker 容器:
# docker run -I -t --name container1 ubuntu /bin/bash
# docekr 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
 UP BROADCAST RUNNING MULTICAST  MTU:1472  Metric:1
 RX packets:36 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:4956 (4.9 KB)  TX bytes:648 (648.0 B)

lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

# 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
 UP BROADCAST RUNNING MULTICAST  MTU:1472  Metric:1
 RX packets:27 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:4201 (4.2 KB)  TX bytes:648 (648.0 B)

lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

现在我们知道了container1container2的 IP 地址,我们可以 ping 它们:

# 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 容器。

让我们考虑一下我们的设置,如下图所示,其中包含两个主机,主机 1主机 2,运行 Ubuntu 14.04:

多主机 OVS

在两个主机上安装 Docker 和 Open vSwitch:

# wget -qO- https://get.docker.com/ | sh
# sudo apt-get install openvswitch-switch

安装ovs-docker实用程序:

# cd /usr/bin
# wget https://raw.githubusercontent.com/openvswitch/ovs/master/utilities/ovs-docker
# chmod a+rwx ovs-docker

默认情况下,Docker 选择一个随机网络来运行其容器。它创建一个桥,docker0,并为其分配一个 IP 地址(172.17.42.1)。因此,主机 1主机 2docker0桥 IP 地址相同,这使得两个主机中的容器难以通信。为了克服这个问题,让我们为网络分配静态 IP 地址,即192.168.10.0/24

让我们看看如何更改默认的 Docker 子网。

在主机 1 上执行以下命令:

# 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

添加br0 OVS 桥:

# ovs-vsctl add-br br0

创建到其他主机的隧道并将其附加到:

# add-port br0 gre0 -- set interface gre0 type=gre options:remote_ip=30.30.30.8

br0桥添加到docker0桥:

# brctl addif docker0 br0

在主机 2 上执行以下命令:

# 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

添加br0 OVS 桥:

# ip link set br0 up
# ovs-vsctl add-br br0

创建到其他主机的隧道并将其附加到:

# br0 bridge ovs-vsctl add-port br0 gre0 -- set interface gre0 type=gre options:remote_ip=30.30.30.7

br0桥添加到docker0桥:

# brctl addif docker0 br0

docker0桥连接到另一个桥br0。这次是一个 OVS 桥。这意味着容器之间的所有流量也通过br0路由。

此外,我们需要连接两台主机的网络,容器正在其中运行。为此目的使用 GRE 隧道。该隧道连接到br0 OVS 桥,结果也连接到docker0

在两台主机上执行上述命令后,您应该能够从两台主机上 ping 通docker0桥地址。

在主机 1 上,使用ping命令会生成以下输出:

# 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

在主机 2 上,使用ping命令会生成以下输出:

# 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 上,使用以下代码:

# docker run -t -i --name container1 ubuntu:latest /bin/bash

在主机 2 上,使用以下代码:

# docker run -t -i --name container2 ubuntu:latest /bin/bash

现在我们可以从container1 ping 通container2。通过这种方式,我们使用 Open vSwitch 连接多台主机上的 Docker 容器。

使用覆盖网络进行网络连接 - Flannel

Flannel 是提供给每个主机用于 Docker 容器的子网的虚拟网络层。它与 CoreOS 捆绑在一起,但也可以在其他 Linux OS 上进行配置。Flannel 通过实际连接自身到 Docker 桥来创建覆盖网络,容器连接到该桥,如下图所示。要设置 Flannel,需要两台主机或虚拟机,可以是 CoreOS 或更可取的是 Linux OS,如下图所示:

使用覆盖网络进行网络连接 - Flannel

如果需要,可以从 GitHub 克隆 Flannel 代码并在本地构建,如下所示,可以在不同版本的 Linux OS 上进行。它已经预装在 CoreOS 中:

# git clone https://github.com/coreos/flannel.git
Cloning into 'flannel'...
remote: Counting objects: 2141, done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 2141 (delta 6), reused 0 (delta 0), pack-reused 2122
Receiving objects: 100% (2141/2141), 4.
Checking connectivity... done.

# sudo docker run -v `pwd`:/opt/flannel -i -t google/golang /bin/bash -c "cd /opt/flannel && ./build"
Building flanneld...

可以使用 Vagrant 和 VirtualBox 轻松配置 CoreOS 机器,如下链接中提到的教程:

coreos.com/os/docs/latest/booting-on-vagrant.html

创建并登录到机器后,我们将发现使用etcd配置自动创建了 Flannel 桥:

# ifconfig flannel0
flannel0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1472
 inet 10.1.30.0  netmask 255.255.0.0  destination 10.1.30.0
 unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500 (UNSPEC)
 RX packets 243  bytes 20692 (20.2 KiB)
 RX errors 0  dropped 0  overruns 0  frame 0
 TX packets 304  bytes 25536 (24.9 KiB)
 TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

可以通过查看subnet.env来检查 Flannel 环境:

# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.30.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=true

为了重新实例化 Flannel 桥的子网,需要使用以下命令重新启动 Docker 守护程序:

# source /run/flannel/subnet.env
# sudo rm /var/run/docker.pid
# sudo ifconfig docker0 ${FLANNEL_SUBNET}
# sudo docker -d --bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU} & INFO[0000] [graphdriver] using prior storage driver "overlay"
INFO[0000] Option DefaultDriver: bridge
INFO[0000] Option DefaultNetwork: bridge
INFO[0000] Listening for HTTP on unix (/var/run/docker.sock)
INFO[0000] Firewalld running: false
INFO[0000] Loading containers: start.
..............
INFO[0000] Loading containers: done.
INFO[0000] Daemon has completed initialization
INFO[0000] Docker daemon
commit=cedd534-dirty execdriver=native-0.2 graphdriver=overlay version=1.8.3

也可以通过查看subnet.env来检查第二台主机的 Flannel 环境:

# cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.1.0.0/16
FLANNEL_SUBNET=10.1.31.1/24
FLANNEL_MTU=1472
FLANNEL_IPMASQ=true

为第二台主机分配了不同的子网。也可以通过指向 Flannel 桥来重新启动此主机上的 Docker 服务:

# source /run/flannel/subnet.env
# sudo ifconfig docker0 ${FLANNEL_SUBNET}
# sudo docker -d --bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU} & INFO[0000] [graphdriver] using prior storage driver "overlay"
INFO[0000] Listening for HTTP on unix (/var/run/docker.sock)
INFO[0000] Option DefaultDriver: bridge
INFO[0000] Option DefaultNetwork: bridge
INFO[0000] Firewalld running: false
INFO[0000] Loading containers: start.
....
INFO[0000] Loading containers: done.
INFO[0000] Daemon has completed initialization
INFO[0000] Docker daemon
commit=cedd534-dirty execdriver=native-0.2 graphdriver=overlay version=1.8.3

Docker 容器可以在各自的主机上创建,并且可以使用ping命令进行测试,以检查 Flannel 叠加网络的连通性。

对于主机 1,请使用以下命令:

#docker run -it ubuntu /bin/bash
INFO[0013] POST /v1.20/containers/create
INFO[0013] POST /v1.20/containers/1d1582111801c8788695910e57c02fdba593f443c15e2f1db9174ed9078db809/attach?stderr=1&stdin=1&stdout=1&stream=1
INFO[0013] POST /v1.20/containers/1d1582111801c8788695910e57c02fdba593f443c15e2f1db9174ed9078db809/start
INFO[0013] POST /v1.20/containers/1d1582111801c8788695910e57c02fdba593f443c15e2f1db9174ed9078db809/resize?h=44&w=80

root@1d1582111801:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:0a:01:1e:02
 inet addr:10.1.30.2  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::42:aff:fe01:1e02/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1472  Metric:1
 RX packets:11 errors:0 dropped:0 overruns:0 frame:0
 TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:969 (969.0 B)  TX bytes:508 (508.0 B)
lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

对于主机 2,请使用以下命令:

# docker run -it ubuntu /bin/bash
root@ed070166624a:/# ifconfig
eth0       Link encap:Ethernet  HWaddr 02:42:0a:01:1f:02
 inet addr:10.1.31.2  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::42:aff:fe01:1f02/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1472  Metric:1
 RX packets:18 errors:0 dropped:2 overruns:0 frame:0
 TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:1544 (1.5 KB)  TX bytes:598 (598.0 B)
lo         Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
root@ed070166624a:/# ping 10.1.30.2
PING 10.1.30.2 (10.1.30.2) 56(84) bytes of data.
64 bytes from 10.1.30.2: icmp_seq=1 ttl=60 time=3.61 ms
64 bytes from 10.1.30.2: icmp_seq=2 ttl=60 time=1.38 ms
64 bytes from 10.1.30.2: icmp_seq=3 ttl=60 time=0.695 ms
64 bytes from 10.1.30.2: icmp_seq=4 ttl=60 time=1.49 ms

因此,在上面的例子中,我们可以看到 Flannel 通过在每个主机上运行flanneld代理来减少的复杂性,该代理负责从预配置的地址空间中分配子网租约。Flannel 在内部使用etcd来存储网络配置和其他细节,例如主机 IP 和分配的子网。数据包的转发是使用后端策略实现的。

Flannel 还旨在解决在 GCE 以外的云提供商上部署 Kubernetes 时的问题,Flannel 叠加网格网络可以通过为每个服务器创建一个子网来简化为每个 pod 分配唯一 IP 地址的问题。

总结

在本章中,我们了解了 Docker 容器如何使用不同的网络选项(如 Weave、OVS 和 Flannel)在多个主机之间进行通信。Pipework 使用传统的 Linux 桥接,Weave 创建虚拟网络,OVS 使用 GRE 隧道技术,而 Flannel 为每个主机提供单独的子网,以便将容器连接到多个主机。一些实现,如 Pipework,是传统的,并将随着时间的推移而过时,而其他一些则设计用于在特定操作系统的上下文中使用,例如 Flannel 与 CoreOS。

以下图表显示了 Docker 网络选项的基本比较:

Summary

在下一章中,我们将讨论在使用 Kubernetes、Docker Swarm 和 Mesosphere 等框架时,Docker 容器是如何进行网络连接的。

第四章:Docker 集群中的网络

在本章中,您将学习在使用 Kubernetes、Docker Swarm 和 Mesosphere 等框架时,Docker 容器是如何进行网络化的。

我们将涵盖以下主题:

  • Docker Swarm

  • Kubernetes

  • Kubernetes 集群中的网络化容器

  • Kubernetes 网络与 Docker 网络的不同之处

  • 在 AWS 上的 Kubernetes

  • Mesosphere

Docker Swarm

Docker Swarm 是 Docker 的本地集群系统。Docker Swarm 公开标准的 Docker API,以便与 Docker 守护程序通信的任何工具也可以与 Docker Swarm 通信。基本目标是允许一起创建和使用一组 Docker 主机。Swarm 的集群管理器根据集群中的可用资源调度容器。我们还可以在部署容器时指定受限资源。Swarm 旨在通过将容器打包到主机上来保存其他主机资源,以便为更重和更大的容器而不是将它们随机调度到集群中的主机。

与其他 Docker 项目类似,Docker Swarm 使用即插即用架构。Docker Swarm 提供后端服务来维护您的 Swarm 集群中的 IP 地址列表。有几种服务,如 etcd、Consul 和 Zookeeper;甚至可以使用静态文件。Docker Hub 还提供托管的发现服务,用于 Docker Swarm 的正常配置。

Docker Swarm 调度使用多种策略来对节点进行排名。当创建新容器时,Swarm 根据最高计算出的排名将其放置在节点上,使用以下策略:

  1. Spread:这根据节点上运行的容器数量来优化和调度容器

  2. Binpack:选择节点以基于 CPU 和 RAM 利用率来调度容器

  3. 随机策略:这不使用计算;它随机选择节点来调度容器

Docker Swarm 还使用过滤器来调度容器,例如:

  • Constraints:这些使用与节点关联的键/值对,例如environment=production

  • 亲和力过滤器:这用于运行一个容器,并指示它基于标签、镜像或标识符定位并运行在另一个容器旁边

  • 端口过滤器:在这种情况下,选择节点是基于其上可用的端口

  • 依赖过滤器:这会在同一节点上协同调度依赖容器

  • 健康过滤器:这可以防止在不健康的节点上调度容器

以下图解释了 Docker Swarm 集群的各个组件:

Docker Swarm

Docker Swarm 设置

让我们设置我们的 Docker Swarm 设置,其中将有两个节点和一个主节点。

我们将使用 Docker 客户端来访问 Docker Swarm 集群。Docker 客户端可以在一台机器或笔记本上设置,并且应该可以访问 Swarm 集群中的所有机器。

在所有三台机器上安装 Docker 后,我们将从命令行重新启动 Docker 服务,以便可以从本地 TCP 端口 2375(0.0.0.0:2375)或特定主机 IP 地址访问,并且可以使用 Unix 套接字在所有 Swarm 节点上允许连接,如下所示:

$ docker -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock –d &

Docker Swarm 镜像需要部署为 Docker 容器在主节点上。在我们的示例中,主节点的 IP 地址是192.168.59.134。请将其替换为您的 Swarm 主节点。从 Docker 客户端机器上,我们将使用以下命令在主节点上安装 Docker Swarm:

$ sudo docker -H tcp://192.168.59.134:2375 run --rm swarm create
Unable to find image 'swarm' locally
Pulling repository swarm
e12f8c5e4c3b: Download complete
cf43a42a05d1: Download complete
42c4e5c90ee9: Download complete
22cf18566d05: Download complete
048068586dc5: Download complete
2ea96b3590d8: Download complete
12a239a7cb01: Download complete
26b910067c5f: Download complete
4fdfeb28bd618291eeb97a2096b3f841

在执行命令后生成的 Swarm 令牌应予以注意,因为它将用于 Swarm 设置。在我们的案例中,它是这样的:

"4fdfeb28bd618291eeb97a2096b3f841"

以下是设置两节点 Docker Swarm 集群的步骤:

  1. 从 Docker 客户端节点,需要执行以下docker命令,使用 Node 1 的 IP 地址(在我们的案例中为192.168.59.135)和在前面的代码中生成的 Swarm 令牌,以便将其添加到 Swarm 集群中:
$ docker -H tcp://192.168.59.135:2375 run -d swarm join --addr=192.168.59.135:2375 token:// 4fdfeb28bd618291eeb97a2096b3f841
Unable to find image 'swarm' locally
Pulling repository swarm
e12f8c5e4c3b: Download complete
cf43a42a05d1: Download complete
42c4e5c90ee9: Download complete
22cf18566d05: Download complete
048068586dc5: Download complete
2ea96b3590d8: Download complete
12a239a7cb01: Download complete
26b910067c5f: Download complete
e4f268b2cc4d896431dacdafdc1bb56c98fed01f58f8154ba13908c7e6fe675b

  1. 通过用 Node 2 的 IP 地址替换 Node 1 的 IP 地址,重复上述步骤来为 Node 2 执行相同的操作。

  2. 需要在 Docker 客户端节点上使用以下命令在主节点上设置 Swarm 管理器:

$ sudo docker -H tcp://192.168.59.134:2375 run -d -p 5001:2375 swarm manage token:// 4fdfeb28bd618291eeb97a2096b3f841
f06ce375758f415614dc5c6f71d5d87cf8edecffc6846cd978fe07fafc3d05d3

Swarm 集群已设置,并且可以使用驻留在主节点上的 Swarm 管理器进行管理。要列出所有节点,可以使用 Docker 客户端执行以下命令:

$ sudo docker -H tcp://192.168.59.134:2375 run --rm swarm list \ token:// 4fdfeb28bd618291eeb97a2096b3f841
192.168.59.135:2375
192.168.59.136:2375

  1. 以下命令可用于获取有关集群的信息:
$ sudo docker -H tcp://192.168.59.134:5001 info
Containers: 0
Strategy: spread
Filters: affinity, health, constraint, port, dependency
Nodes: 2
agent-1: 192.168.59.136:2375
 └ Containers: 0
 └ Reserved CPUs: 0 / 8
 └ Reserved Memory: 0 B / 1.023 GiB
 agent-0: 192.168.59.135:2375
 └ Containers: 0
 └ Reserved CPUs: 0 / 8
 └ Reserved Memory: 0 B / 1.023 GiB

  1. 可以通过指定名称为swarm-ubuntu并使用以下命令,在集群上启动测试ubuntu容器:
$ sudo docker -H tcp://192.168.59.134:5001 run -it --name swarm-ubuntu ubuntu /bin/sh

  1. 可以使用 Swarm 主节点的 IP 地址列出容器:
$ sudo docker -H tcp://192.168.59.134:5001 ps

这样就完成了两节点 Docker Swarm 集群的设置。

Docker Swarm 网络设置

Docker Swarm 网络与 libnetwork 集成,甚至支持覆盖网络。libnetwork 提供了一个 Go 实现来连接容器;它是一个强大的容器网络模型,为应用程序和容器的编程接口提供网络抽象。Docker Swarm 现在完全兼容 Docker 1.9 中的新网络模型(请注意,我们将在以下设置中使用 Docker 1.9)。覆盖网络需要键值存储,其中包括发现、网络、IP 地址和更多信息。

在以下示例中,我们将使用 Consul 更好地了解 Docker Swarm 网络:

  1. 我们将使用docker-machine提供一个名为sample-keystore的 VirtualBox 机器:
$ docker-machine create -d virtualbox sample-keystore
Running pre-create checks...
Creating machine...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Provisioning created instance...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
To see how to connect Docker to this machine, run: docker-machine.exe env sample-keystore

  1. 我们还将在sample-keystore机器上使用以下命令在端口8500部署progrium/consul容器:
$ docker $(docker-machine config sample-keystore) run -d \
 -p "8500:8500" \
 -h "consul" \
 progrium/consul -server –bootstrap
Unable to find image 'progrium/consul:latest' locally
latest: Pulling from progrium/consul
3b4d28ce80e4: Pull complete
e5ab901dcf2d: Pull complete
30ad296c0ea0: Pull complete
3dba40dec256: Pull complete
f2ef4387b95e: Pull complete
53bc8dcc4791: Pull complete
75ed0b50ba1d: Pull complete
17c3a7ed5521: Pull complete
8aca9e0ecf68: Pull complete
4d1828359d36: Pull complete
46ed7df7f742: Pull complete
b5e8ce623ef8: Pull complete
049dca6ef253: Pull complete
bdb608bc4555: Pull complete
8b3d489cfb73: Pull complete
c74500bbce24: Pull complete
9f3e605442f6: Pull complete
d9125e9e799b: Pull complete
Digest: sha256:8cc8023462905929df9a79ff67ee435a36848ce7a10f18d6d0faba9306b97274
Status: Downloaded newer image for progrium/consul:latest
1a1be5d207454a54137586f1211c02227215644fa0e36151b000cfcde3b0df7c

  1. 将本地环境设置为sample-keystore机器:
$ eval "$(docker-machine env sample-keystore)"

  1. 我们可以按以下方式列出 consul 容器:
$ docker ps
CONTAINER ID       IMAGE           COMMAND           CREATED       STATUS        PORTS                                 NAMES
1a1be5d20745   progrium/consul  /bin/start -server  5 minutes ago  Up 5 minutes   53/tcp, 53/udp, 8300-8302/tcp, 8400/tcp, 8301-8302/udp, 0.0.0.0:8500->8500/tcp   cocky_bhaskara

  1. 使用docker-machine创建 Swarm 集群。两台机器可以在 VirtualBox 中创建;一台可以充当 Swarm 主节点。在创建每个 Swarm 节点时,我们将传递 Docker Engine 所需的选项以具有覆盖网络驱动程序:
$ docker-machine create -d virtualbox --swarm --swarm-image="swarm" --swarm-master --swarm-discovery="consul://$(docker-machine ip sample-keystore):8500" --engine-opt="cluster-store=consul://$(docker-machine ip sample-keystore):8500" --engine-opt="cluster-advertise=eth1:2376" swarm-master
Running pre-create checks...
Creating machine...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Provisioning created instance...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Configuring swarm...
To see how to connect Docker to this machine, run: docker-machine env swarm-master

在前面的命令中使用的参数如下:

  • --swarm:用于配置具有 Swarm 的机器。

  • --engine-opt:此选项用于定义必须提供的任意守护程序选项。在我们的情况下,我们将在创建时使用--cluster-store选项,告诉引擎覆盖网络可用性的键值存储的位置。--cluster-advertise选项将在特定端口将机器放入网络中。

  • --swarm-discovery:用于发现与 Swarm 一起使用的服务,在我们的情况下,consul将是该服务。

  • --swarm-master:用于将机器配置为 Swarm 主节点。

  1. 还可以创建另一个主机并将其添加到 Swarm 集群,就像这样:
$ docker-machine create -d virtualbox --swarm --swarm-image="swarm:1.0.0-rc2" --swarm-discovery="consul://$(docker-machine ip sample-keystore):8500" --engine-opt="cluster-store=consul://$(docker-machine ip sample-keystore):8500" --engine-opt="cluster-advertise=eth1:2376" swarm-node-1
Running pre-create checks...
Creating machine...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Provisioning created instance...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Configuring swarm...
To see how to connect Docker to this machine, run: docker-machine env swarm-node-1

  1. 可以按以下方式列出机器:
$ docker-machine ls
NAME            ACTIVE   DRIVER       STATE     URL               SWARM
sample-keystore   -     virtualbox   Running   tcp://192.168.99.100:2376
swarm-master      -     virtualbox   Running   tcp://192.168.99.101:2376  swarm-master (master)
swarm-node-1      -     virtualbox   Running   tcp://192.168.99.102:2376   swarm-master

  1. 现在,我们将将 Docker 环境设置为swarm-master
$ eval $(docker-machine env --swarm swarm-master)

  1. 可以在主节点上执行以下命令以创建覆盖网络并实现多主机网络:
$ docker network create –driver overlay sample-net

  1. 可以使用以下命令在主节点上检查网络桥:
$ docker network ls
NETWORK ID         NAME           DRIVER
9f904ee27bf5      sample-net      overlay
7fca4eb8c647       bridge         bridge
b4234109be9b       none            null
cf03ee007fb4       host            host

  1. 切换到 Swarm 节点时,我们可以轻松地列出新创建的覆盖网络,就像这样:
$ eval $(docker-machine env swarm-node-1)
$ docker network ls
NETWORK ID        NAME            DRIVER
7fca4eb8c647      bridge          bridge
b4234109be9b      none             null
cf03ee007fb4      host            host
9f904ee27bf5     sample-net       overlay

  1. 创建网络后,我们可以在任何主机上启动容器,并且它将成为网络的一部分:
$ eval $(docker-machine env swarm-master)

  1. 使用约束环境设置为第一个节点启动示例ubuntu容器:
$ docker run -itd --name=os --net=sample-net --env="constraint:node==swarm-master" ubuntu

  1. 我们可以使用ifconfig命令检查容器是否有两个网络接口,并且可以通过 Swarm 管理器部署的容器在任何其他主机上都可以访问。

Kubernetes

Kubernetes 是一个容器集群管理工具。目前,它支持 Docker 和 Rocket。这是一个由 Google 支持的开源项目,于 2014 年 6 月在 Google I/O 上推出。它支持在各种云提供商上部署,如 GCE、Azure、AWS 和 vSphere,以及在裸机上部署。Kubernetes 管理器是精简的、可移植的、可扩展的和自愈的。

Kubernetes 有各种重要组件,如下列表所述:

  • Node:这是 Kubernetes 集群的物理或虚拟机部分,运行 Kubernetes 和 Docker 服务,可以在其上调度 pod。

  • Master:这维护 Kubernetes 服务器运行时的运行状态。这是所有客户端调用的入口点,用于配置和管理 Kubernetes 组件。

  • Kubectl:这是用于与 Kubernetes 集群交互的命令行工具,以提供对 Kubernetes API 的主访问权限。通过它,用户可以部署、删除和列出 pod。

  • Pod:这是 Kubernetes 中最小的调度单元。它是一组共享卷且没有端口冲突的 Docker 容器集合。可以通过定义一个简单的 JSON 文件来创建它。

  • 复制控制器:它管理 pod 的生命周期,并确保在给定时间运行指定数量的 pod,通过根据需要创建或销毁 pod。

  • 标签:标签用于基于键值对识别和组织 pod 和服务。

以下图表显示了 Kubernetes Master/Minion 流程:

Kubernetes

在 AWS 上部署 Kubernetes

让我们开始在 AWS 上部署 Kubernetes 集群,可以使用 Kubernetes 代码库中已经存在的配置文件来完成:

  1. aws.amazon.com/console/上登录 AWS 控制台。

  2. console.aws.amazon.com/iam/home?#home上打开 IAM 控制台。

  3. 选择 IAM 用户名,选择安全凭证选项卡,然后单击创建访问密钥选项。

  4. 创建密钥后,下载并保存在安全的地方。下载的.csv文件将包含访问密钥 ID秘密访问密钥,这将用于配置 AWS CLI。

  5. 安装并配置 AWS CLI。在本例中,我们使用以下命令在 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. 可以使用控制台或 AWS CLI 单独创建角色,并使用定义角色权限的 JSON 文件创建角色:
$ aws iam create-role --role-name Test-Role --assume-role-policy-document /root/kubernetes/Test-Role-Trust-Policy.json

可以将角色附加到上述配置文件,该配置文件将完全访问 EC2 和 S3,如下截图所示:

在 AWS 上部署 Kubernetes

  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 OS 的t2.micro。该过程需要 5 到 10 分钟,之后主节点和从节点的 IP 地址将被列出,并可用于访问 Kubernetes 集群。

Kubernetes 网络及其与 Docker 网络的区别

Kubernetes 偏离了默认的 Docker 系统网络模型。其目标是使每个 pod 具有由系统管理命名空间赋予的 IP,该 IP 与系统上的其他物理机器和容器具有完全对应关系。为每个 pod 单元分配 IP 可以创建一个清晰、向后兼容且良好的模型,在这个模型中,可以像处理 VM 或物理主机一样处理单元,从端口分配、系统管理、命名、管理披露、负载平衡、应用程序设计以及从一个主机迁移到另一个主机的 pod 迁移的角度来看。所有 pod 中的所有容器都可以使用它们的地址与所有其他 pod 中的所有其他容器进行通信。这也有助于将传统应用程序转移到面向容器的方法。

由于每个 pod 都有一个真实的 IP 地址,它们可以在彼此之间进行通信,无需进行任何翻译。通过在 pod 内外进行相同的 IP 地址和端口配置,我们可以创建一个无 NAT 的扁平地址空间。这与标准的 Docker 模型不同,因为在那里,所有容器都有一个私有 IP 地址,这将使它们能够访问同一主机上的容器。但在 Kubernetes 的情况下,pod 内的所有容器都表现得好像它们在同一台主机上,并且可以在本地主机上访问彼此的端口。这减少了容器之间的隔离,并提供了简单性、安全性和性能。端口冲突可能是其中的一个缺点;因此,一个 pod 内的两个不同容器不能使用相同的端口。

在 GCE 中,使用 IP 转发和高级路由规则,Kubernetes 集群中的每个 VM 都会额外获得 256 个 IP 地址,以便轻松地在 pod 之间路由流量。

GCE 中的路由允许您在 VM 中实现更高级的网络功能,比如设置多对一 NAT。这被 Kubernetes 所利用。

除了虚拟机具有的主要以太网桥之外,还有一个容器桥cbr0,以区分它与 Docker 桥docker0。为了将 pod 中的数据包传输到 GCE 环境之外,它应该经历一个 SNAT 到虚拟机的 IP 地址,这样 GCE 才能识别并允许。

其他旨在提供 IP-per-pod 模型的实现包括 Open vSwitch、Flannel 和 Weave。

在类似 GCE 的 Open vSwitch 桥的 Kubernetes 设置中,采用了将 Docker 桥替换为kbr0以提供额外的 256 个子网地址的模型。此外,还添加了一个 OVS 桥(ovs0),它向 Kubernetes 桥添加了一个端口,以便提供 GRE 隧道来传输不同 minions 上的 pod 之间的数据包。IP-per-pod 模型也在即将出现的图表中有更详细的解释,其中还解释了 Kubernetes 的服务抽象概念。

服务是另一种广泛使用并建议在 Kubernetes 集群中使用的抽象类型,因为它允许一组 pod(应用程序)通过虚拟 IP 地址访问,并且被代理到服务中的所有内部 pod。在 Kubernetes 中部署的应用程序可能使用三个相同 pod 的副本,它们具有不同的 IP 地址。但是,客户端仍然可以访问外部公开的一个 IP 地址上的应用程序,而不管哪个后端 pod 接受请求。服务充当不同副本 pod 之间的负载均衡器,并且对于使用此应用程序的客户端来说是通信的单一点。Kubernetes 的服务之一 Kubeproxy 提供负载均衡,并使用规则访问服务 IP 并将其重定向到正确的后端 pod。

部署 Kubernetes pod

现在,在以下示例中,我们将部署两个 nginx 复制 pod(rc-pod)并通过服务公开它们,以便了解 Kubernetes 网络。决定应用程序可以通过虚拟 IP 地址公开以及请求应该代理到哪个 pod 副本(负载均衡器)由服务代理负责。有关更多详细信息,请参考以下图表:

部署 Kubernetes pod

部署 Kubernetes pod 的步骤如下:

  1. 在 Kubernetes 主节点上,创建一个新文件夹:
$ mkdir nginx_kube_example
$ cd nginx_kube_example

  1. 在您选择的编辑器中,创建将用于部署 nginx pod 的.yaml文件:
$ 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命令:

$ 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 create命令创建 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

Mesosphere

Mesosphere 是一个软件解决方案,提供了管理服务器基础设施的方法,并基本上扩展了 Apache Mesos 的集群管理能力。Mesosphere 还推出了DCOS数据中心操作系统),用于通过将所有机器跨越并将它们视为单台计算机来管理数据中心,提供了一种高度可扩展和弹性的部署应用程序的方式。DCOS 可以安装在任何公共云或您自己的私有数据中心,从 AWS、GCE 和 Microsoft Azure 到 VMware。Marathon 是 Mesos 的框架,旨在启动和运行应用程序;它用作 init 系统的替代品。Marathon 提供了诸如高可用性、应用程序健康检查和服务发现等各种功能,帮助您在 Mesos 集群环境中运行应用程序。

本节描述了如何启动单节点 Mesos 集群。

Docker 容器

Mesos 可以使用 Marathon 框架来运行和管理 Docker 容器。

在本练习中,我们将使用 CentOS 7 来部署 Mesos 集群。

  1. 使用以下命令安装 Mesosphere 和 Marathon:
# sudo rpm -Uvh http://repos.mesosphere.com/el/7/noarch/RPMS/mesosphere-el-repo-7-1.noarch.rpm
# sudo yum -y install mesos marathon

Apache Mesos 使用 Zookeeper 进行操作。Zookeeper 在 Mesosphere 架构中充当主选举服务,并为 Mesos 节点存储状态。

  1. 通过指向 Zookeeper 的 RPM 存储库来安装 Zookeeper 和 Zookeeper 服务器包,如下所示:
# sudo rpm -Uvh http://archive.cloudera.com/cdh4/one-click-install/redhat/6/x86_64/cloudera-cdh-4-0.x86_64.rpm
# sudo yum -y install zookeeper zookeeper-server

  1. 通过停止和重新启动 Zookeeper 来验证 Zookeeper:
# sudo service zookeeper-server stop
# sudo service zookeeper-server start

Mesos 使用简单的架构,在集群中智能地分配任务,而不用担心它们被安排在哪里。

  1. 通过启动mesos-mastermesos-slave进程来配置 Apache Mesos,如下所示:
# sudo service mesos-master start
# sudo service mesos-slave start

  1. Mesos 将在端口5050上运行。如下截图所示,您可以使用您机器的 IP 地址访问 Mesos 界面,这里是http://192.168.10.10:5050Docker containers

  2. 使用mesos-execute命令测试 Mesos:

# export MASTER=$(mesos-resolve `cat /etc/mesos/zk` 2>/dev/null)
# mesos help
# mesos-execute --master=$MASTER --name="cluster-test" --command="sleep 40"

  1. 运行mesos-execute命令后,输入Ctrl + Z以暂停命令。您可以看到它在 Web UI 和命令行中的显示方式:
# hit ctrl-z
# mesos ps --master=$MASTER

Mesosphere 堆栈使用 Marathon 来管理进程和服务。它用作传统 init 系统的替代品。它简化了在集群环境中运行应用程序。下图显示了带有 Marathon 的 Mesosphere 主从拓扑结构:

Docker containers

Marathon 可以用来启动其他 Mesos 框架;因为它设计用于长时间运行的应用程序,它将确保它启动的应用程序即使在它们运行的从节点失败时也会继续运行。

  1. 使用以下命令启动 Marathon 服务:
# sudo service marathon start

您可以在http://192.168.10.10:8080上查看 Marathon GUI。

使用 Docker 部署 web 应用

在这个练习中,我们将安装一个简单的 Outyet web 应用程序:

  1. 使用以下命令安装 Docker:
# sudo yum install -y golang git device-mapper-event-libs docker
# sudo chkconfig docker on
# sudo service docker start
# export GOPATH=~/go
# go get github.com/golang/example/outyet
# cd $GOPATH/src/github.com/golang/example/outyet
# sudo docker build -t outyet.

  1. 在将其添加到 Marathon 之前,使用以下命令测试 Docker 文件:
# sudo docker run --publish 6060:8080 --name test --rm outyet

  1. 在浏览器中转到http://192.168.10.10:6060/以确认它是否正常工作。一旦确认,您可以按下CTRL + C退出 Outyet Docker。

  2. 使用 Marathon Docker 支持创建 Marathon 应用程序,如下所示:

# vi /home/user/outyet.json
{
 "id": "outyet",
 "cpus": 0.2,
 "mem": 20.0,
 "instances": 1,
 "constraints": [["hostname", "UNIQUE", ""]],
 "container": {
 "type": "DOCKER",
 "docker": {
 "image": "outyet",
 "network": "BRIDGE",
 "portMappings": [ { "containerPort": 8080, "hostPort": 0, "servicePort": 0, "protocol": "tcp" }
 ]
 }
 }
}

# echo 'docker,mesos' | sudo tee /etc/mesos-slave/containerizers
# sudo service mesos-slave restart

  1. 使用 Marathon Docker 更好地配置和管理容器,如下所示:
# curl -X POST http://192.168.10.10:8080/v2/apps -d /home/user/outyet.json -H "Content-type: application/json"

  1. 您可以在 Marathon GUI 上检查所有应用程序,如下截图所示,网址为http://192.168.10.10:8080使用 Docker 部署 web 应用

使用 DCOS 在 AWS 上部署 Mesos

在最后一节中,我们将在 AWS 上部署 Mesosphere 的最新版本 DCOS,以便在我们的数据中心管理和部署 Docker 服务:

  1. 通过转到导航窗格并在网络和安全下选择密钥对来在需要部署集群的区域创建 AWS 密钥对:在 AWS 上使用 DCOS 部署 Mesos

  2. 创建后,可以按以下方式查看密钥,并应将生成的密钥对(.pem)文件存储在安全位置以备将来使用:在 AWS 上使用 DCOS 部署 Mesos

  3. 可以通过在官方 Mesosphere 网站上选择1 Master模板来创建 DCOS 集群:在 AWS 上使用 DCOS 部署 Mesos

也可以通过在堆栈部署中提供亚马逊 S3 模板 URL 的链接来完成:

在 AWS 上使用 DCOS 部署 Mesos

  1. 点击下一步按钮。填写诸如堆栈名称密钥名称之类的细节,这些细节是在上一步中生成的:在 AWS 上使用 DCOS 部署 Mesos

  2. 在点击创建按钮之前,请查看细节:在 AWS 上使用 DCOS 部署 Mesos

  3. 5 到 10 分钟后,Mesos 堆栈将被部署,并且可以在以下截图中显示的 URL 上访问 Mesos UI:在 AWS 上使用 DCOS 部署 Mesos

  4. 现在,我们将在预先安装了 Python(2.7 或 3.4)和 pip 的 Linux 机器上安装 DCOS CLI,使用以下命令:

$ sudo pip install virtualenv
$ mkdir dcos
$ cd dcos
$ curl -O https://downloads.mesosphere.io/dcos-cli/install.sh
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
 Dload  Upload   Total   Spent    Left  Speed
100  3654  100  3654    0     0   3631      0  0:00:01  0:00:01 --:--:--  3635
$ ls
install.sh
$ bash install.sh . http://mesos-dco-elasticl-17lqe4oh09r07-1358461817.us-west-1.elb.amazonaws.com
Installing DCOS CLI from PyPI...
New python executable in /home/vkohli/dcos/bin/python
Installing setuptools, pip, wheel...done.
[core.reporting]: set to 'True'
[core.dcos_url]: set to 'http://mesos-dco-elasticl-17lqe4oh09r07-1358461817.us-west-1.elb.amazonaws.com'
[core.ssl_verify]: set to 'false'
[core.timeout]: set to '5'
[package.cache]: set to '/home/vkohli/.dcos/cache'
[package.sources]: set to '[u'https://github.com/mesosphere/universe/archive/version-1.x.zip']'
Go to the following link in your browser:
https://accounts.mesosphere.com/oauth/authorize?scope=&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&client_id=6a552732-ab9b-410d-9b7d-d8c6523b09a1&access_type=offline
Enter verification code: Skipping authentication.
Enter email address: Skipping email input.
Updating source [https://github.com/mesosphere/universe/archive/version-1.x.zip]
Modify your bash profile to add DCOS to your PATH? [yes/no]  yes
Finished installing and configuring DCOS CLI.
Run this command to set up your environment and to get started:
source ~/.bashrc && dcos help

DCOS 帮助文件可以列出如下:

$ source ~/.bashrc && dcos help
Command line utility for the Mesosphere Datacenter Operating System (DCOS). The Mesosphere DCOS is a distributed operating system built around Apache Mesos. This utility provides tools for easy management of a DCOS installation.
Available DCOS commands:

 config       Get and set DCOS CLI configuration properties
 help         Display command line usage information
 marathon     Deploy and manage applications on the DCOS
 node         Manage DCOS nodes
 package      Install and manage DCOS packages
 service      Manage DCOS services
 task         Manage DCOS tasks

  1. 现在,我们将使用 DCOS 包在 Mesos 集群上部署一个 Spark 应用程序,然后更新它。使用dcos <command> --help获取详细的命令描述:
$ dcos config show package.sources
[
 "https://github.com/mesosphere/universe/archive/version-1.x.zip"
]
$ dcos package update
Updating source [https://github.com/mesosphere/universe/archive/version-1.x.zip]

$ dcos package search
NAME       VERSION            FRAMEWORK     SOURCE             DESCRIPTION
arangodb   0.2.1                True     https://github.com/mesosphere/universe/archive/version-1.x.zip   A distributed free and open-source database with a flexible data model for documents, graphs, and key-values. Build high performance applications using a convenient SQL-like query language or JavaScript extensions.
cassandra  0.2.0-1               True     https://github.com/mesosphere/universe/archive/version-1.x.zip  Apache Cassandra running on Apache Mesos.
chronos    2.4.0                 True     https://github.com/mesosphere/universe/archive/version-1.x.zip  A fault tolerant job scheduler for Mesos which handles dependencies and ISO8601 based schedules.
hdfs       0.1.7                 True     https://github.com/mesosphere/universe/archive/version-1.x.zip  Hadoop Distributed File System (HDFS), Highly Available.
kafka      0.9.2.0               True     https://github.com/mesosphere/universe/archive/version-1.x.zip  Apache Kafka running on top of Apache Mesos.
marathon   0.11.1                True     https://github.com/mesosphere/universe/archive/version-1.x.zip  A cluster-wide init and control system for services in cgroups or Docker containers.
spark      1.5.0-multi-roles-v2  True     https://github.com/mesosphere/universe/archive/version-1.x.zip  Spark is a fast and general cluster computing system for Big Data.

  1. Spark 包可以按以下方式安装:
$ dcos package install spark
Note that the Apache Spark DCOS Service is beta and there may be bugs, incomplete features, incorrect documentation or other discrepancies.
We recommend a minimum of two nodes with at least 2 CPU and 2GB of RAM available for the Spark Service and running a Spark job.
Note: The Spark CLI may take up to 5min to download depending on your connection.
Continue installing? [yes/no] yes
Installing Marathon app for package [spark] version [1.5.0-multi-roles-v2]
Installing CLI subcommand for package [spark] version [1.5.0-multi-roles-v2]

  1. 部署后,可以在 DCOS UI 的Services选项卡下看到,如下图所示:在 AWS 上使用 DCOS 部署 Mesos

  2. 为了在前面的 Marathon 集群上部署一个虚拟的 Docker 应用程序,我们可以使用 JSON 文件来定义容器映像、要执行的命令以及部署后要暴露的端口:

$ nano definition.json
{
 "container": {
 "type": "DOCKER",
 "docker": {
 "image": "superguenter/demo-app"
 }
 },
 "cmd":  "python -m SimpleHTTPServer $PORT",
 "id": "demo",
 "cpus": 0.01,
 "mem": 256,
 "ports": [3000]
}

  1. 应用程序可以添加到 Marathon 并列出如下:
$ dcos marathon app add definition.json
$ dcos marathon app list
ID       MEM    CPUS  TASKS  HEALTH  DEPLOYMENT  CONTAINER  CMD
/demo   256.0   0.01   1/1    ---       ---        DOCKER   python -m SimpleHTTPServer $PORT
/spark  1024.0  1.0    1/1    1/1       ---        DOCKER   mv /mnt/mesos/sandbox/log4j.properties conf/log4j.properties && ./bin/spark-class org.apache.spark.deploy.mesos.MesosClusterDispatcher --port $PORT0 --webui-port $PORT1 --master mesos://zk://master.mesos:2181/mesos --zk master.mesos:2181 --host $HOST --name spark

  1. 可以按以下方式启动前面的 Docker 应用程序的三个实例:
$ dcos marathon app update --force demo instances=3
Created deployment 28171707-83c2-43f7-afa1-5b66336e36d7
$ dcos marathon deployment list
APP    ACTION  PROGRESS  ID
/demo  scale     0/1     28171707-83c2-43f7-afa1-5b66336e36d7

  1. 通过单击Services下的Tasks选项卡,可以在 DCOS UI 中看到部署的应用程序:在 AWS 上使用 DCOS 部署 Mesos

摘要

在本章中,我们学习了使用各种框架的 Docker 网络,例如本地 Docker Swarm。使用 libnetwork 或开箱即用的覆盖网络,Swarm 提供了多主机网络功能。

另一方面,Kubernetes 与 Docker 有不同的视角,其中每个 pod 都有一个独特的 IP 地址,并且可以通过服务的帮助在 pod 之间进行通信。使用 Open vSwitch 或 IP 转发和高级路由规则,Kubernetes 网络可以得到增强,以提供在不同子网上的主机之间以及将 pod 暴露给外部世界的连接能力。在 Mesosphere 的情况下,我们可以看到 Marathon 被用作部署容器的网络的后端。在 Mesosphere 的 DCOS 的情况下,整个部署的机器堆栈被视为一个机器,以提供在部署的容器服务之间丰富的网络体验。

在下一章中,我们将通过了解内核命名空间、cgroups 和虚拟防火墙,学习有关基本 Docker 网络的安全性和 QoS。

第五章:Docker 容器的安全性和 QoS

在本章中,我们将学习安全性是如何在容器的上下文中实现的,以及如何实施 QoS 策略以确保 CPU 和 IO 等资源按预期共享。大部分讨论将集中在这些主题在 Docker 上下文中的相关性。

在本章中,我们将涵盖以下内容:

  • 文件系统限制

  • 只读挂载点

  • 写时复制

  • Linux 功能和 Docker

  • 在 AWS ECS(EC2 容器服务)中保护容器

  • 理解 Docker 安全性 I - 内核命名空间

  • 理解 Docker 安全性 II - cgroups

  • 使用 AppArmour 来保护 Docker 容器

  • Docker 安全基准

文件系统限制

在本节中,我们将研究 Docker 容器启动时的文件系统限制。下一节解释了只读挂载点和写时复制文件系统,这些文件系统被用作 Docker 容器的基础和内核对象的表示。

只读挂载点

Docker 需要访问文件系统,如 sysfs 和 proc,以使进程正常运行。但它不一定需要修改这些挂载点。

两个主要的只读挂载点是:

  • /sys

  • /proc

sysfs

sysfs 文件系统加载到挂载点/sys中。sysfs 是一种表示内核对象、它们的属性和它们之间关系的机制。它提供了两个组件:

  • 用于通过 sysfs 导出这些项目的内核编程接口

  • 一个用户界面,用于查看和操作这些项目,它映射回它们所代表的内核对象

以下代码显示了挂载的挂载点:

{
  Source:      "sysfs",
  Destination: "/sys",
  Device:      "sysfs",
  Flags:       defaultMountFlags | syscall.MS_RDONLY,
},

上述代码的参考链接在github.com/docker/docker/blob/ecc3717cb17313186ee711e624b960b096a9334f/daemon/execdriver/native/template/default_template_linux.go

procfs

proc 文件系统(procfs)是 Unix-like 操作系统中的一个特殊文件系统,它以分层文件样式的结构呈现有关进程和其他系统信息的信息。它加载到/proc中。它提供了一个更方便和标准化的方法来动态访问内核中保存的进程数据,而不是传统的跟踪方法或直接访问内核内存。它在引导时映射到名为/proc的挂载点:

{
  Source:      "proc",
  Destination: "/proc",
  Device:      "proc",
  Flags:       defaultMountFlags,
},

使用/proc的只读路径:

ReadonlyPaths: []string{
  "/proc/asound",
  "/proc/bus",
  "/proc/fs",
  "/proc/irq",
  "/proc/sys",
  "/proc/sysrq-trigger",
}

/dev/pts

这是另一个在创建过程中作为读写挂载的挂载点。/dev/pts完全存在于内存中,没有任何内容存储在磁盘上,因此可以安全地以读写模式加载它。

/dev/pts中的条目是伪终端(简称 pty)。Unix 内核有终端的通用概念。终端提供了应用程序通过终端设备显示输出和接收输入的方式。一个进程可能有一个控制终端。对于文本模式应用程序,这是它与用户交互的方式:

{
  Source:      "devpts",
  Destination: "/dev/pts",
  Device:      "devpts",
  Flags:       syscall.MS_NOSUID | syscall.MS_NOEXEC,
  Data:        "newinstance,ptmxmode=0666,mode=0620,gid=5",
},

/sys/fs/cgroup

这是 cgroups 实现的挂载点,并且在容器中加载为MS_RDONLY

{
  Source:      "cgroup",
  Destination: "/sys/fs/cgroup",
  Device:      "cgroup",
  Flags:       defaultMountFlags | syscall.MS_RDONLY,
},

写时复制

Docker 使用联合文件系统,这是写时复制文件系统。这意味着容器可以使用相同的文件系统镜像作为容器的基础。当容器向镜像写入内容时,它会被写入到特定于容器的文件系统中。即使它们是从相同的文件系统镜像创建的,一个容器也不能访问另一个容器的更改。一个容器不能改变镜像内容以影响另一个容器中的进程。以下图解释了这个过程:

写时复制

Linux 功能

1.2 版本之前的 Docker 容器可以在特权模式下获得完整的功能,或者它们可以遵循允许的功能白名单,同时放弃所有其他功能。如果使用--privileged标志,它将授予容器所有功能。这在生产中是不推荐的,因为它真的很不安全;它允许 Docker 作为直接主机下的进程拥有所有特权。

使用 Docker 1.2 引入了两个docker run标志:

  • --cap-add

  • --cap-drop

这两个标志为容器提供了细粒度的控制,例如:

  • 更改 Docker 容器接口的状态:
docker run --cap-add=NET_ADMIN busybox sh -c "ip link eth0 down"

  • 防止 Docker 容器中的任何 chown:
docker run --cap-drop=CHOWN ...

  • 允许除mknod之外的所有功能:
docker run --cap-add=ALL --cap-drop=MKNOD ...

Docker 默认以受限的功能集启动容器。功能将根和非根的二进制模式转换为更精细的访问控制。例如,提供 HTTP 请求的 Web 服务器需要绑定到端口 80 进行 HTTP 和端口 443 进行 HTTPs。这些服务器不需要以根模式运行。这些服务器可以被授予net_bind_service功能。

在这个情境中,容器和服务器有一些不同。服务器需要以 root 模式运行一些进程。例如,ssh,cron 和网络配置来处理 dhcp 等。另一方面,容器不需要这种访问。

以下任务不需要在容器中发生:

  • ssh 访问由 Docker 主机管理

  • cron 作业应该在用户模式下运行

  • 网络配置,如 ipconfig 和路由,不应该在容器内发生

我们可以安全地推断容器可能不需要 root 权限。

可以拒绝的示例如下:

  • 不允许挂载操作

  • 不允许访问套接字

  • 阻止对文件系统操作的访问,如更改文件属性或文件所有权

  • 阻止容器加载新模块

Docker 只允许以下功能:

Capabilities: []string{
  "CHOWN",
  "DAC_OVERRIDE",
  "FSETID",
  "FOWNER",
  "MKNOD",
  "NET_RAW",
  "SETGID",
  "SETUID",
  "SETFCAP",
  "SETPCAP",
  "NET_BIND_SERVICE",
  "SYS_CHROOT",
  "KILL",
  "AUDIT_WRITE",
},

对先前代码的引用在github.com/docker/docker/blob/master/daemon/execdriver/native/template/default_template_linux.go

可以在 Linux man-pages 中找到所有可用功能的完整列表(man7.org/linux/man-pages/man7/capabilities.7.html)。

运行 Docker 容器的一个主要风险是,容器的默认功能和挂载集可能提供不完整的隔离,无论是独立使用还是与内核漏洞结合使用。

Docker 支持添加和删除功能,允许使用非默认配置文件。这可以通过删除功能或添加功能使 Docker 更安全或更不安全。用户的最佳做法是删除除了明确需要的功能之外的所有功能。

在 AWS ECS 中保护容器

亚马逊EC2 容器服务(ECS)提供了一个高度可扩展、高性能的容器管理服务,支持 Docker 容器。它允许您轻松地在一组托管的亚马逊 EC2 实例上运行应用程序。Amazon ECS 消除了您安装、操作和扩展自己的集群管理基础设施的需要。通过简单的 API 调用,您可以启动和停止启用 Docker 的应用程序,并查询集群的完整状态。

在以下示例中,我们将看到如何使用两个 Docker 容器部署一个安全的 Web 应用程序,一个包含一个简单的 Web 应用程序(应用程序容器),另一个包含启用了限流的反向代理(代理容器),可以用来保护 Web 应用程序。这些容器将在 Amazon EC2 实例上使用 ECS 部署。如下图所示,所有网络流量将通过限流请求的代理容器路由。此外,我们可以在代理容器上使用各种安全软件执行过滤、日志记录和入侵检测等活动。

以下是这样做的步骤:

  1. 我们将从 GitHub 项目构建一个基本的 PHP Web 应用程序容器。以下步骤可以在单独的 EC2 实例或本地机器上执行:
$ sudo yum install -y git
$ git clone https://github.com/awslabs/ecs-demo-php-simple-app

  1. 切换到ecs-demo-php-simple-app文件夹:
$ cd ecs-demo-php-simple-app

  1. 我们可以检查Dockerfile如下,以了解它将部署的 Web 应用程序:
$ cat Dockerfile

  1. 使用 Dockerfile 构建容器镜像,然后将其推送到您的 Docker Hub 帐户。 Docker Hub 帐户是必需的,因为它可以通过指定容器名称来在 Amazon ECS 服务上部署容器:
$ docker build -t my-dockerhub-username/amazon-ecs-sample.

此处构建的镜像需要将dockerhub-username(无空格)作为第一个参数。

下图描述了黑客无法访问 Web 应用程序,因为请求通过代理容器进行过滤并且被阻止:

在 AWS ECS 中保护容器

  1. 将 Docker 镜像上传到 Docker Hub 帐户:
$ docker login

  1. 检查以确保您的登录成功:
$ docker info

  1. 将您的镜像推送到 Docker Hub 帐户:
$ docker push my-dockerhub-username/amazon-ecs-sample

  1. 创建示例 Web 应用程序 Docker 容器后,我们将创建代理容器,如果需要,还可以包含一些与安全相关的软件,以加强安全性。我们将使用定制的 Dockerfile 创建一个新的代理 Docker 容器,然后将镜像推送到您的 Docker Hub 帐户:
$ mkdir proxy-container
$ cd proxy-container
$ nano Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y nginx
COPY nginx.conf /etc/nginx/nginx.conf
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
EXPOSE 80
CMD service nginx start

在上一个 Dockerfile 中,我们使用了一个基本的 Ubuntu 镜像,并安装了 nginx,并将其暴露在 80 端口。

  1. 接下来,我们将创建一个定制的nginx.conf,它将覆盖默认的nginx.conf,以确保反向代理配置正确:
user www-data;
worker_processes 4;
pid /var/run/nginx.pid;

events {
 worker_connections 768;
 # multi_accept on;
}

http {
 server {
 listen           80;

 # Proxy pass to servlet container
 location / {
 proxy_pass      http://application-container:80;
 }
 }
}

  1. 构建代理 Docker 镜像并将构建的镜像推送到 Docker Hub 帐户:
$ docker build -t my-dockerhub-username/proxy-image.
$ docker push my-dockerhub-username/proxy-image

  1. 可以通过转到 AWS 管理控制台(aws.amazon.com/console/)来部署 ECS 容器服务。

  2. 在左侧边栏中单击“任务定义”,然后单击“创建新任务定义”。

  3. 给你的任务定义起一个名字,比如SecurityApp

  4. 接下来,单击“添加容器”,并插入推送到 Docker Hub 帐户的代理 Web 容器的名称,以及应用程序 Web 容器的名称。使用“通过 JSON 配置”选项卡查看 JSON 的内容,以查看您创建的任务定义。它应该是这样的:

Proxy-container:
Container Name: proxy-container
Image: username/proxy-image
Memory: 256
Port Mappings
Host port: 80
Container port: 80
Protocol: tcp
CPU: 256
Links: application-container
Application container:
Container Name: application-container
Image: username/amazon-ecs-sample
Memory: 256
CPU: 256

单击“创建”按钮以部署应用程序。

  1. 在左侧边栏中单击“集群”。如果默认集群不存在,则创建一个。

  2. 启动一个 ECS 优化的 Amazon 机器映像(AMI),确保它具有公共 IP 地址和通往互联网的路径。

  3. 当您的实例正在运行时,导航到 AWS 管理控制台的 ECS 部分,然后单击“集群”,然后单击“默认”。现在,我们应该能够在“ECS 实例”选项卡下看到我们的实例。

  4. 从 AWS 管理控制台选项卡的左侧导航到任务定义,然后单击“运行任务”。

  5. 在下一页上,确保集群设置为“默认”,任务数为“1”,然后单击“运行任务”。

  6. 进程完成后,我们可以从挂起状态到绿色运行状态看到任务的状态。

  7. 单击“ECS”选项卡,我们可以看到先前创建的容器实例。单击它,我们将获得有关其公共 IP 地址的信息。通过浏览器点击此公共 IP 地址,我们将能够看到我们的示例 PHP 应用程序。

理解 Docker 安全性 I—内核命名空间

命名空间提供了对内核全局系统资源的包装器,并使资源对于命名空间内的进程看起来像是有一个隔离的实例。全局资源更改对于相同命名空间中的进程是可见的,但对其他进程是不可见的。容器被认为是内核命名空间的一个很好的实现。

Docker 实现了以下命名空间:

  • pid 命名空间:用于进程隔离(PID—进程 ID)

  • net 命名空间:用于管理网络接口(NET—网络)

  • IPC 命名空间:用于管理对 IPC 资源(IPC—进程间通信)的访问

  • mnt 命名空间:用于管理挂载点(MNT—挂载)

  • uts 命名空间:用于隔离内核和版本标识(UTSUnix Time sharing System

在 libcontainer 中添加命名空间支持需要在 GoLang 的系统层中添加补丁(codereview.appspot.com/126190043/patch/140001/150001src/syscall/exec_linux.go),以便可以维护新的数据结构用于 PID、用户 UID 等。

pid 命名空间

pid 命名空间隔离了进程 ID 号空间;不同 pid 命名空间中的进程可以拥有相同的 pid。pid 命名空间允许容器提供功能,如暂停/恢复容器中的一组进程,并在容器内部的进程保持相同的 pid 的情况下将容器迁移到新主机。

新命名空间中的 PID 从 1 开始。内核需要配置标志CONFIG_PID_NS才能使命名空间工作。

pid 命名空间可以嵌套。每个 pid 命名空间都有一个父命名空间,除了初始(根)pid 命名空间。pid 命名空间的父命名空间是使用 clone 或 unshare 创建命名空间的进程的 pid 命名空间。pid 命名空间形成一棵树,所有命名空间最终都可以追溯到根命名空间,如下图所示:

pid 命名空间

net 命名空间

net 命名空间提供了与网络相关的系统资源隔离。每个网络命名空间都有自己的网络设备、IP 地址、IP 路由表、/proc/net目录、端口号等。

网络命名空间使容器在网络方面变得有用:每个容器可以拥有自己的(虚拟)网络设备和绑定到每个命名空间端口号空间的应用程序;主机系统中适当的路由规则可以将网络数据包定向到与特定容器关联的网络设备。使用网络命名空间需要内核配置CONFIG_NET_NS选项(lwn.net/Articles/531114/)。

由于每个容器都有自己的网络命名空间,基本上意味着拥有自己的网络接口和路由表,net 命名空间也被 Docker 直接利用来隔离 IP 地址、端口号等。

基本网络命名空间管理

通过向clone()系统调用传递一个标志CLONE_NEWNET来创建网络命名空间。不过,从命令行来看,使用 IP 网络配置工具来设置和处理网络命名空间是很方便的:

# ip netns add netns1

这个命令创建了一个名为netns1的新网络命名空间。当 IP 工具创建网络命名空间时,它将在/var/run/netns下为其创建一个绑定挂载,这样即使在其中没有运行任何进程时,命名空间也会持续存在,并且便于对命名空间本身进行操作。由于网络命名空间通常需要大量配置才能准备好使用,这个特性将受到系统管理员的赞赏。

ip netns exec命令可用于在命名空间内运行网络管理命令:

# ip netns exec netns1 ip link list
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

这个命令列出了命名空间内可见的接口。可以使用以下命令删除网络命名空间:

# ip netns delete netns1

这个命令移除了指向给定网络命名空间的绑定挂载。然而,命名空间本身将持续存在,只要其中有任何进程在其中运行。

网络命名空间配置

新的网络命名空间将拥有一个环回设备,但没有其他网络设备。除了环回设备外,每个网络设备(物理或虚拟接口,桥接等)只能存在于单个网络命名空间中。此外,物理设备(连接到真实硬件的设备)不能被分配到除根之外的命名空间。相反,可以创建虚拟网络设备(例如虚拟以太网或 vEth)并分配给命名空间。这些虚拟设备允许命名空间内的进程通过网络进行通信;决定它们可以与谁通信的是配置、路由等。

创建时,新命名空间中的lo环回设备是关闭的,因此即使是环回的ping也会失败。

# ip netns exec netns1 ping 127.0.0.1
connect: Network is unreachable

在前面的命令中,我们可以看到由于 Docker 容器的网络命名空间存储在单独的位置,因此需要创建到/var/run/netns的符号链接,可以通过以下方式完成:

# pid=`docker inspect -f '{{.State.Pid}}' $container_id`
# ln -s /proc/$pid/ns/net /var/run/netns/$container_id

在这个例子中,通过启动该接口来实现,这将允许对环回地址进行 ping。

# ip netns exec netns1 ip link set dev lo up
# ip netns exec netns1 ping 127.0.0.1
 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.052 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.042 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.044 ms
64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.031 ms
64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.042 ms

这仍然不允许netns1和根命名空间之间的通信。为了实现这一点,需要创建和配置虚拟以太网设备。

# ip link add veth0 type veth peer name veth1
# ip link set veth1 netns netns1

第一条命令设置了一对连接的虚拟以太网设备。发送到veth0的数据包将被veth1接收,反之亦然。第二条命令将veth1分配给netns1命名空间。

# ip netns exec netns1 ifconfig veth1 10.0.0.1/24 up
# ifconfig veth0 10.0.0.2/24 up

然后,这两条命令为这两个设备设置了 IP 地址。

# ping 10.0.0.1
# ip netns exec netns1 ping 10.0.0.2

现在可以进行双向通信,就像之前的ping命令所示。

如前所述,命名空间不共享路由表或防火墙规则,运行routeiptables -Lnetns1中将证明这一点:

# ip netns exec netns1 route
Kernel IP routing table
Destination   Gateway    Genmask        Flags    Metric Ref    Use Iface
10.0.0.0         *      255.255.255.0     U        0  0  0       veth1

# ip netns exec netns1 iptables -L
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

用户命名空间

用户命名空间允许用户和组 ID 在命名空间内进行映射。这意味着命名空间内的进程的用户 ID 和组 ID 可以与其在命名空间外的 ID 不同。一个进程在命名空间外可以具有非零用户 ID,同时在命名空间内可以具有零用户 ID。该进程在用户命名空间外进行操作时没有特权,但在命名空间内具有 root 特权。

创建新的用户命名空间

通过在调用clone()unshare()时指定CLONE_NEWUSER标志来创建用户命名空间:

clone() 允许子进程与调用进程共享其执行上下文的部分,例如内存空间、文件描述符表和信号处理程序表。

unshare() 允许进程(或线程)取消与其他进程(或线程)共享的执行上下文的部分。当使用fork()vfork()创建新进程时,执行上下文的一部分,例如挂载命名空间,会隐式共享。

如前所述,Docker 容器与 LXC 容器非常相似,因为为容器单独创建了一组命名空间和控制组。每个容器都有自己的网络堆栈和命名空间。除非容器没有特权访问权限,否则不允许访问其他主机的套接字或接口。如果将主机网络模式赋予容器,那么它才能访问主机端口和 IP 地址,这可能对主机上运行的其他程序造成潜在威胁。

如下例所示,在容器中使用host网络模式,并且能够访问所有主机桥接设备:

docker run -it --net=host ubuntu /bin/bash
$ ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:1d:36:0d:0d
 inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:1dff:fe36:d0d/64 Scope:Link
 UP BROADCAST MULTICAST  MTU:1500  Metric:1
 RX packets:24 errors:0 dropped:0 overruns:0 frame:0
 TX packets:38 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:1608 (1.6 KB)  TX bytes:5800 (5.8 KB)

eno16777736 Link encap:Ethernet  HWaddr 00:0c:29:02:b9:13
 inet addr:192.168.218.129  Bcast:192.168.218.255  Mask:255.255.255.0
 inet6 addr: fe80::20c:29ff:fe02:b913/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:4934 errors:0 dropped:0 overruns:0 frame:0
 TX packets:4544 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:2909561 (2.9 MB)  TX bytes:577079 (577.0 KB)

$ docker ps -q | xargs docker inspect --format '{{ .Id }}: NetworkMode={{ .HostConfig.NetworkMode }}'
52afb14d08b9271bd96045bebd508325a2adff98dbef8c10c63294989441954d: NetworkMode=host

在审核过程中,应该检查所有容器,默认情况下网络模式是否设置为default而不是host

$ docker ps -q | xargs docker inspect --format '{{ .Id }}: NetworkMode={{ .HostConfig.NetworkMode }}'
1aca7fe47882da0952702c383815fc650f24da2c94029b5ad8af165239b78968: NetworkMode=default

每个 Docker 容器都连接到以太网桥,以便在容器之间提供互连性。它们可以相互 ping 以发送/接收 UDP 数据包并建立 TCP 连接,但如果有必要,可以进行限制。命名空间还提供了一种简单的隔离,限制了在其他容器中运行的进程以及主机的访问。

我们将使用以下nsenter命令行实用程序进入命名空间。它是 GitHub 上的一个开源项目,可在github.com/jpetazzo/nsenter上找到。

使用它,我们将尝试进入现有容器的命名空间,或者尝试生成一组新的命名空间。它与 Docker exec命令不同,因为nsenter不会进入 cgroups,这可以通过使用命名空间来逃避资源限制,从而为调试和外部审计带来潜在好处。

我们可以从 PyPI 安装nsenter(它需要 Python 3.4),并使用命令行实用程序连接到正在运行的容器:

$ pip install nsenter

使用以下命令替换 pid 为容器的 pid:

$ sudo nsenter --net --target=PID /bin/ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 inet 127.0.0.1/8 scope host lo
 valid_lft forever preferred_lft forever
 inet6 ::1/128 scope host
 valid_lft forever preferred_lft forever
14: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
 link/ether 02:42:ac:11:00:06 brd ff:ff:ff:ff:ff:ff
 inet 172.17.0.6/16 scope global eth0
 valid_lft forever preferred_lft forever
 inet6 fe80::42:acff:fe11:6/64 scope link
 valid_lft forever preferred_lft forever

我们可以使用docker inspect命令使其更加方便:

  1. 首先启动一个新的 nginx 服务器:
$ docker run -d --name=nginx -t nginx

  1. 然后获取容器的 pid:
PID=$(docker inspect --format {{.State.Pid}} nginx)

  1. 连接到正在运行的 nginx 容器:
$ nsenter --target $PID --uts --ipc --net –pid

docker-enter也是可以用来进入容器并指定 shell 命令的包装器,如果没有指定命令,将调用一个 shell。如果需要在不执行其他命令行工具的情况下检查或操作容器,可以使用上下文管理器来实现:

import subprocess
from nsenter import Namespace
with Namespace(mypid, 'net'):
# output network interfaces as seen from within the mypid's net NS:
 subprocess.check_output(['ip', 'a'])

理解 Docker 安全 II - cgroups

在本节中,我们将看看 cgroups 如何构成容器隔离的基础。

定义 cgroups

控制组提供了一种将任务(进程)及其所有未来的子任务聚合/分区到分层组中的机制。

cgroup 将一组任务与子系统的参数关联起来。子系统本身是用于定义 cgroups 边界或为资源提供的资源控制器。

层次结构是一组以树状排列的 cgroups,系统中的每个任务都恰好位于层次结构中的一个 cgroup 中,并且一组子系统。

为什么需要 cgroups?

Linux 内核中有多个努力提供进程聚合,主要用于资源跟踪目的。

这些努力包括 cpusets、CKRM/ResGroups、UserBeanCounters 和虚拟服务器命名空间。所有这些都需要基本的进程分组/分区概念,新分叉的进程最终进入与其父进程相同的组(cgroup)。

内核 cgroup 补丁提供了必要的内核机制,以有效地实现这些组。它对系统快速路径的影响很小,并为特定子系统提供了钩子,例如 cpusets,以提供所需的附加行为。

手动创建一个 cgroup

在以下步骤中,我们将创建一个cpuset控制组:

# mount -t tmpfs cgroup_root /sys/fs/cgroup

tmpfs是一种将所有文件保存在虚拟内存中的文件系统。tmpfs中的所有内容都是临时的,即不会在硬盘上创建任何文件。如果卸载tmpfs实例,则其中存储的所有内容都会丢失:

# mkdir /sys/fs/cgroup/cpuset
# mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
# cd /sys/fs/cgroup/cpuset
# mkdir Charlie
# cd Charlie
# ls
cgroup.clone_children  cpuset.cpu_exclusive  cpuset.mem_hardwall     cpuset.memory_spread_page  cpuset.sched_load_balance  tasks
cgroup.event_control   cpuset.cpus           cpuset.memory_migrate   cpuset.memory_spread_slab  cpuset.sched_relax_domain_level
cgroup.procs           cpuset.mem_exclusive  cpuset.memory_pressure  cpuset.mems                notify_on_release

为此 cgroup 分配 CPU 和内存限制:

# /bin/echo 2-3 > cpuset.cpus
# /bin/echo 0 > cpuset.mems
# /bin/echo $$ > tasks

以下命令显示/Charlie作为 cpuset cgroup:

# cat /proc/self/cgroup
11:name=systemd:/user/1000.user/c2.session
10:hugetlb:/user/1000.user/c2.session
9:perf_event:/user/1000.user/c2.session
8:blkio:/user/1000.user/c2.session
7:freezer:/user/1000.user/c2.session
6:devices:/user/1000.user/c2.session
5:memory:/user/1000.user/c2.session
4:cpuacct:/user/1000.user/c2.session
3:cpu:/user/1000.user/c2.session
2:cpuset:/Charlie

将进程附加到 cgroups

将进程 IDPID{X}添加到任务文件中,如下所示:

# /bin/echo PID > tasks

请注意,这是PID,而不是 PIDs。

您一次只能附加一个任务。如果有多个任务要附加,您必须一个接一个地执行:

# /bin/echo PID1 > tasks
# /bin/echo PID2 > tasks
...
# /bin/echo PIDn > tasks

通过回显0将当前 shell 任务附加:

# echo 0 > tasks

Docker 和 cgroups

cgroups 作为 Docker 的 GitHub 存储库(github.com/opencontainers/runc/tree/master/libcontainer/cgroups)下的 libcontainer 项目的一部分进行管理。有一个 cgroup 管理器,负责与内核中的 cgroup API 进行交互。

以下代码显示了管理器管理的生命周期事件:

type Manager interface {
 // Apply cgroup configuration to the process with the specified pid
 Apply(pid int) error
 // Returns the PIDs inside the cgroup set
 GetPids() ([]int, error)
 // Returns statistics for the cgroup set
 GetStats() (*Stats, error)
 // Toggles the freezer cgroup according with specified state
 Freeze(state configs.FreezerState) error
 // Destroys the cgroup set
 Destroy() error
 // Paths maps cgroup subsystem to path at which it is mounted.
 // Cgroups specifies specific cgroup settings for the various subsystems
 // Returns cgroup paths to save in a state file and to be able to
 // restore the object later.
 GetPaths() map[string]string
 // Set the cgroup as configured.
 Set(container *configs.Config) error
}

使用 AppArmor 保护 Docker 容器

AppArmor 是一种强制访问控制MAC)系统,是内核增强功能,用于将程序限制在有限的资源集合中。AppArmor 的安全模型是将访问控制属性绑定到程序,而不是用户。

AppArmor 约束是通过加载到内核中的配置文件提供的,通常在启动时加载。AppArmor 配置文件可以处于两种模式之一:强制执行或投诉。

以强制执行模式加载的配置文件将导致强制执行配置文件中定义的策略,并报告策略违规尝试(通过 syslog 或 auditd)。

投诉模式下的配置文件不会强制执行策略,而是报告策略违规尝试。

AppArmor 与 Linux 上的其他一些 MAC 系统不同:它是基于路径的,允许混合强制和投诉模式配置文件,使用包含文件来简化开发,并且比其他流行的 MAC 系统具有更低的入门门槛。以下图显示了与应用程序相关联的 AppArmour 应用程序配置文件:

使用 AppArmor 保护 Docker 容器

AppArmor 是一项成熟的技术,最初出现在 Immunix 中,后来集成到 Ubuntu、Novell/SUSE 和 Mandriva 中。核心 AppArmor 功能从 Linux 内核 2.6.36 版本开始就已经在主线内核中;AppArmor、Ubuntu 和其他开发人员正在进行工作,将其他额外的 AppArmor 功能合并到主线内核中。

您可以在wiki.ubuntu.com/AppArmor找到有关 AppArmor 的更多信息。

AppArmor 和 Docker

在 Docker 内运行的应用程序可以利用 AppArmor 来定义策略。这些配置文件可以手动创建,也可以使用一个名为 bane 的工具加载。

注意

在 Ubuntu 14.x 上,确保安装了 systemd 才能使以下命令生效。

以下步骤显示了如何使用这个工具:

  1. 从 GitHub 下载 bane 项目:
$ git clone https://github.com/jfrazelle/bane

确保这是在您的 GOPATH 目录中完成的。例如,我们使用了/home/ubuntu/go,bane 源代码下载在/home/Ubuntu/go/src/github.com/jfrazelle/bane

  1. 安装 bane 编译所需的 toml 解析器:
$ go get github.com/BurntSushi/toml

  1. 转到/home/Ubuntu/go/src/github.com/jfrazelle/bane目录并运行以下命令:
$ go install

  1. 您将在/home/Ubuntu/go/bin中找到 bane 二进制文件。

  2. 使用.toml文件创建配置文件:

Name = "nginx-sample"
[Filesystem]
# read only paths for the container
ReadOnlyPaths = [
 "/bin/**",
 "/boot/**",
 "/dev/**",
 "/etc/**",
 …
]
AllowExec = [
 "/usr/sbin/nginx"
]
# denied executable files
DenyExec = [
 "/bin/dash",
 "/bin/sh",
 "/usr/bin/top"
]

  1. 执行 bane 加载配置文件。sample.toml是在/home/Ubuntu/go/src/github.com/jfrazelle/bane目录中的文件:
$ sudo bane sample.toml
# Profile installed successfully you can now run the profile with # `docker run --security-opt="apparmor:docker-nginx-sample"`

这个配置文件将使大量路径变为只读,并且只允许在我们将要创建的容器中执行 nginx。它禁用了 TOP、PING 等。

  1. 一旦配置文件加载,您就可以创建一个 nginx 容器:
$ docker run --security-opt="apparmor:docker-nginx-sample" -p 80:80 --rm -it nginx bash

注意,如果 AppArmor 无法找到文件,将文件复制到/etc/apparmor.d目录并重新加载 AppArmour 配置文件:

$ sudo invoke-rc.d apparmor reload

使用 AppArmor 配置文件创建 nginx 容器:

ubuntu@ubuntu:~/go/src/github.com$ docker run --security-opt="apparmor:docker-nginx-sample" -p 80:80 --rm -it nginx bash
root@84d617972e04:/# ping 8.8.8.8
ping: Lacking privilege for raw socket.

以下图显示了容器中运行的 nginx 应用程序如何使用 AppArmour 应用程序配置文件:

AppArmor and Docker

Docker 安全基准

以下教程展示了一些重要的准则,应遵循以在安全和生产环境中运行 Docker 容器。这是从 CIS Docker 安全基准benchmarks.cisecurity.org/tools2/docker/CIS_Docker_1.6_Benchmark_v1.0.0.pdf中引用的。

定期审计 Docker 守护程序

除了审计常规的 Linux 文件系统和系统调用外,还要审计 Docker 守护程序。Docker 守护程序以 root 权限运行。因此,有必要审计其活动和使用情况:

$ apt-get install auditd
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
 libauparse0
Suggested packages:
 audispd-plugins
The following NEW packages will be installed:
 auditd libauparse0
0 upgraded, 2 newly installed, 0 to remove and 50 not upgraded.
Processing triggers for libc-bin (2.21-0ubuntu4) ...
Processing triggers for ureadahead (0.100.0-19) ...
Processing triggers for systemd (225-1ubuntu9) ...

如果存在审计日志文件,则删除:

$ cd /etc/audit/
$ ls
audit.log
$ nano audit.log
$ rm -rf audit.log

为 Docker 服务添加审计规则并审计 Docker 服务:

$ nano audit.rules
-w /usr/bin/docker -k docker
$ service auditd restart
$ ausearch -k docker
<no matches>
$ docker ps
CONTAINER ID    IMAGE      COMMAND    CREATED    STATUS   PORTS     NAMES
$ ausearch -k docker
----
time->Fri Nov 27 02:29:50 2015
type=PROCTITLE msg=audit(1448620190.716:79): proctitle=646F636B6572007073
type=PATH msg=audit(1448620190.716:79): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=398512 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=PATH msg=audit(1448620190.716:79): item=0 name="/usr/bin/docker" inode=941134 dev=08:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 nametype=NORMAL
type=CWD msg=audit(1448620190.716:79):  cwd="/etc/audit"
type=EXECVE msg=audit(1448620190.716:79): argc=2 a0="docker" a1="ps"
type=SYSCALL msg=audit(1448620190.716:79): arch=c000003e syscall=59 success=yes exit=0 a0=ca1208 a1=c958c8 a2=c8

为容器创建一个用户

目前,Docker 不支持将容器的 root 用户映射到主机上的非 root 用户。用户命名空间的支持将在未来版本中提供。这会导致严重的用户隔离问题。因此,强烈建议确保为容器创建一个非 root 用户,并使用该用户运行容器。

如我们在以下片段中所见,默认情况下,centos Docker 镜像的user字段为空,这意味着默认情况下容器在运行时将获得 root 用户,这应该避免:

$ docker inspect centos
[
 {
 "Id": "e9fa5d3a0d0e19519e66af2dd8ad6903a7288de0e995b6eafbcb38aebf2b606d",
 "RepoTags": [
 "centos:latest"
 ],
 "RepoDigests": [],
 "Parent": "c9853740aa059d078b868c4a91a069a0975fb2652e94cc1e237ef9b961afa572",
 "Comment": "",
 "Created": "2015-10-13T23:29:04.138328589Z",
 "Container": "eaa200e2e187340f0707085b9b4eab5658b13fd190af68c71a60f6283578172f",
 "ContainerConfig": {
 "Hostname": "7aa5783a47d5",
 "Domainname": "",
 "User": "",
 contd

在构建 Docker 镜像时,可以在 Dockerfile 中提供test用户,即权限较低的用户,如以下片段所示:

$ cd
$ mkdir test-container
$ cd test-container/
$ cat Dockerfile
FROM centos:latest
RUN useradd test
USER test
root@ubuntu:~/test-container# docker build -t vkohli .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM centos:latest
 ---> e9fa5d3a0d0e
Step 2 : RUN useradd test
 ---> Running in 0c726d186658
 ---> 12041ebdfd3f
Removing intermediate container 0c726d186658
Step 3 : USER test
 ---> Running in 86c5e0599c72
 ---> af4ba8a0fec5
Removing intermediate container 86c5e0599c72
Successfully built af4ba8a0fec5
$ docker images | grep vkohli
vkohli    latest     af4ba8a0fec5      9 seconds ago     172.6 MB

当我们启动 Docker 容器时,可以看到它获得了一个test用户,而docker inspect命令也显示默认用户为test

$ docker run -it vkohli /bin/bash
[test@2ff11ee54c5f /]$ whoami
test
[test@2ff11ee54c5f /]$ exit
$ docker inspect vkohli
[
 {
 "Id": "af4ba8a0fec558d68b4873e2a1a6d8a5ca05797e0bfbab0772bcedced15683ea",
 "RepoTags": [
 "vkohli:latest"
 ],
 "RepoDigests": [],
 "Parent": "12041ebdfd3f38df3397a8961f82c225bddc56588e348761d3e252eec868d129",
 "Comment": "",
 "Created": "2015-11-27T14:10:49.206969614Z",
 "Container": "86c5e0599c72285983f3c5511fdec940f70cde171f1bfb53fab08854fe6d7b12",
 "ContainerConfig": {
 "Hostname": "7aa5783a47d5",
 "Domainname": "",
 "User": "test",
 Contd..

不要在容器上挂载敏感主机系统目录

如果敏感目录以读写模式挂载,可能会对这些敏感目录内的文件进行更改。这些更改可能带来安全隐患或不必要的更改,可能使 Docker 主机处于受损状态。

如果在容器中挂载了/run/systemd敏感目录,那么我们实际上可以从容器本身关闭主机:

$ docker run -ti -v /run/systemd:/run/systemd centos /bin/bash
[root@1aca7fe47882 /]# systemctl status docker
docker.service - Docker Application Container Engine
 Loaded: loaded (/lib/systemd/system/docker.service; enabled)
 Active: active (running) since Sun 2015-11-29 12:22:50 UTC; 21min ago
 Docs: https://docs.docker.com
 Main PID: 758
 CGroup: /system.slice/docker.service
[root@1aca7fe47882 /]# shutdown

可以通过使用以下命令进行审计,该命令返回当前映射目录的列表以及每个容器实例是否以读写模式挂载:

$ docker ps -q | xargs docker inspect --format '{{ .Id }}: Volumes={{ .Volumes }} VolumesRW={{ .VolumesRW }}'

不要使用特权容器

Docker 支持添加和删除功能,允许使用非默认配置文件。这可能通过删除功能使 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 ps -q | xargs docker inspect --format '{{ .Id }}: Privileged={{ .HostConfig.Privileged }}'
930aaa93b4e44c0f647b53b3e934ce162fbd9ef1fd4ec82b826f55357f6fdf3a: Privileged=true

总结

在本章中,我们深入探讨了 Docker 安全性,并概述了 cgroups 和内核命名空间。我们还讨论了文件系统和 Linux 功能的一些方面,容器利用这些功能来提供更多功能,例如特权容器,但代价是在威胁方面更容易暴露。我们还看到了如何在 AWS ECS(EC2 容器服务)中部署容器以在受限流量中使用代理容器来在安全环境中部署容器。AppArmor 还提供了内核增强功能,以将应用程序限制在有限的资源集上。利用它们对 Docker 容器的好处有助于在安全环境中部署它们。最后,我们快速了解了 Docker 安全基准和在生产环境中进行审核和 Docker 部署期间可以遵循的一些重要建议。

在下一章中,我们将学习使用各种工具在 Docker 网络中进行调优和故障排除。

第六章:Docker 的下一代网络堆栈:libnetwork

在本章中,我们将学习关于 Docker 的新网络堆栈:libnetwork,它提供了一个可插拔的架构,具有单主机和多主机虚拟网络的默认实现:

  • 介绍

  • 目标

  • 设计

  • CNM 对象

  • CNM 属性

  • CNM 生命周期

  • 驱动程序

  • 桥接驱动程序

  • 覆盖网络驱动程序

  • 使用 Vagrant 进行覆盖网络

  • 使用 Docker Machine 和 Docker Swarm 的覆盖网络

  • 手动创建覆盖网络并将其用于容器

  • 容器网络接口

  • Calico 的 libnetwork 驱动程序

目标

libnetwork 是用 go 语言编写的,是连接 Docker 容器的新方法。其目标是提供一个容器网络模型,帮助程序员并提供网络库的抽象。libnetwork 的长期目标是遵循 Docker 和 Linux 的哲学,提供独立工作的模块。libnetwork 的目标是为容器提供网络的可组合需求。它还旨在通过以下方式将 Docker Engine 和 libcontainer 中的网络逻辑模块化为单一可重用库:

  • 用 libnetwork 替换 Docker Engine 的网络模块

  • 作为一个允许本地和远程驱动程序为容器提供网络的模型

  • 提供一个用于管理和测试 libnetwork 的工具 dnet-仍在进行中的工作(参考自github.com/docker/libnetwork/issues/45)。

设计

libnetwork 实现了容器网络模型CNM)。它规范了为容器提供网络所需的步骤,同时提供了一个抽象,可用于支持多个网络驱动程序。其端点 API 主要用于管理相应的对象,并对其进行簿记,以提供 CNM 模型所需的抽象级别。

CNM 建立在三个主要组件上。下图显示了 libnetwork 的网络沙盒模型:

设计

CNM 对象

让我们详细讨论 CNM 对象。

沙盒

这包含容器网络堆栈的配置,包括管理路由表、容器接口和 DNS 设置。沙箱的实现可以是 Linux 网络命名空间、FreeBSD 监狱或其他类似的概念。一个沙箱可以包含来自多个网络的许多端点。它还表示容器的网络配置,如 IP 地址、MAC 地址和 DNS 条目。libnetwork 利用特定于操作系统的参数来填充沙箱所代表的网络配置。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 地址空间相关联。每个端点都与endpointInterface结构相关联。

网络

网络是能够直接相互通信的端点组。它在同一主机或多个主机之间提供所需的连接,并在创建或更新网络时通知相应的驱动程序。例如,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
}

每个网络控制器都引用以下内容:

  • 数据结构 driverTable 中有一个或多个驱动程序

  • 数据结构中有一个或多个沙盒

  • 数据存储库

  • ipamTable

下图显示了网络控制器如何位于Docker 引擎和其连接的容器和网络之间:

网络控制器

CNM 属性

有两种类型的属性,如下:

  • 选项:它们对终端用户不可见,但是提供了一种灵活的机制,可以直接从用户传递驱动程序特定的配置数据的键值对。只有当键匹配一个众所周知的标签时,libnetwork 才会处理选项,结果值被选中,这由一个通用对象表示。

  • 标签:它们是选项的一个子集,是终端用户变量,使用-labels选项在 UI 中表示。它们的主要功能是执行特定于驱动程序的操作,并且它们从 UI 传递。

CNM 生命周期

容器网络模型的消费者通过 CNM 对象及其 API 进行交互,以网络管理他们管理的容器。

驱动程序在网络控制器中注册。内置驱动程序在 libnetwork 内部注册,而远程驱动程序通过插件机制(WIP)在 libnetwork 中注册。每个驱动程序处理特定的网络类型。

使用libnetwork.New() API 创建一个网络控制器对象来管理网络的分配,并可选择使用特定于驱动程序的选项配置驱动程序。

使用控制器的NewNetwork() API 通过提供名称和networkType来创建网络。networkType参数有助于选择相应的驱动程序,并将创建的网络绑定到该驱动程序。从这一点开始,对网络的任何操作都将由该驱动程序处理。

controller.NewNetwork() API 还接受可选的选项参数,其中包含驱动程序特定的选项和标签,驱动程序可以用于其目的。

network.CreateEndpoint()可以调用以在给定网络中创建新的端点。此 API 还接受可选的选项参数,这些参数随驱动程序而异。

当在网络中创建端点时,将调用驱动程序的driver.CreateEndpoint,它可以选择在网络中创建端点时保留 IPv4/IPv6 地址。驱动程序将使用driver API 中定义的InterfaceInfo接口来分配这些地址。IPv4/IPv6 地址是完成端点作为服务定义所需的,以及端点公开的端口。服务端点是应用程序容器正在侦听的网络地址和端口号。

endpoint.Join()可用于将容器附加到端点。Join操作将为该容器创建一个沙盒(如果不存在)。驱动程序利用沙盒键来标识附加到同一容器的多个端点。

有一个单独的 API 用于创建端点,另一个用于加入端点。

端点表示独立于容器的服务。创建端点时,为容器保留了资源,以便稍后附加到端点。这提供了一致的网络行为。

当容器停止时,将调用endpoint.Leave()。驱动程序可以清理在Join()调用期间分配的状态。当最后一个引用端点离开网络时,libnetwork 将删除沙盒。

只要端点仍然存在,libnetwork 将继续持有 IP 地址。当容器(或任何容器)再次加入时,这些地址将被重用。这确保了在容器停止和重新启动时重用容器的资源。

endpoint.Delete()用于从网络中删除端点。这将导致删除端点并清理缓存的sandbox.Info

network.Delete()用于删除网络。如果没有端点附加到网络上,则允许删除。

驱动程序

驱动程序拥有一个网络,并负责使网络工作并管理它。网络控制器提供了一个 API,用于使用特定标签/选项配置驱动程序,这些标签/选项对用户不可见,但对 libnetwork 透明,并且可以由驱动程序直接处理。驱动程序可以是内置的(如桥接、主机或覆盖)和远程的(来自插件提供者),可以部署在各种用例和部署场景中。

驱动程序拥有网络实现,并负责管理它,包括IP 地址管理(IPAM)。以下图解释了这个过程:

Driver

以下是内置驱动程序:

  • Null:为了与旧的docker --net=none向后兼容,存在这个选项,主要是在不需要网络的情况下。

  • :它提供了一个特定于 Linux 的桥接实现驱动程序。

  • 覆盖:覆盖驱动程序实现了可以跨多个主机网络封装的网络。我们将深入研究其中两种实现:与 Consul 的基本设置和使用 Vagrant 部署覆盖驱动程序的设置。

  • 远程:它提供了一种支持远程传输的驱动程序的手段,可以根据选择编写特定的驱动程序。

桥驱动程序

桥驱动程序代表了一个在 Linux 桥上充当 libcontainer 网络的包装器。它为每个创建的网络创建一个 veth 对。一个端点连接到容器,另一个端点连接到桥。以下数据结构表示了一个桥接网络:

type driver struct {
  config      *configuration
  etwork      *bridgeNetwork
  natChain    *iptables.ChainInfo
  filterChain *iptables.ChainInfo
  networks    map[string]*bridgeNetwork
  store       datastore.DataStore
  sync.Mutex
}

在桥驱动程序中执行的一些操作:

  • 配置 IPTables

  • 管理 IP 转发

  • 管理端口映射

  • 启用桥网过滤

  • 在桥上设置 IPv4 和 IPv6

以下图表显示了如何使用docker0veth对来表示网络,以连接端点和docker0桥:

桥驱动程序

覆盖网络驱动程序

libnetwork 中的覆盖网络使用 VXLan 和 Linux 桥来创建叠加的地址空间。它支持多主机网络:

const (
  networkType  = "overlay"
  vethPrefix   = "veth"
  vethLen      = 7
  vxlanIDStart = 256
  vxlanIDEnd   = 1000
  vxlanPort    = 4789
  vxlanVethMTU = 1450
)
type driver struct {
  eventCh      chan serf.Event
  notifyCh     chan ovNotify
  exitCh       chan chan struct{}
  bindAddress  string
  neighIP      string
  config       map[string]interface{}
  peerDb       peerNetworkMap
  serfInstance *serf.Serf
  networks     networkTable
  store        datastore.DataStore
  ipAllocator  *idm.Idm
  vxlanIdm     *idm.Idm
  once         sync.Once
  joinOnce     sync.Once
  sync.Mutex
}

使用 Vagrant 使用覆盖网络

覆盖网络是在两个容器之间创建的,VXLan 隧道通过桥接连接容器。

覆盖网络部署 Vagrant 设置

这个设置是使用 Docker 实验版本部署的,它会定期更新,可能不支持一些功能:

  1. 克隆官方的 libnetwork 存储库,并切换到docs文件夹:
$ git clone
$ cd
 libnetwork/docs

  1. Vagrant 脚本已经存在于存储库中;我们将使用以下命令为我们的 Docker 覆盖网络驱动程序测试部署三节点设置:
$ vagrant up
Bringing machine 'consul-server' up with 'virtualbox' provider...
Bringing machine 'net-1' up with 'virtualbox' provider...
Bringing machine 'net-2' up with 'virtualbox' provider...
==> consul-server: Box 'ubuntu/trusty64' could not be found.
Attempting to find and install...
 consul-server: Box Provider: virtualbox
 consul-server: Box Version: >= 0
==> consul-server: Loading metadata for box 'ubuntu/trusty64'
 consul-server: URL: https://atlas.hashicorp.com/ubuntu/trusty64
==> consul-server: Adding box 'ubuntu/trusty64' (v20151217.0.0) for
provider: virtualbox
 consul-server: Downloading:
https://atlas.hashicorp.com/ubuntu/boxes/trusty64/versions/20151217.0.0/providers/virtualbox.box
==> consul-server: Successfully added box 'ubuntu/trusty64'
(v20151217.0.0) for 'virtualbox'!
==> consul-server: Importing base box 'ubuntu/trusty64'...
==> consul-server: Matching MAC address for NAT networking...
==> consul-server: Checking if box 'ubuntu/trusty64' is up to date...
==> consul-server: Setting the name of the VM:
libnetwork_consul-server_1451244524836_56275
==> consul-server: Clearing any previously set forwarded ports...
==> consul-server: Clearing any previously set network interfaces...
==> consul-server: Preparing network interfaces based on
configuration...
 consul-server: Adapter 1: nat
 consul-server: Adapter 2: hostonly
==> consul-server: Forwarding ports...
 consul-server: 22 => 2222 (adapter 1)
==> consul-server: Running 'pre-boot' VM customizations...
==> consul-server: Booting VM...
==> consul-server: Waiting for machine to boot. This may take a few minutes...
consul-server:
101aac79c475b84f6aff48352ead467d6b2b63ba6b64cc1b93c630489f7e3f4c
==> net-1: Box 'ubuntu/vivid64' could not be found. Attempting to find and install...
 net-1: Box Provider: virtualbox
 net-1: Box Version: >= 0
==> net-1: Loading metadata for box 'ubuntu/vivid64'
 net-1: URL: https://atlas.hashicorp.com/ubuntu/vivid64
\==> net-1: Adding box 'ubuntu/vivid64' (v20151219.0.0) for provider: virtualbox
 net-1: Downloading:
https://atlas.hashicorp.com/ubuntu/boxes/vivid64/versions/20151219.0.0/providers/virtualbox.box
contd...

  1. 我们可以按照 Vagrant 列出已部署的机器如下:
$ vagrant status
Current machine states:
consul-server           running (virtualbox)
net-1                   running (virtualbox)
net-2                   running (virtualbox)
This environment represents multiple VMs. The VMs are all listed above with their current state. For more information about a specific VM, run `vagrant status NAME`.

  1. 感谢 Vagrant 脚本,设置已经完成;现在,我们可以 SSH 到 Docker 主机并启动测试容器:
$ vagrant ssh net-1
Welcome to Ubuntu 15.04 (GNU/Linux 3.19.0-42-generic x86_64)
* Documentation:https://help.ubuntu.com/
System information as of Sun Dec 27 20:04:06 UTC 2015
System load:  0.0               Users logged in:       0
Usage of /:   4.5% of 38.80GB   IP address for eth0:   10.0.2.15
Memory usage: 24%               IP address for eth1:    192.168.33.11
Swap usage:   0%                IP address for docker0: 172.17.0.1
Processes:    78
Graph this data and manage this system at:  https://landscape.canonical.com/
Get cloud support with Ubuntu Advantage Cloud Guest:  http://www.ubuntu.com/business/services/cloud

  1. 我们可以创建一个新的 Docker 容器,在容器内部我们可以列出/etc/hosts文件的内容,以验证它是否具有先前部署的覆盖桥规范,并且在启动时自动连接到它:
$ docker run -it --rm ubuntu:14.04 bash
Unable to find image 'ubuntu:14.04' locally
14.04: Pulling from library/ubuntu
6edcc89ed412: Pull complete
bdf37643ee24: Pull complete
ea0211d47051: Pull complete
a3ed95caeb02: Pull complete
Digest: sha256:d3b59c1d15c3cfb58d9f2eaab8a232f21fc670c67c11f582bc48fb32df17f3b3
Status: Downloaded newer image for ubuntu:14.04

root@65db9144c65b:/# cat /etc/hosts
172.21.0.4  2ac726b4ce60
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.21.0.3  distracted_bohr
172.21.0.3  distracted_bohr.multihost
172.21.0.4  modest_curie
172.21.0.4  modest_curie.multihost

  1. 同样,我们也可以在另一个主机net-2中创建 Docker 容器,并验证覆盖网络驱动程序的工作,因为尽管部署在不同的主机上,但这两个容器都能够相互 ping 通。

在前面的示例中,我们使用默认选项启动了 Docker 容器,并且它们自动添加到了覆盖类型的多主机网络中。

我们还可以创建一个单独的覆盖桥,并使用--publish-service选项手动将容器添加到其中,该选项是 Docker 实验的一部分:

vagrant@net-1:~$ docker network create -d overlay tester
447e75fd19b236e72361c270b0af4402c80e1f170938fb22183758c444966427
vagrant@net-1:~$ docker network ls
NETWORK ID           NAME               DRIVE
447e75fd19b2         tester             overlay
b77a7d741b45         bridge             bridge
40fe7cfeee20         none               null
62072090b6ac         host               host

第二个主机也将看到此网络,我们可以使用 Docker 命令中的以下选项在这两个主机中的覆盖网络中创建容器:

$ docker run -it --rm --publish-service=bar.tester.overlay ubuntu:14.04 bash

我们将能够验证覆盖驱动程序的工作,因为这两个容器都能够相互 ping 通。此外,还可以使用 tcpdump、wireshark、smartsniff 等工具来捕获 vXLAN 数据包。

使用 Docker Machine 和 Docker Swarm 创建覆盖网络

本节介绍了创建多主机网络的基础知识。Docker 引擎通过覆盖网络驱动程序支持多主机网络。覆盖驱动程序需要以下先决条件才能工作:

  • 3.16 Linux 内核或更高版本

  • 访问键值存储

  • Docker 支持以下键值存储:Consul、etcd 和 ZooKeeper

  • 连接到键值存储的主机集群

  • 集群中每个主机上的 Docker 引擎守护程序

此示例使用 Docker Machine 和 Docker Swarm 来创建多网络主机。

Docker Machine 用于创建键值存储服务器和集群。创建的集群是 Docker Swarm 集群。

以下图解释了如何使用 Docker Machine 设置三个虚拟机:

使用 Docker Machine 和 Docker Swarm 创建覆盖网络

先决条件

  • Vagrant

  • Docker 引擎

  • Docker Machine

  • Docker Swarm

键值存储安装

覆盖网络需要一个键值存储。键值存储存储有关网络状态的信息,例如发现、网络、端点、IP 地址等。Docker 支持各种键值存储,如 Consul、etcd 和 Zoo Keeper。本节已使用 Consul 实现。

以下是安装键值存储的步骤:

  1. 创建名为mh-keystore的 VirtualBox 虚拟机。

当新的虚拟机被配置时,该过程会将 Docker Engine 添加到主机上。Consul 实例将使用 Docker Hub 帐户中的 consul 镜像(hub.docker.com/r/progrium/consul/):

$ docker-machine create -d virtualbox mh-keystore
Running pre-create checks...
Creating machine...
(mh-keystore) Creating VirtualBox VM...
(mh-keystore) Creating SSH key...
(mh-keystore) Starting VM...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
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 Docker to this machine, run: docker-machine env mh-keystore

  1. mh-keystore虚拟机上启动先前创建的progrium/consul容器:
$ docker $(docker-machine config mh-keystore) run -d \
>     -p "8500:8500" \
>     -h "consul" \
>     progrium/consul -server –bootstrap

Unable to find image 'progrium/consul:latest' locally
latest: Pulling from progrium/consul
3b4d28ce80e4: Pull complete
…
d9125e9e799b: Pull complete
Digest: sha256:8cc8023462905929df9a79ff67ee435a36848ce7a10f18d6d0faba9306b97274
Status: Downloaded newer image for progrium/consul:latest
032884c7834ce22707ed08068c24c503d599499f1a0a58098c31be9cc84d8e6c

使用 bash 扩展$(docker-machine config mh-keystore)将连接配置传递给 Docker run命令。客户端从在mh-keystore机器中运行的progrium/consul镜像启动程序。容器名为consul(标志-h),并监听端口8500(您也可以选择任何其他端口)。

  1. 将本地环境设置为mh-keystore虚拟机:
$ eval "$(docker-machine env mh-keystore)"

  1. 执行docker ps命令,确保 Consul 容器已启动:
$ docker ps
CONTAINER ID      IMAGE            COMMAND               CREATED
032884c7834c   progrium/consul   "/bin/start -server -"   47 seconds ago
 STATUS          PORTS
Up 46 seconds  53/tcp, 53/udp, 8300-8302/tcp, 8301-8302/udp, 8400/tcp, 0.0.0.0:8500->8500/tcp
NAMES
sleepy_austin

创建具有两个节点的 Swarm 集群

在此步骤中,我们将使用 Docker Machine 为您的网络配置两个主机。我们将在 VirtualBox 中创建两个虚拟机。其中一个机器将是 Swarm 主节点,将首先创建。

创建每个主机时,将使用覆盖网络驱动程序的选项通过 Swarm 传递给 Docker Engine,具体步骤如下:

  1. 创建一个 Swarm 主节点虚拟机mhs-demo0
$ docker-machine create \
-d virtualbox \
--swarm --swarm-master \
--swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
mhs-demo0

在创建时,您提供引擎守护程序--cluster-store选项。此选项告诉引擎覆盖网络的键值存储位置。bash 扩展$(docker-machine ip mh-keystore)解析为您在前一节的第 1 步中创建的 Consul 服务器的 IP 地址。--cluster-advertise选项会在网络上宣传该机器。

  1. 创建另一个虚拟机mhs-demo1并将其添加到 Docker Swarm 集群:
$ docker-machine create -d virtualbox \
 --swarm \
 --swarm-discovery="consul://$(docker-machine ip mh-keystore):8500" \
 --engine-opt="cluster-store=consul://$(docker-machine ip mh-keystore):8500" \
 --engine-opt="cluster-advertise=eth1:2376" \
mhs-demo1

Running pre-create checks...
Creating machine...
(mhs-demo1) Creating VirtualBox VM...
(mhs-demo1) Creating SSH key...
(mhs-demo1) Starting VM...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
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...
Configuring swarm...
Checking connection to Docker...
Docker is up and running!
To see how to connect Docker to this machine, run: docker-machine env mhs-demo1

  1. 使用 Docker Machine 列出虚拟机,以确认它们都已启动并运行:
$ docker-machine ls

NAME          ACTIVE   DRIVER       STATE     URL                         SWARM                DOCKER   ERRORS
mh-keystore   *        virtualbox   Running   tcp://192.168.99.100:2376                        v1.9.1
mhs-demo0     -        virtualbox   Running   tcp://192.168.99.101:2376   mhs-demo0 (master)   v1.9.1
mhs-demo1     -        virtualbox   Running   tcp://192.168.99.102:2376   mhs-demo0            v1.9.1

此时,虚拟机正在运行。我们准备使用这些虚拟机为容器创建多主机网络。

创建覆盖网络

使用以下命令创建覆盖网络:

$ docker network create --driver overlay my-net

我们只需要在 Swarm 集群中的一个主机上创建网络。我们使用了 Swarm 主节点,但此命令可以在 Swarm 集群中的任何主机上运行:

  1. 检查覆盖网络是否正在运行,使用以下命令:
$ docker network ls

bd85c87911491d7112739e6cf08d732eb2a2841c6ca1efcc04d0b20bbb832a33
rdua1-ltm:overlay-tutorial rdua$ docker network ls
NETWORK ID          NAME                DRIVER
bd85c8791149        my-net              overlay
fff23086faa8        mhs-demo0/bridge    bridge
03dd288a8adb        mhs-demo0/none      null
2a706780454f        mhs-demo0/host      host
f6152664c40a        mhs-demo1/bridge    bridge
ac546be9c37c        mhs-demo1/none      null
c6a2de6ba6c9       mhs-demo1/host     host

由于我们正在使用 Swarm 主环境,我们能够看到所有 Swarm 代理上的所有网络:每个引擎上的默认网络和单个覆盖网络。在这种情况下,有两个引擎在mhs-demo0mhs-demo1上运行。

每个NETWORK ID都是唯一的。

  1. 依次切换到每个 Swarm 代理并列出网络:
$ eval $(docker-machine env mhs-demo0)

$ docker network ls
NETWORK ID          NAME                DRIVER
bd85c8791149        my-net              overlay
03dd288a8adb        none                  null
2a706780454f        host                  host
fff23086faa8        bridge              bridge

$ eval $(docker-machine env mhs-demo1)
$ docker network ls

NETWORK ID          NAME                DRIVER
bd85c8791149        my-net              overlay
358c45b96beb        docker_gwbridge     bridge
f6152664c40a        bridge              bridge
ac546be9c37c        none                null
c6a2de6ba6c9        host                host

两个代理都报告它们具有使用覆盖驱动程序的my-net网络。我们有一个运行中的多主机覆盖网络。

以下图显示了如何使用覆盖my-net创建并连接两个容器:

创建覆盖网络

使用覆盖网络创建容器

以下是使用覆盖网络创建容器的步骤:

  1. mhs-demo0上创建一个名为c0的容器,并连接到my-net网络:
$ eval $(docker-machine env mhs-demo0)
root@843b16be1ae1:/#

$ sudo docker run -i -t --name=c0 --net=my-net  debian /bin/bash

执行ifconfig以查找c0的 IP 地址。在这种情况下,它是10.0.0.4

root@843b16be1ae1:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:0a:00:00:04
 inet addr:10.0.0.4  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::42:aff:fe00:4/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1
 RX packets:17 errors:0 dropped:0 overruns:0 frame:0
 TX packets:17 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:1474 (1.4 KB)  TX bytes:1474 (1.4 KB)

eth1      Link encap:Ethernet  HWaddr 02:42:ac:12:00:03
 inet addr:172.18.0.3  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe12:3/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:8 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)

lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

  1. mhs-demo1上创建一个名为c1的容器,并连接到my-net网络:
$ eval $(docker-machine env mhs-demo1)

$ sudo docker run -i -t --name=c1 --net=my-net  debian /bin/bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
0bf056161913: Pull complete
1796d1c62d0c: Pull complete
e24428725dd6: Pull complete
89d5d8e8bafb: Pull complete
Digest: sha256:a2b67b6107aa640044c25a03b9e06e2a2d48c95be6ac17fb1a387e75eebafd7c
Status: Downloaded newer image for ubuntu:latest
 root@2ce83e872408:/#

  1. 执行ifconfig以查找c1的 IP 地址。在这种情况下,它是10.0.0.3
root@2ce83e872408:/# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:0a:00:00:03
 inet addr:10.0.0.3  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::42:aff:fe00:3/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1
 RX packets:13 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:1066 (1.0 KB)  TX bytes:578 (578.0 B)

eth1      Link encap:Ethernet  HWaddr 02:42:ac:12:00:02
 inet addr:172.18.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe12:2/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:7 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:578 (578.0 B)  TX bytes:578 (578.0 B)

lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

  1. c0(10.0.0.4) ping c1(10.0.0.3),反之亦然:
root@2ce83e872408:/# ping 10.0.04
PING 10.0.04 (10.0.0.4) 56(84) bytes of data.
64 bytes from 10.0.0.4: icmp_seq=1 ttl=64 time=0.370 ms
64 bytes from 10.0.0.4: icmp_seq=2 ttl=64 time=0.443 ms
64 bytes from 10.0.0.4: icmp_seq=3 ttl=64 time=0.441 ms

容器网络接口

容器网络接口CNI)是一个规范,定义了可执行插件如何用于配置 Linux 应用容器的网络接口。CNI 的官方 GitHub 存储库解释了一个 go 库如何解释实施规范。

容器运行时首先为容器创建一个新的网络命名空间,在其中确定该容器应属于哪个网络以及应执行哪些插件。网络配置以 JSON 格式定义,并在容器启动时定义应为网络执行哪个插件。CNI 实际上是一个源自 rkt 网络协议的不断发展的开源技术。每个 CNI 插件都被实现为可执行文件,并由容器管理系统、docker 或 rkt 调用。

将容器插入网络命名空间,即将 veth 对的一端连接到容器,将另一端连接到桥接,然后分配一个 IP 给接口,并通过调用适当的 IPAM 插件设置与 IP 地址管理一致的路由。

CNI 模型目前用于 Kubernetes 模型中 kubelet 的网络。Kubelet 是 Kubernetes 节点的最重要组件,负责在其上运行容器的负载。

kubelet 的 CNI 包定义在以下 Kubernetes 包中:

Constants
const (
 CNIPluginName        = "cni"
 DefaultNetDir        = "/etc/cni/net.d"
 DefaultCNIDir        = "/opt/cni/bin"
 DefaultInterfaceName = "eth0"
 VendorCNIDirTemplate = "%s/opt/%s/bin"
)
func ProbeNetworkPlugins
func ProbeNetworkPlugins(pluginDir string) []network.NetworkPlugin

以下图显示了 CNI 的放置:

容器网络接口

CNI 插件

根据官方 GitHub 存储库(github.com/appc/cni),CNI 插件需要的参数以便将容器添加到网络中为:

  • 版本:调用者使用的 CNI 规范的版本(调用插件的容器调用)。

  • 容器 ID:这是可选的,但建议使用,并定义了容器在活动时在管理域中应该有一个唯一的 ID。例如,IPAM 系统可能要求为每个容器分配一个唯一的 ID,以便可以正确地将其与后台运行的容器相关联。

  • 网络命名空间路径:这表示要添加的网络命名空间的路径,例如/proc/[pid]/ns/netbind-mount/link到它。

  • 网络配置:这是描述容器可以加入的网络的 JSON 文档,并在以下部分中进行了解释。

  • 额外参数:它允许根据每个容器的需求对 CNI 插件进行细粒度配置。

  • 容器内部接口的名称:这是分配给容器的名称,并符合 Linux 对接口名称的限制。

实现的结果如下:

  • 分配给接口的 IP 地址:这是根据要求分配给网络的 IPv4 地址或 IPv6 地址。

  • DNS 名称服务器列表:这是 DNS 名称服务器的优先顺序地址列表。

网络配置

网络配置以 JSON 格式呈现,可以存储在磁盘上或由容器运行时从其他来源生成。以下 JSON 中的字段很重要,如下所述:

  • cniVersion(字符串):这是此配置符合的 CNI 规范的语义版本 2.0。

  • name(字符串):这是网络名称。它在主机(或其他管理域)上的所有容器中是唯一的。

  • type(字符串):指的是 CNI 插件可执行文件的文件名。

  • ipMasq(布尔值):可选,设置主机上的 IP 伪装,因为主机需要作为无法路由到容器分配的 IP 的子网的网关。

  • ipam:具有 IPAM 特定值的字典。

  • type(字符串):指的是 IPAM 插件可执行文件的文件名。

  • routes(列表):CNI 插件应确保通过网络路由可达的子网列表(以 CIDR 表示)。每个条目都是包含的字典:

  • dst(字符串):CIDR 表示法中的子网

  • gw(字符串):要使用的网关的 IP 地址。如果未指定,则假定子网的默认网关(由 IPAM 插件确定)。

插件特定 OVS 的示例配置如下:

{
  "cniVersion": "0.1.0",
  "name": "pci",
  "type": "ovs",
  // type (plugin) specific
  "bridge": "ovs0",
  "vxlanID": 42,
  "ipam": {
    "type": "dhcp",
    "routes": [ { "dst": "10.3.0.0/16" }, { "dst": "10.4.0.0/16" } ]
  }
}

IP 分配

CNI 插件为接口分配 IP 地址并为接口安装必要的路由,因此它为 CNI 插件提供了很大的灵活性,并且许多 CNI 插件在内部具有支持多种 IP 管理方案的相同代码。

为了减轻 CNI 插件的负担,定义了第二种类型的插件,IP 地址管理插件IPAM),它确定接口 IP/子网、网关和路由,并将此信息返回给主要插件以应用。 IPAM 插件通过网络配置文件中定义的ipam部分或存储在本地文件系统上的数据获取信息。

IP 地址管理界面

IPAM 插件通过运行可执行文件来调用,该文件在预定义路径中搜索,并由 CNI 插件通过CNI_PATH指示。 IPAM 插件从此可执行文件接收所有系统环境变量,这些变量传递给 CNI 插件。

IPAM 通过 stdin 接收网络配置文件。成功的指示是零返回代码和以下 JSON,它被打印到 stdout(在ADD命令的情况下):

{
  "cniVersion": "0.1.0",
  "ip4": {
    "ip": <ipv4-and-subnet-in-CIDR>,
    "gateway": <ipv4-of-the-gateway>,  (optional)
    "routes": <list-of-ipv4-routes>    (optional)
  },
  "ip6": {
    "ip": <ipv6-and-subnet-in-CIDR>,
    "gateway": <ipv6-of-the-gateway>,  (optional)
    "routes": <list-of-ipv6-routes>    (optional)
  },
  "dns": <list-of-DNS-nameservers>     (optional)
}

以下是使用 CNI 运行 Docker 网络的示例:

  1. 首先,安装 Go Lang 1.4+和 jq(命令行 JSON 处理器)以构建 CNI 插件:
$ wget https://storage.googleapis.com/golang/go1.5.2.linux-amd64.tar.gz
$ tar -C /usr/local -xzf go1.5.2.linux-amd64.tar.gz
$ export PATH=$PATH:/usr/local/go/bin
$ go version
go version go1.5.2 linux/amd64
$ sudo apt-get install jq

  1. 克隆官方 CNI GitHub 存储库:
$ git clone https://github.com/appc/cni.git
Cloning into 'cni'...
remote: Counting objects: 881, done.
remote: Total 881 (delta 0), reused 0 (delta 0), pack-reused 881
Receiving objects: 100% (881/881), 543.54 KiB | 313.00 KiB/s, done.
Resolving deltas: 100% (373/373), done.
Checking connectivity... done.

  1. 现在我们将创建一个netconf文件,以描述网络:
mkdir -p /etc/cni/net.d
root@rajdeepd-virtual-machine:~# cat >/etc/cni/net.d/10-mynet.conf <<EOF
>{
>  "name": "mynet",
>  "type": "bridge",
>  "bridge": "cni0",
>  "isGateway": true,
>  "ipMasq": true,
>  "ipam": {
>    "type": "host-local",
>    "subnet": "10.22.0.0/16",
>    "routes": [
>      { "dst": "0.0.0.0/0" }
>    ]
>  }
>}
> EOF

  1. 构建 CNI 插件:
~/cni$ ./build
Building API
Building reference CLI
Building plugins
 flannel
 bridge
 ipvlan
 macvlan
 ptp
 dhcp
 host-local

  1. 现在我们将执行priv-net-run.sh脚本,以创建带有 CNI 插件的私有网络:
~/cni/scripts$ sudo CNI_PATH=$CNI_PATH ./priv-net-run.sh ifconfig
eth0      Link encap:Ethernet  HWaddr 8a:72:75:7d:6d:6c
 inet addr:10.22.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::8872:75ff:fe7d:6d6c/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:1 errors:0 dropped:0 overruns:0 frame:0
 TX packets:1 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:90 (90.0 B)  TX bytes:90 (90.0 B)

lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

  1. 使用之前使用 CNI 插件设置的网络命名空间运行 Docker 容器:
~/cni/scripts$ sudo CNI_PATH=$CNI_PATH ./docker-run.sh --rm busybox:latest /bin/ifconfig
eth0      Link encap:Ethernet  HWaddr 92:B2:D3:E5:BA:9B
 inet addr:10.22.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::90b2:d3ff:fee5:ba9b/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:2 errors:0 dropped:0 overruns:0 frame:0
 TX packets:2 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:180 (180.0 B)  TX bytes:168 (168.0 B)

lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Project Calico 的 libnetwork 驱动程序

Calico 为连接容器、虚拟机或裸金属提供可扩展的网络解决方案。 Calico 使用可扩展的 IP 网络原则作为第 3 层方法提供连接。 Calico 可以在不使用覆盖或封装的情况下部署。 Calico 服务应该作为每个节点上的一个容器部署,并为每个容器提供其自己的 IP 地址。 它还处理所有必要的 IP 路由、安全策略规则和在节点集群中分发路由。

Calico 架构包含四个重要组件,以提供更好的网络解决方案:

  • Felix,Calico 工作进程,是 Calico 网络的核心,主要路由并提供所需的连接到主机上的工作负载。 它还为出站端点流量提供内核接口。

  • 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 支持以下命令:

$ calicoctlOverride 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 Swarm

以下图显示了 Calico 架构:

Project Calico 的 libnetwork 驱动程序

在接下来的教程中,我们将在单节点机器上运行 Calico 的手动设置,该机器使用 Docker 1.9,这最终将 libnetwork 从实验版本带到主要发布版本,并且可以直接配置 Calico,而无需其他 Docker 实验版本的需要:

  1. 获取 etcd 的最新版本并在默认端口 2379 上进行配置:
$ curl -L https://github.com/coreos/etcd/releases/download/v2.2.1/etcd-v2.2.1-linux-amd64.tar.gz -o etcd-v2.2.1-linux-amd64.tar.gz
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
 Dload  Upload   Total   Spent    Left  Speed
100   606    0   606    0     0    445      0 --:--:--  0:00:01 --:--:--   446
100 7181k  100 7181k    0     0   441k      0  0:00:16  0:00:16 --:--:-- 1387k
$ tar xzvf etcd-v2.2.1-linux-amd64.tar.gz
etcd-v2.2.1-linux-amd64/
etcd-v2.2.1-linux-amd64/Documentation/
etcd-v2.2.1-linux-amd64/Documentation/04_to_2_snapshot_migration.md
etcd-v2.2.1-linux-amd64/Documentation/admin_guide.md
etcd-v2.2.1-linux-amd64/Documentation/api.md
contd..
etcd-v2.2.1-linux-amd64/etcd
etcd-v2.2.1-linux-amd64/etcdctl
etcd-v2.2.1-linux-amd64/README-etcdctl.md
etcd-v2.2.1-linux-amd64/README.md

$ cd etcd-v2.2.1-linux-amd64
$ ./etcd
2016-01-06 15:50:00.065733 I | etcdmain: etcd Version: 2.2.1
2016-01-06 15:50:00.065914 I | etcdmain: Git SHA: 75f8282
2016-01-06 15:50:00.065961 I | etcdmain: Go Version: go1.5.1
2016-01-06 15:50:00.066001 I | etcdmain: Go OS/Arch: linux/amd64
Contd..
2016-01-06 15:50:00.107972 I | etcdserver: starting server... [version: 2.2.1, cluster version: 2.2]
2016-01-06 15:50:00.508131 I | raft: ce2a822cea30bfca is starting a new election at term 5
2016-01-06 15:50:00.508237 I | raft: ce2a822cea30bfca became candidate at term 6
2016-01-06 15:50:00.508253 I | raft: ce2a822cea30bfca received vote from ce2a822cea30bfca at term 6
2016-01-06 15:50:00.508278 I | raft: ce2a822cea30bfca became leader at term 6
2016-01-06 15:50:00.508313 I | raft: raft.node: ce2a822cea30bfca elected leader ce2a822cea30bfca at term 6
2016-01-06 15:50:00.509810 I | etcdserver: published {Name:default ClientURLs:[http://localhost:2379 http://localhost:4001]} to cluster 7e27652122e8b2ae

  1. 打开新的终端,并通过运行以下命令将 Docker 守护程序配置为 etcd 键值存储:
$ service docker stop
$ docker daemon --cluster-store=etcd://0.0.0.0:2379
INFO[0000] [graphdriver] using prior storage driver "aufs"
INFO[0000] API listen on /var/run/docker.sock
INFO[0000] Firewalld running: false
INFO[0015] Default bridge (docker0) is assigned with an IP address 172.16.59.1/24\. Daemon option --bip can be used to set a preferred IP address
WARN[0015] Your kernel does not support swap memory limit.
INFO[0015] Loading containers: start.
.....INFO[0034] Skipping update of resolv.conf file with ipv6Enabled: false because file was touched by user
INFO[0043] Loading containers: done.
INFO[0043] Daemon has completed initialization
INFO[0043] Docker daemon       commit=a34a1d5 execdriver=native-0.2 graphdriver=aufs version=1.9.1
INFO[0043] GET /v1.21/version
INFO[0043] GET /v1.21/version
INFO[0043] GET /events
INFO[0043] GET /v1.21/version

  1. 现在,在新的终端中,以以下方式启动 Calico 容器:
$ ./calicoctl node --libnetwork
No IP provided. Using detected IP: 10.22.0.1
Pulling Docker image calico/node:v0.10.0
Calico node is running with id: 79e75fa6d875777d31b8aead10c2712f54485c031df50667edb4d7d7cb6bb26c
Pulling Docker image calico/node-libnetwork:v0.5.2
Calico libnetwork driver is running with id: bc7d65f6ab854b20b9b855abab4776056879f6edbcde9d744f218e556439997f
$ docker ps
CONTAINER ID        IMAGE                           COMMAND         CREATED             STATUS              PORTS               NAMES
7bb7a956af37        calico/node-libnetwork:v0.5.2   "./start.sh"           3 minutes ago       Up 3 minutes             calico-libnetwork
13a0314754d6        calico/node:v0.10.0             "/sbin/start_runit"    3 minutes ago       Up 3 minutes             calico-node
1f13020cc3a0        weaveworks/plugin:1.4.1         "/home/weave/plugin"   3 days ago          Up 3 minutes             weaveplugin

  1. 使用最近在 Docker CLI 中引入的docker network命令创建 Calico 桥接:
$docker network create –d calico net1
$ docker network ls
NETWORK ID          NAME                DRIVER
9b5f06307cf2        docker_gwbridge     bridge
1638f754fbaf        host                host
02b10aaa25d7        weave               weavemesh
65dc3cbcd2c0        bridge              bridge
f034d78cc423        net1                calico

  1. 启动连接到 Calico net1桥接的busybox容器:
$docker run --net=net1 -itd --name=container1 busybox
1731629b6897145822f73726194b1f7441b6086ee568e973d8a88b554e838366
$ docker ps
CONTAINER ID        IMAGE                           COMMAND                CREATED             STATUS              PORTS               NAMES
1731629b6897        busybox                         "sh"                   6 seconds ago       Up 5 seconds                            container1
7bb7a956af37        calico/node-libnetwork:v0.5.2   "./start.sh"           6 minutes ago       Up 6 minutes                            calico-libnetwork
13a0314754d6        calico/node:v0.10.0             "/sbin/start_runit"    6 minutes ago       Up 6 minutes                            calico-node
1f13020cc3a0        weaveworks/plugin:1.4.1         "/home/weave/plugin"   3 days ago          Up 6 minutes                            weaveplugin
$ docker attach 1731
/ #
/ # ifconfig
cali0     Link encap:Ethernet  HWaddr EE:EE:EE:EE:EE:EE
 inet addr:10.0.0.2  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::ecee:eeff:feee:eeee/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:29 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:5774 (5.6 KiB)  TX bytes:648 (648.0 B)

eth1      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
 inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
 inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
 RX packets:21 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:4086 (3.9 KiB)  TX bytes:648 (648.0 B)

lo        Link encap:Local Loopback
 inet addr:127.0.0.1  Mask:255.0.0.0
 inet6 addr: ::1/128 Scope:Host
 UP LOOPBACK RUNNING  MTU:65536  Metric:1
 RX packets:0 errors:0 dropped:0 overruns:0 frame:0
 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

在容器内部,我们可以看到容器现在连接到了 Calico 桥接,并且可以连接到同一桥接上部署的其他容器。

摘要

在本章中,我们深入研究了 Docker 网络的一些更深层次和更概念性的方面之一,其中之一是 libnetworking,这是未来的 Docker 网络模型,已经随着 Docker 1.9 的发布而开始成形。在解释 libnetworking 的同时,我们还研究了 CNM 模型及其各种对象和组件以及其实现代码片段。接下来,我们详细研究了 CNM 的驱动程序,主要是覆盖驱动程序,并作为 Vagrant 设置的一部分进行部署。我们还研究了容器与覆盖网络的独立集成,以及与 Docker Swarm 和 Docker Machine 的集成。在接下来的部分中,我们解释了 CNI 接口、其可执行插件以及使用 CNI 插件配置 Docker 网络的教程。

在最后一节中,详细解释了 Calico 项目,它提供了一个基于 libnetwork 的可扩展网络解决方案,并与 Docker、Kubernetes、Mesos、裸机和虚拟机进行集成。

posted @ 2024-05-06 18:33  绝不原创的飞龙  阅读(15)  评论(0编辑  收藏  举报