natインスタンスの冗長化
こんにちは。プラットフォームの小宮です。
他を冗長化してもnatインスタンスを冗長化してないと、
プライベートセグメントでHAしてるサーバ達がAWSのAPIサーバと通信できなくなって詰むなあと思いまして、
先人の皆さまの記事を参考にして以下のとおりにしました。
・なんとなくどうするか検討
AmazonLinuxで作っちゃったので、たぶんHeartbeatとか入れづらいし、そもそもheartbaet使う必要ない気がする。
待機系から監視してaws的にルーティングテーブル挿げ替えるだけでよさそう。
待機系natインスタンスについて、
作成しておかないと作成と起動とルーティングテーブル作るところもやらないといけないので切替時間が長くなる。
インスタンス代がかかるけど起動もしたままがいいと思われ。
・とりあえず既存のAmazonLinuxのNATインスタンスの設定をいじるところから
sudoできるようにしてみる
1 2 3 4 5 6 |
# usermod -G wheel user-op # id user-op uid=500(user-op) gid=501(user-op) groups=501(user-op),10(wheel) # visudo %wheel ALL=(ALL) ALL |
コメントはずす。
他のインスタンスから渡ってsudoできるか確認する
あとnatインスタンスにpythonのツール入れておく
メール送信も設定
AmazonLinuxではsendmailが動いてる模様だった。
1 2 3 4 5 6 7 8 |
vi /etc/mail/submit.cf D{MTAHost}[172.18.10.22] Djnat01.hoge.com ※nat02はそう変える service sendmail stop chkconfig sendmail off yum install mailx echo hoge |mail -s testkomi komiyay@xxxxx |
ロケールを合わせる
1 2 3 4 5 6 7 8 9 10 |
mv /etc/localtime /etc/localtime.org ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime date Thu Nov 14 10:25:59 JST 2013 crontab -e # Time Sync 0 * * * * /usr/sbin/ntpdate -bs 172.18.10.24 service ntpd stop chkconfig ntpd off |
カーネルチューニングを入れる
1 2 3 4 5 6 7 8 9 10 11 |
# cp -p /etc/sysctl.conf{,.org} # vi /etc/sysctl.conf vm.swappiness = 30 net.ipv4.tcp_fin_timeout = 10 net.ipv4.tcp_max_syn_backlog = 8192 net.core.somaxconn = 8192 net.ipv4.tcp_keepalive_intvl = 3 net.ipv4.tcp_keepalive_probes = 2 net.ipv4.tcp_keepalive_time = 10 # sysctl -p |
・natインスタンスとそれをデフォルトゲートウェイにしたルーティングテーブルを別途作っておく
NATインスタンスはAMIをコピーして同じセグメントに作る
作った
i-005a5e02
Source/DestCheckをDisableにしないとRouterTableにルート設定する時の出口にできるインスタンスとして出てこない。
rtb-yyyyyyyy
というルーティングテーブルのDestination0.0.0.0/0(つまりデフォゲ)のターゲットを、
先ほどのインスタンスに指定してAddしておく
この時点でこのテーブルにAssosiationしてるサブネットは存在しない状態。
・とりあえずプライベート側のホストでyahooにpingしながらルーティングテーブルをawsのコマンドで挿げ替えてみる
マニュアルを見てみる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# aws ec2 replace-route-table-association help SYNOPSIS replace-route-table-association [--dry-run | --no-dry-run] --association-id <value> --route-table-id <value> --association-id (string) The ID representing the current association between the original route table and the subnet. --route-table-id (string) The ID of the new route table to associate with the subnet. # aws ec2 describe-route-tables help SYNOPSIS describe-route-tables [--dry-run | --no-dry-run] [--route-table-ids <value>] [--filters <value>] --filters Name=string1,Values=string1,string2 association.subnet-id |
コマンドでのフェイルオーバーを確認する
参考:http://d.hatena.ne.jp/j3tm0t0/20120814/1344971491
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
active_rt=rtb-xxxxxxxx standby_rt=rtb-yyyyyyyy subnetid=subnet-zzzzzzzz aws ec2 describe-route-tables --route-table-ids ${active_rt} --filters Name=association.subnet-id,Values=${subnetid} "Associations": [ { "SubnetId": "subnet-zzzzzzzz", "RouteTableAssociationId": ";rtbassoc-7c535b1e", "RouteTableId": "rtb-xxxxxxxx" }, association=`aws ec2 describe-route-tables --route-table-ids ${active_rt} --filters Name=association.subnet-id,Values=${subnetid}|grep -A 1 ${subnetid}|awk '{print $2}'|tail -1|sed -e 's/[",]//g'` # aws ec2 replace-route-table-association --route-table-id ${standby_rt} --association-id $association { "NewAssociationId": "rtbassoc-52767e30" } # aws ec2 describe-route-tables --filters Name=association.sub net-id,Values=${subnetid}|grep -A 1 ${subnetid}|awk '{print $2}'|tail -1|sed -e 's/[",]//g' rtbassoc-52767e30 |
確かにスタンバイルートに切り替わったことをマネジメントコンソールからも確認できた。
コマンド単体での切替試験は成功。yahooへのpingが途切れることはなかった。
・フェイルオーバーの条件を検討する
以下を参考に考えると裏側にいるホストが3台くらいyahooとかと疎通が通らなくなったらフェイルオーバとかでいいのではないか。
http://d.hatena.ne.jp/hirose31/20131105/1383623672
serf使うと楽なんじゃないのかな。調べてみる。
メッセージングツールSerfをEC2で使ってみる | Developers.IO
Serf を使ってみた – jedipunkz’ blog
https://dl.bintray.com/mitchellh/serf/
【Serf】v0.2.0 へのバージョンアップと、変わった所を確認してみた | Pocketstudio.jp log3
serf-muninでmunin-nodeの監視自動追加/削除 | Pocketstudio.jp log3
Serf+HAProxyで作るAutomatic Load Balancer – Glide Note – グライドノート
natの冗長化の参考:
NATインスタンス冗長化の深淵な話 – (ひ)メモ
suz-lab – blog: “High Availability NAT”の作成(CentOS6)
cloudpackブログ: (ELBとからめて)Hostヘッダでの振り分けをHAProxyでやってみる
NATインスタンスを冗長構成にしてみた – log4moto
serf入れてみたり調べてみたけど
結果的に新しすぎて情報が極端に少ないようなのでシェルスクリプトで頑張ることに。
(カスタムユーザイベントの例がいまいち少なくて今回の需要に合わない感じがした)
pacemakerでiptablesが落ちた時にF/Oというおなじみのパターンでもいいんだけど、
待機系からssh越しにpingして3つくらいのノードがダメならF/Oというほうが実情に即している模様。
cronで定期実行するかmonとかにするか。
⇒時間もないし簡単だからcronでいいや。
とりあえずpingだからrootで無くても大丈夫なような
natの公開鍵を裏にいるホスト全部の/home/user-op/.ssh/authorized_keysに登録する
1 2 3 4 5 |
$ ssh user-op@nfs02 ping 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=49 time=37.8 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=49 time=60.2 ms |
こんな感じで確認は可能なのであとはどういうスクリプトを何でどう実行するのかを考えます
・フェイルオーバさせるスクリプトの実行をどうするか考える
完成したスクリプト↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
mkdir /opt/{bin,log} vi /opt/bin/chk_backseg_ping.sh ------------------------ #!/bin/bash # # chk_backseg_ping.sh: 非公開セグメントのホストからpingチェックし失敗後NATのF/O を実施 # 依存関係:awsコマンド, /etc/hosts # 更新履歴:20131114 - create komiyay # ## variables title=hoge datetime=`/bin/date +%Y/%m/%d.%H:%M:%S` nochk_time=30 up_time=`uptime|grep min|awk '{print $3}'|sed 's/,//g'` mailto=xxxxx@xxxx.net log=/opt/log/aws_error.log backseg='172.18.20' hostgroup=`grep $backseg /etc/hosts|egrep -v -a 'vip|sorry|^#'|awk '{print $2}'|perl -pe 's/\n/ /g'` ssh_chkfile=/home/user-op/.ssh/authorized_keys dir=`echo $(cd $(dirname $0); pwd)` lockfile=$dir/nat_fo_complete user=user-op target1=8.8.8.8 target2=8.8.4.4 target3=yahoo.co.jp pin_count=4 pin_int=0.5 pin_wait=3 ng_count=3 active_rt=rtb-xxxxxxxx standby_rt=rtb-yyyyyyyy subnetid=subnet-zzzzzzzz ## user defined functions nat_failover() { echo `date +"%Y-%m-%d %T"` "start replace-route-table-association" >> $log eval `aws ec2 describe-route-tables|grep -A 2 ${subnetid}|egrep '(RouteTableAssociationId|RouteTableId)'|sed -e 's/[", ]//g'|awk -F : '{print $1"="$2}'` if [ ${RouteTableId} = ${active_rt} ] then aws ec2 replace-route-table-association --route-table-id ${standby_rt} \ --association-id ${RouteTableAssociationId} >> $log 2>&1 result=`echo $?` echo ${result} > ${lockfile} failstat="fail from ${active_rt} to ${standby_rt}, segment:${backseg}, status:${result}" else aws ec2 replace-route-table-association --route-table-id ${active_rt} \ --association-id ${RouteTableAssociationId} >> $log 2>&1 result=`echo $?` echo ${result} > ${lockfile} failstat="fail from ${standby_rt} to ${active_rt}, segment:${backseg}, status:${result}" fi } mail_fail() { printf "NAT_failover done. please check site.\n ${failstat}" \ |mail -s "(${title}) nat_f/o_${datetime}" ${mailto} } ## main processing ### failover complete check if [ -f ${lockfile} ];then exit else : fi ## Check invalid few minutes after startup if [ -n "$up_time" ];then if [ $up_time -lt $nochk_time ];then exit else : fi else : fi ### ssh check for i in $hostgroup do ssh ${user}@${i} ls ${ssh_chkfile} result=`echo $?` if [ $result -eq 0 ];then chkhosts="$chkhosts $i" fi done ### ping check ng_hosts=0 for i in $chkhosts do target=`printf "$target1\n$target2\n$target3"|sort -R|head -1` ssh ${user}@${i} ping -q -c ${pin_count} -i ${pin_int} -w ${pin_wait} ${target} > /dev/null 2>&1 result=`echo $?` if [ ${result} -ne 0 ];then ng_hosts=`expr $ng_hosts + 1` fi done ### fail over if the ng_hosts equal to or greater than the ng_count if [ $ng_hosts -ge $ng_count ] then if [ -f ${lockfile} ];then exit else : fi nat_failover mail_fail else : fi exit 0 ------------------------ chmod +x /opt/bin/chk_backseg_ping.sh |
見ればわかるかもしれませんが敢えて解説すると
hostsからバックセグのチェックするホストを全部取得し、
pingの結果が3台以上のホストでNGな場合にnat02側のルートに再アソシエイトします。
一応sshの接続性チェックをしています。
pingのtargetも3つの中からランダム指定にしたので全滅でない限りは、
targetに不通でGlobalに出れる状態でf/oはしないでしょう。たぶん。
NAT自体がAPIサーバに到達できなかった場合にはF/Oに失敗しますが、
完了ロックファイルは作成されて異常終了ステータスが入るので再実行はされません。
定期的にstop/startとかする環境の場合はnatを最後に起動するなど起動順に気をつけないとだめそうで、
natが起動してないとHA組んでる裏側のサーバがAPI通信できなくなってしまうので、
uptimeが数分たたないとcheckが始まらないようにする等の分岐を入れたほうがいいかということで一応いれました。
・テスト結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# time bash -x ./chk_backseg_ping.sh ~略~ + ssh user-op@nfs01 ping -q -c 4 -i 0.5 -w 3 yahoo.co.jp ++ echo 0 + result=0 + '[' 0 -ne 0 ']' + for i in '$chkhosts' ++ head -1 ++ sort -R ++ printf '8.8.8.8\n8.8.4.4\nyahoo.co.jp' + target=8.8.8.8 + ssh user-op@nfs02 ping -q -c 4 -i 0.5 -w 3 8.8.8.8 ++ echo 0 + result=0 + '[' 0 -ne 0 ']' + '[' 0 -ge 3 ']' + : + exit 0 real 0m19.274s user 0m0.124s sys 0m0.280s |
pingの疎通が問題なかったためフェイルオーバーしなかった模様。
次はnat01のiptablesでもstopしてglobalにpingが通らない状態にしてf/oするか確かめてみる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[root@nat01 ~]# service iptables status Table: nat Chain PREROUTING (policy ACCEPT) num target prot opt source destination Chain INPUT (policy ACCEPT) num target prot opt source destination Chain OUTPUT (policy ACCEPT) num target prot opt source destination Chain POSTROUTING (policy ACCEPT) num target prot opt source destination 1 MASQUERADE all -- 172.18.0.0/16 0.0.0.0/0 [root@nat01 ~]# service iptables stop iptables: Flushing firewall rules: [ OK ] iptables: Setting chains to policy ACCEPT: nat [ OK ] iptables: Unloading modules: [ OK ] [root@nat01 ~]# service iptables status iptables: Firewall is not running. |
適当なバックセグメントのサーバからpingうちながら実施。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# time bash -x ./chk_backseg_ping.sh + title=hoge ++ /bin/date +%Y/%m/%d.%H:%M:%S + datetime=2013/11/15.15:34:16 + mailto=xxxxxxx@xxxx.net + log=/opt/log/aws_error.log + backseg=172.18.20 ++ awk '{print $2}' ++ perl -pe 's/\n/ /g' ++ egrep -v -a 'vip|sorry|^#' ++ grep 172.18.20 /etc/hosts + hostgroup='lvs01 lvs02 cache01 cache02 db01 db02 web01 mov01 nfs01 nfs02 ' + ssh_chkfile=/home/user-op/.ssh/authorized_keys ++++ dirname ./chk_backseg_ping.sh +++ cd . +++ pwd ++ echo /opt/bin + dir=/opt/bin + lockfile=/opt/bin/nat_fo_complete + user=user-op + target1=8.8.8.8 + target2=8.8.4.4 + target3=yahoo.co.jp + pin_count=4 + pin_int=0.5 + pin_wait=3 + ng_count=3 + active_rt=rtb-xxxxxxxx + standby_rt=rtb-yyyyyyyy + subnetid=subnet-zzzzzzzz + '[' -f /opt/bin/nat_fo_complete ']' + : + for i in '$hostgroup' + ssh user-op@lvs01 ls /home/user-op/.ssh/authorized_keys /home/user-op/.ssh/authorized_keys ++ echo 0 + result=0 + '[' 0 -eq 0 ']' + chkhosts=' lvs01' ~略~ + for i in '$hostgroup' + ssh user-op@nfs02 ls /home/user-op/.ssh/authorized_keys /home/user-op/.ssh/authorized_keys ++ echo 0 + result=0 + '[' 0 -eq 0 ']' + chkhosts=' lvs01 lvs02 cache01 cache02 db01 db02 web01 mov01 nfs01 nfs02' + ng_hosts=0 + for i in '$chkhosts' ++ head -1 ++ sort -R ++ printf '8.8.8.8\n8.8.4.4\nyahoo.co.jp' + target=8.8.4.4 + ssh user-op@lvs01 ping -q -c 4 -i 0.5 -w 3 8.8.4.4 ++ echo 1 + result=1 + '[' 1 -ne 0 ']' ++ expr 0 + 1 + ng_hosts=1 + for i in '$chkhosts' ++ head -1 ++ sort -R ++ printf '8.8.8.8\n8.8.4.4\nyahoo.co.jp' + target=yahoo.co.jp + ssh user-op@lvs02 ping -q -c 4 -i 0.5 -w 3 yahoo.co.jp ++ echo 1 + result=1 + '[' 1 -ne 0 ']' ++ expr 1 + 1 + ng_hosts=2 ~略~ + for i in '$chkhosts' ++ head -1 ++ sort -R ++ printf '8.8.8.8\n8.8.4.4\nyahoo.co.jp' + target=8.8.4.4 + ssh user-op@nfs02 ping -q -c 4 -i 0.5 -w 3 8.8.4.4 ++ echo 1 + result=1 + '[' 1 -ne 0 ']' ++ expr 9 + 1 + ng_hosts=10 + '[' 10 -ge 3 ']' + '[' -f /opt/bin/nat_fo_complete ']' + : + nat_failover ++ date '+%Y-%m-%d %T' + echo 2013-11-15 15:34:50 'start replace-route-table-association' ++ egrep '(RouteTableAssociationId|RouteTableId)' ++ sed -e 's/[", ]//g' ++ grep -A 2 subnet-zzzzzzzz ++ awk -F : '{print $1"="$2}' ++ aws ec2 describe-route-tables + eval RouteTableAssociationId=rtbassoc-6a212908 RouteTableId=rtb-xxxxxxxx ++ RouteTableAssociationId=rtbassoc-6a212908 ++ RouteTableId=rtb-xxxxxxxx + '[' rtb-xxxxxxxx = rtb-xxxxxxxx ']' + aws ec2 replace-route-table-association --route-table-id rtb-yyyyyyyy --association-id rtbassoc-6a212908 ++ echo 0 + result=0 + echo 0 + failstat='fail from rtb-xxxxxxxx to rtb-yyyyyyyy, segment:172.18.20, status:0' + mail_fail + mail -s '(hoge) nat_f/o_2013/11/15.15:34:16' xxxxxxx@xxxx.net + printf 'NAT_failover done. please check site.\n fail from rtb-xxxxxxxx to rtb-yyyyyyyy, segment:172.18.20, status:0' + exit 0 real 0m35.361s user 0m1.392s sys 0m0.456s |
フェイルオーバするときの実行時間は35秒なので毎分実行しても大丈夫ではある。(ホストが増えすぎなければ
バックグラウンドで動かすと並列実行できるとか見かけたけどよくわからなかったので気にしないことにします。
(並列実行とかするとたぶんDoS攻撃ちっくになるので良ろしくないんじゃないのかとも思いました。)
負荷とか実行時間が気になる場合は、全部じゃなくてランダムに選んだ複数のホストでチェックするとかでもいい気がします。
変えるならこんな感じでしょうか。
1 2 3 4 5 |
hostgroup=`grep $backseg /etc/hosts|egrep -v -a 'vip|sorry|^#'|awk '{print $2}'|perl -pe 's/\n/ /g'` ↓ check_num=6 hostgroup=`grep $backseg /etc/hosts|egrep -v -a 'vip|sorry|^#'|awk '{print $2}'|sort -R|head -${check_num}|perl -pe 's/\n/ /g'` |
何も考えずにcron登録すると複数回実行されることを防ぐためにロックファイルを作るようにしたので、
復旧するときにそれを手動で管理する必要があります。
ロックファイルがある状態でテストしてみると変数定義直後に終了します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# time bash -x ./chk_backseg_ping.sh ~略~ + pin_wait=3 + ng_count=3 + active_rt=rtb-xxxxxxxx + standby_rt=rtb-yyyyyyyy + subnetid=subnet-zzzzzzzz + '[' -f /opt/bin/nat_fo_complete ']' + exit real 0m0.039s user 0m0.000s sys 0m0.012s |
参考にしたスクリプトは以下URLに載ってるのです。
NATインスタンスを冗長構成にしてみた – log4moto
個人的に少し気にしているのはyahoo.co.jpにそんなにpingしていいのかどうか。
8.~もgoogleらしいので、まあいいことにします。
こんなの↓もあるようです。あとはjpじゃないyahooにpingしてみるという意見も。
pingチェックサイト(試験運用) – 学術情報ネットワーク(SINET4、サイネット・フォー)
・とりあえず2分おきにcron登録(スタンバイ機にて)
1 2 3 4 5 |
# crontab -e # crontab -l ## nat check and failover script */2 * * * * /opt/bin/chk_backseg_ping.sh |
後日修正した↓ので追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# diff chk_backseg_ping.sh old/chk_backseg_ping.sh.20140210 31d30 < current_ids=$dir/current_ids 36,41c35 > aws ec2 describe-route-tables|grep -A 2 ${subnetid}|egrep '(RouteTableAssociationId|RouteTableId)'|sed -e 's/[", ]//g'|awk -F : '{print $1"="$2}' > $current_ids > source $current_ids > if [ -z "${RouteTableAssociationId}" ];then > printf "Failed to set the value of RouteTableAssociationId and RouteTableId..\n Nat-failover did not start. Maybe aws-api-server unreachable."|tee -a ${log}|mail -s "(${title}) nat_f/o_${datetime}" ${mailto} > exit < fi --- > eval `aws ec2 describe-route-tables|grep -A 2 ${subnetid}|egrep '(RouteTableAssociationId|RouteTableId)'|sed -e 's/[", ]//g'|awk -F : '{print $1"="$2}'` |
以上、見ていただいてありがとうございました。