T-CREATOR

Ansible 役割設計:Roles/Collections でスケーラブルに分割する指針

Ansible 役割設計:Roles/Collections でスケーラブルに分割する指針

Ansible を使ったインフラ自動化を進めていくと、Playbook の肥大化に悩むことは少なくありません。最初は小さかったコードが、時間とともに複雑化し、メンテナンスが困難になっていく――そんな課題を解決するのが、Roles と Collections を活用した役割設計です。

この記事では、Ansible で大規模なインフラを管理する際に必要となる、スケーラブルな役割設計の指針をお伝えします。Roles と Collections の違いや使い分けのポイント、実際の設計パターンまで、実務で役立つ具体的な知識を身につけていきましょう。

背景

Ansible における役割分担の重要性

Ansible は YAML ベースのシンプルな構文で、サーバー構成管理やデプロイ自動化を実現できる便利なツールですね。しかし、管理対象が増えるにつれて、Playbook ファイルは肥大化し、コードの重複やメンテナンス負荷が高まります。

このような課題を解決するために、Ansible では Roles(ロール)Collections(コレクション) という仕組みが用意されています。これらを適切に活用することで、コードの再利用性が向上し、チーム開発でも保守しやすい構成を実現できるでしょう。

Roles と Collections の位置づけ

下記の図は、Ansible における Roles と Collections の関係性を示しています。

mermaidflowchart TB
  playbook["Playbook<br/>(play定義)"] -->|呼び出し| roles["Roles<br/>(役割単位)"]
  playbook -->|利用| collections["Collections<br/>(パッケージ単位)"]
  collections -->|含む| roles
  collections -->|含む| modules["Modules<br/>(処理単位)"]
  collections -->|含む| plugins["Plugins"]
  roles -->|構成| tasks["Tasks"]
  roles -->|構成| handlers["Handlers"]
  roles -->|構成| vars["Variables"]
  roles -->|構成| templates["Templates"]

上記の図からわかるように、Roles は「役割」単位でタスクや変数、テンプレートをまとめたものであり、Collections はそれらをさらにパッケージとして再利用可能にする仕組みなのです。Collections には Roles だけでなく、モジュールやプラグインも含められるため、より広範な用途に対応できます。

図で理解できる要点

  • Playbook から Roles と Collections を呼び出して処理を実行する
  • Collections は Roles、Modules、Plugins を含む上位のパッケージ概念
  • Roles は Tasks、Handlers、Variables、Templates などで構成される

課題

Playbook 肥大化による問題点

Ansible を使い始めた頃は、すべての処理を 1 つの Playbook に記述することが多いですね。しかし、管理対象が増えると以下のような問題が発生します。

#課題詳細
1可読性の低下数百行に及ぶ YAML ファイルは、処理の流れを追うのが困難になります
2コードの重複同じタスクを複数の Playbook で記述すると、修正時に漏れが発生しやすくなります
3テストの困難性大きな Playbook は部分的なテストが難しく、変更の影響範囲が見えにくくなります
4チーム開発の障壁複数人で同じファイルを編集すると、コンフリクトが頻発します
5再利用性の欠如プロジェクト間で共通の処理を流用する仕組みがないと、車輪の再発明が起こります

役割分割の判断基準が不明確

Roles や Collections の存在は知っていても、「どこまでを Role にすべきか」「いつ Collection として切り出すべきか」といった判断基準が不明確なことも課題です。適切な粒度で分割しないと、かえって複雑性が増してしまうこともあります。

下記の図は、役割分割の判断に迷う典型的なシナリオを示しています。

mermaidflowchart LR
  start["タスク増加"] --> decision{"分割すべき?"}
  decision -->|基準不明| over["過剰分割<br/>(複雑化)"]
  decision -->|基準不明| under["分割不足<br/>(肥大化)"]
  decision -->|明確な指針| optimal["適切な粒度<br/>(保守性向上)"]

明確な設計指針がないと、過剰分割による複雑化や、分割不足による肥大化のどちらかに陥ってしまうのです。

解決策

Roles を活用した役割ベース設計

