2014年10月13日月曜日

(番外編)AWSに冗長化NFSサーバを

AWS で冗長化 NFS サーバを構築できないか、という話が出てきました。
気は乗らないのですが、やるだけやってみよう、ということになった次第です。やってみたけど駄目だった、という結論になるかもしれません。セキュリティ要件を下げるとか、障害時の挙動がおかしくても受け入れるとか、そのようなことを危惧しています。

2014/10/28追記
本稿で紹介したAWS用冗長化NFSサーバが商品化されました(RHEL6、CentOS6、Oracle Linux6 対応)。

AWS には共有ストレージがありません。NAS もなければ iSCSI Target もありません。シェアード・エブリシング型クラスタは基本的には組めません。
S3 は、ファイルシステムとしてマウントして使うなどは想定外です。1文字書くだけでも数秒という単位の時間を要します。使いどころを考える必要があります。
GlusterFS でよいのではという話もあろうかと思いますが、ペタバイト・クラスにも対応可能な共有ストレージとして設計されているものであり、GB 単位の共有ストレージとしては不向きだと思います。
AWS で冗長化する場合は、シェアード・ナッシング型の構成とする必要があります。データベースであればレプリケーションの登場です。ブロックデバイスをレプリケーションできる DRBD を利用すれば、冗長化 NFS サーバが構築できると思います。

基本的には、冗長化 NFS サーバの一つの完成形としてまとめた、本編の
SoftLayerに冗長化NFSサーバを(その1)
http://dba-ha.blogspot.jp/2014/08/softlayernfs.html
を移植すればよいです。検証目的に絞るという観点で、移植作業自体は半日ほどで完了しました。
問題は、AWS にはいろいろな制限があるため、それをどうするか対策を考えなければなりません。

基本的な方針としては、AWS でできないことは素直にあきらめる、です。
また、同一構成とするためだけの意味しか持たない手段は排除します。
例えば、複数の NIC を追加する構成などはとりません。冗長性が上がるわけでもなければ使える帯域が増えるわけでもなく、セキュリティが向上するわけでもありません。わざわざ設計を複雑にするだけです。CDP に書いてあるから、というだけであれば却下します。Backnet パターンの実装などは、書かれている実装方法をそのまま真に受けたりせず、書かれていないことも手順追加 (Advanced routing を駆使する等) すればそれなりに動くようにはできる部分もありますが…。AWS では全てのサブネットにルートテーブルを指定する必要があります。指定しない場合はデフォルトのルートテーブルが指定されているものとみなされます。ルートテーブルの定義に、ローカルネットワーク (VPC) 内はすべてルーティングするという設定があります。この定義は削除できません。実質的に削除したのと同じ効果を狙った設定追加も可能だとは思いますが、作成できるルートテーブルの数に制限があるので、サブネットが増えると対応が難しくなるかもしれません。基本的な考え方としては、サブネット間の通信制限はセキュリティグループ等のファイアウォール機能で実現する必要があります。ネットワークを分割する目的で NIC を追加する意味がどれだけあるのか疑問です。サブネットを分けてもルーティング不能なネットワークに分割したことにはなりません。ブロードキャストやマルチキャストもサポートされておらず、デフォルトのセキュリティグループ設定ではサブネット内の通信も全く許可されていません (実は例外があって、設定をみただけではわかりませんが、リンクローカルアドレス、VPC 予約アドレスについてはサブネットとは無関係に、全許可となっています)。
NAT サーバを冗長化するために NIC を追加する構成を見かけますが、NAT サーバは可能な限り排除すべきものと考えています。スケールアウトや冗長化が難しいという問題があるからです。ただ単に、インターネットへのアクセス目的であれば、プロキシサーバを用意すべきです。
複数の NIC を追加する必要が出てくるのは、そのような制約のある仮想アプライアンスを導入する場合や、たくさんの IP アドレスを付与したい場合 (仮想 NIC に付与できる IP アドレスが限られており、仮想 NIC を追加すれば付与できる IP アドレスを増やすことができます) に限られるのではないか、というのが私の結論です。

セキュリティグループの話が出たので、こちらの制約も触れておきます。
セキュリティグループは、AWS がファイアウォールを仮想化したもの、ということになるのだと思います。
一番の制約は、ログが取得できないことです。拒否した通信のログを取りたければセキュリティグループは全許可に設定し、ログ取得可能な別のファイアウォールでログを取ることになります。許可した通信のログをとりたければ、やはりログ取得可能な別のファイアウォールを追加してそちらでログをとることになります。このようにすればすべてのログが取れる、というわけではない点に注意してください。
ホワイトリストしか設定できない点もいろいろな制約を生み出します。通信相手としては、CIDR でアドレス指定するか、セキュリティグループ ID を指定するしかありません。通信相手は指定できますが、自身側のどの IP アドレスと対応させるかは指定できません。ホスト名やドメイン名等は指定できません。AWS のサービス利用を許可するために IP アドレスを問い合わせるとホスト名しか教えてくれません。結果、全許可となってしまうことがあります。ブラックリストは指定できません。ソースポートも指定できません。ftp のように、どのポートが使われるかその時にならないと分からないプロトコルはポートの特定ができないので、広範囲にポートを開けておく必要が出てきます。NFS などはサーバ側である程度ポートを固定できるので何とかなることも多いですが、とりあえず、関連ポートを自動的に開けてくれるようなインテリジェントな機能を期待してはいけません。
AWS には、ACL というファイアウォール機能もありますが、ステートレスであり、登録できるルール数が限られており、ログも取得できないので、基本的には使い道がないと判断しています。
AWS のセキュリティは、土台部分についてはいろいろな認定を受けているという安心感がありますが、AWS 上に構築するシステムがセキュアかどうかは全く別の話であり、SIer の腕の見せ所なのだと思われます。ファイアウォールをどこに設置するのが合理的なのか、その結論が受け入れられるのか、大きな問題です。最適解を求めると、AWS を使わないこととなってしまいかねないので、どこかで妥協が必要となってきます。社内のセキュリティ基準の方を変えるしかない、ということも多々あろうかと思われます。

