aws-cliを使って全Webサーバに同じコマンドを実行する

カテゴリ: 未分類 | タグ:

今回は、AWSのcliユーティリティを使用して、複数のec2サーバに対してコマンドを実行します。例として、タグに"role=web"が指定された全てのec2インスタンスに対してuptimeコマンドを実行してみます。

最初にまとめ

結果だけ知りたい人のために結論を先に書くと、下記の手順で全てのWebサーバに対して同じコマンドが実行できます。

  1. webサーバのインスタンスにタグをつける(区別がつけばkey/valueは何でもよい)
key=role
value=web
  1. AWSのAIMサービスで、API使用のためのキーを咲く絵師する。

  2. aws-cliの接続情報を登録

aws configure
  1. jqコマンドをインストール
sudo yum install jq
  1. 下記のスクリプトを作って、実行すれば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
こちらもおススメ

コメントを残す

メールアドレスが公開されることはありません。