OpenShift GitOpsオペレーターとPipelinesオペレーターの利用

# はじめに
この記事では、OpenShift 4.8のOpenShift GitOpsオペレーターの「Argo CD」機能、OpenShift Pipelinesオペレーターの「トリガー」機能と「パイプライン」機能を使用して下図に示す環境を構築します。sourceリポジトリが変更された場合、OpenShift Pipelinesによって変更内容が反映されたコンテナイメージが作成され、OpenShift GitOpsにより自動的にデプロイされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/cb6a52ea-a17d-0137-17ab-845d60f7b02c.png)
「Argo CD」と「パイプライン」は一般公開機能、「トリガー」はテクノロジープレビューです。

https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.8/html/cicd/_gitops#gitops-release-notes-1-1_gitops-release-notes

https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.8/html/cicd/_pipelines#op-release-notes-1-5_op-release-notes

環境やアプリケーションについては以下の記事を参照ください。

■ GitBucket環境

https://qiita.com/y-akio/items/daaf23de3f7505288d07

■ Spring Bootアプリケーション

https://qiita.com/y-akio/items/155ae44188a6df02138c

# 1. オペレーターの導入
OpenShiftコンソールのOperatorHubからオペレーターを導入します。

■ Red Hat OpenShift GitOpsオペレーター
![gitops.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/a147bc2d-3a5a-8266-580b-11bc7544ca20.png)
■ Red Hat OpenShift Pipelinesオペレーター
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/656213f5-f93b-f79f-47a6-d087000c6bb2.png)
# 2. Red Hat OpenShift GitOpsオペレーターの利用
GitOpsオペレーター(Argo CD)はgitサーバー上のマニフェストを指定したネームスペースに適用します。事前準備としてコンテナイメージを作成し、同コンテナイメージからデプロイメントを作成するマニフェスト「spring-liberty.yaml」をGitBucketのmanifestリポジトリに保管します。

## 2.1. コンテナイメージの作成
OpenShiftでコンテナイメージ(イメージストリーム)を作成します。コンテナイメージはOpen Libertyではなく、Universal Base ImageベースのAdoptOpenJDKを元にしました。前者はUbuntuベースですが、apt-getコマンドに必要なファイルが削除されているらしく、追加パッケージ導入を行いたい場合に不便なためです。

```bash:classicノード
ls -l
### 標準出力↓
-rw-r--r--. 1 root root      709  7月 21 23:15 Dockerfile
-rw-r--r--. 1 root root 90051554  7月  8 21:15 openliberty-webProfile8-21.0.0.7.zip
-rw-r--r--. 1 root root      669  7月 16 20:47 server.xml
-rw-r--r--. 1 root root 13138959  7月 16 20:26 spring-liberty-plain.war

oc new-project spring-liberty
oc new-build --name=spring-liberty --strategy=docker --binary
oc start-build spring-liberty --from-dir=. --follow
### 標準出力↓
Caching blobs under "/var/cache/blobs".

Pulling image adoptopenjdk/openjdk11:ubi ...
Getting image source signatures
Copying blob sha256:a50df8fd88fecefc26fd331f832672108deb08cf9d2b303a5b86156a7f51b5d8
Copying blob sha256:1cadda38f72dece653de82063e3c8e910265fe7a342ec2fb73ad8e540c47e1f7
Copying blob sha256:55856c01f309dc2b4236d868a57af0d1fdaeed19a8a30d859885994cb716811a
Copying blob sha256:51ca547cb0d95827734417e81c9460d237e576f948eb5dde6fe7985814811678
Copying config sha256:5a656a98d5affc32fb7fdf1fc24210978d7d06200c4ce68040a4d65d9aa5cc7e
Writing manifest to image destination
Storing signatures
Adding transient rw bind mount for /run/secrets/rhsm
STEP 1: FROM adoptopenjdk/openjdk11:ubi
STEP 2: ENV PATH=/opt/ol/wlp/bin:$PATH     LOG_DIR=/logs
--> ede80b38f8e
STEP 3: RUN mkdir /opt/ol /logs
--> 48ae27ea010
STEP 4: COPY openliberty-webProfile8-21.0.0.7.zip /opt/ol/
--> e2c5428489f
STEP 5: WORKDIR /opt/ol
--> f81995989a1
STEP 6: RUN /opt/java/openjdk/bin/jar -xf openliberty-webProfile8-21.0.0.7.zip     && rm -f openliberty-webProfile8-21.0.0.7.zip     && chmod +x /opt/ol/wlp/bin/server     && /opt/ol/wlp/bin/server create     && ln -s /opt/ol/wlp/usr/servers/defaultServer /config

Server defaultServer created.
--> 241d11d5490
STEP 7: COPY server.xml /config/
--> 52014da9879
STEP 8: COPY spring-liberty-plain.war /config/apps/
--> e3ae7198e68
STEP 9: RUN chmod -R g+rw /opt/ol/wlp  && chmod -R g+rw /logs
--> 39c92498925
STEP 10: EXPOSE 9080
--> b9187b0ed67
STEP 11: CMD ["/opt/ol/wlp/bin/server","run","defaultServer"]
--> 27ac34fba56
STEP 12: ENV "OPENSHIFT_BUILD_NAME"="spring-liberty-1" "OPENSHIFT_BUILD_NAMESPACE"="spring-liberty"
--> d6cb92453ae
STEP 13: LABEL "io.openshift.build.name"="spring-liberty-1" "io.openshift.build.namespace"="spring-liberty"
STEP 14: COMMIT temp.builder.openshift.io/spring-liberty/spring-liberty-1:4c475cdd
--> 3ac5f861cbb
3ac5f861cbb75a8725d8d371ed178fe52eb8fc3ee3f910b58d0fc614daadfcb2

Pushing image image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty:latest ...
･･･
Successfully pushed image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty@sha256:0d58eabe3907412d77e4951d672ec3baf1b6b1bdbc8a79f7e573ad2a85ebb4cb
Push successful

oc get is
### 標準出力↓
NAME             IMAGE REPOSITORY                                                                 TAGS     UPDATED
spring-liberty   image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty   latest   About a minute ago
```

