2022年、カヤックは ISUCON 12の出題を担当しています

みなさんこんにちは〜! 技術ブログではとてもお久しぶりの acidlemon です。

さて、今年もISUCONの季節がやってきましたね。

ISUCONは、ざっくりいうとLINEが主催する、Webアプリケーションのスピードアップコンテストです。「い感じにピードアップコンテスト」なので略してISUCONという感じです。詳しくは 公式サイトを読んでもらうのがよいですが、毎年カヤックもコンテストの参加者だったり出題者だったり優勝者だったりといった形で関わっております。

これまでの開催と、今年の開催はどのような感じになっているかというと…

昨年の ISUCON 11 はカヤックのfujiwara組が久々に優勝しまして、今年のISUCON 12はカヤックが出題の一部を担当することとなっております。作問担当としては、もう1社、サイバーエージェントのみなさんも参加しており、ワイワイしながら作問しております。

カヤックの出題はISUCON 3、ISUCON 8に続きこれが3回目。私個人としてはISUCON 3以来の2回目です。ISUCON 3のときはfujiwaraと2人でせっせと予選本選の2問作って移植までやっていたので、その時と比較すると運営や作問チームがしっかり組織化されていていやーすごいな〜と感心しきりです。

さて、ISUCON 12は予選参加できるチーム枠数が最大700チームということで、過去最大規模になっています。6月1日に予選参加の受け付けを開始しましたが、3分経たずに220チームが埋まってしまいまして、ポータル担当のfujiwaraと私で「あまりにも早すぎる…」と震えておりました。

結果的に、秒間1.3チームくらいのペースで枠が埋まったため参加登録ポータルからDiscord APIを叩くところがRate Limitされてしまいました。参加枠をゲットされた方にはDiscordのRoleがなかなか付かなくてご心配をおかけしましたが、なんとかリカバリしておりますので大目に見ていただけますと幸いです。

ISUCON 12では予選へのエントリーを3期に分けておりまして、昨日エントリー枠がうまって申し込めなかった、という人にもあと2回チャンスがあります。

このような感じで、次は来週月曜日の20時、最後が来週土曜日の朝10時となっております。気合い入れて作問していますので、みなさまこのビッグウェーブに是非ご参加ください。

カヤックでは、いい感じにスピードアップするのが好きなエンジニアと働きたい人を募集しています!

ステージング環境における検証用データベースの立ち上げを自動化する取り組み

SREチーム(新卒)の市川恭佑です。

カヤックのサービスでは、信頼性の担保を目的として、ステージング環境を作成する方針を取っています。 ステージング環境では、検証の精度を高めるために、量・質ともに本番環境に類似したデータベースが求められる局面が頻出します。 そこで今回は、Tonamel という自社サービスにおける、検証用データベースの立ち上げを自動化する取り組みについて紹介します。

サービスの置かれていた状況と解決方針

Tonamel の実行基盤は Amazon Web Services (AWS) 上にあり、本番環境とステージング環境は別のアカウントとして、同一の AWS Organizations 組織内に構築されています。 もともと、ステージング環境では、本番環境のデータは利用せず、手作業でダミーデータを作成していました。 それゆえに、データベースに格納されているデータ量は本番環境と比べて明らかに少なく、データベースマイグレーションなどの主要な変更に対して、メンテナンス時間の見積もりやリスクの洗い出しに難がありました。 この問題を解決するべく、以下の二つの対策を検討しました。

概要 懸念点
ダミーデータ生成スクリプトの作成
  • 本番データに対するダミーデータの質的な差異
  • DBスキームの変更に応じたメンテナンス
本番環境にあるデータの複製
  • センシティブな情報の取り扱い
  • AWSのクロスアカウント環境構築

どちらでもデータ量を本番環境に近づけることは可能ですが、ダミーデータの場合、データの質も近づけることは困難です。 特に、RDBMSにおける関係テーブルやインデックスについては、格納されるデータの質がアプリケーションのパフォーマンスに重大な影響を与える1ので、ここに本番環境との大きな差異があると検証の精度が損なわれます。 これに対し、Tonamel ではステージング環境へのアクセス権が、本番環境を操作可能な開発者に限られている等の複数の理由から、センシティブな情報に対する懸念点を解消できたため、本番環境にあるデータの複製を選択しました。

AWS Step Functions によるワークフロー構築

Step Functions の採用に至った経緯

