preloader
技術

GolangのVendoringをCIでキャッシュしたときの話

By なんぞー 03 December 2019
GolangのVendoringをCIでキャッシュしたときの話

こんにちは、なんぞーです!

この記事は Go5 Advent Calendar 2019 の 3 日目の記事です!

日頃、業務や趣味で Golang を書いている皆様方の中には、CircleCI や Github Actions などの CI で自動化をなさっている方も多いと思います! 僕もその一人なのですが、Go modules の vendoring を CI でキャッシュした際にハマってしまったことがあったので、記事にしたいと思います。

何をしようとしたか

utility 系のライブラリ(中にサブパッケージがいくつかある)をプロジェクトや会社単位で作って、様々なプロジェクトで使い回すといったことは多々あると思います! その utility 系のライブラリを使用した開発を行っている際に、vendoring のキャッシュを CI 上で行い、CI の高速化を測ろうとしました

実際どんな感じで開発していたか

今回は開発プロジェクトを「project1」、utility ライブラリを「go-utility」とすることにします。

このような構成の utility ライブラリだとします。

開発初期、project1@v1.0 では example1example2 を使っていました。

その時の go.mod と go.sum は

module github.com/nandehu0323/project1

go 1.12

require (
	github.com/nandehu0323/go-utility v1.0.0
)
github.com/nandehu0323/go-utility v1.0.0 h1:[hash]
github.com/nandehu0323/go-utility v1.0.0/go.mod h1:[hash]

このようになっていました。

また CI の設定ファイルでは、Tag を付けるとgo mod vendorを行い、依存パッケージをキャッシュしテストに通過後、キャッシュされた依存パッケージを用いてビルドを行うという処理を設定していました。

version: 2
reference:
  work_dir: &work_dir /go/src/tmp

  golang_container: &golang_container
    docker:
      - image: circleci/golang:1.12
        environment:
          GO111MODULE: "on"
    working_directory: *work_dir

  test_go: &test_go
    run:
      name: Testing Go
      command: |
        go test ./...

  build_docker_image: &build_docker_image
    run:
      name: Build Docker Image
      command: |
        [ビルド処理]

jobs:
  test:
    <<: *golang_container
    steps:
      - checkout
      - run: go mod vendor
      - save_cache:
          name: Saving Go Vendor Cache
          key: go-vendor-cache-{{ checksum "go.sum" }}
          paths:
            - /go/src/tmp/vendor
      - *test_go

  build:
    <<: *build_container
    steps:
      - checkout
      - restore_vendor_cache:
          name: Restoring Go Vendor Cache
          keys:
            - go-vendor-cache-{{ checksum "go.sum" }}
            - go-vendor-cache-
      - *build_docker_image

workflows:
  version: 2
  test_-->_build:
    jobs:
      - test:
          filters:
            branches:
              only: /.*/
            tags:
              only: /^v.*/
      - build:
          requires:
            - test
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^v.*/

次に開発が進み project1@v1.1 になり、 example3 のパッケージを使う必要が出てきました。 その他の依存関係の変更はなく、go-utility の使用パッケージが増えました。

そこで、いつも通りタグを付けると CI が Build の時点で落ちてしまうようになりました。

build github.com/nandehu0323/project1/cmd/app: cannot load github.com/nandehu0323/go-utility/example3: open /app/vendor/github.com/nandehu0323/go-utility/example3: no such file or directory

go.modgo.sum にはちゃんと go-utility@v1.0.0 を使用するように記述されているし、 go mod tidy を行っても差分は見つかりません。

原因

原因としては、

  • go mod vendor は、サブパッケージが含まれている依存パッケージでは必要なサブパッケージのみを vendor ディレクトリに保存する
    • project1@v1.0で必要な example1example2 しか cache に含まれていない。(初歩的なミスで Go 使いの方からは笑われそうです笑)
  • 依存パッケージの追加および削除は行われていないので、go.mod および go.sum の差分はない
    • よって、 {{ checksum "go.sum" }} が変化しないので cache の更新が行われない

このような utility系のパッケージを使用していてそれ以外の依存関係のアップデートが無くutility系のパッケージ内の新しいサブパッケージをimportする 環境下では CI の設定に気をつけないとこのようなことが起こるみたいです。

ここで、僕は CI(CircleCI)の Cache を削除しようとしましたが、version2 ではRerun without cacheができないらしく、困っていました。

そこで、プロジェクトキャッシュのクリアを参考に設定ファイルを変更することにしました。

変更後

jobs:
  test:
    <<: *golang_container_config
    steps:
      - checkout
      - run: go mod vendor
      - save_cache:
          name: Saving Go Vendor Cache
          key: go-vendor-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "go.sum" }}
          paths:
            - /go/src/tmp/vendor
      - *test_go

  build:
    <<: *cloud-sdk_container_config
    steps:
      - checkout
      - restore_vendor_cache:
          name: Restoring Go Vendor Cache
          keys:
            - go-vendor-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "go.sum" }}
            - go-vendor-cache-{{ .Environment.CACHE_VERSION }}-
      - *build_docker_image

以降、同じような環境で再現した場合はCACHE_VERSIONを変えてキャッシュクリアをするように変更しました。

まとめ

今回は、かなり特殊な環境下で起こる現象だと思いますが、go mod vendorの動作などをちゃんと理解していなかったために起こったものでした。 なかなかこのような事象に遭遇することはないし、Gopher の皆さんなら余裕で解決できると思いますが、もしいつか困っている人がこの記事をみて解決の糸口にしてもらえたらなと思いこの記事を書き残します 👍

最後まで読んで頂きましてありがとうございました!

Twitter(@nandehu0323)もやっておりますのでぜひご感想やアドバイスお待ちしております!