技術

Fabricでサーバーの初期設定を自動化する

Fabricというアプリケーションのデプロイ作業や管理作業を自動化するためのツール(Pythonモジュール含む)があります。
アプリケーションのデプロイ作業や管理作業の自動化の為のツールと聞くと、訳の分からんDSL覚えなきゃいけないのかだるいなぁとか、謎のアーキテクチャに合わせなきゃいけないのかめんどくせとか思ってしまうタイプだったんですが、FabricはPythonで書けます。仕組みも(概念的にはでありますが)単純です。

ローカルからリモートへSSHで接続して行える作業だったら基本的に何でも自動化出来ます。
で、リモートのサーバーを複数定義しておくと、自動化した作業を1つずつ全部に適用してくれます。

ロードバランサーの下にWebサーバーが複数あって、それらに自動的にアプリケーションを配置・再配置するために使うってのがよくあるパターンだと思います。

今回は、複数のサーバーに同じ初期設定を行うという想定で試してみました。
1台だけセットアップして後はそのディスクイメージをコピーとかしたほうが楽だったりするんですが、それが出来なかったり難しかったり面倒だったりする場合もあるため、この想定も無駄ではないかななんて。

環境は以下のとおり。
ローカルマシン(Fabricを動かすマシン) Mac
サーバー1(ip:192.168.1.21) CentOS5
サーバー2(ip:192.168.1.22) CentOS5
サーバー3(ip:192.168.1.23) CentOS5
あらかじめサーバーはすべて作業用ユーザーtestuserで鍵認証ログインして作業するように設定済み。
(サーバーはUbuntuマシン上のKVMで動かしました。)

まず、ローカルマシンにFabricをインストールします。

easy_install fabric

この3台のサーバーに対して以下の内容を自動で実行するようにスクリプトを書いてみます。
・必要なソフトウェアパッケージのインストール
・Python2.7のインストールなど
・mecabのインストールなど
・簡単な動作確認

以下をfabfile.pyという名前で保存。

# -*- coding: utf-8 -*-

import os.path
from fabric.api import run, sudo, settings, abort, put, cd, env
from fabric.contrib.console import confirm
from fabric.decorators import runs_once

# サーバー情報
env.hosts = ['192.168.1.21', '192.168.1.22', '192.168.1.23']
env.user = 'testuser'
env.password = 'test'
env.key_filename = [os.path.expanduser('~/.ssh/secret_key')]

def setup():
	'''
	セットアップ
	'''
	prepare_setup()
	install_packages()
	install_python27()
	install_setuptools()
	easy_install()
	install_mecab()
	install_mecab_python()

def install_packages():
	'''
	必要なパッケージのインストール
	'''
	sudo('yum -y install '
		'gcc gcc-c++ zlib-devel '
		'tcl-devel tk-devel readline-devel sqlite-devel '
		'dbm-devel db4-devel gdbm-devel ncurses-devel '
		'bzip2-devel openssl-devel pcre pcre-devel '
		'mysql-server mysql-devel screen ',
	pty=True)

def install_python27():
	'''
	python2.7のインストール
	'''
	with cd('~/'):
		with settings(warn_only=True):
			result = run('ls /usr/bin/python2.7')
		if not result.succeeded:
			run('wget http://www.python.org/ftp/python/2.7/Python-2.7.tgz')
			run('tar zxvf Python-2.7.tgz')
			with cd('./Python-2.7'):
				run('./configure --prefix=/usr/local/python --enable-shared')
				run('make')
				sudo('make install', pty=True)
				sudo('cp libpython2.7.so.1.0 /usr/lib/', pty=True)
				sudo('cp libpython2.7.so /usr/lib/', pty=True)
				sudo('/sbin/ldconfig', pty=True)
				sudo('ln -s /usr/local/python/bin/python /usr/bin/python2.7', pty=True)

def install_setuptools():
	'''
	setuptoolsのインストール
	'''
	with cd('~/'):
		with settings(warn_only=True):
			result = run('ls /usr/bin/easy_install2.7')
		if not result.succeeded:
			run('wget http://pypi.python.org/packages/2.7/s/setuptools/setuptools-0.6c11-py2.7.egg')
			sudo('sh setuptools-0.6c11-py2.7.egg', pty=True)
			sudo('ln -s /usr/local/python/bin/easy_install /usr/bin/easy_install2.7', pty=True)