AWS では、VIP が使えません。AWS でできないことは素直にあきらめるという方針なのですが、ここだけはどうしても検討が必要です。
EIP (グローバル IP アドレス) を VIP の代用として構築する、という手法が案内されていることも多いですが、セキュリティ要件を満たさないことが多いです。
NFS サーバやデータベース・サーバがグローバル IP アドレスを持つ、というのは許容されないのが通常の感覚だと思われます。EIP への通信を行うクライアントもグローバル IP アドレスを持つ必要が出てきます。
動的にルートテーブルを書き換えたり、仮想 NIC を付け替えたり、という手法も考えられますが、これらも AWS が用意している API を呼び出す必要があります (EIP の付け替えも当然 API 呼び出しが必要です)。AWS の API はインターネット上にあるサーバと通信しなければならないという制約があります。NFS サーバやデータベース・サーバが直接インターネット上のサーバと通信することは許容されないというのが通常の感覚だと思われます。プロキシサーバを経由すればよいのでは、という話もありそうですが、それではセキュリティ要件を満たせない、ということも多いと思います。*.amazonaws.com や *.aws.amazon.com を通信相手として許容すると、AWS にある全サーバを許可したに等しく、個人でも簡単に AWS を利用できることからすると、情報漏えいの経路としては大問題です。もっと絞り込む必要があります。ただし、どう絞り込めばよいかは AWS のサポートが教えてくれないので、自分で検証していくしかありません。運用に組み込む必要もあるかもしれません。API を使用することは、システムを AWS に依存させることを意味するので、作りこめば作りこむほど、他のクラウドへの移行の妨げとなり、ベンダーロックインの完成です。AWS にとっては、API を使用してもらえることは非常に旨味のある話ということになります。VIP が使えるようになると API 使用率が下がるので、AWS にとってはメリットがありません。
このように考えていくと、VIP が使えないことがどれだけの制約となっているかがわかります。

VIP が使えないことをカバーする手段としては ELB (Elastic Load Balancer) の利用があります。

AWS では、常に、データセンタ (AZ、Availability Zone) をまたいで (Multi AZ で) 冗長化したシステムを構築することが推奨されています。1つのデータセンタがまるっと障害を起こす、ということを今までにも何度かやらかしているので、突然長時間にわたって止まってもよいというシステムでもない限り、Multi AZ での冗長化が必須です。また、「イキガミ」のように、あなたのサーバが数週間後のある日ある時、消滅するという予告メールが来ます。このメールを受け取ったら、サーバを移動する必要があります。移動といっても、ライブマイグレーションのようなものはありません。サーバを停止し、バックアップを取得し、バックアップから新規にサーバを構築する必要があります。AWS 側は何もしてくれません。保守する側で、このような事態を想定して手順化しておく必要があります。ライブマイグレーションがないということは、AWS 側でハイパーバイザのメンテナンス停止が必要となるたびにこのようなことが起こるということです。どれだけの頻度で起こるかは、運次第ですが、今までの SIer の常識からするとかなりの高確率とだけ言っておきます。事前メールがない場合もあると明言されています。つまり、いつでもバックアップからリストアできる体制が必要ということです。リージョン内に生き残っている AZ があれば、SLA 上はサービス停止時間に含めないことになっています。これでも売れるのですから、AWS に魅力を感じている人の熱気はすさまじいものがあります。AWS マンセーな記事ばかりが世に溢れているので、このような事実は知られていないのだと思います。ライバル社もパブリッククラウドを啓蒙するために AWS を貶めることは言わないほうが得策だと思っているのではないかと思われます。もっともライバル社の技術者は AWS を使う機会がなく、弱点を知らないというのが真相のようです。敵を知り己を知れば百戦危うからず、という孫子の兵法は生かされていないようです。AWS の弱点勉強会を開いてほしい、という要望も冗談で承っています。

冗長化は必須、という前提であるにもかかわらず、AWS が用意してくれている冗長化手段は基本的には ELB を利用するしかないという点を意識して設計していく必要があります。
AWS の設計では、ELB をいかに利用するかが成否を決めると思っています。
ELB を L4 ロードバランサとして使用している限りは、keepalived による代替が可能なので、ベンダーロックインの心配が減ります (AWS 上で Keepalived を動かそうとすると、マルチキャスト通信や VIP が使えないので冗長化ができませんし、なにより割高となることが多いと思われます)。

AWS の課金体系は、片っ端から従量課金となっています。ELB は、通信さえ発生しなければ、冗長化の仕組みや機能の割には非常に安いです。月額で数千円程度です。こんなに安い冗長化されたロードバランサは他では見られません。通信料や分散先サーバを増やしてもらう等の従量課金で稼ぐ仕組みです。AZ 内の通信は無料、というセールストークが頭に叩き込まれている人にとって、AZ 内部で ELB を利用した場合に通信費用が従量課金されることを忘れられがちなので注意が必要です。AWS が従量課金で安い、という営業トークの裏側には、危険な課金体系がいたるところに出てきます。使わないときにシステムを停止すれば安くなるとか、オートスケールを利用して必要な時だけ増やせばトータルコストが安くなるとか、運よく条件がハマった時といった、多くの利用者からみると適用するのがなかなか難しいことが多い部分については安い、というカラクリがいたるところに散りばめられています。通信しなければ安い ELB を積極活用する必要がありますが、通信しなければ安いといわれても…。どこかの OS がセキュリティのアドバンテージとして C2 認定を自慢していましたが中身を見るとネットワークに接続しなければという条件が付いていたこととかぶります。

ELB は冗長化されていて、負荷に応じてオートスケールする優れた構成を持っています。Multi AZ でロードバランスすることもできます。ただし、できるということと、使うということは別です。東京リージョンの AZ 間のサーバで ping を打つと 2 ms 以上の時間がかかっています。ELB を介した AZ 間通信がどれだけ時間がかかるかは計測していませんが、リアルタイムで多くのデータを共有する場合には、Active - Active 構成はやめたほうがよいと思います。Active データセンタと DR (Disaster Recovery) データセンタで構成し、自動切り替えにするか手動切り替えにするかを選択することになるのではないかと思います。

