ArgoCDのブルーグリーンデプロイ、GitOpsをローカル環境で試す

# はじめに
以前作った、ローカルの k8s で動作するアプリケーションに以下を導入しました。

- ArgoCD Rollouts によるブルーグリーンデプロイ
- ArgoCD にリポジトリを監視してもらい、変更があれば自動でデプロイ（GitOps）

作ったアプリケーションの内容は別の記事で書いてます。
https://qiita.com/pokotyan/items/cfa34bb68fb95444eff0

コードはこちら
https://github.com/pokotyan/k8s-request-counter-emitter

# skaffold、kustomize
skaffold、kustomize を利用しており、こんな感じのディレクトリ構成にしました。
kustomize によりマージされたマニフェストが `k8s/manifest/app/app.yml` として吐き出されるようにしています。

```
├── k8s
│   ├── kustomize
│   │   ├── base
│   │   │   ├── app
│   │   │   │   ├── deployment.yml
│   │   │   │   ├── ingress.yml
│   │   │   │   ├── kustomization.yml
│   │   │   │   ├── nginx-default-configmap.yml
│   │   │   │   ├── nginx-nginx-configmap.yml
│   │   │   │   └── service.yml
│   │   │   └── redis
│   │   │       ├── deployment.yml
│   │   │       ├── kustomization.yml
│   │   │       └── service.yml
│   │   └── overlays
│   │       └── local
│   │           ├── build.sh
│   │           └── kustomization.yml
│   ├── manifest
│   │   └── app
│   │       └── app.yml
│   └── skaffold.yml
```


skaffold でファイルを監視し、ホットリロードするようにしています。

```yml:k8s/skaffold.yml
apiVersion: skaffold/v2beta10
kind: Config
metadata:
  name: app
build:
  artifacts:
    - image: pokoytan/k8s-request-counter-emitter
      context: ./
      docker:
        dockerfile: ./Dockerfile
  tagPolicy:
    sha256: {}
  local:
    push: false
    useBuildkit: true
profiles:
  - name: local
    deploy:
      kustomize:
        paths: ["k8s/kustomize/overlays/local"]
```

# ArgoCD
参考：https://qiita.com/ttr_tkmkb/items/ff4d88f3a91db982b0ab

