Internal Developer Platform の紹介
私の職場は遅く始まり、日本に来てから仕事を始めました。それ以前はずっと自分で起業していました。恥ずかしい話ですが、ビジネスの規模は大きくなく、最大でも10人程度だったため、大規模なソフトウェアエンジニアチームを管理した経験はありません。この分野の知識は常に不足していました。このたび新しい会社に転職し、ようやくInternal Developer Platform(以下、IDP)について理解できたので、ここに記録を残します。
私の考えでは、IDPの目的は、大規模(少なくとも100人以上)のソフトウェアエンジニアチームが協力して、信頼性の高いソフトウェアを継続的に提供することです。internaldeveloperplatform.orgを調べたところ、いくつかのアウトオブボックスフレームワークがあるものの、全体的に見て、各企業はビジネスの違いに応じて技術選定を行うため、ほとんどすべてのIDPは企業のインフラチームによって自社で構築されています。そのため、IDPのある会社で働かない限り、IDPがどのようなもので、どのような利点があるのか、なぜIDPを採用するのかを理解するのは難しいです。
internaldeveloperplatform.orgによれば、IDPには5つのコアコンポーネントがあり、それぞれアプリケーション設定管理、インフラストラクチャオーケストレーション、環境管理、デプロイメント管理、役割ベースのアクセス制御です。この説明はあまりにも退屈なので、具体的な例を通じて説明します。理解を容易にし、情報を脱敏化するために、いくつかの簡略化を行います。
一般的なIDPの実装方法は、アプリケーションコード、インフラストラクチャ設定、デプロイメント設定、および権限管理をできるだけ統一されたGitOpsプロセスに収束させることです。この記事では、monorepo + Bazel + GitHub + Kubernetes + Argo CDを例に説明します。
Monorepo
IDPが最初に目に入るのは巨大なmonorepoで、すべてのチームのすべてのプロジェクトのすべてのソースコードがこのmonorepoに格納されています。あまりにも巨大なため、bazelを使用してビルドを管理する必要があります。このmonorepoにはソフトウェアのソースコードだけでなく、terraform設定(つまりInfrastructure as Code)、k8s設定、そしてGitHubのさまざまな設定も含まれています。bazelはGoogleが開発したビルドツールで、超大規模なmonorepoのために特別に設計されており、すべての言語、すべてのフレームワーク、すべてのプロジェクトをサポートしています。
ソフトウェアコードの更新
aliceはチームteam0のプログラマーで、彼女のプロジェクトはmonorepoのprojects/fooにあります。彼女は機能を完成させ、foo/feature-xブランチにプッシュし、PRを提出しました。この時、コードの品質を保証するために、インフラチームが開発したGitHub Actionsが即座に起動し、bazelスクリプトを自動的に実行してlint、CI、コンパイルチェックなどを行います。これらのタスクのいずれかが失敗した場合、マージはできず、aliceはしっかりとコードを書くことを強いられます。
すべてのGitHub Actionsが通過しても、alice自身は勝手にコードをマージすることはできません。IDPを使用しているのは大企業であり、大企業には独自のプロセスがあります。PRを発起すると、GitHubは自動的にGitHub組織のチーム、つまりteam0のメンバーにコードレビューを要求します。なぜGitHubがfooにteam0のメンバーがレビューする必要があることを知っているのか?それはmonorepoのルートディレクトリにあるCODEOWNERSが設定されているからです。
bobはGitHubからのメール通知を受け取った後、approveをクリックし、lgtmとコメントを残しました。この時、PRはマージできるようになります。team0の誰でもマージボタンをクリックできます。
mainブランチにマージされた後、インフラチームが開発したGitHubアプリが即座に起動し、新しいdocker/OCIイメージをパッケージする必要があるかどうかをチェックし、必要であればbazelを使用してパッケージを開始します。パッケージが完了すると、自動的に内部のdockerレジストリ(例えばGCPのArtifact Registry)にプッシュされ、monorepoのinfra/k8s/foo/xxx.yaml内のk8sデプロイメントのイメージタグを変更するための2つのPRが自動的に発起されます。なぜ2つなのかというと、一つはdev/staging環境のyamlを変更し、もう一つはprod環境のyamlを変更するからです。各PRには、今回のイメージビルドがどのコミットに基づいているかが含まれており、追跡が容易です。
CODEOWNERSに基づいて、GitHubはこの時team0にメールを送信します。aliceまたはbobがメールを見た後、approveをクリックしてマージを実行できます。彼らはクリックしても、クリックしなくても構いません。dev/staging環境のPRだけをマージすることも、prod環境のPRをマージすることも、何もマージしないこともできます。これは彼らのチームの問題であり、インフラチームは干渉しません。
イメージタグのPRがmainにマージされると、argocdが即座に同期し、自動的にデプロイを試みます。
依存関係の更新
インフラチームは、あるnpmパッケージが汚染されたことを知り、即座に対応します。monorepoがすべての依存関係を一元管理しているおかげで、彼らはpackage.jsonを更新し、infra/update-x-depsにプッシュしてmainにマージするだけで済みます。このプロセスは上記の流れと同じです。mainが更新されると、fooがこのnpmパッケージに依存しているため、前述のGitHubアプリがfooのイメージを再度パッケージし、team0のメンバーにメールを送信します。aliceとbobはマージをクリックしてargocdデプロイをトリガーする責任があります。fooだけでなく、このnpmパッケージに依存している他のすべてのプロジェクトも同様のプロセスを自動的に実行します。
インフラチームは、特定の言語/フレームワークのOCIイメージの基本依存関係をアップグレードすることを決定した場合、例えばdebianのバージョンを更新するだけで、bazelの設定を更新し、再度上記のプロセスを実行するだけです。mainにマージされた後、aliceとbobは通知を受け取り、デプロイするかどうかを決定します。
こうして、基本依存関係の管理は完全にセキュリティ運用チームに任せられ、アプリケーション開発チームはビジネス実装にのみ集中できます。
ソフトウェアの新規作成
team1のcarolは新しいプロジェクトbarを作成する必要があり、pgデータベースを使用する必要があります。carolは以下を行う必要があります:
- monorepoのprojectsディレクトリに新しいプロジェクトディレクトリを作成し、bazelが必要とするBUILDファイルを維持する
- monorepoのterraform関連ディレクトリにdb関連のファイルを作成する
- monorepoのinfra/k8sディレクトリにbar関連のデプロイメントを作成し、envを入力する。envがsecretの場合、hashicorp vaultを通じて管理する必要があります
- monorepoのterraformおよびinfra/k8s関連ディレクトリにドメイン名関連の設定を作成する
これらすべての準備が整ったら、carolはPRを発起し、mainにマージします。これらのPRは必ずしもcarolが処理する必要はなく、インフラの人がterraformの部分を手伝うこともあります。理想的な状況では、backstageのようなWeb UIがこれらのGitOpsをカプセル化し、社内の開発者にとって、会社のIDPを使用することがSaaSソフトウェアを使用するように簡単になるでしょう。
上記と同様に、PRが発起されると、GitHub ActionsはすぐにCI、bazelスクリプト、lintなどのチェックを実行します。terraformを変更したため、CIはtf-planもチェックします。これらのCIのいずれかが失敗した場合、マージはできません。CODEOWNERSに基づき、terraform関連ディレクトリの変更にはインフラチームのメンバーによるコードレビューが必要です。責任の分担が明確に保たれます。
ソフトウェアアクセス制限
すべてのチームのすべてのソフトウェアは、1つの(dev/staging/prod) k8sにデプロイされており、異なるプロジェクトは単にnamespaceが異なるだけなので、理論的には相互にアクセスできます。しかし、安全性を考慮して、このようなアクセスはデフォルトで禁止される必要があります。monorepoのk8s部分では、istioルールのセットを定義し、各チームが自分のk8s定義ディレクトリでオーバーライドできるようにします。CODEOWNERSには、同様の変更がmainにマージされる際にインフラチームのコードレビューが必要であることを設定できます。
インターフェースの再利用
あるソフトウェアは社内使用を目的としており、内部SDKのようなものです。これらのSDKのサーバーがk8sにデプロイされると、内部および外部ネットワークアクセスアドレスを持ちます。前述のbackstageは、アプリケーションの新規作成プロセスを簡素化するだけでなく、チーム内部のメンバーに明確な内部文書やアーキテクチャの概要を提供し、重複開発を効果的に回避します。
この中で最も重要なのは可観測性であり、dev/prodクラスター内部にはさまざまなコレクターがあり、grafana cloudのようなプラットフォームに転送されます。
もう一つ重要なのは内部のSSOであり、会社内部にはkeycloakのようなOIDCプロバイダーがデプロイされている可能性があり、他のサービスは登録やログインを再度記述する必要がなく、クラスター内のインターフェースに統一して接続できます。
もう一度5つのコアコンポーネントを見てみる
上記の具体的な例を考慮して、IDPがさまざまな業界ツールを統合して以下の問題を解決する方法を見てみましょう。
アプリケーション設定管理
主にk8sのenv設定を通じて解決されます。secret部分はhashicorp vaultを通じて管理できますが、彼らのサービスは本当に高価です。調べたところ、dotenvxやenvsecretsなどの代替案もあります。絶対にどんな秘密鍵もプロジェクトのソースコードに直接導入してはいけません。
インフラストラクチャオーケストレーション
主にterraformとk8sのresources requests/limits、node affinity、さらにはkedaなどを通じて解決されます。
環境管理
異なるdev/staging/prod k8sクラスターを準備し、argocdを通じてmonorepoの異なる部分にバインドすることで解決されます。
デプロイメント管理
argocdを通じて解決されます。
役割ベースのアクセス制御
コード変更管理は主にGitHub Teams、ブランチ保護、ルールセット、CODEOWNERS、必要なステータスチェックを通じて実現されます。実行時アクセス制御はKubernetes RBAC、namespaceの隔離、サービスアカウント、ネットワークポリシー、またはIstio AuthorizationPolicyなどのメカニズムを通じて実現されます。
結論
この記事はあくまで参考のためのもので、現在私はインフラチームの一員ではなく、IDPのユーザーとして外部の視点から自分の経験を基にいくつかの推論を行いました。情報を脱敏化するために多くの簡略化と変更を行いました。例えば、実際に私が働いている場所のIDPにはWeb UIがありませんが、本当に必要だと思います。コーディングエージェントがGitOpsにWebシェルを提供するのはすぐにできるはずです。IDPはビジネスに強く関連しており、普遍的な標準は存在しません。異なる企業の異なる組織構造、さらにはオフィスの政治がIDPの最終的な姿に影響を与えます。
私自身のいくつかの起業プロジェクトを考えると、現在私はソロプレナーであり、自分自身に責任を持つだけなので、GitOpsを大幅に簡略化しました。レビューは必要なく、自分自身をレビューすることにも意味はありません。結局、私は自分自身に責任を持つだけです。プッシュするだけで、GitHub Actionが自動的にCIを実行し、CIが通過すればパッケージされ、パッケージが完了すれば自動的にイメージタグが更新され、argocdが自動的にデプロイされます。また、私はbazelのような強力なツールを使用する必要もなく、npmパッケージで十分で、rustやpythonプロジェクトでもnpmスクリプトを使用できます。