(レポート) DEV301: AWS CLIによる自動化 #reinvent

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。

みんな大好きAWS CLIのセッション資料「(DEV301) Automating AWS with the AWS CLI」を読んでみました。

AWS CLI

AWS CLI(Command Line Interface)は、Amazon Linuxでは標準でインストールされているAWSの各種サービスを操作することの出来るコマンドです(他のOSはインストーラなどが提供されている)。 コマンド単体ではインタラクティブにひとつの操作しかできませんが、シェルスクリプトを作成することでかなりの作業を自動化することができます。 シェルスクリプトと親和性が高いため、簡単なタスクであればシェルスクリプトとして作成し、cronなどで運用すると便利です。

良いシェルスクリプト

良いシェルスクリプトとして次のような条件が提案されていました。

  • 100 < SLOC (Source Lines Of Code)
  • Commands in sequenc(逐一処理)
  • Piping input/output(パイプによるイン/アウト)
  • Simple domain logic(シンプルなドメインのロジック)

シェルスクリプトはオブジェクト指向プログラムでも関数型プログラムでもありません。 なるべく小さなサイズで、小さなドメインを扱い、逐一処理を心がけましょう。 また、他のシェルスクリプトやコマンドと親和性を高くするためには、インプット/アウトプットでパイプを意識すると良いと提案しています。

標準パターン

それでは、AWS CLIを利用したシェルスクリプトで代表的なパターンを紹介しましょう。 なお、前提条件として、AWS CLI, bash, AWS についてある程度の知識が必要となります。

結果から単一パラメータを取得する

最初のパターンは、AWS CLIの結果から単一パラメータを取得するパターンです。 AWS CLIの実行結果を、次のコマンドの入力の一部にマッピングします。 この時、--query--outputオプションが便利です。

例えば、特定のAMIから作成されて起動しているインスタンスIDを抽出したいとします。 ワンライナーが大好きであれば、次のように書くでしょう。

aws ec2 create-tags --tags Key=purpose,Value=dev --resources \
$(aws ec2 run-instances --image-id ami-12345 --query Instances[].InstanceId --output text) 

--queryオプションで出力対象となる項目を絞り込み、--outputオプションでJSONではなく標準テキストを指定しています。 その結果をec2 create-tagsの--resourcesオプションにそのまま渡しているわけです。

しかし、これでは、最初のクエリーでインスタンスIDが存在しなかった場合などのエラー処理(Error handling)がありません。 次のようにシェルスクリプトを改良することで、適切なエラー処理を行うことができます。

instance_ids=$(aws ec2 run-instances --image-id ami-12345 \
--query Instances[].InstanceId --output text) || errexit "Could not run instance" aws 
ec2 create-tags --tags Key=purpose,Value=dev --resources "$(instance_ids)"\
 || errexit "<errmsg>"

可読性も高いですね。 コマンドが実行に失敗した場合に、調査が不要なことは大切です。

AWS CLIの実行結果毎にAWS CLIを実行する

例えば、iamのユーザリストを取得し、それぞれについて削除処理を行いたいケースです。

誰もが思いつくのはforループを使うことでしょう。

for name in $(aws iam list-users --query "Users[].[UserName]" --output text); do 
  aws iam delete-user --user-name "$name"
done

この処理は逐一処理であり、誰にでも解りやすいシェルスクリプトです。 ところが、逐一iamのdelete-userコマンドを実行するため、実行完了までに長い時間を要することになります。 この対策としてxargsコマンドのパラレルオプションを使うことができます。

aws iam list-users --query "Users[].[UserName]" --output text | \
xargs -I {} -P 10 aws iam delete-user --user-name "{}"

xargsコマンドはコマンドの実行結果をパラメータとして渡せます({}部分がパラメータ)。 また、Pオプションで最大10ユーザの削除コマンドが並列実行されるため、逐一処理よりも高速に処理を完了できる可能性があります。

JSONは後からクエリ

例えば、ec2 describe-instanceは非常に大きなJSONを結果として返します。 その中でひとつの項目だけを使いたいのであれば、--outputオプションで事足ります。 しかし、あれもこれも使いたい場合は複数回のAWSコマンドを実行することになり、効率がよくありません。

そのような場合は、jqコマンドを活用しましょう。

instance=$(aws run-instance --image-id ami-1234 --query Instances[0]) 

query() { jp -u "$2" <<<"$1" } 

instance_id=$(query "$instance" InstanceId) state_name=$(query "$instance" State.Name) instance_name=$(query "$instance" "Tags[?Key=='Name'].Value | [0]")

AWSコマンドのアウトプットとして、JSON(--output json)を利用します。 アウトプットは変数か一時ファイルに保存し、jqコマンドで必要に応じたパラメータを抽出してください。

リソースの存在チェック

最後のパターンは、テンプレートとして利用できるリソースチェックです。 サンプルコードをご覧ください。

resource_exists() {
	# 1. Determine command to run with query
	local cmd 
	local num_matches 
	if [[ -z "$2" ]]; then
	  cmd="$1 --query length(*[0])" 
	else 
	  cmd="$1 --query $2" 
	fi 
	# 2. Run command and check errors 
	num_matches=$($command) 
	if [[ "$?" -ne 0 ]]; then 
	  echo "Could not check if resource exists, exiting." 
	  exit 2 
	fi
	# 3. Check length of query result
	if [[ "$num_matches" -gt 0 ]]; then 
	  # RC of 0 mean the resource exists, success. 
	  return 0 
	else 
	  return 1 
	fi
}

次のように、チェック用のスクリプトを文字列として、resource_existsファンクションに渡して利用します。

list_profiles="aws iam list-instance-profiles"
if resource_exists "$list_profiles" "InstanceProfiles[?InstanceProfileName=='dev-ec2-instance']" 
then 
  echo "Instance profile exists." 
else 
  echo "Missing IAM instance profile 'dev-ec2-instance'" 
  create_instance_profile 
fi

リソースの存在チェックのコードが関数にまとまっているので、本体のロジックの可読性が高くなっています。 実際に実行するコマンドは文字列として渡しているので、ストラテジパターン的な使い方になっています。 シェルスクリプトであれば、実行するロジックが文字列で渡せばストラテジパターンになるというのは目から鱗ですね。

まとめ

AWS CLIを使ったシェルスクリプトで自動化を行いましょう。

シェルスクリプトとして実行する場合に重要なのは、パラメータの扱いです。 結果から単一のパラメータを抽出することで、次のAWSコマンドの引数として利用できます。

また、シェルスクリプトとはいえど、エラー処理は可能な限り実装しておきましょう。 シェルスクリプトでは、実行する処理は文字列として渡せばいいので、存在チェックのような典型的なパターンはストラテジ関数として定義してしまうのも便利です。

以上、よいAWS CLI生活を!