#はじめに
Ansible超初心者向けハンズオンです。Ansibleを使ってローカルVM構築を自動化してみましょう。
※この記事はサポーターズCoLab様でのイベント用に作成したものですが、Ansibleを始めてみたいという方の参考にしていただけたら幸いです。
#事前準備
ハンズオン実施前にに以下1〜3の準備をお願いします。
なお、お手持ちのPCに以下が全て揃っていれば事前準備は特に必要ありません。
- Virtualbox
- Vagrant
- python2.6または2.7の入ったCentOS6のbox
##1.Virtualboxのインストール
下記URLからVirtualboxの最新版をダウンロードして、インストーラを実行してください。
https://www.virtualbox.org/wiki/Download
インストール後、VirtualBoxを起動できればOKです。
Macの場合は、ターミナルを開き下記コマンドでバージョンが表示されることでも確認できます。
VBoxManage -v
# 5.2.8r121009
##2.Vagrantのインストール
下記URLからVagrantの最新版をダウンロードして、インストーラを実行してください。
https://www.vagrantup.com/
インストール後、Windowsならコマンドプロンプト、Macならターミナルを開き、下記コマンドでバージョンが表示されればOKです。
vagrant -v
# Vagrant 2.0.3
##3.Vagrant Boxの追加
Windowsならコマンドプロンプト、Macであればターミナルを開き、下記コマンドを実行してvagrant boxを追加してください。かなり時間かかります。
vagrant box add centos6-4 https://github.com/2creatives/vagrant-centos/releases/download/v6.4.2/centos64-x86_64-20140116.box
終了後、以下のコマンドでvagrant boxの追加を確認できればOKです。
vagrant box list
# centos6-4 (virtualbox, 0)
不安な方は実際にVMを作成してsshできるかまで確認しておいてください。
vagrant init centos6-4 # Vagrantfile作成
vagrant up # VMの立ち上げ。多少時間がかかる
vagrant ssh # ログインできればOK
#ハンズオンにあたっての注意
- ここから先は基本的にMacを想定して手順を記述しています。Windowsの方は適宜コマンドプロンプトもしくはGUI操作に読み替えてください。簡単に読み替えられない部分に関しては注釈を入れてあります。
- とにかく動かして使ってみるのが目的なので、細かい説明はしません。「Ansible 入門」で検索すれば偉大な先人たちが書いた素晴らしい記事がたくさん出てきますのでそちらを読みましょう。
#ハンズオン1〜Ansibleを使ってみよう〜
##目標
- Ansibleを動かして、VMにコマンドをインストールしてみる
- Ansibleの最小構成を知る
手順
作業用VM作成
Ansibleで構成を管理するVMを作成します。
適当に作業用ディレクトリを作り、その中で適当にVMを作ります。
mkdir ansible-hands-on
cd ansible-hands-on
vagrant init centos6-4
vagrant up
vagrant status
# Current machine states:
#
# default running (virtualbox)
#
# The VM is running. To stop this VM, you can run `vagrant halt` to
# shut it down forcefully, or you can run `vagrant suspend` to simply
# suspend the virtual machine. In either case, to restart it again,
# simply run `vagrant up`.
上記のように、「running」と出力されていればVMが作成されています。
次にVMにログインします。Macの場合は以下のコマンド。
vagrant ssh
vagrantユーザでログインできているはずですので確かめます。
id
# uid=500(vagrant) gid=500(vagrant) groups=500(vagrant),10(wheel)
hostname
# vagrant-centos64.vagrantup.com
※Windowsの場合はvagrant sshではログインできないため注意が必要です!
teraterm等のsshクライアントを用いて、「vagrant@127.0.0.1」に接続してください。パスワードに「vagrant」を入れればログインできるはずです。
Ansibleをインストールする
ここからはVMにログインした状態で作業を行います。
今回はあくまでハンズオンなので、アクセス権限等を考えなくてもいいようにrootユーザになっておきます。
su - root # パスワードは"vagrant"
さて、まずは肝心のAnsibleをインストールしましょう。
yum localinstall -y http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
yum -y install -y ansible
インストールが無事完了したかどうか、Ansibleのバージョンを表示させて確かめましょう。
ansible --version
# ansible 2.3.1.0
# config file = /etc/ansible/ansible.cfg
# configured module search path = Default w/o overrides
# python version = 2.6.6 (r266:84292, Jul 10 2013, 22:48:45) [GCC 4.4.7 20120313 (Red Hat 4.4.7-3)]
※もし「Error: Cannot retrieve metalink for repository: epel.(略)」のようなエラーメッセージが表示される場合、以下のコマンドを実行してからもう一度インストールしてみてください。
sed -i -e 's/mirrorlist=https/mirrorlist=http/g' /etc/yum.repos.d/epel.repo
Ansibleを動かしてみる
ansible --version の出力にもあるように、/etc/ansible/というディレクトリが自動的に作成されています。ここに移動して設定などを行なっていきます。
cd /etc/ansible/
ls
# ansible.cfg hosts roles
Ansibleでプロビジョニングを行うために必須なのは、inventory(hosts)とplaybookです。一つずつ見ていきましょう。
inventory
inventoryってなんやねん?と思うかもしれませんが、要はhostsファイルのことです。複数サーバのプロビジョニングを行うときには重要ですが、今回はとりあえず中身を全消しして、下のように書いておけばOKです。
[localhost]
127.0.0.1
playbook
playbookってなんや?おう?と思うかもしれませんが、要はyamlファイルのことです。名前は何でも大丈夫です。処理内容をymlで記述します。
練習用として、「nkfコマンドをyumでインストールする」という記述をしたいと思います。main.ymlという名前のファイルを作成し、以下のように書いてください。
- name: Ansible sample 1
hosts: localhost
connection: local
tasks:
- name: install nkf
yum: name=nkf
これで準備は完了です。Ansibleでプロビジョニングを実行してみましょう。
まず、nkfコマンドがインストールされていないことを確認します。
nkf -g hosts
# -bash: /usr/bin/nkf: No such file or directory
コマンドがないのでエラーになりますね。
次にPlaybookを実行します。
ansible-playbook main.yml
以下のような出力がされると思います。
PLAY [Ansible sample 1] **********************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************
ok: [127.0.0.1]
TASK [install nkf] ***************************************************************************************************
changed: [127.0.0.1]
PLAY RECAP ***********************************************************************************************************
127.0.0.1 : ok=2 changed=1 unreachable=0 failed=0
TASK [install nkf]のところがchangedになっていれば成功です。nkfコマンドがインストールされたかどうかを確認します。
nkf -g hosts
# ASCII (LF)
Playbookにより、コマンドのインストールができました。やったぜ。ちなみにnkfは文字コードの推測や変換を行える超便利コマンドです。知らない人は今覚えましょう。
ちょっとだけ解説
main.ymlについて
main.ymlの中身を上から順番に解説します。
- name: Ansible sample 1
hosts: localhost
connection: local
tasks:
- name: install nkf
yum: name=nkf
- 最初のnameはこのplaybookの名前です。別になくても構いません
- hostsはプロビジョニングの対象サーバを表しています。hostsファイルに[localhost]というグループを作成したので今回はそれを指定しています。直接127.0.0.1と書いても動きます
- connection: localはローカルのプロビジョニングをする際に必要な記述です。これがないとssh接続します
- tasks配下に実際のタスクを書いていきます。タスクには様々なモジュールが利用できます
- ここのnameはタスクごとの名前です。今回はnkfをインストールするタスクなのでこの名前にしました
- yumモジュールを使ってnkfをインストールする、という記述です
冪等性について
もう一度同じplaybookを実行してみてください。
ansible-playbook main.yml
今度は以下のような出力がされるのではないでしょうか。
PLAY [Ansible sample 1] **********************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************
ok: [127.0.0.1]
TASK [install nkf] ***************************************************************************************************
ok: [127.0.0.1]
PLAY RECAP ***********************************************************************************************************
127.0.0.1 : ok=2 changed=0 unreachable=0 failed=0
先ほどとは違い、TASK [install nkf]のところがchangedではなくokになっています。サーバの状態が変更されていないためです。つまり、nkfはすでにインストール済みなので、yumモジュールはnkfをインストールしません。
この後何度同じplaybookを実行しても、サーバの状態は変更されません。このように、ある操作を1回実行しても複数回実行しても結果が同じであるという性質を**冪等性(べきとうせい)**といいます。
Ansibleでは、特定のモジュール以外は冪等性を持っています。Playbookの冪等性を保たせることが、上手にAnsibleを使いこなすために大切なことの一つです。
#ハンズオン2〜色々なモジュールを使ってみよう〜
目標
ハンズオン1ではyumでnkfコマンドをインストールしました。これにはyumモジュールを利用しています。Ansibleにはモジュールが色々ありますので、よく使いそうなものを試してみましょう。
なお、モジュールの一覧はModule Indexにありますので困ったらこちらを参照しましょう。
手順
ファイル/ディレクトリを作成する
ファイルやディレクトリを作成するにはfileモジュールを利用します。
まずはディレクトリを作成します。main.ymlの末尾に以下を追記してください。
- name: directory creation sample
file: path=/etc/ansible/sampledir
state=directory
owner=root
group=root
mode=0644
見たままですが、pathにはパスを、stateにはfile、ownerやgroupは設定したいユーザ/グループ、modeにはパーミッションを指定してください。
注意点としては、modeには必ず4桁の数字を書くようにしてください。(3桁で644とか書くと予期せぬ動作をすることがあるらしいので)
ansible-playbookを実行すると、samplidirというディレクトリが作成されているのが分かると思います。
ls -l
# -rw-r--r-- 1 root root 18066 Jun 1 21:49 ansible.cfg
# -rw-r--r-- 1 root root 56 Jun 27 23:47 hosts
# -rw-r--r-- 1 root root 1037 Jun 28 00:03 main.yml
# drwxr-xr-x 2 root root 4096 Jun 1 21:49 roles
# drw-r--r-- 2 root root 4096 Jun 27 23:38 sampledir
ファイルの作成も一応できますが、冪等性が失われてしまうため基本的には後述のcopyモジュールやtemplateモジュールを使うべきです。一応やり方だけ確認しておきます。
- name: file creation sample
file: path=/etc/ansible/samplefile
state=touch
owner=root
group=root
mode=0644
先ほどとほぼ同じで、stateをtouchに変えればファイルが作成されます。ただしファイルが存在していてもtouchしてしまうので毎回サーバの状態が変更されてしまいます。使うのはやめましょう。
ファイル/ディレクトリをコピーする
ファイル/ディレクトリのコピーにはcopyモジュールを使います。たとえば、ホストOS上にあるファイルをゲストOSとの共有ディレクトリに置いて、それをAnsibleで任意の場所にコピーしましょう。
まず、ホストOSのゲストOSの共有ディレクトリ(デフォルトではVagrantfileのあるディレクトリ)に、コピー用ファイルcopy.txtとコピー用ディレクトリcopydirを作成してください。copydirの中には適当にファイルを作成しておいてください。
自分の場合(Mac)は以下のようにしました。
touch copy.txt
mkdir copydir
mkdir copydir/dir
touch copydir/dir/dir_copy_sample.txt
続いて、下記をmain.ymlに追加してください。
- name: file copy sample
copy: src=/vagrant/copy.txt
dest=/etc/ansible/copy.txt
owner=root
group=root
mode=0644
- name: directory copy sample
copy: src=/vagrant/copydir/
dest=/etc/ansible/copydir/
owner=root
group=root
mode=2775
ansible-playbookを実行して、ファイルとディレクトリがコピーされていることを確認してください。
ファイルをコピーして変数を展開する
ファイルをコピーするだけでなく、中身の一部をサーバによって変えたいときがあると思います。たとえばWebサーバとDBサーバで何かしらの接続先を変えるなどです。ローカルVMの場合はあまりないかもしれませんが、変数を別に切り出しておけば変更が容易になるというメリットもありますので紹介しておきます。
変数を展開するためには、jinja2というテンプレートを使用します。拡張子は" .j2 "です。先ほどと同様に、ホストOSの共有ディレクトリにtemplate.j2というファイルを作成し、以下のような内容を記述してください。
this is jinja template.
{{ sample_var }}
次に、変数sample_varを定義します。変数を定義する方法はいくつかありますが、inventoryに記載するのが簡単です。
[localhost]
127.0.0.1
[localhost:vars]
sample_var=hoge
このようにすれば、sample_varにhogeという文字列がセットされます。
- name: template copy sample
template: src=/vagrant/template.j2
dest=/etc/ansible/template.txt
owner=root
group=root
mode=0644
ansible-playbookを実行して、コピーされたtemplate.txtの中身を確認してください。
cat template.txt
# this is jinja template.
#
# hoge
変数sample_varが展開されて、hogeと出力されました。便利っぽい。
もっと高度なことがやりたい場合は公式を参照して下さい。
http://jinja.pocoo.org/docs/dev/templates/
コマンドを実行する
commandモジュールを使えばUnixコマンドを実行することができます。ただし、このモジュールは常にchangedを返すため、冪等性が失われます。 なるべく使わないようにしましょう。
どうしても使いたい場合は、whenなどを使用して条件をつけ、冪等性が保たれるように工夫した方が良いです。ただし今回はとりあえず試してみましょう。
- name: command sample
command: touch command.log
実行してみてください。以下のような出力になるはずです。
PLAY [ansible hands-on] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [127.0.0.1]
TASK [install nkf] *************************************************************
ok: [127.0.0.1]
TASK [file creation sample] ****************************************************
ok: [127.0.0.1]
TASK [directory creation sample] ***********************************************
ok: [127.0.0.1]
TASK [file copy sample] ********************************************************
ok: [127.0.0.1]
TASK [directory copy sample] ***************************************************
ok: [127.0.0.1]
TASK [template copy sample] ****************************************************
ok: [127.0.0.1]
TASK [command sample] **********************************************************
[WARNING]: Consider using file module with state=touch rather than running
touch
changed: [127.0.0.1]
PLAY RECAP *********************************************************************
127.0.0.1 : ok=8 changed=1 unreachable=0 failed=0
WARNINGが出ましたね。「commandモジュールでtouchするのではなくて、fileモジュールでstate=touchにしなさい」とのことです。このようにcommandモジュールは基本的に推奨されないため、まずは他のモジュールでやりたいことを実現できないか探すようにしましょう。
ついでに、もう一度ansible-playbookを実行してみてください。command sampleのところがまたchangedになっていると思います。何回やってもchangedになってしまいます。冪等性が失われている状態です。
まあハンズオンなので一旦気にせず進みます。
コマンドをシェル経由で実行する
commandモジュールと似たようなものにshellモジュールがあります。こちらは、shell経由でコマンドを実行できます。commandモジュールとの大きな違いは、パイプやリダイレクトが使用できることです。main.ymlに以下を追記してみてください。
- name: shell sample
shell: echo 'this is shell sample.' > shell.log
リダイレクトを利用して、echoで標準出力に出力された結果をshell.logに書き込んでいます。commandモジュールではこういうことはできません。基本的にはshellモジュールの方が便利なので、commandよりこちらを利用した方がいいと思います。ただし、実行結果がリモートサーバのシェル環境に影響されますので注意しましょう。
もちろんshellモジュールにも冪等性はありません。工夫して使いましょう。
エラーを無視して続行する
ここからはモジュールのオプションを少しだけ紹介します。
あるタスクででエラーが発生した場合、そこでansibleの実行自体が止まってしまいます。main.ymlのcommand sampleとshell sampleの間にエラーになるタスクを追加してみましょう。
- name: command sample
command: touch command.log
- name: command error sample
command: sl
- name: shell sample
shell: echo 'this is shell sample.' > shell.log
slという未インストールのコマンドを実行するタスクを追加しました。この状態で実行すると、shell sampleが実行されず、command error sampleのエラーで終了します。
このエラーで終了せずにそのまま次のタスクに進みたい場合は、ignore_errorsオプションをtrueにします。
- name: command error sample
command: sl
ignore_errors: true
再度実行して、shell sampleが再度実行されていることを確認してください。
リストを使って同じ処理を複数回実行する
with_itemsオプションを使えば、同じタスクの一部だけを変更しつつ複数回実行することができます。foreach的な感じだと思ってもらえば大丈夫です。使い方は以下の通りです。
- name: shell with_items sample
shell: echo '{{ item }}' >> shell.log
with_items:
- item1
- item2
- item3
shell.log中身を確認してください。
もちろん複数の変数を使用することもできます。
変更があったら他のタスクを実行する(ハンドラ)
今までtasks配下にタスクを記述してきましたが、タスクにnotifyオプションをつけることでハンドラを呼び出すことができます。notifyオプションをつけたタスクがchangedになったときハンドラが実行されます。たとえば、「apacheの設定ファイルをcopyして変更した場合はapacheを再起動する」というようなことができます。
ハンドラはhandlers配下に記述します。
handlers:
- name: handler sample
command: touch handler.log
次にnotifyの設定をしましょう。copy sampleタスクにnotifyオプションを追加します。
- name: file copy sample
copy: src=/vagrant/copy.txt
dest=/etc/ansible/copy.txt
owner=root
group=root
mode=0644
notify: handler sample
notifyの値には実行したいハンドラのnameを記述します。名前って大事ですね。
この状態でansible-playbookを実行してみてください。copy sampleタスクはokなので、ハンドラは実行されません。
では、ホストOSからcopy.txtの中身を適当に編集して、再度ansible-playbookしてみましょう。
PLAY [ansible hands-on] ********************************************************
TASK [Gathering Facts] *********************************************************
ok: [127.0.0.1]
TASK [install nkf] *************************************************************
ok: [127.0.0.1]
TASK [directory creation sample] ***********************************************
ok: [127.0.0.1]
TASK [file creation sample] ****************************************************
changed: [127.0.0.1]
TASK [file copy sample] ********************************************************
changed: [127.0.0.1]
TASK [directory copy sample] ***************************************************
ok: [127.0.0.1]
TASK [template copy sample] ****************************************************
ok: [127.0.0.1]
TASK [command sample] **********************************************************
[WARNING]: Consider using file module with state=touch rather than running
touch
changed: [127.0.0.1]
TASK [command error sample] ****************************************************
fatal: [127.0.0.1]: FAILED! => {"changed": false, "cmd": "sl", "failed": true, "msg": "[Errno 2] No such file or directory", "rc": 2}
...ignoring
TASK [shell sample] ************************************************************
changed: [127.0.0.1]
TASK [shell with items sample] *************************************************
changed: [127.0.0.1] => (item=item1)
changed: [127.0.0.1] => (item=item2)
changed: [127.0.0.1] => (item=item3)
TASK [shell with multiple item sample] *****************************************
changed: [127.0.0.1] => (item={u'owner': u'root', u'path': u'item1', u'group': u'root', u'mode': 420})
changed: [127.0.0.1] => (item={u'owner': u'vagrant', u'path': u'item2', u'group': u'vagrant', u'mode': 511})
RUNNING HANDLER [handler sample] ***********************************************
changed: [127.0.0.1]
PLAY RECAP *********************************************************************
127.0.0.1 : ok=13 changed=7 unreachable=0 failed=0
copy sampleがchangedなので、ハンドラが実行されます。最後にRUNNING HANDLERと出力されますね。handler.logが生成されたかどうか確認してみましょう。
ls -l handler.log
# -rw-r--r-- 1 root root 0 Jun 28 08:42 handler.log
良い感じですね。このように実行される条件が決まっているのであれば、commandモジュールなどを使っても冪等性は保たれます。
ちょっとだけ解説
この他にもモジュールはたくさんありますが、既存のモジュールではどうしても対応できない場合があると思います。そういう場合はshellモジュールでゴリゴリのシェルスクリプトを書くのも良いですが(良くない)、モジュールを自作するという手もあります。Ansibleのモジュールはpythonである必要はなく、好きな言語で書くことができます(それこそシェルでも良い)。興味のある人は試してみましょう。
#ハンズオン3〜実用的な設定をしよう〜
目標
もう少し実用的な形にしてみたいと思います。具体的には以下を目指します。
- httpdとphpenvをインストール、あとはgitとかbashとかその辺も設定する
- ホストOSでvagrant upしたあとvagrant provisionすれば全て設定される状態にする
- 変更があったらplaybookやテンプレートファイルを更新して、vagrant provisionすればVMが最新の状態になる
- rolesを使用してメンテナンス性の高い構成にする
手順
rolesとは
先ほどまでは1つのPlaybookに全ての構成を詰め込んでいました。
Ansibleでは、規約に沿ってディレクトリを構成することで、Playbookや設定ファイルなどをロールごとに分割することができ、これをrolesと呼びます。非常に便利です。
http://docs.ansible.com/ansible/playbooks_roles.html
やってみよう
https://github.com/natsukinoue/ansible-vagrant-centos73-docker
こちらにVagrantfileとPlaybook、シェルスクリプト等を含むサンプルリポジトリを用意しました。
使い方は非常に簡単で、cloneしてきたらディレクトリ内でvagrant upするだけです。
CentOS 7.3のboxに以下の様なRolesを設定しています。
- common
- docker
- php
まずcommon roleでとりあえず入れておきたいコマンドをインストールしたり、myuserという名前のsudoユーザを作成したり、ディレクトリを作成したりしています。
dockerのroleでは文字通りdockerをインストールし、myuser権限でdockerを実行できる様に設定。あとdocker-composeも入れてます。
phpのroleはまあ、おまけです。例えばrubyを入れたい人は同じような感じでrbenvあたりを入れればいいと思います。nodeとかでもなんでも。
動作確認はphpでもいいですが、せっかくなのでdockerイメージをビルドしてコンテナを動かしてみます。
cd /vagrant/docker/
docker build .
これで空のCentOS 7.3イメージが構築されると思いますので、docker runしてみてください。何もプロセスがないのでコンテナが落ちます。docker ps -aとかすると残骸が見れるはずです。
というわけで、git cloneとvagrant upだけでdockerが使える状態のCentOS 7.3が手に入りました。楽ちんですね。
なおこのリポジトリは結構急ぎで作ったので、ここが間違ってるとかこういう書き方した方がいいよとかあったらPRもらえると喜びます。
終わりに
ここまで読んでいただいた方、お疲れさまでした。
これで、超初心者から初心者くらいにはなれたのではないでしょうか。(僕のことです)
一応簡単な設定だけならできるようになりました。しかし、この先まだまだやることがあります。たとえば
- SELinuxとか色々なサービスの設定
- ssh経由でリモートサーバをプロビジョニングする。それも複数台
- jinja2テンプレートやrolesを駆使してもっと複雑な設定をする
- ちゃんと公式を読む
- etc..
などなどです。幸いなことに、日本語で書かれた入門向け資料がたくさんありますので、「ansible 入門」「ansible tutorial」などでググりまくりましょう。
それでは、良いサーバ構成管理ライフを。