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

はじめに

 コンテナを更新するための仕組みについて、コンテナ名とタグの関係を説明し、コンテナ名とコンテナのハッシュ値でコンテナをKubernetesでデプロイする方法の説明します。

 また、実行しているコンテナのハッシュ値やコンテナレジストリ(Quay.io)のAPIを使用してハッシュ値を取得する方法を説明します。

 最後に、コンテナ名とコンテナのハッシュ値でアップデートの仕組みを考える方法を説明します。

実行コンテナのアップデートについて

 コンテナをKubernetesで実行させるには、コンテナの名前とタグをPodリソースに記載してKubernetesリソースに適応することでコンテナが実行します。通常でコンテナの運用でアップデートを考える場合は、タグでバージョン管理することでa version、b versionとコンテナのバージョン管理することができます。

図1 バージョン別コンテナの実行

 タグによるコンテナ切り替えの図を図1に記載しました。その図について簡単に説明しますと、a version実行させたときにコンテナレジストリから、aというコンテナをpullしてローカルに保存れ、それを使って実行します。aからbに切り替えた時に、新規にbというコンテナをローカルにダウンロードしてaのコンテナを残したままbが起動します。もし、既存コンテナがある状態で、bからaに切り替えた場合は通常の動作は、図2のように、ローカルのaというコンテナを使用して実行されます。

図2 バージョン別コンテナの実行(既存コンテナ有り時)

 余談となりますが、図2の動作はimagePullPolicyとパラメータがIfNotPresentの時の動作です。特に設定しない場合は、コンテナのタグによって表1の通りの動作をします。

表1 コンテナタグ別のPod起動時の動作

