Skip to content

Infrastructure Adapter をカスタマイズし、機能を追加する

本チュートリアルでは、Qmonus Value Streamを使って、Infrastructure Adapterを編集し、デプロイしたflask-demoアプリケーションの構成を変更し、新たな機能を追加する手順を解説します。

実施する前に、Infrastructure Adapterを作成し、アプリケーションをデプロイするのチュートリアルが完了していることをご確認ください。

以下のステップを通して、Infrastructure Adapterを拡張できることを確認していきます。

  • Infrastructure Adapter のComposite機能を用いて、Kubernetes の水平Pod自動スケーリング機能であるHorizontal Pod Autoscaler(HPA)を追加します。
  • CI/CDパイプラインを実行して新たにデプロイされるリソースを確認します。

Composite機能について

Composite機能は、既存のInfrastructure Adapterを組み合わせて、新たなInfrastructure Adapterを作成できる機能です。これにより、Infrastructure Adapterの再利用性を向上できます。仕様については、Infrastructure Adapter Specificationを確認ください。

1. Infrastructure Adapterの編集(CLI)

Infrastructure Adapterを作成し、アプリケーションをデプロイするで作成したプライベートリポジトリと、deploy-own-applicationブランチをそのまま利用します。 ここでは、Infrastructure Adapterの特徴の一つである、Composite機能を用いて、Infrastructure AdapterにHPAの機能を追加します。

Metrics Serverの事前準備

HPAを利用するためには、KubernetesクラスタにMetrics Serverがインストールされている必要があります。 以下のコマンドを実行して、Metrics Serverがインストールされていることを確認してください。もしインストールされていない場合は、Metrics Server 公式ドキュメントのREADMEを参照してインストールしてください。

bash
kubectl get deployment -n kube-system | grep -i metrics-server

以下のようにInfrastructure Adapterを編集し、アプリケーションの構成を変更します。 まずはComposite元となるhpa.cueを新たに作成します。 ハイライトをしている箇所は、パラメータ化している箇所になります。

bash
# ローカルリポジトリへ移動
cd <repository name>

# deploy-own-applicationブランチに移動
git switch deploy-own-application

# .valuestream ディレクトリに移動
cd .valuestream

# Composite元となるhpa.cueを作成する
vim local/hpa.cue
go
package hpa

DesignPattern: {
	name: "hpa"

	parameters: {
		appName:      string
		minReplicas:  int | *1
		maxReplicas:  int | *3
		cpuThreshold: int | *80
	}

        resources: app: {
            hpa: {
			apiVersion: "autoscaling/v2"
			kind:       "HorizontalPodAutoscaler"
			metadata: {
				name: parameters.appName + "-hpa"
			}
			spec: {
				scaleTargetRef: {
					apiVersion: "apps/v1"
					kind:       "Deployment"
					name:       parameters.appName 
				}
				minReplicas: parameters.minReplicas 
				maxReplicas: parameters.maxReplicas 
				metrics: [{
					type: "Resource"
					resource: {
						name: "cpu"
						target: {
							type:               "Utilization"
							averageUtilization: parameters.cpuThreshold 
						}
					}
				}]
			}
		}
	}
}

前回チュートリアルで作成したflask-app.cueを編集して、作成したhpa.cueをCompositeします。 変更箇所はDIFF、正式なコードはCODEブロックを確認ください。

  • flask-app.cue内で定義しているmaxReplicascpuThresholdはそれぞれ350の値が使用されます。
  • flask-app.cue内でminReplicasは未定義のため、hpa.cueで設定したデフォルト値の1が使用されます。
  • hpaStatusパラメータは、HPAを有効にするかどうかを制御するためのフラグであり、trueの場合にのみCompositeされるようにしています。
bash
# Composite機能を使ってInfrastructure Adapterを編集する
vim local/flask-app.cue
go
package flaskapp

import ( 
    "github.com/qmonus/sample/local:hpa"
)

