AnsibleでCloudStackを操作する(基礎編:仮想マシン作成とプロビジョニング)

  • 10
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

最近よく耳にする構成管理ツールのAnsibleですがサーバーの設定だけでなくCloudStackの各種リソースを扱うこともできます。

今回はAnsibleによるCloudStackの操作の基礎として仮想マシン作成と簡単なプロビジョニングを行ってみます。

IDCF Cloud Advent Calendar 2015@snicker_jp さんの記事と内容がかなり重複しているので、ぜひこちらの記事も読んでみてください。

AnsibleだけでIDCFクラウドの仮想マシン立ち上げから、HAProxyを使ったSSLオフローダーを構築する #idcfrontier : 元うなぎ屋

今回の記事で使用しているファイルは下記レポジトリにあります。

https://github.com/atsaki/ansible-cloudstack-example-nginx

AnsibleのCloudStackモジュールについて

AnsibleはファイルやユーザーなどOSの操作だけでなく、各種クラウドの仮想マシンやボリュームなどのリソースを扱うこともできます。

CloudStackのモジュールが開発が進められており、Ansible 2.0から標準でインストールされる予定です。

少しややこしいのですがCloudStackモジュールは以下の2つのレポジトリで
開発が進められています。
同じ開発者が中心となって開発を進めており互換性はありますが、完全に同じと
いうわけではないので注意が必要です。

  • ansible/ansible-modules-extras
    • Ansibleの一部として開発がすすめられているレポジトリ
    • Ansible 2.0のリリース時にはこちらが組み込まれる
  • resmo/ansible-cloudstack
    • CloudStackモジュール単体で開発が進められているレポジトリ
    • こちらで修正を行ってからansible/ansible-modules-extras に反映されるケースが多い

今回はresmo/ansible-cloudstack を使用します。

CloudStackモジュールのインストール

まずはCloudStackモジュールを使用できる環境を用意します。
ここでは、以下の2つの方法を紹介します。

Ansibleがインストールされている場合

すでにAnsibleがインストールされている場合は、playbookと同じディレクトリの
library というディレクトリにCloudStackモジュールをダウンロードします。

$ git clone https://github.com/resmo/ansible-cloudstack.git library

CloudStackモジュールが必要とするPythonパッケージもインストールしておきます。

$ pip install cs sshpubkeys

Dockerを使用する場合

Dockerを使える環境の構築方法は多くの資料があるので割愛します。
Docker Machineを使用するのが簡単かと思います。

AnsibleとCloudStackモジュールのイメージを作成するためのDockerfileを用意します。
Pythonパッケージのインストール、CloudStackモジュールの取得、ライブラリのパスへの追加などを行っています。

Dockerfile
FROM python:2.7
RUN pip install cs ansible sshpubkeys
RUN mkdir -p /usr/share/ansible && \
    cd /usr/share/ansible && \
    git clone https://github.com/resmo/ansible-cloudstack.git
RUN mkdir -p /etc/ansible ; \
    echo "[defaults]"                                       > /etc/ansible/ansible.cfg; \
    echo "host_key_checking = False"                       >> /etc/ansible/ansible.cfg; \
    echo "library = /usr/share/ansible/ansible-cloudstack" >> /etc/ansible/ansible.cfg

Dockerfileからイメージをビルドします。

$ docker build -t ansible .

docker run で作成したイメージにAnsibleがインストールされていることが確認できます。

$ docker run --rm ansible ansible --version
ansible 1.9.4
  configured module search path = None

csの設定

CloudStackモジュールが使用しているcsがCloudStack APIを実行できるようにエンドポイント、APIキー、シークレットキーの設定を行います。

設定ファイルを使用して設定することもできますが、今回は環境変数を使って設定してみます。

環境変数CLOUDSTACK_ENDPOINTCLOUDSTACK_KEYCLOUDSTACK_SECRETにエンドポイント、APIキー、シークレットキーをセットします。

