ありlog

コンピュータ楽しい

fabricを使ってwebサイト(django)の環境設定やdeployを自動化した

自分のwebサイトのapacheの設定やら何やら忘れてしょうがないので、この機会にfabricで環境設定やらdeployなりの手順をコードに落として自動化することにした。

サーバはさくらVPSのCent OS 6.8。

以下メモとして。

前作業(サーバの初期設定)

サーバを新しくしたので最初から。こういうとこもコード化できたらいいが、とりあえず手作業でやった。

  • rootユーザのパスワードを変更
  • 一般ユーザを追加、sudo権限を与える
  • サーバアクセス用のssh鍵をlocalで生成し、scpでサーバに送る。適切なパーミッションに設定する
  • 公開鍵認証でのみログインできるようにssh接続設定を変更
  • local PCでssh configに新しいサーバの情報を追加し、ログインしやすくする(fabricでも使う)
  • ファイアウォールの設定

さくらのVPS サーバの初期設定ガイド

作業ログ

rootでserverにlogin

# root passwd change, add general user
## root login
ssh root@hoge.sakura.ne.jp

rootのpassword変更、一般userの追加

# rootのパスワード変更
passwd

# 一般ユーザの追加、パスワード変更
useradd hogeuser
passwd hogeuser

一般userにsudo権限を与える

# 一般ユーザにsudo権限を与える
## login as root
ssh root@hoge.vs.sakura.ne.jp

usermod -G wheel hogeuser

visudo

wheelにすべての権限を与えるよう変更

## Allows people in group wheel to run all commands
%wheel  ALL=(ALL)       ALL

localでssh鍵を生成。scpでserverに送る。 ssh鍵以外のloginを禁止するように/etc/ssh/sshd_configを変更する

# generate ssh
ssh-keygen -t rsa -C sakura_2nd_server -f ~/.ssh/id_rsa_sakura2

# scp ssh-key to server
scp ~/.ssh/id_rsa_sakura2.pub hogeuser@hoge.vs.sakura.ne.jp:

# login to server as general user
ssh hogeuser@hoge.vs.sakura.ne.jp

scpでserverに送った公開鍵のpermissionを正しく設定する

mkdir .ssh
chmod 700 .ssh
cat id_rsa_sakura2.pub >.ssh/authorized_keys
chmod 600 .ssh/authorized_keys
rm id_rsa_sakura2.pub

# change sudo ssh setting
sudo vim /etc/ssh/sshd_config

公開鍵認証以外でssh接続できないようにする

#Port22
→Port****

#PermitRootLogin yes
→PermitRootLogin no

PasswordAuthentication yes
→PasswordAuthentication no

sshd_configをreloadさせる

sudo /etc/rc.d/init.d/sshd reload

localから秘密鍵以外でloginできないか確認する

# loginできないのを確認
ssh root@hoge.vs.sakura.ne.jp
ssh hogeuser@hoge.vs.sakura.ne.jp

# loginできるのを確認
ssh hogeuser@hoge.vs.sakura.ne.jp -i ~/.ssh/id_rsa_sakura2

# sshd_configを編集
emacs ~/.ssh/config

新しいserverの情報をssh configに追加

Host                    sakura2
                        HostName        hoge.vs.sakura.ne.jp
                        Port            22
                        IdentityFile    ~/.ssh/id_rsa_sakura2
                        User            hogeuser

loginできるか確認

ssh sakura2

あとはファイアウォールの設定など

(参考)

iptablesの設定方法

図解でわかる Linuxサーバ構築・設定のすべて

図解でわかる Linuxサーバ構築・設定のすべて

ここからはfabricで

fabfile

接続先サーバの設定

自分しか利用者がいないので、~/.ssh/config の情報を再利用した。

from fabric.api import *

env.use_ssh_config = True
env.hosts = ['sakura2']

これで自動的にfabricが~/.ssh/config を読み込んでくれ、'sakura2'という名のhostのhost name, port, 公開鍵, user nameなどを使ってくれる。

ネイティブのSSH configファイルの活用 (fabric公式ドキュメント)

nginxのinstall

nginxの公式にもあるように、yum install nginxでinstallされるnginxは古かったりするので、nginx yum repositoryを追加する。

from fabric.api import *
from fabric.contrib.files import upload_template, exists, append

yum_repo_folder = '/etc/yum.repos.d'
nginx_version = '1.10.1'
nginx_dir = 'nginx-{}'.format(nginx_version)
nginx_url = 'http://nginx.org/download/{}.tar.gz'.format(nginx_dir)
nginx_conf_path = '/etc/nginx/conf.d/arieee.conf'

def add_nginx_yum_repo():
    # local PCのrepoファイルをサーバに送る
    with lcd(LOCAL_APP_FOLDER): # mac local
        template = 'conf/nginx.repo'
        upload_template(template, yum_repo_folder, use_sudo=True)

def get_nginx():
    add_nginx_yum_repo()
    sudo('yum -y update')
    sudo('yum -y install nginx')

def start_nginx():
    sudo('/usr/sbin/nginx')

def reload_nginx():
    sudo('/usr/sbin/nginx -s reload')

@task
def install_nginx():
    get_nginx()
    start_nginx()
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

python環境(python, pip, virtualenv)のinstall

pyenvでinstallしたpythonはどのユーザからも共通して使ってほしいので、~/.bash_profileではなく、/etc/profile.d下に置いた。(他の方法あるのかな?)

def install_python_dev():
    sudo('yum -y update')
    sudo('yum -y install python-devel')
    # pyenvでのpython installに必要なlibrary
    # https://github.com/yyuu/pyenv/wiki/Common-build-problems
    sudo('yum -y install zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel')

def install_pyenv():
    sudo('git clone https://github.com/yyuu/pyenv.git /usr/local/.pyenv')
    # システム全体にpythonのversionを適用したいので、/etc/profile.d/ に設定を書き込む
    sudo('echo \'export PYENV_ROOT="/usr/local/.pyenv"\' >> /etc/profile.d/pyenv.sh')
    sudo('echo \'export PATH="$PYENV_ROOT/bin:$PATH"\' >> /etc/profile.d/pyenv.sh')
    sudo('echo \'eval "$(pyenv init -)"\' >> /etc/profile.d/pyenv.sh')
    sudo('exec $SHELL -l')

    sudo('pyenv install 2.7.11')
    sudo('pyenv global 2.7.11')
    sudo('pyenv rehash')

def install_pip():
    with cd('/tmp'):
        run('wget https://bootstrap.pypa.io/get-pip.py')
        sudo('python get-pip.py')
        # upgrade pip
        sudo('pip install -U pip')
        # install uwsgi, virtualenv
        sudo('pip install uwsgi')
        sudo('pip install virtualenv')

@task
def setup_python_env():
    install_python_dev()
    install_pyenv()
    install_pip()

django, uwsgiのための準備

  • uwsgi用のwwwユーザを作成
  • uwsgi用のlogフォルダ、runフォルダを作成
  • uwsgiをemperor modeのための設定ファイルフォルダを作成 (/etc/uwsgi/vassals)
  • django application用のvirtualenvを作成

/var/run/uwsgiは、将来的にuwsgiのプロセスをプログラムから起動・終了したりするために、プロセスIDのファイルを格納するフォルダとして使う。

def create_www_user():
    # cent os doesn't have www-data
    sudo('groupadd www-data')
    sudo('useradd -d /home/www-data -g www-data -r -s /sbin/nologin www-data')

def setup_uwsgi_emperor():
    sudo('mkdir -p /var/log/uwsgi')
    sudo('mkdir -p /var/run/uwsgi')
    sudo('chown www-data:www-data /var/log/uwsgi')
    sudo('chown www-data:www-data /var/run/uwsgi')

    # create a directory for the vassals
    sudo('mkdir -p /etc/uwsgi/vassals')