DesignPattern: {
    name:        "flaskapp"
    description: "Flask application deployment with Infrastructure Adapter."

    parameters: {
        k8sNamespace: string
        imageName:    string
        hpaStatus:    "true" | *"false"
    }

    _const: {
        #name      : "flask-demo"
        #port      : 80
        #targetPort: 5000
    }

    resources: {
        app: {
            resource0: {
                apiVersion: "apps/v1"
                kind:       "Deployment"
                [...]
            }
            resource1: {
                [...]
            }
            resource2: {
                [...]
            }
        }
    }

     composites: [ 
         if parameters.hpaStatus == "true" { // hpaStatusがtrueのときのみCompositeする
             {
                 pattern: hpa.DesignPattern
                 params: {
                     appName:      _const.#name
                     maxReplicas:  3
                     cpuThreshold: 50
                 }
             }
         },
     ]
}
go
package flaskapp

import (
    "github.com/qmonus/sample/local:hpa"
)

DesignPattern: {
    name:        "flaskapp"
    description: "Flask application deployment with Infrastructure Adapter."

    parameters: {
        k8sNamespace: string
        imageName: string
        hpaStatus: "true" | *"false"
    }

    _const: {
        #name      : "flask-demo"
        #port      : 80
        #targetPort: 5000
    }

    resources: {
        app: {
            resource0: {
                apiVersion: "apps/v1"
                kind:       "Deployment"
                metadata: {
                    name:      _const.#name
                    namespace: parameters.k8sNamespace
                }
                spec: {
                    replicas: 1
                    selector: {
                        matchLabels: {
                            app: _const.#name
                        }
                    }
                    template: {
                        metadata: {
                            labels: {
                                app: _const.#name
                            }
                        }
                        spec: {
                            containers: [{
                                name:  _const.#name
                                image: parameters.imageName
                                ports: [{
                                    containerPort: _const.#targetPort
                                }]
                                resources: {
                                    requests: {
                                        cpu:    "5m"
                                        memory: "32Mi"
                                    }
                                    limits: {
                                        cpu:    "100m"
                                        memory: "128Mi"
                                    }
                                }
                            }]
                        }
                    }
                }
            }
            resource1: {
                apiVersion: "v1"
                kind:       "Service"
                metadata: {
                    name:      _const.#name
                    namespace: parameters.k8sNamespace
                }
                spec: {
                    type: "ClusterIP"
                    ports: [{
                        name:       "http"
                        port:       80
                        protocol:   "TCP"
                        targetPort: _const.#targetPort
                    }]
                    selector: {
                        app: _const.#name
                    }
                }
            }
            resource2: {
                apiVersion: "networking.k8s.io/v1"
                kind:       "Ingress"
                metadata: {
                    name:      _const.#name
                    namespace: parameters.k8sNamespace
                }
                spec: {
                    rules: [{
                        http: {
                            paths: [{
                                path:     "/"
                                pathType: "Prefix"
                                backend: {
                                    service: {
                                        name: _const.#name
                                        port: {
                                            name: "http"
                                        }
                                    }
                                }
                            }]
                        }
                    }]
                }
            }
        }
    }

    composites: [
        if parameters.hpaStatus == "true" { // hpaStatusがtrueのときのみCompositeする
            {
                pattern: hpa.DesignPattern
                params: {
                    appName:      _const.#name
                    maxReplicas:  3
                    cpuThreshold: 50
                }
            }
        },
    ]
}

この時点でのフォルダ構成は以下のようになります。

<repository name>/
├── .valuestream/
│   ├── cue.mod/
│   │   └── module.cue
│   ├── local/
│   │   ├── flask-app.cue
│   │   └── hpa.cue
│   ├── output/
│   │   ├── manifest.yaml
│   │   └── pipeline.yaml
│   ├── assemblyline-dev.yaml
│   ├── params.json
│   ├── qvs.yaml
│   └── sample-manifests.yaml
├── app.py
├── Dockerfile
└── requirements.txt

2. QVS Configの編集 (CLI)

AssemblyLineのDeployment ConfigでHPAの有効/無効を制御するため、qvs.yamlにflask-app.cueで定義したhpaStatusをparams・designPatternsに追加します。

bash
vim qvs.yaml
yaml
params:
   - name: k8sNamespace
     type: string
   - name: imageName
     type: string
   - name: hpaStatus
     type: string

modules:
   - name: github.com/qmonus/sample
     local:
       path: .
  - name: qmonus.net/adapter/official

designPatterns:
  # Infrastructure Adapter
   - pattern: github.com/qmonus/sample/local:flaskapp
     params:
       k8sNamespace: $(params.k8sNamespace)
       imageName: $(params.imageName)
       hpaStatus: $(params.hpaStatus)

  # CI/CD Adapter
  - pattern: qmonus.net/adapter/official/pipeline/build:buildkitGcp
  - pattern: qmonus.net/adapter/official/pipeline/deploy:simple
