kakakakakku blog

Weekly Tech Blog: Keep on Learning!

GitHub Actions で [skip ci] のような「カスタムメッセージ」でワークフローを制御する

GitHub Actions を使っていて,コミットはプッシュするけどビルドはまだ実行したくないというときに [skip ci] をよく使っている.GitHub Actions では2021年にサポートされていて,他にも [no ci][skip actions] などのメッセージも使える👌

docs.github.com

github.blog

最近 GitHub Actions ワークフローを実装しているときに [skip ci] のような「カスタムメッセージ」を使ってワークフローのステップを制御できたら便利かもしれない💡と思って試してみた.結論としてはワークフローのステップで if: contains(github.event.head_commit.message, '[do something]') にように実装すれば簡単に実現できた❗️

🐰 workflow.yml サンプル

name: Sandbox Custom Message

on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master

jobs:
  sandbox:
    runs-on: ubuntu-latest
    steps:
      - name: START
        run: echo start!
      - name: DO SOMETHING
        run: echo yay!
        if: contains(github.event.head_commit.message, '[do something]')
      - name: END
        run: echo end!

動作確認

実行結果

トラフィックの急増に耐えられるソリューション!? Virtual Waiting Room on AWS を試した

ウェブサービスのトラフィックが急増して障害が発生してしまうことはあると思う.そういうときにリクエストを一時的にバッファリングして,適切に入場制限をする仕組みとして「Virtual Waiting Room(仮想待合室)」という仕組みがある.実際にショップ・病院などに行ったときに番号札を受け取って待合室で待つという体験をしたことがあると思う.

Virtual Waiting Room on AWS

AWS ではソリューションとして(サービスではなく)「Virtual Waiting Room on AWS」を提供している.Virtual Waiting Room on AWS をデプロイすると,サービス側に対するトラフィックを「仮想待合室」で制御できる.

AWS 以外で関連するサービスとしては Cloudflare Waiting RoomQueue-it などもある❗️個人的には 018 サポートサイトで Cloudflare Waiting Room・トイザらスサイトで Queue-it が導入されているのを体験したことがある👀

aws.amazon.com

Virtual Waiting Room on AWS 関連サイト

ソリューションサイト以外に Virtual Waiting Room on AWS に関連するサイトとして,ソリューションを詳細に解説した Implementation Guide と Amazon Web Services Blog の記事がある.Virtual Waiting Room on AWS を試す前に一度ザッと読んでおくと良いと思う💡

docs.aws.amazon.com

aws.amazon.com

また GitHub には AWS CloudFormation テンプレート・AWS Lambda 関数のコードなども公開されていて参考になる❗️

github.com

さらに @_kensh さんが ServerlessDays Tokyo 2023 で発表された資料「サーバーレスで仮想待合室を作ろう!」で Virtual Waiting Room on AWS が必要になるシチュエーションと実装詳細の解説がまとまってて最高だった👏

speakerdeck.com

Virtual Waiting Room on AWS の動作イメージ

最初に Virtual Waiting Room on AWS の動作イメージを見てもらうとわかりやすくなると思う💡まず,待合室サイトの初期ページにアクセスして,表示されている Reserve ボタンを押すと順番待ちに入れる.

待合室サイトの初期ページにアクセスする

以下のキャプチャは5回ほど待合室サイトにアクセスしたところ.You are number 5 in line と表示されている通り,今は5名待っていることになる.この Waiting Room 画面では My PositionServing Position など待合室のステータスを確認できる.

待合室サイトで待つ

今度はコントロールパネル(管理画面)にアクセスする.この画面でも待合室のステータスを確認できる.そして,デフォルトでは Serving Counter 0 になっていて全員ブロックされているため,Increment Serving CounterIncrement by: 100Change ボタンを押してサービス側に通す人数制限を緩和する.

コントロールパネルにアクセスする

すると,待合室サイト側で Waiting for line to advance ボタンが押せるようになり,今度は実際のサービスサイト(今回は EC サイトのモックアップ)にアクセスできる.ザッとこんな感じ❗️

サービスサイトにアクセスする

Purchase now ボタンを押して購入操作を試す

Virtual Waiting Room on AWS をセットアップする

Virtual Waiting Room on AWS をセットアップするときは AWS Solutions Library のサイトにある Launch in the AWS Console ボタンを押して us-east-1 リージョンに AWS CloudFormation スタックをデプロイすれば OK👌2024年1月20日時点で問題なく動く😃

しかし1点注意点があって,AWS CloudFormation スタック名は短くしておく必要がある.僕は vwr-on-aws にして動作確認できたので,真似してもらって良いと思う.詳しくは記事の最後に書くけど,最初スタック名を virtual-waiting-room-on-aws にしたらデプロイエラーになって時間を無駄にした💣

