Tektonとskopeoを使ったコンテナイメージのコピー

こんにちは、Red Hatでソリューションアーキテクトをしている石川です。
以前に本ブログでOpenShiftで利用可能なCIパイプラインであるTektonについてご紹介しましたが、今回はTektonを使ってCIパイプラインの中でコンテナイメージを外部のレジストリにコピーする方法についてご紹介させて頂こうと思います。

以前の記事は以下となります。 Tektonの基本やインストール方法について知りたいという方はこちらの記事をご参照下さい。 rheb.hatenablog.com rheb.hatenablog.com

なぜイメージのコピーが必要か?

最近ではOpenShiftをシステムの本番環境だけでなく、開発環境、ステージング環境と、一つのシステムであっても複数のクラスタをご利用頂くケースが多くなっています。
OpenShiftにはクラスタの内部にイメージを保管するレジストリがあるのですが、ステージング環境や本番環境で動かすアプリケーションのイメージを個々のクラスタの内部レジストリから取ってこようとする場合、あらかじめ開発環境でビルドしたイメージをそれぞれの環境にコピーしておく必要があります。

f:id:jpishikawa:20220303101331p:plain

コピーを行う方法として、Podmanなどを使い手動でpullとpushを繰り返す方法も考えられますが、新規のコンテナイメージが作られるたびにこうした作業を繰り返すのは手間がかかり、また人手による操作だと作業ミスが発生する可能性もあります。
そのため本記事ではOpenShift Pipelines(Tekton)とskopeoを使ってCIパイプラインの中でイメージコピーの仕組みを実装していきたいと思います。

利用するパイプライン

今回利用するパイプラインとして以下を準備しました。
f:id:jpishikawa:20220303101402p:plain
それぞれのTaskでは以下を行なっています。
・git-clone: コンテナ化するアプリケーションのソースコードの取得
・s2i-nodejs: コンテナのビルドと内部レジストリへのプッシュ
・skopeo-copy: コンテナイメージの別クラスタへのコピー

コンテナイメージのコピーについてはskopeoを使用します。
skopeoはコンテナイメージを複数のレジストリ間で移動させたいときに便利なツールです。 デーモンレスで動作し、かつコピーの実行にあたってroot権限が不要など、気軽に利用することができます。
skopeoの利用方法についてはこちらのブログが参考になると思います。

さて今回はこのskopeoをTektonのTaskとして利用するわけですが、OpenShiftではOpenShift Pipelines Operatorをインストールする際に、クラスタ全体で利用可能なClusterTaskの一つとしてskopeo-copyがインストールされます。そのため今回はそちらを使ってPipelineを作成します。
実際に作成したPipelineのyamlは以下となります。(クリックで展開)

skopeo-pipeline.yaml

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: skopeo-pipeline
spec:
  workspaces:
  - name: shared-workspace
  - name: tmp-vol


  params:  
  - name: git-url
    type: string
  - name: git-revision  
    type: string
  - name: target-path
    type: string
  - name: dest-imageurl
    type: string

  tasks:
  - name: git-clone-health
    taskRef:
      name: git-clone
      kind: ClusterTask
    params:
    - name: url
      value: $(params.git-url)
    - name: revision
      value: $(params.git-revision)
    workspaces:
    - name: output
      workspace: shared-workspace

  - name: build-container
    taskRef:
      name: s2i-nodejs
      kind: ClusterTask
    runAfter:
    - git-clone-health
    workspaces:
    - name: source
      workspace: shared-workspace
    params:
    - name: PATH_CONTEXT
      value: $(workspaces.source.path)/$(params.target-path)
    - name: IMAGE
      value: image-registry.openshift-image-registry.svc:5000/source/health-record:latest

  - name: copy-image
    taskRef:
      name: skopeo-copy
      kind: ClusterTask
    runAfter:
    - build-container
    workspaces:
    - name: images-url
      workspace: tmp-vol
    params:
    - name: srcImageURL
      value: 'docker://image-registry.openshift-image-registry.svc:5000/source/health-record' 
    - name: destImageURL
      value: $(params.dest-imageurl)
    - name: srcTLSverify
      value: 'false'
    - name: destTLSverify
      value: 'false'

環境の準備

今回は2つのOpenShiftクラスタを準備し、その間でイメージのコピーを行います。
一つはAWSのオハイオ(us-east-2)リージョンに構築したROSAクラスタで、もう一つは東京(ap-northeast-1)リージョンにIPIで構築したOCPクラスタです。
ROSAクラスタ側でCIパイプラインを実行し、OCP IPIクラスタの内部レジストリにイメージをコピーしたいと思います。

クラスタを構築したらすぐにイメージのコピーができるかというとそうではなく、事前に以下の三点の準備が必要となります。
1. それぞれのクラスタでNamespaceを作成しておく
2. あて先となるクラスタ(OCP IPI側)でレジストリを外部公開しておく
3. あて先のレジストリの認証情報を取得し、CIパイプライン実行時のServiceAccountに紐付ける

1. それぞれのクラスタでNamespaceを作成しておく