```dockerfile:Dockerfile
FROM adoptopenjdk/openjdk11:ubi

ENV PATH=/opt/ol/wlp/bin:$PATH \
    LOG_DIR=/logs

RUN  mkdir /opt/ol /logs
COPY openliberty-webProfile8-21.0.0.7.zip /opt/ol/

WORKDIR /opt/ol
RUN /opt/java/openjdk/bin/jar -xf openliberty-webProfile8-21.0.0.7.zip \
    && rm -f openliberty-webProfile8-21.0.0.7.zip \
    && chmod +x /opt/ol/wlp/bin/server \
    && /opt/ol/wlp/bin/server create \
    && ln -s /opt/ol/wlp/usr/servers/defaultServer /config

COPY server.xml /config/
COPY spring-liberty-plain.war /config/apps/

RUN chmod -R g+rw /opt/ol/wlp \
 && chmod -R g+rw /logs

EXPOSE 9080

CMD ["/opt/ol/wlp/bin/server", "run", "defaultServer"]
```

```xml:server.xml
<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">
    <!-- Enable features -->
    <featureManager>
        <feature>jsp-2.3</feature>
    </featureManager>

    <!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
    <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9080" accessLoggingRef="accessLogging" />
    <httpAccessLogging id="accessLogging" filePath="/logs/http_access.log"/>

    <!-- Automatically expand WAR files and EAR files -->
    <applicationManager autoExpand="true"/>

    <webApplication contextRoot="/" location="spring-liberty-plain.war" />
</server>
```

## 2.2. GitBucketへのマニフェストの保管
GitBucketのmanifestリポジトリにマニフェスト「spring-liberty.yaml」を保管します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/af3eee99-f1ba-1595-5a40-f3d26cb0240e.png)

```yaml:spring-liberty.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spring-liberty
  labels:
    app: spring-liberty
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spring-liberty
  template:
    metadata:
      labels:
        app: spring-liberty
    spec:
      containers:
        - name: spring-liberty
          image: image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty
          ports:
            - containerPort: 9080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 9080
---
apiVersion: v1
kind: Service
metadata:
  name: spring-liberty
spec:
  selector:
    app: spring-liberty
  type: ClusterIP
  ports:
   - protocol: TCP
     port: 80
     targetPort: 9080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: spring-liberty
spec:
  rules:
    - host: spring-liberty.apps.ocp.cloud.vpc
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: spring-liberty
                port:
                  number: 80
```

## 2.3. GitOpsオペレーター(Argo CD)によるコンテナのデプロイ
GitOpsオペレーターのカスタムリソース、または、argocdコマンドを使用してGitBucketのmanifestリポジトリに保管されている「spring-liberty.yaml」を適用します。GitBucketはOpenShiftクラスター内にデプロイされており、ネームスペース「gitbucket」のサービス「gitbucket」でアクセス可能であるため、下記の内部アドレスからマニフェストを取得します。
`http://gitbucket.gitbucket.svc.cluster.local/git/ocp/manifest.git`

端末からブラウザでアクセスする際は下記の外部アドレスを使用します。
`http://gitbucket.apps.ocp.cloud.vpc/git/ocp/manifest.git`

■ GitOpsオペレーターのカスタムリソースによるArgo CDの設定

```bash:classicノード
# マニフェスト適用の許可
oc adm policy add-role-to-user admin                                                    \
  system:serviceaccount:openshift-gitops:openshift-gitops-argocd-application-controller \
  -n spring-liberty

# GitOpsオペレーターのカスタムリソースによるArgo CDの設定
oc apply -f application.yaml

# adminユーザーパスワード取得
oc extract secret/openshift-gitops-cluster -n openshift-gitops --to=-
```

```yaml:application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: spring-liberty
  namespace: openshift-gitops
spec:
  project: default
  source:
    repoURL: http://gitbucket.gitbucket.svc.cluster.local/git/ocp/manifest.git
    targetRevision: HEAD
    path: spring-liberty
  destination:
    server: https://kubernetes.default.svc
    namespace: spring-liberty
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
```

