diff --git a/SUMMARY.md b/SUMMARY.md index 4ad078a..23a39fb 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -45,7 +45,6 @@ * [参考文档](image/dockerfile/references.md) * [Dockerfile 多阶段构建](image/multistage-builds/README.md) * [实战多阶段构建 Laravel 镜像](image/multistage-builds/laravel.md) - * [构建多种系统架构支持的 Docker 镜像](image/manifest.md) * [其它制作镜像的方式](image/other.md) * [实现原理](image/internal.md) * [操作容器](container/README.md) @@ -63,20 +62,18 @@ * [数据管理](data_management/README.md) * [数据卷](data_management/volume.md) * [挂载主机目录](data_management/bind-mounts.md) -* [使用网络](network/README.md) +* [网络配置](network/README.md) + * [快速配置指南](network/quick_guide.md) * [外部访问容器](network/port_mapping.md) - * [容器互联](network/linking.md) + * [容器访问控制](network/access_control.md) + * [端口映射实现](network/port_mapping.md) + * [配置 docker0 网桥](network/docker0.md) + * [自定义网桥](network/bridge.md) + * [编辑网络配置文件](network/config_file.md) * [配置 DNS](network/dns.md) -* [高级网络配置](advanced_network/README.md) - * [快速配置指南](advanced_network/quick_guide.md) - * [容器访问控制](advanced_network/access_control.md) - * [端口映射实现](advanced_network/port_mapping.md) - * [配置 docker0 网桥](advanced_network/docker0.md) - * [自定义网桥](advanced_network/bridge.md) - * [工具和示例](advanced_network/example.md) - * [编辑网络配置文件](advanced_network/config_file.md) - * [配置 HTTP/HTTPS 网络代理](advanced_network/http_https_proxy.md) - * [实例:创建一个点到点连接](advanced_network/ptp.md) + * [配置 HTTP/HTTPS 网络代理](network/http_https_proxy.md) + * [工具和示例](network/example.md) + * [实例:创建一个点到点连接](network/ptp.md) * [Docker Buildx](buildx/README.md) * [BuildKit](buildx/buildkit.md) * [使用 buildx 构建镜像](buildx/buildx.md) diff --git a/advanced_network/README.md b/advanced_network/README.md deleted file mode 100644 index 02106c8..0000000 --- a/advanced_network/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# 高级网络配置 - ->注意:本章属于 `Docker` 高级配置,如果您是初学者,您可以暂时跳过本章节,直接学习 [Docker Compose](../compose) 一节。 - -本章将介绍 Docker 的一些高级网络配置和选项。 - -当 Docker 启动时,会自动在主机上创建一个 `docker0` 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。它会在挂载到它的网口之间进行转发。 - -同时,Docker 随机分配一个本地未占用的私有网段(在 [RFC1918](https://datatracker.ietf.org/doc/html/rfc1918) 中定义)中的一个地址给 `docker0` 接口。比如典型的 `172.17.42.1`,掩码为 `255.255.0.0`。此后启动的容器内的网口也会自动分配一个同一网段(`172.17.0.0/16`)的地址。 - -当创建一个 Docker 容器的时候,同时会创建了一对 `veth pair` 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 `eth0`;另一端在本地并被挂载到 `docker0` 网桥,名称以 `veth` 开头(例如 `vethAQI2QT`)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。 - -![Docker 网络](./_images/network.png) - -接下来的部分将介绍在一些场景中,Docker 所有的网络定制配置。以及通过 Linux 命令来调整、补充、甚至替换 Docker 默认的网络配置。 diff --git a/advanced_network/_images/network.png b/advanced_network/_images/network.png deleted file mode 100644 index fdf8928..0000000 Binary files a/advanced_network/_images/network.png and /dev/null differ diff --git a/advanced_network/access_control.md b/advanced_network/access_control.md deleted file mode 100644 index c99fb77..0000000 --- a/advanced_network/access_control.md +++ /dev/null @@ -1,56 +0,0 @@ -# 容器访问控制 - -容器的访问控制,主要通过 Linux 上的 `iptables` 防火墙来进行管理和实现。`iptables` 是 Linux 上默认的防火墙软件,在大部分发行版中都自带。 - -## 容器访问外部网络 -容器要想访问外部网络,需要本地系统的转发支持。在Linux 系统中,检查转发是否打开。 - -```bash -$sysctl net.ipv4.ip_forward -net.ipv4.ip_forward = 1 -``` -如果为 0,说明没有开启转发,则需要手动打开。 -```bash -$sysctl -w net.ipv4.ip_forward=1 -``` -如果在启动 Docker 服务的时候设定 `--ip-forward=true`, Docker 就会自动设定系统的 `ip_forward` 参数为 1。 - -## 容器之间访问 -容器之间相互访问,需要两方面的支持。 -* 容器的网络拓扑是否已经互联。默认情况下,所有容器都会被连接到 `docker0` 网桥上。 -* 本地系统的防火墙软件 -- `iptables` 是否允许通过。 - -### 访问所有端口 -当启动 Docker 服务(即 dockerd)的时候,默认会添加一条转发策略到本地主机 iptables 的 FORWARD 链上。策略为通过(`ACCEPT`)还是禁止(`DROP`)取决于配置`--icc=true`(缺省值)还是 `--icc=false`。当然,如果手动指定 `--iptables=false` 则不会添加 `iptables` 规则。 - -可见,默认情况下,不同容器之间是允许网络互通的。如果为了安全考虑,可以在 `/etc/docker/daemon.json` 文件中配置 `{"icc": false}` 来禁止它。 - -### 访问指定端口 -在通过 `-icc=false` 关闭网络访问后,还可以通过 `--link=CONTAINER_NAME:ALIAS` 选项来访问容器的开放端口。 - -例如,在启动 Docker 服务时,可以同时使用 `icc=false --iptables=true` 参数来关闭允许相互的网络访问,并让 Docker 可以修改系统中的 `iptables` 规则。 - -此时,系统中的 `iptables` 规则可能是类似 -```bash -$ sudo iptables -nL -... -Chain FORWARD (policy ACCEPT) -target prot opt source destination -DROP all -- 0.0.0.0/0 0.0.0.0/0 -... -``` - -之后,启动容器(`docker run`)时使用 `--link=CONTAINER_NAME:ALIAS` 选项。Docker 会在 `iptable` 中为 两个容器分别添加一条 `ACCEPT` 规则,允许相互访问开放的端口(取决于 `Dockerfile` 中的 `EXPOSE` 指令)。 - -当添加了 `--link=CONTAINER_NAME:ALIAS` 选项后,添加了 `iptables` 规则。 -```bash -$ sudo iptables -nL -... -Chain FORWARD (policy ACCEPT) -target prot opt source destination -ACCEPT tcp -- 172.17.0.2 172.17.0.3 tcp spt:80 -ACCEPT tcp -- 172.17.0.3 172.17.0.2 tcp dpt:80 -DROP all -- 0.0.0.0/0 0.0.0.0/0 -``` - -注意:`--link=CONTAINER_NAME:ALIAS` 中的 `CONTAINER_NAME` 目前必须是 Docker 分配的名字,或使用 `--name` 参数指定的名字。主机名则不会被识别。 diff --git a/advanced_network/bridge.md b/advanced_network/bridge.md deleted file mode 100644 index 0ce6a52..0000000 --- a/advanced_network/bridge.md +++ /dev/null @@ -1,45 +0,0 @@ -# 自定义网桥 - -除了默认的 `docker0` 网桥,用户也可以指定网桥来连接各个容器。 - -在启动 Docker 服务的时候,使用 `-b BRIDGE`或`--bridge=BRIDGE` 来指定使用的网桥。 - -如果服务已经运行,那需要先停止服务,并删除旧的网桥。 - -```bash -$ sudo systemctl stop docker -$ sudo ip link set dev docker0 down -$ sudo brctl delbr docker0 -``` - -然后创建一个网桥 `bridge0`。 - -```bash -$ sudo brctl addbr bridge0 -$ sudo ip addr add 192.168.5.1/24 dev bridge0 -$ sudo ip link set dev bridge0 up -``` - -查看确认网桥创建并启动。 - -```bash -$ ip addr show bridge0 -4: bridge0: mtu 1500 qdisc noop state UP group default - link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff - inet 192.168.5.1/24 scope global bridge0 - valid_lft forever preferred_lft forever -``` - -在 Docker 配置文件 `/etc/docker/daemon.json` 中添加如下内容,即可将 Docker 默认桥接到创建的网桥上。 - -```json -{ - "bridge": "bridge0", -} -``` - -启动 Docker 服务。 - -新建一个容器,可以看到它已经桥接到了 `bridge0` 上。 - -可以继续用 `brctl show` 命令查看桥接的信息。另外,在容器中可以使用 `ip addr` 和 `ip route` 命令来查看 IP 地址配置和路由信息。 diff --git a/advanced_network/config_file.md b/advanced_network/config_file.md deleted file mode 100644 index b084c79..0000000 --- a/advanced_network/config_file.md +++ /dev/null @@ -1,5 +0,0 @@ -# 编辑网络配置文件 - -Docker 1.2.0 开始支持在运行中的容器里编辑 `/etc/hosts`, `/etc/hostname` 和 `/etc/resolv.conf` 文件。 - -但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来,也不会被 `docker commit` 提交。 diff --git a/advanced_network/docker0.md b/advanced_network/docker0.md deleted file mode 100644 index c21ce8b..0000000 --- a/advanced_network/docker0.md +++ /dev/null @@ -1,37 +0,0 @@ -# 配置 docker0 网桥 - -Docker 服务默认会创建一个 `docker0` 网桥(其上有一个 `docker0` 内部接口),它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。 - -Docker 默认指定了 `docker0` 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500 Bytes,或宿主主机网络路由上支持的默认值。这些值都可以在服务启动的时候进行配置。 - -* `--bip=CIDR` IP 地址加掩码格式,例如 192.168.1.5/24 -* `--mtu=BYTES` 覆盖默认的 Docker mtu 配置 - -也可以在配置文件中配置 DOCKER_OPTS,然后重启服务。 - -由于目前 Docker 网桥是 Linux 网桥,用户可以使用 `brctl show` 来查看网桥和端口连接信息。 - -```bash -$ sudo brctl show -bridge name bridge id STP enabled interfaces -docker0 8000.3a1d7362b4ee no veth65f9 - vethdda6 -``` -*注:`brctl` 命令在 Debian、Ubuntu 中可以使用 `sudo apt-get install bridge-utils` 来安装。 - - -每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的 IP 地址分配给容器的 eth0 端口。使用本地主机上 `docker0` 接口的 IP 作为所有容器的默认网关。 - -```bash -$ sudo docker run -i -t --rm base /bin/bash -$ ip addr show eth0 -24: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 - link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff - inet 172.17.0.3/16 scope global eth0 - valid_lft forever preferred_lft forever - inet6 fe80::306f:e0ff:fe35:5791/64 scope link - valid_lft forever preferred_lft forever -$ 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.3 -``` diff --git a/advanced_network/example.md b/advanced_network/example.md deleted file mode 100644 index e8bf802..0000000 --- a/advanced_network/example.md +++ /dev/null @@ -1,9 +0,0 @@ -# 工具和示例 - -在介绍自定义网络拓扑之前,你可能会对一些外部工具和例子感兴趣: - -## pipework -Jérôme Petazzoni 编写了一个叫 [pipework](https://github.com/jpetazzo/pipework) 的 shell 脚本,可以帮助用户在比较复杂的场景中完成容器的连接。 - -## playground -Brandon Rhodes 创建了一个提供完整的 Docker 容器网络拓扑管理的 [Python库](https://github.com/brandon-rhodes/fopnp/tree/m/playground),包括路由、NAT 防火墙;以及一些提供 `HTTP` `SMTP` `POP` `IMAP` `Telnet` `SSH` `FTP` 的服务器。 diff --git a/advanced_network/how_connect.md b/advanced_network/how_connect.md deleted file mode 100644 index 8b13789..0000000 --- a/advanced_network/how_connect.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/advanced_network/http_https_proxy.md b/advanced_network/http_https_proxy.md deleted file mode 100644 index d8dcc92..0000000 --- a/advanced_network/http_https_proxy.md +++ /dev/null @@ -1,74 +0,0 @@ -# 配置 HTTP/HTTPS 网络代理 - -使用Docker的过程中,因为网络原因,通常需要使用 HTTP/HTTPS 代理来加速镜像拉取、构建和使用。下面是常见的三种场景。 - -## 为 dockerd 设置网络代理 - -"docker pull" 命令是由 dockerd 守护进程执行。而 dockerd 守护进程是由 systemd 管理。因此,如果需要在执行 "docker pull" 命令时使用 HTTP/HTTPS 代理,需要通过 systemd 配置。 - -- 为 dockerd 创建配置文件夹。 -``` -sudo mkdir -p /etc/systemd/system/docker.service.d -``` - -- 为 dockerd 创建 HTTP/HTTPS 网络代理的配置文件,文件路径是 /etc/systemd/system/docker.service.d/http-proxy.conf 。并在该文件中添加相关环境变量。 -``` -[Service] -Environment="HTTP_PROXY=http://proxy.example.com:8080/" -Environment="HTTPS_PROXY=http://proxy.example.com:8080/" -Environment="NO_PROXY=localhost,127.0.0.1,.example.com" -``` - -- 刷新配置并重启 docker 服务。 -``` -sudo systemctl daemon-reload -sudo systemctl restart docker -``` - -## 为 docker 容器设置网络代理 - -在容器运行阶段,如果需要使用 HTTP/HTTPS 代理,可以通过更改 docker 客户端配置,或者指定环境变量的方式。 - -- 更改 docker 客户端配置:创建或更改 ~/.docker/config.json,并在该文件中添加相关配置。 -``` -{ - "proxies": - { - "default": - { - "httpProxy": "http://proxy.example.com:8080/", - "httpsProxy": "http://proxy.example.com:8080/", - "noProxy": "localhost,127.0.0.1,.example.com" - } - } -} -``` - -- 指定环境变量:运行 "docker run" 命令时,指定相关环境变量。 - -| 环境变量 | docker run 示例 | -| -------- | ---------------- | -| HTTP_PROXY | --env HTTP_PROXY="http://proxy.example.com:8080/" | -| HTTPS_PROXY | --env HTTPS_PROXY="http://proxy.example.com:8080/" | -| NO_PROXY | --env NO_PROXY="localhost,127.0.0.1,.example.com" | - -## 为 docker build 过程设置网络代理 - -在容器构建阶段,如果需要使用 HTTP/HTTPS 代理,可以通过指定 "docker build" 的环境变量,或者在 Dockerfile 中指定环境变量的方式。 - -- 使用 "--build-arg" 指定 "docker build" 的相关环境变量 -``` -docker build \ - --build-arg "HTTP_PROXY=http://proxy.example.com:8080/" \ - --build-arg "HTTPS_PROXY=http://proxy.example.com:8080/" \ - --build-arg "NO_PROXY=localhost,127.0.0.1,.example.com" . -``` - -- 在 Dockerfile 中指定相关环境变量 - -| 环境变量 | Dockerfile 示例 | -| -------- | ---------------- | -| HTTP_PROXY | ENV HTTP_PROXY="http://proxy.example.com:8080/" | -| HTTPS_PROXY | ENV HTTPS_PROXY="http://proxy.example.com:8080/" | -| NO_PROXY | ENV NO_PROXY="localhost,127.0.0.1,.example.com" | - diff --git a/advanced_network/port_mapping.md b/advanced_network/port_mapping.md deleted file mode 100644 index dc6f92d..0000000 --- a/advanced_network/port_mapping.md +++ /dev/null @@ -1,57 +0,0 @@ -# 映射容器端口到宿主主机的实现 - -默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。 - -## 容器访问外部实现 - -容器所有到外部网络的连接,源地址都会被 NAT 成本地系统的 IP 地址。这是使用 `iptables` 的源地址伪装操作实现的。 - -查看主机的 NAT 规则。 - -```bash -$ sudo iptables -t nat -nL -... -Chain POSTROUTING (policy ACCEPT) -target prot opt source destination -MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16 -... -``` - -其中,上述规则将所有源地址在 `172.17.0.0/16` 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。 - -## 外部访问容器实现 - -容器允许外部访问,可以在 `docker run` 时候通过 `-p` 或 `-P` 参数来启用。 - -不管用那种办法,其实也是在本地的 `iptable` 的 nat 表中添加相应的规则。 - -使用 `-P` 时: - -```bash -$ iptables -t nat -nL -... -Chain DOCKER (2 references) -target prot opt source destination -DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80 -``` - -使用 `-p 80:80` 时: - -```bash -$ iptables -t nat -nL -Chain DOCKER (2 references) -target prot opt source destination -DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80 -``` - -注意: - -* 这里的规则映射了 `0.0.0.0`,意味着将接受主机来自所有接口的流量。用户可以通过 `-p IP:host_port:container_port` 或 `-p IP::port` 来指定允许访问容器的主机上的 IP、接口等,以制定更严格的规则。 - -* 如果希望永久绑定到某个固定的 IP 地址,可以在 Docker 配置文件 `/etc/docker/daemon.json` 中添加如下内容。 - -```json -{ - "ip": "0.0.0.0" -} -``` diff --git a/advanced_network/ptp.md b/advanced_network/ptp.md deleted file mode 100644 index 738a751..0000000 --- a/advanced_network/ptp.md +++ /dev/null @@ -1,45 +0,0 @@ -# 示例:创建一个点到点连接 -默认情况下,Docker 会将所有容器连接到由 `docker0` 提供的虚拟子网中。 - -用户有时候需要两个容器之间可以直连通信,而不用通过主机网桥进行桥接。 - -解决办法很简单:创建一对 `peer` 接口,分别放到两个容器中,配置成点到点链路类型即可。 - -首先启动 2 个容器: -```bash -$ docker run -i -t --rm --net=none base /bin/bash -root@1f1f4c1f931a:/# -$ docker run -i -t --rm --net=none base /bin/bash -root@12e343489d2f:/# -``` - -找到进程号,然后创建网络命名空间的跟踪文件。 -```bash -$ docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a -2989 -$ docker inspect -f '{{.State.Pid}}' 12e343489d2f -3004 -$ sudo mkdir -p /var/run/netns -$ sudo ln -s /proc/2989/ns/net /var/run/netns/2989 -$ sudo ln -s /proc/3004/ns/net /var/run/netns/3004 -``` - -创建一对 `peer` 接口,然后配置路由 -```bash -$ sudo ip link add A type veth peer name B - -$ sudo ip link set A netns 2989 -$ sudo ip netns exec 2989 ip addr add 10.1.1.1/32 dev A -$ sudo ip netns exec 2989 ip link set A up -$ sudo ip netns exec 2989 ip route add 10.1.1.2/32 dev A - -$ sudo ip link set B netns 3004 -$ sudo ip netns exec 3004 ip addr add 10.1.1.2/32 dev B -$ sudo ip netns exec 3004 ip link set B up -$ sudo ip netns exec 3004 ip route add 10.1.1.1/32 dev B -``` -现在这 2 个容器就可以相互 ping 通,并成功建立连接。点到点链路不需要子网和子网掩码。 - -此外,也可以不指定 `--net=none` 来创建点到点链路。这样容器还可以通过原先的网络来通信。 - -利用类似的办法,可以创建一个只跟主机通信的容器。但是一般情况下,更推荐使用 `--icc=false` 来关闭容器之间的通信。 diff --git a/advanced_network/quick_guide.md b/advanced_network/quick_guide.md deleted file mode 100644 index 455a818..0000000 --- a/advanced_network/quick_guide.md +++ /dev/null @@ -1,26 +0,0 @@ -# 快速配置指南 - -下面是一个跟 Docker 网络相关的命令列表。 - -其中有些命令选项只有在 Docker 服务启动的时候才能配置,而且不能马上生效。 - -* `-b BRIDGE` 或 `--bridge=BRIDGE` 指定容器挂载的网桥 -* `--bip=CIDR` 定制 docker0 的掩码 -* `-H SOCKET...` 或 `--host=SOCKET...` Docker 服务端接收命令的通道 -* `--icc=true|false` 是否支持容器之间进行通信 -* `--ip-forward=true|false` 请看下文容器之间的通信 -* `--iptables=true|false` 是否允许 Docker 添加 iptables 规则 -* `--mtu=BYTES` 容器网络中的 MTU - -下面2个命令选项既可以在启动服务时指定,也可以在启动容器时指定。在 Docker 服务启动的时候指定则会成为默认值,后面执行 `docker run` 时可以覆盖设置的默认值。 - -* `--dns=IP_ADDRESS...` 使用指定的DNS服务器 -* `--dns-search=DOMAIN...` 指定DNS搜索域 - -最后这些选项只有在 `docker run` 执行时使用,因为它是针对容器的特性内容。 - -* `-h HOSTNAME` 或 `--hostname=HOSTNAME` 配置容器主机名 -* `--link=CONTAINER_NAME:ALIAS` 添加到另一个容器的连接 -* `--net=bridge|none|container:NAME_or_ID|host` 配置容器的桥接模式 -* `-p SPEC` 或 `--publish=SPEC` 映射容器端口到宿主主机 -* `-P or --publish-all=true|false` 映射容器所有端口到宿主主机 diff --git a/buildx/multi-arch-images.md b/buildx/multi-arch-images.md index 8b190fb..e90dfb7 100644 --- a/buildx/multi-arch-images.md +++ b/buildx/multi-arch-images.md @@ -1,38 +1,62 @@ -# 使用 buildx 构建多种系统架构支持的 Docker 镜像 +# 构建多种系统架构支持的 Docker 镜像 -在之前的版本中构建多种系统架构支持的 Docker 镜像,要想使用统一的名字必须使用 [`$ docker manifest`](../image/manifest.md) 命令。 +Docker 镜像可以支持多种系统架构,这意味着你可以在 `x86_64`、`arm64` 等不同架构的机器上运行同一个镜像。这是通过一个名为 "manifest list"(或称为 "fat manifest")的文件来实现的。 -在 Docker 19.03+ 版本中可以使用 `$ docker buildx build` 命令使用 `BuildKit` 构建镜像。该命令支持 `--platform` 参数可以同时构建支持多种系统架构的 Docker 镜像,大大简化了构建步骤。 +## Manifest List 是什么? -## 新建 `builder` 实例 +Manifest list 是一个包含了多个指向不同架构镜像的 manifest 的文件。当你拉取一个支持多架构的镜像时,Docker 会自动根据你当前的系统架构选择并拉取对应的镜像。 -Docker for Linux 不支持构建 `arm` 架构镜像,我们可以运行一个新的容器让其支持该特性,Docker 桌面版无需进行此项设置。 +例如,官方的 `hello-world` 镜像就支持多种架构。你可以使用 `docker manifest inspect` 命令来查看它的 manifest list: ```bash -$ docker run --rm --privileged tonistiigi/binfmt:latest --install all +$ docker manifest inspect hello-world +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "manifests": [ + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 525, + "digest": "sha256:80852a401a974d9e923719a948cc5335a0a4435be8778b475844a7153a2382e5", + "platform": { + "architecture": "amd64", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 525, + "digest": "sha256:3adea81344be1724b383d501736c3852939b33b3903d02474373700b25e5d6e3", + "platform": { + "architecture": "arm", + "os": "linux", + "variant": "v5" + } + }, + // ... more architectures + ] +} ``` -由于 Docker 默认的 `builder` 实例不支持同时指定多个 `--platform`,我们必须首先创建一个新的 `builder` 实例。同时由于国内拉取镜像较缓慢,我们可以使用配置了 [镜像加速地址](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) 的 [`dockerpracticesig/buildkit:master`](https://github.com/docker-practice/buildx) 镜像替换官方镜像。 +## 使用 `docker buildx` 构建多架构镜像 -> 如果你有私有的镜像加速器,可以基于 https://github.com/docker-practice/buildx 构建自己的 buildkit 镜像并使用它。 +在 Docker 19.03+ 版本中,`docker buildx` 是推荐的用于构建多架构镜像的工具。它使用 `BuildKit` 作为后端,可以大大简化构建过程。 + +### 新建 `builder` 实例 + +首先,你需要创建一个新的 `builder` 实例,因为它支持同时为多个平台构建。 ```bash -# 适用于国内环境 -$ docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master - -# 适用于腾讯云环境(腾讯云主机、coding.net 持续集成) -$ docker buildx create --use --name=mybuilder-cn --driver docker-container --driver-opt image=dockerpracticesig/buildkit:master-tencent - -# $ docker buildx create --name mybuilder --driver docker-container - -$ docker buildx use mybuilder +$ docker buildx create --name mybuilder --use +$ docker buildx inspect --bootstrap ``` -## 构建镜像 +### 构建和推送 -新建 Dockerfile 文件。 +使用 `docker buildx build` 命令并指定 `--platform` 参数,可以同时构建支持多种架构的镜像。`--push` 参数会将构建好的镜像和 manifest list 推送到 Docker 仓库。 -```docker +```dockerfile +# Dockerfile FROM --platform=$TARGETPLATFORM alpine RUN uname -a > /os.txt @@ -40,87 +64,58 @@ RUN uname -a > /os.txt CMD cat /os.txt ``` -使用 `$ docker buildx build` 命令构建镜像,注意将 `myusername` 替换为自己的 Docker Hub 用户名。 - -`--push` 参数表示将构建好的镜像推送到 Docker 仓库。 - ```bash -$ docker buildx build --platform linux/arm,linux/arm64,linux/amd64 -t myusername/hello . --push - -# 查看镜像信息 -$ docker buildx imagetools inspect myusername/hello +$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t your-username/multi-arch-image . --push ``` -在不同架构运行该镜像,可以得到该架构的信息。 +构建完成后,你就可以在不同架构的机器上拉取并运行 `your-username/multi-arch-image` 这个镜像了。 -```bash -# arm -$ docker run -it --rm myusername/hello -Linux buildkitsandbox 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 armv7l Linux +### 架构相关的构建参数 -# arm64 -$ docker run -it --rm myusername/hello -Linux buildkitsandbox 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 aarch64 Linux +在 `Dockerfile` 中,你可以使用一些预定义的构建参数来根据目标平台定制构建过程: -# amd64 -$ docker run -it --rm myusername/hello -Linux buildkitsandbox 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 Linux -``` +* `TARGETPLATFORM`: 构建镜像的目标平台,例如 `linux/amd64`。 +* `TARGETOS`: 目标平台的操作系统,例如 `linux`。 +* `TARGETARCH`: 目标平台的架构,例如 `amd64`。 +* `TARGETVARIANT`: 目标平台的变种,例如 `v7`。 +* `BUILDPLATFORM`: 构建环境的平台。 +* `BUILDOS`: 构建环境的操作系统。 +* `BUILDARCH`: 构建环境的架构。 +* `BUILDVARIANT`: 构建环境的变种。 -## 架构相关变量 +例如,你可以这样编写 `Dockerfile` 来拷贝特定架构的二进制文件: -`Dockerfile` 支持如下架构相关的变量 - -**TARGETPLATFORM** - -构建镜像的目标平台,例如 `linux/amd64`, `linux/arm/v7`, `windows/amd64`。 - -**TARGETOS** - -`TARGETPLATFORM` 的 OS 类型,例如 `linux`, `windows` - -**TARGETARCH** - -`TARGETPLATFORM` 的架构类型,例如 `amd64`, `arm` - -**TARGETVARIANT** - -`TARGETPLATFORM` 的变种,该变量可能为空,例如 `v7` - -**BUILDPLATFORM** - -构建镜像主机平台,例如 `linux/amd64` - -**BUILDOS** - -`BUILDPLATFORM` 的 OS 类型,例如 `linux` - -**BUILDARCH** - -`BUILDPLATFORM` 的架构类型,例如 `amd64` - -**BUILDVARIANT** - -`BUILDPLATFORM` 的变种,该变量可能为空,例如 `v7` - -### 使用举例 - -例如我们要构建支持 `linux/arm/v7` 和 `linux/amd64` 两种架构的镜像。假设已经生成了两个平台对应的二进制文件: - -* `bin/dist-linux-arm` -* `bin/dist-linux-amd64` - -那么 `Dockerfile` 可以这样书写: - -```docker +```dockerfile FROM scratch -# 使用变量必须申明 ARG TARGETOS - ARG TARGETARCH COPY bin/dist-${TARGETOS}-${TARGETARCH} /dist -ENTRYPOINT ["dist"] +ENTRYPOINT ["/dist"] ``` + +## 使用 `docker manifest` (底层工具) + +`docker manifest` 是一个更底层的命令,可以用来创建、检查和推送 manifest list。虽然 `docker buildx` 在大多数情况下更方便,但了解 `docker manifest` 仍然有助于理解其工作原理。 + +### 创建 manifest list + +```bash +# 首先,为每个架构构建并推送镜像 +$ docker buildx build --platform linux/amd64 -t your-username/my-app:amd64 . --push +$ docker buildx build --platform linux/arm64 -t your-username/my-app:arm64 . --push + +# 然后,创建一个 manifest list,将它们组合在一起 +$ docker manifest create your-username/my-app:latest \ + --amend your-username/my-app:amd64 \ + --amend your-username/my-app:arm64 + +# 最后,推送 manifest list +$ docker manifest push your-username/my-app:latest +``` + +### 检查 manifest list + +你可以使用 `docker manifest inspect` 来查看一个 manifest list 的详细信息,如上文所示。 \ No newline at end of file diff --git a/etcd/etcdctl-v2.md b/etcd/etcdctl-v2.md deleted file mode 100644 index d37e4ce..0000000 --- a/etcd/etcdctl-v2.md +++ /dev/null @@ -1,281 +0,0 @@ -# 使用 etcdctl v2 - -`etcdctl` 是一个命令行客户端,它能提供一些简洁的命令,供用户直接跟 `etcd` 服务打交道,而无需基于 `HTTP API` 方式。这在某些情况下将很方便,例如用户对服务进行测试或者手动修改数据库内容。我们也推荐在刚接触 `etcd` 时通过 `etcdctl` 命令来熟悉相关的操作,这些操作跟 `HTTP API` 实际上是对应的。 - -`etcd` 项目二进制发行包中已经包含了 `etcdctl` 工具,没有的话,可以从 [github.com/etcd-io/etcd/releases](https://github.com/etcd-io/etcd/releases) 下载。 - -`etcdctl` 支持如下的命令,大体上分为数据库操作和非数据库操作两类,后面将分别进行解释。 - -``` -$ etcdctl -h -NAME: - etcdctl - A simple command line client for etcd. - -USAGE: - etcdctl [global options] command [command options] [arguments...] - -VERSION: - 2.0.0-rc.1 - -COMMANDS: - backup backup an etcd directory - mk make a new key with a given value - mkdir make a new directory - rm remove a key - rmdir removes the key if it is an empty directory or a key-value pair - get retrieve the value of a key - ls retrieve a directory - set set the value of a key - setdir create a new or existing directory - update update an existing key with a given value - updatedir update an existing directory - watch watch a key for changes - exec-watch watch a key for changes and exec an executable - member member add, remove and list subcommands - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS: - --debug output cURL commands which can be used to reproduce the request - --no-sync don't synchronize cluster information before sending request - --output, -o 'simple' output response in the given format (`simple` or `json`) - --peers, -C a comma-delimited list of machine addresses in the cluster (default: "127.0.0.1:4001") - --cert-file identify HTTPS client using this SSL certificate file - --key-file identify HTTPS client using this SSL key file - --ca-file verify certificates of HTTPS-enabled servers using this CA bundle - --help, -h show help - --version, -v print the version -``` - -## 数据库操作 -数据库操作围绕对键值和目录的 CRUD (符合 REST 风格的一套操作:Create)完整生命周期的管理。 - -etcd 在键的组织上采用了层次化的空间结构(类似于文件系统中目录的概念),用户指定的键可以为单独的名字,如 `testkey`,此时实际上放在根目录 `/` 下面,也可以为指定目录结构,如 `cluster1/node2/testkey`,则将创建相应的目录结构。 - -*注:CRUD 即 Create, Read, Update, Delete,是符合 REST 风格的一套 API 操作。* - -### set -指定某个键的值。例如 -```bash -$ etcdctl set /testdir/testkey "Hello world" -Hello world -``` -支持的选项包括: -```bash ---ttl '0' 该键值的超时时间(单位为秒),不配置(默认为 0)则永不超时 ---swap-with-value value 若该键现在的值是 value,则进行设置操作 ---swap-with-index '0' 若该键现在的索引值是指定索引,则进行设置操作 -``` - -### get -获取指定键的值。例如 -```bash -$ etcdctl set testkey hello -hello -$ etcdctl update testkey world -world -``` - -当键不存在时,则会报错。例如 -```bash -$ etcdctl get testkey2 -Error: 100: Key not found (/testkey2) [1] -``` - -支持的选项为 -```bash ---sort 对结果进行排序 ---consistent 将请求发给主节点,保证获取内容的一致性 -``` - -### update -当键存在时,更新值内容。例如 -```bash -$ etcdctl set testkey hello -hello -$ etcdctl update testkey world -world -``` - -当键不存在时,则会报错。例如 -```bash -$ etcdctl update testkey2 world -Error: 100: Key not found (/testkey2) [1] -``` - -支持的选项为 -```bash ---ttl '0' 超时时间(单位为秒),不配置(默认为 0)则永不超时 -``` - -### rm -删除某个键值。例如 -```bash -$ etcdctl rm testkey -``` - -当键不存在时,则会报错。例如 -```bash -$ etcdctl rm testkey2 -Error: 100: Key not found (/testkey2) [8] -``` - -支持的选项为 -```bash ---dir 如果键是个空目录或者键值对则删除 ---recursive 删除目录和所有子键 ---with-value 检查现有的值是否匹配 ---with-index '0' 检查现有的 index 是否匹配 -``` - -### mk -如果给定的键不存在,则创建一个新的键值。例如 -```bash -$ etcdctl mk /testdir/testkey "Hello world" -Hello world -``` -当键存在的时候,执行该命令会报错,例如 -```bash -$ etcdctl set testkey "Hello world" -Hello world -$ ./etcdctl mk testkey "Hello world" -Error: 105: Key already exists (/testkey) [2] -``` - -支持的选项为 -```bash ---ttl '0' 超时时间(单位为秒),不配置(默认为 0)则永不超时 -``` - -### mkdir -如果给定的键目录不存在,则创建一个新的键目录。例如 -```bash -$ etcdctl mkdir testdir -``` -当键目录存在的时候,执行该命令会报错,例如 -```bash -$ etcdctl mkdir testdir -$ etcdctl mkdir testdir -Error: 105: Key already exists (/testdir) [7] -``` -支持的选项为 -```bash ---ttl '0' 超时时间(单位为秒),不配置(默认为 0)则永不超时 -``` - -### setdir - -创建一个键目录,无论存在与否。 - -支持的选项为 -```bash ---ttl '0' 超时时间(单位为秒),不配置(默认为 0)则永不超时 -``` - -### updatedir -更新一个已经存在的目录。 -支持的选项为 -```bash ---ttl '0' 超时时间(单位为秒),不配置(默认为 0)则永不超时 -``` - -### rmdir -删除一个空目录,或者键值对。 - -若目录不空,会报错 -```bash -$ etcdctl set /dir/testkey hi -hi -$ etcdctl rmdir /dir -Error: 108: Directory not empty (/dir) [13] -``` - -### ls -列出目录(默认为根目录)下的键或者子目录,默认不显示子目录中内容。 - -例如 -```bash -$ ./etcdctl set testkey 'hi' -hi -$ ./etcdctl set dir/test 'hello' -hello -$ ./etcdctl ls -/testkey -/dir -$ ./etcdctl ls dir -/dir/test -``` - -支持的选项包括 -```bash ---sort 将输出结果排序 ---recursive 如果目录下有子目录,则递归输出其中的内容 --p 对于输出为目录,在最后添加 `/` 进行区分 -``` - -## 非数据库操作 - -### backup -备份 etcd 的数据。 - -支持的选项包括 -```bash ---data-dir etcd 的数据目录 ---backup-dir 备份到指定路径 -``` -### watch -监测一个键值的变化,一旦键值发生更新,就会输出最新的值并退出。 - -例如,用户更新 testkey 键值为 Hello world。 -```bash -$ etcdctl watch testkey -Hello world -``` - -支持的选项包括 -```bash ---forever 一直监测,直到用户按 `CTRL+C` 退出 ---after-index '0' 在指定 index 之前一直监测 ---recursive 返回所有的键值和子键值 -``` -### exec-watch -监测一个键值的变化,一旦键值发生更新,就执行给定命令。 - -例如,用户更新 testkey 键值。 -```bash -$ etcdctl exec-watch testkey -- sh -c 'ls' -default.etcd -Documentation -etcd -etcdctl -etcd-migrate -README-etcdctl.md -README.md -``` - -支持的选项包括 -```bash ---after-index '0' 在指定 index 之前一直监测 ---recursive 返回所有的键值和子键值 -``` - -### member - -通过 list、add、remove 命令列出、添加、删除 etcd 实例到 etcd 集群中。 - -例如本地启动一个 etcd 服务实例后,可以用如下命令进行查看。 - -```bash -$ etcdctl member list -ce2a822cea30bfca: name=default peerURLs=http://localhost:2380,http://localhost:7001 clientURLs=http://localhost:2379,http://localhost:4001 -``` - -## 命令选项 -* `--debug` 输出 cURL 命令,显示执行命令的时候发起的请求 -* `--no-sync` 发出请求之前不同步集群信息 -* `--output, -o 'simple'` 输出内容的格式 (`simple` 为原始信息,`json` 为进行json格式解码,易读性好一些) -* `--peers, -C` 指定集群中的同伴信息,用逗号隔开 (默认为: "127.0.0.1:4001") -* `--cert-file` HTTPS 下客户端使用的 SSL 证书文件 -* `--key-file` HTTPS 下客户端使用的 SSL 密钥文件 -* `--ca-file` 服务端使用 HTTPS 时,使用 CA 文件进行验证 -* `--help, -h` 显示帮助命令信息 -* `--version, -v` 打印版本信息 diff --git a/image/manifest.md b/image/manifest.md deleted file mode 100644 index b55d749..0000000 --- a/image/manifest.md +++ /dev/null @@ -1,162 +0,0 @@ -# 构建多种系统架构支持的 Docker 镜像 -- docker manifest 命令详解 - -我们知道使用镜像创建一个容器,该镜像必须与 Docker 宿主机系统架构一致,例如 `Linux x86_64` 架构的系统中只能使用 `Linux x86_64` 的镜像创建容器。 - -> Windows、macOS 除外,其使用了 [binfmt_misc](https://docs.docker.com/docker-for-mac/multi-arch/) 提供了多种架构支持,在 Windows、macOS 系统上 (x86_64) 可以运行 arm 等其他架构的镜像。 - -例如我们在 `Linux x86_64` 中构建一个 `username/test` 镜像。 - -```docker -FROM alpine - -CMD echo 1 -``` - -构建镜像后推送到 Docker Hub,之后我们尝试在树莓派 `Linux arm64v8` 中使用这个镜像。 - -```bash -$ docker run -it --rm username/test -``` - -可以发现这个镜像根本获取不到。 - -要解决这个问题,通常采用的做法是通过镜像名区分不同系统架构的镜像,例如在 `Linux x86_64` 和 `Linux arm64v8` 分别构建 `username/test` 和 `username/arm64v8-test` 镜像。运行时使用对应架构的镜像即可。 - -这样做显得很繁琐,那么有没有一种方法让 Docker 引擎根据系统架构自动拉取对应的镜像呢? - -我们发现在 `Linux x86_64` 和 `Linux arm64v8` 架构的计算机中分别使用 `golang:alpine` 镜像运行容器 `$ docker run golang:alpine go version` 时,容器能够正常的运行。 - -这是什么原因呢? - -原因就是 `golang:alpine` 官方镜像有一个 [`manifest` 列表 (`manifest list`)](https://docs.docker.com/registry/spec/manifest-v2-2/)。 - -当用户获取一个镜像时,Docker 引擎会首先查找该镜像是否有 `manifest` 列表,如果有的话 Docker 引擎会按照 Docker 运行环境(系统及架构)查找出对应镜像(例如 `golang:alpine`)。如果没有的话会直接获取镜像(例如上例中我们构建的 `username/test`)。 - -我们可以使用 `$ docker manifest inspect golang:alpine` 查看这个 `manifest` 列表的结构。 - -```bash -$ docker manifest inspect golang:alpine -``` - -```json -{ - "schemaVersion": 2, - "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", - "manifests": [ - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 1365, - "digest": "sha256:5e28ac423243b187f464d635bcfe1e909f4a31c6c8bce51d0db0a1062bec9e16", - "platform": { - "architecture": "amd64", - "os": "linux" - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 1365, - "digest": "sha256:2945c46e26c9787da884b4065d1de64cf93a3b81ead1b949843dda1fcd458bae", - "platform": { - "architecture": "arm", - "os": "linux", - "variant": "v7" - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 1365, - "digest": "sha256:87fff60114fd3402d0c1a7ddf1eea1ded658f171749b57dc782fd33ee2d47b2d", - "platform": { - "architecture": "arm64", - "os": "linux", - "variant": "v8" - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 1365, - "digest": "sha256:607b43f1d91144f82a9433764e85eb3ccf83f73569552a49bc9788c31b4338de", - "platform": { - "architecture": "386", - "os": "linux" - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 1365, - "digest": "sha256:25ead0e21ed5e246ce31e274b98c09aaf548606788ef28eaf375dc8525064314", - "platform": { - "architecture": "ppc64le", - "os": "linux" - } - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 1365, - "digest": "sha256:69f5907fa93ea591175b2c688673775378ed861eeb687776669a48692bb9754d", - "platform": { - "architecture": "s390x", - "os": "linux" - } - } - ] -} -``` - -可以看出 `manifest` 列表中包含了不同系统架构所对应的镜像 `digest` 值,这样 Docker 就可以在不同的架构中使用相同的 `manifest` (例如 `golang:alpine`) 获取对应的镜像。 - -下面介绍如何使用 `$ docker manifest` 命令创建并推送 `manifest` 列表到 Docker Hub。 - -## 构建镜像 - -首先在 `Linux x86_64` 构建 `username/x8664-test` 镜像。并在 `Linux arm64v8` 中构建 `username/arm64v8-test` 镜像,构建好之后推送到 Docker Hub。 - -## 创建 `manifest` 列表 - -```bash -# $ docker manifest create MANIFEST_LIST MANIFEST [MANIFEST...] -$ docker manifest create username/test \ - username/x8664-test \ - username/arm64v8-test -``` - -当要修改一个 `manifest` 列表时,可以加入 `-a` 或 `--amend` 参数。 - -## 设置 `manifest` 列表 - -```bash -# $ docker manifest annotate [OPTIONS] MANIFEST_LIST MANIFEST -$ docker manifest annotate username/test \ - username/x8664-test \ - --os linux --arch x86_64 - -$ docker manifest annotate username/test \ - username/arm64v8-test \ - --os linux --arch arm64 --variant v8 -``` - -这样就配置好了 `manifest` 列表。 - -## 查看 `manifest` 列表 - -```bash -$ docker manifest inspect username/test -``` - -## 推送 `manifest` 列表 - -最后我们可以将其推送到 Docker Hub。 - -```bash -$ docker manifest push username/test -``` - -## 测试 - -我们在 `Linux x86_64` `Linux arm64v8` 中分别执行 `$ docker run -it --rm username/test` 命令,发现可以正确的执行。 - -## 官方博客 - -详细了解 `manifest` 可以阅读官方博客。 - -* https://www.docker.com/blog/multi-arch-all-the-things/ diff --git a/network/README.md b/network/README.md index d1b7dcf..e6f0c7e 100644 --- a/network/README.md +++ b/network/README.md @@ -1,3 +1,48 @@ -# Docker 中的网络功能介绍 +# 网络配置 -Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。 +当 Docker 启动时,会自动在主机上创建一个 `docker0` 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。它会在挂载到它的网口之间进行转发。 + +同时,Docker 随机分配一个本地未占用的私有网段(在 [RFC1918](https://datatracker.ietf.org/doc/html/rfc1918) 中定义)中的一个地址给 `docker0` 接口。比如典型的 `172.17.42.1`,掩码为 `255.255.0.0`。此后启动的容器内的网口也会自动分配一个同一网段(`172.17.0.0/16`)的地址。 + +当创建一个 Docker 容器的时候,同时会创建了一对 `veth pair` 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 `eth0`;另一端在本地并被挂载到 `docker0` 网桥,名称以 `veth` 开头(例如 `vethAQI2QT`)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。 + +![Docker 网络](./_images/network.png) + +## 用户自定义网络 + +虽然默认的 `bridge` 网络可以满足大部分需求,但为了更好地隔离容器、或满足特定的网络需求,我们推荐使用用户自定义网络。 + +用户可以创建 `bridge`、`overlay` 或 `macvlan` 等不同类型的自定义网络。 + +### 创建一个自定义 bridge 网络 + +```bash +$ docker network create my-net +``` + +### 连接容器到自定义网络 + +在启动容器时,可以使用 `--network` 选项来指定网络。 + +```bash +$ docker run -it --rm --name busybox1 --network my-net busybox sh +$ docker run -it --rm --name busybox2 --network my-net busybox sh +``` + +在 `busybox1` 的终端中,可以 `ping` 通 `busybox2`。 + +```bash +/ # ping busybox2 +PING busybox2 (172.19.0.3): 56 data bytes +64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.083 ms +``` + +### 容器互联的废弃与替代 + +在 Docker 的早期版本中,`--link` 选项被用来连接容器。然而,这个功能现在已经被废弃,并且不推荐在生产环境中使用。 + +**注意:`--link` 是一个遗留功能。它可能会在未来的版本中被移除。我们强烈建议使用用户自定义网络来连接多个容器。** + +使用自定义网络,容器之间可以通过容器名直接进行通信,这比使用 `--link` 更加灵活和强大。 + +接下来的部分将介绍在一些场景中,Docker 所有的网络定制配置。以及通过 Linux 命令来调整、补充、甚至替换 Docker 默认的网络配置。 diff --git a/network/linking.md b/network/linking.md deleted file mode 100644 index e24ebd9..0000000 --- a/network/linking.md +++ /dev/null @@ -1,67 +0,0 @@ -# 容器互联 - -如果你之前有 `Docker` 使用经验,你可能已经习惯了使用 `--link` 参数来使容器互联。 - -随着 Docker 网络的完善,强烈建议大家将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 `--link` 参数。 - -## 新建网络 - -下面先创建一个新的 Docker 网络。 - -```bash -$ docker network create -d bridge my-net -``` - -`-d` 参数指定 Docker 网络类型,有 `bridge` `overlay`。其中 `overlay` 网络类型用于 [Swarm mode](../swarm_mode/),在本小节中你可以忽略它。 - -## 连接容器 - -运行一个容器并连接到新建的 `my-net` 网络 - -```bash -$ docker run -it --rm --name busybox1 --network my-net busybox sh -``` - -打开新的终端,再运行一个容器并加入到 `my-net` 网络 - -```bash -$ docker run -it --rm --name busybox2 --network my-net busybox sh -``` - -再打开一个新的终端查看容器信息 - -```bash -$ docker container ls - -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -b47060aca56b busybox "sh" 11 minutes ago Up 11 minutes busybox2 -8720575823ec busybox "sh" 16 minutes ago Up 16 minutes busybox1 -``` - -下面通过 `ping` 来证明 `busybox1` 容器和 `busybox2` 容器建立了互联关系。 - -在 `busybox1` 容器输入以下命令 - -```bash -/ # ping busybox2 -PING busybox2 (172.19.0.3): 56 data bytes -64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.072 ms -64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.118 ms -``` - -用 ping 来测试连接 `busybox2` 容器,它会解析成 `172.19.0.3`。 - -同理在 `busybox2` 容器执行 `ping busybox1`,也会成功连接到。 - -```bash -/ # ping busybox1 -PING busybox1 (172.19.0.2): 56 data bytes -64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.064 ms -64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.143 ms -``` - -这样,`busybox1` 容器和 `busybox2` 容器建立了互联关系。 - -## Docker Compose - -如果你有多个容器之间需要互相连接,推荐使用 [Docker Compose](../compose)。 diff --git a/network/port_mapping.md b/network/port_mapping.md index 022287a..dc6f92d 100644 --- a/network/port_mapping.md +++ b/network/port_mapping.md @@ -1,79 +1,57 @@ -# 外部访问容器 +# 映射容器端口到宿主主机的实现 -容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 `-P` 或 `-p` 参数来指定端口映射。 +默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。 -当使用 `-P` 标记时,Docker 会随机映射一个端口到内部容器开放的网络端口。 +## 容器访问外部实现 -使用 `docker container ls` 可以看到,本地主机的 32768 被映射到了容器的 80 端口。此时访问本机的 32768 端口即可访问容器内 NGINX 默认页面。 +容器所有到外部网络的连接,源地址都会被 NAT 成本地系统的 IP 地址。这是使用 `iptables` 的源地址伪装操作实现的。 + +查看主机的 NAT 规则。 ```bash -$ docker run -d -P nginx:alpine - -$ docker container ls -l -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -fae320d08268 nginx:alpine "/docker-entrypoint.…" 24 seconds ago Up 20 seconds 0.0.0.0:32768->80/tcp bold_mcnulty +$ sudo iptables -t nat -nL +... +Chain POSTROUTING (policy ACCEPT) +target prot opt source destination +MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16 +... ``` -同样的,可以通过 `docker logs` 命令来查看访问记录。 +其中,上述规则将所有源地址在 `172.17.0.0/16` 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。 + +## 外部访问容器实现 + +容器允许外部访问,可以在 `docker run` 时候通过 `-p` 或 `-P` 参数来启用。 + +不管用那种办法,其实也是在本地的 `iptable` 的 nat 表中添加相应的规则。 + +使用 `-P` 时: ```bash -$ docker logs fa -172.17.0.1 - - [25/Aug/2020:08:34:04 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0" "-" +$ iptables -t nat -nL +... +Chain DOCKER (2 references) +target prot opt source destination +DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80 ``` -`-p` 则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort`。 - -## 映射所有接口地址 - -使用 `hostPort:containerPort` 格式本地的 80 端口映射到容器的 80 端口,可以执行 +使用 `-p 80:80` 时: ```bash -$ docker run -d -p 80:80 nginx:alpine -``` - -此时默认会绑定本地所有接口上的所有地址。 - -## 映射到指定地址的指定端口 - -可以使用 `ip:hostPort:containerPort` 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1 - -```bash -$ docker run -d -p 127.0.0.1:80:80 nginx:alpine -``` - -## 映射到指定地址的任意端口 - -使用 `ip::containerPort` 绑定 localhost 的任意端口到容器的 80 端口,本地主机会自动分配一个端口。 - -```bash -$ docker run -d -p 127.0.0.1::80 nginx:alpine -``` - -还可以使用 `udp` 标记来指定 `udp` 端口 - -```bash -$ docker run -d -p 127.0.0.1:80:80/udp nginx:alpine -``` - -## 查看映射端口配置 - -使用 `docker port` 来查看当前映射的端口配置,也可以查看到绑定的地址 - -```bash -$ docker port fa 80 -0.0.0.0:32768 +$ iptables -t nat -nL +Chain DOCKER (2 references) +target prot opt source destination +DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80 ``` 注意: -* 容器有自己的内部网络和 ip 地址(使用 `docker inspect` 查看,Docker 还可以有一个可变的网络配置。) -* `-p` 标记可以多次使用来绑定多个端口 +* 这里的规则映射了 `0.0.0.0`,意味着将接受主机来自所有接口的流量。用户可以通过 `-p IP:host_port:container_port` 或 `-p IP::port` 来指定允许访问容器的主机上的 IP、接口等,以制定更严格的规则。 -例如 +* 如果希望永久绑定到某个固定的 IP 地址,可以在 Docker 配置文件 `/etc/docker/daemon.json` 中添加如下内容。 -```bash -$ docker run -d \ - -p 80:80 \ - -p 443:443 \ - nginx:alpine +```json +{ + "ip": "0.0.0.0" +} ```