自己动手写Docker系列 -- 6.3 手动配置容器网络(下)

658 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情

简介

网络部分较为复杂,本篇先利用之前写好的基础容器和网桥部分,加上手工给容器配置网络,让其容器与外部网络部分功能正常,为后面程序编写打下基础

源码说明

同时放到了Gitee和Github上,都可进行获取

本章节对应的版本标签是:5.8,防止后面代码过多,不好查看,可切换到标签版本进行查看

思路梳理

在以前的文章中,我们完成了基础容器的功能,在单机情况下基本可用,但没有网络那就不完善

网络部分目前就写了一个IP分配管理工具,和network命令,用于创建网桥

在本人尝试过程中,如果不对网络配置部分有一定的了解,对应后面网络部分的编写和理解会感到很懵

所以我们需要进行一定的热身,先手工给我们的容器配置上网络,后面再用代码完成这部分的功能

关于如何手工进行配置,在《自己动手写Docker》和文章:好文 Linux Network Namespace 与 Bridge 网桥中,都有了很好的描述

跟着上面的链接走一遍了,基本上就通了(PS:如果在ping 114.114.114.114 始终不行,可能是之前的网络配置过于乱了,将iptables规则进行清理或者重启下主机)

对于手动创建网络和配置网络,这里就不再重复,本文的重点放到如果应用上面的知识给目前的容器配置网络

手动配置容器网络

实验环境基于代码的标签:6.2

该部分代码,完成了基本的容器功能和网桥的创建,基于这个基础,我们要做的事情如下:

  • 单容器:
    • 容器能访问宿主机网络(上篇中已完成)
    • 容器能访问外网
  • 多容器-同局域网能访问
  • 多容器-不同局域网能访问

下面我们继续来完善配置

添加Iptables规则,让容器能访问外网

上篇中我们完成了宿主机与容器的网络连通,但容器目前还是不能访问外网,我们需要添加如下的iptables规则

iptables -t nat -A POSTROUTING -s 162.16.0.0/24 -j MASQUEREAD

目前宿主机的iptables规则如下,看到docker也配置了这样的一条命令

$ iptables  -t  nat  -nL --line-numbers
Chain PREROUTING (policy ACCEPT)
num  target     prot opt source               destination
1    DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
1    DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

# 下面看到docker也添加了一条这样的规则
Chain POSTROUTING (policy ACCEPT)
num  target     prot opt source               destination
1    MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0
2    MASQUERADE  all  --  182.18.0.0/24        0.0.0.0/0
3    MASQUERADE  all  --  162.16.0.0/24        0.0.0.0/0

Chain DOCKER (2 references)
num  target     prot opt source               destination
1    RETURN     all  --  0.0.0.0/0            0.0.0.0/0

下面我们从容器内访问下:114.114.114.114,看到成功访问

$ ./main exec bird ping 114.114.114.114
PING 114.114.114.114 (114.114.114.114): 56 data bytes
64 bytes from 114.114.114.114: seq=0 ttl=77 time=32.301 ms
64 bytes from 114.114.114.114: seq=1 ttl=72 time=33.687 ms
64 bytes from 114.114.114.114: seq=2 ttl=75 time=33.727 ms
64 bytes from 114.114.114.114: seq=3 ttl=84 time=34.131 ms
64 bytes from 114.114.114.114: seq=4 ttl=82 time=35.087 ms
64 bytes from 114.114.114.114: seq=5 ttl=95 time=33.474 ms
^C
--- 114.114.114.114 ping statistics ---
6 packets transmitted, 6 packets received, 0% packet loss
round-trip min/avg/max = 32.301/33.734/35.087 ms

新增同局域网内的容器,进行访问

和上篇基本一样的操作,将新起动的容器分配同上一个容器一样的局域网ip:162.16.0.3

