BASE開発チームブログ

Eコマースプラットフォーム「BASE」( https://thebase.in )の開発チームによるブログです。開発メンバー積極募集中! https://www.wantedly.com/companies/base/projects

次世代の管理画面を作るフロントエンドの取り組み

f:id:aiyoneda:20190205184206p:plain

フロントエンドエンジニアの松原(@simezi9)です。BASEでは現在ショップ向けの管理画面をリニューアルするプロジェクトが進んでいて、UI/UXの更新と同時に創業当時から継ぎ足して作ってきたフロントエンドの技術スタックを一新しようとしています。この記事では、具体的にそのフロントエンドの更新でどのようなことに取り組んでいるのかをいくつかご紹介したいと思います。

Vue + TypeScriptを利用したMPA(multi page application)化

HTMLの構築をPHP(サーバーサイド)からJS(クライアントサイド)へ移行する

従来の「BASE」の画面ではPHPでHTMLの構築を行っていましたが、HTMLの構築をすべてPHPのコードから分離させて、Vueによるクライアントサイドでのレンダリングにしています。また管理画面の特性上(1ページあたりの閲覧時間が長く相対的にローディングの時間が短くなる)、サーバーサイドレンダリングなどは特に行っていません。

クライアントサイドでのRoutingの導入

Routingに関しては管理画面の機能を大きな単位でいくつかに分割して機能間のRoutingはCakePHPで行い、機能内ではSPAのようにクライアントサイドで細かくRoutingを行っていく構成になっています。フロントエンドのRoutingはVue Routerを利用しています。

f:id:simezi9:20190205125100p:plain f:id:simezi9:20190205125115p:plain

大雑把に責務を整理すると以下のようになります。

  • PHPサーバの役割
    • 大きな機能単位でのRouting
    • RestAPIの提供
  • Javascript(Vue.js)の役割
    • 機能内でのRouting
    • 画面の描画

RestAPIの構築とAPI Blueprintによるカタログの作成

データの取得・操作は基本的に全てAPI経由で行えるように必要なだけのAPIを用意しています。作成したAPIはAPI Blueprintを利用してカタログ化されていてサーバサイドのエンジニアとフロントエンドのエンジニアが疎結合に作業を進めていけるようになっています。

f:id:simezi9:20190205125454p:plain

フロント側でも基本的にAPIの構成と1エンドポイントに対して1ファイルで対応するようにinfra層を構築しています

f:id:simezi9:20190205125511p:plain

なぜVue+TSか

もともとBASEではHTML/CSS/JSによるフロントの構築をすべてデザイナーが担っていてフロントエンドエンジニアというポジションはありませんでした。この次世代管理画面プロジェクトでのフレームワーク選定もデザイナー陣主導で行われました。そのなかでRiot.jsやReactなども試した上で、Vue.jsがもっとも技術導入がスムーズに出来そうだという結論となりVueを採用しました。 また、プロジェクトのサブテーマとして「次の5年を支える」というものがあり、TSが提供する型システムによる安定性がプロダクトのライフサイクルをサポートしてくれることを期待してTSを導入しています。

開発していて実際どうか?

現行のVue2.xとTSの組み合わせはReactなどと比べると劣る面があることは否めない(props周りなど、型が失われてしまう場所が多い)ですが、API通信などのVueが絡まない部分でのTSの型システムのサポートは非常に強力ですし、Vueの次期メジャーバージョンであるVue3.0ではコアがTSに移行し、サポートの強化も表明されており今後に向けた選択肢としてVue.js + TSの組み合わせは有力だと思っています。

Vue.js + TSで最も問題になるのは、公式のドキュメントがJSで書かれていることであり、TSの書き方と若干サンプルコードが異なる という点です。これに関してはVue.jsに馴染みのあるメンバーがいれば大した問題にはなりませんが、全員がVueを初めて触るような場合には、いきなりTSを導入することには慎重になったほうがいいかもしれません(TSとJSはファイル単位で共存できるので後から追加することは難しくない)。

Storeをどうしているか

Vue.jsではデータを一元管理するためのライブラリとしてVuexを使うのが一般的ですが、現状でのVuexはTSとの相性があまりよくなく、せっかくAPIレスポンスの型を定義したのにいざVueコンポーネントで取り出そうとすると型情報が失われてしまいます。そこでTSを活かすためにあえてVuexを採用せず、GlobalEventBusのような構造を拡張した独自のStoreを使っています。この独自実装に関してはそのメリット・デメリットは以下のように現状では一長一短に感じています

  • メリット
    • TSの型をVueコンポーネント内部で利用して堅牢なコードを書ける
    • シンプルな実装で可読性が高い
  • デメリット
    • 最低限の機能しか持たず、Vue.jsのエコシステムの流れからは外れる
    • Vue.js devtoolsのVuexサポートが利用できない

後者のdevtoolsが使えない点に関しては、公式の実装を参考にして、storeの内部状態のダンプなど最低限のデバッグは可能にしています。

f:id:simezi9:20190205125532p:plain

AtomicDesignに基づいた共通UIライブラリの構築

次世代管理画面プロジェクトと並行して社内のサービスで共通して利用するためのUIコンポーネントライブラリを構築しています。現在BASEではAtomic Designを採用したデザインシステムの構築を進めており、そのSketch上のデザインシステムと対応する形でStorybookを利用してコンポーネントライブラリを作っています。 storybook-addon-vue-info@storybook/addon-knobsを利用して、メンバーがStorybookを見るだけでアプリを構築していけるように整備を進めています。

f:id:simezi9:20190205125548p:plain f:id:simezi9:20190205125555p:plain

ブランチ単位での自動デプロイ

このライブラリのリポジトリではブランチを切ってPRを作るたびにブランチ単位でStorybookがビルドされるようになっており、デザイナーとエンジニアの確認が簡単に行えるようになっています。

f:id:simezi9:20190206120054p:plain

自前でUIライブラリを実装していく上での工夫・苦労

  • 最初にコンポーネントを全部用意しようと考えない
  • 一番小さいレイヤ(Atoms)のコンポーネントから作る
  • コンポーネントだけを作ろうとせず、実際に使いながらライブラリを育てる
    • 特にformを構成するコンポーネントなど
  • SketchのデータとStorybookのデータを一致させることにこだわりすぎない
    • 見た目だけではわからないデザインの意図を拾ってStorybookに反映することを心がける

汎用的なコンポーネントを作るのは見た目の単純さに反して考慮することが多く、一発で作ろうとしてもなかなか作りきれません。有名なVueのUIコンポーネントライブラリの一つとしてVuetifyがありますが、その実装を見てもbuttonのtsファイルが170行inputで300行あります。実際自分でコンポーネントを作っても、「あの機能がない」、「このカスタムができない」と問題が続々と出てきては一個ずつ対処する繰り返しになります。その状態で複雑なコンポーネントを用意しても、品質が上がらずコンポーネントの修正の足かせになりがちなので、小さなコンポーネントから我慢して作っていくことが必要だと感じました。

最後に

BASEの次世代フロントエンド環境はまだ開発がはじまったばかりで、実際にはまだまだ課題が山積みです。次世代の管理画面も、UIコンポーネントライブラリも、自分の力で構築していってみたいというエンジニアを募集しているので、興味がありましたらぜひ一声かけていただければと思います。

open.talentio.com

PHPカンファレンス仙台2019にてBASEから2名登壇・スポンサー協賛しました #phpconsen

f:id:khigashigashi:20190204204450j:plain

こんにちは、BASE BANKでエンジニアをしている東口(@hgsgtk)です。

さていきなり本題ですが、2019年1月26日(土)にPHPカンファレンス仙台2019が開催され、ネットショップ作成サービス「BASE」は、シルバースポンサーとして協賛しました。

大変盛り上がった最高のカンファレンスだったのですが、BASEからは、私と田中(@tenkoma)がセッションスピーカーとして登壇しましたので、それぞれの発表内容についてレポートいたします。

phpcon-sendai.net

発表資料

テストを書くのがつらくならないテスト駆動開発のアプローチ

私、東口(@hgsgtk)は、午前の最初の登壇で30分の発表をしました。

「テストがつらい」状況の様々な要因のうち、「テストを書くのが億劫」なことや「テストを書くのが難しいコードになる」といった要因にフォーカスを当て、それらに対してテスト駆動開発のアプローチを活用することで解決できないかという内容です。

f:id:khigashigashi:20190204205341j:plain
ライブコーディング中の様子

当日は、ライブコーディングにてテスト駆動開発の流れを説明しました。 懇親会や後日Twitterなどで、感想や質問をいただけたのが大変ありがたかったです。

当日頂いた質問

テストを書くことに慣れてないと実際にやり始めるのに一苦労しそう

「テストを先に書く」という点について、テストを書くのに慣れていないと確かに難しいと思います。そのため、まずはテストを書くことに慣れるというのは前提として必要だろうという話をしました。

こちらについては、以前PHPカンファレンス大阪2018にて、テストを書いたことがないエンジニアがテストを書けるようになるまでやったことという発表しましたので、こちらの資料を参考にしていただくと良いかもしれません。

PhpStormとPHPUnitを連携してユニットテスト作成を楽にする

田中(@tenkoma)より、午後2つ目の登壇で30分の発表をしました。

PHPStormとPHPUnitを連携することでユニットテスト作成・実行を効率的に進めるというテーマで発表です。

f:id:khigashigashi:20190204205536j:plain
発表中の様子

田中の個人ブログにて当日の発表の様子をレポートしておりますので、こちらも併せてご覧ください。

tenkoma.hatenablog.com

最後に

PHPカンファレンス仙台2019は今年初開催でしたが、良い意味で「初開催とは思えない」ほどしっかり運営されていて、とても楽しいイベントでした。 運営の皆様、素晴らしいカンファレンスをありがとうございました!

次はPHPerKaigi2019

次のカンファレンスは、3月29日〜3月31日にPHPerKaigi 2019が開催されます。 BASEからは今回仙台で登壇した田中・東口が同じく登壇します。

田中からは、「PhpStormでコードを理解する技術」(レギュラートーク 15分)というテーマで発表します。

fortee.jp

東口からは、「「質」の良いユニットテストを書くためのプラクティス」(レギュラートーク 30分)というテーマを発表いたします。

fortee.jp

また、ネットショップ作成サービス「BASE」は、ゴールドスポンサーとして協賛しています。

ぜひ、皆さんPHPerKaigi 2019でお会いしましょう!

カートの負荷試験におけるApache JMeterの活用

f:id:ymiyamura:20190122163945j:plain

先週に引き続き、BASEでサーバサイドエンジニアをしている宮村です。

先日、負荷試験の取り組みについて紹介させていただきましたが、今回はその際に使用したApache JMeterの活用について紹介させていただきたいと思います。

JMeterは高機能なツールなので使いこなすと強力ですが、少し複雑な機能のテストを行おうというとき、ややとっつきにくい部分もあるのではないかと思います(私はそうでした)。具体的な使い方をいくつか知っておくだけで、ぐっと便利に使えるようになると思いますので、これから負荷試験を行おうという方に少しでも参考になれば幸いです。

選定理由

負荷試験を行うツールはいくつかありますが、今回は下記の条件が満たせるものを探していました。

  • セッション管理できること
  • ページ遷移を伴うシナリオが作成できること
  • シナリオでレスポンスの値を取得して使えること
  • 攻撃サーバがスケールすること
  • 習熟コストが高すぎないこと

これが満たされていれば、どれでもいいかな〜と思っていたので、以前少し使ったことがあったJMeterでできそうだと見えてきた時点でJMeterで進めることに決めました。

他に見たものを少しだけ紹介します。

Apache Bench:導入及び使用がとても簡単ですが、複雑なシナリオを扱うことができないため、カートの負荷試験には不向きであると判断しました。

Locust:JMeterと機能的には類似しているとのことで良さそうにも思いましたが、私個人にとってPythonでシナリオを書けることがあまりメリットにならないこともあり、選択しませんでした。

シナリオ作成

下記の手順で、シナリオを準備していきます。

  1. テストケースの作成
  2. 作業PCへのJMeterの導入
  3. 記録コントローラでテストシナリオのベースを作成
  4. 実現したいテストシナリオへ改修

1. テストケースの作成

どのような高負荷状態を作りたいかを決めます。〇〇のページに、単位時間あたり〇〇のアクセスを、〇〇の時間発生させる、というようなものになるかなと思います。 これはツールによらない工程ですが、これがなければシナリオは作れません。今回は、過去の高負荷状態を再現できるようにケースを作成しました。

例)商品Aを1個、クレジットカードで購入するユーザが1分間に100人のペースで5分間来る、等(※数字はイメージです。)

