はじめに
この記事は、『実践 プラットフォームエンジニアリング」の内容を要約したものです。
第1章の記事はこちらです。
ポイント
- プラットフォームは、一度作って終わりの基盤ではなく、長く使われ改善され続けるプロダクトである。価値の中心は、開発者が他チームを待たずに使えるセルフサービス体験にある
- 組織構造・意思決定の習慣・エンジニアリング文化を変えずにプラクティスだけを導入しても失敗する
- 権限と責任を持つテクニカルプロダクトオーナー(TPO)が開発者の声を代弁し、利用状況の観察をもとにバックログとロードマップを決める
- 顧客(日常的に使う開発者)とステークホルダー(要件に影響するが日常的には使わない人)を分ける。ステークホルダーが抱える懸念を解消するために、セルフサービスで使えるAPIか、テスト可能な成果基準を提供してもらう
- 最適化の単位は、個々のチームではなくエンドツーエンドのプロセス全体である。機能はAPIファーストで提供し、アーキテクチャは実装結果と利用状況の観察に基づいて決める
- 最初はMVPをアーリーアダプターに届け、仮説を検証しながら機能を増やす。開発は単一チームで始める
- プラットフォームへの変更は、すべて自動パイプラインを通す。機能を作る前に動作を確かめるためのデータを決め、受け入れ基準に含める(オブザーバビリティ駆動開発)
- 要望は仮説として扱い、実装の前後にデータで価値を確かめる。守るべき設計ルールはアーキテクチャ適応度関数としてテストにし、変化に耐えられるようにする
- ケイパビリティは利用者の体験を境界としてドメインに分け、ドメインごとに疎結合なチームを置く。コスト削減のような横断的な課題も、ドメイン境界の中で利用者の体験として解決する
- Kubernetesは拡張可能なAPIを持つため、統制や自動化をプラットフォームに組み込みやすい
ソフトウェア・デファインドのプロダクトとアーキテクチャ
プラットフォームエンジニアリングのプラクティスを取り入れようとする企業が、組織構造・意思決定の習慣・エンジニアリング文化を戦略に含めなければ、その取り組みは失敗する。
→ 組織を今の状態に導いたのは、その構造と意思決定と文化そのものだから
組織構造は、内部プロダクトがプロダクトデリバリーモデルをどれだけ効果的に実践できているかという観点で評価し、見直す。
今のモデルが良い社内プロダクトの提供に繋がっていない場合は、責任を再配置し、新しい指標と期待値を設定し、必要なら構造そのものを変える。
内部プロダクトは長期にわたって使われる。その過程で要件もツールも技術も変化する。
その中でプロダクト体験を維持するには、プロダクト全体が**ソフトウェア・デファインドである(= 自動化されたオーケストレーションにより作成・維持される)**必要がある。
プロダクトデリバリーモデル
従来型(プロジェクト型)では、ツールやインフラは一度きりの調達物として扱われる。実際には使わない人が選定し、構築する。
昔はそれがスタンダードだったが、既にその価値は失われている。
プロダクトデリバリーモデルでは、ユーザー体験が中心で、変化を前提に小さく頻繁にリリースする。
成功は計画を完了したかどうかではなく、開発者がプラットフォームをどれだけうまく使えるかで計測すべき。
エンジニアリングプラットフォームは、チームが品質・セキュリティ・コンプライアンスを満たしながら、最小限の摩擦でソフトウェアを作り、テストし、デプロイし、運用できるようにする、長期にわたって改善され続けるプロダクトである。
その決定的な特徴がセルフサービス体験。
ユーザーは摩擦や遅延から解放されてプロダクトを利用でき、かつその裏側では統制や要件が自動化されている状態。
テクニカルプロダクトオーナーシップ
内部プロダクトは外部向けのプロダクトと同様、権限と責任を持つプロダクトオーナーによる方向づけと集中が欠かせない。
**テクニカルプロダクトオーナー(TPO)**が次の役割を担う。
- 顧客(開発者)の声を代弁する
- 顧客体験を損なうことなく、ステークホルダーのニーズを満たす方法を見つける
- 市場がどの方向性に向かっているかを把握し、理解する
- バックログの優先順位を決定し、リリース順序を決める
- 利用状況や顧客体験の観察から、期待された価値が届いていないと判断した場合はロードマップを変更する
TPOによるオーナーシップがなければ、当初のビジョンからかけ離れた価値の低いプロダクトになってしまう。
顧客 vs ステークホルダー
- 顧客:実際にプラットフォームを使う人で、主に開発者やエンジニア
- ステークホルダー:セキュリティ・財務・法務・インフラの責任者のように、要件に影響を与えるが日常的には使わない人たち
両者の成功基準は大きく異なる。
社内のステークホルダーは様々な懸念を抱えており、プラットフォームに対する発言権を持つこと、オーナーとして管理することを期待する。
ただし実際は、そうしなくても懸念は解消できる。
ステークホルダーには、次のどちらかを出してもらう。
- 自分たちのケイパビリティをセルフサービスで使えるようにするサービスインターフェースAPI
例:ペネトレーションテスト。セキュリティチームがスキャン機能をAPIで提供すれば、開発者は誰にも頼まずにスキャンでき、セキュリティチームはログから誰が何をスキャンしたかを把握できる。 - 明確でテスト可能な成果基準
例:認証。「社内認証の単一のソースを使い、OAuth 2.0に準拠すること」とだけ決めれば、実装は別のチームが担える。
ステークホルダーとの関係は、ステークホルダーマップで可視化する。
縦軸:バックログへの影響力
横軸:プラットフォームの目標との整合度
プラットフォームが成功するには、影響力の大きいステークホルダー(図中のCTO、CEO)の整合度が高くなければならない。
影響力が大きく整合度の低いステークホルダー(図中のCTO)については、反対意見や制約を一覧にし、その制約がどこから来ているのかまで突き止める。
ステークホルダーがプラットフォームの提供に与える影響は、追跡し続ける必要がある。
プロダクトのための最適化
プラットフォームエンジニアとして、組織構造のどの側面がエンジニアリングとプロダクトの品質に貢献し、どの側面が品質を損なっているかを把握するためのプラクティスは、次の3つである。
- 構築・提供・運用のエンドツーエンドのプロセス全体が、品質と成功のために評価・設計されている
- 各機能サイロの中だけで最適化しても、プロセス全体では品質を下げてしまうことがある
- すべての機能がサービスインターフェース(API)ファーストで提供されている
- アーキテクチャの意思決定が、実装結果と顧客の使用状況の観察に基づいている
- 機能の実装を決める際に、プラットフォームチームの独断で評価を行い、「将来の要件に備えて」最も多機能な実装を選択するのはアンチパターン。
- 将来の要件を正確に予測することはできない。
- 肥大化した万能型の技術は、小さく最適化されたソリューションよりもコスト高になる。
MVPとアーリーアダプターの重要性
MVP(最小実行可能プロダクト)は、顧客に段階的な価値を届け、最初の仮説を検証できる最小のプロダクトである。
最初から完全に機能するプロダクトを届ける必要はない。
(最初から数か月かけてフル機能でリリースしても、1つ2つの機能しか価値を生んでいないこともある)
最初に何を決めたとしても、それは仮説として扱う。アーリーアダプターの行動を注意深く分析して仮説を検証し、方向を調整していく。
そうやって段階的に機能を増やしていけばいい。
最初のリリースは単一のプラットフォームデリバリーチームから始めることが推奨される。
→ プラットフォームを小規模かつ高品質なアーキテクチャ設計のもとで素早く立ち上げ、価値を実証できる。
時間の経過とともにスピードアップが必要になれば、複数チーム体制へと移行していく。
そして、TPOによってロードマップが管理されていることが必須要件。
ソフトウェア・デファインドプラットフォーム
チェックリストに従いながら設定し、ファイルを手動アップロードし、物理ハードウェアを手作業で積み上げる従来のインフラ管理はアンチパターン。
プラットフォームエンジニアリングに移行するにあたって、一貫したソフトウェアエンジニアリングプラクティスを土台とする必要がある。
プラクティスを定めるときは、チーム全体の合意文書を作成しておくと、迷いなく進められるので役に立つ。
プラットフォームのソフトウェアデリバリーライフサイクル
計画から運用までのサイクルを繰り返し回していく。
- 計画:次に取り組む事項を決める。作業を小さく分け、TPOが成功の姿と計測方法を決める。これが初期の受け入れ基準になる
- 設計:アーキテクチャと実装を決定する。新しい機能なら、本格的な実装の前に小さく実験して、価値を裏付けるエビデンスを得ておく
- コード:設計と受け入れ基準を充足するテストを先に書いてから実装する
- ビルドとテスト:プラットフォームに対する変更(デプロイや設定変更など)は自動パイプラインでのみ行う。テストもすべてその中で実行する。本番候補にする前に、開発者には見えないテスト環境(サンドボックスやプレビュー環境?)を2つ以上通す。一時的な検証環境が必要なときも、手で作らずパイプラインから作る
- リリース:利用者の本番経路の外にあるプレビュー環境へ出し、次に非本番、最後に本番へリリースする
- 観察:何かを壊していないか、開発者がどう影響を受けたか、監視とアラートが有効だったかを確かめる
- 運用:稼働を維持するだけでなく、監視体制全体の有効性(問題を早期に検知できているか、変更が予期しない問題を引き起こしていないか)を評価して改善する
オブザーバビリティ駆動開発
例として、次のような障害を考える。
開発チームが断続的な通信障害を報告し、開発者が3時間デバッグしても原因が見つからず、プラットフォーム側の問題としてチケットを上げた。
プラットフォームチームはログの確認に2時間、新サービスのデバッグにさらに1時間かけ、詳細なネットワークログを有効にしてから分析に1時間かかった。原因は、外部ソースにだけ適用するはずだったサービスメッシュ(サービス間の通信を制御する仕組み)のトラフィックレート制限が、内部サービスの通信にもかかっていたことだった。
修正とデプロイは10分で済んだが、チームは丸1日を失った。
この障害で時間がかかったのは、修正が難しかったからではなく、「制限が作動した」ことを示すデータがなかったからである。
オブザーバビリティ駆動開発(ODD)は、テスト駆動開発の考え方を広げ、テレメトリ(メトリクス、ログ、トレース)とアラートを受け入れ基準に含める考え方である。機能を作る前に「正しく動いていることを確かめるには、どんなデータが必要か」を決め、そのデータが取れていなければ完成とみなさない。
この例のレート制限機能なら、制限が作動するたびに識別できるイベントを出し、送信元と宛先、内部の通信かどうか、ブロックの頻度などを記録しておく。そうすれば、内部の通信まで制限されていることに開発・テストの段階で気づけた可能性が高く、本番で起きたとしても原因はすぐにわかったはずである。
進化的プラットフォームアーキテクチャ
プラットフォームを設計する上では多くの選択(AとBのどちらのツールを使うか、など)に直面するが、プラットフォームに進化的な柔軟性を持たせるためには、「変更しやすいか」「他の技術に移れるか」の要件も組み込んで選択する必要がある。
プラットフォームが1年後にどう使われているかは予測できない。現状よりも優れた選択肢があとから出てくることもある。
不可欠だと思われていたケイパビリティが実は不要だったということは日常的に起こる。
インクリメンタルな設計のためのバックログ管理
プラットフォームに寄せられた要望は、そのまま実装項目にするのではなく、その要望が本当に解くべき課題なのか、解決したときに十分な価値が出るのかを確認する。
例:ステークホルダーが「Gitリポジトリをブラウザから作れるGUIがほしい」という要望を出してきた。
理由は、開発者の時間を節約するためにこのチームが代わりにリポジトリを作成しているが、CLIに慣れておらず時間がかかっているから。
→ すぐにGUIを作るとは決めない。「リポジトリ作成に時間がかかっている」という仮説として扱い、実際の利用状況を調べる。
・平均的なチームは年12回しかリポジトリを作成していなかった
・要望を出したチーム以外では、開発者自身が1〜2分で作成していた
・要望を出したチームがGUIで操作しても、10分以上かかる見込み
→ GUIが使えるようになっても、ほとんどの開発者は使わない。
→ 開発者の数分を節約するために、別のチームが年間数時間を費やしているだけになる。
→ 開発・維持コストまで含めると、機能を実装するほど損失が増える。
一方で、
・GUIによって1チームあたり年間数十時間を節約できる
・利用するチームが100チームある
のであれば、実装する価値は大きくなる。
ただし、価値を事前に完全に予測できるとは限らない。そこで必要になるのがオブザーバビリティである。
機能を作るときは、動作しているかだけでなく、
・実際に何チームが使っているか
・どのくらい継続して使われているか
・作業時間がどれだけ減ったか
・想定していた価値が本当に生まれているか
を後から確認できるデータも一緒に用意する。
リリース後のデータから期待した価値が出ていないと分かれば、改善するか、維持コストを払い続けるよりも削除するかを判断できる。
GUI自体が本当に必要な機能なのか、それとも価値を届けるための一つの手段にすぎないのかを検証し、実装後もデータで確かめながら設計を更新していく。
ユーザー、ステークホルダー、市場からのフィードバックの収集
従来のDevOpsでは、以下の流れで要望を処理していた。
開発者が依頼
↓
リクエストキューに追加
↓
基本的にFIFOで順番に処理
一方、プラットフォームの機能は個々のチームのリクエストのために構築するのではなく、すべてのチームが利用するケイパビリティとして提供する。
プラットフォームのアイデアは、主に次の4つから集める。
- ステークホルダー
- 開発者・アーキテクト
- 業界や競合他社の動向
- 実際のプラットフォーム利用状況
そして、依頼されたから実装するのではなく、TPOが価値を分析し、プラットフォームに合わなければ却下する。
アーキテクチャ適応度関数
プラットフォームは変化を前提に設計する必要がある。
そのために使うのが、アーキテクチャ適応度関数である。
アーキテクチャ適応度関数
ソフトウェアアーキテクチャの特定の側面がどれだけうまく機能しているかを客観的に計測するためのツール。
変更を加えてもアーキテクチャパターンと決定事項が維持されていることをアサートするユニットテスト。
たとえば、APIに次のようなリポジトリパターンを採用するとする。
ハンドラ
↓
サービス
↓
リポジトリ
↓
データストア
データベース固有の処理をデータストア層に閉じ込めておけば、
RDS for PostgreSQL
↓
Aurora Serverless
のようにデータベースを変更しても、上位のサービスやリポジトリまで大きく書き換えずに済む。
ただし、設計書に「この構造を守る」と書くだけでは、時間が経つにつれて崩れていく。
そこで、
・ServiceからDatastoreを直接呼んでいないか
・Repository以外からDatastoreへアクセスしていないか
・すべてのDatastore実装が共通インターフェースを満たしているか
などをテストとして実行する。誰かがルールを破る変更を入れればCIが失敗する。
ドメイン駆動プラットフォーム設計
エンジニアリング組織では日々大量の意思決定が行われ、そのどれもがプラットフォームの利用体験に影響する。
意思決定の拠り所がなければ、場当たり的な対応が標準になってしまう。
プロダクトとしてプラットフォームを提供する組織では、次の特徴がある。
- 自分たちがプロダクトを提供している、という認識を持っている(ユーザーが他者に頼らずプロダクトを使えることを前提にする)
- ケイパビリティをドメインとサブドメインに分割し、ドメインごとにチームを置く
- 各チームは他チームと疎結合で、自分たちのロードマップを進められる
- チーム外との細かな調整なしに作業できる
- 依存するサービス・依存されるサービスとは独立して、データも含めてデプロイ・リリースできる
- ダウンタイムをほぼゼロにして、いつでもデプロイできる
ドメインを分けるときは「ユーザーがどんな体験を受けるのか」を境界とする。
ドメインのすべての機能を、ユーザーがチケットを起票したり担当チームとやり取りしたりせずに利用できていれば、有効なドメインを特定できたと言える。
ドメインを分ける際は、第1章で紹介した8つのドメインの考え方を使う。
【ユーザー体験レイヤー】開発者が直接触れる部分
④ 顧客IDプロバイダー
⑧ プラットフォームプロダクトサービス
【アプリケーションレイヤー】開発者のアプリが動く部分
⑤ クラウドコントロールプレーン基盤
⑥ コントロールプレーンサービス
⑦ コントロールプレーンエクステンション
【基盤】
③ トランジットネットワークレイヤー
② クラウドアカウントベースライン
① クラウド管理ID
たとえば、プラットフォームの戦略会議で、ステークホルダーの一人が「コストが高くなりすぎている」と発言したとする。
参加者がそれぞれ
- コスト追跡ソフトウェア(IBM Apptioなど)を導入する
- タグ付けのツールを整えて、自動アラートを出す
- 開発者がインフラを作るたびに、コスト承認プロセスを通す
という解決策を出した。
「コストを下げる」という広い視点で議論を進めることの問題点は、全員がコスト削減に目を向けているものの、その解決策自体が別のコストを生むことは考えていないことにある。
議論の範囲をドメイン境界に絞ると、目指すべき成果(= ユーザーにどんな体験を提供するか)に議論を集中できる。
・このケイパビリティを担えるドメインチームはどこか。そのチームに余力はあるか
・このケイパビリティを作るのに必要なのに、まだプラットフォームにないもの(他のドメインの機能など)はないか
・結果をどう計測するか
その結論をADR(アーキテクチャ決定記録)に残す。
・コスト効率のよいリソース利用は、プラットフォームの必須要件である
・プラットフォームのコストは利用チームごとに追跡・配賦し、リアルタイム(少なくとも月次)で見られるようにする
・コンピューティングは、Karpenterが管理するノードプールを使う。標準のノードプールに加え、合わない場合は各チームが独自のノードプールを定義できる
・新しいチームには、平均的なチームの利用量から決めたリソース上限(クォータ)を自動で設定する。利用状況に容易にアクセスでき、制限に近づくとアラートが送信される
・リソース上限の調整や、使わない時間帯に非本番環境を自動で停止・縮小する設定を、チーム自身が行えるセルフサービス手段を用意する
このADRを実装するにあたって、最初は2つのデリバリーチームで始めるとする。
- 開発者体験チーム:④顧客IDと⑧プラットフォームプロダクトサービス(開発者ポータル、プラットフォームAPI)を担当
- プラットフォームクラウドインフラチーム:残りのドメインを担当
開発者体験チームは、開発者ポータルやAPIから、クォータ変更などのセルフサービス機能を開発者に提供する。
その裏でクラウドインフラチームの機能を使うことになるが、担当者に依頼するのではなく、クラウドインフラチームが用意したセルフサービスの手段を使う。
この例で大事なのは、コスト削減を単独の目標として追うのではなく、プラットフォームの境界と目標の中で取り組んでいる点である。
コスト対策の機能ごとに、どのドメインが担い、利用者にどんな体験として届けるかを決めている。
たとえば、コストの見える化やクォータ変更は開発者体験チームがポータルやAPIで提供し、ノードプールによる効率化はクラウドインフラチームが裏側で担う。
クラウドネイティブ技術の進化的影響
クラウドネイティブの枠組みにおけるエンジニアリングプラットフォームの構築では、分散サービスアーキテクチャプラットフォームに必須とされる技術の多くを、主要クラウドベンダーからAPI経由で利用できる。
中でもKubernetesは、分散サービスのオーケストレーションと、そのオーケストレーションモデルの拡張性を標準化したという点で重要である。
サーバーレスのように、オーケストレーションを抽象化した技術も選択肢になるが、拡張性に欠ける点が大きな懸念である。
たとえば、Kubernetesであれば
- Mutatingアドミッションコントローラー(リソースが作られる直前に、その内容を自動で書き換える仕組み)で、デプロイ時に必要なサイドカー(シークレットを取得するものなど)を自動追加する
- Validatingアドミッションコントローラー(ルール違反なら拒否する仕組み)で、署名されていないコードのデプロイを拒否する
といった拡張ができるが、サーバーレスにはこうした拡張用のAPIがない。
そのため、プラットフォームの自動化でカバーできない部分は、開発者が自分で対応しなければならない。