跨城区局域网的搭建(基于Docker)

概述:

当时家里和公司都有服务器,想统一管理,但两边的内网是隔离的。传统的端口映射太麻烦,而且有些服务不想暴露在公网。试过一些方案,最后觉得用 Docker OpenVPN 最省事。

选择 Docker OpenVPN 而不是传统 VPN,主要是因为容器化部署特别方便,证书管理也简单得多。最后的效果很好,通过域名就能访问远程内网的各种服务,省去了很多配置麻烦。

网络拓扑:

mermaid
graph TD
    classDef gateway fill:#e67e22,stroke:#d35400,color:#fff,stroke-width:2px
    classDef lb fill:#2980b9,stroke:#2471a3,color:#fff,stroke-width:2px
    classDef proxy fill:#5dade2,stroke:#2e86c1,color:#fff
    classDef vpn fill:#5dade2,stroke:#2e86c1,color:#fff
    classDef arm fill:#85c1e9,stroke:#2e86c1,color:#333
    classDef isp fill:#76d7c4,stroke:#1abc9c,color:#333
    classDef user fill:#bdc3c7,stroke:#7f8c8d,color:#333

    subgraph overseas["海外服务区域"]
        UserOut["外网用户"]:::user
        OS["海外服务"]
        CDN["CDN"]
        SS1["SS Server 1"]:::proxy
        SS2["SS Server 2"]:::proxy
        UserOut -->|用户访问| OS
        OS -->|用户访问| CDN
        CDN -->|用户访问| SS2
        OS -->|Shadowsocks| SS1
        OS -->|Shadowsocks| SS2
    end

    GFW["GFW 防火墙"]:::gateway
    LB["负载均衡"]:::lb

    subgraph internal["内网核心区域"]
        subgraph proxyVpn["接入代理 & VPN"]
            UserIn["内网用户"]:::user
            Nginx["Nginx"]:::proxy
            Haproxy["Haproxy"]:::proxy
            OpenVPN["OpenVPN"]:::vpn
            UserIn -->|用户访问| Nginx
            UserIn -->|用户访问| Haproxy
            Nginx <-->|DockerNet| Haproxy
            Haproxy -->|DockerNet| OpenVPN
        end
        subgraph armCluster["多线出口 ARM 集群"]
            ARM1["ARM1"]:::arm --> Telecom["电信AD"]:::isp
            ARM2["ARM2"]:::arm --> Mobile["移动AD"]:::isp
            ARM3["ARM3"]:::arm --> Unicom["联通AD"]:::isp
        end
        Mickey["Mickey"]:::user
    end

    SS1 -->|Shadowsocks| GFW
    SS2 -->|Shadowsocks| GFW
    GFW -->|Shadowsocks| LB
    LB -->|SS| ARM1
    LB -->|SS| ARM2
    LB -->|SS| ARM3

    SS1 -.->|TCP BBR| GFW
    GFW -.->|BBR| ARM1
    GFW -.->|BBR| ARM2
    GFW -.->|BBR| ARM3

    OpenVPN -->|VPN 隧道| Mickey
    Telecom -->|VPN| Mickey
    Mobile -->|VPN| Mickey
    Unicom -->|VPN| Mickey

整体架构分三个区域:海外服务区通过 Shadowsocks 穿墙,经过 GFW 和负载均衡后分发到内网的 ARM 服务器集群,ARM 板子分别接电信、移动、联通三条线路做出口。内网通过 Nginx/Haproxy 做代理接入,OpenVPN 提供 VPN 隧道让个人用户远程访问。

代理流程:

mermaid
graph TD
    classDef client fill:#27ae60,stroke:#1e8449,color:#fff,stroke-width:2px
    classDef proxy7 fill:#5dade2,stroke:#2471a3,color:#fff,stroke-width:1px
    classDef streamSelect fill:#e67e22,stroke:#d35400,color:#fff,stroke-width:1px
    classDef stream fill:#76d7c4,stroke:#1abc9c,color:#333,stroke-width:1px
    classDef cache fill:#27ae60,stroke:#1e8449,color:#fff,stroke-width:2px
    classDef lb7 fill:#e67e22,stroke:#d35400,color:#fff,stroke-width:1px
    classDef backend fill:#27ae60,stroke:#1e8449,color:#fff,stroke-width:2px
    classDef moduleBox fill:#ecf0f1,stroke:#7f8c8d,stroke-width:1px

    subgraph L1_7["第一层代理 · 七层协议"]
        direction TB
        C["客户端"]:::client
        A1["第一层代理"]:::proxy7
        S1["指向第一层的 stream"]:::streamSelect
        Cache["缓存"]:::cache
        C --> A1
        A1 --> S1
        S1 --> Cache
    end

    subgraph L1_4["第一层代理 · 四层协议"]
        direction TB
        ST1["第一层 stream"]:::stream
        SE1["选择下一层 stream"]:::streamSelect
        S1 --> ST1
        ST1 --> SE1
    end

    subgraph L2_4["第二层代理 · 四层协议"]
        direction TB
        ST2["第二层 stream"]:::stream
        SE2["选择下一层 stream"]:::streamSelect
        SE1 --> ST2
        ST2 --> SE2
    end

    subgraph L3_7["第三层代理 · 七层协议"]
        direction TB
        A3["第三层代理"]:::proxy7
        LB7["七层协议负载均衡"]:::lb7
        Backend["后端系统"]:::backend
        SE2 --> A3
        A3 --> LB7
        LB7 --> Backend
    end