2. 作業PCへのJMeterの導入

公式ページに従い、インストールを行います。

必要に応じてJavaのインストールを併せて行います。

(この工程の情報はWeb上にたくさんありますので、詳細は割愛させていただきます)

3. 記録コントローラでテストシナリオのベースを作成

シナリオ作成には「記録コントローラ」を使用しました。

JMeterをプロキシとして動作させ、ブラウザのプロキシ設定をJMeterに向けることで、ブラウザでの操作をJMeterで記録する機能です。

これを使えば記録したシナリオをそのまま使用できるはずでしたが、いくつかのパラメータが欠けていたので補ったり、不要なシナリオを削除したりといった作業が必要でした。

ただ、この修正の工程が必要であったとしても、記録コントローラを使うメリットは大きいと感じましたのでおすすめです。

4. テストシナリオの改修

1で決めたテストケースを実現できるよう、各種の設定値を書き加えたり変更したりします。

今回は、たとえば商品IDなどのいくつかのパラメータは変数として定義するなど、必要に応じて記録されたシナリオに改修を加えていきました。

ここで、便利だったエレメントを以下に紹介します。

便利だったエレメント8選

  1. ユーザ定義変数
    • 手元の環境でシナリオを作成し、試験環境にで実行するということをしたので、urlなど環境固有の値を簡単に切り替える必要があり使用しました。
  2. HTTP認証マネージャ
    • basic認証をかけた環境で試験をするのに使用しました。
  3. HTTPクッキーマネージャ
    • セッションを利用するので使用しました。
    • 異なるユーザの購入を想定したので、「繰り返しごとにクッキーを破棄する」設定にしました
  4. アサーション
    • 各リクエストで、レスポンスコードは問題ないが、期待するレスポンスが返らない場合にテストを失敗させるため使用しました。
    • 具体的には、正しく次の画面に遷移した場合と、エラーで同じ画面にとどまっているにもかかわらずレスポンスコードは正常な場合を区別したい場合に使用しました。
  5. HTMLリンクパーサ
  6. 正規表現抽出
    • 前の画面で生成した値を、後続のリクエストのパラメータとして使う場合に使用しました。
    • はじめは後述のHTMLリンクパーサを使っていたのですが、JMeterサーバを複数台構成にして、リモート実行した際に正常に動作しないことがあったため、正規表現抽出に切り替えました。
  7. 統計レポート
    • シナリオ実行の様子を眺めるのに使いました。
    • JMeterには様々な高機能なリスナがあり便利なのですが、リクエスト数を増やした場合に、リスナの処理が重くなってしまうそうです。それを避けるため、これとシンプルデータライタの2つだけを実際の試験実行時には使用しました。
  8. シンプルデータライタ
    • シナリオ失敗の具体的な状況を調査するため、結果はすべてCSVファイルに書き出しました。
    • リクエスト失敗の原因調査等、必要に応じてExcelで調査しました。

