🔀

L4DSRで冗長化・負荷分散対応したVPN環境構築

2023/06/27に公開

はじめに

筆者はIPSec/IKEv2にて自宅へVPN接続出来る環境を作っていましたが、VPNサーバが1台しかないのは心もとないため
負荷分散や冗長化を目的としてLVSを用いたL4DSRを構築し、VPNサーバを複数台の構成にしました。その備忘録としてここにまとめます。
また今回はVPNサーバを構築してますが、DNSやHTTPサーバももちろんOKです。

VPNサーバの立ち上げ

VPN自体は以下のDockerコンテナを使って構築。構築手順は省略します。
https://github.com/hwdsl2/docker-ipsec-vpn-server

 docker compose ps
NAME                IMAGE               COMMAND             SERVICE             CREATED             STATUS              PORTS
ipsec-vpn-server    ikev2-vpn-vpn       "/opt/src/run.sh"   vpn                 2 hours ago         Up 2 hours          0.0.0.0:500->500/udp, :::500->500/udp, 0.0.0.0:4500->4500/udp, :::4500->4500/udp

DSR(Direct Server Return)とは

リアルサーバに来たパケットを、LoadBalancerを経由せずに直接クライアントに返却する方式です。
イメージとしては以下の通り。

今回構築する環境

論理構成図

ネットワーク

  • 192.168.0.0/22
    今回は同一レンジ上に構築します。

ロードバランサ

  • OS: Ubuntu22.04
  • Virtual IP: 192.168.2.10
  • Real IP
    • lb00: 192.168.1.20
    • lb01: 192.168.1.21

リアルサーバ

  • OS: Ubuntu22.04
  • Virtual IP: 192.168.2.10
  • Real IP
    • vpn00: 192.168.1.30
    • vpn01: 192.168.1.31

ロードバランサ構築

必要なパッケージをインストール

sudo apt install -y keepalived ipvsadm

各種設定の追加

ロードバランサ側

カーネルパラメータの設定

パケット転送を有効にする。

/etc/sysctl.conf
net.ipv4.ip_forward = 1

以下で反映。

sudo sysctl -p

keepalivedの設定

keepalivedで実際に仮想IPを割り当てます。

/etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
   router_id LVS_IKE
}
vrrp_instance VRRP_1 {
   ! priorityでMasterの判定を行うので2台ともにBACKUP
   state BACKUP
   ! VRRPのインターフェースを指定
   interface ens18
   ! VRRPのID
   virtual_router_id 52
   ! Master判定優先度(Master側の値を大きくする)
   priority 100
   ! VRRP送信間隔
   advert_int 3
   ! フェイルバックはしない
   nopreempt
   virtual_ipaddress {
       192.168.2.10/22 dev ens18
   }
}
include virtualservers/*.conf

ヘルスチェックの設定

/etc/keepalived/virtualservers/vpn500.conf
! Configuration File for virtualserver
virtual_server 192.168.2.10 500 {
    ! 監視周期 5秒
    delay_loop 5
    ! ラウンドロビンで負荷分散する
    lvs_sched rr
    ! パケット転送方式は、Direct Return。
    lvs_method DR
    ! プロトコル指定
    protocol UDP
    ! sv-a-vpn
    real_server 192.168.1.30 500 {
        ! 重み
        weight 1
        ! ヘルスチェック失敗の場合は削除ではなくWeightをゼロにする
        inhibit_on_failure
        VPN_CHECK {
           connect_port 500
           ! ポーリングのタイムアウト時間(秒)
           connect_timeout 5
           ! リトライ回数
           retry 3
        }
    }
    ! sv-b-vpn
    real_server 192.168.1.31 500 {
        ! 重み
        weight 1
        ! ヘルスチェック失敗の場合は削除ではなくWeightをゼロにする
        inhibit_on_failure
        VPN_CHECK {
           connect_port 500
           ! ポーリングのタイムアウト時間(秒)
           connect_timeout 5
           ! リトライ回数
           retry 3
        }
    }
}
/etc/keepalived/virtualservers/vpn4500.conf
! Configuration File for virtualserver
virtual_server 192.168.2.10 4500 {
    ! 監視周期 5秒
    delay_loop 5
    ! ラウンドロビンで負荷分散する
    lvs_sched rr
    ! パケット転送方式は、Direct Return。
    lvs_method DR
    ! プロトコル指定
    protocol UDP
    ! sv-a-vpn
    real_server 192.168.1.30 4500 {
        ! 重み
        weight 1
        ! ヘルスチェック失敗の場合は削除ではなくWeightをゼロにする
        inhibit_on_failure
        VPN_CHECK {
           connect_port 4500
           ! ポーリングのタイムアウト時間(秒)
           connect_timeout 5
           ! リトライ回数
           retry 3
        }
    }
    ! sv-b-vpn
    real_server 192.168.1.31 4500 {
        ! 重み
        weight 1
        ! ヘルスチェック失敗の場合は削除ではなくWeightをゼロにする
        inhibit_on_failure
        VPN_CHECK {
           connect_port 4500
           ! ポーリングのタイムアウト時間(秒)
           connect_timeout 5
           ! リトライ回数
           retry 3
        }
    }
}

ipvsadmの設定

L4での負荷分散を実施します。

# udp 500
sudo ipvsadm -A -u 192.168.2.10:500 -s wlc
sudo ipvsadm -a -u 192.168.2.10:500 -r 192.168.1.30:500 -g -w 1
sudo ipvsadm -a -u 192.168.2.10:500 -r 192.168.1.31:500 -g -w 1

# udp 4500
sudo ipvsadm -A -u 192.168.2.10:4500 -s wlc
sudo ipvsadm -a -u 192.168.2.10:4500 -r 192.168.1.30:4500 -g -w 1
sudo ipvsadm -a -u 192.168.2.10:4500 -r 192.168.1.31:4500 -g -w 1
# 設定の確認
sudo ipvsadm -S
# 動作状況の確認
sudo ipvsadm -l
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
UDP  192.168.2.10:isakmp wlc
  -> 192.168.1.30:isakmp          Route   1      0          0
  -> 192.168.1.31:isakmp          Route   1      0          0
UDP  192.168.2.10:ipsec-nat-t wlc
  -> 192.168.1.30:ipsec-nat-t     Route   1      0          0
  -> 192.168.1.31:ipsec-nat-t     Route   1      0          0

ロードバランサの設定は以上です。

リアルサーバ(VPNServer)側

リアルサーバではループバックアドレスの変更を行います。

# This is the network config written by 'subiquity'
network:
  ethernets:
    ens18:
      dhcp4: false
      addresses: [192.168.1.30/22]
      gateway4: 192.168.0.1
      nameservers:
        addresses: [192.168.0.1, 1.1.1.1]
# 以下を追加
    lo:
      renderer: networkd
      match:
        name: lo
      addresses:
        - 192.168.2.10/32

以下で反映。

sudo netplan apply

確認。

ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet 192.168.2.10/32 scope global lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 36:cc:d5:2e:70:7a brd ff:ff:ff:ff:ff:ff
    altname enp0s18
    inet 192.168.1.30/22 brd 192.168.3.255 scope global ens18
       valid_lft forever preferred_lft forever
    inet6 fe80::34cc:d5ff:fe2e:707a/64 scope link
       valid_lft forever preferred_lft forever
...

また、ARP応答をしないようにするため次のカーネルパラメータを追加。

/etc/sysctl.conf
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2

以下で反映。

sudo sysctl -p

以上で構築は完了です。
この状態で外部から接続出来るように、DNSやNAPTの設定を済ませた上でiPhoneから接続すると

このように無事に接続できました。

またLoadBalancerの片方を落とした場合に、IPが切り替わるかを下記のコマンドで確認すると良いでしょう。

ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 2a:2d:3d:f0:e0:e8 brd ff:ff:ff:ff:ff:ff
    altname enp0s18
    inet 192.168.1.21/22 brd 192.168.3.255 scope global ens18
       valid_lft forever preferred_lft forever
    inet 192.168.2.10/22 scope global secondary ens18
       valid_lft forever preferred_lft forever
    inet6 fe80::282d:3dff:fef0:e0e8/64 scope link
       valid_lft forever preferred_lft forever

Discussion