[AWS]Systems ManagerのAutomationとAnsibleを使ってAMIを作成する

アイキャッチ AWS EC2

コンニチハ、千葉です。

Systems ManagerのAutomation機能を利用するとカスタムAMIの作成を自動化できます。

AutoScaling利用時、Packer等でカスタムAMIを作成すると思いますがこれの置き換えを想定しています。EC2上でPackerを動かしている場合、EC2を減らすことができるかなと思います。

環境

以下の環境で試しました

  • ソースAMI:Amazon Linux
  • リポジトリ:CodeCommit
  • プロビジョニング:Ansible 2.2.3.0

やってみた

リポジトリ作成

Ansibleコードを格納するリポジトリを作成します。リポジトリは、GitHubやS3などでもいいと思いますが、今回はEC2利用時のリポジトリへのアクセスをシームレスに行えるCodeCommitを選択しました。EC2へIAMロールをアタッチすることで簡単でセキュアにCodeCommitへアクセスすることができます。

リポジトリを作成します。

20170519-systemsmanager-ansible-1

Ansible Playbookをリポジトリへプッシュ

Ansible PlaybookをCodeCommitへpushします。

今回はサンプルとして以下のファイルをpushしました。Nginxをインストールし起動します。

local$ tree
.
├── hosts
├── nginx
│   └── tasks
│       └── main.yml
└── web.yml

各ファイルの中身です

local$ cat hosts
[local]
localhost
local$
calocal$ cat web.yml
#
# web.yml
#
- hosts: local
  connection: local
  become: yes
  roles:
    - nginx
local$ cat nginx/tasks/main.yml
---
- name: nginx installed
  yum:
    name: nginx
- name: nginx service started
  service:
    name: nginx
    state: started
    enabled: yes

IAMロール作成

2つIAMロールを作成します。

Automation実行用IAMロール

1つめのIAMロールとして、Automationへ割り当てるIAMロールを作成します。 このIAMロールは、Automationサービスが実行できる権限の指定となります。

今回は、AutomationRole という名前で作成しました。ロールにはAmazonSSMAutomationRole をアタッチします。

追加でインラインポリシーに以下のポリシーを作成します。追加する権限はEC2インスタンスにIAMロールをアタッチするための権限です。

