継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

S3 バケット内オブジェクト削除のライフサイクルをタグでコントロールする

タダです.

S3 に格納しているオブジェクトの中で,タグをつけたものをライフサイクルルールで削除したいという要望が業務でありました.S3 のライフサイクルでオブジェクトを削除する時にバケット全体のオブジェクトへ適用することは経験があったのですが,ライフサイクルルールをタグ指定でやるのは初だったのでこの記事で設定をまとめていきます.

ライフサイクル の設定

消したいオブジェクトは S3 にアップされる時に delete:true というタグをつけてもらうようにしていたので,タグで制御するようにルールを作りました.また,オブジェクトが削除するのを1日ごとにするためのライフサイクルールのアクションを定義しました.

f:id:sadayoshi_tada:20210818221147p:plain f:id:sadayoshi_tada:20210818221345p:plain

なお,ライフサイクルの考え方としてはドキュメントに記載があり,オブジェクト作成してからの日数で計算されてアクションが実行される点が注意でオブジェクトが S3 上にできてから1日経過後の日本時刻に消えない!っていうのは自分も勘違いしていました.

Amazon S3 は、ルールに指定された日数をオブジェクトの作成時間に加算し、得られた日時を翌日の午前 00:00 UTC (協定世界時) に丸めることで、時間を算出します。たとえば、あるオブジェクトが 2014 年 1 月 15 日午前 10 時 30 分 (UTC) に作成され、移行ルールに 3 日と指定した場合、オブジェクトの移行日は 2014 年 1 月 19 日 0 時 0 分 (UTC) となります。

docs.aws.amazon.com

実際にアップロードしたオブジェクトの管理概要の項目を見ると,8/18 PM 08:38に作られたものだから1日加算し,かつその日時から翌日の午前 00:00 UTC (協定世界時)に丸めた時間(JST だと +9時間後)が有効期限日で表示されるようになりました.

f:id:sadayoshi_tada:20210818221823p:plain

ライフサイクルの実行確認

自分が確認する限りですが,指定の時間ぴったりになくならずラグが多少あるなと思いました.とはいえ,指定したタグのオブジェクトで削除されることが確認できました.

まとめ

S3 のライフサイクルルールを,タグをつけたオブジェクトに対して実行する設定を試したので記事にしました.特定のファイルだけ削除したいといった用途に使えており,柔軟なファイル操作をマネージドに任せられて助かっています.同じような事象に悩む方の参考になれば嬉しいです!

Step Functions の Map を使って ECS Fargate の並列処理を構築した

タダです.

バッチ処理の中でこれまでは AWS Batch 1台のコンテナで完結していた処理があったのですが,処理時間が非常に長くかかってしまう部分があり,その処理を外出しして並列処理ができるよう Step Functions の Map を使ってみました.この記事で要点をまとめていければと思います.

docs.aws.amazon.com

構成イメージ

構成イメージは下記のものになります.ECS Fargate の起動とパラメーターを渡す Lambda と Map を使って多数の ECS Fargate が起動するため各コンテナのジョブステータスをポーリングする Lambda が並列処理の成功と失敗を判定します.Fargate は起動後インプットファイルの取得とアウトプットの出力で S3 バケットを使うような動きをします.なお,Fargate は Fargate Spot で起動させています.

f:id:sadayoshi_tada:20210809214619p:plain

Step Functions の 定義

Step Functions の定義は下記のような ASL を設定しました.最初の Lambda で ECS Fargate を起動して次の Lambda で ECS Fargate のタスク実行状況をポーリングして成功か失敗かを判定する動作をします.60秒待機した後,再度ポーリングし,ジョブが終了していた場合,成功か失敗かを判定するような形です.

ASL 定義

