从blueprint工具搭建过程看docker network管理

  1. 1. 让它Run起来
    1. docker-compose版本过低
    2. docker network创建失败
    3. MariaDB初始化失败:Can’t initialize timers
    4. 账号注册
  2. 2. 让工具禁止访问外网
    1. 通过iptables限制流量出入
    2. docker network自身隔离
    3. docker compose多network管理
  3. 最后

周二的时候,客户端主程找到我们说有一个工具(docker image),需要我们帮忙搭建一下,丢给我们一个github地址:blueprintue-self-hosted-edition,然后提出了简单的需求:

  1. 只有指定的人才可以操作;
  2. 要做网络隔离,不能让这个工具后台偷偷的上传机密的数据;

接到需求后,大概看了一下这个工具,是一个UE蓝图的可视化展示的工具,就是将UE导出的buleprint的描述数据在页面上进行可视化展示的工具,方便直接查看。

然后就是开始着手本地继续搭建了,repo提供了基于localhost的docker compose配置文件:docker-compose-localhost.yml,看了一下配置,主要包括三个部分:

  1. Traefik (pronounced traffic):和Nginx齐名的两大反向代理工具,Traefik的设计能为云原生应用程序提供动态配置和服务发现功能,而 Nginx 提供了经过验证的性能、高效的流量管理以及像负载平衡和缓存这样强大的功能。
  2. MariaDB:一款基于MySQL重写了存储引擎的DB,完全兼容MySQL,包括API命令行,使之能轻松成为MySQL的代替品。
  3. MailDev:邮件开发和测试工具,它可以作为一个SMTP服务器,捕获应用程序发送的电子邮件,而不会真正将其发送到外部邮件服务器,这在开发和测试期间非常有用。
  4. blueprintue-self-hosted-edition:实现blueprint解析和可视化展示的功能都在这里。

1. 让它Run起来

既然该工具提供了:docker-compose-localhost.yml,我们可以通过docker-compose直接运行管理blueprintue-self-hosted-edition及其所有赖的image。

说实话,虽然对cloud native已经有了解和实践,但是上云工作都是基于Kubernetes来配置相关工作负载,还真没有使用过Docker Compose,相对于复杂的Kubernetes容器编排系统,Docker Compose主要用于开发和测试环境,提供基础的容器编排功能,适用于较小规模的部署,特别是工具类型的应用,主要特点有:

  • 定义和运行多容器应用程序:Docker Compose允许你在一个YAML文件中定义一个多容器应用程序。然后,你可以使用一个命令来启动你的应用程序,而Compose会处理启动你应用程序所需的所有服务。
  • 服务隔离:在Docker Compose中,你的应用程序的服务可以在一个隔离的环境中运行。这意味着每个服务都在其自己的容器中运行,这些容器之间通过网络进行通信。
  • 易于配置:Docker Compose使用YAML文件进行配置,这使得配置过程非常直观和易于理解。
  • 开发、测试、生产环境统一:Docker Compose可以在所有环境中工作,包括生产、阶段、开发、测试,以及CI工作流程。

下面是安装过程的相关错误记录:

docker-compose版本过低

docker-compose -f docker-compose-localhost.yml pull拉取所需的image,结果提示:

1
2
"./docker-compose-localhost.yml" is unsupported. You might be seeing this error because you're using the wrong Compose file version. Either specify a supported version (e.g "2.2" or "3.3") and place your service definitions under the `services` key, or omit the `version` key and place your service definitions at the root of the file to use version 1.
For more on the Compose file format versions, see https://docs.docker.com/compose/compose-file/

docker-compose版本过低,直接通过curl按照最新Release版本,如下:

1
curl -L "https://github.com/docker/compose/releases/download/v2.27.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

docker network创建失败

解决上面的docker-compose版本问题后,就可以启动应用程序了,通过命令docker-compose -f docker-compose-localhost.yml up -d,结果又提示错误:

1
2
3
[+] Running 1/0
✘ Network docker-examples_default Error 0.0s
failed to create network docker-examples_default: Error response from daemon: could not find an available, non-overlapping IPv4 address pool among the defaults to assign to the network

