まだ機能的に足りないところもあるが、頑張ったら使える感覚だった。
githubにもfeedbackが送れる所があれば送ろうと思う。
circleciでやっていたことはざっくり書くと以下。
- test系
- build系(only master)
- base imageのbuild & push
- k8s上で動かすprod imageのbuild & push
- deploy系(only master)
- GKE上にhelm secrets upgrade
これをgithub actionsに移行した際にcircleciとの差分を感じた機能は以下。
- slack通知
- 自分のリポジトリでは未実装、デフォルトは失敗するとメール通知が来る
- 未確認だが多分いろいろな人がbeta向けのslack通知actionsを書いているのではなかろうか
- そのうち良さげなactionsがあれば採用しようと思うが、野良を極力使いたくないのでできれば公式orgの中でサポートしてほしい
- 現状で使えそうなのは https://github.com/pullreminders/slack-action
- ただし自分でpayloadをstring形式のjsonで記述することになり個人的には辛いので待ち
- と言いながら自炊した GitHub Actions(beta)向けにslack通知プラグインを作った - 839の日記
- filter機能
- yml単位でしかbranchのfilter機能が存在しない
- 1つのworkflowに複数jobを定義して、jobの依存関係を持たせているときに困る
- e.g. featureブランチではtest jobだけ走らせるが、masterブランチではtest/buildを並列して走らせ、両方とも成功したらdeployを行う
- circleciのようにjob単位でbranch filterが行えるようになっていてほしい
- workflow(yml)を分けた場合は別workflowのjobの状態検知ができないことは確認
- yml単位でしかbranchのfilter機能が存在しない
- cache機能
- azure pipelineの方には一応あるようだが、github actionsにはドキュメントを読んだ感じだと見当たらなかった
- circleciはfreeかつprivateだと並列に走らせられないが、github actionsは並列実行できるので結果的にトントンぐらいの速度で終わっている
- circleciではbuild/testが直列だったが、github actionsになって並列実行になったためトータルの待ち時間が減ったがキャッシュがないのでjob単位の実行速度は伸びた
dirty hackをすればおそらくキャッシュも可能- https://github.com/actions/toolkit/tree/master/packages/tool-cache
- setup-goとかは内部的に上記を使ってキャッシュしている
- と思っていたが、実はキャッシュされていなかったのでissueを立てた*1が挙動の勘違いで仕様だった
- artifacts
- アップロードする方法はあるようだが*2、方法についてドキュメントが見つけられなかった*3
- 公式プラグインとして提供されていた https://github.com/actions/upload-artifact
- アップロードする方法はあるようだが*2、方法についてドキュメントが見つけられなかった*3
- environment variable
- step単位でしか指定できないので全てのstepで指定したいenvironment variableがあると記述が冗長になる
- secrets
- env keyでのみ指定できる模様
- circleciはenvとsecretが同一のような扱い(envは管理画面でもfilterされる)だが、github actionsはやや扱いが異なる
- 複数回使うsteps.ifの比較値をsecrets経由で一元管理にしようとしたが、使えないらしく弾かれた
- env keyでのみ指定できる模様
- dockerのprepare
- circleciでは明示的に指定する必要があったが、github actionsでは特にしていなく使えるのはDXがよかった
- merge時の挙動
- github actionsはmergeした際は2回actionが走る模様
これは不具合かも?と思ったが何か理由があるのかもしれない、自分で使う際は2回走ると完全に無駄なのでsteps.ifでfilterしている
yml自体の書き味の違いはざっくりと書くと以下の感じ。
circleci
version: 2.1 orbs: gcr: circleci/gcp-gcr@0.6.1 gke: circleci/gcp-gke@0.2.0 docker: circleci/docker@0.5.13 jobs: test-job: working_directory: /go/src/github.com/8398a7/app docker: - image: circleci/golang:1.12.6-stretch environment: CI: "true" GO111MODULE: "on" steps: - checkout - restore_cache: key: prepare-ci-tools-{{ checksum "scripts/prepare-ci-tools.sh" }} - run: name: prepare ci tools command: scripts/prepare-ci-tools.sh - save_cache: key: prepare-ci-tools-{{ checksum "scripts/prepare-ci-tools.sh" }} paths: - ./bin - run: name: helm-lint command: scripts/helm-lint.sh - run: name: kubeval command: scripts/kubeval.sh - restore_cache: key: go-mod-{{ checksum "go.mod" }} - run: name: go mod download command: go mod download - save_cache: key: go-mod-{{ checksum "go.mod" }} paths: - /go/pkg/mod/cache - run: name: golangci-lint run command: bin/golangci-lint run - run: name: test command: make test - run: name: build command: make build - store_artifacts: path: /go/src/github.com/8398a7/app/cover.html build-job: working_directory: /go/src/github.com/8398a7/app docker: - image: google/cloud-sdk steps: - checkout - setup_remote_docker - docker/check - run: name: pull app-base command: docker pull 8398a7/app-base:latest - docker/build: image: 8398a7/app-base dockerfile: build/Dockerfile extra_build_args: --cache-from 8398a7/app-base:latest tag: latest - docker/push: image: 8398a7/app-base tag: latest - gcr/build-image: image: app dockerfile: build/app/Dockerfile tag: $(get tag) - gcr/gcr-auth - gcr/push-image: image: app tag: $(get tag) deploy-job: machine: true environment: HELM_HOME: /home/circleci/.helm GOOGLE_APPLICATION_CREDENTIALS: /home/circleci/gcloud-service-key.json steps: - checkout - restore_cache: key: prepare-cd-tools-{{ checksum "scripts/prepare-cd-tools.sh" }} - run: name: prepare cd tools command: scripts/prepare-cd-tools.sh - save_cache: key: prepare-cd-tools-{{ checksum "scripts/prepare-cd-tools.sh" }} paths: - ./bin - gke/install - gke/init - run: name: get credentials command: gcloud container clusters get-credentials $GKE_CLUSTER - run: name: upgrade app command: bin/helm secrets upgrade prod-app ./deployments/app --install --wait --namespace app -f ./deployments/values.yaml -f ./deployments/secrets.yaml --set app.image.tag=$(get tag) workflows: version: 2 test-build-deploy-workflow: jobs: - test-job - build-job: filters: branches: only: master - deploy-job: requires: - test-job - build-job filters: branches: only: master
github actions
name: CI on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - uses: actions/setup-go@v1 with: version: 1.12.6 id: go - run: scripts/prepare-ci-tools.sh - run: scripts/helm-lint.sh env: CI: true - run: scripts/kubeval.sh env: CI: true - run: go mod download - run: bin/golangci-lint run - run: make test - run: make build - uses: actions/upload-artifact@master with: name: coverage path: cover.html build: runs-on: ubuntu-latest steps: - uses: actions/checkout@master if: contains(github.ref, 'master') - name: docker login run: echo $DOCKER_PASSWORD | docker login -u 8398a7 --password-stdin docker.io if: contains(github.ref, 'master') env: DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - name: pull app-base run: docker pull 8398a7/app-base:latest if: contains(github.ref, 'master') - name: build app-base run: docker build -t 8398a7/app-base:latest --cache-from 8398a7/app-base:latest -f build/Dockerfile . if: contains(github.ref, 'master') - name: push app-base run: docker push 8398a7/app-base:latest if: contains(github.ref, 'master') - name: build gcr.io/gcp_project/app run: docker build -t gcr.io/gcp_project/app:$(get tag) -f build/app/Dockerfile . if: contains(github.ref, 'master') - name: install gcloud sdk run: | export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - sudo apt-get update && sudo apt-get install -y google-cloud-sdk if: contains(github.ref, 'master') - name: initialize gcloud sdk run: | echo $GCLOUD_SERVICE_KEY > ${HOME}/gcloud-service-key.json gcloud auth activate-service-account --key-file=${HOME}/gcloud-service-key.json gcloud --quiet config set project gcp_project gcloud --quiet config set compute/zone gcp_zone gcloud auth configure-docker --quiet --project gcp_project env: GCLOUD_SERVICE_KEY: ${{ secrets.GCLOUD_SERVICE_KEY }} if: contains(github.ref, 'master') - name: push gcr.io/gcp_project/app run: docker push gcr.io/gcp_project/app:$(get tag) if: contains(github.ref, 'master') deploy: runs-on: ubuntu-latest needs: [test, build] steps: - uses: actions/checkout@master if: contains(github.ref, 'master') - name: install gcloud sdk run: | export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - sudo apt-get update && sudo apt-get install -y google-cloud-sdk if: contains(github.ref, 'master') - name: initialize gcloud sdk run: | echo $GCLOUD_SERVICE_KEY > ${HOME}/gcloud-service-key.json gcloud auth activate-service-account --key-file=${HOME}/gcloud-service-key.json gcloud --quiet config set project gcp_project gcloud --quiet config set compute/zone gcp_zone env: GCLOUD_SERVICE_KEY: ${{ secrets.GCLOUD_SERVICE_KEY }} if: contains(github.ref, 'master') - run: scripts/prepare-cd-tools.sh env: HELM_HOME: /home/runner/.helm if: contains(github.ref, 'master') - name: get credentials run: gcloud container clusters get-credentials gke_cluster if: contains(github.ref, 'master') - name: upgrade app run: bin/helm secrets upgrade prod-app ./deployments/app --install --wait --namespace app -f ./deployments/values.yaml -f ./deployments/secrets.yaml --set app.image.tag=$(get tag) env: HELM_HOME: /home/runner/.helm GOOGLE_APPLICATION_CREDENTIALS: /home/runner/gcloud-service-key.json if: contains(github.ref, 'master')
見ての通り、circleciのjob単位branch filter機能がないのでそれに該当するものは全てにsteps.ifでブランチのfilterをかけている。
また、masterマージ時の2回発火を防ぐためgithub.event.afterでも条件を書いている。また、master時にbranch delete eventでjobを走らせないためにgithub.event.deletedを見て条件を書いている。- 8/17頃からbranch delete eventでは発火しなくなったようなので条件を書く必要がなくなった
正直全てのstepで同じifを何度も書くのは見栄えもメンテナンス性も悪いので、job単位branch filterはほしいなぁと感じた。
ちなみにworkflowをrerunさせたときにはgithubで取得できる値がちょっと違うようで、deploy系がうまく動作しなかった。
"ref": "refs/heads/master", "sha": "dd47c626ef90ca4ee193d02a1cc0a253a5ba53a6", "repository": "8398a7/app", "repositoryUrl": "git://github.com/8398a7/app.git", "actor": "8398a7", "workflow": "CI", "head_ref": "", "base_ref": "", "event_name": "push", "event": *** "action": "rerequested", "check_suite": *** "after": "dd47c626ef90ca4ee193d02a1cc0a253a5ba53a6", "app": ***
event.check_suiteの下にafterが生えるようになっていた。
"description": "Powers your .github/main.workflow.",
とか書かれてるし、まだHCLのものの挙動を引きずってたりするのかもしれない…。
上記のような感じで移行してみたが、circleciのfree plan上限が割と厳しい月もあるのでgithub actionsに移行してこのあたりは解消されそうなのがよかった。
GAになるまでに改良されたり変更されたりすると思うので継続的に更新していきたいと思う。