Roles は、関連するタスク、変数、ハンドラー、テンプレートなどを 1 つの役割単位でまとめる仕組みです。これにより、Playbook はシンプルになり、再利用性が高まります。

Roles の基本構造

Ansible の Role は、以下のようなディレクトリ構造で構成されます。

plaintextroles/
└── webserver/
    ├── tasks/
    │   └── main.yml       # メインタスク
    ├── handlers/
    │   └── main.yml       # ハンドラー(再起動など)
    ├── templates/
    │   └── nginx.conf.j2  # Jinja2テンプレート
    ├── files/
    │   └── index.html     # 配布ファイル
    ├── vars/
    │   └── main.yml       # 変数定義
    ├── defaults/
    │   └── main.yml       # デフォルト変数
    ├── meta/
    │   └── main.yml       # メタ情報(依存関係など)
    └── README.md          # ドキュメント

各ディレクトリには明確な役割があり、必要に応じて使い分けることで、コードの見通しが良くなります。

Roles 分割の指針

#指針説明
1機能単位で分割1 つの Role は 1 つの機能を担当するwebserver, database, monitoring
2独立性を重視Role 間の依存は最小限にし、単体で動作可能にするcommon Role に依存しすぎない
3変数は defaults で定義カスタマイズ可能な変数は defaults/ に配置するポート番号、パス設定など
4ドキュメント必須README.md で Role の目的と使い方を明記する必須変数、使用例を記載
5適度な粒度小さすぎず大きすぎない粒度を意識するタスク数 10 ~ 50 行程度

Playbook から Roles を呼び出す

以下は、Playbook から複数の Roles を呼び出す基本的な例です。

yaml---
# site.yml
- name: Webサーバーのセットアップ
  hosts: web_servers
  become: true

  roles:
    - common # 共通設定
    - webserver # Webサーバー設定
    - monitoring # 監視エージェント設定

上記のように、Playbook は簡潔になり、各 Role が独立して機能することで、テストや再利用が容易になります。

変数の優先順位を理解する

Ansible では、変数の定義場所によって優先順位が異なります。これを理解することで、柔軟な設定管理が可能になるでしォう。

yaml# roles/webserver/defaults/main.yml
# デフォルト値(優先順位:低)
---
nginx_port: 80
nginx_worker_processes: auto
yaml# roles/webserver/vars/main.yml
# Role固有の変数(優先順位:中)
---
nginx_config_path: /etc/nginx/nginx.conf
yaml# group_vars/web_servers.yml
# グループ固有の変数(優先順位:高)
---
nginx_port: 8080 # defaults を上書き

このように、defaults で汎用的なデフォルト値を定義し、環境ごとの差異は group_vars や host_vars で上書きすることで、同じ Role を複数環境で使い回せますね。

Collections による再利用性の向上

Collections は、Roles、Modules、Plugins を 1 つのパッケージとしてまとめ、配布・再利用を容易にする仕組みです。Ansible Galaxy を通じて公開したり、プライベートリポジトリで管理したりできます。

Collections の構造

Collections は以下のような構造を持ちます。

plaintextmycollection/
└── namespace/
    └── collection_name/
        ├── plugins/
        │   ├── modules/
        │   ├── inventory/
        │   └── filter/
        ├── roles/
        │   ├── role1/
        │   └── role2/
        ├── playbooks/
        ├── docs/
        ├── galaxy.yml      # メタデータ
        └── README.md

Collections 作成の手順

Collections を新規作成する手順を順を追って説明します。

ステップ 1: Collection の初期化

以下のコマンドで、Collection のひな形を生成できます。

bash# namespace と collection_name を指定
ansible-galaxy collection init mycompany.infrastructure

ステップ 2: galaxy.yml の編集

生成された galaxy.yml にメタデータを記述します。

yaml# mycompany/infrastructure/galaxy.yml
---
namespace: mycompany
name: infrastructure
version: 1.0.0
readme: README.md
authors:
  - Your Name <your.email@example.com>
description: 社内インフラ管理用 Collection
license:
  - MIT
tags:
  - infrastructure
  - webserver
  - database
dependencies: {}

ステップ 3: Roles や Modules の配置

既存の Roles を Collection 内に移動します。

bash# Roles を Collection に配置
mv roles/webserver mycompany/infrastructure/roles/
mv roles/database mycompany/infrastructure/roles/

ステップ 4: Collection のビルドとインストール

Collection をビルドして、他のプロジェクトで利用可能にします。

bash# Collection をビルド
cd mycompany/infrastructure
ansible-galaxy collection build

# 生成された tar.gz を他環境でインストール
ansible-galaxy collection install mycompany-infrastructure-1.0.0.tar.gz

Collections の使用方法

Playbook から Collection を利用する際は、FQCN(Fully Qualified Collection Name)を使用します。

yaml---
# site.yml
- name: Collection を使ったサーバーセットアップ
  hosts: all
  become: true

  collections:
    - mycompany.infrastructure # Collection を宣言

  roles:
    - webserver # mycompany.infrastructure.webserver として解決
    - database # mycompany.infrastructure.database として解決

このように、Collection を使うことで、組織全体で統一された Role を簡単に共有できるようになります。

Roles と Collections の使い分け指針

下記の表は、Roles と Collections をどのように使い分けるべきかの指針を示しています。

#観点Roles を選択Collections を選択
1スコープ単一プロジェクト内で完結複数プロジェクトで共有
2配布方法Git リポジトリで管理Ansible Galaxy や社内リポジトリ
3依存関係シンプルな構成Roles + Modules + Plugins の組み合わせ
4バージョン管理Git タグで管理galaxy.yml でセマンティックバージョニング
5更新頻度頻繁に変更される比較的安定している

一般的には、まず Roles で設計を始め、複数プロジェクトで共通化が必要になった段階で Collections に昇格させる流れが自然です。

具体例

ケーススタディ: Web アプリケーション環境の構築

ここでは、Web アプリケーション環境を構築する実際のシナリオを通じて、Roles と Collections の設計方法を具体的に見ていきましょう。

要件定義

以下のような環境を Ansible で構築するケースを考えます。

#コンポーネント役割
1Nginxリバースプロキシ、静的ファイル配信
2PostgreSQLデータベース
3Redisセッションストア、キャッシュ
4Node.js アプリバックエンド API
5Prometheus + Grafana監視・可視化

アーキテクチャ設計

下記の図は、構築する環境の全体像を示しています。

mermaidflowchart TB
  user["ユーザー"] -->|HTTPS| nginx["Nginx<br/>(リバースプロキシ)"]
  nginx -->|プロキシ| app["Node.js App<br/>(API)"]
  app -->|読み書き| postgres[("PostgreSQL<br/>(DB)")]
  app -->|セッション| redis[("Redis<br/>(キャッシュ)")]
  monitor["Prometheus"] -->|メトリクス収集| nginx
  monitor -->|メトリクス収集| app
  monitor -->|メトリクス収集| postgres
  grafana["Grafana"] -->|可視化| monitor

この環境を Roles に分割すると、以下のような構成になります。

Roles 構成の設計

plaintextroles/
├── common/              # 全サーバー共通設定
├── nginx/               # Nginx設定
├── postgresql/          # PostgreSQL設定
├── redis/               # Redis設定
├── nodejs_app/          # Node.jsアプリデプロイ
├── prometheus/          # Prometheus設定
└── grafana/             # Grafana設定

共通 Role の実装例

まず、すべてのサーバーで必要となる共通設定を定義します。

yaml# roles/common/tasks/main.yml
---
# OSパッケージの更新
- name: システムパッケージを最新化
  apt:
    update_cache: yes
    upgrade: dist
  when: ansible_os_family == "Debian"
yaml# タイムゾーン設定
- name: タイムゾーンを Asia/Tokyo に設定
  timezone:
    name: Asia/Tokyo
yaml# 必須パッケージのインストール
- name: 必須パッケージをインストール
  apt:
    name:
      - curl
      - wget
      - git
      - vim
    state: present
yaml# セキュリティ設定
- name: UFW ファイアウォールを有効化
  ufw:
    state: enabled
    policy: deny
    direction: incoming

- name: SSH ポートを許可
  ufw:
    rule: allow
    port: '22'
    proto: tcp

上記のように、共通 Role では OS の基本設定やセキュリティ対策など、すべてのサーバーで必要な処理をまとめています。

Nginx Role の実装例

次に、Nginx の設定を行う Role を作成します。

yaml# roles/nginx/defaults/main.yml
---
nginx_port: 80
nginx_ssl_port: 443
nginx_worker_processes: auto
nginx_upstream_server: '127.0.0.1:3000'
yaml# roles/nginx/tasks/main.yml
---
# Nginxのインストール
- name: Nginx をインストール
  apt:
    name: nginx
    state: present
    update_cache: yes
yaml# 設定ファイルのデプロイ
- name: Nginx 設定ファイルをデプロイ
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    mode: '0644'
  notify: Restart nginx
yaml# サイト設定のデプロイ
- name: サイト設定をデプロイ
  template:
    src: site.conf.j2
    dest: /etc/nginx/sites-available/default
    mode: '0644'
  notify: Reload nginx
yaml# Nginxの起動と有効化
- name: Nginx を起動し、自動起動を有効化
  systemd:
    name: nginx
    state: started
    enabled: yes
yaml# roles/nginx/handlers/main.yml
---
- name: Restart nginx
  systemd:
    name: nginx
    state: restarted

- name: Reload nginx
  systemd:
    name: nginx
    state: reloaded

ハンドラーを使うことで、設定ファイル変更時のみサービスを再起動できるため、効率的な運用が可能になります。

PostgreSQL Role の実装例

データベース設定を行う Role です。

yaml# roles/postgresql/defaults/main.yml
---
postgresql_version: '14'
postgresql_db_name: 'appdb'
postgresql_db_user: 'appuser'
postgresql_db_password: 'changeme'
postgresql_listen_addresses: 'localhost'
yaml# roles/postgresql/tasks/main.yml
---
# PostgreSQLのインストール
- name: PostgreSQL をインストール
  apt:
    name:
      - 'postgresql-{{ postgresql_version }}'
      - postgresql-contrib
      - python3-psycopg2 # Ansible用モジュール
    state: present
yaml# データベースの作成
- name: データベースを作成
  become: yes
  become_user: postgres
  postgresql_db:
    name: '{{ postgresql_db_name }}'
    encoding: UTF-8
    lc_collate: ja_JP.UTF-8
    lc_ctype: ja_JP.UTF-8
    template: template0
    state: present
yaml# ユーザーの作成
- name: データベースユーザーを作成
  become: yes
  become_user: postgres
  postgresql_user:
    name: '{{ postgresql_db_user }}'
    password: '{{ postgresql_db_password }}'
    db: '{{ postgresql_db_name }}'
    priv: ALL
    state: present
yaml# 設定ファイルの配置
- name: postgresql.conf を配置
  template:
    src: postgresql.conf.j2
    dest: '/etc/postgresql/{{ postgresql_version }}/main/postgresql.conf'
    owner: postgres
    group: postgres
    mode: '0644'
  notify: Restart postgresql

Node.js アプリケーション Role の実装例

アプリケーションのデプロイを行う Role です。

yaml# roles/nodejs_app/defaults/main.yml
---
app_name: 'myapp'
app_port: 3000
app_repo: 'https://github.com/mycompany/myapp.git'
app_version: 'main'
app_user: 'appuser'
app_dir: '/opt/{{ app_name }}'
node_version: '18.x'
yaml# roles/nodejs_app/tasks/main.yml
---
# アプリケーションユーザーの作成
- name: アプリケーション用ユーザーを作成
  user:
    name: '{{ app_user }}'
    system: yes
    shell: /bin/bash
    home: '{{ app_dir }}'
yaml# Node.jsのインストール
- name: NodeSource リポジトリを追加
  shell: 'curl -fsSL https://deb.nodesource.com/setup_{{ node_version }} | bash -'
  args:
    creates: /etc/apt/sources.list.d/nodesource.list

- name: Node.js をインストール
  apt:
    name: nodejs
    state: present
    update_cache: yes
