Yakstは、海外の役立つブログ記事などを人力で翻訳して公開するプロジェクトです。
9年弱前投稿 修正あり

Engineering at Monsanto: AWS上でのetcdのクラスタリング

バイオ化学メーカーのモンサントはAWS上にetcdのクラスタを展開している。興味があったので読んでみた。

原文
etcd Clustering in AWS - Engineering at Monsanto (English)
翻訳依頼者
Db2247a5b4226e1f6f74a9bd87810090
翻訳者
Db2247a5b4226e1f6f74a9bd87810090 Hiroaki Sano
原著者への翻訳報告
未報告


概要

数ヶ月の間、我々のチームはAWS上で自動化されたDockerコンテナを構築することにフォーカスしている。我々はCoreOSを基盤として使おうと決めた。CoreOSはコンテナをコアとした軽量なOSだ。 各マシンのコンテナのスケジューリングとしてfleetを使っている。fleetはホストがターミネートされたとしてもコンテナを稼働させ続ける。 CoreOSとfleetはクラスタ内のすべてのマシンと、稼働しているコンテナの状態を共有する必要がある。 これにはetcdが効果的だ。etcdはクラスタの情報と共通のコンフィグを保存する分散キーバリューストアとして使われる。 巨大なプロダクション環境にて、etcdは3つもしくは5つのホストで稼働するよう設計されている。

図

Bootstrapの問題

etcdはクラスタを形成するのにbootstrapを必要とする。これはいくつかの方法によって成し遂げられる。最初は我々はetcdのディスカバリサービスを使った。しかしAWSのオートスケーリンググループを使った際、奇妙な挙動を発見した。すなわちサービスがゴーストIPを返却した。加えて、ディスカバリサービスは、クラスタに追加する/はずすbootstrap後の問題をケアしてくれない。結局は、我々は外部システムの依存性を軽減するために静的な手法を選んだ。

我々の最初のアプローチはetcd 0.4を使い、CloudFormationで3つの専用のEC2インスタンスを作成することだった。 次のコードは、cloud-configを使うことによって、マシンのIPにアクセスできるようにするものだ。

coreos:
  etcd:
     addr: localhost:4001    
     peer-addr: localhost:7001
     peers: $ip_from_this_machine$:7001,$ip_from_other_machine$:7001,$ip_from_another_machine$:7001

このアプローチは次のような多少の欠点があるが十分に機能する。

ロバストネス

etcdはインフラにとって非常に重要で特別な扱いを必要とする。我々は固定IPを付与し、CloudWatchのアラームをセットし、さらに別の監視も施していた。私の同僚であるPhil CryerはPets vs Cattleの概念を守っていて、特にAWSのような環境(アマゾンはEC2インスタンスが壊れない保証はしない)において、どのようにしてこの"特殊な"環境の問題を回避するべきかに挑戦していた。

CloudFormationのアップデート

時折、我々はインフラの変更を必要とした。コンフィグレーションの変更のためにCloudFormationを使った。etcdのマシンに変化がなければ、AWSは変更を適用するためにすべて同時にリブートしてしまうだろう。もしこれが発生したら、クラスタは使えなくなり再構築するはめになるだろう。

解決策

見込みのある解決法を考案するにあたり、ワーカーマシンとして使っていた機能から、AWSのオートスケーリングに変更した。このケースでは、我々はetcdサーバーの数を増やしたり減らしたりしたくない。たとえホストが故障したとしてもクラスタサイズは固定はしたい。しかしながらこれはetcdのbootstrapをどのように調整するかを理解する新たなチャレンジだ。

この時期になるとCoreOSはetcd2をアルファリリースチャンネルにリリースした。この新しいバージョンにはブートストラップの変更と、動的なコンフィグ変更ができる機能が盛り込まれていた。それらはオートスケーリンググループでのクラスタのメンバー管理に柔軟さをもたらした。

ブートストラップ

我々の最初の関心はブートストラップの自動化だった。我々はもはや以前のアプローチのようにIPを固定していなかったので、他のメカニズムを必要としていた。幸運にもAmazonのCLIは我々が必要としているツールだった。しかしながら、我々はCoreOSを使っているので、我々はCoreOS上にCLIをインストールできず、それを成すためのコンテナの作成が必要だった。 次の関心はCLIを使うための認証情報をどのように取得するのかということだった。ここで、我々はIAMを使い、ec2:Describeとautoscaling:Describeのリードオンリー権限をサーバーに付与した。これらのツールによって、必要としていたものを簡単なbashスクリプトでつくることができた。

ec2_instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)

ec2_instance_ip=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4)

asg_name=$(aws autoscaling describe-auto-scaling-groups --region us-east-1 \
  | jq --raw-output ".[] map(select(.Instances[].InstanceId | contains(\"$ec2_instance_id\"))) | .[].AutoScalingGroupName")

etcd_peer_urls=$(aws ec2 describe-instances --region us-east-1 --instance-ids \
$(aws autoscaling describe-auto-scaling-groups --region us-east-1 --auto-scaling-group-name $asg_name | jq .AutoScalingGroups[0].Instances[].InstanceId | xargs) \ | jq -r '.Reservations[].Instances | map("http://" + .NetworkInterfaces[].PrivateIpAddress + ":2379")[]')

このスクリプトはAWSのメタデータエンドポイントから、EC2のインスタンスIDとIPを検索するところから開始する。cliとjqを使って我々はオートスケーリンググループに属しているインスタンスの名前を探すことができる。ここから、我々はオートスケーリンググループのマシンのIPを見つけることができる。 それから我々はこの情報を次のファイルに書き出す。

