AWS
Ansible
aws-cli
EC2SystemsManager

EC2 Systems ManagerでAnsibleを使ってみた

More than 1 year has passed since last update.


背景

1ヶ月ほど前に、EC2 Systems Managerのドキュメントに"AWS-RunAnsiblePlaybook"が追加されました。

Running Ansible Playbooks using EC2 Systems Manager Run Command and State Manager

この機能を使うための事前設定を含めて、一通りの設定を行ってみます。

例によって、AWS CLIで実施します。


動作要件


  • Managed Instanceとなるためのインスタンスプロファイルを設定していること

  • SSM Agentがインストールされていること

  • Ansibleがインストールされていること


大まかな手順


  1. Managed Instanceの作成

  2. Playbookの作成

  3. コマンドの実行(AWS-RunAnsiblePlaybook)

  4. 動作確認


前提条件


  • 以降で利用するコマンドを実行する権限が付与されたインスタンス上で作業を実施しています。


    • インスタンスプロファイルを設定しています。



  • Amazon Linux上で実行しています。


0. 事前準備


リージョンの指定


コマンド

export AWS_DEFAULT_REGION="ap-northeast-1"



AWS CLIのインストール


コマンド

sudo pip install awscli


必要に応じてアップデートしてください。


コマンド

sudo pip install awscli -U



1. Managed Instanceの作成


テンプレートの作成

CloudFormationでまとめて作成します。


コマンド

CF_TEMPLATE_FILE_NAME="ec2-systems-manager.yml"



コマンド

cat << EOF > ${CF_TEMPLATE_FILE_NAME}

AWSTemplateFormatVersion: "2010-09-09"
Description: JAWS-UG CLI EC2 Systems Manager LT Ansible
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: "10.0.0.0/16"
IGW:
Type: AWS::EC2::InternetGateway
AttachIGW:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: VPC
InternetGatewayId:
Ref: IGW
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
CidrBlock: "10.0.0.0/24"
MapPublicIpOnLaunch: true
VpcId:
Ref: VPC
PublicRT:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
PublicDefaultRoute:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: "0.0.0.0/0"
GatewayId:
Ref: IGW
RouteTableId:
Ref: PublicRT
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnet
RouteTableId:
Ref: PublicRT
SecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: String
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
VpcId:
Ref: VPC
SecurityGroupIngress:
Type: "AWS::EC2::SecurityGroupIngress"
Properties:
GroupId:
Ref: SecurityGroup
IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0

Role:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
-
Effect: "Allow"
Principal:
Service:
- "ec2.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"

InstanceProfile:
Type: "AWS::IAM::InstanceProfile"
Properties:
Path: "/"
Roles:
- Ref: Role

Instance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: ami-bbf2f9dc
InstanceType: t2.micro
SecurityGroupIds:
- Ref: SecurityGroup
SubnetId:
Ref: PublicSubnet
IamInstanceProfile:
Ref: InstanceProfile
UserData:
Fn::Base64: |
#!/bin/bash
cd /tmp
sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
sudo pip install ansible
BucketPlaybook:
Type: AWS::S3::Bucket

Outputs:
InstanceID:
Value:
Ref: Instance
IPAddress:
Value:
!GetAtt Instance.PublicIp
BucketPlaybook:
Value:
Ref: BucketPlaybook
EOF



コマンド

aws cloudformation validate-template \

--template-body file://${CF_TEMPLATE_FILE_NAME}


結果

{

"CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]",
"Description": "JAWS-UG CLI EC2 Systems Manager LT Ansible",
"Parameters": [],
"Capabilities": [
"CAPABILITY_IAM"
]
}


スタックの作成


コマンド

CF_STACK_NAME="TestAnsiblePlaybook"



コマンド

aws cloudformation create-stack \

--stack-name ${CF_STACK_NAME} \
--template-body file://${CF_TEMPLATE_FILE_NAME} \
--capabilities "CAPABILITY_IAM"


コマンド

aws cloudformation wait stack-create-complete \

--stack-name ${CF_STACK_NAME}


コマンド

aws cloudformation describe-stacks \

--stack-name ${CF_STACK_NAME}


インスタンスIDの確認


コマンド

OUTPUTKEY_INSTANCEID="InstanceID"



コマンド

INSTANCE_ID=$(aws cloudformation describe-stacks \

--stack-name ${CF_STACK_NAME} \
--query "Stacks[].Outputs[?OutputKey==\`${OUTPUTKEY_INSTANCEID}\`].OutputValue[]" \
--output text) \
&& echo ${INSTANCE_ID}


