kakakakakku blog

Weekly Tech Blog: Keep on Learning!

LocalStack を使って DynamoDB TTL 機能をローカル環境で試す

LocalStack は Amazon DynamoDB TTL (Time To Live) 機能をサポートしている❗️Amazon DynamoDB を実環境にデプロイする前に TTL の動作確認ができて便利👌最近 LocalStack の Amazon DynamoDB TTL 機能を試す機会があって,簡単にまとめておこうと思う〜 \( 'ω')/ LocalStack の Amazon DynamoDB で TTL 機能がサポートされていることは案外知られていなかったりすると思う.

docs.localstack.cloud

LocalStack の Amazon DynamoDB TTL 機能

LocalStack の Amazon DynamoDB TTL 機能を使うためには大きく2種類の選択肢がある📝

  1. 1時間ごとに削除する
  2. API を実行する

1. 1時間ごとに削除する

1時間ごとに削除する仕組みは Amazon DynamoDB TTL 機能の挙動に似ていて,期限切れになった後に非同期に削除される🗑実際の Amazon DynamoDB TTL 機能はドキュメントに 数日以内に削除 と書いてあって,LocalStack だと1時間ごとに削除されるという違いがある.

docs.aws.amazon.com

1時間ごとに削除する場合は環境変数 DYNAMODB_REMOVE_EXPIRED_ITEMS=1 を設定して LocalStack を起動する必要がある🚀

$ DYNAMODB_REMOVE_EXPIRED_ITEMS=1 localstack start -d

動作確認のために awslocal コマンドで Amazon DynamoDB テーブルを作る.今回は expired_at を Amazon DynamoDB TTL 機能の対象にする👌

$ awslocal dynamodb create-table \
    --table-name sandbox-ttl \
    --attribute-definitions AttributeName=id,AttributeType=S \
    --key-schema AttributeName=id,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST

$ awslocal dynamodb update-time-to-live \
    --table-name sandbox-ttl \
    --time-to-live-specification 'Enabled=true, AttributeName=expired_at'

そしてサンプルデータを投入する.今回は expired_at18:00 / 19:00 / 20:00 を設定しておく.

$ awslocal dynamodb put-item --table-name sandbox-ttl \
  --item '{ "id": { "S": "076c35eb-21bb-4f48-aa38-b27e513c0774" }, "title": { "S": "Title A" }, "expired_at": { "N": "1721466000" } }'
$ awslocal dynamodb put-item --table-name sandbox-ttl \
  --item '{ "id": { "S": "16244eb7-22ff-44c3-8411-17cc270fedf9" }, "title": { "S": "Title B" }, "expired_at": { "N": "1721469600" } }'
$ awslocal dynamodb put-item --table-name sandbox-ttl \
  --item '{ "id": { "S": "654a47a1-cd1c-42e8-9eff-0e5296442cb8" }, "title": { "S": "Title C" }, "expired_at": { "N": "1721473200" } }'
