GitHub Enterprise(オンプレミス版) のActionsでAMIの作成を自動化したときに詰まったポイント

GitHub Enterprise Server(オンプレミスで動かせるやつ)を使ってるんですが、
v3.0.0からついにActionsがGAとなりました。

* CI周りにあまりコストをかけられない(saasが使いにくい)
* AWSのCodePiplineがGitHub Enterpriseに対応してない(awsがユースケースにマッチしなかった)
* CI周りの管理にあまり時間をかけたくない(Jenkinsのプラグインアップデートの面倒見るの疲れる)
* オープンソースのCIツールの運用構築に時間をかけたくない(学習や運用構築、ツール選定に時間がかかりすぎる)

ということで、Actions使いたいなーと思ってたので、GAを機に使ってみました。


結局、セルフホストランナーが必要になるので、
ホストの面倒を見なければいけないつらさは残りますが、
多様なJenkinsのプラグインアップデートなどはあまり考えずに、
ランナーのアップデートにのみ注力できる点は魅力的です。

---

今回はpackerでAMIを作成するまでに詰まったいくつかのポイントをリストします。

* Actionsのタブが出てこない
* RunnerプロセスをkillしてもGitHub上では生きた状態とみなされる
* Actionが動いている最中に、Runnerプロセスを停止するとタイムアウトするまで動いてる状態とみなされる
* 公式のaction以外のuseを使うには設定がいる
* terratestが通らない

ちなみに、セルフホストランナーはAutoScalingGroupで構築したEC2インスタンスを使っています。

実際にはオートスケールさせていません。
オートスケールしたくなった時にしやすいようにASGを使っています。
またヘルスチェックが失敗したときに自動で復旧してくれることを狙っています。

dockerやk8sやecsを利用しなかったのは、
docker on dockerや、挟むミドルウェアが増えることで考慮する事項が増えること。
どれだけ利用されるか分からない状況下で細かな運用構築するよりは、
必要になったタイミングで考えることが適切と思ったからです。

## Actionsのタブが出てこない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143313/711c12c9-6703-8993-e62f-74d2f2b97728.png)

ちゃんと設定しないと、まずここが出ません。
ちゃんとというのは、EC2で使う場合、

* Actionsを有効にする
* 成果物をアップロードするためのS3のバケットを設定する
* そのバケットにアクセスするためのアクセスキーとシークレットキーを設定する

これらがうまく設定されていないとタブが出てきません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143313/cc4404e5-a835-dae3-d0b8-0a4212097eef.png)

はじめ、IAMロールに権限を付与しているため、バケットの設定だけで良いと考えて設定していました。
結局AccessKeyを設定しないとうまくいかないということが判明し、アクセスキーを設定して解消しました。

ちなみに権限もうまく設定されていないと動きません。

## RunnerプロセスをkillしてもGitHub上では生きた状態とみなされる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143313/d2018f51-b05e-9d7b-1ef1-6344f68c6375.png)

この画像の状態が、今使えるよー、という表示ですが、Runnerプロセスをkillした時でもこの状態になっています。

これはセルフホストランナーが動作しているインスタンスを更新する際にネックになります。

5分～10分程度経過すると使えない状態になりますが、
インスタンス更新時にはこのホストは別のEC2インスタンスに入れ替わっているので、
表示されているホストはもはや不要な状態になります。
GitHubにいらなくなったホストはもはや認識しておいてほしくないわけです。

そもそも、実際は動かせない状態なのに、
動かせると認識されているのはあまりよくありません。
なんかよく分からないけどエラーが出た、状態になってしまいます。

これはデフォルトのユニットファイルを利用していると避けられなさそうだったので、
停止処理をラップしたスクリプトを作成しました。

停止処理中にGitHubのAPIを利用して、グループから自分自身を外す、という流れです。

```shell
  curl \
    -s \
    -X DELETE \
    -H "Accept: application/vnd.github.v3+json" \
    -H "Authorization: bearer ${ghe_api_key}" \
    https://ghe.domain.name/api/v3/orgs/${orgs}/actions/runner-groups/${id}/runners/${runnerid}
```

グループを指定して動かしてもらうようにすれば、
グループから外した段階でそのランナーが利用されることはなくなります。

ghe_api_keyはadmin:orgの権限を持ったキーです。
パラメータストアに保持して停止時に取得しています。

## Actionが動いている最中に、Runnerプロセスを停止するとタイムアウトするまで動いてる状態とみなされる

ActionがRunnerに割り当てられている場合、前の図でみられるIdleはBusyの状態になります。

Busyの状態でプロセスを停止したらどうなるかなと思ってやってみたところ、
タイムアウトするまでアクションはぐるぐる動いているような状態となっていました。

オートスケールなどで停止する際に、プロセスをいきなり停止すると死ぬってことですね。

