首页>>新闻资讯>>云计算

多网卡模式下Golang应用的流量从指定网卡流入流出方案

2024-08-15 09:21:25 6

最近因业务需要,需要在多网卡模式下实现Go应用的流量从指定网卡流入,请求外网服务时候流量需要从该网卡流出功能。从指定网卡流入很容易实现,只要go应用listen对应网卡即可,但请求外网服务时候就相对麻烦些了。在实践中总结出有三种方案可行。各有优劣。

假定服务器网卡情况如下:

实际上我们的服务器使用云服务器,网卡是弹性网卡(eni),绑定的是弹性ip(eip)。三种方案对普通服务器也是能达到目的的。

Go应用示例代码:

package main import ( "flag" "fmt" "io/ioutil" "net/http" "strings" ) var addr = flag.String("addr", ":8080", "the http server address") func init() { flag.Parse() } func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { splits := strings.Split(*addr, ":") localIP := "" if len(splits) == 2 { localIP = splits[0] } res, err := HTTPGet("http://haoip.cn") if err != nil { panic(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { panic(err) } fmt.Fprintf(w, "网卡的IP: %s\n\n", localIP) fmt.Fprintf(w, "出口的IP:\n\n %s", body) }) err := http.ListenAndServe(*addr, nil) if err != nil { panic(err) } } // HTTPGet func func HTTPGet(url string) (*http.Response, error) { req, _ := http.NewRequest("GET", url, nil) client := &http.Client{} req.Header.Set("User-Agent", "curl/7.47.0") return client.Do(req) } 复制代码

方案一 应用流量出口绑定网卡

当请求外网服务时候,程序需要在每一处http client的地方指定源IP为特定网卡的IP。该方案原理简单,不依赖外部其他服务,缺点就是对应用有侵入性。

示例程序中HTTPGet方法需要进行如下改动:

// localIP是网卡IP func HTTPGet(url, localIP string) (*http.Response, error) { req, _ := http.NewRequest("GET", url, nil) client := &http.Client{ Transport: &http.Transport{ Dial: func(netw, addr string) (net.Conn, error) { // localIP 网卡IP,":0" 表示端口自动选择 lAddr, err := net.ResolveTCPAddr(netw, localIP+":0") if err != nil { return nil, err } rAddr, err := net.ResolveTCPAddr(netw, addr) if err != nil { return nil, err } conn, err := net.DialTCP(netw, lAddr, rAddr) if err != nil { return nil, err } return conn, nil }, }, } return client.Do(req) } 复制代码

运行程序时候分别要监听指定网卡的ip:

go run main.go --addr=172.31.0.8:8090 go run main.go --addr=172.31.0.14:8090 复制代码

这样当我们访问109.25.48.65:8090时候,流量从eth0流入到go应用,当go请求haoip.cn地址,使用eth0这个网卡流出的。访问119.26.38.75:8090时候,流出从eth1流入go应用,当go请求haoip.cn地址,流量从eth1这个网卡流出。

方案二 基于Docker容器技术

Docker容器基于namespace实现网络、进程、挂载等资源隔离功能。如果将go应用打包成镜像,绑定指定网卡,以容器形式运行不就可以实现流量流出控制了。

创建Dockerfile,并写入以下内容:

FROM golang:1.14 LABEL maintainer="tink tink@example.com" WORKDIR /app COPY . . RUN go build -o main . EXPOSE 8080 CMD ["./main"] 复制代码

Dockerfile文件和go应用示例代码放在同一个目录下,然后执行以下命令构成go应用的镜像:

docker build -t eip . 复制代码

创建docker bridge网络

我们创建一个专门用于go应用的bridge网络: eip_bridge。子网范围是172.19.0.0/16

docker network create --subnet=172.19.0.0/16 --opt "com.docker.network.bridge.name"="eip_bridge" eip_bridge 复制代码

运行go应用容器

// 绑定网卡eth0 docker run --network=eip_bridge -p 172.31.0.8:8090:8080 --ip=172.19.0.100 -d eip:latest // 绑定网卡eth1 docker run --network=eip_bridge -p 172.31.0.14:8090:8080 --ip=172.18.0.101 -d eip:latest 复制代码

上面命令说明:

-p 172.31.0.8:8090:8090 给该容器端口映射,主机端口8090映射到容器端口8080,由于指定ip为172.31.0.8,这样我们就可以109.25.48.65:8090访问这个容器了。--ip=172.19.0.100。为该容器指定一个固定ip

通过上面命令运行容器,这样每个容器的流量流入是从指定网卡流入。但容器内请求外网服务时候并没有从各自指定网卡流出。实际上走的都是一个同一个网卡,要么eth0,要么eth1。这是因为容器流出的都会经过eip_bridge这个网桥,而这个网桥流出流量的目的地址要么是eth0,要么是eth1。

这时候我们可以通过SNAT技术,将容器的源地址分别改成对应绑定的网卡地址就可以了。

sudo iptables -t nat -I POSTROUTING -p all -s 172.19.0.100 -j SNAT --to-source 172.31.0.8 sudo iptables -t nat -I POSTROUTING -p all -s 172.19.0.101 -j SNAT --to-source 172.31.0.14 复制代码

注意:网桥的流量流出也是通过iptables的snat进行实现的。针对特定容器的iptables规则一定要在该网桥的规则前面。

方案三 DNAT/SNAT技术实现

方案思路是创建两个虚拟网卡或网桥br-eip1(172.19.0.100)和br-eip2(172.19.0.101)。go应用分别监听网卡br-eip1和网卡br-eip2。通过DNAT技术将来eth0的流量导向br-eip1,将来自eth1的流程导向br-eip2。通过SNAT技术将从br-eip1流出的外部流量导向eth0,br-eip2流出的流量导向eth1

创建虚拟网卡

apt-get install bridge-utils // 安装brctl sudo brctl addbr br-eip1 // 添加网桥 sudo ip link set br-eip1 up // 激活网桥 sudo ifconfig br-eip1 172.19.0.100 // 指定br-eip1网桥的ip sudo brctl addbr br-eip2 // 添加网桥 sudo ip link set br-eip2 up // 激活网桥 sudo ifconfig br-eip2 172.19.0.101 // 指定br-eip2网桥的ip 复制代码

DNAT配置流量流入

sudo iptables -t nat -I PREROUTING -d 172.31.0.8/32 ! -i br-eip1 -p tcp -m tcp --dport 8090 -j DNAT --to-destination 172.19.0.100:8090 sudo iptables -t nat -I PREROUTING -d 172.31.0.14/32 ! -i br-eip2 -p tcp -m tcp --dport 8090 -j DNAT --to-destination 172.19.0.101:8090 复制代码

SNAT配置流量流出

sudo iptables -t nat -I POSTROUTING -p all -s 172.19.0.100 -j SNAT --to-source 172.31.0.8 sudo iptables -t nat -I POSTROUTING -p all -s 172.19.0.101 -j SNAT --to-source 172.31.0.14 复制代码

作者:tink

链接:

https://juejin.im/post/6893311691849728008

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关标签:

发表评论:

评论记录:

未查询到任何数据!