T-CREATOR

Ansible CI/CD 運用:GitHub Actions で lint/test/デプロイを自動化

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 つのフェーズで構成します。

フェーズ構成

#フェーズ目的主要ツール
1Lintコード品質チェックyamllint、ansible-lint
2Test動作検証Molecule、pytest
3Deploy環境へのデプロイ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/falseyes/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_COLORSANSIBLE_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 から以下のシークレットを登録します。

#シークレット名内容
1DEPLOY_SSH_KEYデプロイ先サーバーへの SSH 秘密鍵
2DEPLOY_HOST_STAGINGステージング環境のホスト名
3DEPLOY_HOST_PRODUCTION本番環境のホスト名
4ANSIBLE_VAULT_PASSWORDAnsible Vault のパスワード

これらのシークレットは暗号化され、ワークフロー内でのみ参照可能になります。

インベントリファイルの準備

inventories​/​staging​/​hosts.ymlinventories​/​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 形式で定義します。グループ(webserversdatabases)ごとに管理することで、特定のサーバー群にだけタスクを適用できます。

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 環境を作成し、以下の設定を行います。

承認ルールの設定手順

  1. Settings > Environments > New environment をクリック
  2. 環境名に production を入力
  3. Required reviewers にチーム名または個人を追加(例:@devops-team
  4. Wait timer を設定(例:5 分)して、デプロイ前に確認時間を確保
  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 作成時

  1. 開発者が feature ブランチから develop ブランチへ Pull Request を作成
  2. GitHub Actions が自動的にトリガーされ、Lint ジョブが実行される
  3. yamllint と ansible-lint がコードをチェック
  4. Lint が成功すると、Test ジョブが各ロールに対して並列実行される
  5. Molecule が Docker コンテナを起動し、ロールを適用してテスト
  6. すべてのテストが成功すると、Pull Request にグリーンチェックマークが表示される

develop ブランチへのマージ時

  1. Pull Request が承認され、develop ブランチにマージ
  2. GitHub Actions が再度トリガーされる
  3. Lint と Test が再実行される(マージコミットに対して)
  4. テストが成功すると、deploy-staging ジョブが実行される
  5. Staging 環境へ自動的にデプロイされる
  6. デプロイ結果が Slack に通知される

main ブランチへのマージ時(本番デプロイ)

  1. develop から main への Pull Request を作成
  2. レビュー後、main ブランチにマージ
  3. Lint → Test → deploy-staging が順次実行される
  4. deploy-production ジョブが実行され、承認待ち状態になる
  5. GitHub の Actions タブに「Review required」が表示される
  6. 承認者が変更内容を確認し、「Approve and deploy」ボタンをクリック
  7. 本番環境へのデプロイが開始される
  8. デプロイ成功後、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 のエラーログをチェックし、ERRORCRITICAL が含まれている場合は警告を出します。

まとめ

本記事では、GitHub Actions を使った Ansible の CI/CD パイプラインを構築する方法を詳しく解説しました。

実装した CI/CD パイプラインのポイント

#ポイント効果
1Lint フェーズyamllint と ansible-lint によるコード品質の自動チェック
2Test フェーズMolecule による複数環境での自動テスト
3Deploy フェーズステージング環境での自動デプロイと本番環境への承認付きデプロイ
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 パイプラインは、インフラ運用の自動化と品質向上に大きく貢献します。本記事で紹介した実装例を基に、ぜひ自分のプロジェクトに合わせたパイプラインを構築してみてください。

関連リンク