Ansible と Heat による高度なオーケストレーション

heat を使うと、「システム設計書」をパラメーター化して、OpenStack上に何度でも再現できるようになります。さらに「手順書」をパラメータ化するAnsibleを組み合わせることで、より高度な自動化が実現できるようになります。

準備

ansible を使えるように準備します。今回の環境では既にインストール済みです。

In [ ]:
workon ansible
In [ ]:
source ~/openrc
In [1]:
ansible --version
ansible 2.3.0.0
  config file = 
  configured module search path = Default w/o overrides
  python version = 2.7.5 (default, Nov  6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
In [2]:
nova list
+----+------+--------+------------+-------------+----------+
| ID | Name | Status | Task State | Power State | Networks |
+----+------+--------+------------+-------------+----------+
+----+------+--------+------------+-------------+----------+
In [3]:
heat stack-list
WARNING (shell) "heat stack-list" is deprecated, please use "openstack stack list" instead
+----+------------+--------------+---------------+--------------+
| id | stack_name | stack_status | creation_time | updated_time |
+----+------------+--------------+---------------+--------------+
+----+------------+--------------+---------------+--------------+

ファイルの取得

Heat テンプレート、 Ansible Playbook、OpenStackの認証ファイルを取得しています。

In [4]:
wget https://raw.githubusercontent.com/irixjp/josug-34th-materials/master/simple-system.yaml
--2017-06-10 04:22:30--  https://raw.githubusercontent.com/irixjp/josug-34th-materials/master/simple-system.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.72.133
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.72.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3209 (3.1K) [text/plain]
Saving to: ‘simple-system.yaml’

100%[======================================>] 3,209       --.-K/s   in 0s      

2017-06-10 04:22:31 (67.1 MB/s) - ‘simple-system.yaml’ saved [3209/3209]

In [19]:
wget https://raw.githubusercontent.com/irixjp/josug-34th-materials/master/ansible_os_stack.yaml
--2017-06-10 04:30:20--  https://raw.githubusercontent.com/irixjp/josug-34th-materials/master/ansible_os_stack.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.72.133
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.72.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2213 (2.2K) [text/plain]
Saving to: ‘ansible_os_stack.yaml’

100%[======================================>] 2,213       --.-K/s   in 0s      

2017-06-10 04:30:21 (30.9 MB/s) - ‘ansible_os_stack.yaml’ saved [2213/2213]

In [6]:
wget https://raw.githubusercontent.com/irixjp/josug-34th-materials/master/clouds.yaml
--2017-06-10 04:22:34--  https://raw.githubusercontent.com/irixjp/josug-34th-materials/master/clouds.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.72.133
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.72.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 188 [text/plain]
Saving to: ‘clouds.yaml’

100%[======================================>] 188         --.-K/s   in 0s      

2017-06-10 04:22:34 (31.9 MB/s) - ‘clouds.yaml’ saved [188/188]

In [7]:
mv -f clouds.yaml ~/.config/openstack/clouds.yaml

heat によるシステム環境の構築

1つのネットワークと2台のサーバーをHeatを使って起動します。このサーバーは起動するだけで、特にアプリの設定等は行いません。

In [9]:
cat simple-system.yaml
heat_template_version: 2015-10-15

description: |
  セキュリティグループの作成
  仮想ネットワークを作成
  仮想ネットワークとルーターを接続
  論理ポートの作成と、Floating IPの割り当て
  仮想サーバーの起動

parameters:
  flavor:
    type: string
    default: m1.tiny
  image:
    type: string
    default: cirros-0.3.5
  public_network:
    type: string
    default: public
  ext_router:
    type: string
    default: Ext-Router
  key:
    type: string
    default: demo-key

resources:
  # セキュリティグループ
  sec_group:
    type: OS::Neutron::SecurityGroup
    properties:
      rules:
      - remote_ip_prefix: 0.0.0.0/0
        protocol: tcp
        port_range_min: 1
        port_range_max: 65535

  # キーペア
  key_pair:
    type: OS::Nova::KeyPair
    properties:
      name: { get_param: key }
      save_private_key: true

  # 仮想ネットワーク
  heat_network:
    type: OS::Neutron::Net
    properties:
      name: heat-net

  # 仮想サブネット
  heat_subnet:
    type: OS::Neutron::Subnet
    properties:
      name: heat-subnet
      ip_version: 4
      network_id: { get_resource: heat_network }
      cidr: 172.16.22.0/24
      gateway_ip: 172.16.22.254
      enable_dhcp: True

  # 仮想ルーターへの接続
  router_interface:
    type: OS::Neutron::RouterInterface
    properties:
      router: { get_param: ext_router }
      subnet: { get_resource: heat_subnet }

  # 論理ポートの作成
  neutron_port1_eth0:
    type: OS::Neutron::Port
    properties:
      network: { get_resource: heat_network }
      security_groups:
        - open-all
  neutron_port2_eth0:
    type: OS::Neutron::Port
    properties:
      network: { get_resource: heat_network }
      security_groups:
        - open-all

  # Floating IPの作成
  floating_ip1:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: { get_param: public_network }
  floating_ip2:
    type: OS::Neutron::FloatingIP
    properties:
      floating_network: { get_param: public_network }

  # 論理ポートへの割り当て
  floating_ip_assoc1:
    type: OS::Neutron::FloatingIPAssociation
    properties:
      floatingip_id: { get_resource: floating_ip1 }
      port_id: { get_resource: neutron_port1_eth0 }
  floating_ip_assoc2:
    type: OS::Neutron::FloatingIPAssociation
    properties:
      floatingip_id: { get_resource: floating_ip2 }
      port_id: { get_resource: neutron_port2_eth0 }

  # 仮想サーバー
  server1:
    type: OS::Nova::Server
    properties:
      flavor: { get_param: flavor }
      image: { get_param: image }
      key_name: { get_resource: key_pair }
      networks:
        - port: { get_resource: neutron_port1_eth0 }
  server2:
    type: OS::Nova::Server
    properties:
      flavor: { get_param: flavor }
      image: { get_param: image }
      key_name: { get_resource: key_pair }
      networks:
        - port: { get_resource: neutron_port2_eth0 }

outputs:
  server_ip:
    value:
      - { get_attr: [floating_ip1, floating_ip_address] }
      - { get_attr: [floating_ip2, floating_ip_address] }
  private_key:
    value: { get_attr: [ key_pair, private_key ] }
In [10]:
heat stack-create -f simple-system.yaml simple-server
WARNING (shell) "heat stack-create" is deprecated, please use "openstack stack create" instead
WARNING (shell) "heat stack-list" is deprecated, please use "openstack stack list" instead
+--------------------------------------+---------------+--------------------+----------------------+--------------+
| id                                   | stack_name    | stack_status       | creation_time        | updated_time |
+--------------------------------------+---------------+--------------------+----------------------+--------------+
| 896a841f-f042-441a-bc8f-0b961d99db72 | simple-server | CREATE_IN_PROGRESS | 2017-06-09T19:23:30Z | None         |
+--------------------------------------+---------------+--------------------+----------------------+--------------+
In [11]:
nova list
+--------------------------------------+------------------------------------+--------+------------+-------------+----------+
| ID                                   | Name                               | Status | Task State | Power State | Networks |
+--------------------------------------+------------------------------------+--------+------------+-------------+----------+
| 523ce16c-5ba8-460d-97d8-6b339376d8f3 | simple-server-server1-pgrghge7q3t4 | BUILD  | networking | NOSTATE     |          |
| 57e02d22-795e-4f7a-926f-505fd88501a7 | simple-server-server2-jyvz5datm7pd | BUILD  | scheduling | NOSTATE     |          |
+--------------------------------------+------------------------------------+--------+------------+-------------+----------+

COMPLETEになるのを待ちます。

In [12]:
heat stack-list
WARNING (shell) "heat stack-list" is deprecated, please use "openstack stack list" instead
+--------------------------------------+---------------+-----------------+----------------------+--------------+
| id                                   | stack_name    | stack_status    | creation_time        | updated_time |
+--------------------------------------+---------------+-----------------+----------------------+--------------+
| 896a841f-f042-441a-bc8f-0b961d99db72 | simple-server | CREATE_COMPLETE | 2017-06-09T19:23:30Z | None         |
+--------------------------------------+---------------+-----------------+----------------------+--------------+

このHeatテンプレートは作成されたサーバーのIPアドレスと、キーペアの値を出力するように構成されています(この値を使って連携を行います)

In [13]:
heat output-show simple-server --all
WARNING (shell) "heat output-show" is deprecated, please use "openstack stack output show" instead
"-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAuSM8VzcNP3m7JlKJJL7oG1sX20maPWKjYmW8YEvZRCjJRu0X\njMOGmQUnqx2dmAsmr5SfkNjUIoNDFuSN1m4ElZ4pMoNzdbA8T0Am+0H1bqt5CBmU\nMuq2jpL+Z1Q3KEjcD5yjKXlCxVym2+pGgCMg4lNwNZm7C9pPr93uGRjnHwge/+r9\nLVGrvEnxLHErV8SCTHN7wegopMoeKG/iqtNIhZlT4V0eJma3LWNfd3OlHvCo+cBr\nr6qrifWeqmJ22Pf0+ePF37t+1Dy0BWPJ5FGW4mPWwk0dYOwnQldNNnbHkn6qW0/n\nDD5kUpl6r0zp22HEN+pwxJd0X1lLmYAzC0yI0wIDAQABAoIBAQCEj1ssI2nj8jhz\ndxNlcR/mPnI1fFzo6RCNasTXuldlu0Jq+2YqTvG37P37QqhNbmaTv3oFD4vM7mmC\nIcIBMCMuMeHTnlV0heyY3hlLPG4MgNCXYSFA19hA+7UWrTEVMh4HO6OEFaQehnme\n6v7xzrVD+HRWnK1WDkmSZfiOlcse5szU8D0ngic2ew1Xrc/Ks7SQVLUpcxiLGYWx\nqHpCetxi3H/+Y+B/SpBsu52NV7o6iZZv5EfdITWscSTEqitbgRdb+QkZsw+MMLuE\nxb5BuKGoz01SE8xQkpdSmuNHWhpFr+1/u634hpE66vyUtx6rcjF9ZdFA4nits02K\nwABalpJxAoGBAN+E8hMhA4Gi//zpTlfon6svOuyfYyUK1SWbSOyXQwWJljKNuv/B\nB6q+GKa8+esNxoyjgfspmcOx55jF6ZTwQ55PlGYCjoreHNI9PXIqlPNld0qAzppF\nlh1v8phoTViZqtL8rIMlfTXDEuSpnfyWgNf3okczop4MJkcQzF+f76q5AoGBANQK\nd+QNyDd7sESQPOwqqeCDiDv1jy1/qTYYE2+33z3q8rAkSRtwLED7CkTCwsoWpJC3\nfW+KGkuv7jMp0rJLiLpXth+LfqirIxBprkASxuSWEvsz2tE6S/DXCO3FPtZnewkQ\n2Nsb1BCTEhSvOz2VASYgG+oi+CJ9DdkUnfcNNNnrAoGBANdqcEbhRVjV9/IOA9QT\nPHaloRtVDR8xDnUc8C4cJZqbnCaCk5tStInohSeE7zOK5jS6jFW8JRYGShbhfpls\n6S4UsgeSJUBOieZQ4Pw7b6wXuN0TwInL0L30GxCpTQm7p3eJztTh94ctxvKbScia\nX/hED6ChcfLx8J4C4YSzRi75AoGAI1CXBSlydiMoiVLIgsDcSCM/9lSAgyBm6zZ5\nTjr+eE6AGuwALQyyoiPtYuRZEtVQlTWqM4B5vq9PacAGcmF4JSjkkT5nWuodzeb7\n+XnnHD0Obj4FrKEb8sGtoAQg4g6cBDeFFoD9AcGs2jsRD8GYTmQ8ofMvBndzbfss\nD/BoI/8CgYBKmSpj753mbzm9xT1mRmVwkesJI3sNeeeXjo71MDChL3St/5D8MzrC\nE5b86T66Offm1SRJtluCSkqrPQ+anJJfrufFH/dIKzky+BUBl1tNBQvpNdhj3c8J\nrNEQerYXtOwnN2vmi9skygZ+gXNnjJBAomY/2F0Y2hFwOuL58KwJWQ==\n-----END RSA PRIVATE KEY-----\n"
[
  "192.168.99.210", 
  "192.168.99.209"
]

いったんStackを削除し、次にAnsibleとの連携を行います。

In [14]:
heat stack-delete -y simple-server
WARNING (shell) "heat stack-delete" is deprecated, please use "openstack stack delete" instead
Request to delete stack simple-server has been accepted.

削除されるのを待ちます。

In [15]:
heat stack-list
WARNING (shell) "heat stack-list" is deprecated, please use "openstack stack list" instead
+----+------------+--------------+---------------+--------------+
| id | stack_name | stack_status | creation_time | updated_time |
+----+------------+--------------+---------------+--------------+
+----+------------+--------------+---------------+--------------+

Ansible との連携

このHeatテンプレートをAnsibleと連携させます。ダウンロードしたPlaybookは以下のように動作します。

  • AnsibleからHeatテンプレートを実行
  • Ansible はHeatのアウトプット値を使ってサーバーへ接続
  • OSの各種設定を実施(今回は簡単なコマンド実行するだけ)

ただし、今回の環境では Cirros というテスト用イメージを使用している関係もあり、通常は動作するコマンド等に制限があります。そのため、簡単なコマンドを実行してその結果を取得だけにしています。

実際にAnsibleを使う場合は、単純なコマンドではなく、パッケージをインストールするモジュールや、サービスの設定を行うモジュールを利用して環境の設定を行うことになります。

In [20]:
cat ansible_os_stack.yaml
- name: create stack by heat simple-server.yaml
  hosts: localhost
  gather_facts: no
  connection: local
  max_fail_percentage: 0

  tasks:
    - set_fact: ansible_python_interpreter=/home/openstack/.virtualenvs/ansible/bin/python
    - os_stack:
        name: "simple-server"
        cloud: handson1
        state: present
        template: "simple-system.yaml"
        wait: yes
      register: stack_return

    - set_fact:
        os_servers: "{{ result[0].output_value }}"
      vars:
        query: "[?output_key=='server_ip']"
        result: "{{ stack_return.stack.outputs | json_query(query) }}"

    - set_fact:
        private_key: "{{ result[0].output_value }}"
      vars:
        query: "[?output_key=='private_key']"
        result: "{{ stack_return.stack.outputs | json_query(query) }}"

    - debug: var=os_servers
    - debug: var=private_key

- name: make in-memory inventory from heat-outputs
  hosts: localhost
  gather_facts: no
  connection: local
  max_fail_percentage: 0

  tasks:
    - add_host:
        name: "{{ item }}"
        groups: os_servers
      with_items:
        - "{{ os_servers }}"

    - debug:
        var: groups

- name: prepare keypair file for os_servers
  hosts: localhost
  gather_facts: no
  connection: local
  max_fail_percentage: 0

  tasks:
    - shell: |
        if [ -e private_key.pem ]; then rm -f private_key.pem; fi
        echo "{{ private_key }}" > private_key.pem
        chmod 600 private_key.pem

- name: wait instance boot
  hosts: os_servers
  gather_facts: no
  max_fail_percentage: 0
  remote_user: cirros
  vars:
    ansible_ssh_private_key_file: private_key.pem

  tasks:

    - wait_for:
        port: 22
        host: "{{ ansible_host|default(ansible_ssh_host|default(inventory_hostname)) }}"
        state: started
        delay: 3
      connection: local

- name: configure and launch web server
  hosts: os_servers
  gather_facts: no
  max_fail_percentage: 0
  remote_user: cirros
  become: yes
  vars:
    ansible_ssh_private_key_file: private_key.pem

  tasks:

    - raw: hostname
      register: result_hostname
    - raw: ls -1 --color=never /
      register: result_ls

    - debug: var=result_hostname
    - debug: var=result_ls
In [17]:
rm -f ~/.ssh/known_hosts

実際にPlaybookを実行します。

In [21]:
export ANSIBLE_HOST_KEY_CHECKING=False
ansible-playbook ansible_os_stack.yaml
 [WARNING]: Host file not found: /etc/ansible/hosts

 [WARNING]: provided hosts list is empty, only localhost is available


PLAY [create stack by heat simple-server.yaml] *********************************

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [os_stack] ****************************************************************
changed: [localhost]

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [set_fact] ****************************************************************
ok: [localhost]

TASK [debug] *******************************************************************
ok: [localhost] => {
    "changed": false, 
    "os_servers": [
        "192.168.99.202", 
        "192.168.99.203"
    ]
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "changed": false, 
    "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAsjX8mmRvPmZQYbR2KH6pdw3juaxhS/cr13zE/Lm/HC6KIc7o\nWQzSexhVvgrOHEoLUHqsfR5gPCDOD1+j+ttETc7gj1ykLxGegUV2LgrYRzTMdxNU\nLqbmUqjIMjLFqWjgEZV/N7ilZFZ4ha4oHuP+KSsv38WkA0E01V1UJZQHxjMu451b\nXZRaWwEfirEy2LjIcWQcRGsjHm0hb7mo41ajzBibgHSghgloWZRidnk8QTvSn8Bc\nt2FzaGUwEy/3PBiAVwhBLe5Qkt/96CWED7zQWhij8ArL1zLUFA1k3VxhoMXZH2oR\nlCxvALa+YpI3ZzVJkbwv3k6G2VO727nUICxCTwIDAQABAoIBAHAziVzziw6wgD9j\nzVjllnC8r/oAzCl174Z2qdesYuStcI6kpKkcZ5DU23cRen9ZJxJ+igckjtaMmPAY\n+TdBwhly/POGEmEIKNGfGC8Y6N68IVd/NetBbZXxakogqU5mF1060KQWfxtTTgJs\nihoY1ba52CR9XSwna8pCPm6CavqfxhdU7/4Qh+5hsgsKZAhn6PjKhwJ2C3/6DcZq\nB6WNI+LLoijqYGfmZvwMvyrQGFysHtCUG3ne4GzkqXO7noDDCV+NeYpg8FCF6TsG\nMu3CIdFdTeviq/qImhcDvdrGSelV+VkRI6jF4GDEaIUdMJl7N9ecAIyn+ntvzDxX\nH9+LirECgYEA14Kj51ZVoE+HERz+xXJwcg7RAzj9DOQusLD4oX3vT14YuLgpV4Zt\nCZS4hc3lXlqeSir9NTJGrQf6uvz+4lyOAYyb2dLEtBQl5VcWS5+mRQ06jZykVK+m\nOG9V8HYhom73pGsb6I/Ae2PSFgUsLmKhwh1UORvAQrKL579/o7q0xhUCgYEA07Fc\n39of2rki3xw7f1y0G/Ygl+QGxhfc2ViSNhEuo4jK4hi111wPxrmE5UKlcT9FbGI7\nDrcoiLuJhrEhUj5LlVInp19dtdlVHfVixGsklK8pKcM1rQ/exfV1uK8fnhp3Pic0\ngeAK9aTUa1rrLiowB4rqhniluUEVuzFv+obfw9MCgYAFmMj06xMSNL6HPT+IlS5C\naAwZj1NTEGH+CLqMRx796q2trmq0cTNHumsIXo6EYsoVlwQ4a3PWboyeao+cBVdS\nxvoNMCIXoyZQzHAxypWg2XUZ1GWYJW+lq46JzieVdlhMFtQEuCcBjVgZZkz9b2ZQ\nCj7ztLrLgKONgzWnZOPo+QKBgESULcE7GZyy41AiBkhRUOvKBW8PaP5dlqc2oU8w\nq8pAs3Ehji8xXM8FJL9cotoYJDZjcDxpK5F4J6ph0aiiE7xQ/BWluGcD/CYTa6a0\nCwq73/ruiYICJSo+RZ/J49VFP6H782+rlAFWH5aTwqKW+i5HW4iqpw1nv1GAR4WC\nkhUPAoGAT7RoUUG3xyUPvMdSmcIrvXH0GAGx7Qoq87PSBeAjZHBdiv5gO8ZZIxBd\nf5/fj3ly2nEg/AUzxq6RH2QzywSKL7Ci0cN9vEQ7bsNTzv2m5mKKjTuIYm7+ObqW\ngZsSVMYPiTHUpyT2tgwQse4GmhapWtkH/F+7W3ugL9mB5AH6ams=\n-----END RSA PRIVATE KEY-----\n"
}

PLAY [make in-memory inventory from heat-outputs] ******************************

TASK [add_host] ****************************************************************
changed: [localhost] => (item=192.168.99.203)
changed: [localhost] => (item=192.168.99.202)

TASK [debug] *******************************************************************
ok: [localhost] => {
    "changed": false, 
    "groups": {
        "all": [
            "192.168.99.202", 
            "192.168.99.203"
        ], 
        "os_servers": [
            "192.168.99.202", 
            "192.168.99.203"
        ], 
        "ungrouped": [
            "localhost"
        ]
    }
}

PLAY [prepare keypair file for os_servers] *************************************

TASK [command] *****************************************************************
changed: [localhost]

PLAY [wait instance boot] ******************************************************

TASK [wait_for] ****************************************************************
ok: [192.168.99.202]
ok: [192.168.99.203]

PLAY [configure and launch web server] *****************************************

TASK [raw] *********************************************************************
changed: [192.168.99.203]
changed: [192.168.99.202]

TASK [raw] *********************************************************************
changed: [192.168.99.203]
changed: [192.168.99.202]

TASK [debug] *******************************************************************
ok: [192.168.99.202] => {
    "changed": false, 
    "result_hostname": {
        "changed": true, 
        "rc": 0, 
        "stderr": "Warning: Permanently added '192.168.99.202' (RSA) to the list of known hosts.\r\nShared connection to 192.168.99.202 closed.\r\n", 
        "stdout": "simple-server-server1-d4mhvpp3i66a\r\n", 
        "stdout_lines": [
            "simple-server-server1-d4mhvpp3i66a"
        ]
    }
}
ok: [192.168.99.203] => {
    "changed": false, 
    "result_hostname": {
        "changed": true, 
        "rc": 0, 
        "stderr": "Warning: Permanently added '192.168.99.203' (RSA) to the list of known hosts.\r\nShared connection to 192.168.99.203 closed.\r\n", 
        "stdout": "simple-server-server2-ultzgbcsfve7\r\n", 
        "stdout_lines": [
            "simple-server-server2-ultzgbcsfve7"
        ]
    }
}

TASK [debug] *******************************************************************
ok: [192.168.99.202] => {
    "changed": false, 
    "result_ls": {
        "changed": true, 
        "rc": 0, 
        "stderr": "Shared connection to 192.168.99.202 closed.\r\n", 
        "stdout": "bin\r\nboot\r\ndev\r\netc\r\nhome\r\ninit\r\ninitrd.img\r\nlib\r\nlinuxrc\r\nlost+found\r\nmedia\r\nmnt\r\nold-root\r\nopt\r\nproc\r\nroot\r\nrun\r\nsbin\r\nsys\r\ntmp\r\nusr\r\nvar\r\nvmlinuz\r\n", 
        "stdout_lines": [
            "bin", 
            "boot", 
            "dev", 
            "etc", 
            "home", 
            "init", 
            "initrd.img", 
            "lib", 
            "linuxrc", 
            "lost+found", 
            "media", 
            "mnt", 
            "old-root", 
            "opt", 
            "proc", 
            "root", 
            "run", 
            "sbin", 
            "sys", 
            "tmp", 
            "usr", 
            "var", 
            "vmlinuz"
        ]
    }
}
ok: [192.168.99.203] => {
    "changed": false, 
    "result_ls": {
        "changed": true, 
        "rc": 0, 
        "stderr": "Shared connection to 192.168.99.203 closed.\r\n", 
        "stdout": "bin\r\nboot\r\ndev\r\netc\r\nhome\r\ninit\r\ninitrd.img\r\nlib\r\nlinuxrc\r\nlost+found\r\nmedia\r\nmnt\r\nold-root\r\nopt\r\nproc\r\nroot\r\nrun\r\nsbin\r\nsys\r\ntmp\r\nusr\r\nvar\r\nvmlinuz\r\n", 
        "stdout_lines": [
            "bin", 
            "boot", 
            "dev", 
            "etc", 
            "home", 
            "init", 
            "initrd.img", 
            "lib", 
            "linuxrc", 
            "lost+found", 
            "media", 
            "mnt", 
            "old-root", 
            "opt", 
            "proc", 
            "root", 
            "run", 
            "sbin", 
            "sys", 
            "tmp", 
            "usr", 
            "var", 
            "vmlinuz"
        ]
    }
}

PLAY RECAP *********************************************************************
192.168.99.202             : ok=5    changed=2    unreachable=0    failed=0   
192.168.99.203             : ok=5    changed=2    unreachable=0    failed=0   
localhost                  : ok=9    changed=3    unreachable=0    failed=0   

Ansible 経由でHeatが実行され、そこで作成されたサーバー内でコマンドが実行されている事が確認できます。

In [22]:
heat stack-list
WARNING (shell) "heat stack-list" is deprecated, please use "openstack stack list" instead
+--------------------------------------+---------------+-----------------+----------------------+--------------+
| id                                   | stack_name    | stack_status    | creation_time        | updated_time |
+--------------------------------------+---------------+-----------------+----------------------+--------------+
| d5e614a9-40f4-487a-83a6-dc71f054a202 | simple-server | CREATE_COMPLETE | 2017-06-09T19:30:41Z | None         |
+--------------------------------------+---------------+-----------------+----------------------+--------------+

環境を削除しておきます。

In [24]:
heat stack-delete -y simple-server
WARNING (shell) "heat stack-delete" is deprecated, please use "openstack stack delete" instead
Request to delete stack simple-server has been accepted.

今回の例では、Ansible → Heat → Nova/Neutron という連携をしていますが、Ansible → Nova/Neutron と直接連携することも可能です。また、Ansible は今回の用にクラウドやサーバーの操作だけではなく、ネットワーク機器の操作も行うことも可能です。