Skip to content

feat(gcp): 設定ディレクトリをグループ単位にし GCP_AUTH_MODE を新設する (PLAN39 PR3) - #125

Merged
takemi-ohama merged 2 commits into
release/PLAN39from
feature/PLAN39-gcloud
Aug 29, 2026
Merged

takemi-ohama merged 2 commits into
release/PLAN39from
feature/PLAN39-gcloud

Conversation

@takemi-ohama

Copy link
Copy Markdown
Contributor

Pull Request

概要

PLAN39 の 3 本目。問題 1「gcloud auth login がコンテナ再作成で消える」の本体です。
gcloud / gws の設定ディレクトリをグループボリューム配下へ向け、ユーザー OAuth が
コンテナを作り直しても保たれ、かつグループをまたいで共有されないようにします。
あわせてサービスアカウント鍵に依存しない ADC を既定の経路にできるようにします。

設計判断は issues/PLAN39_account-group-volume-separation.md(モード: architecture)に。

関連 Issue

実装上の要点(プランからの変更 1 点)

プランは「entrypoint で export CLOUDSDK_CONFIG=... する」と書いていましたが、
それでは docker exec のシェルに届きません。コンテナの環境変数はホスト側の
生成 compose が決めるもので、entrypoint の export / unset は PID 1 の子孫にしか効きません。
実機で確認しました。

$ docker exec carmo-ai-dev-1 sh -c 'echo GAC=$GOOGLE_APPLICATION_CREDENTIALS'
GAC=/home/ubuntu/.config/gcloud/credentials.json   # entrypoint の外なのでそのまま見える

そこで ホスト側(lib/devbase/env/gcp_auth.py)で決めて compose の environment: に載せる
形にしました。entrypoint はディレクトリを用意するだけです。AC12 の「コンテナ内で 2 変数が
未設定であること」はこの経路でしか満たせません。

変更点

lib/devbase/env/gcp_auth.py(新規)

関数 役割
resolve_auth_mode(env) GCP_AUTH_MODE が adc / key ならその値。未設定・空・未知の値なら auto 判定(鍵の env があれば key、無ければ adc)
container_env(env) dev サービスへ載せる CLOUDSDK_CONFIG / GOOGLE_WORKSPACE_CLI_CONFIG_DIR / 解決済み GCP_AUTH_MODE
filter_key_env_names(names, mode) adc のとき GOOGLE_APPLICATION_CREDENTIALS / BIGQUERY_KEY_FILE を列挙から外す

未知の値を拒否せず auto へ倒すのは、タイプミスで既存プロジェクトが起動できなくなるのを
避けるためです。auto は現行 main と同じ挙動になります。

認証モード

値 挙動
adc(推奨) 鍵を書かない。2 変数をコンテナへ渡さない。ADC は $CLOUDSDK_CONFIG/application_default_credentials.json に委ねる
key 従来どおり GCP_CREDENTIALS_BASE64__<profile> を復号して書き、2 変数を渡す
未設定 鍵の env があれば key、無ければ adc

adc で 2 変数を「空にする」のではなく「渡さない」のが要点です。値だけ残して実体が無いと
ADC はユーザー認証へフォールバックせず DefaultCredentialsError で落ちます(プラン 前提 10)。
compose の environment: に名前が載らなければ、Compose はその変数をコンテナへ渡しません。

設定ディレクトリ

CLOUDSDK_CONFIG=/persistent/group/gcloud
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/persistent/group/gws

この 2 行で credentials.db / access_tokens.db / legacy_credentials/ / configurations/ /
application_default_credentials.json と gws の credentials.enc / .encryption_key が
グループボリュームへ移ります。CLOUDSDK_CONFIG は gcloud CLI 専用ではなく google.auth の
探索経路そのものなので、BigQuery クライアント等も同じ場所を見ます。

containers/base/entrypoint.sh

  • devbase_setup_cloud_config_dirs — 設定ディレクトリを用意する。空の named volume は
    root 所有で作られ uid 1000 では書けないため、書けなければ chown する(プラン 前提 18)
  • devbase_setup_gcp_credentials — 認証モードに応じて鍵を書く / 書かない。
    ホストが古い場合やプロジェクト env 直書きに対する保険として、adc では 2 変数を unset もする

containers/base/Dockerfile

npm グローバルへ @googleworkspace/cli を追加しました。gws はこれまでどのコンテナにも
入っておらず
(稼働中の dev コンテナ 14 本すべてで command -v gws が空)、設定だけ
永続化しても復旧しないためです(プラン 前提 17)。

lib/devbase/env/collectors/google.py

devbase env init は鍵を登録したときだけ 2 変数を書くようにしました。従来は鍵の有無に
関係なく書いており、実体の無いパスが env に残ると ADC がフォールバックできません。

鍵の出力先について

~/.config/gcloud/credentials.json(既定)は変えていません。既存プロジェクトの env が
BIGQUERY_KEY_FILE にこのパスを書いているためです。ただし CLOUDSDK_CONFIG を向け直した後の
~/.config/gcloud は gcloud の設定ディレクトリではなく単なる鍵の置き場になり、
コンテナ層(揮発)に残ります。したがって鍵は毎起動 env から書き直され、
永続領域には残りません(AC13)。プロファイルを切り替えても旧い鍵は存在しないので、
削除仕様そのものが不要になりました。

やらないこと(スコープ外)

  • snapshot のグループ対応・devbase status 表示・ボリューム構造のドキュメント — PR4
  • Google 認証の手順書 docs/user/google-auth.md — PR5
    (本 PR のドキュメントからリンクしていますが、実体は PR5 で追加します。
    release ブランチ内では PR5 の merge までリンク切れです)

満たす受け入れ条件

  • AC1 / AC2 の前提(設定ディレクトリがグループボリュームに載り、gws がイメージに入る)
  • AC11: 鍵モードで従来どおりサービスアカウント鍵が使える
  • AC12: adc / key / 未設定を任意に切り替えられる。key → adc の戻り方向も含む
  • AC13: サービスアカウント鍵が永続化されない

影響と互換性

  • base イメージの再ビルドが必要です(devbase build --no-cache)
  • 鍵を持つ既存プロジェクトは auto 判定で key になり、現行と同じ挙動です
  • adc へ移すのは利用者の明示操作です
  • gcloud は並行実行を想定していないため(公式ドキュメント)、同一グループの複数コンテナが
    同時に gcloud を叩くと database is locked が出うります。恒久対策は取らず、
    再実行で回避する旨をドキュメントに書きました

動作確認

  • uv run pytest が green
  • bash -n containers/base/entrypoint.sh が pass
  • 「entrypoint の export が docker exec に届かない」ことを実機で確認(上記コンソール)
  • devbase build --no-cache 後の実機確認(gcloud auth login → down → up →
    再認証なしで gcloud auth list)は release PR でまとめて実施

自動テスト

$ uv run pytest tests/ -q
1589 passed in 64.05s                        # exit=0

$ uv run pytest tests/env/test_gcp_auth.py tests/volume/test_compose_gcp_auth.py tests/containers/test_entrypoint_gcp_auth.py -q
40 passed in 0.28s                           # exit=0

🤖 Generated with Claude Code

https://claude.ai/code/session_01S5oA2PqY6UX2Ca3t78886t

gcloud / gws の設定ディレクトリをグループボリューム配下へ向け、ユーザー OAuth が
コンテナを作り直しても保たれ、かつグループをまたいで共有されないようにする。
あわせてサービスアカウント鍵に依存しない ADC を既定の経路にする。

- `CLOUDSDK_CONFIG=/persistent/group/gcloud` /
  `GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/persistent/group/gws` を**ホスト側の生成
  compose で渡す**。entrypoint の export は PID 1 の子孫にしか効かず、
  docker exec のシェルには届かない (実機で確認)。entrypoint は空の named volume が
  root 所有で作られる件に対応してディレクトリを用意するだけにする
- `GCP_AUTH_MODE` (`adc` / `key` / 未設定=auto) を新設。`adc` では鍵を書かず、
  GOOGLE_APPLICATION_CREDENTIALS と BIGQUERY_KEY_FILE を **compose の列挙から外す**。
  値だけ残して実体が無いと ADC はユーザー認証へフォールバックせず
  DefaultCredentialsError で落ちるため、空文字ではなく「渡さない」を選ぶ
- `devbase env init` は鍵を登録したときだけ上記 2 変数を書く (従来は無条件)
- `@googleworkspace/cli` をベースイメージへ追加。gws はどのコンテナにも入っておらず、
  設定だけ永続化しても復旧しないため
- ドキュメント: `GCP_AUTH_MODE` の使い分けと、`~/.config/gcloud` が gcloud の
  設定ディレクトリではなくなったこと、並行実行の制約

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5oA2PqY6UX2Ca3t78886t

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | codex | REQUEST_CHANGES

ADC モードでは、Compose に明示されたキー用パスも生成結果から除去してください。

Comment thread lib/devbase/volume/compose.py

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 1 | gemini | REQUEST_CHANGES

PLAN39 の GCP 認証モードの対応において、ホスト側(Python)とコンテナ側(entrypoint.sh)で鍵の有無の判定・フォールバック条件に乖離があり、docker exec 用の環境変数と実際のコンテナ状態に不整合が生じるリスクが見つかりました。
該当箇所にインラインコメントを残していますので、ロジックを両者で揃えるよう修正をお願いします。

Comment thread lib/devbase/env/gcp_auth.py
cross-review round 1 の 2 件に対応する。どちらも「ホスト側は鍵モードのつもりで
2 変数を渡すのに、コンテナ側には鍵の実体が無い」という同じ壊れ方を招く。

- 元の compose.yml の `environment:` に `GOOGLE_APPLICATION_CREDENTIALS` /
  `BIGQUERY_KEY_FILE` が直書きされていると、機密の列挙を絞るだけでは生成 compose
  に残る。adc では鍵をどのサービスにも書かないので、`_drop_env_names` で全
  サービスから名前ごと取り除く (map / list 両記法。空になれば environment ごと削除)
- 鍵の有無の判定を entrypoint と揃える。`GCP_ACTIVE_PROFILE` (未設定なら default)
  のプロファイル 1 本だけを見て、無ければ後方互換の
  GOOGLE_APPLICATION_CREDENTIALS_BASE64 を見る。全プロファイル走査 (any) だと
  別プロファイルの鍵だけでホストが key と判定してしまう
- `GCP_AUTH_MODE=key` 宣言でもアクティブな鍵が無ければ adc へ倒す (警告付き)。
  entrypoint が同じフォールバックを持つため、ホストだけ key だと実体の無い
  パスが docker exec のシェルへ残り DefaultCredentialsError になる

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5oA2PqY6UX2Ca3t78886t
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

🔧 /ndf:fix サマリ (round 1)

対応件数: critical=0 / major=2 / minor=0 (合計 2 件)
deferred: 0 件 / rejected: 0 件
commit: 70d0a3e
CI: NONE (このリポジトリの CI は main 向け PR でのみ走ります)

詳細

# 指摘元 箇所 対応
1 codex lib/devbase/volume/compose.py 修正 — adc で元の compose.yml に直書きされた GOOGLE_APPLICATION_CREDENTIALS / BIGQUERY_KEY_FILE が生成物に残っていた。_drop_env_names() を追加し、全サービスの environment から名前ごと削除する
2 gemini lib/devbase/env/gcp_auth.py 修正 — 鍵の有無の判定を entrypoint.sh と揃えた。アクティブプロファイル 1 本だけを見る形にし、GCP_AUTH_MODE=key 宣言でも鍵が無ければ adc へフォールバックする

どちらも「ホスト側は鍵モードのつもりで 2 変数を渡すのに、コンテナ側には鍵の実体が無い」という同じ壊れ方(docker exec 時の DefaultCredentialsError)を招くもので、両方とも major と再判定して修正しました。

変更ファイル

  • lib/devbase/volume/compose.py — _drop_env_names() 追加 / generate_scaled_compose() で adc 時に適用
  • lib/devbase/env/gcp_auth.py — active_profile() 追加、has_service_account_key() / resolve_auth_mode() を entrypoint と同条件へ
  • lib/devbase/env/keys.py — GOOGLE_APPLICATION_CREDENTIALS_BASE64 を定義(リテラル重複の解消)
  • docs/user/environment-variables.md — 判定はアクティブプロファイル 1 本/key 宣言時のフォールバック/直書きパスも外すことを追記
  • tests/env/test_gcp_auth.py, tests/volume/test_compose_gcp_auth.py — 上記のケースを追加

品質チェック

  • uv run pytest -q → 1602 passed
  • python3 -m compileall -q lib bin → OK
  • bash -n containers/base/entrypoint.sh → OK

@codex @gemini 再レビューをお願いします。

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 2 | gemini | APPROVE

指摘事項が解消されていることを確認しました。追加の修正提案はありません。

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 cross-review | round 2 | codex | APPROVE

修正が必要な指摘はありません。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant