「外部のAIコードレビューサービスはソースコードを社外に送ることになるので、セキュリティポリシー上使えない」
そんな課題を抱えるLinuxエンジニアや開発チームは多いはずです。この記事では、GitLab CI/CDパイプラインにOllamaを組み込み、マージリクエスト(MR)の差分をローカルLLMで自動コードレビューする仕組みを構築する手順を解説します。
コードはすべて社内サーバーのOllama上で処理され、外部には一切送りません。GitLab RunnerのシェルモードによるOllama連携、.gitlab-ci.ymlの設定、GitLab APIを使ったMRへのコメント自動投稿まで、実際に動く形でまとめました。
この記事のポイント
・GitLab RunnerをシェルモードでOllamaと同じサーバーに配置すれば、MR差分を直接ローカルLLMに送れる
・.gitlab-ci.ymlに`only: - merge_requests`を指定するとMR作成時だけジョブが起動する
・Python 20行でOllama APIに差分を投げてレビューを取得し、GitLab APIでMRコメントに自動投稿できる
・差分は`head -c 3000`で切り詰めてトークン上限を超えないようにするのが最初の実務的対処
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
GitLab CI/CDにOllamaを組み込む構成の全体像
この仕組みが動く流れは次の通りです。1. エンジニアがGitLabにMR(マージリクエスト)を作成する
2. GitLab CIがトリガーされ、GitLab RunnerがCIジョブを起動する
3. RunnerがMRの差分(`git diff`)を取得する
4. 差分テキストをOllamaの`/api/generate`エンドポイントに送信する
5. Ollamaがローカルで推論してレビューコメントを返す
6. RunnerがGitLab APIを呼んでMRにコメントを自動投稿する
構成のポイントはGitLab RunnerとOllamaを**同じLinuxサーバー**に配置する点です。RunnerをDockerモード(docker executor)で動かすとコンテナ内からホストの`localhost:11434`にアクセスできないため、この構成ではシェル実行モード(shell executor)を選びます。
「なぜローカルLLMか」という問いに対する答えは単純で、ソースコードを外部のAIサービスに送ることへのセキュリティ上の懸念が拭えないからです。開発中の社内システムや顧客データを扱うコードが外部サービスに渡ることは、多くの組織で認められていません。このような背景については「社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢」でも詳しく解説しています。
使用するモデルはPhi-4(14B)を推奨します。Phi-4はコード理解力が高く、14Bパラメータでも実用的なレビューコメントを生成できます。GPUありの環境なら差分3,000文字程度のレビューに30~90秒程度かかります。LLMのモデル選定については「ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント」も参照してください。
前提環境の確認(GitLab・GitLab Runner・Ollama)
この記事では以下の環境を前提とします。・GitLab CE/EE 17.x(Ubuntu 22.04以上にセルフホスト)
・Ollama 0.3以上インストール済み(`ollama serve`が起動中)
・Phi-4モデル取得済み(`ollama pull phi4`)
・Ubuntu Server 22.04以上(GitLab RunnerをOllamaと同じサーバーに配置する)
・Python 3.11以上・`requests`ライブラリインストール済み
Ollamaの初期セットアップについては「Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド」を先に参照してください。
GitLab側の準備として、MRにコメントを投稿するためのアクセストークンが必要です。GitLabの「Settings → Access Tokens」から`api`スコープ付きのProject Access Tokenを発行します。トークン文字列は後の手順でGitLab CI/CD変数として登録するため、控えておいてください。
Pythonの`requests`ライブラリが未インストールの場合は先に入れておきます。
# requestsライブラリのインストール(gitlabrunnerユーザー環境) pip3 install requests
GitLab Runnerをシェル実行モードでインストール・登録する
1. GitLab Runnerのインストール
GitLab公式リポジトリからRunnerパッケージをインストールします。# GitLab Runner公式リポジトリの追加 curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash # インストール sudo apt install -y gitlab-runner # バージョン確認 gitlab-runner --version
2. GitLabプロジェクトからRunner登録トークンを取得する
GitLabの対象プロジェクトのページを開き、「Settings → CI/CD → Runners」に進みます。「New project runner」ボタンからRunnerを作成し、表示される登録トークンを控えます。タグは`ollama-review`を設定します。このタグを.gitlab-ci.ymlで指定することで、Ollamaが動いているサーバーのRunnerにのみジョブを割り当てられます。
3. Runnerをシェルモードで登録する
# Runnerの登録(シェルモード) sudo gitlab-runner register \ --url "https://gitlab.example.com" \ --token "glrt-YOUR_RUNNER_TOKEN" \ --executor "shell" \ --description "ollama-code-reviewer" \ --tag-list "ollama-review" # Runnerの起動確認 sudo systemctl status gitlab-runner
シェルモードのRunnerはジョブを`gitlab-runner`ユーザーの権限で実行します。Ollamaが`localhost:11434`で動いていれば、追加設定なしにアクセスできます。
4. gitlab-runnerユーザーの環境を確認する
# gitlab-runnerユーザーとしてOllama疎通確認 sudo -u gitlab-runner curl -s http://localhost:11434/api/tags | python3 -m json.tool | head -10
.gitlab-ci.ymlの基本設計(Ollama疎通確認から始める)
まずOllamaにアクセスできることを確認するだけのシンプルなジョブを作り、動作を確認します。プロジェクトのルートに`.gitlab-ci.yml`を作成してください。# .gitlab-ci.yml(疎通確認用・最小構成) stages: - review ollama-ping: stage: review tags: - ollama-review only: - merge_requests script: - curl -s http://localhost:11434/api/tags | python3 -m json.tool | head -5 - echo "Ollama疎通OK"
MRを作成してパイプラインを確認します。GitLabの「CI/CD → Pipelines」でジョブが緑になり、`Ollama疎通OK`が出力されれば準備完了です。
GitLab CI/CD変数の登録
MRコメント投稿に使うProject Access TokenをGitLab CI/CD変数として安全に管理します。GitLabプロジェクトの「Settings → CI/CD → Variables」を開き、以下の変数を追加します。
・Variable Key: `GITLAB_REVIEW_TOKEN`
・Value: 先ほど発行したProject Access Token
・Protected: オン(保護ブランチのみ)
・Masked: オン(ログに値が表示されない)
変数はマスクされるため、CIログにトークンが漏れる心配がありません。
MRの差分をOllamaでコードレビューするPythonスクリプトを実装する
1. レビュースクリプトの配置
ジョブ本体のロジックをPythonスクリプトとして分離することで、.gitlab-ci.ymlをシンプルに保てます。スクリプトをプロジェクトのリポジトリに含める(例: `scripts/mr_review.py`)のが最もシンプルな運用です。2. mr_review.pyの実装
#!/usr/bin/env python3 # scripts/mr_review.py import os import subprocess import sys import requests OLLAMA_URL = "http://localhost:11434/api/generate" MODEL = "phi4:14b-q4_K_M" GITLAB_URL = os.environ["CI_SERVER_URL"] PROJECT_ID = os.environ["CI_PROJECT_ID"] MR_IID = os.environ.get("CI_MERGE_REQUEST_IID", "") TOKEN = os.environ["GITLAB_REVIEW_TOKEN"] TARGET_BR = os.environ.get("CI_MERGE_REQUEST_TARGET_BRANCH_NAME", "main") if not MR_IID: print("MRコンテキスト外です。スキップします。") sys.exit(0) # 差分を取得(py/sh/goのみ・先頭3000文字に切り詰め) result = subprocess.run( ["git", "diff", f"origin/{TARGET_BR}...HEAD", "--", "*.py", "*.sh", "*.go", "*.rb", "*.js"], capture_output=True, text=True ) diff = result.stdout[:3000] if not diff.strip(): print("レビュー対象の差分がありません。スキップします。") sys.exit(0) # Ollamaにレビューを依頼する prompt = ( "あなたは経験豊富なコードレビュアーです。" "以下のgit diffを日本語でレビューしてください。\n" "バグのリスク・セキュリティ上の問題・改善点を箇条書きで指摘してください。\n" "問題がなければ「問題なし」と一言記載してください。\n\n" f"{diff}" ) resp = requests.post(OLLAMA_URL, json={ "model": MODEL, "prompt": prompt, "stream": False, "options": {"temperature": 0.2, "num_ctx": 4096} }, timeout=180) resp.raise_for_status() review_text = resp.json()["response"] # GitLab APIでMRにコメントを投稿する comment_body = ( f"## AIコードレビュー(Ollama / {MODEL})\n\n" f"{review_text}\n\n" f"---\n*このコメントはローカルLLMによって自動生成されました。内容は参考情報として扱ってください。*" ) api_resp = requests.post( f"{GITLAB_URL}/api/v4/projects/{PROJECT_ID}/merge_requests/{MR_IID}/notes", headers={"PRIVATE-TOKEN": TOKEN}, json={"body": comment_body}, timeout=30 ) api_resp.raise_for_status() print(f"MRコメントを投稿しました(note_id={api_resp.json()['id']})")
3. .gitlab-ci.ymlにレビュージョブを追加する
# .gitlab-ci.yml(コードレビュージョブ追加版) stages: - review ollama-code-review: stage: review tags: - ollama-review only: - merge_requests variables: GIT_FETCH_EXTRA_FLAGS: "--tags" before_script: # ターゲットブランチの最新をfetchして差分を正確に取得する - git fetch origin ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME} script: - python3 scripts/mr_review.py
このジョブをコミットしてMRを開くと、パイプラインが起動してOllamaがレビューを生成し、MRコメントが自動投稿されます。
CIパイプラインにセキュリティチェックジョブを追加する
コードレビュージョブとは別に、パターンベースのセキュリティチェックを並行して走らせるジョブを追加できます。ハードコードされたパスワードやAPIキーの疑いをdiff内から検出します。1. セキュリティチェックスクリプト(scripts/security_check.py)
#!/usr/bin/env python3 # scripts/security_check.py import os, re, subprocess, sys, requests PATTERNS = [ (r'(?i)password\s*=\s*["\'][^"\']{4,}["\']', "ハードコードされたパスワードの疑い"), (r'(?i)secret\s*=\s*["\'][^"\']{8,}["\']', "ハードコードされたシークレットの疑い"), (r'(?i)api_key\s*=\s*["\'][^"\']{8,}["\']', "ハードコードされたAPIキーの疑い"), (r'(?i)token\s*=\s*["\'][A-Za-z0-9_\-]{16,}["\']', "ハードコードされたトークンの疑い"), (r'-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----', "秘密鍵が含まれている疑い"), ] OLLAMA_URL = "http://localhost:11434/api/generate" MODEL = "phi4:14b-q4_K_M" GITLAB_URL = os.environ["CI_SERVER_URL"] PROJECT_ID = os.environ["CI_PROJECT_ID"] MR_IID = os.environ.get("CI_MERGE_REQUEST_IID", "") TOKEN = os.environ["GITLAB_REVIEW_TOKEN"] TARGET_BR = os.environ.get("CI_MERGE_REQUEST_TARGET_BRANCH_NAME", "main") if not MR_IID: sys.exit(0) result = subprocess.run( ["git", "diff", f"origin/{TARGET_BR}...HEAD"], capture_output=True, text=True ) diff = result.stdout # パターンマッチによる検出 findings = [] for line_no, line in enumerate(diff.splitlines(), 1): for pattern, label in PATTERNS: if re.search(pattern, line): findings.append(f"- 行{line_no}: {label}\n `{line[:120].strip()}`") if not findings: print("セキュリティパターンのマッチなし。") sys.exit(0) # 検出内容をOllamaで精査して誤検知を除去する finding_text = "\n".join(findings) prompt = ( "以下はコード差分から検出されたセキュリティ懸念のリストです。" "各項目が実際のリスクかどうかを判断し、誤検知を除外した上で、" "真のリスクのみを日本語で簡潔に説明してください。\n\n" f"{finding_text}" ) resp = requests.post(OLLAMA_URL, json={ "model": MODEL, "prompt": prompt, "stream": False, "options": {"temperature": 0.1} }, timeout=120) resp.raise_for_status() analysis = resp.json()["response"] # MRコメントに投稿 comment = ( f"## セキュリティチェック結果(Ollama / {MODEL})\n\n" f"**パターン検出数: {len(findings)}件**\n\n" f"{analysis}\n\n" f"---\n*自動チェック結果です。最終判断は担当者が行ってください。*" ) api_resp = requests.post( f"{GITLAB_URL}/api/v4/projects/{PROJECT_ID}/merge_requests/{MR_IID}/notes", headers={"PRIVATE-TOKEN": TOKEN}, json={"body": comment}, timeout=30 ) api_resp.raise_for_status() print(f"セキュリティチェック結果を投稿しました(検出: {len(findings)}件)") sys.exit(1 if findings else 0) # 検出があればCIを失敗扱いにする
2. .gitlab-ci.ymlにセキュリティジョブを追加する
# .gitlab-ci.yml(セキュリティジョブ追加・最終版) stages: - review variables: GIT_FETCH_EXTRA_FLAGS: "--tags" .mr-base: tags: - ollama-review only: - merge_requests before_script: - git fetch origin ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME} ollama-code-review: extends: .mr-base stage: review script: - python3 scripts/mr_review.py allow_failure: true # レビュー失敗でもMRをブロックしない ollama-security-check: extends: .mr-base stage: review script: - python3 scripts/security_check.py allow_failure: false # 秘密鍵検出時はMRをブロックする
2つのジョブは並列で実行されるため、合計の待ち時間はどちらか長い方だけで済みます。
よくあるエラーと対処法
1. curl: (7) Failed to connect to localhost port 11434
Ollamaが起動していないか、`OLLAMA_HOST`の設定が`127.0.0.1`に限定されています。対処: `ollama serve`が起動しているか確認します。systemdサービスの場合は`sudo systemctl status ollama`で確認します。gitlab-runnerユーザーからのアクセスを許可するには、`/etc/systemd/system/ollama.service.d/override.conf`に`Environment="OLLAMA_HOST=0.0.0.0:11434"`を追加して再起動します(ファイアウォールで外部からのアクセスは別途遮断すること)。
2. RequestException: 504 Gateway Timeout
Ollamaの推論に時間がかかりすぎてタイムアウトしています。対処: `requests.post(..., timeout=180)`を大きくするか、差分の切り詰めサイズを3,000から2,000に減らします。GPUがない環境ではさらに時間がかかるため、CIジョブのタイムアウト設定(`timeout`キー)も延ばします。
3. 401 Unauthorized(GitLab API)
`GITLAB_REVIEW_TOKEN`の値が正しくない、またはスコープが不足しています。対処: Project Access Tokenの`api`スコープが有効であることを確認します。GitLab CI/CD変数に登録した値に余分なスペースや改行が混入していないかも確認してください。
4. git diff で差分が空になる
`before_script`でターゲットブランチをfetchしていないと、`origin/main`が参照できず差分が空になることがあります。対処: `git fetch origin ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}`を`before_script`に必ず入れてください。また、`GIT_STRATEGY: fetch`または`clone`が設定されているか確認します。
5. モデル読み込みに時間がかかり最初のリクエストがタイムアウトする
Ollamaはモデルをメモリにロードする時間が初回リクエストで発生します。phi4:14b-q4_K_Mは起動から最初のレスポンスまで1分前後かかる場合があります。対処: Ollamaのモデルを常駐させておくため、`OLLAMA_KEEP_ALIVE`環境変数を`-1`(無制限)に設定します。`/etc/systemd/system/ollama.service.d/override.conf`に`Environment="OLLAMA_KEEP_ALIVE=-1"`を追加して`sudo systemctl daemon-reload && sudo systemctl restart ollama`で反映します。
まとめ
GitLab CI/CDとOllamaを組み合わせると、社内サーバーだけでコードレビューとセキュリティチェックを自動化できます。ソースコードが外部に出ないため、機密性の高いプロジェクトでも安心して導入できます。今回の構成を一言でまとめると「GitLab Runner(shell)→ Ollama API → GitLab API」の3段パイプです。応用として、レビュー対象の言語フィルタを変えたり、プロンプトをプロジェクト固有のコーディング規約に合わせたりすることで、チームの実情に即した自動化が可能になります。
| 作業 | コマンド・設定のポイント |
|---|---|
| GitLab Runnerインストール | sudo apt install -y gitlab-runner |
| シェルモードで登録 | gitlab-runner register --executor shell --tag-list ollama-review |
| Ollama疎通確認 | sudo -u gitlab-runner curl http://localhost:11434/api/tags |
| MR差分取得 | git diff origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}...HEAD |
| Ollama APIへ送信 | POST /api/generate(model: phi4:14b-q4_K_M, stream: false) |
| MRコメント投稿 | POST /api/v4/projects/:id/merge_requests/:iid/notes |
| 秘密鍵検出でMRブロック | security_check.pyのsys.exit(1)+allow_failure: false |
| 主なエラー対処 | タイムアウト延長・差分切り詰め・OLLAMA_KEEP_ALIVE=-1設定 |
ローカルLLMのCI/CD活用を2日間のハンズオンで体験する
コードレビュー自動化やセキュリティチェックなど、ローカルLLMを実務に組み込む実践的な活用術を実機GPU環境で手を動かしながら習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:OllamaにRedisセマンティックキャッシュを導入する方法|同一質問の重複LLM呼び出しを削減してチームサーバーのレスポンスを高速化する手順
- この記事の属するカテゴリ:ローカルLLMへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます