Ansibleで自社Wordpressを移行を自動化

先日弊社Webサイト、http://www.cflat-inc.com のサーバーをAWSからさくらVPSに移行しました。元々t1.microを使っていたのですが、さすがに非力すぎて時折アクセスできないこともありました。かといってsmallを使うとスペックの割にコストがかかると考えさくらVPSを採用しました。

サーバーが決まったら次は環境の移行を行いますが、失敗することなく同一の環境を構築するためにまずはVirtualBox仮想マシンで動作を検証し、問題がなければさくらVPSでセットアップを行うという流れを取ることにしました。つまり2度セットアップを行うことになります。

2度同じセットアップを行うのはミスが入り込む余地だらけですし、そもそも面倒なのでいわゆるプロビジョニングツールを使うことにしました。PuppetとかChefに代表されるアレですね。今回はその中でも(機能的に)軽量であるという噂のAnsibleをトライアルするいい機会だと考え、Ansibleを使って移行を行うことにしました。

ちなみにAnsibleはhomebrewでインストールすることができます。↓でOK。

$ brew install ansible

Ansible 最終形

Ansibleでは、playbookというYAML形式の設定ファイルのようなものに処理を色々と書いておいて、playbookを指定して実行することで対象のサーバーを操作することができます。

今回最終的には次のようなコマンドを実行することになります。

$ ansible-playbook -i hosts all.yml

これはhostsというファイルに書いてあるサーバーに対してall.ymlに書いてある処理をしてね、という意味になります。

まずhostsですが、次のようになっています。

# hosts

[server]
# 192.168.33.11
cfalt2

コメントアウトしている一つ目のIPはVagrantで立ちあげたVirtualBoxIPアドレスです。テスト時はこのコメントを外します。

2つ目のcflat2とあるのは、~/.ssh/configに書いてある設定の中のホスト名です。本番サーバーを意味します。

次にplaybookのall.ymlはこうなっています。

# all.yml

- include: ssh-iptables.yml
- include: yum.yml
- include: lamp.yml
- include: mysql-secure-installation.yml
- include: mysql-initialize.yml
- include: prepare-contents.yml

playbookでは他のplaybookをincludeすることができるようになっています。all.ymlではもう少し細かい単位に分けたplaybookをincludeするだけにしてあります。書かれている内容は上から順番に実行されます。

ざっくり言うと、

  1. iptablesの設定して
  2. yumの設定して
  3. ApachePHPMySQL入れて
  4. MySQLのインストールして
  5. MySQLの初期設定して(ここは↑とまとめてもよかった)
  6. 元サイトのコンテンツを入れる

という流れになっています。

各playbook解説

playbookを載せると長くなるかもしれませんが、せっかくの事例紹介なので全部お見せします。

ssh-iptables.yml

まずはSSHiptablesの設定をするplaybook。

- name: change ssh settings and iptables
  hosts: server
  sudo: yes
  tasks:
    - name: create iptables
      template: src=files/iptables.j2 dest=/etc/sysconfig/iptables

    - name: disallow root SSH access
      lineinfile: dest=/etc/ssh/sshd_config regexp="^#PermitRootLogin " line="PermitRootLogin no" state=present
    - name: disallow password authentication
      lineinfile: dest=/etc/ssh/sshd_config regexp="^#PasswordAuthentication " line="PasswordAuthentication no" state=present

    # NOTE: Combine two tasks to keep ansible access with port 22
    - name: restart sshd and restart iptables
      shell: service sshd restart && /etc/init.d/iptables restart

いきなりAnsible固有の色々がでてきたのでここで多少解説をいれておきます。

項目 意味
name その部分の名前、確かログに出るぐらいの意味
hosts 対象ホスト、hostsの[server]セクションにある全てのhostを対象にする
sudo rootで実行するかどうか
tasks 処理を書くところ
template テンプレートファイルをdestにコピーする。変数展開も使える。
lineinfile destファイルに指定した行があることを確約したり、置換したりする
shell shellコマンドを実行する

このplaybookの中でtemplateが参照しているfiles/iptables.j2iptablesの設定を書いておきます。

yum.yml

続いてyumリポジトリを追加します。OSに最初から設定されているリポジトリだけでは足りなかったので。書くのが遅れましたがOSはCentOS 6.3です。

commandyumとかありますが、なんとなく読めると思います。

- name: yum setting
  hosts: server
  sudo: yes
  tasks:
    - name: update yum
      command: yum -y update

    # repository
    - name: add repository 'rpmforge-repo'
      yum: name=http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm state=present
    - name: add repository 'remi-repo'
      yum: name=http://rpms.famillecollet.com/enterprise/remi-release-6.rpm state=present

lamp.yml

次にApacheMySQLPHPを入れましょう。