id title expired_at
076c35eb-21bb-4f48-aa38-b27e513c0774 Title A 1721466000(2024/07/20 18:00:00
16244eb7-22ff-44c3-8411-17cc270fedf9 Title B 1721469600(2024/07/20 19:00:00
654a47a1-cd1c-42e8-9eff-0e5296442cb8 Title C 1721473200(2024/07/20 20:00:00

サンプルデータ投入直後の状態を確認する.

$ awslocal dynamodb scan --table-name sandbox-ttl
{
    "Items": [
        {
            "title": {
                "S": "Title A"
            },
            "id": {
                "S": "076c35eb-21bb-4f48-aa38-b27e513c0774"
            },
            "expired_at": {
                "N": "1721466000"
            }
        },
        {
            "title": {
                "S": "Title C"
            },
            "id": {
                "S": "654a47a1-cd1c-42e8-9eff-0e5296442cb8"
            },
            "expired_at": {
                "N": "1721473200"
            }
        },
        {
            "title": {
                "S": "Title B"
            },
            "id": {
                "S": "16244eb7-22ff-44c3-8411-17cc270fedf9"
            },
            "expired_at": {
                "N": "1721469600"
            }
        }
    ],
    "Count": 3,
    "ScannedCount": 3,
    "ConsumedCapacity": null
}

1時間ほど待って,もう一度データを確認する.期待通りに Title A のデータが消えている👌

$ awslocal dynamodb scan --table-name sandbox-ttl
{
    "Items": [
        {
            "title": {
                "S": "Title C"
            },
            "id": {
                "S": "654a47a1-cd1c-42e8-9eff-0e5296442cb8"
            },
            "expired_at": {
                "N": "1721473200"
            }
        },
        {
            "title": {
                "S": "Title B"
            },
            "id": {
                "S": "16244eb7-22ff-44c3-8411-17cc270fedf9"
            },
            "expired_at": {
                "N": "1721469600"
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}

2. API を実行する

次に API を試す.LocalStack は一時的な動作確認で使うことも多く,個人的は1時間待つよりは API でサッと消せるのが便利だと思う❗️LocalStack には /_aws/dynamodb/expired という Amazon DynamoDB TTL 機能専用の API エンドポイントがある👌

同じサンプルデータのまま API を実行する.

$ curl -X DELETE localhost:4566/_aws/dynamodb/expired
{"ExpiredItems": 1}

データを確認すると,期待通りに Title B のデータが消えている👌

$ awslocal dynamodb scan --table-name sandbox-ttl
{
    "Items": [
        {
            "title": {
                "S": "Title C"
            },
            "id": {
                "S": "654a47a1-cd1c-42e8-9eff-0e5296442cb8"
            },
            "expired_at": {
                "N": "1721473200"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

その他

LocalStack ドキュメントを読んでいたらエンドポイントの表記に誤りがあって修正しておいた ✅

すぐに merge してもらえた〜 \( 'ω')/

github.com

GitHub Codespaces で起動した LocalStack に Resource Browser から接続する

GitHub Codespaces で起動した LocalStack に LocalStack Resource Browser から接続できる👌ただし LocalStack ポートを公開するためあくまで一時的な検証用途で使う前提として \( 'ω')/

LocalStack CLI セットアップ

ドキュメントを参考に GitHub Codespaces に LocalStack CLI をセットアップする.

$ curl --output localstack-cli-3.5.0-linux-amd64-onefile.tar.gz \
    --location https://github.com/localstack/localstack-cli/releases/download/v3.5.0/localstack-cli-3.5.0-linux-amd64-onefile.tar.gz
$ sudo tar xvzf localstack-cli-3.5.0-linux-*-onefile.tar.gz -C /usr/local/bin

docs.localstack.cloud

LocalStack 起動

LocalStack をデフォルトの 4566 ポートで起動する.

$ localstack start -d

ポート共有

GitHub Codespaces の 4566 ポートを公開する🌍

$ gh codespace ports visibility 4566:public

URL は https://<CODESPACE_NAME>-<PORT>.app.github.dev というフォーマットになる.

$ echo https://${CODESPACE_NAME}-4566.app.github.dev
https://xxx-xxxx-xxxxxxxx-xxxxxxxxxxxxxxx-4566.app.github.dev

docs.github.com

LocalStack Resource Browser 設定

LocalStack Resource Browser の Endpoint に取得した URL を設定すると接続できる👌

動作確認

awslocal コマンドをセットアップして,LocalStack 上に Amazon S3 バケット codespaces-sandbox をデプロイする.

$ awslocal s3api create-bucket \
    --bucket codespaces-sandbox \
    --create-bucket-configuration LocationConstraint=ap-northeast-1
{
    "Location": "http://codespaces-sandbox.s3.localhost.localstack.cloud:4566/"
}

LocalStack Resource Browser で Amazon S3 バケットを確認できた👌

お掃除

検証が終わったらお掃除をする.ポート共有をプラベートに戻して,LocalStack を停止しておく🛑

$ gh codespace ports visibility 4566:private
$ localstack stop

Amazon Inspector Lambda 標準スキャンで Lambda Layer に含まれる CVE を検出する

Amazon Inspector Lambda 標準スキャンを有効化すると「AWS Lambda 関数」「AWS Lambda Layer」を対象に脆弱性 (CVE) を検出できる🔐 Lambda Layer もサポートしていることを確認してみた👌

docs.aws.amazon.com

requests 2.30.0

今回は Python パッケージの requests を使う.requests 2.30.0 には Medium レベルの脆弱性 CVE-2023-32681 が含まれているため,今回は意図的に requests 2.30.0 を含んだ Lambda Layer を作っておく.AWS SAM を使えば Lambda Layer も簡単に作れる❗️

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  requests2300:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: requests-2-30-0
      ContentUri: requests-2.30.0/
      CompatibleRuntimes:
        - python3.12
    Metadata:
      BuildMethod: python3.12

pypi.org

Lambda Layer を追加した動作確認用の AWS Lambda 関数も作っておく.コードは以下のように requests を使って API(今回は random.dog)を呼び出すシンプルな実装にしておく🐕

import requests


def lambda_handler(event, context):
    print(requests.get('https://random.dog/woof.json').json())

動作確認

すると Amazon Inspector で CVE を検出できた❗️

標準スキャンから除外する

ドキュメントに書いてある通り,Amazon Inspector Lambda 標準スキャンの対象になるのは 過去90日間に呼び出された・または更新された AWS Lambda 関数になるため,日常的に実行している AWS Lambda 関数は基本的にすべて対象になる.もし除外する場合は InspectorExclusion: LambdaStandardScanning というタグを付ければ OK👌

人と向かい合おう!エンジニアリングマネジャーに限らずおすすめできる「エンジニアリングマネジャー入門」を読んだ

2024年7月14日に出版された新著「エンジニアリングマネジャー入門」を読んだ📕

本書は「人と向かい合う」ことにフォーカスしていて,エンジニアリングマネジャーがどんなことを日々考えて,どんなことに日々対処しているのかという実践的なノウハウがまとまっていた.著者が Google をはじめとした多くの組織で実践してきた体験談がベースになっているからこその説得力も感じられる一冊だった💡

本書は翻訳を担当された @iwashi86 さんに送っていただいた❗️活動量の多さと影響力の広さに驚きです🎉

出版おめでとうございます〜 \( 'ω')/

目次

本書は「計24種類」のトピックから構成されていて,一つ一つコンパクトにまとまってて読みやすかった📕

もし気になるトピックがあったら読んでみてもらえればと.

  • Part 1: 自分のチーム
    • Chapter 1: 自分のチームを大切にする
    • Chapter 2: 価値観の価値
    • Chapter 3: 信頼と弱さ
    • Chapter 4: 自分のチームは「彼ら」ではなく「私たち」
    • Chapter 5: 幸せとやる気の原動力
    • Chapter 6: 長期的な従業員のケア
    • Chapter 7: キャリアラダー
    • Chapter 8: 重要な1on1
  • Part 2: コラボレーション
    • Chapter 9: マネジャーとしてのコミュニケーション
    • Chapter 10: チェンジマネジメント
    • Chapter 11: フィードバックの与え方
    • Chapter 12: フィードバックを受け取る
    • Chapter 13: 良いミーティング
    • Chapter 14: 対立のマネジメント
    • Chapter 15: クロスチームとオープンソースのコラボレーション
  • Part 3: チームが最高の仕事をできるように支援する
    • Chapter 16: チームの仕事の優先度付け
    • Chapter 17: プルリクエストのスコープを絞る方法
    • Chapter 18: 実行の速度
    • Chapter 19: プロダクトとエンジニアリングの時間配分
  • Part 4: 自分の仕事
    • Chapter 20: ハイレベルでの優先度付け
    • Chapter 21: 日々の優先度付け
    • Chapter 22: 境界線を設定する
    • Chapter 23: まず自分を大切にすること
    • Chapter 24: 自分を信じること

pub.jmam.co.jp

エンジニアリングマネジャーに限らず読もう

本書を読んでまず感じたのは「書籍タイトルを見て自分には関係ないな〜と思わず一度読んでみるべきでは!?」ということだった😀

僕自身が過去にエンジニアリングマネジャー的なポジションを兼務していた時期があったり,そもそもポジションに関係なく,たとえ IC (Individual Contributor) だとしても組織や人に目を向けることが当たり前だと思っていることも関係するけど(全員リーダー❗️),エンジニアリングマネジャーに限らず読んで欲しいな〜と思った \( 'ω')/

目指すキャリアパスにエンジニアリングマネジャーという選択肢がなかったとしても,エンジニアリングマネジャーが日々こういうことを考えているんだな〜と知っておくことは重要だし,今こういう行動をしたらきっとエンジニアリングマネジャーは喜んでくれるだろうな〜💕というある意味 "打算的な" 振る舞いもできたりすると思う.

そして比較的ジュニアなエンジニアにも本書をおすすめしたく,キャリアを進めていく人たちはこういうことを考えているんだよ〜という気付きになると思う.視野が広がってもっと働きやすくなりそう💪

訳者あとがきが良かった

本書を読み終えて,どの Chapter も良かったけど,特に好きな Chapter はどこかな〜と考えたら以下の3つになった💡自分自身も意識しているし,改めて勉強になった Chapter だった.

  • Chapter 3: 信頼と弱さ
  • Chapter 8: 重要な1on1
  • Chapter 14: 対立のマネジメント

Chapter 以外だと,本書の最後にある「訳者あとがき」がたった3ページながらとても良かった👏本書の特徴を紹介しつつ,同じエンジニアリングマネジャーというテーマながら方向性の異なる「エレガントパズル」との比較も紹介されていて,併読したくなった.また本書を読んで今すぐピンと来なくてもまた読み直してくれれば良いよ〜という実践と読書の融合を推奨している流れも良かった❗️

エレガントパズルまだ読んでなくてゴメンナサイ🙏

フィードバックは僕自身の改善ポイント

Chapter 11 と Chapter 12 は「フィードバック」がトピックになっていて,僕自身はフィードバックを与えることに少し苦手意識があるため,特に Chapter 11 は数回読み直した.

本書に載っている価値観ワークのサンプルとして「優しいフィードバックを受けたい価値観」と「率直なフィードバックを好む価値観」という例が載っていた.僕自身は率直なフィードバックを求めていて,回りくどいフィードバックを嫌う傾向にあるため,相手にも「良かれと思って」フィードバックをするけど,受ける側の心の状態が整ってなく受け入れてもらえなかったという経験もあり,過去を反省しながら読んだ.フィードバックをしたりされたりすることは誰しもあるし,やはりエンジニアリングマネジャーに限らず読むべきポイントがまとまってるな〜と感じた.

引用されていた書籍「あなたを成長させるフィードバックの授業」は読んでみよう📕

誤植など

  • P.91 車用社用(文脈的に間違ってるかな?と感じた) in a car で適切とのことでした🙏
  • P.116 あなた今からあなたは今から
  • P.126 できるたけできるだけ
  • P.201 不適切かしれませんが不適切かもしれませんが

まとめ

またイイ一冊に出会えた❗️

「エンジニアリングマネジャー入門」おすすめです〜 \( 'ω')/

Powertools for AWS Lambda (Python) の Feature flags で「時間ベースフィーチャーフラグ」を試す

Powertools for AWS Lambda (Python) の Feature flags を使って「時間ベースフィーチャーフラグ (Time based feature flags)」を試してみた.Powertools for AWS Lambda (Python) の Feature flags はデフォルトだと AWS AppConfig をバックエンドにしていて,時間ベース以外に文字列部分一致・数値範囲など柔軟に条件をしてフラグ制御 (true or false) ができる👌

docs.powertools.aws.dev

検証環境

アプリケーション (AWS Lambda) とフィーチャーフラグ (AWS AppConfig) は運用上ライフサイクルが異なるため別のスタックにした.

├── application
│   ├── src
│   │   └── app.py
│   └── template.yaml
└── features
    └── template.yaml

1. features

まず AWS AppConfig をデプロイする.Powertools ドキュメントを参考に AWS CloudFormation テンプレートを書いた.ちなみに Powertools ドキュメントに載っているサンプルだと AWS AppConfig のデプロイ戦略にビルトインの AppConfig.AllAtOnce が指定されていた📝これは即時デプロイをしたら10分間待つ戦略で (BakeTime 10 minutes) 今回はサクサクと検証をしたく,BakeTime なしのカスタムデプロイ戦略を追加した💡

そして HostedConfigurationVersion に時間ベースフィーチャーフラグ (Time based feature flags) を設定した.今回はサンプルとして開始時間と終了時間が決まっているキャンペーン(campaign-acampaign-b)の制御をする❗️

  • campaign-a: 2024-07-06T12:00:00 ~ 2024-07-06T12:29:59
  • campaign-b: 2024-07-06T12:30:00 ~ 2024-07-06T12:59:59

ちなみに時間ベースだと,他には「毎日◯時〜◯時」「毎週◯曜日」という設定もできる📅

AWSTemplateFormatVersion: 2010-09-09

Resources:
  Application:
    Type: AWS::AppConfig::Application
    Properties:
      Name: sandbox
  Environment:
    Type: AWS::AppConfig::Environment
    Properties:
      Name: prd
      ApplicationId: !Ref Application
  ConfigurationProfile:
    Type: AWS::AppConfig::ConfigurationProfile
    Properties:
      Name: profile
      ApplicationId: !Ref Application
      LocationUri: hosted
  HostedConfigurationVersion:
    Type: AWS::AppConfig::HostedConfigurationVersion
    Properties:
      ApplicationId: !Ref Application
      ConfigurationProfileId: !Ref ConfigurationProfile
      ContentType: application/json
      Content: |
        {
          "campaign-a": {
            "default": false,
            "rules": {
              "Enable Campaign A": {
                "when_match": true,
                "conditions": [
                  {
                    "action": "SCHEDULE_BETWEEN_DATETIME_RANGE",
                    "key": "CURRENT_DATETIME",
                    "value": {
                      "START": "2024-07-06T12:00:00",
                      "END": "2024-07-06T12:29:59",
                      "TIMEZONE": "Asia/Tokyo"
                    }
                  }
                ]
              }
            }
          },
          "campaign-b": {
            "default": false,
            "rules": {
              "Enable Campaign B": {
                "when_match": true,
                "conditions": [
                  {
                    "action": "SCHEDULE_BETWEEN_DATETIME_RANGE",
                    "key": "CURRENT_DATETIME",
                    "value": {
                      "START": "2024-07-06T12:30:00",
                      "END": "2024-07-06T12:59:59",
                      "TIMEZONE": "Asia/Tokyo"
                    }
                  }
                ]
              }
            }
          }
        }
  DeploymentStrategy:
    Type: AWS::AppConfig::DeploymentStrategy
    Properties:
      Name: Custom.AllAtOnce
      DeploymentDurationInMinutes: 0
      GrowthFactor: 100
      FinalBakeTimeInMinutes: 0
      ReplicateTo: NONE
  Deployment:
    Type: AWS::AppConfig::Deployment
    Properties:
      ApplicationId: !Ref Application
      EnvironmentId: !Ref Environment
      ConfigurationProfileId: !Ref ConfigurationProfile
      ConfigurationVersion: !Ref HostedConfigurationVersion
      DeploymentStrategyId: !Ref DeploymentStrategy

ちなみに AWS AppConfig 自体のフィーチャーフラグとは関係なく,AWS AppConfig をバックエンドにした Powertools for AWS Lambda (Python) の仕組みになる.コンソールだと 自由形式の設定プロファイル となる.少しややこしい😇

2. application

次は AWS Lambda をデプロイする.AWS SAM テンプレートで Powertools Layer を設定しておく.あと IAM Role に AWS AppConfig を操作する権限を付けておく.Powertools ドキュメントには appconfig:GetLatestConfigurationappconfig:StartConfigurationSession で十分と書いてあった✅

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  Function:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: powertools-feature-flags-time-based
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.12
      Architectures:
        - x86_64
      Layers:
        - arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:75
      Role: arn:aws:iam::000000000000:role/xxxxx

コードでは Powertools for AWS Lambda (Python) の Feature flags で evaluate() を実行して,結果を表示するシンプルな実装にした👌

from aws_lambda_powertools.utilities.feature_flags import AppConfigStore, FeatureFlags

app_config = AppConfigStore(application='sandbox', environment='prd', name='profile')
feature_flags = FeatureFlags(store=app_config)


def lambda_handler(event, context):
    campaign_a = feature_flags.evaluate(name='campaign-a', default=False)
    campaign_b = feature_flags.evaluate(name='campaign-b', default=False)

    features = {
        'campaign-a': campaign_a,
        'campaign-b': campaign_b,
    }

    print(features)

動作確認

計4回実行してみた🕐

期待通りの結果になったー \( 'ω')/

👇 11:50
{'campaign-a': False, 'campaign-b': False}
👇 12:10
{'campaign-a': True, 'campaign-b': False}
👇 12:40
{'campaign-a': False, 'campaign-b': True}
👇 13:10
{'campaign-a': False, 'campaign-b': False}

フィーチャーフラグのキャッシュ機能

Powertools for AWS Lambda (Python) の Feature flags は毎回 AWS AppConfig から取得するのではなく,デフォルトでは「5秒間」キャッシュされる📂AWS Lambda 関数のライフサイクルとして,初期化後にもフィーチャーフラグを更新できる👌

docs.aws.amazon.com

AWS AppConfig の呼び出し回数を減らすこととフィーチャーフラグの鮮度を高めることのトレードオフにはなるけど,もしキャッシュ時間を変更する場合は AppConfigStore で初期化するときに max_age を設定して変更できる.

app_config = AppConfigStore(application='sandbox', environment='prd', name='profile', max_age=30)

単体テスト

Powertools ドキュメントには pytest-mock を使って AWS AppConfig に依存せずテストをするサンプル実装が載っていた.LocalStack も AWS AppConfig をサポートしているけど,LocalStack Pro で使えるため,契約が必要になってしまう💨

docs.localstack.cloud

関連記事

kakakakakku.hatenablog.com