クライド上でVRRPを実現する

# 概要
[Oracle Cloud Infrastructure（OCI）](https://www.oracle.com/jp/cloud/)上にてVRRPの検証を行いました

# 仕組み
オンプレ環境と比較してクラウドはネットワーク冗長化の制約があるため以下のように実現しています
クラウドはOCI, ネットワークデバイスはVYOSを使用しました
- VRRP Hello
    - オンプレ：Multicastで実行可能
    - OCI：Multicast通信の制限があるのでUnicastを使用
- VRRP切り替え
    - オンプレ：BACKUPがMASTERに切り替わり、新MASTERよりGratuitous ARP送出
    - OCI：BACKUPがMASTERに切り替わり、以下手順でセカンダリIP(VIP)を移動
        - 新MASTERからAPI Gatewayにcurl POST発行
        - API GatewayからFunction呼び出し
        - 旧MASTERのVNICのセカンダリIP(VIP)を新MASTERのVNICに移動するFunctionsを実行
        
# 検証構成図
- ACTIVEルーターをVYOS1, STANDBYルーターをVYOS2
- サブネット
    - 10.107.1.0/24 : 外部ルーター接続
    - 10.107.2.0/24 : VRRP稼働、サーバーが存在
    - 10.107.3.0/24 : API GWおよびFn用

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/0372233b-8c34-76e6-e624-b10db2e87504.png">

# 前提
以下を前提としています
- Functionを作成したことがある
- オンプレ～OCI間のVPN構築済

# 準備
## OCI Policy
IPアドレスを操作するためのポリシーは以下を設定します
- FunctionsからIPアドレス更新を実行
*Allow dynamic-group [functionsリソースを含むグループ] to use private-ips in compartment [コンパートメント名]*
*Allow dynamic-group [functionsリソースを含むグループ] to use vnics in compartment [コンパートメント名]*
または
*Allow dynamic-group [functionsリソースを含むグループ] to use virtual-network-family in compartment [コンパートメント名]*

## Seconday IP
- OCIコンソールナビゲーションメニューからコンピュート>>インスタンス>>対象のインスタンス(例:VYOS1)を選択
- ResourcesからアタッチされたVNIC>>セカンダリVNIC>>IPv4 アドレスを選択して[セカンダリ・プライベートIPアドレスの割当て]を押下してセカンダリIPを入力して[割当て]を押下(例:10.107.2.100)
- 作成したセカンダリIPの3点リーダー(︙)をクリックしてプライべートIP OCIDのコピーを選択

## ルーティング
- OCIコンソールナビゲーションメニューからネットワーキング>>仮想クラウド・ネットワーク>>対象VCN>>ルート表>>対象ルート表を選択して[ルート・ルール]を押下
- 以下を設定して[ルートルールの追加]を押下
    - ターゲット・タイプ：プライベートIP
    - 宛先タイプ：CIDRブロック
    - 宛先CIDRブロック：対向側CIDR
    - ターゲット選択：プライべートIP OCID

## Functions
- OCIコンソールナビゲーションメニューから開発者サービス>>ファンクションを選択して[アプリケーションの作成]を押下
- 以下を設定して[作成]を押下
    - 名前：任意
    - サブネット：Functionを実行するサブネット(例:10.107.3.0/24)
    - 作成したアプリケーション下でFunctionを設定
```func.py
import io
import json
import logging
import oci

def handler(ctx, data: io.BytesIO = None):
    try:
        body = json.loads(data.getvalue())
        private_ip_id = body["private_ip_id"]
        vnic_id = body["vnic_id"]

        # Resource principal
        signer = oci.auth.signers.get_resource_principals_signer()
        
        # Update private ip using resource principal
        core_client = oci.core.VirtualNetworkClient(config={},signer=signer,service_endpoint="https://iaas.ap-tokyo-1.oraclecloud.com")

        update_private_ip_response = core_client.update_private_ip(
            private_ip_id=private_ip_id,
            update_private_ip_details=oci.core.models.UpdatePrivateIpDetails(
                vnic_id=vnic_id
            )
        )

    except Exception as e:
        print('ERROR: bad Event!', flush=True)
        raise
```

```func.yaml
schema_version: 20180708
name: updateprivateip
version: 0.0.8
runtime: python
build_image: fnproject/python:3.11-dev
run_image: fnproject/python:3.11
entrypoint: /python/bin/fdk /function/func.py handler
memory: 256
```

```requirements.txt
fdk>=0.1.86
oci
```
アプリケーションを指定してFunctionsをデプロイ

```
fn -v deploy --app <App名>
```
## API Gateway
### ネットワーク・セキュリティ・グループ(NSG)
NSGを設定してAPI GatewayへのアクセスはVYOSのみに制限します
- OCIコンソールナビゲーションメニューからネットワーキング>>仮想クラウド・ネットワーク>>対象VCN>>リソース　ネットワーク・セキュリティ・グループを選択して[ネットワーク・セキュリティ・グループの作成]を押下
- 以下を設定して[作成]を押下
    - 名前：任意
    - ルール：
        - 方向：イングレス
        - ソース・タイプ：CIDR
        - ソースCIDR：VYOS1 LAN IP(例：10.107.1.98/32)
        - IPプロトコル：TCP
        - ソース・ポート範囲：All
        - 宛先ポート範囲：443
    - 別のルール：
        - 方向：イングレス
        - ソース・タイプ：CIDR
        - ソースCIDR：VYOS2 LAN IP(例：10.107.1.99/32)
        - IPプロトコル：TCP
        - ソース・ポート範囲：All
        - 宛先ポート範囲：443
### API Gateway
- OCIコンソールナビゲーションメニューから開発者サービス>>API管理>>ゲートウェイから[ゲートウェイの作成]を押下
- 以下を設定して[ゲートウェイの作成]を押下
    - 名前：任意
    - タイプ：プライベート
    - ネットワーク：対象ネットワークを指定（例：10.107.3.0)
    - ネットワーク・セキュリティ・グループの有効化にチェックして作成したNSGを選択