yaml
params:
  - name: k8sNamespace
    type: string
  - name: imageName
    type: string
  - name: hpaStatus
    type: string

modules:
  - name: github.com/qmonus/sample
    local:
      path: .
  - name: qmonus.net/adapter/official

designPatterns:
  # Infrastructure Adapter
  - pattern: github.com/qmonus/sample/local:flaskapp
    params:
      k8sNamespace: $(params.k8sNamespace)
      imageName: $(params.imageName)
      hpaStatus: $(params.hpaStatus)

  # CI/CD Adapter
  - pattern: qmonus.net/adapter/official/pipeline/build:buildkitGcp
  - pattern: qmonus.net/adapter/official/pipeline/deploy:simple

3. Infrastructure Adapterの動作確認(CLI)

本手順では、qvsctl manifest compileコマンドとparams.jsonのパラメータファイルを用いて、Infrastructure Adapterが正しく動作するかどうかを確認します。

params.jsonhpaStatusパラメータを追加し、値をtrueに設定します。

bash
# params.jsonを作成する(実際にはQmonus Value Streamの中で自動的に作成される)
vim params.json
json
{
    "params": [
        {
            "name": "k8sNamespace",
            "value": "flask-demo"
        },
        {
            "name": "imageName",
            "value": "dummyimage"
        }, 
        {
            "name": "hpaStatus",
            "value": "true"
        }
    ]
}

Infrastructure Adapterが正しくコンパイルできるかどうかを確認します。

bash
# qvsctlでKubernetes Manifestにコンパイル
qvsctl manifest compile -m . -c qvs.yaml -p params.json -o output/manifest.yaml
less output/manifest.yaml

以下のように、HorizontalPodAutoscalerリソースが追加されていることを確認してください。

yaml
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: flask-demo-hpa
spec:
  maxReplicas: 3
  metrics:
    - resource:
        name: cpu
        target:
          averageUtilization: 50
          type: Utilization
      type: Resource
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: flask-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask-demo
  namespace: flask-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flask-demo
  template:
    metadata:
      labels:
        app: flask-demo
    spec:
      containers:
        - name: flask-demo
          image: dummyimage
          ports:
            - containerPort: 5000
          resources:
            requests:
              cpu: 5m
              memory: 32Mi
            limits:
              cpu: 100m
              memory: 128Mi

---
[...]

4. Pipeline Manifestの生成および登録 (CLI)

qvs.yamlで定義したhpaStatusパラメータをpipelineに反映させるため、qvsctl pipeline compileコマンドを用いてPipeline ManifestをQmonus Value Streamへ登録します。

bash
# コンパイルする (prefixはApplication名と同一にしてください)
qvsctl pipeline compile -c qvs.yaml --prefix flask-demo -o output/pipeline.yaml

# qvsctl auth コマンド使用して認証する(出力されたurlへアクセスしてください)
qvsctl auth

# Project Nameを取得する
qvsctl project list

# 取得したProject Nameを環境変数に設定する
export projectName=<PROJECT NAME>

# Pipeline Manifestを登録する
qvsctl pipeline apply -p ${projectName} -f output/pipeline.yaml

git add、 git commit、 git pushを行い、リモートリポジトリに変更を反映します。

bash
# リモートリポジトリにpushする
git add --all
git commit -m "Infrastructure Adapter Extension QVS Tutorial"
git push origin deploy-own-application

# コミットIDを控える
git log -1

5. Deployment Configの設定 (GUI)

AssemblyLine詳細画面より、不足している下記のパラメータを入力します。

  • QVS Configのパラメータで定義したhpaStatus
  1. 左メニューより、AssemblyLineを選択します。
  2. 画面中央に表示される flask-demo-dev を選択します。
  3. Pipeline Stagesに表示されている deploy カードを選択します。
  4. 画面右に表示される Edit Deployment Config ボタンをクリックします。
  5. YAMLモードのEditorが表示されますので、Dockerイメージの格納先をYAML形式で入力します。必要なパラメータ一覧は以下の通りです。
    • hpaStatus: "true" (これを設定することで、HPAが有効になります)

