Argo CD が参照していないリポジトリからイメージバージョンを更新する方法
クラウドエース北野です。
Argo CD が参照していないアプリケーションのリポジトリから Kubernetes マニフェストのイメージバージョンを更新する CD パイプラインの作成方法を紹介します。
概要
アプリケーションのコードが格納されているリポジトリで次のようなタスクを実行すると、コンテナイメージのプッシュと同時にシステムのポッドのコンテナイメージのバージョンの更新ができます。
- アプリケーションのビルド
- コンテナイメージのプッシュ
- CD パイプラインのリポジトリのチェックアウト
- 構成ファイルのイメージバージョン更新
- 更新した構成ファイルのコミット
GitHub Actions で上記を実行するとき、3 ~ 5 のジョブのステップを次のように定義できます。
- name: Checkout Kubernetes Manifest Repository
uses: actions/checkout@v2
with:
repository: <Organization Name>/<Repository Name>
token: ${{ vars.PAT }}
path: .
- name: Update image version
working-directory: <MANIFEST FILE DIRECTORY PATH>
run: |
sed -i '<IMAGE PATH>/s/.*/\ \ value\:\ <IMAGE PATH>:${{ github.sha }}/' <MANIFEST FILE>
git config --local user.email ${{ vars.USER_EMAIL }}
git config --local user.name ${{ vars.USER_NAME }}
git add <MANIFEST FILE>
git commit -m <COMMENT>
git push
はじめに
Argo CD はベストプラクティスとして、アプリケーションのソースコードと、Kubernetes のマニフェストを別々のリポジトリで管理すべきだとしています。これはリポジトリを分けることで CI と CD を分離でき、本番環境へのアプリケーションのリリースを細かく制御できるということがあげられます。
アプリケーションのコードと Kubernetes のマニフェストを分離して管理すると、リリースのフローは次のようになります。
- コード開発
- コンテナイメージのイメージ格納リポジトリへのプッシュ
- Kubernetes マニフェストのイメージバージョンの更新
- Kubernetes マニフェストの適用
2 のタスクは GitHub Actions などの CI ツールで、4 のタスクは Argo CD などの CD ツールでそれぞれ自動化できます。そのため、リポジトリのファイルを更新すると、安全に本番環境をリリースできます。
しかし、リポジトリを分けて管理しているため、開発環境も同じフローで更新する必要があります。そのため、開発環境で機能を Kubernetes 上で検証しようと思うと、アプリケーションのコードの更新をして、Kubernetes のマニフェストを更新するという 2 回のコミットが必要となり、非常に煩雑なものとなります。
そこで、この記事では 2 から 3 のタスクをアプリケーションの GitHub リポジトリで自動化する方法を紹介します。この方法を適用すると、アプリケーションのリポジトリを更新すると、Kubernetes マニフェストも同時に更新されるので、1 回のコミットで開発環境の更新が実現されます。
GitHub Actions の設計
アプリケーションのリポジトリで次のような GitHub Actions のジョブを定義します。
- アプリケーションのビルド
- コンテナイメージのプッシュ
- Kubernetes マニフェストリポジトリのチェックアウト
- 構成ファイルのイメージバージョン更新
- 更新した構成ファイルのコミット
GitHub Actions で別のプライベートリポジトリをチェックアウトするには、次のように token に Personal Access Token (以降 PAT と呼びます)を指定すると実施できます。このとき PAT のスコープには repo
を選択したものをご利用ください。
- name: Checkout Kubernetes Manifest Repository
uses: actions/checkout@v2
with:
repository: <Organization Name>/<Repository Name>
token: ${{ secrets.PAT }}
path: .
repository で、別のリポジトリを組織/リポジトリ名で指定します。path でチェックアウトするパスを指定します。チェックアウトすると、リポジトリのルート以下の内容がすべてそのパスに展開されます。
GitHub Actions で変更内容をコミットするには、ユーザー情報を設定する必要があるので以下のようにタスクを定義します。
- name: Update image version in Kubernetes Manifest
working-directory: src/cloudrun/helloworld/overlayers/dev
run: |
git config --local user.email ${{ vars.USER_EMAIL }}
git config --local user.name ${{ vars.USER_NAME }}
git add <FILES>
git commit -m <COMMENT>
git push
user.email と user.name は、PAT を発行した GitHub アカウントのユーザー名と e-mail アドレスを代入してください。
イメージバージョンは、git のコミットハッシュを指定する必要があるため、sed コマンドを使い次のようにバージョンを置換します。
sed -i '/<CONTAINER IMAGE PATH>/s/.*/\ \ value\:\ <CONTAINER IMAGE PATH>:${{ github.sha }}/' <MANIFEST FILE>
これらの内容をまとめると、GitHub Actions は次のようになります。
name: Update image version
on:
workflow_dispatch:
jobs:
update_img_ver:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and Push Image
run: |
docker build -t <IMAGE PATH>:${{ github.sha }} .
docker push <IMAGE PATH>:${{ github.sha }}
- name: Checkout Kubernetes Manifest Repository
uses: actions/checkout@v2
with:
repository: <Organization Name>/<Repository Name>
token: ${{ secrets.PAT }}
path: .
- name: Update image version in Kubernetes manifest
working-directory: src/cloudrun/helloworld/overlayers/dev
run: |
sed -i '/<CONTAINER IMAGE PATH>/s/.*/\ \ value\:\ <CONTAINER IMAGE PATH>:${{ github.sha }}/' <MANIFEST FILE>
git config --local user.email ${{ vars.USER_EMAIL }}
git config --local user.name ${{ vars.USER_NAME }}
git add kustomization.yaml
git commit -m "update hello image version from app_sample_repo GH"
git push
GitHub Actions の設計内容の実施
GitHub Actions 設計内容を次のようなシステム構成で試してみます。
上記の構成を次のようにして構築します。
- Google Cloud 上への Artifact Registry の作成
- アプリケーションコードのリポジトリの GitHub Actions から Docker イメージの登録
- Google Kubernetes Engine (以降 GKE と呼びます)の作成
- GKE 上への Argo CD の作成
- Kubernetes マニフェストのリポジトリを SYNC する Argo CD の Application の作成
- Argo CD から Kubernetes リソースのデプロイ
さらに検証では、アプリケーションコードのリポジトリの GitHub Actions からイメージの登録と、マニフェストを更新して、Argo CD でイメージが更新されるのかを確認します。
事前準備
GitHub Actions で内容を試すために次の内容を構築します。
- Google Cloud での設定
- Artifact Registry の作成
- Workload Identity の作成
- GKE の作成
- Certificate Manager の作成
- Argo CD の設定
- Argo CD のインストール
- Application の作成
GKE を作成し、Argo CD をインストールする作業はこちらの記事を参照してください。
Google Cloud の設定
Docker イメージを登録する Artifact Registry は次のように作成します。
resource "google_artifact_registry_repository" "main" {
repository_id = <ARTIFACT REGISTRY REPOSITORY NAME>
project = var.project
location = "asia-northeast1"
format = "DOCKER"
}
GitHub Actions から Artifact Registry にプッシュするためには GitHub Actions を認証する必要があります。この記事では、Workload Identity を使い認証させます。Workload Identity の詳細については、こちらを参照してください。
Workload Identity および、認証に使うサービスアカウントは次のように作成します。
resource "google_service_account" "main" {
account_id = <SERVICE ACCOUNT ID>
project = var.project
}
resource "google_project_iam_member" "main" {
member = google_service_account.main.member
project = var.project
role = "roles/artifactregistry.writer"
}
resource "google_iam_workload_identity_pool" "main" {
workload_identity_pool_id = <WORKLOAD IDENTITY POOL NAME>
project = var.project
}
resource "google_iam_workload_identity_pool_provider" "main" {
workload_identity_pool_provider_id = <WORKLOAD IDENTITY POOL PROVIDER>
project = var.project
workload_identity_pool_id = google_iam_workload_identity_pool.main.workload_identity_pool_id
attribute_condition = "assertion.job_workflow_ref.contains(\"<GITHUB ORGANIZATION>/<GITHUB REPOSITORY>\")"
attribute_mapping = {
"google.subject" = "assertion.repository"
}
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
resource "google_service_account_iam_member" "main" {
service_account_id = google_service_account.main.account_id
role = "roles/iam.workloadIdentityUser"
member = format("principal://iam.googleapis.com/%s/subject/%s", google_iam_workload_identity_pool.main.name, <GITHUB ORGANIZATION>/<GITHUB REPOSITORY>)
}
サンプルアプリ
Kubernetes でデプロイするアプリケーションは、次のように Hello World! を返すものとします。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!\n")
})
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
このアプリケーションのコンテナイメージにする Dockerfile は次の通りです。
FROM golang as builder
WORKDIR /app
RUN go mod init helloworld
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /helloworld
FROM gcr.io/distroless/base-debian11
WORKDIR /
COPY /helloworld /helloworld
EXPOSE 8080
USER nonroot:nonroot
CMD ["/helloworld"]
Kubernetes マニフェスト
このコンテナイメージを次のようなディレクトリ構造の Kustomize でデプロイします。
.
├── base
│ ├── deployment.yaml
│ └── kustomization.yaml
└── overlayes
├── deployment.yaml
└── kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ./deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld
namespace: default
labels:
app: helloworld
backstage.io/kubernetes-id: helloworld
spec:
selector:
matchLabels:
app: helloworld
template:
metadata:
labels:
app: helloworld
spec:
containers:
- name: helloworld
image: $IMAGE
ports:
- containerPort: 8080
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
patchesJson6902:
- target:
kind: Deployment
name: helloworld
path: ./deployment.yaml
- op: replace
path: /spec/template/spec/containers/0/image
value: <IMAGE PATH>:<VERSION>
Argo CD の設定
Argo CD には Kubernetes のマニフェストを格納しているリポジトリを連携し、リポジトリを連携する次のようなアプリケーションを作成します。
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: helloworld
namespace: argocd
spec:
destination:
namespace: default
server: https://kubernetes.default.svc
project: default
source:
path: <MANIFEST FILE PATH>
repoURL: <REPOSITORY PATH>
targetRevision: HEAD
syncPolicy:
automated: {}
GitHub Actions
Hello World を表示するアプリケーションのコードを管理しているリポジトリに次の GitHub Actions を作成します。
name: Update helloworld image
on:
workflow_dispatch:
jobs:
update_img_ver:
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
defaults:
run:
working-directory: src/golang/helloworld
steps:
- uses: actions/checkout@v4
- name: Authenticate to google cloud
id: authorization-googlecloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ vars.WORKLOADIDENTITY }}
service_account: ${{ vars.SERVICEACCOUNT }}
- name: Set up Cloud SDK
id: setup-cloudsdk
uses: google-github-actions/setup-gcloud@v2
- name: Configure docker client
run: gcloud auth configure-docker asia-northeast1-docker.pkg.dev
- name: Build and Push Image
run: |
docker build -t ${{ vars.ARTIFACTREGISTRY }}/<IMAGENAME>:${{ github.sha }} .
docker push ${{ vars.ARTIFACTREGISTRY }}/<IMAGENAME>:${{ github.sha }}
- name: Checkout Kubernetes Manifest Repository
uses: actions/checkout@v2
with:
repository: AriaAquamarine/kubernetes_studies
token: ${{ secrets.PAT }}
path: .
- name: Update helloworld image version in Kubernetes Manifest
working-directory: src/k8s/helloworld/overlayes
run: |
sed -i '/<IMAGE PATH>/s/.*/\ \ value\:\ <IMAGE PATH>:${{ github.sha }}/' deployment.yaml
git config --local user.email ${{ vars.USER_EMAIL }}
git config --local user.name ${{ vars.USER_NAME }}
git add deployment.yaml
git commit -m "update hello image version from app_sample_repo GH"
git push
GitHub Actions の変数、シークレットは次の値を設定します。
変数名 | 内容 |
---|---|
WORKLOADIDENTITY | Workload Identity ID |
SERVICEACCOUNT | サービスアカウント email |
ARTIFACTREGISTRY | Artifact Registry のパス |
PAT | GitHub アカウントの PAT |
USER_EMAIL | GitHub アカウントの email |
USER_NAME | GitHub アカウント名 |
アプリケーションリポジトリからのイメージの更新
main.go のコードを 次のように Argo CD Test! と表示されるように変更して、GitHub にプッシュして GitHub Actions を実行してみます。
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Argo CD Test!\n")
})
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
このときのコミットハッシュは次のようになりました。
git log -1 | tee | head -n1
commit d89bbea08fc332419741046f161d3dc34b0bfe39
GitHub Actions を実行して、ジョブが完了した後に Argo CD の Application を Refresh して DIFF を確認します。
差分を確認すると、コンテナのイメージバージョンが git のコミットハッシュと一致していることが確認できます。
さいごに
Argo CD が参照していないアプリケーションコードのリポジトリから Kubernetes マニフェストのイメージバージョンを更新する方法を紹介しました。この方法を開発環境のデプロイに適用すると、開発を円滑にすすめることができるようになります。
この方法はどの環境にも適用できてしまうので、本番環境に適用してしまうと、安全なデプロイパイプラインを実現できなくなるので、導入には注意してください。また、開発環境に導入しても本番環境に適用できないように GitHub の Rules などを作成して、意図していない GitHub Actions が作成されないように保護してください。
Discussion