{
    "Comment": "Processing ECS Fargate",
    "StartAt": "test",
    "States": {
      "test": {
        "Type": "Map",
        "End": true,
        "Iterator": {
          "StartAt": "Lambda Invoke",
          "States": {
            "Lambda Invoke": {
              "Type": "Task",
              "Resource": "arn:aws:states:::lambda:invoke",
              "OutputPath": "$.Payload",
              "Parameters": {
                "Payload.$": "$",
                "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:run-sfn-execute"
              },
              "Retry": [
                {
                  "ErrorEquals": [
                    "Lambda.ServiceException",
                    "Lambda.AWSLambdaException",
                    "Lambda.SdkClientException"
                  ],
                  "IntervalSeconds": 2,
                  "MaxAttempts": 6,
                  "BackoffRate": 2
                }
              ],
              "Next": "status Lambda Invoke"
            },
            "status Lambda Invoke": {
              "Type": "Task",
              "Resource": "arn:aws:states:::lambda:invoke",
              "ResultPath": "$.status",
              "Parameters": {
                "Payload.$": "$",
                "FunctionName": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:job-polling"
              },
              "Retry": [
                {
                  "ErrorEquals": [
                    "Lambda.ServiceException",
                    "Lambda.AWSLambdaException",
                    "Lambda.SdkClientException"
                  ],
                  "IntervalSeconds": 2,
                  "MaxAttempts": 6,
                  "BackoffRate": 2
                }
              ],
              "Next": "Job Complete Check"
            },
            "Job Complete Check": {
              "Type": "Choice",
              "Choices": [
                {
                  "Variable": "$.status.Payload",
                  "StringEquals": "FAILLED",
                  "Next": "Notify Fail"
                },
                {
                  "Variable": "$.status.Payload",
                  "StringEquals": "SUCCEEDED",
                  "Next": "Notify Success"
                }
              ],
              "Default": "Wait 60 Seconds"
            },
            "Wait 60 Seconds": {
              "Type": "Wait",
              "Seconds": 60,
              "Next": "status Lambda Invoke"
            },
            "Notify Success": {
              "Type": "Pass",
              "Result": "Success",
              "End": true
            },
            "Notify Fail": {
              "Type": "Pass",
              "Result": "Fail",
              "End": true
            }
          }
        },
        "ItemsPath": "$.parameter",
        "Parameters": {
          "hoge.$": "$.fuga",
          "s3_bucket.$": "$.s3_bucket"
        }
      }
    }
  }

ワークフロー の処理結果確認

ワークフローの処理結果を確認すると以下のようなイメージで遷移し,期待通り動作していることを確認できました.

ワークフロー動作イメージ

f:id:sadayoshi_tada:20210809215552p:plain

注意点

一点だけ使っていて注意点があったのですが,Map の同時実行数が40以上を超えるとMaxConcurrencyの注記にもあるように一気に処理しない動作でした.ただ,一気に処理しきれない分も失敗にならず順次実行されるのでこの点を認識していれば問題ないのかなという感想を持ちました.

同時反復は制限される場合があります。この現象が発生すると、一部の反復は前の反復が完了するまで開始されません。入力配列に40項目を超えると、この問題が発生する可能性が高くなります。

docs.aws.amazon.com

40以上実行した場合の処理イメージ(第1弾の処理)

f:id:sadayoshi_tada:20210809221833p:plain

40以上実行した場合の処理イメージ(第2弾の処理)

f:id:sadayoshi_tada:20210809221843p:plain

40以上実行した場合の処理イメージ(最終結果)

f:id:sadayoshi_tada:20210809221852p:plain

まとめ

Step Functions の Map を使った ECS Fargate の並列処理ワークフロー を構築した話をまとめました.この変更により元々1台のサーバーで全処理をやろうとして4時間以上かかっていた処理だったのですが,外出しして約20分まで時間を短縮するのに寄与しており並列処理の選択肢として有効に効かせられたと思います.同じような課題にあたらている方の参考になれば嬉しいです!

Aurora レプリカ Auto Scaling のイベントをトリガーに Lambda を発火する

タダです.

Aurora レプリカ Auto Scaling で追加されたイベントをトリガーに EC2 の Apache を再起動(graceful)を実行したいと思い,Lambda を発火させるようにしてみたのでこの記事でまとめていきます.

経緯

Aurora レプリカ Auto Scaling で増えたインスタンスに対して接続数が増えない事象がありました.読み込みエンドポイントを使っていれば,負荷が分散されるはずなのにうまく Auto Scaling を活用できていませんでした.

Aurora Auto Scaling から益を得るには、お使いのアプリケーションが新しい Aurora レプリカへの接続をサポートしている必要があります。これを行うには、Aurora 読み込みエンドポイントを使用することをお勧めします。

増えたレプリカに負荷が分散されてないイメージ f:id:sadayoshi_tada:20210803091010p:plain

調査していたところ Apache を載せた EC2 からの接続が増える前のレプリカに繋いだままになっていました.Apache を再起動(gracefule)してみたところ Auto Scaling で増えたレプリカにも接続が増えたため,Auto Scaling を有効活用を目的にした Aurora レプリカ Auto Scaling のイベントをトリガーに Apache 再起動(graceful)をする Lambda を作ることにしました.

実装

EventBridge のルール

Auto Scaling のイベントを拾うための EventBridge のルールを以下の定義で設定しました.application-autoscaling-のプレフィクスがついたリソースが作成・削除されたらイベントを拾うようにしており,このイベントをトリガーに SNS -> Chatbot で通知が来るかをまず確認したところ期待通りの通知が来ました.なお,この設定に至るまでに AWS サポートの方々にご協力いただきました,ありがとうございました!🙇‍♂️

{
  "source": ["aws.rds"],
  "detail": {
    "EventCategories": ["creation", "deletion"],
    "SourceIdentifier": [{
      "prefix": "application-autoscaling-"
    }]
  }
}

Chatbot で通知されたイベント f:id:sadayoshi_tada:20210803071628p:plain f:id:sadayoshi_tada:20210803071652p:plain

EventBridge で意図したイベントを拾うことができたので,次に Lambda のコードを書いていきます.

Lambda のコード

Lambda のコードとして次のようなコードを Python 3.8 で書いていきました.Nameタグでhogeとついた EC2 に対して SSM RunCommand 経由でsystemctl reload httpdを実行するようなコードにしました.当然ですが,IAM ロールに SSM RunCommand を実行する権限をつけておきます.

コード例

import boto3
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)
ssm = boto3.client('ssm')

def lambda_handler(event, context):
    try:
        logger.info('Start RunCommand Execution')
        response = ssm.send_command(
            Targets = [{
                'Key':'tag:Name',
                'Values': [
                    'hoge'
                ]
            }],
            DocumentName='AWS-RunShellScript',
            Parameters = {
                'commands': [
                    'sudo su -',
                    'systemctl reload httpd'
                ],
                'executionTimeout': ['3600']
            }
        )
        logger.info('Function complete')
        return "Complete."

Aurora Auto Scaling イベント発生時の Lambda 動作確認

スケジュールの Auto Scaling でレプリカを2台に増やす設定を入れてイベント発火の動作確認します.Read replica(s) application-autoscaling-4d16462e-0d83-44cc-8c27-2bbc9e06779f were successfully added by rds.のイベントがログに記録され,Aurora レプリカが追加されたことを確認できました.

f:id:sadayoshi_tada:20210803084348p:plain

同時刻に Lambda が発火されていることをログより確認できましたので意図通りの動作確認ができました.

START RequestId: 88dbdbb0-5909-4360-8298-1e6318335067 Version: $LATEST
[INFO]  2021-08-02T07:57:42.810Z    88dbdbb0-5909-4360-8298-1e6318335067    Start RunCommand Execution
[INFO]  2021-08-02T07:57:43.110Z    88dbdbb0-5909-4360-8298-1e6318335067    Function complete
END RequestId: 88dbdbb0-5909-4360-8298-1e6318335067

まとめ

Aurora レプリカ Auto Scaling イベント時に発火する Lambda の コードと EventBridge の設定をまとめてみました.Aurora レプリカ Auto Scaling をスケジュールで使っていてスケールアウトはすぐだけどスケールインがタイミングがわからず困っていたので,EventBridge で拾ってイベント駆動の仕組み化できたのはよかったです.同じような悩みを持つ方の参考になれば嬉しいです.