[ArgoCD のインストール、ArgoCD のパスワード設定](https://argoproj.github.io/argo-cd/getting_started/)をします。

ポートフォワードの設定をして、ArgoCD のGUI にアクセスできるようにします。

```
kubectl port-forward svc/argocd-server -n argocd 8080:443
```

`https://localhost:8080/` にアクセスすると、こんな画面になるので、設定したパスワードでログインします。
![スクリーンショット 2021-02-06 15.27.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/2afecf25-89bf-0239-68c3-499f14fe576f.png)


ArgoCD が対象にするクラスターをローカルの docker-desktop にします。

```
argocd cluster add docker-desktop
```

`argocd app create` を実行して、ArgoCD のアプリケーションを作成し、リポジトリを監視させます。

```
argocd app create k8s-request-counter-emitter \
--repo https://github.com/pokotyan/k8s-request-counter-emitter \
--path k8s/kustomize/overlays/local \
--dest-server https://kubernetes.default.svc \
--dest-namespace default \
--sync-policy automated \
--auto-prune \
--self-heal
```

上記のコマンドで、 ArgoCD が以下のような監視、自動デプロイをやってくれるようになります。

このリポジトリの
`--repo https://github.com/pokotyan/k8s-request-counter-emitter`
このパスにあるマニフェストに
`--path k8s/kustomize/overlays/local`
何か変化があれば、自動でデプロイする
`--sync-policy automated`


`argocd app create` が完了すると、 ArgoCD により ローカル環境へアプリケーションがデプロイされます。
GUI 上でもアプリケーションが追加されており、リポジトリとの同期状態や動作状態がわかるようになります。

![screencapture-localhost-8080-applications-k8s-request-counter-emitter-2021-02-06-15_33_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/ce4afaf8-18cf-7657-07cd-9b768e67339c.png)

## いろんな方法がある ArgoCD のアプリケーション作成

上記では、 `argocd app create` を実行して、ArgoCD のアプリケーションリソースを作成しましたが、他のやり方もあります。
こんな感じのマニフェストを用意し、 `kubectl apply` することでも作成できます。

```yml:k8s/manifest/argo/application.yml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: k8s-request-counter-emitter
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/pokotyan/k8s-request-counter-emitter
    targetRevision: HEAD
    path: k8s/kustomize/overlays/local
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
```

また、ArgoCD の GUI コンソールからぽちぽちすることでも作成できます。
![スクリーンショット 2021-02-06 19.22.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/b5cb2571-c586-4f81-13fd-7276014d8b62.png)


# CI/CD
参考：
https://tech.gunosy.io/entry/argo-rollouts
https://qiita.com/yakisuzu/items/6b6a65d9cbc0422f4b3d
https://github.com/argoproj/argo-cd/issues/1648#issuecomment-546006238


ArgoCD はリポジトリの変更を検知すると、指定したクラスターに自動でデプロイを行います。なので、リポジトリにコードがマージされたら
- アプリケーションのイメージをビルド
- マニフェストのイメージのタグを動的に書き換える
- 現在の状態をコミットし、リポジトリへ push

という流れを構築する必要があります？（他にもやり方はあるのかもしれない）
そのため、こんな感じの GithubActions を作成しました。

```yml:.github/workflows/main.yml
name: ciname: ci

on:
  push:
    branches: main

jobs:
  push-image-to-docker-hub:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build Image
        run: |-
          docker build --tag pokotyan/k8s-request-counter-emitter:${GITHUB_SHA} .

      - name: Push Image
        run: |-
          docker push pokotyan/k8s-request-counter-emitter:${GITHUB_SHA}
  deploy-local:
    runs-on: ubuntu-latest
    needs: push-image-to-docker-hub
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up Kustomize
        run: |-
          curl -sfLo kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v3.1.0/kustomize_3.1.0_linux_amd64
          chmod +x ./kustomize

      - name: Set up Git
        run: |
          git config --local user.email "tomohiro_soga@yahoo.co.jp"
          git config --local user.name "pokotyan"

      - name: Set up kubeval
        run: |-
          wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz
          tar xf kubeval-linux-amd64.tar.gz
          sudo cp kubeval /usr/local/bin

      - name: Commit after Build manifest
        run: |
          bash ./k8s/kustomize/overlays/local/build.sh pokotyan/k8s-request-counter-emitter:${GITHUB_SHA}
          git commit -am "Update image to pokotyan/k8s-request-counter-emitter:$GITHUB_SHA"
          git pull
          git push origin main
```

main のブランチに何か変更があれば
- アプリケーションのイメージをビルド、 DockerHub へ push
- マニフェストのイメージのタグを動的に書き換える
- 現在の状態をコミットし、リポジトリへ push

の流れを実行します。動的に書き換えているイメージのタグは現在のコミットハッシュを利用するようにしています。
タグを動的に書き換える際に実行している build.sh はこんな感じのスクリプトで `kustomize edit set image` を利用して、[kustomize の base となるマニフェスト内にある app-image の記述](https://github.com/pokotyan/k8s-request-counter-emitter/blob/main/k8s/kustomize/base/app/deployment.yml#L29)を動的に現在のアプリのイメージに書き換えています。

```bash:k8s/kustomize/overlays/local/build.sh
#!/bin/bash

set -euo pipefail

cd $(dirname $0)

IMAGE_NAME="$1"
kustomize edit set image app-image="${IMAGE_NAME}"
kustomize build . >../../../manifest/app/app.yml | kubeval --strict --ignore-missing-schemas
```

また、 kustomize で一つにマージされたマニフェストに対して、 kubeval でバリデーションチェックをしています。
[kubeval はまだ CRD のリソースには対応していない](https://blog.varlog.dev/2019/04/kubeval%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%8A%E6%89%8B%E8%BB%BD%E3%81%ABkubernetes-manifest%E3%82%92validate%E3%81%99%E3%82%8B/)ので、そのまま kubeval をかけると Rollout のところでエラーになります。なので、 `--ignore-missing-schemas` のオプションをつけて CRD だった場合はチェックをスルーするようにしています。

余談ですが、 main ブランチの変更をトリガーとする GithubActions の処理内で main ブランチへの push をしたら GithubActions の無限ループになるんじゃないかと思ったんですが、特にそんな動作にはなりませんでした。

# ArgoCD Rollouts
参考:
https://qiita.com/ttr_tkmkb/items/4f8c1050855fc0a050ff
https://caddi.tech/archives/1702

[ArgoCD Rollouts のコントローラーと、ArgoCD Rollouts の cli をインストール](https://argoproj.github.io/argo-rollouts/installation/)します。

Deployment の yaml を Rollout の yaml に書き換えます。
https://argoproj.github.io/argo-rollouts/features/bluegreen/

Service をこんな感じで、二つ用意します。
active-service が、実際にアプリケーションから参照されるサービスで、
preview-service が、新しくデプロイされる pod が準備完了となるまで控えておくサービス、みたいなイメージです。

```yml:k8s/kustomize/base/app/service.yml
apiVersion: v1
kind: Service
metadata:
  name: active-service
  labels:
    app: app
spec:
  type: NodePort
  selector:
    app: app
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
  name: preview-service
spec:
  type: NodePort
  selector:
    app: app
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
```

これだけで、Argo Rollouts によるブルーグリーンデプロイができるようになります。



## ブルーグリーンデプロイがされていく様子を眺める

```
kubectl argo rollouts get rollout app --watch --cluster docker-desktop
```

で Rollouts の状態をリアルタイムで眺めることができます。
実際に、どんな感じで表示が移り変わっていくかを確認してみます。

1） `8877ec` のイメージを利用する pod が現在 `active-service` にぶら下がっている
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/cd2022d3-15b8-4f95-3d57-ed976a01e90d.png)

2) main ブランチへ何かしらのコード変更を push をする
（ここでは main ブランチへ直pushしていますが、実運用であれば main ブランチへのプルリクのマージがトリガーになると思います）
<img width="700" alt="スクリーンショット 2021-01-31 13.05.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/dd77ea62-7426-2f6a-6301-7976d55b431f.png">


3) main ブランチへの push により GithubActions が動く
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/8929bad5-a013-a522-8a86-3249c008d44c.png)

4) GithubActions の処理でアプリケーションのイメージのビルド、マニフェストのイメージタグの更新、 main ブランチへの commit、push がされる
イメージのタグが現在のコミットハッシュの 35308c に更新される
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/2bf97fe9-9414-834c-6595-f7c3de1202cd.png)

5) ArgoCD がリポジトリの更新を検知し、ブルーグリーンデプロイが動き始める。
まず、 preview-service に紐づく pod の作成が動き始める。 pod が利用するイメージはタグが `35308c` の新しいもの。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/e75f47c3-0f55-148b-116b-2669bbfe4ea3.png)

6) pod の作成が完了し、 `35308c` のイメージを利用する pod の方が `active-service` に紐づく。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/f02a450c-ec45-4449-5513-df31c6e2592e.png)

7) 古い `8877ec` のイメージを利用する pod の削除処理が始まる。
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/06eabaa7-6146-b029-9f79-f8d38b3f9003.png)

8) `8877ec` のイメージを利用する pod は全部消え、 `35308c` のイメージを利用する pod だけが active-service にぶら下がる形で残る。
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131625/6426ef5c-437e-2478-9fc6-fc601f1b08e0.png)


# ハマったところ
開発している最中は

```
skaffold dev -p local --port-forward -f k8s/skaffold.yml
```

で、k8s のアプリケーションをローカルにデプロイしていました。
この状態で、 `argocd app create` を実行すると
skaffold によるローカル環境へのデプロイと、 ArgoCD によるローカル環境へのデプロイが両方動き、正しくアプリケーションが動作しない感じになりました。
なので、 `argocd app create` を実行する前は skaffold は事前に落としておく必要がありました。
（逆もしかりで skaffold で動かすなら `argocd app delete` で ArgoCD のアプリケーションは消しておく）

ArgoCD による GitOps を導入する場合、 ArgoCD が対象とするクラスターは本番環境とかだと思うので、ローカルで ArgoCD を試す時ならではのハマりポイントな気がしました。

# 最後に
今回、めんどくさくてアプリケーションのコードと k8s のマニフェストを同じリポジトリで管理していますが、 本来なら分けた方がいいです。([ArgoCD のベストプラクティス](https://blog.argoproj.io/5-gitops-best-practices-d95cb0cbe9ff)にも記載がある)

アプリケーションのコードとマニフェストを同じリポジトリで管理してしまうと、ちょっとしたコードの修正のたびにデプロイが動くということになります。デプロイのタイミングやデプロイを実行できるメンバーを管理したいという場合にはマニフェストのリポジトリが分かれていた方がやりやすいです。