0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

コンテナイメージの更新契機でCartographer経由でTektonパイプラインを実行する。

Last updated at Posted at 2022-07-05

コンテナイメージを作り直した際、その更新契機で自動でスキャンやらデプロイをしたくなることがある。
今回は、イメージのbuildをk8sリソースとして扱えるkpackと、k8sのリソースの状態を入出力情報として別リソースに橋渡しできるCartographerを組み合わせて、コンテナイメージの更新契機でTektonのPipelineを実行し、スキャンやらデプロイが出来そうなところを確認する。
なお、Feasibility確認のためのメモなので、実際にスキャンとかデプロイは実施しない。

前提ソフトウェアのインストール

CartographerとTektonをk8sクラスタにインストールする。
今回は後で削除が楽にできるよう、kappを使ってインストールしているが、kubectlで代用してもよい。

Cartographerのインストールは以下の手順となる。

kapp deploy -a cartographer -f https://github.com/vmware-tanzu/cartographer/releases/download/v0.4.2/cartographer.yaml -n default -y

Tektonのインストールは以下の手順となる。

kapp deploy -a tekton -f https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml -n default -y

リソースの作成

Cartographerはテンプレートと呼ばれるManifestを生成する雛形と、テンプレートを組み合わせたClusterSupplyChainClusterSupplyChainを呼び出すWorkloadから構成される。
(それ以外もあるけど、今回はCartographerの説明が主ではないので割愛)

最終的にはkind: Workloadをapplyすることで、以下を自動で実行するものを作成する。

ClusterSupplyChainを呼び出し、テンプレートから kind:Image と kind:Pipeline を作成
  ├──> Imageのビルド
  └──> Pipelineの呼び出し
       ├─-> Task1の実行 (Scan相当)
       └─-> Task2の実行 (Deploy相当)

今回は以下の手順で作成していく。

  1. テンプレートの作成
  2. ClusterSupplyChainの作成
  3. Workloadの作成

テンプレートの作成

最初に、Gitのソースを元に自動でコンテナイメージを作成してくれるkind: Imageを生成するためのテンプレート(ClusterImageTemplate)を作成する。

apiVersion: carto.run/v1alpha1
kind: ClusterImageTemplate
metadata:
  name: image
spec:
  imagePath: .status.latestImage
  template:
    apiVersion: kpack.io/v1alpha1
    kind: Image
    metadata:
      name: $(workload.metadata.name)$
    spec:
      tag: myharbor.hoge/myapp/node-hello-world
      serviceAccount: default
      builder:
        kind: ClusterBuilder
        name: default
      source:
        git:
          revision: main
          url: https://mygitlab.hoge/myapp/node-hello-world.git

なお、ビルド元のコードはこちらからForkしたものを利用している。

metadataの$(workload.metadata.name)$はCartographer固有のsimple templateという書き方である。詳細はこちらを参照のこと。
親リソースであるWorkloadmetadata.nameから値を引っ張っていると考えておくとよい。
本当はtagやgitのurlもsimple templateまたはyttで置換する方がテンプレートっぽい使い方になるのだが、今回は検証が主たる目的なので時短のためにハードコーディングした。
実際に複数ユーザに使ってもらう場合は、これらの値は外から渡せるようにテンプレート化しよう。

また、ClusterBuilderは事前に作成している。作り方が分からない人はkpackのチュートリアルBuilderClusterBuilderに変更して作成する、もしくはTanzu Build Serviceを購入するとよい。

次に、TektonのPipelineを実行するためのテンプレートを作成する。

apiVersion: carto.run/v1alpha1
kind: ClusterTemplate
metadata:
  name: app-deploy
spec:
  template:
    apiVersion: carto.run/v1alpha1
    kind: Runnable
    metadata:
      name: $(workload.metadata.name)$
    spec:
      serviceAccountName: default
      runTemplateRef:
        name: tekton-pipeline
      selector:
        resource:
          apiVersion: tekton.dev/v1beta1
          kind: Pipeline
        matchingLabels:
          pipeline: scan-and-deploy
      inputs:
        image-with-hash: $(image)$
---
apiVersion: carto.run/v1alpha1
kind: ClusterRunTemplate
metadata:
  name: tekton-pipeline