def setup_virtualenv():
    with cd(BASE_FOLDER):
        run('virtualenv venv_arieee')

nginx, wsgiの設定ファイルのupload

nginx, wsgiの設定ファイルについては、以下を参考にした。

def upload_nginx_conf():
    with lcd(LOCAL_APP_FOLDER): # mac local
        template = 'conf/nginx.tmpl' # local file
        context = {'app_folder': APP_FOLDER, 'remote_static_folder':REMOTE_STATIC_ROOT}
        upload_template(template, nginx_conf_path, context, use_sudo=True)

    # delete existing default.conf
    if exists('/etc/nginx/conf.d/default.conf'):
        sudo('mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.org')

def upload_uwsgi_conf():
    if not exists(os.path.dirname(uwsgi_ini_path)):
        run('mkdir -p {}'.format(os.path.dirname(uwsgi_ini_path)))

    with lcd(LOCAL_APP_FOLDER): # mac local
        template = 'conf/arieee_wsgi.ini.tmpl'
        context = {'app_folder': APP_FOLDER, 'base_folder': BASE_FOLDER, 'django_folder': DJANGO_FOLDER}
        upload_template(template, uwsgi_ini_path, context, use_sudo=True)

    # symlink from the default config directory to your config file
    sudo('ln -sf {} /etc/uwsgi/vassals/'.format(uwsgi_ini_path))

    reload_nginx()

def run_uwsgi_emperor():
    sudo('uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data')
# arieee_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
chdir           = %(django_folder)s
# Django's wsgi file
module          = arieee.wsgi
# the virtualenv (full path)
home            = %(base_folder)s/venv_arieee

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 1
# the socket (use the full path to be safe
socket          = /var/run/uwsgi/arieee.net.sock
# pid
pidfile     = /var/run/uwsgi/arieee.net.pid
# log
daemonize   = /var/log/uwsgi/arieee.net.log
# ... with appropriate permissions - may be needed
chmod-socket    = 666
# clear environment on exit
vacuum          = true
# the upstream component nginx needs to connect to
upstream django {
     server unix:///var/run/uwsgi/arieee.net.sock;
     # server 127.0.0.1:8001;
}

# configuration of the server
server {
       # the port your site will be served on
       listen 80;
       # the domain name it will serve for
       server_name  .arieee.net;
       charset      utf-8;

       # max upload size
       client_max_body_size 75M;

       # Django media
       location /media  {
       alias %(app_folder)s/arieee/media;
       }

       location /static {
       alias %(remote_static_folder)s;
       }

       # Finally, send all non-media requests to the Django server.
       location / {
            uwsgi_pass  django;
        include     %(app_folder)s/uwsgi_params;
       }
}

deploy

@task
def first_deploy():
    create_www_user()
    setup_uwsgi_emperor()
    setup_virtualenv()
    deploy()

@task
def deploy():
    with settings(warn_only=True):
        if run("test -d {}".format(APP_FOLDER)).failed:
            run("git clone {} {}".format(GIT_REPOSITORY, APP_FOLDER))
        with cd(APP_FOLDER):
            run("git pull")

    # install needed python libraries
    with prefix('source {}/bin/activate'.format(VENV_FOLDER)):
        with cd(APP_FOLDER):
            run('pip install -r requirements.txt')

    # uplodad nginx conf
    upload_nginx_conf()

    # uwsgi
    upload_uwsgi_conf()
    setup_uwsgi_emperor()
    run_uwsgi_emperor()

以上のfabricのtaskを以下のように実行

fab install_nginx setup_python_env first_deploy

めちゃくちゃ疲れた。。dockerとか使ったらもっと楽になるのかな? 自動化するのは楽しいけどあんまり生産的じゃないので、こういう作業はできるだけ最小化したい。。 あとwsgi, uwsgiがまだあんまりわかっていないのでもうちょっと勉強したい。

