zaki work log

作業ログやら生活ログやらなんやら

[Kubernetes / Ansible] Ansibleを使ったHelm version3操作とカスタマイズYAML指定時の注意点 (kubernetesコレクションver 1.1.1時点)

AnsibleでHelm version3を操作する。

使用モジュール

docs.ansible.com

Helm version3 を使うには、community.kubernetes collectionのhelmモジュールを使用する。

このモジュールはHelm関連のPythonパッケージは必要とせず、CLIのhelmコマンドを内部から実行する仕様になっている。
よってAnsibleの実行環境・権限でhelmコマンドも使用できるようにKUBECONFIG関連も設定しておく。

旧helmモジュール

community.general collectionにも同名のhelmモジュールがあるが、こちらはHelm version2向け。
また、Ansible 2.9以前のhelmモジュールや、2.10でもFQCN表記しない場合はこちらのcommunity.general.helmが使われるので注意。

検証環境

(venv) [zaki@cloud-dev helm-sample]$ ansible --version
ansible 2.10.2
  config file = /home/zaki/src/ansible-sample/tmp/helm-sample/ansible.cfg
  configured module search path = ['/home/zaki/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/zaki/src/ansible-sample/venv/lib64/python3.6/site-packages/ansible
  executable location = /home/zaki/src/ansible-sample/venv/bin/ansible
  python version = 3.6.8 (default, Apr  2 2020, 13:34:55) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

community.kubernetes collectionバージョンは1.1.1 (pip install ansibleでインストールしている)

(venv) [zaki@cloud-dev helm-sample]$ grep version $VIRTUAL_ENV/lib/python3.6/site-packages/ansible_collections/community/kubernetes/MANIFEST.json
  "version": "1.1.1",

対象クラスタはkindで構築したKubernetes 1.19環境。

(venv) [zaki@cloud-dev helm-sample]$ kubectl version --short 
Client Version: v1.19.2
Server Version: v1.19.1
(venv) [zaki@cloud-dev helm-sample]$ helm version --short
v3.3.4+ga61ce56
(venv) [zaki@cloud-dev helm-sample]$ kubectl get pod -A
NAMESPACE            NAME                                              READY   STATUS    RESTARTS   AGE
kube-system          coredns-f9fd979d6-6qtq6                           1/1     Running   0          40m
kube-system          coredns-f9fd979d6-t6w67                           1/1     Running   0          40m
kube-system          etcd-kind-1.19-control-plane                      1/1     Running   0          40m
kube-system          kindnet-flqmf                                     1/1     Running   0          40m
kube-system          kindnet-jvk54                                     1/1     Running   0          40m
kube-system          kindnet-l74r8                                     1/1     Running   0          40m
kube-system          kube-apiserver-kind-1.19-control-plane            1/1     Running   0          40m
kube-system          kube-controller-manager-kind-1.19-control-plane   1/1     Running   3          40m
kube-system          kube-proxy-4585c                                  1/1     Running   0          40m
kube-system          kube-proxy-kqfdk                                  1/1     Running   0          40m
kube-system          kube-proxy-kzkvd                                  1/1     Running   0          40m
kube-system          kube-scheduler-kind-1.19-control-plane            1/1     Running   3          40m
local-path-storage   local-path-provisioner-78776bfc44-7q7fk           1/1     Running   2          40m

まだアプリケーションpodは何もない状態。

playbook例

Helmリポジトリを設定し、MySQLのチャートをインストールする例。

repository設定

helmモジュールでなくhelm_repositoryモジュールを使用する。

  - name: Add default repository
    community.kubernetes.helm_repository:
      name: stable
      repo_url: https://kubernetes-charts.storage.googleapis.com

この内容でansible-playbookを実行すれば、実行ユーザの~/.config/helm/repositories.yamlが設定される。

helm repo update

CLIだとリポジトリ設定後によく実行するけど、この処理単体を実現するplaybookの記述は無い。
後述のchartインストールのplaybookに以下のパラメタを追加すれば良い。

      update_repo_cache: true

Helm chartのインストール

helmモジュールを使用する。
基本的にはExampleを見れば使い方はすぐにわかると思う。

  - name: helm
    community.kubernetes.helm:
      name: mysql-sample
      update_repo_cache: true
      chart_ref: stable/mysql
      release_namespace: db
      create_namespace: true
(venv) [zaki@cloud-dev helm-sample]$ kubectl get pod -n db
NAME                            READY   STATUS    RESTARTS   AGE
mysql-sample-56859879c4-mckq8   1/1     Running   0          38s
(venv) [zaki@cloud-dev helm-sample]$ helm ls -n db
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
mysql-sample    db              1               2020-11-01 11:00:31.421374967 +0900 JST deployed        mysql-1.6.7     5.7.30     

この通りデプロイされた。
また、上記のplaybookは再実行しても冪等性が保たれてokとなる。

カスタマイズYAML

現バージョンでは2通りの設定が可能。
ただしvalues_filesは制限がある。

release_values (valuesもaliasで使用可能)

チャートに対するカスタマイズYAMLrelease_values以下に直接記載する。
以下はServiceのtypeのカスタマイズ

  - name: helm
    community.kubernetes.helm:
      name: mysql-sample
      update_repo_cache: true
      chart_ref: stable/mysql
      release_namespace: db
      create_namespace: true
      release_values:
        service:
          type: NodePort
          port: 3306

これをデプロイすれば、type:NodePortのServiceとなる。

(venv) [zaki@cloud-dev helm-sample]$ kc get svc -n db
NAME           TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
mysql-sample   NodePort   10.96.86.245   <none>        3306:31456/TCP   26m

values_files (注意点有り)

もう一つがカスタマイズYAMLファイルを別途作成し、そのパスを指定するもの。

  - name: helm
    community.kubernetes.helm:
      name: mysql-sample
      update_repo_cache: true
      chart_ref: stable/mysql
      release_namespace: db
      create_namespace: true
      values_files:
      - ./mysql-conf/values-service.yaml
## Configure the service
## ref: http://kubernetes.io/docs/user-guide/services/
service:
  annotations: {}
  ## Specify a service type
  ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
  type: NodePort
  port: 3306
  # nodePort: 32000
  # loadBalancerIP:

こういうこと。

ただしこのvalues_filesを指定する場合は現バージョンでは注意が必要で、「カスタマイズYAML指定無しでreleaseがデプロイされている状態で、values_filesの指定のみで変更を行うとする」と、機能しない。(変更が適用されない)

具体的には、CLIhelm get values <release-name>nullになる状態からの変更が行われない。

ソースを見る限り、デプロイが行われる条件が「release_valuesの値とデプロイ済みreleaseのカスタマイズYAMLの値が不一致の場合」に処理されるため、values_filesの設定が参照されていない。

github.com

この設定を反映させるには、追加でforce: trueの付与も必要だが、helmの引数に--forceが付与されてチャート全体が再デプロイされる動作になるため、注意が必要。
例えばこのmysqlチャートのようにpvを使ってる場合など。

TASK [helm] *******************************************************************
fatal: [localhost]: FAILED! => changed=false 
  command: /usr/local/bin/helm --namespace=db upgrade -i --reset-values --force --create-namespace --values=./mysql-conf/values-service.yaml mysql-sample stable/mysql
  msg: |-
    Failure when executing Helm command. Exited 1.
    stdout:
    stderr: Error: UPGRADE FAILED: failed to replace object: PersistentVolumeClaim "mysql-sample" is invalid: spec: Forbidden: spec is immutable after creation except resources.requests for bound claims
    core.PersistentVolumeClaimSpec{
            AccessModes:      []core.PersistentVolumeAccessMode{"ReadWriteOnce"},
            Selector:         nil,
            Resources:        core.ResourceRequirements{Requests: core.ResourceList{s"storage": {i: resource.int64Amount{value: 8589934592}, Format: "BinarySI"}}},
    -       VolumeName:       "",
    +       VolumeName:       "pvc-c32003b0-ac97-4fc9-8d06-022e4e8162a2",
    -       StorageClassName: nil,
    +       StorageClassName: &"standard",
            VolumeMode:       &"Filesystem",
            DataSource:       nil,
    }
     && failed to replace object: Service "mysql-sample" is invalid: spec.clusterIP: Invalid value: "": field is immutable
  stderr: |-
    Error: UPGRADE FAILED: failed to replace object: PersistentVolumeClaim "mysql-sample" is invalid: spec: Forbidden: spec is immutable after creation except resources.requests for bound claims
    core.PersistentVolumeClaimSpec{
            AccessModes:      []core.PersistentVolumeAccessMode{"ReadWriteOnce"},
            Selector:         nil,
            Resources:        core.ResourceRequirements{Requests: core.ResourceList{s"storage": {i: resource.int64Amount{value: 8589934592}, Format: "BinarySI"}}},
    -       VolumeName:       "",
    +       VolumeName:       "pvc-c32003b0-ac97-4fc9-8d06-022e4e8162a2",
    -       StorageClassName: nil,
    +       StorageClassName: &"standard",
            VolumeMode:       &"Filesystem",
            DataSource:       nil,
    }
     && failed to replace object: Service "mysql-sample" is invalid: spec.clusterIP: Invalid value: "": field is immutable
  stderr_lines: <omitted>
  stdout: ''
  stdout_lines: <omitted>

ちなみに、カスタマイズYAML設定済みのreleaseが動いている状態での再実行であれば、force設定無しでvalues_filesの内容は適用される。
(むしろ冪等性が保たれず常に実行される)

ドキュメントを見るとvalues_filesは1.1.0で追加された新しい設定なので、実装側の処理漏れっぽいかな。。


サンプルコード(playbook全体)

github.com

(11/5追記) コレクションのFQCNの変更について

www.ansible.com

11/4のこのブログ記事で、AnsibleからHelmを使うサンプル付きの記事が公開されてました。

重要なのは以下。

Please note that prior to the release of kubernetes.core 1.1, its contents were released as community.kubernetes. With this content becoming Red Hat support and certified content, a name change was in order. We are in the process of making that transition.

community.kubernetesからkubernetes.coreにコレクション名が変更されることになったらしい。

github.com

Galaxy Pageにもすでに作成されているので、そのうち入れ替わる模様。

galaxy.ansible.com