はじめに
DevOpsプラットフォームの取り組みを紹介する3回目の記事です。
Qmonus Value Streamのアーキテクトの牧志 (@JunMakishi) です。
本記事では、Qmonus Value Streamの独自技術であるCloud Native Adapterを紹介します。はじめにInfrastructure as Codeの課題を指摘し、Cloud Native Adapterを使ってこれらの課題をどう解決するのかを解説します。
Infrastructure as Codeの課題
Infrastructuer as Code (以下IaC) は、特定のツールを指すのではなく、インフラストラクチャをコードで記述し、ソフトウェアと同じように取り扱うプラクティスを指します。インフラストラクチャのリソース構成や設定をコードで記述・適用することで、再現性、一貫性、および透明性のあるインフラストラクチャを得られます。分散されたコンポーネントを組み合わせてシステムを組み上げるクラウドアーキテクチャを運用する上で、その構成をコードで管理することは必要不可欠であると考えます。
Cloud Native Days Tokyo 2021の発表では、IaCを、「User Interface」「Workflow」「Test」および「Feedback Loop」の4つに分解して考察しました(図)。Qmonus Value Streamのコア技術であるCloud Native Adapterは、これらのうちUser InterfaceとWorkflowの課題を解決するための方式です。
User Interfaceの課題
IaCにおけるUser Interfaceは、インフラストラクチャのリソース構成と設定の期待値をコードで定義するための方式です。一般的に、JSON、YAML、または専用のDSLなどが利用されています。
試験環境と商用環境の間で一貫したインフラストラクチャ構成を得るためには、IaC実装のパラメータ化を進める必要があります。例えば、APIアプリケーションをデプロイする場合、ユーザに公開するAPIのFQDNをパラメータ化するようなIaC実装が求められます。試験環境と商用環境で想定する負荷が異なり、割り当てるCPUやメモリ量をパラメータ化することもあるでしょう。
ここで、Kustomize、Python+Jinjaなどのツール、またはDSL専用の記法を用いてパラメータ化することが考えられます。しかし、コードの規模が大きくなるほど、パラメータの管理が煩雑になります。インフラストラクチャは指定可能な設定値が多岐にわたるため、複数のユースケースに対応しようとするあまり、パラメータの数が多くなる傾向にあります。IaCを実践していて、各パラメータがインフラストラクチャのどの値を設定しているかを追いかけるのが難しいと感じたことがある方は少なくないでしょう。
また、ソフトウェア開発のプラクティスに従い、インフラストラクチャ設定をグループ化・抽象化していくことで、より規模の大きいインフラストラクチャを管理できます。しかし、YAMLなどの既存のインターフェースでは、リソース間の依存関係や構成全体の見通しが悪くなる傾向にあると考えます。例えば、繰り返し処理やまとまったコンテクストを1つのモジュールにまとめる、実装したコードを事前に検証する、といったソフトウェア開発プラクティスを適用することが難しいと考えています。
私たちは、IaCがソフトウェアエンジニアにとって、読み書きしやすく、かつメンテナンスしやすいものである必要があると考えます。IaCの「User Interface」として、ソフトウェアプラクティスを適用でき、スケーラブルにインフラストラクチャ構成を宣言できるデータ記述言語が求められます。インフラストラクチャのコンフィグ定義を扱うのに適したもので、見通しよくIaC実装をモジュール化やグループ化できる言語が必要です。
Workflowの課題
IaCにおけるWorkflowは、インフラストラクチャを設定するまでの一連の手続きを自動化するための方式です。例えば、Terraformは、列挙されたリソース間の依存関係を解決して、リソース設定を適用する順序を制御します。
様々なIaCツールがありますが、チームのDevOpsとソフトウェアアーキテクチャにもとづき、 IaCを実装してからツールを実行してインフラストラクチャに適用するまでの一連のワークフローを簡単に自動化する手段やデファクトスタンダードがまだないと考えています。チームごとに独自のスクリプトを用意し、多様なツールを組みあわせるためのグルーコードや、前述したパラメータを埋めるための煩雑な処理などを実装する必要があります。その高いメンテナンスコストは運用フェーズでチームに重くのしかかってきます。
再現性の高いインフラストラクチャ構成を得るためにIaCを実践したとしても、それを適用するスクリプト実装を信頼性高く保たない限り、変更に弱いインフラストラクチャとなってしまうリスクを孕んでいます。そのため、パラメータ設定ミスを誘発してしまわないよう、注意深くスクリプトを修正する必要がでてきます。例えば、Jenkinsとそこで動かすスクリプトをメンテナンスするために多大な労力を割いているチームも多いことでしょう。
私たちは、システムアーキテクチャ設計の肝の一つは、継続的にデリバリする仕組みを作ることであり、IaC実装においてそのワークフローを考慮するべきと考えます。 IaCの「Workflow」として、インフラストラクチャをデプロイするまでの一連のワークフローを簡単に自動化し、使いたいクラウド技術・ツールを、自チームのCI/CDパイプラインに苦痛なく組み入れられるような方式が求められます。
発展途上のエコシステム
近年、前述したIaCのワークフローの課題に取り組むソリューションが台頭してきています。
Terraform Cloudは、HashiCorp社が提供するSaaSです。「Plan, Policy Check, Apply」というTerraformを実行するまでの一連の流れをSaaSで管理することを可能とし、チームで一貫したプラクティスを適用可能としています。
Waypointは、同様にHashiCorp社が開発しているオープンソースソフトウェアです。「Build, Deploy, Release」という開発からソフトウェアリリースまでのワークフローを連結するソリューションを提供しています。
Daggerは、Dockerの創始者であるSolomon Hykesらにより開発されているオープンソースソフトウェアです。ポータブルなCI/CDワークフローを記述・実行できるエンジンを提供し、チームサイロでメンテナンスしていたGlueコードを無くすことができます。
KubeVelaは、Alibaba Cloudのチームが中心となって開発しているオープンソースソフトウェアです。プラットフォームチームが、複数のKubernetesリソースとそれらをデプロイするワークフローとをセットでパッケージングすることを可能としています。
それぞれ、既存ツールでは足りなかったワークフローの自動化を達成するためのソリューションを提供しています。詳細については、それぞれのWebサイトを参照ください。
このように、前述したIaCの課題は、エコシステムの中でも近年注目されている課題であるものの、まだまだデファクトスダンダードと呼べるソリューションが確立されていません。そこで、私たちは、これらの課題を解決するための独自の方式をQmonus Value Streamに実装し、提案しています。
私たちの提案:Cloud Native Adapter
Qmonus Value Streamは、Cloud Native Adapterと呼ばれる独自のIaC実装を提案し「User Interface」と「Workflow」の課題を解決しています。そのアイデアは、KubeCon EU 2020で「Design Pattern as Code」という名称で紹介しています。
Cloud Native Adapterのコアとなるアプローチを以下に挙げます。
- ワークフロー定義を含む統一的なインターフェース
- CUE言語を使って、クラウドアーキテクチャを構成する「インフラストラクチャ構成」とそれをデプロイする「ワークフロー」をまとめて1つのインターフェースで宣言する。
- 上記によって、インフラストラクチャを継続的にデリバリする「Workflow」を提供する。
- コンフィグのモジュール化と結合
- 宣言的に記述された「インフラストラクチャ構成」と「ワークフロー」について、再利用可能な形に分割して、そのデータ構造を隠蔽せずに「結合」する。
- 1の統一的なインターフェースで上記の仕組みを提供することで、見通しの良いIaC実装のモジュール化を促進し、ソフトウェアエンジニアにとってメンテナンスしやすい「User Interface」を提供する。
以下、それぞれのアプローチを紹介します。
1. ワークフロー定義を含む統一的なインターフェース
以下のイメージ図で示すように、Cloud Native Adapterは、「インフラストラクチャの構成」と「ワークフロー」をCUE言語を使って宣言します。Qmonus Value Streamは、このCloud Native Adapterのうち、インフラストラクチャ構成を宣言する箇所を読み出し、インフラストラクチャに適用するマニフェストを生成します。また、ワークフローを宣言する箇所を読み出すことで、このマニフェストを適用するためのCI/CDパイプラインを生成します。
CUE言語は、型付データ記述言語であり、データ構造をシンプルに表現・操作できるだけでなく、モジュール化などといったソフトウェアプラクティスを適用できます。前述したDaggerやKubeVelaでもCUE言語を採用しています。CUE言語の詳細は、別の記事で紹介予定です。
以下に、Cloud Native Adapterの実装例として、APIアプリケーションを公開する「GKE API Adapter」を紹介します。resources
fieldにインフラストラクチャのリソース設定を宣言し、pipelines
fieldにCI/CDパイプライン定義を宣言します。この例では、GCPへ適用する publicIp/securityPolicy
設定とKubernetesへ適用する service/ingress
設定に加えて、CI/CDパイプライン中でKubernetesへの適用後に実行すべきAPIの正常性確認 testApi
タスクを1つのAdapterとしてパッケージ化しています。GCPとKubernetesという異なるAPIだけでなく、CI/CDのロジックまで1つのインターフェースで記述できることが分かります。
package api DesignPattern: { name: "GKE API Adapter" // infrastructure configuration resources: { gcpPublicIp: ... gcpSeculicyPolicy: ... k8sService: ... k8sIngress: ... } // CI/CD operations pipelines: { // tasks in "deploy" stage deploy: { applyManifest: ... // a task to test API endpoint after deploying resources testApi: ... } } }
上記の例のようにInfrastructureとCI/CDパイプラインを1つのCloud Native Adapterとしてまとめるのではなく、それぞれ異なるAdapterとして実装することも可能です。第2回連載の前編では、GKEでAPIサービス公開するインフラストラクチャ構成を宣言したCloud Native Adapterを、また後編では、bookinfoをデプロイするワークフローを宣言したCloud Native Adapterを使ったデモを紹介しました。
マルチクラウドのインフラストラクチャ構成だけでなく、その構成をデプロイするワークフローまでを1つのCloud Native Adapterとしてまとめることで、継続的なデリバリ・自動化までを視野に入れたIaCを実装できます。CUE言語を書くだけで実践的なパイプラインとともにインフラストラクチャを拡張・成長させられるようなユーザ体験(Developer Experience)を提供したいと考えています。
さらに、CUE言語を活用して、宣言したインフラストラクチャ構成およびパイプライン定義におけるデータ構造を安全に評価・検証できます。このCUE言語の強みについては、別の記事で紹介します。
2. コンフィグのモジュール化と結合
Cloud Native Adapterは、宣言的に記述した「インフラストラクチャ構成」と「ワークフロー」をグループ化、モジュール化できます。以下のイメージ図に示すように、ユーザは、再利用できる単位にパッケージ化された複数のモジュールを選択して、1つのシステムアーキテクチャを組み上げることができます。
CUE言語は、宣言的に記述されたデータ構造を結合し一貫した結果を出力できることが特徴です。Cloud Native Adapterは、インフラストラクチャ構成とワークフロー定義について、そのデータ構造を過度に隠蔽・抽象化せずに、再利用出来る単位に分割できます。Qmonus Value Streamのユーザは、このCUE言語の特徴を活用し、分割された任意のCloud Native Adapterを結合することで、目的に沿ったインフラストラクチャ構成とワークフロー定義を得られます。
上記イメージ図で示したCloud Native Adapterを結合する実装例を以下に示します。前述した「GKE API Adapter」に加えて、Securityルールを適用するAdapterや、監視 (uptime check) 設定するAdapterを composites
field中に宣言しています。Qmonus Value Streamは、これをコンパイルすることで、各Adapterがそれぞれ宣言しているリソース設定とCI/CDパイプライン定義を結合して、1つのシステムとCI/CDパイプラインを生成します。さらに、コード例の下部に示しているように、resources
fieldやpipelines
fieldに独自のインフラストラクチャ構成ルールやワークフロータスクを宣言することで、カスタマイズを加えることができます。
DesignPattern: { // unify exisitng Cloud Native Adapter composites: [ { pattern: api.DesignPattern params: { ... } }, { pattern: containerSecurity.DesignPattern params: { ... } }, { pattern: uptimecheck.DesignPattern params: { ... } } ] // customize resources: { // application specific configuration k8sConfigmap: { metadata: name: "application-config" data: "example.conf": "..." // mount the configuration k8sDeployment: spec: template: spec: volumes: [ { name: "config" configMap: name: "application-config" } ] } // application specific tasks pipelines: { ... } }
Helmのように自己完結型のパッケージを作るアプローチの場合、アプリケーション単体の設定を1つのパッケージにまとめられますが、クラウドプロバイダごとの設定や、セキュリティや監視といった運用設定については、別の手段で追加することになります。カスタマイズ性を追求する場合、1つのパッケージが受け取るパラメータを増やすことになり、パッケージが複雑化します。
一方、Cloud Native Adapterのアプローチでは、機能や目的単位でIaC実装をモジュール化し、ユーザ側で必要なモジュールを選択することでアーキテクチャを拡張します。具体的には、ユーザは、1つのモジュールを過度に抽象化およびパラメータ化することなく、コンテクストごとにまとまったデータだけを宣言したCloud Native Adapterを作成します。上記の例では、 resources
fieldに、拡張したい追加のリソース設定だけを宣言しています。Qmonus Value Streamは、結合対象のCloud Native Adapterを読み出し、それぞれが宣言したリソース設定について、データ構造をそのままに結合します。
このアプローチによって、Qmonus Value Streamのユーザは、汎化したモジュールを作りながらIaC全体を見通しよくシンプルに記述できます。
このように、Cloud Native Adapterは、アーキテクチャやコンテクストの境界でモジュール化し、ユーザ側で再利用するモジュールを選択・結合するプラクティスを提供します。例で挙げたようなセキュリティ設定や監視設定を1つのモジュールに押し込む必要がなくなります。私たちは、モジュール化というソフトウェアプラクティスを活用し、スケーラブルで直感的にクラウドアーキテクチャを組み上げることができるユーザ体験を提供したいと考えています。
おわりに
DevOpsプラットフォームの取り組み連載の3回目の記事として、Qmonus Value Streamチームが取り組んでいるInfrastructure as Codeの課題と、それを解決するための独自IaC実装であるCloud Native Adapterを紹介しました。
Qmonus Value Streamチームは、メンバ一人一人が、利用者であるアプリケーション開発者のために信念を持って活動を続けています。 プラットフォームを内製開発するポジション、プラットフォームを使ってNTTグループのクラウド基盤やCI/CDを構築支援するポジションそれぞれで 、一緒に働く仲間を募集していますので、興味を持たれた方は応募いただけると嬉しいです。
次回は、CUE言語についてさらに踏み込んでご紹介します。お楽しみに!