TECHSTEP

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

【メモ】GitHub ActionsとGitHub Container Registryを利用したCI/CDパイプラインの例

はじめに

今回はタイトルの通り、GItHub ActionsとGitHub Container Registryを利用したCI/CDを用意し、コンテナイメージのビルドからKubernetesクラスターへのデプロイまでを実行するサンプルを作成しました。

構成

CI/CDのパイプラインのパターンはいくつか考えられますが、今回メインで用意したのは、以下のような特徴を備えたものです。

今回の構成は以下のようになります。CI/CDパイプラインはGitHub Actionsで、コンテナイメージのレジストリGitHub Contianer Registryを利用しています。KubernetesクラスターはAmazon EKSを利用していますが、kubectl でアクセスできる環境であれば何でも良いと思います。

f:id:FY0323:20210504141133j:plain

f:id:FY0323:20210504141150j:plain

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はこちら。

参考にしたドキュメントはこちら。


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で有効にする必要があります。

f:id:FY0323:20210504130938j:plain

またGitHub Container Registryにアクセスするために、GitHubのPersonal Access Tokenを用意する必要があります。

f:id:FY0323:20210504131014j:plain

GitHub Secretsに必要な値を設定する

GitHub Secretsには以下の値を設定しています。

  • AWS_ACCESS_KEY_ID : AWSへのアクセスに利用するアカウントID
  • AWS_SECRET_ACCESS_KEY : AWSへのアクセスに利用するシークレットアクセスキー
  • CR_PAT : GitHub Container Registry用のPersonal Access Token。
  • KUBE_CONFIG : Kubernetesクラスターへアクセスするのに利用する kubeconfig
  • SLACK_WEBHOOK_URL : SlackチャンネルにJobの実行結果を送信するためのIncoming Webhook URL
  • USERNAME : コンテナレジストリへのイメージ格納時に使用

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 の機能を利用することで、これを実現することが可能です。ただしこの機能はパブリックリポジトリでのみ利用可能なので、実際のプロジェクトで利用するには制限が発生しそうです。

f:id:FY0323:20210504133843j:plain