環境ごとにパラメータを変えて、Kubernetesマニフェストを自動生成する

# はじめに

Kubernetes / Openshift の テンプレートエンジンとして、Openshift のテンプレート機能を試してみたので、その結果をアウトプットとして残しておきます。もし、他にも同じような取り組みをされている方がいらっしゃいましたら、よい方法を教えていただけると幸いです。

# Kubernetesにおける 宣言的な設定 について

「宣言的な記述による設定」は、Kubernetes/Openshiftの最も重要な機能です。（と私は考えています）

「宣言的な記述」とは、アプリケーションを稼働させるシステムの「あるべき姿」を記述することです。「宣言的な記述による設定」は、それだけで、そのあるべき姿が実現できることです。Kubernetes/Openshiftにおいて、コンテナそのものに限らず、ストレージやネットワーク、シークレットやレジストリなど、アプリケーションを稼働させるために必要な構成を、全て「宣言的な記述による設定」として管理することができます。


## 今まで

従来、以下のようなツールを用いて、クラウドサービスやその上のOS・ミドルウェアへ、宣言的な記述での設定・構成が行われてきました。ツールを用いた宣言的な記述を通して、ITインフラをコードで構成・管理する考え方を *Infrastructure as Code* と呼びます。

* クラウドサービスに対して ... [AWS CFn](https://aws.amazon.com/jp/cloudformation/), [Azure ARM Template](https://azure.microsoft.com/ja-jp/services/arm-templates/), [Terraform](https://www.terraform.io/)
* OS・ミドルウェアに対して ... ChefやAnsibleなどの構成管理ツール

一方、以下のようなことに起因して理想的に宣言的なコードが書けないシーンがあることも、また事実です。

* ツールの仕様がしょぼい、機能が足りない
* そもそもクラウドサービスやOS・ミドルウェアが宣言的な記述での設定を想定して作られていない
* 手作業前提の手順をそのまま無理やり宣言的なコードで記述しようとしている

結果、「Infrastructure as Code疲れ」という言葉を産み出してしまいました。

* [Infrastructure as Codeに疲れたので、僕たちが本来やりたかったことを整理する](https://speakerdeck.com/shogomuranushi/infrastructure-as-code-is-very-tired)


## これから

一方、Kubernetesは リソースをあるべき姿と突き合わせ、差分があると埋めてくれるReconciliation Loop (突合せループ)と呼ばれる機能が実装されているなど、Infrastructure as Code Nativeなソフトウェアです。これにより、従来、TerraformやChefでやりたかった宣言的な記述による設定が、よりシンプルに・強力に・実用的になったと感じています。

Kubernetes/Openshift の宣言的な記述をもつ設定ファイルをマニフェストといいます。再掲しますが、Kubernetes/Openshiftにおいて、コンテナそのものに限らず、ストレージやネットワーク、シークレットやレジストリなど、アプリケーションを稼働させるために必要な構成を、全て「宣言的な記述による設定」として管理することができます。

また、マニフェストは通常YAML形式で記述します。YAML形式はプレーンテキストであり、Gitでバージョン管理するのに適した形式です。

ゆえに、Kubernetes/Openshiftでアプリケーションやシステムを運用する上で、Gitによるマニフェストの管理は非常に重要です。Gitによるマニフェストの管理することは、GitでKubernetes/Openshiftを管理することとほぼ同義です。私達が今までやってきた運用、これからやろうとしている運用が、上記のKubernetes/Openshiftのメリットを崩さないように十分注意する必要があります。


# テンプレートエンジンが必要な理由

Git(Github)を中心とした Kubernetes/Openshiftの運用を考える上で、基本的な考え方は以下の通りです。

1. 開発環境でコンテナイメージとマニフェストを開発・テスト
2. Gitレポジトリのdevブランチにpush [^1]
3. devブランチ -> prodブランチに Pull Request
4. Merge後、本番環境にマニフェストをapply

[^1]: featureブランチにpushして、devブランチへのPull Requestを挟むかもしれません

ところが現実世界では、なかなかそうはうまくいきません。開発環境での開発・テストを終え、いざ本番環境にapplyしようとすると、いろいろな不都合が生じます。

* Dev環境とProd環境でパラメータを変えたい（からマニフェストを修正しないといけない）
* そもそも権限分離の関係で、namespaceが違う（からマニフェストを修正しないといけない）
* もっというとコンテナレジストリも違う（からマニフェストを修正しないといけない）

本番環境にマニフェストを適用する際に、開発環境でテスト済みのマニフェストを手で編集してから適用するのは、私達自身のあるべき姿ではないはずです。所謂、「運用で回避」は悲劇しか生みません。上記の制約を解決する仕組みづくりが大切です。

上記を解決するための一つの方策が、manifestをパラメータに応じて生成してくれるテンプレートエンジンの活用です。マニフェストのテンプレートエンジンとして代表的なものとしては以下が挙げられます。

1. Helm
2. Kustomize
3. Openshift テンプレート機能

結論から言うと、今回、私は Red Hat Openshiftで使うマニフェストのテンプレートエンジンを探していたので、`3. Openshift テンプレート機能` を採用してみました。

## Helm

Helm([公式](https://github.com/helm/helm)) は 設定済みのKubernetesリソースパッケージである Helm Chartsを管理するためのツールです。マニフェストの生成ができますが、機能的にtoo muchであったため、まずは見送りました。

## Kustomize

Kustomizeは、Kubernetes向けのマニフェストManagementツールです。（[公式Github](https://github.com/kubernetes-sigs/kustomize)） Kubernetesの設定コマンドである `kubectl`と統合され、`kubectl`経由で利用することができます。

ただし、以下のような記述を見つけたので、IBM CloudのOpenshift3.11ユーザーな私は採用を見送りました。

* [Kustomize を使用して、複数環境で再利用するアプリを 4.3 クラスターでパッケージ化する](https://cloud.ibm.com/docs/openshift?topic=openshift-openshift_apps&locale=ja#kustomize
)


> 注記：バージョン 3.11 を実行する OpenShift クラスターでは Kustomize はサポートされていません。


# Openshift テンプレート機能の検証

早速、Openshiftのテンプレート機能を試してみます。コマンド的には、Openshiftの ocコマンドに統合されているため、新規に準備するものは マニフェストのテンプレートファイルと設定値ファイルの２つになります。

* [第10章 TEMPLATES (テンプレート)](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/3.9/html/developer_guide/dev-guide-templates)

## テンプレートファイル

手元にあったNetwork LoadBalancerのマニフェストを元にテンプレートを作成します。実際のマニフェストは `objects:` から `parameters:` までになります。置換したいパラメータを `"${PRIVATE_VLANID}"`のように変数化します。テンプレートファイルを配置するディレクトリは、分かりやすければどこでも良いようです。

```yaml:template/nlb_template.yaml
apiVersion: v1
kind: Template
metadata:
  name: nlb-template
  annotations:
    description: "NLB template for cluster"
    tags: "sample-tag"
objects:
- apiVersion: v1
  kind: Service
  metadata:
    name: nlb
    annotations:
      service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: private
      service.kubernetes.io/ibm-load-balancer-cloud-provider-vlan: "${PRIVATE_VLANID}"
  spec:
    type: LoadBalancer
    selector:
      app: sampleapp
    ports:
    - name: port01
      protocol: TCP
      port: 12345
    - name: port02
      protocol: TCP
      port: 12346
    loadBalancerIP: ${LBIP}
parameters:
- description: private_vlanid
  displayName: private_vlanid
  name: PRIVATE_VLANID
  required: true
- description: LoadBalancer_IP
  displayName: LoadBalancer_IP
  name: LBIP
  required: true
```  

## パラメータファイル

パラメータ化の実際の値を記入するパラメータファイルを作成します。本番環境用と開発環境用のパラメータファイルを作成し、いずれもテンプレートファイルと一緒にバージョン管理します。パラメータファイルを配置するディレクトリは、分かりやすければどこでも良いようです。

```config:build/dev
PRIVATE_VLANID=010001
LBIP=10.0.0.1
```

```config:build/prod
PRIVATE_VLANID=000001
LBIP=192.168.0.1
```



## 生成

早速マニフェストを生成してみます。まずは、開発環境用です。

`$ oc process --local -f template/nlb_template.yaml --param-file build/dev --output yaml`

```yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: private
      service.kubernetes.io/ibm-load-balancer-cloud-provider-vlan: "010001"
    name: nlb
  spec:
    loadBalancerIP: 10.0.0.1
    ports:
    - name: port01
      port: 12345
      protocol: TCP
    - name: port02
      port: 12346
      protocol: TCP
    selector:
      app: sampleapp
    type: LoadBalancer
kind: List
metadata: {}
```

パラメータ化した部分が実際の値に置き換わっているのが分かります。

次に、本番環境用のマニフェストを生成します。

`$ oc process --local -f template/nlb_template.yaml --param-file build/prod --output yaml`

```yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: private
      service.kubernetes.io/ibm-load-balancer-cloud-provider-vlan: "000001"
    name: nlb
  spec:
    loadBalancerIP: 192.168.0.1
    ports:
    - name: port01
      port: 12345
      protocol: TCP
    - name: port02
      port: 12346
      protocol: TCP
    selector:
      app: sampleapp
    type: LoadBalancer
kind: List
metadata: {}
```

パラメータファイルの値によって、本番環境用のマニフェストが生成されたのが分かります。

また、以下のようにすることで、生成されたマニフェストを直接 `oc apply` することも可能です。実際には、このコマンドをShell Scriptでラッピングして生成・適用を一気に行ったり、CIに登録することになるでしょう。

`$ oc process --local -f template/nlb_template.yaml --param-file build/prod | oc apply -f -`

# まとめ

マニフェストのテンプレートエンジンが必要な理由を説明した上で、実際に Openshiftのテンプレート機能を使って、テンプレートから本番環境・開発環境用のマニフェストを生成してみました。

Kubernetes/Openshiftを実際に運用することを見据えると、理想通りにいかないことが増えてくるものです。それを今まで通り、「運用で回避」「職人が手で対応」とかやっていると、Kubernetes/Openshiftの良いところをスポイルしてしまうことになります。ノウハウやツールを共有することで、より楽に運用できるように取り組みたいと思います。


## 参考

[運用課題の指南書、Kubernetesで実践するクラウドネイティブDevOps](https://blog.mosuke.tech/entry/2020/02/26/cloud-native-devops/)
[「Kubernetesで運用する」その前に　Kubernetesを本番環境で利用する際のポイント](https://www.atmarkit.co.jp/ait/articles/1901/16/news028.html)