https://github.com/redhat-developer/openshift-gitops-getting-started

■ argocdコマンドによるArgo CDの設定

```bash:classicノード
# argocdコマンド取得
wget https://github.com/argoproj/argo-cd/releases/download/v2.0.4/argocd-linux-amd64
mv argocd-linux-amd64 /usr/local/bin/argocd
chmod +x /usr/local/bin/argocd

# マニフェスト適用の許可
oc adm policy add-role-to-user admin                                                    \
  system:serviceaccount:openshift-gitops:openshift-gitops-argocd-application-controller \
  -n spring-liberty

# ログイン
password=`oc get secret openshift-gitops-cluster -n openshift-gitops -ojsonpath='{.data.admin\.password}' | base64 -d`
argocd login openshift-gitops-server-openshift-gitops.apps.ocp.cloud.vpc \
  --username admin --password ${password} --insecure

# argocdコマンドによるArgo CDの設定
argocd app create spring-liberty --repo http://gitbucket.gitbucket.svc.cluster.local/git/ocp/manifest.git \
  --path spring-liberty --dest-server https://kubernetes.default.svc                                      \
  --dest-namespace spring-liberty --sync-policy automated

# アプリケーション一覧
argocd app list
### 標準出力↓
NAME            CLUSTER                         NAMESPACE       PROJECT  STATUS  HEALTH   SYNCPOLICY  CONDITIONS  REPO
                      PATH            TARGET
spring-liberty  https://kubernetes.default.svc  spring-liberty  default  Synced  Healthy  Auto        <none>      http://gitbucket.gitbucket.svc.cluster.local/git/ocp/manifest.git  spring-liberty
```

https://argoproj.github.io/argo-cd/getting_started/

なお、argocdコマンドの場合、GitOpsオペレーター画面にApplicationインスタンスが表示されません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/e9913c4c-2b40-88dd-0524-eb65a865dc30.png)

設定完了後、下記URLからArgo CDにログインすると、デプロイされたことを確認できます。
`https://openshift-gitops-server-openshift-gitops.apps.ocp.cloud.vpc/`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/75c021e7-ed39-6687-1a60-625aafc418ec.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/5856c5f2-18dc-cc85-0609-ececd9193803.png)

# 3. Red Hat OpenShift Pipelinesオペレーターの利用
## 3.1. パイプライン処理用コンテナイメージ作成
下表のパイプライン『source-pipeline』がコンパイル・war作成とGitBucketリポジトリ「container」の更新を行い、パイプライン『container-pipeline』がコンテナイメージ作成とGitBucketリポジトリ「manifest」の更新を行います。

| パイプライン | タスク | ステップ | 実行コマンド |
| ---- | ---- | ---- | ---- |
| source-pipeline | build-source | java-build<br>git-push-war | gradle build<br>git clone / push |
| container-pipeline | build-container | container-build<br>git-push-manifest | oc new-build / start-build<br>git clone / push

パイプライン処理の前提として、gradleやocコマンドの実行が可能なコンテナのイメージを作成します。

```bash:classicノード
wget https://services.gradle.org/distributions/gradle-7.1.1-bin.zip
wget https://mirror.openshift.com/pub/openshift-v4/clients/ocp/4.8.2/openshift-client-linux-4.8.2.tar.gz

ls -l
### 標準出力↓
-rw-r--r--. 1 root root       478  7月 20 20:59 Dockerfile
-rw-r--r--. 1 root root  52029653  7月 20 20:29 caches.tar.gz
-rw-r--r--. 1 root root        53  7月 22 23:03 git.store
-rw-r--r--. 1 root root        95  7月 22 23:04 gitconfig
-rw-r--r--. 1 root root 112231447  7月  2 21:29 gradle-7.1.1-bin.zip
-rw-r--r--. 1 root root  49717645  6月 30 02:58 openshift-client-linux-4.8.2.tar.gz

oc project spring-liberty
oc new-build --name=pipeline-operator --strategy=docker --binary
oc start-build pipeline-operator --from-dir=. --follow
oc get is
### 標準出力↓
NAME                IMAGE REPOSITORY                                                                    TAGS     UPDATED
pipeline-operator   image-registry.openshift-image-registry.svc:5000/spring-liberty/pipeline-operator   latest   23 seconds ago
spring-liberty      image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty      latest   15 minutes ago
```

「java-build」ステップ実行の度にgradleやキャッシュをダウンロードしたくないため、コンテナイメージに追加しています。キャッシュは端末で『**gradle build**』した際にダウンロードされたファイルを圧縮したものです。

```Dockerfile:Dockerfile
FROM registry.access.redhat.com/ubi8/ubi:latest

ADD openshift-client-linux-4.8.0.tar.gz /usr/bin/
ADD caches.tar.gz /tmp
COPY gradle-7.1.1-bin.zip /usr/local/
COPY gitconfig /etc/
COPY git.store /tmp/

RUN dnf install -y git \
 && dnf install -y java-11-openjdk-devel.x86_64 \
 && cd /usr/local; jar -xvf /usr/local/gradle-7.1.1-bin.zip \
 && chmod +x /usr/local/gradle-7.1.1/bin/gradle

ENV PATH $PATH:/usr/local/gradle-7.1.1/bin
ENV GRADLE_USER_HOME /tmp
```

