vol.131を読み終えたら「vol.132を刊行しました」とメールが届いていた。怖い。
今回は実践的なコンテナ導入記が特集されていて、自分のメモ書きも多め。
- サバンナ便り
- オブジェクト指向神話からの脱却
- iOS16最前線
- コンテナ化実践ガイド
- ヌーラボのBacklog SREをされている吉澤さんの記事。業務でもコンテナ周りの知識強化必要なので、とても参考になった。
- サーバの増加や多様化で生じるトイル(プロダクションサービスを動作させることに関連する作業。手作業で繰り返し行われ、自動化することが可能なもので、長期的価値はなく、作業量がサービスの成長に比例して多くなる傾向がある)の問題。
- コンテナ化のメリットはコンテナ技術自体(Dockerなど)がもたらすものと、オーケストレーション(Kubernetesなど)がもたらすものに分けられる。前者は開発者自身がイメージ作成定義できることやサーバ環境を共通化できること、後者はリソース割り当てを開発者自身が迅速にできることやヘルスチェック・スケールアウト/イン・ローリングアップデートなどを自動化できること。
- デメリットは学習コストのほか、処理速度や応答速度などのパフォーマンスに影響が出たり、クラウドコストが上がったりすること。また、オーケストレーションの仕組みに合わせてデプロイや運用手順などを修正あるいは作り直しが必要になること。開発を継続する場合にはメリットが上回る。
- コンテナ化しておくとマイクロサービス化しやすい。サービスのボトルネックがあるときもその箇所のサービスだけ数やリソースを増強できる。マイクロサービスはサーバレスにすることもできる。ドメイン境界を明確にするためにリファクタリングし、サービス分割する必要がある。データベースも分割する必要がある。順序はコンテナ化→マイクロサービス化でもよい。
- 既存サービスのコンテナ化にあたっては、計画フェーズ・改善フェーズ・コンテナ化フェーズ・リリースフェーズに分けて考える。
- レガシーなシステムでは暗黙の前提がコンテナ化の阻害要因になることがあり、アプリケーション自体を修正するための改善フェーズが必要。Backlogでも次々と要修正項目が発生。
- 計画フェーズではスモールスタートで始め、実際に動かさないと不明なことも多いので事前調査期間も短めにする、ただし頻繁に関係者レビューを行う。
- 現状調査。固定IPアドレスが必要、永続的なローカルディスクを必要とする機能やプロセス、自動化が難しい作業、はコンテナ化を阻害する可能性。
- オーケストレーションツールやサービスの選択。AWSならECSやEKS。アプリケーションがクラウド上で動作しているならそのクラウドのマネージドサービスを推奨(ネットワーク設定やIAMを流用できる)。すでにオーケストレーションを導入済みならそれに乗る。
- タスクの洗い出し。改善フェーズで実施すること、コンテナ化フェーズで実施すること。
- 関係者との合意形成。システムの将来像の合意、プロジェクトのゴールについての合意。
- スケジュール立案。改善フェーズ(要開発系エンジニア)・コンテナ化フェーズ(要インフラ系エンジニア)の進め方。ボリュームや人員バランス、オーケストレーションの利用機能などで考える。
- 阻害課題が多く、改善フェーズのボリュームが大きすぎる場合はコンテナ化を先送りし、課題解決をまず進める。
- 改善フェーズでは、サービス提供を続けながらコンテナ化予定のアプリケーションを改善する。コンテナ化する前に1つずつ解決して本番環境に投入することで、コンテナ化後の問題をコンテナ化時の問題のみに絞れる。現在のシステム状態とコンテナ化した後のシステム状態(ビルド結果やネットワーク設定など)をできるだけ近づける。ビルド結果をDockerイメージにしてpushするなど。改善範囲はコンテナ化を阻害するところのみに限定する。
- オーケストレーションの運用コストが上がらないよう、アプリケーション側を改修する。スクリプトでのデプロイや運用手順の自動化、操作のワークアラウンドなどを解消する。
- 古いIaCコードがあれば実情に合わせて更新する。ネットワークまわりのルールも整理する。IAMも整理する。
- 固定IPアドレス依存の認証認可を置き換え(セキュリティグループ化など)。
- 永続ディスクを要しないように変更(qmailという名を令和で聞くとは…)。監査ログをS3バケットにアップロードする箇所は、マネージドサービスのKinesis Data Firehoseを採用。
- ワークアラウンドで対処しているウォームアップ処理・シャットダウン処理を書き直し。
- いよいよコンテナ化フェーズ。アプリケーションの開発と差異が生じないよう、なるべく短期間で終わらせる。可能ならここだけでも人員を追加する。開発チームの開発内容を把握し、阻害課題になる実装がなされるのを防ぐ。
- オーケストレーション未導入であれば小さなアプリケーションの本番環境に導入してみる。導入済みでも対象コンテナ化で未使用のものがあれば本番環境で検証していく。
- コンテナイメージの作成。従来の作業や構築をDockerfileに置き換える。イメージビルドもCIにしておく。補助コンテナのサイドカーコンテナを導入する(BacklogではログアップロードのためのFluentdサイドカーコンテナ)。
- オーケストレーションのプローブ設定。足りなければ追加、あるいは実装する。
- デプロイパイプライン整備。マニフェスト作成、Kubernetesの場合はkubectlでのデプロイテスト。既存のデプロイパイプラインの修正、ローリングアップデートや冗長化のための設定。
- 監視整備。Backlogの場合はMackerelからPrometheus+Grafanaに変更したとのこと。Fluentdやアラートも調整。
- 性能テストでの検証。リソースや台数を変化させてみる、現在のサーバと比較してみる。オートスケールもテスト。
- 外部コンポーネントやオーケストレーションとの結合テスト。自動化しておく。
- 最後のリリースフェーズ。コンテナ化されたアプリケーションを本番環境にデプロイし、コンポーネント接続を確認、リクエストを徐々に切り替え。切り戻し手順を整備しておく、短期間で終わらせる、役割分担を明確化して担当者未定作業を防ぐ。
- 安全のためにメトリクスやログを監視しつつ段階的に切り替える。分け方は、機能単位、リクエスト数単位、それらの組み合わせ、など。Backlogではリバースプロキシで対処している。
- 完全移行が終わるまで従来サーバとコンテナでデプロイパイプラインを共通化する。正常の判断をするためのメトリクスやログの確認手順を決めておく。デプロイ・運用・アラート対応のマニュアルを作り、関係者レビューを実施して整備する。
- リリース後のチューニング。コンテナ数やリソースには余裕を持たせて開始し、徐々に減らして適正化していく。効果測定を行う。デプロイ時間・頻度、応答時間やエラー率。関係者ヒアリング。マイクロサービス化の検討。
- Kubernetesのモニタリング等の理由でMackerelがなくなったのは残念なところではあるが、超力作のコンテナ化移行記事だった。
- 22周年記念エッセイ 私が大切にしていること
- フロントエンドコンポーネント駆動開発
- Goに入りては…
- sync/atomicによる排他制御の使い方。atoimc.Int32のような型が使える。CompareAndSwapを使ってリセットする。
- Ruby 3標準添付ライブラリ紹介
- SREで開発を加速させる
- パイプライン形式のSLI実装。同期書き込み型と非同期書き込み型それぞれのWebサービス構成に基づいて考えてみる。
- 同期書き込み型の例構成: Userからの書き込みリクエストがUser→ALB→ECS→RDSと流れ、レスポンスがECS→ALB→Userと返る。
- 非同期書き込み型の例構成: Userからの書き込みリクエストがUser→ALB→ECS→Kinesis Data Streamsと流れる。Kinesisに渡したところでUserにレスポンスを返す。処理のほうはKinesisからLambda、DynamoDBと渡っていく。書き込みリクエストが多い・書き込み時のデータに時間がかかる加工が必要などのサービスを想定。
- ALB、ECS、Kinesis、Lambda、DynamoDBの監視項目を調べる。エラー率と処理時間に注目し、その他キャパシティ項目も考える。エラー率が可用性、処理時間がレイテンシというSLIで捉えられる。
- 非同期書き込みでは、ALBから導かれる可用性・レイテンシはKinesisに渡すまでの時間や成功しか意味しない。SLIでは機能が提供される(たとえば動画が見られる状態になった)までの可用性やレイテンシを計測したい。例でいえばLambda・DynamoDBまで済んだところまでのレイテンシを考慮したい。
- ストリーム処理やバッチ処理を伴うサービスを合わせて、パイプライン形式のサービスと呼ぶことにする。両者ではSLIの設計は異なる。
- 新鮮さ: 時間の閾値よりも最近に更新されたデータの比率。最終的に処理されたデータの更新日時に対するSLI。サービスとして何が大事かを考える。例のケースではストリーム処理全体がどれくらいで終わり、ユーザーにデータを提供できるかでSLIを設計する。
- 正確性: 正しい値の出力につながったパイプラインへの入力レコードの比率。
- 自分たちで定義したアプリケーション仕様の正確性であり、実装も自前となる。正解がわかっているproverデータを投入して成功したかを定期的に計測する。アプリケーション自体もテストしやすい状態にしておく必要がある。
- カバレッジ: 時間ウィンドウ内に処理に成功した入力レコードの比率。例のケースでは、特定のタイムウィンドウ内で投入された処理対象データに対して処理したデータ量の比率を使う。値=カバレッジが低いと、処理があふれていて、新鮮さもすぐに悪化することになる。
- KinesisのIncomingRecords、IncomingBytes、GetRecords.Records、GetRecords.Bytesを使う。Lambdaアプリケーションで受け取り/処理の件数をログ出力する。
- パイプライン形式の場合、デファクトがないのでサービス要件と向き合って、本当に計測したい値が何かを考える。ステップごとのメトリクスを組み合わせて全体を計測できないか検討する。
- X-Rayなどのトレーシングを使い、エンドツーエンドでの処理時間をトレースする方法もある。すべてのリクエストをトレースするのは高コストなのでサンプリング数は限定される。