今回は、AWSのcliユーティリティを使用して、複数のec2サーバに対してコマンドを実行します。例として、タグに"role=web"が指定された全てのec2インスタンスに対してuptimeコマンドを実行してみます。
最初にまとめ
結果だけ知りたい人のために結論を先に書くと、下記の手順で全てのWebサーバに対して同じコマンドが実行できます。
- webサーバのインスタンスにタグをつける(区別がつけばkey/valueは何でもよい)
key=role
value=web
-
AWSのAIMサービスで、API使用のためのキーを咲く絵師する。
-
aws-cliの接続情報を登録
aws configure
- jqコマンドをインストール
sudo yum install jq
- 下記のスクリプトを作って、実行すればOK
#! /bin/bash
roleName=web
lines=`aws ec2 describe-instances --filters "Name=tag:role,Values=$roleName" | jq --raw-output '.Reservations[].Instances[] | "\(.PrivateIpAddress)"'`
while read line
do
echo -e "\n**** $line ****"
ssh -n $line "uptime"
done <<END
$lines
END
上記の例ではすべてのwebサーバにuptimeコマンドを実行しているので、実行したいコマンドに書き換えてください。
スクリプトの説明
前述した結論に達するために試行錯誤した過程を説明します。各手順を細かく理解したい場合は、コマンドを順に入力しながら出力を見れば理解できるかと思います。
webサーバのインスタンスにタグをつける
今回は、ec2インスタンスの中から、全てのwebサーバに対して特定のコマンドを実行してみます。
どのインスタンスがWebサーバなのか区別するために、ec2のインスタンス一覧画面で各サーバにタグをつけます。
今回はkey=role, value=webのタグで、webサーバにタグをつけます。
Webサーバは3台用意しました。
上記のキャプチャではwebサーバ以外にbastionというサーバが有りますが、これは踏み台サーバとして用意したものなので、コマンド実行の対象外とします。
認証情報を取得する
awsのcliコマンドで、AWS環境の操作を行うときに必要な、認証情報をIAM(Identity and Access Management)で取得します。
権限は、初めてIAMを使用する場合でよくわからない場合、いったんAdministratorAccessを選んでおけば、全ての権限が付与できます。
※AdministratorAccessは、権限としては非常に強力なので、動作確認が終わったら適切な権限を付与しなおした方が良いです。
接続の情報を設定する
まずは、awsコマンドを使えるようにする必要があります。Amazon Linux AMIイメージからec2インスタンスを作れば、最初からawsコマンドは使用できます。手元のPCで操作したい場合は別途インストールしてください。
インストールが終わったら、aws configureコマンドでIAMの情報を登録します。Default region nameは、東京リージョンならap-northeast-1などになるかと思います。また、Default output formatは何も入力しなくても大丈夫です。
> aws configure
AWS Access Key ID [None]: AKIAZHXNYQUACLMKxxxx
AWS Secret Access Key [None]: WfMwPjLkTr87CMtQxgbk96EzCZzX7DqE8idxxxx
Default region name [None]: us-east-1
Default output format [None]:
awsコマンドを使ってみる
とりあえず以下のコマンドあたりを入力してみます。最後のaws ec2 describe-instancesコマンドの出力は非常に大量で見づらいですが、この後で必要な情報だけ抽出できるように加工します。
# awsコマンドのヘルプを見る
aws help
# awsコマンドのうち、EC2操作に関するヘルプを見る
aws ec2 help
# awsコマンドのでec2サーバの一覧を取得してみる
aws ec2 describe-instances
awsコマンドを実行時、Tabキーで補完が効くようにする
こちらは、任意ですが設定しておくと、awsコマンドを使う時に便利です。
下記のコマンドを入力し、タブ補完を有効にする(bashの場合)
> complete -C aws_completer aws
その後、aws ec2 descまで入力してTabキーを押す
↓
aws ec2 describe-まで補完される
"aws ec2 describe-"まで入力された時点で、再度Tabを押す
↓
候補がすべて表示される
> aws ec2 describe-
describe-account-attributes describe-instance-status describe-spot-fleet-instances
describe-addresses describe-internet-gateways describe-spot-fleet-request-history
describe-availability-zones describe-key-pairs describe-spot-fleet-requests
describe-bundle-tasks describe-moving-addresses describe-spot-instance-requests
describe-classic-link-instances describe-nat-gateways describe-spot-price-history
describe-conversion-tasks describe-network-acls describe-stale-security-groups
describe-customer-gateways describe-network-interface-attribute describe-subnets
describe-dhcp-options describe-network-interfaces describe-tags
describe-egress-only-internet-gateways describe-placement-groups describe-volume-attribute
describe-export-tasks describe-prefix-lists describe-volumes
describe-flow-logs describe-regions describe-volumes-modifications
describe-host-reservation-offerings describe-reserved-instances describe-volume-status
describe-host-reservations describe-reserved-instances-listings describe-vpc-attribute
describe-hosts describe-reserved-instances-modifications describe-vpc-classic-link
describe-iam-instance-profile-associations describe-reserved-instances-offerings describe-vpc-classic-link-dns-support
describe-identity-id-format describe-route-tables describe-vpc-endpoints
describe-id-format describe-scheduled-instance-availability describe-vpc-endpoint-services
describe-image-attribute describe-scheduled-instances describe-vpc-peering-connections
describe-images describe-security-group-references describe-vpcs
describe-import-image-tasks describe-security-groups describe-vpn-connections
describe-import-snapshot-tasks describe-snapshot-attribute describe-vpn-gateways
describe-instance-attribute describe-snapshots
describe-instances describe-spot-datafeed-subscription
describe-instancesでwebサーバの情報だけを取得する
ここまで準備ができたら、まずはwebサーバのPrivate IPアドレス一覧を取得します。説明のために少し遠回りしながら作業を進めていきます。
Webサーバの情報のみを抽出
まずは、--filtersオプションを使用して、タグにrole=webがついたインスタンスの情報を取得します。
出力は量が多いので省略しますが、結果を注意深く確認すると、role=webのタグが付いたインスタンスの情報だけが出ていることが分かります。
aws ec2 describe-instances --filters "Name=tag:role,Values=web"
jqコマンドでjsonファイルから必要な項目のみを抽出する
先ほどの"aws ec2 describe-instances"コマンドの結果はjson形式で詳細な情報が出力されるのは良いのですが、結果をシェルスクリプトなどで利用するには少し不便でです。このためjqコマンドを利用して出力されたjsonテキストから必要な情報だけを取得してファイルに出力させてみます。
今回は、以下のようにインスタンスID,IPアドレス,サーバ名をタブ区切りで別ファイルに出力させてみます。
i-07efb98ecd8d131w3 10.0.1.26 web_web03
i-0a2dcd5ab6d1722w1 10.0.1.42 web_web01
i-081836395251474w2 10.0.2.128 web_web02
jqコマンドのインストール
まずjqコマンド手元の環境にインストールします。amazon linuxの場合は下記のコマンドでインストールが可能です。
sudo yum install jq
jqコマンドで出力結果の加工を行う
ここからはjqコマンドの使い方説明を兼ねて、細かく説明していきます。
まずは試しにjqコマンドの動作確認もかねて、インスタンスIDの情報だけを取得してみます。
aws ec2 describe-instancesの出力内容をjqコマンドにパイプで繋げます。インスタンスIDの情報はjsonのReservations.Instances.InstanceIdノードにあるのですが、下記のコマンドで抽出可能です
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq '.Reservations[].Instances[] | {InstanceId}'
{
"InstanceId": "i-07efb98ecd8d131w3"
}
{
"InstanceId": "i-0a2dcd5ab6d1722w1"
}
{
"InstanceId": "i-081836395251474w2"
}
次に、InstanceIdに加えて、InstanceTypeとPrivateIpAddressも抽出してみます。
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq '.Reservations[].Instances[] | {InstanceId, InstanceType, PrivateIpAddress}'
{
"InstanceId": "i-07efb98ecd8d131w3",
"InstanceType": "t2.nano",
"PrivateIpAddress": "10.0.1.26"
}
{
"InstanceId": "i-0a2dcd5ab6d1722w1",
"InstanceType": "t2.nano",
"PrivateIpAddress": "10.0.1.42"
}
{
"InstanceId": "i-081836395251474w2",
"InstanceType": "t2.nano",
"PrivateIpAddress": "10.0.2.128"
}
さらに、インスタンスの名前も出力してみます。インスタンスの名前はkey=Nameのタグとして登録されています。
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq '.Reservations[].Instances[] | {InstanceId, InstanceType, PrivateIpAddress, Tags}'
{
"InstanceId": "i-07efb98ecd8d131w3",
"InstanceType": "t2.nano",
"PrivateIpAddress": "10.0.1.26",
"Tags": [
{
"Value": "web",
"Key": "role"
}
]
}
{
"InstanceId": "i-0a2dcd5ab6d1722w1",
"InstanceType": "t2.nano",
"PrivateIpAddress": "10.0.1.42",
"Tags": [
{
"Value": "web_web01",
"Key": "Name"
},
{
"Value": "web",
"Key": "role"
}
]
}
{
"InstanceId": "i-081836395251474w2",
"InstanceType": "t2.nano",
"PrivateIpAddress": "10.0.2.128",
"Tags": [
{
"Value": "web",
"Key": "role"
},
{
"Value": "web_web02",
"Key": "Name"
}
]
}
先ほどの出力は全てのタグがネストして出力されているため、タグのうちNameだけを出力させます。
(Nameのタグを指定していないインスタンス場合は、出力されないので注意が必要です)
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq '.Reservations[].Instances[] | {InstanceId, PrivateIpAddress, InstanceName: (.Tags[] | select(.Key=="Name").Value)}'
{
"InstanceId": "i-07efb98ecd8d131w3",
"PrivateIpAddress": "10.0.1.26",
"InstanceName": "web_web03"
}
{
"InstanceId": "i-0a2dcd5ab6d1722w1",
"PrivateIpAddress": "10.0.1.42",
"InstanceName": "web_web01"
}
{
"InstanceId": "i-081836395251474w2",
"PrivateIpAddress": "10.0.2.128",
"InstanceName": "web_web02"
}
次に、jsonをHashではなくarrayとして返すよう、変更します。コマンドが長くなるのでNameの出力はいったん抑止します
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq '.Reservations[].Instances[] | [.InstanceId, .PrivateIpAddress]'
[
"i-07efb98ecd8d131w3",
"10.0.1.26"
]
[
"i-0a2dcd5ab6d1722w1",
"10.0.1.42"
]
[
"i-081836395251474w2",
"10.0.2.128"
]
@csvフィルタを使って、csv形式で出力させてみます。
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq '.Reservations[].Instances[] | [.InstanceId, .PrivateIpAddress, (.Tags[] | select(.Key=="Name").Value)] | @csv'
"\"i-07efb98ecd8d131w3\",\"10.0.1.26\",\"web_web03\""
"\"i-0a2dcd5ab6d1722w1\",\"10.0.1.42\",\"web_web01\""
"\"i-081836395251474w2\",\"10.0.2.128\",\"web_web02\""
次は、tsvで出力するパターンです
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq '.Reservations[].Instances[] | "\(.InstanceId)\t\(.PrivateIpAddress)"'
"i-07efb98ecd8d131w3\t10.0.1.26"
"i-0a2dcd5ab6d1722w1\t10.0.1.42"
"i-081836395251474w2\t10.0.2.128"
全体がクォートされているので、--raw-outputを指定して、エスケープを抑止します。
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq --raw-output '.Reservations[].Instances[] | "\(.InstanceId)\t\(.PrivateIpAddress)"'
i-07efb98ecd8d131w3 10.0.1.26
i-0a2dcd5ab6d1722w1 10.0.1.42
i-081836395251474w2 10.0.2.128
再度、インスタンスのNameも出力する
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq --raw-output '.Reservations[].Instances[] | "\(.InstanceId)\t\(.PrivateIpAddress)\t\(.Tags[] | select(.Key=="Name").Value)"'
i-07efb98ecd8d131w3 10.0.1.26 web_web03
i-0a2dcd5ab6d1722w1 10.0.1.42 web_web01
i-081836395251474w2 10.0.2.128 web_web02
最後に、結果をリダイレクトして別ファイルに保存します。
> aws ec2 describe-instances --filters "Name=tag:role,Values=web" | jq --raw-output '.Reservations[].Instances[] | "\(.InstanceId)\t\(.PrivateIpAddress)\t\(.Tags[] | select(.Key=="Name").Value)"' > hostlist.txt
保存したサーバ一覧ファイル元にコマンドを実行する
ここまで来たら準備はほぼ完了です。シェルスクリプトでもpythonでも好きな言語で、出力結果を読み込んで処理を行えばよいです。
例1:awkスクリプトでの実行
今回は例として、まず、awkのスクリプトを用意します。
途中のcmd変数で代入しているように指定されたファイルの内容を元に、全サーバへssh IPアドレス uptime
のコマンドを実行しています。
またdryrunオプションをつけるとcmdの内容を実行せずに画面に出力されるので、何を実行するのかを事前に確認できます。
# RunAll.awk
{
# show target host
printf("\n*** [%s] %s *** \n", $3, $1);
# run script
cmd = sprintf( "ssh %s \"uptime\"", $2);
if (dryrun == "1") {
print cmd;
} else {
system(cmd);
}
}
上記のファイルを実行します。
まずはdryrunのオプション付きで実行しています。
cat hostlist.txt | awk -f RunAll.awk -v dryrun=1
*** [web_web03] i-07efb98ecd8d131w3 ***
ssh 10.0.1.26 "uptime"
*** [web_web01] i-0a2dcd5ab6d1722w1 ***
ssh 10.0.1.42 "uptime"
*** [web_web02] i-081836395251474w2 ***
ssh 10.0.2.128 "uptime"
実行するコマンドは大丈夫そうなので、今度はdryrunなしで本当に実行させます。
cat hostlist.txt | awk -f RunAll.awk
*** [web_web03] i-07efb98ecd8d131w3 ***
07:12:30 up 1:59, 4 users, load average: 0.00, 0.00, 0.00
*** [web_web01] i-0a2dcd5ab6d1722w1 ***
07:12:30 up 1:59, 1 users, load average: 0.02, 0.00, 0.00
*** [web_web02] i-081836395251474w2 ***
07:12:30 up 1:59, 1 users, load average: 0.24, 0.20, 0.30
例2:bashスクリプトでの実行
bashスクリプトにしたい場合は、以下のような感じになるかと思います。こちらの例ではホスト一覧をファイルに出力せずに、変数に格納しています。
#! /bin/bash
roleName=web
lines=`aws ec2 describe-instances --filters "Name=tag:role,Values=$roleName" | jq --raw-output '.Reservations[].Instances[] | "\(.PrivateIpAddress)"'`
while read line
do
echo -e "\n**** $line ****"
ssh -n $line "uptime"
done <<END
$lines
END
実行結果は以下の通りです
# 権限を付与
> chmod u+x ./RunAll.bash
# スクリプトの実行
> ./RunAll.bash
**** 10.0.1.26 ****
07:47:08 up 2:34, 2 users, load average: 0.09, 0.02, 0.01
**** 10.0.1.42 ****
07:47:08 up 2:34, 2 users, load average: 0.00, 0.00, 0.00
**** 10.0.2.128 ****
07:47:08 up 2:34, 2 users, load average: 0.00, 0.00, 0.00