Gemini CLIの全社利用を支える技術

Gemini CLIの全社利用を支える技術

こんにちは。一番好きなマジックナンバーは 0x5F3759DF 1 な、技術戦略部CTOブロックの塩崎です。

先日、当社から以下のプレスリリースを発表いたしました。その中でも書かれているように、1人あたり月額200ドルの基準のもと、Claude CodeやGemini CLIをはじめとした各種AI開発ツールを利用可能になりました。

corp.zozo.com

この記事ではAI開発ツールの1つであるGemini CLIを全社で使えるようにするため、Google Cloud管理者として実施したことを紹介します。Gemini CLIやClaude Codeなどに関しては以下のような記事がよく目立ちますが、この記事にはそのような内容が書かれておりません。

  • 俺が考えたベストのGemini CLI設定
  • Gemini CLIを使って効率を上げる10の方法
  • Gemini CLIで開発効率が〇〇%アップ

むしろ、Gemini CLIを利用するプログラマではなく、Gemini CLIなどのツールを導入したいと相談を受けているシステム管理者向けの記事です。

Gemini CLIの認証方法について

Gemini CLIには複数の認証方法があり、どれを利用するかによって利用規約やプライバシーポリシーが変化します。そのため、全社導入にあたってどの認証方法を使うべきかを決める必要があります。

以下の表は認証方法毎の違いをまとめたものです。

No 認証方法 アカウント 入力が学習に使われるか 利用規約 プライバシーポリシー
(1) Gemini Code Assist via Google 個人 使われる Google Terms of Service Gemini Code Assist Privacy Notice for Individuals
(2) Gemini Code Assist via Google Google Workspaceまたは有料版Gemini Code Assist 使われない Google Cloud Platform Terms of Service Gemini Code Assist Privacy Notice for Standard and Enterprise
(3) Gemini Developer API 無料 使われる Gemini API Terms of Service - Unpaid Services Google Privacy Policy
(4) Gemini Developer API 有料 使われない Gemini API Terms of Service - Paid Services Google Privacy Policy
(5) Vertex AI Gen API 使われない Google Cloud Platform Service Terms Google Cloud Privacy Notice

参考: GitHub - google-gemini/gemini-cli/docs/tos-privacy.md

(1)と(3)はどちらも無料で利用できますが、入力が学習に利用されます。これらの認証方法はAIモデルを通した情報漏洩などの懸念があるため利用を禁止しています。また、(4)はこれ以降で説明するような費用管理や権限管理の仕組みを構築する上でやや難があったため、こちらも利用を禁止しています。

残りの認証方法は(2)と(5)ですが、今回は(5)を採用することにしました。(2)と(5)の主な違いは定額課金か従量課金という点です。定額課金にすると費用管理がしやすくなる一方で、サブスクリプションの購入などの契約面で時間を取られることも見込まれたため、運用初期は(5)の従量課金のみを採用しています。ゆくゆくは(2)の定額課金プランも対象とし、利用量に応じてどちらを使うべきかを見極めていく予定です。

これ以降では(5)のVertex AI Gen APIを認証方法として利用してGemini CLIを使うための方法を紹介していきます。

Gemini CLIを利用するためのGoogle Cloudプロジェクト

Gemini CLIを利用するためにGoogle Cloudプロジェクトが必要なため、これをどうするかを考えます。部署ごとにプロジェクトを作成してもらうのか、全社統一で1つのプロジェクトを利用するのかという2つの方針が考えられます。今回は後者の全社統一で1つのプロジェクトを利用する方法を採りました。

以下の理由から全社統一プロジェクトにメリットがあると考えたためです。

  • 各部署にプロジェクト作成をしてもらうよりも、統一プロジェクトにしたほうがGemini CLIの利用申請が簡素化される
  • 各自の権限や費用などを中央集権的に管理できる

利用開始するための社内申請

Gemini CLIを利用するための社内申請にはGitHubを使うことにしました。まず、先ほど紹介したGoogle CloudプロジェクトにterraformでIaC(Infrastructure as Code)を導入しました。そして、マージをトリガーにしてterraform applyをGitHub Actionsから実行するようにしました。

