TECHSTEP

ITインフラ関連の記事を公開してます。

ArgoCD Image Updaterを試す

今回はArgoCD Image Updaterを使ってみます。

ArgoCD Image Updaterとは

ArgoCD Image Updaterは、コンテナレジストリを定期的にチェックし、新しいイメージが配置されていれば、KubernetesマニフェストファイルやArgoCD Applicationを自動的に更新する機能を提供します。名称の通りArgoCDと一緒に使うことが前提で、ArgoCD Image Updaterがマニフェストを書き換えると、ArgoCDがそれを検知し、自動的にリソースの更新を行うような構成が可能となります。

ArgoCDはソースコードリポジトリ上のマニフェストファイルとクラスター上のリソース定義との差分を検知して、リソースの更新を行います。そのため、アプリケーションを更新するには、何らかの方法でマニフェストファイルの修正を行い、Podの再作成を行わなければなりません。

これまではCIの処理の中でマニフェストファイルの修正を行ったり、手動でファイルを修正することが多かったですが、ArgoCD Image Updaterを導入することでこの処理をカスタマイズする手間が省けると見込めます。

なお、ArgoCD Image Updaterと同じような機能を提供するプロダクトとしては、 Fluxが有名かと思います。Fluxにはイメージタグを自動更新する機能が最初から組み込まれており、 ImageRepository などのカスタムリソースを使うことで実現できます。

イメージ更新のプロセス

ArgoCD Image Updaterがどのようにイメージの更新を検知・更新するかは、ドキュメントに記載されています

  1. Kubernetes APIかArgoCD APIを使い、現在ArgoCD Applicationがクラスター上に存在するかをスキャンします。どちらのAPIを利用するかは ArgoCD Image Updaterをインストールする方法によります
  2. 見つかったApplicationの中に argocd-image-updater.argoproj.io/image-list というAnnotationが存在するかをチェックします。このAnnotationは、ArgoCD Image Updaterがチェックする対象のコンテナイメージレジストリを指定するもので、ArgoCD Image Updaterを使用する場合は必須の設定です。
  3. Annotationに設定されているイメージが、実際にApplicationによってデプロイされているかをチェックします。
  4. 指定したイメージが実際にデプロイされていれば、それをアップデート対象のイメージとして、対象のコンテナレジストリにアクセスします。そして、Applicationのファイルで指定する設定に従い、新しいバージョンのイメージがレジストリ中に存在するかをチェックします。
  5. 新しいバージョンのイメージがあれば、設定に従いイメージタグを更新します。更新方法は後述する2種類が存在します。

※参考リンク:

前提条件

前提として、ArgoCD Image Updaterは現在開発中のステータスにあるプロダクトです。そのため、将来破壊的な変更が入る可能性が高いとのことです。

また、利用するにあたり、現時点でいくつかの制限事項が存在します。

  • 更新対象となるイメージは、ArgoCD Applicationが管理するリソースのみが対象となる
  • 更新対象となるイメージは、Kustomize/Helmでマニフェストを管理するものだけが対象となる。Helmの場合はパラメータを使ってイメージタグを指定する必要がある。
  • コンテナイメージリポジトリへのアクセスに使う秘匿情報は、ArgoCD Image Updaterと同じクラスター上に存在しなければならない
  • ArgoCDから実行するロールバックには対応しておらず、ロールバック中のApplicationも更新しようとする (ArgoCDはそのようなApplicationの自動Syncを無効化する)。

※参考リンク:

ArgoCD Image Updaterを使ってみる

ここから実際にArgoCD Image Updaterを使ってみます。

事前準備

ArgoCD Image Updaterをデプロイする前に、EKSクラスター上にArgoCDをデプロイし、そこからGitHubリポジトリと同期した状態を作ります。

今回は事前に以下のリソースを用意しました。アプリケーションを修正してGitHubにPushするとCIが起動し、ECRに新しいイメージがPushされるようにします。また、GitHubリポジトリをprivateにしているため、ArgoCDがリポジトリにアクセスするよう、Kubernetes Secret リソースを用意しています。

詳細は書かないですが、使用したファイルの一部は以下に載せます。

GitHubリポジトリ中のディレクトリ構成

.
├── .github
│   └── workflows
│       └── docker-build-push.yaml
├── README.md
├── app
│   ├── Dockerfile
│   └── main.go
├── application.yaml
└── manifest
    ├── base
    │   ├── deployment.yaml
    │   └── kustomization.yaml
    └── overlays
        └── dev
            └── kustomization.yaml

.github/workflows/docker-build-push.yaml

name: Dockerfile build and push to ECR
on:
  push:
    branches:
    - main
    paths:
    - 'app/*'
    - '.github/workflows/docker-build-push.yaml'
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

env:
  IAM_ROLE_ARN: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-role
  ECR_REGION: ap-northeast-1
  ECR_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-1.amazonaws.com
  ECR_REPOSITORY: argocd-image-updater-test

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-region: ${{ env.ECR_REGION }}
        role-to-assume: ${{ env.IAM_ROLE_ARN}}
    - name: Login to ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1
    - name: Create Image Tag
      id: image-tag
      run: |
        SHA=${{ github.sha }}
        TAG=$(TZ=UTC-9 date '+%Y%m%d')-${SHA:0:7}
        echo "DOCKER_TAG=$TAG" >> $GITHUB_ENV
    - name: build and push to ECR
      id: build-image
      uses: docker/build-push-action@v2
      with:
        push: true
        file: app/Dockerfile
        tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.DOCKER_TAG }}

application.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/fy0323/argocd-image-updater-test.git
    targetRevision: main
    path: manifest/overlays/dev/
  destination:
    server: https://kubernetes.default.svc
    namespace: default

manifest/base/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  labels:
    app: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: app
        image: <AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221112-540daf5
        ports:
        - containerPort: 8080

---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
spec:
  selector:
    app: sample
  ports:
  - protocol: TCP
    name: http
    port: 80
    targetPort: 8080

manifest/base/kustomization.yaml

resources:
- deployment.yaml

manifest/overlays/dev/kustomization.yaml

resources:
- ../../base
commonLabels:
  env: develop

ArgoCD Image Updater導入前のアプリケーションは、以下のような文字列を出力します。以降のイメージ更新では、この文字列を修正して比較します。

$ kubectl run nginx --image nginx

$ kubectl exec -it nginx -- curl http://sample-app
<html><body>Hello, Argocd Image Updater</body></html>

ArgoCD Image Updaterの導入

ここから上記構成にArgoCD Image Updaterを導入します。また、ArgoCD Image UpdaterがECRを使うために追加が必要な設定もあるので、そちらも併せて設定します。

ArgoCD Image Updaterは複数のコンテナイメージリポジトリで動作することが確認されていますが、現時点でAmazon ECRはドキュメントには記載されていません。ただ、GitHub issueには設定方法が紹介されていたり、すでに本番環境で導入されている例もあるため、それらを参考にしています。

※参考:

インストール用ファイルの用意

ArgoCD Image Updaterのインストール用のファイルはGitHubに配置されています。今回はこのファイルを一部修正する必要があるため、事前に手元に用意しておきます。

IRSAの用意

ArgoCD Image Updaterがコンテナイメージの検索や更新の検知をするには、ターゲットのコンテナレジストリにアクセスする必要があります。ArgoCD Image UpdaterにECRへのアクセス権を付与するには、Image Updater Podの動作するNodeか、Pod自体にIAMポリシーを付与する必要があります。セキュリティのことを考慮すると後者のほうが望ましいため、今回はIRSA (IAM Role for Service Account) を利用します。

IRSAの設定は今回AWSマネージドコンソールから行いました(手順はこちらなどを参照してください)。ArgoCD Image Updaterが必要な権限はECRに対する読み取り権限だけなので、今回は AmazonEC2ContainerRegistryReadOnly というAWSのマネージドポリシーを付与したロールを用意しました。

また、ArgoCD Image UpdaterのService AccountがIAMロールを利用するため、マニフェストファイルを修正します。

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: argocd-image-updater
    app.kubernetes.io/part-of: argocd-image-updater
  name: argocd-image-updater
  # 以降を追加
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<AWS account id>:role/role-for-ecr-access
argocd-image-updater-config ConfigMapの修正

ECRへのアクセスを行うには、IAMの権限を所有していることに加え、アクセス用の認証トークンを使用する必要があります。このトークンは12時間ごとに無効になるため、定期的にトークンを取得する必要があります。

認証トークンを定期的に取得するにはCronJobを使う方法もありますが、今回は argocd-image-updater-configスクリプトを仕込む方法にしました。

argocd-image-updater-config はArgoCD Image Updaterのインストール時に合わせて作成されるConfigMapで、アクセス先のコンテナレジストリの情報やGitHubへのコミットメッセージなどの設定を定義するファイルです。このファイルに以下のような設定を追加します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-image-updater-config
  namespace: argocd
# 以降を設定
data:
  registries.conf: |
    registries:
      - name: ECR
        api_url: https://<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
        prefix: <AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
        credentials: ext:/app/scripts/ecr-login.sh
        credsexpire: 10h
  ecr-login.sh: |
    #!/bin/sh
    aws ecr --region ap-northeast-1 get-authorization-token --output text --query 'authorizationData[].authorizationToken' | base64 -d

また、上記ConfigMapで取得した認証トークンをPodから利用するため、 argocd-image-updater Deploymentにも修正をします。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: argocd-image-updater
    app.kubernetes.io/part-of: argocd-image-updater
  name: argocd-image-updater
spec:

(一部抜粋)

      # 以降を追加
        volumeMounts:
        - mountPath: /app/scripts
          name: ecr-login-script
      volumes:
      - configMap:
          defaultMode: 0755
          items:
          - key: ecr-login.sh
            path: ecr-login.sh
          name: argocd-image-updater-config
          optional: true
        name: ecr-login-script

最終的にArgoCD Image Updaterのデプロイに使用したマニフェストファイルは以下の通りです。

argocd-image-updater-manifest.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: argocd-image-updater
    app.kubernetes.io/part-of: argocd-image-updater
  name: argocd-image-updater
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<AWS account id>:role/role-for-ecr-access
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: argocd-image-updater
    app.kubernetes.io/part-of: argocd-image-updater
  name: argocd-image-updater
rules:
- apiGroups:
  - ""
  resources:
  - secrets
  - configmaps
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - argoproj.io
  resources:
  - applications
  verbs:
  - get
  - list
  - update
  - patch
- apiGroups:
  - ""
  resources:
  - events
  verbs:
  - create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: argocd-image-updater
    app.kubernetes.io/part-of: argocd-image-updater
  name: argocd-image-updater
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: argocd-image-updater
subjects:
- kind: ServiceAccount
  name: argocd-image-updater
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-image-updater-config
  namespace: argocd
data:
  registries.conf: |
    registries:
      - name: ECR
        api_url: https://<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
        prefix: <AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
        credentials: ext:/app/scripts/ecr-login.sh
        credsexpire: 10h
  ecr-login.sh: |
    #!/bin/sh
    aws ecr --region ap-northeast-1 get-authorization-token --output text --query 'authorizationData[].authorizationToken' | base64 -d
---
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/name: argocd-image-updater-ssh-config
    app.kubernetes.io/part-of: argocd-image-updater
  name: argocd-image-updater-ssh-config
---
apiVersion: v1
kind: Secret
metadata:
  labels:
    app.kubernetes.io/name: argocd-image-updater-secret
    app.kubernetes.io/part-of: argocd-image-updater
  name: argocd-image-updater-secret
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/name: argocd-image-updater
    app.kubernetes.io/part-of: argocd-image-updater
  name: argocd-image-updater
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: argocd-image-updater
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app.kubernetes.io/name: argocd-image-updater
    spec:
      containers:
      - command:
        - /usr/local/bin/argocd-image-updater
        - run
        env:
        - name: APPLICATIONS_API
          valueFrom:
            configMapKeyRef:
              key: applications_api
              name: argocd-image-updater-config
              optional: true
        - name: ARGOCD_GRPC_WEB
          valueFrom:
            configMapKeyRef:
              key: argocd.grpc_web
              name: argocd-image-updater-config
              optional: true
        - name: ARGOCD_SERVER
          valueFrom:
            configMapKeyRef:
              key: argocd.server_addr
              name: argocd-image-updater-config
              optional: true
        - name: ARGOCD_INSECURE
          valueFrom:
            configMapKeyRef:
              key: argocd.insecure
              name: argocd-image-updater-config
              optional: true
        - name: ARGOCD_PLAINTEXT
          valueFrom:
            configMapKeyRef:
              key: argocd.plaintext
              name: argocd-image-updater-config
              optional: true
        - name: ARGOCD_TOKEN
          valueFrom:
            secretKeyRef:
              key: argocd.token
              name: argocd-image-updater-secret
              optional: true
        - name: IMAGE_UPDATER_LOGLEVEL
          valueFrom:
            configMapKeyRef:
              key: log.level
              name: argocd-image-updater-config
              optional: true
        - name: GIT_COMMIT_USER
          valueFrom:
            configMapKeyRef:
              key: git.user
              name: argocd-image-updater-config
              optional: true
        - name: GIT_COMMIT_EMAIL
          valueFrom:
            configMapKeyRef:
              key: git.email
              name: argocd-image-updater-config
              optional: true
        - name: IMAGE_UPDATER_KUBE_EVENTS
          valueFrom:
            configMapKeyRef:
              key: kube.events
              name: argocd-image-updater-config
              optional: true
        image: quay.io/argoprojlabs/argocd-image-updater:latest
        imagePullPolicy: Always
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 3
          periodSeconds: 30
        name: argocd-image-updater
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 3
          periodSeconds: 30
        volumeMounts:
        - mountPath: /app/config
          name: image-updater-conf
        - mountPath: /app/config/ssh
          name: ssh-known-hosts
        - mountPath: /app/.ssh
          name: ssh-config
        - mountPath: /app/scripts
          name: ecr-login-script
      serviceAccountName: argocd-image-updater
      volumes:
      - configMap:
          items:
          - key: registries.conf
            path: registries.conf
          - key: git.commit-message-template
            path: commit.template
          name: argocd-image-updater-config
          optional: true
        name: image-updater-conf
      - configMap:
          name: argocd-ssh-known-hosts-cm
          optional: true
        name: ssh-known-hosts
      - configMap:
          name: argocd-image-updater-ssh-config
          optional: true
        name: ssh-config
      - configMap:
          defaultMode: 0755
          items:
          - key: ecr-login.sh
            path: ecr-login.sh
          name: argocd-image-updater-config
          optional: true
        name: ecr-login-script

ArgoCD Image Updaterのデプロイ

上記修正を行ったマニフェストファイルをデプロイします。

$ kubectl apply -n argocd -f argocd-image-updater-manifest.yaml
serviceaccount/argocd-image-updater created
role.rbac.authorization.k8s.io/argocd-image-updater created
rolebinding.rbac.authorization.k8s.io/argocd-image-updater created
configmap/argocd-image-updater-config created
configmap/argocd-image-updater-ssh-config created
secret/argocd-image-updater-secret created
deployment.apps/argocd-image-updater created
導入後の動作確認

ArgoCD Image Updaterをデプロイ後、作成したPodからECRにアクセスし、コンテナイメージを検索できるかを確認します。ArgoCD Image UpdaterはCLIも用意されており、作成したPodからコマンドを実行することで、簡易的な動作確認をとることができます。

$ kubectl exec -it argocd-image-updater-ddbf7df64-prgk6 -n argocd -- argocd-image-updater test <AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test --credentials ext:/app/scripts/ecr-login.sh --registries-conf-path /app/config/registries.conf
DEBU[0000] Creating in-cluster Kubernetes client
INFO[0000] retrieving information about image            image_alias= image_name=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test registry_url=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
DEBU[0000] rate limiting is disabled                     prefix=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com registry="https://<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com"
INFO[0000] Loaded 1 registry configurations from /app/config/registries.conf
INFO[0000] /app/scripts/ecr-login.sh                     dir= execID=2d322
INFO[0003] /app/scripts/ecr-login.sh                     dir= execID=d8ba2
INFO[0004] Fetching available tags and metadata from registry  application=test image_alias= image_name=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test registry_url=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
INFO[0004] Found 3 tags in registry                      application=test image_alias= image_name=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test registry_url=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
DEBU[0004] found 3 from 3 tags eligible for consideration  image=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test
INFO[0004] latest image according to constraint is <AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221112-5cd2a03  application=test image_alias= image_name=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test registry_url=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com

※参考リンク:

ArgoCD Image Updaterの動作確認

ここからArgoCD Image Updaterを使ってコンテイメージの更新を行います。

コンテナイメージの更新方法

ArgoCD Image Updaterによるイメージの更新方法は大きく2つあり、動作中の Application リソースに直接編集を加える ( write-back-method: argocd ) か、GitHubリポジトリに向けてイメージを修正するコミットを作成する ( write-back-method: git ) かになります。

※参考リンク:

write-back-method: argocd

argocd による更新は、ArgoCD APIを通してパラメータの上書きを行います。デフォルトではこちらの方法が設定されます。

この方法はArgoCD Web UIやCLIによってアプリケーションを更新する場合に適している方法です。Image Updaterによる変更はマニフェストファイル等には保存されず、既存のApplicationを削除し再作成した場合、Image Updaterによる更新は反映されません。

こちらの方法では、ソースコードリポジトリに定義されたマニフェストとは異なる定義のアプリケーションが動作することになるため、GitOpsの原則からは外れた使い方となりますが、プロジェクトによってはこちらを採用するケースもあるようです。

write-back-method: git

git による更新は、パラメータの上書きのためにgitを利用します。この方法を有効にすると、ターゲットブランチ配下で .argocd-source-<appName>.yaml というファイルが作成され、更新するイメージの名称が更新されます。これにより、Image Updaterによるパラメータの上書きがgitに保存され、Gitopsの原則に従う形で更新することが可能となります。

動作確認: argocd

まずは argocd のほうを試します。事前に以下のようにApplicationファイルを修正します。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app
  namespace: argocd
  # 以下のannotationsを追加
  annotations:
    argocd-image-updater.argoproj.io/write-back-method: argocd
    argocd-image-updater.argoproj.io/image-list: app=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test
    argocd-image-updater.argoproj.io/app.update-strategy: latest
spec:
  project: default
  source:
    repoURL: https://github.com/fy0323/argocd-image-updater-test.git
    targetRevision: main
    path: manifest/overlays/dev/
  destination:
    server: https://kubernetes.default.svc
    namespace: default

metadata.annotations に追加した項目について補足です。

  • argocd-image-updater.argoproj.io/write-back-method : Updaterによるイメージの上書き方法を指定します。 argocd / git のいずれかを指定可能です。
  • argocd-image-updater.argoproj.io/image-list : ターゲットとなるコンテナレジストリを指定します。ここではコンテナレジストリ名のエイリアスを設定することができます。今回は app という名称でエイリアスを設定します。
  • argocd-image-updater.argoproj.io/app.update-strategy : アップデートの対象とするコンテナイメージをどう特定するか定義します。これは image-list で設定したレジストリに対して設定し、 semver / latest / digest などの方法を指定できます。今回はレジストリ内で作成された最新バージョンのイメージを対象とする latest を指定してます。

※参考リンク:

上記マニフェストファイルをデプロイします。

$ kubectl apply -f argocd-image-updater-test/application.yaml
application.argoproj.io/app configured

この状態でアプリケーションのほうを修正し、新しいコンテナイメージを作成します。しばらくすると、Applicationの状態が OutOfSync となり、差分ができていることが確認できます。

$ argocd app list
NAME  CLUSTER                         NAMESPACE  PROJECT  STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO                                                     PATH                    TARGET
app   https://kubernetes.default.svc  default    default  OutOfSync  Healthy  <none>      <none>      https://github.com/fy0323/argocd-image-updater-test.git  manifest/overlays/dev/  main

ここからSyncをしてPodを更新すると、アプリケーションに修正内容が反映されたことが確認できます。

$ argocd app sync app
TIMESTAMP                  GROUP        KIND   NAMESPACE                  NAME    STATUS    HEALTH        HOOK  MESSAGE
2022-11-15T16:58:00+09:00            Service     default            sample-app    Synced   Healthy
2022-11-15T16:58:00+09:00   apps  Deployment     default            sample-app  OutOfSync  Healthy
2022-11-15T16:58:00+09:00   apps  Deployment     default            sample-app  OutOfSync  Healthy              deployment.apps/sample-app configured
2022-11-15T16:58:00+09:00            Service     default            sample-app    Synced   Healthy              service/sample-app unchanged
2022-11-15T16:58:00+09:00   apps  Deployment     default            sample-app    Synced  Progressing              deployment.apps/sample-app configured

(以降割愛)

$ kubectl exec -it nginx -- curl http://sample-app
<html><body>Hello, Argocd Image Updater 20221115-01</body></html>

参考: イメージタグ更新時のArgoCD Image Updaterログ

$ kubectl logs -n argocd argocd-image-updater-ddbf7df64-prgk6 -f

(一部抜粋)

time="2022-11-15T07:54:51Z" level=info msg="Starting image update cycle, considering 1 annotated application(s) for update"
time="2022-11-15T07:54:51Z" level=warning msg="\"latest\" strategy has been renamed to \"newest-build\". Please switch to the new convention as support for the old naming convention will be removed in future versions." image_alias=app image_name=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test registry_url=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
time="2022-11-15T07:54:51Z" level=info msg="Setting new image to <AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221115-a1fbb26" alias=app application=app image_name=argocd-image-updater-test image_tag=20221115-5938771 registry=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
time="2022-11-15T07:54:51Z" level=info msg="Successfully updated image '<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221115-5938771' to '<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221115-a1fbb26', but pending spec update (dry run=false)" alias=app application=app image_name=argocd-image-updater-test image_tag=20221115-5938771 registry=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
time="2022-11-15T07:54:51Z" level=info msg="Committing 1 parameter update(s) for application app" application=app
time="2022-11-15T07:54:52Z" level=info msg="Successfully updated the live application spec" application=app
time="2022-11-15T07:54:52Z" level=info msg="Processing results: applications=1 images_considered=1 images_skipped=0 images_updated=1 errors=0"

動作確認: git

続いて git のほうも試します。今回はApplicationマニフェストargocd-image-updater.argoproj.io/write-back-method の修正を行い、再度デプロイします。

$ kubectl apply -f argocd-image-updater-test/application.yaml
application.argoproj.io/app configured

$ kubectl describe app -n argocd app
Name:         app
Namespace:    argocd
Labels:       <none>
Annotations:  argocd-image-updater.argoproj.io/app.update-strategy: latest
              argocd-image-updater.argoproj.io/image-list: app=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test
              argocd-image-updater.argoproj.io/write-back-method: git
API Version:  argoproj.io/v1alpha1
Kind:         Application

(以下割愛)

この状態でアプリケーションのほうを修正し、新しいコンテナイメージを作成します。しばらくすると、GitHubのほうに新しいファイルが作られていることが確認できます。

$ git pull origin main
$ ls -la manifest/overlays/dev/
total 0
drwxr-xr-x 1 testadm testadm 4096 Nov 15 17:33 .
drwxr-xr-x 1 testadm testadm 4096 Nov 12 10:08 ..
-rw-r--r-- 1 testadm testadm  118 Nov 15 17:33 .argocd-source-app.yaml # 新規追加されたファイル
-rw-r--r-- 1 testadm testadm   52 Nov 12 10:08 kustomization.yaml

$ cat .argocd-source-app.yaml
kustomize:
  images:
  - <AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221115-7f8b2c9

またApplicationの状態も OutOfSync となり、差分ができていることが確認できます。これをSyncすることで、アプリケーションへの修正が反映されたことが確認できます。

$ argocd app list
NAME  CLUSTER                         NAMESPACE  PROJECT  STATUS     HEALTH   SYNCPOLICY  CONDITIONS  REPO                                                     PATH                    TARGET
app   https://kubernetes.default.svc  default    default  OutOfSync  Healthy  <none>      <none>      https://github.com/fy0323/argocd-image-updater-test.git  manifest/overlays/dev/  main

$ argocd app sync app
TIMESTAMP                  GROUP        KIND   NAMESPACE                  NAME    STATUS    HEALTH        HOOK  MESSAGE
2022-11-15T17:12:27+09:00            Service     default            sample-app    Synced   Healthy
2022-11-15T17:12:27+09:00   apps  Deployment     default            sample-app  OutOfSync  Healthy
2022-11-15T17:12:27+09:00            Service     default            sample-app    Synced   Healthy              service/sample-app unchanged
2022-11-15T17:12:27+09:00   apps  Deployment     default            sample-app  OutOfSync  Healthy              deployment.apps/sample-app configured
2022-11-15T17:12:27+09:00   apps  Deployment     default            sample-app    Synced  Progressing              deployment.apps/sample-app configured

(以降割愛)

$ kubectl get pods
NAME                          READY   STATUS    RESTARTS   AGE
nginx                         1/1     Running   0          65m
sample-app-74b49d7856-ln75h   1/1     Running   0          30s

$ kubectl exec -it nginx -- curl http://sample-app
<html><body>Hello, Argocd Image Updater 20221115-02</body></html>

なお、 git 方式の場合、以降はイメージタグは .argocd-source-app.yaml を参照します。そのため、git 方式でImage Updaterを有効にすると、 例えば手動でマニフェストファイル (今回なら deployment.yaml ) のイメージタグを更新しても、起動するPodには反映されません。

参考: イメージタグ更新時のArgoCD Image Updaterログ

$ kubectl logs -n argocd argocd-image-updater-ddbf7df64-prgk6 -f

(一部抜粋)

