Systems Managerのスクリプト実行でgithubとS3をサポートしました

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

ウィスキー、シガー、パイプをこよなく愛する、と書き出すと読まれない可能性もあるようですが気にしない事にした大栗です。

EC2の管理を行えるSystems ManagerでgithubやS3に配置したファイルをローカルにダウンロードして実行することが可能になりました。

AWS-RunRemoteScript ドキュメント

AWS-RunRemoteScriptドキュメントは、github(プライベートリポジトリを含む)とS3にあるファイルをダウンロードして、実行を行うSSMドキュメントです。

ドキュメントの詳細

AWS-RunRemoteScriptの概要をCLIで確認します。CreatedDateが 1508980336.016 なので 2017-10-26 01:12:16 UTCに作成されています。

$ aws ssm describe-document --name AWS-RunRemoteScript

{
    "Document": {
        "Status": "Active",
        "Hash": "80c872204542b86fd0f8e399413eaae27a4c371a15332228f07afd71188cbbb6",
        "Name": "AWS-RunRemoteScript",
        "Parameters": [
            {
                "Type": "String",
                "Name": "sourceType",
                "Description": "(Required) Specify the source type."
            },
            {
                "DefaultValue": "{}",
                "Type": "StringMap",
                "Name": "sourceInfo",
                "Description": "(Required) Specify the information required to access the resource from the source. If source type is GitHub, then you can specify any of the following: 'owner', 'repository', 'path', 'getOptions', 'tokenInfo'. If source type is S3, then you can specify 'path'."
            },
            {
                "DefaultValue": "",
                "Type": "String",
                "Name": "commandLine",
                "Description": "(Required) Specify the command line to be executed. The following formats of commands can be run: 'pythonMainFile.py argument1 argument2', 'ansible-playbook -i \"localhost,\" -c local example.yml'"
            },
            {
                "DefaultValue": "",
                "Type": "String",
                "Name": "workingDirectory",
                "Description": "(Optional) The path where the content will be downloaded and executed from on your instance."
            },
            {
                "DefaultValue": "3600",
                "Type": "String",
                "Name": "executionTimeout",
                "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)."
            }
        ],
        "Tags": [],
        "DocumentType": "Command",
        "PlatformTypes": [
            "Windows",
            "Linux"
        ],
        "DocumentVersion": "1",
        "HashType": "Sha256",
        "CreatedDate": 1508980336.016,
        "Owner": "Amazon",
        "SchemaVersion": "2.2",
        "DefaultVersion": "1",
        "LatestVersion": "1",
        "Description": "Execute scripts stored in a remote location. The following remote locations are currently supported: GitHub (public and private) and Amazon S3 (S3). The following script types are currently supported: #! support on Linux and file associations on Windows."
    }
}

次にドキュメントの中身を見ていきましょう。mainStepsを見るとaws:downloadContentプラグインでファイルをダウンロードして、Windowsの場合に'aws:runPowerShellScript'でPowerShellを実行、Linuxの場合にaws:runShellScriptでスクリプトを実行するという複合ドキュメントになっていることが分かります。

$ aws ssm get-document --name AWS-RunRemoteScript | jq '

