kind local registryでCI内でテストを回す

Kubernetes関連のツールを作ったときなどに,E2Eテストをしたいことがある.実際にKubernetesクラスタに入れてみて,動かしてみてどうか,というのは開発環境でやるにしても結構手間だ. そういうときにkindはすごく便利なんだけど,これのlocal registryは更に便利だった.

kind内で使うdocker imageどうする問題

kindは1コマンドでローカルにkubernetesクラスタを作ってはくれる. ただ,内部でPodを動かすにはdocker imageをどこかからpullできる必要がある.

E2Eテストをする場合,成果物のdocker imageをkind内で動かしてほしい場合がほとんどだろうから,これをどこかにpushして,kind内でpullできる必要がある.

publicなdocker registryで良いのであれば,例えはhub.docker.comやghcr.ioなどにpushしておいて,それを使う必要がある. privateなdocker registryであれば,kindにデプロイするPodの定義に,imagePullSecrets を指定しておく必要があるだろう.

いずれにしろ,以下のような手順を踏む必要はある.

  1. 成果物をdocker buildする
  2. public/privateなdocker registryにpushする
  3. 上記docker imageを利用するような環境変数,設定等を書く
  4. 上記docker imageを使う形でE2Eテストをkind内で動かす

さて,これをCI内で実行しようとすると,更に問題が発生する.

forkされてPRをもらったときにsecretsは使えない問題

E2Eテストとしてkindを使う場合,それをCIに組み込むだろう.GitHub ActionsやCircleCIに組み込んだ場合,一つ大きな問題がsecretsをどうするかという問題だ. パスワードやAccessToken等は,GitHub Actionsであればsecretsに,CircleCIであれば環境変数に書くのが普通だろう. ただ,こうしたsecretsは基本的にforkされたリポジトリでは読み出せないようになっている.

docs.github.com

リポジトリがforkされた場合,そしてそこからPullRequestが出された場合,CIとしてはPullRequestのhookで動くことになる.ただしリポジトリ自体はforkした人のものであり,そこにどのようなコードをpushするかはforkした人に委ねられる. そのため,CIのstep内で echo とかしてしまえば環境変数は全部見えるのだ.

だから,secrets等もそこで読み出せてはいけないのである.

ただ,そうなると問題なのは,前述のdocker imageをどこにpushするかという問題だ. hub.docker.comにしろghcr.ioにしろ,pushするにはAccessTokenが必要になり,それはsecretsだ. ということは,これはforkされたリポジトリからのPullRequestでは使えない.

local registryでimageを外部にpushせずにkind内で使う

いろいろ考えたが,そもそも外部にpushするのは諦めたほうが良いのではないかという結論に至った.どう考えてもsecretsなしでpushはできないし,secretsを読み込めた時点でセキュリティ的にNGだろう.

こういうときに使えるのが kind local registryだ.

kind.sigs.k8s.io

これにより,localhostにdocker registryを立てることができ,そこにdocker imageをpushすることができる.そして,そのregistryはkind内のPodからアクセス可能だ.このフローの中にはsecretsは一切出てこない.そのため,secretsの管理を考える必要がなく,forkされたリポジトリだろうが普通に実行可能だ.

というわけで,以下のようなGitHub Actionsのworkflowを書いた.

env:
  IMAGE_NAME: my-image
  KIND_VERSION: v0.10.0
  KUBECTL_VERSION: v1.20.2

jobs:
  e2e-test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/setup-go@v2
        with:
          go-version: '^1.15.0'
      - uses: actions/checkout@master
      - name: Install kind
        env:
          KIND_VERSION: ${{ env.KIND_VERSION }}
          BIN_DIR: ${{ github.workspace }}/tools/
        run: |
          mkdir -p $BIN_DIR
          curl -sSLo "$BIN_DIR/kind" "https://github.com/kubernetes-sigs/kind/releases/download/$KIND_VERSION/kind-linux-amd64"
          chmod +x "$BIN_DIR/kind"
          echo "$BIN_DIR" >> "$GITHUB_PATH"
      - name: Install kubectl
        env:
          KUBECTL_VERSION: ${{ env.KUBECTL_VERSION }}
          BIN_DIR: ${{ github.workspace }}/tools/
        run: |
          mkdir -p $BIN_DIR
          curl -sSLo "$BIN_DIR/kubectl" "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
          chmod +x "$BIN_DIR/kubectl"
          echo "$BIN_DIR" >> "$GITHUB_PATH"
      - name: Setup kind
        run: |
          ./scripts/kind-with-registry.sh
      - name: Info
        run: |
          kind version
          kubectl cluster-info
          kubectl version
      - name: Build docker image
        run: |
          IMAGE_ID=localhost:5000/$IMAGE_NAME
          SHA=${{ github.sha }}
          docker build . --file Dockerfile --tag $IMAGE_ID:$SHA
          docker push $IMAGE_ID:$SHA
      - name: Install ginkgo
        run: |
          go get -u github.com/onsi/ginkgo/ginkgo
      - name: Testing
        run: |
          IMAGE_ID=localhost:5000/$IMAGE_NAME
          SHA=${{ github.sha }}
          export IMAGE=$IMAGE_ID:$SHA
          go mod download
          ginkgo -r ./e2e

./scripts/kind-with-registry.sh は,ここのものをまるごとコピーした.

これでkindによるE2Eテストは,secretsを使う必要がなくなった.