aws.amazon.com

あとコントロールパネルに Amazon API Gateway のエンドポイントを実行するための権限を持ったアクセスキーを設定する必要がある.今回は vwr-on-aws という名前の IAM User にした.ポリシーは AWS CloudFormation で作られている IAM Group があってそこにアタッチすれば引き継がれる👌Implementation Guide に IAM User を作ると載ってるけど,隅々まで読まないと気付きにくいと思う.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "execute-api:Invoke"
            ],
            "Resource": "arn:aws:execute-api:us-east-1:000000000000:m6y97xvptd/*",
            "Effect": "Allow"
        }
    ]
}

docs.aws.amazon.com

AWS CloudFormation スタックは基本機能だと大きく3つのネストスタックに分割されている🧩

  • vwr-on-aws
    • CoreModuleStack
    • AuthorizersModuleStack
    • SampleModuleStack

AWS CloudFormation スタック一覧

デプロイ時間は「27分ぐらい」だった.特に Amazon ElastiCache クラスタの構築(論理名 RedisReplicationGroup)に10分ぐらい待たされるため,もしデプロイが進んでいるのか不安になったら Amazon ElastiCache コンソールを確認してみると良いと思う.

2024-01-20 11:34:00 UTC+0900
2024-01-20 12:00:56 UTC+0900

すべてデプロイが終わったら AWS CloudFormation スタックのアウトプットから ControlPanelURLWaitingRoomURL の値を控えておく.既に検証環境は削除していてアクセスできないけど,実際に以下のような URL になっていた📝

# ControlPanelURL
https://d1096am4wacsdc.cloudfront.net/control-panel/index.html?eventId=Sample&publicApiUrl=https://d3bxth0af8tkl8.cloudfront.net&privateApiUrl=https://m6y97xvptd.execute-api.us-east-1.amazonaws.com/api&regionName=us-east-1

# WaitingRoomURL
https://d1096am4wacsdc.cloudfront.net/waiting-room-site/index.html?eventId=Sample&publicApiUrl=https://d3bxth0af8tkl8.cloudfront.net&commerceApiUrl=https://fv5dbf3ata.execute-api.us-east-1.amazonaws.com/store

アーキテクチャ図

Virtual Waiting Room on AWS のアーキテクチャ図は Implementation Guide に載っている.コンポーネントも多く複雑に感じるけど,optional と書いてある部分を除けば Public and private APIs と書いてある部分が中心になる.

Architecture overview - Virtual Waiting Room on AWS より引用

しかし AWS Lambda / Amazon DynamoDB などはあくまで概要レベルのアーキテクチャ図になっていて,詳細も Implementation Guide に載っている.Amazon API Gateway から呼び出される AWS Lambda 関数の多さに驚く👀

Solution components - Virtual Waiting Room on AWS より引用

Virtual Waiting Room on AWS の仕組みと各コンポーネントの挙動に関しては Implementation Guide の How the solution worksSolution components に詳しく載っている.これらを読みながら実際にデプロイされた AWS リソースの設定などを探っていくのが良いと思う.

docs.aws.amazon.com

docs.aws.amazon.com

個人的に挙動を調査したログを残しておく.まず AssignQueueNum API は {"api_request_id": "581280c8-79b3-4212-b3eb-7b3e84c9b91f"} のようなレスポンスを返していて,GetQueueNumber API は {"queue_number": 2, "entry_time": 1705721798, "event_id": "Sample", "status": 1} のようなレスポンスを返している💡

また Virtual Waiting Room で待機してるときは GetServingNumber API と GetWaitingNum API を定期的に呼び出していて(ポーリングしていて)それぞれ {"waiting_num": 3}{"serving_counter": "1"} のようなレスポンスを返していて,Virtual Waiting Room から次に進めるかどうかを制御している😀DevTools で待機中の API 呼び出しを眺めていると理解しやすいと思う👌

待機中の API 呼び出し

順番が来たら JWT トークンを取得して,サービス側の Amazon API Gateway の認証に使う.Amazon API Gateway の設定を見ると WaitingRoomAuthorizer という Amazon API Gateway Lambda オーソライザーの設定が見つかる🔑

Amazon API Gateway Lambda オーソライザー

AWS CloudFormation スタックを削除する

そのままにしておくとコストが発生するため,Virtual Waiting Room on AWS の検証が終わったら AWS CloudFormation スタック vwr-on-aws を削除する.削除する順番としては「1. IAM User」→「2. AWS CloudFormation スタック」→「3. Amazon S3 バケット(2つ)」で,詳しくは Implementation Guide にすべて書いてある👌