terraform applyのアーキテクチャ図

まず、権限管理用のGitHubリポジトリには以下のようなGemini CLI利用者が列挙されているYAMLファイルを用意します。

- sato@example.com
- suzuki@example.com
- takahashi@example.com

そして、このYAMLファイルを読み取ってリソースを作成するためのtfファイルも用意します。

resource "google_project_iam_member" "vertex-ai-user" {
  for_each = toset(yamldecode(file("${path.module}/users.yaml")))
  project  = <プロジェクトID>
  member   = format("user:%s", each.value)
  role     = "roles/aiplatform.user"
}

その後は、前述のYAMLファイルにGemini CLIを利用したいユーザーを追加してPRを作成すれば申請作業が完了します。

PRのサンプル

管理者側の権限を反映する作業も簡素化されており、GitHubでMergeボタンを押すのみで完了します。

利用費の集計

当社では開発AIエージェントを自由に使える制度がスタートしましたが、1人あたり月額200ドルという費用の目安があります。この金額を超過していないか確認するために、以下のような仕組みを使ってGemini CLIでどの程度の費用が発生したのかを集計しています。

費用集計のアーキテクチャ図

課金情報と監査ログをBigQueryにexportし、それらを集計することで各個人の費用を集計しています。また、個人毎の費用情報と組織マスタを突き合わせて、部署毎の費用も確認できるようにしています。

課金情報をBigQueryにエクスポートするためには、以下のドキュメントに従って設定しています。

cloud.google.com

監査ログをBigQueryにエクスポートするための設定は以下のようにterraformで管理しています。Gemini CLI用のプロジェクトだけではなく、Organization内の全部のプロジェクトに対して監査ログをBigQueryにエクスポートする設定を入れています。

data "google_organization" "zozo-com" {
  domain = "zozo.com"
}

resource "google_organization_iam_audit_config" "zozo-com" {
  org_id  = data.google_organization.zozo-com.org_id
  service = "allServices"
  audit_log_config {
    log_type = "ADMIN_READ"
  }
  audit_log_config {
    log_type = "DATA_READ"
  }
  audit_log_config {
    log_type = "DATA_WRITE"
  }
}

resource "google_logging_organization_sink" "audit_log_sink" {
  name   = "audit_log_sink"
  org_id = data.google_organization.zozo-com.org_id

  destination      = "bigquery.googleapis.com/${google_bigquery_dataset.audit_log.id}"
  include_children = true
  filter           = "protoPayload.@type=\"type.googleapis.com/google.cloud.audit.AuditLog\""
}

参考:
Terraform provider for Google Cloud - google_organization_iam_audit_config
Terraform provider for Google Cloud - google_logging_organization_sink

これらのデータをどのように集計しているのかも紹介します。

まずは以下のように課金情報から1時間毎の費用を集計します。この時にGeminiモデルもGROUP BY条件に入れています。

-- NOTE: 将来モデルが増えた場合に修正する
create temporary function ski_id_to_model_name(sku_id string) as (
  case sku_id
  when 'A121-E2B5-1418' then 'gemini-2.5-pro'   -- Gemini 2.5 Pro Text Input - Predictions
  when '5DA2-3F77-1CA5' then 'gemini-2.5-pro'   -- Gemini 2.5 Pro Text Output - Predictions
  when 'E367-697F-F274' then 'gemini-2.5-pro'   -- Gemini 2.5 Pro Thinking Text Output - Predictions
  when 'E941-1B12-88B9' then 'gemini-2.5-pro'   -- Gemini 2.5 Pro Input Text Caching
  when 'FDAB-647C-5A22' then 'gemini-2.5-flash' -- Gemini 2.5 Flash GA Text Input - Predictions
  when 'AF56-1BF9-492A' then 'gemini-2.5-flash' -- Gemini 2.5 Flash GA Text Output - Predictions
  when 'A253-E8A3-DE5C' then 'gemini-2.5-flash' -- Gemini 2.5 Flash GA Thinking Text Output - Predictions
  when 'CD33-11F4-1220' then 'gemini-2.5-flash' -- Gemini 2.5 Flash Ga Thinking Text Output - Predictions
  when 'A1C1-77CC-6FAE' then 'gemini-2.5-flash' -- Gemini 2.5 Flash GA Input Text Caching
  else "others"
  end
);