CI実行を行うクラスタにはsource、あて先のクラスタではdestinationというNamespaceをそれぞれ作成し、これらを使っていきます。 それぞれのクラスタに対して、oc new-projectコマンド、もしくはOpenShiftのコンソールを使ってこれらのNamespaceを作成しておきましょう。

2. あて先となるクラスタでレジストリを公開しておく

IPIで作成されたOCPクラスタではデフォルトだと内部レジストリが外部に公開されていないため、コンテナイメージをPushすることができません。
そのため以下のコマンドを実行し、レジストリの外部公開を行うよう設定を変更します。

oc patch configs.imageregistry.operator.openshift.io/cluster --patch '{"spec":{"defaultRoute":true}}' --type=merge

設定が反映されるとopenshift-image-registryのNamespaceにて、外部公開用のRouteを確認することができます。

f:id:jpishikawa:20220303101458p:plain

レジストリの公開についての詳細はこちらのドキュメントをご参照下さい。

3. あて先のレジストリの認証情報を取得し、CIパイプライン実行時のServiceAccountに紐付ける

公開されたレジストリにイメージをPushするためには、イメージのPushを行うことが可能なユーザー、もしくはServiceAccountとして認証される必要があります。
OpenShiftでは各Namespaceにデフォルトで、defaultbuilderdeployerという3つのServiceAccountが存在します。このうちbuilderはレジストリにイメージをPushすることができる権限を持っているため、今回はこのbuilderの認証情報を利用したいと思います。

あて先クラスタ側で以下のコマンドを実行するとdestinationNamespaceにおけるbuilderの認証情報が出力されます。

SECRET=$(oc -n destination get secret | grep builder-docker | awk {'print $1'})
oc -n destination get secret $SECRET -o jsonpath="{.data['\.dockercfg']}" | base64 --decode | jq '.["image-registry.openshift-image-registry.svc:5000"].password'
# 実行結果の例
"eyJhbGci...(中略)...HddvV0"

得られた認証情報をCI実行を行う方のクラスタに追加していきましょう。 ここからはコンソールで操作をしていきます。
sourceNamespaceにてSecret一覧を開き、pipeline-dockercfgから始まるSecretを選択して編集を行います。
このSecretはパイプライン実行を行うServiceAccountであるpipelineで利用されます。

f:id:jpishikawa:20220303101517p:plain
"認証情報の追加"をクリックし以下を入力します。
レジストリサーバーのアドレス: 公開したあて先クラスタのレジストリのURL
ユーザー名: serviceaccount
パスワード: builderの認証情報

認証情報を登録することで、CI実行側のクラスタであて先クラスタのレジストリにアクセスする準備が整いました。

パイプラインの実行

それではいよいよパイプラインを実行していきたいと思います。
CI実行を行うクラスタにはOpenShift Pipelinesをインストールした上で、先ほどのskopeo-pipeline.yamlを適用しておきましょう。

oc apply -n source -f skopeo-pipeline.yaml

今回はPipelineRunを作成し、Pipelineの実行を行います。EventListnerを使ったPipelineの実行については本記事の冒頭にある以前の記事をご参照下さい。

PipelineRunのyamlは以下となります。(クリックで展開)

skopeo-pipelinerun.yaml

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  generateName: skopeo-pipeline-run-
spec:
  pipelineRef:
    name: skopeo-pipeline
  params:
    - name: target-path
      value: 'site'
    - name: git-url
      value: 'https://gitlab.com/jpishikawa/starter-kit-cicd-app'
    - name: git-revision
      value: 'main'
    - name: dest-imageurl
      value: 'docker://default-route-openshift-image-registry.apps.mycluster3.sandbox663.opentlc.com/destination/starter-kit-cicd-app'
  workspaces:
    - name: shared-workspace
      volumeClaimTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
    - name: tmp-vol
      emptyDir: {}

あて先となるレジストリのURLを.spec.params.dest-imageurlというパラメーターの中で指定していますが、先頭部分がhttps://ではなくdocker://から始まる点に注意して下さい。

このファイルを適用するとパイプラインの実行が開始されます。

oc create -n source -f skopeo-pipelinerun.yaml

実行してしばらく待つと以下のような形でパイプラインが成功となります。
f:id:jpishikawa:20220303101543p:plain

今度はあて先側のクラスタを見て、イメージがコピーされているか確認をしましょう。
コンソールのdestinationNamespaceにて、画面左側の"ビルド">"イメージストリーム"を開くとコピーされたイメージを確認することができます。
f:id:jpishikawa:20220303101612p:plain
これでCIパイプラインの中でイメージをコピーできることが確認できました。

まとめ

本記事ではTektonとskopeoを使って、イメージのビルドから外部のレジストリへのコピーまで一連の流れとして行う方法をご紹介しました。 いくつかの事前準備は必要となりますが、skopeoを使うことでイメージのコピーが簡単に実施できることをご理解いただけたのではないでしょうか。
さて、今回はあて先のクラスタにてレジストリを外部公開してコピーを実現しましたが、そもそもレジストリを外部公開したくないケースもあるのではと考えます。 そこで次回はskupperを使い、レジストリを外部公開をせずにイメージをコピーする方法についてご紹介したいと思います。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。