spec:
  template:
    apiVersion: tekton.dev/v1beta1
    kind: PipelineRun
    metadata:
      generateName: scan-and-deploy-
    spec:
      pipelineRef:
        name: scan-and-deploy
      params:
        - name: last-image
          value: $(runnable.spec.inputs.image-with-hash)$

ClusterTemplateはリソースのテンプレートであり、spec.templateでどういうk8sリソースのテンプレートかを定義する。
今回はRunnableというリソースのテンプレートとして定義する。
Runnableはリソースのアップデートを検知して、リソースを再デプロイしなおすためのリソースで、inputsのフィールドでClusterImageTemplate.status.latestImageを受け取って、runTemplateRefで定義した参照元リソースに渡している(image-with-hashの部分は好きな名前でOK)。
runTemplateRefのselectorはkind: Pipelineであり、すなわちTektonのPipelineにkpackで生成したImageの名前(.status.latestImage)を渡している。
また、PipelineRunClusterRunTemplateで受け取ったimageをparams.valueで渡している。
これにより以下のようなフローが実現できるようになる(イメージスキャン以降はTekton Pipelineで実装)。

最後に、スキャンとデプロイ(になる予定)のPipelineを定義する。

apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: scan-task
spec:
  params:
    - name: last-image
  steps:
    - name: scan-image
      image: centos
      command:
        - bash
        - -cxe
        - |-
          set -o pipefail
          echo "Scan $(params.last-image)"
          exit 0
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: deploy-task
spec:
  params:
    - name: last-image
  steps:
    - name: deploy-image
      image: centos
      command:
        - bash
        - -cxe
        - |-
          set -o pipefail
          echo "Deploy $(params.last-image)"
          exit 0
---
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: scan-and-deploy
  labels:
    pipeline: scan-and-deploy
spec:
  params:
  - name: last-image
  tasks:
  - name: scan
    taskRef:
      name: scan-task
    params:
    - name: last-image
      value: $(params.last-image)
  - name: deploy
    runAfter:
    - scan
    taskRef:
      name: deploy-task
    params:
    - name: last-image
      value: $(params.last-image)

kind: PipelineのnameはClusterRunTemplatePipelineRunで記載したpipelineRefとあわせること。
あと、一応Pipeline風に動作させるため、deployステージ相当の箇所にはrunAfterでscan後に動くよう設定した。

以上がテンプレート関連箇所のManifestとなる。

ClusterSupplyChainの作成

ここでは、先ほど作成したテンプレートを使った、

  1. イメージのビルド
  2. パイプラインの呼び出し
    の2つのみを呼び出す、シンプルなClusterSupplyChainを作成する。
    作成したkind: ClusterSupplyChainは以下となる。
apiVersion: carto.run/v1alpha1
kind: ClusterSupplyChain
metadata:
  name: supply-chain
spec:
  selector:
    app.tanzu.vmware.com/workload-type: myapp
  resources:
    - name: image-builder
      templateRef:
        kind: ClusterImageTemplate
        name: image

    - name: deployer
      templateRef:
        kind: ClusterTemplate
        name: app-deploy
      images:
        - resource: image-builder
          name: image

selectorは後で作るworkloadのラベルと一致する必要がある。
一致するworkloadが作成されると、このClusterSupplyChainが動作する。
deployer部分のimagesでimage-builderの結果をapp-deployというClusterTemplateに渡している。

Workloadの作成

最後にWorkloadリソースを作成する。

apiVersion: carto.run/v1alpha1
kind: Workload
metadata:
  name: node-hello-world
  labels:
    app.tanzu.vmware.com/workload-type: myapp
spec:
  image: hogehoge

実は今回はWorkloadの入力を使わないのでspec.imageの箇所は何でもよい。
なので、実際に確認した際もhogehogeとしている。
ClusterSupplyChainをキックするのにラベルが必要なので、ここだけ間違いないようにする。

実行結果

テンプレート関連をapplyして、ClusterSupplyChainがreadyになったところで、ClusterSupplyChainをapplyする。
これでk8sクラスタとしては準備OKなので、満を持してWorkloadをapplyして、リソースの状態を観察する。
なお、今回はWorkloadから派生して作成されるk8sリソースをまとめて観察するために、kubectl pluginのtreeを利用する。
実行直後は以下ようになる。

$ kubectl tree workload node-hello-world
NAMESPACE  NAME                                              READY    REASON              AGE
default    Workload/node-hello-world                         Unknown  MissingValueAtPath  97s
default    └─Image/node-hello-world                          Unknown                      94s
default      ├─PersistentVolumeClaim/node-hello-world-cache  -                            94s
default      └─SourceResolver/node-hello-world-source        -                            94s

これはkpackによりImage作成中であり、ImageのlatestImageが空になっているため、MissingValueAtPathが表示されている。
kpコマンドでもImage作成中なのが確認できる。

$ kp image list
NAME                READY      LATEST REASON    LATEST IMAGE    NAMESPACE
node-hello-world    Unknown    CONFIG                           default

少し待ってkp build logsを実行すると、コンテナイメージのビルドの様子を見ることが出来る。

$ kp build logs node-hello-world
===> PREPARE
Build reason(s): CONFIG
CONFIG:
	resources: {}
	- source: {}
	+ source:
	+   git:
	+     revision: main
	+     url: https://gitlab.gitlab.hogeeee.info/imurata/node-hello-world.git
:(省略)

イメージの作成が終わった頃に再度workloadを取得すると、Pipelineに処理が遷移している事がわかる。

$ kubectl tree workload node-hello-world
NAMESPACE  NAME                                              READY  REASON        AGE
default    Workload/node-hello-world                         True   Ready         6m40s
default    ├─Image/node-hello-world                          True                 6m37s
default    │ ├─Build/node-hello-world-build-1                -                    2m16s
default    │ │ └─Pod/node-hello-world-build-1-build-pod      False  PodCompleted  2m15s
default    │ ├─PersistentVolumeClaim/node-hello-world-cache  -                    6m37s
default    │ └─SourceResolver/node-hello-world-source        True                 6m37s
default    └─Runnable/node-hello-world                       True   Ready         80s
default      └─PipelineRun/scan-and-deploy-48zk2             -                    75s
default        ├─TaskRun/scan-and-deploy-48zk2-deploy        -                    49s
default        │ └─Pod/scan-and-deploy-48zk2-deploy-pod      False  PodCompleted  49s
default        └─TaskRun/scan-and-deploy-48zk2-scan          -                    75s
default          └─Pod/scan-and-deploy-48zk2-scan-pod        False  PodCompleted  74s

Podの状態がFalseになっているが、これはJobのように動作してPod自体が終了しているためで、状態としては正しい。Podのログを見てみると、先程実装したTaskの処理が実行されていることが分かる。
また、AGEを見るとscanの方が生存時間が長いため、scan->deployの順で動作したことが分かる。

実際の出力結果はこちら。

$ kubectl logs Pod/scan-and-deploy-48zk2-scan-pod
+ set -o pipefail
+ echo 'Scan myharbor.hoge/myapp/node-hello-world@sha256:5ee4288116e32ead948c1922295f1f66a81c8d4556c3bff5c53a6af6e3b946ef'
+ exit 0
Scan myharbor.hoge/myapp/node-hello-world@sha256:5ee4288116e32ead948c1922295f1f66a81c8d4556c3bff5c53a6af6e3b946ef
$ kubectl logs Pod/scan-and-deploy-48zk2-deploy-pod
+ set -o pipefail
+ echo 'Deploy myharbor.hoge/myapp/node-hello-world@sha256:5ee4288116e32ead948c1922295f1f66a81c8d4556c3bff5c53a6af6e3b946ef'
+ exit 0
Deploy myharbor.hoge/myapp/node-hello-world@sha256:5ee4288116e32ead948c1922295f1f66a81c8d4556c3bff5c53a6af6e3b946ef

無事イメージの更新契機でTektonのPipelineを走らせることが出来た。
なお、この状態でkp image trigger node-hello-worldで再度イメージを更新すると、パイプラインが再実行されることも確認できる。

今回はCartographerの肝である、yttやsimple templateを活用したテンプレートの汎用化までは触れなかったが、このようにテンプレートとClusterSupplyChainを作成することで、k8sのワークフローを作成できる、という1つのCartographerのサンプルは紹介できたかと思う。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?