【セッションレポート】AWSの「隙間」を埋める隙間家具OSS開発【#AWSDevDay】

こんばんわ、札幌のヨシエです。
先日より開催されているAWS主催のAWS DevDay Tokyo 2019に参加しております。
タイトルが魅力的だった面白法人カヤック藤原様のセッションを拝聴出来ましたのでレポートを書きます。

登壇者

株式会社カヤック 藤原 俊一郎 氏(@fujiwara
「みんなのGo言語」を書いてる人!!

スライド

アジェンダ

  • AWSの「隙間」とは
  • 「隙間家具 OSS」の事例と設計思想
  • Rin / s32cs / ssmwrapについて
  • なぜOSSなのか?

AWSの「隙間」とは

  • AWSはマネージドサービスがある
  • コア機能でリリースされる
  • その他便利機能が入っていない
  • 徐々に機能が増えていく

例題)RDS for MySQL

RDS for MySQLを例にして、隙間を考えてみる

  • サービスのコア部分にあたるMulti-AZは最初期に実装された
  • しかしRDS for MySQLを利用するためにはいくつか課題があった
    • デフォルトのタイムゾーン変更
      • デフォルトのタイムゾーンがUTCであり、JSTへのタイムゾーン変更が出来なかった
      • 対応としてタイムゾーンをUTC→JSTに変換するSQL文をパラメーターグループのinit_connectで実行していた
    • 連携便利機能としてCloudWatch Logsとの連携に時間がかかった
      • この部分が隙間として考えている

他サービスとの連携

  • 上記のCloudWatch Logsとの連携で目的としていたことは、SlowLogなどのDBエンジンが出力するログがストリーミングで確認する方法がなく、ユーザー側で独自に処理を加えていた

隙間が多いマネージドサービスを使うかどうか

隙間が多いマネージドサービスを使用するためには考える必要がある

  • マネージドサービスはAWSによって機能が強化される
  • 反面、自前運用部分は勝手に強くならない
  • パッチ適用やバージョンアップなどの人的コストを十分に払えないなら多少不便でもマネージドを選択することで時間経過とともに解消される可能性がある

隙間家具を自作する

前提として以下のことに気をつける

  • 小さく、ユースケース内で適切な汎用度を持ったものを作る
  • AWSの機能アップデートによって隙間が埋まったらいつでも捨てられる(外せる、削除出来る)粒度で作る
    • マネージドAとBが連携する時に足りない機能を隙間家具として作る
    • この隙間がマネージドの成長で解消された時に削除する

事例と設計思想

S3とRedshiftの間

  • Redshiftのデータ取り込みにはS3からのコピーが必須
  • Redshiftへ接続し、取り込むオブジェクトを指定してCOPYクエリを発行する必要がある
  • S3から継続的に取り込むマネージドな方法は存在しなかった(出来たらS3に置いたら自動で入ってほしかった)
  • そこで「Rin」というOSSを開発した

Rin(fujiwara/Rin)

  • S3イベント通知をSQSに送信
  • SQSのメッセージを元にRedshiftにCOPYを発行して取り込みを行うGo製ツール
  • 柔軟な設定として、スキーマに正規表現を使えるようにすることで複数テーブルを選択できるようにした
  • 2015/5に開発

  • ただし、別のアプローチも存在した

    • Fluentdにログを送り、S3へのアップロード後にRedshiftへCOPYを発行する
    • 運用してみると、S3アップロードとRedshiftへの取り込みが不可分があった
    • Redshiftが定期メンテやリサイズなどのでクラスターが停止すると、S3へのアップロードとCOPYが一体のため、リトライはアップロードからやり直しとなってしまう
    • 結果として本来やりたいことのS3とRedshiftへのCOPYが実行できず、S3にゴミが溜まってしまう

結果

  • Rin開発から5ヶ月後にKinesis Firehoseが登場
  • ログを送るとS3やRedshiftやElasticSearchにログを出してくれるのでやりたいことが出来るようになった

Rinの寿命は潰えたのか?

  • 実はまだ使っている
  • Fluentdからの取り組みはFirehoseで使っているがELBログはKinesis Firehoseを使わずにS3へ配置される
  • このようなものに対してRin継続して利用することで逐次取り込み対応に活躍している

Rinの教訓

  • どう考えてもマネージドになってほしいものはAWSの機能アップデートによって実装される
  • 複数のサービス間連携を一度に処理するとリトライが複雑になる
  • 単機能に特化しつつ、そのドメインでの汎用性をもたせると使い回せる
  • マネージドになった時にきれいに取り外せる設計が大事

s32cs(fujiwara/s32cs)

  • CloudSearchを利用しているところでデータを継続的に取り込めない課題があった
  • 2019-10月現在もマネージドでデータを継続的に取り込む方法がない状態のため、s32csを作成
  • s32cs = S3 to CloudSearch
  • S3のイベントトリガーが出来ることでLambda関数を実行
  • 関数ではS3に配置されたndjsonを加工
  • HTTP PostでCloudSearchに投入する

CloudSearchへの投入の課題

  • CloudSearchへは5MB以下のJSON配列を投入する必要がある
  • 細かい単位で投入するとパフォーマンスが劣化する
  • s32csに求めたものは「配列形式への加工、分割」「HTTP POST」を考えた s32cs以前
  • cronで過去5minのデータをDBから取得
  • データ自体の加工やHTTP POSTで投入するバッチで対応

本当にやりたいこと

  • 本当は順番に流し込みたかった、それが実現できないので5min間隔でLambdaを実行することで対応した
  • そこでイベントドリブンでFirehoseにストリームで処理することで、S3イベントトリガ処理のn分毎に実行するcronを代替する形で実装した
  • ストリームをn分事のchunkでS3に吐き出す処理をFirehoseで実施
  • S3には指定時間、または指定サイズに達した条件でオブジェクトが生成される
  • このように実装することで分散処理が可能になる
  • バッチ処理側に状態を保つ必要がない
  • 指定サイズを条件とすることでファイル容量の見積もりが立てられるので、安心してオンメモリ処理ができる

マネージド・サービスはいろいろある

上記のようなOSSを踏まえて、マネージドサービスを振り返ってみる。

  • クラウドネイティブ:インスタンスを意識しない(出来ないもの)
    • SQS
    • S3
    • CloudWatch
    • Lambda
    • DynamoDB
  • クラウドネイティブに近い:インスタンスはおぼろげに見えるがスケールが自動
    • ELB
    • CloudSearch
  • クラウドネイティブになりきれない:インスタンスが見えてスケールが自動で出来ない
    • RDS
    • ElastiCache

上記のように考えてみた結果 - クラウドネイティブなものを活用していくとスケールしやすい - クラウドネイティブな思考法は大事 というところに行き着いた。

s32csの教訓

  • n分ごとcron処理することは本当にやりたいことを実現しないと思われる
  • 適切に制約されたデータはイベントドリブンによる処理を実装することが良い
  • データ容量による制約をかけることでオンメモリ処理がしやすい
  • 状態はマネージドサービスに、状態を持たない処理だけ書くとスケールが容易になる

ssmwrap

  • SSMパラメーターストアの値を環境変数に設定してコマンドをexecする
  • 同様の処理をやりたいユーザーが多く、様々なリポジトリがGithubを探すと出てくる

ssmwrap

  • SSMパラメーターストアに/prod/DBPASSなどが入っていれば環境変数に値を設定してコマンドを実行する
  • これはECS task(コンテナ)にパラメーターストアの値を渡すために開発した
  • execする(子プロセスとして起動しない)wrapperなので、コンテナのENTRYPOINTに指定できる
  • リトライ実行可能としている、これはパラメーターストアがエラーを返した場合、リトライ出来ないと厳しい
    • ParameterStoreのリミットが公開されていない時期に負荷を掛けたことで一斉にParameterStoreへ値を取得しにいったことで503エラーが頻発したことが教訓となった
  • 2018-11 secrets機能リリースされたことで上記処理が実現できるようになった
  • secrets機能がリリースされた後もssmwrapの利用価値は無くなっていなかった
  • ECS以外でも使えることから、EC2で動作するShellscriptのシェバンに指定することで値を環境変数に渡せる
#! /usr/bin/ssmwrap -path /prod/ -- /bin/sh
echo $DB_PASS
  • AWS外(CircleCIなど)での実行にも仕込むことが可能
  • システム全てがECSでない状態ではまだまだ利用価値はある
  • 次に考えたことは指定したパスの値をファイルとして書き出してからexecを実行したい
    • パラメーターストアの値からファイルを書き出せない
    • 例えば鍵ファイルの値を取得して、ファイル化することが出来ない
  • Goで書かれていることからライブラリとしてLambdaでも使用することが可能
  • 現状ではLambdaでパラメーターストアの値を取得できないので、対応策の一つにSDK利用を考えた
  • SDKを利用すると複数コードの記述が必要となって見通しが悪くなるので、ライブラリとして書き出すことでコードの見通しが良くなることを考慮した
    • 見通しが悪いことでマネージドになった時に取り外しやすいような設計でありたかった
    • 環境ファイルにパラメーターストアの値を設定することに集中する事ができる
  • 現状ではECS secretsがサポート済み
  • Lambdaは未対応(そう遠くない時に出来ると思っている)
  • 値をファイルに書き出してからコンテナ起動ことはk8sでは出来るが、ECSでは現状出来ない
  • 将来マネージドになりそうな部分のため、アプリケーションコードへのベタ書きで解決しないと考えている
  • これまでの記述のように小さいツール・ライブラリに切り出しておくことで取り外しが容易になる

取り外せるものをわざわざ作る理由

  • アプリケーションで直接記載するとすると密結合する
  • いずれサポートされることを考えて取り外すことを主としているので、取り外したい時に密結合すると取り外しづらくなる
  • ライブラリではないコードは往々にして別のプロジェクトにコピペされるのでソース修正で触れなくなりがち
    • このような状況になると別担当者が触らない部分が出来てしまう、または変更コストが高くなる
    • ライブラリとして作成することで低コストにつながることから修正などを進めやすくなる

OSSとして作る

  • 自分らしか使わなくてもOSSにしまうことには良いことが多い
  • プロダクトの設計指針として「これをOSSにするならどうするかを考えるのは有用」(社内事情でOSSにに出来なくても)
  • ドキュメントを書く気になる
    • READMEは頑張って書くモチベにつながる
  • 過度な社内事情の混入を防ぐ
    • 「社内/そのプロジェクト利用以外でOSSである意味があるのか?」
  • 社内事情が反映されづらいように責任分界点を見極めることで将来のアップデートによってプロダクトを外しやすくなって設計がきれいになる
  • 魔改造版が増殖するのを防ぐ
    • 独立したパッケージになってないとコードをコピペで実装されることがある
    • コピペによって実装された部分は回収が行われると新機能やバグフィックスに追従できない
  • 同じ様な隙間家具をみんなが毎回手書きするのは無駄!!

まとめ

  • AWSマネージドサービスには隙間があるので隙間家具を作ってよりよい運用を目指す
  • OSSとして作ることを考慮することでより良い設計、実装となる
  • 全てを隙間家具として作るのではなく、AWSには要望はちゃんと伝えましょう!
  • アップデートに備えていつのタイミングでも取り外せるように設計しましょう

最後に

まず、隙間家具と表現されたのはとても腹落ちしやすい表現と思いました。
AWSは非常に早いサイクルでアップデートが実行されますが、弊社も同じように登壇者様が仰るような新サービスを利用/検証にて壁にぶつかる点は多々あり、メリデメや代替案、別サービスによる対処といったことをお話させて頂くことがあります。
この様な面をソースコードと各AWSサービスの特性を利用することで課題を解決することこそ「AWSを使いこなしている」と評価されると思いました。