按照Docker Compose Networking说明,Docker Compose启动容器的时候会自动创建默认的容器网络,应用中的多容器都是用该网段进行通信以及和外部隔离,如下是官方说明:

By default Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by the service’s name.

搜了一下相关问题,发现Docker的Issue有提到:https://github.com/docker/for-linux/issues/418,这是老版本Docker的一个BUG,原因是Docker Compose自动创建网段的时候某个网段被使用,没有使用其他可用的网段,针对我们的机器发现CentOS 7.2 会有这个报错,但是较新的CeontOS 7.9 就不会报错,坑爹。

针对老的发行版本的这个问题,有两种解决方案:

  • 方式一:手动创建docker net,然后在应用的docker compose的配置中配置要使用的网络,如下:
1
2
3
4
5
6
7
8
9
# 手动创建docker network网络
docker network create localdev --subnet 10.0.1.0/24

# 修改docker-compose.yml
version: '3'
networks:
default:
external:
name: localdev
  • 方式二:在应用的docker compose的配置中配置要使用的网络,且指定网段,这样就可以自动创建,如下:
1
2
3
4
5
networks:
default:
ipam:
config:
- subnet: 10.10.1.0/24

这里我才用第二种方案,能够保证问题通过配置来解决,不需要额外再敲命令了。

MariaDB初始化失败:Can’t initialize timers

解决上面问题后,docker-compose成功启动了应用,但是发现都打开页面提示内部有error ,查看日志发现:mariadbd有如下报错:

1
2
3
database                         | 2024-06-18 03:40:51+00:00 [ERROR] [Entrypoint]: mariadbd failed while attempting to check config
database | command was: mariadbd --verbose --help
database | Can't initialize timers

搜了一下,MariaDB的官方Issue也有反馈这个问题:https://github.com/MariaDB/mariadb-docker/issues/434,看了一下又是该死的Docker兼容问题,如下解释:

Tested docker 18.09.1 from Debian 10 and confirmed that --security-opt seccomp=unconfined can start mariadb:10.8.3.
By stracing what is happening further its happens that a tgkill happens with SIGURG on the newly created tread without the unconfined seccom. Running with the latest default docker seccomp filter wasn’t sufficient to run this either.

So to be clear, mariadb wants to start. Its the container runtime and their seccomp filters preventing it. Please lodge bug reports with your container runtime vendor/distro.

问题其实就是Docker Image采用的是Linux较新版本的内核采用了libseccomp新版本支持的特性,但是旧版本的Docker,runc不支持的原因。所以可以通过在docker compose的mariadb的配置添加以下配置来解决:

1
2
3
4
5
mariadb:
image: mariadb
...
security_opt:
- seccomp=unconfined

这个问题同样发现CentOS 7.2 会有这个报错,但是较新的CeontOS 7.9 就不会报错,再次坑爹。

账号注册

解决上面的问题后,工具通过http://x.x.x.x打开了,然后进行账号注册,注册后,发现提示创建成功且确认邮件已经发送,需要确认后才可以登陆。如下:

当时没有仔细看这个工具中包含的MailDev的用途,我以为直接发送到我的邮箱中了呢,可是试了几次,都没收到,无奈直接登陆mariadb的容器中,直接看用户信息表的结构:

项目中如下代码,是通过confirmed_at字段是否被set来标记是否邮箱确认的

1
2
3
4
5
6
7
8
9
10
// app/services/www/UserService.php
public static function isUserConfirmedAccount(int $userID): array
{
...

if ($user['confirmed_at'] !== null) {
return [$isConfirmedAccount, $hasToSendEmail];
}
...
}

接下来就简单了,直接修改db就可以了:

1
update users set confirmed_at=now() where username="alias";

后面仔细去看了一下该工具的docker-compose-localhost.yml,发现maildev对外暴露了1080端口,然后我登陆了一下,看到了确认邮件居然发到这里了,如下,哭死😭,这才去认真看了一下MailDev是干什么的,这里不多说了,最开始的时候简单介绍过了MailDev。

2. 让工具禁止访问外网