Tonamel の永続化されたデータベースは Amazon Aurora (RDS) および Amazon DynamoDB を併用しています。 データストアが複数の独立したリソースにまたがっている以上、強い整合性を保証するデータベース複製の実現は困難です。 また、複製されたデータベースの利用者は本番環境ではなく、あくまでステージング環境です。 データに不整合がある際にアプリケーションが一貫した挙動を保てば、ここでは問題にはなりません。

また、責務についても、ビジネスロジックを実装したアプリケーション本体とは異なり、分離されたものであるべきです。 このような性質から、実装コストも考慮して、厳密ではなくとも比較的容易に要求を満たすために、ワークフローエンジンに頼る方針を取りました。

ワークフロー実行エンジンには Step Functions を選択しました。 ちょうど2021年の9月に AWS SDK 統合がアナウンスされ、社内でもデプロイ用ツール Stefunny の開発が進んでいたのが主な理由です。 Step Functions の開発体験には(特に型関係で)まだまだ課題がありますが、これから利用が活発になる見通しです。

ワークフローの全体像

ワークフローの全体像は以下のとおりです。 本番環境アカウントにある Aurora と DynamoDB を、ともに AWS Backup の機能でバックアップ作成し、Backup ボールト(Vault)に格納します。これをステージング環境アカウントの Backup ボールトにコピーして、リストアを実行します。

以下の画像はイメージですが、バックアップの(クロスアカウント)コピーは本番環境側のワークフローが行なっていることに注意してください。

ワークフロー全体のイメージ画像

Aurora のクロスアカウント共有可能なバックアップ手段は、これ以外にも効果的なものが用意されています。しかし DynamoDB については現在この方法が最もシンプルかつ現実的なため、AWS Backup を選択し、Aurora 側もこれに統一しました。

なお、AWS Backup については利用にあたっていくつか注意点があるため、後述します。

Amazon EventBridge を用いたワークフローのクロスアカウント統合

本番環境とステージング環境のアカウントにある、二つの Step Functions ワークフローは EventBridge で接続しました。先にイメージを示します。

EventBridge によるワークフロー接続のイメージ画像

まず、本番環境側では、 Step Functions ワークフローが終了次第、デフォルトのイベントバスに AWS によって発行されるイベントが流れます。これに対し、「バックアップ用の Step Functions ワークフローの成功通知であった場合はステージングのイベントバスに通知する」というルールを登録することでステージング環境に通知します。

次に、ステージング環境では、本番環境から受け取ったイベントがデフォルトのイベントバスに流れてくるので、「バックアップ用の Step Functions ワークフローの成功通知であった場合はリストア用のワークフローを発火する」というルールを登録すれば十分です。

なお、イベントバスについては、AWSが発行するイベントを用いるため、AWS によってデフォルトで用意されているものを選択しました2。 カスタムイベントバスによる統合も可能かもしれませんが、必要性がないと判断したため未検証です。

EventBridge の権限設定

EventBridge ターゲットには AWS Identity and Access Management (IAM) のロールを設定できます。 同時に、EventBridge イベントバスにはリソースベース・ポリシーも設定できます。

クロスアカウントの検証作業は大きな手間と時間を要するので、ここではシンプルな設定方法を紹介します。 ただし、他のオプションを含む詳細については公式ドキュメント群をご参照ください。

本番環境:

EventBridge ターゲットのロールについて、ステージング環境のデフォルトのイベントバスに対する events:PutEvents を許可します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": "events:PutEvents",
            "Resource": "arn:aws:events:ap-northeast-1:${stg_account_id}:event-bus/default"
        }
    ]
}

ステージング環境:

まず、EventBridge イベントバスについて、本番環境のアカウントからのアクセスを許可3します。

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "AllowOtherAccounts",
    "Effect": "Allow",
    "Principal": {
      "AWS": "arn:aws:iam::${prod_account_id}:root"
    },
    "Action": "events:PutEvents",
    "Resource": "arn:aws:events:ap-northeast-1:${stg_account_id}:event-bus/default"
  }]
}

次に、EventBridge ターゲットのロールについて、復元用の Step Functions ワークフローの実行を許可します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": "states:StartExecution",
            "Resource": "arn:aws:states:ap-northeast-1:${stg_account_id}:stateMachine:${workflow_name}"
        }
    ]
}

以上で EventBridge を用いた Step Functions ワークフローのクロスアカウント統合に必要な権限の設定は完了です。

AWS Backup によるクロスアカウント・バックアップの注意点

AWS の各種サービスには、それぞれ個別にバックアップやリカバリの機能が提供されています。 AWS Backup は基本的にそれらを抽象化したもので、難解にとらえる必要はありません。