with hourly_gemini_cost as (
  select
    timestamp_trunc(usage_start_time, HOUR) as datetime_hour,
    sum(cost) as cost,
    ski_id_to_model_name(sku.id) as model_name,
  from <課金情報>
  where project.id = <Gemini CLI用プロジェクト> and service.description = 'Vertex AI'
  group by all
)

さらに、監査ログから各個人の1時間毎のAPIコール数を取得します。こちらも同様にGeminiモデル毎のコール数になるようにGROUP BY条件を設定しています。

with api_call_count as (
  select
    timestamp_trunc(timestamp, HOUR) as datetime_hour,
    protopayload_auditlog.authenticationInfo.principalEmail as email,
    array_last(split(protopayload_auditlog.resourceName, '/')) as model_name,
    count(*) as count,
  from 監査ログ
  where
    resource.labels.project_id = <Gemini CLI用プロジェクト> and
    resource.labels.service = 'aiplatform.googleapis.com' and
    protopayload_auditlog.methodName in (
      'google.cloud.aiplatform.v1beta1.PredictionService.GenerateContent',
      'google.cloud.aiplatform.v1beta1.PredictionService.StreamGenerateContent'
    )
  group by all
)

そして上記の2つの情報を結合して各個人のコストを集計しています。各時間帯・Geminiモデル毎の費用を各個人のAPIコール数で按分して各個人の費用としています。厳密にはAPIコール数ではなくInput・Outputしたトークン数で按分しないと正しい値にはなりませんが、監査ログにトークン数が含まれていないため、APIコール数で近似しています。

with api_call_count_and_ratio as (
  select
    datetime_hour,
    email,
    model_name,
    count,
    count / sum(count) over(partition by datetime_hour, model_name) as ratio,
  from api_call_count
), hourly_personal_cost as (
  select
    datetime_hour,
    email,
    cost * ratio as personal_cost,
  from hourly_gemini_cost
  left join api_call_count_and_ratio using(datetime_hour, model_name)
  where email is not null
), daily_personal_cost as (
  select
    date(datetime_hour, "Asia/Tokyo") as date_jst,
    email,
    sum(personal_cost) as personal_cost,
  from hourly_personal_cost
  group by all
  order by date_jst, email
)

select * from daily_personal_cost

さらに、この情報に組織図マスタを結合させて、組織毎の費用を集計しています。組織マスタはkintoneに格納されており、以下の記事で紹介している方法でBigQueryから読み出しできるようにしています。

techblog.zozo.com

これらの集計値はGoogle SpreadsheetとBigQueryの連携機能を使い、結果を毎日Spreadsheetに出力しています。

support.google.com

使いすぎ防止

定期的な集計をしているとはいえ、利用者全員が毎日の集計結果を見るわけではありません。そのため、過度な利用があった場合にSlackなどに通知をしたり、権限を一時的に削除してこれ以上の使いすぎを防止する仕組みも必要です。そのような仕組みもGemini CLIの全社展開のために作成したので紹介します。

費用集計のアーキテクチャ図

この仕組みを構築するうえで大事なことは情報の鮮度です。大量の費用が短時間で発生した場合にも迅速に対応するためには鮮度が大事です。先ほど紹介した費用集計の方法ですと、監査ログは十分な鮮度を持っていますが、課金情報の鮮度が低いため、そのまま使うことはできません。

そのために、代わりにCloud Monitoringから取得できる以下のメトリクスを利用します。このメトリクスでLLMモデルにInput・Outputされたトークンの数を取得できます。

aiplatform.googleapis.com/publisher/online_serving/token_count

cloud.google.com

また、以下のフィールドを group_by_fields に指定します。

フィールド名 格納されている情報
metric.label.type InputかOutputか
resource.label.model_user_id モデル名

そして、ここで取得した情報とモデル毎の単価情報を合わせて、1時間毎の費用を取得しています。

def get_hourly_token_usage(project_id: str):
    client = MetricServiceClient()
    project_name = f"projects/{project_id}"

    now = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0)
    start_time = now - timedelta(days=3)
    interval = TimeInterval(start_time=start_time, end_time=now)

    filter_str = 'metric.type = "aiplatform.googleapis.com/publisher/online_serving/token_count"'

    aggregation = Aggregation(
        alignment_period={"seconds": 3600},  # 1 hour
        per_series_aligner=Aggregation.Aligner.ALIGN_SUM,
        cross_series_reducer=Aggregation.Reducer.REDUCE_SUM,
        group_by_fields=["metric.label.type", "resource.label.model_user_id"],
    )

    request = ListTimeSeriesRequest(
        name=project_name,
        filter=filter_str,
        interval=interval,
        view=ListTimeSeriesRequest.TimeSeriesView.FULL,
        aggregation=aggregation,
    )

    hourly_data = defaultdict(list)

    try:
        results = client.list_time_series(request)

        for result in results:
            token_type = result.metric.labels.get("type", "unknown")
            model_user_id = result.resource.labels.get("model_user_id", "unknown")

            for point in result.points:
                timestamp = point.interval.end_time.strftime("%Y-%m-%d %H:%M:%S UTC")
                token_count = point.value.int64_value

                hourly_data[timestamp].append({
                    "token_type": token_type,
                    "model_user_id": model_user_id,
                    "token_count": token_count,
                })

    except Exception as e:
        print(f"An error occurred while processing: {e}")
        return

なお、この方法で鮮度良く課金情報が取得できるなら先に紹介したCloud Billingの情報は不要なのではと考える方もいるかもしれません。しかし、実際のGeminiの課金の計算はより複雑なため、正確な費用を求めるためにはCloud Billingの情報が必要です。本章の仕組みは鮮度が良いですが、近似的な値になってしまいます。

費用を使いすぎてしまった場合の処理は、通知と権限削除の2種類を用意しています。通知処理は予算の80%を超過してしまった場合の処理です。Slackで該当のユーザーに予算額の80%に達した旨を通知します。

Geminiコストアラートのスクショ

権限削除は予算金額の100%を超過してしまった場合の処理です。この処理では該当ユーザーのGemini CLIを利用する権限を削除します。通常のIAM権限(Allow policy)を削除してしまうと、最初に紹介したterraformが構成ドリフトを起こしてしまうため、Deny Policyに追加することで権限を削除しています。

cloud.google.com

1日あたりの予算金額はデフォルトでは10ドルにしてあり、月間では200ドルには達しない程度になっています。一時的に金額を引き上げたい場合には、以下のようなYAMLファイルを修正します。これらの設定値の調整をするための申請はGitHubでPRを投げてもらうことで実現しています。

# このファイルを修正すると1日あたりの予算上限を上書きできます。
# 書き換えた場合、月間の予算上限をオーバーしないように自力で管理してください。
# With great power comes great responsibility.

- user: suzuki@example.com
  daily_budget_usd: 200
  notify_threshold_in_percent: 80

これらの処理はCloud RunやCloud Schedulerを使い定期的に実行しています。

なお、この仕組みの構築に必要なコードのほとんどはGemini CLIに書いてもらいました。1〜2割くらいは手で書きましたが、残りはGemini CLIに任せることができました。

GitHub ActionsでGemini CLIを実行する

GitHub ActionsでGemini CLIを実行するための方法についても紹介します。

この時、認証情報をどのようにGitHub Actionsに渡すべきかが重要になります。Service AccountのJSONキーやGemini APIキーなどのような認証情報をGitHub ActionsのSecretにセットする方法が考えられますが、この方法は非推奨です。

docs.github.com

その代わりにWorkload Identity Federationという仕組みを使ってGemini CLIを利用するための認証情報をGitHub Actionsに渡します。Workload Identity Federationを行うためには、Workload Identity PoolとWorkload Identity Providerを予め作成します。以下のドキュメントに従ってそれらを作成しておきます。

github.com

そして、以下のようにService Accountを作成して、そのService AccountにGemini CLIの権限を付与します。

data "google_project" "ai-coding" {
  project_id = local.project_id
}

locals {
  github_actions_service_accounts = yamldecode(file("${path.module}/github_actions_service_accounts.yaml"))
}

resource "google_service_account" "github-actions" {
  for_each = { for github_actions_service_account in local.github_actions_service_accounts : github_actions_service_account["repository"] => {
    repository = github_actions_service_account["repository"]
  } }

  account_id   = format("gha-%s", replace(each.value.repository, "_", "-"))
  display_name = format("GitHub Actions(%s)", each.value.repository)
}

resource "google_service_account_iam_member" "github-actions" {
  for_each = { for github_actions_service_account in local.github_actions_service_accounts : github_actions_service_account["repository"] => {
    repository = github_actions_service_account["repository"]
  } }

  service_account_id = google_service_account.github-actions[each.value.repository].name
  role               = "roles/iam.workloadIdentityUser"
  member             = format("principalSet://iam.googleapis.com/projects/%d/locations/global/workloadIdentityPools/github-actions/attribute.repository/<組織名>/%s", data.google_project.ai-coding.number, each.value.repository)
}

resource "google_project_iam_member" "vertex-ai-service-account" {
  for_each = { for github_actions_service_account in local.github_actions_service_accounts : github_actions_service_account["repository"] => {
    repository = github_actions_service_account["repository"]
  } }

  project = local.project_id
  role    = "roles/aiplatform.user"
  member  = google_service_account.github-actions[each.value.repository].member
}

上記のterraformで参照している、 github_actions_service_accounts.yaml は以下のようなYAMLです。新規にGemini CLIを動かしたいリポジトリが増えた場合には、利用者にはこのYAMLを修正するPRを作成する形で権限付与の申請をしてもらいます。

- repository: <Gemini CLIを動かしたいリポジトリ名>
  division: 〇〇本部
  department: 〇〇部

terraformではYAML内のrepositoryフィールドのみを読みだしていますが、それ以外のフィールドは部署ごとの費用集計に利用しています。

そして、以下のようなYAMLを .github/workflows 配下に配置すれば、GitHub Actions内でgeminiコマンドが利用できます。MacなどでGemini CLIを起動する際には初回起動時にインタラクティブな手順で認証方法を設定しますが、GitHub Actionsではこの方法が使えません。そのため、認証方法をVertex AIにするための設定をJSONファイルに直接書き出しています。

name: Gemini CLI Example

# ジョブの起動条件は各自でお好みに
on:
  push:
    branches:
      - main

env:
  GOOGLE_CLOUD_PROJECT: <プロジェクトID>
  GOOGLE_CLOUD_LOCATION: <どのリージョンのGeminiモデルを呼び出すのかを指定>

permissions:
  contents: 'read'
  id-token: 'write'

jobs:
  run-gemini-cli:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: 'Authenticate to Google Cloud'
        uses: 'google-github-actions/auth@v2'
        with:
          workload_identity_provider: projects/<Project Numberを入れる>/locations/global/workloadIdentityPools/github-actions/providers/github-actions
          service_account: <サービスアカウント>

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '22'

      - name: Install Gemini CLI
        run: npm install -g @google/gemini-cli

      # NOTE: 認証方法の設定方法がプリミティブすぎる気がするので、将来的にいい感じのactionがでてきて欲しい
      - name: Create Gemini settings
        run: |
          mkdir /home/runner/.gemini && echo '{"selectedAuthType": "vertex-ai"}' > /home/runner/.gemini/settings.json

      - name: Run Gemini CLI
        run: |
          gemini -p "hello"

全社統一プロジェクト以外でGemini CLIを利用していることの検知

Gemini CLIを利用するプロジェクトを中央集権化するためには、それ以外のプロジェクトでGemini CLIを利用している社員の検知も重要です。Vertex AIの権限を付与すれば、他のプロジェクトでもGemini CLIを使えてしまうため、それらの利用を検知して、中央管理のプロジェクトに移行してもらう必要があります。

監査ログに対して以下のクエリを実行すると、中央管理のプロジェクト以外でGemini CLIを利用しているユーザーを抽出できます。Gemini CLIがGemini APIを呼び出す時のUserAgentは、GeminiCLI/ から始まっているため、この条件で抽出しています。

select distinct
  resource.labels.project_id,
  protopayload_auditlog.authenticationInfo.principalEmail as email,
  regexp_extract(protopayload_auditlog.requestMetadata.callerSuppliedUserAgent, r'^(.+)/.+$') as ai_coding_tool,
from <監査ログ>
where
  resource.labels.project_id <> '中央管理するプロジェクトID' and
  resource.labels.service = 'aiplatform.googleapis.com' and
  starts_with(protopayload_auditlog.requestMetadata.callerSuppliedUserAgent, 'GeminiCLI/')
  order by project_id asc, email asc, ai_coding_tool asc

Gemini CLIに脆弱性が報告されたときの影響範囲の調査

先日、Gemini CLIに深刻な脆弱性が発見されたという記事が公開されました。最新版ではこの脆弱性は修正済みなので、Gemini CLIの利用者を調査してバージョンを上げるように促す必要があります。本章ではその作業を紹介いたします。

arstechnica.com

まず、以下のクエリでGemini CLIを利用しているユーザーを抽出できます。

select distinct
  resource.labels.project_id,
  protopayload_auditlog.authenticationInfo.principalEmail as email,
from <監査ログ>
where
  resource.labels.service = 'aiplatform.googleapis.com' and
  starts_with(protopayload_auditlog.requestMetadata.callerSuppliedUserAgent, 'GeminiCLI/')
  order by project_id asc

そして、抽出されたユーザーに対してGemini CLIを最新版に上げるようにアナウンスを行っています。

Gemini CLIのアップデートをアナウンス

今後の展望

この仕組みの今後の展望についても紹介します。

今回紹介した仕組みはVertex AI Gen APIでGemini CLIを利用するためのものでしたが、定額課金でもGemini CLIを利用できます。Gemini CLIを多く使いたい人はこちらのプランで利用したほうがコストパフォーマンス良く利用できます。そのため、定額課金でGemini CLIを利用するための社内の準備が整い次第、利用費の多い人を定額課金に切り替えるような運用フローも予定しています。その際にはこの記事で紹介した費用集計の仕組みなどにも手をいれる必要がありますが、コスト効率の良い開発AIエージェント活用のためにも定額プランの活用は必要です。

また、Vertex AI Model GardenでAnthropic社のモデルの利用もできます。Claude Codeの使うモデルプロバイダをGoogle Cloudにする場合の費用集計の仕組みなどの構築も予定しています。

まとめ

Gemini CLIを全社員が利用できるようにするための様々な仕組みを紹介しました。「Gemini CLIで効率アップ!」という記事は毎日のようにSNSを賑わせていますが、本記事のような管理側に立った記事も増えていくと嬉しいです。

この記事を書くにあたって、以下のエムスリーさんの記事の監査ログを活用するアイデアなどを参考にさせていただきました。AI開発ツールの活用方法だけはなく、管理系のノウハウも公開して頂けていることにお礼申し上げます。

www.m3tech.blog

また、本記事で紹介した仕組みはこのタイミングでゼロから作ったわけではなく、監査ログや課金情報をBigQueryに集めている部分は既存の仕組みを流用しました。BigQueryはとてもパワフルな分析DBなので、とりあえず様々な情報を格納しておくと、今回のようなケースで潰しが効くのでオススメです。

ZOZOでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募ください。

corp.zozo.com

カテゴリー