Flux+GitLabでGitOpsを試す


先日、GitLabがFluxとの統合を[発表した](https://about.gitlab.com/blog/2023/02/08/why-did-we-choose-to-integrate-fluxcd-with-gitlab/)。まだ具体的な機能は見当たっていないが、[Flux(beta)](https://docs.gitlab.com/ee/user/clusters/agent/gitops/flux.html)というページもあり、近いうちにGitLabからいい感じでFluxが使えるようになるのだと思われる。
このメモはその来たるべき未来に備えて、Flux+GitLabを使ってGitOpsを試した時のもの。
なお、GitLabオフィシャルのチュートリアルは[こちら](https://docs.gitlab.com/ee/user/clusters/agent/gitops/flux_tutorial.html)。

## Fluxとは
[Flux](https://fluxcd.io/)はKubernetes向けのContinuos Deliveryツールで、CNCFのプロジェクトにもなっている。
CNCFの[Graduated Project](https://www.cncf.io/projects/)であるため、ある程度成熟されていることはCNCFからもお墨付きである。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/69fc0705-5c5e-8295-33b3-68e59430eed1.png">

よく比較される対象として[ArgoCD](https://argoproj.github.io/cd/)があるが、ArgoCDはリッチなUIやOIDC連携など機能的には豊富であるが独自拡張している面が強く、Fluxはk8sの標準リソースを中心にGitOpsを実現しており、軽さやシンプルさではFluxが勝ると思われる。
なお、Tanzu Mission Control(TMC)のContinuosDelivery機能でもFluxを採用している（確認した際の記事は[こちら](https://qiita.com/ipppppei/items/8835db7fa45d572c5ced)）。TMCのCD機能とこのメモでやってることは大体同じだが、TMCの方がGUIで設定可能で導入の難度は低い。

## Fluxのインストール
FluxCDのkubernetesクラスタへの導入はHelmでもyamlでもなく、fluxコマンドで行う。
最初にコマンドをインストールする。インストール方法は[こちら](https://fluxcd.io/flux/installation/#install-the-flux-cli)。
自環境はMacなのでbrewでinstallする。
```
brew install fluxcd/tap/flux
```
補完機能を入れる人は以下も実施する。
```
echo "source <(flux completion bash)" >> ~/.bashrc
. ~/.bashrc
```

次にFluxがGitLabにアクセスするためのPersonal Access Tokenを発行する。参考にしたドキュメントは[この辺り](https://fluxcd.io/flux/installation/#gitlab-and-gitlab-enterprise)になる。
適当にアカウントを作成し(ここではflux-testとした)、apiに対して読み書きを許可したTokenを発行する。
![1679655622953.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/0689b1e5-a4a4-c03f-7ab4-69be5ca7dbd8.png)
ここで発行したTokenはFluxの設定をGitリポジトリに格納するために使う。
発行したアクセスTokenを環境変数に設定する。
```
export GITLAB_TOKEN="xxxxxxxxxxxxxxx"
```

次にFluxの設定を書き込むリポジトリを用意する。空のリポジトリを作成し、先程作成したユーザ(flux-test)が参照できるように権限設定する。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/5e17cac8-832a-95c8-a59a-3fb55dd12ac2.png">

準備が出来たらインストールをfluxコマンドで実施する。
```
flux bootstrap gitlab \
  --hostname=gitlab.gitlab.hogeeee.info \
  --owner=myapp \
  --repository=flux-config \
  --branch=main　\
  --token-auth
```
オプションは以下の意味となる。
- `--hostname`：GitLabのURL
- `--owner`：GitLabのユーザ名かグループ名(上の実行例ではグループ名を指定）
- `--repository`：リポジトリ名
- `--branch`：ブランチ名
- `--token-auth`：同期する際にSSHキーではなくPersonalAccessTokenで同期する
- `--path`：リポジトリのパス（サブグループ等直下にない場合に指定する）\
※今回は`--path`は未使用。

インストールに成功すると、指定したリポジトリ内にファイルが作成される。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/92cd67d0-832c-35ba-1e1b-cd31aa3f4abd.png">
`gotk-components.yaml`を見るとFluxがデプロイするコンポーネントに関するManifestであることが分かる。
また、以下のコンポーネントがクラスタに展開される。
```
$ kubectl get pod -n flux-system
NAME                                       READY   STATUS    RESTARTS   AGE
helm-controller-fcfbdb98f-hmvn7            1/1     Running   0          12m
kustomize-controller-644db84bbb-mtlq8      1/1     Running   0          12m
notification-controller-6766ff88ff-wxgjf   1/1     Running   0          12m
source-controller-5c596db589-9w7j9         1/1     Running   0          12m
```

インストールされたコンポーネントは以下のような関係性になっている。（画像は[公式サイト](https://fluxcd.io/flux/components/)より引用）

![1679796910648.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/84e41d72-6c39-024e-9459-838194c3d77c.png)

各コンポーネントの役割は以下である（多分）。
- Helm Controller：HelmChartの管理や各種動作の自動実行
- Kustomize Controller：k8sクラスタに[Kustomize](https://kustomize.io/)でManifestを適用する
- Notification Controller：各種ソースからの通知を受け取って各コントローラにハンドリングする
- Source Controller：各種ソース(Helm, Git等)の認証管理や変更検知

## GitOpsの確認
次にGitに格納したManifestがデプロイできるかを確認する。

Manifestを格納するリポジトリを作成し、Cloneする。
```
git clone https://gitlab.gitlab.hogeeee.info/myapp/flux-demo-manifest.git
```
サンプルとなるManifestを格納する。kubectl-neatプラグインを使っているが、入れていない人は省いてリダイレクトしてもらってOK。
```
cd flux-demo-manifest
kubectl create deploy nginx --image nginx --replicas 1 --dry-run=client -o yaml | kubectl neat -f - > ./nginx-deploy.yaml
git add *
git commit -m "initial commit"
git push origin main
```

次にデプロイ用のTokenを作成する。リポジトリの`Settings`->`Deploy tokens`から`read_repository`の権限をつけて作成する。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/fb760c49-10f5-8e61-82b9-ef9f13a7aa4f.png">
  

作成後、表示されたUsernameとTokenを控えておく。
次に、flux-demoというNamespaceを作成し、そこにFluxがGitを見に行くのに使うTokenを登録する。
```
kubectl create ns flux-demo
flux create secret git flux-deploy-authentication \
         --url=https://gitlab.gitlab.hogeeee.info/myapp/flux-demo-manifest \
         --namespace=flux-demo \
         --username="gitlab+deploy-token-5" \
         --password="xxxxxxxxx"
```
`--username`と`--password`は先程GitLabで作成したDeployトークンのユーザ名とTokenを設定する。

次にFluxが見に行くGitのリポジトリ情報を登録する。
```yaml
cat << EOF > ./git-repo.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: web-app-manifests
  namespace: flux-demo
spec:
  interval: 1m0s
  ref:
    branch: main
  secretRef:
    name: flux-deploy-authentication
  url: https://gitlab.gitlab.hogeeee.info/myapp/flux-demo-manifest
EOF
kubectl apply -f ./git-repo.yaml
```
先程作成したSecretを見に行くので、Secretと同じNamespaceにデプロイする。
最後に、Kustomizationを作成して、実際にGitに格納したManifestをデプロイする。
```yaml
cat << EOF > ./kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: nginx-source-kustomization
  namespace: flux-demo
spec:
  interval: 1m0s
  path: ./
  prune: true
  sourceRef:
    kind: GitRepository
    name: web-app-manifests
    namespace: flux-demo
  targetNamespace: flux-demo
EOF
kubectl apply -f ./kustomization.yaml
```
暫く待つとPodが作成される。
```
$ kubectl get pod -n flux-demo
NAME                    READY   STATUS    RESTARTS   AGE
nginx-8f458dc5b-pwd2l   1/1     Running   0          61s
```
なお、Kustomizationリソースはデプロイ直後はエラーを出力し、しばらくしてリコンサイルして`ReconciliationSucceeded`に遷移する。イベントは以下のようになっていた。
```
Events:
  Type     Reason                   Age                   From                  Message
  ----     ------                   ----                  ----                  -------
  Warning  ArtifactFailed           106s (x5 over 5m46s)  kustomize-controller  failed to verify archive: computed checksum '918828706d3dda025af38db7a922fc4147a106893ef23950d805c2eefcc920c6' doesn't match provided 'eb37990198ab8bba00ebf28212088df917f5953c00bcb8fd9f3b17be36ef21a3' (check whether file size exceeds max download size)
  Normal   Progressing              46s                   kustomize-controller  Deployment/flux-demo/nginx created
  Normal   ReconciliationSucceeded  46s                   kustomize-controller  Reconciliation finished in 137.13567ms, next run in 1m0s
```
リコンサイルが効いている確認として、deploymentを削除してみる。
```
kubectl delete deploy nginx -n flux-demo
```
Kustomizationのintervalを1分で設定していたので、1分程度待てばdeploymentが再作成されてPodも立ち上がる。
```
$ kubectl get pod -n flux-demo
NAME                    READY   STATUS    RESTARTS   AGE
nginx-8f458dc5b-lfnds   1/1     Running   0          9s
```
次にGit側のManifestにも修正を加えてみる。レプリカ数を3に変更してリポジトリにpushする。
```
vi nginx-deploy.yaml
git add nginx-deploy.yaml
git commit -m "changed replicas"
git push origin main
```
しばらくすると、GitRepositoryリソースの方で新しいrevisionを取得した旨が表示される。
```
$ kubectl describe gitrepositories web-app-manifests -n flux-demo
：（省略）
Events:
  Type    Reason                      Age                   From               Message
  ----    ------                      ----                  ----               -------
  Normal  NewArtifact                 21m                   source-controller  stored artifact for commit 'initial commit'
  Normal  info                        20m (x2 over 21m)     source-controller  Fetched revision: main/269944292b0b955d01d10f5b820e0d1a69039892
  Normal  GitOperationSucceeded       2m44s (x19 over 20m)  source-controller  no changes since last reconcilation: observed revision 'main@sha1:269944292b0b955d01d10f5b820e0d1a69039892'
  Normal  NewArtifact                 104s                  source-controller  stored artifact for commit 'changed replicas'
  Normal  info                        103s                  source-controller  Fetched revision: main/ed5af1bd776d1f4d20a366964637b72f4339181b
```
GitRepositoryリソースで変更をキャッチ後、Kustomizationにも伝搬し、以下のようなイベント出力とともにdeploymentリソースが再デプロイされる。
```
  Normal   ReconciliationSucceeded  8m10s                kustomize-controller  Reconciliation finished in 109.090363ms, next run in 1m0s
  Normal   Progressing              7m10s (x3 over 15m)  kustomize-controller  Deployment/flux-demo/nginx created
  Normal   ReconciliationSucceeded  7m10s                kustomize-controller  Reconciliation finished in 136.902853ms, next run in 1m0s
  Normal   Progressing              3m54s                kustomize-controller  Deployment/flux-demo/nginx configured
```
最終的にPod数も3つになり、Git上の変更を自動で拾ってクラスタに反映していることが確認できる。
```
$ kubectl get pod -n flux-demo
NAME                    READY   STATUS    RESTARTS   AGE
nginx-8f458dc5b-22qf2   1/1     Running   0          5m27s
nginx-8f458dc5b-lfnds   1/1     Running   0          8m43s
nginx-8f458dc5b-zsqbl   1/1     Running   0          5m27s
```

## Gitにpush後の即時反映
CI/CDパイプラインに組み込むことを考えた場合、`GitRepository`、`Kustomization`それぞれのリソースのintervalを待たずにデプロイしたいケースが考えられる。その場合、`GitRepository`に`.spec.suspend`という一時停止のパラメタがあるので、これを`true`->`false`にすることで即時反映が可能となる。

```
 kubectl patch gitrepository --type merge -p "{\"spec\":{\"suspend\":true}}" web-app-manifests -n flux-demo
 kubectl patch gitrepository --type merge -p "{\"spec\":{\"suspend\":false}}" web-app-manifests -n flux-demo
```