備忘
課題
Ansible MoleculeにてVirtualBox上のVMを利用する場合、molecule-vagrantが提供されている。
しかしサポートされる環境が少なく今後のサポートも不安が残る。
例えばfedora上のVirtualBoxはサポートされていない。
ソリューション
delegatedドライバーを使用してvagrantコマンドを実行することで制御を行う
環境
- Fedora Linux 35 (Workstation Edition)
- Molecule 3.5.2
- Ansible 2.11.6
- Vagrant 2.2.19
- VirtualBox
構成例
molecule定義テンプレートを含むロールテンプレートを作成
$ molecule init role delegated_vagrant
...
$ cd delegated_vagrant
$ tree .
.
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── molecule
│ └── default # ここにvagrant定義ファイルであるVagrantfileを追加
│ ├── INSTALL.rst
│ ├── converge.yml
│ ├── create.yml # <= vm作成ロジックを追記
│ ├── destroy.yml # <= vm削除ロジックを追記
│ ├── molecule.yml # platforms の項を Vagrantfileの定義名と合わせる
│ └── verify.yml
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
meta/main.ymlを修正
meta/main.yml
galaxy_info:
namespace: my_galaxy_namespace # 適当な英小文字、数字、アンダーバーからなる文字列で追記。指定しないとauthorが使用され、スペースが許されないとエラーとなる。
author: your name
description: your role description
company: your company (optional)
...
molecule.yml を編集
molecule/default/molecule.yml
---
dependency:
name: galaxy
driver:
name: delegated
platforms:
- name: instance1 # VagrantFile の config.vm.define の値に合わせる
- name: instance2
provisioner:
name: ansible
verifier:
name: ansible
Vagrantfile を配置
ここでは2つのvmを定義
molecule/default/Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure("2") do |config|
config.vm.box = "generic/rhel8"
config.vm.box_version = "3.0.20" # RHEL8.2
config.vm.define "instance1" do |ins| # molecule.yml における platforms 要素の name と一致させる
ins.vm.hostname = "instance1"
ins.vm.network "private_network", ip: "192.168.56.10"
ins.vm.provider "virtualbox" do |vb|
vb.memory = 2048
end
end
config.vm.define "instance2" do |ins| # molecule.yml における platforms 要素の name と一致させる
ins.vm.hostname = "instance2"
ins.vm.network "private_network", ip: "192.168.56.11"
ins.vm.provider "virtualbox" do |vb|
vb.memory = 1024
end
end
end
molecule.yml に全ての情報を記載してVagrantfileをテンプレートから作る方法もあり得る。
このVagrantfileが存在するディレクトリーで以下のようなことが可能。
$ cd molecule/default
$ ls Vagrantfile
Vagrantfile
vmを構成して起動
$ vagrant up
Bringing machine 'instance1' up with 'virtualbox' provider...
Bringing machine 'instance2' up with 'virtualbox' provider...
==> instance1: Importing base box 'generic/rhel8'...
==> instance1: Matching MAC address for NAT networking...
==> instance1: Checking if box 'generic/rhel8' version '3.0.20' is up to date...
...
vmの状態表示
$ vagrant status
Current machine states:
instance1 running (virtualbox)
instance2 running (virtualbox)
This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.
vmへのログイン
$ vagrant ssh instance1
[vagrant@instance1 ~]$ head /etc/os-release
NAME="Red Hat Enterprise Linux"
VERSION="8.2 (Ootpa)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="8.2"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Red Hat Enterprise Linux 8.2 (Ootpa)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:8.2:GA"
HOME_URL="https://www.redhat.com/"
[vagrant@instance1 ~]$ logout
Connection to 127.0.0.1 closed.
vmに対する接続情報を表示
$ vagrant ssh-config
Host instance1
HostName 127.0.0.1
User vagrant
Port 2205
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile /home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance1/virtualbox/private_key
IdentitiesOnly yes
LogLevel FATAL
Host instance2
HostName 127.0.0.1
User vagrant
Port 2206
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
PasswordAuthentication no
IdentityFile /home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance2/virtualbox/private_key
IdentitiesOnly yes
LogLevel FATAL
簡易YAML化ロジック例
パス名に空白などが入っていない前提
$ vagrant ssh-config | sed '/^$/d' | awk '$1 == "Host" {print "- "$1": "$2} $1 != "Host" {print " "$1": "$2}'
- Host: instance1
HostName: 127.0.0.1
User: vagrant
Port: 2205
UserKnownHostsFile: /dev/null
StrictHostKeyChecking: no
PasswordAuthentication: no
IdentityFile: /home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance1/virtualbox/private_key
IdentitiesOnly: yes
LogLevel: FATAL
- Host: instance2
HostName: 127.0.0.1
User: vagrant
Port: 2206
UserKnownHostsFile: /dev/null
StrictHostKeyChecking: no
PasswordAuthentication: no
IdentityFile: /home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance2/virtualbox/private_key
IdentitiesOnly: yes
LogLevel: FATAL
vmを削除
$ vagrant destroy -f
==> instance2: Forcing shutdown of VM...
==> instance2: Destroying VM and associated drives...
==> instance1: Forcing shutdown of VM...
==> instance1: Destroying VM and associated drives...
元のディレクトリーに戻る
$ cd -
vmを作成するplaybookのテンプレートにvagrant upロジックを追記
molecule/default/create.yml
---
- name: Create
hosts: localhost
connection: local
gather_facts: false
no_log: "{{ molecule_no_log }}"
tasks:
- name: vagrant up # vmを作成、起動
shell: vagrant up
- name: vagrant ssh-config # 作成vmへの接続情報をYAML化
shell: >-
vagrant ssh-config | sed '/^$/d' | awk '$1 == "Host" {print "- "$1": "$2} $1 != "Host" {print " "$1": "$2}'
register: r
- name: Populate instance config dict # 以降で作成vmへの接続情報をmoleculeに連携
loop: "{{ r.stdout|from_yaml }}"
set_fact:
instance_conf_dict:
instance: "{{ item.Host }}"
address: "{{ item.HostName }}"
user: "{{ item.User }}"
port: "{{ item.Port }}"
identity_file: "{{ item.IdentityFile }}"
register: instance_config_dict
- name: Convert instance config dict to a list
set_fact:
instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}"
- name: Dump instance config
copy:
content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}"
mode: 0600
vmを削除するplaybookのテンプレートにvagrant destroy -fロジックを追記
molecule/default/destroy.yml
---
- name: Destroy
hosts: localhost
connection: local
gather_facts: false
no_log: "{{ molecule_no_log }}"
tasks:
# Developer must implement.
- name: vagrant destroy -f
shell: vagrant destroy -f
# Mandatory configuration for Molecule to function.
- name: Populate instance config
set_fact:
instance_conf: {}
- name: Dump instance config
copy:
content: |
# Molecule managed
{{ instance_conf | to_json | from_json | to_yaml }}
dest: "{{ molecule_instance_config }}"
mode: 0600
when: server.changed | default(false) | bool
tasks/main.ymlの例
適当に編集
tasks/main.yml
---
- debug:
var: ansible_distribution
- debug:
var: ansible_distribution_version
動作確認
初期ステータス
$ molecule list
INFO Running default > list
╷ ╷ ╷ ╷ ╷
Instance Name │ Driver Name │ Provisioner Name │ Scenario Name │ Created │ Converged
╶───────────────┼─────────────┼──────────────────┼───────────────┼─────────┼───────────╴
instance1 │ delegated │ ansible │ default │ false │ false
instance2 │ delegated │ ansible │ default │ false │ false
╵ ╵ ╵ ╵ ╵
インスタンス作成
$ molecule create
...
PLAY [Create] ******************************************************************
TASK [vagrant up] **************************************************************
changed: [localhost]
TASK [vagrant ssh-config] ******************************************************
changed: [localhost]
TASK [Populate instance config dict] *******************************************
ok: [localhost] => (item={'Host': 'instance1', 'HostName': '127.0.0.1', 'User': 'vagrant', 'Port': 2205, 'UserKnownHostsFile': '/dev/null', 'StrictHostKeyChecking': False, 'PasswordAuthentication': False, 'IdentityFile': '/home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance1/virtualbox/private_key', 'IdentitiesOnly': True, 'LogLevel': 'FATAL'})
ok: [localhost] => (item={'Host': 'instance2', 'HostName': '127.0.0.1', 'User': 'vagrant', 'Port': 2206, 'UserKnownHostsFile': '/dev/null', 'StrictHostKeyChecking': False, 'PasswordAuthentication': False, 'IdentityFile': '/home/foo/repo/delegated_vagrant/molecule/default/.vagrant/machines/instance2/virtualbox/private_key', 'IdentitiesOnly': True, 'LogLevel': 'FATAL'})
TASK [Convert instance config dict to a list] **********************************
ok: [localhost]
TASK [Dump instance config] ****************************************************
changed: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
INFO Running default > prepare
WARNING Skipping, prepare playbook not configured.
立ち上げたインスタンスに構成変更playbook(converge.yml)を適用
この例ではdebugモジュールでOSの種類と番号を表示しているのみ。
$ molecule converge
...
PLAY [Converge] ****************************************************************
TASK [Gathering Facts] *********************************************************
ok: [instance2]
ok: [instance1]
TASK [Include delegated_vagrant] ***********************************************
TASK [delegated_vagrant : debug] ***********************************************
ok: [instance1] => {
"ansible_distribution": "RedHat"
}
ok: [instance2] => {
"ansible_distribution": "RedHat"
}
TASK [delegated_vagrant : debug] ***********************************************
ok: [instance1] => {
"ansible_distribution_version": "8.2"
}
ok: [instance2] => {
"ansible_distribution_version": "8.2"
}
PLAY RECAP *********************************************************************
instance1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
instance2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
moleculeが認識している状態を表示
$ molecule list
INFO Running default > list
╷ ╷ ╷ ╷ ╷
Instance Name │ Driver Name │ Provisioner Name │ Scenario Name │ Created │ Converged
╶───────────────┼─────────────┼──────────────────┼───────────────┼─────────┼───────────╴
instance1 │ delegated │ ansible │ default │ true │ true
instance2 │ delegated │ ansible │ default │ true │ true
╵ ╵ ╵ ╵ ╵
vmにログイン
$ molecule login -h instance1
INFO Running default > login
Last login: Sat Nov 20 17:56:00 2021 from 10.0.2.2
[vagrant@instance1 ~]$ hostname
instance1
[vagrant@instance1 ~]$ head -2 /etc/os-release
NAME="Red Hat Enterprise Linux"
VERSION="8.2 (Ootpa)"
[vagrant@instance1 ~]$ logout
Connection to 127.0.0.1 closed.
vmの削除
$ molecule destroy
...
PLAY [Destroy] *****************************************************************
TASK [vagrant destroy -f] ******************************************************
changed: [localhost]
TASK [Populate instance config] ************************************************
ok: [localhost]
TASK [Dump instance config] ****************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
INFO Pruning extra files from scenario ephemeral directory