Github Actionsでbranch作成/削除にフックしてFeature環境を構築する

最近Github Actionsを触る機会があったのですが、まだ自分のgithub accountはbetaのwait list待ちで業務で使ってるrepostioryでしか使えないので、使い方とかポイントを忘れないようにメモ。ついでにいくつか公式のactionにPR送ったり、KubernetesIngress Rulesを編集するためのActionを公開していたりもするので、こちらも紹介します。

Feature環境の自動作成

業務では開発中の機能を手軽にDev環境で確認するために、特定の命名規則に従ったブランチ名でGithubにpushすると、自動でdev環境のKubernetesクラスターにリリースしIngressでエンドポイントを用意して閲覧できるようにしています。これ自体はそんなに珍しくなくて検索するといくつか同じような記事が見つかります。

これまでもSpeeeさんの事例のようにwebhook eventを監視してoperationを行うサーバーを用意して解決することはできました。弊社ではCircle CIなどでfeature環境を作成したりもしています。 ただSpeeeさんの事例では自分たちでサーバーを用意して運用しないといけません。また弊社がこれまでやっていたようにCIサービスでfeature環境を作成する場合にはbranchの削除にトリガーできません。社内の別のチームではbotを立ててbranchの削除を監視してたりもしたみたいですが、これだけのためにbotたてるのも少し手間になります。

GithubのあらゆるイベントにトリガーできるGithub Actionsを使えば、branchがpushされたときにfeature環境を作成し、branchが削除されたときにfeature環境を削除するといったオペレーションを、自分たちでサーバーを管理することなく実現できます。業務ではKubernetesを使っているので、全体像としては次のような感じになります。

f:id:nwpct1:20190222231923j:plain
ざっくりした全体像。実際にはmicroserviceが30以上あり、それらのtraffic routingまではコントロールできる環境を用意できていないので、webのツールや動画配信サーバー(HLS playlist, MPEG-DASH manifest, MPEG-2 TSやFragmented MP4といったメディアセグメントを提供)など一部のコンポーネントだけがこの機能を有効にしています。

  1. feature-abc のように feature-*命名規則に従ってbranchを作成しGithubにPush
  2. Kubernetes Deploymentをbranch用に作成
  3. Kubernetes Serviceをfeatureブランチ用に新規で作成 1
  4. Ingress (ingress-gce) でエンドポイント作成 2
  5. Google Cloud DNSのRecordsetsの作成
  6. https://feature-abc-webapp.foo.com でアクセスして動作確認

Github Actions

基本的な使い方は公式ドキュメントをみてください。

https://developer.github.com/actions/

いくつか悩んだり調べた中でメモしておきたいポイントを中心に残します。

credential情報の管理

外部に漏れては困る情報は Secret によりGithub RepositoryのSettingsで指定できます (参照 https://developer.t.com/actions/creating-workflows/storing-secrets/)。 Actionsを追加する際にも「Secret」というフィールドがありますが、そこから指定してもやってることは同じです。

実は1月頃に一度Github Actionsの利用を検討したことがあったのですが、当時はまだLimited Public Beta期間中でProduction Secretsを保存してはいけませんでした。 今回はLimited Public Betaがとれたため、改めてGithub Actionsを調査することにしました。

f:id:nwpct1:20190222164218p:plain
github-action-secrets

ブランチ名のフィルター

pushイベントに対してすべてトリガーしてほしいわけではなく、特定の命名規則に従ったbranchでのみ実行してほしいものです。GITHUB_REFS という環境変数の中に refs/head/feature-A のような形式でブランチ名やタグ名が入っています。 refs/head/のprefixを削除して利用すればOKです。公式で用意されている↓のactionがこの操作をしてくれているのでこちらを利用しましょう。

bin/filter at master · actions/bin · GitHub

f:id:nwpct1:20190222164042p:plain
github-actions-filter

ただbranch削除時のfilterにはこの方法が使えません。 delete triggerは GITHUB_REF にdefault branchつまりmasterを指定が指定されています。環境変数からbranch名を取り出すことはできません。そのかわり GITHUB_EVENT_PATH 環境変数が示す場所にWebhookのevent情報がそのままjson形式で入っています。

delete でtriggerしたときは DeleteEvent の形式なので、 ref フィールドよりブランチ名が取り出せます。公式で用意してほしい機能なので↓にPRをだしました。

github.com

まだマージされていないので c-bata/bin/filter@master を指定して使っています。 deleted_branch feature-* のようにargsを指定すれば使えます。

マージされたので公式の actions/bin/filter@master を使用してください。そちらには deleted_tag フィルターも追加しています。

f:id:nwpct1:20190227010255p:plain
c-bata/actions/filter deleted_branch feature-*

GCPのService Accountからgcloudの認証を行う

f:id:nwpct1:20190227010437p:plain
Activate gcloud command using service-account

公式で用意されている↓のactionを用いることで実現できます。Service AccountはSecret GCLOUD_AUTHbase64 encodeしたservice accountのjsonファイルを与えればOKです (ex: base64 ./service-account.json )。

gcloud/auth at master · actions/gcloud · GitHub

少し驚いたのですがgcloudコマンドの実行は別のactionとして定義し、↓を利用して実行します。

gcloud/cli at master · actions/gcloud · GitHub

gcloudコマンドのcredential情報は、Homeディレクトリ以下に作成されます。Github Actionsは裏側で /github/workspace を常にマウントしそこをHomeディレクトリに設定しているようです。このディレクトリは次のactionでもそのままの状態で引き継がれます。gcloudの認証とgcloudコマンドの実行は別のactionでやるのがGithub Actionらしいやり方なようです。

kubectlの実行

gcloud authができるようになったので、kubernetes clusterのcredentials情報を取得してkubectlを実行します。既存でよさそうなものがなかったのですが、https://github.com/actions/gcloud で管理されるのがみんな幸せかと思うので PRを出しました。

Add github action for kubectl by c-bata · Pull Request #9 · actions/gcloud · GitHub

gcloudコマンドにならって、PROJECT_IDやZONE、K8S clusterをセットするactionとkubectlの実行用actionを分割しました。まだマージはされていないので c-bata/gcloud/kubectl-config@master および c-bata/gcloud/kubectl@master を指定して使っています。

f:id:nwpct1:20190227010829p:plain

ingress rules書き換えツールの実行

deploymentsやserviceをfeature環境ごとに個別に作っていたようにingressもfeature環境ごとにつくることもできるのですが、大きいチームだったので大量にFeature環境が立ち上がりLoadbalancerの作成上限に引っかかったことがありました。そのため全てのfeature環境で1つのingressを使いまわし、Spec.Rulesに振り分け設定を追加して webapp.foo.comfeature-a-webapp.foo.com を振り分けています。管理の都合上もその方がいいかなと思います。IngressのSpec.Rulesの編集にはもともとnodeで書かれたscriptが社内で使われていたのですが、kubectlのwrapperになっていてclient-goが使えるGoで書いたほうが色々楽だったので今回書き直しました。↓で公開しています。

GitHub - abema/github-actions-ingress-rules-editor: Edit ingress rules to build feature environments automatically on Github Actions.

c-bata/go-actions

github actionsの調査もかねてutilityライブラリ作りました。 正直使うほどでもないシーンが多いと思いますが、よければ使ってみてください。

GitHub - c-bata/go-actions: go-actions provides the utilities for Github Actions.

面倒だったこと

branchを削除したときにはmasterブランチのmain.workflowが参照され、実行されます。そのためbranchの削除にtriggerして何らかの処理を行いたいとき、一度そのbranchをmasterにマージして削除しないと動作確認ができません。

Add deleted_branch and deleted_tag filters by c-bata · Pull Request #42 · actions/bin · GitHub みたいな機能はとりあえず書いてmasterにマージしてbranchを削除して、問題があればまたbranchを作ってmasterにマージしてbranchを削除しないと確認できずmasterのcommit logが結構汚れます。仕事のrepositoryでそれをやることになったので申し訳ないなと思いながら開発してました。

おわりに

はやく自分のrepositoryでも使ってみたい


  1. 執筆時点では ingress-gce がClusterIPへのヒモ付に対応してないのでServiceTypeはNodePortを使用しています。NodePortの番号は特に指定していないのでKubernetes側にrandomに割り振ってもらっています。

  2. 次の手順でGoogle Cloud DNS Recordsetsを作りますが、もしそちらをterraformで管理して消し忘れとかをなくしたいのであれば、Google compute address(静的IPアドレス)の払い出しもTerraformで行って、Ingress側の metadata.annotations.kubernetes.io/ingress.global-static-ip-name で指定して使うのが管理の都合上いいかと思います。