前言
起因是一次 rootless 模式下的部署,为了能让 transmission 容器能够使用 ipv6,且不用考虑访问安全,便直接在 compose 中填写了 host 网络来暴露容器网络,但是它似乎并不 work(X。查看 container 状态时发现 transmission 容器没有产生有效的端口映射,为了解决这个问题,我觉得我们需要去了解docker网络的运行原理。
概念复习
Docker Rootless
一般docker进程是运行和守护都是在根用户(Root)下进行的,但是这样子做其实存在很大的安全隐患,主要原因是在运行态时的 privilege-escalation attacks 等等原因,因此在实际部署中会考虑使用 Rootless 的方式部署到用户来保证权限的隔离,此时的用户运行层级便会来到无根用户手中,可以有效避免 container 权限外泄的问题。
Docker网络
关于docker网络官方是使用驱动程序提供,通过驱动程序来提供几个基础的网路类型:bridge,host,overlay,ipvlan,macvlan, none 和其它 Networking Plugins 支持的网络类型。
docker rootless 官方说明中使用 slirp4netns 和 vpnkit 来提供无根网络。
命名空间(namespace)
关于 linux 下的命名空间由 linux kernel 提供。命名空间通过将全局资源包装在一个抽象内,使得命名空间内的进程看来他们拥自己的全局资源的独立实例,这让进程只会看到与自己相关的一部分进程,这样当命名空间外的进程发生变化时,不会对空间范围内的进程产生影响。这也是现在的 container 得以运行的一大支撑,通过内核空间的 namespace 隔离主系统进程与容器进程,完成一个进程级别的虚拟化。
运行分析
我们这里以 rootless 的运行流程为例。
当启动 rootless container 时,container 内的 root 会被映射出来,这里开始与一般态docker产生了区别,一般态使用 --userns-remap 标志将 container 内的 root 映射到系统 root来做进程隔离,而 rootless 则会在前者基础上将 container root 映射到由用户命名空间提供的用户,从而隔离用户识别与权限。
回到前言的问题,我们能在 docker 官方找到这个说法在docker官网得到了解答:
-
IPAddressshown indocker inspectis namespaced inside RootlessKit’s network namespace. This means the IP address is not reachable from the host withoutnsenter-ing into the network namespace. -
Host network (
docker run --net=host) is also namespaced inside RootlessKit.
因此如果我们的 network 设置为 host, 此时的端口默认映射便会出现在 rootlesskit 的网络上。
关于 rootlesskit 的运行原理官方对其行为是如此描述的:
- RootlessKit creates
user_namespaces(7)andmount_namespaces(7), and executesnewuidmap(1)/newgidmap(1)along withsubuid(5)andsubgid(5). - RootlessKit also supports isolating
network_namespaces(7)with userspace NAT using “slirp”. Kernel-mode NAT using SUID-enabledlxc-user-nic(1)is also experimentally supported.
也就是说,此时的 rootlesskit 将 container network 使用命名空间进行隔离,并利用 slirp4netns 来对容器内的流量进行代理,这样也解释了为何 namespace 会导致 host 网络失效以及为何我们在查看系统网口的时候我们看不到和根模式下由 docker 创建的 bridge 的原因,因为这部分的网络不属于 system network_namespace, 故容器网络直接使用 host 则会将容器直接暴露在 rootless 下的 host中,即 slirp netsns,且在无根状态下的 slirp4netns 并没有使用特权端口的权限。