最近因业务需要,需要在多网卡模式下实现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 " 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/
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://sigusoft.com/goland/1112.html