yaml# アプリケーションのデプロイ
- name: アプリケーションコードをクローン
  git:
    repo: '{{ app_repo }}'
    dest: '{{ app_dir }}'
    version: '{{ app_version }}'
    force: yes
  become: yes
  become_user: '{{ app_user }}'
  notify: Restart app
yaml# 依存パッケージのインストール
- name: npm install を実行
  command: npm ci --production
  args:
    chdir: '{{ app_dir }}'
  become: yes
  become_user: '{{ app_user }}'
  notify: Restart app
yaml# systemdサービスファイルの配置
- name: systemd サービスファイルを配置
  template:
    src: app.service.j2
    dest: '/etc/systemd/system/{{ app_name }}.service'
    mode: '0644'
  notify:
    - Reload systemd
    - Restart app
yaml# roles/nodejs_app/handlers/main.yml
---
- name: Reload systemd
  systemd:
    daemon_reload: yes

- name: Restart app
  systemd:
    name: '{{ app_name }}'
    state: restarted

メイン Playbook の実装

すべての Role を組み合わせたメイン Playbook です。

yaml# site.yml
---
- name: 共通設定を適用
  hosts: all
  become: true
  roles:
    - common

- name: Webサーバーをセットアップ
  hosts: web_servers
  become: true
  roles:
    - nginx
    - nodejs_app

- name: データベースサーバーをセットアップ
  hosts: db_servers
  become: true
  roles:
    - postgresql

- name: キャッシュサーバーをセットアップ
  hosts: cache_servers
  become: true
  roles:
    - redis

- name: 監視サーバーをセットアップ
  hosts: monitoring_servers
  become: true
  roles:
    - prometheus
    - grafana

このように Playbook を記述すると、サーバーの役割ごとに必要な設定を適用できます。

インベントリファイルの設計

サーバーをグループ化したインベントリファイルの例です。

ini# inventory/production/hosts
[web_servers]
web01.example.com
web02.example.com

[db_servers]
db01.example.com

[cache_servers]
cache01.example.com

[monitoring_servers]
monitor01.example.com

