はじめに
今回はタイトルの通り、GItHub ActionsとGitHub Container Registryを利用したCI/CDを用意し、コンテナイメージのビルドからKubernetesクラスターへのデプロイまでを実行するサンプルを作成しました。
構成
CI/CDのパイプラインのパターンはいくつか考えられますが、今回メインで用意したのは、以下のような特徴を備えたものです。
- 管理対象はGoのコードとDockerfile、Kubernetesマニフェストファイル
- CIでは、Goのコード・Dockerfileの変更を伝えるPR発行時にGoのテストを実行し、テストに成功してMergeを行った際にDockerfileを用いたイメージビルドを実行する
- CDでは、Kubernetesマニフェストの変更を伝えるPRを発行した際、KubernetesマニフェストファイルのValidationを実行し、Mergeを行った際にKubernetesクラスターへのデプロイを実行する
今回の構成は以下のようになります。CI/CDパイプラインはGitHub Actionsで、コンテナイメージのレジストリはGitHub Contianer Registryを利用しています。KubernetesクラスターはAmazon EKSを利用していますが、kubectl
でアクセスできる環境であれば何でも良いと思います。
GitHub Actions
今回のサンプルはこちらのリポジトリに配置しています。
ディレクトリ構造は以下の通り。
. ├── .github │ └── workflows │ ├── docker-build-and-push.yaml │ ├── k8s-deploy.yaml │ ├── k8s-validate.yaml │ └── test-and-build-code.yaml ├── README.md ├── app │ ├── dockerfile │ ├── main.go │ └── main_test.go └── manifest ├── deployment.yaml └── test.sh
CIは以下のようなアクションを用意しました。コンテナイメージビルド時に ghcr.io
を指定して、GitHub Container Registryを利用しています。また secrets.USERNAME
という値を指定していますが、この理由については後述します。
test-and-build-code.yaml
name: CI test Go code on: pull_request: branches: - main paths: - 'app/**' jobs: test-and-build: runs-on: ubuntu-latest steps: - name: setup uses: actions/setup-go@v1 with: go-version: 1.15 - name: checkout uses: actions/checkout@v2 - name: go test run: | go test -v ./app - name: notification uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} fields: repo,message,commit,author,action,eventName,ref,workflow,job,took env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} if: always()
docker-build-and-push.yaml
name: CI docker build and push on: pull_request: branches: - main paths: - 'app/**' types: [closed] jobs: docker-build-and-push: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v2 - name: Set up Docker Builder uses: docker/setup-buildx-action@v1 - name: Log into GitHub Container Registry uses: docker/login-action@v1 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.CR_PAT }} - name: build container image uses: docker/build-push-action@v2 with: context: ./app file: dockerfile push: true tags: ghcr.io/${{ secrets.USERNAME }}/sample-app:latest - name: notification uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} fields: repo,message,commit,author,action,eventName,ref,workflow,job,took env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} if: always()
使用したActionsはこちら。
- Checkout
- Setup Go environment
- Build and push Docker images
- action-slack
- Docker Setup Buildx
- Docker Login
- Build and Push Docker Images
参考にしたドキュメントはこちら。
- Qiita - Github ActionsでGithub Container RegistryにDocker imageをpushする最小Workflow
- Qiita - Golang - Dockerfileの最小構成
- Zenn - GitHub ActionsでSlack通知するなら8398a7/action-slackが機能豊富かつお手軽で超便利
- DigitalOcean - Community/Question: Github Action to deploy Docker Image from Github Packages
CDは以下のようなアクションを用意しました。
k8s-validate.yaml
name: CD validate Kubernetes manifests on: pull_request: branches: - main paths: - 'manifest/**' - 'app/**' jobs: validation: name: validate k8s manifest runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v2 - name: validation uses: instrumenta/kubeval-action@master with: files: ./manifest/ version: 1.18 - name: notification uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} fields: repo,message,commit,author,action,eventName,ref,workflow,job,took env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} if: always()
k8s-deploy.yaml
name: CD deploy container to Kubernetes on: pull_request: branches: - main paths: - 'manifest/**' - 'app/**' types: [closed] jobs: deploy-to-EKS: name: deploy manifests to k8s runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v2 - name: login to AWS uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 - name: set kubectl uses: azure/setup-kubectl@v1 - name: deploy manifest env: KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} run: | echo "$KUBE_CONFIG" > /tmp/kubeconfig export KUBECONFIG=/tmp/kubeconfig kubectl apply -f manifest/ chmod +x manifest/test.sh sh ./manifest/test.sh - name: notification uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} fields: repo,message,commit,author,action,eventName,ref,workflow,job,took env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} if: always()
Kubernetes Podデプロイ後に実行しているテストスクリプト中で kubectl exec ~
を実行しています。GitHub ActionsではTTYを使用することができないため、 kubectl
オプションには -i
のみを指定しています。
使用したActionsはこちら。
参考にしたドキュメントはこちら。
利用時の前提条件
上記アクションを利用するうえで必要な設定も書いておきます。
GitHub Container Registryを利用可能にする
GitHub Container Registryは現在パブリックベータで提供されるため、GitHubでFeature Previewで有効にする必要があります。
またGitHub Container Registryにアクセスするために、GitHubのPersonal Access Tokenを用意する必要があります。
GitHub Secretsに必要な値を設定する
GitHub Secretsには以下の値を設定しています。
AWS_ACCESS_KEY_ID
: AWSへのアクセスに利用するアカウントIDAWS_SECRET_ACCESS_KEY
: AWSへのアクセスに利用するシークレットアクセスキーCR_PAT
: GitHub Container Registry用のPersonal Access Token。KUBE_CONFIG
: Kubernetesクラスターへアクセスするのに利用するkubeconfig
SLACK_WEBHOOK_URL
: SlackチャンネルにJobの実行結果を送信するためのIncoming Webhook URLUSERNAME
: コンテナレジストリへのイメージ格納時に使用
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
は、利用するクラウドプロバイダーに合わせて変更します。KUBE_CONFIG
はクラスター作成後の ~/.kube/config
ファイルの内容を設定します。
USERNAME
については少し補足すると、通常は repository_owner
を指定すればGitHubアカウント名を指定することができるのですが、私の作成したアカウント名に大文字が含まれており、このまま利用すると repository name must be lowercase.
というエラーが発生します。そのためここでは小文字に直したアカウント名を指定しています。
KubernetesからイメージをPullできるようSecretを設定する
GitHub Container Registryはデフォルトでプライベートレジストリになります。KubernetesクラスターがプライベートレジストリからイメージをPullするには、コンテナレジストリにアクセスするためのSecretリソースを作成し、イメージを利用するPodマニフェスト中でImagePullSecret
を設定する必要があります。
私はWindows端末でWSL2を利用しているため、以下のようなコマンドを実行してSecretリソースを作成しました。
# secretリソースの作成 $ kubectl create secret docker-registry regcred \ --docker-server=https://ghcr.io \ --docker-username=fy0323 \ --docker-password=<GitHub_Personal_Access_Token> secret/regcred created # 作成後の確認 $ kubectl get secret regcred -oyaml apiVersion: v1 data: .dockerconfigjson: <base64_decoded_data> kind: Secret metadata: creationTimestamp: "2021-05-02T08:11:01Z" managedFields: - apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:data: .: {} f:.dockerconfigjson: {} f:type: {} manager: kubectl-create operation: Update time: "2021-05-02T08:11:01Z" name: regcred namespace: default resourceVersion: "75368" selfLink: /api/v1/namespaces/default/secrets/regcred uid: ee113b17-4d53-4e19-b475-887f3acc32a2 type: kubernetes.io/dockerconfigjson
Podマニフェストでは、以下のように spec.imagePullSecrets
を指定します。
(中略) spec: containers: - name: app image: ghcr.io/fy0323/sample-app:latest ports: - containerPort: 8080 imagePullSecrets: - name: regcred
その他
今回はCIとCDを別のタイミングで実行するよう設定していますが、CIでコンテナイメージをビルドしたタイミングで、テスト環境などのクラスターに対してデプロイを実行したい場合もあります。その場合は、今回のアクションをベースに、アクションの実行パスやタイミングを変更することで実現できます。
また、それとは別に、本番環境へのデプロイは、指定のレビューアの許可を得ることで実行したい、という場合もあります。GitHub Actionsの Environment
の機能を利用することで、これを実現することが可能です。ただしこの機能はパブリックリポジトリでのみ利用可能なので、実際のプロジェクトで利用するには制限が発生しそうです。