しかし、一般に個別のバックアップ機能を直接使うことが多いせいか、Web 上に見つかる知見が心もとないのも事実です。 概要を知りたい場合には公式ドキュメントか、Classmethodさんのブログが役立ちます。

ここでは、クロスアカウントのユースケースに限って、著者が今回の取り組みで気になったポイントのみを紹介します。

ボールト関連で要求される暗号化キーへのアクセス

AWS Backup では、ボールトに AWS Key Management Service (KMS) の鍵を暗号化キーとして登録します(作成時に指定しない場合、AWSが用意したデフォルトの鍵が利用されます)。

まず、ドキュメントにあるとおり、コピー元のボールトとコピー先のボールトは、互いに同じ鍵で暗号化されている必要があります。つまり、クロスアカウントのコピーをするには、暗号化キーもクロスアカウントに共有する必要があります。今回の事例では、本番側に共有専用の KMS 鍵を作成し、ステージング側からのアクセスを受け入れるように設定しました。

そして、この暗号化キーへのアクセス権は、バックアップジョブの実行ロールだけでなく、ボールトの作成者にも要求されます。 KMS のリソースベース・ポリシーは厳しめに設定するのが良いので、クロスアカウントだからといって焦らずにボールトの作成者を特定し、必要な対象にのみ権限を与えると良いです。

マネジメントコンソール上に現在発生している問題

執筆時点(2022年5月)において AWS Backup のマネジメントコンソールでは、少なくとも二つの軽微な問題が発生しています。 カヤックでは、基本的に Infrastructure as Code (IaC) を推進していますが、インフラ開発自体の検証ではコンソールを使うことも多々あります。 これらの問題についてはサポートケースを作成し、すでに AWS に報告済みなので、あくまで修正されるまでの情報共有としての記述です。

鍵の指定:

一つめは、ボールトの作成画面で別のアカウントにある KMS 鍵を指定できないことです。 自由記述の許されないセレクトボックスになっていて、候補には現在のアカウントの鍵しか出ません。 代わりに、AWS CLI などを経由して CreateBackupVault API を叩くことで KMS 鍵の Amazon Resource Name (ARN) を直接指定できます。

現在のコンソールでボールトを作成する際に出てくる画面のスクリーンショット

アクセス権の案内:

二つめは、コピージョブを作成する画面に出てくるアクセス権限の案内です。 公式ドキュメントも最新のコンソールと少しずれていますが、直感的な操作で別アカウントのボールトの指定までは到達できます。すると、ボールトの ARN を入力した時点で、青い長方形に囲まれたアクセス権に関する案内が表示されます。

現在のコンソールでコピージョブを作成する際に出てくる画面のスクリーンショット

ここで、Allow ボタンを押下すると、画面の中央に次のようなリソースベース・ポリシーが表示されます。 AWS リソースが変更されるような操作は今のところ発生しないようです。

この例は、コピー元アカウント(XXXXXXXXXXXXXXX)にある src というボールト内のリカバリーポイントをコピーするにあたって、コピー先アカウント(123456789012)にある dest というボールトを送信先に指定したものです。

案内では src のボールトの(リソースベース)ポリシーにコピー先アカウント(123456789012)からのアクセスを許可するように、とのメッセージとともにポリシー例が提示されますが、実際に必要なものは逆です。dest のボールトのポリシーに、コピー元アカウント(XXXXXXXXXXXXX)からのアクセスを許可する必要があります。セマンティクスも、提示されているもの(つまり "allow dest to copy sth into src")は明らかにおかしいです。

現在のコンソールでコピージョブを作成する際に出てくる画面のスクリーンショット_2

結論として、ボールトのリソースベース・ポリシーは、以下のようなものをコピー先にのみ付与すれば十分です。コピー元には必要ありません(今回の事例ではXXXXXXXXXXXXXは本番のアカウントID、付与するボールトはステージング側)。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Allow XXXXXXXXXXXXX to copy into this vault",
      "Effect": "Allow",
      "Action": "backup:CopyIntoBackupVault",
      "Resource": "*",
      "Principal": {
        "AWS": "arn:aws:iam::XXXXXXXXXXXXX:root"
      }
    }
  ]
}

なお、コピージョブの実行ロールには別途アクセス権を付与する必要があります。 また、コピージョブ自体を StartCopyJob で発火する場合には、発火時のユーザーに iam:PassRole 権限が必要です。

アプリケーションの接続先データベースの切り替え

検証用データベースを立ち上げ終わったら、そのデータベースをアプリケーションが利用できるように接続先を切り替えます。 このステップは、初期化方法やパラメータの保持方法など、アプリケーションを取り巻くさまざまな要因に影響されて好ましい対処が変わります。 そのため、概要に軽く触れたのち、実装中の気づきを紹介する程度にとどめます。

