IPv4 マルチキャストによるフェイルオーバー

## 1. 概要
この記事では、IPv4 マルチキャストによるルーティングフェイルオーバーの Golang マイクロサンプルコードを投稿します。

## 2. はじめに
フェイルオーバー、マスター/スレーブ、死活監視構成は、どうしても設計が複雑化・大型化してしまいがちですが、この仕組みは、VRRP や HSRP、GLBP のようなルータ用のプロトコルは使用していないものの、ルーター、ローバーバランサー、リバースプロキシなどを別立てする必要がなく、応用次第ではとてもシンプルなマイクロサービスとして有用となりえるかもしれません。
UDP の信頼性をリトライにより担保し、マルチキャストにより処理コストを削減します。
また、応用次第では集権型監視設計ではなく、個々のコンピュータが相互に監視しあう分散型自律ネットワーク設計が可能となり、さらには、IPv6 による実用も可能です。

## 3. 環境
* RHEL-7 系
* Go 1.9

## 4. 構成
* master x 1
    * MIP: 192.0.2.1
    * VIP: 192.0.2.3
* backup x 1
    * MIP: 192.0.2.2

## 5. 設計
![failover_multicast_ipv4.png](https://qiita-image-store.s3.amazonaws.com/0/260161/c403d566-8944-d4bb-4966-d583190edbb1.png)

1. master：一定間隔でマルチキャストグループへハートビートを送信。
2. backup：マスターノードからのハートビートを受信。
3. backup：一定間隔でマスターノードからのハートビートの内容を監視。
4. backup：マスターノードからのハートビート不達を検知。
5. backup：一定回数のリトライ監視超過でマスターノードダウン判定。
5. backup：VIP の奪取と、全ノード ARP テーブルへ VIP の更新をブロードキャスト。

## 6. Golang サンプルコード
@ master
`$ sudo nvim ~/go/failover_multicast_ipv4_master.go`

```go:~/go/failover_multicast_ipv4_master.go
package main

import (
	"fmt"
	"net"
	"time"
)

func main() {
	mcast_ip4 := "224.0.0.1"
	mcast_port := ":56789"
	mcast_addr := mcast_ip4 + mcast_port
	wait_time := 1
	message := "master"

	fmt.Printf("Send to multicast address: %s\n", mcast_addr)
	conn, err := net.Dial("udp", mcast_addr)
	_Error(err)
	defer conn.Close()

	for {
		time.Sleep(time.Duration(wait_time) * time.Second)
		conn.Write([]byte(message))
		fmt.Printf("%s\n", message)
	}
}

func _Error(_err error) {
	if _err != nil {
		panic(_err)
	}
}
```

@ backup

`$ sudo nvim ~/go/failover_multicast_ipv4_backup.go`

```go:~/go/failover_multicast_ipv4_backup.go
package main

import (
	"fmt"
	"net"
	"os/exec"
	"time"
)

func main() {
	mcast_ip4 := "224.0.0.1"
	mcast_port := ":56789"
	mcast_addr := mcast_ip4 + mcast_port
	pulse_interval := 1
	pulses := []int64{1, 1}
	pulse_check := 0
	pulse_retry := 3
	ucast_vip4 := "192.0.2.3"
	ucast_cidr := "/24"
	ucast_mask := ucast_vip4 + ucast_cidr
	ucast_if := "eth0"

	fmt.Printf("Listen multicast address: %s\n", mcast_addr)
	mcast_byte, err := net.ResolveUDPAddr("udp", mcast_addr)
	_Error(err)

	listener, err := net.ListenMulticastUDP("udp", nil, mcast_byte)
	_Error(err)
	defer listener.Close()

	// For master pulse receiver
	buffer := make([]byte, 8)
	go func() {
		for {
			length, remote_mip4, err := listener.ReadFrom(buffer)
			_Error(err)

			if string(buffer[:length]) == "master" {
				pulses[1] = time.Now.UnixNano()

				fmt.Printf("Reveived multicast from Master MIP: %v\n", remote_mip4)
			}
		}
	}()

	// For master pulse checker
	for {
		// Sleep pulse check interval seconds
		time.Sleep(time.Duration(pulse_interval) * time.Second)

		// Wait master boot up
		if pulses[1] == 1 {
			continue
		}

		// Detected normal pulse
		if pulses[0] != pulses[1] {
			pulses[0] = pulses[1]
			pulse_check = 0
			continue
		}

		// Detected abnormal pulse
		pulse_check++

		// Retry pulse check
		if pulse_check < pulse_retry {
			fmt.Printf("Retry pulse check: %v\n", pulse_check)
			continue
		}

		// Detected master cardiac arrest
		fmt.Println("Detected master cardiac arrest.")
		pulses[1] = 0

		// Asign VIP
		fmt.Printf("Reasign Unicast VIP: %s\n", ucast_mask)
		err = exec.Command("ip", "-f", "inet", "addr", "add", ucast_mask, "dev", ucast_if).Run()
		_Error(err)

		// Execute arping
		fmt.Println("Replace ARP tables.")
		err = exec.Command("arping", "-q", "-U", "-c5", "-w1", ucast_vip4, "-I", ucast_if).Run()
		_Error(err)

		fmt.Println("Suceeded failover.")
		break
	}

}

func _Error(_err error) {
	if _err != nil {
		panic(_err)
	}
}
```


## 7. Golang サンプル実行
@ master
`$ sudo go run ~/go/failover_multicast_ipv4_master.go`

```
[user@master ~/go]$ sudo go run failover_multicast_ipv4_master.go
Send to multicast address: 224.0.0.1:56789
master
master
...
```

@ backup
`$ sudo go run ~/go/failover_multicast_ipv4_backup.go`

```
[user@backup ~/go]$ sudo go run failover_multicast_ipv4_backup.go
Listen multicast address: 224.0.0.1:56789
Reveived multicast from Master MIP: 192.0.2.1:42573
Reveived multicast from Master MIP: 192.0.2.1:42573
...
```

@ master
`[control]+[c]`

```
[user@master ~/go]$ sudo go run failover_multicast_ipv4_master.go
Send to multicast address: 224.0.0.1:56789
master
master
...
^Csignal: interrupt
```

@ backup

```
[user@backup ~/go]$ sudo go run failover_multicast_ipv4_backup.go
Listen multicast address: 224.0.0.1:56789
Reveived multicast from Master MIP: 192.0.2.1:42573
Reveived multicast from Master MIP: 192.0.2.1:42573
...
Retry pulse check: 1
Retry pulse check: 2
Detected master cardiac arrest.
Reasign Unicast VIP: 192.0.2.3/24
Replace ARP tables.
Suceeded failover.
```

`$ sudo ip addr`

```
[user@backup ~/go]$ sudo ip addr
...
1: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    ...
    inet 192.0.2.2/24 brd 10.250.255.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever
    inet 192.0.2.3/24 scope global secondary eth0
       valid_lft forever preferred_lft forever
..
```

## 8. まとめ
この記事では、IPv4 マルチキャストによるルーティングのフェイルオーバーの Golang マイクロサンプルコードを投稿しました。
フェイルオーバー、マスター/スレーブ、死活監視構成は、どうしても設計が複雑化・大型化してしまいがちですが、この仕組みは、VRRP や HSRP、GLBP のようなルータ用のプロトコルは使用していないものの、ルーター、ローバーバランサー、リバースプロキシなどを別立てする必要がなく、応用次第ではとてもシンプルなマイクロサービスとして有用となりえるかもしれません。
また、応用次第では個々のコンピュータを相互監視しあう自律ネットワーク設計が可能となりえます。
次回以降の記事で、シンプルな同期の仕組みを投稿する予定をしてますが、これらのマイクロサービスを組み合わせることで、とてもシンプルなデータリソースの冗長化構成への応用が可能となります。