以下は入力例です。

yaml
hpaStatus: "true"
k8sNamespace: flask-demo
imageRegistryPath: asia-northeast1-docker.pkg.dev/gcp-project/qmonus
imageShortName: flask-demo
imageTag: v0.0.1
pathToContext: .
  1. Saveボタンを押下し、deployのInput Parameterが全て緑で表示されていることを確認します。

6. AssemblyLineの実行 (GUI)

Infrastructure Adapterを作成し、アプリケーションをデプロイするで実施したのと同様に、登録したAssemblyLineを実行し、再度、flask-demoアプリケーションをKubernetesにデプロイします。

  1. 左メニューより、AssemblyLineを選択します。
  2. 画面中央に表示される flask-demo-dev を選択します。
  3. Pipelines (Stages) に表示されている deploy カードを選択し、全てのパラメータが埋まっていることを確認します。
  4. 以下のInput Parameterを入力し、 RUN ボタンを押下してAssemblyLineを実行します。
    • gitRevision: (手順4でのgit commit時のコミットID)

7. デプロイされたアプリケーションの確認 (CLI)

Kubernetesにアクセスして、デプロイに成功したアプリケーションを確認します。

  • Podを確認することで、サンプルアプリケーションが正常に動作していることを確認できます。
  • HPAリソースを確認することで、追加したHorizontalPodAutoscalerを確認できます。
bash
# GKEクラスタへ接続する
gcloud container clusters get-credentials <クラスタ> --zone <ノードゾーン> --project <GCPプロジェクトID>

# Podの確認 (Podが3台でRunningになっていることを確認)
kubectl get pod -n flask-demo

NAME                          READY   STATUS    RESTARTS   AGE
flask-demo-xxxxxxxxxx-aaaaa   1/1     Running   0          10m
flask-demo-xxxxxxxxxx-bbbbb   1/1     Running   0          30s
flask-demo-xxxxxxxxxx-ccccc   1/1     Running   0          30s

# 新たにCompositeされたHPAを確認する
kubectl get hpa -n flask-demo

NAME              REFERENCE                TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
flask-demo-hpa    Deployment/flask-demo    60%/50%   1         3         3          30s

Ingressに割り当てられたIPアドレスへアクセスし、アプリケーションが正常に動作していることを確認します。

bash
# IngressのEXTERNAL-IPを確認
kubectl get ingress -n flask-demo

# 確認したIPアドレスへアクセス
curl http://${external_ip}; echo
Hello from Qmonus Value Stream

CPU使用率について

kubectl get hpa -n flask-demoコマンドで取得したHPAのTARGETSの値がcpuThresholdで設定した閾値(50%)以下となっている場合は、Podがオートスケーリングしません。 その場合は、flask-demo.cueのcomposites内にcpuThresholdをTARGETSの値以下に変更して、再度手順4のPipeline Manifestの生成からやり直してください。

yaml
     composites: [
         if parameters.hpaStatus == "true" { // hpaStatusがtrueのときのみCompositeする
             {
                 pattern: hpa.DesignPattern
                 params: {
                     appName:      _const.#name
                     maxReplicas:  3
                     cpuThreshold: "<TARGETS以下の値>"
                 }
             }
         },
     ]

8. ブランチをマージする

動作確認が完了したら、作業ブランチをmainブランチにマージします。 GitHub の対象リポジトリからマージを実施してください。

  1. GitHubの対象リポジトリにアクセスします。
  2. 画面上部のPull requestsタブをクリックします。
  3. deploy-own-applicationブランチのPull Request画面を開きます。
  4. Pull Request画面で変更内容を確認し、問題がなければMerge pull requestボタンをクリックします。
  5. Confirm mergeボタンをクリックしてマージを完了します。
  6. マージ完了後、Delete branchボタンをクリックして作業ブランチを削除します(任意)。

解説

本チュートリアルではInfrastructure Adapterを作成し、アプリケーションをデプロイするで使用したInfrastructure Adapterを拡張して新たにHorizontalPodAutoscaler(HPA)による水平Pod自動スケーリング機能を追加しました。 このように、Qmonus Value Streamでは、Infrastructure AdapterのComposite機能を用いて、異なるInfrastructure Adapterを再利用できます。

次のステップ

次は以下のチュートリアルに進んで、CI/CD Adapterを作成してみましょう。