Ansible CI/CD 運用:GitHub Actions で lint/test/デプロイを自動化
Ansible のインフラコード管理において、手動でのテストやデプロイは時間がかかり、ミスも起こりやすくなります。GitHub Actions を活用することで、Playbook の品質チェックから本番デプロイまでを完全自動化できるのです。
本記事では、Ansible の CI/CD パイプラインを GitHub Actions で構築する方法を、具体的なワークフロー定義とともに詳しく解説します。ansible-lint によるコード品質チェック、Molecule によるテスト自動化、そして安全なデプロイ戦略まで、実践的な CI/CD 運用を段階的に学べる内容となっています。
背景
Ansible における CI/CD の必要性
Ansible は宣言的な YAML ファイルでインフラを管理できる強力なツールですが、Playbook やロールが複雑化すると品質管理が難しくなります。
手動での実行では以下のような課題が生じやすくなるでしょう。
- 構文エラーや非推奨モジュールの見落とし
- 複数環境での動作確認漏れ
- デプロイ手順の属人化とミス発生
- 変更履歴の追跡困難
これらの課題を解決するために、CI/CD パイプラインによる自動化が重要になります。
GitHub Actions を選ぶ理由
GitHub Actions は、リポジトリに統合された CI/CD プラットフォームとして以下の利点があります。
| # | 項目 | 利点 |
|---|---|---|
| 1 | リポジトリ統合 | GitHub リポジトリと完全に統合され、追加のサービス連携が不要 |
| 2 | 無料枠 | パブリックリポジトリは無料、プライベートも月 2,000 分まで無料 |
| 3 | マトリックスビルド | 複数の OS やバージョンで並列テスト可能 |
| 4 | シークレット管理 | 暗号化されたシークレットで認証情報を安全に管理 |
| 5 | 豊富なアクション | Ansible や Python 関連の公式・コミュニティアクションが充実 |
以下の図は、GitHub Actions による Ansible CI/CD パイプラインの全体像を示します。
mermaidflowchart TB
push["コード push"] --> trigger["GitHub Actions<br/>トリガー"]
trigger --> lint["Lint フェーズ"]
trigger --> test["Test フェーズ"]
lint --> yamllint["yamllint"]
lint --> ansiblelint["ansible-lint"]
test --> molecule["Molecule テスト"]
molecule --> docker["Docker コンテナで<br/>テスト環境構築"]
docker --> verify["動作検証"]
verify --> approval{"手動承認"}
approval -->|承認| deploy["Deploy フェーズ"]
approval -->|却下| end_process["処理終了"]
deploy --> staging["Staging 環境<br/>デプロイ"]
staging --> production["Production 環境<br/>デプロイ"]
production --> notify["通知"]
notify --> end_process
この図は、コードの push から本番デプロイまでの一連の流れを表しています。Lint と Test は並列実行され、すべてが成功した場合のみ手動承認を経てデプロイフェーズに進みます。
課題
Ansible プロジェクトで発生する典型的な問題
Ansible を使った運用では、以下のような問題が頻繁に発生します。
構文エラーとベストプラクティス違反
YAML の構文エラーや、非推奨モジュールの使用、冪等性が保証されていないタスクなどは、実行時まで気づきにくい問題です。
yaml# 問題のある Playbook 例
- name: 非推奨モジュールの使用
hosts: servers
tasks:
- name: パッケージインストール
apt: name=nginx state=present # 非推奨の記法
- name: 冪等性のないコマンド
shell: echo "test" >> /tmp/test.txt # 実行毎に追記される
上記のコードには、古い記法の使用や冪等性が保証されていないタスクが含まれており、本番環境で予期しない動作を引き起こす可能性があります。
テスト環境の構築コスト
複数の OS バージョンや構成でのテストは、手動で環境を準備すると時間とコストがかかってしまいます。
デプロイ手順の複雑さと属人化
デプロイ手順が文書化されていても、実行者によって解釈が異なり、ミスが発生しやすくなります。
以下の図は、手動運用と CI/CD 自動化の違いを示します。
mermaidflowchart LR
subgraph manual["手動運用"]
dev1["開発者"] --> local1["ローカルで<br/>構文チェック"]
local1 --> manual_test["手動テスト"]
manual_test --> manual_deploy["手動デプロイ"]
manual_deploy --> risk["ミスのリスク"]
end
subgraph cicd["CI/CD 自動化"]
dev2["開発者"] --> git["Git push"]
git --> auto_lint["自動 Lint"]
auto_lint --> auto_test["自動テスト"]
auto_test --> auto_deploy["自動デプロイ"]
auto_deploy --> safe["安全・高速"]
end
手動運用では各ステップで人的ミスが発生しやすいのに対し、CI/CD 自動化ではコードの品質が保証され、デプロイの安全性と速度が大幅に向上します。
CI/CD 導入時の課題
CI/CD を導入する際にも、以下のような課題があります。
| # | 課題 | 内容 |
|---|---|---|
| 1 | 認証情報の管理 | SSH 鍵やクラウドの API キーを安全に扱う必要がある |
| 2 | ワークフローの複雑さ | Lint、Test、Deploy の各フェーズを適切に設計する必要がある |
| 3 | テスト戦略 | 何をテストし、どこまで自動化するかの判断が難しい |
| 4 | デプロイ戦略 | ステージング環境と本番環境の切り替えを安全に行う |
解決策
GitHub Actions ワークフローの基本設計
GitHub Actions を使った Ansible CI/CD パイプラインは、以下の 3 つのフェーズで構成します。
フェーズ構成
| # | フェーズ | 目的 | 主要ツール |
|---|---|---|---|
| 1 | Lint | コード品質チェック | yamllint、ansible-lint |
| 2 | Test | 動作検証 | Molecule、pytest |
| 3 | Deploy | 環境へのデプロイ | ansible-playbook |
以下の図は、ワークフローの詳細な実行フローを示しています。
mermaidstateDiagram-v2
[*] --> CheckoutCode
CheckoutCode --> SetupPython
SetupPython --> InstallDeps
InstallDeps --> LintPhase
state LintPhase {
[*] --> YamlLint
YamlLint --> AnsibleLint
AnsibleLint --> [*]
}
LintPhase --> TestPhase
state TestPhase {
[*] --> MoleculeCreate
MoleculeCreate --> MoleculeConverge
MoleculeConverge --> MoleculeVerify
MoleculeVerify --> MoleculeDestroy
MoleculeDestroy --> [*]
}
TestPhase --> DeployPhase
state DeployPhase {
[*] --> DeployStaging
DeployStaging --> ManualApproval
ManualApproval --> DeployProduction
DeployProduction --> [*]
}
DeployPhase --> [*]
この状態遷移図は、各フェーズが順番に実行され、前のフェーズが成功した場合のみ次のフェーズに進むことを表しています。
Lint フェーズの実装
Lint フェーズでは、YAML ファイルの構文チェックと Ansible のベストプラクティスチェックを行います。
yamllint の設定
まず、.yamllint ファイルを作成して、YAML ファイルのフォーマットルールを定義します。
yaml# .yamllint
extends: default
rules:
line-length:
max: 120
level: warning
indentation:
spaces: 2
indent-sequences: true
comments:
min-spaces-from-content: 1
truthy:
allowed-values: ['true', 'false', 'yes', 'no']
この設定ファイルでは、行の長さの上限を 120 文字に設定し、インデントは 2 スペース、真偽値は明示的に true/false や yes/no を使用するように指定しています。
ansible-lint の設定
次に、.ansible-lint ファイルで Ansible 固有のルールを定義します。
yaml# .ansible-lint
profile: production
exclude_paths:
- .cache/
- test/
- molecule/
skip_list:
- yaml[line-length]
warn_list:
- experimental
- no-changed-when
- no-handler
profile: production は本番環境向けの厳格なチェックを行う設定です。exclude_paths でテスト用のディレクトリを除外し、skip_list で特定のルールをスキップできます。
GitHub Actions ワークフロー(Lint フェーズ)
.github/workflows/ci.yml ファイルを作成し、Lint ジョブを定義します。
yaml# .github/workflows/ci.yml
name: Ansible CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
lint:
name: Lint Ansible Code
runs-on: ubuntu-latest
steps:
- name: コードをチェックアウト
uses: actions/checkout@v4
この部分では、ワークフローのトリガーとなるイベント(main および develop ブランチへの push と pull request)を定義し、ubuntu-latest 環境で実行するジョブを設定しています。
yaml- name: Python 環境のセットアップ
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
Python 3.11 をインストールし、pip のキャッシュを有効にして、次回以降のビルド時間を短縮します。
yaml- name: 依存パッケージのインストール
run: |
python -m pip install --upgrade pip
pip install yamllint ansible-lint ansible
必要なツール(yamllint、ansible-lint、ansible)をインストールします。
yaml- name: YAML 構文チェック
run: yamllint .
- name: Ansible Lint チェック
run: ansible-lint
yamllint でプロジェクト全体の YAML ファイルをチェックし、続いて ansible-lint で Ansible 固有のベストプラクティスをチェックします。
Test フェーズの実装
Test フェーズでは、Molecule を使って実際にコンテナ環境でロールを実行し、期待通り動作するかを検証します。
Molecule の初期化
既存のロール(例:roles/webserver)に対して Molecule を初期化します。
bash# Molecule の初期化コマンド
cd roles/webserver
molecule init scenario --driver-name docker
このコマンドは、Docker ドライバを使用した Molecule のテストシナリオを生成します。
Molecule 設定ファイル
molecule/default/molecule.yml を編集して、テスト環境を定義します。
yaml# molecule/default/molecule.yml
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: instance-ubuntu22
image: geerlingguy/docker-ubuntu2204-ansible:latest
pre_build_image: true
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
command: /lib/systemd/systemd
この設定では、Ubuntu 22.04 のコンテナイメージを使用し、systemd を有効にして実際の環境に近い状態でテストできます。
yamlprovisioner:
name: ansible
config_options:
defaults:
callbacks_enabled: profile_tasks
verifier:
name: ansible
provisioner セクションでは Ansible を使ってロールを適用し、verifier でも Ansible を使って検証タスクを実行します。profile_tasks コールバックにより、各タスクの実行時間を計測できます。
検証タスクの定義
molecule/default/verify.yml ファイルで、デプロイ後の検証タスクを記述します。
yaml# molecule/default/verify.yml
- name: Verify
hosts: all
gather_facts: false
tasks:
- name: Nginx サービスが起動しているか確認
ansible.builtin.service_facts:
- name: Nginx が running 状態であることを検証
ansible.builtin.assert:
that:
- ansible_facts.services['nginx.service'].state == 'running'
fail_msg: 'Nginx service is not running'
success_msg: 'Nginx service is running correctly'
このタスクでは、Nginx サービスが正しく起動しているかを service_facts モジュールで取得し、assert モジュールで検証しています。
yaml- name: HTTP ポート 80 が LISTEN しているか確認
ansible.builtin.wait_for:
port: 80
state: started
timeout: 30
msg: 'Port 80 is not listening'
wait_for モジュールを使って、HTTP ポート(80 番)が 30 秒以内に LISTEN 状態になることを確認します。
yaml- name: HTTP リクエストが成功するか確認
ansible.builtin.uri:
url: http://localhost
return_content: true
register: response
- name: レスポンスステータスを検証
ansible.builtin.assert:
that:
- response.status == 200
fail_msg: 'HTTP request failed with status {{ response.status }}'
success_msg: 'HTTP request successful'
実際に HTTP リクエストを送信し、ステータスコード 200 が返ることを確認します。これにより、Web サーバーが正しく動作していることを保証できます。
GitHub Actions ワークフロー(Test フェーズ)
.github/workflows/ci.yml に Test ジョブを追加します。
yamltest:
name: Test with Molecule
runs-on: ubuntu-latest
needs: lint
strategy:
matrix:
role: [webserver, database, monitoring]
steps:
- name: コードをチェックアウト
uses: actions/checkout@v4
needs: lint により、Lint ジョブが成功した場合のみ Test ジョブが実行されます。strategy.matrix で複数のロールを並列テストできます。
yaml- name: Python 環境のセットアップ
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Molecule のインストール
run: |
python -m pip install --upgrade pip
pip install molecule molecule-docker ansible-core docker
Molecule と Docker プラグイン、Ansible をインストールします。molecule-docker は Docker をテスト環境として使用するために必要です。
yaml- name: Molecule テスト実行
run: |
cd roles/${{ matrix.role }}
molecule test
env:
PY_COLORS: 1
ANSIBLE_FORCE_COLOR: 1
各ロールのディレクトリに移動し、molecule test コマンドを実行します。環境変数 PY_COLORS と ANSIBLE_FORCE_COLOR により、ログに色付けされて見やすくなります。
yaml- name: テスト結果のアップロード
if: always()
uses: actions/upload-artifact@v4
with:
name: molecule-logs-${{ matrix.role }}
path: roles/${{ matrix.role }}/molecule/default/.molecule/
retention-days: 7
if: always() により、テストが失敗した場合でもログをアーティファクトとしてアップロードします。これにより、失敗原因の調査が容易になります。
Deploy フェーズの実装
Deploy フェーズでは、テストに合格したコードを実際の環境にデプロイします。
SSH 認証情報の設定
GitHub リポジトリの Settings > Secrets から以下のシークレットを登録します。
| # | シークレット名 | 内容 |
|---|---|---|
| 1 | DEPLOY_SSH_KEY | デプロイ先サーバーへの SSH 秘密鍵 |
| 2 | DEPLOY_HOST_STAGING | ステージング環境のホスト名 |
| 3 | DEPLOY_HOST_PRODUCTION | 本番環境のホスト名 |
| 4 | ANSIBLE_VAULT_PASSWORD | Ansible Vault のパスワード |
これらのシークレットは暗号化され、ワークフロー内でのみ参照可能になります。
インベントリファイルの準備
inventories/staging/hosts.yml と inventories/production/hosts.yml を作成します。
yaml# inventories/staging/hosts.yml
all:
children:
webservers:
hosts:
staging-web-01:
ansible_host: 192.168.1.10
ansible_user: deploy
ansible_port: 22
databases:
hosts:
staging-db-01:
ansible_host: 192.168.1.20
ansible_user: deploy
ansible_port: 22
ステージング環境のホスト情報を YAML 形式で定義します。グループ(webservers、databases)ごとに管理することで、特定のサーバー群にだけタスクを適用できます。
yaml# inventories/production/hosts.yml
all:
children:
webservers:
hosts:
prod-web-01:
ansible_host: 10.0.1.10
ansible_user: deploy
ansible_port: 22
prod-web-02:
ansible_host: 10.0.1.11
ansible_user: deploy
ansible_port: 22
databases:
hosts:
prod-db-01:
ansible_host: 10.0.2.10
ansible_user: deploy
ansible_port: 22
本番環境では、Web サーバーを複数台構成にして冗長性を確保しています。
デプロイ用 Playbook
playbooks/deploy.yml を作成します。
yaml# playbooks/deploy.yml
- name: Deploy Web Application
hosts: webservers
become: true
serial: 1
max_fail_percentage: 0
pre_tasks:
- name: デプロイ開始通知
ansible.builtin.debug:
msg: 'Starting deployment to {{ inventory_hostname }}'
serial: 1 により、複数ホストがある場合でも 1 台ずつ順番にデプロイされます。max_fail_percentage: 0 は、1 台でも失敗したら全体のデプロイを中止する設定です。
yamlroles:
- role: webserver
tags: ['webserver']
post_tasks:
- name: サービスの起動確認
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: ヘルスチェック
ansible.builtin.uri:
url: 'http://{{ ansible_host }}/health'
status_code: 200
register: health_check
retries: 3
delay: 5
until: health_check.status == 200
デプロイ後に Nginx サービスの起動確認とヘルスチェックを実施します。ヘルスチェックは最大 3 回リトライし、5 秒間隔で実行されます。
yaml- name: デプロイ完了通知
ansible.builtin.debug:
msg: 'Deployment completed successfully on {{ inventory_hostname }}'
デプロイが成功したことを明示的にログ出力します。
GitHub Actions ワークフロー(Deploy フェーズ)
.github/workflows/ci.yml に Deploy ジョブを追加します。
yamldeploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/develop'
environment:
name: staging
url: https://staging.example.com
steps:
- name: コードをチェックアウト
uses: actions/checkout@v4
needs: test により Test ジョブが成功した場合のみ実行されます。if 条件で develop ブランチへの push 時のみデプロイするように制限しています。
yaml- name: Python 環境のセットアップ
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Ansible のインストール
run: |
python -m pip install --upgrade pip
pip install ansible
デプロイに必要な Ansible をインストールします。
yaml- name: SSH 鍵のセットアップ
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.DEPLOY_HOST_STAGING }} >> ~/.ssh/known_hosts
GitHub Secrets から SSH 秘密鍵を取得し、適切なパーミッション(600)を設定します。ssh-keyscan でホストの公開鍵を事前に取得し、接続時の確認プロンプトを回避します。
yaml- name: Ansible Vault パスワードファイル作成
run: |
echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_password
chmod 600 .vault_password
Ansible Vault で暗号化された変数を復号化するためのパスワードファイルを作成します。
yaml- name: Staging 環境へデプロイ
run: |
ansible-playbook -i inventories/staging/hosts.yml \
playbooks/deploy.yml \
--vault-password-file .vault_password \
--extra-vars "env=staging" \
-v
ansible-playbook コマンドでステージング環境にデプロイします。-v オプションで詳細なログを出力し、問題発生時の調査を容易にします。
yaml- name: デプロイ結果の通知
if: always()
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"text": "Staging Deployment ${{ job.status }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Staging deployment *${{ job.status }}*\nCommit: ${{ github.sha }}\nActor: ${{ github.actor }}"
}
}
]
}
Slack への通知を行い、デプロイの成功・失敗を開発チームに即座に共有します。if: always() により、デプロイが失敗した場合も通知されます。
本番環境へのデプロイ(手動承認付き)
yamldeploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: deploy-staging
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://example.com
steps:
- name: コードをチェックアウト
uses: actions/checkout@v4
- name: Python 環境のセットアップ
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Ansible のインストール
run: |
python -m pip install --upgrade pip
pip install ansible
本番環境へのデプロイは、main ブランチへのマージ時のみ実行されます。environment: production 設定により、GitHub の Environment Protection Rules を適用でき、手動承認を必須にできます。
yaml- name: SSH 鍵のセットアップ
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.DEPLOY_HOST_PRODUCTION }} >> ~/.ssh/known_hosts
- name: Ansible Vault パスワードファイル作成
run: |
echo "${{ secrets.ANSIBLE_VAULT_PASSWORD }}" > .vault_password
chmod 600 .vault_password
ステージング環境と同様に、SSH 鍵と Vault パスワードをセットアップします。
yaml- name: Production 環境へデプロイ
run: |
ansible-playbook -i inventories/production/hosts.yml \
playbooks/deploy.yml \
--vault-password-file .vault_password \
--extra-vars "env=production" \
--check \
-v
ansible-playbook -i inventories/production/hosts.yml \
playbooks/deploy.yml \
--vault-password-file .vault_password \
--extra-vars "env=production" \
-v
本番環境では、まず --check オプション(ドライラン)で変更内容を確認し、その後実際のデプロイを実行します。これにより、予期しない変更を防ぎます。
yaml- name: デプロイ結果の通知
if: always()
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"text": "Production Deployment ${{ job.status }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Production deployment *${{ job.status }}*\nCommit: ${{ github.sha }}\nActor: ${{ github.actor }}\nEnvironment: https://example.com"
}
}
]
}
本番デプロイの結果も Slack で通知し、全メンバーに共有します。
手動承認の設定
GitHub リポジトリの Settings > Environments から production 環境を作成し、以下の設定を行います。
承認ルールの設定手順
- Settings > Environments > New environment をクリック
- 環境名に
productionを入力 - Required reviewers にチーム名または個人を追加(例:
@devops-team) - Wait timer を設定(例:5 分)して、デプロイ前に確認時間を確保
- Deployment branches で
mainブランチのみを許可
これにより、本番環境へのデプロイは必ず承認者によるレビューが必要になり、誤ったデプロイを防げます。
以下の図は、承認フローを含むデプロイプロセスを示しています。
mermaidsequenceDiagram
participant Dev as 開発者
participant GH as GitHub Actions
participant Reviewer as 承認者
participant Staging as Staging 環境
participant Prod as Production 環境
Dev->>GH: main ブランチへ merge
GH->>GH: Lint + Test 実行
GH->>Staging: Staging へ自動デプロイ
Staging-->>GH: デプロイ成功
GH->>Reviewer: 承認リクエスト通知
alt 承認される場合
Reviewer->>GH: デプロイ承認
GH->>Prod: Production へデプロイ
Prod-->>GH: デプロイ成功
GH->>Dev: 通知(成功)
else 却下される場合
Reviewer->>GH: デプロイ却下
GH->>Dev: 通知(却下)
end
このシーケンス図は、開発者が main ブランチにマージしてから、承認者のレビューを経て本番環境にデプロイされるまでの流れを示しています。
具体例
実践プロジェクト構成
実際のプロジェクトでの構成例を示します。
ディレクトリ構造
bashansible-project/
├── .github/
│ └── workflows/
│ └── ci.yml # CI/CD ワークフロー定義
├── .yamllint # YAML Lint 設定
├── .ansible-lint # Ansible Lint 設定
├── ansible.cfg # Ansible 設定ファイル
├── inventories/
│ ├── staging/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ └── all.yml
│ └── production/
│ ├── hosts.yml
│ └── group_vars/
│ └── all.yml
├── playbooks/
│ ├── deploy.yml
│ └── rollback.yml
└── roles/
├── webserver/
│ ├── tasks/
│ │ └── main.yml
│ ├── templates/
│ ├── handlers/
│ │ └── main.yml
│ └── molecule/
│ └── default/
│ ├── molecule.yml
│ ├── converge.yml
│ └── verify.yml
└── database/
└── ...
このプロジェクト構造により、環境ごとの設定分離、ロールベースの管理、テストの自動化が実現できます。
ansible.cfg 設定
ansible.cfg ファイルで Ansible の動作をカスタマイズします。
ini# ansible.cfg
[defaults]
# インベントリファイルのデフォルトパス
inventory = inventories/production/hosts.yml
# ロールのパス
roles_path = roles
# SSH 接続設定
host_key_checking = False
timeout = 30
基本的な Ansible の設定を記述します。host_key_checking = False は、初回接続時の確認プロンプトを無効化します(CI/CD 環境では必要な設定です)。
ini# 並列実行数
forks = 10
# 出力設定
stdout_callback = yaml
bin_ansible_callbacks = True
# リトライ設定
retry_files_enabled = True
retry_files_save_path = .retry
# ログ設定
log_path = ./ansible.log
forks = 10 により、最大 10 ホストまで並列実行できます。stdout_callback = yaml で出力を YAML 形式にして可読性を向上させます。
ini[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True
pipelining = True により、SSH 接続のオーバーヘッドを削減し、実行速度が向上します。
ロールの実装例
roles/webserver/tasks/main.yml の実装例を示します。
yaml# roles/webserver/tasks/main.yml
- name: システムパッケージの更新
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
tags: ['packages']
cache_valid_time: 3600 により、1 時間以内に apt update が実行されていればスキップされ、冪等性が保たれます。
yaml- name: Nginx のインストール
ansible.builtin.apt:
name: nginx
state: present
tags: ['packages', 'nginx']
notify: Restart Nginx
Nginx をインストールし、変更があった場合は Restart Nginx ハンドラーを呼び出します。
yaml- name: Nginx 設定ファイルの配置
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: 'nginx -t -c %s'
tags: ['config', 'nginx']
notify: Reload Nginx
validate オプションにより、設定ファイルを配置する前に構文チェックが実行されます。これにより、無効な設定ファイルが配置されてサービスが停止するリスクを防げます。
yaml- name: アプリケーション用ディレクトリの作成
ansible.builtin.file:
path: /var/www/html
state: directory
owner: www-data
group: www-data
mode: '0755'
tags: ['config']
Web アプリケーション用のディレクトリを作成し、適切な所有者とパーミッションを設定します。
yaml- name: アプリケーションファイルのデプロイ
ansible.builtin.copy:
src: '{{ playbook_dir }}/../dist/'
dest: /var/www/html/
owner: www-data
group: www-data
mode: '0644'
tags: ['deploy']
notify: Reload Nginx
ビルドされたアプリケーションファイルをデプロイします。notify により、変更があった場合のみ Nginx がリロードされます。
yaml- name: Nginx サービスの起動と有効化
ansible.builtin.service:
name: nginx
state: started
enabled: true
tags: ['nginx']
Nginx サービスを起動し、OS 起動時に自動起動するよう設定します。
ハンドラーの定義
roles/webserver/handlers/main.yml を作成します。
yaml# roles/webserver/handlers/main.yml
- name: Restart Nginx
ansible.builtin.service:
name: nginx
state: restarted
listen: Restart Nginx
- name: Reload Nginx
ansible.builtin.service:
name: nginx
state: reloaded
listen: Reload Nginx
ハンドラーは、タスクから notify されたときのみ実行され、すべてのタスクが完了した後に 1 度だけ実行されます。Reload は設定ファイルの再読み込みのみで、接続を維持できます。
実際の CI/CD 実行例
Pull Request を作成した際の実行フローを示します。
Pull Request 作成時
- 開発者が feature ブランチから develop ブランチへ Pull Request を作成
- GitHub Actions が自動的にトリガーされ、Lint ジョブが実行される
- yamllint と ansible-lint がコードをチェック
- Lint が成功すると、Test ジョブが各ロールに対して並列実行される
- Molecule が Docker コンテナを起動し、ロールを適用してテスト
- すべてのテストが成功すると、Pull Request にグリーンチェックマークが表示される
develop ブランチへのマージ時
- Pull Request が承認され、develop ブランチにマージ
- GitHub Actions が再度トリガーされる
- Lint と Test が再実行される(マージコミットに対して)
- テストが成功すると、deploy-staging ジョブが実行される
- Staging 環境へ自動的にデプロイされる
- デプロイ結果が Slack に通知される
main ブランチへのマージ時(本番デプロイ)
- develop から main への Pull Request を作成
- レビュー後、main ブランチにマージ
- Lint → Test → deploy-staging が順次実行される
- deploy-production ジョブが実行され、承認待ち状態になる
- GitHub の Actions タブに「Review required」が表示される
- 承認者が変更内容を確認し、「Approve and deploy」ボタンをクリック
- 本番環境へのデプロイが開始される
- デプロイ成功後、Slack へ通知される
以下の図は、ブランチ戦略と連動した CI/CD フローを示しています。
mermaidflowchart TD
feature["feature ブランチ"] -->|Pull Request| develop["develop ブランチ"]
develop -->|Lint + Test| staging_deploy["Staging 自動デプロイ"]
develop -->|Pull Request| main["main ブランチ"]
main -->|Lint + Test| staging_check["Staging デプロイ"]
staging_check -->|成功| approval["手動承認待ち"]
approval -->|承認| prod_deploy["Production デプロイ"]
approval -->|却下| reject["デプロイ中止"]
prod_deploy --> notify["Slack 通知"]
reject --> notify
この図は、feature ブランチでの開発から本番デプロイまでの Git ブランチ戦略と CI/CD パイプラインの関係を示しています。
エラー処理とロールバック
デプロイに失敗した場合のロールバック用 Playbook も用意しておきます。
ロールバック Playbook
playbooks/rollback.yml を作成します。
yaml# playbooks/rollback.yml
- name: Rollback to Previous Version
hosts: webservers
become: true
serial: 1
vars:
rollback_version: "{{ lookup('env', 'ROLLBACK_VERSION') | default('previous') }}"
tasks:
- name: 以前のバージョンを確認
ansible.builtin.stat:
path: '/var/www/html.{{ rollback_version }}'
register: rollback_path
ロールバック先のバージョンが存在するか確認します。
yaml- name: ロールバック先が存在しない場合は失敗
ansible.builtin.fail:
msg: 'Rollback version {{ rollback_version }} does not exist'
when: not rollback_path.stat.exists
- name: 現在のバージョンをバックアップ
ansible.builtin.command:
cmd: mv /var/www/html /var/www/html.failed
removes: /var/www/html
changed_when: true
現在のバージョンを .failed サフィックス付きで保存します。
yaml- name: 以前のバージョンに戻す
ansible.builtin.command:
cmd: 'cp -r /var/www/html.{{ rollback_version }} /var/www/html'
creates: /var/www/html
changed_when: true
notify: Reload Nginx
- name: サービスのヘルスチェック
ansible.builtin.uri:
url: 'http://{{ ansible_host }}/health'
status_code: 200
register: health_check
retries: 5
delay: 3
until: health_check.status == 200
ロールバック後、ヘルスチェックで正常性を確認します。
yaml - name: ロールバック完了通知
ansible.builtin.debug:
msg: "Rollback to {{ rollback_version }} completed on {{ inventory_hostname }}"
handlers:
- name: Reload Nginx
ansible.builtin.service:
name: nginx
state: reloaded
ロールバックが完了したことをログに記録します。
ロールバック実行用ワークフロー
.github/workflows/rollback.yml を作成します。
yaml# .github/workflows/rollback.yml
name: Rollback Production
on:
workflow_dispatch:
inputs:
version:
description: 'Rollback to version'
required: true
default: 'previous'
jobs:
rollback:
name: Rollback Production
runs-on: ubuntu-latest
environment:
name: production
steps:
- name: コードをチェックアウト
uses: actions/checkout@v4
- name: Python 環境のセットアップ
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Ansible のインストール
run: |
python -m pip install --upgrade pip
pip install ansible
手動トリガー(workflow_dispatch)により、必要なときにロールバックを実行できます。
yaml- name: SSH 鍵のセットアップ
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.DEPLOY_HOST_PRODUCTION }} >> ~/.ssh/known_hosts
- name: ロールバック実行
run: |
ansible-playbook -i inventories/production/hosts.yml \
playbooks/rollback.yml \
--extra-vars "rollback_version=${{ github.event.inputs.version }}" \
-v
env:
ROLLBACK_VERSION: ${{ github.event.inputs.version }}
ユーザーが入力したバージョンに基づいてロールバックを実行します。
yaml- name: ロールバック結果の通知
if: always()
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"text": "Production Rollback ${{ job.status }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Production rollback to version *${{ github.event.inputs.version }}* ${{ job.status }}\nTriggered by: ${{ github.actor }}"
}
}
]
}
ロールバックの結果を Slack に通知します。
監視とアラート
デプロイ後の監視を Playbook に組み込むこともできます。
ヘルスチェックタスク
roles/monitoring/tasks/main.yml を作成します。
yaml# roles/monitoring/tasks/main.yml
- name: 継続的なヘルスチェック(5分間)
ansible.builtin.uri:
url: 'http://{{ ansible_host }}/health'
status_code: 200
timeout: 5
register: health_result
retries: 60
delay: 5
until: health_result.status == 200
failed_when: false
changed_when: false
デプロイ後 5 分間、5 秒間隔でヘルスチェックを実行します。failed_when: false により、失敗してもタスク自体は失敗とみなされません。
yaml- name: ヘルスチェック失敗時にロールバック
ansible.builtin.include_role:
name: rollback
when: health_result.status != 200
ヘルスチェックが失敗した場合、自動的にロールバックロールを実行します。
yaml- name: アプリケーションログの確認
ansible.builtin.command:
cmd: tail -n 100 /var/log/nginx/error.log
register: error_log
changed_when: false
- name: エラーログに問題がある場合は警告
ansible.builtin.fail:
msg: 'Error detected in logs: {{ error_log.stdout }}'
when: "'ERROR' in error_log.stdout or 'CRITICAL' in error_log.stdout"
Nginx のエラーログをチェックし、ERROR や CRITICAL が含まれている場合は警告を出します。
まとめ
本記事では、GitHub Actions を使った Ansible の CI/CD パイプラインを構築する方法を詳しく解説しました。
実装した CI/CD パイプラインのポイント
| # | ポイント | 効果 |
|---|---|---|
| 1 | Lint フェーズ | yamllint と ansible-lint によるコード品質の自動チェック |
| 2 | Test フェーズ | Molecule による複数環境での自動テスト |
| 3 | Deploy フェーズ | ステージング環境での自動デプロイと本番環境への承認付きデプロイ |
| 4 | ロールバック | デプロイ失敗時の迅速な復旧 |
| 5 | 監視 | デプロイ後のヘルスチェックと自動ロールバック |
導入効果
CI/CD パイプラインを導入することで、以下のような効果が期待できます。
品質向上: Lint と Test により、コードの品質が保証され、本番環境でのエラーが大幅に減少します。構文エラーや非推奨モジュールの使用を事前に検出できるため、安心してデプロイできるようになるでしょう。
デプロイ速度の向上: 手動でのテストとデプロイが不要になり、Pull Request のマージから本番デプロイまでが数分で完了します。ステージング環境での自動検証により、本番環境への影響を最小限に抑えられます。
属人化の解消: デプロイ手順がコード化されることで、誰でも同じ手順でデプロイできるようになります。新しいメンバーがジョインしても、すぐにデプロイプロセスを理解できるでしょう。
安全性の向上: 手動承認フローと自動ロールバック機能により、本番環境への変更を安全に行えます。問題が発生しても迅速に以前のバージョンに戻せるため、ダウンタイムを最小限に抑えられます。
次のステップ
さらに CI/CD パイプラインを進化させるために、以下のような拡張も検討してみてください。
複数クラウド対応: AWS、GCP、Azure など複数のクラウドプロバイダーに対応したデプロイパイプラインを構築できます。Ansible の cloud モジュールを活用することで、クラウドリソースの作成からアプリケーションデプロイまでを一貫して管理できるでしょう。
Blue-Green デプロイ: 新バージョンを別環境にデプロイし、トラフィックを切り替える方式を導入することで、さらにダウンタイムを削減できます。
カナリアリリース: 一部のユーザーにのみ新バージョンを公開し、問題がなければ徐々に展開する戦略も有効です。
セキュリティスキャン: Trivy や Snyk などのセキュリティスキャンツールを CI パイプラインに組み込み、脆弱性を早期に検出できるようにしましょう。
Ansible と GitHub Actions を組み合わせた CI/CD パイプラインは、インフラ運用の自動化と品質向上に大きく貢献します。本記事で紹介した実装例を基に、ぜひ自分のプロジェクトに合わせたパイプラインを構築してみてください。
関連リンク
articleAnsible CI/CD 運用:GitHub Actions で lint/test/デプロイを自動化
articleAnsible 役割設計:Roles/Collections でスケーラブルに分割する指針
articleAnsible モジュール 100 連発チートシート:file/user/service/git ほか
articleAnsible セットアップ完全版:macOS/Homebrew/pyenv で安全に導入
articleAnsible と Terraform/Puppet/Chef 比較:宣言/手続の違いと併用戦略
articleAnsible トラブルシュート:UNREACHABLE!/FAILED! を 3 分で切り分ける
articleRuby とは?2025 年版の特徴・強み・最新エコシステムを徹底解説
articlePHP とは?2025 年版の特徴・強み・できることを徹底解説【保存版】
articleJotai 運用ガイド:命名規約・debugLabel・依存グラフ可視化の標準化
articleZod vs Ajv/Joi/Valibot/Superstruct:DX・速度・サイズを本気でベンチ比較
articleYarn でモノレポ設計:パッケージ分割、共有ライブラリ、リリース戦略
articleJest を可観測化する:JUnit/SARIF/OpenTelemetry で CI ダッシュボードを構築
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来