はじめに
前回の記事のまとめを読んでからだと、雰囲気がつかみやすいかも?
実現したかったこと
GitHub Actionsを用いてgoの静的解析、テスト/ビルドを行うCIを構築したい。
その際、毎回モジュールのインストールが挟まると時間がかかっちゃうので、キャッシュを使って短縮したい。という話の続き。
前回の記事の段階では、キャッシュヒット、と通知されたが、go vet
やgo test
を行う際にモジュールのインストールが発生する。保存したはずのキャッシュが使えてねえ...
起きていたこと
中身がキャッシュされていなかった事件
キャッシュ自体はされていましたが、モジュールがキャッシュされていませんでした。なんのためのキャッシュや...
原因を調べる
静的解析、テスト/ビルドをわけた、問題のyamlは以下。ちょっと長め。
... 省略
jobs:
# cacheが関係ないジョブ
static-check:
name: StaticCheck
runs-on: ubuntu-latest
container: golang:1.12
steps:
# set go version
- name: Set up Go 1.12
uses: actions/setup-go@v1
with:
go-version: 1.12
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
# cache保存用にディレクトリを作っておく
- name: before cache
run: |
mkdir -p ~/go/pkg/mod
# cache
- name: cache
uses: actions/cache@v1
id: cache-go
with:
path: ~/go/pkg/mod
key: ${{ env.cache-version }}-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# cacheがヒットしたか確認
- name: after cache
run: |
echo "-->${{steps.cache-go.outputs.cache-hit}}<--"
# キャッシュがヒットしなかったら install module
- name: Get dependencies
if: steps.cache-go.outputs.cache-hit != 'true'
run: |
... 省略
# Run fmt
... 省略 fmt, lint, vetを行う
build:
name: Test-Build
runs-on: ubuntu-latest
needs: [static-check]
services:
... 省略
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.12
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
... 省略
# cache保存用にディレクトリを作っておく
- name: before cache
run: |
mkdir -p ~/go/pkg/mod
# cache
- name: cache
uses: actions/cache@v1
id: cache-go
with:
path: ~/go/pkg/mod
key: ${{ env.cache-version }}-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# cacheがヒットしたか確認
- name: after cache
run: |
echo "-->${{steps.cache-go.outputs.cache-hit}}<--"
# キャッシュがヒットしなかったら install module
- name: Get dependencies
if: steps.cache-go.outputs.cache-hit != 'true'
run: |
... 省略
# Run test
... 以下省略。テストとビルドを行います。
原因はここ。
container: golang:1.12
はい。これだけです。static-check
のjobには設定されていますが、build
の方にはないですね...。
container
指定の何があかんかったかというと、GOPATHに違いがでてしまうことでした。
container
を使用した時と、使用なかった時で、GOPATHが変わっているんですね...。
それぞれ、pathを比較してみました。
container
まず、container
を使用した時のPATH
container を指定していた時のPATH
// $GOPATH
/go
// $HOME
/github/home
// $PATH
/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
goのモジュールは、/go/pkg/mod
に入ります。こいつをキャッシュしたいわけですね。
actions/setup-go
次に、actions/setup-go
を使用したのPATH。
actions/setup-go だけの時のPATH
// $GOPATH
// $HOME
/home/runner
// $PATH
/opt/hostedtoolcache/go/1.12.17/x64/bin:/usr/share/rust/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/runner/.dotnet/tools:/home/runner/.config/composer/vendor/bin
$GOPATH
が表示されない!ですが、go env
で確認してみると、$GOPATH=/home/runner/go
に設定されていることが確認できます。
goのモジュールは、/home/runner/go/pkg/mod
に入ります。
つまり?
前日の、ディレクトリを作ったらいけた!という現象は。。。
-
~/go/pkg/mod
を作成したが、 - 実際にモジュールがインストールされているのは
/go/pkg/mod
つまり、空っぽのディレクトリをキャッシュしてたんですね。なんというアホ。
試したこと
双方、containerを使用してみる
キャッシュをtarで展開するときに、permission denied
にハマりまくった。/go
にアクセスすることが困難なので、chmod
なりで対抗してみましたが、流石にきびしい。。。
4/15追記: そもそもpathの設定をミスっていた気がする
片方だけcontainerを使用
static-check
はcontainer
を指定し、build
では指定しない、という方法。
actions/cacheのpath
に違いが出る以外は何も変わらないが、actionsの実行結果をみていると、コンテナの初期化に時間がかかっていることがわかりました...。
containerを使用しない
一瞬でうまくいきました...golint
やgoimports
はPATHの関係で、少しいじりましたが、それ以外はとても快適。
どのくらい時間に差が出るのか
static-check
、build
の2つのjobを完了するまでに、どれだけの時間がかかるかみてみました。
- キャッシュを一切使わない: 4m15s
- 片方だけcontainerを使用、キャッシュを使う: 3m20s
- 双方containerを使用せず、キャッシュを使う: 2m36s
containerの初期化が凄まじい。最終的にはキャッシュを使うことで、1m45sの短縮。やるじゃん...。
まとめ
container
を指定したのは理由が、actions/setup-go
だと/go/bin
にpathが通っていなかったことでした。
golint
や、goimports
をPATHの追加や、~/go/bin/golint
などと書かなくてよくなるので、、、、。
いけるじゃん!って思う前に、中身がどうなってるのか確認しないとダメだなあ...
最終的なyaml
※ testにmysqlが必要なので環境を用意しています。
name: Go
on:
pull_request:
branches:
- '*'
env:
cache-version: v10
jobs:
static-check:
name: StaticCheck
runs-on: ubuntu-latest
steps:
# set go version
- name: Set up Go 1.12
uses: actions/setup-go@v1
with:
go-version: 1.12
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
# cache store
- name: cache
uses: actions/cache@v1
id: cache-go
with:
path: ~/go/pkg/mod
key: ${{env.cache-version}}-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: after cache
run: |
echo "-->${{steps.cache-go.outputs.cache-hit}}<--"
- name: Install go modules
if: steps.cache-go.outputs.cache-hit != 'true'
run: |
echo "run install go module"
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
# Run fmt
- name: Go fmt
run: |
go get -u golang.org/x/tools/cmd/goimports
gofmt -s -w cmd/
~/go/bin/goimports -w cmd/
# Run lint
- name: Go Lint
run: |
go get -u golang.org/x/lint/golint
~/go/bin/golint ./...
# Run vet
- name: Go vet
run: |
go vet ./...
build:
name: Test-Build
runs-on: ubuntu-latest
needs: [static-check]
services:
mysql:
image: mysql:5.7
ports:
- 3306:3306
options: --health-cmd "mysqladmin ping -h localhost" --health-interval 20s --health-timeout 10s --health-retries 10
env:
MYSQL_ROOT_PASSWORD: pass
MYSQL_DATABASE: sample
steps:
- name: Set up Go 1.12
uses: actions/setup-go@v1
with:
go-version: 1.12
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: SetUp ddl
run: |
mysql --protocol=tcp -u root -ppass sample < ./ddl.sql
# cache store
- name: cache
uses: actions/cache@v1
id: cache-go
with:
path: ~/go/pkg/mod
key: ${{env.cache-version}}-${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# cacheがヒットしたか確認
- name: after cache
run: |
echo "-->${{steps.cache-go.outputs.cache-hit}}<--"
- name: Get dependencies
if: steps.cache-go.outputs.cache-hit != 'true'
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Test
run: go test -v ./...
- name: Build
run: go build -v ./cmd/main.go