redash のコンテナログを CloudWatch Logs に出力する

タダです.

redash を EC2 上のコンテナで動かしているのですが,ログを外出ししていなかったので何かあったらいちいちサーバーの中に入る必要がありました.そこで,CloudWatch Logs に出して見えた方がいいと思い,検証したのでこの記事でまとめていきます.なお,redash のバージョンは8.0.0.b32245の環境で確認しています.

事前準備

事前準備として EC2 の IAM ロールの権限に CloudWatch Logs へのアクセス権限をつけていきます.ドキュメント に沿って次のポリシーをつけました.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

次に,ログを保管する CloudWatch Logs のロググループを作っておきます.今回は /var/lib/docker/redash/logs というのを作りました.以上で事前準備が完了です.

redash の設定

続いてコンテナ側の CloudWatch Logs 設定を定義していきます.コンテナのログは/var/lib/docker/containars/[コンテナ ID]/配下にでており,EC2 のログとは異なりコンテナのログは awslogsロギングドライバを使って出力されているログを格納するので CloudWatch Agent の導入は不要です.redash のコンテナはdocker-compose.ymlで管理されているので,ドキュメントに沿って定義を追加します.今回は4つのコンテナのログを CloudWatch Logs に出力します.

  • redash_server_1
  • redash_scheduled_worker_1
  • redash_scheduler_1
  • redash_adhoc_worker_1

servicesセクション以下で,loggingセクションを追加し,CloudWatch Logs 設定を記載します.awslogs-region でリージョン,awslogs-groupでロググループ名,tagでログストリーム名を指定しています.tagでの注意点として CloudWatch Logs APIがストリーム名に:をサポートしていないため,Docker イメージがalpine:latestな場合 {{ .ImageName }}tagで指定するとエラーになります.今回のtagではredash/redash_8.0.0.b32245-コンテナ名-コンテナ IDというログストリーム名になるよう指定しています.

version: "2"
x-redash-service: &redash-service
  image: redash/redash:8.0.0.b32245
  depends_on:
    - postgres
    - redis
  env_file: /opt/redash/env
  restart: always
services:
  server:
    <<: *redash-service
    command: server
    ports:
      - "5000:5000"
    environment:
      REDASH_WEB_WORKERS: 4
      GUNICORN_CMD_ARGS: "--timeout 90"
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: /var/lib/docker/redash/logs
        tag: '{{ with split .ImageName ":" }}{{join . "_"}}{{end}}-{{.Name}}-{{.ID}}'
  scheduler:
    <<: *redash-service
    command: scheduler
    environment:
      QUEUES: "celery"
      WORKERS_COUNT: 1
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: /var/lib/docker/redash/logs
        tag: '{{ with split .ImageName ":" }}{{join . "_"}}{{end}}-{{.Name}}-{{.ID}}'
  scheduled_worker:
    <<: *redash-service
    command: worker
    environment:
      QUEUES: "scheduled_queries,schemas"
      WORKERS_COUNT: 1
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: /var/lib/docker/redash/logs
        tag: '{{ with split .ImageName ":" }}{{join . "_"}}{{end}}-{{.Name}}-{{.ID}}'
  adhoc_worker:
    <<: *redash-service
    command: worker
    environment:
      QUEUES: "queries"
      WORKERS_COUNT: 2
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: /var/lib/docker/redash/logs
        tag: '{{ with split .ImageName ":" }}{{join . "_"}}{{end}}-{{.Name}}-{{.ID}}'
  redis:
    image: redis:5.0-alpine
    restart: always
  postgres:
    image: postgres:9.6-alpine
    env_file: /opt/redash/env
    volumes:
      - /opt/redash/postgres-data:/var/lib/postgresql/data
    restart: always
  nginx:
    image: redash/nginx:latest
    ports:
      - "80:80"
    depends_on:
      - server
    links:
      - server:redash
    restart: always

docker-compose up -d で再起動します.CloudWatch Logs の画面を確認すると4つのログストリームができているので意図通りです.

