Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

コンテンツデータをEC2からAWS Fargate+Amazon EFSに移行した話

f:id:modokkin:20210422102643p:plain

どうも、ヤプリ SREチームのはぶちんです。久しぶりのブログ投稿です。
今回はEC2上に保管されたコンテンツデータをAWS Fargate(以下Fargate)+Amazon EFS(以下EFS)に移行したので、困ったことや結果的にどう実現したかをご紹介したいと思います。

ヤプリって?

Yappli(ヤプリ)はアプリ開発・運用・分析をノーコード(プログラミング不要)で提供するアプリプラットフォームです。 詳細はサービスサイトをご覧ください。 https://yapp.li/

ヤプリのコンテンツデータって?

本記事では、アプリのコンテンツを表現するために必要な設計図のようなものをコンテンツデータとして表現しています。
このデータは、可能ならデータ自体をRDBなどスケール可能な仕組みに移行する事が好ましいのですが、ヤプリで提供している機能が多岐にわたり移行にはまだしばらく時間を要するため、現在でも一部でファイルを扱う仕組みを採用しています。

問題点

アプリが扱う一部のコンテンツデータはコンテンツ更新時に参照系サーバにデータをコピーして利用しているのですが、新系統のAPIではgRPCを採用していることから旧系統のコンテンツデータを読み取る際に単一のラッパー(下図のコンテンツデータ管理サービス)を更新系のAPIと共有することになり、このままでは不安があるのでAPIを分離して参照系のサービスを用意する必要がありました。

新系統のAPIから旧系統のコンテンツデータを読み取る図 f:id:modokkin:20210323110250p:plain

インフラ構成を考える

いくつかの方法を検討したのですが、このあと紹介する2つのパターンに絞り比較的実装工数が抑えられそうな「プランA Fargate+EFSを活用する」で進め、何らかの問題が生じた場合にプランBを検討するという方針を決めました。
その結果、現状の利用状況であればプランAで十分実用に耐えられると判断しました。しかしその道のりは想像よりも長いものでした。。。

プランA Fargate+EFSを活用する(採用した案)

f:id:modokkin:20210323110321p:plain

良い点

  • インフラの管理コストを抑えるため、新しい系統はFargateを主要なインフラとして利用していた
  • 検討を始めた頃にちょうどFargateでEFSが利用できるようになった
  • EFSなら参照系のストレージを集約でき、不整合が起こらない

気になる点

  • EBSよりレイテンシーが大きくなるのでパフォーマンス影響が気になった
  • データ同期の遅延が実用上問題ないか検証が必要だった
  • 利用にはFargateのプラットフォームバージョンを1.4.0以上にする必要があった
  • Fargateならインフラの管理が不要

プランB 読み取り用のEC2インスタンスを用意する(お蔵入り)

f:id:modokkin:20210323110307p:plain

良い点

  • 既存環境の複製自体は容易
  • EBSを利用するのでパフォーマンスを維持できる

気になる点

  • 複数のサーバーにデータを配布する必要が有り、各サーバーの同期状態を管理するのが面倒
  • コンテンツデータのコピーがネットワーク越しになり、インスタンス数の増減なども考慮すると案外仕組みが複雑になりそうだった
  • レガシーなデプロイの仕組みを踏襲する必要があり避けたかった
  • これ以上管理対象のEC2インスタンスを増やしたくない

苦労した点

いざ開発環境で検証を始めると、様々な問題にぶつかりました。
その中でもインフラ寄りで印象に残った点を3つほどご紹介します。

意図しない通信エラー

パフォーマンス検証時に連続したアクセスを行った場合は問題ないのですが、5分ごとにタイミングを空けてアクセスするとアプリケーション側で意図しない通信エラーが発生する事象が発生しました。
Fargateなのでデバッグがやりにくかったのですが、検証用コンテナにSSHできるようにし、tcpdumpなどネットワーク解析系ツールで調査したところNLBで通信断が発生してる事が分かりました。(さらっと書いてますが原因を突き止めるまでにかなり苦労しました)
後々調べるとNLBのタイムアウトは下記のドキュメントに記述が有り、仕様であることが分かりました。

Elastic Load Balancing は、TCP フローのアイドルタイムアウト値を 350 秒に設定します。この値を変更することはできません。クライアントまたはターゲットは TCP キープアライブパケットを使用して、アイドルタイムアウトをリセットできます。

引用: https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/network/network-load-balancers.html

この事象は、envoyの設定でTCP keep aliveを有効にすることで回避可能なのですが、タイミングを同じくしてALBがgRPCに対応したので結局ALBに乗り換えました。(あと数ヶ月早ければ…)

envoy設定例

    upstream_connection_options:
      tcp_keepalive:
        keepalive_probes: 10
        keepalive_time: 300
        keepalive_interval: 5

ちなみにコンテナの調査についても、最近Amazon ECS Execという機能がリリースされたので今ならSSHではなくこちらを使ったほうが良いと思います。コンテナの辛い部分が解消されましたね。 https://aws.amazon.com/jp/blogs/news/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/

lsyncdで特定ファイルが同期されない

コンテンツデータをEFSに同期するためにlsyncd(inotifyを利用してファイルを変更検知とイベントを実行してくれるサービス)とrsyncを組み合わせて利用しているのですが、同期したいファイルが複数階層にあり、かつ同期不要なファイルも混在しているため条件がやや複雑でハマりました。原因としてはrsyncで正規表現が使えると思いこんでしまっていたのですが、rsyncは独自のルールに対応した記述が可能で正規表現の "[a-z1-9]{10}[.]" は "??????????." と指定することで同等の条件を再現することができました。
lsyncdのバージョンにもよるみたいですが、結果的に以下のような記述で同期できるようになりました。

sync {
    default.rsync,
    maxProcesses = 1, #同時実行プロセス数
    source="{SOURCE_PATH}", #コピー元
    target="{TARGET_PATH}", #コピー先
    delay=3, #変更検知から同期するまでの待ち時間(秒)
    copy_links=ture, #シンボリックリンクの元ファイルを辿る
    delete="running", #lsyncd起動時は削除処理を行わない
    rsync = {
      _extra = { "--include=*/", "--include=/main.file", "--include=??????????.file", "--include=hoge.file", "--exclude=*" }, #コピー元ディレクトリ以下の/main.file、?????????.file、hoge.fileに一致するファイルを対象とする
      prune_empty_dirs=true, #空ディレクトリは除外
      owner = true,
      group = true
    }
}

ファイル数が多く、EBSからEFSへの同期は最大で10秒程度の遅延がありますが、ほとんどは5秒以内に収まっており今回のユースケースでは大きな問題にはなりませんでした。

EFSバーストクレジット足りない問題

クライアントからのリクエストに対するパフォーマンスはクリアしたのですが、EFSの全てのファイルのハッシュ値をEBSの元データと比較する監視スクリプトを定期実行したところ、結構バーストクレジットを消費することが分かりました(下記画像はddコマンドで負荷を与え続けた場合のイメージ)。

f:id:modokkin:20210329115953p:plain
青線のクレジットバランスが減り続けている時のイメージ

EFSのバーストスループットモードのクレジット回復量は、ディスク使用量によって変動するため今回は9GBのコンテンツデータに対して50GBのダミーファイルを作成することでクレジットが横ばいになる状況を作ることが出来ました。

f:id:modokkin:20210329155446p:plain
青線のクレジットバランスが消費してもすぐに元に戻っている

参考までに5GBのファイルを10個作成する場合は下記のようなddコマンドになります。

for i in {1..10}; do dd if=/dev/zero of=efs_dummy${i}.dat bs=1M count=5120 ; done

EFSを採用してよかったところ

EFSが採用出来るかどうかはユースケースにマッチするかにもよりますし、導入した場合の構築やパフォーマンス測定などはある程度工数がかかりますが、いざ運用を開始した後の管理コストはかなり低いのではと思います。
バーストスループットモードであればバーストクレジットがある限りは高いパフォーマンスを維持出来ますし、監視もバーストクレジットとI/Oリミット使用率などを見ておけばある程度の兆候はつかめるのかなと思っています。
何よりFargateからもEC2からも自由にファイルを操作出来るのはかなり便利です(今回はFargate側ではEFSをReadonlyでマウントしていますが)。

まとめ

Fargate+EFSを採用することで、ファイル形式のコンテンツデータを扱うシステムで、アプリケーション側の変更を最小限に抑えつつインフラ側の変更で可用性を高めることができました。
実際にはサーバーサイドの開発メンバーにかなり助けてもらいながら進めたので、初めて導入するとなるとそれなりに工数が掛かると思いますが、結果的に運用の負担を減らすことができたので苦労した甲斐が合ったかなと思います。
同様の事例はあまりないかもしれませんが、少しでもEFSの導入を検討されている方の参考になれば幸いです。