CLOSE

アプリケーション開発者は Amazon ECS あるいは Kubernetes をどこまで知るべきか(全3記事)

「コード書きました、あとはよろしく」では優れたソフトウェアは生まれない コンテナのスペシャリストが語る、運用性を損なう8つの実装例

今押さえておくべき知識をアップデートし、ノウハウを共有し、さらなるスキルアップを実現する場として開催されている、AWS で最も Developer に特化したカンファレンス「AWS Dev Day Online Japan」。ここでSr. Product Developer Advocate, Elastic Containersの原氏が登壇。続いて、運用性に優れたソフトウェアが重要な理由と、運用性を損なう8つの実装例について紹介します。前回はこちらから。

You build it, you run it

原トリ氏:(スライドの「You build it, you run it」を指して)この言葉、聞いたこと、見たことがある方がいるかもしれません。これは、2006年にACM(Association for Computing Machinery)という団体が、Amazon AWSのCTOであるワーナー・ヴォゲルスに対して行ったインタビューの最中に彼が言った言葉です。

ACMは、チューリング賞の表彰をしていることで有名な団体です。この「You build it, you run it」という言葉自体はそれなりに有名だと僕は思っていますが、この言葉をそのまま読むと、「作ったら運用しろ」という意味になりますね。しかし、それだけではない、この言葉に込められた意味を深く理解してもらうために、この言葉が使われた前後の文章も見てみようと思います。

インタビュー全体はそれなりの長さがあります。ほかの部分での会話の意図も汲みながら僕が日本語にしていくので、多少意訳も入りますがご了承ください。

「You build it, you run it」が入っている一節はこんな言葉から始まります。「アプリケーション開発者が運用上の責務を持つことで、サービスのクオリティが、ユーザーあるいはお客さまの視点からもテクノロジーの視点からも、よりよいものになる。

伝統的なモデルでは、一度ソフトウェアを書いたら開発と運用の間にある壁、その向こうに開発したソフトをポンと投げて、あとは忘れてしまう。Amazonはそうではない。

作った者自身がそれを実行・運用する。

この考え方を実践することで、開発者は日々のオペレーション、運用と直接接することになる。それによりサービスを外で触っているお客さまの声が開発者に直接届くようになる。このお客さまからのフィードバックループが、サービスの品質を向上させるうえで非常に重要なんだ」。

この文脈で、Amazon AWSのCTOであるワーナー・ヴォゲルスは「You build it, you run it」と言っているわけです。僕はこの言葉と、その背景にある今話してきた内容がとても好きで、すごく納得感があります。ただ、こう思う方もいるはずです。「とはいえ、運用に知見のある人や、チームに運用業務を集める・運用チームを作るのって自然な流れじゃないか?」。そうです。開発者や運用に限らず、マーケティングもそうだし、人事もそうだし、業務を細分化して専業化するのは、会社組織であれば必然的な流れです。

では何が問題なのかと言うと、「コードを書きました、あとはよろしく、さようなら」という開発者の姿勢が問題だと言っているわけです。組織図上、チームが開発と運用に分かれていること自体は特に問題ではない。開発と運用の両者が共通のゴールを持って、共同して、運用上の責任を持つことがとても大事だと。なぜなら、運用性に優れたソフトウェアは、運用を無視した開発からは生まれないからです。

なぜ運用性に優れたソフトウェアが重要なのか

では、なぜ運用性に優れたソフトウェアが重要なのかについて話しましょう。(スライドを指して)これは見慣れたサプライチェーンです。コードを書き、それがビルドされ、テストされてデプロイされて、デプロイした後はモニタリングをして、うまくいっているようであればさらにロールアウトを進めて、うまくいっていないようであればロールバックをする。非常によくあるサプライチェーンです。

ここで重要なのは、開発した時点のソフトウェアには価値がないということです。運用フェーズに到達して初めて、ユーザー、お客さまに価値を提供できます。

つまり、ソフトウェアというのは価値を提供するために、基本的には運用フェーズに向かいますが、開発段階で見て見ぬふりをした複雑性は、すべて後段にある運用フェーズに押し込まれるわけです。

開発から押し込まれた複雑性はどうなるか。運用に不必要なワークアラウンドを生みます。運用で生まれてしまったワークアラウンドは、将来開発者が新しい機能を作ろうという時や、何か変更しようという時に、予期しない制約を作り出します。

つまり、このサプライチェーンはコードから運用、開発から運用に向かう一方向に見えますが、実際にはここにループがあるんです。ソフトウェア開発者が本質的な機能開発に集中するために、開発者が運用に参加して、運用者が開発に参加して、包括的な、ループするサプライチェーンの品質を向上させるべきだ。これがここまでの結論です。

運用性を損なう実装1:スケールアウトできない、しにくいアプリ実装

