🤖

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

2021/02/06に公開

はじめに

以前作った、ローカルの 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 でファイルを監視し、ホットリロードするようにしています。

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 のパスワード設定をします。

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

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

https://localhost:8080/ にアクセスすると、こんな画面になるので、設定したパスワードでログインします。
スクリーンショット 2021-02-06 15.27.49.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

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

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

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

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 を作成しました。

.github/workflows/main.yml
name: 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 の記述を動的に現在のアプリのイメージに書き換えています。

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 のリソースには対応していないので、そのまま 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 をインストールします。

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

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

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

  2. main ブランチへ何かしらのコード変更を push をする
    (ここでは main ブランチへ直pushしていますが、実運用であれば main ブランチへのプルリクのマージがトリガーになると思います)
    スクリーンショット 2021-01-31 13.05.35.png

  3. main ブランチへの push により GithubActions が動く
    2.png

  4. GithubActions の処理でアプリケーションのイメージのビルド、マニフェストのイメージタグの更新、 main ブランチへの commit、push がされる
    イメージのタグが現在のコミットハッシュの 35308c に更新される
    3.png

  5. ArgoCD がリポジトリの更新を検知し、ブルーグリーンデプロイが動き始める。
    まず、 preview-service に紐づく pod の作成が動き始める。 pod が利用するイメージはタグが 35308c の新しいもの。
    4.png

  6. pod の作成が完了し、 35308c のイメージを利用する pod の方が active-service に紐づく。
    6.png

  7. 古い 8877ec のイメージを利用する pod の削除処理が始まる。
    7.png

  8. 8877ec のイメージを利用する pod は全部消え、 35308c のイメージを利用する pod だけが active-service にぶら下がる形で残る。
    8.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 のベストプラクティスにも記載がある)

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

Discussion