「git-push-war」や「git-push-manifest」ステップで必要なGitBucket認証は下記ファイルで行います。

```bash:gitconfig
[user]
        email = ocp@example.com
        name = ocp
[credential]
        helper = store --file /tmp/git.store
```

```bash:git.store
http://ocp:ocp@gitbucket.gitbucket.svc.cluster.local
```

https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%81%AE%E4%BF%9D%E5%AD%98

## 3.2. GitBucketへのソースコードとコンテナ資材の保管
GitBucketのsourceリポジトリにソースコードを保管します。ソースコードについてはこちらの[記事](https://qiita.com/y-akio/items/155ae44188a6df02138c)を参照ください。Spring BootとOpen Libertyによるシンプルなアプリケーションです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/6889ccab-6cd7-f70a-9a7f-2b8a0627673a.png)

GitBucketのcontainerリポジトリにコンテナ資材を保管します。[本記事](https://qiita.com/y-akio/private/f7240d1f1736d6533899#21-コンテナイメージの作成)でspring-libertyアプリケーションのコンテナイメージ作成に使用したものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/cd9bd7b2-9296-b753-a566-c6d196b498f2.png)

sourceリポジトリ、containerリポジトリそれぞれにwebhook設定を追加します。webhook送信先のリスナーPodは後続の作業で起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/5b1a2898-fc1c-794e-a413-136e3c04b138.png)

## 3.3. トリガー機能、パイプライン機能マニフェストの適用
webhookをトリガーとして利用するために「binding.yaml」と「listener.yaml」を適用し、トリガーを契機にパイプラインを実行するために「source-pipeline.yaml」と「container-pipeline.yaml」を適用します。各ファイルでは以下のカスタムリソースを作成しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/c194bd74-869c-445f-b93b-94d99665b952.png)

```bash:classicノード
oc project spring-liberty

# トリガーバインディング
oc apply -f binding.yaml

# イベントリスナーPod起動
oc apply -f listener.yaml

# パイプライン処理
oc apply -f source-pipeline.yaml
oc apply -f container-pipeline.yaml

oc get pod,ing
### 標準出力↓
NAME                                  READY   STATUS    RESTARTS   AGE
pod/el-listener-665ccf8947-lhljc      1/1     Running     3          52s
pod/pipeline-operator-1-build         0/1     Completed   0          6m29s
pod/spring-liberty-1-build            0/1     Completed   0          20m
pod/spring-liberty-84c44ffc5b-46p99   1/1     Running     0          12m
pod/spring-liberty-84c44ffc5b-46p99   1/1     Running     0          12m

NAME                                            HOST/PORT                                       PATH   SERVICES         PORT            TERMINATION   WILDCARD
route.route.openshift.io/el-listener            el-listener-spring-liberty.apps.ocp.cloud.vpc          el-listener      http-listener                 None
route.route.openshift.io/spring-liberty-mp6m5   spring-liberty.apps.ocp.cloud.vpc               /      spring-liberty   <all>                         None
```

トリガーバインディングは、トリガーテンプレートに渡すwebhookのパラメータを定義しています。

```yaml:binding.yaml
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerBinding
metadata:
  name: binding
spec:
  params:
  - name: gitrevision
    value: $(body.head_commit.id)
  - name: gitrepositoryurl
    value: $(body.repository.clone_url)
```

イベントリスナーは、GitBucketのリポジトリ名を判別してトリガーテンプレートを実行します。sourceリポジトリとcontainerリポジトリのディレクトリ名を取得するために、GitBucketリポジトリにプッシュされた1つ目のファイル名を『gitmodified』として取得しています(トリガーバインディングで定義できませんでした)。イベントリスナーのマニフェストを適用するとトリガーPodが起動されます。トリガーPodはwebhookを受信しパイプライン処理用Podを起動します。

```yaml:listener.yaml
apiVersion: triggers.tekton.dev/v1alpha1
kind: EventListener
metadata:
  name: listener
spec:
  serviceAccountName: pipeline
  triggers:
    - name: source
      interceptors:
        - cel:
            filter: "body.repository.name in ['source']"
            overlays:
              - key: gitmodified
                expression: body.head_commit.modified[0]
      bindings:
        - name: gitmodified
          value: $(extensions.gitmodified)
        - ref: binding
      template:
        ref: source-template
    - name: container
      interceptors:
        - cel:
            filter: "body.repository.name in ['container']"
            overlays:
              - key: gitmodified
                expression: body.head_commit.modified[0]
      bindings:
        - name: gitmodified
          value: $(extensions.gitmodified)
        - ref: binding
      template:
        ref: container-template
```

トリガーテンプレートはパイプラインを、パイプラインはタスクを実行します。いずれのパイプラインも単一のタスクで構成され、タスクはシェルスクリプトとして実装しています。