{
  "schemaVersion": "2.2",
  "description": "Execute scripts stored in a remote location. The following remote locations are currently supported: GitHub (public and private) and Amazon S3 (S3). The following script types are currently supported: #! support on Linux and file associations on Windows.",
  "parameters": {
    "sourceType": {
      "description": "(Required) Specify the source type.",
      "type": "String",
      "allowedValues": [
        "GitHub",
        "S3"
      ]
    },
    "sourceInfo": {
      "description": "(Required) Specify the information required to access the resource from the source. If source type is GitHub, then you can specify any of the following: 'owner', 'repository', 'path', 'getOptions', 'tokenInfo'. If source type is S3, then you can specify 'path'.",
      "type": "StringMap",
      "displayType": "textarea",
      "default": {}
    },
    "commandLine": {
      "description": "(Required) Specify the command line to be executed. The following formats of commands can be run: 'pythonMainFile.py argument1 argument2', 'ansible-playbook -i \"localhost,\" -c local example.yml'",
      "type": "String",
      "default": ""
    },
    "workingDirectory": {
      "type": "String",
      "default": "",
      "description": "(Optional) The path where the content will be downloaded and executed from on your instance.",
      "maxChars": 4096
    },
    "executionTimeout": {
      "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).",
      "type": "String",
      "default": "3600",
      "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)"
    }
  },
  "mainSteps": [
    {
      "action": "aws:downloadContent",
      "name": "downloadContent",
      "inputs": {
        "sourceType": "{{ sourceType }}",
        "sourceInfo": "{{ sourceInfo }}",
        "destinationPath": "{{ workingDirectory }}"
      }
    },
    {
      "precondition": {
        "StringEquals": [
          "platformType",
          "Windows"
        ]
      },
      "action": "aws:runPowerShellScript",
      "name": "runPowerShellScript",
      "inputs": {
        "runCommand": [
          "",
          "$directory = Convert-Path .",
          "$env:PATH += \";$directory\"",
          " {{ commandLine }}",
          "if ($?) {",
          "    exit $LASTEXITCODE",
          "} else {",
          "    exit 255",
          "}",
          ""
        ],
        "workingDirectory": "{{ workingDirectory }}",
        "timeoutSeconds": "{{ executionTimeout }}"
      }
    },
    {
      "precondition": {
        "StringEquals": [
          "platformType",
          "Linux"
        ]
      },
      "action": "aws:runShellScript",
      "name": "runShellScript",
      "inputs": {
        "runCommand": [
          "",
          "directory=$(pwd)",
          "export PATH=$PATH:$directory",
          " {{ commandLine }} ",
          ""
        ],
        "workingDirectory": "{{ workingDirectory }}",
        "timeoutSeconds": "{{ executionTimeout }}"
      }
    }
  ]
}

実行時のパラメータ

スクリプトのダウンロード元によって、実行時のパラメータが異なります。

パラメータ 必須/任意 内容 備考
sourceType 必須 "GitHub"または"S3"
sourceInfo 必須 ダウンロード元情報 詳細は後述
commandLine 必須 スクリプト名 実行するスクリプト名
workingDirectory 任意 実行時ディレクトリ スクリプトを実行する時のディレクトリ
executionTimeout 任意 デフォルトは36oo秒。1〜28800。 実行タイムアウトの秒数

sourceInfoaws:downloadContentプラグインで使用するパラメータなので、ドキュメントを確認します。

aws:downloadContent

項目 S3 github
(Public)
github
(Private)
備考
owner 不要 必須 必須 githubのオーナー。
repository 不要 必須 必須 githubのリポジトリ名。
path 必須 必須 必須 ダウンロードするファイルのパス。
getOptions 不要 任意 任意 ダウンロード元のブランチやコミットIDを指定します。
tokenInfo 不要 任意 必須 githubのアクセストークンを指定します。

例として、以下のようになります。

{
  "owner": "maroon1st",
  "repository": "blog-test-20171030-private",
  "path": "test_guthub_private.sh",
  "tokenInfo": "{{ssm-secure:github-token}}"
}

試してみる

事前準備

ここではSSM Agentがデフォルトで入っているAmazon Linux AMI 2017.09.0を対象としています。設定内容としては、以下の点に注意して下さい。

  • インターネットへのアクセス(SSMのエンドポイントへのアクセス)が可能
  • IAM RoleでAmazonEC2RoleforSSMポリシーとAWSKeyManagementServicePowerUserポリシーを設定する

ただし、SSM Agentのデフォルトバージョンが2.1.4.0と少し古いので最新にします。EC2のコンソールでコマンドの実行メニューからコマンドを実行をクリックします。

EC2_Management_Console

ドキュメントでAWS-UpdateSSMAgentを選択して、対象インスタンスを選びRunをクリックします。

EC2_Management_Console

少し経つとコマンドが成功します。

EC2_Management_Console

出力でログを見るとバージョンが2.1.4.0から最新の2.2.64.0にアップデートしたことが分かります。

EC2_Management_Console

S3のスクリプト

サンプルとして、こんな感じのスクリプトをS3に配置します。

test_s3.sh

#!/bin/bash

date

echo RunScript From S3 New Version !

curl -s -w '\n' http://169.254.169.254/latest/meta-data/local-hostname
curl -s -w '\n' http://169.254.169.254/latest/meta-data/instance-id
curl -s -w '\n' http://169.254.169.254/latest/meta-data/placement/availability-zone

S3のリンクを控えておきます。

S3_Management_Console

EC2のコンソールでコマンドの実行メニューから、またコマンドを実行をクリックして、ドキュメントでAWS-RunRemoteScriptを選択して、対象インスタンスを選びます。

EC2_Management_Console