(スライドを指して)では、「運用性を損なう実装8選」です。たくさんあるのでどれにするか非常に迷ったんですが、今日はライトなものから、忘れがちだけどありそうなものまで8つ用意しました。カジュアルに見ていきましょう。

1つ目は、スケールアウトできない、しにくいアプリ実装です。よくない実装例としてどのようなものがあるか。アプリケーションの状態をローカルディスクに、あるいはローカルディスクから読み書きしているコードです。

(スライドを指して)これはNode.jsのコードです。ユーザーIDのサフィックスをつけたファイルにセッションを書き出している。AWS環境の構築をしている方は、これを見れば笑うと思います。「こんなことしたら、Load BalancerのスティッキーセッションをONにしないとうまくいかないじゃん」と。こういう実装はよく見るし、いろいろなお客さんと話をしていると本当によくあるんです。

では、この実装の何が問題か。こんな実装になってると、マシンの台数を増やせません。この実装が入っていると、Load Balancerが前に置かれてアプリケーションが複数台後ろに並んでいる場合、どこにリクエストが届いたかによって、ユーザーはログアウトした状態みたいになってしまうんです。そのため、マシンの台数を増やせません。

では、マシンの台数が増やせない、スケールアウトできない場合にはどうするか。スループットを上げるために、マシンをスケールアップする必要性が生まれます。そうすると、台数は増えず1台の性能が上がっただけなので、確かにスループットは高まるかもしれませんが、そのマシンが故障した時にサービス全体に与える影響がとても大きい。

さらに、基本的にスケールアップの限界はスケールアウトよりも早いです。そもそもアプリケーションの実装として、スケールアップしたマシンのリソースを全部使い切れないなんてことがあります。例えば、AWS上でEC2インスタンスを使っているのなら、EC2インスタンスのタイプは有限なので「これより上の性能はない」というケースもあり得ます。

では、どう実装すればよいか。アプリケーションの状態は外部に永続化しましょう。例えばRDS(Amazon RDS)のようなRDBMS(Relational DataBase Management System)やS3、あるいはElastiCache Redisもよく使われます。状態を外部に永続化していない、例に挙げたような実装が非常に運用を難しくするわけです。

運用性を損なう実装2:依存パッケージ解決を自動化できない

2つ目は、依存パッケージ解決を自動化できない。よくない実装例を見ていきましょう。「そのライブラリはこのURLからダウンロードしてきて、libフォルダに置いてビルドすればとおりますよ。バージョンはよくわかりませんが、最新を使っておけばいいんじゃないですか。社内共通ライブラリは共有ファイルサーバーにあるので、コピーしてきてください」。最悪です。

何が問題か。新バージョンのデプロイのたびにどのバージョンを使うのか確認したり、開発と運用の間でのやり取りが必要になったりします。あるいは、手作業でダウンロードして配置することが必要になる。チーム間の依存関係が発生することによって、デリバリ速度が低下します。さらに、意図しないライブラリバージョンを入れてしまうようなミスオペレーションが発生する可能性がある。

では、どうすればよかったか。これは当然の解答ですが、利用しているプログラミング言語やフレームワークでサポートされている、依存解決ツールを使いましょう。

社内共通ライブラリは、エンタープライズなみなさんにはよくある話です。僕個人の意見でもありますが、どうしても依存解決ツールをサポートしていない社内共通ライブラリのようなものを使う必要がある場合は、共有ファイルサーバーから持ってくるより、アプリケーションリポジトリにバンドルするほうがマシだと考えています。

運用性を損なう実装3:安全に停止的できない

3つ目は、安全に停止できないアプリケーションがたくさんあります。よくない実装例として、処理中にSIGTERM(アプリケーションの終了指示)が送られてくる可能性をまったく考慮していないコードが書かれている。

考慮していないだけならまだいいのですが、SIGTERMやSIGKILLを受け取ると異常終了します。SIGKILLの場合はKILLなので、異常終了するのが正しいのかもしれませんが、SIGTERMを受け取って異常終了するのはいただけません。

何が問題か。例えば、異常終了によってデータベースに記録されたデータが不正になってしまう。あるいは、デプロイをする時に、エンドユーザーに500系のエラーが返ってしまう可能性がある。さらに、それらを防ぐために、ワークアラウンドとしてデプロイ時にサービスの提供を中断してデプロイしなければならなくなると、ユーザー体験が悪化する。

では改善策は何か。SIGTERMを受け取ったら、終了処理をきちんと行う実装を追加しましょう。ただし、これは簡単ではなく、処理中のデータの取り扱いまで含めて設計のレベルできちんと考慮されている必要があります。あるいは、Webアプリケーションフレームワークなどによって、一定程度終了処理が実装済みであることも当然必要です。

運用性を損なう実装4:起動処理が長い