time="2022-11-15T08:06:52Z" level=info msg="Starting image update cycle, considering 1 annotated application(s) for update"
time="2022-11-15T08:06:52Z" level=warning msg="\"latest\" strategy has been renamed to \"newest-build\". Please switch to the new convention as support for the old naming convention will be removed in future versions." image_alias=app image_name=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test registry_url=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
time="2022-11-15T08:06:53Z" level=info msg="Setting new image to <AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221115-7f8b2c9" alias=app application=app image_name=argocd-image-updater-test image_tag=20221115-a1fbb26 registry=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
time="2022-11-15T08:06:53Z" level=info msg="Successfully updated image '<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221115-a1fbb26' to '<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com/argocd-image-updater-test:20221115-7f8b2c9', but pending spec update (dry run=false)" alias=app application=app image_name=argocd-image-updater-test image_tag=20221115-a1fbb26 registry=<AWS account id>.dkr.ecr.ap-northeast-1.amazonaws.com
time="2022-11-15T08:06:53Z" level=info msg="Committing 1 parameter update(s) for application app" application=app
time="2022-11-15T08:06:53Z" level=info msg="Starting configmap/secret informers"
time="2022-11-15T08:06:53Z" level=info msg="Configmap/secret informer synced"
time="2022-11-15T08:06:53Z" level=info msg="Initializing https://github.com/fy0323/argocd-image-updater-test.git to /tmp/git-app2064956039"
time="2022-11-15T08:06:53Z" level=info msg="rm -rf /tmp/git-app2064956039" dir= execID=839c5
time="2022-11-15T08:06:53Z" level=info msg="secrets informer cancelled"
time="2022-11-15T08:06:53Z" level=info msg="configmap informer cancelled"
time="2022-11-15T08:06:53Z" level=info msg=Trace args="[rm -rf /tmp/git-app2064956039]" dir= operation_name="exec rm" time_ms=2.52375
time="2022-11-15T08:06:53Z" level=info msg="git fetch origin --tags --force" dir=/tmp/git-app2064956039 execID=1bda6
time="2022-11-15T08:06:54Z" level=info msg=Trace args="[git fetch origin --tags --force]" dir=/tmp/git-app2064956039 operation_name="exec git" time_ms=926.411977
time="2022-11-15T08:06:54Z" level=info msg="git config user.name argocd-image-updater" dir=/tmp/git-app2064956039 execID=82001
time="2022-11-15T08:06:54Z" level=info msg=Trace args="[git config user.name argocd-image-updater]" dir=/tmp/git-app2064956039 operation_name="exec git" time_ms=5.418636
time="2022-11-15T08:06:54Z" level=info msg="git config user.email noreply@argoproj.io" dir=/tmp/git-app2064956039 execID=87e8b
time="2022-11-15T08:06:54Z" level=info msg=Trace args="[git config user.email noreply@argoproj.io]" dir=/tmp/git-app2064956039 operation_name="exec git" time_ms=5.845921
time="2022-11-15T08:06:54Z" level=info msg="git checkout --force main" dir=/tmp/git-app2064956039 execID=29d9f
time="2022-11-15T08:06:54Z" level=info msg=Trace args="[git checkout --force main]" dir=/tmp/git-app2064956039 operation_name="exec git" time_ms=10.429219
time="2022-11-15T08:06:54Z" level=info msg="git clean -fdx" dir=/tmp/git-app2064956039 execID=434ff
time="2022-11-15T08:06:54Z" level=info msg=Trace args="[git clean -fdx]" dir=/tmp/git-app2064956039 operation_name="exec git" time_ms=6.051158
time="2022-11-15T08:06:54Z" level=info msg="git add /tmp/git-app2064956039/manifest/overlays/dev/.argocd-source-app.yaml" dir=/tmp/git-app2064956039 execID=b8197
time="2022-11-15T08:06:54Z" level=info msg=Trace args="[git add /tmp/git-app2064956039/manifest/overlays/dev/.argocd-source-app.yaml]" dir=/tmp/git-app2064956039 operation_name="exec git" time_ms=11.407465
time="2022-11-15T08:06:54Z" level=info msg="git commit -a -F /tmp/image-updater-commit-msg2947554435" dir=/tmp/git-app2064956039 execID=07c3b
time="2022-11-15T08:06:54Z" level=info msg=Trace args="[git commit -a -F /tmp/image-updater-commit-msg2947554435]" dir=/tmp/git-app2064956039 operation_name="exec git" time_ms=32.495799
time="2022-11-15T08:06:54Z" level=info msg="git push origin main" dir=/tmp/git-app2064956039 execID=068e8
time="2022-11-15T08:06:55Z" level=info msg=Trace args="[git push origin main]" dir=/tmp/git-app2064956039 operation_name="exec git" time_ms=1042.2358430000002
time="2022-11-15T08:06:55Z" level=info msg="Successfully updated the live application spec" application=app
time="2022-11-15T08:06:55Z" level=info msg="Processing results: applications=1 images_considered=1 images_skipped=0 images_updated=1 errors=0"

【メモ】AWS CodeCommit / CodeBuild + Flux を使ったGitLab Flowの構築

今回は以前のGitHub Flowの構成例に続き、GitLab Flowの実現を目指した例を考えました。

なお、今回は前回と大部分同じ構成なので、説明を省いている箇所があります。

GitLab Flowとは

GitLab Flowも開発ワークフローの一種で、以下のようなルールに従って開発を行います。

  1. 開発はmasterからfeatureブランチを作成して行う
  2. featureブランチでの開発後、Merge Requestを提出
  3. レビュー実施後、問題なければmasterブランチへマージ
  4. masterブランチからステージング環境へデプロイ
  5. ステージング環境で確認後、masterからpre-prodブランチへマージ
  6. pre-prodブランチからpre-production環境へデプロイ
  7. pre-prodからprodブランチへマージ
  8. prodブランチからprod環境へデプロイ

f:id:FY0323:20220224220011p:plain

GitLab Flowではmasterブランチのほかに、デプロイする環境用のブランチを用意します。ステージング環境で動作を確認した後、別の環境用ブランチへマージを行い、それぞれのブランチから環境へのデプロイを行います。hotfixによるバグ対応は、masterブランチで確認後、hotfixブランチを別ブランチへマージすることで対応します。

GitLab Flowの利点として、本番環境へのデプロイのタイミングを制御できること、各環境でテスト済みであることが保証できること、などが挙げられます。GitHub Flowでは、featureブランチをマージするたびに本番環境へデプロイ出来ることを想定しますが、リリース時間をコントロールできないようなアプリケーション(例:iOSアプリケーションなど)には適応が難しい場合があります。またソースコードをマージするのと別のタイミングにリリースを行いたい場合も、GitHub Flowでは難しくなります。

一方、コミット先のブランチを誤ると、誤って別の環境にデプロイされてしまうこと、GitHub Flowに比較するとフローが複雑であることが、欠点として考えられます。GitLab Flowではデプロイする環境ごとにブランチを分けているので、マージする先のブランチを間違えてしまうと、いきなり本番環境へのデプロイが発生してしまう可能性があります。これを防ぐため、ブランチに対する操作を制限する、などの設定を行うことが必要です。

※参考:

全体像

今回のパイプラインの全体像は以下の通りです。前回の構成とほぼ同じですが、FluxによるEKSへのデプロイ先が複数存在し、それぞれブランチと紐づいている点が違います。

GitLab Flowは codecommit-cd というKubernetesマニフェスト用のリポジトリに対して適用しています。なお codecommit-ci というアプリケーション用リポジトリは、前回と同様GitHub Flowを適用する想定です。

※参考:

f:id:FY0323:20220224215359p:plain

今回もEKSへのデプロイはFluxを利用しました。ただし、Fluxで利用する機能は、環境によって変更しています。

staging環境は、CIでコンテナイメージをビルド・プッシュしたのをトリガーに、自動的にアプリケーションをデプロイします。一方pre-prod・prod環境は、それぞれブランチへマージが行われたのをトリガーに、アプリケーションのデプロイを行います。そのため、staging環境では、CodeCommitリポジトリ・ECRレジストリをモニタリングするためのリソースを、pre-prod・prod環境ではCodeCommitリポジトリをモニタリングするリソースを作成します。

なお、今回はすべての環境で同じマニフェストファイルを利用しています。実際は環境ごとに異なる設定を導入することが多いですが、今回は Deployment / Service だけを使うシンプルな構成のため、共通のファイルを使うことにしました。

実際の開発フローは以下のような流れを想定しています。今回の新規追加分は 太字 で示しています。

  • 開発者はアプリケーションの機能追加・修正のためにmainブランチからfeatureブランチを作成する
  • featureブランチ上で開発を行い、mainブランチに対するPRを作成する
  • PRの作成を検知して自動テストが実行される
  • テストに合格したらレビューを依頼し、修正などを経てmainブランチへマージする
  • mainブランチへマージされると、コンテナイメージのビルドを実行する
  • 新しいコンテナイメージがPushされるのを検知し、マニフェストのイメージタグの書き換えと、ステージング環境への自動デプロイが実行される
  • ステージング環境での動作確認を完了後、mainブランチからpre-prodブランチへのPRを作成・レビュー依頼をする
  • pre-prodブランチへマージをすると、pre-prod環境への自動デプロイが実行される
  • pre-prod環境での動作確認を完了後、pre-prodブランチからprodブランチへのPRを作成・レビュー依頼をする
  • prodブランチへマージをすると、prod環境への自動デプロイが実行される

開発の流れ

ここでは追加した部分だけ追記します。

ステージング環境での動作確認を完了後、mainブランチからpre-prodブランチへのPRを作成・レビュー依頼をする

pre-prod環境用のマニフェストファイルに修正を行い、mainブランチからpre-prodブランチへのPRを作成します。マニフェストファイルの修正は、コンテナイメージタグの変更を想定します。

pre-prodブランチへマージをすると、pre-prod環境への自動デプロイが実行される

mainブランチからpre-prodブランチへマージを行うと、マニフェストファイルの変更をFluxが検知して、pre-prod環境への自動デプロイを実行します。

pre-prod環境での動作確認を完了後、pre-prodブランチからprodブランチへのPRを作成・レビュー依頼をする

pre-prod環境向けと同様、prod環境用のマニフェストファイルを修正し、PRを作成します。

prodブランチへマージをすると、prod環境への自動デプロイが実行される

prodブランチへマージを行うことで、prod環境への自動デプロイを実行します。

環境構築

今回の環境構築は、前回のものと大部分重なっているため、差分だけ紹介しています。

CodeCommit / CodeBuildの作成

今回は以下のマニフェストファイルを使用しました。前回と比べ、承認ルールテンプレート、IAMポリシーの追加をしています。ここでは追加した分だけ載せています。

sample-resources.yaml

  IAMPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties: 
      Description: prohibit direct push to codecommit
      Path: /
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action: 'codecommit:GitPush'
            Resource: '*'
            Condition:
              "StringEqualsIfExists":
                "codecommit:References": # 対象のブランチを追加
                  - "refs/heads/main"
                  - "refs/heads/pre-prod"
                  - "refs/heads/prod"
              "Null":
                "codecommit:References": 
                  - "false"
(中略)
 
  # Approval Rule Template
  RuleTemplate:
    Type: Community::CodeCommit::ApprovalRuleTemplate
    Properties:
      Name: !Ref RuleTemplateName
      Description: test rule
      Content:
        Version: "2018-11-08"
        DestinationReferences: # 対象のブランチを追加
          - "refs/heads/main"
          - "refs/heads/pre-prod"
          - "refs/heads/prod"
        Statements:
          - Type: "Approvers"
            NumberOfApprovalsNeeded: 1
            ApprovalPoolMembers:
              - "*"

Fluxのインストール

EKSクラスターを作成後、各環境ごとにFluxをインストールします。

今回ステージング環境には前回と同じリソースの作成をしますが、pre-prod / prod環境では不要なリソースは作成せず、以下のものを作成します。

  • GitRepository
  • Kustomization

使用するマニフェストファイルは以下の通りです。GitRepository で指定するブランチ名を変更しています。

---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: codecommit-cd
  namespace: flux-system
spec:
  interval: 1m
  url: https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/codecommit-cd
  ref:
    branch: pre-prod # ブランチ名を変更
  gitImplementation: libgit2
  secretRef:
    name: https-credentials
---
apiVersion: v1
kind: Secret
metadata:
  name: https-credentials
  namespace: flux-system
type: Opaque
data:
  username: <base64 encoded username>
  password: <base64 encoded password>
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
  name: codecommit-cd
  namespace: flux-system
spec:
  interval: 5m0s
  path: ./manifest
  prune: true
  sourceRef:
    kind: GitRepository
    name: codecommit-cd
  validation: client

【メモ】AWS CodeCommit / CodeBuild + Flux を使ったGitHub Flowの構築例

今回はAWS CodeCommit/CodeBuildとFluxを使い、GitHub Flowを実現することを目指して構成を考えてみました。

GitHub Flowとは

GitHub Flowは開発ワークフローの1つであり、以下のようなルールが定められた、比較的シンプルなワークフローになります。

  1. master ブランチは常にデプロイ可能である
  2. 作業用ブランチをmasterから作成する
  3. 作業用ブランチをリモートブランチに定期的にプッシュする
  4. フィードバックや助言が欲しい時、ブランチをマージしたいときは、 プルリクエスト を作成する
  5. プルリクエストが承認されたらmasterへマージする
  6. masterへのマージが完了したら直ちにデプロイを行う

f:id:FY0323:20220119170728p:plain

GitHub Flowでは、運用するコードは1つのバージョンだけを持ち、常にデプロイ可能なメインラインに頻繁にインテグレーションすることを目指します。また通常の機能開発と本番環境向けの修正を同じフローで対応します。このためGit Flowと異なり、リリース専用のreleaseブランチや、本番環境のバグ修正に使うHotfixブランチなどは利用しません。