f:id:ymiyamura:20190128134101p:plain
完成したシナリオの例

実行

シナリオ実行

AWS上のWindowsサーバ1台にJMeterを設置し、少ないリクエスト数から徐々に負荷をかけていきました。

最初は、シナリオや設定値の妥当性の確認も兼ねて、1リクエストから実行していきます。

リクエスト数を増やす過程で、JMeterサーバがボトルネックかな?というタイミングでリモート(Linuxサーバ2台)で実行するように変更しました。 socket write error, failed to respond, closed connection というエラーが、シンプルデータライタで取得したログに出力されたタイミングがそれです。

結果の記録

どういうシナリオを流したか、結果はどうだったかをスプレッドシートに都度記録しました。「実験ノート」をとる要領で、下記のような内容を都度記録しておきました。これが後に結果レポートになっていきます。

  • リクエストの種類
  • 成功数、失敗数
  • シナリオ完了までの時間
    • 成功はしているが遅延している、を検知
  • 実行時刻
    • 各種ログを後から調査するため

まとめ

  • フォームの入力やセッションを利用し、複数画面の遷移を伴う機能であるカート機能の負荷試験を、JMeterを使うことで実施できました。
  • ツールの選定には、試験でどういった機能が必要かをまず明確にするのが重要でした。
  • シナリオが資産として残っているので、今後も改善しながら負荷対策に役立てていく予定です。

