LoginSignup
2
3

More than 5 years have passed since last update.

FreeBSDのCARPについて、及びHAST制御スクリプト

Posted at

特徴など

インストール・設定

  • インストールは特に必要なし(FreeBSD 11.0、おそらく10.xも必要なし)
  • /boot/loader.conf
    • carpモジュールをロード
# sysrc -f /boot/loader.conf carp_load="YES"
  • /etc/rc.conf
    • ネットワーク設定にCARPで使うvipの設定を追加
      • vhid {id} -- バーチャルホストID。CARPで冗長化するIPアドレスごとの識別子
      • advskew {n} -- 死活監視への応答遅延(n/256秒)
        • この値をいくつにするのが適切かは未検証
      • pass {phrase} -- パスフレーズ。冗長化するIF間で合わせる
      • IPアドレス/マスク -- ネットマスクは必ず32にする
host1
# tee -a /etc/rc.conf <<EOF
ifconfig_vtnet0="inet 192.168.0.101 netmask 255.255.255.0"
ifconfig_vtnet0_alias0="inet vhid 1 pass {パスフレーズ} alias 192.168.0.100/32"
EOF
host2
# tee -a /etc/rc.conf <<EOF
ifconfig_vtnet0="inet 192.168.0.102 netmask 255.255.255.0"
ifconfig_vtnet0_alias0="inet vhid 1 advskew 50 pass {パスフレーズ} alias 192.168.0.100/32"
EOF
  • /usr/local/etc/devd/carp.conf
    • devdの設定
      • ※CARPの状態変更を全て受け取る場合
      • system -- システム名。"CARP"
      • subsystem -- サブシステム名。CARPの場合は {vhid}@{if名}
      • type -- 通知の種別。CARPの場合は"INIT", "MASTER", "BACKUP"のいずれか
      • action -- 実行するコマンド
        • 後述の制御スクリプトを利用している
/usr/local/etc/devd/carp.conf
notify 0 {
        match "system"          "CARP";
        match "subsystem"       "[0-9]+@[0-9a-z]+";
        match "type"            "(INIT|MASTER|BACKUP)";
        action "/path/to/carphast.sh $type $subsystem";
};
  • /etc/rc.local
    • devdが起動する前に制御が必要な場合
# tee -a /etc/rc.local <<EOF
/path/to/carphast.sh AUTO 1@vtnet0
EOF

状態・操作について

  • 状態は INIT, MASTER, BACKUP の3種類
    • 基本的にはMASTER/BACKUPを切り替える
  • 実行したサーバの状態をBACKUP(Standby)に変更する
    • 反対側サーバのstateがMASTER(Active)に変わる
# ifconfig vtnet0 vhid 1 state backup
  • 実行したサーバの状態をMASTER(Active)に変更する
    • 反対側サーバのstateがBACKUP(Standby)に変わる
    • advskew値が小さいほうでのみ可能(同じなら両方可能)
# ifconfig vtnet0 vhid 1 state master
  • 実行したサーバの状態をINITにする
    • インターフェイスを停止した時にINITになる模様
    • 反対側サーバのstateがMASTER(Active)に変わる
# ifconfig vtnet0 down
  • shutdownやrebootした時は残っているほうがMASTERになる
  • 起動時は先にbootしたほうがMASTERになる
  • 中途半端な死活状態になった時にどうなるかは未確認
    • pingが通っていれば生きている扱い?

状態変更時・起動時の処理について

  • HAProxyのようなネットワークのみで完結するものであれば特に処理は必要ないと思われる
  • devdが状態変更時に制御スクリプトを呼び出す
    • サーバ起動時はdevdが開始されてないため、/etc/rc.local で制御スクリプトを呼び出す
  • MySQLをfailoverする場合はおおよそ下記のようになると思われる
    • CARPがtype:INITになった時:
      • ※この時は何もしない?
    • CARPがtype:BACKUPになった時:
      • service mysql-server stop
      • ファイルシステムのアンマウント(ufsとzfsで違う)
      • hastctl role seconday all
    • CARPがtype:MASTERになった時:
      • hastctl role primary all
      • ファイルシステムのマウント(ufsとzfsで違う)
      • service mysql-server start