# docker-compose up -d
redash_postgres_1 is up-to-date
redash_redis_1 is up-to-date
Recreating redash_server_1           ... done
Recreating redash_scheduled_worker_1 ... done
Recreating redash_adhoc_worker_1     ... done
Recreating redash_scheduler_1        ... done
Recreating redash_nginx_1            ... done

CloudWatch Logs の画面 f:id:sadayoshi_tada:20210730065524p:plain

まとめ

EC2 上に出ていた redash のコンテナログを CloudWatch Logs に出してみました.EC2 のサーバーログを出すより簡単にログを出すことができてログを確認する時は AWS コンソールか VSCode などから確認できるので手間が減るのでありがたい機能です.

July Tech Festa 2021 に『今日から実践!継続的に自分の軌跡を残す方法』で登壇した

タダです.

7/18 開催の「July Tech Festa 2021」にて「今日から実践!継続的に自分の軌跡を残す方法」と題して自分が取り組んできた継続的アウトプットのやり方や取り組んだことで起きた変化をお話しさせていただきました.この記事で発表を振り返ってきます.

techfesta.connpass.com

発表資料

発表資料はこちらです.

発表動画

発表動画はこちらです.

www.youtube.com

当日の発表はYouTubeアーカイブされてるので他のセッションも見えてなくて残念...と思っている方は是非登録することをオススメします.

www.youtube.com

他のセッションの発表資料も connpass の資料一覧から見ることができます.

techfesta.connpass.com

発表の振り返り

JTF への登壇は今年の登壇に続いて2回目でした.オンラインの登壇も何回か経験させてもらっていたので,緊張はそこまでしてなかったような感覚だったのですが,発表では結構カミカミでしたし,早口になっていたりする部分もあって聞きづらいところあったら申し訳ないです🙇‍♂️

さて,発表の補足をしていきます.習慣化の1つ目の取り組みで続けやすい自分ルールを作るっていうので AWS 公式ブログでアップデートをサマリーするっていうのは習慣化のきっかけにはなったのですが,週1に必ず更新するっていう強い意思が欠如していた時があり,今週は更新しなくていいだろとか今週はアップデートまとめられなかったけど来週まとめよみたいなだらけていた時期もありました.この点,カックさんから教わった,記事更新のペースにおける例外を設けないというアンチパターンになっていたかなと思うのでもし発表見られてやってみたいと思う方いらしたらお気をつけください.

発表でも触れましたが,Zenn とはてなブログの使い分けが結構カミカミだったので改めて補足します.発表後の質問でもいただきましたが,Zenn は GitHub に記事をあげれば自動で公開してくれるのが体験としてよくて,検証や調査したトピックを Zenn に上げて,一通りメモを整理してはてなブログに書くのが効率も良く一度 Zenn にまとめているから記事も構成しやすくてお勧めです.

いくつか継続するためにやってきたことをお話しさせてもらったのですが,継続のモチベーションになってるのはアウトプットを楽しめるのが大事になってくるのかなと思います.ブログを良くしたくてブログのことを考える時間が増えて,考えてきたことを実践してうまくいったり,うまくいかなかったり試行錯誤しながら続けていくとブログが愛らしくなってきますw また,自分にとっての楽しさの中に人との出会いが大きくあってアウトプットを通じて出会えた方々はありがたい関係だと思うのでもっと出会い増やしたいみたいなモチベーションがあります.なので,発表でお話ししたいくつかの取り組みとは別でアウトプットすることの楽しさって自分はどこにあるっけ?を考えてみるのもいいかもしれません.

関連記事

sadayoshi-tada.hatenablog.com sadayoshi-tada.hatenablog.com

まとめ

登壇の振り返りとして補足を書いてみました.Twitter のコメントをみさせてもらったところ,ブログを書いている方が多くて嬉しかったのと発表後にメンションいただいた方で感想をくださったので励みになりました.ブログを楽しんで書く人が増えるきっかけに少しでもなれば嬉しいです!発表の機会をいただきありがとうございました!!