Appearance
Infrastructure Adapter Specification
アプリケーションを動かすクラウドアーキテクチャを構成する「インフラストラクチャ構成」を定義するInfrastructure Adapterの仕様について以下に示します。
以下の定義のように、Infrastructure Adapterは、大きく分けて、初期化時に利用するparameters
、出力するResource Manifest(クラウド、およびアプリケーションの設定ファイル)を宣言するresources
、および組み合わせるInfrastructure Adapterのリストcomposites
から構成されます。
Qmonus Value Streamは、このInfrastructure Adapterを解決し、Resource Manifestを生成します。この処理をコンパイル
、と呼びます。
cue
#DesignPattern: {
name: string
description: string | *""
// input parameters
parameters: {[string]: _}
// enumerates design pattern
composites?: [...#CompositeDef]
// whether to group resources after composite
group: string | *""
// resource declaration
resources?: #ResourceOutput
// lazy evaluation
defer?: {
#ResourceOutput
}
}
Infrastructure Adapterの基本要素
name
およびdescription
フィールドは、Infrastructure Adapterについての説明を記述します。現在、これらのフィールドがコンパイル時に利用されることはありません。 group
フィールドは、出力するResource Manifestをグループ化する際に宣言します。本フィールドはDeprecatedであり、後述するcomposites
内で指定するgroup
フィールドを利用ください。
ここで、name
は必須フィールドです。
例
cue
name: "pattern01"
description: "a sample infrastructure adapter"
group: "group01"
Parameters
parameters
フィールドは、Infrastructure Adapterの初期化時に、外から受け取るパラメータのリストを宣言します。コンパイル時に渡されたパラメータの値は、ここで宣言したparameters
にバインドされます。 具体的には、QVS ConfigでInfrastructure Adapterを指定した際に一緒に宣言したパラメータparams
が、ここで宣言したparameters
にバインドされます。また、後述するComposite処理においても、Infrastructure Adapterとそれに渡すパラメータを宣言でき、QVS Configと同様に、ここで宣言したparameters
にバインドします。
ここで、parameters
は必須フィールドです。
例
string型のパラメータであるnameおよびnamespace、int型のパラメータであるreplicas、#Secret型のパラメータであるapiKeyを受け取ります。
cue
parameters: {
name: string
namespace: string
replicas: int
apiKey: #Secret
}
#Secret
parameters
フィールドでは一般的な型に加えて、独自に#Secret
型を定義することで、CLIによりSecretオブジェクトがBindingされるフィールドを宣言できます。 #Secret
型は以下のようなkey
とversion
のフィールドを含む型である必要があります。
cue
#Secret: {
key: string
version: string
}
#Secret
の各フィールドはそれぞれ以下の内容を意味しています。
フィールド名 | 説明 |
---|---|
key | 機密情報の管理サービスにおいて登録されている機密情報の名前 |
version | 機密情報の管理サービスにおいて登録されている機密情報のバージョン |
Resources
resources
フィールドは、Infrastructure Adapterが出力するResource Manifestについて、リソースIDをkeyとしたObjectの形で宣言します。 以下の#ResourceBase
に示すように、Resource Manifestは、プロバイダ定義、APIスキーマおよびメタデータの固定フィールドを持ちます。さらに、#ResourceSpec
に示すように、APIスキーマごとの固有なフィールドを宣言したものがResource Manifestであり、#ResourceOutput
で示すように、app
またはappSetup
をkeyにまとめた形で、resources
フィールドに定義します。
provider
フィールドは、kubernetes
やgcp
といった、プロバイダ定義を宣言します。apiVersion
およびkind
フィールドは、リソースのAPIスキーマを宣言します。metadata
フィールドは、リソースのメタ情報を宣言します。ここで、provider
、apiVersion
、kind
、metadata.name
は必須フィールドです(output
は現在利用されません)。
app
およびappSetup
は、それぞれリソースをデプロイするStageを宣言します。app
stageは、アプリケーションを実際にインストール・アップデートするstageであるのに対し、appSetup
は事前作業としてアプリケーションが動作するために必要な周辺のリソースをデプロイするstageを表します。
cue
// Infrastructure Adapterの抜粋
#DesignPattern: {
// resource declaration
resources?: #ResourceOutput
...
}
#ResourceBase: {
// cloud provider
provider: string
// API schema
apiVersion: string
kind: string
// metadata
metadata: {
name: string
{[string]: _}
}
// output to save
output: [...string]
}
#ResourceSpec: {
#ResourceBase
// any resource specific configuration
{[string]: _}
}
#ResourceOutput: {
// application setup stage
appSetup?: {[string]: #ResourceSpec}
// application deployment stage
app?: {[string]: #ResourceSpec}
}
例
appSetup stageでServiceとIngressを、app stageでDeploymentリソースのManifestを出力します。
cue
resources: {
app: deployment: {
provider: "kubernetes"
apiVersion: "apps/v1"
kind: "Deployment"
metadata: name: "app01"
spec: ...
}
appSetup: service: {
provider: "kubernetes"
apiVersion: "v1"
kind: "Service"
metadata: name: "service01"
spec: ...
}
appSetup: ingress: {
provider: "kubernetes"
apiVersion: "networking.k8s.io/v1beta1"
kind: "Ingress"
metadata: name: "ingress01"
spec: ...
}
}
Composite
composites
フィールドは、他のInfrastructure Adapterを組み合わせて新しいInfrastructure Adapterを定義するために、組み合わせるInfrastructure Adapterとそれに渡すパラメータを宣言します。Infrastructure Adapterのコンパイル時に、ここで宣言されたInfrastructure Adapterを読み出し、各Infrastructure Adapterが出力するResource Manifestを解決します。その後、解決したManifestを、resources
フィールドに全て結合します。この一連の処理をComposite処理と呼びます。
以下の#CompositeDef
で示すように、composites
フィールドは、組み合わせたInfrastructure Adapterについてのルールをリストで宣言します。具体的には、CompositeするInfrastructure Adapterを指定するpattern
、バインドするパラメータを指定するparams
、およびComposite時に指定したInfrastructure Adapterが出力するResource Manifestのグループ化を指定するgroup
を宣言します。
ここで、pattern
およびparams
は必須フィールドです。またgroup
フィールドを指定することで、Composite処理で解決したリソース定義をグループ化し、resources
フィールドに結合する際に、他のリソースと結合しないよう、分離できます。
cue
// Infrastructure Adapterの抜粋
#DesignPattern: {
// resource declaration
composites?: [...#CompositeDef]
...
}
#CompositeDef: {
// design pattern to composite
pattern: #DesignPattern
// parameters to bind on composite
params: {[string]: _}
// group composited resource to isolate from others
group?: string
}
例
app
Infrastructure AdapterをCompositeしたリソース定義に対して、独自のannotationをさらに追加します。
cue
import (
app "module-to-load"
)
composites: [
{
pattern: app.DesignPattern
params: {
name: "app01"
namespace: "test-namespace"
replicas: 3
}
}
]
resources: {
app: deployment: {
metadata: annotations: "my-original": "label"
}
}
Defer
defer
フィールドは、Infrastructure Adapterのコンパイル処理のなかで最後に評価するデータを宣言します。Composite処理後に解決したResource Manifestに追加のデータを加える、などといった遅延評価を実現できます。
例
app
Infrastructure AdapterをCompositeしたリソース定義全てに対して、独自のannotationを追加します。
cue
import (
app "module-to-load"
)
composites: [
{
pattern: app.DesignPattern
params: {
name: "app01"
namespace: "test-namespace"
replicas: 3
}
}
]
resources: {}
defer: {
app: {
for key, res in resources.app {
"\(key)": metadata: annotations: "my-original": "label"
}
}
}
Annotation付与によるデプロイ動作制御
Infrastructure Adapterを実装する際、特定のAnnotationを付与することで、リソースのデプロイ挙動を制御できます。以下の三種類のAnnotationが設定できます。
vs.axis-dev.io/dependsOn
:リソース間の依存関係を指定できます。vs.axis-dev.io/ignore-changes
:リソースのパラメータを更新しないように制御できます。vs.axis-dev.io/keepOnDelete
: リソースを削除しないよう制御できます。
dependsOn
dependsOn
によるリソース間の依存関係を示すことで、依存先のリソースから先に作成・更新されるようになります。逆に削除のときは依存元のリソースが消えてから、依存先のリソースが削除されます。 依存先のリソースを更新するケースとして、依存元リソース名をsource
、依存先の旧リソース名をtarget-old
、新リソース名をtarget-new
とすると、
target-new
が作成されるsource
が更新されるtarget-old
が削除される
という順序でデプロイが行われます。デプロイ失敗時には、後続の更新や削除が行われません。
Annotationとして指定する際の命名規則は以下のようになります。
"vs.axis-dev.io/dependsOn": ${apiGroup}:${kind}::${namespace}/${name}
${apiGroup}
は、apiVersion
の中でversionを除いたもの(例:apiVersion: networking.k8s.io/v1
の場合、networking.k8s.io
)を指定します。version以外の指定がない場合、core
を指定します (例:apiVersion:v1
のみの場合、core
になります)。${kind}
は、manifestのkindをそのまま指定します。${namespace}
が指定されていない場合、${name}
のみ指定します。 (/を除きます)
例
sample-app
Deployment にsample-externalsecret-\(_hash)
External Secret への依存関係を持たせることで、External Secretの更新とDeploymentの更新順序を制御する例を挙げます。
cue
_deployment: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: {
name: "sample-app"
namespace: "test-namespace"
annotations: "vs.axis-dev.io/dependsOn": "external-secrets.io:ExternalSecret::test-namespace/sample-externalsecret-\(_hash)"
}
[...]
}
External Secretのリソース名は、そのSpecによって決定するハッシュ値 _hash
を含んでいると仮定した場合、External SecretのSpecを更新する際に、リソース名が変わります。Qmonus Value Streamは、デプロイ時に新しい名前でExternal Secretリソースを作成し、古いリソースは削除します。 上記のサンプルコードのように、DeploymentリソースからExternal Secretリソースへの依存関係を宣言することで、Qmonus Value Streamは、新しいExternal Secretリソースが作成された後にDeploymentリソースを更新するため、Deploymentリソースから新しいSecret情報を確実に参照できます。また、Deploymentリソースの更新が成功しない限り古いExternal Secretリソースを削除しないため、Deploymentリソースの更新失敗時には古いExternal Secretを参照したままの状態、すなわち失敗時の状態を保つことができます。
ignore-changes
ignore-changes
にリソースのプロパティ名を指定することで、該当プロパティの値の変更を無視します。Qmonus Value Streamは、ignore-changes
で指定されたプロパティについては、実際にリソースに適用されている値を尊重し、値を上書きすることはありません。本機能は、特定のプロパティをCI/CDのサイクルの外で変更し、その値を維持したい場合に有効です。 例外として、初回リソース作成時のみ、ignore-changes
は効きません。
Annotationとして指定する際の命名規則は以下のようになります。
- ドット区切りで指定します。 (例:
spec: { selector: { app: x } }
は、spec.selector.app
) - リストの場合、[]で指定します。(例:
containers: [ { image: x }]
は、containers[0].image
) - カンマ "," 区切りで指定することで複数指定できます。
例
HorizontalPodAutoscalerがCI/CDの外で動作し、Deploymentのreplica数をコントロールしている中で、Qmonus Value Streamにsample-deployment
Deployment のspec.replicas
の値の更新を無視させます。
cue
_deployment: {
apiVersion: "apps/v1"
kind: "Deployment"
metadata: {
name: "sample-deployment"
annotations: "vs.axis-dev.io/ignore-changes": "spec.replicas"
}
spec: {
replicas: 2 # 初回以外の値の更新を無視する
[...]
}
}
このようにすることで、Horizontal Pod Autoscalerを使って、以下のようにDeploymentのreplica数を動的に変更できます。
- Qmonus Value Streamを使って、Deploymentのreplica数を指定してデプロイします。
- Horizontal Pod Autoscalerを使って、CI/CDの外でDeploymentのreplica数を動的に変更します。
ignore-changes
でreplicas
プロパティを指定することで以降のデプロイ時にreplicas
の変更を無視します。
keepOnDelete
keepOnDelete
のannotationを指定してデプロイされたリソースはデプロイ処理中の削除処理から保護されるようになります。また、annotationの値(以下、keepOnDelete
キー)には同じ目的でデプロイするリソースに同じ名前のキーを指定してください。そうすることでkeepOnDelete
キーの単位でリソースをまとめてバージョン管理をし、将来は古いバージョンのリソースを自動で削除する機能を提供予定です。
本機能を利用することで、Blue/Green デプロイメントでConfigMap/Secretリソース等を使用するアプリケーションをデプロイする際に、Blue/Greenが切り替わる前のコンテナが参照している古いConfigMap/Secretを削除せずに維持できます。
ただし以下の注意点があります。
- ConfigMap/Secretリソースを適切にrevision管理するため下記例のようにリソース名がユニークなものになるように検討してください。
keepOnDelete
設定をせずにデプロイされているリソースは本機能の対象にならないため、既にデプロイされているリソースを削除したくない場合は手動でkeepOnDelete
annotationをリソースに付与してください。暗黙的にrevisionの値を"0"
が指定されているものとして扱います。(*1)- 手動でrevisionのannotationを追加・編集・削除をすることはしないでください。
(*1) CLI (kubectl)を利用して手動でannotationを付与する場合は以下のようなコマンドで実施できます。
e.g. sample-namespace
にある sample-config-abcd
ConfigMapリソースを削除処理から保護したい。keepOnDelete
キーはsample-config
とする。
# kubectl annotate コマンドを利用します
kubectl -n sample-namespace annotate configmaps sample-config vs.axis-dev.io/keepOnDelete=sample-config
例
ConfigMapリソースをapp-config
というキーを持っているリソース群でrevision管理する場合の例を下記に記載します。また、下記の例ではConfigMapリソースが保持するデータが異なれば別リソースであるという考えを基に、dataフィールドの内容をhash化したものをリソース名のsuffixとして付与するという方法をとっています。
cue
import (
"crypto/sha256"
"encoding/hex"
"encoding/yaml"
"strings"
)
// ConfigMap の data 部分だけYaml化する
let _data = yaml.Marshal(_configMap.data)
// Yaml化したデータをハッシュ化し、最初の10文字だけ取り出す
let _hash = strings.SliceRunes(hex.Encode(sha256.Sum256(_data)), 0, 10)
_configMap: {
apiVersion: "apps/v1"
kind: "ConfigMap"
metadata: {
name: "sample-configmap-\(_hash)"
annotations: "vs.axis-dev.io/keepOnDelete": "app-config" // app-config キーでrevisionを管理
}
data: {
host: "example.co.jp"
log_level: "verbose"
}
}
上記サンプルをコンパイルし生成されたConfigMapリソース名がsample-configmap-308a0d0c4c
だとし、このリソースを環境にデプロイするとします。環境には既に以下のようなConfigMapリソースがデプロイ済みであった場合、
yaml
apiVersion: apps/v1
kind: ConfigMap
metadata:
name: sample-configmap-2ed2af4518
annotations:
vs.axis-dev.io/keepOnDelete: app-config
vs.axis-dev.io/revision: "1"
data:
host: example.co.jp
log_level: info
最終的にデプロイされるリソースの状態は以下のようになります。
- 既にデプロイされていた
sample-configmap-2ed2af4518
リソースは従来では削除されます。しかし上記のようにkeepOnDelete
を指定することで削除処理から保護されて残り続けます。 - 新規にデプロイされる
sample-configmap-308a0d0c4c
リソースはrevisionが"2"
として設定された状態でデプロイされます。