- 作成したゲートウェイから呼び出すFunctionsを選択してリソース デプロイメントから[デプロイメントの作成]を押下
- 以下を設定して[作成]を押下
        - 名前：任意
        - パス接頭辞：任意(例：/icnfnapp）
        - 認証：なし
        - パス：任意（例：/updateprivateip）
        - メソッド：POST
        - バックエンド・タイプ：Oracleファンクション
        - アプリケーション：作成したアプリケーション
        - 関数名：作成したFunction

## VYOS
ルーターVYOS1およびVYOS2にVRRPを設定します。
設定のポイントは以下のとおりです。
- StateがMasterになった場合Curl POSTするShellを実行
- VYOS1復旧時にMasterとなるDelay値を60秒にする

### VYOS1
- config抜粋
```
set high-availability vrrp group vrrp01 hello-source-address '10.107.2.98'
set high-availability vrrp group vrrp01 interface 'eth1'
set high-availability vrrp group vrrp01 peer-address '10.107.2.99'
set high-availability vrrp group vrrp01 preempt-delay '60'
set high-availability vrrp group vrrp01 priority '200'
set high-availability vrrp group vrrp01 transition-script master '/config/scripts/updateprivateip_1.sh'
set high-availability vrrp group vrrp01 virtual-address 10.107.2.100/24
set high-availability vrrp group vrrp01 vrid '10'

set interfaces ethernet eth0 address '10.107.1.98/24'
set interfaces ethernet eth1 address '10.107.2.98/24'

set protocols static route 0.0.0.0/0 next-hop 10.107.1.1
```
- Shell
```/config/scripts/updateprivateip_1.sh
#!/bin/bash

curl -k -X POST https://10.107.3.105/icnfnapp/updateprivateip -d '{"private_ip_id":"<Secondary IP OCID>","vnic_id":"<VYOS1 Secondary VNIC OCID>"}'
```
### VYOS2
- config抜粋
```
set high-availability vrrp group vrrp01 hello-source-address '10.107.2.99'
set high-availability vrrp group vrrp01 interface 'eth1'
set high-availability vrrp group vrrp01 peer-address '10.107.2.98'
set high-availability vrrp group vrrp01 priority '100'
set high-availability vrrp group vrrp01 transition-script master '/config/scripts/updateprivateip_2.sh'
set high-availability vrrp group vrrp01 virtual-address 10.107.2.100/24
set high-availability vrrp group vrrp01 vrid '10'

set interfaces ethernet eth0 address '10.107.1.99/24'
set interfaces ethernet eth1 address '10.107.2.99/24'

set protocols static route 0.0.0.0/0 next-hop 10.107.1.1

```
- Shell
```/config/scripts/updateprivateip_2.sh
#!/bin/bash

curl -k -X POST https://10.107.3.105/icnfnapp/updateprivateip -d '{"private_ip_id":"<Secondary IP OCID>","vnic_id":"<VYOS1 Secondary VNIC OCID>"}'
```

# 検証
ACTIVEルーターを落として迂回できることを確認しました。なお、環境の都合上海外サイトを使用しているためレイテンシーが大きいですがテストには支障ありません。

## 通常時
- VRRP StateをVYOSから確認
```
user@VYOS1:~$ show vrrp
Name    Interface      VRID  State      Priority  Last Transition
------  -----------  ------  -------  ----------  -----------------
vrrp01  eth1             10  MASTER          200  2d17h48m29s

user@VYOS2:~$ show vrrp
Name    Interface      VRID  State      Priority  Last Transition
------  -----------  ------  -------  ----------  -----------------
vrrp01  eth1             10  BACKUP          100  2d17h48m26s
```
- OCIコンソールからセカンダリIP付与を確認
VYOS1
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/c3871690-e37d-f9de-a5d6-333ffffb0a25.png">

VYOS2
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/6119045d-c8b7-fd42-ac79-1f8463b064c0.png">
- オンプレからサーバーにPING
```CMD
C:\>ping 10.107.2.200 -t

10.107.2.200 に ping を送信しています 32 バイトのデータ:
10.107.2.200 からの応答: バイト数 =32 時間 =382ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =378ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =391ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =371ms TTL=60
```

## VYOS1障害
- VRRP StateをVYOSから確認
```
user@VYOS1は障害のためログなし
user@VYOS2:~$ show vrrp
Name    Interface      VRID  State      Priority  Last Transition
------  -----------  ------  -------  ----------  -----------------
vrrp01  eth1             10  MASTER          100  24s
```
- OCIコンソールからセカンダリIP付与を確認
VYOS1
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/d36a01bf-b5d6-f471-643f-3b361d64b9b8.png">

VYOS2
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/160ff581-913d-9670-becc-46a3e12b2599.png">

- オンプレからサーバーにPINGして復旧を確認
```CMD
C:\>ping 10.107.2.200 -t

10.107.2.200 に ping を送信しています 32 バイトのデータ:
10.107.2.200 からの応答: バイト数 =32 時間 =379ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =432ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =377ms TTL=60
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
要求がタイムアウトしました。
10.107.2.200 からの応答: バイト数 =32 時間 =376ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =370ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =367ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =377ms TTL=60
```

## VYOS1復旧
- VRRP StateをVYOSから確認
```
user@VYOS1:~$ show vrrp
Name    Interface      VRID  State      Priority  Last Transition
------  -----------  ------  -------  ----------  -----------------
vrrp01  eth1             10  MASTER          200  10s

user@VYOS2:~$ show vrrp
Name    Interface      VRID  State      Priority  Last Transition
------  -----------  ------  -------  ----------  -----------------
vrrp01  eth1             10  BACKUP          100  14s
```

- OCIコンソールからセカンダリIP付与を確認
VYOS1
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/3ebf5f97-53ed-447a-a51e-ebe004a8c64e.png">

VYOS2
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730992/9a294681-08c9-2f25-2abc-3ee3743018f7.png">

- オンプレからサーバーにPING
```CMD
C:\>ping 10.107.2.200 -t

10.107.2.200 に ping を送信しています 32 バイトのデータ:
10.107.2.200 からの応答: バイト数 =32 時間 =376ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =383ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =380ms TTL=60
略
10.107.2.200 からの応答: バイト数 =32 時間 =380ms TTL=60
10.107.2.200 からの応答: バイト数 =32 時間 =374ms TTL=60

10.107.2.200 の ping 統計:
    パケット数: 送信 = 190、受信 = 190、損失 = 0 (0% の損失)、
ラウンド トリップの概算時間 (ミリ秒):
    最小 = 354ms、最大 = 723ms、平均 = 383ms

```