Source TypeをS3にして、Source Infoでpathに先程のS3のリンクURLを設定します。Command Lineではスクリプト名を指定します。必要な項目を入力したらRunをクリックします。

EC2_Management_Console

このように成功します。downloadContentrunPowerShellScriptrunShellScriptの3個のプラグインが動いていることが分かります。

EC2_Management_Console

出力を表示させると以下のようになり、downloadContentでファイルをダウンロードして、runPowerShellScriptをスキップし、runShellScriptでスクリプトが実行されていることが分かります。

EC2_Management_Console

EC2_Management_Console

EC2_Management_Console

github(Public)のスクリプト

サンプルとして、githubにmaroon1st/blog-test-20171030というパブリックリポジトリを作成しました。

blog-test-20171030/test_guthub.sh

#!/bin/bash

date

echo RunScript From github !

curl -s -w '\n' http://169.254.169.254/latest/meta-data/local-hostname
curl -s -w '\n' http://169.254.169.254/latest/meta-data/instance-id
curl -s -w '\n' http://169.254.169.254/latest/meta-data/placement/availability-zone

このリポジトリを使って試します。

今までと同様にEC2のコンソールでコマンドの実行メニューから、またコマンドを実行をクリックして、ドキュメントでAWS-RunRemoteScriptを選択して、対象インスタンスを選びます。

EC2_Management_Console

Source Typeをgithubにします。Source Infoは以下のように入力します。

{
  "owner": "maroon1st",
  "repository": "blog-test-20171030",
  "path": "test_guthub.sh"
}

Command Lineではスクリプト名を指定します。必要な項目を入力したらRunをクリックします。

EC2_Management_Console

普通にスクリプトが実行されます。

EC2_Management_Console

github(Private)のスクリプト

サンプルとして、githubにmaroon1st/blog-test-20171030-privateというプライベートリポジトリを作成しました。

maroon1st_blog-test-20171030-private

実行するスクリプトはこんな感じです。

blog-test-20171030-private/test_guthub_private.sh

#!/bin/bash

date

echo RunScript From github private !

curl -s -w '\n' http://169.254.169.254/latest/meta-data/local-hostname
curl -s -w '\n' http://169.254.169.254/latest/meta-data/instance-id
curl -s -w '\n' http://169.254.169.254/latest/meta-data/placement/availability-zone

githubではSettings -> Developer settingsからaccess tokenを作成しておきます。Rum Commandでgithubのaccess tokenを指定する必要がありますが、access tokenは機密情報です。そのためEC2 Systems Managerパラメータストアで暗号化して保存します。

AWS KMS で EC2 Systems Managerパラメータストアを暗号化

EC2コンソールのパラメータストア -> パラメータの作成 をクリックします。

タイプ安全な文字列(SecureString)を選択します。そしてKMS キー IDではデフォルト以外のキーを選択します。デフォルトキーを選択するとKMSへのアクセス権限がなくても復号化できてしまうため意味がありません。access tokenをに入力してパラメータの作成をクリックします。なお、使用するKMS キーで設定しているIAM Roleをキーユーザーに設定しておくことを忘れないでください。

EC2_Management_Console

これでアクセスキーの登録が完了しました。

ここからは先程と同様にコマンドを実行をクリックして、ドキュメントでAWS-RunRemoteScriptを選択して、対象インスタンスを選びます。

Source Info以外はパブリックリポジトリと同様に入力します。Source Infoは以下のように入力します。tokenInfoにaccess tokenを設定するのですが、パラメータストアで安全な文字列にしているため{{ssm-secure:パラメータ名}}となる形式で設定します。ここでは{{ssm-secure:github-token}}となります。

{
  "owner": "maroon1st",
  "repository": "blog-test-20171030-private",
  "path": "test_guthub_private.sh",
  "tokenInfo": "{{ssm-secure:github-token}}"
}

EC2_Management_Console

実行結果を見ると、このようにスクリプトが実行されています。

EC2_Management_Console

さいごに

今回githubやS3に保存されているファイルを使って実行できるようになりました。

Amazon Linux 2017.09ではSSM Agentがデフォルトで入っているため素の状態からでもOSにログインせずスクリプトの実行までできるようになりました。githubのプライベートリポジトリのファイルでも使用可能であるため、内部向けスクリプトでも使用できます。

この機能によりOSの設定の自由度が上がり、OSから上のメンテナンスもやりやすくなったのではないでしょうか。