出于保密的需求,为了不让该开源工具后台运行的时候有上传blueprint的数据的可能,客户端希望禁止该工具访问外网的能力。

通过iptables限制流量出入

最开始能想到的就是通过iptables进行流量的限制,由于Docker Compose启动的应用会创建一个独立和隔离的Docker Network,所以第一个能想到的就是限制该bridge网卡的出入流量,类似如下:

1
2
3
4
5
# 允许从 netA 到指定外部 IP 段的流量
iptables -A FORWARD -i br-netA -o eth0 -s <netA网段> -d <外部IP段> -j ACCEPT

# 允许从指定外部 IP 段回到 netA 的流量
iptables -A FORWARD -i eth0 -o br-netA -s <外部IP段> -d <netA网段> -j ACCEPT

但是我们办公区域的网段很多,而且有时候还会变化,所以不太好配置,就是配置了也不太好维护,同样也担心影响主机其他的业务在跑。

docker network自身隔离

由于整个程序是通过Docker Compose进行编排管理的,问了一下Claude,才想到Docker的network天然设计上就进行了容器网络的抽象和隔离,Docker network提供了Internal的网络类型,Internal网络的典型场景包括:

  • 数据库集群
  • 内部微服务通信
  • 处理敏感数据的应用

Internal网络的特点包括:

  1. 隔离性:Internal网络完全与外部网络隔离,包括主机网络和其他Docker网络。
  2. 仅内部通信:连接到Internal网络的容器只能相互通信,无法访问外部网络或被外部网络访问。
  3. 安全性:由于与外部网络完全隔离,Internal网络提供了更高级别的安全性,适合处理敏感数据或运行需要严格隔离的应用程序。
  4. 无需端口映射:因为无法从外部访问,所以不需要进行端口映射。

如下docker compose配置:将整个工具所创建的容器网络设置为internal,对外不可见,

1
2
3
4
5
6
networks:
default:
internal: true
ipam:
config:
- subnet: 10.10.1.0/24

这样启动应用后,应用就无法访问外部网络,同样外部也访问不了了,那怎么样才可以主动进行访问呢?因为虽然容器网络和主机网络同样做了隔离,但是可以通过容器的IP和端口,还是能在主机上访问的,所有想到在主机上单独搭建一个非容器的nginx代理,然后将外部的请求转发到本地的容器中

上述方案是可行的,但是又不想单独维护一个非容器的应用,用来转发,那想到的是:单独起一个traefik的容器,此容器网络是external的,然后负责转发请求给处于internal容器网络的此工具。那怎么讲两个隔离的容器网络打通呢?答案是:docker network connect,如下:

1
2
3
4
5
# 启动一个默认网络(external)的blueprint-enter容器,负责对外暴露,然后进行流量代理
docker run -d -p 8888:8888 -v /var/run/docker.sock:/var/run/docker.sock -v $PWD/traefik-http-enter.yml:/etc/traefik/traefik.yml --name blueprint-enter traefik

# 把blueprint-enter容器桥接到docker-examples_default
docker network connect docker-examples_default blueprint-enter

经过上面的配置,对外的blueprint-enter容器就可以将流量转发给处于internal网络的工具了,搞定。

docker compose多network管理

虽然上面解决了不让工具访问外面,但是多启动了代理服务,还是不方便管理的,后来看到docker compose network的官方文档中看到用户可以自定义整个容器网络的拓扑,可以让同时让一个容器连接到不同的网络中,这就方便了,直接修改工具的docker-compose-localhost.yml如下:

  1. 创建两个容器网络,默认的网络是internal的,和外部网络隔离,另一个名为external-net的网络,和外部网络是互通的;
  2. 整个工具中的traefik容器同时处于两个容器网络中,这样外部可以通过traefik讲流量妆发给处理internal网络的实际工具容器服务,这样可以完美解决这个问题了;

最后

通过这个工具的搭建,还是需要去仔细学习一下docker 容器化网络的设计和实现的,这样能更好的理解cloud native中整个网络拓扑的隔离和安全是如何做的,面对问题能够有一个快速整体的认知,才能游刃有余,从容不迫。