{
    "Version": "2012-10-17",
    "Statement": [
        
        {
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

次に信頼関係を編集します。SSMサービのassumeRoleを許可します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "ssm.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

20170519-systemsmanager-ansible-2

EC2インスタンスアタッチ用IAMロール

EC2インスタンスにアタッチするIAMロールを作成します。このIAMロールには、CodeCommitの読み取りと、SSMの実行許可を付与します。

20170519-systemsmanager-ansible-3

ドキュメント作成

Automationで実行する内容を定義したドキュメントを作成します。ドキュメントの書き方はAmazon EC2 Systems Manager の Automation をやってみた #reinventを参考に。

まず、EC2のコンソールからドキュメントを選択します。

20170519-systemsmanager-ansible-4

ドキュメントのタイプは「Automation」を選択します。下記のコードをコピってはります。

20170519-systemsmanager-ansible-5

{
  "schemaVersion":"0.3",
  "description":"Update a Linux AMI. By default this provisioning with Ansible.",
  "assumeRole":"{{AutomationAssumeRole}}",
  "parameters":{
    "SourceAmiId":{
      "type":"String",
      "description":"(Required) The source Amazon Machine Image ID."
    },
    "InstanceIamRole":{
      "type":"String",
      "description":"(Required) An IamInstanceProfileName that is allowed to perform RunCommand on an EC2 instance."
    },
    "AutomationAssumeRole":{
      "type":"String",
      "description":"(Required) Role under which to execute this automation."
    },
    "TargetAmiName":{
      "type":"String",
      "description":"(Optionacd) The name of the new AMI that will be created. Default is a system-generated string including the source AMI id, and the creation time and date.",
      "default":"UpdateLinuxAmi_from_{{SourceAmiId}}_on_{{global:DATE_TIME}}"
    },
    "InstanceType":{
      "type":"String",
      "description":"(Optional) Type of instance to launch as the workspace host. Instance types vary by region. Default is t2.micro.",
      "default":"t2.micro"
    },
    "AnsiblePlaybookRepo":{
      "type":"String",
      "description":"(Required) The URL of CodeCommitRepository(HTTPS)."
    },
    "AnsibleVersion":{
      "type":"String",
      "description":"(Option) Version of Ansible. Defalt is 2.2.3.0",
      "default":"2.2.3.0"
    },
    "AnsiblePlaybook":{
      "type":"String",
      "description":"(Required) yml of AnsiblePlaybook. ex: web.yml"
    }
  },
  "mainSteps":[
    {
      "name":"launchInstance",
      "action":"aws:runInstances",
      "maxAttempts":3,
      "timeoutSeconds":1200,
      "onFailure":"Abort",
      "inputs":{
        "ImageId":"{{SourceAmiId}}",
        "InstanceType":"{{InstanceType}}",
        "UserData":"IyEvYmluL2Jhc2gNCg0KZnVuY3Rpb24gZ2V0X2NvbnRlbnRzKCkgew0KICAgIGlmIFsgLXggIiQod2hpY2ggY3VybCkiIF07IHRoZW4NCiAgICAgICAgY3VybCAtcyAtZiAiJDEiDQogICAgZWxpZiBbIC14ICIkKHdoaWNoIHdnZXQpIiBdOyB0aGVuDQogICAgICAgIHdnZXQgIiQxIiAtTyAtDQogICAgZWxzZQ0KICAgICAgICBkaWUgIk5vIGRvd25sb2FkIHV0aWxpdHkgKGN1cmwsIHdnZXQpIg0KICAgIGZpDQp9DQoNCnJlYWRvbmx5IElERU5USVRZX1VSTD0iaHR0cDovLzE2OS4yNTQuMTY5LjI1NC8yMDE2LTA2LTMwL2R5bmFtaWMvaW5zdGFuY2UtaWRlbnRpdHkvZG9jdW1lbnQvIg0KcmVhZG9ubHkgVFJVRV9SRUdJT049JChnZXRfY29udGVudHMgIiRJREVOVElUWV9VUkwiIHwgYXdrIC1GXCIgJy9yZWdpb24vIHsgcHJpbnQgJDQgfScpDQpyZWFkb25seSBERUZBVUxUX1JFR0lPTj0idXMtZWFzdC0xIg0KcmVhZG9ubHkgUkVHSU9OPSIke1RSVUVfUkVHSU9OOi0kREVGQVVMVF9SRUdJT059Ig0KDQpyZWFkb25seSBTQ1JJUFRfTkFNRT0iYXdzLWluc3RhbGwtc3NtLWFnZW50Ig0KcmVhZG9ubHkgU0NSSVBUX1VSTD0iaHR0cHM6Ly9hd3Mtc3NtLWRvd25sb2Fkcy0kUkVHSU9OLnMzLmFtYXpvbmF3cy5jb20vc2NyaXB0cy8kU0NSSVBUX05BTUUiDQoNCmNkIC90bXANCmdldF9jb250ZW50cyAiJFNDUklQVF9VUkwiID4gIiRTQ1JJUFRfTkFNRSINCmNobW9kICt4ICIkU0NSSVBUX05BTUUiDQouLyIkU0NSSVBUX05BTUUiIC0tcmVnaW9uICIkUkVHSU9OIg0K",
        "MinInstanceCount":1,
        "MaxInstanceCount":1,
        "IamInstanceProfileName":"{{InstanceIamRole}}"
      }
    },
    {
      "name":"updateOSSoftware",
      "action":"aws:runCommand",
      "maxAttempts":3,
      "timeoutSeconds":1200,
      "onFailure":"Abort",
      "inputs":{
        "DocumentName":"AWS-RunShellScript",
        "InstanceIds":[
          "{{launchInstance.InstanceIds}}"
        ],
        "Parameters":{
          "commands":[
            "set -e",
            "yum -y install git",
            "pip install --upgrade pip",
            "pip install ansible=={{AnsibleVersion}}",
            "git clone --config credential.helper='!aws --region us-east-1 codecommit credential-helper $@' --config credential.UseHttpPath=true {{AnsiblePlaybookRepo}} /var/ansible-playbook",
            "cd /var/ansible-playbook",
            "/usr/local/bin/ansible-playbook -i hosts {{AnsiblePlaybook}}"
          ]
        }
      }
    },
    {
      "name":"stopInstance",
      "action":"aws:changeInstanceState",
      "maxAttempts":3,
      "timeoutSeconds":1200,
      "onFailure":"Abort",
      "inputs":{
        "InstanceIds":[
          "{{launchInstance.InstanceIds}}"
        ],
        "DesiredState":"stopped"
      }
    },
    {
      "name":"createImage",
      "action":"aws:createImage",
      "maxAttempts":3,
      "onFailure":"Abort",
      "inputs":{
        "InstanceId":"{{launchInstance.InstanceIds}}",
        "ImageName":"{{TargetAmiName}}",
        "NoReboot":true,
        "ImageDescription":"AMI Generated by EC2 Automation on {{global:DATE_TIME}} from {{SourceAmiId}}"
      }
    },
    {
      "name":"terminateInstance",
      "action":"aws:changeInstanceState",
      "maxAttempts":3,
      "onFailure":"Continue",
      "inputs":{
        "InstanceIds":[
          "{{launchInstance.InstanceIds}}"
        ],
        "DesiredState":"terminated"
      }
    }
  ],
  "outputs":[
    "createImage.ImageId"
  ]
}

Automationの実行

手順はコレが最後です。Automationを実行してAMIを作成します。EC2のコンソールから「Automation」を選択します。

20170519-systemsmanager-ansible-6

先程作成したドキュメントを選択し、各パラメータを入力します。

20170519-systemsmanager-ansible-7

以下は入力例です。注意点として、AutomationAssumeRoleはARN指定、InstanceIamRoleはインスタンスプロファイル名(ARNではない)を指定します。

20170519-systemsmanager-ansible-10

実行し、ステップが完了することを確認します。

20170519-systemsmanager-ansible-8

AMIができてます。

20170519-systemsmanager-ansible-9

AMIの確認

作成されたAMIからEC2を作成し確認しました。Nginxがインストールされて、起動できてました。

[ec2-user@ip-172-31-16-39 ~]$ rpm -qa | grep nginx
nginx-1.10.2-1.30.amzn1.x86_64
[ec2-user@ip-172-31-16-39 ~]$ ps -ef | grep nginx
root      2564     1  0 08:24 ?        00:00:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nginx     2567  2564  0 08:24 ?        00:00:00 nginx: worker process
ec2-user  2685  2661  0 08:35 pts/0    00:00:00 grep --color=auto nginx
[ec2-user@ip-172-31-16-39 ~]$

最後に

Systems Managerはパッチ適用だったり、コマンドの定期実行だったり可能ですが今回はAMIの自動化できるAutomationの機能を使ってみました。一度フローを作成しておくと、AMIを変更する時の効率的に対応できるようになります。是非参考になればと。