nginx
supervisord
gunicorn
Falcon

Amazon Linux で Falcon + Gunicorn + Supervisord + nginx を設定する

AWS EC2 を使用してクラウド上にREST-APIを用意してあれこれしたいと思った時、最速で実装できる様にメモを残します。
Pythonは2.7系です。

インストールは以下

$ sudo yum update
$ sudo yum install nginx
$ sudo yum --enablerepo=epel install supervisor
$ sudo service supervisord start
$ pip install gunicorn
$ pip install falcon

nginxの自動起動を設定

$ sudo systemctl enable nginx
$ sudo systemctl start nginx
$ sudo chkconfig nginx on

まず Falcon で REST-API のサンプルコードを実装します。

これはそのまま、 python ajax.py で動作します。外部からREST-APIとして動きます。
これをデーモン化してサーバ起動時に自動で動く様にします。

cd
/home/ec2-user
vim ajax.py

以下のサンプルコードでは、ajaxの1番目・2番目のアクセスURLに対してGETパラメータで、colorを指定します。colorの値は、 green_on,red_on の2種類としています。

値をセットする例としては、1番目にgreen_on、2番目にred_onを指定したい場合は、以下の様に指定します。

http://サーバドメイン/ajax/1/?color=green_on
http://サーバドメイン/ajax/2/?color=red_on

次に値をゲットする例としては、1番目、2番目は、以下の様に指定します。すると、セットした値がjsonで帰ってきます。

http://サーバドメイン/ajax/1/
{
result: "green_on"
}
http://サーバドメイン/ajax/2/
{
result: "red_on"
}

この様な動作を行うソースが以下となります。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
##
## REST API Sample
##
import json
import falcon
from datetime import datetime

##
## route 指定パラメータとGETパラメータの混合サンプル
##
class HelloResource(object):
    def on_get(self, req, resp, name):
        param = req.get_param('color')
        if (param):
            ## getパラメータがあれば保存処理
            f = open('/tmp/ajax'+name+'.txt','w')
            f.write(param)
            f.close()
            resp.body = '{"result": "' + param + '"}'
        else:
            ## パラメータ無しなら保存したデータを返す
            f = open('/tmp/ajax'+name+'.txt','r')
            ret = f.read()
            resp.body = '{"result": "' + ret + '"}'
        resp.status = falcon.HTTP_200

app = falcon.API()
app.add_route("/ajax/{name}", HelloResource())

if __name__ == "__main__":
    from wsgiref import simple_server
    httpd = simple_server.make_server("0.0.0.0", 80, app)
    httpd.serve_forever()

Supervisord の起動設定を記述します。

Supervisord はコマンドをサーバプロセス化してくれます。が、それ自体のデーモン記述が無いので以下の様に書きます。

cd /etc/init.d
vim supervisord 
#!/bin/bash
. /etc/rc.d/init.d/functions

RETVAL=0

start() {
        echo -n $"Starting supervisord: "
        daemon supervisord
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ] && touch /var/lock/subsys/supervisord
}

stop() {
        echo -n $"Stopping supervisord: "
        killproc supervisord
        echo
        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/supervisord
}

restart() {
        stop
        start
}

case "$1" in
  start)
        start
        ;;
  stop) 
        stop
        ;;
  restart|force-reload|reload)
        restart
        ;;
  condrestart)
        [ -f /var/lock/subsys/supervisord ] && restart
        ;;
  status)
        status supervisord
        RETVAL=$?
        ;;
  *)
        echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart}"
        exit 1
esac

exit $RETVAL

Supervisordの設定を記述します。

デーモン化した後に設定ファイルも無いので記述します。

[etc]# vim supervisord.conf

[unix_http_server]
file=/tmp/supervisor.sock   ; the path to the socket file

[supervisord]
logfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log
logfile_maxbytes=50MB        ; max main logfile bytes b4 rotation; default 50MB
logfile_backups=10           ; # of main logfile backups; 0 means none, default 10
loglevel=info                ; log level; default info; others: debug,warn,trace
pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
nodaemon=false               ; start in foreground if true; default false
minfds=1024                  ; min. avail startup file descriptors; default 1024
minprocs=200                 ; min. avail process descriptors;default 200

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket

[include]
files = /etc/supervisord.d/*.conf

Supervisor のアプリ毎の設定を記述します。

目的のアプリについての設定ファイルを記述します。pythonダイレクト指定もコメントアウトで例示します。

mkdir /etc/supervisord.d
cd /etc/supervisord.d

[supervisord.d]# vim falcon.conf

[program:ajax]
# pythonをダイレクト指定する(gunicorn,nginx不使用)場合は以下
# command=/root/.pyenv/versions/2.7.14/bin/python /home/ec2-user/ajax.py
command=/root/.pyenv/shims/python /home/ec2-user/ajax.py
# gunicornで起動する場合は以下
command=/root/.pyenv/versions/2.7.14/bin/gunicorn ajax:app --config /home/ec2-user/guniconf.py
directory=/home/ec2-user/
user=root

gunicorn のコンフィグファイルを記述します。

[ec2-user]# vim guniconf.py

import multiprocessing

# Server Socket
bind = 'unix:/tmp/gunicorn_ajax.sock'
backlog = 2048

# Worker Processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync'
worker_connections = 1000
max_requests = 0
timeout = 30
keepalive = 2
debug = True
spew = False

# Logging
errorlog = '/var/log/gunicorn_error.log'
loglevel = 'debug'
accesslog = '/var/log/gunicorn_access.log'

# Process Name
proc_name = 'ajax'

nginx の設定を記述します。

[nginx]# vim nginx.conf

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    index   index.html index.htm;

    upstream app_server {
        server unix:/tmp/gunicorn_ajax.sock;
    }

    server {
#        listen       80 default_server;
#        listen       [::]:80 default_server;
#        server_name  localhost;
#        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            # 以下4行を追加
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $http_host;
            proxy_redirect off;
            proxy_pass   http://app_server;
        }

        # redirect server error pages to the static page /40x.html
        #
        error_page 404 /404.html;
            location = /40x.html {
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}