Lambda 不要なAMIを定期削除する

開発中、テスト用の環境にデプロイするたびにAMIを自動取得していたら、不要なAMIが山のようになってしまったので掃除をする。

以前作成したこちらの記事のLambdaを元に、不要なAMIを定期的に削除するようにしてみます。
Lambda 不要なAMIとスナップショットをまとめて削除する

従来の方法(手動実行用)

元のLambda関数では下記のように、削除対象のAMIを手打ちして、eventから取得していました。

{
    "delete_amis": [
        "ami-0123456789abcdef1",
        "ami-0123456789abcdef2",
        "ami-0123456789abcdef3"
    ]
}
def lambda_handler(event, context):
    delete_amis = event['delete_amis']
    print(delete_amis)

    for delete_ami in delete_amis:
        # Delete AMI.
        unregister_ami(delete_ami)
    
        # Delete Snapshots.
        delete_related_snapshots(delete_ami)
    
    return

今回の方法(自動実行用)

  1. 現在所有しているAMIの一覧から、適当なフィルターをかけつつ一部を取得します。
  2. AMIの作成日部分を取得して、一週間以上古いものというざっくり基準で削除対象のリストに放り込みます。
    (リスト内包表記となぜか実行時間が変わらなかったので、可読性がヤバいことになるのを避けるためにappendで書いています)
  3. 削除対象となったAMI-IDのリストを元に、以前作成したAMI+Snapshotの削除処理を実行します。
def get_old_ami():
    # Describe target AMIs.
    try:
        response = ec2.describe_images(
            Filters=[{'Name': 'name', 'Values': ['delete-sample*']}]
        )
        if response.get('ResponseMetadata', {}).get('HTTPStatusCode', -1) != 200:
            raise('# Cannot describe images!')
        images = response.get('Images')
        print('# All delete-sample images:', len(images))
    except ClientError as e:
        print(e.response['Error']['Code'])
        print(e.response['Error']['Message'])
        logging.error("# Describe images error: %s", e)
        return
    
    seven_days = 604800
    old_images = []
    epoc_now = int(time.time())
    print('# epoc now:', epoc_now)
    
    # Those older than 7 days are subject to deletion.
    for image in images:
        creation_date = image.get('CreationDate')
        datetime = dt.strptime(creation_date, "%Y-%m-%dT%H:%M:%S.000Z")
        epoc_datetime = int(dt.timestamp(datetime))

        if epoc_now - epoc_datetime > seven_days:
            old_images.append(image.get('ImageId'))

    return old_images
def lambda_handler(event, context):
    # Get the AMI to be deleted.
    delete_amis = get_old_ami()
    print('# Target AMIs:', len(delete_amis))
    print(delete_amis)

    for delete_ami in delete_amis:
        # Delete AMI.
        unregister_ami(delete_ami)
    
        # Delete Snapshots.
        delete_related_snapshots(delete_ami)
    
    return

定期実行

トリガーにEventBridgeを指定し、cron式でスケジュールを設定しました。
JSTで「火~土 AM00:00」に実行したい場合、下記のように指定します。

cron(0 15 ? * MON-FRI *)

Kusanagiセットアップ

EC2でKusanagiを実行するにあたって変更等を行った箇所 早いと噂のKusanagiを検証用にインスタンスを作成したので、その備忘録

前提kusanagiAMIでのインスタンスの起動が出来ており、SSH接続できる状態

  1. 管理者権限で実行
    • sudo su -

  1. Kusanagiをセットアップ
    • セットアップに関しては、公式からわかりやすい手順が記載されている。 こちらを参照

  1. Kusanagiをプロビジョニング
    • これに関しても、公式からわかりやすい記事が用意されているので
      こちらを参照
    • ※プロビジョニングをやり直したい時は、下記コマンドで削除して再試行すればOK
      • kusanagi remove -y [プロファイル名]

  1. phpiniを変更(アップロード最大容量をUP)
    • /etc/php7.d/php.iniにある下記2つの設定を変更
      • post_max_size = 16M
      • upload_max_filesize = 16M
    • 設定の変更をkusanagiに読み込ませる
      • kusanagi php7

  1. All-in-One WP Migration プラグインを入れる
    • ブラウザでWP管理画面にアクセスして、プラグインを導入
    • ※FTPを選択して、kusanagiパスワードを入力する
    • ※このままでは、ファイルサイズが大きかったりするとファイルをアップロードできない可能性が高い

  1. NginxのPOST許容サイズを変更
    • 下記2つのファイルを変更する
      • /etc/nginx/conf.d/[プロファイル名]_http.conf
      • /etc/nginx/conf.d/[プロファイル名]_ssl.conf
    • 変更内容
      • client_max_body_size = 200M
    • Nginxを再起動
      • systemctl restart nginx

  1. ディレクトリの所有者を変更
    • chown -R httpd:www wp-content/plugins/all-in-one-wp-migration/storage
    • chown -R httpd:www wp-content/ai1wm-backups

  1. All-in-One WP Migrationでバックアップファイルをアップロードして完成
    • ファイルサイズが大きいと、時間がかかったりします….

  1. まとめ
    • 少しやることが多いけど、All-in-One WP Migrationのおかげでかなり楽に移行できる。
    • 画像たっぷりのサイトは、Kusanagiに移行してもあまり意味が無いかも…

  1. 参考サイト

