Flux OCI Artifactsサポートを利用したGitOpsについて（ハンズオン編）

Fujitsu Advent Calendarの3日目です。2日目と同じテーマであるFluxによるGitOpsについて書きたいと思います。昨日のブログでは、ハンズオン部分が間に合わなかったので3日目も空いていることですし、記事を [概要編](https://qiita.com/nekia/items/3d26b3e51d705d5890bc) と ハンズオン編 で分けてみました。

* 昨日の概要編はこちら
    * [祝CNCF Graduated project👏Flux OCI Artifactsサポートを利用したGitOpsについて（概要編）](https://qiita.com/nekia/items/3d26b3e51d705d5890bc)


----
# ハンズオンで実現するGitOps環境

実現する環境は以下の要素で構成されます：

* Github上のリポジトリ
* GitHub ActionによるCIパイプライン
* Github上のコンテナレジストリ（OCIレジストリ）
* k8sクラスタ上のFlux

Github上のリポジトリへのコミットによってCIパイプラインがトリガーされ、OCI ArtifactsをOCIレジストリに格納するジョブが実行されます。k8sクラスタ上のFluxがOCI Artifactsのハッシュが更新されたことを検出して、OCI Artifactsに含まれるマニフェストを取得し、クラスタに適用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/130aa7ad-8c79-63c9-a690-6c05ac51395a.png)（出典：[OCI cheatsheet | Flux](https://fluxcd.io/flux/cheatsheets/oci-artifacts/)）

最終的には以下のような構成を作り上げていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/468c69d5-4982-118d-d946-ad1c997e0211.png)

それでは１つずつ見ていきます。

# k8sクラスタの作成

まずはローカルにシングルノードクラスタを立ち上げます。

```
$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.25.3) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Thanks for using kind! 😊
```

# GitOpsのSourceとなる空のGitリポジトリを作成する

Github上に空のリポジトリを作成します。ここにFluxの設定に関するマニフェストをこの後格納していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/179521aa-ed65-8745-9304-2cef7b277226.png)

この段階では上記のような構成になります。

# FluxのVSCode拡張機能を使ってクラスタ上にFlux CRDsをセットアップする

先ほど作成したGitリポジトリをクローンして、VSCodeで開きます。

```
$ git clone https://github.com/nekia/qiita-podinfo
$ cd qiita-podinfo
$ code .
```

VSCodeに拡張機能`GitOps Tools for Flux`を追加します。ここで1点注意が必要です。デフォルトでインストールされるバージョンではなく、プレリリース版（執筆時点では`v0.22.1668822354`）をインストールしないと、私の環境だとうまく動作しませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/58e9d3b8-0bd8-68b0-f3ef-b1a47e01d2a4.png)

* 拡張機能のページに満たす必要のある依存関係がきさいしてあるので別途対応が必要です（kubectl/flux/gitコマンドのインストール）

拡張機能をインストールするとサイドメニューにGitOpsのアイコンが現れるので、クリックしてGitOps Viewに移動します。ここで正しくセットアップできていると以下のように、先に生成したk8sクラスタ `kind-kind` が検出されています。ここから `SOURCES`もしくは`WORKLOADS`ウィンドウ内の `Enable GitOps`をクリックし、クラスタにFluxの各種CRDをインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/f7293190-bfab-4251-91d0-c21728429800.png)
これによりクラスタ内にGitOpsに必要な各種コントローラがインストールされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/885083b6-6dd1-740f-0a57-451f099eb385.png)

# GitOps Toolkitに対してターゲットとなるソースリポジトリとデプロイメント方法を設定

