踏み台サーバ不要?EC2 Run Commandだけでインスタンス管理するためのシェルスクリプトを書いてみた
西澤です。EC2 Run Commandはとても便利なのですが、AWS Management Consoleからコマンド実行するのはイマイチだし、CUIでやるには覚えないといけないコマンドが多くて辛いなと思っていたので、シェルスクリプトで一発で実行できるようにしてみました。その内容について簡単にご紹介したいと思います。
EC2 Run Commandとは?
あまり有名なサービスでは無いので、SSM
やRun Command
を聞いたことが無い、という方は、まずこちらをご覧ください。概念的なところは、GUIも利用しながら操作した方が理解しやすいと思います。
- AWSの新サービス SSM を知っていますか? | Developers.IO
- 【新機能】EC2に任意のコマンドを実行できるボタン「Run Command」が追加(まだWindowsだけ) | Developers.IO
- 【新機能】EC2に任意のコマンドを実行できるボタン「Run Command」がついにLinuxにも追加 | Developers.IO
事前準備
EC2 Run Commandを利用するに当たって必要な設定は、下記公式ドキュメントにも記載されていますのでご確認ください。
- User Guide for Linux Instances#Amazon EC2 Run Command の前提条件 - Amazon Elastic Compute Cloud
- User Guide for Microsoft Windows Instances#Amazon EC2 Run Command の前提条件 - Amazon Elastic Compute Cloud
IAMロール準備
そのまま利用可能なAWS管理ポリシーが用意されていますので、こちらを利用しましょう。Run Command実行対象のEC2側には、AmazonEC2RoleforSSM
ポリシーを付与したIAMロールを割り当てておきましょう。
$ POLICY_ARN=arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM $ DEFUALT_POLICY_VERSION=$(aws iam list-policy-versions \ --policy-arn ${POLICY_ARN} \ --query "Versions[?IsDefaultVersion==\`true\`].VersionId" \ --output text) $ aws iam get-policy-version \ --policy-arn ${POLICY_ARN} \ --version-id ${DEFUALT_POLICY_VERSION} { "PolicyVersion": { "CreateDate": "2015-10-23T22:12:37Z", "VersionId": "v2", "Document": { "Version": "2012-10-17", "Statement": [ { "Action": [ "ssm:DescribeAssociation", "ssm:GetDocument", "ssm:ListAssociations", "ssm:UpdateAssociationStatus", "ssm:UpdateInstanceInformation" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "ec2messages:AcknowledgeMessage", "ec2messages:DeleteMessage", "ec2messages:FailMessage", "ec2messages:GetEndpoint", "ec2messages:GetMessages", "ec2messages:SendReply" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "cloudwatch:PutMetricData" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "ec2:DescribeInstanceStatus" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "ds:CreateComputer", "ds:DescribeDirectories" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:DescribeLogGroups", "logs:DescribeLogStreams", "logs:PutLogEvents" ], "Resource": "*", "Effect": "Allow" }, { "Action": [ "s3:PutObject", "s3:GetObject", "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts", "s3:ListBucketMultipartUploads" ], "Resource": "*", "Effect": "Allow" } ] }, "IsDefaultVersion": true } }
一方で、EC2 Run Commandを実行する担当者用としては、AmazonSSMFullAccess
を利用すれば良いでしょう。実質的にEC2インスタンスの管理者権限を有していることになりますので、取扱については十分に注意してください。
$ POLICY_ARN=arn:aws:iam::aws:policy/AmazonSSMFullAccess $ DEFUALT_POLICY_VERSION=$(aws iam list-policy-versions \ --policy-arn ${POLICY_ARN} \ --query "Versions[?IsDefaultVersion==\`true\`].VersionId" \ --output text) $ aws iam get-policy-version \ --policy-arn ${POLICY_ARN} \ --version-id ${DEFUALT_POLICY_VERSION} { "PolicyVersion": { "CreateDate": "2016-03-07T21:09:12Z", "VersionId": "v2", "Document": { "Version": "2012-10-17", "Statement": [ { "Action": [ "cloudwatch:PutMetricData", "ds:CreateComputer", "ds:DescribeDirectories", "ec2:DescribeInstanceStatus", "logs:*", "ssm:*", "ec2messages:*" ], "Resource": "*", "Effect": "Allow" } ] }, "IsDefaultVersion": true } }
SSM Agentの準備
Linux環境はデフォルトでは、SSM Agentが導入されていません。ちょうど本日のリリースでAmazon Linuxではyum
による導入が可能になりましたので、こちらを利用すれば簡単ですね。Windowsはデフォルトで導入されているEC2Configサービスを利用して動作する為、エージェントの導入等は不要です。
利用するドキュメントの確認
Run Commandから利用可能なコマンド群は、Documentという名前で管理されています。今回は、任意のコマンドを実行できる環境を作りたかったので、下記の2つのドキュメントを利用することにしました。
- Windows用
- AWS-RunPowerShellScript
- Linux用
- AWS-RunShellScript
$ aws ssm list-documents \ --query "DocumentIdentifiers[?contains(Name,\`ShellScript\`)]" [ { "Owner": "Amazon", "Name": "AWS-RunPowerShellScript", "PlatformTypes": [ "Windows" ] }, { "Owner": "Amazon", "Name": "AWS-RunShellScript", "PlatformTypes": [ "Linux" ] } ]
$ DOCNAME=AWS-RunPowerShellScript $ aws ssm get-document \ --name ${DOCNAME} \ --query "Content" \ --output text { "schemaVersion":"1.2", "description":"Run a PowerShell script or specify the paths to scripts to run.", "parameters":{ "commands":{ "type":"StringList", "description":"(Required) Specify the commands to run or the paths to existing scripts on the instance.", "minItems":1, "displayType":"textarea" }, "workingDirectory":{ "type":"String", "default":"", "description":"(Optional) The path to the working directory on your instance.", "maxChars":4096 }, "executionTimeout":{ "type":"String", "default":"3600", "description":"(Optional) The time in seconds for a command to be completed before it is considered to have failed. Default is 3600 (1 hour). Maximum is 28800 (8 hours).", "allowedPattern":"([1-9][0-9]{0,3})|(1[0-9]{1,4})|(2[0-7][0-9]{1,3})|(28[0-7][0-9]{1,2})|(28800)" } }, "runtimeConfig":{ "aws:runPowerShellScript":{ "properties":[ { "id":"0.aws:runPowerShellScript", "runCommand":"{{ commands }}", "workingDirectory":"{{ workingDirectory }}", "timeoutSeconds":"{{ executionTimeout }}" } ] } } }
$ DOCNAME=AWS-RunShellScript $ aws ssm get-document \ --name ${DOCNAME} \ --query "Content" \ --output text { "schemaVersion":"1.2", "description":"Run a shell script or specify the commands to run.", "parameters":{ "commands":{ "type":"StringList", "description":"(Required) Specify a shell script or a command to run.", "minItems":1, "displayType":"textarea" }, "workingDirectory":{ "type":"String", "default":"", "description":"(Optional) The path to the working directory on your instance.", "maxChars":4096 }, "executionTimeout":{ "type":"String", "default":"3600", "description":"(Optional) The time in seconds for a command to complete before it is considered to have failed. Default is 3600 (1 hour). Maximum is 28800 (8 hours).", "allowedPattern":"([1-9][0-9]{0,3})|(1[0-9]{1,4})|(2[0-7][0-9]{1,3})|(28[0-7][0-9]{1,2})|(28800)" } }, "runtimeConfig":{ "aws:runShellScript":{ "properties":[ { "id":"0.aws:runShellScript", "runCommand":"{{ commands }}", "workingDirectory":"{{ workingDirectory }}", "timeoutSeconds":"{{ executionTimeout }}" } ] } } }
SSM Agentとの疎通確認
下記コマンドでSSM Agentとの疎通確認を行うことができます。IAMロールが適切に割り当てられ、SSM Agentがインターネットと通信できる状態となっていれば、PingStatus
がOnline
となっているはずです。
$ aws ssm describe-instance-information { "InstanceInformationList": [ { "IsLatestVersion": false, "ComputerName": "ip-10-200-1-103.us-west-2.compute.internal\n", "PingStatus": "Online", "InstanceId": "i-6445c27c", "IPAddress": "10.200.1.103", "ResourceType": "EC2Instance", "AgentVersion": "1.2.290.0", "PlatformVersion": "2016.09", "PlatformName": "Amazon Linux AMI", "PlatformType": "Linux", "LastPingDateTime": 1475043780.582 }, { "IsLatestVersion": false, "ComputerName": "ip-10-200-1-36.us-west-2.compute.internal\n", "PingStatus": "ConnectionLost", "InstanceId": "i-8e46c196", "IPAddress": "10.200.1.36", "ResourceType": "EC2Instance", "AgentVersion": "1.2.290.0", "PlatformVersion": "2016.09", "PlatformName": "Amazon Linux AMI", "PlatformType": "Linux", "LastPingDateTime": 1475039485.941 }, { "IsLatestVersion": true, "ComputerName": "WIN-HVTPS1N9GQA.WORKGROUP", "PingStatus": "Online", "InstanceId": "i-e20b8cfa", "ResourceType": "EC2Instance", "AgentVersion": "3.19.1153", "PlatformVersion": "6.3.9600", "PlatformName": "Windows Server 2012 R2 Standard", "PlatformType": "Windows", "LastPingDateTime": 1475043638.601 } ] }
EC2 Run Commandだけでインスタンス管理するシェルスクリプト
細かいところは割愛しますが、下記のような仕様としました。自己責任でご利用ください。おかしな点があればご指摘いただけると嬉しいです。
- 実行対象インスタンスのNameタグと実行したいコマンドを引数で指定
- 実行対象は1台のみ(
send-command
コマンド自体は複数台に同時実行が可能) - 正常動作時はなるべく余計な情報を出力しない
- タイムアウトは30秒(時間のかかる処理は想定しない)
- 実行するコマンド内に記号がある場合の動作は全く考慮していません
#!/bin/bash # Check Arguments if [ $# -lt 2 ]; then echo "usage: $0 [EC2NAME] \"[COMMAND]\"" exit 1 fi # Set Variables EC2NAME="$1" COMMAND="$2" # Check EC2 Instance ID EC2ID=$(aws ec2 describe-instances \ --query "Reservations[].Instances[?Tags[?Key==\`Name\`].Value|[0]==\`${EC2NAME}\`].[InstanceId]" \ --output text) NUMBER=$(echo ${EC2ID} | wc -w) if [ ${NUMBER} -ge 2 ]; then echo "Two or more EC2 Instances with the name \"${EC2NAME}\" are found." exit 1 fi if [ "${EC2ID}" = "" ]; then echo "EC2:\"${EC2NAME}\" is not found." exit 1 fi echo "### InstanceID: ${EC2ID}" # Check SSM Agent Status PINGSTATUS=$(aws ssm describe-instance-information \ --query "InstanceInformationList[?InstanceId==\`${EC2ID}\`].PingStatus" \ --output text) if [ "${PINGSTATUS}" != "Online" ]; then echo "SSM Agent on EC2:\"${EC2NAME}\" is not Online." exit 1 fi # Check Platform Type PLATFORM_TYPE=$(aws ssm describe-instance-information \ --query "InstanceInformationList[?InstanceId==\`${EC2ID}\`].PlatformType" \ --output text) echo "### PlatformType: ${PLATFORM_TYPE}" # Exec Command TIMEOUT=30 if [ "${PLATFORM_TYPE}" = "Linux" ]; then DOCNAME=AWS-RunShellScript elif [ "${PLATFORM_TYPE}" = "Windows" ]; then DOCNAME=AWS-RunPowerShellScript else echo "PlatformType:\"${PLATFORM_TYPE}\" is not supported." exit 1 fi echo "### DocumentName: ${DOCNAME}" echo "### ExecCommand: \"${COMMAND}\"" COMMAND_ID=$(aws ssm send-command \ --instance-ids ${EC2ID} \ --document-name ${DOCNAME} \ --timeout-seconds ${TIMEOUT} \ --parameters commands="${COMMAND}",executionTimeout="${TIMEOUT}" \ --query "Command.CommandId" \ --output text) # Get Command Result while : do RESULT=$(aws ssm list-command-invocations \ --command-id ${COMMAND_ID} \ --detail \ --query "CommandInvocations[].[Status]" \ --output text) if [ "${RESULT}" = "Pending" -o "${RESULT}" = "InProgress" ]; then echo "(Exec Status is now \"${RESULT}\". Waiting...)" sleep 5 else echo "=====================================================================" break fi done # Get Command Result if [ "${RESULT}" = "Success" ]; then aws ssm list-command-invocations \ --command-id ${COMMAND_ID} \ --detail \ --query "CommandInvocations[].CommandPlugins[].Output" \ --output text else echo "Command Failed. Check the output below." aws ssm list-command-invocations \ --command-id ${COMMAND_ID} \ --detail \ --query "CommandInvocations[]" fi exit 0
下記は実行例です。敢えて出力は少なめにしているので、より詳細な情報が必要であればAWS Management Consoleからも確認してみてください。
$ ./ssm_run_command.sh usage: ./ssm_run_command.sh "[EC2NAME]" "[COMMAND]" $ ./ssm_run_command.sh ssmtest4 "uname -n" EC2:"ssmtest4" is not found. $ ./ssm_run_command.sh ssmtest2 "pwd" ### InstanceID: i-8e46c196 SSM Agent on EC2:"ssmtest2" is not Online. $ ./ssm_run_command.sh ssmtest1 "hostname;date" ### InstanceID: i-6445c27c ### PlatformType: Linux ### DocumentName: AWS-RunShellScript ### ExecCommand: "hostname;date" ===================================================================== ip-10-200-1-103 Wed Sep 28 05:42:15 UTC 2016 $ ./ssm_run_command.sh ssmtest3 "hostname;Get-Date" ### InstanceID: i-e20b8cfa ### PlatformType: Windows ### DocumentName: AWS-RunPowerShellScript ### ExecCommand: "hostname;Get-Date" (Exec Status is now "Pending". Waiting...) (Exec Status is now "InProgress". Waiting...) ===================================================================== WIN-HVTPS1N9GQA 2016年9月28日 5:41:50
まとめ
今回ご紹介したスクリプトをベースにして、用途に合わせた形で作り変えてご活用いただけたら嬉しいです。繰り返しですが、自己責任でのご利用をお願いします。