- name: Installing Apache, PHP, MySQL
  hosts: server
  sudo: yes
  vars:
    httpd_port: 80
    mysql_port: 3306

  tasks:
    # Apache
    - name: install apache
      yum: name=httpd enablerepo=remi,epel,rpmforge state=present
    - name: start httpd
      service: name=httpd state=started enabled=yes
    - name: add iptables rule for httpd
      lineinfile: dest=/etc/sysconfig/iptables regexp="{{ httpd_port }}" line="-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport {{ httpd_port }} -j ACCEPT" insertbefore="^# add end" state=present
      notify: restart iptables
    # PHP
    - name: install php
      yum: name={{ item }} enablerepo=remi,epel,rpmforge state=present
      with_items:
        - php
        - php-mysql
    # MySQL
    - name: install mysql
      yum: name={{ item }} enablerepo=remi,epel,rpmforge state=present
      with_items:
        - mysql
        - mysql-server
        - MySQL-python
    - name: add iptables rule for mysql
      lineinfile: dest=/etc/sysconfig/iptables regexp="{{ mysql_port }}" line="-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport {{ mysql_port }} -j ACCEPT" insertbefore="^# add end" state=present
      notify: restart iptables

  handlers:
    - name: restart iptables
      service: name=iptables state=restarted

長いですね。ここで見るべきはvarshandlersnotifywith_itemsあたりでしょうか。

項目 意味
vars 変数を使える。あとから参照するには{{ variable }} などとする
handlers notifyとセットで使う。他のタスクから呼び出せる共通の処理。
notify handlersに書いた処理の名前を指定することでその処理を呼び出せる。
with_items 複数の項目に同じコマンドを実行したいときに使う。項目は{{ item }}として参照できる。

mysql-secure-installation.yml

これもなんとなく読めると思います。

  • rootパスワードの設定
  • template.my.cnfファイルをコピー

などを行います。

-  name: MySQL setup
   hosts: server
   sudo: yes
   vars:
     root_db_password: XXXXXXXX
     mysql_root_password: XXXXXXXXX
   tasks:

     - name: ensure mysql is running and starts on boot
       service: name=mysqld state=started enabled=true

     # Need to do this for idempotency, see
     # http://ansible.cc/docs/modules.html#mysql-user
     - name: update mysql root password for all root accounts
       mysql_user: name=root host=localhost password={{ root_db_password }}

     - name: copy .my.cnf file with root password credentials
       template: src=files/.my.cnf dest=/root/.my.cnf owner=root mode=0600
  
     - name: update mysql root password for all root accounts
       mysql_user: name=root host={{ item }} password={{ root_db_password }}
       with_items:
         - 127.0.0.1
         - ::1
  
     - name: ensure anonymous users are not in the database
       mysql_user: name='' host={{ item }} state=absent
       with_items:
         - localhost
         - 192.168.33.11
         
     - name: remove the test database
       mysql_db: name=test state=absent

mysql-initialize.yml

ここではサイト用のMySQLユーザーやデータベースを作っています。

- name: Creating DB User
  hosts: server
  vars:
    dbname: dbname
    dbuser: dbuser
    loginuser: root
    loginpassword: XXXXXXXX

  tasks:
    - name: create database for wordpress
      mysql_db: db={{ dbname }} state=present encoding=utf8 login_user={{ loginuser }} login_password={{ loginpassword }}

    - name: create a user for wordpress
      mysql_user: name={{ dbuser }} password="YYYYYYYY" priv={{ dbname }}.*:ALL state=present login_user={{ loginuser }} login_password={{ loginpassword }}

prepare-contents.yml

このplaybookではコンテンツをサーバーにセットアップします。

ウェブサイトにはWordpressを用いています。このWordpressのファイル(テーマやプラグイン含む)はgitで管理しているのでgitから取得します。また、データベースに入っているデータ類はあらかじめmysqldumpなどでエクスポートしておきます。

  • gitをインストール
  • wordpressをgitからclone
  • apacheのvirtualhost設定をコピーする
  • apacheを再起動
  • sqlファイルをコピーし、DBにインポート
- name: instal Git and clone wordpress from git repo
  hosts: server
  sudo: yes
  tasks:
    # Git
    - name: install git
      yum: name=git enablerepo=remi,epel,rpmforge state=present
      
    - name: create contents dir
      file: path=/var/www/contents state=directory owner=apache group=apache

    - name: clone from repo
      git: repo=your_repo dest=/var/www/path/to/dir

    - name: copy vhost
      template: src=files/virtual.conf dest=/etc/httpd/conf.d/virtual.conf owner=root mode=0644
      notify: restart httpd

    # initial data
    - name: copy database.sql to server
      copy: src=files/database.sql dest=/tmp

    - name: import database.sql to db
      mysql_db: name=dbname state=import target=/tmp/database.sql
      
  handlers:
    - name: restart httpd
      service: name=httpd state=restarted

以上のplaybookを全て実行するとサーバーのセットアップからコンテンツの移行までを一括して行ってくれます。

$ ansible-playbook -i hosts all.yml

この状態でサーバーにアクセスすると元サイトと同じ状態になっていることがわかると思います。あとはドメインの設定を変更して、新サーバーを指すようにしてあげれば完了です。

Ansibleのようなツールを使うことで仮想環境でテストしてから本番に適用というのがとても楽ですし、次に同じようなの移行が必要になっても、最後のコンテンツ部分(gitとかsql)の部分を新しくしておくだけで、あとの手順はコマンド一発になり非常に便利です。サーバーの状態がコードとして書かれているというのもよく言われるメリットですね。

Ansibleを使うことで学習コストがかかりましたが、手作業でやってしまおうという誘惑にまけなくてよかったと感じています。

Ansible、Chefに比べるとシンプルで使いやすいという印象をうけました。今回のような小さめのプロジェクトではAnsible優位な気がします。