7.07.2013

Setup Shogi-server on AWS with Fabric

AWS: shogi-server を Fabric で構築する

目的

コンピュータ将棋のネット対局用サーバ shogi-server を AWS の EC2 インスタンス上に構築する。

このとき、全ての設定作業を Fabric で完結させたい。

 

前提

 

準備するもの

 

iptables

shogi-server (CSAプロトコル) で使う 4081 ポートを許可、ロギング設定など。

# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 4081 -j ACCEPT
-N LOGGING
-A INPUT -j LOGGING
-A LOGGING -j LOG --log-level warning --log-prefix "DROP:" -m limit
-A LOGGING -j DROP
COMMIT

 

sudoers

sudo の際に環境変数 PATH を引き継ぐようにしないと、なぜか Ruby のインストールに失敗してしまう。
env_keep に PATH を追記した sudoers ファイルをあらかじめ用意しておく。

# diff -u ./sudoers.20130707 ./sudoers
--- ./sudoers.20130707	2013-05-10 03:53:29.636195742 +0900
+++ ./sudoers	2013-07-07 11:47:48.647961526 +0900
@@ -76,6 +76,7 @@
 Defaults    env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"
 Defaults    env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"
 Defaults    env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"
+Defaults    env_keep += "PATH"
 
 #
 # Adding HOME to env_keep may enable a user to run unrestricted

 

起動スクリプト

shogi-server をサービスとして登録し、起動/停止を行うためのスクリプト。
/etc/init.d/shogi-server として配備。

PIDファイルを /var/run、ログを /var/log/shogi-server 配下に作成している。

サービスは専用ユーザ shogi で実行される。

#!/bin/bash
#
# Start/Stop shogi-server daemon
#
# chkconfig: 2345 85 15
# description: shogi-server daemon mode

PROC_NAME=shogi-server
PROC_USER=shogi
PROC_GROUP=shogi
PROC_BIN=/opt/shogi-server/shogi-server
PID_FILE=/var/run/shogi-server.pid
DAEMON_LOG_DIR=/var/log/shogi-server
PLAYER_LOG_DIR=$DAEMON_LOG_DIR/player-logs
CONSOLE_LOG=$DAEMON_LOG_DIR/console.log

EVENT_NAME=event1
PORT_NUMBER=4081

# Sanity checks
[ -x $PROC_BIN ] || exit 1

# Source function library & LSB routines
. /etc/rc.d/init.d/functions

RETVAL=0

start()
{
    echo -n $"Starting $PROC_NAME: "

    mkdir -p $DAEMON_LOG_DIR $PLAYER_LOG_DIR
    touch $PID_FILE
    chown $PROC_USER:$PROC_GROUP $DAEMON_LOG_DIR $PLAYER_LOG_DIR $PID_FILE

    su - $PROC_USER -c \
        "ruby $PROC_BIN --daemon $DAEMON_LOG_DIR --pid-file $PID_FILE \
        --player-log-dir $PLAYER_LOG_DIR $EVENT_NAME $PORT_NUMBER \
        > $CONSOLE_LOG 2>&1"
    RETVAL=$?
    if [ $RETVAL -eq 0 ]; then
        success
    else
        rm -f $PID_FILE
        failure
    fi
    echo
}

stop()
{
    echo -n $"Stopping $PROC_NAME:"
    if [ -f $PID_FILE ]; then
        killproc -p $PID_FILE $PROC_NAME -TERM
        rm -f $PID_FILE
    fi
    RETVAL=$?
    [ $RETVAL -eq 0 ] && success || failure
    echo
}

case "$1" in
    start)
        start
        RETVAL=$?
        ;;
    stop)
        stop
        RETVAL=$?
        ;;
    status)
        status -p $PID_FILE $PROC_BIN
        RETVAL=$?
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart}"
        RETVAL=2
        ;;
esac

exit $RETVAL

 

logrotate 定義ファイル

ログファイルの洗い替えは logrotate を利用する。
/etc/logrotate.d/shogi-server として配備。