初めて、MarkDown形式で投稿してみました✨

Lambdaでレイヤーを追加する方法


Lambdaって便利ですよね…AWSを利用していて何か便利にしたい!と思ったときに必ずと言っていいほどに活躍してくれています。そんなLambda関数をもっと便利に使うために、「レイヤー」と呼ばれる機能があります。


1.レイヤーとは

公式ドキュメントでは、こんなことが書かれています。


Lambdaレイヤーは、追加のコードまたはデータを含むことができる .zip ファイルアーカイブです。レイヤーには、ライブラリ、 カスタムランタイム 、データ、または設定ファイルを含めることができます。レイヤーを使用すると、コードの共有と責任の分離を促進し、ビジネスロジックの記述をより迅速に繰り返すことができます。
レイヤーは、 .zip ファイルアーカイブとしてデプロイされた Lambda関数でのみ使用できます。コンテナイメージとして定義された関数では、コンテナイメージの作成時に、優先ランタイムとすべてのコード依存関係をパッケージ化します。詳細については、AWS コンピューティングブログの「コンテナイメージの Lambda レイヤーと拡張機能の使用

」を参照してください。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-layers.html

ざっくりとまとめると、pipなどのライブラリをLambda関数間で共有できたりします。
レイヤーを使う前は、個々の関数で必要なライブラリをpipして、それをひとまとめにしてzipでアップロードしていました。
ですが、このレイヤーを利用することで、あの関数で使ったライブラリが便利だからこっちでも使いたい!となっても、その関数にレイヤーを追加するだけで利用できるようになります。


2.レイヤー追加

1.左横のメニューから「レイヤー」を選択

2.右上の「レイヤーの追加」を選択

3.名前と説明を記入してzipファイルをアップロード、ランタイムを選択し右下の「作成」をクリック

この時に気を付けたいのが、「ファイル名」です。pipしたライブラリ群をzipします。そしてそのファイル名は、「python.zip」としてください。そうしないとレイヤーを追加できても関数で、うまく動作しません。

4.完成!


3.最後に

これで、一つの関数でしか使えなかったpipも、他の関数でも利用できるようになりました。

最後にもう一度だけ、「アップロードするzipファイルの名前は、python.zipにしましょう」

Firebase javascript でやる初めの一歩 Windows10

私はFirebaseをアプリへのPush通知では使ったことはあるけれども、Webの方では使ったことはないので、ちょっとやってみたという感じです。

環境はWindows10です。

早い話が、下記の動画を見ればすぐわかります(笑)。

全部英語ですが、まぁソースコード見てると大体わかりますし、どうしても英語聞きたいときは0.5倍ぐらいで聞くとなんとか聞けたりします。

わかりにくいところだけ補足します。

①apikeyとかなんなのかわからない。

const firebaseConfig = {
    apiKey:これ
};

ですよね~。

これは、Firebaseの方で先にアカウントを作っておいて、「アプリを登録」ということをする必要があります。

下記のFirebaseのサイトにアクセスして、プロジェクトを作ります。

https://console.firebase.google.com/u/0/?hl=ja

その後、作ったプロジェクトに「アプリを追加」します。

Firebaseの画面 左上の「プロジェクトの概要」をクリックするとこれになります。

アプリを</>というマークがJavascriptですので、Javascriptで登録します。

すると、このapiKeyとかprojectIdとかをコピペすればすむ画面が出てきます。

②ブラウザから動作させてみる

動画の通り、index.jsとindex.htmlを作ります。

import { initializeApp } from 'https://www.gstatic.com/firebasejs/9.0.0/firebase-app.js';
import { getAnalytics } from 'https://www.gstatic.com/firebasejs/9.0.0/firebase-analytics.js';;

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional

const firebaseConfig = {
    apiKey: "置き換えてね",
    authDomain: "置き換えてね",
    projectId: "置き換えてね",
    storageBucket: "置き換えてね",
    messagingSenderId: "置き換えてね",
    appId: "置き換えてね",
    measurementId: "置き換えてね"
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
console.log(analytics);
console.log("にゃー");
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="module" src="index.js"></script>
</head>
<body>
ウキー
</body>
</html>

動画内では

serve

というコマンドがさらっと使われております。

これ、Node.jsのコマンドなんですね。事前に

$ npm install -g serve

とやって、serveをnpmでインストールしておきます。

(この動画の冒頭でもnpm使っているので、npmが入っていないということはないと思いますが、npmが入っていなければnpmを先に入れましょう。)

はい、それではindex.jsとindex.htmlがおいてあるディレクトリ(ここではC:\study\firebase)で

serve src

とやります。

http://localhost:5000/

へアクセスします。

ちゃんとブラウザのコンソールにAnalyticsのオブジェクトが表示されましたね。


プログラミングにおける循環参照 なぜいけないのか

循環参照という言葉はExcelで見たことがある人もいるかと思いますが、プログラミングの現場においても、起こることはあります。

普通に循環参照って検索すると、大体Excelの話が出てきますが、循環参照自体は下記のサイトの説明がわかりやすいと思います。

https://gimo.jp/glossary/details/circular_definition.html

今回は、弊社のプログラマーさんと話していて、このような構成になっていたところ

「なんでいけないんですか?」

と言われてパッと答えられなかったので書いておきます。

例えば、ここに構成が複雑なオブジェクト、Itemクラスがあります。

Itemオブジェクトはあまりに複雑なので、ユーザーもItemからコピーして作りたいという要望があったとします。

なので、ItemMasterというクラスが爆誕します。で、ItemMasterはItemを参照して、そのItemのコピーをバンバン作っていきます。

ちなみに、できたItemはユーザーが個別にプロパティをちょこちょこ編集できる仕様です。

クラスとプロパティを抜粋して書くと、次のようになります。

<?php


class ItemMaster{

    public $id;
    public $item_id;

}


class Item{

    public $id;
    public $item_master_id;


}

文章で書くと、

「10番のアイテムができがよいので、コピペして作りたい。そこで、10番をマスターとして、アイテムマスター(Id:1)を作る。アイテムマスターから、11番、12番、13番のアイテムを作る。」

ということです。

ここまでは問題がなさそうです。

実際の問題①

アイテムを更新することを考えてみましょう。

「10番のアイテムのプロパティにちょっと変更を加えた、アイテムマスター (Id:1) から作られた アイテムたちに都度変更を加えるのは大変だから、マスターが更新されたらマスターから作られたアイテムはマスターの変更を反映して変更する。つまり11番、12番、13番のアイテムに10番の変更が反映される。」

そのために、次のようにアップデートメソッドを実装します。

class ItemMaster{

    public $id;
    public $item_id;

    public function updateItem(){
        //このマスターから作られたItemを更新する
    }
    //以下続く
}


class Item{

    public $id;
    public $item_master_id;

    public function updateItemMaster(){
        //このアイテムから作られたItemMasterを更新する
    }
    //以下続く

}

ここで、もしItemのプロパティ $item_master_id と ItemMasterのプロパティ $item_id がお互い同じものを見ることになったとします。

つまり

Item $id=10, $item_master_id=1

ItemMaster $id=1, $item_id=10

文章にすると

「10番のアイテムは、アイテムマスター (Id:1) から作られた。」

「アイテムマスター (Id:1) は10番のアイテムから作られた。」

が両立します。すでに頭の中がぐちゃぐちゃになっちゃいますよね。とりあえず、次のような状態です。

「10番のアイテムができがよいので、コピペして作る。そこで、10番をマスターとして、アイテムマスター(Id:1)を作る。アイテムマスターから、11番、12番、13番のアイテムを作る。後日、誤操作で10番もアイテムマスター (Id:1)から作られたことになった。」

実際の問題①としては、では10番のアイテムを更新したとき、10番をもとに作られたアイテムマスター(Id:1)が更新され、そのアイテムマスターから作られたアイテム10番、11番、12番、13番のアイテムが更新されることになります

10番はそこで更新されているので、またアイテムマスター (Id:1) を更新します。 そのアイテムマスターから作られたアイテム10番、11番、12番、13番のアイテムが更新されます。10番が更新されたので…以下無限に続く。

という無限ループになってしまいます。

実際の問題②

実際の問題②として、ではアイテムマスターから生まれたアイテムをまたアイテムマスターにした時はどうでしょうか?実際の問題①を忘れて頂いて、初期状態になったとします。

「13番のアイテムもできがいいので、アイテムマスターにしよう。」

となった場合

「13番のアイテムをもとに、アイテムマスター(Id:2)を作る。アイテムマスター (Id:2) から、14番、15番のアイテムを作る。13番のアイテムは、アイテムマスター (Id:1)から作られており、 アイテムマスター (Id:1) はアイテム10番から作られている。」

こうなった場合、アイテム10番に更新をした場合、

「 アイテムマスター(Id:1)が更新される。 アイテムマスター(Id:1) から作られた11番、12番、13番が10番と同じ内容に更新される。13番が更新されたので、 アイテムマスター (Id:2) を更新する。 アイテムマスター (Id:2) から、14番、15番のアイテムを更新する。内容は、14番、15番は10番と同じ内容のアイテムになる。 」

ということになります。


この話は、絶対に循環参照がダメという話ではないです。やむを得ずこういう設計になってしまうこともあるでしょう(´ω`)。その場合、こういうリスクがありますよ。ということなので、上記の例でいえば

・絶対にアイテムマスターとアイテムは同じオブジェクトを参照しない

・アイテムマスターから作られたアイテムを、アイテムマスターにしない

などを気を付ければうまく使えるかもしれません。

複雑なオブジェクト イメージ
この世のものは全部複雑ですよねー。