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. 設計

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

6. Golang サンプルコード

@ master
$ sudo nvim ~/go/failover_multicast_ipv4_master.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/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 のようなルータ用のプロトコルは使用していないものの、ルーター、ローバーバランサー、リバースプロキシなどを別立てする必要がなく、応用次第ではとてもシンプルなマイクロサービスとして有用となりえるかもしれません。
また、応用次第では個々のコンピュータを相互監視しあう自律ネットワーク設計が可能となりえます。
次回以降の記事で、シンプルな同期の仕組みを投稿する予定をしてますが、これらのマイクロサービスを組み合わせることで、とてもシンプルなデータリソースの冗長化構成への応用が可能となります。