OllamaをGitLab CI/CDに組み込む方法|マージリクエストのコードレビューとセキュリティチェックをローカルLLMで自動化する手順

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)ローカルLLM > OllamaをGitLab CI/CDに組み込む方法|マージリクエストのコードレビューとセキュリティチェックをローカルLLMで自動化する手順
「GitLabのマージリクエストでコードレビューを毎回手動でやるのが追いつかず、品質のばらつきが出ている」
「外部の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`で切り詰めてトークン上限を超えないようにするのが最初の実務的対処


OllamaをGitLab CI/CDに組み込む方法|マージリクエストのコードレビューとセキュリティチェックをローカルLLMで自動化する手順

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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

インストール後、`gitlab-runner`ユーザーが自動作成されます。このユーザーのホームが`/home/gitlab-runner`になります。

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

登録後に`/etc/gitlab-runner/config.toml`が作成されます。`executor = "shell"`になっていれば正常です。

シェルモードの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"

`only: - merge_requests`の指定が重要です。これがないとすべてのpushでジョブが起動してしまいます。

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

`before_script`でターゲットブランチをfetchするのは重要です。これがないと`git diff origin/main...HEAD`が差分を正しく計算できない場合があります。

このジョブをコミットして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をブロックする

`allow_failure: true`をコードレビューに設定することで、LLMの応答エラーでMRそのものがブロックされるのを防げます。セキュリティチェックは`allow_failure: false`とし、秘密鍵が検出された場合はマージをブロックします。

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ハンズオン形式で実施しています。

>> ローカルAIマスターセミナーの詳細を確認する
ローカルLLMの構築・運用に関する関連記事もあわせて参考にしてください。

Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。