| パイプライン | タスク | ステップ | 実行コマンド |
| ---- | ---- | ---- | ---- |
| source-pipeline | build-source | java-build<br>git-push-war | gradle build<br>git clone / push |
| container-pipeline | build-container | container-build<br>git-push-manifest | oc new-build / start-build<br>git clone / push

```yaml:source-pipeline.yaml
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
  name: source-template
spec:
  params:
  - name: gitrevision
  - name: gitrepositoryurl
  - name: gitmodified
  resourcetemplates:
  - apiVersion: tekton.dev/v1beta1
    kind: PipelineRun
    metadata:
      generateName: source-pipeline-run-
    spec:
      pipelineRef:
        name: source-pipeline
      params:
      - name: gitmodified
        value: $(tt.params.gitmodified)
      resources:
      - name: git-source
        resourceSpec:
          type: git
          params:
          - name: revision
            value: $(tt.params.gitrevision)
          - name: url
            value: $(tt.params.gitrepositoryurl)
---
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: source-pipeline
spec:
  params:
  - name: gitmodified
  resources:
    - name: git-source
      type: git
  tasks:
    - name: build-source
      taskRef:
        kind: Task
        name: build-source
      params:
      - name: gitmodified
        value: $(params.gitmodified)
      resources:
        inputs:
        - name: git-source
          resource: git-source
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: build-source
spec:
  params:
  - name: gitmodified
  resources:
    inputs:
    - name: git-source
      type: git
  steps:
    - name: java-build
      image: image-registry.openshift-image-registry.svc:5000/spring-liberty/pipeline-operator:latest
      script: |
        #!/usr/bin/env bash
        set -x
        target=`echo $(params.gitmodified) | awk -F"/" '{print $1}'`
        cd git-source/${target}
        gradle build --no-daemon
    - name: git-push-war
      image: image-registry.openshift-image-registry.svc:5000/spring-liberty/pipeline-operator:latest
      script: |
        #!/usr/bin/env bash
        set -x
        target=`echo $(params.gitmodified) | awk -F"/" '{print $1}'`
        git clone http://gitbucket.gitbucket.svc.cluster.local/git/ocp/container.git
        cd container
        cp -p /workspace/git-source/${target}/build/libs/*.war ./${target}/
        git commit -a -m "update war file"
        git push -u origin master
```

```yaml:container-pipeline.yaml
apiVersion: triggers.tekton.dev/v1alpha1
kind: TriggerTemplate
metadata:
  name: container-template
spec:
  params:
  - name: gitrevision
  - name: gitrepositoryurl
  - name: gitmodified
  resourcetemplates:
  - apiVersion: tekton.dev/v1beta1
    kind: PipelineRun
    metadata:
      generateName: container-pipeline-run-
    spec:
      pipelineRef:
        name: container-pipeline
      params:
      - name: gitmodified
        value: $(tt.params.gitmodified)
      resources:
      - name: git-source
        resourceSpec:
          type: git
          params:
          - name: revision
            value: $(tt.params.gitrevision)
          - name: url
            value: $(tt.params.gitrepositoryurl)
---
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: container-pipeline
spec:
  params:
  - name: gitmodified
  resources:
    - name: git-source
      type: git
  tasks:
    - name: build-container
      taskRef:
        kind: Task
        name: build-container
      params:
      - name: gitmodified
        value: $(params.gitmodified)
      resources:
        inputs:
        - name: git-source
          resource: git-source
---
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
  name: build-container
spec:
  params:
  - name: gitmodified
  resources:
    inputs:
    - name: git-source
      type: git
  steps:
    - name: container-build
      image: image-registry.openshift-image-registry.svc:5000/spring-liberty/pipeline-operator:latest
      script: |
        #!/usr/bin/env bash
        set -x
        # 修正対象ディレクトリ名=BuildConfig名=ImageStream名
        target=`echo $(params.gitmodified) | awk -F"/" '{print $1}'`
        yyyymmddhhmm=`date +%Y%m%d%H%M`

        cd git-source
        oc -n spring-liberty delete bc ${target}
        oc -n spring-liberty new-build --name=${target} --strategy=docker --binary --to=${target}:${yyyymmddhhmm}
        oc -n spring-liberty start-build ${target} --from-dir=${target} --follow
        echo `oc -n spring-liberty get is ${target} | tail -1 | awk '{print $2}'`:${yyyymmddhhmm} > ../tag
    - name: git-push-manifest
      image: image-registry.openshift-image-registry.svc:5000/spring-liberty/pipeline-operator:latest
      script: |
        #!/usr/bin/env bash
        set -x
        target=`echo $(params.gitmodified) | awk -F"/" '{print $1}'`
        image=`cat tag`
        git clone http://gitbucket.gitbucket.svc.cluster.local/git/ocp/manifest.git

        cd manifest
        cp -p ${target}/spring-liberty.yaml .
        sed '/.*image.*/c\          image: '$image'' spring-liberty.yaml > ${target}/spring-liberty.yaml
        rm -f spring-liberty.yaml
        git commit -a -m "update image"
        git push -u origin master
```

※「container-build」ステップと「git-push-manifest」ステップ間でイメージストリームのタグの文字列(yyyymmddhhmm)をファイルで受け渡しています。タスク間でのファイル受け渡す場合はworkspacesを使う必要があります。

https://github.com/tektoncd/pipeline/blob/main/docs/workspaces.md

## 3.4. sourceリポジトリ更新を契機としたパイプライン処理の実行

sourceリポジトリを更新するとGitBucketはwebhookをトリガーPodに送信し以下が実行されます。
パイプラインは[こちら](https://qiita.com/y-akio/private/f7240d1f1736d6533899#31-%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E5%87%A6%E7%90%86%E7%94%A8%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90)で作成したpipeline-operatorが実行します。

■ パイプライン「source-pipeline」
(1) sourceリポジトリをクローン。
(2) ソースコードのコンパイルとwar作成を実行(java-buildステップ)。
(3) warファイルをcontainerリポジトリにプッシュ(git-push-warステップ)。

containerリポジトリ更新に伴い、GitBucketはwebhookをトリガーPodに送信し以下が実行されます。
パイプラインは[こちら](https://qiita.com/y-akio/private/f7240d1f1736d6533899#31-%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E5%87%A6%E7%90%86%E7%94%A8%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E4%BD%9C%E6%88%90)で作成したpipeline-operatorが実行します。

■ パイプライン「container-pipeline」
(1) containerリポジトリをクローン。
(2) コンテナイメージを作成(container-buildステップ)。
(3) manifestリポジトリのマニフェストのイメージタグを修正しプッシュ(git-push-manifestステップ)。

| パイプライン | タスク | ステップ | 実行コマンド |
| ---- | ---- | ---- | ---- |
| source-pipeline | build-source | java-build<br>git-push-war | gradle build<br>git clone / push |
| container-pipeline | build-container | container-build<br>git-push-manifest | oc new-build / start-build<br>git clone / push

※ 「spring-liberty.yaml」に差分がある場合は自動適用するようにArgo CDを設定しました。これによりsourceリポジトリへの変更が反映されたコンテナが自動的にデプロイされます。

パイプラインが実行されたことはOpenShiftコンソールで確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/4369293c-abf9-3589-8563-0e3b99e1a3c4.png)
パイプラインはPodとして実行されるため、コマンドラインで確認することもできます。

```bash:classicノード
oc get pod
### 標準出力↓
NAME                                                           READY   STATUS        RESTARTS   AGE
container-pipeline-run-4cq6k-build-container-2zh4t-pod-tv675   0/3     Completed   0          3m1s
el-listener-665ccf8947-lhljc                                   1/1     Running     0          9m41s
source-pipeline-run-tk5jw-build-source-sb6k2-pod-v8v5x         0/3     Completed   0          5m57s
spring-liberty-1-build                                         0/1     Completed   0          2m47s
spring-liberty-84c44ffc5b-46p99                                1/1     Running     0          3h50m
spring-liberty-84c44ffc5b-mb675                                1/1     Running     0          3h50m
```

パイプラインPodの各コンテナのログは下記の通りです。

```bash:souce-pipelineログ
oc logs source-pipeline-run-tk5jw-build-source-sb6k2-pod-v8v5x -c step-git-source-git-source-hfbpm
### 標準出力↓
{"level":"info","ts":1628424362.5006046,"caller":"git/git.go:169","msg":"Successfully cloned http://gitbucket.apps.ocp.cloud.vpc/git/ocp/source.git @ 27e474577c9ebadcf152d104758c77681ef45405 (grafted, HEAD) in path /workspace/git-source"}
{"level":"info","ts":1628424362.9797778,"caller":"git/git.go:207","msg":"Successfully initialized and updated submodules in path /workspace/git-source"}

oc logs source-pipeline-run-tk5jw-build-source-sb6k2-pod-v8v5x -c step-java-build
### 標準出力↓
++ echo spring-liberty/src/main/resources/templates/index.html
++ awk -F/ '{print $1}'
+ target=spring-liberty
+ cd git-source/spring-liberty
+ gradle build --no-daemon

Welcome to Gradle 7.1.1!

Here are the highlights of this release:
 - Faster incremental Java compilation
 - Easier source set configuration in the Kotlin DSL

For more details see https://docs.gradle.org/7.1.1/release-notes.html

To honour the JVM settings for this build a single-use Daemon process will be forked. See https://docs.gradle.org/7.1.1/userguide/gradle_daemon.html#sec:disabling_the_daemon.
Daemon will be stopped at the end of the build
> Task :compileJava
> Task :processResources
> Task :classes
> Task :bootWarMainClassName
> Task :bootWar
> Task :war
> Task :assemble
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
> Task :check
> Task :build

BUILD SUCCESSFUL in 13s
7 actionable tasks: 7 executed

oc logs source-pipeline-run-tk5jw-build-source-sb6k2-pod-v8v5x -c step-git-push-war
### 標準出力↓
++ echo spring-liberty/src/main/resources/templates/index.html
++ awk -F/ '{print $1}'
+ target=spring-liberty
+ git clone http://gitbucket.gitbucket.svc.cluster.local/git/ocp/container.git
Cloning into 'container'...
+ cd container
+ cp -p /workspace/git-source/spring-liberty/build/libs/spring-liberty-plain.war /workspace/git-source/spring-liberty/build/libs/spring-liberty.war ./spring-liberty/
+ git commit -a -m 'update war file'
[master 6fea6d6] update war file
 1 file changed, 0 insertions(+), 0 deletions(-)
+ git push -u origin master
remote: Updating references: 100% (1/1)
To http://gitbucket.gitbucket.svc.cluster.local/git/ocp/container.git
   2f2eced..6fea6d6  master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
```

```bash:container-pipelineログ
oc logs container-pipeline-run-4cq6k-build-container-2zh4t-pod-tv675 -c step-git-source-git-source-p6jlz
### 標準出力↓
{"level":"info","ts":1628424399.2615502,"caller":"git/git.go:169","msg":"Successfully cloned http://gitbucket.gitbucket.svc.cluster.local/git/ocp/container.git @ 6fea6d6c6038737bb2ea2506887f506822486011 (grafted, HEAD) in path /workspace/git-source"}
{"level":"info","ts":1628424399.3050532,"caller":"git/git.go:207","msg":"Successfully initialized and updated submodules in path /workspace/git-source"}

oc logs container-pipeline-run-4cq6k-build-container-2zh4t-pod-tv675 -c step-container-build
### 標準出力↓
++ echo spring-liberty/spring-liberty-plain.war
++ awk -F/ '{print $1}'
+ target=spring-liberty
++ date +%Y%m%d%H%M
+ yyyymmddhhmm=202108081206
+ cd git-source
+ oc -n spring-liberty delete bc spring-liberty
buildconfig.build.openshift.io "spring-liberty" deleted
+ oc -n spring-liberty new-build --name=spring-liberty --strategy=docker --binary --to=spring-liberty:202108081206
    * A Docker build using binary input will be created
      * The resulting image will be pushed to image stream tag "spring-liberty:202108081206"
      * A binary build was created, use 'oc start-build --from-dir' to trigger a new build

--> Creating resources with label build=spring-liberty ...
    imagestreamtag.image.openshift.io "spring-liberty:202108081206" created
    buildconfig.build.openshift.io "spring-liberty" created
--> Success
+ oc -n spring-liberty start-build spring-liberty --from-dir=spring-liberty --follow
Uploading directory "spring-liberty" as binary input for the build ...
.
Uploading finished
build.build.openshift.io/spring-liberty-1 started
Receiving source from STDIN as archive ...
Caching blobs under "/var/cache/blobs".

Pulling image adoptopenjdk/openjdk11:ubi ...
Getting image source signatures
Copying blob sha256:a50df8fd88fecefc26fd331f832672108deb08cf9d2b303a5b86156a7f51b5d8
Copying blob sha256:85467c3c6735e1bdeba615c5811c3bfa4b7e42f387093ba9c929902a923f9ef7
Copying blob sha256:f87862706a80347fea48f6831e2d1716630d49ec2a800a35488870774e6e3924
Copying blob sha256:1cadda38f72dece653de82063e3c8e910265fe7a342ec2fb73ad8e540c47e1f7
Copying config sha256:6ab8439568645d0182c301fb0fec38766809eac20ddba195c053d97eb77fb2aa
Writing manifest to image destination
Storing signatures
Adding transient rw bind mount for /run/secrets/rhsm
STEP 1: FROM adoptopenjdk/openjdk11:ubi
STEP 2: ENV PATH=/opt/ol/wlp/bin:$PATH     LOG_DIR=/logs
--> 35fa6734584
STEP 3: RUN mkdir /opt/ol /logs
--> 280ade3aa67
STEP 4: COPY openliberty-webProfile8-21.0.0.7.zip /opt/ol/
--> 3dcef6b1390
STEP 5: WORKDIR /opt/ol
--> 587003315d9
STEP 6: RUN /opt/java/openjdk/bin/jar -xf openliberty-webProfile8-21.0.0.7.zip     && rm -f openliberty-webProfile8-21.0.0.7.zip     && chmod +x /opt/ol/wlp/bin/server     && /opt/ol/wlp/bin/server cr
eate     && ln -s /opt/ol/wlp/usr/servers/defaultServer /config

Server defaultServer created.
--> 7cbc69b3c97
STEP 7: COPY server.xml /config/
--> 7d5221ee65f
STEP 8: COPY spring-liberty-plain.war /config/apps/
--> a87032b7b63
STEP 9: RUN chmod -R g+rw /opt/ol/wlp  && chmod -R g+rw /logs
--> ccc05ca86b9
STEP 10: EXPOSE 9080
--> fdc0f5b0ccb
STEP 11: CMD ["/opt/ol/wlp/bin/server","run","defaultServer"]
--> bbc3f1f2add
STEP 12: ENV "OPENSHIFT_BUILD_NAME"="spring-liberty-1" "OPENSHIFT_BUILD_NAMESPACE"="spring-liberty"
--> a952995f435
STEP 13: LABEL "io.openshift.build.name"="spring-liberty-1" "io.openshift.build.namespace"="spring-liberty"
STEP 14: COMMIT temp.builder.openshift.io/spring-liberty/spring-liberty-1:008481a8
--> 97cccbf39b7
97cccbf39b75b66e912963ce70bc33c3750c9ff0ba8833be8b6506859bdb0643

Pushing image image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty:202108081206 ...
Getting image source signatures
Copying blob sha256:6ff9fc7fbdf59d5c6697ebe16d89c43e9edeefd77761be6794d328b6e71384a1
Copying blob sha256:1672952b6d300fc188b0668944ae42fdbb0af36c61f71e58f16b6f91087da702
Copying blob sha256:85467c3c6735e1bdeba615c5811c3bfa4b7e42f387093ba9c929902a923f9ef7
Copying blob sha256:f87862706a80347fea48f6831e2d1716630d49ec2a800a35488870774e6e3924
Copying blob sha256:1cadda38f72dece653de82063e3c8e910265fe7a342ec2fb73ad8e540c47e1f7
Copying blob sha256:afb6362017366010f205e31757be2056dbcb226ed2cbba684420d16bbd44879a
Copying blob sha256:72bf331dd109ae0916c4323808ece74e3bb70352cd0335779b51bda458e8d4dd
Copying blob sha256:a50df8fd88fecefc26fd331f832672108deb08cf9d2b303a5b86156a7f51b5d8
Copying blob sha256:a669218bc263dcdc2db78074a354f7c66d61f8c14882228596996055538c0e89
Copying blob sha256:a70f15e7dd8ab97adbc7b1af812b4d6a21cc5b6c3aad35e97e8c3e70bba2e832
Copying config sha256:97cccbf39b75b66e912963ce70bc33c3750c9ff0ba8833be8b6506859bdb0643
Writing manifest to image destination
Storing signatures
Successfully pushed image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty@sha256:102c010db289c0ffc76cc182cf66bd394a3b6198113a0ac54e63ab63a23e319c
Push successful
++ oc -n spring-liberty get is spring-liberty
++ tail -1
++ awk '{print $2}'
+ echo image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty:202108081206

oc logs container-pipeline-run-4cq6k-build-container-2zh4t-pod-tv675 -c step-git-push-manifest
### 標準出力↓
++ echo spring-liberty/spring-liberty-plain.war
++ awk -F/ '{print $1}'
+ target=spring-liberty
++ cat tag
+ image=image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty:202108081206
+ git clone http://gitbucket.gitbucket.svc.cluster.local/git/ocp/manifest.git
Cloning into 'manifest'...
+ cd manifest
+ cp -p spring-liberty/spring-liberty.yaml .
+ sed '/.*image.*/c\          image: image-registry.openshift-image-registry.svc:5000/spring-liberty/spring-liberty:202108081206' spring-liberty.yaml
+ rm -f spring-liberty.yaml
+ git commit -a -m 'update image'
[master 54dc17a] update image
 1 file changed, 1 insertion(+), 1 deletion(-)
+ git push -u origin master
remote: Updating references: 100% (1/1)
To http://gitbucket.gitbucket.svc.cluster.local/git/ocp/manifest.git
   154ac78..54dc17a  master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
```

# 4. GitOpsオペレーターによるコンテナの自動更新
パイプライン『source-pipeline』とパイプライン『container-pipeline』が実行され、最終的にGitBucketのmanifestリポジトリの「spring-liberty.yaml」のイメージタグ部分が更新されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/5669c31e-5bfc-b800-2248-c251d222e227.png)

