一、外部访问容器
默认情况下,容器是能够访问外部宿主机的,宿主机如果能访问外网,那么容器也是能访问外网的。但是更多的情况下我们需要容器中的一些网络应用能够被外部服务访问,可以通过 -P 或 -p 参数来指定端口映射来实现该需求。
-P和-p的区别:当使用 -P 标记时,Docker 会随机映射一个端口到内部容器开放的网络端口;而-p需要我们手动指定外部映射端口。
当使用 -P 标记时,Docker 会随机映射一个端口到内部容器开放的网络端口。
如下:我们运行一个nginx容器,使用-P后通过docker ps -l查看随机映射到宿主机的32777端口,此时访问本机的 32777端口即可访问容器内 NGINX 默认页面。
[root@k8s-m1 ~]# docker run -itd -P nginx
7f6b08549969e4d8d2d719939526a923ce9991c376418eb0753def511f689923
[root@k8s-m1 ~]# docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7f6b08549969 nginx "/docker-entrypoint.…" 4 seconds ago Up 2 seconds 0.0.0.0:32777->80/tcp crazy_pike
同样的,可以通过 docker logs 命令来查看访问记录。
[root@k8s-m1 ~]# docker logs 7f
10.12.13.1 - - [05/Jun/2023:01:15:40 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36" "-"
当使用-p标记时,则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有 ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort。
映射所有接口地址
使用 hostPort:containerPort 格式本地的 80 端口映射到容器的 80 端口,可以执行
docker run -d -p 80:80 nginx
此时默认会绑定本地所有接口上的所有地址。
映射到指定地址的指定端口
docker run -d -p 192.168.2.140:80:80 nginx
此时会映射到指定地址的指定端口
映射到指定地址的任意端口
使用 ip::containerPort 绑定 localhost 的任意端口到容器的 80 端口,本地主机会自动分配一个端口。
docker run -d -p 127.0.0.1::80 nginx
还可以使用 udp 标记来指定 udp 端口
docker run -d -p 127.0.0.1:80:80/udp nginx
#示例
[root@k8s-m1 ~]# docker run -itd -p 80:80 nginx
a1593e534b1a7fabeacd77dfc2c4f17402f871789c7c7d43d417990605649fc1
[root@k8s-m1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1593e534b1a nginx "/docker-entrypoint.…" 3 seconds ago Up 1 second 0.0.0.0:80->80/tcp zealous_thompson
查看映射端口配置
使用 docker port 来查看当前映射的端口配置,也可以查看到绑定的地址
[root@k8s-m1 ~]# docker port a1
80/tcp -> 0.0.0.0:800
注意:
更多详细信息包括网络存储啥的可以使用 docker inspect 查看
-p 标记可以多次使用来绑定多个端口
如:docker run -d -p 80:80 -p 443:443 nginx
二、容器互联
可以使用 --link 参数来使容器互联。或者建议将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 --link 参数。详见上一章节。
三、容器访问控制
容器的网络访问,主要都通过 Linux 上的 iptables 防火墙来进行实现和管理。iptables 是 Linux 上默认的防火墙软件,在大部分发行版中都自带。
容器访问外部网络
容器要想访问外部网络,需要本地系统的转发支持。在Linux 系统中,检查转发是否打开。
[root@k8s-m1 ~]# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
如果为 0,说明没有开启转发,则需要手动打开。
[root@k8s-m1 ~]#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 规则。可自行测试效果,实际单独使用情况较少。
四、自定义网桥
docker默认的网桥为 docker0(172.16.0.0/16),在docker服务启动时会自动创建,但用户也可以自定义网桥来连接各个容器。
在启动 Docker 服务的时候,使用 -b BRIDGE或–bridge=BRIDGE 来指定使用的网桥。
如果服务已经运行,那需要先停止服务,并删除旧的网桥。
[root@k8s-m1 ~]# systemctl stop docker
[root@k8s-m1 ~]# ip link set dev docker0 down
[root@k8s-m1 ~]# brctl delbr docker0
然后创建一个网桥 bridge0。
[root@k8s-m1 ~]# brctl addbr bridge0
[root@k8s-m1 ~]# ip addr add 192.168.0.1/16 dev bridge0
[root@k8s-m1 ~]# ip link set dev bridge0 up
查看确认网桥创建并启动。
[root@k8s-m1 ~]# ip addr show bridge0
3: bridge0:
link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.1/24 scope global bridge0
valid_lft forever preferred_lft forever
在 Docker 配置文件 /etc/docker/daemon.json 中添加如下内容,即可将 Docker 默认桥接到创建的网桥上。
{
“bridge”: “bridge0”,
}
启动 Docker 服务。
新建一个容器,可以看到它已经桥接到了 bridge0 上。
可以继续用 brctl show 命令查看桥接的信息。另外,在容器中可以使用 ip addr 和 ip route 命令来查看 IP 地址配置和路由信息。
五、配置 HTTP/HTTPS 网络代理
官网指导文档:https://docs.docker.com/network/proxy/
使用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 。并在该文件中添加相关环境变量。
{
“proxies”: {
“default”: {
“httpProxy”: “http://proxy.example.com:3128”,
“httpsProxy”: “https://proxy.example.com:3129”,
“noProxy”: “*.test.example.com,.example.org,127.0.0.0/8”
}
}
}
刷新配置并重启 docker 服务。
[root@k8s-m1 ~]# systemctl daemon-reload
[root@k8s-m1 ~]# 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 --rm alpine sh -c ‘env | grep -i _PROXY’
https_proxy=http://proxy.example.com:3129
HTTPS_PROXY=http://proxy.example.com:3129
http_proxy=http://proxy.example.com:3128
HTTP_PROXY=http://proxy.example.com:3128
no_proxy=.test.example.com,.example.org,127.0.0.0/8
NO_PROXY=.test.example.com,.example.org,127.0.0.0/8
docker build 过程设置网络代理
在容器构建阶段,如果需要使用 HTTP/HTTPS 代理,可以通过指定 “docker build” 的环境变量,或者在 Dockerfile 中指定环境变量的方式。
docker build \
--no-cache \
--progress=plain \
- <<EOF
FROM alpine
RUN env | grep -i _PROXY
EOF
#这个的意思就是从alpine镜像中环境变量获取_proxy的相关配置,大家可以测试看看效果。
六、实例:创建一个点到点连接
默认情况下,Docker 会将所有容器连接到由 docker0 提供的虚拟子网中。
用户有时候需要两个容器之间可以直连通信,而不用通过主机网桥进行桥接。
解决办法很简单:创建一对 peer 接口,分别放到两个容器中,配置成点到点链路类型即可。
首先启动 2 个容器:
[root@k8s-m1 ~]# docker run -di -t --rm --net=none centos /bin/bash
d58ec925720fc908997c4f1ed360a89c239ba5a2c7fb65cbca4b5c39af10c72e
[root@k8s-m1 ~]# docker run -di -t --rm --net=none centos /bin/bash
6728e25d6c8b3b4152f643ce3899ea2f37c305c5be2cf826aa5fe466f98db81f
找到进程号,然后创建网络命名空间的跟踪文件。
[root@k8s-m1 ~]# docker inspect -f '{{.State.Pid}}' 67
3087
[root@k8s-m1 ~]# docker inspect -f '{{.State.Pid}}' d5
896
[root@k8s-m1 ~]# mkdir -p /var/run/netns
[root@k8s-m1 ~]#ln -s /proc/3087/ns/net /var/run/netns/3087
[root@k8s-m1 ~]# ln -s /proc/896/ns/net /var/run/netns/896
创建一对 peer 接口,然后配置路由
[root@k8s-m1 ~]# ip link add A type veth peer name B
#将A放在名字为3087的网络命名空间,设置ip,启用,设置路由`在这里插入代码片`
[root@k8s-m1 ~]# ip link set A netns 3087
[root@k8s-m1 ~]# ip netns exec 3087 ip addr add 10.0.0.1/32 dev A
[root@k8s-m1 ~]# ip netns exec 3087 ip link set A up
[root@k8s-m1 ~]# ip netns exec 3087 ip route add 10.0.0.2/32 dev A
#将A放在名字为896的网络命名空间,设置ip,启用,设置路由
[root@k8s-m1 ~]# ip link set B netns 896
[root@k8s-m1 ~]# ip netns exec 896 ip addr add 10.0.0.2/32 dev B
[root@k8s-m1 ~]# ip netns exec 896 ip link set B up
[root@k8s-m1 ~]# ip netns exec 896 ip route add 10.0.0.1/32 dev B
然后测试
[root@k8s-m1 ~]# ip netns exec 3087 ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.173 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.063 ms
^C
通过测试发现现在这 2 个容器可以相互 ping 通,并成功建立连接。点到点链路不需要子网和子网掩码。
此外,也可以不指定 --net=none 来创建点到点链路。这样容器还可以通过原先的网络来通信。
利用类似的办法,可以创建一个只跟主机通信的容器(将另一个veth的端放在宿主机)。但是一般情况下,更推荐使用 --icc=false 来关闭容器之间的通信。