午前2時のパイプライン障害
時刻は午前2時。本番環境への重要なホットフィックスをプッシュしているところです。CI/CDパイプラインが開始されますが、緑色のチェックマークの代わりに、画面いっぱいの赤いテキストが表示されます。あるいは、新入社員のラップトップをセットアップしているだけかもしれません。go mod tidyを実行すると、すべてが停止します。ターミナルには以下が表示されます。
go get github.com/acme-corp/secret-api: git ls-remote -q https://github.com/acme-corp/secret-api.git: exit status 128
この exit status 128 は、「アクセス拒否」を意味するGitの一般的なエラーです。これは、Goのツールチェーンがプライベートリポジトリをパブリックリポジトリであるかのように取得しようとしているために発生します。認証のハンドシェイクが失敗し、Gitが接続を遮断します。
なぜビルドが失敗するのか
デフォルトでは、Goはすべてのモジュールがパブリックであると想定しています。これにより、プライベートコードにおいて2つの大きな障害が発生します。
- Googleプロキシ: Goは通常、
proxy.golang.orgからモジュールを取得します。リポジトリがプライベートであるため、Googleのプロキシはそれを参照できません。その結果、404または410エラーが返され、ビルドが停止します。 - HTTPSの罠: GoはHTTPS経由でモジュールを取得しようとします。マシンがGitHubに対してSSHキーを使用しているのに、HTTPS用の認証ヘルパーが設定されていない場合、Gitは失敗します。対話型ではないGoコマンドの実行中には、パスワードを入力することができないからです。
ステップ1:プライベート空間を定義する
まず、パブリックプロキシとチェックサムデータベースをスキップすべきモジュールをGoに伝えます。これには環境変数 GOPRIVATE を使用します。これにはカンマ区切りのパターンを指定できます。
ターミナルでこのコマンドを実行してください。github.com/acme-corp は実際の組織名に置き換えてください。
go env -w GOPRIVATE=github.com/acme-corp/*
この単純なフラグには大きな役割があります。acme-corp パス以下のものはすべてプライベートであることをGoに伝えます。これにより、これらのリポジトリに対してGoはGoogleプロキシとパブリックチェックサムデータベース(GOSUMDB)を完全にバイパスするようになります。
ステップ2:GitにSSHの使用を強制する
GOPRIVATE を設定することでGoがGitHubと直接通信するようになりますが、デフォルトのプロトコルは依然としてHTTPSです。開発にSSHキーを使用している場合は、Gitにプロトコルを動的に切り替えさせる必要があります。
グローバルの .gitconfig に以下の書き換えルールを追加します。
git config --global url."git@github.com:".insteadOf "https://github.com/"
これで、Goが git ls-remote https://github.com/acme-corp/secret-api.git を実行すると、Gitがそれをインターセプトします。URLを git@github.com:acme-corp/secret-api.git に書き換え、SSHエージェントが認証を処理することで、ダウンロードが即座に成功します。
CI/CDでの代替案:パーソナルアクセストークン
GitHub ActionsやJenkinsのような環境では、SSHキーの管理はしばしば手間がかかります。そのような場合は、HTTPS経由でパーソナルアクセストークン(PAT)を使用する方がスマートです。
SSHの書き換えの代わりに、CIスクリプトで次のコマンドを使用します。
# GitHub Actionsの場合
git config --global url."https://${GH_TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/"
セキュリティ上のヒント: このトークンを直接コードに記述しないでください。認証情報を安全に保つために、環境変数やシークレット管理ツールを使用して、実行時に ${GH_TOKEN} を注入するようにしてください。
ステップ3:修正を確認する
古い、壊れたメタデータを参照していないことを確認するために、ローカルのモジュールキャッシュをクリアします。その後、再度依存関係の解決を試みます。
go clean -modcache
go mod tidy
コマンドが何も出力せずに終了すれば成功です。go env GOPRIVATE を実行すれば、いつでも設定を再確認できます。
注意すべき一般的な落とし穴
1. コロンとスラッシュの罠
insteadOf を使用する場合、構文が非常に厳密です。git@github.com:(コロンあり)を使用する場合、HTTPS側も https://github.com/(末尾にスラッシュあり)と一致させる必要があります。ここでわずかな不一致があると、git@github.com:/user/repo のような不正なURLになり、再び128エラーが発生します。
2. チェックサムデータベース
GONOPROXY だけを使おうとする開発者がいますが、これは避けてください。プロキシはスキップしてもチェックサムデータベース(GOSUMDB)をスキップしない場合、Goは依然としてパブリックな台帳に対してプライベートコードを検証しようとします。プライベートコードはその台帳に存在しないため、失敗します。GOPRIVATE は両方の設定を一度に行うため、より確実な選択肢です。
3. Dockerコンテナの問題
Dockerビルドは、128エラーが頻発する原因となります。コンテナ内はクリーンな状態であり、ホストマシンの .gitconfig やSSHキーを認識していません。Dockerfile 内で明示的に環境変数を設定するか、ビルド引数として渡す必要があります。
# Dockerfileの例
ARG GITHUB_TOKEN
ENV GOPRIVATE=github.com/acme-corp/*
RUN git config --global url."https://${GITHUB_TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/"
まとめ
- Goのデフォルト設定は、すべてのコードがパブリックであることを前提としています。内部プロジェクトでは、
GOPRIVATEが非常に役立ちます。 exit status 128は、ほとんどの場合、Gitの認証失敗に集約されます。insteadOfを使ったテクニックは、go.modファイルを書き換えることなくプロトコルの不一致を修正する、最もスマートな方法です。