OpenShift GitOpsオペレーターはマニフェスト更新を検知し、spring-libertyコンテナをデプロイしました(Pod名がspring-liberty-84c44ffc5b～からspring-liberty-75cd7848b6～に更新)。以下はArgo CD管理画面のスクリーンショットです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/d6d56843-2358-7d79-b5b7-060436d7d042.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1413476/9609cf74-256a-ad13-9da1-f85bb3e23117.png)

# おわりに
OpenShift 4.8のOpenShift GitOpsオペレーターの「Argo CD」機能、OpenShift Pipelinesオペレーターの「トリガー」機能と「パイプライン」機能を使用して、souceリポジトリの更新を契機にコンテナが自動的にデプロイされる環境を構築しました。

Openshift Pipelinesのカスタムリソースのパラメータ定義は冗長に感じますが、タスク自体はシェルスクリプトで実装できるため、比較的扱い易い印象を受けました。それでもマニフェストのイメージタグ部分を更新するのは煩雑なので、OpenShift GitOpsオペレーターにイメージ更新を検知する「Argo CD Image Updater」が取り込まれることを期待しています。

spring-libertyコンテナをOpenShift独自のDeploymentConfigで作成すればで対応できますが、「oc new-app」コマンドで作成されるアプリケーションのデフォルトがDeploymentConfigからDeploymentに変更されていることもあり、今回は後者を採用しました。以下は「oc new-app -h」の抜粋です。

> Options:
  --as-deployment-config=false: 
  If true create this application as a deployment config, which allows for hooks and custom strategies.