EBSボリュームにアタッチされているEC2インスタンスと同じタグを付けて、「Cost Explorer」や「Resource Group」でのグループ化を助けるスクリプトを書いた
昨年AWSから発表された「Cost Explorer」と「Resource Group」の機能を活用するに当たって、こういう需要はそこそこあると思ったので、表題の通りの簡単なスクリプトのメモっておきます。
ただ、このスクリプトではプロセスフォークのコストよりも、AWS CLIの実行コストの方が全体に占める割合が圧倒的に大きいので、手早く書ける以上にあえてsetでリソースを節約する理由はそれほどないですね。。。*1
前々回エントリ「AWS CLIとjqを使って、AWSのELBボリュームがアタッチされているEC2インスタンス名を出力するワンライナーを書いた - 双六工場日誌」では、EC2インスタンスから「Name」タグを抜き出ししていますが、今回はAWSの「Cost Explorer」のサンプルにある「Cost Center」を抽出対象のタグとしたいと思います。また、インスタンスIDは結果の正しさを確認するためには必要ですが、タグ付けには不要なので、このスクリプトでは省略し、EBSのボリュームIDだけ抜き出します。
そして、それと同じタグをEBSにもつけて、Cost Explorerで、EBSも含めたEC2コストも見られるようにするという寸法です。また、EC2インスタンスにはすでに「Cost Center」のタグが付いていて、Cost Explorer上で、そのタグ別集計の機能が有効化されていることを前提とします。こちらの設定方法はAWSのドキュメントに書かれているので、ここでは割愛。
前置きが長くなりましたが、スクリプト本体は以下の通り。
#!/bin/bash set -ue tag_list=`aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | .BlockDeviceMappings[].Ebs.VolumeId + " " + ( .Tags[] | select(.Key == "Cost Center")|.Value )'` echo "$tag_list" | while read line do set -- $line aws ec2 create-tags --resources $1 --tags Key="Cost Center",Value=$2 done
たったこれだけですが、これでインスタンス作成時にタグを付けておけば、それと関連するボリュームをまとめてみたり、コストを集約したりできるようになります。「Resource Group」と一緒に「Tag Editor」機能も追加されていますが、EBSのボリュームを一つひとつつけるのはちょっと手間なんですよね。
上記では同期するタグは固定値にしましたが、ユースケースによっては引数にして、同期するタグを実行時に決めてもいいかなと思います。ただ、その場合は途中の"set --"でpositional parameterが書き換わってしまうので、スクリプトの最初で引数を別の変数に入れておく必要があるので、その点はご注意ください。
今日はこの辺で。
シェルスクリプトの中で1行ずつ変数を分割する際には、cutとかawkとか余計なプロセスを起動せずsetを使って分割した方が効率的
シェルスクリプトの中で、スペース区切りもしくはタブ区切りのレコードを扱うことがよくあると思います。
たとえば、前回のエントリ「AWS CLIとjqを使って、AWSのELBボリュームがアタッチされているEC2インスタンス名を出力するワンライナーを書いた - 双六工場日誌」のスクリプトの出力は以下のようになります。
i-ec56a9f5 vol-07d00601 servername
i-ec56a9f5 vol-8f550991 servername
このようなレコードの特定の列を取り出して、処理する際にどうするのが効率的か、というのがこのエントリのお題です。
非常に古い話題なので、昔からシェルスクリプトを書いている人には自明な話ではありますが、最近、シェルの標準機能の話を聞く機会がなく、失われつつある技術になってきている気がしているので、改めて確認ということで。
例として挙げたレコードから最初の1列目(インスタンスID)だけ取り出したい時、たとえばこんな方法があります。
line="i-ec56a9f5 vol-07d00601 servername" instance_id=`echo $line | cut -d' ' -f1` volome_id=`echo $line | cut -d' ' -f2` instance_name=`echo $line | cut -d' ' -f3`
こうするとフィールドの切り出しごとに新しく"cut"のプロセスが起動されるため、ワンライナーや実行回数の少ないバッチ処理用のスクリプトの場合はいいのですが、監視スクリプトのような一定の頻度で呼び出されるものは、その差がボディーブローのように効いてくることがあります。
このようにプロセスフォークのコストが無視できない場合は(下線部追記)、シェルスクリプト内で各フィールドの値を使う場合は以下のように"set"で分割しましょう。
line="i-ec56a9f5 vol-07d00601 servername" set -- $line instance_id=$1 volome_id=$2 instance_name=$3
ここで使っている$1、$2、$3といった変数は"the positional parameters"と言われるもので、シェルスクリプトでは、スクリプトや関数の引数を参照する際に使っていることが多いと思います。この変数は"set --"のあとに、スクリプトの引数のようなイメージで、空白区切りの文字列を入れるとシェル内で再設定することができます。(シェルスクリプトの引数として与えていた値は消去されます)
この2つで、どれだけの差が出るのか、以下のような簡単なベンチマークを取ってみました。実行環境は、僕のMBA 2012です。
続きを読むAWS CLIとjqを使って、AWSのELBボリュームがアタッチされているEC2インスタンス名を出力するワンライナーを書いた
件名の通りのAWSのELBボリュームがアタッチされているEC2インスタンス名を、AWS CLIで取得しようと思ったら、ちょっと手間取ったので、その結果をワンライナーにしたメモ。
結果はこちらです。こういうワンライナーになったのは、"join"コマンドを使ってみたかったという雑念も半分ぐらいありますね。
- ボリューム情報とインスタンス構成情報をjoin
join <(aws ec2 describe-volumes | jq -r '.Volumes[].Attachments[] | .InstanceId + " " + .VolumeId' | sort) <(aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | .InstanceId + " " + ( .Tags[] | select(.Key == "Name")|.Value )' | sort)
"join"コマンドを使わない別解はこちら。上は、ボリュームの情報から撮り始めてしまっていましたが、EC2の構成情報を見たら、そちらに必要な情報がすべて入っていたので、上のようにトリッキーなことをしなくても大丈夫でした。
- インスタンス構成情報利用
aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | .InstanceId + " " + .BlockDeviceMappings[].Ebs.VolumeId + " " + ( .Tags[] | select(.Key == "Name")|.Value )'
実行の際には、AWS CLIとjqが必要なので、事前にインストールして、AWS CLIからAWSの適切なリージョンにアクセスできるようにセットアップまで済ませておいてください。AWS CLI自体のセットアップ方法は、いろいろなブログに書いてあると思うので、ここでは割愛します。
結論はここまで。以下は、詳細説明です。
続きを読むAnsibleを使い出す前に押さえておきたかったディレクトリ構成のベストプラクティス
Ansibleのディレクトリ構成を決める際、プロダクション環境、ステージング環境、開発環境といった環境ごとに異なる設定を変更する方法でしっくり来るものを思いつかず、どうしたものかと悩んでいたのですが、今日見つけたブログ記事でそれもスッキリ解消したのでメモっておきます。
結論
まず結論を。プロダクション環境、ステージング環境、開発環境といった環境ごとに異なる設定する場合は、以下のように対応するのが良さそうです。
- ディレクトリ構成は、公式ドキュメントに従う。
Best Practices — Ansible Documentation
- プロダクション、ステージング、開発など、ステージごとの変数切替は以下のブログを参考に、"group_vars"を利用して行う。
- インベントリファイルの中に、"[production:children]"のようなグループすべてが属するグループを作ってしまい、そのグループに対応するファイル("group_vars/production"など)を作成する。
Multistage environments with Ansible – Ross Tuck
同じ悩みを持っていて、ここまでの内容とリンク先を見て納得した人は以上で、このエントリで伝えたかったことは終わりです。
悩んでいたこと
これだけではあっさりしすぎなので、悩んでいた内容も書いておきます。
Ansible公式のディレクトリ構成のベストプラクティス
Ansibleの公式ドキュメントの「Best Practice」には、以下のようなディレクトリ構成が掲載されています。
ポイントは、以下のような感じ。
- 役割ごとにPlaybookは1つ。”site.yml"は、サイト全体のPlaybookで、役割ごとのPlaybookをincludeするのみ
- プロダクション、ステージングなど、環境ごとにインベントリファイルを作成する
- タスクの中身は、roleに書き、役割ごとに用意したPlaybookで利用する。
- 変数は、group_vars, host_varsで設定(ただし、host_varsの利用はできるだけ避ける)
Best Practices — Ansible Documentation
続きを読む構想3年、ついに『なれる! SELinux〜エピソード 0〜』をコミケ3日目・西く09bで頒布します!!!
表題の通り、3年前にネタだけ出して、2年前にプロットだけ書きなぐって放置していた『なれる! SELinux』の本文を書いて、明日12/30の冬コミ3日目で出せることになったので、遅ればせながらお知らせです。
いつか時間ができたら出すとずっと言っていたので、出す出す詐欺にならなくてよかったですw
スペースは、西2ホール・西く09b「サーバ擬人化ユーザ会」です。
ストーリーは、基本的には以下のエントリにあるプロットの通りです。
なれる! SELinux のプロット(案)のメモ書き - 双六工場日誌
A5・8ページの薄い本なので、過度な期待はご遠慮くださいw
また、今回も「ちゃんおぷ」と合同ブースなので、「ちゃんおぷ」のドラマCDも一緒に頒布する予定です。ちゃんおぷの頒布物の情報はこちらへ → ちゃんおぷ!C87情報!
今回のコミケでは、このセットと一緒に、毎度おなじみになってきた社畜ストラップの新作も頒布します。頒布物の一覧は以下の通り。
コミケ3日目・西く09bでお待ちしています。
「ELBからの通信で408が多発する」件で、結局どうすべきか調べたのでまとめた
少し前にQiitaにこんなエントリが載っていました。問題としては、ELB配下に配置したApacheサーバで、レスポンスコード408で中身のないログが数秒おきに出続けるというものです。
AWS - ELBからの通信で408が多発する - Qiita
この現象は、自分のところでも出ていて、このエントリにあるように公式フォーラムを見ても解決せず、原因がわからず困っていました。このエントリを見て、自分でも検証してみましたが、事象の分析としては、このQiitaのエントリにある内容の通りみたいです。
TCPコネクションを張ったあと、HTTPデータが来ないため、mod_reqtimeoutがDoSと判定して、apacheがコネクションを終了させている。
この件をググって見つけたのですが、qpstudyのリーダーこと、@iaraさんも2年前にこれを取り上げていますね。
この問題に当たっている環境
上記のQiitaの情報を元に検証した結果、具体的にこの問題が出る環境は以下の通り。(CentOS6, 7, Ubuntu12.04 LTS, 14.04 LTSで確認)
Apache 2.2.15以降で、mod_reqtimeoutが有効化されているもの
- 2.2.15 からmod_reqtimeoutが入っているが、2.2系ではデフォルト無効のため、CentOS 6 のhttpdパッケージではこの問題に該当せず。
- ただし、Ubuntu 12.04 LTSのapache2パッケージには、以下の設定が入っているため、問題が発生。
- /etc/apache2/mods-available/reqtimeout.conf
- RequestReadTimeout header=20-40,minrate=500
- RequestReadTimeout body=10,minrate=500
- 参考: mod_reqtimeout - Apache HTTP Server Version 2.2
- 2.2.15 からmod_reqtimeoutが入っているが、2.2系ではデフォルト無効のため、CentOS 6 のhttpdパッケージではこの問題に該当せず。
Apache 2.4系
- Apache 2.4系では、mod_reqtimeoutがデフォルトで有効になっており、デフォルト値は「header=20-40,MinRate=500 body=20,MinRate=500」となっている。
- 参考: mod_reqtimeout - Apache HTTP Server Version 2.4
問題への対処法
対処法は、Qiitaのエントリにある通り、ELBの設定の「Description」タブから「Connection Settings: Idle Timeout」を「RequestReadTimeout header」の最低値よりも小さくすること。
「RequestReadTimeout header」は、HTTPヘッダ受信のタイムアウトで、モバイル通信で極端に回線状態が悪くなければ通常は数秒もかかりません。これはもともと「slowloris」という攻撃の対策で入ったモジュールで、攻撃を受けやすい環境であれば、20秒よりも短くすることも検討した方がいいパラメータです。そのため、こちらを長くしたり無効化するのはあまりおすすめできません。
一方、ELBの「Connection Settings: Idle Timeout」は、アイドル状態のコネクションを維持する時間の設定なので、パフォーマンスチューニングの範囲の設定変更になります。そもそも、Apache2.2、2.4は「KeepAliveTimeout」のデフォルト値は5秒で、そこで接続を切ってコネクション再接続のコストとサーバ側リソースのバランスをとっているので、こちらを特にいじっていない環境では、「Connection Settings: Idle Timeout」も「5秒」まで短くしても問題ないと思います。ただ、ELBとの接続なので、クライアントとサーバとの関係が1対1とないところが、少し悩ましいですね。。。
とはいえ、特に「KeepAliveTimeout」を長く設定している環境でなければ、「Connection Settings: Idle Timeout」を5秒〜20秒に設定するがFAだと思います。
これからCentOS 7が本格的に使われ始めると、これに当たる人が結構出てきそうですね。数年前からこの問題にぶつかっていた人は、Ubuntuユーザが多そうです。
以上。