設計の肝となる ELB の特性は、特に弱点については把握しておく必要があります。
★冗長化されたロードバランサといえば、固定の VIP を持っているというのが常識として頭にありますが、ELB は固定の VIP を持っていません。名前でアクセスする必要があります。DNS ラウンドロビンが基本です。クライアント側は、ELB 名に対応する IP アドレスをキャッシュしてはいけません。IP アドレスは実際によく変わっています。
★ELB を作り直すと、同じ名前を取得できません。ELB 名を直接使わずに cname レコードを DNS サーバに登録し、cname で利用するのがセオリーのようです。

※2014/10/24追記
マルチ AZ 構成とした場合、A レコードや cname レコードに登録されている名前で接続すると、接続先が同一データセンタの ELB を経由するか、別のデータセンタの ELB を経由するかは運次第です。都度、名前から IP を調べ、同一データセンタの ELB に接続するような仕組みを作りこむべきです。

★分散アルゴリズムはラウンドロビンしかありません(一部LeastConnectionになっている部分もあるがいずれにせよアルゴリズムの選択はできません)。2度目以降の通信について Sticky セッションを利用することで分散先を固定する仕組みがあります。クッキーを使うので、クッキーを使えない通信ではラウンドロビンのみです。
★分散先のサーバを起動した後、ELB はなかなかその分散先サーバを見つけてくれません。仕様です (http://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/US_DeReg_Reg_Instances.html)。何のためのヘルスチェックかよくわかりませんが、とにかく仕様です。
★ELB が受付可能なのは tcp のみで、udp は設定不能です。ウエルノンポートは、25/tcp, 80/tcp, 443/tcp のみ受付可能です。stone と組み合わせれば、これらの制約を回避できる場面が増えると思います。「stone の rpm を作る (http://dba-ha.blogspot.jp/2014/08/stone-rpm.html)」を参照していただければと思います。tcp リピータとして有名ですが udp を tcp に変換する機能も持っています。NFS サーバを ELB 経由で利用する場合、NFSv4 でないと難しいと思います。NFSv2、NFSv3 はあきらめます。NFSv4 だけであれば、2049/tcp のみを利用すればよいはずです。
★アクセスログを S3 に保存することが最近可能となりました。S3 へのアクセスが許容されていない環境では、アクセスログを取得する手段がありません。
★アクセス元 IP アドレスが HTTP ヘッダーに記録されます。HTTP ヘッダーを使用できない通信では、アクセスログを見るしかありません。
★Sorry サーバに通信を振り向ける機能はありません。
★Big IP が持っている機能のすべてを期待してはいけません。通信要件を詰めていくと、Big IP を仮想サーバとして構築する必要が出てくることもあろうかと思われます。


冗長化された NFS サーバの構築をしていくわけですが、今回の検証では AWS でしか動かなくても構わない、という前提を取ります。ベンダーロックインを避けたいのであれば AWS で API を利用するという選択肢がなくなります。AWS 互換をうたうクラウドが世の中にはありますが、S3 の一部の機能のみについての話です。それ以外については、互換性を持つ意味がないと思います。AWS 以外でこれほど制約のあるクラウドを提供した場合、売れないことが確信できるからです。AWS は先駆者としての功績と今までの経験が評価されているのであり、他のクラウドがそっくりまねたとしたら、それはただの○○システム以外の何物でもありません。

とりあえず試作機ということで、Amazon Linux で構築します。ライセンス料が無料だからです。CentOS でいいのでは、という疑問はもっともです。私も最初は CentOS での構築を考えていましたが、いざ構築を始めようとすると、どの AMI (Amazon Machine Image ?) を選べばよいかで迷ってしまいます。どこのだれが構築して提供しているかわからないものを利用するわけにはいかないので、絞っていくと Amazon Linux しかなくなってしまいました (Marketplace にあるものは無条件に信じてもよいでしょうか。Community AMIs はどうでしょうか。Official と銘打っている AMI は Official な人が構築したという保証がどこにあるのでしょうか。AWS のサポートに問い合わせると分かるでしょうか)。AWS のセキュアな利用は、イメージの持ち込みから始まるのだと思います。今回はそこまで手間をかけられませんので、Amazon Linux としました。Amazon Linux にセキュリティに関わる何物かが故意に仕掛けられていることはない、ということが信用できないのであれば、これまた、AWS という選択肢はなくなるという話になります。
イメージの持ち込みといえば、VM Import を利用することになるのだと思いますが、またこちらも制約が多いです。実際に試して問題がないことを確認してから利用を提案しないと痛い目に合う確率はかなり高いです。Linux の場合だと、イメージ持込みは HVM のみとなります。PV でしかライセンス許諾されていないアプリケーションが存在していた関係で HVM イメージを PV 用に改造 (逆? PV 用のボリュームで既存のデータをすべて消して、HVM のイメージをマウントして chroot してゴニョゴニョしてから PV 用のボリュームにコピー) しましたが、Linux だからできる話で、他の OS でこのようなものがあると、おそらくお手上げです。Windows Server のインポートも一筋縄ではいかないですが、話があまりにもそれるのでやめておきます。

Amazon Linux は CentOS6 を AWS 仕様にしてカーネルをアップデートしたバージョン、という認識で問題はないと思っています。epel を使える設定が入っています。

前置きが長くなってしまいました。
まず、pacemaker をインストールしようとしたのですが、なぜかリポジトリに存在しませんでした。heartbeatcorosync が用意されているのに不可解です。
HA Japan が提供している src.rpm からビルドします。Amazon Linux 上で実行するビルド手順を示しますが、GitHub に rpm を置きましたので、そちらを利用すれば手間が省けます。本番用はご自身でビルドしてください。


yum -y install \
 asciidoc \
 autoconf \
 automake \
 cluster-glue-libs-devel \
 corosync \
 corosynclib-devel \
 docbook-style-xsl \
 flex \
 gcc-c++ \
 bzip2-devel \
 git \
 glib2-devel \
 gnutls-devel \
 heartbeat \
 heartbeat-devel \
 heartbeat-libs \
 help2man \
 intltool \
 kernel-devel \
 libcurl-devel \
 libesmtp-devel \
 libselinux-devel \
 libtool \
 libtool-ltdl \
 libtool-ltdl-devel \
 libuuid-devel \
 libxml2-devel \
 libxslt-devel \
 make \
 ncurses-devel \
 ncurses-libs \
 net-snmp-devel \
 pam-devel \
 openssl-devel \
 pkgconfig \
 publican \
 python \
 python-devel \
 rpm-build \
 swig
yum -y localinstall http://ftp.iij.ad.jp/pub/linux/centos/6.5/os/x86_64/Packages/{lm_sensors-devel-3.1.1-17.el6.x86_64.rpm,lm_sensors-3.1.1-17.el6.x86_64.rpm,lm_sensors-libs-3.1.1-17.el6.x86_64.rpm}
wget http://iij.dl.sourceforge.jp/linux-ha/61791/pacemaker-1.0.13-2.1.el6.srpm.tar.gz
tar xzvf pacemaker-1.0.13-2.1.el6.srpm.tar.gz
rpmbuild --rebuild pacemaker-1.0.13-2.1.el6.srpm/pacemaker-1.0.13-2.el6.src.rpm


ここから、試作機の構築作業を始めます。

Amazon Linux (最新の HVM 版) をランチします。とりあえず、t2.micro で2台用意します。それぞれ、20GB の EBS を追加します。
この2台の間の通信は自由にできるようにセキュリティグループを設定しておきます。あとで出てくる ELB サブネットからの通信も許可しておく必要があります。VPC の作成やこのあたりまでの手順は省略します。

必要となるパッケージをインストールします。pacemaker は先ほどビルドしたものをインストールしています。HA Japan が提供するパッケージも一部入れておきます。DRBD はユーザランドのツールのみインストールします。DRBD のカーネルモジュールは、Amazon Linux の場合、すでに入っています。バージョンを確認 (modinfo drbd) して、同じバージョンのツールを導入します。


sudo yum -y update
sudo yum -y localinstall https://raw.githubusercontent.com/pcserver-jp/aws/master/{pacemaker-1.0.13-2.el6.x86_64.rpm,pacemaker-libs-1.0.13-2.el6.x86_64.rpm}
sudo rpm -Uvh --replacefiles http://elrepo.org/linux/elrepo/el6/x86_64/RPMS/drbd84-utils-$(modinfo drbd | grep ^version: | awk '{print $2}')-1.el6.elrepo.x86_64.rpm
wget http://iij.dl.sourceforge.jp/linux-ha/61791/pacemaker-1.0.13-2.1.el6.x86_64.repo.tar.gz
tar xzvf pacemaker-1.0.13-2.1.el6.x86_64.repo.tar.gz -C /tmp/
sudo yum -y -c /tmp/pacemaker-1.0.13-2.1.el6.x86_64.repo/pacemaker.repo install pm_extras pm_diskd
rm -rf /tmp/pacemaker-1.0.13-2.1.el6.x86_64.repo pacemaker-1.0.13-2.1.el6.x86_64.repo.tar.gz

sudo chkconfig --del blk-availability
sudo chkconfig --del corosync
sudo chkconfig --del drbd
sudo chkconfig --del heartbeat
sudo chkconfig --del ip6tables
sudo chkconfig --del logd
sudo chkconfig --del mdmonitor
sudo chkconfig --del messagebus
sudo chkconfig --del netconsole
sudo chkconfig --del netfs
sudo chkconfig --del nfs
sudo chkconfig --del nfslock
sudo chkconfig --del quota_nld
sudo chkconfig --del rdisc
sudo chkconfig --del rpcbind
sudo chkconfig --del rpcgssd
sudo chkconfig --del rpcsvcgssd
sudo chkconfig --del saslauthd


以下のパラメータを設定します。
・1号機のプライベート IP アドレス (HA1_IP)
・2号機のプライベート IP アドレス (HA2_IP)
・1号機のホスト名 (HA1_NODE)
・2号機のホスト名 (HA2_NODE)
・DRBD で共有する領域のサイズ (DRBD_SIZE)
・DRBD が使うパスワード (DRBD_PASSWORD)
・NFS マウントを許可するクライアント (ELB のサブネット) をコンマ区切りで指定


sudo mkdir -p /etc/ha.d/
cat << EOF | sudo tee /etc/ha.d/param_cluster
HA1_IP=10.20.100.79
HA2_IP=10.20.100.80
HA1_NODE=ip-10-20-100-79
HA2_NODE=ip-10-20-100-80
DRBD_SIZE=10G
DRBD_PASSWORD=password
NFS_CLIENTS=10.20.30.0/24
EOF
. /etc/ha.d/param_cluster


共有ディスク領域を準備します。


lsblk
NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
xvda    202:0    0    8G  0 disk
mqxvda1 202:1    0    8G  0 part /
xvdf    202:80   0   20G  0 disk

DEV=xvdf

sudo parted /dev/$DEV mklabel msdos
Information: You may need to update /etc/fstab.

sudo parted /dev/$DEV mkpart primary 1MiB 100% set 1 lvm on
Information: You may need to update /etc/fstab.

sudo pvcreate /dev/${DEV}1
  Physical volume "/dev/xvdf1" successfully created

sudo vgcreate -s 32M vg0 /dev/${DEV}1
  Volume group "vg0" successfully created

sudo lvcreate --name drbd0 --size $DRBD_SIZE vg0
  Logical volume "drbd0" created


DRBD の設定ファイルを作成します。


cat << EOF | sudo tee /etc/drbd.d/global_common.conf
global {
  usage-count no;
}
common {
  handlers {
    local-io-error "/usr/lib/drbd/notify-io-error.sh; /usr/lib/drbd/notify-emergency-shutdown.sh; echo 1 > /proc/sys/kernel/sysrq; echo o > /proc/sysrq-trigger ; halt -f";
    fence-peer "/usr/lib/drbd/crm-fence-peer.sh";
    after-resync-target /usr/lib/drbd/crm-unfence-peer.sh;
  }
  startup {
#wfc#    wfc-timeout 10;
#wfc#    degr-wfc-timeout 10;
#wfc#    outdated-wfc-timeout 10;
  }
  disk {
    on-io-error detach;
    fencing resource-only;
    al-extents 6433;
    c-plan-ahead 20;
    c-delay-target 100;
    c-fill-target 0;
    c-max-rate 10M;
    c-min-rate 1M;
  }
  net {
    protocol C;
    max-buffers 128k;
    sndbuf-size 0;
    rcvbuf-size 0;
    cram-hmac-alg sha1;
    shared-secret "$DRBD_PASSWORD";
    congestion-fill 10M;
    congestion-extents 2000;
    csums-alg md5;
    verify-alg md5;
    use-rle;
  }
}
EOF

cat << EOF | sudo tee /etc/drbd.d/r0.res
resource r0 {
  volume 0 {
    device /dev/drbd0;
    disk /dev/vg0/drbd0;
    meta-disk internal;
  }
  on $HA1_NODE {
    address $HA1_IP:7788;
  }
  on $HA2_NODE {
    address $HA2_IP:7788;
  }
}
EOF


heartbeat の設定ファイルを作成します。ハートビートが1つしか指定できません。この点は劣化しています。そのかわり、Active 機と Stand-by 機を同セグメントに配置しなくても大丈夫な設計になっています。速度が許すのであれば、データセンタをまたいで配置することもできます。


MY_IP=$(ip addr show eth0 | grep 'inet ' | awk '{print $2}' | awk -F/ '{print $1}')
[ "$MY_IP" = "$HA1_IP" ] && PEER_IP=$HA2_IP || PEER_IP=$HA1_IP
cat << 'EOF' | sudo tee /etc/ha.d/authkeys
auth 1
1 crc
EOF
sudo chmod 600 /etc/ha.d/authkeys
cat << EOF | sudo tee /etc/ha.d/ha.cf
crm yes
debug 0
logfacility local2
keepalive 2
warntime 7
deadtime 10
initdead 48
udpport 694
ucast eth0 $PEER_IP
node $HA1_NODE
node $HA2_NODE
uuidfrom nodename
autojoin none
respawn root /usr/lib64/heartbeat/ifcheckd
EOF


pacemaker にロードする設定ファイルを作成します。VIP 関連設定、NFSv3 用設定、ping 監視設定、Disk 監視設定を削除しています。Disk 監視は面倒なので削っただけです。他はあきらめました。ping 監視は状況が確定できれば追加可能だと思います。例えば、同一サブネットであればゲートウェイを指定します。そうでない場合は、別の適切な、ping 応答するサーバを探す必要があります。


NFS_CLIENTS=$(echo $NFS_CLIENTS | tr , " ")
cat << EOF | sudo tee /etc/ha.d/crm_nfsserver
primitive p_drbd_r0 ocf:linbit:drbd \\
  params drbd_resource="r0" \\
  op start   interval="0" timeout="240s" \\
  op monitor interval="31s" role="Master" timeout="20s" \\
  op monitor interval="29s" role="Slave"  timeout="20s" \\
  op notify  interval="0" timeout="90s" \\
  op stop    interval="0" timeout="120s" \\
  op promote interval="0" timeout="90s" \\
  op demote  interval="0" timeout="90s"
primitive p_fs_export ocf:heartbeat:Filesystem \\
  params device=/dev/drbd0 directory=/export fstype=ext4 run_fsck="no" \\
  op start   interval="0"   timeout="60s" \\
  op monitor interval="10s" timeout="40s" \\
  op stop    interval="0"   timeout="60s"
primitive p_rpcbind lsb:rpcbind \\
  op monitor interval="30s"
primitive p_nfslock lsb:nfslock \\
  op monitor interval="30s"
primitive p_nfsserver lsb:nfs \\
  op monitor interval="30s"
primitive p_exp_root ocf:heartbeat:exportfs \\
  params fsid="0" directory="/export" \\
  options="rw,sync,crossmnt,insecure" \\
  clientspec="$NFS_CLIENTS" wait_for_leasetime_on_stop="false" \\
  op start interval="0" timeout="240s" \\
  op stop  interval="0" timeout="100s"
primitive p_exp_share ocf:heartbeat:exportfs \\
  params fsid="1" directory="/export/share" \\
  options="rw,sync,mountpoint,insecure" \\
  clientspec="$NFS_CLIENTS" wait_for_leasetime_on_stop="false" \\
  op start   interval="0"   timeout="240s" \\
  op monitor interval="30s" \\
  op stop    interval="0"   timeout="100s" \\
  meta is-managed="true"
group g_nfs p_fs_export p_rpcbind p_nfslock p_nfsserver p_exp_root p_exp_share
ms ms_drbd_r0 p_drbd_r0 \\
  meta master-max="1" master-node-max="1" clone-max="2" \\
  clone-node-max="1" notify="true" target-role="Started" \\
  is-managed="true"
location lc_nfs g_nfs 100: $HA1_NODE
colocation cl_nfs inf: g_nfs ms_drbd_r0:Master
order ord_nfs 0: ms_drbd_r0:promote g_nfs:start
property stonith-enabled="false"
property no-quorum-policy="ignore"
property pe-error-series-max="100"
property pe-warn-series-max="100"
property pe-input-series-max="100"
rsc_defaults migration-threshold="2"
rsc_defaults resource-stickiness="200"
EOF


NFS の設定を変更します。


cat << 'EOF' | sudo tee /etc/sysconfig/nfs
MOUNTD_NFS_V2="no"
#MOUNTD_NFS_V3="no"
RQUOTAD_PORT=875
#RPCRQUOTADOPTS=""
#LOCKDARG=
LOCKD_TCPPORT=32803
LOCKD_UDPPORT=32769
#RPCNFSDARGS="-N 2 -N 3"
RPCNFSDARGS="-N 2"
RPCNFSDCOUNT=16
#NFSD_MODULE="noload"
#NFSD_V4_GRACE=90
#RPCMOUNTDOPTS=""
MOUNTD_PORT=892
#STATDARG=""
STATD_PORT=662
STATD_OUTGOING_PORT=2020
RPCIDMAPDARGS="-S"
#SECURE_NFS="yes"
#RPCGSSDARGS=""
#RPCSVCGSSDARGS=""
#RDMA_PORT=20049
EOF
sudo sed -i -e 's/^udp6/#udp6/' -e 's/^tcp6/#tcp6/' /etc/netconfig


ここまでの作業は1号機と2号機で行ってください。
この時点でバックアップを取得するために一度停止して再起動するのはよい考えです。

次は1号機での作業です。
DRBD 領域を初期化し、Active 機として作動させます。


sudo drbdadm create-md r0
Writing meta data...
initializing activity log
NOT initializing bitmap
New drbd meta data block successfully created.

sudo sed -i -e '/wfc-timeout/ s/^#wfc#//' /etc/drbd.d/global_common.conf
sudo /etc/init.d/drbd start
Starting DRBD resources: [
     create res: r0
   prepare disk: r0
    adjust disk: r0
     adjust net: r0
]
..........
***************************************************************
 DRBD's startup script waits for the peer node(s) to appear.
 - In case this node was already a degraded cluster before the
   reboot the timeout is 10 seconds. [degr-wfc-timeout]
 - If the peer was available before the reboot the timeout will
   expire after 10 seconds. [wfc-timeout]
   (These values are for resource 'r0'; 0 sec -> wait forever)
 To abort waiting enter 'yes' [  10]:
.
sudo sed -i -e '/wfc-timeout/ s/^\([^#]\)/#wfc#\1/' /etc/drbd.d/global_common.conf
sudo drbdadm primary --force all
cat /proc/drbd
version: 8.4.3 (api:1/proto:86-101)
srcversion: 88927CDF07AEA4F7F2580B2
 0: cs:WFConnection ro:Primary/Unknown ds:UpToDate/Outdated C r----s
    ns:0 nr:0 dw:0 dr:728 al:0 bm:0 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:10485404

sudo mkfs.ext4 /dev/drbd0
mke2fs 1.42.9 (28-Dec-2013)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
655360 inodes, 2621351 blocks
131067 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=2151677952
80 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

sudo tune2fs -c 0 -i 0 /dev/drbd0
tune2fs 1.42.9 (28-Dec-2013)
Setting maximal mount count to -1
Setting interval between checks to 0 seconds

sudo mkdir -p /export
sudo mount /dev/drbd0 /export

sudo /etc/init.d/rpcbind start
Starting rpcbind:                                          [  OK  ]

sudo /etc/init.d/nfslock start
Starting NFS statd:                                        [  OK  ]

sudo /etc/init.d/nfs start
Initializing kernel nfsd:                                  [  OK  ]
Starting NFS services:                                     [  OK  ]
Starting NFS quotas:                                       [  OK  ]
Starting NFS mountd:                                       [  OK  ]
Starting NFS daemon:                                       [  OK  ]
Starting RPC idmapd:                                       [  OK  ]

sudo /etc/init.d/nfs stop
Shutting down NFS daemon:                                  [  OK  ]
Shutting down NFS mountd:                                  [  OK  ]
Shutting down NFS quotas:                                  [  OK  ]
Shutting down RPC idmapd:                                  [  OK  ]

sudo /etc/init.d/nfslock stop
Stopping NFS statd:                                        [  OK  ]

sudo /etc/init.d/rpcbind stop
Stopping rpcbind:                                          [  OK  ]

sudo umount /var/lib/nfs/rpc_pipefs/
sudo mv /var/lib/nfs /export/
sudo ln -s /export/nfs /var/lib/nfs
sudo rmdir /export/nfs/rpc_pipefs/
sudo mkdir -p /var/lib/rpc_pipefs/
sudo ln -s /var/lib/rpc_pipefs /export/nfs/rpc_pipefs

sudo mkdir -p /export/share
sudo chmod 755 /export/share
sudo chown nfsnobody:nfsnobody /export/share
sudo umount /export/
sudo drbdadm secondary all

sudo /etc/init.d/drbd stop
Stopping all DRBD resources: .

sudo /etc/init.d/heartbeat start
Starting High-Availability services: Done.

while ! sudo crm_mon -1rfA | grep "Online: \[ $(uname -n) \]"; do sleep 5; done
Online: [ ip-10-20-100-79 ]

sudo crm configure load update /etc/ha.d/crm_nfsserver
INFO: building help index

while ! sudo crm_mon -1rfA | grep p_exp_share | grep Started; do sleep 5; done
     p_exp_share        (ocf::heartbeat:exportfs):      Started ip-10-20-100-79

sudo crm_mon -1frA
============
Last updated: Sat Oct 11 12:21:36 2014
Stack: Heartbeat
Current DC: ip-10-20-100-79 (2707e513-7593-a34a-244a-449e4e6a758a) - partition with quorum
Version: 1.0.13-a83fae5
1 Nodes configured, unknown expected votes
2 Resources configured.
============

Online: [ ip-10-20-100-79 ]

Full list of resources:

 Resource Group: g_nfs
     p_fs_export        (ocf::heartbeat:Filesystem):    Started ip-10-20-100-79
     p_rpcbind  (lsb:rpcbind):  Started ip-10-20-100-79
     p_nfslock  (lsb:nfslock):  Started ip-10-20-100-79
     p_nfsserver        (lsb:nfs):      Started ip-10-20-100-79
     p_exp_root (ocf::heartbeat:exportfs):      Started ip-10-20-100-79
     p_exp_share        (ocf::heartbeat:exportfs):      Started ip-10-20-100-79
 Master/Slave Set: ms_drbd_r0
     Masters: [ ip-10-20-100-79 ]
     Stopped: [ p_drbd_r0:1 ]

Node Attributes:
* Node ip-10-20-100-79:
    + master-p_drbd_r0:0                : 10000

Migration summary:
* Node ip-10-20-100-79:


次は2号機での作業です。
DRBD 領域を初期化し、Stand-by 機として作動させます。
この手順は、Stand-by 機の直し方でもあります。障害復旧手順となります。


sudo drbdadm create-md r0
Writing meta data...
initializing activity log
NOT initializing bitmap
New drbd meta data block successfully created.

sudo mkdir -p /export
sudo mkdir -p /var/lib/rpc_pipefs/
sudo rm -rf /var/lib/nfs
sudo ln -s /export/nfs /var/lib/nfs

sudo /etc/init.d/heartbeat start
Starting High-Availability services: Done.

while ! grep -q sync /proc/drbd 2> /dev/null; do date;sleep 5; done
Sat Oct 11 12:21:41 UTC 2014

cat /proc/drbd
version: 8.4.3 (api:1/proto:86-101)
srcversion: 88927CDF07AEA4F7F2580B2
 0: cs:SyncTarget ro:Secondary/Primary ds:Inconsistent/UpToDate C r-----
    ns:0 nr:4608 dw:4608 dr:265856 al:0 bm:16 lo:2 pe:1 ua:0 ap:0 ep:1 wo:f oos:10219676
        [>....................] sync'ed:  2.6% (9980/10236)Mfinish: 0:16:34 speed: 10,264 (10,220) want: 10,240 K/sec

while grep sync /proc/drbd; do date;sleep 60; done
        [>....................] sync'ed:  4.4% (9796/10236)Mfinish: 0:16:00 speed: 10,436 (10,304) want: 10,240 K/sec
Sat Oct 11 12:22:30 UTC 2014
        [==================>.] sync'ed: 98.0% (212/10236)Mfinish: 0:00:21 speed: 10,232 (10,224) want: 10,240 K/sec
Sat Oct 11 12:38:30 UTC 2014

cat /proc/drbd
version: 8.4.3 (api:1/proto:86-101)
srcversion: 88927CDF07AEA4F7F2580B2
 0: cs:Connected ro:Secondary/Primary ds:UpToDate/UpToDate C r-----
    ns:0 nr:7828 dw:7828 dr:10485176 al:0 bm:640 lo:0 pe:0 ua:0 ap:0 ep:1 wo:f oos:0

sudo crm_mon -1frA
============
Last updated: Sat Oct 11 12:40:14 2014
Stack: Heartbeat
Current DC: ip-10-20-100-79 (2707e513-7593-a34a-244a-449e4e6a758a) - partition with quorum
Version: 1.0.13-a83fae5
2 Nodes configured, unknown expected votes
2 Resources configured.
============

Online: [ ip-10-20-100-79 ip-10-20-100-80 ]

Full list of resources:

 Resource Group: g_nfs
     p_fs_export        (ocf::heartbeat:Filesystem):    Started ip-10-20-100-79
     p_rpcbind  (lsb:rpcbind):  Started ip-10-20-100-79
     p_nfslock  (lsb:nfslock):  Started ip-10-20-100-79
     p_nfsserver        (lsb:nfs):      Started ip-10-20-100-79
     p_exp_root (ocf::heartbeat:exportfs):      Started ip-10-20-100-79
     p_exp_share        (ocf::heartbeat:exportfs):      Started ip-10-20-100-79
 Master/Slave Set: ms_drbd_r0
     Masters: [ ip-10-20-100-79 ]
     Slaves: [ ip-10-20-100-80 ]

Node Attributes:
* Node ip-10-20-100-79:
    + ip-10-20-100-80-eth0              : up
    + master-p_drbd_r0:0                : 10000
* Node ip-10-20-100-80:
    + ip-10-20-100-79-eth0              : up
    + master-p_drbd_r0:1                : 10000

Migration summary:
* Node ip-10-20-100-79:
* Node ip-10-20-100-80:


ここまでの作業で、ELB への組み込みを除き、構築完了です。
SoftLayer 版に比べると、まだまだ設定が漏れています。チューニング関係、セキュリティ関係はほぼ手つかずです。IP アドレスも DHCP から取得せず固定すべきです。NTP サーバもローカルネットワーク内に用意してそちらと同期すべきです。他にもいろいろありますが、検証目的であれば十分と判断しています。

スイッチオーバ (手動フェイルオーバ) させてみます。


cat << 'EOF' | sudo tee /etc/ha.d/switch
#!/bin/bash
crm_mon -rfA1 | grep 'p_exp_share.*exportfs'
crm resource move p_exp_share 2> /dev/null
echo
date
echo switching cluster ...
echo
while ! crm_mon -rfA1 | grep -q 'p_exp_share.*exportfs.*Stopped';
do
  sleep 1
done
while ! crm_mon -rfA1 | grep -q 'p_exp_share.*exportfs.*Started';
do
  sleep 1
done
crm resource unmove p_exp_share
crm_mon -rfA1 | grep 'p_exp_share.*exportfs'
date
EOF
sudo chmod 755 /etc/ha.d/switch

sudo /etc/ha.d/switch
     p_exp_share        (ocf::heartbeat:exportfs):      Started ip-10-20-100-79

Sat Oct 11 13:01:40 UTC 2014
switching cluster ...

     p_exp_share        (ocf::heartbeat:exportfs):      Started ip-10-20-100-80
Sat Oct 11 13:01:57 UTC 2014

sudo /etc/ha.d/switch
     p_exp_share        (ocf::heartbeat:exportfs):      Started ip-10-20-100-80

Sat Oct 11 13:02:23 UTC 2014
switching cluster ...

     p_exp_share        (ocf::heartbeat:exportfs):      Started ip-10-20-100-79
Sat Oct 11 13:02:40 UTC 2014


ELB を作成します。
ELB 用のサブネット作成やセキュリティグループの設定手順は省略します。


「Create Load Balancer」 ボタンをクリックし、ウィザードを起動します。

1. Define Load Balancer
「Load Balancer name」 に 「nfs-test」 と入力します。
「Create LB Inside」 にて VPC を選択します。
「Create an internal load balancer」 にチェックを入れます。
「Enable advanced VPC configuration」 にチェックを入れます。
「Listener Configuration」 にて、以下のように設定します。
・Load Balancer Protocol: TCP
・Load Balancer Port: 2049
・Instance Protocol: TCP
・Instance Port: 2049
「Continue」 ボタンをクリックします。

2. Configure Health Check
「Ping Protocol」 にて TCP を選択します。
「Ping Port」 に 「2049」 と入力します。
「Response Timeout」 に 「2」 を入力します。
「Health Check Interval」 に 「5」 を入力します。
「Unhealthy Threshold」 にて 「2」 を選択します。
「Healthy Threshold」 にて 「2」 を選択します。
「Continue」 ボタンをクリックします。

3. Select Subnets
適切なサブネットを追加します。
「Continue」 ボタンをクリックします。

4. Assign Security Groups
「Assign a security group」 にて 「Select an existing security group」 を選択します。
適切なセキュリティグループを選択します。
「Continue」 ボタンをクリックします。

5. Add EC2 Instances
Active 機のみ選択します。Stand-by 機は後で Active 化してから追加します。
「Enable Connection Draining」 のチェックを外します。
「Continue」 ボタンをクリックします。

6. Add Tags
必要があればタグを追加できます。今回は何も追加しません。
「Continue」 ボタンをクリックします。

7. Review
内容を確認後、「Create」 ボタンをクリックします。


先ほど追加した Active 機の 「Status」 が 「InService」 となっているのを確認してください。
スイッチオーバさせます。
「Status」 が 「OutOfService」 となっているのを確認してください。

この状態で現在 Active 機となっているもう1台を ELB に登録します。
 「Status」 が 「InService」 となっているのを確認してください。

もう一度スイッチオーバさせます。
「Status」 の状態が、Active 機は 「InService」、Stand-by 機は 「OutOfService」 となっていれば正常に動作しています。

後は、複数の NFS クライアントを作成してマウントし、動作確認をしていくことになります。ELB との連携が心配です。ELB の IP アドレスが変わったときに追随するのか、追随させるための機能を盛り込めるのかどうか。クライアント側でアンマウント、マウントし直し、というカラクリを作りこむ必要があるかもしれません。作りこむのであれば、ELB を経由せずに直接 Active 機へ接続しなおせばよいという話になるので、ELB を入れている意味がありません。NFSv4 はインターネット経由でも使えることが設計目標となっていますので、実際にインターネットを経由させるかどうかは別として、このようなカラクリが間に入っていても動いてくれるのではないかという期待を持っています。検証が必要です。

※2014/10/14追記
NFS のエクスポートオプションで insecure を追加する必要がありました。 

※2014/10/24追記
ELB障害時の対策がクライアント側で一工夫必要でした。 ローカルで動かしている stoned 経由で接続し、ELB の IP アドレスが変更されたら stoned を再起動します。マウントのし直しや、クライアントアプリが操作をやり直す必要はないです。ELB が固定 VIP を持っていないことに対する汎用的な対策としても有効な対処法となります。

ELB を使ったこの構成で、 NFS サーバとしては問題があって採用見送りが確定したとしても、MySQL や PostgreSQL 等は問題なく動くと思うので、本稿もそれなりの価値は残るのではないかと思います。RDS がいいと思っている方はそちらを使ってください。要件と仕様が合致すればこれほど便利なものもありません。そうでない場合は、負けず劣らず、いろいろな制約に悩むこと請合いです。RDS を使うと決めたのであれば、要件を RDS の仕様に合わせると幸せになれます。

性能検証にも工数を割く必要があります。検証時の状況や運にも左右されるようですが、わざわざ性能の良いものを選ぶと却って性能が落ちる、という結果をいっぱい見ていますので、最適解を見つけるのに時間がかかります。このあたりの情報はばらつきが多いのでちょっと公開できそうにありません。公開してもすぐに変わるかもしれません。

無理やりにでも AWS 上で動かさなければならない、という状況もあるかと思います。その場合は、いろいろと裏技を駆使することになります。サポート不要、ライセンス的に許容ということであれば、Oracle RAC を AWS 上で動かすこともできますので、意味があるかどうかは別として、何が何でも不可能という話も少ないかもしれません。VPC という仮想ネットワーク内に、更に仮想ネットワークを構築してしまうとか、マルチキャストパケットを見つけたらユニキャストに置き換えて送出するとか、いろいろな裏技があります。本稿では、裏技は使っていません。API も直接は使っていません。管理コンソールからの操作のみです。API を使おうとすると「ロール」の使用を勧められますが、セキュリティホールとしか思えない事象をサポートに問い合わせてもこちらの要件に合わせるとたまたま「ロール」の使用を見合わせるべきケースという結論となり、仕様ということになります。セキュリティさえ気にしなくてよければ、技術者としては最高に面白い玩具として楽しめるのですが、残念です。

AWS の利用は、セキュリティにどれだけ工数をかけなければならないか、という点にも注意が必要です。IAM 設計はコンサルタントを付けたうえで経験者が数人で数か月はかかるとみておいた方がよいと思います。運用設計にも当然影響します。運用コストも上積みしておかなければなりません。AWS 上のサーバからインターネットへのアクセスは自由にしてよく、従業員に悪い人はいないし悪い人に付け込まれる従業員もいないし、ミスを犯す従業員もいないということでセキュリティ基準を緩めてよいのであれば、たいして工数をかけなくても大丈夫だと思います。IAM を使えば API 利用に関してソース IP 制限を設けることができるとのことですが、使える機能も制限がかかります。例えば、マーケットプレイスにある AMI (約1600) を利用してインスタンスを作成できなくなります。VM Import も使えません。Quick Start にある AMI (約20) は大丈夫でした。EC2 インスタンスの作成というスタート時点ですらこれほどの機能制限がかかってしまいます。他にもありますが、各自検証してみてください。現時点でいくら使っているのかを確認することすらできなくなります。仕様です。

AWS を利用する場合は、サポート契約なり、コンサルタント契約が必須です。私のような技術者ができないというと無能扱いとなりかねません。世界で売れている AWS で、この程度のことができないわけがない、という話になります。彼らがいえば仕様となり、主が万人の受け入れるべきルールとして定め給うたものとして許容されることになり丸く収まる確率が高いです。裏技を使う場合も、彼らに言われてから使うようにすべきです。AWS に関わる技術者の一番大切な仕事は、彼らから言質をとることかもしれません。技術者の仕事としてはこれほどつまらないものはないですが、已むを得ません。


この番外編の続きは、要望が多ければ書くかもしれません。
できましたら、本編の方もご参照頂ければ幸いです。

0 件のコメント:

コメントを投稿