※参考:

パイプラインの全体像

今回用意したパイプラインの全体像は以下の通りです。今回、アプリケーションコードのテストやコンテナイメージのビルドはAWS CodeBuild、EKS環境へのデプロイは Flux を採用しています。

なお今回GitHub Flowを適用しているのはアプリケーションコードの開発フローのみになります。マニフェストの開発には特にフローを設定していません。

f:id:FY0323:20220119170800p:plain

AWS CodeBuildの部分は、以前試した構成を再利用しています。

EKSなどKubernetesクラスターへのマニフェストの自動デプロイは、以下の2つのどちらかのアプローチを採用するかと思います。今回は2つ目の、GitOpsのアプローチで実行しています。

  • CIOps: アプリケーション変更のマージ時に、コンテナイメージビルドと合わせて kubectl 等でデプロイをする
  • GitOps: コンテナイメージの更新を検知してテスト環境への自動デプロイを実行する

※参考:

ここでは Flux というCI/CD用ツールを利用します。Fluxの利用方法については 以前紹介したことがあるので、そちらをご覧ください。

実際の開発フローは、以下のような流れを想定しています。

f:id:FY0323:20220119174128p:plain

  • 開発者はアプリケーションの機能追加・修正のためにmainブランチからfeatureブランチを作成する
  • featureブランチ上で開発を行い、mainブランチに対するPRを作成する
  • PRの作成を検知して自動テストが実行される
  • テストに合格したらレビューを依頼し、修正などを経てmainブランチへマージする
  • mainブランチへマージされると、コンテナイメージのビルドを実行する
  • 新しいコンテナイメージがPushされるのを検知し、マニフェストのイメージタグの書き換えと、テスト環境への自動デプロイが実行される
  • テスト環境で問題がないことを確認したら、対象のコミットに対しGitタグを付与する
  • Gitタグを検知し、本番環境への自動デプロイが実行される

※参考:

開発フローの流れ

開発者はアプリケーションの機能追加・修正のためにmainブランチからfeatureブランチを作成する

AWSではIAMユーザー・グループに対してIAMポリシーを適用し、CodeCommitに対する操作制限をかけることができます。

今回は testuser というIAMユーザーを作成しました。このユーザーは testgroup というIAMグループに属しており、testgroup にはあらかじめ main ブランチへの直接PushができないようIAMロールを付与しています。

ここでは testusercodecommit-ci というCodeCommitリポジトリをCloneし、作業用のブランチを作成します。

なお、IAMユーザー作成後はAWSマネジメントコンソールにアクセスし、CodeCommit用のアクセス情報を取得します。今回はHTTPS認証を利用しました。

※参考:

featureブランチ上で開発を行い、mainブランチに対するPRを作成する

featureブランチ上でソースコードなどに修正を行い、mainブランチへのPRを作成します。

PRの作成を検知して自動テストが実行される

codecommit-ci でPRが作成されると、AWS EventBridgeを経由して codebuild-ci-test というCodeBuildプロジェクトのジョブが起動します。 codebuild-ci-test ではテストが実行され、実行結果はSlackに通知されます。

f:id:FY0323:20220120203750p:plain

テストに合格したらレビューを依頼し、修正などを経てmainブランチへマージする

CodeBuildによるテストが無事通ったら、レビューを依頼してPRのチェックをします。今回は承認ルールテンプレートも作成しており、PRに対して1件の承認がないとマージできないよう設定しています。

※参考:

mainブランチへマージされると、コンテナイメージのビルドを実行する

PRのレビューで問題がなければ承認・マージを行います。EventBridgeがマージを検知すると codebuild-ci-build というCodeBuildプロジェクトがジョブを開始します。このジョブではDockerfileを用いたイメージのビルド、ECRリポジトリへのPushを行います。ジョブの結果はSlackへ通知されます。

f:id:FY0323:20220120204444p:plain

なお今回コンテナイメージのタグは main-<date>-<Commit IDの先頭7文字> という形式にしています。

※参考:

新しいコンテナイメージがPushされるのを検知し、マニフェストのイメージタグの書き換えと、テスト環境への自動デプロイが実行される

ECRに新しいイメージがPushされると、あらかじめEKSにインストールされたFluxがイメージの更新を検知し、codecommit-cd というCodeCommitリポジトリ上のマニフェストファイルを書き換えます。

マニフェストファイルが書き換わると、Fluxがリポジトリ上の変更を検知し、EKSクラスターへ自動的に更新を行います。これにより、コンテナイメージが更新されることで、テスト環境への自動デプロイを実現します。

f:id:FY0323:20220119171002p:plain

※参考:

テスト環境で問題がないことを確認したら、対象のコミットに対しGitタグを付与する

今回は作成しませんでしたが、テスト環境での動作確認が取れたら、特定のGitタグを付与することで本番環境のPodが更新されることを想定しています。ここでもFluxを利用し、以下のようなマニフェストファイルを使うことで、Gitタグを検知してアップデートを行います。

apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: prod
  namespace: flux-system
spec:
  interval: 1m
  url: https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/codecommit-cd
  ref:
    semver: ">=3.1.0-rc.1 <3.2.0" # Gitタグの形式を指定
  gitImplementation: libgit2
  secretRef:
    name: https-credentials

※参考:

Gitタグを検知し、本番環境への自動デプロイが実行される

前項の通り、FluxがGitタグを検知して自動デプロイを実行します。

ここまでで、今回のGitHub Flowの流れは終了です。

環境構築

最後に、今回の環境構築についての紹介です。

CodeCommit / CodeBuildの作成

CodeCommit / CodeBuild関連のリソースは、以下のマニフェストファイルで作成します。ここでは承認ルールテンプレートなども合わせて作成します。承認ルールテンプレートの作成は以前試したこちらの記事をご確認ください。

sample-resources.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: Sample resources for GitHub flow
 
Parameters:
  CodeCommitRepoNameForCI:
    Type: String
    Default: "codecommit-ci"
  CodeBuildProjectForCITest:
    Type: String
    Default: "codebuild-ci-test"
  CodeBuildProjectForCIBuild:
    Type: String
    Default: "codebuild-ci-build"
  RuleForCITest:
    Type: String
    Default: "rule-ci-test"
  RuleForCIBuild:
    Type: String
    Default: "rule-ci-build"
  CodeCommitRepoNameForCD:
    Type: String
    Default: "codecommit-cd"
  ECRRepoName:
    Type: String
    Default: "test-ecr-cicd"
  NotifySlackChannel:
    Type: String
    Description: Slack Channel ID
    Default: "xxxxxxxxxxx"
  NotifyChatbotWorkspaceId:
    Type: String
    Description: Chatbot Workspace ID
    Default: "xxxxxxxxxxx"
  UserName:
    Type: String
    Default: "testuser"
  UserPassword:
    Type: String
    Default: "testuser@1234"
  GroupName:
    Type: String
    Default: "testgroup"
  RuleTemplateName:
    Type: String
    Default: "testrule"
 
Resources:
  ###########################
  # Sample resources for CI #
  ###########################
  # CodeCommit for CI
  CodeCommitForCI:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref CodeCommitRepoNameForCI
      RepositoryDescription: CodeCommit repository for CI
  # CodeBuild + IAM for CI
  CodeBuildForCITest:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref CodeBuildProjectForCITest
      Description: CodeBuild project for CI test
      ServiceRole: !GetAtt BuildRoleForCITest.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      Source:
        Location: !GetAtt CodeCommitForCI.CloneUrlHttp
        Type: CODECOMMIT
        BuildSpec: |
          version: 0.2
 
          phases:
            install:
              runtime-versions:
                golang: 1.14
            build: 
              commands:
                - echo Test the Go code
                - go test -v $CODEBUILD_SRC_DIR/app
  BuildRoleForCITest:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: "Allow"
            Action: 'sts:AssumeRole'
            Principal:
              Service: codebuild.amazonaws.com
  BuildPoliciesForCITest:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: Policy-for-ci-test
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:*
            Resource: '*'
            Effect: Allow
          - Resource: "*"
            Effect: Allow
            Action:
              - 'codebuild:*'
              - 'codecommit:*'
              - 'events:*'
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'iam:*'
      Roles:
        - Ref: BuildRoleForCITest
 
  CodeBuildForCIBuild:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Ref CodeBuildProjectForCIBuild
      Description: CodeBuild project for CI build
      ServiceRole: !GetAtt BuildRoleForCIBuild.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:18.09.0
        # please set the variables
        EnvironmentVariables:
        - Name: "ECR_REPO"
          Value: !Ref ECRRepoName
        - Name: "BRANCH"
          Value: "main"
      Source:
        Location: !GetAtt CodeCommitForCI.CloneUrlHttp
        Type: CODECOMMIT
        BuildSpec: |
          version: 0.2
 
          phases:
            pre_build:
              commands:
                - echo Login to ECR
                - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
                - $(aws ecr get-login --no-include-email --region ap-northeast-1)
                - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPO
                - echo Set IMAGE_TAG
                - DATE=$(date "+%Y%m%d%H%M")
                - COMMITID=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | head -c 7)
                - IMAGE_TAG=$BRANCH-$DATE-$COMMITID
            build: 
              commands:
                - echo Build Docker image
                - docker build -t $REPOSITORY_URI:$IMAGE_TAG app/
            post_build:
              commands:
                - echo Push to ECR
                - docker push $REPOSITORY_URI:$IMAGE_TAG
  BuildRoleForCIBuild:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: "Allow"
            Action: 'sts:AssumeRole'
            Principal:
              Service: codebuild.amazonaws.com
  BuildPoliciesForCIBuild:
    Type: "AWS::IAM::Policy"
    Properties:
      PolicyName: Policy-for-ci-build
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:*
            Resource: '*'
            Effect: Allow
          - Resource: "*"
            Effect: Allow
            Action:
              - 'ecr:*'
              - 'codebuild:*'
              - 'codecommit:*'
              - 'events:*'
              - 'logs:CreateLogGroup'
              - 'logs:CreateLogStream'
              - 'logs:PutLogEvents'
              - 'iam:*'
      Roles:
        - Ref: "BuildRoleForCIBuild"
  # EventBridge for CI
  EventBridgeForCITest:
    Type: AWS::Events::Rule
    Properties:
      Description: EventBridge for PR test
      EventPattern:
        source:
          - "aws.codecommit"
        resources: 
          - !GetAtt CodeCommitForCI.Arn
        detail-type:
          - "CodeCommit Pull Request State Change"
        detail:
          event:
            - "pullRequestCreated"
          pullRequestStatus:
            - "Open"
          destinationReference:
            - "refs/heads/main"
      Name: !Ref RuleForCITest
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CodeBuildForCITest.Arn
          Id: AppPRTest
          RoleArn: !GetAtt EventBridgeRoleForCITest.Arn
  EventBridgeRoleForCITest:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: Policy-eventbridge-for-ci-test
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Resource: !GetAtt CodeBuildForCITest.Arn
                Action: 
                  - codebuild:StartBuild
  EventBridgeForCIBuild:
    Type: AWS::Events::Rule
    Properties:
      Description: EventBridge for PR build
      EventPattern:
        source:
          - "aws.codecommit"
        resources: 
          - !GetAtt CodeCommitForCI.Arn
        detail-type:
          - "CodeCommit Pull Request State Change"
        detail:
          event:
            - "pullRequestMergeStatusUpdated"
          pullRequestStatus:
            - "Closed"
          destinationReference:
            - "refs/heads/main"
      Name: !Ref RuleForCIBuild
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt CodeBuildForCIBuild.Arn
          Id: AppPRTest
          RoleArn: !GetAtt EventBridgeRoleForCIBuild.Arn
  EventBridgeRoleForCIBuild:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - events.amazonaws.com
            Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: Policy-eventbridge-for-ci-build
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Resource: !GetAtt CodeBuildForCIBuild.Arn
                Action: 
                  - codebuild:StartBuild
 
  # CodeCommit for CD
  CodeCommitForCD:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref CodeCommitRepoNameForCD
      RepositoryDescription: CodeCommit repository for CD
 
  ###########################
  # Sample resources common #
  ###########################
  # ECR
  ECRForCICD:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Ref ECRRepoName
      ImageScanningConfiguration: 
        ScanOnPush: "true"
  # Chatbot
  ChatbotSlackConfiguration:
    Type: AWS::Chatbot::SlackChannelConfiguration
    Properties:
      ConfigurationName: chatbot-slack-configuration
      IamRoleArn: !GetAtt ChatbotRole.Arn
      LoggingLevel: ERROR
      SlackChannelId: !Ref NotifySlackChannel
      SlackWorkspaceId: !Ref NotifyChatbotWorkspaceId
  ChatbotRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - chatbot.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess
  # CodeStar Notification Rule
  NotificationRoleForCITest:
    Type: 'AWS::CodeStarNotifications::NotificationRule'
    Properties:
      Name: 'notifications for CI test'
      DetailType: FULL
      Resource: !GetAtt CodeBuildForCITest.Arn
      EventTypeIds: 
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
      Targets: 
        - TargetType: AWSChatbotSlack 
          TargetAddress: !GetAtt ChatbotSlackConfiguration.Arn
  NotificationRoleForCIBuild:
    Type: 'AWS::CodeStarNotifications::NotificationRule'
    Properties:
      Name: 'notifications for PR build'
      DetailType: FULL
      Resource: !GetAtt CodeBuildForCIBuild.Arn
      EventTypeIds: 
        - codebuild-project-build-state-failed
        - codebuild-project-build-state-succeeded
      Targets: 
        - TargetType: AWSChatbotSlack 
          TargetAddress: !GetAtt ChatbotSlackConfiguration.Arn
 
  # IAM User
  IAMPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties: 
      Description: prohibit direct push to codecommit
      Path: /
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action: 'codecommit:GitPush'
            Resource: '*'
            Condition:
              "StringEqualsIfExists":
                "codecommit:References": 
                  - "refs/heads/main"
              "Null":
                "codecommit:References": 
                  - "false"
  IAMUser:
    Type: AWS::IAM::User
    Properties:
      Groups:
        - !Ref IAMGroup
      UserName: !Ref UserName
      LoginProfile:
        Password: !Ref UserPassword
        PasswordResetRequired: "false"
  IAMGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref GroupName
      ManagedPolicyArns:
        - !Ref IAMPolicy
        - "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"
  # Approval Rule Template
  RuleTemplate:
    Type: Community::CodeCommit::ApprovalRuleTemplate
    Properties:
      Name: !Ref RuleTemplateName
      Description: test rule
      Content:
        Version: "2018-11-08"
        DestinationReferences:
          - "refs/heads/main"
        Statements:
          - Type: "Approvers"
            NumberOfApprovalsNeeded: 1
            ApprovalPoolMembers:
              - "*"
  RepoAssociation:
    Type: Community::CodeCommit::RepositoryAssociation
    Properties:
      ApprovalRuleTemplateArn: !Ref RuleTemplate
      RepositoryNames: 
        - !Ref CodeCommitRepoNameForCI

