Closed10

CI環境でプライベートなリポジトリをgo getしたい

ぱんだぱんだ

具体的にはモノレポ管理のマルチモジュールなGoのリポジトリをCloud Buildで動かしてビルドしたいのだけどモジュールから別のモジュールを依存関係として追加してると自分たちがプライベートなリポジトリとして管理しているリポジトリのモジュールを依存関係としてCloud Build環境から追加する必要があり、その際に認証とかいろいろ考えることが多かったのでメモ。

ぱんだぱんだ

つまづき1 - そもそもGoのマルチモジュールについて

前もいろいろ考えた

https://zenn.dev/jy8752/scraps/21b9d548cc9b09

簡単にまとめるとGoでマルチモジュールやる場合はreplaceディレクティブを使ってやってたけど運用的に微妙なところが多く、replaceディレクティブを書かなくてもローカルで開発できるようworkspaceが出た。で、ローカルで開発している分にはコンパイルエラーやgoplsの警告が消えて開発できていい感じなのだけどデプロイするときに問題が出てくる。

具体的にはGitHub ActionsのようなCI環境でそのままビルドしてデプロイしようとすると依存関係の解決ができなくてビルドが失敗することがある。

このように失敗するのは当然でローカルではreplaceディレクティブやgoのworkspaceを使って解決できていた依存関係のため、それらがないCI環境では他の依存モジュール同様go getで取得できるようにしなければならない。

go getで取得できるようにするにはモジュールを一旦リリースしなければならない。そうなるとタグを打ってバージョン管理もしなければならない。

これに慣れている熟練者と慣れていない人たちでGoのworkspaceに関しては認識の齟齬が生まれているような気がする。

なんにせよ、正しく運用しようとするならば依存モジュールにタグを打って一旦リリースする。そのリリースバージョンをgo getして依存関係に追加しておく。ローカルではgo の workspaceを使うことで開発中のモジュールをいちいちタグを打って公開しなくてすむ。replaceディレクティブと違ってgo.modを編集する必要もない。

みたいな感じだろう。タグ打ちやバージョン管理戦略は組織によるところが多いがこれはチーム内で話し合っていい感じのところ落とし込むしかないだろう。

で、ちゃんとバージョン管理できたモジュールを公開できたとして、今度はプライベートなリポジトリで公開されているモジュールをどう取ってくるかみたいな話になってくる。

ぱんだぱんだ

つまづき2 - プライベートなリポジトリのモジュールをCI環境から取得できない

これあんまり理解してなかった。

なので一旦まとめました。

https://zenn.dev/jy8752/scraps/7892e3b3cf9fa4

で、今回はGitHub ActionsではなくCloud Buildからビルドとデプロイを行いたい。

  • GITHUB_TOKEN GitHub Actionsは使わないので使えない
  • Deploy keys 秘密鍵の管理があれだが認証は通せそう
  • PAT できそうだがトークンの更新とかはやりたくない
  • GitHub App GitHub Actionsを使わないのでだめそう?

となるとDeploy keysを使ってやるのが現実的だろうか?

とにかく、GitHub ActionsにしりCloud Buildにしろ別のプライベートリポジトリにアクセスしようとすると認証を通さないといけないのでけっこうめんどくさいという話。

ぱんだぱんだ

ここまでのまとめ

  • Goのマルチモジュール構成で別のモジュールへの依存がある場合、適切にバージョン管理をして依存関係に追加しておいたほうがいい。(その場しのぎのreplaceディレクティブやworkspaceを使ってもCIでつまづく)
  • CIでprivateリポジトリなどの認証が必要なモジュールをダウンロードする必要がある場合、別途認証処理を適切にする必要がある。GitHub ActionsならGitHub Appを使うのが今は主流っぽい。別のCI環境ならdeploy keysやPATなどを使ってするのが良さそう。
ぱんだぱんだ

リンクまとめ

Goのマルチモジュールについてよくまとめられている
https://qiita.com/takashabe/items/c0c8dd8bfdc13fc743aa

上と同じ方の記事でタグやバージョンについて参考になる
https://qiita.com/takashabe/items/5ef6193a3f92411bf2c5

NxというGo x monorepoの新しい選択肢
https://speakerdeck.com/kamikazezirou/nxdegou-zhu-surugomonorepo

GitHub Appを使って認証してprivateな依存モジュールを解決する(これのCloud Build版がやりたい)
https://zenn.dev/farstep/articles/32751d92dd1452

ぱんだぱんだ

Goのprivateリポジトリ管理のモジュールの取得について

Goはまずprivateで非公開のモジュールをそのままではgo getできない。理由は以下の記事がわかりやすかったがgo getが直接ソースコードを取得しにいっているわけではなくGOPROXYで設定されているプロキシサーバーを見にいくこととモジュールの検証サーバーも通るらしく、非公開モジュールはそれらに対応していないためらしい。

https://zenn.dev/88888888_kota/articles/fb5f2d7471cb86

そこでGo1.13からGOPRIVATEという環境変数が追加されている。この環境変数にリポジトリなどを指定することでそれに一致するモジュールはプロキシやチェックサムなどを避けることができる。

なのでGoでモノレポのようなマルチモジュール構成でprivateなモジュール間依存がある場合、GOPRIVATEを設定しないとモジュールの取得ができない。

さらに、そもそもGitHub リポジトリへの認証が必要な場合、その設定もする必要がある。

https://zenn.dev/shootacean/articles/go-get-from-github-private-repository

PATのようなトークンを常に使うようにgit configを上書きする方法。GOPRIVATEも当然設定する必要がある。

ぱんだぱんだ

Cloud Buildでprivateなモジュール依存があるGoのプログラムをビルドする

GitHub Actionsが使えないとなると以下のようにdeploy keysを使う感じになるのか?

https://www.dssolution.jp/ja/build-golang-code-that-uses-modules-in-private-repositories-with-cloudbuild-ja/

秘密鍵の管理にCloud KMSを使っていて、暗号化された秘密鍵を複合化してsshの設定してみたいな感じ。少しめんどくさい気もする。もう少し簡単にしたい

GitHub Appのトークンは使えないのか?

https://docs.github.com/ja/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app

たぶんJWTを組み上げてHTTPリクエストすればインストールアクセストークンというものが発行されるのでそれで認証できる。

ただ、Cloud Buildの設定ファイルの中でリクエストを組み上げるのはあんまりやりたくない...

ぱんだぱんだ

GitHubのdeploy keysに設定したssh鍵でgo getする

リポジトリのdeploy keysに公開鍵を設定して、秘密鍵を使ったsshでプライベートなモジュールをgo getするようにやってみたがうまくいかない。いろいろやったけどできなかったので今回は諦めてトークン認証の方向で考える。

ぱんだぱんだ

GitHub Appを使ってgo getする

deploy keysを使ったSSH経由のgo getがなぜかうまくいかなかったのでトークンを使用する。
PATは個人に依存するのと有効期限の管理が発生するのでGitHub Appのインストールアクセストークンを使う方向で考える。

GitHub Actionsであればトークンの生成のactionが公開されているがCloud Build経由になると使えないので自前で生成する方向で考える。

といってもAppIdと秘密鍵を使い公式ドキュメントに記載の手順で

  • JWTの作成
  • インストールID取得APIを実行
  • インストールアクセストークン発行のAPIを実行

の手順でいけるよう。JWT組み立てるのが少し複雑になりがちだが以下の記事のスクリプトがそのまま使えた。ほんとありがとうございます。

https://zenn.dev/tmknom/articles/github-apps-token#自前実装によるgithub-appsトークンの生成

cloudbuild.yaml
steps:
  - name: "gcr.io/cloud-builders/git"
    entrypoint: "bash"
    env:
      - "GITHUB_API_URL=https://api.github.com"
      - "GITHUB_REPOSITORY=<owner/repository>"
    secretEnv:
      - "APP_ID"
      - "GITHUB_APP_PRIVATE_KEY"
    args:
      - "-c"
      - |
        apt-get update && apt-get install -y jq
        ./generate-token.sh > token
  - name: "gcr.io/cloud-builders/docker"
    entrypoint: "bash"
    args:
      - "-c"
      - |
        docker build --build-arg TOKEN=$(cat token) -t demo-module-a .
    id: "build"

timeout: "1200s"

availableSecrets:
  secretManager:
    - versionName: projects/<project-id>/secrets/APP_ID/versions/latest
      env: "APP_ID"
    - versionName: projects/<project-id>/secrets/GITHUB_APP_PRIVATE_KEY/versions/latest
      env: "GITHUB_APP_PRIVATE_KEY"

とりあえずこれでいけた。

ぱんだぱんだ

まとめ

  • Goでマルチモジュールなモノレポをやるときreplaceディレクティブやworkspaceを使えばローカルの開発は困らないが、deployではまる
  • なのでモジュール間依存があるならばちゃんと依存モジュールにタグを打ってpushし、go getできるようにしておく必要がある
  • CI環境やdockerのイメージビルドでモジュール依存を解決するのにGitHubの認証を通す必要があり、GOPRIVATE環境変数を設定してモジュールをリポジトリから直接取得するようにする必要がある
  • GitHub ActionsでやるならばGitHub Appのインストールアクセストークンを使うと楽。トークン生成用のactionが公開されているので簡単
  • Cloud Buildのような別のCI環境でやる場合も自前でJWTを組み立ててGitHubのAPIを使えばトークンの取得はできる
  • なぜかdeploy keysを使ったSSHによるgo getがうまくいかなかった
このスクラップは4ヶ月前にクローズされました