※OpenShift Advent Calendar 2024の12日目の記事です。
タイトルの元ネタ
緊急SOS!池の水ぜんぶ抜く大作戦
この記事の目的
OpenShift Pipelinesを利用して、以下のようなパイプラインをゼロからGUIで作成します。必要な手順を全て解説していますので、初心者の方でも「なんかそれっぽいパイプライン」を作れるようになります。
また、OpenShift上で「継続的デリバリー」を実現する為のツール全般についても丁寧に解説しています。
それではやっていきましょう〜!
はじめに
Kubernetes/OpenShiftを「使いこなす」上で避けては通れないのが「継続的デリバリー(Continuous Delivery)」の実現です。「入門 継続的デリバリー(オライリー)」によれば、継続的デリバリーで実施していることは以下2つだそうです。
- いつでも安全にソフトウェアの変更を提供することができる
- ボタンを1つ押すぐらい簡単に、そのソフトウェアをデリバリーできる
また、CDF(Continuous Delivery Foundation)による定義は以下のとおりです。
- いつでも変更がリリース可能であることを立証している
- リリースプロセスを自動化している
なお、よく「継続的デリバリー」は継続的インテグレーション(Continuous Integration)とセットで「CICD」と言われたりもしますが、上述の「入門 継続的デリバリー」によると、「なにか厳密に違うとか同じとかはなく、まぁ意図するところとしては同じです」という感じらしい。
これをOpenShiftの上で実現しようとした場合、以下の2つのツールを使うことが有効でしょう。
OpenShift GitOps
- OSSのArgoCDをOpenShiftに組み込んで提供するもの
- Gitリポジトリに格納したManifestファイル(Single Source of Truth)を自動的にKubernetesクラスタに適用してくれる
- GitOpsを実現するための要
OpenShift Pipelines
- OSSのパイプラインマネジメントツールである「Tekton」をRed Hatによるエンタープライズサポート付き機能として取り込んだもの
- オリジナルのTektonが用意する豊富なTaskテンプレートを活用し、ローコードでパイプライン作成や編集が可能
- オリジナルのTektonにGUI(Graphical User Interface)が具備され、圧倒的に直感的な操作が可能
どちらのツールもOpenShiftの「OperatorHub」からインストールできます。
OpenShift初心者にとっての壁
それはなんといってもパイプライン作成ではないでしょうか。パイプラインとは、アプリケーションをコーディングしてからコンテナ化(Build)してOpenShiftにDeployするまでの一連の必要な作業(Task)をコードとして定義し、それらの順序やデータの受け渡し、各種パラメータを定義したものです。つまり、「継続的デリバリー」を構成する非常に重要な要素です。コンテナアプリケーションを本格的に活用し、アプリケーション開発の生産性を向上するためには、高度に自動化された再現性のあるプロセスを実現することが重要です。そのためにパイプラインが必要になるのです。
パイプラインの必要性に気づくプロセス
OpenShiftでのコンテナアプリケーション開発運用に足を踏み入れた多くの方々は、概ね以下のような順でパイプラインの必要性に気づくと思われます。
- 君は「コンテナ仮想化」という仮想化技術について知り、その有用性に期待を持つ
- 次にDockerfileが書けるようになり、コンテナイメージのBuildができるようになり、イメージレジストリにPushしたり、イメージを指定してコンテナアプリケーションをDeployできるようになった
- 君は「どうやら本格的に『イミュータブルインフラストラクチャ』とやらを実現するには、Manifestファイルを書かないといけないらしい」という事に気づき、YAMLを書くようになった
- さらにManifestをそのままOpenShiftクラスタに適用(Apply)するのではなく、GitリポジトリにSingle Source of Truthとして管理し、ArgoCDを介してGitOpsすることを覚えた
- 最終的に君はソースコードを編集し、それを契機にソースの静的解析やコンテナイメージのBuild、レジストリへのPushやイメージスキャンを実行し、OKとなったコンテナイメージをもってManifestをApplyしたいと思うようになり、「継続的デリバリー」を実現するパイプラインの作成に関心をもった
- そこで君は、自分のOpenShiftクラスタにOpenShift Pipelines Operatorのインストールし、コンソール左側メニューに「Pipelines」というメニューが増えていることに気づいた。
君は期待に胸を踊らせて左側の「Pipelines」をクリックしてみた
そこにはなにもなかった。不思議に思った君はすかさず「Create」をクリックしてみた。
「そうか、これからパイプラインを作るんだ。さぁ、やるぞ〜」
いきなりこの画面に行き、君は頭が真っ白になり、そしてこう言った。
画像:児童相談所の保護訓練で虐待親になりきる警察が「役に入りすぎ」と話題に
テーブルトークRPG風のテキストは一旦ここまでにしておいて...
先に、本記事を通して作成する簡単なTektonパイプラインを示します。
このパイプラインは非常に基本的なものですが、それでも立派なパイプラインです。パイプラインは、左から右へ各Taskを実行していきます。各Taskでやっていることを先に説明します。
- Gitlab上でソースコードを編集するとパイプラインに対してPush通知が飛ぶ
- それを受けてGitlab上のソースコードを
git clone
してくる - OpenShift上にDeploy済みの「Sonarqube」でソースコードの静的解析を行う
- コンテナイメージをBuildし、内部レジストリにPushする
- 内部レジストリ内のイメージを「Trivy」でスキャンする
- スキャン結果が問題なければ、「Skopeo」を使って内部レジストリから外部レジストリ(DockerHub)にイメージをコピーする
-
oc rollout
コマンドで、指定したDeploymentのロールアウトを実行し、最新のコンテナイメージを反映してアプリケーションをアップデートする
基本的なパイプラインではありますが、最低限必要なセキュリティを確保しながら、「自動化されたパイプライン感」があるものを作れます。こいつを全てGUIで作成することを目指しましょう。
Tektonパイプラインについて
作成を開始する前に、改めてTektonパイプラインの仕組み・概念を説明します。
Tektonパイプラインを構成する主な要素は以下の3つです。
- Pipeline
- Task
- Workspace
それぞれ見ていきます。
Pipeline
Pipelineとは、後述する「Task」の順序性や「Workspace」との紐づけを行い、「このTaskをやってOKだったら次はこのTaskを実行して...」を管理する存在です。また、各Taskに適用するパラメータ(変数)を定義します。
これらの要素を図解すると以下のようになります。
Task
Taskは、文字通り何らかの作業です。先程示したパイプラインの例で言えば、git clone
してくるTaskや、コンテナイメージスキャンを実行するTask、ArgoCDをSyncするTaskなど。これらのTaskは実はコンテナイメージから起動するPodです。「〇〇する」Taskもひとつのコンテナアプリケーションとして動きます。そしてそれはKubernetes上ではPodとして起動・管理されます。Taskを実行するタイミングでPodが起動し、Taskが完了すればPodは削除されます。これの連なりがパイプラインです。
Taskは、特定のNameSpace内のみで利用できるものと、クラスタを横断して利用できる「Cluster Task(CT)」の2種類が存在します。Tektonで利用できるTaskのテンプレートはTekton Hubに多く掲載されており、すべてのTaskをゼロから作る必要はありません。だいたいのパイプラインはテンプレートの組み合わせだけでなんとかなります。
Workspace
Podとして起動・実行される各Taskは、Task終了時にPodが消えてしまいますので、その間に生成されたデータも消えてしまいます。「①ソースコードをダウンロード(git clone
)してくるTask」の後に、「②ソースコードを静的解析するTask」を実行したい場合、Workspaceが無いと①のTaskが完了すると、ダウンロードしてきたソースコードが消えてしまいます。そうすると次の②のTask(Sonarqubeによるソースコード静的解析)が実施できません。それは困ります。
そこで、パイプライン実行中に各Task間でやりとりされるデータを保持する場(Workspace)を提供する永続ボリュームが必要になります。つまり、パイプラインには基本的にひとつ以上のPV(PersistentVolume)が要求されます。それを要求するPVC(PersistentVolumeClaim)も必要になります。
パイプラインが持つ特徴
まず、各Task自体は必ず「変数」を持ちます。そしてパイプラインは「汎用性」をもたせることを前提とします。ここでいう汎用性とは、「基本的に固有のアプリケーションにしか使えないようなハードコーディングはしない」ことを意図しています。例えば、今回作るパイプラインの最初のTaskでgit clone
してくるTaskがありますが、このTaskはいくつかの変数をもっています。
例えばgit clone
元のURLや、ブランチ名などです。今回利用するソースコードが置いてあるリポジトリのURLは「 https://gitlab.com/masaki-oomura/simple-stateful-app 」ですが、このURLをそのままTaskにハードコーディングしてしまった場合、このアプリケーションのみをgit clone
してくる用途にしか使えません。
一方でパイプラインを利用する大前提となる変数については、「定数」としてTaskに値をハードコーディングすることもあるでしょう。例えば、mainブランチにあるソースコードしか使わないといった場合、git clone
Taskのブランチを司る変数には直接main
と埋め込んでしまっても良いはずです。
また、各Taskの変数はPipelineが持つパラメータを参照させます。つまり、パイプラインを実行する際に引数として与えられたパラメータが、各Taskの変数に代入されて実際に実行されます。
TaskRunとPipelineRun
あくまでTaskやPipelineは抽象化されたリソースです。これらが実際に実行される際には対応するインスタンスが起動してきます。それらはそのまま、TaskRunやPipelineRunと言います。
同じPipelineに異なるパラメータを入れて3回実行した場合は、PipelineRunは3つ立ち上がります。その中で各TaskはTaskRunとして起動し、そのTaskRunの実体はPodです。
ちなみに、本記事を通して作成するパイプライン
を構成しているYAMLファイルは以下の通りです。Tektonパイプライン自体もOpenShiftの上で動くものである以上、YAML形式のコードとして定義することができます。
※長いですが、一旦無心でスクロールしてください。
apiVersion: tekton.dev/v1
kind: Pipeline
name: simple-stateful-app-pipeline
namespace: simple-stateful-app
spec:
finally:
- name: openshift-client
params:
- name: SCRIPT
value: oc rollout restart deployment $(params.DEPLOYMENT_NAME) -n $(params.NAMESPACE_NAME)
- name: VERSION
value: latest
taskRef:
kind: ClusterTask
name: openshift-client
workspaces:
- name: manifest-dir
workspace: workspace
params:
- default: 'https://gitlab.com/<your-gitlab-username>/simple-stateful-app.git'
description: ソースコードを取ってくる先のGitリポジトリのURL
name: GIT_URL
type: string
- default: ./source/Dockerfile
description: イメージをBuildする為のDockerfileのありかを示すパス
name: DOCKERFILE_PATH
type: string
- default: ./source
description: イメージをBuildする対象のアプリケーションのソースコードのありかを示すパス
name: SOURCE_PATH
type: string
- default: 'image-registry.openshift-image-registry.svc:5000/simple-stateful-app/simple-stateful-app'
description: コピー元の内部レジストリのURL
name: INTERNAL_REGISTRY_URL
type: string
- default: docker.io/<your-dockerhub-username>/simple-stateful-app
description: コピー先の外部レジストリのURL
name: EXTERNAL_REGISTRY_URL
type: string
- default: node-app-deployment
description: ロールアウト対象のDeployment名を指定します
name: DEPLOYMENT_NAME
type: string
- default: simple-stateful-app
description: Deploymentの存在するNameSpaceを指定します
name: NAMESPACE_NAME
type: string
- default: 'http://docker-sonarqube-git.sonarqube.svc.cluster.local:9000'
description: Serviceから提供されるSonarqubeの内部ホスト名
name: SONAR_HOST_URL
type: string
- default: <your-projectkey>
description: SonarqubeのCI統合設定で払い出された`projectKey`
name: SONAR_PROJECT_KEY
type: string
- default: <your-sonar-token>
description: SonarqubeのCI統合設定で払い出された`SONAR_TOKEN`
name: SONAR_TOKEN
type: string
tasks:
- name: git-clone
params:
- name: url
value: $(params.GIT_URL)
- name: revision
value: main
- name: refspec
value: ''
- name: submodules
value: 'true'
- name: depth
value: '1'
- name: sslVerify
value: 'true'
- name: crtFileName
value: ca-bundle.crt
- name: subdirectory
value: ''
- name: sparseCheckoutDirectories
value: ''
- name: deleteExisting
value: 'true'
- name: httpProxy
value: ''
- name: httpsProxy
value: ''
- name: noProxy
value: ''
- name: verbose
value: 'true'
- name: gitInitImage
value: 'registry.redhat.io/openshift-pipelines/pipelines-git-init-rhel8@sha256:dd5c8d08d52e304a542921634ebe6b5ff3d63c5f68f6d644e88417859b173ec8'
- name: userHome
value: /home/git
taskRef:
kind: ClusterTask
name: git-clone
workspaces:
- name: output
workspace: workspace
- name: buildah
params:
- name: IMAGE
value: $(params.INTERNAL_REGISTRY_URL)
- name: BUILDER_IMAGE
value: 'registry.redhat.io/rhel8/buildah@sha256:5c7cd7c9a3d49e8905fc98693f6da605aeafae36bde5622dc78e12f31db3cd59'
- name: STORAGE_DRIVER
value: vfs
- name: DOCKERFILE
value: $(params.DOCKERFILE_PATH)
- name: CONTEXT
value: $(params.SOURCE_PATH)
- name: TLSVERIFY
value: 'true'
- name: FORMAT
value: oci
- name: BUILD_EXTRA_ARGS
value: ''
- name: PUSH_EXTRA_ARGS
value: ''
- name: SKIP_PUSH
value: 'false'
runAfter:
- sonarqube-scanner
taskRef:
kind: ClusterTask
name: buildah
workspaces:
- name: source
workspace: workspace
- name: skopeo-copy
params:
- name: srcImageURL
value: 'docker://$(params.INTERNAL_REGISTRY_URL)'
- name: destImageURL
value: 'docker://$(params.EXTERNAL_REGISTRY_URL)'
- name: srcTLSverify
value: 'true'
- name: destTLSverify
value: 'true'
runAfter:
- trivy-scanner
taskRef:
kind: ClusterTask
name: skopeo-copy
workspaces:
- name: images-url
workspace: workspace
- name: trivy-scanner
params:
- name: ARGS
value:
- image
- '--severity HIGH,CRITICAL'
- name: TRIVY_IMAGE
value: 'docker.io/aquasec/trivy@sha256:944a044451791617cc0ed2ee4d1942a4f66b790d527fcd0575a6b399ccbc05a1'
- name: IMAGE_PATH
value: $(params.INTERNAL_REGISTRY_URL)
- name: AIR_GAPPED_ENABLED
value: 'false'
runAfter:
- buildah
taskRef:
kind: Task
name: trivy-scanner
workspaces:
- name: manifest-dir
workspace: workspace
- name: sonarqube-scanner
params:
- name: SONAR_HOST_URL
value: $(params.SONAR_HOST_URL)
- name: SONAR_PROJECT_KEY
value: $(params.SONAR_PROJECT_KEY)
- name: SONAR_TOKEN
value: $(params.SONAR_TOKEN)
runAfter:
- git-clone
taskRef:
kind: Task
name: sonarqube-scanner
workspaces:
- name: source
workspace: workspace
- name: sonar-settings
workspace: workspace
workspaces:
- name: workspace
OpenShift初心者の方、これから初めてパイプライン作成を学ぶ方にいきなり「YAMLファイルを見て勉強しろ」と言うのは酷です。このパイプラインを全てGUIで作っちゃおうというのが本記事の趣旨です。一度GUIで作られたパイプラインはOpenShiftの中でYAMLファイル化されますので、それを元に少しずつ学んでいけるはずです。
さて、そんなパイプラインの作成を始める前に、事前準備をしていきます。
事前準備
OpenShiftクラスタの準備
もし気軽に触れるOpenShiftクラスタがない場合は、無料のRed Hatアカウントさえ作れば、ROSAがすぐに試せる「Red Hat OpenShift Service on AWS Hands-on Experience」を利用しましょう。
「Create a ROSA cluster」をクリックすれば、ROSAが起動します。Cluster-admin権限をもったアカウントがもらえるので、自由にOperatorもインストール可能です。
「Red Hat OpenShift Service on AWS Hands-on Experience」は8時間×3回まで利用できます。詳しい利用開始までの流れは、日本語ガイドを参照ください。
2つのOperatorをインストールする
日本語ガイドからのリンク先である日本語の説明書を参考にして、「OpenShift GitOps Operator」と「OpenShift Pipelines Operator」をインストールしておきます。以下リンクから直接それぞれのOperatorのインストール方法を見ることができます。
必要なファイルとコンテナイメージレジストリの用意
OpenShiftクラスタが用意できたら、今回DeployするサンプルアプリのソースコードとManifest一式を用意します。また、パイプラインの中でBuildしたイメージをPushする先の外部イメージレジストリを用意しましょう。
リポジトリのフォーク
まずはご自身のGitリポジトリに「 https://gitlab.com/masaki-oomura/simple-stateful-app.git 」をフォークしておいてください。プロジェクト名は「simple-stateful-app」、リポジトリは「公開(パブリック)」にしておきましょう。
以後はフォーク後のリポジトリを使っていきます。今、ご自身のリポジトリのURLはhttps://gitlab.com/<your-gitlab-username>/simple-stateful-app.git
となっているはずです。
Docker Hubに自身のイメージレジストリを作成
DockerHubは無料でコンテナレジストリを作成することができるサービスです。自身のアカウントを作成、ログイン後に「Create repository」をクリックします。
NameSpaceは自身のそれ、リポジトリ名はとりあえず「simple-stateful-app」としておきます。レジストリも「公開(パブリック)」にしておきます。
Docker commands欄にコンテナレジストリへのアクセスコマンドが表示されます。
つまり、<your-docker-username>/simple-stateful-app
がコンテナイメージのPush先ということです。
サンプルアプリのManifestをArgoCD経由でApplyする
では、サンプルアプリを先にOpenShiftにDeployします。今回はフォークしてきたリポジトリにあるManifestをSingle Source of Truthとし、ArgoCDによるGitOpsを実施します。
サンプルアプリについて
サンプルアプリ「simple-stateful-app」は、バックエンドがMysql、フロントエンドがNode.jsアプリケーションとなっている、非常に簡単なステートフルアプリケーションです。「Enter Text」欄に文字列を入力し「Add Text」をクリックすると、Mysqlのテーブルに文字列が保存されます。
データベースに登録された文字列を検索したい場合は「Search Text」欄に検索ワードを入力し「Search」をクリックします。
部分一致で検索結果が表示されます。このアプリケーションは、フロントエンドがPodが3つで起動し、アクセスのたびにラウンドロビンして負荷分散しています。現在アクセスしているPodの名称が「Host Name」欄に表示されます。
なお、MysqlのPodはひとつだけです。MysqlのデータはPersistent Volumeによって永続化されています。
さて、まずはこのアプリをご自身のOpenShiftクラスタにDeployしますが、コンテナイメージをBuild&Pushし、さらにソースコードを一部編集します。
コンテナイメージをBuildする
まずは、先ほどフォークしてきたリポジトリをローカルPCにPullしてきて、コンテナイメージをBuildします。アプリケーションのソースコード及びDockerfileの格納場所はsimple-stateful-app/source
配下です。
値 | 説明 |
---|---|
<your-gitlab-username> |
ご自身のGitlabのユーザ名に置き換えてください |
<your-docker-hub-username> |
ご自身のDockerHubのユーザ名に置き換えてください |
~ % git clone https://gitlab.com/<your-gitlab-username>/simple-stateful-app.git
~ % cd simple-stateful-app
simple-stateful-app % docker build -t <your-dockerhub-username>/simple-stateful-app:latest ./source
(中略)
Successfully tagged <your-dockerhub-username>/simple-stateful-app:latest
次に、今BuildしたイメージをPushしたいと思いますが、ターミナルからDockerHubにログインする必要があります。そのためには、「Personal access token」を作成する必要があります。https://app.docker.com/settings/personal-access-tokensにアクセスし、
「Generate new token」をクリックしたら、
Read, Write, Delete権限を持つToken(今回はlogin token
としました)を作成しておきます。
ログインコマンドが表示されますので、これを使ってターミナルからログインしてください。
なお、この「Personal access token」は後ほども使いますので、忘れないようにどこかにコピーしておいてください。
simple-stateful-app % docker login <your-dockerhub-username>
Password: <Personal access token>
Login Succeeded!
これでDockerHubにイメージをPushできるようになりましたので、早速Pushします。
simple-stateful-app % docker push <your-dockerhub-username>/simple-stateful-app:latest
(中略)
Writing manifest to image destination
DockerHubの画面を見ると、イメージがPushされた事を確認できます。
次は、Manifestを編集します。
Manifestを編集
ソースコードを一部修正する必要があります。先ほどフォークしてきたリポジトリの中で修正する対象ファイルは4つです。
root/
├ app-of-apps/
| └ app-of-apps.yaml ←修正対象
├ applications/
| ├ mysql-app.yaml ←修正対象
| ├ namespace-app.yaml ←修正対象
| └ nodejs-app.yaml ←修正対象
| manifest/
| ├ namespace/
| | └ namespace.yaml
| ├ mysql/
| | ├ configmap.yaml
| | ├ deployment.yaml ←修正対象
| | ├ pvc.yaml
| | ├ secret.yaml
| | └ service.yaml
| └ nodejs/
| ├ configmap.yaml
| ├ deployment.yaml
| ├ route.yaml
| └ service.yaml
|
(以下略)
それぞれ、自身のGitリポジトリのURL及び自身のDockerHubの情報に書き換えます。ターミナルで以下コマンドを実行することで、対象のファイルを自身のユーザ情報に書き換えることができます。
値 | 説明 |
---|---|
<your-gitlab-username> |
ご自身のGitlabのユーザ名に置き換えてください |
<your-docker-hub-username> |
ご自身のDockerHubのユーザ名に置き換えてください |
# 環境変数の設定。ご自身のユーザ名に置き換えて実行してください。
simple-stateful-app % export YOUR_GITLAB_USERNAME=<your-gitlab-username>
simple-stateful-app % export YOUR_DOCKERHUB_USERNAME=<your-dockerhub-username>
# 対象ファイルに対する値の置換
simple-stateful-app % sed -i '' "s/<your-gitlab-username>/${YOUR_GITLAB_USERNAME}/g" app-of-apps/app-of-apps.yaml
simple-stateful-app % sed -i '' "s/<your-gitlab-username>/${YOUR_GITLAB_USERNAME}/g" apps/mysql-app.yaml
simple-stateful-app % sed -i '' "s/<your-gitlab-username>/${YOUR_GITLAB_USERNAME}/g" apps/namespace-app.yaml
simple-stateful-app % sed -i '' "s/<your-gitlab-username>/${YOUR_GITLAB_USERNAME}/g" apps/nodejs-app.yaml
simple-stateful-app % sed -i '' "s/<your-dockerhub-username>/${YOUR_DOCKERHUB_USERNAME}/g" manifest/nodejs/deployment.yaml
これでご自身のGitlab(リポジトリ)及びDockerHub(イメージレジストリ)の情報に書き換えが完了しましたので、それを自身のGitlabに再度Pushしておきます。
simple-stateful-app % git add .
simple-stateful-app % git commit -m "overwrote to my user info."
simple-stateful-app % git push
これでManifestファイルの準備が完了しました。では、このアプリケーションをApplyしましょう。
ArgoCDプロジェクト・アプリケーションをApply
今回のサンプルアプリはGitopsのApp of AppsパターンでDeployします。
ご安心ください。簡単にイラストで説明します。
今から手作業でApplyするYAMLファイルは2つです。1つめはproject.yaml
です。
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: simple-stateful-app
namespace: openshift-gitops
spec:
clusterResourceWhitelist:
- group: '*'
kind: '*'
destinations:
- namespace: '*'
server: '*'
sourceRepos:
- '*'
ArgoCDにはArgoCD側独自の論理空間「プロジェクト」を有しています。これがOpenShiftの「プロジェクト」と同名なので非常にややこしいので注意してください。ArgoCDアプリケーションもArgoCDプロジェクトもGitOps Operatorをインストールすると使えるAPIを使って作成するカスタムリソースです。
2つめはapp-of-apps.yaml
です。
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: simple-stateful-app
namespace: openshift-gitops
spec:
destination:
namespace: openshift-gitops
server: 'https://kubernetes.default.svc'
source:
path: apps
repoURL: >-
https://gitlab.com/<your-gitlab-username>/simple-stateful-app.git
#自身のGitlabリポジトリ名に変更します
targetRevision: main
project: simple-stateful-app
syncPolicy:
automated:
prune: true
selfHeal: true
OpenShift/KubernetesにアプリケーションをDeployするためには、ManifestファイルをApplyする必要がありますが、それらのManifestをひとつひとつApplyするのではなく、
「Gitリポジトリにに一式入れておいたんで、ArgoCDくんまとめてApplyしておいてね〜。なんならGit上で変更があったら勝手に反映よろしくニキ〜」
とするのがGitOpsですが、この 「よろしくニキ〜」 自体もYAMLファイルとして表す事ができるのです。これが「ArgoCDアプリケーション」というManifestファイルです。今回、app-of-apps.yaml
をApplyすると、更に別のArgoCDアプリケーション(YAMLファイル)をApplyさせます。具体的には
namespace-app.yaml
mysql-app.yaml
nodejs-app.yaml
です。
これら3つのArgoCDアプリケーションが、最終的にNameSpace、Mysql、Node.jsアプリケーションのYAMLファイルをそれぞれApplyしていくわけです。この様に、複数のコンポーネントが連携して構成されるアプリケーションについて、各コンポーネント毎のArgoCDアプリケーション(子アプリケーション)を定義し、それらを束ねるArgoCDアプリケーション(親アプリケーション)のYAMLファイルによって管理するパターンを「App of appsパターン」と言います。
それでは、OpenShiftクラスタに「ArgoCDプロジェクト」と「ArgoCDアプリケーション」をApplyします。OpenShiftにログイン済みのターミナルで以下コマンドを実行してください
simple-stateful-app % oc apply -f project.yaml
simple-stateful-app % oc apply -f app-of-apps/app-of-apps.yaml
OpenShift Gitopsのコンソールにログインしてみます。
OpenShiftのコンソール画面の右上の■が9こ並んだボタンを押すと、「OpenShift GitOps」のログイン画面に遷移できます。ログインするとアプリケーションがDeployされている状況を確認できます。しばらく待つと全てのコンポーネントがSyncされます。
OpenShiftのトポロジー画面でもNode.jsのPodとMysqlのPodが確認できました。
Routeからアプリケーションにアクセスして遊んでみてください。
これでArgoCDを介したアプリケーションのDeployが完了しました。ここからいよいよ本題のパイプライン作成に入ります。(やっと本編)
プロジェクト「simple-stateful-app」内でパイプラインを作成する
「開発者向け表示」の左端メニュー「Pipelines」をクリックします。まだ何もないので「Create」からパイプライン作成をスタートしましょう。
パイプラインの名前はお好きなものにしておきます。
3つのTaskを追加
いきなり完成形のパイプラインを作る前に、まずは「ソースコードを取ってきてイメージをBuildしDocker HubにPushさせる」パイプラインの作成を目指します。その為には3つのTaskが必要です。
ひとつめはソースコードをgit-clone
してくるTaskです。「Taskの追加」をクリックすると、Task検索窓が出てきます。
git-clone
と打つと、関連Taskが出てきます。ここではRed Hatが提供するTaskを選択します。このTask「git-clone」は、最初からOpenShiftクラスタ内のすべてのNameSpaceで利用できるTask「Cluster Task」です。「追加」をクリックしましょう。
Taskが追加されました。
こんな要領でTaskの追加ができます。次にイメージをBuildするTask「Buildah」を追加します。今追加したTask「git-clone」の横にマウスオーバーすると青い+ボタンが出てきます。
ここから後続Taskを追加します。
「Taskの追加」をクリックすると、再び検索窓が出ました。
「buildah」と入れると関連するTaskが複数出てきますが、先程と同様にRed Hatが提供するCluster Task(CT)を選択、追加します。
できました。
全く同じ流れで、次はレジストリ間でイメージをコピーするTask「skopeo-copy」を追加します。
できました。
パラメータを設定する
Taskを追加しても、PipeRunやTaskRunを実行するためのパラメータが設定されていないと動きません。
さて、この3つのTaskのパイプラインの連なり「ソースコードを取ってきてイメージをBuildしDocker HubにPushさせる」に必要なパラメータとは何でしょうか??
それは最低限以下の5つです。
-
git-clone
- ソースコードを取ってくる先のGitリポジトリのURL
-
buildah
- イメージをBuildする為のDockerfileのありかを示すパス
- イメージをBuildする対象のアプリケーションのソースコードのありかを示すパス
-
skopeo-copy
- コピー元の内部レジストリのURL
- コピー先の外部レジストリのURL
例えば、Task「git-clone」をクリックして設定メニューを開くと、url
というパラメータが必須になっています。
これはGitリポジトリのURL(https://gitlab.com/<your-gitlab-username>/simple-stateful-app.git
)に他なりません。ただし、これをこんな風に
Taskのパラメータにハードコーディングしてしまうと、他のアプリケーションに対しても使い回せるパイプラインになりません。Task内のパラメータは「変数」としておいて、その変数に代入する値(パラメータ)はPipeline側に持たせます。
「パラメーターの追加」をクリックすると、文字通りパラメータ追加画面になります。パラメータには名前と説明、そしてデフォルト値を設定する事ができます。ここで名前とはKeyの様なものです。デフォルト値は、PipelineRunを実行する際にパラメータが空欄だった際にデフォルトで突っ込んでくれる値を指定できます。
ここにそれぞれ必要な5つのパラメータを設定します。「名前」は必ずしも記事の通りである必要はありません。他の人が見てわかりやすいものを選んでください。また大文字である必要もありません。
(ただし、よくパラメータは大文字とアンダーバーを使うのでそれに倣っているだけです)
名前 | 説明 | デフォルト値 |
---|---|---|
GIT_URL | ソースコードを取ってくる先のGitリポジトリのURL | https://gitlab.com/<your-gitlab-username>/simple-stateful-app.git |
DOCKERFILE_PATH | イメージをBuildする為のDockerfileのありかを示すパス | ./source/Dockerfile |
SOURCE_PATH | イメージをBuildする対象のアプリケーションのソースコードのありかを示すパス | ./source |
INTERNAL_REGISTRY_URL | コピー元の内部レジストリのURL | image-registry.openshift-image-registry.svc:5000/simple-stateful-app/simple-stateful-app |
EXTERNAL_REGISTRY_URL | コピー先の外部レジストリのURL | docker.io/<your-dockerhub-username>/simple-stateful-app |
いくつかのパラメータについては少し詳しく解説します。
DOCKERFILE_PATH
ですが、Gitリポジトリのディレクトリ「source」内にDockerfileがありますね。なのでこのパスを指定しています。ルートディレクトリをカレントディレクトリ.
とするならば、Dockerfileのパスは./source/Dockerfile
です。また、「DockerfileでイメージとしてBuildする対象のアプリケーションのソースコードのありか」を示すSOURCE_PATH
は、./source
となります。
次に内部レジストリのURLを表すINTERNAL_REGISTRY_URL
ですが、OpenShiftクラスタ内からアクセスできる内部レジストリのURLの命名規則は以下のようになっています。
image-registry.openshift-image-registry.svc:5000/<NameSpace名>/<Image名>
今回はNameSpace「simple-stateful-app」内に「simple-stateful-app」というイメージ名でBuildしたイメージをPushする形にしました。一方、外部レジストリのURLEXTERNAL_REGISTRY_URL
についてですが、Docker Hubで作成したリポジトリの命名規則は以下の通りとなっています。
<your-dockerhub-username>/<repositry-name>
これでPipeline側には必要なパラメータについては入力が完了しました。
Workspaceの追加
次にこのパイプラインにWorkspaceを追加します。が、めちゃくちゃ簡単です。「ワークスペースの追加」をクリックして任意の名前(例:workspace)と入力して完了です。
各Taskにパラメータを参照させる
次に、Pipeline側に設定した変数を各Taskから参照させる作業を行います。
まずはTask「git-clone」からいきましょう。Taskをクリックしてパラメータ設定メニューをひらきます。
Task側のパラメータurl
には、Pipeline側のパラメータGIT_URL
を参照させたいです。ご丁寧に「このフォームで変数を参照する際は、この形式を使用します: $(」と書いてあるので、やってみます。
Pipeline側で設定したパラメータ一覧が出てきました。S(params.GIT_URL)
を選びます。
ついでに、Git clone
してくるべきブランチも明示的に指定しておきます。ここではmain
と直入力しちゃいます。
あとのパラメータは空欄でOKです。このまま下の方にスクロールすると、Workspaceを設定する箇所が出てきます。
このTask「git-clone」にとって必須のワークスペース「output」に、先程Pipelineに追加したWorkspace(名称:workspace)を追加しましょう。これでTask「git-clone」のパラメータとWorkspaceの設定は完了です。次にTask「buildah」をクリックして同様に設定していきましょう。
Task「buildah」ではIMAGE
というパラメータが必須の模様です。これは説明読んでもちょっとわかりくいんですが、イメージの最初のPush先(つまり内部レジストリ)の宛先になります。つまり、Pipeline側のパラメータINTERNAL_REGISTRY_URL
を参照させる必要があります。
つまり、以下の様に入力できればOKです。
$(params.INTERNAL_REGISTRY_URL)
次に設定する必要があるパラメータはDOCEKRFILE
とCONTEXT
です。
それぞれ、DockerfileのパスDOCKERFILE_PATH
とソースコードのパスSOURCE_PATH
を参照させればOKです。つまり以下のように入力できればOKです。
必須のWorkspacesource
についても「workspace」を選択しておきましょう。
さて、これでTask「buildah」に必要なパラメータ設定も完了です。次はTask「skopeo」です。「skopeo-copy」をクリックします。
「skopeo-copy」で必要なパラメータは、srcImageURL
とdestImageURL
であり、それぞれのパラメータの説明にある通り、イメージのコピー元とコピー先を指定します。つまり、INTERNAL_REGISTRY_URL
とEXTERNAL_REGISTRY_URL
を指定すればOKです。
ただし!Skopeoの仕様上、イメージレジストリの宛先(URL)の前にdocker://
と入れる必要があります。これは仕様です。つまり以下の様に入力できればOKです。
必須のWorkspace名images-url
についてもこれまで同様「workspace」を入れておきます。
お疲れ様でした。3つのTaskから構成されるパイプラインはこれで完成まであと一歩です。一旦、「作成」をクリックしてください。
各Taskのパラメータの詳細説明や仕様については、Tekton Hubを確認することで理解できます。例えば、「skopeo-copy」の仕様(イメージレジストリの宛先(URL)の前にdocker://
と入れる必要がある 等)はTekton Hubの「skopeo-copy」ページに記載されています。
PVCを追加する
パイプラインにはWorkspace用のPVCが必要でしたね。以下のManifestをOpenShiftコンソール画面のYAML適用画面からApplyして、NameSpace「simple-stateful-app」にPVCを作成します。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: workspace-pvc
namespace: simple-stateful-app
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
「作成」をクリックします。
ステータスが「Pending」となっているのは、まだPVCで要求したPVがバインドされていないからです。一回パイプラインを回せばバインドされますので気にせずでOKです。
とりあえずパイプラインを回してみる
パイプラインを回してみましょう。右上プルダウン「Actions」から「Start」をクリックします。
パラメータにはデフォルト値が最初から入っています。下の方にスクロールして「Workspaces」欄の「workspace」でPVCを選択し、先程作成したworkspace-pvc
を選びます。
これで一旦OKです。右下青いボタン「Start」をクリックすると、パイプラインが回り始めました!
残念。パイプラインが失敗しました。イメージのBuildまでは上手く行ったのですが...
https://x.com/showshow_jp/status/1645443699814588416/photo/1
パイプラインが失敗した場合は必ず何らかのログが出るので参考にしましょう。
パソコンを殴ってはいけません。パソコンが壊れてしまいます。
「Logs」タブでTaskのログが見れます。
Task「skopeo-copy」のログを抜粋すると、
docker.io: requested access to the resource is denied
ということでした。これ、Docker Hubにイメージをアップロード(Push)するための権限が無いよということです。そういえば、パイプラインのパラメータで外部レジストリのURLEXTERNAL_REGISTRY_URL
のデフォルト値に<your-dockerhub-username>/simple-stateful-app/simple-stateful-app
を設定したものの、DockerHubにイメージをPushするための認証情報の設定をしておりません。これだとパイプラインがイメージをPushすることができません。
DockerHubの認証情報の設定
OpenShiftコンソール画面の「開発者向け表示」にて、「シークレット」をクリックします。
NameSpace「simple-stateful-app」に作成されたシークレット一覧が見えますが、pipeline-dockercfg-<ランダム文字列>
をクリックします。このSecretはOpenShift Pipleines OperatorをインストールしたタイミングですべてのNameSpaceに自動的に作成されるService Account「pipeline」にマウントされるSecretです。Service Account「pipeline」に更にSecretを追加でマウントします。追加でマウントするSecretこそが、DockerHubの認証情報を含むそれになります。
Secretを追加作成
直前と同じ画面の右上「作成」からSecretを作成しますが、「イメージのプルシークレット」を選択しましょう。
シークレット名は何か適当なもの(例:dockerhub-secret
)を入れておきます。その他、以下のように値を入れておきます。
項目 | 入れるべき値 |
---|---|
認証タイプ | イメージレジストリーの認証情報 |
レジストリーサーバーのアドレス | docker.io |
ユーザー名 | 自身のDockerHubのユーザー名 |
パスワード | DockerHubのPersonal access token |
「作成」をクリックしてください。これでDockerHubにイメージをPushする為の認証情報をSecretとして作成できました。あとは、こいつをService Account「pipeline」にマウントすればOKです。
DockerHubの認証情報をService Accountにマウントする
「管理者向け表示」にて左端メニュー「ユーザー管理」から「ServiceAccounts」をクリックすると、Service Account「pipeline」が確認できます。
YAMLビューで中身を見ると、既にひとつのSecretを参照しています。これがOpenShift Pipelinesをインストールすると自動で作成されるSecret(内部イメージレジストリのプルシークレット)です。
kind: ServiceAccount
apiVersion: v1
metadata:
name: pipeline
namespace: simple-stateful-app
secrets:
- name: pipeline-dockercfg-<ランダム文字列>
imagePullSecrets:
- name: pipeline-dockercfg-<ランダム文字列>
これを以下のように追記しましょう。
kind: ServiceAccount
apiVersion: v1
metadata:
name: pipeline
namespace: simple-stateful-app
secrets:
- name: pipeline-dockercfg-<ランダム文字列>
+ - name: dockerhub-secret
imagePullSecrets:
- name: pipeline-dockercfg-<ランダム文字列>
+ - name: dockerhub-secret
追記ができたら「保存」をクリックします。
このService Account「pipeline」がパイプラインの中で実行されるTask「skopeo-copy」におけるイメージのPushを担いますので、こいつが参照するSecretにDockerHubの認証情報を設定しました。
再度パイプラインを回す
パイプライン画面に戻ります。
右端の三点リーダをクリック、プルダウンから開始」を選択し、初回同様、WorkspaceにPVCを選択しておきます。「開始」をクリックするとパイプラインが再び回り始めます。
今度はパイプラインが成功しました。やったぜ。続いて、DockerHubのリポジトリ画面を見てみます。
イメージがPushされたことが確認できます。おめでとうございます。これで非常に簡単ではありますが、Tektonパイプラインをゼロから作成し、コンテナイメージをBuildしレジストリにPushする流れの自動化を達成しました。ここからは、もう少し実用的なパイプラインにするべく、Taskの追加と設定を行っていきます。ただし、容量としてはこれまで追加した3つのTaskのそれと全く同様です。
- Pipeline編集画面でTaskを検索して追加する
- 汎用的にTaskに持たせたい変数をPipeline側で定義する
- その変数をTask側で参照させる
$(params.環境変数)
基本的には全てこの流れで新しいTaskを追加・設定していきます。
DeploymentをRolloutするTaskを追加
3つのTaskで構成したパイプラインを元にTaskを増やしましょう。まずはDeploymentをロールアウトするTaskを新規に作成・追加します。もし先ほど作成したパイプラインにおいてコンテナイメージが再度Buildされても、Deploymentに対してRollout、要はコンテナイメージを再度PullしてきてPodを再作成する指示を出さないと、アプリケーションの変更が反映されません。なお、任意のDeploymentのRolloutはocコマンドやkubectlコマンドで実施できる非常に簡単なものです。例えばocコマンドだと以下のコマンドで可能です。
oc rollout restart deployment DEPLOYMENT_NAME -n NAMESPACE_NAME
ここで、DEPLOYMENT_NAME
は任意のDeloymentの名称です。今回の例で言えば「node-app-deployment」です。
また、NAMESPACE_NAME
は今回の例で言えば「simple-stateful-app」です。
っていうocコマンドを打ってくれるTaskを追加すれば、DeploymentのRolloutをしてくれるわけです。
Task「openshift-client」を追加
早速やっていきます。パイプラインの編集モードに切り替えます。
「Finally Taskの追加」をクリックします。
Task検索窓で「openshift-client」と検索し、Red Hatが提供するCluster Task(CT)のそれを追加しましょう。「openshift-client」は任意のocコマンドを実行してくれるTaskです。
パラメータを設定
さて、このパイプラインにパラメータDEPLOYMENT_NAME
とNAMESPACE_NAME
を新たに追加します。
名前 | 説明 | デフォルト値 |
---|---|---|
・・・ | ・・・ | ・・・ |
DEPLOYMENT_NAME | ロールアウト対象のDeployment名を指定します | node-app-deployment |
NAMESPACE_NAME | Deploymentの存在するNameSpaceを指定します | simple-stateful-app |
画面の状況でいうとこんな感じです。下2つが今新たに追加したパラメータです。
次に「openshift-client」をクリックします。「SCRIPT」欄にはそのままocコマンド自体を入力できます。しかしパラメータを変数部分で指定してあげる必要があるので、以下のように入力しましょう。
oc rollout restart deployment $(params.DEPLOYMENT_NAME) -n $(params.NAMESPACE_NAME)
他のTaskと同様にWorkspaceも指定してあげればこれでOKです。「保存」をクリックします。パイプラインにTaskを追加していく方法がわかってきたでしょうか?繰り返しになりますが、Taskに必要なパラメータ(PARAMETER
)をPipeline側に設定し、それを参照する様に$(params.PARAMETER)
と記述すれば、汎用性の高いパイプラインを作成できます。
Sourceコードを編集
では、Gitlab上のアプリケーションのソースコードを編集して、パイプライン回してみます。GitLabのWeb IDE上で編集してもいいし、ローカルPCにCloneしてきたソースコードを編集&プッシュしてもOKです。
今回編集するファイルはroot/source/public/index.html
です。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node.js MySQL App</title>
<style>
.title {
color: rgb(255, 0, 0);
}
</style>
(以下略)
color: rgb(255, 0, 0)
では、アプリのタイトル部分の文字の色を司っています。
ソースコード編集の一環としてRGBコードを変えてみます。ソースコードを編集したら、Commit&Pushしておいてください。今回は適当にcolor: rgb(147, 14, 214);
としてみました。紫っぽい色です。
パイプラインを回す
では、パイプラインをぶん回しましょう。先ほどと同様の手順で「開始」をクリック、
PVCを選択して実行します。
パイプラインが回り始めます。
Task「openshift-client」が動いているタイミングでトポロジー画面でアプリケーションのアイコンを確認すると、Rolloutが走っている状況が見えます。
アプリケーションに変更が反映されたか、アプリの画面を更新して確認してください。
タイトル欄が指定したカラーコードの色になりました。ソースコードを編集し、パイプラインを回したら自動的にコンテナイメージがBuildされ、外部レジストリにイメージがPushされ、Rolloutされてアプリケーションが更新されました。だいぶいい感じに自動化されてきた気がします。ただし、これだと「半自動」な状態です。理想的には「ソースコードの編集結果をPushしたら、何もせずとも勝手にパイプラインが回り始めてアプリケーションに変更が反映されること」を目指したいです。
ソースコードのPushを受けてパイプラインを自動的に回す
ソースコードを編集し、Gitリポジトリに内容をPushしたら、自動的にPipelineRunを実行してくれたらとても便利です。なお、GitリポジトリにはソースのPushを含む各種のイベント発生を契機にして、特定のAPIエンドポイントに対してWebhookを発信する機能があります。その通知内容を受けてパラメータに値を突っ込んでPipelineRunを実行してくれる、そんな便利な存在が「Trigger」です。
Triggerの追加
パイプライン画面の三点リーダから「トリガーの追加」を選択します。
「Webhook - Git provider type」のプルダウンからは各種Gitサービスのイベント通知(Webhook)に対応した選択肢が存在します。今回は「Gitlab-push」を選択します。
Gitlabにソースコードの変更がPushされた事を契機(トリガー)にして、Triggerがパイプラインのパラメータに値を入れ(何も指定しない場合はデフォルト値が入る)、PipelineRunを作成・実行してくれます。また、TriggerではWorkspaceの設定も可能です。
パイプラインを実行する際に毎度手入力していたWorkspaceの設定をTrigger側で設定しておけば、PipelineRun実行時に自動的に同内容を設定して実行してくれます。WebhookとWorkspaceの設定ができたら「追加」をクリックします。NameSpaceのトポロジー画面を見ると、何やら「el-evnet-listener-<ランダム文字列>」とかいうDeploymentが適用されています。
こいつが公開するRouteにTriggerで設定したイベント通知(Webhook通知)を飛ばすと、設定した値をパラメータに代入してパイプラインをぶん回すところまで一気にやってくれます。早速GitlabでWebhook通知の設定を行いましょう。
GitlabのWebhook通知設定
それでは、このEventListenerに対して送信するWebhookの設定をGitlab上で行います。Gitlabリポジトリ「log-test」の左端メニュー「設定」→「Webhooks」と進みます。
「新しいWebhookを追加」をクリックします。
URLにはEventListenerのRouteのURLを入力します。
EventListenerのRouteのURLはOpenShiftのトポロジー画面で確認できます。
トリガーには「プッシュイベント」を選択します。今回はmainブランチに対するPushのみをWebhook通知発信イベントとして定義しようと思いますので、「ワイルドカードパターン」にmain
と入れておきます。
これで設定は完了です。一番下までスクロールして「Webhookを追加」をクリックします。
これでWebhookが追加されました。早速試してみます。
再びGitlab上でソースコードを編集してPushする
再度、root/source/public/index.html
で色を変更してみます。今度はこんな色color: rgb(14, 214, 27)
にしてみました。黄緑色っぽい色です。再びソースコードをPushします。
パイプラインを確認してください。おー!自動的にパイプラインが回り始めています!
パイプラインが完了しました。
アプリケーションの画面をリフレッシュすると、ホスト名が先程編集した通りのカラーコードになっています。
この様に、パイプラインにTriggerを追加するとGitリポジトリのWebhook通知を受けるEventListenerがDeployされます。ソースコードを編集しGitにPushすると、そのイベントがWebhook通知としてEventListenerに飛びます。それを受けて自動的にパイプラインが回るようになりました。これでかなり「継続的デリバリー」みが出てきました。
コンテナイメージスキャンTaskを追加
さて、これまではソースコードの変更をGitにPushすると自動的にコンテナイメージがBuildされ、外部レジストリにPush、そのままDeployされていました。そこで、コンテナイメージのスキャンを実行するTaskを入れたいと思います。イメージスキャンのTaskとしてOSSの「Trivy」を使います。
「Trivy」は軽量な脆弱性検知ツールで、コンテナイメージもスキャンできます。コンテナイメージのみならず、ManifestファイルやIaCのファイル(Terraform 等)もスキャンできます。DesktopやServerにインストールして利用することもできますが、TektonのTaskとしても実行可能です。
Task「Trivy-scanner」を追加
早速パイプラインにTaskを追加しましょう。これまで同様、パイプライン編集画面からTask「Skopeo-copy」の手前にTaskを追加し、
検索窓にて「Trivy」と検索、TektonHubで公開されている「Trivy-scanner」を追加します。
Trivyコマンド
Trivyは先述の通りコンテナイメージスキャンにも対応しているツールですが、TrivyをDesktop等で動かす場合のコマンド(Trivyコマンド)にて、CVSSにて定義される深刻度(UNKNOWN、LOW、MEDIUM、HIGH、CRITICAL)を指定することができます。例えばHIGH以上の脆弱性をチェック対象とする場合、
trivy image IMAGE_NAME --severity HIGH,CRITICAL
とコマンドを記載する必要があります。
パラメータを設定
パイプライン編集画面でTask「trivy-scanner」をクリックすると、ARG
というパラメータが必須になっていますが、ここにはTrivyコマンドの要素を記載します。今回は上述のコマンドを実行するものとします。
この様に記載することで、先に示したTrivyコマンドと同じ事を実行してくれます。次に、同じく必須になっているパラメータIMAGE_PATH
ですが、これはコンテナイメージのパスになります。直前のTask「Buildah」でBuildされたコンテナイメージはINTERNAL_REGISTRY_URL
で指定した内部レジストリに格納されています。ということは、IMAGE_PATH
にはINTERNAL_REGISTRY_URL
を指定してあげればOKです。
これまで通りWorkspaceには「workspace」を指定すればOKです。「保存」をクリックします。
プッシュイベントを発生させてパイプラインを回す
ではパイプラインを再度回してみましょう。毎度毎度ソースコードを編集するのも面倒なので、GitlabのWebhookのプルダウン「テスト」から「プッシュイベント」をクリックして、強制的にGitLabからWebhook通知を送ります。
するとパイプラインが回り始めます。
しばらくすると、パイプラインが成功しました。
ログのタブからTask「trivy-scanner」のログを見てみます。
HIGH以上の脆弱性は検知されなかったようです。良かったです。
Total: 0 (HIGH: 0, CRITICAL: 0)
Sonarqubeによるソースコード静的解析を実現
更にパイプラインを増強したいと思います。いわゆる「シフトレフト」を志向する観点で、ソースコードの静的解析は避けては通れないでしょう。「Sonarqube」はソースコードやManifestファイル、IaCの静的解析ツールとして非常に幅広く使われています。
Sonarqubeが利用できるようにする
まずはOpenShiftの中にSonarqubeをDeployします。その前段として、とりあえず適当なNameSpace(例:sonarqube)を作成しました。
PostgresqlをDeployする
では「SonarqubeをDeploy!」の前に、Sonarqubeのデータを永続化するためのPostgresqlをDeployします。
simple-stateful-app $ oc apply -f sonarqube/postgresql -n sonarqube
deployment.apps/postgresql created
persistentvolumeclaim/postgresql-pvc created
secret/postgresql-secret created
service/postgresql-service created
PostgresqlがDeployされました。Postgresqlはインターネットに公開する必要はないので、Routeは作成しません。
PostgresqlコンテナイメージはRed Hatが公開しているregistry.redhat.io/rhel8/postgresql-15
を利用します。これには、環境変数を設定する事ができます。今回はSecretファイルで以下の環境変数を設定しています。
apiVersion: v1
kind: Secret
metadata:
name: postgresql-secret
type: Opaque
stringData: #ユーザ名とパスワード、初期作成するDB名を指定できます
POSTGRESQL_USER: user
POSTGRESQL_PASSWORD: password
POSTGRESQL_DATABASE: sonarqubedb
# 通常のApplication開発において、Secretを公開Repositoryに置くのはNGです。あくまでデモ用途です。
Postgresqlのユーザ名/パスワードに加え、初期作成するDatabase名を指定する事ができます。その他にもいくつかの環境変数を設定する事ができます。詳細はRed Hat Ecosystem Catalog を参照ください。
Sonarqubeの環境変数をApplyする
SonarqubeをPostgresqlに接続するために必要な情報をそれぞれConfigMapとSecretとして用意しています。環境変数のKeyはSonarqubeの仕様に準じています。以下のコマンドをターミナルで実行してこれらをApplyしましょう。
simple-stateful-app $ oc apply -f sonarqube/sonarqube-env -n sonarqube
configmap/mysql-initdb-config created
secret/sonarqube-secret created
Sonarqubeコンテナが接続するPostgresqlコンテナの宛先をConfigMapで指定してます。具体的には以下のYAMLです。
apiVersion: v1
kind: ConfigMap
metadata:
name: sonarqube-configmap
data:
SONAR_JDBC_URL: jdbc:postgresql://postgresql-service.sonarqube.svc.cluster.local:5432/sonarqubedb
ここで、postgresql-service.sonarqube.svc.cluster.local:5432
はOpenShiftクラスタ内でPostgresqlコンテナにアクセスする為の内部ホスト名です。これは、Serviceによって提供されます。
また、SonarqubeがPostgresqlコンテナのDatabase「sonarqubedb」にアクセスするための情報(ユーザ名/パスワード)は以下のSecretで提供しています。
apiVersion: v1
kind: Secret
metadata:
name: sonarqube-secret
type: Opaque
stringData:
SONAR_JDBC_USERNAME: user
SONAR_JDBC_PASSWORD: password
これは、元々PostgresqlコンテナをDeployする際に利用したSecret「postgresql-secret」で指定したユーザ名/パスワードと同じものです。
SonarqubeをS2IでDeploy
それでは、SonarqubeをDeployしましょう。Sonarqubeの公式Dockerfileを使い、Source ot Image(S2I)を使ってOpenShiftクラスタにSonarqubeをDeployします。本記事の執筆時点ではVer9とVer10が存在しているようですが、今回はサポートなしの無料で利用するため、9/community
の中のDockerfileを使います。
では、OpenShiftのトポロジー画面を右クリックし、「Gitからのインポート」をクリックします。
必要なパラメータを入力します。
項目 | 値 |
---|---|
GitリポジトリーURL | https://github.com/SonarSource/docker-sonarqube.git |
Gitリファレンス | master |
コンテキストディレクトリー | /9/community |
値を正しく入力すると、Dockerfileを検知してくれます。
少し画面を下にスクロールします。「一般」及び「Build」欄はデフォルトの設定をそのまま使います。もしアプリケーション名や名前を変更したければご自由にわかりやすいものに変えてください。
さらに画面を下にスクロールします。「デプロイ」欄は以下のように設定します。
項目 | 値 |
---|---|
リソースタイプ | Deployment |
SONAR_JDBC_URL | sonarqube-configmap / SONAR_JDBC_URL |
SONAR_JDBC_USERNAME | sonarqube-secret / SONAR_JDBC_USERNAME |
SONAR_JDBC_PASSWORD | sonarqube-secret / SONAR_JDBC_PASSWORD |
以下のように入力できればOKです。
これで、S2Iの際に当該SecretとConfigMapから環境変数を渡してコンテナイメージをBuildしてくれます。もう少し画面を下にスクロールし、「作成」をクリックしましょう。
Sonarqubeの公式Dockerfileを確認してみると、Sonarqubeコンテナは9000番ポートで公開されています。故にターゲットポートは9000となるわけです。また、この後Sonarqubeにログインしていくつかの設定を行いますので、そのために「routeの作成」にチェックを入れておきます(デフォルトでチェック済み)
SonarqubeコンテナのS2Iが始まるので、しばし待ちます。
Deployできました!
Sonarqubeにログインする
ではRouteからSonarqubeのコンソール画面にログインしましょう。初期ユーザ情報は以下の通りです。
- ID:
admin
- Password:
admin
初期パスワードの変更を求められるので、適宜変更して「Update」をクリックします。
無事にログインできました。
SonarqubeとGitlabリポジトリを接続する
ログインが成功しましたので、Sonarqubeのソース解析対象プロジェクトを登録します。
今回はGitlabをクリックします。以下のようなポップアップが出てきます。
項目 | 値 |
---|---|
Configuration name | 適当なものを入力します。今回は「simple-stateful-app」とします |
GitLab API URL | https://gitlab.com/api/v4 |
Personal Access Token | ご自身のGitリポジトリで作成したもの |
Gitlabの「Personal Access Token」は、「設定」→「アクセストークン」と進み、
「新しいトークンを追加」をクリックし、
トークン名は適当もの(例:mytoken
)を入力し、先ほどのポップアップ画面の「Personal Access Token」欄の説明にあったようにロールは「Reporter」を選択し、「api」スコープを選択します。
SonarQube needs a Personal Access Token to report the Quality Gate status on Merge Requests in GitLab. To create this token, we recommend using a dedicated GitLab account with Reporter permission to all target projects. The token itself needs the api scope.
有効期限は適切に設定してください。設定しなくても構いません。
設定が完了したら、少し画面を下にスクロールして、「プロジェクトのアクセストークンを作成」をクリックします。
アクセストークンが作成されました。一度しか表示されないので必ずコピーしてSonarqubeに忘れずに設定しましょう。
今作成した「Personal Access Token」をペーストしたら、「Save Configration」をクリックします。
再度「Personal Access Token」の入力を求められますので、同じものをペーストして「Save」をクリックします。
SonarqubeとGitlabリポジトリが接続されました。それでは、CIツールとの連携の設定を進めます。「Set up」をクリックします。
SonarqubeとTekton Pipelineを統合する為の設定
Sonarqubeに接続されたGitlabリポジトリの内容を分析する為のCIツールの設定を行いますが、Tektonは選択肢にラインナップされていないので、「Other CI」をクリックします。
ここで、CIツール(=Tekton)にわたすべきトークン情報を設定します。「Token name」は適当なものを入力します。デフォルト値でも構いません。また、トークン有効期限は適宜選んでください。設定しなくても構いません。「Generate」をクリックすると、トークンが作成されます。
トークンが作成されました。「Continue」をクリックします。
ここで適切なアプリケーションのBuild方式を選択します。今回作成したアプリケーションは、Node.jsになるので「What option best describes your build?」欄は「Other」をクリックします。次にOS種別を聞かれますので、「What is your OS?」欄は「Linux」を選択します。
画面を少し下にスクロールすると、「Configure a SONAR_TOKEN environment variable in your CI settings」欄に、SONAR_TOKEN
というKeyに先ほど作成したトークンの値を設定する様に指定する旨が表示されます。
実は、この後設定するSonarqubeによるソースコードスキャンのパラメータに、このSONAR_TOKEN
を設定することで、Taskが実行できるようになります。また、画面をまた少し下にスクロールすると、「Execute the Scanner」欄にいくつかのパラメータが設定されていますが、この中でprojectKey
を後ほど利用します。
sonar-scanner \
-Dsonar.projectKey=gitou-laboo_simple-stateful-app_AZOw-iNqbM60U_OATmZD \
-Dsonar.sources=. \
-Dsonar.host.url=https://docker-sonarqube-git-sonarqube.apps.rosa.moomura-hcp.elgb.p3.openshiftapps.com
お疲れ様でした。ともかくSONAR_TOKEN
とprojectKey
を忘れないようにメモしておいてください。
Sonarqubeによるソースコードスキャンをパイプラインに追加&設定する
一旦Sonarqubeの画面はそのままにして、再びOpenShiftのコンソール画面に戻ってきます。これまで通りの流れで、パイプライン編集画面に入ります。
では、Taskを追加します。
Task「sonarqube-scanner」を追加
Task「git-clone」の前にTaskを追加します。
これまで通りTaskを検索しますが、検索ボックスには「sonarqube-scanner」と入力します。ここでver1.0かver2.0を選択してInstallしてください。Ver3.0移行はSonarQubeのSaaS版との連携に必要なパラメータ指定が必要なのですが、今回はSelf-Install版を使っているので、1.0か2.0でやってください。今回は一旦2.0のほうでやっていきます
追加した「sonarqube-scanner」をクリックするとパラメータの一覧が表示されます。
しかし、設定したい「SONAR_TOKEN」の入力欄が見当たりません。実は、少しTaskのYAMLファイルを編集してあげる必要があります。一度、この編集のしかかり状態のまま、コンソール画面を「管理者向け表示」に変え、「Pipelines」メニューから「Tasks」をクリックすると、NameSpace「simple-stateful-app」に適用されているTaskを確認できます。「Tasks」欄には、既に追加済みの2つのTaskが確認できますが、「sonarqube-scanner」をクリックします。YAMLビューに変更し、以下のように追記してください。
apiVersion: tekton.dev/v1
kind: Task
(中略)
spec:
params:
- default: ''
description: Host URL where the sonarqube server is running
name: SONAR_HOST_URL
type: string
- default: ''
description: Project's unique key
name: SONAR_PROJECT_KEY
type: string
+ - default: ''
+ description: token info
+ name: SONAR_TOKEN
+ type: string
(中略)
workspaces:
- name: source
- name: sonar-settings
これで、SONAR_TOKEN
をTaskのパラメータとして追加し、そこに与えられた値をSonarqubeの設定ファイルに引き渡す設定を追記することができました。追記できたら「保存」をクリックします。
再び、「開発者向け表示」でパイプライン編集画面に戻ります。
もう一度同じ手順でTask「sonarqube-scanner」のver0.2(インストール済み)を追加します。
再び「sonarqube-scanner」をクリックすると、今後は「SONAR_TOKEN」がパラメーター欄に表示されました。
ただし、これまでのTaskの追加の方法同様、Taskのパラメータに具体的な値をハードコーディングするのは避けます。Pipeline側のパラメーターに以下の値を設定しましょう。
名前 | 説明 | デフォルト値 |
---|---|---|
SONAR_HOST_URL | Serviceから提供されるSonarqubeの内部ホスト名 | http://docker-sonarqube-git.sonarqube.svc.cluster.local:9000 |
SONAR_PROJECT_KEY | SonarqubeのCI統合設定で払い出されたprojectKey
|
<your-projectkey> |
SONAR_TOKEN | SonarqubeのCI統合設定で払い出されたSONAR_TOKEN
|
<your-sonar-token> |
また、Task「sonarqube-scanner」にも、これまで通りパラメーターを$(params.ENV)
の形式で設定してあげます。ワークスペースについては、いずれも「workspace」を設定しましょう。つまり、以下のようになっていればOKです。「保存」をクリックしてパイプラインの編集結果を保存します。
さて、SonarqubeをDeployしソースコード静的解析の為のTask「sonarqube-scanner」も追加できました。頑張って各種パラメータも設定しました。はぁ〜疲れた。上手くいくでしょうか!?運命のパイプライン実行です。
Sonarqubeによるソースコードスキャンをパイプライン内で実行する
再度、Gitlabで「プッシュイベント」を発生させて、パイプラインを自動的に回します。
パイプラインが回り始めました!どうなるでしょうか!?
あ、、、
失敗しました。
焦らず怒らずログを見ます。
んん?どうやらSonarqubeにログインできていないようです。
step-sonar-properties-create
2024-12-11T09:11:26.790483519Z ---------------------------
2024-12-11T09:11:26.791557738Z sonar.projectKey=gitou-laboo_simple-stateful-app_AZOw-iNqbM60U_OATmZD
2024-12-11T09:11:26.791557738Z sonar.host.url=http://docker-sonarqube-git.sonarqube.svc.cluster.local:9000
2024-12-11T09:11:26.791557738Z sonar.sources=.
step-sonar-scan
2024-12-11T09:11:26.948568549Z INFO: Scanner configuration file: /opt/sonar-scanner/conf/sonar-scanner.properties
2024-12-11T09:11:26.948934483Z INFO: Project root configuration file: /workspace/source/sonar-project.properties
(中略)
Not authorized. Analyzing this project requires authentication. Please provide a user token in sonar.login or other credentials in sonar.login and sonar.password.
どうやら、Task「sonarqube-scanner」においては、「step-sonar-properties-create」において、各種パラメータをプロパティファイルに反映している様です。そして、どうやらパイプライン上のTask「sonarqube-scanner」に設定したパラメータSONAR_TOKEN
がsonar.login
に引き渡されていないということでエラーが起きているという風に読み取れます。では、改めてTask「sonarqube-scanner」のYAMLを確認してみます。パイプラインの詳細画面から、Taskの詳細画面に飛ぶことができます。「Tasks」欄の「sonarqube-scanner」をクリックしてください。
98行目から101行目までを見ると、なにやらやってます。
(中略)
else
touch sonar-project.properties
echo "sonar.projectKey=$(params.SONAR_PROJECT_KEY)" >> sonar-project.properties
echo "sonar.host.url=$(params.SONAR_HOST_URL)" >> sonar-project.properties
echo "sonar.sources=." >> sonar-project.properties
fi
(中略)
ここでどうやら、sonar.projectKey
にPipeline側で設定したSONAR_PROJECT_KEY
を代入したり、sonar.host.url
にSONAR_HOST_URL
を代入したりして、sonar-project.properties
に反映(echo)している様です。ということは、以下のように追記したら、sonar.login
にSONAR_TOKEN
を渡してプロパティファイルに反映してくれるのではないでしょうか?
(中略)
else
touch sonar-project.properties
echo "sonar.projectKey=$(params.SONAR_PROJECT_KEY)" >> sonar-project.properties
echo "sonar.host.url=$(params.SONAR_HOST_URL)" >> sonar-project.properties
echo "sonar.sources=." >> sonar-project.properties
+ echo "sonar.login=$(params.SONAR_TOKEN)" >> sonar-project.properties
fi
(中略)
追記できたら「保存」をクリックします。
もう一度パイプラインを回す。
三度、root/source/public/index.html
で色を変更してみましょう。こんな色color: rgb(214, 121, 14);
にしてみました。オレンジっぽい色です。ソースコードをPushし、パイプラインを自動起動させましょう!
パイプラインが回り始めました。
やりました!パイプラインが成功しました!
PipelineRunのログで「sonarqube-scanner」をクリックすると、Sonarqubeによるソースコードスキャン結果のログを確認できます。
また、Sonarqubeの画面コンソールに切り替えると、表示が変わり、スキャン結果の詳細を確認する事ができるようになっています。
今回、脆弱性は発見されなかったものの、Dockerfileの内容について確認するような指摘をもらいました。
無事にソースコード編集結果がアプリケーションに反映されています。
おわりに
最後までお疲れ様でした。本記事で少しでもOpenShift Pipelines(Tekton)でパイプラインを作成・編集し、自動化されたデリバリーの仕組み構築に踏み出す際の敷居を低くできれば幸いです。素のKubernetes自体はコード(YAML)とコマンド(kubectl)による操作しか対応していませんが、OpenShiftはそうした敷居の高さを少しでも低くする為に、豊富な画面コンソールを有しています。Tektonについても、OpenShift Pipelinesとして独自のGUIを提供しています。これは、一部の「CLI絶対君主制」や「YAML原理主義者」の方々からは「けしからん」と思われてしまうかもしれませんが、、しかし「便利なものは使う」という精神に基づき、OpenShiftでは積極的にGUIも使いたいと思う筆者なのでした。
おわり