CARPを利用したHAST制御スクリプト

  • CARPの状態を利用してHASTを管理する
    • 第1引数にCARPの状態を渡す
    • 第1引数がAUTOの時はCARPのstateから判断する
    • 第2引数にvhidとIF名が渡されるが、現在は未使用
      • vhidが複数ある場合には制御したほうがよさそう
  • 制御スクリプトと同じディレクトリに carphast.conf があればその設定を読み込む
    • services は起動したいサービス名。複数可
    • zpools は importしたいZFSのプール名。複数可
    • resources は 利用するHASTのリソース名。複数可。allの時は全て利用する
  • /dev/hast/* に現れるデバイスは /etc/fstab の設定に応じてmountする
    • ZFSはimportしたあとに/etc/fstabの内容でmountしている
carphast.conf
services="mysql-server"
zpools="hast-zfs"
resources="all"
#resources="shared1 shared2"
carphast.sh
#!/bin/sh

conffile=${0%.sh}".conf"
if [ -f $conffile ]; then
. $conffile
fi

resources=${resources:-all}

action=$1
vhid=${2%@*}
ifname=${2#*@}

syslog_facility="user.notice"
syslog_tag="carp-hast"
maxwait=60
delay=3

logger="/usr/bin/logger -p $syslog_facility -t $syslog_tag"


if [ "$resources" = "all" ]; then
  hastdevs=$(/sbin/hastctl dump | /usr/bin/awk '/^[[:space:]]*resource:[[:space:]]/ {print $2}')
else
  hastdevs="$resources"
fi


# 
case "$action" in
  MASTER|BACKUP|INIT)
    $logger "State Changed. I/F: $ifname VHID: $vhid state: $action"
  ;;

  AUTO)
    action=$(/sbin/ifconfig $ifname | /usr/bin/awk '/[[:space:]]*carp:[[:space:]]+([A-Z]+)[[:space:]]vhid[[:space:]]'"$vhid"'[[:space:]]?/ {print $2; exit}' )
    if [ "$action" ]; then
      $logger "State Changed. I/F: $ifname VHID: $vhid state: $action"
    else
      die "carp state not found"
    fi
  ;;

  *)
    die "$action is not yet implemented"
  ;;

esac


reverse_list()
{
  _revlist=
  for _revfile in $*; do
    _revlist="$_revfile $_revlist"
  done
  echo $_revlist
}

die()
{
  $logger "FATAL: "$*
  exit 1
}


# check hastd enabled
if ! /bin/pgrep -q hastd; then
  $logger "hastd not running"
  exit
fi


stop_services()
{
  for service in $( reverse_list $* ); do
    if /usr/sbin/service ${service} onestatus | /usr/bin/grep -q "running as" ; then
      /usr/sbin/service ${service} onestop \
        || $logger "Unable to stop service: ${service}."
    fi
  done
}

change_role()
{
  roletype=$1
  shift 1

  for hdev in $*; do
    /sbin/hastctl role $roletype $hdev \
        || $logger "Unable to change role to $roletype for resource: $hdev"
  done
}

# main
case "$action" in
  BACKUP|INIT)
    # stop services
    stop_services $services

    # unmount zfs
    for pool in $zpools; do
      if /sbin/zpool status | /usr/bin/grep -q "pool: $pool" ; then
        /sbin/zpool export -f $pool \
            || $logger "Unable to export zpool: ${pool}."
      fi
    done

    # unmount ufs
    for mdev in $(/sbin/mount -p | /usr/bin/awk '/^\/dev\/hast\// {print $1}'); do
      for hdev in $hastdevs; do
        if [ "$mdev" = "/dev/hast/$hdev" ]; then
          /sbin/umount -f $mdev \
            || $logger "Unable to unmount: ${mdev}."
        fi
      done
    done

    # change role
    if [ "$action" = "BACKUP" ]; then
      roletype="secondary"
    else
      roletype="init"
    fi
    change_role $roletype $resources

    $logger "Change role $roletype completed."
  ;;

  MASTER)
    # stop services
    stop_services $services

    # wait for not running secondary
    for hdev in $hastdevs; do
      for i in $(/usr/bin/jot $maxwait); do
        /bin/pgrep -fq "hastd: ${hdev} \(secondary\)" || break
        sleep 1
      done

      if /bin/pgrep -fq "hastd: ${hdev} \(secondary\)" ; then
        die "Secondary process for resource ${hdev} is still running after $maxwait seconds."
      fi
    done

    # change role primary
    change_role primary $resources
    sleep $delay

    # wait for the /dev/hast/* devices to appear
    for hdev in $hastdevs; do
      for i in $(/usr/bin/jot $maxwait); do
        [ -c /dev/hast/$hdev ] && break
        sleep 1
      done

      if [ ! -c /dev/hast/$hdev ]; then
        die "GEOM provider /dev/hast/$hdev did not appear."
      fi
    done

    # mount zfs
    if [ "$zpools" ]; then
      for pool in $zpools; do
        /sbin/zpool status | /usr/bin/grep -q -E "[[:space:]]*$pool[[:space:]]+ONLINE[[:space:]]" && break;

        /sbin/zpool import -d /dev/hast/ -f -N $pool \
          || die "Unable to import zpool: ${pool}."
      done

      /sbin/zpool scrub $zpools

      for pool in $zpools; do
        for mdev in $(/usr/bin/awk '/^'"$pool"'\// {print $1}' /etc/fstab); do
          $logger "mount $mdev"
          /sbin/mount -p | /usr/bin/grep -q -e "^${mdev}[[:space:]]" && break;

          /sbin/mount ${mdev} \
            || die "Unable to mount: ${mdev}."
        done
      done
    fi

    # mount ufs
    for mdev in $(/usr/bin/awk '/^\/dev\/hast\// {print $1}' /etc/fstab); do
      for hdev in $hastdevs; do
        if [ "$mdev" = "/dev/hast/$hdev" ]; then
          $logger "mount $mdev"
          /sbin/mount -p | /usr/bin/grep -q -e "^${mdev}[[:space:]]" && break;

          /sbin/fsck -y -t ufs ${mdev} \
            || die "Failed to fsck: ${mdev}."

          /sbin/mount ${mdev} \
            || die "Unable to mount: ${mdev}."
        fi
      done
    done

    # start services
    for service in ${services}; do
      /usr/sbin/service ${service} onestart \
        || $logger "Failed to start service: ${service}."
    done

    $logger "Change role primary completed."
  ;;

esac

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3