ETCD_INITIAL_CLUSTER_STATE=new
ETCD_NAME=$ec2_instance_id
ETCD_INITIAL_CLUSTER="$etcd_initial_cluster"

そして起動するときにetcdにこの情報をロードするように指示する。これらの変更で、ハードコーディングなしにetcdをオートスケーリンググループから起動できる。

クラスタのメンバーの維持

etcdは、マシンがクラスタから抜ける前に削除され、後に再ジョインすることを期待している。我々は可用性に問題を起こさずに、マシンを殺し、完全に置き換えられるようにビルドしたい。もちろん多少のチャレンジがあった...

新規の検出 vs 既存のクラスタ

最初に我々が発見したものは、新しいクラスタだろうが既存のクラスタだろうが、etcdが正確に動作するようetcdに明確に伝える必要があるということだ。ちょっとしたトライアルアンドエラーのあとに我々はたどり着いた:

etcd_existing_peer_urls=
etcd_existing_peer_names=
etcd_good_member_url=

for url in $etcd_peer_urls; do
    case "$url" in
        *$ec2_instance_ip*) continue;;
    esac

    etcd_members=$(curl -f -s $url/v2/members)

    if [[ $? == 0 && $etcd_members ]]; then
        etcd_good_member_url="$url"
        echo "etcd_members=$etcd_members"
        etcd_existing_peer_urls=$(echo "$etcd_members" | jq --raw-output .[][].peerURLs[0])
        etcd_existing_peer_names=$(echo "$etcd_members" | jq --raw-output .[][].name)
        break
    fi
done

if [[ $etcd_existing_peer_urls && $etcd_existing_peer_names != *"$ec2_instance_id"* ]]; then
    echo "joining existing cluster"
else
    echo "creating new cluster"
fi

このアイデアは、etcdが正確に動作しているなら、オートスケーリンググループのそれぞれのマシンに接続を試みる。もしレスポンスがなければそれは新規のクラスタである。メンバのリストとともにレスポンスが返れば、ブートストラップ状態になる。起動する最初のマシンはオートスケーリンググループ内の他のマシンとそのインスタンスIDやIPを知っているということを覚えておこう。インスタンスIDがリストにあれば、単にクラスタへのジョインが遅れているだけでブートストラップの中であると判断する。

メンバの追加/削除

一度クラスタにジョインしたら、新しいメンバをクラスタに加えるステップになる。

# eject bad members from cluster
peer_regexp=$(echo "$etcd_peer_urls" | sed 's/^.*http:\/\/\([0-9.]*\):[0-9]*.*$/contains(\\"\1\\")/' | xargs | sed 's/ */ or /g')

bad_peer=$(echo "$etcd_members" | jq --raw-output ".[] | map(select(.peerURLs[] | $peer_regexp | not )) | .[].id")

if [[ $bad_peer ]]; then
    for bp in $bad_peer; do
        echo "removing bad peer $bp"
        curl -f -s "$etcd_good_member_url/v2/members/$bp" -XDELETE
    done
fi

etcd_initial_cluster=$(curl -s -f "$etcd_good_member_url/v2/members" | jq --raw-output '.[] | map(.name + "=" + .peerURLs[0]) | .[]' | xargs | sed 's/ */,/g')$(echo ",$ec2_instance_id=http://${ec2_instance_ip}:2380")

echo "adding instance ID $ec2_instance_id with IP $ec2_instance_ip"
curl -f -s -XPOST "$etcd_good_member_url/v2/members" -H "Content-Type: application/json" -d "{\"peerURLs\": [\"http://$ec2_instance_ip:2380\"], \"name\": \"$ec2_instance_id\"}"

最初のステップではクラスタのどのメンバーがターミネートされたかを検出しようとする。これはetcdによって通知されるメンバーのリストと、オートスケーリンググループのマシンのリストを比較することによって推測することができる。一度、ダメなホストを見つけると、そのマシンを削除するためにクラスタ内の正常に稼働しているメンバの一つにRESTなリクエストを送る。その後、別のRESTリクエストを送ることによって、etcdが起動する前に新しいマシンをクラスタに追加することができる。

etcdのバグ

この時点で、我々はクラスタにマシンを追加/削除する良い知見を得たと思っていた。そしてスケールするかどうかのテストを開始した。一つの死亡ノードがある状態で、死んだノードの削除を試すまでは、マシンをターミネートするときは常にクラスタの状態は良好だった。 API経由で死んだノードを削除したあと、クラスタの状態が悪くなり、書き込みができなくなってしまった。 この状態の数分後、クラスタの問題は治まり、良好になった。一度良好になれば、新しいマシンの追加とクラスタへの書き込みが可能になった。私はCoreOSチームに、死亡ノードの削除後に数分間クラスタが不安定になる件のバグレポートを送った。するとすぐに返信と解決案が返ってきた(CoreOSチームはすばらしい!)。私は新しいビルドをためし、確かな解決策であることを報告した。このbugfixはマージされ、1〜2週間後にリリースされるようだ。

結論

AWSオートスケーリンググループにてetcdのクラスタ構築を完全自動化することができた。 GitHub/DockerHubに公開したので広いコミュニティで使ってほしい。issue、コントリビュート、コメント、質問をお待ちしております。

次の記事
InfluxDB専用のメトリクスコレクタ「Telegraf」(InfluxDB Blogより)
前の記事
SQLにおけるIPアドレスの比較

Feed small 記事フィード

新着記事Twitterアカウント