Lambda (Python) で EC2 のイメージ (AMI) を作成してみる

# ■ はじめに

![963f9ca9395c096c234f891ad8877b28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161939/d838a89d-5dc1-d693-0517-add60ca2ce07.png)

ずいぶん昔に作成したものですが…

特定の EC2 インスタンスについて、定期的にイメージを作成したかったのでやってみました。

# ■ プログラムの流れ

1. EC2 インスタンスのイメージの一覧情報を取得
1. 古いイメージを削除
1. EC2 インスタンスの一覧情報を取得
1. 一覧情報の内、特定の EC2 インスタンスについて、イメージを作成

- イメージを作成する EC2 インスタンスの識別としては、今回は、Name タグの先頭文字列が「★」であれば、イメージを作成することに。

# ■ コード

コードは、以下。

```python:MaintenanceEC2_Image.py
#!/usr/bin/env python
# -*- coding: utf-8-unix; -*-
"""AWS Lambda Function - Maintenance EC2 Images
"""
from __future__ import print_function

import logging
import boto3
import os

# 「Python 3 で少しだけ便利になった datetime の新機能 - Qiita」
# <https://qiita.com/methane/items/d7ac3c9af5a2c659bc51>
from datetime import datetime, timezone, timedelta
TimeZone = timezone(timedelta(hours=+9), 'JST')

# 「Lambdaの本番業務利用を考える① - ログ出力とエラーハンドリング | ナレコムAWSレシピ」
# <https://recipe.kc-cloud.jp/archives/9968>
logger = logging.getLogger()
logLevelTable={'DEBUG':logging.DEBUG,'INFO':logging.INFO,'WARNING':logging.WARNING,'ERROR':logging.ERROR,'CRITICAL':logging.CRITICAL}
if os.environ.get('logging_level') in logLevelTable :
    logger.setLevel(logLevelTable[os.environ['logging_level']])
else:
    logger.setLevel(logging.WARNING)

#
StartDateTime = datetime.now(TimeZone)

def AmazonEC2_DescribeDeregisterImages(response_images, Image_Description_Head):
    try:
        deregister_images = []
        for n, k in enumerate(response_images["Images"]):
            id = response_images["Images"][n].get("ImageId")
            name = response_images["Images"][n].get("Name")
            description = response_images["Images"][n].get("Description")
            state = response_images["Images"][n].get("State")
            creationdate = response_images["Images"][n].get("CreationDate")
            if description.startswith(Image_Description_Head):
                logger.debug("(%04d/%04d) EC2 Image | [My] ID:[%s] Name:[%s] Description:[%s] State:[%s] CreationDate:[%s]",
                n + 1, len(response_images["Images"]), id, name, description, state, creationdate)
                deregister_images.append({
                    "ImageId" : id,
                    "Name" : name,
                    "Description" : description,
                    "State" : state,
                    "CreationDate" : creationdate,
                })
            else:
                logger.debug("(%04d/%04d) EC2 Image | [--] ID:[%s] Name:[%s] Description:[%s] State:[%s] CreationDate:[%s]",
                n + 1, len(response_images["Images"]), id, name, description, state, creationdate)
    except Exception as e:
        logger.exception('Error dosomething: %s', e)
    else:
        pass
    finally:
        pass
    return deregister_images
    
def AmazonEC2_DescribeCreateImageInstances(response_instances):
    try:
        create_image_instances = []
        for n, k in enumerate(response_instances["Reservations"]):
            id = response_instances["Reservations"][n]["Instances"][0].get("InstanceId")
            state = response_instances["Reservations"][n]["Instances"][0]["State"].get("Name")
            name = ""
            for nn, kk in enumerate(response_instances["Reservations"][n]["Instances"][0]["Tags"]):
                if response_instances["Reservations"][n]["Instances"][0]["Tags"][nn]["Key"] == "Name":
                    name = response_instances["Reservations"][n]["Instances"][0]["Tags"][nn]["Value"]
            if name.startswith("★"):
                logger.debug("(%04d/%04d) EC2 Instance | [Do] ID:[%s] Name:[%s] State:[%s]",
                n + 1, len(response_instances["Reservations"]), id, name, state)
                create_image_instances.append({
                    "InstanceId" : id,
                    "Name" : name,
                    "State" : state,
                })
            else:
                logger.debug("(%04d/%04d) EC2 Instance | [--] ID:[%s] Name:[%s] State:[%s]",
                n + 1, len(response_instances["Reservations"]), id, name, state)
    except Exception as e:
        logger.exception('Error dosomething: %s', e)
    else:
        pass
    finally:
        pass
    return create_image_instances

def lambda_handler(event, context):
    try:
        global StartDateTime
        StartDateTime = datetime.now(TimeZone)
        logger.info("StartDateTime:[%s]", StartDateTime)
        #
        logger.info("event:[%s]", event)
        logger.info("context:[%s]", context)
        #
        ImageCountMax_All = int(event["ImageCountMax_All"])
        logger.info("ImageCountMax_All:[%d]", ImageCountMax_All)
        #
        Image_Description_Head_Name = event["Image_Description_Head_Name"]
        Image_Description_Head = "Created by " + Image_Description_Head_Name + " from "
        logger.info("Image_Description_Head:[%s]", Image_Description_Head)
        #
        DeregisterImages_DryRun = event.get("DeregisterImages_DryRun")
        if not DeregisterImages_DryRun:
            DeregisterImages_DryRun = False
        logger.info("DeregisterImages_DryRun:[%s]", DeregisterImages_DryRun)
        if not isinstance(DeregisterImages_DryRun, bool):
            return "[ERROR] Parameter DeregisterImages_DryRun Format is Bool"
        CreateImages_DryRun = event.get("CreateImages_DryRun")
        if not CreateImages_DryRun:
            CreateImages_DryRun = False
        logger.info("CreateImages_DryRun:[%s]", CreateImages_DryRun)
        if not isinstance(CreateImages_DryRun, bool):
            return "[ERROR] Parameter CreateImages_DryRun Format is Bool"
        #
        ec2_client = boto3.client("ec2")
        response_images = ec2_client.describe_images(Owners=['self'])
        response_instances = ec2_client.describe_instances()
        # Amazon EC2 - Deregister Images
        deregister_images = AmazonEC2_DescribeDeregisterImages(response_images, Image_Description_Head)
        for n, k in enumerate(sorted(deregister_images, key=lambda image: image['CreationDate'], reverse=True)):
            if n < ImageCountMax_All:
                logger.info(("(%04d/%04d) Deregister Image | [--] "
                    "ID:[%s] Name:[%s] Description:[%s] State:[%s] CreationDate:[%s]"),
                    n + 1, len(deregister_images),
                    k["ImageId"], k["Name"], k["Description"], k["State"], k["CreationDate"])
                continue
            logger.info(("(%04d/%04d) Deregister Image | [Do] "
                "ID:[%s] Name:[%s] Description:[%s] State:[%s] CreationDate:[%s]"),
                n + 1, len(deregister_images),
                k["ImageId"], k["Name"], k["Description"], k["State"], k["CreationDate"])
            if DeregisterImages_DryRun:
                logger.info("Deregister Image - Dry Run. (DeregisterImages_DryRun is %s)", DeregisterImages_DryRun)
                continue
            response_deregister_image = ec2_client.deregister_image(
                ImageId=k["ImageId"])
            logger.info("Deregister Image Response | [%s] | %s", k["ImageId"], response_deregister_image)
        # Amazon EC2 - Create Images
        create_image_instances = AmazonEC2_DescribeCreateImageInstances(response_instances)
        for n, k in enumerate(create_image_instances):
            instanceid = k["InstanceId"]
            name = instanceid + StartDateTime.strftime("_%Y%m%d%H%M%S")
            description = Image_Description_Head + instanceid + " (" + StartDateTime.strftime("%Y/%m/%d %H:%M:%S") + ")"
            logger.info(("(%04d/%04d) Create Image [Do] "
                "ID:[%s] Name:[%s] Description:[%s] NoReboot:[%d]"),
                n + 1, len(create_image_instances),
                instanceid, name, description, True)
            if CreateImages_DryRun:
                logger.info("Create Image - Dry Run. (CreateImages_DryRun is %s)", CreateImages_DryRun)
                continue
            response_create_image = ec2_client.create_image(
                InstanceId=instanceid, Name=name, Description=description, NoReboot=True)
            logger.info("Create Image Response | [%s] | %s", instanceid, response_create_image)
    except Exception as e:
        logger.exception('Error dosomething: %s', e)
    else:
        pass
    finally:
        pass
    #
    return "normal end"
```

# ■ Lambda への引数

- ImageCountMax_All
    - イメージの最大保持数。新しい方から数えて、この値を超えたイメージは削除。
- DeregisterImages_DryRun
    - イメージ削除処理のドライランの設定。これが、`false` になっていないと、削除しない。
- CreateImages_DryRun
    - イメージ作成処理のドライランの設定。これが、`false` になっていないと、作成しない。
- Image_Description_Head_Name
    - 作成したイメージの `description` の先頭に付与する特定の文字列。当該プログラムで作成したイメージかどうかの判別に使用。

```json
{
    "ImageCountMax_All": 360,
    "DeregisterImages_DryRun": false,
    "CreateImages_DryRun": false,
    "Image_Description_Head_Name": "Lambda (MaintenanceEC2_Image)"
}
```

# ■ Lambda に設定する環境変数

- logging_level : `[ログレベル]`

# ■ ハマりポイント

- 昔のことなので、覚えていません…

🤔🤔🤔

# ■ まとめ

参考になれば ♪♪♪

👋👋👋