/var/log/shogi-server/shogi-server.log
/var/log/shogi-server/player-logs/*.log {
    weekly
    rotate 4
    missingok
    notifempty
    copytruncate
    compress
}

 

Fabric 用スクリプト

Fabric で使う Python スクリプト。
env.hosts はハードコーディングして使ってもよい。

標準APIの put 関数にも sudo 機能があるものの、SELinux 対応やバックアップ作成などが物足りなかったので
独自の _put という関数でラップしている。

EC2 のファイアウォール設定もこのスクリプトに含めているが、分離させた方がいいかもしれない。

# -*- coding: utf-8 -*-
"""
Installing shogi-server and its dependencies.
"""

import os
import json
from fabric.api import *
from fabric.decorators import runs_once, roles, task

env.user = 'ec2-user'
# env.hosts = []
env.use_ssh_config = True

WORK_DIR = os.path.dirname(os.path.abspath(__file__))

APL_USER = 'shogi'
APL_GROUP = 'shogi'
APL_HOME = '/home/%s' % APL_USER

GIT_RBENV = 'git://github.com/sstephenson/rbenv.git'
GIT_RUBY_BUILD = 'git://github.com/sstephenson/ruby-build.git'
GIT_SHOGI_SERVER = \
    'git://git.sourceforge.jp/gitroot/shogi-server/shogi-server.git'

RUBY_VERSION = '1.9.3-p448'

EC2_SECURITY_GROUP = 'quick-start-1'


@task
def setup():
    """Setup tasks for shogi-server."""

    setup_os_user()
    setup_firewall()
    install_ruby()
    install_shogi_server()


@task
def setup_os_user():
    """Add 'shogi' user and 'shogi' group."""

    if sudo('id %s' % APL_USER, quiet=True).succeeded:
        return

    # Add user and group.
    sudo('groupadd -g 501 %s' % APL_GROUP)
    sudo('useradd -u 501 -g %s %s' % (APL_GROUP, APL_USER))

    # Copy sudoers.
    _put('%s/sudoers' % WORK_DIR, '/etc/sudoers', '440', 'root', 'root')


@task
def setup_firewall():
    """Configure linux and EC2 firewall.
        AWS CLI and its connection settings are required."""

    # Linux iptables
    _put('%s/iptables' % WORK_DIR, '/etc/sysconfig/iptables',
         '600', 'root', 'root')
    sudo('service iptables restart')

    # EC2 firewall
    port = 4081
    cidr = '0.0.0.0/0'
    old = local('aws ec2 describe-security-groups', capture=True)
    j = json.loads(old)

    found = (EC2_SECURITY_GROUP, port, cidr) in (
        (a['GroupName'], b['FromPort'], c['CidrIp'])
        for a in j['SecurityGroups']
        for b in a['IpPermissions']
        for c in b['IpRanges'])

    if not found:
        opts = '--group-name %s --ip-protocol tcp ' % EC2_SECURITY_GROUP
        opts += '--from-port %d --to-port %d --cidr-ip %s' % (port, port, cidr)
        local('aws ec2 authorize-security-group-ingress %s' % opts)


@task
def install_ruby():
    """Install git, rbenv, ruby-build and ruby."""

    # Install git.
    sudo('yum -y install git')

    # Install rbenv.
    with cd('/usr/local'):
        sudo('git clone %s %s' % (GIT_RBENV, 'rbenv'))
        sudo('mkdir rbenv/shims rbenv/versions')

    # Install ruby-build.
    with cd('/usr/local'):
        sudo('git clone %s %s' % (GIT_RUBY_BUILD, 'ruby-build'))
    with cd('/usr/local/ruby-build'):
        sudo('./install.sh')

    # Create system-level profile.
    profile = '/etc/profile.d/rbenv.sh'
    sudo("""echo 'export RBENV_ROOT="/usr/local/rbenv"' > %s""" % profile)
    sudo("""echo 'export PATH="/usr/local/rbenv/bin:$PATH"' >> %s""" % profile)
    sudo("""echo 'eval "$(rbenv init -)"' >> %s""" % profile)

    # Install requirements.
    sudo('yum -y groupinstall "Development Tools"')
    sudo('yum -y install openssl-devel readline-devel zlib-devel')

    # Install ruby.
    sudo('su - -c "rbenv install 1.9.3-p448"')
    sudo('su - -c "rbenv global 1.9.3-p448"')
    sudo('su - -c "rbenv rehash"')


@task
def install_shogi_server():
    """Install and start shogi-server as a service."""

    shogi_path = '/opt/shogi-server'

    if sudo('test -d %s' % shogi_path, quiet=True).succeeded:
        return

    # Clone from repository.
    sudo('git clone %s %s' % (GIT_SHOGI_SERVER, shogi_path))
    sudo('chown -R %s:%s %s' % (APL_USER, APL_GROUP, shogi_path))

    # Copy init script.
    _put('%s/shogi-server.init' % WORK_DIR, '/etc/init.d/shogi-server',
         '755', 'root', 'root')

    # Add and start service.
    sudo('chkconfig --add shogi-server')
    sudo('service shogi-server start')

    # Copy logrotate setting.
    _put('%s/shogi-server.logrotate' % WORK_DIR,
         '/etc/logrotate.d/shogi-server', '644', 'root', 'root')


def _put(src, dst, mode, owner, group):
    tmp = '/tmp/%s.tmp' % src.split('/')[-1]
    backup_opts = '--backup=simple --suffix=.`date +%Y%m%d`'
    permission_opts = '-m %s -o %s -g %s' % (mode, owner, group)

    put(local_path=src, remote_path=tmp)
    sudo('install %s %s %s %s' % (backup_opts, permission_opts, tmp, dst))
    sudo('rm -f %s' % tmp)

 

実行方法

fab コマンドで実行。Microインスタンスで20分弱で完了。

$ fab -f ./shogi-server.py --list
$ fab -f ./shogi-server.py -H 対象サーバ setup

0 件のコメント:

コメントを投稿