[参考]

amazonで見ている本にkoboの価格を表示するchrome拡張を作りました

去年の秋からコツコツと所有本を電子化してます.なので新しい本を買う時もできるだけ電子書籍版の方を買いたい. そこで電子書籍版があるかどうか,あったらどこが一番安いかをamazon / kobo (rakuten books) の両方探したりするのですが,まあめんどいです.

ということで,amazonのページにkoboの価格とリンクを表示するchrome拡張を作りました. 自分用に作ったら思ったより便利だったのでchrome storeで公開しています.

こだわりポイント

あくまでamazonのページデザインに沿って表示するので目障りになりません.

f:id:arieee0:20141122201040p:plain

しかもkoboの価格がkindleよりも高ければ灰色に,

f:id:arieee0:20141122172430p:plain

安ければ赤色に,

f:id:arieee0:20141122172412p:plain

など細かい謎の気配りをしてくれます.できるやつです.



ちなみに最初は楽天ぽく表示するバージョンも作ってみて個人的には気に入ってたんですが, 友達に見せたところすごい顔されたのでボツになりました.これです.

f:id:arieee0:20141122183424p:plain

いいですね〜〜〜

だって値段高いのにkoboの方を買いたくなりませんか



なりませんか.すみません.

どんな人に便利か

  • kindle/koboの両方使っている人
  • とにかく安く本が読みたい人

国内2大電子書籍プラットフォームであろうkindleとkoboですが,やはり国内の本の品揃え・価格に関してはkindleの方が現時点では優れていると思います.

しかし,たまにkoboしか取り扱っていなかったり,koboの方が安い時があります.

このchrome拡張はamazonのページ内でkoboの情報を表示するので,そのような場合でも誤って紙の本や高いkindle版を買うことを避けられます.

  • 洋書を読む人

koboが強いのが技術書などの洋書です.

洋書にはkoboしか取り扱いがない本,koboの方が大幅に安い本が結構あります.

よく洋書を買う人は入れても損はないと思います.



ということでKoboでは、この価格 - Chrome Web Store よければ使ってみてください.

chrome拡張の作り方

以下技術情報.

今回chrome拡張初めて作りましたが,意外に(仕組みは)簡単でした.

まず一つのフォルダを用意し,その中にmanifest.jsonというファイルを置いて,プログラムのメタ情報を記述します

{
    "name": "Koboでは、この価格",
    "version": "1.0.3",
    "manifest_version":2,

    "description":"amazonで見ている本のkoboでの価格を表示します",
    "icons":{
        "16":"images/shelf16.png",
        "64":"images/shelf64.png",
        "128":"images/shelf128.png"
    },

    "permissions":[
        "http://www.amazon.co.jp/*"
    ],

    "content_scripts":[
        {
            "css":["style.css"],
            "matches":["http://www.amazon.co.jp/*"],
            "js":["jquery.js","contentscript.js"]
        }
    ],

    "web_accessible_resources": ["images/ajax-loader.gif"]
}

作ったchrome拡張のタイトル・説明や,アイコン,css, jsの場所など

permissionsはユーザーのどの情報にアクセスするのか明記するところです. 今回はユーザーが見ているamazonのページ上の,本のタイトルや著者の情報にアクセスしているので "matches":["http://www.amazon.co.jp/*"]と書いています. もしユーザーのchrome上のbookmarkとかにアクセスするのであれば,それも書きます.

あとはゴリゴリcontentscript.jsで処理を記述すればいいだけです. js慣れてないから結構時間かかった...

koboでの価格を取得するのには 楽天API( 楽天ウェブサービス: API一覧 )を用いています.

タイトルや著者をqueryに含めて投げれば,その本の価格等が取得できます.

koboのセール情報とかポイント情報とかも取得できれば,それを含めてもっと楽天ぽくできるのになあ