具体流程:

  • 购买云服务器部署docker

    需要一台公网 VPS 作为中转,因为 OpenVPN 需要一个公网入口点让各个客户端连接。建议购买支持 systemctl 的Linux系统,比较好管理,并部署docker:

    1. 外挂存储格式化为xfs分区;
    shell
    1
    2
    
      # mkfs.xfs /dev/vdb5
      # echo "/dev/vdb5 /mnt/data xfs defaults 1 1" |tee -a /etc/fstab
    1. 调整docker的目录,两种方法;
    • 挂载/var/lib/docker目录:
    shell
    1
    2
    3
    4
    5
    6
    
      # systemctl stop docker
      # mkdir -p /mnt/data/docker
      # rsync -aXS /var/lib/docker/.  /mnt/data/docker/
      # echo "/mnt/data/docker /var/lib/docker none bind 0 0"|tee -a /etc/fstab
      # mount -a
      # systemctl start docker
    • 指定具体目录: 在/etc/systemd/system/multi-user.target.wants/docker.service里面修改如下:
    shell
    1
    
      ExecStart=/usr/bin/dockerd --storage-driver=overlay2 -g /mnt/hhd/docker
  • 安装docker-openvpn服务:

    docker-openvpn

    • Pick a name for the $OVPN_DATA data volume container, it will be created automatically. 给配置数据卷起个名字
    shell
    1
    
      # OVPN_DATA="ovpn-data"
    • Initialize the $OVPN_DATA container that will hold the configuration files and certificates 初始化 PKI,生成证书体系
    shell
    1
    2
    3
    4
    5
    6
    
      # docker volume create --name $OVPN_DATA
      # docker run -v $OVPN_DATA:/etc/openvpn \
        --rm kylemanna/openvpn ovpn_genconfig \
        -u udp://VPN.SERVERNAME.COM
      # docker run -v $OVPN_DATA:/etc/openvpn \
        --rm -it kylemanna/openvpn ovpn_initpki

    如果使用tcp

    shell
    1
    2
    3
    
      # docker run -v $OVPN_DATA:/etc/openvpn \
        --rm kylemanna/openvpn ovpn_genconfig \
        -u tcp://VPN.SERVERNAME.COM:1443
    • Start OpenVPN server process 启动 OpenVPN 服务端
    shell
    1
    2
    3
    
      # docker run -v $OVPN_DATA:/etc/openvpn -d \
        -p 1194:1194/udp \
        --cap-add=NET_ADMIN kylemanna/openvpn

    OR

    shell
    1
    2
    3
    
      # docker run -v $OVPN_DATA:/etc/openvpn -d \
        -p 1443:1194/tcp \
        --cap-add=NET_ADMIN kylemanna/openvpn

    Running a Second Fallback TCP Container

    shell
    1
    2
    3
    4
    
      # docker run -v $OVPN_DATA:/etc/openvpn \
        --rm -p 1443:1194/tcp \
        --privileged kylemanna/openvpn ovpn_run \
        --proto tcp
    • Generate a client certificate without a passphrase . 生成客户端证书,不需要密码 Retrieve the client configuration with embedded certificates, “CLIENTNAME"可自定义;
    shell
    1
    2
    3
    4
    
      # docker run -v $OVPN_DATA:/etc/openvpn \
        --rm -it kylemanna/openvpn easyrsa build-client-full CLIENTNAME nopass
      # docker run -v $OVPN_DATA:/etc/openvpn \
        --rm kylemanna/openvpn ovpn_getclient CLIENTNAME > CLIENTNAME.ovpn
    • 增加路由规则,让 Docker 内部网络能访问到 VPN 客户端的节点 在docker主机上增加一条路由规则,目的是使其他容器可以通过默认的网络来访问到openvpn客户端的节点:
    shell
    1
    
      # ip route add 192.168.255.0/24 via $DOCKER_OPENVPN_IP
    • 给客户端配置静态内外IP,确保每次连接 IP 不变,方便代理配置
    shell
    1
    2
    
    # cat ccd/CLIENTNAME
    ifconfig-push 192.168.255.10 192.168.255.9
  • 部署前端代理

    • 选择DOCKER-CADDY做反向代理,主要原因是自动 HTTPS 和配置简单
shell
1
2
3
4
5
6
7
docker run -d \
  -v $(pwd)/Caddyfile:/etc/Caddyfile \
  -v $HOME/.caddy:/root/.caddy \
  -p 80:80 -p 443:443 \
  --name caddy \
  --link openvpn:openvpn \
  abiosoft/caddy
  • CADDY的配置参考:
ini
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  http://git.mickeybee.cn {
    redir https://git.mickeybee.cn{url}
  }
  https://git.mickeybee.cn {
    gzip
    proxy / 192.168.xxx.xx:3000
    tls [email protected] {
      max_certs 10
      key_type  p256
    }
  }
  • 部署TCP代理

    • 选择DOCKER-HAPROXY并部署,适合处理数据库连接、SSH 等长连接服务
shell
1
2
3
4
5
6
docker run -d \
  -v $(pwd)/haproxy:/usr/local/etc/haproxy:ro \
  -p xxx:xxx -p yyy:yyy \
  --name haproxy \
  --link openvpn:openvpn \
  haproxy
  • 映射后端端口

    在 HAProxy 配置文件中添加后端服务映射,指定具体的 IP 地址和端口号,支持 TCP 和 HTTP 代理模式。配置完成后重启 HAProxy 服务使配置生效。