今回の事例では、このステップを、二つの連動した Step Functions ワークフローとは切り離して、AWS Lambda 関数として実装しました。 代表的な理由は以下のとおりです。

  • データベースのバックアップやリストアと異なり、長い時間を要するものではない
  • ステージング環境でもアプリケーションの状態や挙動を変えるタイミングはコントロールできた方がいい4
  • Aurora のパスワードを変更する必要が5あるが、Step Functions の Input/Output に秘匿情報を晒したくない
  • Step Functions の開発体験に疲れが溜まり、静的解析が恋しくなった

Lambda 関数は Go で書かれ、下記のステップを同期的に処理します。

  1. Aurora のパスワード更新および完了待機
  2. RDS Proxy のターゲット付け替え
  3. パラメータの更新
  4. アプリケーションの更新

Aurora のパスワード変更に関する注意点

Aurora でマスターユーザーパスワードを即時反映で変更するには ModifyDBCluster API コール時に ApplyImmediately を指定すればよいですが、その完了を機械的に待つには注意が必要です。というのも、パスワード変更中は DescribeDBClusters API レスポンスの DBCluster オブジェクトにある Status"available" から "resetting-master-credentials" になりますが、この変遷にも(観測した範囲で)数秒から数十秒程度の遅延がありました。

ApplyImmediately である以上、変更値はともかく、 Status くらいはアトミックに変遷するだろう」という甘い考えで Modify 後に間髪入れずに Describe を叩くコードを書くと、パスワードの変更処理が始まってすらいない状態にも拘わらず、「パスワード変更が完了して Status"available" に戻った」と勘違いして、プログラムが次に進んでしまいます。スマートとは言い難いですが、固定の待ち時間を挟む対処をしました。

RDS Proxy のターゲットおよびグループに関する注意点

先述の Aurora の Status"resetting-master-credentials" である最中は RDS Proxy のターゲットグループに(ターゲットとして)登録できません。 これが、そもそも Lambda の処理を非同期にもせず Aurora のパスワード変更を待機している理由です6

また、現時点では、RDS Proxy のターゲット・ターゲットグループには以下のような注意すべき制約があります。

  • ターゲットグループは削除も作成もできずデフォルトを使うしかない
  • ターゲットグループに複数のターゲットを同時に登録できない

これにより、接続先の切り替えの際には、古いターゲットを先に解除して新しいターゲットを登録するという順番を守る必要があります。 RDS Proxy は強力で価値のあるサービスですが、比較的新しいものであるがゆえ、細かな挙動が発展途上である印象を受けます。 今回とはまったく別のユースケースでも、しっかりと挙動を確認しておくと良いでしょう。

まとめ

今回は、Tonamel の事例を通して、Webサービスにおける検証用データベースの立ち上げを自動化するシンプルな方法について、その全体像を紹介しました。

このような取り組みは、手動オペレーションによって掛かる明確なコストを削減するだけでなく、信頼性向上のためのプロセスを導入しやすくして、事故によって発生しうる暗黙的なコストを削減する効果もあります。また、SRE的な取り組みというと、敷居の高さを感じて手を引いてしまうことも多いかもしれません。しかし、革新的なプラットフォームとして喧伝できるほどの堅牢性や汎用性を持ったソフトウェアを開発せずとも、クラウド事業者等によって提供されている既存技術の愚直な組み合わせで達成可能なものは沢山あります。

カヤックの SRE チームでは、地に足のついた取り組みを継続し、社内外における情報共有を通して、より効率的にサービスの信頼性を高めていきます。

カヤックでは一緒に働くエンジニアを募集しています。


  1. 弊社にはISUCON(Webサイトのパフォーマンスチューニング大会)の優勝者も複数人在籍しています。先日開かれた社内ISUCONに関する記事はこちら。

  2. 参考 … AWS Black Belt Online Seminar Amazon EventBridge

  3. イベントバスのポリシーは Sid に空文字を受け付けません。そういうこともあるようです。

  4. バックアップ・転送・リストアのプロセスは比較的長時間かかるので、終了するタイミングの見積もり・コントロールが困難です。

  5. Auroraはバックアップ時の認証情報でリストアされます。今回はステージング環境で本番環境と同じユーザー名を使っていますが、パスワードは取得できない設定のため更新が必要です。

  6. 新しい RDS Proxy を作成して、データベース用のドメイン(CNAMEレコード)を更新する等、他の方法も検討・挑戦しましたが、今回は最終的な着地点がここでした。