[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_rsa
yaml# inventory/production/group_vars/web_servers.yml
---
nginx_upstream_server: '127.0.0.1:3000'
app_version: 'v1.2.3'
yaml# inventory/production/group_vars/db_servers.yml
---
postgresql_listen_addresses: '0.0.0.0'
postgresql_db_password: '{{ vault_db_password }}' # Ansible Vault で暗号化

環境ごとに異なる変数を group_vars で定義することで、同じ Role を複数環境で使い回せますね。

Collection への昇格

上記の Roles が複数プロジェクトで利用されるようになった場合、Collection としてパッケージ化することを検討しましょう。

Collection 化の手順

bash# Collection の初期化
ansible-galaxy collection init mycompany.webapp

# Roles を Collection に移動
mv roles/nginx mycompany/webapp/roles/
mv roles/postgresql mycompany/webapp/roles/
mv roles/redis mycompany/webapp/roles/
mv roles/nodejs_app mycompany/webapp/roles/
mv roles/prometheus mycompany/webapp/roles/
mv roles/grafana mycompany/webapp/roles/
yaml# mycompany/webapp/galaxy.yml
---
namespace: mycompany
name: webapp
version: 1.0.0
readme: README.md
authors:
  - Infrastructure Team <infra@example.com>
description: Web アプリケーション環境構築用 Collection
license:
  - MIT
tags:
  - web
  - database
  - monitoring
dependencies:
  community.general: '>=5.0.0'
  community.postgresql: '>=2.0.0'

Collection のビルドと配布

bash# Collection をビルド
cd mycompany/webapp
ansible-galaxy collection build

# プライベートリポジトリにアップロード(例: Artifactory)
# または、Ansible Galaxy にパブリッシュ
ansible-galaxy collection publish mycompany-webapp-1.0.0.tar.gz --api-key=YOUR_API_KEY

Collection を使った Playbook

yaml# site.yml(Collection 利用版)
---
- name: Web アプリケーション環境構築
  hosts: all
  become: true

  collections:
    - mycompany.webapp

  pre_tasks:
    - name: Collection のバージョンを表示
      debug:
        msg: 'Using mycompany.webapp collection version 1.0.0'

  roles:
    - role: common
    - role: nginx
      when: "'web_servers' in group_names"
    - role: nodejs_app
      when: "'web_servers' in group_names"
    - role: postgresql
      when: "'db_servers' in group_names"
    - role: redis
      when: "'cache_servers' in group_names"
    - role: prometheus
      when: "'monitoring_servers' in group_names"
    - role: grafana
      when: "'monitoring_servers' in group_names"

Collection 化することで、他のプロジェクトでも同じ環境構築を簡単に再現できるようになります。

テストと CI/CD 統合

Roles や Collections の品質を保つために、テストを自動化することも重要です。

Molecule によるテスト

Molecule は Ansible Role のテストフレームワークです。

bash# Molecule のインストール
pip install molecule molecule-docker

# テストシナリオの初期化
cd roles/nginx
molecule init scenario --driver-name docker
yaml# roles/nginx/molecule/default/molecule.yml
---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: nginx-ubuntu
    image: ubuntu:22.04
    pre_build_image: true
provisioner:
  name: ansible
verifier:
  name: ansible
yaml# roles/nginx/molecule/default/converge.yml
---
- name: Converge
  hosts: all
  become: true
  roles:
    - role: nginx
yaml# roles/nginx/molecule/default/verify.yml
---
- name: Verify
  hosts: all
  tasks:
    - name: Nginx がインストールされているか確認
      package:
        name: nginx
        state: present
      check_mode: yes
      register: pkg_check
      failed_when: pkg_check.changed

    - name: Nginx が起動しているか確認
      service:
        name: nginx
        state: started
      check_mode: yes
      register: svc_check
      failed_when: svc_check.changed
bash# テストの実行
molecule test

Molecule を使うことで、Role が期待通りに動作することを自動的に検証できます。

GitHub Actions での CI/CD

yaml# .github/workflows/ansible-test.yml
---
name: Ansible Role Test

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        role:
          - nginx
          - postgresql
          - redis
          - nodejs_app

    steps:
      - name: リポジトリをチェックアウト
        uses: actions/checkout@v3

      - name: Python をセットアップ
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: 依存パッケージをインストール
        run: |
          pip install ansible molecule molecule-docker

      - name: Molecule テストを実行
        run: |
          cd roles/${{ matrix.role }}
          molecule test
        env:
          PY_COLORS: '1'
          ANSIBLE_FORCE_COLOR: '1'

このように CI/CD パイプラインに組み込むことで、コード変更時に自動的にテストが実行され、品質を維持できますね。

図で理解できる要点

  • Web アプリ環境を複数の Role に分割(common, nginx, postgresql, redis, nodejs_app, prometheus, grafana)
  • 各 Role は独立してテスト可能で、Molecule を使った自動テストを実装
  • Collection 化により、他プロジェクトでも同じ環境構築を再現可能
  • CI/CD パイプラインで品質を自動的に保証

まとめ

Ansible における Roles と Collections を活用した役割設計は、大規模なインフラ自動化を成功させるための重要な要素です。

本記事では、以下のポイントをお伝えしました。

#ポイント内容
1Roles の基本機能単位で分割し、独立性と再利用性を重視する
2Collections の活用複数プロジェクトで共有する場合は Collection 化を検討する
3適切な粒度小さすぎず大きすぎない、メンテナンス可能な粒度を保つ
4変数管理defaults, vars, group_vars を使い分け、柔軟な設定管理を実現する
5テストの自動化Molecule や CI/CD パイプラインで品質を保証する
6ドキュメントREADME.md で Role の使い方を明確に記述する

最初は小さな Role から始めて、徐々に整理していくアプローチが現実的でしょう。チームで共通認識を持ち、設計指針を明確にすることで、保守性の高い Ansible コードベースを構築できます。

ぜひ、本記事でご紹介した指針を参考に、スケーラブルな Ansible 環境を実現してみてください。

関連リンク