ここではPodinfoという既存のサンプルアプリ(https://github.com/stefanprodan/podinfo) をターゲットにしてみます。クラスタへのGitOps Toolkitのインストールで追加された`GitRepository`というリソースを生成しまが、その際にこのターゲットを指定します。アプリのデプロイメント方法に関しては、指定したリポジトリ内にある`Kustomizations`のマニフェストファイルへのパスを指定します（`./kustomize`）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/14319626-90bb-df2e-b238-1bd627b5fa59.png)

上記の設定で `+ Create` をクリックするとターゲットのGitリポジトリとのReconcileが開始され、しばらくするとサービスがクラスタ上で動作していることが確認できます。

```bash
$ kubectl port-forward svc/podinfo 9898:9898
Forwarding from 127.0.0.1:9898 -> 9898
Forwarding from [::1]:9898 -> 9898
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/e8f8ac86-aa7b-02b9-81de-7faa284c4118.png)

この時点での構成は以下のようになっています。ご覧頂くと分かる通り、アプリを構成するマニフェストはローカルのみに存在してGitOpsと呼べる状態になっていません（Podinfo自体はGitリポジトリから参照していますが、これはあくまでRead OnlyなUpstreamのGitリポジトリをサンプルアプリとして利用しているためGitOpsには該当しません）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/04425edd-a108-c479-ad3a-4f1f268688cf.png)

次のステップでは、現在ローカルのみに存在しているマニフェストをGitリポジトリにPushし、それらをOCI ArtifactsとしてOCIレジストリに登録するCIパイプラインを作成して、クラスタからOCI経由で同期するように変更していきます。

なお、ここまでで作成した Sources と Workloads は一旦削除します。

# マニフェストファイルとOCIレジストリとの同期設定をGitリポジトリにPushする

まずは先ほどと同じ設定で、SourceとKustomizationをVSCode拡張機能から作成し、今度は `YAML` を選択してマニフェストファイルをエクスポートします。

```YAML
# cluster/demo/app.yaml
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: nekia-qiita-podinfo-main
  namespace: flux-system
spec:
  gitImplementation: go-git
  interval: 1m0s
  ref:
    branch: master
  url: https://github.com/stefanprodan/podinfo

---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: podinfo
  namespace: flux-system
spec:
  interval: 1m0s
  path: ./kustomize
  prune: true
  sourceRef:
    kind: GitRepository
    name: nekia-qiita-podinfo-main
  targetNamespace: default
```

これを作成したGitリポジトリの `cluster/demo/app.yaml` にPushします。このマニフェストは、この後作成するCIパイプラインにて、OCI ArtifactsとしてGithub Container Registryに格納されるファイルになります。

次に、OCIレジストリからマニフェストを含むOCI Artifactsを取得する設定と、そのマニフェストを使ってデプロイメントする設定を作成していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/77de765b-5774-c28a-6770-aeee96df5d43.png)

これも `YAML` を選択してマニフェストファイルをエクスポートし、Gitリポジトリの`cluster/demo/sync.yaml` にPushします。

# OCIレジストリをターゲットにした同期をローカルからBootstrapする

この段階でGitOpsの同期を開始します。設定がまだ足りておらずエラーが出るので１つずつ対処していきます。Bootstrap方法は以下の通り、先ほどPushした`cluster/demo/sync.yaml`をローカルでクラスタに適用します。OCIレジストリ上のOCI Artifacts（この後のステップで作成。app.yamlとsync.yamlを含む）との同期をBootstrapします。一度Bootstrapしてしまえば、以降はGitリポジトリへの変更のたびに更新されるOCI ArtifactsをソースとしたGitOps構成ができあがります。

```bash
$ kubectl apply -f cluster/demo/sync.yaml
ocirepository.source.toolkit.fluxcd.io/nekia-qiita-oci-podinfo-main created
kustomization.kustomize.toolkit.fluxcd.io/oci-podinfo created
```

この時点での構成は以下のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/8c07162b-2486-30d5-3bad-c56363b5f6c2.png)

# Github Container Registryへアクセスするためのシークレット作成

同期を開始すると新たに `OCIRepository`が見えるようになりますが、作成に失敗していることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/0005eab3-f2ea-41cb-218b-ab48be0eafd4.png)
設定で指定したシークレット`ghcr-secret`を作成してやる必要がありますので以下のコマンドで作成します。GITHUB_USERとGITHUB_TOKENは別途取得、設定が必要です。

```bash
$ flux create secret oci ghcr-secret --url=ghcr.io --username=$GITHUB_USER --password=$GITHUB_TOKEN
► oci secret 'ghcr-secret' created in 'flux-system' namespace
$ kubectl get secrets -n flux-system
NAME          TYPE                             DATA   AGE
ghcr-secret   kubernetes.io/dockerconfigjson   1      7m43s
```

シークレットを設定すると`OCIRepository`のエラーが `AuthenticationFailed`から`OCIArtifactPullFailed`に変わります。まだGithub Container Registry上に`sync.yaml`で指定したOCI Artifacts `nekia/manifests/podinfo`が存在しないため、Pullに失敗しています。次のステップでGitHub上にGitHub Actionを使ったCIパイプラインを追加してOCI Artifactsをレジストリに追加していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/74190ab3-4d7a-874d-7d7c-86aee7385bd2.png)

# CIパイプラインでOCI Artifactsをレジストリに格納する

Github Actionについては、公式ドキュメントでサンプルが紹介されており、その設定を参考にします。
* [flux2/action at main · fluxcd/flux2](https://github.com/fluxcd/flux2/tree/main/action)

```yaml
name: push-artifact-staging

on:
  push:
    branches:
      - 'main'

permissions:
  packages: write # needed for ghcr.io access

env:
  OCI_REPO: "oci://ghcr.io/nekia/manifests/podinfo"
  MANIFEST_PATH: "./cluster/demo"

jobs:
  kubernetes:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Flux CLI
        uses: fluxcd/flux2/action@main
      - name: Login to GHCR
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      # - name: Generate manifests
      #   run: |
      #     kustomize build ./manifests/staging > ./deploy/app.yaml
      - name: Push manifests
        run: |
          flux push artifact $OCI_REPO:$(git rev-parse --short HEAD) \
            --path=$MANIFEST_PATH \
            --source="$(git config --get remote.origin.url)" \
            --revision="$(git branch --show-current)/$(git rev-parse HEAD)"
      - name: Deploy manifests to staging
        run: |
          flux tag artifact $OCI_REPO:$(git rev-parse --short HEAD) --tag latest
```

このファイルをリポジトリの `.github/workflows`配下にPushするとCIパイプラインが作成され、早速PushをトリガーにしてCIジョブがスタートし、以下の通りGithub Container RegistryにOCI Artifactsが格納されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/58a8c8e2-f013-8b46-7a31-33d793571520.png)

なおCIジョブによってGithub Container Registry上に作成されたOCI Artifactsは以下のコマンドで中身を確認可能です。

```
$ flux pull artifact oci://ghcr.io/nekia/manifests/podinfo:latest --output ./tmp/
► pulling artifact from ghcr.io/nekia/manifests/podinfo:latest
✔ source https://github.com/nekia/qiita-podinfo
✔ revision main/c43f6a46e67730359a6f2d4db9522f94d20308ff
✔ digest ghcr.io/nekia/manifests/podinfo@sha256:0084c529dad5632a7972a0ac18ac447da37db9593ae8a86ba606bb1e12e2d088
✔ artifact content extracted to ./tmp/
$ find ./tmp/ -type f
./tmp/cluster/demo/app.yaml
./tmp/cluster/demo/sync.yaml
```


この状態でOCIRepositoryの同期状態を再確認すると、同期に成功しているはずです。合わせてOCI Artifactsから取得されたマニフェスト`app.yaml`によって`GitRepository`および`Kustomization: podinfo`も作成されているはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/06cbdeee-1aa0-15dc-e7f2-f46a26717589.png)

前述した手順と同じようにServiceをPort Forwardすると、http://localhost:9898/ からPodInfoアプリにアクセスできるはずです。この段階で、本記事の冒頭に載せていた構成が完成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/468c69d5-4982-118d-d946-ad1c997e0211.png)

# PodInfoアプリのバージョンを変更する

`cluster/demo/app.yaml`内で指定しているアプリのバージョンを `6.2.3` から `6.2.2`に変更してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/93fceda4-a259-f617-4f18-bb185b7f797e.png)

Gitレポジトリの変更を検出して新たなバージョンのアプリケーションが自動でデプロイされることが以下の通り確認できると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39615/903262f8-7488-0cd6-04f2-543a2eb17a4e.png)

# Fluxで管理されるリソースの由来をトレースする

例えば今回動かしているPodInfoアプリがどのバージョンで、どのマニフェストに基づいてデプロイメントされているのか、を確認したい場合には以下の手順でトレースが可能です。flux CLIの `trace` コマンドで指定リソースの由来を追跡することが可能です。

```
$ flux trace -n default deployment podinfo

Object:          Deployment/podinfo
Namespace:       default
Status:          Managed by Flux
---
Kustomization:   podinfo
Namespace:       flux-system
Target:          default
Path:            ./kustomize
Revision:        6.2.2/1cf228c67b8f8102db1a6966f81a95c09954b3e8
Status:          Last reconciled at 2022-12-04 22:07:43 +0900 JST
Message:         Applied revision: 6.2.2/1cf228c67b8f8102db1a6966f81a95c09954b3e8
---
GitRepository:   podinfo
Namespace:       flux-system
URL:             https://github.com/stefanprodan/podinfo
Tag:             6.2.2
Revision:        6.2.2/1cf228c67b8f8102db1a6966f81a95c09954b3e8
Status:          Last reconciled at 2022-12-04 22:02:09 +0900 JST
Message:         stored artifact for revision '6.2.2/1cf228c67b8f8102db1a6966f81a95c09954b3e8'
```

`https://github.com/stefanprodan/podinfo`の`6.2.2/1cf228c67b8f8102db1a6966f81a95c09954b3e8`から取得した`GitRepository`に由来することが分かります。更に`GitRepository`リソース`podinfo`のラベルをチェックすると`kustomize`リソース`oci-podinfo`に由来することが読み取れます。

```
$ kubectl describe gitrepositories.source.toolkit.fluxcd.io -n flux-system podinfo | head -n 10
Name:         podinfo
Namespace:    flux-system
Labels:       kustomize.toolkit.fluxcd.io/name=oci-podinfo
              kustomize.toolkit.fluxcd.io/namespace=flux-system
Annotations:  <none>
API Version:  source.toolkit.fluxcd.io/v1beta2
Kind:         GitRepository
Metadata:
  Creation Timestamp:  2022-12-04T01:46:08Z
  Finalizers:
```
次に`kustomize`リソース`oci-podinfo`の詳細を確認すると、`OCIRepository`リソース`nekia-qiita-oci-podinfo-main`が由来であることが更に分かります。
```
$ kubectl get kustomizations.kustomize.toolkit.fluxcd.io -n flux-system oci-podinfo -o jsonpath='{.spec}' | jq
{
  "force": false,
  "interval": "30s",
  "path": "./",
  "prune": true,
  "sourceRef": {
    "kind": "OCIRepository",
    "name": "nekia-qiita-oci-podinfo-main"
  },
  "targetNamespace": "flux-system"
}
```
最後に`OCIRepository`リソース`nekia-qiita-oci-podinfo-main`の詳細を確認すると、`oci://ghcr.io/nekia/manifests/podinfo:latest`のOCI Artifactsを参照していることが分かり、そのOCI Artifactsは`https://github.com/nekia/qiita-podinfo`リポジトリの`main/c43f6a46e67730359a6f2d4db9522f94d20308ff`から作成されていることが分かります。長かった。。。
```
{
  "interval": "30s",
  "provider": "generic",
  "ref": {
    "tag": "latest"
  },
  "secretRef": {
    "name": "ghcr-secret"
  },
  "timeout": "60s",
  "url": "oci://ghcr.io/nekia/manifests/podinfo"
}
{
  "checksum": "6941be0504e583be9b631d3a06ae3d7ae28bc05c9661b0aa0c6cd9d3e8d8b5d4",
  "lastUpdateTime": "2022-12-04T13:02:08Z",
  "metadata": {
    "org.opencontainers.image.created": "2022-12-04T13:01:33Z",
    "org.opencontainers.image.revision": "main/c43f6a46e67730359a6f2d4db9522f94d20308ff",
    "org.opencontainers.image.source": "https://github.com/nekia/qiita-podinfo"
  },
  "path": "ocirepository/flux-system/nekia-qiita-oci-podinfo-main/0084c529dad5632a7972a0ac18ac447da37db9593ae8a86ba606bb1e12e2d088.tar.gz",
  "revision": "latest/0084c529dad5632a7972a0ac18ac447da37db9593ae8a86ba606bb1e12e2d088",
  "size": 532,
  "url": "http://source-controller.flux-system.svc.cluster.local./ocirepository/flux-system/nekia-qiita-oci-podinfo-main/0084c529dad5632a7972a0ac18ac447da37db9593ae8a86ba606bb1e12e2d088.tar.gz"
}
```

* **本来は`flux trace`コマンドでOCI Artifactまで辿れるはずなのですが今回作成した環境だとうまくいかず、コミュニティに質問中です。**


# 最後に

アプリのGitレポジトリとマニフェストのGitレポジトリが出てきて分かりにくい部分があったかと思います。私はおうちK8sクラスタ環境でFluxを使っていますが、今回のOCI Artifactsサポートで解決するようなチャレンジを抱えるほど使い込んでいないため、より理解しやすいデモを共有できませんでしたが、少しでもFluxにでもGitOpsにでも興味持って試してみたいと思っていただけたら幸いです。

---

# 参考URL

概要編にも載せましたが再掲しておきます。

* [今回のデモリポジトリ](https://github.com/nekia/qiita-podinfo)
* [OCI cheatsheet | Flux](https://fluxcd.io/flux/cheatsheets/oci-artifacts/)
* [OCI Repositories | Flux](https://fluxcd.io/flux/components/source/ocirepositories/)
* [go-containerregistry/crane.md at main · google/go-containerregistry](https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane.md)
* [OCI Artifacts Explained. Are they real? Kind of! | by Dan Lorenc | Medium](https://dlorenc.medium.com/oci-artifacts-explained-8f4a77945c13)
* [OCI Artifacts and a View of the Future – Steve Lasker](https://stevelasker.blog/2019/08/25/oci-artifacts-and-a-view-of-the-future/)
* [flux2/rfcs/0003-kubernetes-oci at main · fluxcd/flux2](https://github.com/fluxcd/flux2/tree/main/rfcs/0003-kubernetes-oci)
* [Flux Security & Scalability using VS Code GitOps Extension - YouTube](https://www.youtube.com/watch?v=Bmh7kKYLIhY)
* [Flux’s Security & Scalability with OCI & Helm (Part 2) with Kingdon Barrett - YouTube](https://www.youtube.com/watch?v=uRiCRTSkPOQ)