【AWS】Data Lifecycle Manager を CloudFormation で設定してみた【dlm】

EBS のスナップショットを取得する Data Lifecycle Manager というマネージドサービスがございます。
こちらを一括で設定する CloudFormation のテンプレートを作成しました。

AWSTemplateFormatVersion: "2010-09-09"
Description: "DLM Configuration YAML"

# パラメータセッティング
Parameters:
  ProjectName:
    Type: String
  LotateNum:
    Type: Number
    Default: 3
  GetTime:
    Type: String
    Default: "18:00"

Resources:
  
  # DLM IAM ロール作成
  CreateDlmRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: 'AWSDataLifecycleManagerDefaultRole'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - dlm.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /service-role/
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSDataLifecycleManagerServiceRole
        
  # DLM 作成
  BasicLifecyclePolicy:
    Type: "AWS::DLM::LifecyclePolicy"
    Properties:
      Description: !Join [ "-", [ !Ref ProjectName, dlm ] ]
      State: "ENABLED"
      ExecutionRoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/service-role/AWSDataLifecycleManagerDefaultRole"
      PolicyDetails:
        ResourceTypes:
          - "VOLUME"
        TargetTags:
          -
            Key: "dlmtarget"
            Value: "true"

        Schedules:
          -
            Name: !Join [ "-", [ !Ref ProjectName, daily-schedule ] ]
            TagsToAdd:
              -
                Key: "type"
                Value: !Join [ "-", [ !Ref ProjectName, scheduled-snapshot ] ]

            CreateRule:
              Interval: 24
              IntervalUnit: "HOURS"
              Times:
                - !Ref GetTime

            RetainRule:
              Count: !Ref LotateNum
            CopyTags: true

各種パラメータは下記の通りに設定して下さい。
・ProjectName ⇒ DLM リソース の prefix
・LotateNum ⇒ スナップショットの保持期間を指定
・GetTime ⇒ スナップショットの取得時間を UTCで指定 ( 例:10:00、08:35、02:48 )

【AWS】EBS のスナップショットを取得して世代管理してみる【シェルスクリプト】

こんにちは。
表題の通りです。
DLM でもよいのですが、最長のインターバルが 24 時間なので、要件が合わない時のために。

#!/bin/sh

# スナップショットの保持世代数を定義
LOTATE_NUM=3
# ホストネームを定義
HOSTNAME=
# スナップショットを取得するボリュームIDを定義
VOLID=

# スナップショットを取得
aws ec2 create-snapshot --volume-id $VOLID --tag-specification 'ResourceType="snapshot",Tags=[{Key="Name",Value="script-create"}]' --description "$HOSTNAME snapshot"

# 指定した世代数分になるようにスナップショットを削除
SNAPSHOTS_NUM=`aws ec2 describe-snapshots --output text | grep $VOLID | grep "$HOSTNAME snapshot" | wc -l`
while [ ${SNAPSHOTS_NUM} -gt ${LOTATE_NUM} ]
do
        aws ec2 delete-snapshot --snapshot-id `aws ec2 describe-snapshots --output text | grep $VOLID | grep "$HOSTNAME snapshot" | sort -k 8 | awk 'NR==1 {print $7}'`
        SNAPSHOTS_NUM=`aws ec2 describe-snapshots --output text | grep $VOLID | grep "$HOSTNAME snapshot" | wc -l`
done

# awscli の導入

curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
sudo python get-pip.py
sudo pip install awscli
aws configure

【CloudWatch】AutoScaling のメモリ/ディスク使用率のグループメトリクスを取得する【AWS】

AutoScaling のグループメトリクスとして、メモリとディスク使用率を取得する必要がございました。
調べると AWS がメトリクス取得用のスクリプトを提供しているようです。
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/mon-scripts.html

# 必要なパッケージの導入
yum install -y perl-Switch perl-DateTime perl-Sys-Syslog perl-LWP-Protocol-https perl-Digest-SHA.x86_64
# ディレクトリ移動
cd /usr/local/src/
# スクリプトのインストール
curl https://aws-cloudwatch.s3.amazonaws.com/downloads/CloudWatchMonitoringScripts-1.2.2.zip -O
# スクリプトの解凍
unzip CloudWatchMonitoringScripts-1.2.2.zip && rm CloudWatchMonitoringScripts-1.2.2.zip && cd aws-scripts-mon
# テスト実行
./mon-put-instance-data.pl --mem-util --mem-used --mem-avail --disk-space-util --disk-path=/ --auto-scaling=only
# cronで5分毎に実行
crontab -e
=====================================================================================================================================================================
*/5 * * * * /usr/local/src/aws-scripts-mon/mon-put-instance-data.pl --mem-util --mem-used --mem-avail --disk-space-util --disk-path=/ --auto-scaling=only --from-cron

