自宅OpenStackのAnsible解説

Table of Contents

みなさんこんにちは。

今回は、私が自宅クラウドのOpenStackを構築するのに使っているAnsibleを題材にして、Ansibleの基本的な使い方について紹介したいと思います。 業務で使っているものとは何の関係もないので、これをこのまま本番環境で使える、みたいなものではないです。 ご注意ください。

1. Ansibleのディレクトリ構成

以下が本記事で使用するAnsibleのディレクトリ構成です。 全てのソースコードを見たい方はこちらからどうぞ。 諸事情によりprivate repoにしました。🙏

なおOpenStackにはちゃんと公式のAnsible Playbookもありますが、今回は自分で作ってみたかったため作りました。

.
├── compute.yml
├── controller.yml
├── database.yml
├── inventory.yml
├── requirements.txt
├── roles
│   ├── common
│   │   └── tasks
│   │       └── main.yml
│   ├── compute
│   │   ├── files
│   │   │   └── chrony.conf
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   ├── main.yml
│   │   │   ├── neutron.yml
│   │   │   ├── nova.yml
│   │   │   └── ntp.yml
│   │   └── templates
│   │       ├── neutron.conf.j2
│   │       ├── nova-compute.conf.j2
│   │       ├── nova.conf.j2
│   │       └── openvswitch_agent.ini.j2
│   ├── controller
│   │   ├── files
│   │   │   ├── apache2.conf
│   │   │   ├── apache2_openstack_dashboard.conf
│   │   │   ├── dhcp_agent.ini
│   │   │   ├── horizon_local_settings.py
│   │   │   └── ml2_conf.ini
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   ├── cinder.yml
│   │   │   ├── etcd.yml
│   │   │   ├── glance.yml
│   │   │   ├── horizon.yml
│   │   │   ├── keystone.yml
│   │   │   ├── main.yml
│   │   │   ├── memcached.yml
│   │   │   ├── neutron.yml
│   │   │   ├── nova.yml
│   │   │   ├── ntp.yml
│   │   │   ├── placement.yml
│   │   │   └── rabbitmq.yml
│   │   └── templates
│   │       ├── chrony.conf.j2
│   │       ├── cinder.conf.j2
│   │       ├── etcd.j2
│   │       ├── glance-api.conf.j2
│   │       ├── keystone.conf.j2
│   │       ├── memcached.conf.j2
│   │       ├── metadata_agent.ini.j2
│   │       ├── neutron.conf.j2
│   │       ├── nova.conf.j2
│   │       ├── openvswitch_agent.ini.j2
│   │       └── placement.conf.j2
│   ├── database
│   │   ├── handlers
│   │   │   └── main.yml
│   │   ├── tasks
│   │   │   ├── create_user.yml
│   │   │   ├── main.yml
│   │   │   └── secure_installation.yml
│   │   └── templates
│   │       └── 99-openstack.cnf.j2
│   └── openstack
│       └── tasks
│           └── main.yml
└── vars
    ├── credentials.yml
    └── vars.yml

エントリポイントとなるplaybook

  • database.yml: データベースノードの構築
  • controller.yml: コントローラノードの構築
  • compute.yml: コンピュートノードの構築

インベントリファイル

  • inventory.yml: 対象ノードのIPアドレスやパラメータを定義

各コンポーネントのplaybook

  • roles/common: 全ノード共通設定のplaybook
  • roles/openstack: OpenStackノード共通設定のplaybook
  • roles/database: データベースノードのplaybook
  • roles/controller: コントローラノードのplaybook
  • roles/compute: コンピュートノードのplaybook

変数ファイル

  • vars/vars.yml: 変数ファイル
  • vars/credentials.yml: センシティブな変数ファイル

2. インベントリファイルの解説

Ansibleでは、インベントリファイルに構築対象ノードのIPアドレスやパラメータを定義します。 今回用いたinventory.ymlファイルの中身は以下の様になっています。

all:
  children:
    databases:
    controllers:
    computes:
  vars:
    ansible_user: admin

openstack:
  children:
    controllers:
    computes:

databases:
  hosts:
    master01:
      ansible_host: 192.168.1.101

controllers:
  hosts:
    master01:
      ansible_host: 192.168.1.101
      provider_interface: eth0

computes:
  hosts:
    worker01:
      ansible_host: 192.168.1.102
      provider_interface: enx68e1dcc5f7fa
    worker02:
      ansible_host: 192.168.1.103
      provider_interface: enp5s0f0
  • all
    • 全てのノードを含むグループ
    • vars.ansible_user
      • Ansibleでssh接続するユーザー名を一括で設定
  • openstack
    • OpenStack関連のノードを含むグループ
  • databases
    • データベースノードを含むグループ
    • 今回はcontrollerと同居する構成
  • controllers
    • OpenStackの主要コンポーネントを動作させるコントローラノードを含むグループ
    • master01の単一コントローラ構成
    • provider_interface 変数を定義してPlaybook内で使用
  • computes
    • VMを起動するコンピュートノードを含むグループ
    • worker01, worker02の2台のハイパーバイザを使用
    • provider_interface 変数を定義してPlaybook内で使用