最後に

本番相当の環境でのパフォーマンス改善に興味のある方、これからもさらなる改善を行っていくことになると思いますので、ぜひご連絡ください!

open.talentio.com

Eコマースプラットフォームの成長を支える負荷試験の取り組み

f:id:ymiyamura:20190122163945j:plain

BASEでサーバサイドエンジニアをしている宮村です。

つい最近まで、主にEコマースプラットフォーム「BASE」の決済領域の開発をしていました。決済領域は、いかなる場合でも安定稼働が求められる領域です。いかなる場合でもというのは、BASEが対応する各種決済方法やクーポン、ショップコインなど機能の組み合わせという意味でもそうですが、アクセス集中による高負荷に対しても、同様に安定が求められます。

前者に対しては、弊社東口からユニットテストの取り組みを以前紹介させていただきました。今回は、後者への取り組みとして、負荷試験の取り組みを紹介させていただきたいと思います。

動機

「BASE」は「Eコマースプラットフォーム」ですので、主として提供している機能の一つに、「カート」の機能があります。主として提供しているということは、サービス開始当時から、この機能を提供させていただいているということでもあります。

f:id:ymiyamura:20190122164228p:plain
リリースした頃のBASE

おかげさまで多くのショップ様、購入者様にご利用いただけるようになり、その性能面について把握、改善することがこれまで以上に求められる場面が出てきました。

改善するためには、改善したことが評価できなくてはいけません。購入に関する定量的な指標をおいて、計測することにしました。

準備

環境

前提として本番相当のものを用意することにしました。いくつかの懸念があったのですが、下記のようにクリアすることができました。

1. コスト面の課題

使うときだけ簡単に試験環境を立ち上げられる仕組みを作ったり、関連している諸サービスのうち今回のシナリオで必要なものだけを本番相当にするなど、コストを抑える工夫をしました。

2. 外部サービスとの連携

カートシステムでは、外部の決済サービス等との連携を行っています。負荷試験といっても、外部サービスへの過負荷は望ましくありません。(サービスによっては許可されない場合もあります。)今回は内部の性能試験にフォーカスしたかったこともあり、外部サービスを模したモックサービスを構築してそこに接続するようにしました。

また、試験中の状況の把握や、発生した異常の解析のため、監視サービス等も、本番同様のものを導入しました。

ツール

試験ツールには、諸条件を考慮してApache JMeterを使用しました。

セッション管理やページ遷移を伴うシナリオが必要だったこと、負荷テストサーバーのスケールが容易にできること、メンバーの経験などを考慮してツールの選定を行いました。(JMeterの活用については、別の記事で述べられればと思います。) 結果として、機能や習熟の不足などによる大きな問題もなく、試験を行うことができました。

シナリオ

カートに商品を投入してから、購入者情報の入力、購入完了までの数画面を遷移するものとしました。カート内は購入者情報など入力される値も多いのですが、JMeterの「記録コントローラ」を使用することで比較的手をかけずにシナリオを作ることができました。

実施

役割分担

今回の負荷試験は、主として3名のエンジニア(アプリケーションエンジニア2名、SRE1名)で進めました。

シナリオ構築やモックサービスの構築、シナリオ実行、レポーティングなど、主にアプリケーションに関する部分をアプリケーションエンジニアが担当し、環境構築などの部分を、SREが担当しました。

それぞれがやるべきことに集中できたことで、環境構築の工夫やモックサービスの構築などプラスアルファの部分にも力を割くことができたことはとても良かったように思います。

さて実行

シナリオを実行してみると、いくつかの課題が発生しました。

モックサーバがボトルネックになっていた!

これは元々、様子を見ながら調整する予定でした。LBとappサーバのログを確認し、接続数を増やして安定しました。

f:id:ymiyamura:20190122164441p:plain
SREのメンバーがここをサッと見てボトルネックを特定してくれました

購入ではないところがボトルネックになっていた!

当初のシナリオでは、まず商品画面にアクセスして、カートに商品を投入するということをやっていたのですが、商品画面に使っている外部サービスが開発用のスケールのものでした。今回のシナリオでは不要なアクセスだったため、本当に必要な画面遷移に絞ることで解決しました。

f:id:ymiyamura:20190122164540p:plain
New Relicで、遅くなっている処理を特定しました

想定したところと違うボトルネックが発覚した!

DBがボトルネックになるかな?と想定していたところ、Apacheがボトルネックになっているような挙動を見せました。どちらも本番相当の環境であるため、当初想定していた負荷の強度をクリアしていたこともあり、このあたりが限界であると結論づけました。いくつかの設定を変更して追加の試験を行い、即座に反映できるような変更点は見つけられませんでしたが、次の改善への取り掛かりとして知見が得られました。

f:id:ymiyamura:20190122164603p:plain
Mackerelで監視

f:id:ymiyamura:20190122164639p:plain
DBエンジニアの植木に教えてもらったRDSのPerformance Insightsがとても便利でした

実施中の様子

Slackで実況しながら、わいわいやりました。 負荷を増やしていきながら、エラーなく処理を完了している様子や、謎のエラーが発生していることを随時報告するようにしました。隣の島のリードエンジニアがさっくりと原因を指摘してくれたり、SREのメンバーがインフラを調整してくれたり、次々に現れる課題が攻略されていくさまに感動と感謝でいっぱいになりました。

f:id:ymiyamura:20190122164701p:plain

結果

負荷試験を実施すると決めてから2ヶ月程度で結果を出すところまで行うことができ、定量的なレポートを社内に共有できました。 また、並行して進めていた改修についても、前向きな効果がありそうだという結果も得ることができました。 トラブル以外では決済領域が目立つことは少ないのですが、前向きな成果を共有できたことは、チームにとっても良かったなと思いました。

まとめ

  • 負荷試験を実施することで、システムの性能について定量的な指標を得ることができました。
  • 性能に関する改善を行うにあたり、その効果が見込めることを改修前に示すことができました。高負荷状態は常に発生するものではないので(残念ながら)、同等の環境で再現させることで、その効果や副作用がないことを確認したうえでリリースの判断をすることができました。
  • 高負荷状態を再現させる環境を手に入れたことで、安心してサービスを成長させていく土台ができたかなと思います。他の箇所で高負荷に起因する課題が発生した場合にも、知見と環境を活用して対応していけるものと期待しています。

最後に

本記事では、BASEで実施した負荷試験の取り組みについて紹介させていただきました。負荷試験に取り組むにあたり、特定のツールの使い方以上の実践的な情報がまだ少ないなと感じるところもあり、一つの実践例として本記事が少しでも参考にしていただければ幸いです。 また、本文では簡単な紹介になってしまいましたが、JMeterの活用やモックサーバの構築については今後別の記事で紹介できればと思います。

BASEでは60万ショップの決済を支えるエンジニアを募集しています。支えるだけでなく、より良い決済システムにしていく挑戦をやっていきますので、興味のある方はぜひ こちら からご連絡ください!

open.talentio.com

ECS(Fargate)でコンテナアプリケーションを動かすための設定情報の扱い方

あけましておめでとうございます。 BASE BANK株式会社でソフトウェアエンジニアをやっている東口(@hgsgtk)です。

2018年末から年明けにかけて、EKSが東京リージョンに来たりAWSからのリリースが賑わいを見せていますが、その中から、AWS Fargateの次の新機能を実際のプロダクトに適用しました。

AWS Fargate プラットフォームバージョン 1.3 でシークレットのサポートを追加

これを期に、コンテナアプリケーションにおける設定情報を扱う考え方・それを実現するためのAWSのサービス構成と得られた利点についてまとめてみようと思います。

目次

  • 設定情報の扱いに対する要件
  • コンテナベースの設計思想・原則
  • ECS(Fargate)を用いる場合の設定情報の扱い方

設定情報の扱いに対する要件

開発背景

BASE BANK社では、即時に資金調達ができる金融サービス「YELL BANK(エールバンク)」というプロダクトを開発しています。 以前本ブログに投稿した「GoのAPI開発現場におけるユニットテストTips」 や「Goを運用アプリケーションに導入する際のレイヤ構造模索の旅路 | Go Conference 2018 Autumn 発表レポート」の通り、Go言語でアプリケーションを開発しており、docker buildしたイメージから作成したコンテナをECS(Fargate)上で動かすことで機能を提供しています。

設定情報とは

本記事内での「設定情報」は、データベース、Memcached、他バックエンドサービスなどのリソースハンドルのための認証情報を指します。これらは、本番・ステージング・開発・QAなど環境ごとにそれぞれ異なる値を持つ属性があります。

コンテナアプリケーションの設計として、設定情報をどう取り扱っていくか次に考えていきます。

コンテナベースの設計思想・原則

コンテナベースで動かすアプリケーションでの設計思想について次の2点の資料がよく参照されます。

この2つの資料からコンテナベースアプリケーションにおける設定情報の扱い方について見ていきます。

Beyond the Twelve-Factor App

2012年に、モダンなクラウドアプリケーションのベストプラクティス「The Twelve-Factor App」をHerokuの中の人が書きました。Beyond the Twelve-Factor Appは、2016年にPivotal社が、オリジナルのガイドラインのアップデート・ガイドラインの追加したものです。

クラウド環境を活用したクラウドネイティブアプリケーションの新たなベストプラクティスをガイドラインとしてまとめてくださっています。次のURLから実際にPDFをダウンロードすることができます。

content.pivotal.io

また、次のスライドが大変わかりやすくガイドラインについてまとめてくださっているので、そちらもご参照いただくと良いかと思います。

Chapter 5 Configuration, Credentials and Code

この資料内で、特に設定情報については、Chapter 5 Configuration, Credentials and Codeという章で言及されています。ピックアップすると大きく次の内容です。

  • 設定や認証情報はコードから分離すべき
  • 環境は無限に増えていくため、環境(dev, prd...etc)ごとに設定をグルーピングすべきではない。
  • 設定をコードから分離する一番の方法は環境変数への格納である。

設定情報とコードを分離することを求めており、分離できているかどうかの指標として、今すぐにでもコードをオープンソース化できるかを上げています。

次にもう一つredhatが公開している設計原則について見てみましょう。

Principles of container-based application design (Redhat)

redhatが公開しているホワイトペーパーで、コンテナベース・アプリケーションの設計原則についてまとめています。 次のURLにて日本語版の資料をダウンロードすることができます。

www.redhat.com

IMAGE IMMUTABILITY PRINCIPLE (IIP) イメージ不変性の原則

いくつかの原則を説明している中で設定情報についての扱いに関連のあるものとしてこちらの原則があります。

コンテナ化アプリケーションは不変であるため、ビルドされた後、異なる環境間で変化することは想定されていません。つまり、環境ごとにコンテナの作成や修正を行うのではなく、ランタイムデータの保存に外部手段を利用し、外部化した設定を環境によって使い分けます。

上記の記述により、yamlファイルなどでアプリケーション内で設定情報を分けてそれぞれビルドする方法ではなく、ビルドするアプリケーション内に環境によって異なる情報は入れず、外部に保存した設定情報を使用することが推奨されています。

2つの思想・原則から方針を見出す

これまで見てきた2つの資料から、コードから設定情報を分離し、環境変数から外部設定した設定情報を注入する構成としていきます。

ECS(Fargate)を用いる場合の設定情報の扱い方

設計方針が決まったところで実際にAWS、特にECS(Amazon Elastic Container Service)を利用した場合の設定情報の扱い方についてです。

概要構成

概要構成は次のようなものになります。

f:id:khigashigashi:20190115190403p:plain
概要構成

以下、AWS Fargate プラットフォームバージョン 1.3 でシークレットのサポートを追加で発表された機能を利用するため、プラットフォームバージョンは1.3以上を前提とします。

アプリケーション

環境変数から設定情報を取得する実装とします。例えば、Go言語の場合はosパッケージを利用して次のようなコードを書くことになるでしょう。

dbHost := os.Getenv("DB_HOST")

ビルドしたアプリケーションのコンテナイメージは、ECR(Amazon Elastic Container Registry)Docker Hubといったコンテナレジストリサービスにpushします。

設定情報の保存

設定情報は、SSM(Systems Manager) Parameter Storeに保管していきます。

SSMでは、パラメータを階層的に管理する事が可能です。

docs.aws.amazon.com

具体的には、/stage1/stage2/stage3 といった形で階層を掘っていく形になります。

この際、DBのパスワードなど暗号化して保存したい場合は、KMS(Key Management Store)を用いて暗号化キーを作成し、そのキーを持って暗号化します。

SSMに設定する情報を暗号化する際は登録時に暗号化オプションを付け、KMSキーを指定します。

aws ssm put-parameter --name /goecssample/database/sample/master/password --type "SecureString" --value "password" --key-id "< KeyId >" --description "データベースのmasterユーザーパスワード" --region ap-northeast-1

具体的には、typeをSecureStringとし、key-idに作成したKMSキーを設定するだけです。

コンテナから設定情報を利用

SSMに保存した設定情報を、コンテナに挿入するためにはタスク実行ロールに対してSSMのパラメータ取得・KMSキーでの復号を許可する必要があります。 具体的には、タスク実行ロールに対して以下のようなポリシーを追加します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameters",
        "kms:Decrypt"
      ],
      "Resource": [
        "arn:aws:ssm:region:aws_account_id:parameter/parameter_name",
        "arn:aws:kms:region:aws_account_id:key:key_id"
      ]
    }
  ]
}

タスク定義

ECSのプラットフォームバージョン1.3.0より前、Fargateを利用している場合、System MangerのParameter Storeなどから設定情報をコンテナに注入するには、自前でdocker-entrypoint.shなどからSSMに対してAPIアクセスして取得する必要がありました。

しかし、今回のサポートによって、タスク定義内のコンテナ定義に設定することによって、コンテナ起動時に自動でコンテナに対して設定を注入することができるようになりました。

具体的には、コンテナ定義の環境変数に、環境変数名・SSMのパラメータ名を指定するだけです。

f:id:khigashigashi:20190115190517p:plain
コンテナ定義/環境変数設定

1.3より前は、次のようなentrypoint.shなどを書いて対応していました。

#!/usr/bin/env bash

set -e

export DB_HOST=$(aws ssm get-parameters --name /prd/database/host --query "Parameters[0].Value" --region ap-northeast-1 --output text)

exec "$@"

1.3へのアップデートによって、自前での実装が不要になり、かなりシンプルになりました。

その他

その他、より詳細の説明は、AWS公式の次のドキュメントを参照してみてください。

docs.aws.amazon.com

この構成にして得られた利点

コードから設定情報を分離できた

コードから設定情報を分離したことによって、全アプリケーションエンジニアがすべての設定情報を参照できる状況は避けることができました。 加えて、設定情報は、AWS Systems Manager Parameter Storeに設定されています。 本番・ステージングなどでアカウント自体が分かれている場合は、そもそもアカウントを発行されている人のみに閲覧を絞ることができ、加えてIAMの設定等で誰のアクセスを許可するかを制御することが可能になりました。

設定の量が増えても階層的に扱える

AWS Systems Manager Parameter Storeでは、階層的にパラメータを登録することが可能なため、情報を階層的に扱いたいモチベーションもある程度満たしてくれました。

シークレットのサポートによって得られた嬉しいこと

AWS Fargate プラットフォームバージョン 1.3 でシークレットのサポートを追加によって嬉しかった点も記載します。

コンテナ起動時の複雑性が軽減された

当初、デプロイ時の複雑性を増していたdocker-entrypointでの自前実装が不要になり、アプリケーションの複雑性が減りました。

イメージサイズが小さくなった

副次的な効果として、AWSのAPIに対するアクセスが不要になったため、実行イメージに含めていたpythonaws-cliが不要になりました。 その効果によって、約40MB程度だったイメージサイズが約10MBまで小さくなりました。

まとめ

そもそも、コンテナアプリケーションをどう扱うべきなのかという観点から、実際にAWS Fargateを利用した場合についての説明をさせていただきました。 当初扱いづらさがあった部分もアップデートによって日々使い勝手がよくなってきている印象を覚えています。 現在、ECSなどでアプリケーションを構成しようとしている・または検討している方にとって、一つの参考になれば幸いです。