Docker 网络详解2

在我之前关于 docker 网络的文章中,我介绍了使用 docker CLI 进行网络管理的基础知识。但在现实生活中,你可能不会以这种方式工作,你将通过 docker-compose 配置来编排所需的所有容器。

这就是本文的用武之地 - 让我们看看如何在现实生活中使用网络。涵盖使用 docker-compose 进行网络管理的基础知识、如何将网络用于多存储库/多 docker-compose 项目、微服务以及如何利用它们来测试不同的问题场景,如有限的网络吞吐量、丢失的数据包等。

让我们从一些非常基本的东西开始,如果你已经熟悉 docker-compose,你可能想要跳过下面的一些部分。

Docker-compose 基础知识

使用 docker-compose 开放端口

你可能想要做的第一件事就是简单地开放一个端口:

1
2
3
4
5
6
version: "3.6"
services:
phpmyadmin:
image: phpmyadmin
ports:
- 8080:80

对于刚接触 Docker 的人来说,暴露意味着向外界开放一个端口。你可以通过 IP 限制它,但默认情况下,这意味着每个人都可以访问它。端口暴露在你的网络接口上,而不是容器接口上。在上面的例子中,你可以在端口 8080(localhost:8080)上访问 PhpMyAdmin 容器的端口 80。

如你所见,这非常简单,你只需传递要暴露的端口,遵循与 docker CLI 中相同的想法,即 localPort:containerPort。添加本地端口的监听接口显然也是可能的:

1
2
3
4
5
6
version: "3.6"
services:
phpmyadmin:
image: phpmyadmin
ports:
- 127.0.0.1:8080:80

当你不想将某些服务暴露给外部时,这可能会很方便。

在 docker-compose 文件中连接容器

正如 docker 网络文章中提到的,docker-compose 默认创建一个网络。你可以通过创建一个非常简单的 docker-compose 配置文件轻松检查这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
version: "3.6"
services:
db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- 8080:80
environment:
- PMA_HOSTS=db

让我们运行它并看看会发生什么:

1
2
3
4
docker-compose up -d
Creating network "myexampleproject_default" with the default driver
Pulling db (mariadb:10.3)...
(...)

你可能在第一行就注意到了,为该项目创建了一个名为 myexampleproject_default 的默认网络。它在 docker CLI 中也可见:

1
2
3
4
5
6
docker network ls
NETWORK ID NAME DRIVER SCOPE
a9979ee462fb bridge bridge local
d8b7eab3d297 myexampleproject_default bridge local
17c76d995120 host host local
8224bb92dd9b none null local

docker-compose.yaml 中的所有容器都与此网络连接。这意味着,它们可以轻松相互通信,并且主机可以通过名称解析:

1
2
3
docker-compose exec phpmyadmin bash
root@b362dbe238ac:/var/www/html# getent hosts db
172.18.0.3 db

Docker-compose 网络和面向微服务的架构

在编写面向微服务的项目时,能够在开发环境中模拟至少一部分生产方法非常方便。这包括分离和连接容器组,以及测试网络延迟问题、连接问题等。

将容器划分为单独的网络

但是,如果你不希望容器能够相互通信怎么办?也许你正在编写一个系统,其中一个部分应该对另一个部分隐藏?
实际上,系统的这些部分是使用 AWS VPC 或类似机制分离的,但在开发机器上测试它会很好,对吗?

没问题,让我们看看这个配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: "3.6"
services:
service1-db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
service1-web:
image: nginxdemos/hello
ports:
- 80:80
service2-db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
service2-web:
image: nginxdemos/hello
ports:
- 81:80

如你所见,我们有两个独立的服务,每个服务都包含一个 web 和 db 容器。我们希望仅从其 web 服务访问 db,因此 service2-web 无法直接访问 service1-db。

现在让我们检查一下它是如何工作的:

1
2
3
4
5
docker-compose exec service1-web ash
/ # getent hosts service1-db
172.19.0.2 service1-db service1-db
/ # getent hosts service2-db
172.19.0.5 service2-db service2-db

不幸的是,这些服务并没有按照我们所希望的方式分开。

docker-network.png

不用担心,只需添加非常简单的更改即可实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
version: "3.6"
services:
service1-db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
networks:
- service1
service1-web:
image: nginxdemos/hello
ports:
- 80:80
networks:
- service1
- web
service2-db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
networks:
- service2
service2-web:
image: nginxdemos/hello
ports:
- 81:80
networks:
- service2
- web

networks:
service1:
service2:
web:

我们引入了三个不同的网络(第 31-33 行)——每个服务一个,Web 服务共享一个。为什么我们需要第三个网络?这是允许 service1-web 和 service2-web 之间通信所必需的。我们还为每个服务添加了网络配置(第 7-8、13-15、20-21、26-28 行)。

现在让我们检查一下 service1-web 如何解析名称:

1
2
3
4
5
6
dco exec service1-web ash
/ # getent hosts service2-web
172.22.0.2 service2-web service2-web
/ # getent hosts service2-db
/ # getent hosts service1-db
172.20.0.3 service1-db service1-db

docker-network2.png

如你所见,我们可以通过引入网络并仅将选定的容器连接在一起,轻松实现容器之间的分离。

在多个 docker-compose 文件之间连接容器

通常,上述项目会拆分到 git 存储库之间,或者至少拆分到 docker-compose.yaml 文件之间。因此,开发人员可以单独启动每个服务。我们如何连接这些服务?让我们来看看。

假设我们决定将之前使用的项目拆分为两个单独的存储库。一个用于 service1,另一个用于 service2。这意味着我们有两个 docker-compose.yaml 文件:

1
2
3
4
5
6
7
8
9
10
11
#service1/docker-compose.yaml
version: "3.6"
services:
service1-db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
service1-web:
image: nginxdemos/hello
ports:
- 80:80
1
2
3
4
5
6
7
8
9
10
11
#service2/docker-compose.yaml
version: "3.6"
services:
service2-db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
service2-web:
image: nginxdemos/hello
ports:
- 81:80

如果我们启动这两个配置,service1-web 和 service2-web 将无法相互通信,因为它们将被添加到两个不同的网络:每个 docker-compose.yaml 文件默认创建自己的网络。

1
2
3
4
docker-compose up -d
Creating network "service1_default" with the default driver
Creating service1_service1-web_1 ... done
Creating service1_service1-db_1 ... done

让我们首先重新添加 service1 的网络配置并进行一些小的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: "3.6"
services:
service1-db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
networks:
- service1
service1-web:
image: nginxdemos/hello
ports:
- 80:80
networks:
- service1
- web

networks:
service1:
web:
name: shared-web

我们在第 20 行添加了一些配置。在本例中,我想为我的 Web 网络指定一个固定名称。默认情况下,名称由 PROJECTNAME_NETWORKNAME 组成,项目名称默认为目录名称。我们所在的目录可能对不同的开发人员有不同的名称,因此安全的选择是强制使用这个名称。

现在,对于 service2,我们需要采取一些不同的行动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
version: "3.6"
services:
service2-db:
image: mariadb:10.3
environment:
MYSQL_ROOT_PASSWORD: secret
networks:
- service2
service2-web:
image: nginxdemos/hello
ports:
- 81:80
networks:
- service2
- web

networks:
service2:
web:
external: true #needs to be created by other file
name: shared-web

如你在第 20-21 行中看到的,在本例中我们配置了一个外部网络。这意味着,docker-compose 不会尝试创建它,如果不可用,它将失败。但是一旦可用,它就会重新使用它。

就是这样。Service1 和 2 Web 容器可以相互访问,但它们的数据库是分开的。两者也可以在单独的存储库中开发。

作为上述内容的扩展,你可以查看容器别名以使路由更容易,或者内部路由以进一步隔离服务。

混沌测试

如你所知,当发生中断时,问题不是是否会发生,而是何时发生。最好为这种情况做好准备,并测试系统在出现不同问题时的行为。
如果某些数据包丢失或延迟增加,会发生什么?也许服务离线?

混沌测试就是为此做好准备。

我强烈建议你查看 Pumba,这是一个允许你暂停服务、终止服务的项目,但也会添加网络延迟、丢失、损坏等。

完整描述 pumba 需要大量时间,所以让我们只看一个非常简单的网络延迟模拟。

让我们启动一个 ping 8.8.8.8 的容器:

1
docker run -it --rm --name demo-ping alpine ping 8.8.8.8

现在,查看输出,在单独的控制台选项卡中运行以下命令:

1
pumba netem --duration 5s --tc-image gaiadocker/iproute2 delay --time 3000 demo-ping

就这样!

你还可以在几分钟内实施其他混沌测试。

总结

Dockerdocker-compose 是模拟不同网络配置的绝佳工具,无需设置服务器或虚拟机。命令和配置非常容易使用。结合 Pumba 等外部工具,你还可以测试问题情况并为中断做好准备。


相关文章: