エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

たくさんのオンプレサービスをひたすらクラウドに移して得られた知見まとめ

こんにちは、エムスリーエンジニアリンググループの福林 (@fukubaya) です。 本記事はエムスリー Advent Calendar 2020 の8日目の記事です。

この記事とかこの記事とかこの記事で 書いているように、弊社ではオンプレ環境で稼動するサービスのAWSやGCPへの移行が進行中で、 ここ数ヶ月でクラウド移行作業が自分の業務の9割を占めています。

いろんなサービスのクラウド移行(主にECS Fargate)をやってきて知見が貯まってきたので一旦まとめてみます。 当初は何を考慮しなければいけないのかもよく分かっていませんでしたが、数をこなした結果、気をつけるポイントが分かってきました。 Docker化してECS Fargateで動かすのが目標ですが、GCPでk8sでも基本的に考える点は共通だと思います。

f:id:fukubaya:20201206033709j:plain
秩父ミューズパークは、埼玉県秩父市および秩父郡小鹿野町にまたがる地域にある公園。本文には特に関係ありません。

なお、クラウド移行の話題は過去2回YouTubeライブで配信されているので、よかったら合わせてご覧ください。

まずはクラウドに移す

とにかく移行することが目的なので、リファクタリングやコードの改修はDocker化するのに必要な最小限にとどめる方針です。 移行を進めていくと、リファクタリングなどを合わせてやりたくなりますが、不具合が起きた時に、移行自体が原因か、 コードの改修が原因か切り分けづらくなりますし、移行完了までの時間も長くなるので、改修はあとまわしにします。

設定は環境変数で

Docker化する狙いのひとつとして、Dokcer imageをアプリケーションの動作環境と独立させて、 環境が変わってもそのまま使えるものにすることがあるので、 環境ごとに変えるべき設定はできるだけ環境変数で渡せるようにします。 ECSでは、秘密にしたい設定(パスワードなど)はSSMで、そうでない設定はタスク定義で設定することになります。

まずはアプリケーションの設定がどこに、どのように設定されているか確認します。 設定ファイルにまとまっていればよいですが、設定ファイル以外にコード中に設定が埋まっている場合もあります。 コード中から直接環境変数や、Javaならシステムプロパティを読んでいる場合もあります。 System.getenv() とか System.getProperty() などを git grep して探しましょう。 これらの設定のうち、環境ごとに変えたい変数を環境変数で切り替えられるようにします。

なお、アプリケーションによっては環境ごとに設定ファイルを切り替える (application.prod.conf とか application.dev.conf のように) ものや、 コード中で環境変数の値に応じてif文で切り替える(なので値自体はコードに書いてある)ものがありますが、 Docker image内に環境ごとの設定が入ってしまうことになるので、 個人的にはDocker化する段階で直した方がよいと思います。 が、無理に直して事故るのも恐いので、難しそうならそのままにします。

ログは標準出力に書いてFireLensで振り分け

出力先をすべて標準出力に

ECS Fargateで運用する場合、どのようなLogDriverを使うにしても、 ログはファイルに書き出す訳にはいかないので、すべて標準出力に書くことになります。 モダンなloggingシステムなら、出力先をファイルから標準出力に変更することはそれほど難しくないと思います。

ログを振り分ける

ログがアプリケーションの動作ログ1種類で障害時に確認するだけでよいならば、 そのまま標準出力の内容をCloudWatch logsに出すだけです。 が、そういうアプリケーションは少なくて、本体の動作ログ以外にも、 DBに書かないような利用履歴をログに書いて、それをBigQueryに送ったり、S3に保存したりすることの方が多いです。

ログを複数種類に分けたり、別の場所に送信、保存するならば、 logdriverにFireLensを設定して、FluentdやFluent Bitで振り分けることになります。 豊富なプラグインが揃っていますし、だいたいの用途はこれで解決できるはずです。

ただ、個人的にはここが移行で面倒なポイントでした。 もともとは別々のファイルに書き出されていたログが標準出力にごちゃまぜになって流れてくるのを フィルタして、タグ付けして、それぞれにルーティングする設定を何回も試行錯誤したり、 フィルタしやすいようにアプリケーションの出力側の形式を修正したり、が意外と時間がかかります。

なので、ログの種類が多い、とかFluentd/Fluent Bitの設定が難しいならば、 とりあえずEFSでマウントした領域に保存して、保存したログの処理はLambdaとか小さいEC2で何とかするものありだと思います。

外部との通信を確認

アプリケーションがどこに接続しているか、どこから接続されるか、を調べます。

移行によってIPや経路が変わるので、接続先の設定を更新する必要があります。 特に、インターネットを経由する場合は、接続先がグローバルIPで接続を制限して場合があるので、 NATゲートウェイを通して固定のEIPでアクセスできるようにインフラ構築が必要になる場合もあるので注意が必要です。

接続される場合も、経路によってALBをprivateに置くか、publicに置くか、 SecurityGroupにどのように設定するか、ALBの設定に関わるので、事前に把握する必要があります。

監視はとりあえずCloudWatchとSentry

現状の運用でどのような監視と通知を実施しているかを調べます。

今のところ、細かい監視や計測が必要なサービスはないので、 CloudWatchメトリクスで測れるようなCPUやメモリ、ALBのヘルスチェックに対するメトリクスアラームを設定して、 異常値の場合にAmazon SNS経由でメールやslackで通知するくらいでした。

1つだけGolang実装のサービスで、goroutineの数を監視したかったので、 定期的にCloudWatchのカスタムメトリクスでgoroutineの数を出力させました。

golang.org

より細かい監視やメトリクスが必要ならば、Datadogを使ったり、 最近ならばCloudWatch経由でPrometheusメトリクスを収集するのがよさそうです。

www.m3tech.blog

docs.aws.amazon.com

なお、エラー監視はオンプレでもSentryを使っているので、そのままSentryに任せています。

sentry.io

バッチも忘れずに

アプリケーション本体だけでなく、バッチがあればこれも移行が必要です。 今のところ自分で移行したサービスでは、cron実行されていたスクリプトをLambdaに移すか、 元々Springの @Scheduled で実行されていたのをそのままECSに移行しただけのパターンしかないので、まだあまり知見がありません。

spring.io

調べたところではLambda以外には、AWS BatchとかECS Taskなどが使えそうです。

techblog.timers-inc.com

We are hiring!

エムスリーのクラウド化推進はまだまだ途上で、課題もたくさんあります。 むしろ簡単に移行できるサービスから順に移行しているのでこれからが本番です。 一緒に参加してくれる仲間を募集中です。 来週12/16にはSRE/基盤チームの説明会もあります。 お気軽にお問い合わせください。

open.talentio.com

jobs.m3.com