# 新启动容器2
$ ./main run -d -e bird=123 -name bird2 top
{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-04-23T14:38:34+08:00"}
{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-04-23T14:38:34+08:00"}
{"level":"info","msg":"all command is : top","time":"2022-04-23T14:38:34+08:00"}
{"level":"info","msg":"parent process run","time":"2022-04-23T14:38:34+08:00"}

# 新建veth,设置宿主机端的veth
$ ip link add birdveth2 type veth peer name birdveth3
$  ip link set dev birdveth2 master testbridge
$ ip link set dev birdveth2 up

# 设置容器2内部的veth
$ ps -aux|grep top
root        2769  0.0  0.0 539820 10860 ?        Ssl  07:01   0:01 /usr/libexec/xdg-desktop-portal
root        2773  0.0  0.0 500052 29516 ?        Ssl  07:01   0:00 /usr/libexec/xdg-desktop-portal-gtk
root       39207  0.0  0.0   1328     4 pts/4    S    09:47   0:01 top
root       64560  0.0  0.0   1328     4 pts/4    S    14:38   0:00 top
root       64749  0.0  0.0  12132   724 pts/4    R+   14:47   0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox top

$ ln -s /proc/64560/ns/net /var/run/netns/bird2

$ ip netns list
bird2
bird (id: 2)
k8svip (id: 1)

$ ip link set birdveth3 netns bird2
$ ./main exec bird2 ip link set dev lo up
$ ./main exec bird2 ip link set dev birdveth3 up
$ ./main exec bird2 ip addr add 162.16.0.3/24 dev birdveth3

# 成功ping通网关
$  ./main exec bird2 ping 162.16.0.1
PING 162.16.0.1 (162.16.0.1): 56 data bytes
64 bytes from 162.16.0.1: seq=0 ttl=64 time=0.135 ms
64 bytes from 162.16.0.1: seq=1 ttl=64 time=0.152 ms
^C
--- 162.16.0.1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.135/0.143/0.152 ms

# 容器2能成功联通容器1
$  ./main exec bird2 ping 162.16.0.2
PING 162.16.0.2 (162.16.0.2): 56 data bytes
64 bytes from 162.16.0.2: seq=0 ttl=64 time=0.138 ms
64 bytes from 162.16.0.2: seq=1 ttl=64 time=0.154 ms
^C
--- 162.16.0.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.138/0.146/0.154 ms


# 容器1能成功联通容器2
$  ./main exec bird ping 162.16.0.3
PING 162.16.0.3 (162.16.0.3): 56 data bytes
64 bytes from 162.16.0.3: seq=0 ttl=64 time=0.090 ms
64 bytes from 162.16.0.3: seq=1 ttl=64 time=0.154 ms
^C
--- 162.16.0.3 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.090/0.122/0.154 ms

$ ./main exec bird2 ip route add default via 162.16.0.1

$ ./main exec bird2 ping 114.114.114.114
PING 114.114.114.114 (114.114.114.114): 56 data bytes
64 bytes from 114.114.114.114: seq=0 ttl=82 time=32.376 ms
64 bytes from 114.114.114.114: seq=1 ttl=92 time=37.692 ms
^C
--- 114.114.114.114 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 32.376/35.034/37.692 ms

经过试验,我们处于同一个局域网的容器是能够联通的

配置DNAT,让外部能够访问内部服务

首先我们在容器内监听95端口,然后在宿主机内访问95端口是能正常访问的

# 在容器内监听95端口,能收到相关指令
$ ./main exec bird nc -lp 95
jkjfdks
kfjskd
��������
quit
exit
exit()
quit()

# 在宿主机中进行telnet并发送指令,如上可以进行显示(ctrl + ],后输入quit,退出telnet)
$ telnet 162.16.0.2 95
Trying 162.16.0.2...
Connected to 162.16.0.2.
Escape character is '^]'.
jkjfdks
kfjskd
^C^C
quit
exit
exit()
quit()
^]
telnet> quit
Connection closed.

但是如同我们在使用docker的过程中,我们是映射了端口的,如:docker run -tid --name centos8-2 -p 90:90 centos:8 bash

在上面的示例中,我们访问容器所在宿主机90端口,就能访问到容器的90端口,下面我们就通过配置来达到这个目的

进行下面的配置,下面会将宿主机95端口上的请求,都转到容器 162.16.0.2的95端口上

# 配置规则
$ iptables -t nat -A PREROUTING -p tcp -m tcp --dport 95 -j DNAT --to-destination 162.16.0.2:95

# 查看规则,看到docker也是通过相关配置达到这个目的的
$ iptables  -t  nat  -nL --line-numbers
Chain PREROUTING (policy ACCEPT)
num  target     prot opt source               destination
1    DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL
2    DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:95 to:162.16.0.2:95

Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
1    DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
num  target     prot opt source               destination
1    MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0
2    MASQUERADE  all  --  182.18.0.0/24        0.0.0.0/0
3    MASQUERADE  all  --  162.16.0.0/24        0.0.0.0/0
4    MASQUERADE  tcp  --  172.17.0.3           172.17.0.3           tcp dpt:90

Chain DOCKER (2 references)
num  target     prot opt source               destination
1    RETURN     all  --  0.0.0.0/0            0.0.0.0/0
2    DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:90 to:172.17.0.3:90
# 在宿主机上监听95端口
nc -lp 95

# 新开shell或者在和宿主机同一个局域网的主机上进行telnet连接
telnet 192.168.1.5 95 

重新启动容器监听后,看到相关的输出,证明外部也访问容器服务了

$ ./main exec bird nc -lp 95
jkjfdskfjs

总结

不同局域网的互通,尝试了下,有点难度,目前本人的知识理解还不能做到,只能后面再补上了,先通过实现这部分的功能