はじめに
前回、アップデートするための手順について解説をしました。その応用編として、実際にその手順をAnsibleで自動化して、Kubernetes Operatorとして作成します。
本記事では、その作成手法を機能別に分断して実際のコード説明と組み合わせて動作を解説します。
アップデートの手順について
実際にコンテナのアップデートを行う場合は以下手順を行います。また、絵にすると図1の通りになります。
- Podの動いているコンテナIDの確認
- コンテナレジストリのコンテナIDの確認
- 差分を確認してDeploymentリソースのコンテナIDの更新
図1 アップデート処理の図
手順のOperator化
上記で記載した手順をAnsibleのコードにすることで、自動処理できるようにします。
作成するカスタムリソースについて
実際にOperatorを作る上で起動するトリガーとなるカスタムリソースの名前をUpdateとして、以下の表1カスタムリソースが作成できるように定義します。
表1 Updateカスタムリソース作成の定理
key名 | 名称 | 値 | ||
---|---|---|---|---|
apiVersion | APIのグループ名 | apiextensions.k8s.io/v1 | ||
kind | 種類 | CustomResourceDefinition | ||
metadata.name | ストレージ管理のカスタムリソース定義の名 | updates.install.sios.nginx.testing | ||
spec.scope | 適用範囲 | NameSpaced | ||
spec.versions.name[] | APIグループのバージョン | v1 | ||
spec.names | 名称仕様 | – | ||
plural | 複数の名称 | updates | ||
singular | 単数の名称 | update | ||
listKind | リソースリスト | UpdateList | ||
kind | リソースマニフェスト名 | Update | ||
shortNames[] | 短縮リソース名 | update | ||
categories[] | カテゴリ | – | ||
spec.versions[] | APIの詳細 | API詳細の表2を参照 |
表2 Updateカスタムリソース API詳細 バージョン:v1
Key名 | 名称 | 値 |
name | バージョンの名称 | v1 |
served | REST API経由の提供 | true |
storage | ストレージバージョンの登録 | true |
schema | スキーマ | スキーマ詳細の表3を参照 |
subresources | サブリソース | status: {} |
表3 Updateカスタムリソースのスキーマ詳細
親キー名 | 名称 | キー名 | 変数の型 | 入力制限 | 備考 |
– | spec | spec | object | 制限なし | |
– | status | staus | object | 制限なし | |
spec | 自動削除 | delete_flag | bool | true/false | 初期値:false |
status | 実行中のコンテナ名 | run_name | string | 文字列 | |
実行中のコンテナID | run_id | string | 文字列 | ||
コンテナレジストリ上のコンテナID | registory_id | string | 文字列 | ||
実行中とレジストリの差分 | check_id | boolean | true/false | 初期値:false | |
アップデート処理完了フラグ | update | boolean | true/false | 初期値:false |
実際にOpertorSDKで作成する場合は、以下のコマンドを実行して、Updateリソースのベースを作成します。
operator-sdk create api --group install --version v1 --kind Update --generate-role
また、以下のファイルを記載通り変更します。
- config/crd/bases/install.sios.nginx.testing_updates.yaml
--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: updates.install.sios.nginx.testing spec: group: install.sios.nginx.testing names: kind: Update listKind: UpdateList plural: updates singular: update scope: Namespaced versions: - name: v1 schema: openAPIV3Schema: description: Update is the Schema for the updates API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' type: string kind: description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' type: string metadata: type: object spec: description: Spec defines the desired state of Update type: object x-kubernetes-preserve-unknown-fields: true properties: delete_flag: type: boolean default: false description: "自動削除" status: description: Status defines the observed state of Update type: object x-kubernetes-preserve-unknown-fields: true properties: run_name: type: string description: "実行中のコンテナ名" run_id: type: string description: "実行中のコンテナID" registory_id: type: string description: "レジストリのコンテナID" check_id: type: boolean default: false description: "更新フラグ" update: type: boolean default: false description: "更新完了フラグ" type: object served: true storage: true subresources: status: {}
実行中のコンテナIDを確認する処理
実行中のコンテナ名やコンテナIDを確認する場合は、Podリソース内の以下表のパスを値を確認すれば確認できます。
表2 PodリソースのコンテナIDやコンテナ名とPathについて
名称 | Path | 備考 |
コンテナ名 | .status.containerStatuses[0].image | |
コンテナID | .status.containerStatuses[0].imageID |
実際にkubectlコマンドで、本連載で作成したOperatorが動作しているPodの情報を取得する場合は、以下のコマンドを実行します。
$ oc get pod -o jsonpath={..status.containerStatuses[0].image},{..status.containerStatuses[0].imageID} jsonpath={..status.containerStatuses[0].image},{..status.containerStatuses[0].imageID} quay.io/k_nakajima/nginx-sample:latest,quay.io/k_nakajima/nginx-sample@sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec
上記コマンドでは、Podの名前までは、指定しませんでしたが実際に確認する場合は、Pod名を特定する必要があります。実際に特定する手順と以下の手順でPod名の特定をおこなってから、Podのコンテナ名とコンテナIDの特定を行っています。
- Startのカスタムリソース情報を取得
- StartのカスタムリソースよりDeploymentリソースを特定
- 取得したPodリソースから、Deploymentリソースのセレクトラベル名によりPodリソースを特定
- 特定したPodリソースのコンテナ名とコンテナIDを取得
以上の手順をAnsibleのコードでOperatorを作成する場合は以下の場所のコードに追加します。
- roles/update/tasks/main.yml
# Startのデータ取得 - name: Get List Start # when: (not _wordpress_sios_wordpress_local_wpstart.status.wordpress) community.kubernetes.k8s_info: api_version: install.sios.nginx.testing/v1 kind: Start namespace: '{{ ansible_operator_meta.namespace }}' register: start_list # Start内のnameパラメータの状態確認 - name: Check start name Parameters when: start_list.resources[0].spec.name is defined set_fact: name_data: "{{start_list.resources[0].spec.name}}" # Deploymentパラメータの取得 - name: Get List Deployment community.kubernetes.k8s_info: api_version: apps/v1 kind: Deployment namespace: '{{ ansible_operator_meta.namespace }}' name: "{{name_data}}-sample" register: deploy_wplist # 特定Podパラメータの取得 - name: Get List Pod community.kubernetes.k8s_info: kind: Pod namespace: '{{ ansible_operator_meta.namespace }}' label_selectors: - "app = {{deploy_wplist.resources[0].spec.selector.matchLabels.app}}" register: pod_wplist # 確認テスト用 - debug: msg: "{{pod_wplist.resources[0].status.containerStatuses[0].image}}" - debug: msg: "{{pod_wplist.resources[0].status.containerStatuses[0].imageID}}"
以下の手順で上記のコードを含むOperatorを実行します。
make install make run
別のターミナルで以下のコマンドを実行します。
oc apply -f config/samples/install_v1_start.yaml oc apply -f config/samples/install_v1_update.yaml
ansibleのプログラムが動作しているターミナルで、プログラムがKuberentes環境にアクセスして、Podリソースより、動作しているコンテナの名前やコンテナのIDを以下の通り出力します。
--------------------------- Ansible Task StdOut ------------------------------- ok: [localhost] => { "msg": "quay.io/k_nakajima/nginx-sample:latest" } ------------------------------------------------------------------------------- {"level":"info","ts":1634693826.8018212,"logger":"logging_event_handler","msg":"[playbook debug]","name":"update-sample","namespace":"wp-test","gvk":"install.sios.nginx.testing/v1, Kind=Update","event_type":"runner_on_ok","job":"4011972612362184505","EventData.TaskArgs":""} --------------------------- Ansible Task StdOut ------------------------------- TASK [debug] ******************************** ok: [localhost] => { "msg": "quay.io/k_nakajima/nginx-sample@sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec" }
コンテナレジストリサーバのコンテナIDを確認する
今度は、コンテナレジストリサーバにアクセスしてコンテナIDを確認します。動作としては次の図のように動作します。
図2 コンテナレジストリサーバへのコンテナID確認
実際にcurlコマンドを使用して『quay.io/k_nakajima/nginx-sample』のデータを確認する場合は以下のコマンドを実行すると記載通りになります。
$ curl https://quay.io/api/v1/repository/k_nakajima/nginx-sample {"trust_enabled": false, "description": "", "tags": {"latest": {"image_id": "0d04b0dd8f324b53c10e2e746d6e87ffe2c2b1cf48e4291e9dd4a1aec40f6389", "last_modified": "Thu, 07 Oct 2021 03:04:01 -0000", "name": "latest", "manifest_digest": "sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec", "size": 171490154}}, "tag_expiration_s": 1209600, "is_public": true, "is_starred": false, "is_free_account": true, "kind": "image", "name": "nginx-sample", "namespace": "k_nakajima", "is_organization": false, "state": "NORMAL", "can_write": false, "status_token": "", "can_admin": false}
上記結果より、latestタグのコンテナのsha256値は「tags[‘latest’].manifest_digest」の値である「sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec」であり、現時点では、PodのコンテナとコンテナレジストリのIDが一致していることがわかります。
コンテナレジストリからコンテナのsha256値取得処理をOperatorに組み込む場合は以下のコードに追加記載します。
- roles/update/tasks/main.yml
# コンテナIDをレジストリから確認 - name: check sonar web is up uri: url: https://quay.io/api/v1/repository/k_nakajima/nginx-sample method: GET return_content: yes status_code: 200 body_format: json register: registry_data #確認テスト用 - debug: msg: "{{registry_data.json.tags['latest'].manifest_digest}}"
実際に動作テストすると以下の通り文字列が表示します。
--------------------------- Ansible Task StdOut ------------------------------- TASK [debug] ******************************** ok: [localhost] => { "msg": "sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec" } -------------------------------------------------------------------------------
コンテナの更新処理
上記手段より、コンテナレジストリと実行中のPodより、コンテナIDの値を取得できる方法を確立しました。これにより、コンテナレジストリとPodのコンテナに違いがあるのかわかるようになりました。もし、更新する場合は、本OpertorでNginxを立ち上げる際にDeploymentリソースを使用しているため、Deploymentリソース内のimageの値を変更すれば、更新できることがわかります。これを利用してコンテナ更新のためフローチャートを書くと図3の通りになります。
図3 コンテナ更新のフローチャート
作成したOperatorがDeploymentリソースを監視しているため、コンテナ更新する際にDeploymentリソースを直接変更しても、すぐに戻ってしまうことより、変更できるように以下の通りに変更します。
- roles/start/defaults/main.yml
--- # defaults file for Start name: nginx image: "quay.io/k_nakajima/nginx-sample:latest"
- roles/start/templates/deployment-nginx.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: '{{ name }}-sample' namespace: '{{ ansible_operator_meta.namespace }}' spec: selector: matchLabels: app: {{ name }}-app replicas: 1 template: metadata: labels: app: {{ name }}-app spec: containers: - name: {{ name }}-sample image: {{ image }} ports: - containerPort: 8080
上記修正により、Startリソースを変更することで、Deploymentリソースが変更できるようになったので、以下のコードを追加で記載することで、更新処理を作成します。
- roles/update/tasks/main.yml
#更新フラグの更新 - name: flag update when: (pod_wplist.resources[0].status.containerStatuses[0].imageID != "quay.io/k_nakajima/nginx-sample@{{registry_data.json.tags['latest'].manifest_digest}}") operator_sdk.util.k8s_status: kind: Update api_version: install.sios.nginx.testing/v1 name: '{{ ansible_operator_meta.name }}' namespace: '{{ ansible_operator_meta.namespace }}' status: check_id: true - name: Get List Update community.kubernetes.k8s_info: api_version: install.sios.nginx.testing/v1 kind: Update namespace: '{{ ansible_operator_meta.namespace }}' name: '{{ ansible_operator_meta.name }}' register: update_list #更新処理 - name: deployment update when: pod_wplist.resources[0].status.containerStatuses[0].imageID != "quay.io/k_nakajima/nginx-sample@{{registry_data.json.tags['latest'].manifest_digest}}" and update_list.resources[0].status.check_id and "{{not update_list.resources[0].status.update}}" vars: name: "{{ start_list.resources[0].metadata.name }}" image: "quay.io/k_nakajima/nginx-sample@{{registry_data.json.tags['latest'].manifest_digest }}" block: - name: update deployment data community.kubernetes.k8s: state: present definition: kind: Start apiVersion: install.sios.nginx.testing/v1 metadata: name: "{{ name }}" namespace: '{{ ansible_operator_meta.namespace }}' spec: image: "{{ image }}" - name: update flag operator_sdk.util.k8s_status: kind: Update api_version: install.sios.nginx.testing/v1 name: '{{ ansible_operator_meta.name }}' namespace: '{{ ansible_operator_meta.namespace }}' status: update: true
Ansibleのテストをする際に、エラーが発生する場合は、「operator_sdk.util」モジュールがインストールしていないため起こる可能性があるので、コマンドで 「operator_sdk.util」モジュール でインストールします。
ansible-galaxy collection install operator_sdk.util
現時点では、コンテナレジストリとPodは同じコンテナを使用しているため、updateリソースのstatus内のフラグ情報は以下の通りになります。
$ oc get update -o jsonpath={..status.check_id},{..stapdate} false,false
もし、差分がある場合は、Deploymetリソースの値が変更され、updateリソースのstatus内のフラグ情報は以下の通りになります。
$ oc get update -o jsonpath={..status.check_id},{..status.update} true,true
自動削除処理
最後にリソースの削除削除処理について話します。本記事ではカスタムリソース定義でspec.delete_flagのkeyの値を自動削除処理のフラグとして定義しました。もしそのフラグが真だった場合、自分自身のリソースを削除する処理を組み込みます。
実際に作成する場合ば以下の部分に追加します。
- roles/update/tasks/main.yml
# 自動削除 - name: Auto Delete when: update_list.resources[0].spec.delete_flag community.kubernetes.k8s: state: absent definition: kind: Update apiVersion: install.sios.nginx.testing/v1 metadata: name: "{{ ansible_operator_meta.name }}" namespace: '{{ ansible_operator_meta.namespace }}'
実際に作成する前に、自動削除処理のフラグをtrueにする場合にリソースを作成します。
- config/samples/install_v1_update.yaml
apiVersion: install.sios.nginx.testing/v1 kind: Update metadata: name: update-sample spec: delete_flag: true
上記のリソースを適用し実行すると、処理が完了後、以下のコマンドの通り削除存在しなくなることがわかります。
まとめ
以上の通り、アップデート処理を組み込んだOperatorを作成することができました。単純にアップデートするといっても、この通り手順を明確にして一つ一つコードに落とし込む必要があります。今回は、単純なコンテナ更新のみのアップデートだけでしたが、特定ファイルを上書きする必要があるものやメンテナンスモードに切り替えてからアップデート処理する必要があるもが出たら、さらに追加のバッチ処理が必要になり、本記事のような簡単な方法でアップデートも組めるとは言えなくなります。
このようにOperatorで手順を自動化するために、手順をドキュメントに落としてからコード化する必要があるといえるでしょう。