Serfを使用したNatサーバの冗長化
Serfを使用してNatサーバの冗長化構成を構築したので、メモ。
Serfとは
Serfとは、オーケストレーションを実現できるツールで、Serf間でクラスタを組み、クラスタのメンバーの状態変化時にスクリプトを実行したりできる。
管理サーバを特に必要とせず導入できるため、比較的導入は簡単である。
監視単位としては、ノード単位の監視しかできず、サービスレベルでの監視をしたい場合はConsulで実現できる。
Serfを利用するケースとしては、以下のようなものがある
・WEBサーバ増減に伴うロードバランサーへの登録、解除
・サーバ増減に伴うDNSの更新
Natサーバの冗長構成
今回はAWSにてNatサーバの冗長構成を構築した。
MultiAz環境にてそれぞれのAZにNatサーバを配置し、各WebサーバはそれぞれのAZのNatサーバに通信するように、NatサーバはActive/Active構成とした。
冗長構成の仕組みとしては、2台のNatサーバにインストールしたSerfでクラスタを組み、WebサーバのサブネットのDefaultGWをNatサーバになるようにRouterを設定。
以下構成図の場合、サブネットごとに紐づけるルートテーブルを分けている(WEB1⇒Nat1にルーティング、WEB2⇒Nat2にルーティングするルートテーブル)
障害時には、それぞれのルートテーブルをスクリプトで向きを切り替え、正常のNatサーバ側へ通信するような構成にした。
構成図は以下のとおり。
障害時の挙動は、serf.confにて設定する。
以下例では、メンバーのFail,Leave時にスクリプトを実行している。
{ "tags" : { "role" : "nat" }, "event_handlers" : [ "member-failed=/etc/serf/route-change.sh", "member-leave=/etc/serf/route-change.sh" ], "reconnect_timeout" : "1h", "reconnect_interval" : "10s", "enable_syslog" : true }
スクリプトの中で、1台のNatサーバが障害時にもう1台のNatサーバにリクエストがいくように、Routerの向き先を変更する。
Router_A="AAAA" Router_B="BBBB" dest_cidr_block="0.0.0.0/0" instance_id=`curl -s http://169.254.169.254/latest/meta-data/instance-id` nic_id=`aws ec2 describe-instances --filters "Name=instance-id,Values=$instance_id" | jq '.Reservations[].Instances[].NetworkInterfaces[].NetworkInterfaceId' | sed -e 's/"//g'` for routeid in $Router_A $Router_B do aws ec2 replace-route --route-table-id $routeid \ --destination-cidr-block $dest_cidr_block \ --network-interface-id $nic_id done
起動時のクラスタ組み込み
再起動などの場合に、毎回Natサーバを手動でクラスタに組み込むのは嫌なので、自動で組み込むように設定。
SerfはMulticastでのオートディスカバリによる自動組み込みをサポートしているが、AWS環境ではMulticastを使用できないとのことなので、
今回はinitスクリプトの中でタグを利用し、クラスタの自動組み込みをすることにした。
各NatサーバのTagに、Serf-joinタグを付与し、タグからNatのIPアドレスを検索する。
そして、検索したIPアドレスをSerf joinでメンバー登録する。
以下例でタグからサーバのIPアドレスを取得している。
aws ec2 describe-instances --filters "Name=tag-key,Values=serf-join" "Name=tag-value,Values=true" | jq '.Reservations[].Instances[].PrivateIpAddress' | sed -e 's/"//g'`
AWS環境でのRedis構築
今回RedisをActive/Stanbyで構築したため、備忘録としてブログに残したいと思います。
今回検証した環境は以下。
・Redis 3.0.2
参考資料: Redis Sentinel Documentation – Redis
システム構成としては、Active/Stanbyそれぞれ1台構成。
Redisの設定
RedisをActive/Stanby構成をするには、Slave側に一行以下設定をする。
slaveof masterのIp port
Redis Sentinelの動作
Redis sentinelは、Redisサーバの監視機能、Failoverの機能を提供する管理サーバみたいなもの。実際に、公式ドキュメントでもRedisでの冗長構成の際は、このSentinelを使うのを推奨している。
複数のSentinelがRedisのマスターを監視しており、設定された閾値(Quorum)以上のSentinelがRedisマスターDownと判断した際に、RedisマスターはStatusがOdown(停止)となる。
フェイルオーバーの動作としては、以下のようになる。
・マスターにて障害が発生
・SentinelがマスターのDownを検知(Sdown)
・複数Sentinel間でイベントを通知し、閾値以上のSentinelがSdownと判断した場合、RedisマスターのStatusをOdownに遷移する。
・過半数以上(Sentinelが5台の場合、3台以上)のSentinelがDownと判断した場合はFailoverが実行される。
Sentinelの設定ファイルの以下設定がマスターDownの検知の設定となる。(以下例では、2台以上Sentinelがダウンの同意をすれば、マスターはOdownとなる)
sentinel monitor mymaster redis1 6379 2
検証するまでは、上の設定で2を設定すると2台以上が同意すれば、フェイルオーバーまでするものと思い込んでたが、実際はダウン判定とフェイルオーバーの設定は上記動作の流れで記載した通り、別ものだった。
Redisのフェイルオーバーの設計
RedisMasterに障害が発生した際、上記のSentinelによりFailoverの条件を満たしていれば、Redis自体のFailoverが実行され、SlaveがMasterに遷移する。
しかし、Redisを使用するアプリケーションから見たときには、接続先は障害が発生している旧Masterとなるため、アプリケーション側でRedisの接続先を変更する必要がある。
アプリケーション側のRedis接続先を変える方法はいくつかあるが、今回はVipとAwsのVirtualRouterの変更により、接続先を変更する。
※ネットワーク構成
RedisにそれぞれVipである192.168.10.10のIPアドレスを持たせて、アプリケーション側はRedisの接続先IPをVipである192.168.10.10にする。
Virtual Routerにて、192.168.10.10への通信はRedis MasterのInstanceへルーティングする設定をする。(※Natサーバと同じで、Source/Destのcheckを無効にしておく必要がある)
※Routerの定義(Sample)
送信先 | ターゲット |
---|---|
192.168.10.10(※Redis のVip) | RedisMasterのInstanceId |
そしてMasterに障害が発生した際は、Sentinelの機能により、Failover後にスクリプトを実行できるため、スクリプトの中でVirtual Routerの192.168.10.10の通信を新MasterにするようにAPIを使用して変更する。(aws ec2 replace-route)
また、スクリプトは全Sentinelが動作しているサーバで実行されるため、何度実行されても同じ結果になるようにする必要がある。
そうすることで、アプリケーション側のRedis接続先の切り替えを可能としている。
AWSのネットワーク設計ついて
現在仕事でWEBサービスをAWSで構築しているため、備忘録程度にそのことについて書こうとおもいます。
今回はAWSのVPCのサブネットとセキュリティグループについて、実際に業務で触ったので、その際のネットワークの設計方針に関して書いていきたいと思います。
VPCのサブネット設計方針
VPCのサブネットを分ける考え方として、以下方針でサブネットを設計しました。
- Availability Zone
- 外部(インターネット)へ出る際のルーティング
※私が構築している環境では、Private固定IPアドレスを設定しないため考慮しませんでしたが、固定IPアドレスを使用する場合は、DHCPを使用するサブネットと固定IPのサブネットを分ける必要があると思われます。
以下に大枠のネットワーク構成図を記載します。
ネットワーク構成図
MultiAZ環境
AZに関しては、AZレベルの障害を考慮しMultiAZ環境としています。MultiAZにすることにより、AZ間のレイテンシが発生します。(※上記図はざっくり書きましたが、SubnetはAZを跨げないので、Subnetの数は各AZに3つずつ、計6個必要となります)
ルーティング設計
サブネットの数に関しては、ルーティングの設計をどうするかにより変わってきます。私の環境では、以下の3つの区分でサブネットを分けています。
・Front層:外部へのInbound/outbound通信可能なサブネット(GatewayがInternetGateway)
・App層:外部へはProxyを経由してoutbound通信可能(GatewayがProxyサーバ)
・DB層:外部とは通信不可(外部へ出るGatewayが設定されてない)
基本的には、Publicサブネット(インターネットへ通信可能)とPrivateサブネット(インターネットへ通信不可)の2つで、環境によっては、NatやProxyサーバを使うところでは、3つになります。
Natサーバなどを使うかどうかは、システム要件とあとは好みによってもわかれると思います。NatやProxyを使う要件としては、例えば外部の会社と連携しており、向こうでこちらのGlobalIPアドレスをファイアウォールで通信可能にしているなど、GlobalIPアドレスを固定しないといけないケースなどがあると思います。
そういったシステム要件がなく、単純にYumなどでインターネットへ通信したいだけならば、PublicIPアドレスを各EC2に付与しても通信できるため、Natを使うかどうかは好みの問題なると思います。
セキュリティの設計方針について
セキュリティの考え方としては、以下方針で設計しました。
- セキュリティは基本的にセキュリティグループを使用
- 各セキュリティグループはInboundで制御し、Outboundは使用しない(似たような設定が重複し、設定が複雑になり修正漏れが発生するので)
- セキュリティグループの種類としては、全インスタンス共通のDefaultグループと各サービスごとのセキュリティグループで構成
- 外部からAWSのサーバへのSSH接続は、Bastion(踏み台サーバ)を経由
セキュリティグループの使用
AWSにはセキュリティグループのほかにもNetworkACL(サブネット単位でファイアウォールの制御)もありますが、基本的にインスタンスレベルで制御できるセキュリティグループを使用し、NetworkACLは使用しません。
また、セキュリティグループのSourceの制御は、基本的にSecurityGroupのIDを使用します。IPアドレスのレンジでも許可できますが、セキュリティグループの方が許可範囲をセキュリティグループを付与したインスタンスに絞れるためです。
SSH接続
AWS環境へのSSH接続は、Bastion(踏み台サーバ)を経由して行います。
そうすることで、セキュアな構成になります。また、AWS環境へSSH接続の必要のないときには、Bastionサーバを停止することにより、外部からはSSH接続できず、セキュリティの向上にもつながります。(※一応AZ障害を考慮し、各AZに踏み台サーバ設定していますが、片方のAZの踏み台サーバは障害が発生しないと使用しないため、常時停止しています。)踏み台サーバのセキュリティグループは、社内からのGlobalIPアドレスのみを許可し、その他へのサーバへはBastionからのみ接続許可とする方針としています。