最近よく耳にする構成管理ツールのAnsibleですがサーバーの設定だけでなくCloudStackの各種リソースを扱うこともできます。
今回はAnsibleによるCloudStackの操作の基礎として仮想マシン作成と簡単なプロビジョニングを行ってみます。
IDCF Cloud Advent Calendar 2015の@snicker_jp さんの記事と内容がかなり重複しているので、ぜひこちらの記事も読んでみてください。
AnsibleだけでIDCFクラウドの仮想マシン立ち上げから、HAProxyを使ったSSLオフローダーを構築する #idcfrontier : 元うなぎ屋
今回の記事で使用しているファイルは下記レポジトリにあります。
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モジュールの取得、ライブラリのパスへの追加などを行っています。
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_ENDPOINT
、CLOUDSTACK_KEY
、CLOUDSTACK_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
というファイルを以下の内容で作成します。
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]
セクションにはログインユーザーや秘密鍵など共通で使用する設定を書いておきます。
[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の設定を書いておきます。
---
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のポートも開けます。
---
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-playbook
コマンドで実行できます。
$ 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
が実行されたとき仮想マシンが停止していたら起動することができます。
- 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アドレスを書いたファイルを保存しておくことにしました。ファイルが存在する場合は取得済みなので処理をスキップします。
- 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_firewall
、cs_portforward
を使用します。
これらのモジュールを使用する際の注意点は、必ずゾーンを指定することです。
ゾーンなしでは意図していないゾーンでIPアドレスを探してエラーになる場合があります。
- 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接続可能になるまで待機します。
- 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をインストールします。
---
- 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_tasks
でset_ansible_ssh_host.yml
を実行しています。
set_ansible_ssh_host.yml
では取得済みのIPアドレスを調べansible_ssh_host
にをセットし、対象ホストにSSHできるようにしています。
set_ansible_ssh_host.yml
は、ホスト情報の取得も行います。
これによりホストの情報によって処理を変更することができます。
nginx.yml
では、ディストリビューションによって使用するモジュールを
変更しCentOS、Ubuntuの両方に対応しています。
---
- 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です。
削除の際もゾーンの指定を忘れないよう注意する必要があります。
ゾーンを指定していない場合、複数ゾーンに同じ名前のリソースが存在すると
意図していないほうが消される場合があります。
---
- 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を操作するメリットは、細かい処理の手順を記述しやすいこと、
既存のリソースを管理対象にしやすいこと、対応しているリソースが多いことにあると思います。
次回は、応用編としてこれらのメリットが活用できる例として既存テンプレートのカスタマイズを行います。