aws-cdkを用いた開発事例の紹介

これははてなエンジニアAdvent Calendar 201812月7日の記事です。

昨日はid:susisuさんの「分割」できる疑似乱数生成器でした。

こんにちは。はてなでSREとして働いているid:taketo957です。

現在、所属しているチームにてAWSのプロビジョニングを行う際に、aws-cdkを試験的に利用しています。

本記事では、aws-cdkについて簡単に紹介し、はてなで実際にどのように利用しているかを紹介します。

はじめに

aws-cdkについて簡単に説明します。aws-cdkは、2018年8月に開発者プレビューとして紹介されました。これによると、

AWS CDK はソフトウェア開発のフレームワークであり、クラウドのインフラストラクチャをコードで定義して CloudFormation でプロビジョニングできます。CDK は AWS のサービスと統合され、高レベルかつオブジェクト指向の抽象概念を使ってAWS リソースを定義できます。

とあります。aws-cdkを用いることによって、CloudFormationによるAWSのリソース定義を、ある程度のベストプラクティスが内包された高レベルなライブラリ群によって扱うことができるようになります。

2018年12月の執筆時点での対応言語は、TypeScript、JavaScript、.NET、Javaとなっており、今後はPythonのサポートが予定されているみたいです。 (https://github.com/awslabs/jsii によるとRubyも期待できそう?)

aws-cdkの構造

aws-cdkの構造を理解するには、以下の図がわかりやすいです。(下記のIssueより引用)

github.com

f:id:taketo957:20181206230900p:plain
CDKのアーキテクチャ

  • L2: aws-cdk内でConstruct Libraryとして提供されている部分で、デフォルト設定やベストプラクティスなどが内包されたライブラリ群です。
  • L1: aws-cdk内でcloudformation libraryとして提供されている部分で、CloudFormationのリソース定義から自動生成されているようです。

これらを利用して定義したものを、AWS CDK Toolkitを通じて、CloudFormationのtemplateに変換したり、stackをdeployしたり、deployされたstackとの差分を確認したりできます。

はてなでの利用例

aws-cdkについて簡単に説明したところで、実際にはてなにおいてどのように利用しているかの一例を紹介します。

はてなでは、開発中のbranchを展開し、それを外部から参照できるようにする開発環境のことをdevhostと読んでいます。 具体的には、{branchname}.test.comようなドメインで外部からそのbranchのアプリケーションを参照できる環境です。

今回は、ECSを用いてdevhostの仕組みを構築しました。 このECSを用いたdevhostをaws-cdkを用いて構築した事例を紹介します。

今回のECSを用いたdevhostの要件としては以下のようなものがありました。

  • branchごとに外部から参照できる環境を容易に作成したり、削除できたりできること
  • 上述の環境を管理できるUI(構成図の下部に構成だけ載せていますが今回の記事では触れません)

devhostの仕組みを実現するための、全体の構成は以下のような感じです。

f:id:taketo957:20181207114428p:plain
devhost構成図

リクエストを受ける部分(図上部)は非常に単純です。 Route53を用いて開発用のドメインのHostedZoneとRecordSetを管理し、ALBを用いてbranchごとの環境を展開したECSのServiceに振り分けています。

以下がaws-cdkを用いた具体的な例です。

gist.github.com

上記の両方の要件を満たすためのポイントは、branch名に応じて動的に作成する部分(上記の例では EcsServiceStack )を独立したstack定義として切り出すことです。(かなり省略していたり、簡単のための単一のファイルにまとめたりしていますが雰囲気だけでも伝われば幸いです)

タスク定義に用いるコンテナのimageにはあらかじめ、branch名に対応するtagを付与してあります。 また、ALBではHostヘッダによるtargetの振り分けを行っています。このホスト名はbranch名から生成していますが、あらかじめbranch名をURLで用いることができる形に正規化しておいてあげる必要があります。ListenerにTargetGroupを追加する際に、ListenerのRuleのPriorityの値が被らないように制御してあげる必要がある点もポイントの一つです。

肝心のbranch名に関しては、aws-cdkのcontextという機能を用いて実行時に受け渡しています。

Passing in External Values to Your AWS CDK App — AWS Cloud Development Kit

これまでの定義を用いて、例えば以下のコマンドを実行することで hoge.example.com というドメインから、 hoge ブランチの内容を確認できる環境を立ち上げたりしています。

$ cdk synth EcsService -c 'devhost:branchname=hoge'
$ cdk deploy EcsService -c 'devhost:branchname=hoge'

終わりに

今回は開発者プレビュー版のaws-cdkを用いて実際に開発環境を構築した事例を紹介させていただきました。

aws-cdkを用いることでアプリケーションエンジニアにも構成管理を積極的に行ってもらえるようになったりと、チーム内でポジティブな変化を日々感じています。

一方で、開発者プレビュー段階であることは事実で日常的にbreaking changeと付き合っていく覚悟は必要になってきます。 実際に所属しているチームでは、日常的にaws-cdkに対してPRを送るなどしてコミットしたりしています。

とはいえ、プログラミング言語を通じてAWSのリソース管理を行うことで、CloudFormationを素で利用するよりも柔軟にリソース管理を行うことができると感じています。(例えば、aws-cdk自体もnodeのアプリケーションであるので、Lambdaで動かして何かを行うといったことも可能だと思います)

これからどんどんコミュニティが活性化してくといいですね!

はてなエンジニアAdvent Calendar 2018、明日はid:wtatsuruさんです!

10ms以下のレスポンスタイムを支える継続的負荷テスト

この記事は、はてなエンジニアアドベントカレンダー2016の12月18日の記事です。

はてなエンジニアアドベントカレンダー2016を始めます - Hatena Developer Blog

昨日はid:ikesyoさんの「オープンソース活動への取り組み方」でした。

オープンソース活動への取り組み方 - Hatena Developer Blog

こんにちは。はてなでWebオペレーションエンジニアとして働いているid:taketo957です。
2016年の4月に新卒として入社してからは、社内の仮想化基盤のリソース最適化に取り組んでみたり、

speakerdeck.com

社内の広告配信システムの刷新プロジェクトに関わってきました。

speakerdeck.com

本記事では広告配信システムの刷新を行う中で取り組んだ負荷試験環境を構築する際に考えたことと「継続的にパフォーマンス改善を行うためにはどうすれば良いか」という問題意識の元で取り組んでいることを紹介します。

はじめに

はてなにおける広告配信システムとは何かについて簡単に説明します。 はてなブックマークなどのサービスにはPRタグが付与された広告枠が複数存在しています。 これら複数の広告枠のどこに、どの広告を、いつからいつまで掲載するかを管理し、それらに基づいて適切に配信を行うのがはてなにおける広告配信システムです。

広告配信システムに求められるもの

広告配信システムに対する要求には、以下のようなものが存在します。

  • 高可用性…広告配信は何があっても止めてはいけない。はてなブックマークなどサービスのPV数に比例するリクエストを受けても落ちないようにする。

  • 低レイテンシ…ユーザ体験を維持するために広告表示部分で時間をとってはならない。広告要求リクエストに対して、高速でレスポンスを返却する必要がある。

  • 配信結果の保存…ユーザに広告が表示された、ユーザに広告がクリックされたといった情報の根拠になるログは欠損してはならない。

本記事では、上記3つの要求のうち「低レイテンシ」を実現するために行った取り組みと、それを継続的に行うために取り組んでいることについて紹介します。

低レイテンシを実現するために

低レイテンシを実現するためには、広告配信を行うアプリケーションサーバの性能を最大限に引き出す必要があります。
アプリケーションサーバの性能を最大限に引き出す…」
短絡的ですが、ここから新卒の私は「要するにISUCONできる環境をつくれば良いのか」という結論に至りました。

ISUCONができる環境を言語化すると、以下のものが必要になります。

  • 配信システムに負荷をかけられるもの
  • 負荷をかけた結果をチームの全員で確認できるもの

これらを実現するために、負荷試験ツールのgatlingを用いました。 「広告を表示してからクリックする」などユーザの行動を柔軟に記述できるものである点、ScalaベースのDSLでシナリオを記述することができ、 固有の設定ファイルの記述方法を覚えなくて良くて開発チームにScalaを書ける人がいたという点、負荷試験の実行結果を詳細なHTMLレポートで出力してくれる点という3点からgatlingを選択しました。

負荷試験環境

負荷試験を行うにあたって、本番環境と同等のstaging環境、gatling用のサーバを準備しました。 また、gatlingは負荷試験を実行した結果をHTMLとして吐き出してくれるので、Nginxを通して結果を閲覧できるようにしました。 以下の図で全体のアーキテクチャと併せて今回の環境を示しています。

f:id:taketo957:20161218180108p:plain

負荷試験のシナリオについて

実際の負荷試験において検証したパターンの一部を以下で紹介します。

  • ユーザの行動
    • 配信OKの広告を表示、広告をクリックしない
    • 配信OKの広告を表示、広告クリックする
    • 配信NGの広告に対して表示要求
  • アクセス数のパターン(上記ユーザの行動に対して定義)
    • ピークタイムに耐えられることを確認するために、現状の広告配信システムのピーク値を再現、短い時間実行
    • 長期的に動作した際に異常が発生しないことを確認するために、現状の広告配信システムの平均的な値を再現、長時間実行
    • 配信サーバ1台あたりの限界値を見極めるために、負荷を徐々に増加させていく

上記の環境とシナリオを用いて、負荷試験を行い、アプリケーションプロファイラを用いつつアプリケーションエンジニアとボトルネック改善を行っていきました。
改善内容の概要については、はてなのサーバ/インフラを支える技術〜2016年新卒編〜 / OSC Tokyo 2016 Fall // Speaker Deckで紹介しています。 印象的だったものの一つに、DBへの都度接続を行っていたのですがシステムコールやアプリケーションプロファイラから、この部分のコストが馬鹿にならないことが判明したために常時接続方式に変更したというものがあります。
DBへの接続方式に関しては、Webシステムにおけるデータベース接続アーキテクチャ概論 - ゆううきブログ が詳しいです。

結果

入念に負荷試験を行った結果、現状本番環境において99%ile 10ms以内での応答を実現しており、非常に安定して稼働しています。

開発チーム内でパフォーマンス改善を意識し続けるために

これまで、リリース前に取り組んだことを紹介してきました。 アプリケーションには継続的に変更が加わり続けるわけですが、そのような状況でもパフォーマンスを維持し続け、 パフォーマンスに影響する変更を本番に投入する前に検知するにはどうすれば良いのでしょうか。 先程も述べたように、広告配信は絶対に止めたくないので、デプロイしてからロールバックという選択肢は基本的にはありません。 一般的に言われていることですが、パフォーマンス改善は計測することから始まります。 この計測のプロセスを継続的に自動で行う仕組みがあれば良いと考え、CIのプロセスに負荷試験を取り込むということに取り組んでいます。

概要を以下の図に示します。

f:id:taketo957:20161218193345p:plain

イデアとしては非常に単純で、Jenkinsでビルドとテストが成功した場合にstaging環境へデプロイし、 その後で負荷試験用のシナリオをgatlingサーバにデプロイして負荷試験を実行し、実行結果画面のURLをSlackを通じて開発メンバーに通知するというものです。

負荷試験をCIに組み込むために

負荷試験をCIプロセスに取り込む際に考えられる障壁には以下のようなものが考えられます。

  • エンドポイントの一覧が必要になる

    今回取り組んだ広告配信システムの特性の一つに、一般的なWebアプリケーションと比較してエンドポイントが少ないという特性がありました。 一般的なWebアプリケーションに取り込むことを考える場合には、エンドポイントの一覧を自動で生成するといった仕組みや、重い処理を行う部分に絞って負荷試験を行うというような回避策が考えられます。 現状取り組んでいる中での工夫点として、アプリケーションの変更に追従してエンドポイントの一覧を更新するという作業をしたくないので、 アプリケーション側で「データ生成とそれに対応するエンドポイントを返却するエンドポイント」を実装してもらい、 gatlingのJSON feeders という機能を使ってこの部分の自動化を試みています。

  • シナリオ記述のためのユーザの行動パターンの列挙

    上記に関連しますが、エンドポイントの数が少ないために予想されるリクエストのパターンが非常に少ないという特性も存在しました。 そのため、予想されるリクエストのパターンを列挙し、シナリオを記述するということが可能になっているのだと思います。
    また、負荷試験をCIに組み込むためにはアプリケーションの変化に追従して負荷試験用のシナリオもメンテナンスしていく必要があります。 今回の広告配信システムにおいては、上述の通り「広告を表示してからクリックする」というようなレスポンスに基づいて次のリクエストを組み立てるということを実現したかったのでgatlingを選択しました。 その為シナリオを記述するという作業が発生してしまいますが、単純にエンドポイントを一覧するだけで負荷試験を実施できるvegetaなど他にも様々な負荷試験ツールが存在するので、 Webアプリケーションに合わせて負荷試験ツールを選択することも重要です。

おわりに

はてなの広告配信サーバの性能を維持し続けるために、負荷試験を行いそれを継続的に行うための取り組みについて紹介しました。 負荷試験をリリースサイクルに組み込むことで、最新版のソースコードを本番環境のトラフィックにさらしても大丈夫かというテストが自動的にできることに非常に魅力を感じました。 不安を抱えながら祈るようにデプロイするのではなく、これなら大丈夫と安心してデプロイできるような環境を作っていきたいですね。

今回は仕組みの面にフォーカスして紹介させていただきましたが、またの機会に実際にどのようにしてチューニングをしていったかについても触れていきたいですね!

明日のアドベントカレンダーid:masayoshiです!

株式会社はてなに入社しました

こんにちは、id:taketo957です。

2016年4月より株式会社はてなでWebオペレーションエンジニアとして働くことになりました。

自分は2014年の12月から同じ部署でアルバイトをしていました。元々大学の研究や趣味でアプリケーションを書いてはいたのですが、インターンなどを通じてもっと下のレイヤーについて知りたくなったので応募したのがきっかけです。アルバイト採用直後にいきなり「ISUCONの過去問をやって」と言われて面食らったのは今となってはいい思い出です。

同期が優秀で、しかもパワフルな方が多いので切磋琢磨しながらも負けないように頑張っていきたいと思います。

入社するにあたって当面の目標として、

  • 先輩社員のようにどんどんアウトプットできるようになる
  • 応用的な内容にとどまらずその基礎となることも一緒に身につける

の2点を特に意識していこうと思います。

はてなには、日々自己研鑽を欠かさない方が多いだけでなく、本当に良いものを良いと言い合える素晴らしい雰囲気が漂っていると思います。また、良い意味で変わった方が非常に多いと思います。自分はそんな空気が大好きではてなで働くことを決めました。技術的にはまだまだ未熟ですが、しっかりとWebサービスを支えられるように力をつけていきたいと思っています。

これからもよろしくお願いいたします。