Operator作成について 連載第8回目 自動アップデートのOperatorを作成する・作成編

はじめに

 前回、アップデートするための手順について解説をしました。その応用編として、実際にその手順をAnsibleで自動化して、Kubernetes Operatorとして作成します。

 本記事では、その作成手法を機能別に分断して実際のコード説明と組み合わせて動作を解説します。

アップデートの手順について

 実際にコンテナのアップデートを行う場合は以下手順を行います。また、絵にすると図1の通りになります。

  1. Podの動いているコンテナIDの確認
  2. コンテナレジストリのコンテナIDの確認
  3. 差分を確認してDeploymentリソースのコンテナIDの更新

図1 アップデート処理の図

手順のOperator化

  上記で記載した手順をAnsibleのコードにすることで、自動処理できるようにします。

作成するカスタムリソースについて

 実際にOperatorを作る上で起動するトリガーとなるカスタムリソースの名前をUpdateとして、以下の表1カスタムリソースが作成できるように定義します。

表1 Updateカスタムリソース作成の定理

key名名称
apiVersionAPIのグループ名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
servedREST API経由の提供true
storageストレージバージョンの登録true
schemaスキーマスキーマ詳細の表3を参照
subresourcesサブリソースstatus: {}

表3 Updateカスタムリソースのスキーマ詳細

親キー名名称キー名変数の型入力制限備考
specspecobject制限なし
statusstausobject制限なし
spec自動削除delete_flagbooltrue/false初期値:false
status実行中のコンテナ名run_namestring文字列
実行中のコンテナIDrun_idstring文字列
コンテナレジストリ上のコンテナIDregistory_idstring文字列
実行中とレジストリの差分check_idbooleantrue/false初期値:false
アップデート処理完了フラグupdatebooleantrue/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の特定を行っています。

  1. Startのカスタムリソース情報を取得
  2. StartのカスタムリソースよりDeploymentリソースを特定
  3. 取得したPodリソースから、Deploymentリソースのセレクトラベル名によりPodリソースを特定
  4. 特定した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で手順を自動化するために、手順をドキュメントに落としてからコード化する必要があるといえるでしょう。