3. 各コンポーネントの構築手順

3.1 データベースノード

データベースノードを構築するPlaybookのエントリポイントのdatebase.ymlは以下の様になっています。

- hosts: databases
 become: yes
 roles:
   - database
 vars_files:
   - ./vars/vars.yml
   - ./vars/credentials.yml

ここで database roleを指定することで、roles/database/tasks/main.yml が実行されます。

roles/database/tasks/main.yml では、以下のようなタスクを実行します。

---
- name: install_database
  ansible.builtin.apt:
    name: mariadb-server

- name: start_database
  ansible.builtin.service:
    name: mariadb
    state: started
    enabled: true 

- name: install_python_mysql_package
  ansible.builtin.apt:
    name: python3-pymysql

- name: put_config
  ansible.builtin.template:
    src: ./99-openstack.cnf.j2
    dest:  /etc/mysql/mariadb.conf.d/99-openstack.cnf 
    owner: root
    group: root
    mode: '644'
  notify: restart_database

- name: secure_installation
  ansible.builtin.include_tasks:
    file: secure_installation.yml

- name: create_viewer_user
  community.mysql.mysql_user:
    login_user: root
    login_unix_socket: /run/mysqld/mysqld.sock
    name: viewer
    host: "{{ item }}"
    password: "{{ database_viewer_pass }}"
    priv: '*.*:SELECT'
  loop:
    - localhost
    - '%'
  run_once: true

- name: create_openstack_dbs
  community.mysql.mysql_db:
    name: "{{ item }}"
    login_unix_socket: /run/mysqld/mysqld.sock
  loop:
    - keystone
    - glance
    - placement
    - nova_api
    - nova
    - nova_cell0
    - neutron
    - cinder
  run_once: true

- name: create_openstack_db_users
  ansible.builtin.include_tasks:
    file: create_user.yml
  with_items:
    - { user: 'keystone', dbs: ['keystone'], pass: "{{ database_keystone_pass }}" }
    - { user: 'glance', dbs: ['glance'], pass: "{{ database_glance_pass }}" }
    - { user: 'placement', dbs: ['placement'], pass: "{{ database_placement_pass }}" }
    - { user: 'nova', dbs: ['nova_api', 'nova', 'nova_cell0'], pass: "{{ database_nova_pass }}" }
    - { user: 'neutron', dbs: ['neutron'], pass: "{{ database_neutron_pass }}" }
    - { user: 'cinder', dbs: ['cinder'], pass: "{{ database_cinder_pass }}" }
  loop_control:
    loop_var: outer_item
  run_once: true

1つずつ説明していきましょう。

以下は ansible.builtin.apt モジュールを使用して、mariadb-serverのaptパッケージをインストールしています。 sudo apt intsall mariadb-server みたいなもんですね。 以降、同じ様なパッケージのインストールについては説明を省略します。

- name: install_database
  ansible.builtin.apt:
    name: mariadb-server

以下は ansible.builtint.service モジュールを使用して、systemdのmariadbi.serviceを起動&登録しています。 sudo systemctl enabled --now mariadb に相当します。 こちらも同じ様なタスクが今後も出てくるため、以降は説明を省略します。

- name: start_database
  ansible.builtin.service:
    name: mariadb
    state: started
    enabled: true 

以下は ansible.builtin.template モジュールを使用して、mariadbの設定ファイルを投入しています。

- name: put_config
  ansible.builtin.template:
    src: ./99-openstack.cnf.j2
    dest:  /etc/mysql/mariadb.conf.d/99-openstack.cnf 
    owner: root
    group: root
    mode: '644'
  notify: restart_database

テンプレートの 99-openstack.cnf.j2roles/database/templates/99-openstack.cnf.j2 に格納されています。 {{ ansible_host }} の様に {{ }} で囲まれた変数は、 vars/vars.yml に定義された変数を参照し、templateモジュールによって埋め込むことができます。

[mysqld]
bind-address = {{ ansible_host }}

default-storage-engine = innodb
innodb_file_per_table = on
max_connections = 4096
collation-server = utf8_general_ci
character-set-server = utf8

また、上記タスクでは notify: restart_database という記述により、もしこのタスクが実行されたならば全タスクの完了後に restart_database というハンドラを呼び出します。 ハンドラの記述は roles/database/handlers/main.yml にあります。

---
- name: restart_database
  ansible.builtin.service:
    name: mariadb
    state: restarted

上記の仕組みにより、設定ファイルが変更された時のみその設定を反映するためにmariadbを再起動するというフローを実現することができます。

続いて以下は ansible.builtin.include_tasks モジュールを使用して、別ファイルのタスクをインクルードし、mariadbのsecure_installation相当の処理を実行しています。

- name: secure_installation
  ansible.builtin.include_tasks:
    file: secure_installation.yml

インクルードされるタスクファイルは roles/database/tasks/secure_installation.yml に格納されています。 secure_installation タスクでは、community.mysql モジュールを使用して、rootユーザーのパスワード設定や不要なユーザーの削除、不要なデータベースの削除を行います。 database_root_passvars/credentials.yml に定義されたセンシティブな変数です。以降も同じ様な変数が度々登場します(パスワードとか)。

---
- name: set_root_pass
  community.mysql.mysql_user:
      login_user: root
      login_unix_socket: /run/mysqld/mysqld.sock
      name: root
      host_all: true
      password: "{{ database_root_pass }}"
- name: disable_remote_root_login
  community.mysql.mysql_user:
      login_user: root
      login_unix_socket: /run/mysqld/mysqld.sock
      name: root
      host: "{{ ansible_fqdn | lower }}"
      state: absent
- name: remove_anonymous_users
  community.mysql.mysql_user:
      login_user: root
      login_unix_socket: /run/mysqld/mysqld.sock
      name: ''
      host_all: true
      state: absent
- name: remove_test_database
  mysql_db: 
      login_user: root
      login_unix_socket: /run/mysqld/mysqld.sock
      db: test
      state: absent

以下は community.mysql.mysql_user モジュールを使用して、運用時に使うread onlyのviewerユーザーを作成し、SELECT権限を付与しています。 ここでは loop ディレクティブを使用して、localhostと全てのホスト(%(に対して同じ設定を適用しています。 この様にすることで同じ様なタスクを重複した記述なく実行することができます。

- name: create_viewer_user
  community.mysql.mysql_user:
    login_user: root
    login_unix_socket: /run/mysqld/mysqld.sock
    name: viewer
    host: "{{ item }}"
    password: "{{ database_viewer_pass }}"
    priv: '*.*:SELECT'
  loop:
    - localhost
    - '%'
  run_once: true

以下は community.mysql.mysql_db モジュールを使用して、OpenStackの各コンポーネント用のデータベースを作成しています。

- name: create_openstack_dbs
  community.mysql.mysql_db:
    name: "{{ item }}"
    login_unix_socket: /run/mysqld/mysqld.sock
  loop:
    - keystone
    - glance
    - placement
    - nova_api
    - nova
    - nova_cell0
    - neutron
    - cinder
  run_once: true

最後に以下は、viewerユーザと同じように、OpenStackの各コンポーネント用のユーザを作成しています。

- name: create_openstack_db_users
  ansible.builtin.include_tasks:
    file: create_user.yml
  with_items:
    - { user: 'keystone', dbs: ['keystone'], pass: "{{ database_keystone_pass }}" }
    - { user: 'glance', dbs: ['glance'], pass: "{{ database_glance_pass }}" }
    - { user: 'placement', dbs: ['placement'], pass: "{{ database_placement_pass }}" }
    - { user: 'nova', dbs: ['nova_api', 'nova', 'nova_cell0'], pass: "{{ database_nova_pass }}" }
    - { user: 'neutron', dbs: ['neutron'], pass: "{{ database_neutron_pass }}" }
    - { user: 'cinder', dbs: ['cinder'], pass: "{{ database_cinder_pass }}" }
  loop_control:
    loop_var: outer_item
  run_once: true

ここでインクルードしている create_user.yml は以下の様になっています。

---
- name: create_user
  community.mysql.mysql_user:
    login_user: root
    login_unix_socket: /run/mysqld/mysqld.sock
    name: "{{ outer_item['user'] }}"
    host: "{{ item }}"
    password: "{{ outer_item['pass'] }}"
    priv: "{{ outer_item['dbs'] | map('regex_replace', '(.+)', '\\1.*:ALL') | join ('/') }}"
  loop:
    - localhost
    - "%"

タスクをインクルードするときに loop_control.loop_var: outer_item とすることで、with_items で渡した変数をインクルードされるタスク内で outer_item として参照することができます。

3.2 コントローラノード

OpenStackのコントローラノードを構築するPlaybookのエントリポイントのcontroller.ymlは以下の様になっています。 コントローラノードには、common, openstack, controllerの3つのroleを指定し、全ノード共通の設定、OpenStack用の設定、そしてOpenStackのコントローラノード構築の設定を行います。

- hosts: controllers
  become: yes
  roles:
    - common
    - openstack
    - controller
  vars_files:
    - ./vars/vars.yml
    - ./vars/credentials.yml
  environment:
    OS_PROJECT_DOMAIN_NAME: Default
    OS_USER_DOMAIN_NAME: Default
    OS_PROJECT_NAME: admin
    OS_USERNAME: admin
    OS_PASSWORD: "{{ openstack_admin_pass }}"
    OS_AUTH_URL: http://controller:5000/v3
    OS_IDENTITY_API_VERSION: 3
    OS_IMAGE_API_VERSION: 2

OpenStack用の設定をするタスク roles/openstack/tasks/main.yml は以下の様になっています。

---
- name: install_ubuntu_cloud_keyring
  ansible.builtin.apt:
    name: ubuntu-cloud-keyring

- name: add_cloud_archive_repo
  ansible.builtin.apt_repository:
    repo: deb http://ubuntu-cloud.archive.canonical.com/ubuntu {{ ansible_distribution_release }}-updates/dalmatian main

- name: install_packages
  ansible.builtin.apt:
    name: "{{ item }}"
  loop:
    - software-properties-common
    - cpu-checker
    - bridge-utils
    - vlan
    - libvirt-clients
    - zip
    - python3-openstackclient

- name: load_kernel_modules
  community.general.modprobe:
    name: "{{ item.name }}"
    params: "{{ item.params | default('') }}"
    persistent: 'present'
  loop:
    - { 'name': 'bridge'  }
    - { 'name': 'br_netfilter'  }
    - { 'name': '8021q'  }
    - { 'name': 'vhost_net'  }

- name: disable_iptables_for_bridge
  ansible.posix.sysctl:
    name: "{{ item }}"
    value: '0'
  loop:
    - net.bridge.bridge-nf-call-arptables
    - net.bridge.bridge-nf-call-ip6tables
    - net.bridge.bridge-nf-call-iptables

以下は ansible.builtin.apt, ansible.builtin.apt_repository モジュールを使用して、ubuntu-cloud-keyringパッケージをインストールし、OpenStackのリポジトリを追加しています。 OpenStack公式のInstallation GuideにはUbuntu Nobleでのリポジトリの追加方法が書いていなかったため、Server Worldの記事 を参考にさせていただきました。ありがとうございます。

- name: install_ubuntu_cloud_keyring
  ansible.builtin.apt:
    name: ubuntu-cloud-keyring

- name: add_cloud_archive_repo
  ansible.builtin.apt_repository:
    repo: deb http://ubuntu-cloud.archive.canonical.com/ubuntu {{ ansible_distribution_release }}-updates/dalmatian main

以下は community.general.modprobe モジュールを使用して、OpenStackを動かすのに必要なカーネルモジュールをロードしています。

- name: load_kernel_modules
  community.general.modprobe:
    name: "{{ item.name }}"
    params: "{{ item.params | default('') }}"
    persistent: 'present'
  loop:
    - { 'name': 'bridge'  }
    - { 'name': 'br_netfilter'  }
    - { 'name': '8021q'  }
    - { 'name': 'vhost_net'  }

以下は ansible.posix.sysctl モジュールを使用して、Installation Guideに書いてある通りにiptables周りの設定を無効化しています。

- name: disable_iptables_for_bridge
  ansible.posix.sysctl:
    name: "{{ item }}"
    value: '0'
  loop:
    - net.bridge.bridge-nf-call-arptables
    - net.bridge.bridge-nf-call-ip6tables
    - net.bridge.bridge-nf-call-iptables

以下は roles/controller/tasks/main.yml に記述された、コントローラノード構築用のタスクの一覧です。

---
- name: setup_ntp
  ansible.builtin.include_tasks:
    file: ntp.yml

- name: setup_rabbitmq
  ansible.builtin.include_tasks:
    file: rabbitmq.yml

- name: setup_memcached
  ansible.builtin.include_tasks:
    file: memcached.yml

- name: setup_etcd
  ansible.builtin.include_tasks:
    file: etcd.yml

- name: setup_keystone
  ansible.builtin.include_tasks:
    file: keystone.yml

- name: setup_glance
  ansible.builtin.include_tasks:
    file: glance.yml

- name: setup_placement
  ansible.builtin.include_tasks:
    file: placement.yml

- name: setup_nova
  ansible.builtin.include_tasks:
    file: nova.yml

- name: setup_neutron
  ansible.builtin.include_tasks:
    file: neutron.yml

- name: setup_horizon
  ansible.builtin.include_tasks:
    file: horizon.yml

ntp

setup_ntpタスクの内容は以下の様になっており、chronyのインストールと設定ファイルの配置を行なっています。

---
- name: install_chrony
  ansible.builtin.apt:
    name: chrony

- name: start_chrony
  ansible.builtin.service:
    name: chrony
    state: started
    enabled: true

- name: put_chrony_config
  ansible.builtin.template:
    src: ./chrony.conf.j2
    dest:  /etc/chrony/chrony.conf
    owner: root
    group: root
    mode: '644'
  notify: restart_chrony

rabbitmq

setup_rabbitmqタスクの内容は以下の様になっており、rabbitmq-serverのインストールと設定を行なっています。 ここでは community.rabbitmq.rabbitmq_user モジュールを使用して、OpenStack用のユーザを作成しています。 この様に既存のモジュールを使用して楽に設定をすることができるのがAnsibleの強みです。

---
- name: install_rabbitmq
  ansible.builtin.apt:
    name: rabbitmq-server

- name: start_rabbitmq
  ansible.builtin.service:
    name: rabbitmq-server
    state: started
    enabled: true

- name: create_openstack_user
  community.rabbitmq.rabbitmq_user:
    user: openstack
    password: "{{ rabbitmq_pass }}"
    configure_priv: .*
    read_priv: .*
    write_priv: .*
  run_once: true

memcached

setup_memcachedタスクの内容は以下の様になっており、memcachedのインストールと設定を行なっています。 なんか同じ様なのばっかですね、、、

---
- name: install_memcached
  ansible.builtin.apt:
    name: memcached

- name: install_python_memcached_package
  ansible.builtin.apt:
    name: python3-memcache

- name: start_memcached
  ansible.builtin.service:
    name: memcached
    state: started
    enabled: true

- name: put_memcached_config
  ansible.builtin.template:
    src: ./memcached.conf.j2
    dest:  /etc/memcached.conf
    owner: root
    group: root
    mode: '644'
  notify: restart_memcached

etcd

setup_etcdタスクの内容は以下の様になっており、etcdのインストールと設定を行なっています。

---
- name: install_etcd
  ansible.builtin.apt:
    name: etcd-server

- name: start_etcd
  ansible.builtin.service:
    name: etcd
    state: started
    enabled: true

- name: put_etcd_config
  ansible.builtin.template:
    src: ./etcd.j2
    dest:  /etc/default/etcd
    owner: root
    group: root
    mode: '644'
  notify: restart_etcd

Keystone

さてここからはOpenStackコンポーネントの設定を行っていきます。 setup_keystoneタスクの内容は以下の様になっており、Keystoneのインストールと設定を行なっています。

---
- name: install_keystone
  ansible.builtin.apt:
    name: keystone

- name: put_keystone_config
  ansible.builtin.template:
    src: ./keystone.conf.j2
    dest:  /etc/keystone/keystone.conf
    owner: root
    group: root
    mode: '644'

- name: check_db_sync
  ansible.builtin.command:
    cmd: keystone-manage db_sync --check
  register: check_db_sync
  failed_when: check_db_sync.rc == 1
  changed_when: check_db_sync.rc != 0
  run_once: true

- name: run_db_sync
  ansible.builtin.command:
    cmd: su -s /bin/sh -c "keystone-manage db_sync" keystone
  when: check_db_sync.changed
  run_once: true

- name: run_fernet_setup
  ansible.builtin.command:
    cmd: keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone
  when: check_db_sync.changed
  run_once: true

- name: run_credential_setup
  ansible.builtin.command:
    cmd: keystone-manage credential_setup --keystone-user keystone --keystone-group keystone
  when: check_db_sync.changed
  run_once: true

- name: run_bootstrap
  ansible.builtin.command:
    cmd: >
      keystone-manage bootstrap --bootstrap-password "{{ openstack_admin_pass }}"
        --bootstrap-admin-url http://controller:5000/v3/
        --bootstrap-internal-url http://controller:5000/v3/
        --bootstrap-public-url http://controller:5000/v3/
        --bootstrap-region-id RegionOne
  changed_when: false
  run_once: true

- name: put_apache2_config
  ansible.builtin.copy:
    src: ./apache2.conf
    dest:  /etc/apache2/apache2.conf
    owner: root
    group: root
    mode: '644'
  notify: restart_apache2

以下は ansible.builtin.command モジュールを使用して、Keystoneのデータベースの構築を行います。 shellでコマンドを実行する様なイメージです。

register によって、このタスクの結果を後から参照することができます。 また、failed_whenchanged_when によってこのタスクが失敗した、実行されたとみなされる条件を定義しています。 ansible.builtin.command モジュールはデフォルトではコマンドのステータスコードが0かどうかしか確認しないため、実際に実行するコマンドによってカスタマイズすることが必要です。 また、run_once を定義することによって、もしコントローラノードが複数あった際にもこのタスクは一回しか実行されません。

- name: check_db_sync
  ansible.builtin.command:
    cmd: keystone-manage db_sync --check
  register: check_db_sync
  failed_when: check_db_sync.rc == 1
  changed_when: check_db_sync.rc != 0
  run_once: true

続く以下のタスクでは、先ほどのタスクの結果を参照し、先ほどのタスクが実行されていた場合のみ実行する様に制御しています。 これは when ディレクティブで check_db_sync.changed を参照することで実現しています。

- name: run_fernet_setup
  ansible.builtin.command:
    cmd: keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone
  when: check_db_sync.changed
  run_once: true

- name: run_credential_setup
  ansible.builtin.command:
    cmd: keystone-manage credential_setup --keystone-user keystone --keystone-group keystone
  when: check_db_sync.changed
  run_once: true

Glance

以下は OpenStackのVMディスクイメージ管理コンポーネントのGlanceを構築します。

---
- name: create_glacne_user
  openstack.cloud.identity_user:
    name: glance
    password: "{{ openstack_glance_pass }}"
    domain: default

- name: add_admin_role_to_glance
  openstack.cloud.role_assignment:
    user: glance
    role: admin
    project: service

- name: add_glance_service
  openstack.cloud.catalog_service:
   name: glance
   type: image
   description: OpenStack Image

- name: create_endpoints
  openstack.cloud.endpoint:
     service: glance
     endpoint_interface: "{{ item }}"
     url: http://controller:9292
     region: RegionOne
  loop:
    - public
    - internal
    - admin
  register: endpoints

- name: install_glance
  ansible.builtin.apt:
    name: glance

- name: put_glance_config
  ansible.builtin.template:
    src: ./glance-api.conf.j2
    dest:  /etc/glance/glance-api.conf
    owner: root
    group: root
    mode: '644'
  vars:
    glance_public_endpoint: "{{ endpoints['results'][0]['endpoint']['id'] }}"
  notify: restart_glance_api

- name: add_reader_role_to_glance
  openstack.cloud.role_assignment:
    user: glance
    domain: Default
    system: all
    role: reader

- name: run_db_sync
  ansible.builtin.command:
    cmd: su -s /bin/sh -c "glance-manage db_sync" glance
  changed_when: false
  run_once: true

ここでは openstack.cloud モジュールを使用し、OpenStackのサービスユーザ作成や権限の設定とその他諸々をします。 ここでもエコシステムの恩恵に預かり楽をすることができます。素晴らしいですね。

- name: create_glacne_user
  openstack.cloud.identity_user:
    name: glance
    password: "{{ openstack_glance_pass }}"
    domain: default

- name: add_admin_role_to_glance
  openstack.cloud.role_assignment:
    user: glance
    role: admin
    project: service

- name: add_glance_service
  openstack.cloud.catalog_service:
   name: glance
   type: image
   description: OpenStack Image

- name: create_endpoints
  openstack.cloud.endpoint:
     service: glance
     endpoint_interface: "{{ item }}"
     url: http://controller:9292
     region: RegionOne
  loop:
    - public
    - internal
    - admin
  register: endpoints

Placement

以下は OpenStackのリソース管理コンポーネントのPlacementを構築します。 Glanceとほとんど同じ様なタスクで特に説明することもないので詳細は割愛します。

---
- name: create_placement_user
  openstack.cloud.identity_user:
    name: placement
    password: "{{ openstack_placement_pass }}"
    domain: default

- name: add_admin_role_to_placement
  openstack.cloud.role_assignment:
    user: placement
    role: admin
    project: service

- name: add_placement_service
  openstack.cloud.catalog_service:
   name: placement
   type: placement
   description: Placement API

- name: create_endpoints
  openstack.cloud.endpoint:
     service: placement
     endpoint_interface: "{{ item }}"
     url: http://controller:8778
     region: RegionOne
  loop:
    - public
    - internal
    - admin

- name: install_placement
  ansible.builtin.apt:
    name: placement-api

- name: put_placement_config
  ansible.builtin.template:
    src: ./placement.conf.j2
    dest:  /etc/placement/placement.conf
    owner: root
    group: root
    mode: '644'
  notify: restart_apache2

- name: run_db_sync
  ansible.builtin.command:
    cmd: su -s /bin/sh -c "placement-manage db sync" placement
  changed_when: false
  run_once: true

Nova

以下は OpenStackでの仮想マシン管理コンポーネントのNovaを構築します。

---
- name: create_nova_user
  openstack.cloud.identity_user:
    name: nova
    password: "{{ openstack_nova_pass }}"
    domain: default

- name: add_admin_role_to_nova
  openstack.cloud.role_assignment:
    user: nova
    role: admin
    project: service

- name: add_nova_service
  openstack.cloud.catalog_service:
   name: nova
   type: compute
   description: OpenStack Compute

- name: create_endpoints
  openstack.cloud.endpoint:
     service: nova
     endpoint_interface: "{{ item }}"
     url: http://controller:8774/v2.1
     region: RegionOne
  loop:
    - public
    - internal
    - admin

- name: install_nova_packages
  ansible.builtin.apt:
    name: "{{ item }}"
  loop:
    - nova-api
    - nova-conductor
    - nova-novncproxy
    - nova-scheduler

- name: put_nova_config
  ansible.builtin.template:
    src: ./nova.conf.j2
    dest:  /etc/nova/nova.conf
    owner: root
    group: root
    mode: '644'
  notify: restart_nova

- name: run_api_db_sync
  ansible.builtin.command:
    cmd: su -s /bin/sh -c "nova-manage api_db sync" nova
  changed_when: false
  run_once: true

- name: list_nova_cells
  ansible.builtin.command:
    cmd: su -s /bin/sh -c "nova-manage cell_v2 list_cells" nova
  changed_when: false
  register: cell_list

- name: setl_cell_facts
  set_fact:
    cell0_record: '{{ cell_list.stdout_lines | select("regex", "[0-]{36}") }}'
    cell1_record: '{{ cell_list.stdout_lines | select("regex", " cell1 ") }}'

- name: run_map_cell0
  ansible.builtin.command: 
    cmd: su -s /bin/sh -c "nova-manage cell_v2 map_cell0" nova
  when: not cell0_record

- name: run_create_cell1
  ansible.builtin.command: 
    cmd: su -s /bin/sh -c "nova-manage cell_v2 create_cell --name=cell1 --verbose" nova
  when: not cell1_record

- name: run_db_sync
  ansible.builtin.command:
    cmd: su -s /bin/sh -c "nova-manage db sync" nova
  changed_when: false

特筆すべきことでいうとこちらのset_factを利用したタスク実行の制御ですかね。 list_nova_cells タスクの出力に対して正規表現で一致したものがあれば、すでに作成したいリソースが作成されていると判断して後続のタスクをスキップする様にしています。 ここら辺の処理は悔しいですが本家のopenstack-ansibleをめっちゃ参考にしました。

- name: list_nova_cells
  ansible.builtin.command:
    cmd: su -s /bin/sh -c "nova-manage cell_v2 list_cells" nova
  changed_when: false
  register: cell_list

- name: setl_cell_facts
  set_fact:
    cell0_record: '{{ cell_list.stdout_lines | select("regex", "[0-]{36}") }}'
    cell1_record: '{{ cell_list.stdout_lines | select("regex", " cell1 ") }}'

- name: run_map_cell0
  ansible.builtin.command: 
    cmd: su -s /bin/sh -c "nova-manage cell_v2 map_cell0" nova
  when: not cell0_record

- name: run_create_cell1
  ansible.builtin.command: 
    cmd: su -s /bin/sh -c "nova-manage cell_v2 create_cell --name=cell1 --verbose" nova
  when: not cell1_record

Neutron

以下は OpenStackのネットワーク管理コンポーネントのNeutronを構築します。

---
- name: create_neutron_user
  openstack.cloud.identity_user:
    name: neutron
    password: "{{ openstack_neutron_pass }}"
    domain: default

- name: add_admin_role_to_neutron
  openstack.cloud.role_assignment:
    user: neutron
    role: admin
    project: service

- name: add_neutron_service
  openstack.cloud.catalog_service:
   name: neutron
   type: network
   description: OpenStack Networking

- name: create_endpoints
  openstack.cloud.endpoint:
     service: neutron
     endpoint_interface: "{{ item }}"
     url: http://controller:9696
     region: RegionOne
  loop:
    - public
    - internal
    - admin

- name: install_neutron_packages
  ansible.builtin.apt:
    name: "{{ item }}"
  loop:
    - neutron-server
    - neutron-plugin-ml2
    - neutron-openvswitch-agent
    - neutron-dhcp-agent
    - neutron-metadata-agent

- name: create_br_provider
  openvswitch.openvswitch.openvswitch_bridge:
    bridge: br-provider
    fail_mode: secure

- name: create_br_provider_port
  openvswitch.openvswitch.openvswitch_port:
    bridge: br-provider
    port: "{{ provider_interface }}"

- name: put_neutron_config_templates
  ansible.builtin.template:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
    owner: root
    group: root
    mode: '644'
  notify: restart_neutron
  loop:
    - { src: './neutron.conf.j2', dest: '/etc/neutron/neutron.conf' }
    - { src: './openvswitch_agent.ini.j2', dest: '/etc/neutron/plugins/ml2/openvswitch_agent.ini' }
    - { src: './metadata_agent.ini.j2', dest: '/etc/neutron/metadata_agent.ini' }

- name: put_neutron_config_files
  ansible.builtin.copy:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
    owner: root
    group: root
    mode: '644'
  notify: restart_neutron
  loop:
    - { src: './ml2_conf.ini', dest: '/etc/neutron/plugins/ml2/ml2_conf.ini' }
    - { src: './dhcp_agent.ini', dest: '/etc/neutron/dhcp_agent.ini' }

- name: populate_neutron_db
  ansible.builtin.command:
    cmd: >
      su -s /bin/sh -c "neutron-db-manage \
        --config-file /etc/neutron/neutron.conf \
        --config-file /etc/neutron/plugins/ml2/ml2_conf.ini \
        upgrade head" neutron
  changed_when: false
  run_once: true

Neutronの設定で特筆する所は、openvswitch.openvswitch モジュールを使ってノードのovsの設定をいじっている所です。

以下は openvswitch.openvswitch.openvswitch_bridge モジュールを使用して、br-providerという外部ネットワーク用のインターフェースにつながるbridgeを作成しています。

- name: create_br_provider
  openvswitch.openvswitch.openvswitch_bridge:
    bridge: br-provider
    fail_mode: secure

以下は openvswitch.openvswitch.openvswitch_port モジュールを使用して、br-providerに外部ネットワーク用のインターフェースを接続しています。 ここで接続するインターフェースはノードごとに異なるので、インベントリファイルで指定したインターフェース名を使用して指定します。 ノードのネットワーク設定でMacアドレス指定でインターフェース名を書き換えて、全ノードでインターフェース名を揃えるのでもいいかもしれないですね。

- name: create_br_provider_port
  openvswitch.openvswitch.openvswitch_port:
    bridge: br-provider
    port: "{{ provider_interface }}"

Horizon

以下は OpenStackのダッシュボードコンポーネントのHorizonを構築します。 こちらは特に目新しいものはなく、パッケージをインストールして設定ファイルを置き、サービスを再起動する、といった感じです。

---
- name: install_horizon
  ansible.builtin.apt:
    name: openstack-dashboard

- name: put_horizon_config
  ansible.builtin.copy:
    src: ./horizon_local_settings.py
    dest: /etc/openstack-dashboard/local_settings.py 
    owner: root
    group: root
    mode: '644'
  notify: reload_apache2

- name: put_apache2_config
  ansible.builtin.copy:
    src: ./apache2_openstack_dashboard.conf
    dest: /etc/apache2/conf-available/openstack-dashboard.conf 
    owner: root
    group: root
    mode: '644'
  notify: reload_apache2

3.3 コンピュートノード

OpenStackのコンピュートノードを構築するPlaybookのエントリポイントのcompute.ymlは以下の様になっています。 コンピュートノードには、common, openstack, computeの3つのroleを指定し、全ノード共通の設定、OpenStack用の設定、そしてOpenStackのコンピュートノード構築の設定を行います。

- hosts: computes
  become: yes
  roles:
    - common
    - openstack
    - compute
  vars_files:
    - ./vars/vars.yml
    - ./vars/credentials.yml
  environment:
    OS_PROJECT_DOMAIN_NAME: Default
    OS_USER_DOMAIN_NAME: Default
    OS_PROJECT_NAME: admin
    OS_USERNAME: admin
    OS_PASSWORD: "{{ openstack_admin_pass }}"
    OS_AUTH_URL: http://controller:5000/v3
    OS_IDENTITY_API_VERSION: 3
    OS_IMAGE_API_VERSION: 

以下は roles/compute/tasks/main.yml に記述されたタスクの一覧です。 ntpについてはコントローラノードと大差ないため、ここでは説明を省略します。

---
- name: setup_ntp
  ansible.builtin.include_tasks:
    file: ntp.yml

- name: setup_nova
  ansible.builtin.include_tasks:
    file: nova.yml

- name: setup_neutron
  ansible.builtin.include_tasks:
    file: neutron.yml

Nova

以下は コンピュートノード上で動作し実際に仮想マシンの作成等を行うnova-computeを構築します。 特筆すべきところとして、ここでも既存の community.libvirt モジュールを使用して、libvirtの設定を行っています。 libvirtはインストールするとデフォルトで default という名前のネットワークを作成しますが、OpenStackではこのネットワークを使わないため、変な競合が起こらないようにこのネットワークを削除しています。

---
- name: install_packages
  ansible.builtin.apt:
    name: "{{ item }}"
  loop:
    - nova-compute
    - seabios

- name: disable_libvirt_default_network
  community.libvirt.virt_net:
    name: default
    state: absent
    autostart: false

- name: put_nova_config
  ansible.builtin.template:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
    owner: root
    group: root
    mode: '644'
  notify: restart_nova
  loop:
    - { src: './nova.conf.j2', dest: '/etc/nova/nova.conf' }
    - { src: './nova-compute.conf.j2', dest: '/etc/nova/nova-compute.conf' }

Neutron

以下は コンピュートノード上で動作し、実際にovsをいじって仮想マシンのネットワーク設定を行うneutron-openvswitch-agentを構築します。 コンピュートノードでもコントローラノードと同じように、openvswitch.openvswitch モジュールを使用してovsのbridgeの作成と、bridgeへのインターフェースの接続を行なっています。

---
- name: install_neutron_ovs_agent
  ansible.builtin.apt:
    name: neutron-openvswitch-agent

- name: create_br_provider
  openvswitch.openvswitch.openvswitch_bridge:
    bridge: br-provider
    fail_mode: secure

- name: create_br_provider_port
  openvswitch.openvswitch.openvswitch_port:
    bridge: br-provider
    port: "{{ provider_interface }}"

- name: put_neutron_config_templates
  ansible.builtin.template:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
    owner: root
    group: root
    mode: '644'
  notify: restart_neutron
  loop:
    - { src: './neutron.conf.j2', dest: '/etc/neutron/neutron.conf' }
    - { src: './openvswitch_agent.ini.j2', dest: '/etc/neutron/plugins/ml2/openvswitch_agent.ini' }

4. まとめ

この記事では、Ansibleを使用してOpenStack環境を構築する方法を解説しました。 OpenStackはコンポーネントや必要な設定が多く、毎回手動で構築するのはかなり大変です。 Ansibleの様なツールを利用することで繰り返し行う作業を自動化し、効率的にOpenStack環境を構築することができます。

最後まで読んだいただき、ありがとうございました。

5. 参考資料