ArgoCD Pull Request Generatorを使ってプルリクエスト毎に環境を構築する

概要

ArgoCDを使ってプルリクエスト毎にインフラ環境を起動したのでメモ
argo-cd.readthedocs.io

前提

使用ツール

  • Kubernets
  • Istio
  • ArgoCD
  • Helm
  - name: istio/istio/istioctl@1.17.2
  - name: kubernetes/kubectl@v1.26.3
  - name: argoproj/argo-cd@v2.6.7
  - name: helm/helm@v3.11.2

設計

  • Pull Request毎にService,Deployment etcを構築する
  • Pull Request毎にRoute53レコードを作成する
  • アクセス制御はIstioのVirtual Serviceを使う
  • DB、キャッシュは共通のものを使う
  • アプリケーションリポジトリはモノレポ構成であり、複数のアプリケーションを起動する

ワークフロー

  1. アプリケーションリポジトリでPull Requesetを作成する
  2. 変更されたアプリケーションコードを検知して自動でラベルを付与する
    例:foo-api,bar-apiなど
  3. 手動でpreviewラベルを貼るとArgoCDが検知してインフラリソースを作成する
  4. リソースが作成されたらPull ReqeustにURLがコメントされる

Application Setの実装イメージ

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: preview
spec:
  goTemplate: true
  generators:
    - pullRequest:
        github:
          owner: hogeOrg
          repo: fugaRepo
          labels:
            - preview
        requeueAfterSeconds: 1800
  template:
    metadata:
      name: "preview-{{ .number }}"
    spec:
      ignoreDifferences:
        - group: "*"
          kind: ConfigMap
          namespace: hoge
          jsonPointers:
            - /data
      sources:
        - repoURL: "https://github.com/hogeOrg/fugaRepo.git"
          targetRevision: main
          ref: repo
        - repoURL: "ghcr.io/hogeOrg/fugaRepo"
          targetRevision: 0.0.1
          chart: app-a
          helm:
            releaseName: app-a-{{ .number }}
            valueFiles:
              - $repo/clusters/dev/apps/app-a/values.yaml
              - secrets://https://$${GITHUB_TOKEN}@raw.githubusercontent.com/hogeOrg/fugaRepo/main/clusters/dev/apps/app-a/values.secrets.yaml
            parameters:
              - name: nameOverride
                value: "app-a-{{ .number }}"
              - name: "image.tag"
                value: >-
                  {{if has "app-a" .labels -}}
                  sha-{{ substr 0 7 .head_short_sha }}
                  {{- else -}}
                  main
                  {{- end -}}
              - name: "image.pullPolicy"
                value: Always
              - name: "app.datadogServiceName"
                value: "app-a-{{ .number }}"
            values: |
              resources:
                requests:
                  cpu: 500m

        - repoURL: "ghcr.io/hogeOrg/fugaRepo"
          targetRevision: 0.0.1
          chart: virtual-service
          helm:
            releaseName: virtual-service-{{ .number }}
            parameters:
              - name: nameOverride
                value: api-virtual-service-{{ .number }}
            values: |
              annotations:
                external-dns.alpha.kubernetes.io/target: gateway.hoge.fuga.com
                external-dns.alpha.kubernetes.io/alias: "true"
              hosts:
                - api-{{ .number }}.hoge.fuga.com
              httpRoutes:
                - match:
                    - uri:
                        prefix: /app-a
                  route:
                    - destination:
                        host: app-a-{{ .number }}.hoge.svc.cluster.local
                        port:
                          number: 8000
                - match:
                    - uri:
                        prefix: /
                  route:
                    - destination:
                        host: api.hoge.fuga.com
                        port:
                          number: 443

        - chart: app-b
          repoURL: "ghcr.io/hogeOrg/fugaRepo"
          targetRevision: 0.0.1
          helm:
            releaseName: app-b-{{ .number }}
            parameters:
              - name: "nameOverride"
                value: app-b-{{ .number }}
              - name: "image.tag"
                value: >-
                  {{if has "app-b" .labels -}}
                  sha-{{ substr 0 7 .head_short_sha }}
                  {{- else -}}
                  main
                  {{- end -}}
              - name: "app.datadogServiceName"
                value: "app-b-{{ .number }}"
            valueFiles:
              - $repo/clusters/dev/apps/app-b/values.yaml
              - secrets://https://$${GITHUB_TOKEN}@raw.githubusercontent.com/hogeOrg/fugaRepo/main/clusters/dev/apps/app-b/values.secrets.yaml
            values: |
              resources:
                requests:
                  cpu: 500m

      project: "hoge"
      destination:
        server: https://kubernetes.default.svc
        namespace: hoge

      syncPolicy:
        syncOptions:
          - CreateNamespace=true
          - RespectIgnoreDifferences=true
        automated:
          selfHeal: true
          prune: true

ポイント

一意なリソースを作成する

        hosts:
                - api-{{ .number }}.hoge.fuga.com

識別子にプルリクエスト番号を入れておくことで独立した環境を構築する

イメージの動的な更新

              - name: nameOverride
                value: "app-a-{{ .number }}"
              - name: "image.tag"
                value: >-
                  {{if has "app-a" .labels -}}
                  sha-{{ substr 0 7 .head_short_sha }}
                  {{- else -}}
                  main
                  {{- end -}}

このように書いておくことで、

  • アプリケーションコードが変更されている場合はプルリクエスト内で変更されたイメージタグ
  • アプリケーションコードが変更されていない場合はmainタグ

を利用できる。
ArgoCDのApplicationSetはGo templateを利用できる。

https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/GoTemplate/

Virtual Serviceによるリクエスト制御

IstioのVirtual Serviceを使うことでインフラリソースを短い時間で作成できる。
PRを立ち上げたときに時間がかかるリソースは作らないような設計にする。(ロードバランサー、データベースなどなど)

まとめ

過去やったことをざっくりまとめた。 K8sはエコシステムが充実しているので楽。