def easy_install():
	'''
	必要なpythonモジュールのインストール
	'''
	sudo('easy_install2.7 tornado httplib2 python-dateutil MySQL-python', pty=True)

def install_mecab():
	'''
	mecabのインストール
	'''
	with cd('~/'):
		with settings(warn_only=True):
			result = run('ls /usr/local/bin/mecab')
		if not result.succeeded:
			run('wget http://sourceforge.net/projects/mecab/files/mecab/0.98/mecab-0.98.tar.gz/download')
			run('tar zxvf mecab-0.98.tar.gz')
			with cd('./mecab-0.98'):
				run('./configure --enable-utf8-only')
				run('make')
				sudo('make install', pty=True)
		with settings(warn_only=True):
			result = run('ls /usr/local/lib/mecab/dic/naist-jdic')
		if not result.succeeded:
			run('wget http://jaist.dl.sourceforge.jp/naist-jdic/48487/mecab-naist-jdic-0.6.3-20100801.tar.gz')
			run('tar zxvf mecab-naist-jdic-0.6.3-20100801.tar.gz')
			with cd('mecab-naist-jdic-0.6.3-20100801'):
				run('./configure --with-charset=utf8')
				run('make')
				sudo('make install', pty=True)
			sudo(r"sed 's/^dicdir = .*$/dicdir = \/usr\/local\/lib\/mecab\/dic\/naist-jdic/' /usr/local/etc/mecabrc | tee /usr/local/etc/mecabrc", pty=True)

def install_mecab_python():
	'''
	mecab-pythonのインストール
	'''
	with cd('~/'):
		with settings(warn_only=True):
			result = run("python2.7 -c 'import MeCab'")
		if not result.succeeded:
			run('wget http://jaist.dl.sourceforge.net/project/mecab/mecab-python/0.98/mecab-python-0.98.tar.gz')
			run('tar zxvf mecab-python-0.98.tar.gz')
			with cd('./mecab-python-0.98'):
				run('python2.7 setup.py build')
				sudo('export PATH=$PATH:/usr/local/bin; python2.7 setup.py install', pty=True)
			sudo("echo '/usr/local/lib' >> /etc/ld.so.conf", pty=True)
			sudo('/sbin/ldconfig', pty=True)

def test():
	'''
	setupがうまく行ってるかテスト
	'''
	prepare_test()
	put('./testscript.py', '/tmp')
	run('python2.7 /tmp/testscript.py')

@runs_once
def prepare_setup():
	'''
	セットアップ前のローカル側での準備
	'''
	pass

@runs_once
def prepare_test():
	'''
	テスト前のローカル側での準備
	'''
	script = '''# -*- coding: utf-8 -*-
import MeCab
m = MeCab.Tagger()
print m.parse('本日は晴天なり')
	'''
	with open('./testscript.py', 'wb') as f:
		f.write(script)

これで以下のコマンドでひと通りのセットアップと簡易的なテストが自動的に実行できます。

fab setup test

簡単ですね!

日常的にリモートのサーバーに接続してコマンド打ってる人だったら、PythonもFabricも知らなくてもやってることは分かると思います。
一部分かりづらいと思われる点だけちょっと説明すると、

・sudo関数の引数pty=TrueはTTY経由で接続するために付けています。
コレを付けないと最近のCentOSとかでは「sudo: sorry, you must have a tty to run sudo」というエラーが出る事になります。
TTY経由以外でsudoを使うとパスワード入力をオウム返ししてしまってセキュリティ的にアレなので最近はデフォでTTY経由以外は通さない設定になってるらすぃ。

・cd関数はリモートでchange directoryするって事ですが、withを付けることによってブロックを抜けるときにcurrent directoryを元に戻してくれます。

・Fabricはコマンドが失敗するとそこでAbortして止まってしまうんですが、
with settings(warn_only=True): のブロックでは警告を出すだけでAbortしないようにできます。
失敗しても全体には影響のないコマンドや、成功失敗で分岐したいときに使えます。

・@runs_onceデコレータを付けた関数は実行を1回だけに限ることができます。
他の関数はenv.hostsに設定されたサーバーの数だけ実行されることになります。

今回のはまだ初歩的な使い方だと思います。僕自身今日使い始めたばかりです。
サーバー毎に処理内容を変えたり、サーバーに役割を設定して役割毎に処理内容を変えたりなども出来るそうです。
というかPythonスクリプトそのものなのでやろうと思えば何でも出来ると思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です



※画像をクリックして別の画像を表示

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください