AnsibleとGitLab CIを連携させる設計入門|MRレビューとパイプラインでインフラ変更を安全に適用する方法

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Ansible > AnsibleとGitLab CIを連携させる設計入門|MRレビューとパイプラインでインフラ変更を安全に適用する方法
「GitLab上のリポジトリでAnsible Playbookを管理しているのに、実行だけは手動という状態になっていませんか。」
誰がいつ本番サーバーへ変更を適用したか記録が残らず、障害が起きても「誰が何を当てたのか」を追跡できない。そんな運用上の問題を根本から解消する手段が、GitLab CIとAnsibleの連携設計です。

この記事では、AnsibleとGitLab CIを統合してインフラ変更をパイプラインで安全に管理する設計手順を解説します。RHEL 9系~Ubuntu 24.04 LTSを想定環境として説明します。GitLab CI/CDの基本設定経験があり、Ansible Playbookを手動実行したことがある方を対象にしています。

この記事のポイント

・AnsibleとGitLab CIを連携するとPlaybook実行の履歴・承認フロー・環境別制御が一元管理できる
・.gitlab-ci.ymlにlint→dry-run→staging→productionの4ステージを設計するのが基本形
・シークレットはGitLab CI変数+Ansible Vaultで分離し、リポジトリへの直書きを防ぐ
・MRでのdry-run確認とwhen: manualの組み合わせで本番適用の「ヒューマンゲート」を設ける


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

手動実行の限界とパイプライン化のメリット

Ansibleを手動で実行している運用では、いくつかの構造的な問題が積み重なっていきます。

まず変更履歴の不透明さです。ターミナルでansible-playbookを実行しても、「誰が・いつ・どのPlaybookを・どのインベントリに当てたか」はサーバーのログに断片的に残るだけで、一覧性がありません。障害が起きたとき「昨日の誰かの作業が原因かもしれない」という推測から調査を始めることになります。

次にレビューなしの本番適用です。Playbookの変更をGitLabにコミットしたとしても、実行するタイミングが個人の判断に委ねられると、意図せず未レビューの変更が本番に当たることがあります。

GitLab CIとAnsibleを連携させると、この2つの問題を同時に解消できます。

・全実行がパイプラインとして記録される:いつ誰がどのジョブを動かしたか、GitLabのUI上で確認できる
・MRごとにdry-runが自動実行される:変更差分をレビュアーが確認してからマージできる
・環境別のデプロイが制御される:ステージングは自動、本番は手動承認という流れを設計できる
・シークレットが安全に管理される:SSH鍵やVaultパスワードをGitLab CI変数として保管し、リポジトリへの混入を防ぐ

GitLab CI×Ansible統合の全体設計

連携の全体像を整理すると、次のようなフローになります。

# 全体フロー(例示) Developer | +-- feature branchにPlaybook変更をpush | +-- Merge Request (MR) を作成 | +-- [自動] validate: ansible-lint 実行 +-- [自動] check: ansible-playbook --check --diff | | | MR画面でdiff確認 --> レビュアーがapprove | +-- mainブランチにmerge | +-- [手動トリガー] deploy-staging +-- [手動トリガー] deploy-production

この設計のポイントは「変更差分のレビューをパイプラインの中に組み込む」という考え方です。コードのコードレビューと同じプロセスで、インフラ変更の内容確認を行います。

Ansibleの基礎設計から体系的に学びたい方は、リナックスマスター.JPのAnsibleコンテンツもあわせて参照してください。

GitLab Runner側の要件は以下のとおりです。

・Docker executor:ジョブごとにコンテナが起動・廃棄されるため実行環境が汚染されにくい
・ネットワーク疎通:RunnerからAnsibleの管理対象ホストへSSHが通ること
・権限:Runnerが起動するコンテナからGitLabのプロジェクト変数を参照できること

.gitlab-ci.ymlで実装するパイプラインの構造

1. 4ステージ構成の設計方針

パイプラインは以下の4ステージで構成するのが実用的な基本形です。

・validate:ansible-lintによる静的解析。構文エラーやベストプラクティス違反を早期検出する
・check:--check --diffによるdry-run。変更内容をdiff形式で出力してレビュアーが確認できるようにする
・deploy-staging:ステージング環境への適用。developブランチへのpushで手動トリガー
・deploy-production:本番環境への適用。mainブランチへのマージ後に手動トリガー

validateとcheckをMRのたびに自動実行し、deployは必ず手動で起動する設計にすることで、誤適用のリスクを最小化できます。

2. GitLab RunnerとDockerイメージの選択

Ansibleの実行イメージには、バージョンを固定したものを使うのが原則です。latestタグを使うとAnsibleのバージョンが自動更新されてPlaybookの動作が変わることがあります。特定のansible-coreバージョンを含むイメージをtagで指定するか、自前でDockerfileを管理してプロジェクト専用イメージを作成することを検討してください。

# Dockerfile(プロジェクト専用Ansibleイメージの例) FROM python:3.11-slim RUN pip install --no-cache-dir \ ansible-core==2.17.* \ ansible-lint==24.* \ boto3 \ botocore RUN ansible-galaxy collection install \ amazon.aws \ community.general

プロジェクト専用イメージをGitLab Container Registryに登録しておくと、各ジョブで毎回インストールする手間がなくなり、パイプラインの実行時間も短縮できます。

3. .gitlab-ci.ymlの基本テンプレート

実際の.gitlab-ci.ymlの構成例を示します(出力例・環境により構成は異なります)。

# .gitlab-ci.yml(設計例) stages: - validate - check - deploy-staging - deploy-production # 全ジョブ共通の前処理 .ansible_base: &ansible_base image: registry.gitlab.example.com/myproject/ansible:2.17 before_script: - echo "$VAULT_PASS" > /tmp/vault-pass && chmod 600 /tmp/vault-pass - mkdir -p ~/.ssh - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa - echo "StrictHostKeyChecking no" >> ~/.ssh/config # ステージ1: ansible-lintによる静的解析 lint: <<: *ansible_base stage: validate script: - ansible-lint playbooks/ # ステージ2: MR時のdry-run(変更差分の確認) dry-run: <<: *ansible_base stage: check rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' script: - ansible-playbook -i inventory/staging/hosts.ini --vault-password-file /tmp/vault-pass --check --diff playbooks/site.yml 2>&1 | tee ansible-check.log artifacts: paths: - ansible-check.log when: always # ステージ3: ステージング環境デプロイ(手動) deploy-staging: <<: *ansible_base stage: deploy-staging environment: staging rules: - if: '$CI_COMMIT_BRANCH == "develop"' when: manual script: - ansible-playbook -i inventory/staging/hosts.ini --vault-password-file /tmp/vault-pass playbooks/site.yml # ステージ4: 本番環境デプロイ(手動・保護ブランチ) deploy-production: <<: *ansible_base stage: deploy-production environment: production rules: - if: '$CI_COMMIT_BRANCH == "main"' when: manual script: - ansible-playbook -i inventory/production/hosts.ini --vault-password-file /tmp/vault-pass playbooks/site.yml

`before_script`で`/tmp/vault-pass`と`~/.ssh/id_rsa`に書き出したファイルは、Docker executorであればコンテナ終了時に自動で消えます。Shell executorを使う場合は`after_script`セクションで明示的に削除してください。

シークレット管理:GitLab CI変数とAnsible Vaultの連携

パイプラインで安全にAnsibleを実行するために管理が必要なシークレットは、主に次の2種類です。

・SSH秘密鍵:Ansibleが管理対象ホストへ接続するために使う。GitLab CI/CDのVariable型として登録する
・Ansible Vaultパスワード:暗号化された変数ファイルを復号するために使う。Masked変数として登録する

GitLabの設定画面(Settings > CI/CD > Variables)で変数を登録するとき、以下の点に注意してください。

変数名 型 推奨設定 用途
SSH_PRIVATE_KEY Variable Masked + Protected 管理ホストへのSSH接続
VAULT_PASS Variable Masked + Protected Ansible Vaultの復号パスワード
「Masked」に設定するとジョブログに変数の値が表示されなくなります。「Protected」に設定するとprotected branchのパイプラインでのみ使用できるため、feature/developブランチからのシークレット誤利用を防げます。

Ansible Vault側では、暗号化が必要な変数(DBパスワード、APIキーなど)を`ansible-vault encrypt_string`でインライン暗号化するか、`group_vars/`配下のファイルをファイルごと暗号化して管理します。パイプライン内では`--vault-password-file`オプションで復号します。

# Ansible Vaultでパスワードをインライン暗号化(ローカルで実行して group_vars に貼る) $ ansible-vault encrypt_string 'db_pass_value' --name 'db_password' db_password: !vault | $ANSIBLE_VAULT;1.1;AES256 38383138306433386... ... # CI側では --vault-password-file で復号 $ ansible-playbook site.yml --vault-password-file /tmp/vault-pass

MRでdry-runを使ったレビューフローの設計

GitLab CIとAnsibleの連携で最も効果的な仕組みの一つが、MR(マージリクエスト)に連動したdry-runの自動実行です。

フローは次のようになります。

・ステップ1:開発者がfeatureブランチにPlaybookの変更をpushしてMRを作成する
・ステップ2:CIが自動でdry-runジョブを起動し、--check --diffの結果をアーティファクトとして保存する
・ステップ3:レビュアーがアーティファクトのログを確認し、変更内容が意図どおりかを検証する
・ステップ4:問題がなければMRをapproveしてmainにマージする
・ステップ5:本番デプロイはmanualトリガーで担当者が意識的に起動する

dry-runが有効なのは`--check`モードをAnsibleがサポートしているモジュールに限ります。`shell`モジュールや`command`モジュールはcheck modeで実際には実行されないため、差分が表示されません。Playbook内でshell/commandモジュールの使用を最小限に抑え、冪等性の高いモジュール(`dnf`、`template`、`file`など)で置き換えるとdry-runの精度が上がります。

なお、dry-runの結果をMRのコメントに自動投稿したい場合は、GitLabのAPI(MR Comments APIへのPOST)をCIスクリプトに組み込む方法があります。ただし設定が複雑になるため、まずはアーティファクトをUIから確認する運用から始めるのが現実的です。

ステージング→本番の段階適用設計

本番環境への変更を段階的に適用するために、インベントリを環境別に分離するのが基本設計です。

# 推奨ディレクトリ構造(例示) inventory/ +-- staging/ | +-- hosts.ini # ステージングホスト一覧 | +-- group_vars/ | +-- all.yml # ステージング用変数(vault暗号化) +-- production/ +-- hosts.ini # 本番ホスト一覧 +-- group_vars/ +-- all.yml # 本番用変数(vault暗号化)

環境ごとに`hosts.ini`と`group_vars/`を分けることで、同一のPlaybookをインベントリの切り替えだけで異なる環境に適用できます。.gitlab-ci.ymlのスクリプト内でインベントリパスを切り替えれば、ステージング用と本番用で別々のジョブを定義できます。

本番適用ジョブを`when: manual`にすることで、パイプラインは自動で進行せず担当者が明示的にトリガーを押す必要があります。さらにGitLabの「Environments」機能を使ってDeployment approversを設定すると、複数人の承認が揃わないと本番デプロイが実行されない設計にできます。

ステージングで一定時間稼働させて問題がないことを確認してから本番に適用するというフローを、パイプライン上の設計として強制できるのが大きなメリットです。手動実行では「ステージングで確認した」という口頭確認に頼りがちですが、パイプラインとして設計することで「ステージングジョブが成功していないと本番ジョブのトリガーが現れない」という構造的な強制ができます。

よくある設計ミスと対処法

GitLab CI×Ansible連携でよく見られる設計上の問題点を整理します。

Dockerイメージのバージョンを固定していない
`latest`タグを使うとAnsibleのマイナーバージョンアップで非推奨オプションが動作しなくなる可能性があります。特定のバージョンタグを固定し、アップグレードは意識的に行ってください。

シークレットをリポジトリに直書きしている
Ansible VaultパスワードやSSH鍵をそのままリポジトリに含めるのは最もリスクの高いミスです。GitLab CI/CD変数として管理し、パイプライン内で一時ファイルに書き出す設計を徹底してください。

ANSIBLE_HOST_KEY_CHECKINGを無効化したままにしている
CI環境では`ANSIBLE_HOST_KEY_CHECKING=False`を設定することが多いですが、これは初回接続時のホスト確認を省略する設定です。実際の本番運用では既知ホストをknown_hostsに登録してから有効に戻すか、接続先ホストのFingerprintを事前に登録する設計を検討してください。

dry-runステージを省略している
「lintが通ればdry-runは不要」と判断してcheckステージを省略すると、変更の影響範囲をレビューできなくなります。dry-runは手間よりメリットの方が大きいため、MR時には必ず実行する設計を維持してください。

Shell executorでシークレットが残存する
Shell executorはジョブ終了後にファイルシステムが残るため、`/tmp/vault-pass`や`~/.ssh/id_rsa`が次のジョブから参照できる状態になります。`after_script`でこれらのファイルを削除する処理を必ず入れてください。

本記事のまとめ

AnsibleとGitLab CIを連携させる設計入門として、パイプラインの全体フロー・.gitlab-ci.ymlの構造・シークレット管理・dry-runレビュー・段階適用の設計パターンを解説しました。

設計のポイント 概要
4ステージ構成 lint→check(dry-run)→staging→productionの順に構成する
シークレット管理 SSH鍵・VaultパスワードはGitLab CI変数に登録してMasked+Protected設定
MRでのdry-run merge_request_eventでMR時だけ自動実行し、差分をアーティファクトで提供
環境分離 inventory/staging/とinventory/production/を分離し、group_varsも環境別に管理
本番適用の制御 when: manual + GitLab EnvironmentsのDeployment approversで多段承認を設ける
イメージ固定 latestタグを避け、ansibleバージョンを明示したイメージタグを使う
まずはvalidateとcheck(dry-run)の2ステージだけを導入し、CIが通るようになってからdeploy-stagingを追加するという段階的な導入が現実的です。完全なパイプラインを一度に作ろうとすると設定が複雑になりすぎて、メンテナンスが困難になります。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Ansibleを使った構成管理の設計・実践スキルを体系的に学べるコンテンツを公開しています。>> AnsibleによるLinux構成管理を学ぶ

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

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

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

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

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

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

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

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

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。