また、タイムアウトの設定が厳しめ(デフォルト10秒)で失敗してしまうことがあるので、
余裕を見て1分に伸ばしておきます。(CLOUDSTACK_TIMEOUT=60

ローカルのAnsibleを使用する場合は、以下のようにエクスポートします。

$ export CLOUDSTACK_ENDPOINT=<APIのエンドポイント>
$ export CLOUDSTACK_KEY=<APIキー>
$ export CLOUDSTACK_SECRET=<シークレットキー>
$ export CLOUDSTACK_TIMEOUT=60

Dockerを使用する場合は、まず.envというファイルを以下の内容で作成します。

.env
CLOUDSTACK_ENDPOINT=<APIのエンドポイント>
CLOUDSTACK_KEY=<APIキー>
CLOUDSTACK_SECRET=<シークレットキー>
CLOUDSTACK_TIMEOUT=60

docker runのオプション--env-fileにこのファイルを指定すると実行時に環境変数がセットされます。

設定が正しくできていれば、cs コマンドでAPIが実行できるようになっています。

# ローカルのAnsibleを使用する場合は
$ cs listZones
# Dockerを使用する場合
$ docker run --env-file=.env --rm ansible cs listZones

SSH鍵の作成

ホストへ接続するために使用するSSH鍵を作成しておきます。

$ ssh-keygen -f ansible_ssh -N ""

AnsibleでNginxサーバーを構築する

準備が整ったので、仮想マシン作成とプロビジョニングのNginxサーバーを構築してみます。

Inventoryファイル

Inventoryファイルに作成するホストとグループの設定を書きます。

今回はnginx01 というホストをCloudStack上に作成し、Nginxをインストールします。

Nginxをインストールするホストのグループをnginx 、CloudStack上に作成されるホストをcloudstack グループとし、nginx01 を両方のグループに所属させています。(nginx グループ全体をcloudstack グループに含めています。)

[all:vars] セクションにはログインユーザーや秘密鍵など共通で使用する設定を書いておきます。

hosts
[nginx]
nginx01

[cloudstack:children]
nginx

[all:vars]
ansible_ssh_user=root
ansible_ssh_private_key_file=./ansible_ssh
ansible_python_interpreter=python
cache_dir=./cache

変数の設定

group_vars/cloudstack には、デフォルトで使用するゾーンやテンプレートなどのCloudStackの設定を書いておきます。

group_vars/cloudstack
---

ssh_public_key_file: "./ansible_ssh.pub"
zone_name: "joule"
network_name: "joule-network1"
service_offering_name: "light.S1"
template_name: "CentOS 7.1 64-bit"
firewall_rules:
  - { protocol: "tcp", start_port: 22, end_port: 22 }
port_forwarding_rules:
  - { protocol: "tcp", public_port: 22, private_port: 22 }

nginxグループについてはSSHのポートだけでなくHTTPのポートも開けます。

group_vars/nginx
---

firewall_rules:
  - { protocol: "tcp", start_port: 22, end_port: 22 }
  - { protocol: "tcp", start_port: 80, end_port: 80 }
port_forwarding_rules:
  - { protocol: "tcp", public_port: 22, private_port: 22 }
  - { protocol: "tcp", public_port: 80, private_port: 80 }

Playbookの作成

今回は以下の3つのPlaybookを作成します。

  • deploy.yml
    • 仮想マシンのデプロイ、IPアドレスの取得、ポートフォワーディングの設定を行う
  • nginx.yml
    • 仮想マシンにNginxをインストールする
  • clean.yml
    • 作成・取得したリソースを削除・解放する

Playbookはansible-blaybook コマンドで実行できます。

$ ansible-playbook -i hosts PLAYBOOK.yml

Dockerを使用している場合には、カレントディレクトリをマウントしコンテナからPlaybookが見えるようにします。

$ docker run --env-file=.env -v $(pwd):/data -w /data --rm ansible ¥
    ansible-playbook -i hosts PLAYBOOK.yml

以下それぞれのPlaybookについて簡単に内容を説明します。
詳細についてはレポジトリのファイルを参照してください。

deploy.yml

deploy.yml はホストをCloudStack上に作成し、ファイアウォール・ポートフォワーディング等の設定を行いホストにSSH接続可能な状態にします。

仮想マシンのデプロイ

仮想マシンの管理はcs_instance モジュールで行うことができます。
各パラメータを設定するだけで、仮想マシンの状態を指定されている状態にしてくれます。

SSH接続するためには仮想マシンが起動している必要があります。
state: started としておくと、deploy.yml が実行されたとき仮想マシンが停止していたら起動することができます。

deploy.yml
    - name: "VMの作成"
      cs_instance:
        name: "{{ inventory_hostname }}"
        zone: "{{ zone_name }}"
        networks: ["{{ network_name }}"]
        service_offering: "{{ service_offering_name }}"
        template: "{{ template_name }}"
        ssh_key: "{{ inventory_hostname }}"
        state: started
      register: vm
IPアドレスの取得

仮想マシンへのSSH接続とWebページへの接続のためにIPアドレスを取得します。

IPアドレスの管理はcs_ip_address モジュールで行うことができます。

IPアドレスの取得については注意が必要です。
Ansibleは基本的に前回実行した処理を覚えていないので、単にstate: present で取得すると、実行のたびにIPアドレスを取得してしまいます。

対策は色々ありますが、今回は取得したIPアドレスを書いたファイルを保存しておくことにしました。ファイルが存在する場合は取得済みなので処理をスキップします。

deploy.yml
    - name: "キャッシュ用ディレクトリの作成"
      file:
        path: "{{ vm_cache_dir }}"
        state: "directory"
        mode: "0755"

    - name: "取得済みIPアドレスの確認"
      stat:
        path: "{{ vm_cache_dir + '/ip_address' }}"
      register: cache_ip_address

    - name: "新規IPアドレスの取得"
      cs_ip_address:
        zone: "{{ zone_name }}"
        network: "{{ network_name }}"
      register: ip_address
      when: not cache_ip_address.stat.exists

    - name: "取得したIPアドレスをキャッシュへ書き込み"
      copy:
        content: "{{ ip_address.ip_address }}"
        dest: "{{ vm_cache_dir + '/ip_address' }}"
      when: not cache_ip_address.stat.exists

    - name: "IPアドレスのキャッシュ読み込み"
      set_fact:
        ip_address: "{{ lookup('file', vm_cache_dir + '/ip_address') }}"
ファイアウォール・ポートフォワーディングルールの設定

IPアドレスが取得できたので、SSHとHTTPのファイアウォールと
ポートフォワーディングルールを設定します。

ファイアウォールとポートフォワーディングルールの管理にはそれぞれcs_firewallcs_portforward を使用します。

これらのモジュールを使用する際の注意点は、必ずゾーンを指定することです。
ゾーンなしでは意図していないゾーンでIPアドレスを探してエラーになる場合があります。

deploy.yml
    - name: "ファイアウォールルールの設定"
      cs_firewall:
        zone: "{{ zone_name }}"
        ip_address: "{{ ip_address }}"
        protocol: "{{ item.protocol }}"
        start_port: "{{ item.start_port }}"
        end_port: "{{ item.end_port }}"
        cidr: "{{ item.cidr|default('0.0.0.0/0') }}"
      with_items: firewall_rules
SSH接続可能になるまで待機

最後に仮想マシンがSSH接続可能になるまで待機します。

deploy.yml
    - name: "SSHで接続可能になるまで待機"
      wait_for:
        host: "{{ ip_address }}"
        port: "{{ ansible_ssh_port|default(22) }}"
        search_regex: "OpenSSH"

    - name: "authorized_keysの設定に時間がかかる場合があるので余分に待機"
      pause: minutes=2
      when: vm|changed
作成した仮想マシンにSSHログイン

作成した仮想マシンには以下のコマンドでログインできます。

$ ssh -i ansible_ssh root@$(cat cache/nginx01/ip_address)

nginx.yml

nginx.yml```はホストにNginxをインストールします。

```yaml:nginx.yml
---

- hosts: nginx
  gather_facts: no
  pre_tasks:
    - include: set_ansible_ssh_host.yml
  tasks:
    - name: "Nginxをインストール(Ubuntu)"
      apt:
        name: "nginx"
        update_cache: yes
      when: ansible_distribution == 'Ubuntu'

    - name: "EPELをインストール(CentOS)"
      yum:
        name: "epel-release"
      when: ansible_distribution == 'CentOS'

    - name: "Nginxをインストール(CentOS)"
      yum:
        name: "nginx"
      when: ansible_distribution == 'CentOS'

    - name: "Nginxを起動"
      service:
        name: "nginx"
        enabled: yes
        state: started

Nginxをインストールするタスクの実行前に、pre_tasksset_ansible_ssh_host.yml を実行しています。
set_ansible_ssh_host.yml では取得済みのIPアドレスを調べansible_ssh_host にをセットし、対象ホストにSSHできるようにしています。

set_ansible_ssh_host.yml は、ホスト情報の取得も行います。
これによりホストの情報によって処理を変更することができます。
nginx.ymlでは、ディストリビューションによって使用するモジュールを
変更しCentOS、Ubuntuの両方に対応しています。

set_ansible_ssh_host.yml
---

- set_fact:
    vm_cache_dir: "{{ cache_dir + '/' + inventory_hostname }}"

- name: "取得済みIPアドレスの確認"
  stat:
    path: "{{ vm_cache_dir + '/ip_address' }}"
  register: cache_ip_address
  connection: local
  failed_when: not cache_ip_address.stat.exists

- name: "ansible_ssh_hostに取得済みのIPをセット"
  set_fact:
    ansible_ssh_host: "{{ lookup('file', vm_cache_dir + '/ip_address') }}"

- name: "仮想マシンのステータスを確認"
  cs_instance:
    name: "{{ inventory_hostname }}"
    zone: "{{ zone_name }}"
    state: present
  connection: local
  register: instance

- name: "ホスト情報の取得"
  setup:
  when: instance.state == "Running"

プロビジョニングが正常に完了していれば、ブラウザでcache/nginx01/ip_address のIPアドレスを
開くとNginxの画面が表示されます。

clean.yml

clean.ymlは作成・取得したリソースを解放・解除するためのPlaybookです。

削除の際もゾーンの指定を忘れないよう注意する必要があります。
ゾーンを指定していない場合、複数ゾーンに同じ名前のリソースが存在すると
意図していないほうが消される場合があります。

clean.yml
---

- hosts: cloudstack
  connection: local
  tasks:
    - set_fact:
        vm_cache_dir: "{{ cache_dir + '/' + inventory_hostname }}"

    - name: "SSH公開鍵の削除"
      cs_sshkeypair:
        name: "{{ inventory_hostname }}"
        state: absent

    - name: "取得済みIPアドレスの確認"
      stat:
        path: "{{ vm_cache_dir + '/ip_address' }}"
      register: cache_ip_address

    - name: "IPアドレスのキャッシュ読み込み"
      set_fact:
        ip_address: "{{ lookup('file', vm_cache_dir + '/ip_address') }}"
      when: cache_ip_address.stat.exists

    - name: "IPアドレスの解放"
      cs_ip_address:
        zone: "{{ zone_name }}"
        network: "{{ network_name }}"
        ip_address: "{{ ip_address }}"
        state: absent
      when: cache_ip_address.stat.exists

    - name: "IPアドレスキャッシュの削除"
      file:
        path: "{{ vm_cache_dir + '/ip_address' }}"
        state: absent
      when: cache_ip_address.stat.exists

    - name: "VMの削除"
      cs_instance:
        name: "{{ inventory_hostname }}"
        zone: "{{ zone_name }}"
        state: expunged

    - name: "キャッシュ用ディレクトリの削除"
      file:
        path: "{{ vm_cache_dir }}"
        state: absent

最後に

ここまでAnsibleで仮想マシンを作成しプロビジョニングする方法をみてきましたが、
今回扱った内容であればVagrantやTerraformを使用した方が良いと思います。
これらのツールはIDでリソースを管理しているため、意図しないリソースを操作
してしまう危険が少なく安全です。また、用途が限られているので設定も簡単になっています。

AnsibleでCloudStackを操作するメリットは、細かい処理の手順を記述しやすいこと、
既存のリソースを管理対象にしやすいこと、対応しているリソースが多いことにあると思います。

次回は、応用編としてこれらのメリットが活用できる例として既存テンプレートのカスタマイズを行います。

AnsibleでCloudStackを操作する(応用編:IDCFクラウドテンプレートのカスタマイズ) - Qiita