OpenShift GitOps Operator は 何をしているのか

## はじめに

この記事は以下の方に役立てていただけるかもしれません。

- kubernetes や OpenShift の Operator や コントローラーの実装調査や検証手順の例を知りたい
  - コントローラーのソースコードの着目箇所を知りたい ( :eye: )
  - VSCode のTipsを知りたい ( :bulb: )
- kubernetes と OpenShift の違いや、OpenShiftならではの機能例を知りたい ( :ship: )

記事中、`Argo CD` は OSS、`ArgoCD`は CR ( カスタムリソース ) を指します。

## 動機

[Red Hat OpenShift](https://www.redhat.com/ja/technologies/cloud-computing/openshift) には  [GitOps](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.9/html/cicd/_gitops) の機能があります。さっそく使ってみましょう。
まずは [OpenShift GitOps Operator](https://github.com/redhat-developer/gitops-operator) をインストールします。
![install-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/bd8202c6-1be6-642a-3cdb-645c903b0004.png)

インストールが終わりました。ツールバーに `OpenShift GitOps` メニューが出現します。
![install-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/d457b6e2-bf99-82dd-72d6-8ea9075e573b.png)

これにて無事にGitOpsができるようになりました。めでたし、めでたし。
![argo-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/ef199c38-07e3-1a1b-db4b-63c0c795da92.png)

:ship: このように OpenShift GitOps Operator をインストールすることで Argo CD を使うことができるのが OpenShift GitOps と呼ばれる機能となります。OpenShift GitOps Operator ではなく、コミュニティ版の [Argo CD Operator](https://github.com/argoproj-labs/argocd-operator/) を使う場合との違いが気になりますね、調べてみましょう。

## Operatorのソースコードを読む

OpenShift GitOps Operator のソースコードを読んでみましょう。

https://github.com/redhat-developer/gitops-operator

:bulb: VSCodeでは、`Go to Definition (F12)` で関数に入ったり `Back ( Alt ← )` で出るなどすると効率よく探検できます。ちなみにマウスにボタンがいっぱいあるタイプの方はこれらを割り当てると高速で移動できます。`Forward ( Alt → )`も活躍します。
![01.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/f6890e27-9d6c-c460-7100-ae51aa8ec131.gif)

以降、掲載しているソースコードは、着目している行のみの抜粋となりますため、インデントが揃っていないように見える箇所があったりしますがご了承下さい。

## :eye: この Operator にコントローラーは何個あるのか？

まずは `main.go` を見てみます。初期化しているコントローラーは 4つでした。
OpenShift GitOps Operator のお仕事は4つのコントローラーが担当していそうですね。

1つずつ見ていきます。最初の3つは 以下です。

```go
import (
	"github.com/redhat-developer/gitops-operator/controllers"
)

	if err = (&controllers.ReconcileGitopsService{ // 1つめ
	}).SetupWithManager(mgr); err != nil {
	}

	if err = (&controllers.ReconcileArgoCDRoute{ // 2つめ
	}).SetupWithManager(mgr); err != nil {
	}

	if err = (&controllers.ArgoCDMetricsReconciler{ // 3つめ
	}).SetupWithManager(mgr); err != nil {
	}
```

上記3つのコントローラーは以下のように `controllers/` 配下にありそうですね。後ほど見てみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/603e9959-4de0-78de-fef4-a321ed2451c2.png)

4つめは以下です。おや、このコントローラーは外部のリポジトリにあるもののようです。あっ、これはコミュニティ版の [Argo CD Operator](https://github.com/argoproj-labs/argocd-operator/) に含まれる コントローラーの1つである [argocd-operator/controllers/argocd/argocd_controller.go](https://github.com/argoproj-labs/argocd-operator/blob/master/controllers/argocd/argocd_controller.go) を使っています。

```go
import (
	argocdprovisioner "github.com/argoproj-labs/argocd-operator/controllers/argocd"
)

	if err = (&argocdprovisioner.ReconcileArgoCD{ // 4つめ : これだけは コミュニティ版 ArgoCD Operator の コントローラーだ
	}).SetupWithManager(mgr); err != nil {
	}
```

なるほど、OpenShift GitOps Operator を使って Argo CD を利用する場合でも、このコントローラーの処理についてはコミュニティ版の Argo CD Operator を使った場合と同等の動作となるということですね。( そして、唯一のコミュニティ版の Argo CD Operatorとの 接点でもありますから、きっとこのコントローラーが Argo CD のデプロイなど主要な処理を行うのでしょうと嗅覚もつかいながらソースリーディングを進めていきます。)

### この章のまとめ

ここまでの情報から、OpenShift GitOps Operator のお仕事を担当している4つのコントローラーの内の1つは、コミュニティ版のArgo CD Operatorに含まれるコントローラーであり、Argo CD のデプロイなど主要な処理を担当していそうなこと、残り3つのコントローラーは OpenShift 向けの機能が実装されていそうなことが分かりました。

今後、各コントローラーが watchしているリソースが何か、Reconcile()でクラスタに対して何の操作( create/delete/update )を行っているかを読むことでより確度の高い調査ができそうです。

:ship: コミュニティ版のArgo CD Operatorではなく、OpenShift GitOps Operator を使う場合の違いは3つのコントローラーにありそうですね。OpenShiftでArgo CD を便利に使えるようにしてくれているメリットがあることが期待されます。

それでは、それぞれのコントローラーのお仕事を見ていきましょう。

<details><summary>:bulb: コラム : VSCodeのTips ( ここをぱかっと開いてね ) </summary><div><br>

:bulb: Web版VSCode
GitHubで .(ドット) を押すとブラウザでWeb版のVSCodeが起動しリポジトリのソースコードが開かれますのでそれも便利です。始めは拡張機能が入っていませんが、ブラウザのローカルストレージに拡張機能が保存されますので、一度インストールしておくとブラウザを閉じても設定は消えません。Go 拡張機能は現状は Web版VSCodeでは使用できません、今後の対応に期待しています。

:bulb: Setting Sync
VSCode の Setting Sync機能を使うと設定がクラウド上に保存される( GitHub もしくは Microsoft のアカウントに保存されます ) ので、別マシンや種類の異なるブラウザ間で設定を共有できます。既に自分色に染まったVSCodeがある方は Setting Sync を活用して、Web版含む新しいVSCode 環境の利用開始時には既存VSCodeの設定を適用するのがおすすめです。

</div></details>

## :eye: 各コントローラーがwatchしているリソースは何か？

コントローラーは自身がwatchするリソースを指定します。その実装箇所を探してみます。

https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.9/html/operators/osdk-golang-implement-controller_osdk-golang-tutorial#osdk-golang-controller-resources_osdk-golang-tutorial

前述のmain()から呼び出されている`SetupWithManager()`を見てみます。
ここで探すと良いキーワードは For()などのwatchするリソースを指定するメソッドです。

:bulb: For()にマウスオーバーしてみると、reconcileするリソースを指定し create/delete/update イベントを受け取れるようにするものであると教えてくれます。どのリソースを監視(watch)するかを設定できるメソッドのようですね。Watch()、Own()などの関連メソッドも見てみると良さそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/0b876a7f-612d-e208-53cb-fcd8999eaba6.png)


以下は 1つめのコントローラー、`gitopsservice_controller.go` の SetupWithManager() です。
For()を見ると、`GitopsService` という Custom Resouce を watchしているようですね。

あ、For()に先立って `GitopsService`オブジェクトをクラスタに作成していますね。このコントローラーがwatchするオブジェクトは OpenShift GitOps Operator インストール後には作成もされているようです( OpenShift GitOps Operator インストール後にはすぐに使える Argo CD がデプロイされているのはこれの仕業なのかと予想します(後で的中と分かる)。 )。

```go
func (r *ReconcileGitopsService) SetupWithManager(mgr ctrl.Manager) error {

	gitopsServiceRef := newGitopsService()
	err := r.Client.Create(context.TODO(), gitopsServiceRef)

	return ctrl.NewControllerManagedBy(mgr).
		For(&pipelinesv1alpha1.GitopsService{}, builder.WithPredicates(pred)).
		Complete(r)
}
```

watchしているリソースに変更が生じ create/delete/update イベントが発生した場合、Reconcile() が呼び出されます。Reconcile()には、コントローラーがクラスタに対して行う create/delete/update 操作が実装されています。

https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.9/html/operators/osdk-golang-controller-reconcile-loop_osdk-golang-tutorial

このコントローラーは、watch対象である `GitopsService`オブジェクトを作成していますので、確実にReconcile()が呼び出されることが予想されます。後で Reconcile() も読んでみましょう。

2つめのコントローラーは `argocd_controller.go` です。
コメントが教えてくれていますが Argo CD の WebUI の Route ( サービスをクラスタ外に公開するOpenShiftのオブジェクト ) の変更を watch して ConsoleLink ( `動機`の章で出てきた ツールバーの `OpenShift GitOps` メニュー ) を 生成するために、Routeリソースを監視するようにしています。

よって、Reconcile() は Routeオブジェクトの変更などのイベントに伴い呼び出され、RouteオブジェクトのURLを元に、ConsoleLink の作成や更新処理のロジックが実装されていることが予想されます。後で見てみましょう。

```go
func (r *ReconcileArgoCDRoute) SetupWithManager(mgr ctrl.Manager) error {
	// Watch for changes to argocd-server route in the default argocd instance namespace
	// The ConsoleLink holds the route URL and should be regenerated when route is updated
	return ctrl.NewControllerManagedBy(mgr).
		For(&routev1.Route{}, builder.WithPredicates(filterPredicate(filterArgoCDRoute))).
		Complete(r)
}
```

3つめのコントローラーは`argocd_metrics_controller.go` です。`ArgoCD` という リソース を watchしていますね。

```go
func (r *ArgoCDMetricsReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&argoapp.ArgoCD{}).
		Complete(r)
}
```

このリソースは、コミュニティ版の Argo CD Operator でメンテンナンスされている Custom Resource です。リソース名が `ArgoCD` ですから、Argo CD の望ましいデプロイ状態を宣言するためのリソースのようです。ドキュメントにもそのように書いてあります。

https://argocd-operator.readthedocs.io/en/latest/reference/argocd/

`ArgoCD`オブジェクトの変更などがトリガとなって、\*_**metrics**_controller.go の Reconcile() が呼び出されるということは、Argo CD のデプロイ状態を元にして、Argo CDのメトリクスに関連するロジックの実装があることが予想されます、これも後で見てみましょう。

最後のコントローラーは、コミュニティ版の Argo CD Operatorに含まれるコントローラー( `argocd_controller.go` )です。Argo CD の望ましいデプロイ状態を宣言するための`ArgoCD`リソース を watchしていますね。このコントローラーは Argo CD のデプロイなど主要な処理を行うと予想しましたが、Reconcile()にそのような実装があるかを後で確認しましょう。

```go
// SetupWithManager sets up the controller with the Manager.
func (r *ReconcileArgoCD) SetupWithManager(mgr ctrl.Manager) error {
	setResourceWatches(bldr, r.clusterResourceMapper, r.tlsSecretMapper, r.namespaceResourceMapper)

	bldr.For(&argoprojv1a1.ArgoCD{}, builder.WithPredicates(deleteSSOPred))
```

### この章のまとめ

ここまでの確認により、各コントローラーがwatchしているリソースが明らかになりました。

|        コントローラー        | watchしているリソース |                  Reconcile()の内容の予想                   |
| ---------------------------- | --------------------- | ---------------------------------------------------------- |
| gitopsservice_controller.go  | GitopsService         | まだ分からんが Argo CD の デプロイに関係ありそう           |
| argocd_controller.go         | Route                 | これを元にConsoleLinkを作成していると予想した              |
| argocd_metrics_controller.go | ArgoCD                | これを元にメトリクス収集していると予想した                 |
| argocd_controller.go         | ArgoCD                | これを元にArgo CD のデプロイなど主要な処理を行うと予想した |

続いて、各コントローラーの Reconcile() を確認します。

## :eye: 各コントローラーのクラスタに対する操作は何か？

Reconcile()に実装されている、watchしているリソースに変更が生じた際のロジックを確認します。
ここで着目すると良いキーワードは コントローラーが Reconcile() の中で呼び出している Get() と、Create()/Delete()/Update() です。

Get() でクラスタの現在の状態を取得し、クラスタを望ましい状態に調整する必要があると判定した場合、クラスタに対する操作として、 Create()/Delete()/Update() を行います。コントローラーのお仕事はこれらの呼び出しです。

### gitopsservice_controller.go

まずは、`gitopsservice_controller.go` の Reconcile() を見てみます。先の確認により、このコントローラーがwatchしているリソースは`GitopsService`、かつ、そのオブジェクトも作成しているため、Reconcile() が呼ばれます。

Reconcile() は、`GitopsService`オブジェクトがクラスタにある場合( あります ) 、`ArgoCD`オブジェクトをクラスタに作成するロジックとなっていました。

```go
func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {

			err = r.Client.Create(context.TODO(), defaultArgoCDInstance)
```

このことをもって、ついに OpenShift GitOps 主導のリソースである`GitopsService`オブジェクトと Argo CD コミュニティ主導のリソースである`ArgoCD`オブジェクトの関係性が分かりました。

`GitopsService`リソースは、OpenShift 特有のデプロイ方法などを踏まえて Argo CD をどのようにデプロイしたいかを宣言できます ( 例として、インフラストラクチャーノードにデプロイするための設定 [runOnInfra](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.9/html/cicd/_configuring-argo-cd-to-recursively-sync-a-git-repository-with-your-application#run-argo-cd-instance-on-cluster_configuring-an-openshift-cluster-by-deploying-an-application-with-cluster-configurations) などがあります )。

:ship: このコントローラーは、OpenShift 特有のデプロイ先ノード設定などを踏まえて Argo CD をどのようにデプロイしたいかを宣言している `GitopsService`オブジェクトの内容に従って Argo CD の望ましいデプロイ状態を宣言できる`ArgoCD`オブジェクトをクラスタに作成してくれることがわかりました。コミュニティ版の Argo CD Operator で提供されない、OpenShift 向けのデプロイを行えるようにしてくれるコントローラーであると断定して良さそうですね。

### argocd_controller.go

続いて、`argocd_controller.go` の Reconcile() を見てみます。このコントローラーがwatchしているリソースは`Route`でした。
Reconcile()では、ConsoleLinkオブジェクトがクラスタになければRouteのURLを元に生成し、ある場合はURLを更新するロジックとなっていました。

```go
func (r *ReconcileArgoCDRoute) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {

	consoleLink := newConsoleLink(argocCDRouteURL, "Cluster Argo CD")

			return reconcile.Result{}, r.Client.Create(ctx, consoleLink)

		return reconcile.Result{}, r.Client.Update(ctx, found)
}
```

:ship: このコントローラーは、OpenShift の WebUI のツールバーの `OpenShift GitOps` メニューにArgo CD の WebUI へのリンクを表示してくれるコントローラーと言えます。これも OpenShift にデプロイした Argo CD を使う際には欲しい機能ですね。

### argocd_metrics_controller.go

3つめは`argocd_metrics_controller.go` です。このコントローラーがwatchしているリソースは`ArgoCD`でした。
Reconcile()では、ArgoCDリソースの情報を元に、3つのServiceMonitorオブジェクト と 1つの PrometheusRuleオブジェクト をクラスタに作成していました。これらは [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator) が取り扱うリソースです。

```go
	// Create ServiceMonitor for ArgoCD application metrics
	serviceMonitorLabel := fmt.Sprintf("%s-metrics", request.Name)
	serviceMonitorName := request.Name
	err = r.createServiceMonitorIfAbsent(request.Namespace, argocd, serviceMonitorName, serviceMonitorLabel, reqLogger)

	// Create ServiceMonitor for ArgoCD API server metrics
	serviceMonitorLabel = fmt.Sprintf("%s-server-metrics", request.Name)
	serviceMonitorName = fmt.Sprintf("%s-server", request.Name)
	err = r.createServiceMonitorIfAbsent(request.Namespace, argocd, serviceMonitorName, serviceMonitorLabel, reqLogger)

	// Create ServiceMonitor for ArgoCD repo server metrics
	serviceMonitorLabel = fmt.Sprintf("%s-repo-server", request.Name)
	serviceMonitorName = fmt.Sprintf("%s-repo-server", request.Name)
	err = r.createServiceMonitorIfAbsent(request.Namespace, argocd, serviceMonitorName, serviceMonitorLabel, reqLogger)

	// Create alert rule
	err = r.createPrometheusRuleIfAbsent(request.Namespace, argocd, reqLogger)
```

ServiceMonitor は、監視対象のサービス( Promethues形式のメトリクスを公開しているサービス )を指定できるリソースです。確かに以下の名前でServiceMonitorが作成されていました。上記の%s(request.Name) は `openshift-gitops` のようですね。

```bash
[root@bastion ocp]# oc get ServiceMonitor -n openshift-gitops
NAME                           AGE
openshift-gitops               3h57m
openshift-gitops-repo-server   3h57m
openshift-gitops-server        3h57m
[root@bastion ocp]# oc describe ServiceMonitor -n openshift-gitops | grep "Match Labels" -A 1
    Match Labels:
      app.kubernetes.io/name:  openshift-gitops-metrics
--
    Match Labels:
      app.kubernetes.io/name:  openshift-gitops-repo-server
--
    Match Labels:
      app.kubernetes.io/name:  openshift-gitops-server-metrics
[root@bastion ocp]#
```

OpenShift には、Prometheus Operatorが[デフォルトでデプロイ](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.9/html/monitoring/understanding-the-monitoring-stack#understanding-the-monitoring-stack_understanding-the-monitoring-stack)されており、WebUI でメトリクスを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/800fe501-ecd3-781e-2121-9790b0226ae1.png)

PrometheusRule はアラートの設定ができるリソースです。以下のように作成されていました。

```bash
[root@bastion ocp]# oc get PrometheusRule -n openshift-gitops
NAME                            AGE
gitops-operator-argocd-alerts   3h59m
[root@bastion ocp]# oc describe PrometheusRule -n openshift-gitops gitops-operator-argocd-alerts
Name:         gitops-operator-argocd-alerts
Namespace:    openshift-gitops
Labels:       <none>
Annotations:  <none>
API Version:  monitoring.coreos.com/v1
Kind:         PrometheusRule
(略)
Spec:
  Groups:
    Name:  GitOpsOperatorArgoCD
    Rules:
      Alert:  ArgoCDSyncAlert
      Annotations:
        Message:  ArgoCD application {{ $labels.name }} is out of sync
      Expr:       argocd_app_info{namespace="openshift-gitops",sync_status="OutOfSync"} > 0
      Labels:
        Severity:  warning
Events:            <none>
[root@bastion ocp]#
```

こちらもOpenShiftの WebUI でも確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/f42ee740-3abd-2dbf-a2d2-e6f65bdd73f9.png)

:ship: このコントローラーは、Prometheus Operatorが取り扱えるリソースを生成することにより、Argo CD のメトリクスが収集され、OpenShiftのUIで確認できるようにしてくれるコントローラーと言えます。これも OpenShift にデプロイしたArgo CD を使う際には欲しい機能ですね。


### argocd_metrics_controller.go

最後のコントローラー、コミュニティ版の Argo CD Operatorに含まれるコントローラーです。このコントローラーがwatchしているリソースは`ArgoCD`です。
Reconcile()は、Argo CD の望ましいデプロイ状態を宣言できる`ArgoCD`オブジェクトの内容に従ってクラスタの状態を調整する実装となっており、Deployment, Service, Role など様々なオブジェクトを生成しています。以下は Deployment に関するコードのみ抜粋しましたが、実際は様々なリソースのCreate()などの処理が並んでいます。

```go
func (r *ReconcileArgoCD) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {

	if err := r.reconcileResources(argocd); err != nil {

}

func (r *ReconcileArgoCD) reconcileResources(cr *argoprojv1a1.ArgoCD) error {

	if err := r.reconcileDeployments(cr); err != nil {

}

func (r *ReconcileArgoCD) reconcileDeployments(cr *argoprojv1a1.ArgoCD) error {

	err = r.reconcileServerDeployment(cr)

}

func (r *ReconcileArgoCD) reconcileDeployments(cr *argoprojv1a1.ArgoCD) error {

	return r.Client.Create(context.TODO(), deploy)

}
```

:ship: このコントローラーは、他3つのコントローラーとは異なり、コミュニティ版の Argo CD Operatorに含まれるコントローラーです。Argo CD のデプロイなど主要な処理を行っており、このコントローラーの処理に関しては OpenShift GitOps Operator を使って Argo CD を利用する場合でも、コミュニティ版の Argo CD Operator を使った場合と同じロジックとなるはずです。

## Operator をデバッグできるようにする

ソースリーディングするために必須ではありませんが、デバッグ環境を整えることにより、任意の行でブレークポイントで止めて変数の値を見たり、いわゆるprintfデバッグができるようになったりなど、前述したソースリーディングの結果と、実際の検証結果の両方を突き合わせることにより、確度の高い実証ができるため、理解も深まります。

:bulb: やっぱりVSCodeが便利です。以下はブレークポイントで止めて、変数の値を確認している様子です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/985a6f58-6393-3e98-ba5a-c7c18df95f62.png)

また、様々な試行により環境が汚れてしまうと検証結果に影響が生じる可能性があることから、気軽に消して新規で立ち上げ治せる検証環境もあると便利です。
OpenShiftには、[CRC ( Red Hat CodeReady Containers )](https://developers.redhat.com/products/codeready-containers/overview)というローカルにクラスタを構築できるツールがあり、`crc start` で新規クラスタを立ち上げたり、様々な試行の繰り返しにより汚れてしまったクラスタは `crc delete`でさっぱりと削除できます( kubernetes における Minikube等 と同様の位置付け )。

https://developers.redhat.com/products/codeready-containers/overview

## まとめ

「OpenShift GitOps Operator は 何をしているのか」 について以下のアプローチで迫ってみました。

- この Operator にコントローラーは何個あるのか？
- 各コントローラーがwatchしているリソースは何か？
- 各コントローラーのクラスタに対する操作( create/delete/update )は何か？

ソースリーディング、Operatorによりクラスタに作成されたリソース、動作ログの確認により、以下が分かりました。

コントローラーは4つありました。
1つはコミュニティ版のArgo CD Operatorに含まれるコントローラーであり、Argo CD のデプロイなど主要な処理が実装されていました。

残り3つのコントローラーは コミュニティ版のArgo CD Operator にはなく、OpenShift向けに Argo CD を便利に利用するために以下の機能が実装されていました。

1. デプロイ先ノードの指定など OpenShift 特有のデプロイ宣言に対応
1. Argo CD WebUI へのリンクを OpenShift の WebUI に表示
1. OpenShift に組み込まれているモニタリング機能で Argo CD のメトリクスをモニタリング

本記事が、Operator や コントローラーの実装調査や検証手順の例として、また、OpenShiftならではの機能の例を知る一旦となりましたら幸いです。