ソースコード・Dockerfileの配置

CodeCommitを作成したら、各リポジトリREADME.md を適当に配置し、ローカルへクローンします。クローンしたら、ソースコード・Dockerfileは codecommit-ci リポジトリapp ディレクトリに、Kubernetesマニフェストファイルは codecommit-cd リポジトリmanifest ディレクトリに配置します。

今回利用したソースコード・Dockerfileはこちらです。

main.go

package main
 
import (
    "fmt"
    "net/http"
)
 
func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "<html><body>hello AWS CI/CD</body></html>\n")
}
 
func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

main_test.go

package main
 
import (
    "net/http"
    "net/http/httptest"
    "testing"
)
 
func TestHandler(t *testing.T) {
    testserver := httptest.NewServer(http.HandlerFunc(handler))
    defer testserver.Close()
 
    res, err := http.Get(testserver.URL)
    defer res.Body.Close()
 
    if err != nil {
        t.Fatalf("failed test %#v", err)
    }
    if res.StatusCode != 200 {
        t.Error("response code is not 200")
    }
}

Dockerfile

FROM golang:1.15.6 as builder
 
WORKDIR /go/src
 
COPY ./main.go ./
ARG CGO_ENABLED=0
ARG GOOS=linux
ARG GOARCH=amd64
RUN go build -o /go/bin/main
 
 
FROM scratch as runner
 
COPY --from=builder /go/bin/main /app/main
 
ENTRYPOINT ["/app/main"]

マニフェストファイルはこちらです。Fluxがコンテナイメージタグを検索できるよう、 # {"$imagepolicy": "flux-system:test-policy"} という記述を追加しています。

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-app
  namespace: default
  labels:
    app: sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sample
  template:
    metadata:
      labels:
        app: sample
    spec:
      containers:
      - name: app
        image: 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-cicd:main-000000000000-0123abc # {"$imagepolicy": "flux-system:test-policy"}
        ports:
        - containerPort: 8080
 
---
apiVersion: v1
kind: Service
metadata:
  name: sample-app
  namespace: default
spec:
  selector:
    app: sample
  ports:
  - protocol: TCP
    name: http
    port: 80
    targetPort: 8080

※参考:

EKSクラスターの作成

次にEKSクラスターを作成します。今回は eksctl コマンドで作成しました。

$ eksctl version
0.77.0

$ eksctl create cluster -f eks-clusterconfig.yml 

Fluxのインストール

次にEKSクラスターへFluxをインストールします。今回は flux install コマンドを利用しました。

$ flux --version
flux version 0.24.1

$ flux install --components-extra=image-reflector-controller,image-automation-controller

次にFlux用のリソースを作成します。今回はコンテナイメージリポジトリのモニタリングも行うので、以下のように複数のCustom Resourceを利用しています。

  • GitRepository
  • Kustomization
  • ImageRepository
  • ImagePolicy
  • ImageUpdateAutomation

GitRepository Kustomizationのファイルはこちら。

gitrepo.yaml

---
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
  name: codecommit-cd
  namespace: flux-system
spec:
  interval: 1m
  url: https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/codecommit-cd
  ref:
    branch: main
  gitImplementation: libgit2
  secretRef:
    name: https-credentials
---
apiVersion: v1
kind: Secret
metadata:
  name: https-credentials
  namespace: flux-system
type: Opaque
data:
  username: <base64 encoded username>
  password: <base64 encoded password>
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
  name: codecommit-cd
  namespace: flux-system
spec:
  interval: 5m0s
  path: ./manifest
  prune: true
  sourceRef:
    kind: GitRepository
    name: codecommit-cd
  validation: client

今回はソースコードリポジトリにCodeCommitを利用し、アクセスはHTTPS認証を使っています。FluxからCodeCommitを利用する場合は、こちらのリンク先に書かれている通り、Secret リソースにアクセス情報を定義します。また、Fluxがリポジトリからマニフェストファイルを利用するため、 Kustomization リソースも合わせて作成します。

※参考:

ImageRepository ImagePolicy ImageUpdateAutomation のファイルはこちら。

imagerepo.yaml

---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
  name: test-ecr-cicd
  namespace: flux-system
spec:
  interval: 1m0s
  image: 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-cicd
  secretRef:
    name: ecr-credentials
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
  name: test-policy
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: test-ecr-cicd
  filterTags:
    pattern: '^main-[0-9].*-[a-f0-9].*'
  policy:
    alphabetical:
      order: asc
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: test-automation
  namespace: flux-system
spec:
  interval: 1m0s
  sourceRef:
    kind: GitRepository
    name: codecommit-cd
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@users.noreply.github.com
        name: fluxcdbot
      messageTemplate: '{{range .Updated.Images}}{{println .}}{{end}}'
    push:
      branch: main
  update:
    path: ./manifest
    strategy: Setters

secret-cronjob.yaml

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ecr-credentials-sync
  namespace: flux-system
rules:
- apiGroups: [""]
  resources:
  - secrets
  verbs:
  - get
  - create
  - patch
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: ecr-credentials-sync
  namespace: flux-system
subjects:
- kind: ServiceAccount
  name: ecr-credentials-sync
roleRef:
  kind: Role
  name: ecr-credentials-sync
  apiGroup: ""
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ecr-credentials-sync
  namespace: flux-system
---
apiVersion: v1
kind: Secret
metadata:
  name: aws-credentials
  namespace: flux-system
type: Opaque
data:
  AWS_ACCESS_KEY_ID: <base64 encoded AWS Access Key>
  AWS_SECRET_ACCESS_KEY: <base64 encoded AWS Secret Access Key>
---
apiVersion: batch/v1
kind: CronJob
metadata:
  name: ecr-credentials-sync
  namespace: flux-system
spec:
  suspend: false
  schedule: 0 */6 * * *
  failedJobsHistoryLimit: 1
  successfulJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: ecr-credentials-sync
          restartPolicy: Never
          volumes:
          - name: token
            emptyDir:
              medium: Memory
          initContainers:
          - image: amazon/aws-cli
            name: get-token
            imagePullPolicy: IfNotPresent
            envFrom:
            - secretRef:
                name: aws-credentials
            env:
            - name: REGION
              value: ap-northeast-1
            volumeMounts:
            - mountPath: /token
              name: token
            command:
            - /bin/sh
            - -ce
            - aws ecr get-login-password --region ${REGION} > /token/ecr-token
          containers:
          - image: bitnami/kubectl
            name: create-secret
            imagePullPolicy: IfNotPresent
            env:
            - name: SECRET_NAME
              value: ecr-credentials
            - name: ECR_REGISTRY
              value: 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com
            volumeMounts:
            - mountPath: /token
              name: token
            command:
            - /bin/bash
            - -ce
            - |-
              kubectl create secret docker-registry $SECRET_NAME \
                --dry-run=client \
                --docker-server="$ECR_REGISTRY" \
                --docker-username=AWS \
                --docker-password="$(</token/ecr-token)" \
                -o yaml | kubectl apply -f -

Amazon ECRを利用する場合は、12時間ごとにアクセストークンが更新されるため、定期的にアクセス情報を更新するための CronJob を用意します。とりあえずアクセス用の Secret リソースを作成したかったので、CronJobからJobを作成する以下のコマンドを実行し、Secretリソースを作成しました。

$ kubectl create job docker-registry –from=cronjob/ecr-credentials-sync
$ kubectl get job -n flux-system
NAME                            COMPLETIONS   DURATION   AGE
docker-registry                 1/1           22s        5h6m

※参考:

パイプラインの実行

ここまでで必要なリソースの作成は完了しました。この状態で codebuild-ci-build のジョブが実行されると、ECRへの新規イメージのPushを検知し、CodeCommit上のマニフェストファイルを書き換えます。

# Image Reflector Controllerのログ
$ kubectl logs image-reflector-controller-6d94666b7d-962x8 -n flux-system

(中略)

{"level":"info","ts":"2022-01-16T06:16:31.391Z","logger":"controller.imagerepository","msg":"reconciliation finished in 43.928032ms, next run in 
1m0s","reconciler group":"image.toolkit.fluxcd.io","reconciler kind":"ImageRepository","name":"test-ecr-cicd","namespace":"flux-system"}

マニフェストファイルの変更を検知すると、FluxはEKSクラスターへのデプロイを実行し、新しいコンテナイメージを持つDeploymentが起動します。

# sample-appのイメージタグが変更されている
$ kubectl get pods
NAME                          READY   STATUS    RESTARTS   AGE
sample-app-7689d7759c-7h56f   1/1     Running   0          83m

$ kubectl describe pod sample-app-7689d7759c-7h56f

(中略)

Containers:
  app:
    Container ID:   docker://b94f50458c881f8b11bbf767dc04b9c12bb5f837392e79651c92e278e745d875
    Image:          000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr-cicd:main-202201160619-0ce5ebf

【メモ】AWS CodeCommitのブランチへの操作制限と承認ルールテンプレートの作り方

ソースコードを管理するリポジトリを本格的に運用しようと思うと、リポジトリへの操作に対して制限をかける必要が出てきます。AWS CodeCommitはデフォルトに提供する機能に限りがあり、リポジトリに対する制限をかけるためにいろいろと設定が必要になります。

今回はAWS CodeCommitに対する操作制限をかける方法について調査しました。

ブランチへの操作制限

ブランチへの操作制限は、専用のIAMポリシーを作成し、それをIAMユーザー・グループに紐づけることで実現できます。制限できる内容としては、特定ブランチに対する git push や削除、マージの手法などです。

※参考

今回はシンプルに main ブランチへの直接Pushが実行できないようIAMポリシーを作成し、それを testgroup というIAMグループに付与することで、グループ内のIAMユーザーに操作制限を加えました。

IAMポリシーはjsonで書かれた例が多く見つかりましたが、今回はCloudFormationからYaml形式で書いた例を載せておきます。以下のようなYamlファイルを使ってリソースを作成しました。

Resources:
  CodeCommit:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref RepoName
      RepositoryDescription: test repo
  IAMPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties: 
      Description: prohibit direct push to codecommit
      Path: /
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action: 'codecommit:GitPush'
            Resource: '*'
            Condition:
              "StringEqualsIfExists":
                "codecommit:References": 
                  - "refs/heads/main"
              "Null":
                "codecommit:References": 
                  - "false"
  IAMUser:
    Type: AWS::IAM::User
    Properties:
      Groups:
        - !Ref IAMGroup
      UserName: !Ref UserName
      LoginProfile:
        Password: !Ref UserPassword
        PasswordResetRequired: "false"
  IAMGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref GroupName
      ManagedPolicyArns:
        - !Ref IAMPolicy
        - "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"

※参考

承認ルールテンプレート

承認ルールテンプレートはCodeCommitの機能として備わっており、マネジメントコンソールから作成・管理することができます。

また承認ルールテンプレートの作成は、公式には提供されていないものの、CloudFormationの拡張機能を利用し、コミュニティの公開するパッケージを利用することで、CloudFormationテンプレートからの作成が可能になります。

今回はあまり紹介する例の見当たらなかった、CloudFormationのほうで作ってみました。登録するリソースタイプは Community::CodeCommit::ApprovalRuleTemplate Community::CodeCommit::RepositoryAssociation の2つです。

まずCloudFormationを利用する準備をするため、実行用のロールの作成とCloudFormationタイプの登録を行います。

# IAMロールの作成
$ aws cloudformation create-stack \
   --template-url https://community-resource-provider-catalog.s3.amazonaws.com/community-codecommit-approvalruletemplate-resource-role-0.1.0.yml \
   --stack-name community-codecommit-approvalruletemplate-resource-role \
   --capabilities CAPABILITY_IAM

$ aws cloudformation create-stack \
   --template-url https://community-resource-provider-catalog.s3.amazonaws.com/community-codecommit-repositoryassociation-resource-role-0.1.0.yml \
  --stack-name community-codecommit-repositoryassociation-resource-role \
   --capabilities CAPABILITY_IAM



# CloudFormationタイプの登録
$ aws cloudformation register-type \
   --region "ap-northeast-1" \
   --type-name "Community::CodeCommit::ApprovalRuleTemplate" \
   --schema-handler-package "s3://community-resource-provider-catalog/community-codecommit-approvalruletemplate-0.1.0.zip" \
   --type RESOURCE \
   --execution-role-arn <ApprovalRuleTemplate用のロールARNを指定>

$ aws cloudformation register-type \
   --region "ap-northeast-1" \
   --type-name "Community::CodeCommit::RepositoryAssociation" \
   --schema-handler-package "s3://community-resource-provider-catalog/community-codecommit-repositoryassociation-0.1.0.zip" \
   --type RESOURCE \
   --execution-role-arn <RepositoryAssociation用のロールARNを指定>

タイプ登録後は以下のようにコマンドを実行し、登録の進行状況を確認できます。 aws cloudformation register-type コマンド実行後に RegistrationTokenという出力があるので、そこに表示されたトークンを指定します。

$ aws cloudformation describe-type-registration --registration-token <RegistrationToken>
{
    "ProgressStatus": "IN_PROGRESS",
    "Description": "Deployment is currently in DEPLOY_STAGE of status IN_PROGRESS; ",
    "TypeArn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-ApprovalRuleTemplate",
    "TypeVersionArn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-ApprovalRuleTemplate/00000005"
}
$ aws cloudformation describe-type-registration --registration-token <RegistrationToken>
{
    "ProgressStatus": "COMPLETE",
    "Description": "Deployment is currently in DEPLOY_STAGE of status COMPLETED; ",
    "TypeArn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-ApprovalRuleTemplate",
    "TypeVersionArn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-ApprovalRuleTemplate/00000005"
}

なお、CloudFormationタイプはバージョン情報を含んでおり、何度か登録を繰り返すとバージョンが更新されます。ただしデフォルトで利用するタイプを明示的に変更しないと、CloudFormationからリソースを作成する際に使われるバージョンが古いものになるため、バージョンの変更が必要な場合は、以下のようにバージョンの確認と更新を行います。

# バージョン一覧の取得
$ aws cloudformation list-type-versions --type RESOURCE --type-name "Community::CodeCommit::RepositoryAssociation"
{
    "TypeVersionSummaries": [
        {
            "Type": "RESOURCE",
            "TypeName": "Community::CodeCommit::RepositoryAssociation",
            "VersionId": "00000001",
            "IsDefaultVersion": true,
            "Arn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-RepositoryAssociation/00000001",
            "TimeCreated": "2022-01-06T15:02:17.280000+00:00",
            "Description": "Resource that allows for the association of a particular approval rule template to CodeCommit repositories."
        },

(中略)

        {
            "Type": "RESOURCE",
            "TypeName": "Community::CodeCommit::RepositoryAssociation",
            "VersionId": "00000005",
            "IsDefaultVersion": false,
            "Arn": "arn:aws:cloudformation:ap-northeast-1:111111111111:type/resource/Community-CodeCommit-RepositoryAssociation/00000005",
            "TimeCreated": "2022-01-07T13:27:06.992000+00:00",
            "Description": "Resource that allows for the association of a particular approval rule template to CodeCommit repositories."
        }
    ]
}

# デフォルトバージョンの指定
$ aws cloudformation set-type-default-version \
   --version-id 00000005 \
   --type-name Community::CodeCommit::RepositoryAssociation \
   --type RESOURCE

これで承認ルールテンプレートをCloudFormationから作成できるようになったので、以下のようなファイルを使って作成します。

  RuleTemplate:
    Type: Community::CodeCommit::ApprovalRuleTemplate
    Properties:
      Name: !Ref RuleTemplateName
      Description: test rule
      Content:
        Version: "2018-11-08"
        DestinationReferences:
          - "refs/heads/main"
        Statements:
          - Type: "Approvers"
            NumberOfApprovalsNeeded: 1
            ApprovalPoolMembers:
              - "*"
  RepoAssociation:
    Type: Community::CodeCommit::RepositoryAssociation
    Properties:
      ApprovalRuleTemplateArn: !Ref RuleTemplate
      RepositoryNames: 
        - !GetAtt CodeCommit.Name

※参考

実際の利用

ここまでで必要なリソースの作成が完了したので、作成した testuser というIAMユーザーからCodeCommitへの操作を行います。

※リソースの作成に利用したYamlファイルはこちら。

sample-file.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: codecommit

Parameters:
  RepoName:
    Type: String
    Default: "testrepo"
  UserName:
    Type: String
    Default: "testuser"
  UserPassword:
    Type: String
    Default: "testuser@1234"
  GroupName:
    Type: String
    Default: "testgroup"
  RuleTemplateName:
    Type: String
    Default: "testrule"
  
Resources:
  CodeCommit:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref RepoName
      RepositoryDescription: test repo
  IAMPolicy:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties: 
      Description: prohibit direct push to codecommit
      Path: /
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Deny
            Action: 'codecommit:GitPush'
            Resource: '*'
            Condition:
              "StringEqualsIfExists":
                "codecommit:References": 
                  - "refs/heads/main"
              "Null":
                "codecommit:References": 
                  - "false"
  IAMUser:
    Type: AWS::IAM::User
    Properties:
      Groups:
        - !Ref IAMGroup
      UserName: !Ref UserName
      LoginProfile:
        Password: !Ref UserPassword
        PasswordResetRequired: "false"
  IAMGroup:
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref GroupName
      ManagedPolicyArns:
        - !Ref IAMPolicy
        - "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"
  RuleTemplate:
    Type: Community::CodeCommit::ApprovalRuleTemplate
    Properties:
      Name: !Ref RuleTemplateName
      Description: test rule
      Content:
        Version: "2018-11-08"
        DestinationReferences:
          - "refs/heads/main"
        Statements:
          - Type: "Approvers"
            NumberOfApprovalsNeeded: 1
            ApprovalPoolMembers:
              - "*"
  RepoAssociation:
    Type: Community::CodeCommit::RepositoryAssociation
    Properties:
      ApprovalRuleTemplateArn: !Ref RuleTemplate
      RepositoryNames: 
        - !GetAtt CodeCommit.Name

まずはIAMユーザー画面に移動し、CodeCommitへのアクセス情報を取得します。次にCodeCommitで利用するファイルを適当に用意します。

f:id:FY0323:20220109165300p:plain

ローカルの環境から testuser を使ってCodeCommitリポジトリの取得を行います。

$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/testrepo
Cloning into 'testrepo'...
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': testuser-at-111111111111
Password for 'https://testuser-at-111111111111@git-codecommit.ap-northeast-1.amazonaws.com':
remote: Counting objects: 3, done.
Unpacking objects: 100% (3/3), done.

$ cd testrepo/
$ git branch
* main

ここで README.md を適当に編集し、 main ブランチへ直接Pushをしてみます。するとエラーが発生し、 main への直接Pushができないことが確認できます。

$ vi README.md
$ cat README.md 
# testrepo

20220108

$ git add .
$ git commit -m "initial commit"
[main 9390f5b] initial commit
 1 file changed, 3 insertions(+), 1 deletion(-)
$ git push origin main
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': testuser-at-111111111111
Password for 'https://testuser-at-111111111111@git-codecommit.ap-northeast-1.amazonaws.com': 
Counting objects: 3, done.
Writing objects: 100% (3/3), 262 bytes | 131.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/testrepo
 ! [remote rejected] main -> main (You don't have permission to push changes to this branch.)
error: failed to push some refs to 'https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/testrepo'

ここでは test-branch というブランチから、変更をコミットします。

$ git checkout -b test-branch
Switched to a new branch 'test-branch'
$ git push origin test-branch 
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': testuser-at-111111111111
Password for 'https://testuser-at-111111111111@git-codecommit.ap-northeast-1.amazonaws.com': 
Counting objects: 3, done.
Writing objects: 100% (3/3), 262 bytes | 131.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/testrepo
 * [new branch]      test-branch -> test-branch

CodeCommitの画面に移動し、Pull Requestを作成します。すると承認ルールテンプレートに記載した内容の通り、1件の承認がないとマージができないよう表示されます。

f:id:FY0323:20220109165322p:plain

ここで別のユーザーで再ログインし、承認を行うことで、マージが可能になります。

f:id:FY0323:20220109165755p:plain

f:id:FY0323:20220109165807p:plain

※参考

ArgoCD Projectの使い方を整理する

今回はArgoCDでマルチテナント向けに利用できる Project という機能について整理しようと思います。

ここ最近のArgoCDのアップデートにもProjectに関するものが含まれていたので、そちらも試しています。

Projectとは

ArgoCDでは、Applicationというカスタムリソースの中にGitリポジトリやデプロイ先のクラスター・Namespaceなどの情報を設定します。Projectは、このApplicationを論理的にグループ分けするカスタムリソースです。

ArgoCDでは、複数のチームやプロジェクトで同じArgoCDクラスターを利用する、いわゆるマルチテナントと呼ばれる利用方法を、デフォルトでサポートしています。ArgoCDアカウントとSSOとの統合やRBACの利用などがそれにあたりますが、Projectもその一つに含まれます。

ArgoCDを複数チームが利用する場合、それぞれのチームが利用するApplicationなどのリソースは異なることが多いでしょう。あるチームが別チームのリソースにアクセスできてしまうと、誤ったアプリケーションをデプロイしたり、別のクラスターやNamespaceにデプロイしてしまうような事故にもつながるため、チームごとに利用できるリソースを制限したり条件付けをしたくなります。ArgoCDではProjectを利用することで、それを実現します。

Projectでは、以下のような設定を行い、利用するリソースの制限をすることができます。

なお、ArgoCDはクラスター作成時点でデフォルトのProject ( default ) が作成されます。Application作成時にProjectを指定しないと、デフォルトのProjectと紐づくため、通常Applicationを作成する場合は全てこの default Projectに分類されます。 default はGitリポジトリクラスター・Namespace、リソースの種類に制限がなく、全ての操作が可能です。

Projectの基本的な使い方

ここからProjectの利用方法をなぞってみます。Projectを利用する場合、ユーザーとProjectとをRBACのポリシーで紐づけます。Projectが利用できるリソースの条件やポリシーを設定し、 argocd-rbac-cm というConfigMapでユーザーとProjectとを紐づけることで、ユーザーがそのProjectを操作できるようになります。

※Project-Account-RBACの概要図

f:id:FY0323:20211230212556p:plain

今回はローカルユーザーを作成し、そのユーザーが一部操作できるようなProjectを作成し、権限を付与して操作できるまでを試します。

ArgoCDはインストール済み、 admin ユーザーでArgoCDにログインされている前提で進めます。

ローカルユーザーの作成

まずはローカルユーザーを作成します。ローカルユーザーは argocd-cm ConfigMapに追加することで作成できます。ここでは dev1 というアカウントを作成します。

$ kubectl edit cm argocd-cm -n argocd

~~~
# 以下のようにアカウントを追加

data:
  accounts.dev1: login
~~~

configmap/argocd-cm edited


# 作成後のアカウントの状態
$ argocd account list
NAME   ENABLED  CAPABILITIES
admin  true     login
dev1   true     login

$ kubectl describe cm argocd-cm -n argocd
Name:         argocd-cm
Namespace:    argocd
Labels:       app.kubernetes.io/name=argocd-cm
              app.kubernetes.io/part-of=argocd
Annotations:  <none>

Data
====
accounts.dev1:
----
login
Events:  <none>

アカウント作成後はパスワードを設定します。

$ argocd account update-password \
> --account dev1 \
> --current-password <adminユーザーのパスワード> \
> --new-password <dev1に設定するパスワード>
Password updated


# 設定するパスワードが条件を満たしてないと以下のようなエラーが出力される

$ argocd account update-password \
> --account dev1 \
> --current-password <adminユーザーのパスワード> \
> --new-password <dev1に設定するパスワード>
FATA[0001] rpc error: code = Unknown desc = New password does not match the following expression: ^.{8,32}$. 

上記までで新規アカウントが作成され、ArgoCDにログインできるようになります。

# adminアカウントからログアウトする
$ argocd logout a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443

Logged out from 'a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443'


# dev1アカウントでログインする
$ argocd login a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443 

WARNING: server certificate had error: x509: certificate is valid for localhost, argocd-server, argocd-server.argocd, argocd-server.argocd.svc, argocd-server.argocd.svc.cluster.local, not a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com. Proceed insecurely (y/n)? y

Username: dev1
Password: <dev1パスワードを入力>
'dev1:login' logged in successfully

Context 'a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443' updated

試しにArgoCD UIにアクセスしてみると、アカウントも登録されていることが確認できます。

f:id:FY0323:20211230212828p:plain

※参考:

Projectの作成

次にProjectを作成します。Projectは AppProject というカスタムリソースで定義するか、 argocd proj create コマンドで作成できます。今回利用したyamlファイルはこちらです。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: dev1
  namespace: argocd
spec:
  description: dev1 project
  sourceRepos:
    - '*'
  destinations:
    - namespace: '*'
      server: '*'
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
  roles:
    - name: dev1
      description: dev1 role for dev1 project
      policies:
        - p, proj:dev1:dev1, applications, get, dev1/*, allow

AppProjectspec 配下でどんなパラメータが利用できるか記載します。


  • description : Projectに関する記述
  • sourceRepos : 利用を許可するGitリポジトリを指定
  • destinations : 利用できるデプロイ先を指定
    • namespace : Namespaceの指定
    • server : クラスターURLの指定
  • clusterResourceBlacklist : 作成を禁止するcluster-scope (StorageClass CustomResourceDefinition ClusterRole など) のリソースを指定
    • group : Groupの指定
    • kind : Kindの指定
  • clusterResourceWhitelist : 作成を許可するcluster-scopeのリソースを指定
    • group
    • kind
  • namespaceResourceBlacklist : 作成を禁止するnamescope-scope (Pod Service ResourceQuota など)のリソースを指定
    • group
    • kind
  • namespaceResourceWhitelist : 作成を許可するnamescope-scopeのリソースを指定
    • group
    • kind
  • orphanedResources : ArgoCD applicationに属さないリソースのモニタリングを有効化するとき、warningメッセージを出すか否か
    • warn : true / false を指定
  • roles : Projectと紐づけるRBACロールを指定
    • name : Role名
    • description : Roleの説明
    • policies : ポリシーの記載
    • groups : Roleと紐づけるOIDCグループ
    • jwtTokens : Roleと紐づけるJWTトーク
  • signatureKeys : Gitコミットに対してSyncを許可するためのPGP keyのリスト
    • keyID : 16進数で示したキーID
  • syncWindows : Project内でSyncを実行するタイミングを制御する
    • applications : アプライするApplicationのリスト
    • clusters : アプライするクラスターのリスト
    • duration : Syncを許可する有効期間を指定
    • kind : Syncを許可するか禁止するかを指定。allow / deny を選択
    • manualSync : 手動Syncを許可するか否か。 true false を選択
    • namespaces : アプライするnamespaceのリスト
    • schedule : Cron形式でSyncを開始するスケジュールを指定
    • timeZone : TimeZoneの指定

このうち spec.roles.policies は Casbin-format形式でポリシーを定義します。ポリシーの記載は以下のようなルールで行います。

p, <role/user/group>, <resource>, <action>, <object>, allow

resource action で利用できるのは、以下の通りです。

  • resource: clusters projects applications repositories certificates accounts gpgkeys
  • action: get create update delete sync override action

例えば proj-name というProjectにある role-name というroleに対し、同じProject内の Application へのアクセスを許可する場合は、以下のように設定します。

spec:
  roles:
    - name: test-role
      policies:
        - p, proj:proj-name:role-name, applications, get, proj-name/*, allow

なお、後ほど出てきますが、AppProject でポリシーを指定する場合、resource で利用できるのは applications だけのようです。Projectで利用するポリシーは argocd-rbac-cm ConfigMapでも指定できるのですが、そちらでは repositories など別の resource も指定できます。

ここで上記 AppProject の定義ファイルをデプロイします。なお、デプロイ前には default というProjectが既にあることも確認できます。

# adminアカウントでArgoCDにログインし直す
$ argocd login a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443

# default Projectの確認
$ argocd proj list
NAME     DESCRIPTION  DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
default               *,*           *        */*                         <none>                        <none>          disabled

# dev1 Projectの作成
$ kubectl apply -f proj-dev1.yaml 
appproject.argoproj.io/dev1 created

# 作成後の確認
$ argocd proj list
NAME     DESCRIPTION   DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
default                *,*           *        */*                         <none>                        <none>          disabled
dev1     dev1 project  *,*           *        */*                         <none>                        <none>          disabled

$ argocd proj get dev1
Name:                        dev1
Description:                 dev1 project
Destinations:                *,*
Repositories:                *
Allowed Cluster Resources:   */*
Denied Namespaced Resources: <none>
Signature keys:              <none>
Orphaned Resources:          disabled

# dev1 ProjectのRoleを確認
$ argocd proj role list dev1
ROLE-NAME  DESCRIPTION
dev1       dev1 role for dev1 project

RBACの設定

次にRBACの設定を行うため、 argocd-rbac-cm ConfigMapを修正します。ここでアカウントとRoleを紐づけることで、アカウントからProjectを操作することが可能になります。

argocd-rbac-cm では policy.csv というdataを指定し、そこに必要なポリシーを定義します。ここでは先ほどProjectで設定したRBACポリシーとアカウントとを紐づける設定を行います。アカウントとポリシーとを紐づける場合は、以下のようなルールで定義します。

g, <user/group>, <role>

なお、ArgoCDはデフォルトで readonly admin というポリシーと admin アカウントが用意されています。ポリシーの内容は builtin-policy.csvで定義されており、 adminアカウントは全てのリソースに対する権限を持っています。

$ kubectl edit cm argocd-rbac-cm -n argocd

~~~
(中略)

data:
  policy.csv: |
    g, dev1, proj:dev1:dev1
~~~

configmap/argocd-rbac-cm edited

アクセスのテスト

ここまでで dev1 アカウントから dev1 Project中のApplicationにアクセスできる設定が完了しました。ここで dev1 Projectにテスト用のApplicationを作成してみます。

admin アカウントでArgoCD UIにログインし、テスト用Applicationを作成します。

f:id:FY0323:20211230214343p:plain

次に dev1 アカウントでUIにログインすると、上記Applicationは画面上に確認できます。一方で、上記ApplicationをSync / deleteなどしようとすると、権限が足りないというメッセージが表示されます。

f:id:FY0323:20211230214510p:plain

f:id:FY0323:20211230214607p:plain

dev1 Projectに新しいApplicationを作成しようとしても、同じくエラーメッセージが出力されます。

f:id:FY0323:20211230214727p:plain

またProjectの画面を見ると、 dev1 Projectのみが表示されており、 default Projectは確認ができません。

f:id:FY0323:20211230214838p:plain

一応CLIからも試すと、同様にエラーメッセージが出力されます。

# dev1アカウントでログイン
$ argocd login a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443

# Applicationは確認できる
$ argocd app list
NAME             CLUSTER                         NAMESPACE  PROJECT  STATUS  HEALTH   SYNCPOLICY  CONDITIONS  REPO
     PATH       TARGET
guestbook-admin  https://kubernetes.default.svc  default    dev1     Synced  Healthy  <none>      <none>      https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD

# sync / deleteはできない
$ argocd app sync guestbook-admin
FATA[0000] rpc error: code = PermissionDenied desc = permission denied: applications, sync, dev1/guestbook-admin, sub: dev1, iat: 2021-12-29T04:08:03Z 

$ argocd app delete guestbook-admin
Are you sure you want to delete 'guestbook-admin' and all its resources? [y/n]
y
FATA[0002] rpc error: code = PermissionDenied desc = permission denied: applications, delete, dev1/guestbook-admin, sub: dev1, iat: 2021-12-29T04:08:03Z 

# dev1 Projectしか確認できない
$ argocd proj list
NAME  DESCRIPTION   DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
dev1  dev1 project  *,*           *        */*                         <none>                        <none>          disabled

簡単ですが、ここまででProjectの基本的な利用方法を紹介しました。

※参考:

Projectに関連する機能

ここからProjectと関連する他の機能を紹介します。

Orphaned Resource Monitoring

Orphaned resource monitoringは、ArgoCD Applicationで管理されないリソースを検知し、ArgoCD UIからそのリソースを確認・削除する機能を提供します。Orphaned resource monitoringを有効化するかは AppProject の定義で制御することができます。

ここでは先ほどの dev1 Projectの定義に、以下のようにOrphaned resourceの設定を追加します。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: dev1
  namespace: argocd
spec:
 
(中略)
 
  orphanedResources:
    warn: true

設定後に再度デプロイすると、UIからApplicationでの管理外リソースとして kubernetes EndpointSliceを検知します。

$ kubectl apply -f proj-dev1.yaml 
appproject.argoproj.io/dev1 configured

$ argocd proj get dev1
Name:                        dev1
Description:                 dev1 project
Destinations:                *,*
Repositories:                *
Allowed Cluster Resources:   */*
Denied Namespaced Resources: <none>
Signature keys:              <none>
Orphaned Resources:          enabled (warn=true)

$ kubectl get endpoints
NAME           ENDPOINTS                            AGE
guestbook-ui   192.168.2.161:80                     64m
kubernetes     192.168.0.202:443,192.168.2.65:443   3h2m

f:id:FY0323:20211230215459p:plain

※参考:

Global Project

Global Projectは ver 1.8で追加された機能で、他のProjectに同じ設定を引き継ぐことができます。

ここではまず以下のようにGlobal Projectのソースとなる global Projectを作成します。 global には sourceRepos destinations の設定をしています。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: global
  namespace: argocd
spec:
  description: global project
  sourceRepos:
    - '*'
  destinations:
    - namespace: 'argocd'
      server: '*'
# global Projectの作成
$ kubectl apply -f proj-global.yaml
appproject.argoproj.io/global created

$ argocd proj list
NAME     DESCRIPTION     DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
default                  *,*           *        */*                         <none>                        <none>          disabled
dev1     dev1 project    *,*           *        */*                         <none>                        <none>          enabled (warn=true)
global   global project  *,argocd      *        <none>                      <none>                        <none>          disabled

次にGlobal Projectの設定を argocd-cm ConfigMapに追加します。 argocd-cm には、Global Projectの指定と、どのProjectに設定を引き継ぐかの条件を追加します。現在は Project にラベルを付与し、その設定から条件に合致するものを検索する形でのみ利用できるようです。

argocd-cm には以下のように data.globalProjects という設定を追加し、 matchExpressions にてラベルの条件を記載します。

$ kubectl edit cm argocd-cm -n argocd

~~~
(中略)
data:
  accounts.dev1: login
  globalProjects: |-
    - labelSelector:
      matchExpressions:
        - key: env
          operator: In
          values:
          - dev
      projectName: global
kind: ConfigMap
~~~

configmap/argocd-cm edited


# 追加後の設定内容
$ kubectl describe cm argocd-cm -n argocd
Name:         argocd-cm
Namespace:    argocd
Labels:       app.kubernetes.io/name=argocd-cm
              app.kubernetes.io/part-of=argocd
Annotations:  <none>

Data
====
accounts.dev1:
----
login
globalProjects:
----
- labelSelector:
  matchExpressions:
    - key: env
      operator: In
      values:
      - dev
  projectName: global
Events:  <none>

次に、条件に合致するような test Projectを作成します。こちらには clusterResourceWhitelist のみを設定しています。 global Projectから設定を引き継ぐことができれば、ここに sourceRepos destinations が追加されるはずです。

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: test
  namespace: argocd
  labels:
    env: dev
spec:
  description: test project
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'

ここで上記Projectを作成してみますが、CLIからは特に設定が引き継がれていないように見えます。

# test Projectの作成
$ kubectl apply -f proj-test.yaml 
appproject.argoproj.io/test created


# 作成後の設定内容
$ argocd proj list
NAME     DESCRIPTION     DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
default                  *,*           *        */*                         <none>                        <none>          disabled
dev1     dev1 project    *,*           *        */*                         <none>                        <none>          enabled (warn=true)
global   global project  *,argocd      *        <none>                      <none>                        <none>          disabled
test     test project    <none>        <none>   */*                         <none>                        <none>          disabled

$ kubectl describe appproject test -n argocd
Name:         test
Namespace:    argocd
Labels:       env=dev
Annotations:  <none>
API Version:  argoproj.io/v1alpha1
Kind:         AppProject
Metadata:
  Creation Timestamp:  2021-12-29T05:20:47Z
  Generation:          1
  Managed Fields:
    API Version:  argoproj.io/v1alpha1
    Fields Type:  FieldsV1
    fieldsV1:
      f:metadata:
        f:annotations:
          .:
          f:kubectl.kubernetes.io/last-applied-configuration:
        f:labels:
          .:
          f:env:
      f:spec:
        .:
        f:clusterResourceWhitelist:
        f:description:
    Manager:         kubectl-client-side-apply
    Operation:       Update
    Time:            2021-12-29T05:20:47Z
  Resource Version:  28205
  UID:               5064245e-8823-4dd4-85c2-9cbbbb301c71
Spec:
  Cluster Resource Whitelist:
    Group:      *
    Kind:       *
  Description:  test project
Events:         <none>

一方でUIを確認すると、こちらでは以下のようにGlobal Projectの設定を引き継ぎ、 sourceRepos destinations の設定が追加されています。

f:id:FY0323:20211230220139p:plain

UIでは Inherited from Global Projects というパートもあり、こちらにはGlobal Projectの設定内容が確認できます。

f:id:FY0323:20211230220307p:plain

このあと作成した global-test ProjectでApplicationを作成しましたが、argocd namespaceを指定することで作成できることを確認できました。また argocd でなく default namespaceを指定するとエラーが出力されるのも確認できました。

$ argocd app list
NAME             CLUSTER                         NAMESPACE  PROJECT  STATUS  HEALTH   SYNCPOLICY  CONDITIONS               REPO
                  PATH       TARGET
guestbook-admin  https://kubernetes.default.svc  default    dev1     Synced  Healthy  <none>      OrphanedResourceWarning  https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD
guestbook-test   https://kubernetes.default.svc  argocd     test     Synced  Healthy  <none>      <none>                   https://github.com/argoproj/argocd-example-apps.git  guestbook  HEAD

※参考:

Project scoped Repositories & Clusters

Project scoped Repositories / Clustersは ver 2.2で追加された機能です。概要は以前触れましたが、Project単位で別れたチームに対し、リポジトリクラスターを追加する権限を付与するための機能になります。

ここでは argocd-rbac-cm ConfigMapにポリシーを追加することで、 dev1 アカウントがリポジトリを追加できるようにします。当初は先ほどと同様Projectの中にポリシーを追加しようとしたのですが、ProjectからはApplicationリソースに対するポリシーしか設定することができないようです。

こちらのIssueに対応策が書かれていたのですが、将来的にはProjectからも repositories など別のリソースに対するポリシーを設定できるようになるかもしれません。

まずは検証用に project-scoped Projectを作成します。

kind: AppProject
metadata:
  name: project-scoped
  namespace: argocd
spec:
  description: project-scoped project
  sourceRepos:
    - '*'
  destinations:
    - namespace: '*'
      server: '*'
  clusterResourceWhitelist:
    - group: '*'
      kind: '*'
  roles:
    - name: dev1-scope
      description: role for project-scoped repo
      policies:
        - p, proj:project-scoped:dev1-scope, applications, *, project-scoped/*, allow
# dev1 アカウントでログイン
$ argocd login a9ed881bfa2bd42248dca9000f88f583-776281281.ap-northeast-1.elb.amazonaws.com:443

# project-scoped Projectの作成
$ kubectl apply -f proj-scoped.yaml 
appproject.argoproj.io/project-scoped configured

# 作成後の確認
$ argocd proj list
NAME            DESCRIPTION             DESTINATIONS  SOURCES  CLUSTER-RESOURCE-WHITELIST  NAMESPACE-RESOURCE-BLACKLIST  SIGNATURE-KEYS  ORPHANED-RESOURCES
dev1            dev1 project            *,*           *        */*                         <none>                        <none>          enabled (warn=true)  
project-scoped  project-scoped project  *,*           *        */*                         <none>                        <none>          disabled

次にここでは以下のように argocd-rbac-cm ConfigMapに dev1-scope というポリシーを追加し、 dev1-scope ポリシーと dev1 アカウントを紐づけます。

$ kubectl edit cm argocd-rbac-cm -n argocd

~~~
(中略)
data:
  policy.csv: |
    g, dev1, proj:dev1:dev1
    p, role:dev1-scope, repositories, *, project-scoped/*, allow
    g, dev1, role:dev1-scope
~~~

configmap/argocd-rbac-cm edited

最後に project-scoped Projectを指定しながらリポジトリを追加します。なお argocd repo add コマンドで --project オプションが有効なのは ver 2.2以降なので、 argocd CLIのバージョンが古い場合はアップデートが必要です。

# リポジトリを追加
$ argocd repo add --name project-scoped https://github.com/argoproj/argocd-example-apps.git --project project-scoped
Repository 'https://github.com/argoproj/argocd-example-apps.git' added

$ argocd repo list
TYPE  NAME            REPO                                                 INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
git   project-scoped  https://github.com/argoproj/argocd-example-apps.git  false     false  false  false  Successful           project-scoped

※参考:

その他

Projectをどのように分割するかについては、具体的な事例も少なく、まだBest practiceのようなものは共有されていないようです。ArgoCDでApp-of-appsパターンを利用する場合のProjectについては、こちらの記事が参考になりそうです

※参考:

【メモ】AWS CodeBuildでECRへのアクセス情報を設定するときの選択肢

以前CodeCommit / CodeBuildで ECR / EKS向けのCI/CDパイプラインを作った際、CodeBuild中でECRへのアクセス情報を設定する方法がよくわかっていなかったので、備忘録として書き残しておきます。

各値の取得方法

Amazon ECRへコンテナイメージを格納するには、AWSアカウントID / リージョン / リポジトリ名の3つの値が必要となります。前提として、CodeBuildプロジェクトが作成されたのと同じ環境の情報を取得する場合を書きます。

アカウントID

アカウントを取得する場合は、例えばAWS CLIのコマンド出力をクエリするか、Arnの値から抜き出すことで取得できます。

$ aws sts get-caller-identity --query 'Account' --output text

$ echo ${CODEBUILD_BUILD_ARN} | cut -f 5 -d :

※参考:

リージョン

リージョンを利用する場合は、CodeBuildの環境変数である AWS_DEFAULT_REGION を指定することでリージョン名を取得できます。

※参考

ECRリポジトリ

リポジトリが1つしかない場合は、 aws ecr describe-repositories コマンドの出力からクエリするのが簡単そうです。またこの方法だとリポジトリURIを取得できるので、上記2つの値を取得する必要もなくなります。

$ aws ecr describe-repositories --query 'repositories[0].repositoryName' --output text       
test-ecr

$ aws ecr describe-repositories --query 'repositories[0].repositoryUri' --output text        
000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/test-ecr

リポジトリが複数ある場合、上記コマンドのリストの指定を変更するか、パラメータで直接指定する方法になりそうです。

また、ECRをCloudFormationで作成している場合は、ECR作成時にRepositoryUri を出力することで、CodeBuildから指定することができます。

※参考

別アカウントの場合

別アカウントの情報を簡単に取得することは基本的にできなさそうなので、パラメータで直接指定することになりそうです。

StackSetsを使ってのクロスアカウントなリソース作成後にリソースを参照できたりするんでしょうか。

パラメータでの指定方法

CodeBuildではいくつかパラメータの指定方法があるので、そちらも書いておきます。

buildspec.yaml 中で指定

buildspec.yaml では env にて変数を指定することができます。また指定方法は3種類あり、それぞれ variables parameter-store secrets-manager になります。

variables はkey: value形式で変数を指定するシンプルな形式である一方、 parameter-store はSystems Manager Parameter Storeに格納した情報を、 secrets-manager はSecrets Managerに格納した情報を利用することができます。

※参考:

CodeBuildの EnvironmentVariables

CodeBuildの設定でパラメータを指定することもできます。 EnvironmentVariables にkey: value形式で指定することで、 buildspec.yaml に変数を渡すことができます。

またCloudFormationの Parameter の値を EnvironmentVariables に渡して利用することもできます。

AWSTemplateFormatVersion: "2010-09-09"
Description: CodeBuild
 
Parameters:
  ecrRepo:
    Type: String
    Default: "test-ecr"
Resources:
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: codebuild-test
      Description: Test project for AWS CI/CD
      ServiceRole: !GetAtt BuildRole.Arn
      Artifacts:
        Type: NO_ARTIFACTS
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/docker:18.09.0
        EnvironmentVariables:
        - Name: "ECR_REPO"
          Value: !Ref ecrRepo

ArgoCDの軽量ディストリビューション ArgoCD Coreを動かしてみる

今回はArgoCD CoreというArgoCDの新たなディストリビューションを動かしてみました。

ArgoCD Coreとは

ArgoCD Coreは ArgoCDの ver 2.1で導入された、ArgoCD distributionになります。ArgoCDはデフォルトだとSSO / RBAC / ユーザー管理などの機能を搭載しており、1つのArgoCDインスタンスを複数のチームが利用できる、マルチテナントをサポートしています。

一方でArgoCDを利用するユーザーの中には、マルチテナントをサポートする機能が不要であり、ArgoCDの提供するGitOps的な機能だけを使いたい人も大勢います。そういったユーザー向けに登場したのがArgoCD Coreです。

ArgoCD Coreでは、デフォルトのArgoCDディストリビューションから一部機能を削減し、管理者にとってよりシンプルに利用できるようになっています。特徴は以下の通りです。

  • ArgoCDにアクセスする際、アカウント・パスワード情報を入力しなくてよい
  • ArgoCDにアクセスするためにAPIを外部に公開する必要はない

ArgoCD Coreを動かす

ここからArgoCD Coreを動かします。環境は以下の通り。

インストールはいつもの通り、namespaceの作成とArgoCD関連リソースのデプロイを行います。

$ kubectl create namespace argocd

$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.1.7/manifests/core-install.yaml

デプロイ後は以下の通り。デフォルトと比べると、Dex ServerやArgoCD Serverが無かったり、一部コンポーネントが削減されている様子が見えます。

$ kubectl get pods -n argocd
NAME                                  READY   STATUS    RESTARTS   AGE
argocd-application-controller-0       1/1     Running   0          60s
argocd-redis-5b6967fdfc-2prrf         1/1     Running   0          60s
argocd-repo-server-7598bf5999-brjvv   1/1     Running   0          60s

$ kubectl get svc -n argocd
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)             AGE
argocd-metrics       ClusterIP   10.100.41.200    <none>        8082/TCP            80s
argocd-redis         ClusterIP   10.100.80.99     <none>        6379/TCP            80s
argocd-repo-server   ClusterIP   10.100.204.189   <none>        8081/TCP,8084/TCP   80s

$ kubectl get cm -n argocd
NAME                        DATA   AGE
argocd-cm                   0      90s
argocd-cmd-params-cm        0      90s
argocd-gpg-keys-cm          0      90s
argocd-rbac-cm              0      90s
argocd-ssh-known-hosts-cm   1      90s
argocd-tls-certs-cm         0      90s
kube-root-ca.crt            1      2m18s

$ kubectl get secret -n argocd
NAME                                        TYPE                                  DATA   AGE
argocd-application-controller-token-7sk75   kubernetes.io/service-account-token   3      99s
argocd-redis-token-9rggh                    kubernetes.io/service-account-token   3      99s
argocd-secret                               Opaque                                0      98s
default-token-zsthn                         kubernetes.io/service-account-token   3      2m26s

ここからArgoCD Coreを操作してみます。ArgoCDを操作するためにまずはログインをします。ArgoCD CoreではAPIを公開せずとも argocd login –core と実行するだけでログインが完了できるので、初期セットアップの手間がかなり省けます。

$ argocd login --core
Context 'kubernetes' updated

ArgoCD Coreを操作するには argocd CLIを利用しますが、Coreを利用する場合は作業前に操作するNamespaceを argocd に変更する必要があります。変更しないと argocd-cm を見つけられず、 argocd CLIが実行できないようです。

# Namespace変更前
$ argocd version
FATA[0000] configmap "argocd-cm" not found

$ argocd app list
FATA[0000] configmap "argocd-cm" not found


# 変更後
$ kubectl config set-context --current --namespace=argocd
Context "testuser@eks-cluster.ap-northeast-1.eksctl.io" modified.

$ argocd version
argocd: v2.1.6+a346cf9
  BuildDate: 2021-10-28T19:59:40Z
  GitCommit: a346cf933e10d872eae26bff8e58c5e7ac40db25
  GitTreeState: clean
  GoVersion: go1.16.5
  Compiler: gc
  Platform: linux/amd64
argocd-server: v2.1.6+a346cf9
  BuildDate: 2021-10-28T19:59:40Z
  GitCommit: a346cf933e10d872eae26bff8e58c5e7ac40db25
  GitTreeState: clean
  GoVersion: go1.16.5
  Compiler: gc
  Platform: linux/amd64
  Ksonnet Version: unable to determine ksonnet version: exec: "ks": executable file not found in $PATH
  Kustomize Version: could not get kustomize version: exec: "kustomize": executable file not found in $PATH
  Helm Version: v3.4.2+g23dd3af
  Kubectl Version: v0.21.0
  Jsonnet Version: v0.17.0

$ argocd app list
NAME  CLUSTER  NAMESPACE  PROJECT  STATUS  HEALTH  SYNCPOLICY  CONDITIONS  REPO  PATH  TARGET

ArgoCD UIへアクセスをする場合は argocd admin dashboard を実行するとポートフォワーディングされ、 http://localhost:8080 へアクセスすることでUIが表示されます。

$ argocd admin dashboard
Argo CD UI is available at http://localhost:8080

UIは以下の通りです。ぱっと見は通常のArgoCDとほぼ変わらないように見えますが、ログインはしておらずunknown userとして認識されます。

f:id:FY0323:20211218195851p:plain

Projectも利用できますが、マルチテナント向けの機能なので、いずれなくなるんじゃないかと思ってます。

f:id:FY0323:20211218195906p:plain

f:id:FY0323:20211218195920p:plain

もちろんUIからアプリケーションを登録・デプロイすることもできます。

f:id:FY0323:20211218195929p:plain

感想

今回はArgoCD Coreを動かしてみました。軽く触った感じでは、これまでArgoCDを使う上でちょっと煩わしい部分を省きつつ、ArgoCDで実現したいGitOpsの機能を使うこともできるように感じたので、これからArgoCDを試す場合や小規模なチーム・プロジェクトなどで使う分には十分ではないかと感じました。気になる方は使い始めてみるのもよいかもしれません。

参考リンク