コンテナタグパラメータ指定なし時のパラメータ動作
latestAlways 常にpullしてから立ち上げ
latest以外IfNotPresentローカルに一致するコンテナがないならpull

 もし、タグaのコンテナを更新し、a`コンテナがコンテナレジストリ登録されたあとで、bコンテナから、aコンテナに起動を切り替えると図3のように、ローカルに保存された古いコンテナ実行されます。

図3 コンテナbからコンテナaに切り替えた時の動作図

 そこで、重要になるののがコンテナのハッシュ値となります。図4のように、コンテナのないような異なるならば、各コンテナごとにSHA-256の値が異なります

図4 SHA-256値を指定してコンテナの実行

 定期的に、実行中のコンテナハッシュ値と、コンテナレジストリ内のコンテナハッシュ値を定期的に監視して、異なることを発揮できれば新しいバージョンの検出することが可能であります。また、以下ののようにKubernetesリソースパラメータを変更することで、ハッシュ値を指定してコンテナを起動させることが可能になります。

image: version@sha256:値

 以降に、実行中のコンテナハッシュ値確認方法や、コンテナレジストリのコンテナハッシュ値の確認方法について説明します。

Podリソースについて

 以下のようなDeploymentリソースをOpenShiftで適用してコンテナを立ち上げます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  annotations:
    image.openshift.io/triggers: |-
      [
        {
          "from": {
            "kind": "ImageStreamTag",
            "name": "openshift/hello-openshift:latest"
          },
          "fieldPath": "spec.template.spec.containers[0].image"
        }
      ]
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: quay.io/k_nakajima/nginx-sample:latest
          ports:
            - containerPort: 8080

 DeploymentリソースをKubernetesに適用すると最終的にPodリソースが作成されます。そのPodのリソースを確認すると以下の通り、実行コンテナのSHA-256を得ます。

$ oc get pod -o yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Pod
  metadata:
    annotations:
      k8s.v1.cni.cncf.io/network-status: |-
        [{
            "name": "openshift-sdn",
            "interface": "eth0",
            "ips": [
                "10.217.0.246"
            ],
            "default": true,
            "dns": {}
        }]
      k8s.v1.cni.cncf.io/networks-status: |-
        [{
            "name": "openshift-sdn",
            "interface": "eth0",
            "ips": [
                "10.217.0.246"
            ],
            "default": true,
            "dns": {}
        }]
      openshift.io/scc: restricted
    creationTimestamp: "2021-10-07T03:05:47Z"
    generateName: nginx-54589dd565-
    labels:
      app: nginx
      pod-template-hash: 54589dd565
    managedFields:
    - apiVersion: v1
      fieldsType: FieldsV1
      fieldsV1:
        f:metadata:
          f:generateName: {}
          f:labels:
            .: {}
            f:app: {}
            f:pod-template-hash: {}
          f:ownerReferences:
            .: {}
            k:{"uid":"b165968b-3191-4e72-a839-0c673691f234"}:
              .: {}
              f:apiVersion: {}
              f:blockOwnerDeletion: {}
              f:controller: {}
              f:kind: {}
              f:name: {}
              f:uid: {}
        f:spec:
          f:containers:
            k:{"name":"nginx"}:
              .: {}
              f:image: {}
              f:imagePullPolicy: {}
              f:name: {}
              f:ports:
                .: {}
                k:{"containerPort":8080,"protocol":"TCP"}:
                  .: {}
                  f:containerPort: {}
                  f:protocol: {}
              f:resources: {}
              f:terminationMessagePath: {}
              f:terminationMessagePolicy: {}
          f:dnsPolicy: {}
          f:enableServiceLinks: {}
          f:restartPolicy: {}
          f:schedulerName: {}
          f:securityContext:
            .: {}
            f:fsGroup: {}
            f:seLinuxOptions:
              f:level: {}
          f:terminationGracePeriodSeconds: {}
      manager: kube-controller-manager
      operation: Update
      time: "2021-10-07T03:05:47Z"
    - apiVersion: v1
      fieldsType: FieldsV1
      fieldsV1:
        f:metadata:
          f:annotations:
            f:k8s.v1.cni.cncf.io/network-status: {}
            f:k8s.v1.cni.cncf.io/networks-status: {}
      manager: multus
      operation: Update
      time: "2021-10-07T03:05:51Z"
    - apiVersion: v1
      fieldsType: FieldsV1
      fieldsV1:
        f:status:
          f:conditions:
            k:{"type":"ContainersReady"}:
              .: {}
              f:lastProbeTime: {}
              f:lastTransitionTime: {}
              f:status: {}
              f:type: {}
            k:{"type":"Initialized"}:
              .: {}
              f:lastProbeTime: {}
              f:lastTransitionTime: {}
              f:status: {}
              f:type: {}
            k:{"type":"Ready"}:
              .: {}
              f:lastProbeTime: {}
              f:lastTransitionTime: {}
              f:status: {}
              f:type: {}
          f:containerStatuses: {}
          f:hostIP: {}
          f:phase: {}
          f:podIP: {}
          f:podIPs:
            .: {}
            k:{"ip":"10.217.0.246"}:
              .: {}
              f:ip: {}
          f:startTime: {}
      manager: kubelet
      operation: Update
      time: "2021-10-07T03:06:04Z"
    name: nginx-54589dd565-v9xm9
    namespace: test-wp
    ownerReferences:
    - apiVersion: apps/v1
      blockOwnerDeletion: true
      controller: true
      kind: ReplicaSet
      name: nginx-54589dd565
      uid: b165968b-3191-4e72-a839-0c673691f234
    resourceVersion: "4937689"
    uid: 1313f0e1-7f23-487f-84f5-63de3cedef72
  spec:
    containers:
    - image: quay.io/k_nakajima/nginx-sample:latest
      imagePullPolicy: Always
      name: nginx
      ports:
      - containerPort: 8080
        protocol: TCP
      resources: {}
      securityContext:
        capabilities:
          drop:
          - KILL
          - MKNOD
          - SETGID
          - SETUID
        runAsUser: 1000620000
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: File
      volumeMounts:
      - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
        name: kube-api-access-kg7xb
        readOnly: true
    dnsPolicy: ClusterFirst
    enableServiceLinks: true
    imagePullSecrets:
    - name: default-dockercfg-46zdg
    nodeName: crc-5dd5m-master-0
    preemptionPolicy: PreemptLowerPriority
    priority: 0
    restartPolicy: Always
    schedulerName: default-scheduler
    securityContext:
      fsGroup: 1000620000
      seLinuxOptions:
        level: s0:c25,c10
    serviceAccount: default
    serviceAccountName: default
    terminationGracePeriodSeconds: 30
    tolerations:
    - effect: NoExecute
      key: node.kubernetes.io/not-ready
      operator: Exists
      tolerationSeconds: 300
    - effect: NoExecute
      key: node.kubernetes.io/unreachable
      operator: Exists
      tolerationSeconds: 300
    volumes:
    - name: kube-api-access-kg7xb
      projected:
        defaultMode: 420
        sources:
        - serviceAccountToken:
            expirationSeconds: 3607
            path: token
        - configMap:
            items:
            - key: ca.crt
              path: ca.crt
            name: kube-root-ca.crt
        - downwardAPI:
            items:
            - fieldRef:
                apiVersion: v1
                fieldPath: metadata.namespace
              path: namespace
        - configMap:
            items:
            - key: service-ca.crt
              path: service-ca.crt
            name: openshift-service-ca.crt
  status:
    conditions:
    - lastProbeTime: null
      lastTransitionTime: "2021-10-07T03:05:47Z"
      status: "True"
      type: Initialized
    - lastProbeTime: null
      lastTransitionTime: "2021-10-07T03:06:04Z"
      status: "True"
      type: Ready
    - lastProbeTime: null
      lastTransitionTime: "2021-10-07T03:06:04Z"
      status: "True"
      type: ContainersReady
    - lastProbeTime: null
      lastTransitionTime: "2021-10-07T03:05:47Z"
      status: "True"
      type: PodScheduled
    containerStatuses:
    - containerID: cri-o://8838c47b8a26622f77a969fb2380d99700da8c6a007d817b2b36993517419988
      image: quay.io/k_nakajima/nginx-sample:latest
      imageID: quay.io/k_nakajima/nginx-sample@sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec
      lastState: {}
      name: nginx
      ready: true
      restartCount: 0
      started: true
      state:
        running:
          startedAt: "2021-10-07T03:06:04Z"
    hostIP: 192.168.126.11
    phase: Running
    podIP: 10.217.0.246
    podIPs:
    - ip: 10.217.0.246
    qosClass: BestEffort
    startTime: "2021-10-07T03:05:47Z"
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

以上の値から、.status.containerStatuses[].imageIDの値を確認すると「quay.io/k_nakajima/nginx-sample@sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec」のように実行中のコンテナのIDを確認しました。

レジストリコンテナについて

 quay.ioのコンテナレジストリから、コンテナ情報をしたい場合は以下の通りに入力することで確認します。

curl https://quay.io/api/v1/repository/<user name>/<contena name>

実際に実行してみますと以下の通りになります。

$ 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}

上記のJSONの結果から、「k_nakajima/nginx-sample:latest」のSHA256の値は0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec」

コンテナIDで起動

 今まで、コンテナイメージの指定を「quay.io/k_nakajima/nginx-sample:latest」にしていましたが「quay.io/k_nakajima/nginx-sample@sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec」というように、Deploymentリソース内の.spec.template.spec.containers[0].imageの値を変更すると新しくPodが起動して、ハッシュ値指定のコンテナが立ち上がり、以下のコマンドでImageIDを確認すると同じ値が確認することができます。

$ oc get pod -o jsonpath={..status.containerStatuses[*].imageID}
quay.io/k_nakajima/nginx-sample@sha256:0f152bc1261aea012bbf89977a821f1b319b9dc9feb7f4ba54f08f617b2937ec

まとめ

 今回は、同じ名前、同じタグのコンテナをアップデートした時のアップデートのやり方について説明しました。同じ名前、同じタグでも、コンテナのハッシュ値を実行中のコンテナとコンテナレジストリのコンテナで見比べることで、アップデートしていないか確認することがわかることが理解できたと思います。(仕組み図は図5を参照)

図5 コンテナのアップデート処理について

 次回は、図5の仕組みをOperatorとして組み込んで自動的にアップデートする方法について記載します。