4つ目は、起動処理が長い。よくない実装例として、メチャクチャでかいデータを起動時にダウンロードする処理が書かれている。何が問題か。起動がコンテナオーケストレータのタイムアウトの範囲内に収まらず、デプロイが失敗します。そうなると運用でなんとかカバーしようとする。これも運用業務を非効率化するのでよくありません。また、基本的にデプロイ処理はサービスが多少不安定になっているタイミングなので、長時間に及ぶこともあまりよろしくない。

では、どうするか。起動時の初期処理から、長い時間ダウンロードするような処理を外す方法を検討しましょう。例えば、コンテナイメージを作る時にバンドルするのはどうでしょう。

運用性を損なう実装5:ビルド/パッケージング前に手作業が必要

5つ目は、ビルド/パッケージング前に手作業が必要なアプリケーション。コードに設定値や環境依存のパラメータが直接記述してあるので、これを手で書き換えるところから、ビルドの前にまずこれを書き直すところからやっています。

これの何が問題か。(スライドを指して)そもそも右の例は非常に危険です。さらに、ビルドやデプロイの前に必ず手作業で設定値の書き換えが必要になるので、非常にアジリティを低下させます。ミスオペも怖いです。環境別にコンテナイメージを作る必要が出る可能性も高まります。では、どうするか。環境変数から設定値や環境依存のパラメータを読み取るように実装を変更しましょう。実行時にパラメータを渡せるようにしましょう。

運用性を損なう実装6:実装が実行環境の環境名などに依存している

6つ目は、ビルド/パッケージングの前に手作業が必要の続きです。設定値を実行時に外部から注入できないことってありますよね。環境名、プロダクションならこういうホスト名、ステージングならこういうホスト名ということがあります。あるいは、設定値は設定ファイルからのみ読めるなど、実装が実行環境の環境名などに依存している。まさに(スライドを指して)右の例です。

何が問題か。一番わかりやすいのは、環境を増やそうとした時に、コードを書き換えたり、追記したりすることが必要になります。5つ目と同じで、ビルドやデプロイ前に手作業で設定値を書き換えることが必要になる可能性もあります。環境別にコンテナイメージの作り直しが必要になる可能性も、当然あります。

改善策も同じです。全部ベタ書きしてしまうのではなく、実行時のパラメータとして、環境変数から読み取れるように書き換えましょう。そもそも環境名、プロダクションやステージングは単なるラベルなので、この名前をもとに処理の挙動を変更するようなものではあってはならないですよね。

運用性を損なう実装7:ログをファイルにしか出力できない

7つ目は、ログをファイルにしか出力できない。ログのファイル名などがベタッと書かれていて、そこにしか出せない。これの何が問題か。ログを見るためにコンテナの中に入って中を見るのかということです。入ろうとしたコンテナが直前で異常終了して、ログごと全部消えていたら困りますよね。

「では運用でワークアラウンドしましょう。NFS(Network File System)を使えば大丈夫。」これ、ログのためだけに不要に複雑化したアーキテクチャにする必要がありますか?と思いますよね。アプリケーションのログは、ローカルで開発している時はファイルに出ていていいんです。でも設定によって、実行時パラメータによってログがきちんと標準出力/標準エラー出力に流れるように実装しましょう。

運用性を損なう実装8:“Readレプリカ”を使えない

最後、8つ目は“Readレプリカ”を使えないアプリケーションがあります。よくない実装例は、RDBMSやMySQLなど接続を永続的に持つタイプのデータストア、わかりやすいやつです。1つの接続先しか想定していないコードがたまにあります。

これの何が問題か。書き込み用と読み取り用のデータベースに分けることによって、システム全体のパフォーマンスを向上させるテクニックが使えなくなってしまいます。それによって何が起きるか。必要以上にデータベースをスケールアップしなければいけなくなる。

改善策として、少なくとも書き込み用と読み取り用の2つのデータベースを接続先として利用するケースを想定してコードを書きましょう。だいたい、どんなWebアプリケーションフレームワークもO/Rマッパーも使えるようになっています。

8つ挙げましたが、他にもたくさんあります。

この辺りについては『The Twelve-Factor App』を読みましょう。僕がセッションをすると毎回出てくると思いますが、自分が運用を意識したコードを書けているかを評価するのに最適です。

(次回に続く)

続きを読むには会員登録
(無料)が必要です。

会員登録していただくと、すべての記事が制限なく閲覧でき、
著者フォローや記事の保存機能など、便利な機能がご利用いただけます。

無料会員登録

会員の方はこちら

関連タグ:

この記事のスピーカー

同じログの記事

コミュニティ情報

Brand Topics

Brand Topics

  • 今までとこれからで、エンジニアに求められる「スキル」の違い AI時代のエンジニアの未来と生存戦略のカギとは

人気の記事

新着イベント

ログミーBusinessに
記事掲載しませんか?

イベント・インタビュー・対談 etc.

“編集しない編集”で、
スピーカーの「意図をそのまま」お届け!