ということで、これに関しても、停止時にBusyの状態なら待機するように設定しました。

```shell
function retrieve_runner_status() {
  orgs=$1
  runnerid=$2

  curl \
    -s \
    -H "Accept: application/vnd.github.v3+json" \
    -H "Authorization: bearer ${ghe_api_key}" \
    https://ghe.domain.name/api/v3/orgs/${orgs}/actions/runners/${runnerid} | \
  jq -r ".busy"
}

while true; do
  if [ "$(retrieve_runner_status ${orgs} ${runnerid})" != "false" ];then
    sleep 1
    continue;
  fi
  break;
done
```

## 公式のaction以外のuseを使うには設定がいる

```yaml
- uses: actions/checkout@v2
```

これが使えたから普通にuses使えるんだなと思ってたら、単純には使えませんでした。

https://docs.github.com/ja/enterprise-server@3.0/admin/github-actions/about-using-actions-on-github-enterprise-server

ここに記載されている通り、公式のactionはserverにバンドルされた状態になっています。
公式以外を使う場合はconnectまたはsyncを利用が推奨されています。

## terratestが通らない

* AMIを作成
* terraformでそのAMIを使ってリソースを立てる
* httpで通信したり、sshしてプロセスの有無や、ファイルの存在を確認するなどテストする
* 後片付けする

というようなterratestのコードを使っています。
このテストを動かすのが意外と苦労しました。

結果だけ貼るとこんな感じになりました

```yaml
on:
  push:
    branches:
      - "master"
    paths:
      - "yyyyy/**"
      - ".github/workflows/yyyyy.yaml"
jobs:
  test:
    runs-on: [self-hosted, dev]
    timeout-minutes: 30
    steps:
      - uses: actions/setup-go@v1
        with:
          go-version: 1.13
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2.1.2
        with:
          node-version: '14'
      - uses: hashicorp/setup-terraform@v1.3.1
        with:
          terraform_version: 0.13.1
          terraform_wrapper: false
      - uses: hashicorp-contrib/setup-packer@v1.0.0
        with:
          packer-version: 1.6.5
      - name: Get dependencies
        run: go mod download
        working-directory: ./yyyyy/test
      - name: Test code
        run: go test -v -timeout 30m .
        working-directory: ./yyyyy/test
      - name: Notify test status
        uses: 8398a7/action-slack@v3.9.0
        with:
          github_base_url: https://ghe.domain.name
          status: ${{ job.status }}
          text: yyyyy image test
          fields: repo,message,commit,workflow,author,took,eventName,ref
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: always()
  build:
    needs: test # run after test
    runs-on: [self-hosted, dev]
    timeout-minutes: 20
    container:
      image: hashicorp/packer:1.4.3
    steps:
      - uses: actions/checkout@v2
      - name: packer build packer.json
        run: packer build packer.json
        working-directory: ./yyyyy
      - name: Notify build status
        uses: 8398a7/action-slack@v3.9.0
        with:
          github_base_url: https://ghe.domain.name
          status: ${{ job.status }}
          text: yyyyy image build
          fields: repo,message,commit,workflow,author,took,eventName,ref
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: always()
```

* go modで必要なモジュールをダウンロードする必要がある

https://medium.com/@petriautero/automate-terraform-testing-with-github-actions-and-terratest-78d74331fdf8

CIツール上で動かしてなかったので、go mod initなど上記のブログを参考にCI用に仕立て上げる必要があります。

* terratest上でterraform/packerを利用しているためランナー上にバイナリのダウンロードが必要

setup-terraformが良い感じなのですが、公式にsetup-packerがありません。

https://github.com/marketplace/actions/packer-github-actions

こういうアクションも用意されていますが、terratest上で使うのとはちょっと違います。

なので非公式ですが、それっぽいのがあったので利用しました。

* `/usr/bin/env: node: No such file or directory`でエラーになる

nodejsが内部で動くらしく、そもそもnodejs入れてないので、
`- uses: actions/setup-node@v2.1.2` も追加してやらないと動かないという事態でした。

* terraform output がパースできなかった

https://github.com/gruntwork-io/terragrunt/issues/1202

このissueに引っかかり、terratest内でoutputが処理できなかったとのこと。

解決方法にある通り、setup-terraformに、`terraform_wrapper: false`を指定しないとダメでした。


* その他
  * IAMロールを最小構成にしていたため、ec2のキーペアが取得できないといったエラーがありました
  * テストが10分くらいかかるので、タイムアウトに引っかかりました
  * woking-directoryを設定していなかったのでgo test時にファイルがないといわれました
  * ランナー起動時にグループに所属させるようなオプションがないのでこれもAPIで何とかしました

## まとめ

Actionsで運用を楽にしよう！と思ってオープンソースのCIツールを利用しないでいたけど、
結局いろいろ考えることって出てきますね。
そこそこ大変でした。