🚀

【AWS】ZennのトレンドをSlackに流すBotをLambda + Serverless Frameworkで作ってみた

に公開

はじめに

Zenn に記事を書く回数が増えてきて、「どうすればトレンドに入りやすいんだろう?」と気になるようになりました。

毎日欠かさずチェックできるよう、Slackに自動通知されるBotを作ることにしました。

Slack には RSS を購読する標準の機能がありますが、今回は AWS サーバーレスアーキテクチャの学習を目的として、Lambda + Serverless Framework v4 で実装しました。
Lambda 中心の小さな構成なので、設定がシンプルな Serverless Framework を選択しています。
DynamoDB で通知履歴を管理し、EventBridge で定期実行する仕組みです。

この記事では、Bot の構成と実装のポイント、そして実際に作ってみて得られた学びをまとめていきます。
あくまで個人用に作った小さな仕組みなので、「こんな感じで作れるんだな」というサンプルとして読んでもらえるとうれしいです。

Serverless Framework とは

Serverless Framework は、サーバーレスアプリケーションを簡単に開発・デプロイするためのオープンソースツールです。
YAML ファイル(serverless.yml)の設定だけで、関数の定義、イベント(トリガー)、関連するリソースなどを一元管理し、まとめてデプロイできます。


https://www.serverless.com/

全体構成と完成イメージ

できること

  • 決まった時間に Zenn のトレンド記事が届く
  • 一度通知された記事は届かないので、新着だけを追える

システム構成

全体の構成は次のとおりです。

各要素の役割を整理すると、以下のようになります。

サービス 役割
EventBridge Lambda を定期実行(今回は1日3回:9時、12時、18時)
Lambda(Ruby) RSS 取得 → 通知済チェック → Slack 通知
SSM Parameter Store 設定値と Webhook URL を安全に保管
DynamoDB 通知済み記事を記録し、重複を防止
Slack Incoming Webhook 通知の送信先

動作環境

  • Ruby ランタイム: AWS Lambda ruby3.3
  • Serverless Framework: v4 系
  • 実行環境: AWS Lambda(ap-northeast-1)

Lambda の処理の流れ

Lambda がどのように記事を取得して通知するかを、順を追って見ていきます。

Lambda 起動時に、SSM Parameter Store から設定と Webhook URL を読み込みます(詳細は後述)。その後、以下の流れで記事の取得と通知を行います。

Zenn トレンド記事の取得

RSS フィードから記事を取得し、タイトル・URL・公開日時を Article オブジェクトに格納します。

fetched = @rss_client.fetch_all(config.feeds)

parsed_articles = fetched.flat_map do |feed|
  @rss_parser.parse(feed_name: feed[:name], xml: feed[:xml]).map do |item|
    Article.new(
      title: item.title,
      url: item.link,
      published_at: item.published_at,
      feed_name: item.feed_name
    )
  end
end

通知済みチェック

DynamoDB に保存された通知履歴をもとに、既に通知済みの記事を除外します。

各記事の URL を ID として、DynamoDB に存在するかチェックします。通知済みの記事を除外することで、未通知記事だけが残ります。

Slack への通知

未通知記事が 0 件なら何もせず終了します。1 件以上ある場合、記事を 1 つのメッセージにまとめて通知します。

メッセージは以下のような形式です:

Zennトレンド(N件)です

- タイトル1
  https://zenn.dev/xxxx

- タイトル2
  https://zenn.dev/yyyy

Slack の Incoming Webhook に JSON で POST することで、指定チャンネルに投稿されます。

通知履歴の保存

Slack に通知した記事を DynamoDB に保存します。次回以降の実行時に、この履歴をもとに重複通知を防ぎます。

各記事の URL、タイトル、通知日時を保存します。これで 1 回の実行サイクルが完了です。

SSM Parameter Store の設定

AWS Systems Manager(SSM)の Parameter Store は、設定情報を安全に管理できるサービスです。

今回は、RSS フィードの一覧と Slack Webhook URL を SSM に保存し、Lambda 起動時に読み込むようにしています。

SSM を使う理由

SSM Parameter Store を使うことで、以下のメリットがあります:

  • セキュリティ:Webhook URL を SecureString で暗号化して保存できる
  • 変更の容易さ:通知対象のフィードを変更する際、Lambda を再デプロイせずに SSM の値を更新するだけで済む

保存するパラメータ

SSM には2つのパラメータを保存します。

1. RSS フィードの設定

RSS フィードの一覧を JSON 形式で保存します:

{
  "feeds": [
    { "name": "trend", "url": "https://zenn.dev/feed" }
  ]
}

トレンド全体(https://zenn.dev/feed)だけでなく、特定トピック(例:https://zenn.dev/topics/aws/feed)に変更することも可能です。SSM の JSON を更新するだけで、通知対象を切り替えられます。

2. Slack Webhook URL

以下のURLにアクセスし、「Create an App」→「From scratch」→ アプリ名とワークスペースを指定、で作成できます。

https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/

取得したWebhook URL は Parameter StoreにSecureString として暗号化して保存します。これにより、秘密情報を安全に管理できます。

Lambda での読み込み

Lambda では、環境変数から SSM のパラメータパスを取得し、Aws::SSM::Client で値を読み込みます。

config_param_name = ENV.fetch('CONFIG_PARAM_NAME')
slack_param_name  = ENV.fetch('SLACK_WEBHOOK_PARAM_NAME')

config_json = get_parameter(config_param_name)
slack_webhook_url = get_parameter(slack_param_name, with_decryption: true)

parsed = JSON.parse(config_json)

Config.new(
  feeds: parsed['feeds'] || [],
  slack_webhook_url: slack_webhook_url
)

with_decryption: true を指定することで、SecureString として暗号化された Webhook URL を復号化して取得できます。

通知履歴テーブルの設定

通知済み記事を管理するために、DynamoDB に単一のテーブルを用意します。

テーブル構成

  • テーブル名zenn_trend_articles_${stage}
  • パーティションキーarticle_id(記事の URL)
  • 課金モード:オンデマンド(PAY_PER_REQUEST
  • その他:TTL、GSI なし

パーティションキーとは、DynamoDB で各アイテムを一意に識別するためのキーです。データの保存場所を決定し、アイテムの検索に使用されます。今回は記事の URL を article_id として使用しています。

保存するデータ

各記事について、以下の情報を保存します。

  • article_id(パーティションキー):記事の URL
  • title:記事のタイトル
  • notified_at:通知日時

テーブル定義

serverless.yml では、以下のように定義します。

resources:
  Resources:
    ZennTrendArticlesTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: zenn_trend_articles_${self:provider.stage}
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: article_id
            AttributeType: S
        KeySchema:
          - AttributeName: article_id
            KeyType: HASH

Lambda からのアクセス

Lambda では、環境変数からテーブル名を取得してアクセスします。

repo = Repository.new(table_name: ENV['ARTICLE_TABLE_NAME'])

# 通知済み記事を除外
new_articles = repo.filter_new_articles(parsed_articles)

# 通知後、記事を保存
repo.save_articles(new_articles)

Serverless Framework の設定

Serverless Framework での設定について、重要なポイントをまとめます。

ランタイムと環境変数

まずはベースとなる provider セクションです。

service: zenn-trend-notifier

frameworkVersion: '4'

provider:
  name: aws
  runtime: ruby3.3
  region: ap-northeast-1
  stage: ${opt:stage, 'dev'}

  environment:
    CONFIG_PARAM_NAME: /zenn-trend/${self:provider.stage}/config
    SLACK_WEBHOOK_PARAM_NAME: /zenn-trend/${self:provider.stage}/slack-webhook-url
    ARTICLE_TABLE_NAME: zenn_trend_articles_${self:provider.stage}

ランタイムは ruby3.3、リージョンは東京(ap-northeast-1)を指定しています。

stage--stage dev / --stage prod で切り替えられ、SSM のパスや DynamoDB テーブル名も stage に応じて自動で変わります。

Lambda 関数の定義

Lambda 関数は以下のように定義します。

functions:
  zennTrendNotifier:
    handler: src/handler.main
    timeout: 30
    memorySize: 256
    logRetentionInDays: 30

ハンドラは src/handler.rbmain メソッドを指定しています。

RSS 取得など外部 HTTP 通信を含むため、timeout は 30 秒、メモリは 256MB に設定しています(余裕を持たせた設定です)。

IAM ロール

Lambda 実行ロールには、SSM と DynamoDB に対する最小限の権限だけを付与します。

provider:
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - ssm:GetParameter
          Resource:
            - arn:aws:ssm:${self:provider.region}:${aws:accountId}:parameter/zenn-trend/${self:provider.stage}/*

        - Effect: Allow
          Action:
            - dynamodb:GetItem
            - dynamodb:BatchGetItem
            - dynamodb:PutItem
            - dynamodb:BatchWriteItem
          Resource:
            - arn:aws:dynamodb:${self:provider.region}:${aws:accountId}:table/zenn_trend_articles_${self:provider.stage}

SSM は /zenn-trend/{stage}/* 配下のみ GetParameter を許可し、DynamoDB は対象テーブルに対する Get/Put/Batch 操作のみを許可しています。

CloudWatch Logs の権限は、Serverless Framework が自動で付与します。

定期実行の設定

EventBridge のスケジュール実行は、prod ステージのみで有効化します。

resources:
  Conditions:
    IsProd:
      Fn::Equals:
        - ${self:provider.stage}
        - prod

  Resources:
    ZennTrendNotifierSchedule:
      Type: AWS::Events::Rule
      Condition: IsProd
      Properties:
        ScheduleExpression: cron(0 0,3,9 * * ? *)
        State: ENABLED
        Targets:
          - Arn:
              Fn::GetAtt:
                - ZennTrendNotifierLambdaFunction
                - Arn
            Id: ZennTrendNotifierTarget

    ZennTrendNotifierPermissionForEvents:
      Type: AWS::Lambda::Permission
      Condition: IsProd
      Properties:
        FunctionName:
          Ref: ZennTrendNotifierLambdaFunction
        Action: lambda:InvokeFunction
        Principal: events.amazonaws.com
        SourceArn:
          Fn::GetAtt:
            - ZennTrendNotifierSchedule
            - Arn

IsProd という条件を使って、prod ステージのときだけ EventBridge によるスケジュール実行を有効化しています。

dev ステージではスケジュールを作らず、手動 Invoke で動作確認する運用です。

DynamoDB テーブル

DynamoDB テーブルも resources セクションで定義します。

resources:
  Resources:
    ZennTrendArticlesTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: zenn_trend_articles_${self:provider.stage}
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: article_id
            AttributeType: S
        KeySchema:
          - AttributeName: article_id
            KeyType: HASH

デプロイと動作確認

デプロイと動作確認の手順をまとめます。

前提条件

以下が準備できていることを前提としています:

  • AWS CLI がインストール・設定済み
  • AWS プロファイルが設定済み
  • Slack Webhook URL を取得済み

SSM パラメータの作成

パラメーターの設定と Slack Webhook URL を SSM に登録します。

# RSS フィードの設定
aws ssm put-parameter \
  --name /zenn-trend/prod/config \
  --type String \
  --value '{"feeds":[{"name":"trend","url":"https://zenn.dev/feed"}]}' \
  --profile your-profile \
  --region ap-northeast-1

# Slack Webhook URL
aws ssm put-parameter \
  --name /zenn-trend/prod/slack-webhook-url \
  --type SecureString \
  --value 'https://hooks.slack.com/services/xxx/yyy/zzz' \
  --profile your-profile \
  --region ap-northeast-1

Webhook URL は実際の値に置き換えてください。

デプロイ

Serverless Framework でデプロイします。

sls deploy --stage prod --aws-profile your-profile

デプロイが成功すると、Lambda 関数、DynamoDB テーブル、EventBridge ルールが作成され、AWS コンソールから各リソースを確認できます。

動作確認

手動実行での確認

まず、手動で Lambda を実行して動作を確認します。

sls invoke --stage prod --aws-profile your-profile -f zennTrendNotifier

正常に動作していれば、以下の状態になります:

  • Slack の指定チャンネルに Zenn トレンド記事が投稿される
  • DynamoDB テーブル(zenn_trend_articles_prod)に通知済み記事が保存される
  • CloudWatch Logs にログが出力される

自動実行(EventBridge)の確認

デプロイ後、EventBridge によるスケジュール実行が自動で有効化されます。

スケジュール設定:

  • cron 式cron(0 0,3,9 * * ? *)
  • 実行時刻:UTC 0:00, 3:00, 9:00(= JST 9:00, 12:00, 18:00)

次の実行時間以降に次の2点を確認します。

  • CloudWatch Logs で Lambda が自動実行されているか
  • Slack にトレンド記事が届いているか

まとめ

Zenn のトレンド記事を Slack に自動通知する Bot を作りました。

実際に運用してみると情報収集が楽になり、Serverless Framework v4 や AWS サービスを組み合わせたサーバレス構成を実践できたのも良い学びでした。

今回のコードは GitHub で公開しています。何かの参考になれば嬉しいです。
https://github.com/misugi-linkedge/zenn-trend-notifier

最後までご覧いただき、ありがとうございました。

株式会社L&E Group

Discussion