結果

i-*****************



Playbook用S3バケット名の確認


コマンド

OUTPUTKEY_BUCKETNAME="BucketPlaybook"



コマンド

BUCKET_NAME=$(aws cloudformation describe-stacks \

--stack-name ${CF_STACK_NAME} \
--query "Stacks[].Outputs[?OutputKey==\`${OUTPUTKEY_BUCKETNAME}\`].OutputValue[]" \
--output text) \
&& echo ${BUCKET_NAME}


結果(例)

temp-bucketplaybook-19b6mabyyf43m



IPアドレスの確認


コマンド

OUTPUTKEY_PUBLICIP="IPAddress"



コマンド

PUBLIC_IP=$(aws cloudformation describe-stacks \

--stack-name ${CF_STACK_NAME} \
--query "Stacks[].Outputs[?OutputKey==\`${OUTPUTKEY_PUBLICIP}\`].OutputValue[]" \
--output text) \
&& echo ${PUBLIC_IP}


結果(例)

54.**.**.**



2. Playbookの作成

以下のPlaybookを実行してみます。(本投稿の冒頭で紹介したブログから拝借)


Playbook

---

- hosts: all
become: true

tasks:
- name: gather ec2 facts
action: ec2_facts

- name: install apache on redhat or centos instances
yum: name=httpd state=present
when: ansible_os_family == "RedHat"

- name: install apache on debian or ubuntu instances
apt: name=apache2 state=present
when: ansible_os_family == "Debian"

- name: enable apache on startup and start service for redhat or centos
service: name=httpd enabled=yes state=started
when: ansible_os_family == "RedHat"

- name: enable apache on startup and start service for debian or ubuntu
service: name=apache2 enabled=yes state=started
when: ansible_os_family == "Debian"



Playbookの生成


コマンド

PLAYBOOK_FILE_NAME="test-playbook.yml"



コマンド

cat << EOF > ${PLAYBOOK_FILE_NAME}

---
- hosts: all
become: true

tasks:
- name: gather ec2 facts
action: ec2_facts

- name: install apache on redhat or centos instances
yum: name=httpd state=present
when: ansible_os_family == "RedHat"

- name: install apache on debian or ubuntu instances
apt: name=apache2 state=present
when: ansible_os_family == "Debian"

- name: enable apache on startup and start service for redhat or centos
service: name=httpd enabled=yes state=started
when: ansible_os_family == "RedHat"

- name: enable apache on startup and start service for debian or ubuntu
service: name=apache2 enabled=yes state=started
when: ansible_os_family == "Debian"
EOF

cat ${PLAYBOOK_FILE_NAME}



PlaybookをS3へアップロード


コマンド

aws s3 cp ${PLAYBOOK_FILE_NAME} s3://${BUCKET_NAME}



3. コマンドの実行(AWS-RunAnsiblePlaybook)


コマンド

PARAMETER='{"extravars":["SSM=True"],"check":["False"],"playbookurl":["s3://'${BUCKET_NAME}/${PLAYBOOK_FILE_NAME}'"]}' \

&& echo ${PARAMETER}


コマンド

COMMAND_ID=$(aws ssm send-command \

--document-name "AWS-RunAnsiblePlaybook" \
--instance-ids "${INSTANCE_ID}" \
--parameters ${PARAMETER} \
--query "Command.CommandId" \
--output text) \
&& echo ${COMMAND_ID}


コマンド

aws ssm get-command-invocation \

--command-id ${COMMAND_ID} \
--instance-id ${INSTANCE_ID} \
--query StandardOutputContent \
--output text


結果

ansible 2.3.1.0

config file =
configured module search path = Default w/o overrides
python version = 2.7.12 (default, Sep 1 2016, 22:14:00) [GCC 4.8.3 20140911 (Red Hat 4.8.3-9)]
download: s3://testansibleplaybook-bucketplaybook-hn4g6uw9j3jb/test-playbook.yml to ./playbook.yml

PLAY [all] *********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [gather ec2 facts] ********************************************************
ok: [localhost]

TASK [install apache on redhat or centos instances] ****************************
ok: [localhost]

TASK [install apache on debian or ubuntu instances] ****************************
skipping: [localhost]

TASK [enable apache on startup and start service for redhat or centos] *********
ok: [localhost]

TASK [enable apache on startup and start service for debian or ubuntu] *********
skipping: [localhost]

PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0



4. 動作確認

インスタンスのPublic IPにアクセスして、テストページが表示されたら成功です。