背景
随着 Docker 技术的发展和广泛流行,云原生应用和容器调度管理系统也成为 IT 领域大热的词汇,Kuberentes可以说是乘着Docker和微服务的东风,一经推出便迅速蹿红,所以当下很多开始进行kubernetes的学习。于是很多人将Kubernetes源码拉下来,但在本地进行make quick-release时,经常会遇到镜像拉取失败的错误1
2
3
4make quick-release
....
error: failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to create LLB definition: failed to do request: Head https://k8s.gcr.io/v2/build-image/kube-cross/manifests/v1.23.0-go1.17.2-bullseye.0: net/http: TLS handshake timeout
...
因为quick-release是使用容器进行编译的,这些容器存放在k8s.gcr.io上面,在中国大陆默认情况下是拉不到这些镜像的,这里就必须给docker进程设置一个代理:1
2
3
4
5
6
7# touch /etc/systemd/system/docker.service.d/proxy.conf
# vi /etc/systemd/system/docker.service.d/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 build设置代理1
2
3
4
5# 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" \
-t your/image:tag
当然有一些软件在运行时,不走系统的网络库,这样就算我们如何设置HTTP_PROXY/HTTPS_PROXY环境变量,这些软件也不能很好地工作。那么有没有一种方法让我们从上面这些繁琐的设置中解脱出来,还我一个干净清爽的操作系统呢,答案是肯定的,这就是我们今天要介绍的网关代理模式。
它就和servicemesh一样,将springcloud那一堆微服务要做的事情,下沉到基础设施层,这样我们的程序员就可以专注到业务开发上去了
网络拓扑
笔者的开发模式是windows+linux虚拟机的方式,linux是以虚拟机的方式跑在windows上面,虚拟机通过NAT模式连接至windows。
这里windows(192.168.88.1)其实是vm的网关,vm的数据包发往windows后,再由windows转发至外网。这里有两种思路,一种是主路由代理,一种是旁臂路由代理。
主路由:改造windows,在windows实现路由代理。
旁臂路由:添加一台VM,该VM实现路由代理,我们将其称作路由VM,其它VM的网关指向该路由VM。
由于笔记的宿主机是windows,如何将windows打造成代理路由是笔者的知识盲区,所以这里我们主要介绍旁臂路由模式。
原理
首先,要解释一下目前主流的你懂的上网方式原理,首先我们要在你懂的地方搭建一个服务端(这个不是本文的教学内容),然后在我们内网搭启动相应的客户端,客户端和服务端以某种加密方式通信,伪装成普通的请求(如https),这样逃过你懂的拦截。
这里服务端可以自己搭建也可以购买,一般自己搭建大家称作自建VPS,就是自己购买海外VPS,然后在上面安装Server,别人搭好的Server一般称作机场,机场名称的由来是因为最早ShadowSocket的客户端图标是一架飞机,然后客户端要寻找服务端连接,就好像是找飞机停靠的机场一样
代理模式
代理模式就是我们的手机或pc直接连接到这个客户端:
当我们手机连接wifi的时候有一个代理选项,讲的就是这种模式:
网关模式
网关模式就是将网关指向代理路由,然后路由内部再将数据包转发至该客户端:
我们手机连接wifi时,可以选择手动设置IP:
网关模式也是本文要讲的方式,这里我们主要讲解在代理路由内部,我们要如何将数据包转发至该客户端,现在主流的方式还是利用linux的netfilter的能力来进行转发的,这里又分成两种模式
REDIRECT
1 | # iptables -t nat -A PREROUTING -p tcp -j REDIRECT --to-port 1041 |
该命令将所有外部进来的tcp流量都转发至本地的1041端口,这种方式要确保内核的ip_forward参数是11
2# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
既然修改了数据包的目标IP地址,那么我们程序中不是就拿不到原始的目标IP了吗?linux的conntrack模块会记录链接的原始IP,并且为我们提供了一个系统调用来获取原始IP
getdestaddr(int fd, struct sockaddr_storage *destaddr)
TPROXY
对于TPROXY模式,实现的原理要相对复杂不少,需要借助iptables和路由1
2
3# iptables -t mangle -A PREROUTING -p tcp -j TPROXY --tproxy-mark 0x1/0x1 --on-port 8888
# ip rule add fwmark 0x1/0x1 pref 100 table 100
# ip route add local default dev lo table 100
由于tproxy并没有改变数据包,所以怎么让用户空间接收目标IP不属于本机的包呢?linux为socket提供了一个IP_TRANSPARENT选项,让进程可以bind一个不属于本机的地址,这也正是透明代理神奇的地方。
Openwrt安装(macvlan+docker)
原理搞明白之后,我们接下来进入动手阶段,openwrt是linux的一个发行版,它可以用来作为软路由的操作系统,我们只要装好它就可以得到一个网关代理。
环境准备
1. 安装docker
这里我们使用docker进行openwrt的安装,这样不影响我linux开发机的正常使用。关于docker的安装请大家自行搜索,或者参考centos7上安装docker环境
2. 拉取镜像
镜像列表可以查看这里
笔者是x86_64机器,运行以下命令拉取镜像1
# docker pull registry.cn-shanghai.aliyuncs.com/suling/openwrt:x86_64
网络设置
1. 打开网卡的混杂模式
1 | # ip link set eth0 promisc on |
这里eth0是笔者linux虚拟机的网卡名称,你必须改成你机器的实际网卡名称
这里开混杂模式是因为虚拟机的eth0网卡必须接收不同ip的数据包
2. 创建macvlan网络
这里我们要为docker先创建一个macvlan类型的网络1
2
3
4
5
6
7
8# docker network create -d macvlan --subnet=192.168.88.0/24 --gateway=192.168.88.1 -o parent=eth0 macvlan0
# docker network ls
NETWORK ID NAME DRIVER SCOPE
3f480a69e394 bridge bridge local
cc721823c8ca host host local
8ec9c9727726 kind bridge local
1b330a41a687 macvlan0 macvlan local
a8f4badfc912 none null local
- -d 指定网络类型为macvlan
- –subnet 指定该网络的cidr,它必须和linux开发机同处于一个网段
- –gateway 指定网关,同linux开发机同一个网关即可
- -o parent 指定要从哪个网卡创建macvlan
- macvlan0是网络名称,这个后面创建容器的时候使用
安装配置
1. 创建容器
1 | # docker run --restart always --name openwrt -d --network macvlan0 --ip 192.168.88.254 --privileged registry.cn-shanghai.aliyuncs.com/suling/openwrt:x86_64 /sbin/init |
其中–ip指定容器的ip地址,这里选择254相对靠后的ip,不容易和其它VM冲突。
2. 配置容器网络
先进入容器1
# docker exec -it openwrt bash
编辑/etc/config/network文件1
2
3
4
5
6
7
8
9
10
11
12# vi /etc/config/network
...
config interface 'lan'
option type 'bridge'
option ifname 'eth0'
option proto 'static'
option netmask '255.255.255.0'
option ip6assign '60'
option ipaddr '192.168.88.254'
option gateway '192.168.88.2'
option dns '192.168.88.2'
...
- ipaddr: 必须和上一步–ip参数一致
- gateway: 必须和虚拟机的网关一致
- dns: 最好和虚拟机的一致
编辑完成后,保存并重启network服务1
# /etc/init.d/network restart
最后还必须将br-lan网口设置成混杂模式(这一步还是在容器中执行)1
# ip link set br-lan promisc on
3. 配置虚拟机
macvlan为了安全起见,默认情况下各个子网口和宿主机是不通的,为了让宿主机能访问容器,这里我们要给宿主机单独创建一个子网口,并配置相应的路由1
# ip link add link eth0 dev macv1 type macvlan mode bridge
这里我们基于eth0网口创建了一个名为macv1的子网口,注意mode要指定为bridge,这样它才能和其它子网口通信
创建完网口之后,我们还必须添加一条路由,使得到容器的数据包走该网口1
# ip route add 192.168.88.254/32 dev macv1
最后ping一下看是不是通了1
2
3
4
5ping 192.168.88.254
PING 192.168.88.254 (192.168.88.254) 56(84) bytes of data.
64 bytes from 192.168.88.254: icmp_seq=1 ttl=64 time=0.052 ms
64 bytes from 192.168.88.254: icmp_seq=2 ttl=64 time=0.060 ms
...
4. 配置openwrt
- 添加节点
这里通过浏览器打开http://192.168.88.254 这里你可以替换成你自己的容器ip,默认的用户名和密码:root/password
登录完成后,首先要添加节点
这里上文提到的自建vps或者购买的机场,你一般都会有一串字符串,将它贴到这个地方,点击添加。
完成后可以在节点列表里面看到
- 基本设置
点击基本设置,打开主开关,TCP节点选择上一步添加的节点,UDP节点选择和TCP节点相同即可,然后点击“保存&应用”按钮。
- 设置DNS
切换至DNS项,关闭缓存解析结果和ChinaDNS-NG,最后点击清空IPSET,最后记得保存变更。
- 检测
配置网关代理
最后一步,我们可以将虚拟机的默认网关指向该容器1
2# ip route del default
# ip route add default via 192.168.88.254 dev macv1
到这里我们就可以愉快地make quick-release了
结束语
最后,大家可能觉得这样的配置过于复杂,其实你完成可以将上述步骤固化成脚本,将openwrt配置好后导出镜像,然后在开机的时候自动执行这些脚本就可以了,例如这样的脚本1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1 #!/bin/bash
2
3 # start openwrt
4 docker rm -f openwrt
5 docker run --restart no --name openwrt -d --network macvlan0 --ip 192.168.88.254 --privileged openwrt:hkv2r
ay /sbin/init
6
7 # set route to openwrt
8 ip link add link eth0 dev macv1 type macvlan mode bridge
9 ip link set macv1 up
10 ip route add 192.168.88.254/32 dev macv1
11 ip route del default
12 ip route add default via 192.168.88.254 dev macv1
13
14 # set interface promisc
15 ip link set eth0 promisc on
16 sleep 30
17 docker exec openwrt ip link set br-lan promisc on
这些东西就不在本文的讨论范围之内了,我们主要的目的是教会大家原理,大家可以发挥聪明才智,打造属于自己稳定的网关代理。
参考文章:
https://gsoc-blog.ecklm.com/iptables-redirect-vs.-dnat-vs.-tproxy/
https://www.cfmem.com/2021/08/docker-openwrt.html
https://www.ichenfu.com/2019/04/09/istio-inbond-interception-and-linux-transparent-proxy/