docs.aws.amazon.com

AWS CloudFormation スタック名に注意する

最初 AWS CloudFormation スタック名を virtual-waiting-room-on-aws にしたらデプロイの後半でエラーになって,ロールバックの時間も含めて1時間ほど無駄にしてしまった.エラーになったのは Amazon EventBridge で「targetId の64文字制限」に該当してしまった.確かに virtual-waiting-room-on-aws-CoreModuleStack-SDAGBI8A4ER1-expiredEvents は70文字ある💨

Resource handler returned message: "1 validation error detected: Value 'virtual-waiting-room-on-aws-CoreModuleStack-SDAGBI8A4ER1-expiredEvents' at 'targets.1.member.id' failed to satisfy constraint: Member must have length less than or equal to 64 (Service: EventBridge, Status Code: 400, Request ID: 72b173d6-99c8-445c-9755-d0351b64ab19)" (RequestToken: c69baab4-b984-4a92-e37e-75f30dca430d, HandlerErrorCode: GeneralServiceException)

ちなみに AWS CloudFormation テンプレートを読むと,Amazon SQS キュー名は AWS CloudFormation の「カスタムリソース」を使って文字数を制御しているため,同じ仕組みが入っていれば良いのに〜と思った💡

"WaitingRoomQueue": {
    "Type": "AWS::SQS::Queue",
    "Properties": {
        "QueueName": {
            "Fn::Join": ["-", [ { "Fn::GetAtt": [ "InvokeShortenStackNameLambda", "returnValue" ] }, "WaitingRoomQueue"] ]
        },
        "KmsMasterKeyId": "alias/aws/sqs",
        "KmsDataKeyReusePeriodSeconds": 300,
        "RedrivePolicy": { 
            "deadLetterTargetArn" : {"Fn::GetAtt": [ "WaitingRoomDeadLetterQueue", "Arn" ]},
            "maxReceiveCount" : 2
        },
        "VisibilityTimeout" : 30
    }
},

その他実施ログ

AWS Lambda 関数は計23種類ある

Amazon DynamoDB テーブル QueuePositionEntryTimeTable のデータ例

まとめ

「Virtual Waiting Room(仮想待合室)」という仕組みが欲しくなることはあると思う.AWS を活用して実現できるソリューションとして「Virtual Waiting Room on AWS」を実際に試して紹介した❗️それなりに構成は複雑ですべてを理解しようとすると大変だけど,Virtual Waiting Room on AWS にはカスタマイズ性もあって,既存サービスに組み込みやすく作られている👌とは言え,あくまで個人的にはソリューションではなく「マネージドサービスとして」提供されたらもっと良いのにな〜と思ったりはする💨

ということで今回は Virtual Waiting Room on AWS の紹介でした \( 'ω')/

AWS CDK で AWS CodeBuild の GitHub Webhook を設定する

AWS CodeBuild で GitHub リポジトリにプッシュをしたら Webhook 経由で自動的にビルドを開始する構成を AWS CDK で実装してみた💡ちなみに AWS CDK で AWS CodeBuild の Webhook を設定するだけだと以下のように Failed to call CreateWebhook というエラーが出る場合がある🔥

Failed to call CreateWebhook, reason: Could not find access token for server type github

今回は GitHub Personal Access Tokens (PAT) を AWS Secrets Manager に設定して,AWS CDK の aws_codebuild.GitHubSourceCredentials で認証情報を設定する👌

docs.aws.amazon.com

GitHub Personal Access Tokens (PAT)

まずは GitHub の Personal Access Tokens (PAT) を取得する.現時点だとトークンは classicFine-grained (Beta) の2種類があるけど,今回は classic を使う.Select scopes でトークンに指定するスコープでは repoadmin:repo_hook を許可しておく.スコープに関しては以下のドキュメントにも書いてあった.

docs.aws.amazon.com

AWS Secrets Manager

取得した Personal Access Tokens (PAT) を AWS CLI で AWS Secrets Manager に登録する.名前は github-pat にした.

$ aws secretsmanager create-secret --name github-pat --secret-string XXXXX

👾 sandbox-cdk-codebuild-stack.ts

今回は GitHub リポジトリの master ブランチにプッシュをしたら Webhook 経由で自動的にビルドを開始するように設定した.AWS CodeBuild の buildspec は特に意味はなく echo コマンドにしてある.

import {
    RemovalPolicy,
    SecretValue,
    Stack,
    StackProps,
    aws_codebuild,
    aws_logs,
    aws_iam,
} from 'aws-cdk-lib'
import { Construct } from 'constructs'

export class SandboxCdkCodeBuildStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props)

        const role = new aws_iam.Role(this, 'SandboxCdkCodeBuildRole', {
            roleName: 'sandbox-cdk-codebuild-role',
            assumedBy: new aws_iam.ServicePrincipal('codebuild.amazonaws.com')
        })

        const logGroup = new aws_logs.LogGroup(this, 'SandboxCdkCodeBuildLogs', {
            logGroupName: 'sandbox-cdk-codebuild-logs',
            retention: aws_logs.RetentionDays.ONE_WEEK,
            removalPolicy: RemovalPolicy.DESTROY,
        })

        logGroup.grantWrite(role)

        new aws_codebuild.GitHubSourceCredentials(this, 'SandboxCdkCodeBuildGitHubCredential', {
            accessToken: SecretValue.secretsManager('github-pat')
        })

        const source = aws_codebuild.Source.gitHub({
            owner: 'kakakakakku',
            repo: 'xxxxx',
            branchOrRef: 'master',
            webhook: true,
            webhookFilters: [
                aws_codebuild.FilterGroup
                    .inEventOf(aws_codebuild.EventAction.PUSH)
                    .andBranchIs('master')
            ],
        })

        new aws_codebuild.Project(this, 'SandboxCdkCodeBuild', {
            projectName: 'sandbox-cdk-codebuild',
            source: source,
            buildSpec: aws_codebuild.BuildSpec.fromObject(
                {
                    version: '0.2',
                    phases: {
                        build: {
                            commands: [
                                'echo Hello!'
                            ]
                        }
                    }
                }
            ),
            environment: {
                buildImage: aws_codebuild.LinuxBuildImage.fromCodeBuildImageId('aws/codebuild/amazonlinux2-x86_64-standard:5.0'),
                computeType: aws_codebuild.ComputeType.SMALL,
            },
            role: role,
            logging: {
                cloudWatch: {
                    logGroup: logGroup,
                }
            },
        })
    }
}

動作確認

GitHub リポジトリに適当なコミットをプッシュすると AWS CodeBuild のビルドが自動的に開始された👌送信者は Webhook を意味する GitHub-Hookshot プレフィックスになっていた.

AWS CodeBuild ビルド履歴

ちなみに aws codebuild list-source-credentials コマンドを使って認証情報の設定を確認できる.今回は Personal Access Tokens (PAT) を使ったため authTypePERSONAL_ACCESS_TOKEN になっていた.

$ aws codebuild list-source-credentials
{
    "sourceCredentialsInfos": [
        {
            "arn": "arn:aws:codebuild:ap-northeast-1:000000000000:token/github",
            "serverType": "GITHUB",
            "authType": "PERSONAL_ACCESS_TOKEN"
        }
    ]
}

関連ドキュメント

docs.aws.amazon.com

AWS CDK でリソースに一括でタグを設定する

AWS CDK を使っていてリソースに「一括でタグを設定したい」と思ったら Tags.of(SCOPE).add() を使えば簡単に設定できる💡Tags.of に指定する SCOPEIConstruct インタフェースを実装していれば良くて AppStack も指定できる👌

docs.aws.amazon.com

App(複数スタックに一括でタグを設定するなら)

const app = new cdk.App();
cdk.Tags.of(app).add('Project', 'Sandbox');

Stack(スタックごとに一括でタグを設定するなら)

const app = new cdk.App();
const stack = new SandboxStack(app, 'SandboxStack', {});
cdk.Tags.of(stack).add('Project', 'Sandbox');

小ネタ紹介でしたー \( 'ω')/

Cyber-Dojo で Fizz Buzz の次におすすめするエクササイズ5選

環境構築に悩むことなくペアプログラミング・モブプログラミングの練習ができるので「Cyber-Dojo」をよく使っていて,モブプログラミング未経験のチームに導入を支援するときにも使っている👌 Cyber-Dojo には数年前に紹介した「結果予測機能」もあってテスト駆動開発の練習に使うこともできる❗️

cyber-dojo.org

Cyber-Dojo では多くのプログラミング言語とテストフレームワークの組み合わせから選べるだけでなく,さらに「60 種類」のエクササイズ(アルゴリズム)から選べるという特徴がある.

Cyber-Dojo の create a new practice 画面

まずは Fizz Buzz と Fizz Buzz Plus から

もしプログラミング初学者やモブプログラミング未経験者に教える場合,1番使いやすいのは Fizz Buzz だと思う.アルゴリズムは単純だし,初学者にも理解しやすく,テストコードも含めて程よく楽しめる.

🎲 Fizz Buzz

与えられた数値をそのまま出力しつつ,もし数値が 3 の倍数なら "Fizz" を出力し,5 の倍数なら "Buzz" を出力する.もし 3 と 5 の倍数なら "Fizz Buzz" を出力する.有名な入門アルゴリズムで難易度は低めだと思う.

🎲 Fizz Buzz Plus

Fizz Buzz を少し発展させて,もし数値が 3 を文字列として含むときは "Fizz" を出力し,5 を文字列として含むときは "Buzz" を出力する.よって,31 や 32 は Fizz Buzz Plus だと "Fizz" になるし,52 や 56 は Fizz Buzz Plus だと "Buzz " になる.同じく難易度は低めだと思う.最初から Fizz Buzz Plus を選んでも良いし,まず Fizz Buzz を実装してから,仕様変更があったというストーリーで Fizz Buzz Plus に移行していくのも良いと思う.

ちなみに1年以上前の話だけど,2022年8月に YouTube でライブ配信を担当した 「AWS Developer Live Show: モブプログラミング超入門ライブ!」でもモブプログラミングのテーマとして Fizz BuzzFizz Buzz Plus を選んだ❗️

モブプログラミング中のイメージ図

Cyber-Dojo で Fizz Buzz の次におすすめするエクササイズ5選

Cyber-Dojo を使ってモブプログラミングの導入を支援しているときに Fizz Buzz の次に試すエクササイズのおすすめはあるー?と聞かれることがある.基本的にどのエクササイズでも良いと思うけど,聞かれたときに答えている個人的におすすめのエクササイズ5選を紹介したいと思う.Cyber-Dojo 自体のコードは GitHub に公開されていて,エクササイズ情報(説明文など)は cyber-dojo/exercises-start-points リポジトリで管理されている.

github.com

🎲 Leap Years

与えられた年が「閏年」かどうかを判定する.アルゴリズム的には与えられた年が 4 で割り切れるなら閏年とする.しかし与えられた年が 100 で割り切れて,400 で割り切れない場合は閏年ではないという例外がある.難易度は低めだと思うので,Fizz Buzz の次に試してみると良いかなーと👌

また Python だと calendar モジュールに isleap() 関数が用意されているため,実際に使われている実装とテストコードと答え合わせするのもおすすめ〜 \( 'ω')/

🎲 Anagrams

与えられた文字列から作れる組み合わせを出力する.例えば biro を入力すると biorbrio など他にも多くの組み合わせを出力する.難易度は低めだとは思うけど,与えられた文字列の文字数を固定にするのかどうかや,与えられた文字列に重複した文字があるのかどうかによっても難易度は少し変わると思う.

biro bior brio broi boir bori
ibro ibor irbo irob iobr iorb
rbio rboi ribo riob roib robi
obir obri oibr oirb orbi orib

🎲 ISBN

与えられた ISBN コードが規格に沿っているかどうかを判定する.最後の一桁がチェックディジットになっていて,そこを正しく計算できているかどうかがポイントになる.Fizz Buzz と比べると少し難易度は高くなると思う.

ISBN-10:
0471958697
0 471 60695 2
0-470-84525-2
0-321-14653-0

ISBN-13:
9780470059029
978 0 471 48648 0
978-0596809485
978-0-13-149505-0
978-0-262-13472-9

🎲 Harry Potter

ハリー・ポッターの書籍を同時に複数冊買うと割引になるというカートの計算をする.2冊買うと 5% 割引!というのは誰しも体験したことがあるはずで,イメージしやすいエクササイズだと思う.とは言え,Fizz Buzz と比べると少し難易度は高くなると思う.

2 copies of the first book
2 copies of the second book
2 copies of the third book
1 copy of the fourth book
1 copy of the fifth book

このエクササイズは Coding Dojo の Kata に Potter という名前で載っていて,テストコードも参考になる📕(実は Coding Dojo に Leap Years や Anagram また次に紹介する Bowling も載っている💡)

codingdojo.org

🎲 Bowling Game

ボーリングのスコアを計算する.倒したピンの本数だけではなくストライク・スペアによってスコアが変化するところがポイントで,さらに10ゲーム目は最大3回投げられるという例外もある.ボーリングをしたことがあればイメージしやすいエクササイズだと思うけど,Fizz Buzz と比べると少し難易度は高くなると思う.

  • X : ストライク
  • / : スペア
  • - : 失敗
  • | : 境界線
  • || : 最終フレームのボーナス投球

まとめ

Cyber-Dojo で Fizz Buzz の次におすすめするエクササイズ5選を紹介した❗️他にも良いエクササイズがあったらまた紹介したいと思う \( 'ω')/