※ EC2 から CloudWatch を操作できる権限を持った IAM ロールを事前にアタッチしておく必要がございます。
※ インスタンス毎にキャッシュファイルが作成されるので、EC2 起動時にキャッシュファイルを削除するよう UserData を設定しておきます。
#!/bin/bash
rm -rf /var/tmp/aws-mon/*

※ cloudwatch-agent もありますが、こちらではグループメトリクスは取得できないようです。

【Lambda】アラート通知を判別して別々の絵文字を付与し Slack に通知してみる【slack】

【Lambda】CloudWatch の通知を Lambda で Slack に飛ばしてみる【Slack】


↑ 前回の記事の続きです。

アラーム、リカバリ時の通知が判別し辛いので、それぞれの通知に対して別々の絵文字を付与してみます。
※ ランタイムは python3.7 です。

import boto3
import json
import logging
import os

from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError


# 通知するチャンネル定義
SLACK_CHANNEL = "#xxxxxx"

# WEB_HOOKURL 定義
HOOK_URL = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
    logger.info("Event: " + str(event))
    message = json.loads(event['Records'][0]['Sns']['Message'])
    logger.info("Message: " + str(message))

    alarm_name = message['AlarmName']
    new_state = message['NewStateValue']
    reason = message['NewStateReason']

    stamp = ":warning:"
    if new_state == "ALARM":
        stamp = ":warning:"
    else:
        stamp = ":ok_woman:"
        
    slack_message = {
        'channel': SLACK_CHANNEL,
        'text': "%s %s state is now %s: %s" % (stamp, alarm_name, new_state, reason)
    }

    req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", slack_message['channel'])
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

CloudWatch からの通知が ALARM であれば :warning: を付与、それ以外であれば :ok_woman: を付与します。

【Lambda】CloudWatch の通知を Lambda で Slack に飛ばしてみる【Slack】

担当している案件で CloudWatch からの通知を Slackに飛ばす必要があったので、Lambda で実装してみます。

1.事前準備

■ 通知を飛ばす Slack に Incoming WebHooks を追加しておく
https://slack.com/services/new/incoming-webhook

■ 必要なポリシーを付与した IAM ロールを作成しておく
・CloudWatchReadOnlyAccess
・AWSLambdaBasicExecutionRole

■ CloudWatch + SNS の通知設定

2.Lambda設定

Lambda > 関数 > 関数の作成 > 一から作成

関数名:<>
ランタイム:python 3.7
実行ロールの選択: 事前準備で作成したIAMロール

> トリガーを追加

トリガーの設定:SNS
SNS トピック:「事前準備で作成したSNSトピック」
トリガーの有効化:有効

関数コード

import boto3
import json
import logging
import os

from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError


# 通知を飛ばすチャンネルを定義
SLACK_CHANNEL = "#xxxxxx"

# WEB_HOOKURLを定義
HOOK_URL = "https://hooks.slack.com/services/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
    logger.info("Event: " + str(event))
    message = json.loads(event['Records'][0]['Sns']['Message'])
    logger.info("Message: " + str(message))

    alarm_name = message['AlarmName']
    #old_state = message['OldStateValue']
    new_state = message['NewStateValue']
    reason = message['NewStateReason']

    slack_message = {
        'channel': SLACK_CHANNEL,
        'text': "%s state is now %s: %s" % (alarm_name, new_state, reason)
    }

    req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", slack_message['channel'])
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

⇒「SLACK_CHANNEL」「HOOK_URL」に通知を飛ばすチャンネル名とWebHookURLを定義してください。

【Ansible】AnsibleでAWSのネットワークを構築する【IaC】

Terraform に埋もれがちですが Ansible でも AWS のリソース構築が出来るんですよね。

作るもの

・VPC
・public subnet x 2
・private subnet x 2
・internet gateway
・route table

ディレクトリ構造

├── README.md
├── ansible.cfg
├── hosts
├── roles
│   └── aws_vpc
│       ├── tasks
│       │   └── main.yml
│       └── vars
│           └── main.yml
└── vpc_create.yml

インベントリファイル

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# cat hosts
[localhost]
127.0.0.1

サーバをプロビジョニングする訳ではないので、ローカルホストを指定

Role

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# cat roles/aws_vpc/tasks/main.yml
---
# tasks file for aws_vpc
- name: create_vpc
  ec2_vpc_net:
    name: "{{ vpc_name }}"
    cidr_block: "{{ vpc_cidr }}"
    region: "{{ region }}"
    dns_hostnames: yes
    dns_support: yes
  register: vpc_info

# PUBLIC_SUBNETの作成
- name: create_public_subnet
  ec2_vpc_subnet:
    vpc_id: "{{ vpc_info.vpc.id }}"
    cidr: "{{ item.pub_subnet_cidr }}"
    az: "{{ item.subnet_az }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ item.pub_subnet_name }}" }
  register: pubsub_info
  with_items:
    - "{{ pub_subnet }}"

# PRIVATE_SUBNETの作成
- name: create_private_subnet
  ec2_vpc_subnet:
    vpc_id: "{{ vpc_info.vpc.id }}"
    cidr: "{{ item.pri_subnet_cidr }}"
    az: "{{ item.subnet_az }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ item.pri_subnet_name }}" }
  register: prisub_info
  with_items:
    - "{{ pri_subnet }}"

# IGWの作成
- name: create_igw
  ec2_vpc_igw:
    vpc_id: "{{ vpc_info.vpc.id }}"
    region: "{{ region }}"
    tags: { "Name":"{{ igw_name }}" }
  register: igw_info

# ROUTETABLEの作成(IGW)
- name: create_route_table
  ec2_vpc_route_table:
    vpc_id: "{{ vpc_info.vpc.id }}"
    subnets: "{{ atache_igw_subnet }}"
    routes:
      - dest: 0.0.0.0/0
        gateway_id: "{{ igw_info.gateway_id }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ rttable_pub_name }}" }

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# cat roles/aws_vpc/vars/main.yml
---
# vars file for aws_vpc

# REGION
  region: "ap-northeast-1"

# VPC
  vpc_name: "sanuki-wd-vpc"
  vpc_cidr: "10.0.0.0/16"

# IGW
  igw_name: "sanuki-igw"

# ROUTETABLE(PUBLIC)
  rttable_pub_name: "sanuki-pub-rt"

# PUBLIC_SUBNET
  pub_subnet:
    - { pub_subnet_cidr: "10.0.10.0/24" ,subnet_az: "ap-northeast-1a" ,pub_subnet_name: "sanuki-wd-public-subnet-a" }
    - { pub_subnet_cidr: "10.0.20.0/24" ,subnet_az: "ap-northeast-1c" ,pub_subnet_name: "sanuki-wd-public-subnet-c" }

# PRIVATE_SUBNET
  pri_subnet:
    - { pri_subnet_cidr: "10.0.50.0/24" ,subnet_az: "ap-northeast-1a" ,pri_subnet_name: "sanuki-wd-private-subnet-a" }
    - { pri_subnet_cidr: "10.0.60.0/24" ,subnet_az: "ap-northeast-1c" ,pri_subnet_name: "sanuki-wd-private-subnet-c" }

# IGWに紐付けるサブネット
  atache_igw_subnet:
    - "10.0.10.0/24"
    - "10.0.20.0/24"

playbook

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# cat vpc_create.yml
---
# VPC CREATE Playbook
- name: create vpc subnet igw routetable
  hosts: localhost
  connection: local
  gather_facts: False
  become: False
  roles:
    - aws_vpc

実行

root@DESKTOP-MOGIJIA:/opt/playbook/aws-vpc-2layer# ansible-playbook -i hosts vpc_create.yml

PLAY [create vpc subnet igw routetable] ********************************************************************************

TASK [aws_vpc : create_vpc] ********************************************************************************************
[DEPRECATION WARNING]: Distribution Ubuntu 18.04 on host 127.0.0.1 should use /usr/bin/python3, but is using
/usr/bin/python for backward compatibility with prior Ansible releases. A future Ansible release will default to using
the discovered platform python for this host. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information. This feature
 will be removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in
ansible.cfg.
changed: [127.0.0.1]

TASK [aws_vpc : create_public_subnet] **********************************************************************************
changed: [127.0.0.1] => (item={u'pub_subnet_name': u'sanuki-wd-public-subnet-a', u'subnet_az': u'ap-northeast-1a', u'pub_subnet_cidr': u'10.0.10.0/24'})
changed: [127.0.0.1] => (item={u'pub_subnet_name': u'sanuki-wd-public-subnet-c', u'subnet_az': u'ap-northeast-1c', u'pub_subnet_cidr': u'10.0.20.0/24'})

TASK [aws_vpc : create_private_subnet] *********************************************************************************
changed: [127.0.0.1] => (item={u'pri_subnet_cidr': u'10.0.50.0/24', u'pri_subnet_name': u'sanuki-wd-private-subnet-a', u'subnet_az': u'ap-northeast-1a'})
changed: [127.0.0.1] => (item={u'pri_subnet_cidr': u'10.0.60.0/24', u'pri_subnet_name': u'sanuki-wd-private-subnet-c', u'subnet_az': u'ap-northeast-1c'})

TASK [aws_vpc : create_igw] ********************************************************************************************
changed: [127.0.0.1]

TASK [aws_vpc : create_route_table] ************************************************************************************
changed: [127.0.0.1]

PLAY RECAP *************************************************************************************************************
127.0.0.1                  : ok=5    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

補足

boto3 が必要になります。
pip でインストールしておいて下さい。

pip install boto boto3