【Fabric】作業効率アップ!AMIを自動作成するFabricタスク

Fabricを使って、Amazon Linux AMIを作成する方法を紹介します。AMIの名前を自動取得し、作成する方法も紹介しています。

準備

AWS CLIを叩くための準備です。

リージョンと出力形式の設定

設定していない場合は、リージョンと出力形式を設定しておきます。これはAMIを作成元となるサーバで設定します。

「$HOME/.aws/config」を編集

[default]
output = json
region = ap-northeast-1

ポリシーの設定

今回使うコマンドはec2 describe-imagesec2 create-imageです。これらコマンドを使えるように、ポリシーを設定しておきます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "0001",
            "Effect": "Allow",
            "Action": [
                "ec2:CreateImage",
                "ec2:DescribeImages"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

ポリシーの設定手順はWordPressの記事や画像をS3にバックアップする方法に書きましたので、参考になれば幸いです。

AWS CLIを試す

Policyを設定したら、サーバ上でコマンドを叩いてみます。

describe-imagesを叩くと、AMIの一覧を取得できます。

$ aws ec2 describe-images --owners <ACCOUNT_ID>
{
    "Images": [
        {
        ...

create-imageを叩くと、AMIが新しく作成されます。※<INSTANCE_ID>で指定したサーバはリブートするのでご注意ください。

$ aws ec2 create-image --instance-id <INSTANCE_ID> --name TESTAMI
{
    "ImageId": "XXX-XXXXXXXX"
}

これで準備が整いました。

FabricでAMIを作成

準備が整ったら、Fabricを使ってAMIを作成してみます。以下のスクリプトではfab staging aws_create_imageと打つと、「test-本日のYYYYMMDD-01」という名前で、AMIが作成されます。

# coding: utf-8
from datetime import date
from fabric.api import env, run, abort, task
from fabric.contrib.console import confirm
from fabric.colors import blue


@task
def staging():
    env.hosts = ['staging.remotestance.com']
    env.user = 'ec2-user'
    env.key_filename = '~/.ssh/test.pem'


@task
def aws_create_image():
    """ リリース用のAMIを作成します。 """
    # AMIの名前を「test-本日のYYYYMMDD-01」とします。
    ami_name = date.today().strftime('test-%Y%m%d-01')
    if not confirm('AMI [%s]を作成します。よろしいですか?' % blue(ami_name, bold=True)):
        abort('AMI作成をキャンセルしました。')

    # 自分のインスタンスIDを取得します。
    instance_id = str(
        run('curl http://169.254.169.254/latest/meta-data/instance-id/'))
    cmd = 'aws ec2 create-image --instance-id %s --name %s ' \
          % (instance_id, ami_name)
    run(cmd)

動作確認

aws_create_imageタスクを実行してみます。

$ fab staging aws_create_image
AMI [test-20150617-01]を作成します。よろしいですか? [Y/n] 
...
Done.

作成されたことは、サーバ上で以下のコマンドを打つと確認できます。

$ aws ec2 describe-images --owners <AWS_ACCOUNT_ID> --filters Name=name,Values=test-*-01
{
    "Images": [
        {   
        ...

FabricでAMIを作成(名前を自動的に取得)

上記スクリプトでは、名前が「test-本日のYYYYMMDD-01」となります。そのため、1日に作成するAMIが2個以上の場合に対応できません。

ここではその問題に対応してみます。

AMIのサフィックスを採番

下のタスクは、AMIの「YYYYMMDD-XX」を取得します。例えば、実行日が「2015年6月17日」で、「test-20150617-01」という名前のAMIは既に作成されているとします。その場合、このタスクを使うと「test-20150617-02」を取得できます。

import ...

AWS_AMI_PREFIX = 'test-'
AWS_ACCOUNT_ID = '<AWS_ACCOUNT_ID>'


@task
def get_ami_suffix():
    """
    AMIのサフィックスを決定し、返します。
    例)20150617-02
    """
    yyyymmdd = date.today().strftime('%Y%m%d')  # 本日のYYYYMMDDを取得します。
    idx = 1  # YYYYMMDD-XXの、XXの部分を仮に決めておきます。
    image_name = _get_latest_ami()  # 最新のAMIの名前を取得します。

    if image_name:  # AMIの名前を取得できた場合
        # 最新のAMIの名前のYYYYMMDDとXXを取得します。
        latest_yyyymmdd, str_idx = re.search(
            r'^%s(\d{8})-(\d{2})$' % AWS_AMI_PREFIX, image_name).groups()

        if yyyymmdd == latest_yyyymmdd:
            # 本日の日付でAMIが作成されている場合、最後の-xxに1を足します。
            idx = int(str_idx) + 1

    ami_suffix = '%s-%02d' % (yyyymmdd, idx)
    print('ami_suffix: %s' % ami_suffix)
    return ami_suffix


def _get_latest_ami():
    """最新のAMIの名前を取得します。"""
    res = run('aws ec2 describe-images --owners %(owner)s '
              '--filters Name=name,Values=%(ami_prefix)s*' %
              {'owner': AWS_ACCOUNT_ID, 'ami_prefix': AWS_AMI_PREFIX})
    images = json.loads(res)['Images']
    if not images:
        return None

    # 作成日の降順でソート
    images.sort(key=lambda i: i['CreationDate'], reverse=True)

    return images[0]['Name']

実行し、「YYYYMDDDD-XX」を取得できることを確認します。

$ fab staging get_ami_suffix
...
ami_suffix: 20150617-02

aws_create_imageに組み込む

AMIの名前に使う「YYYYMMDD-XX」を取得できるようになったら、後は先ほどのaws_create_imageに組み込むだけです。

-    ami_name = date.today().strftime('test-%Y%m%d-01')
+    suffix = get_ami_suffix()
+    ami_name = '%s%s' % (AWS_AMI_PREFIX, suffix)

ソース全体

今回紹介したFabricスクリプトの、ソースの全体を載せておきます。また、github上にも公開してあるので、そこから取得することもできます。https://github.com/yusukemurayama/blog-samples/blob/master/fabfile/aws_create_image.py

# coding: utf-8
import json
import re
from datetime import date
from fabric.api import env, run, abort, task
from fabric.contrib.console import confirm
from fabric.colors import blue

AWS_AMI_PREFIX = 'test-'
AWS_ACCOUNT_ID = '<AWS_ACCOUNT_ID>'


@task
def staging():
    env.hosts = ['<HOSTNAME>']
    env.user = 'ec2-user'
    env.key_filename = '<KEY_FILENAME>'


@task
def get_ami_suffix():
    """
    AMIのサフィックスを決定し、返します。
    例)20150617-02
    """
    yyyymmdd = date.today().strftime('%Y%m%d')  # 本日のYYYYMMDDを取得します。
    idx = 1  # YYYYMMDD-XXの、XXの部分を仮に決めておきます。
    image_name = _get_latest_ami()  # 最新のAMIの名前を取得します。

    if image_name:  # AMIの名前を取得できた場合
        # 最新のAMIの名前のYYYYMMDDとXXを取得します。
        latest_yyyymmdd, str_idx = re.search(
            r'^%s(\d{8})-(\d{2})$' % AWS_AMI_PREFIX, image_name).groups()

        if yyyymmdd == latest_yyyymmdd:
            # 本日の日付でAMIが作成されている場合、最後の-xxに1を足します。
            idx = int(str_idx) + 1

    ami_suffix = '%s-%02d' % (yyyymmdd, idx)
    print('ami_suffix: %s' % ami_suffix)
    return ami_suffix


def _get_latest_ami():
    """最新のAMIの名前を取得します。"""
    res = run('aws ec2 describe-images --owners %(owner)s '
              '--filters Name=name,Values=%(ami_prefix)s*' %
              {'owner': AWS_ACCOUNT_ID, 'ami_prefix': AWS_AMI_PREFIX})
    images = json.loads(res)['Images']
    if not images:
        return None

    # 作成日の降順でソート
    images.sort(key=lambda i: i['CreationDate'], reverse=True)

    return images[0]['Name']


@task
def aws_create_image():
    """ リリース用のAMIを作成します。 """
    suffix = get_ami_suffix()
    ami_name = '%s%s' % (AWS_AMI_PREFIX, suffix)
    if not confirm('AMI [%s]を作成します。よろしいですか?' % blue(ami_name, bold=True)):
        abort('AMI作成をキャンセルしました。')
    instance_id = str(
        run('curl http://169.254.169.254/latest/meta-data/instance-id/'))
    cmd = 'aws ec2 create-image --instance-id %s --name %s ' \
          % (instance_id, ami_name)
    run(cmd)

この記事が役に立った場合、シェアしていただけると励みになります!!