PythonのWebアプリ動かす時にやったこととかメモ

2018/07/26 追記: Python 3.7 で更新

はじめに

ISUCONとかコンテスト系のイベントでPythonのWebアプリを出来るだけ早くセットアップしたい時があるので自分なりの手順とかメモ。自分が後で見返す用なのでまとまってないです。データベースに関しては別の記事に分けました。

nwpct1.hatenablog.com


ログインしてまずやること

Gistにセットアップ用のシェルスクリプトを置いておく。

こういうセットアップはAnsibleを使ってもいいかなって思ったけどチューニング系のイベントだと、複数台のサーバを使うこともないし何度もデプロイするから冪等性が保証されてほしいとかもない気がするのでシェルスクリプトを選択。これから紹介するコマンド群をGistに保存して、 curl GIST_URL | sh みたいに実行する。それも面倒なときはシェル上にコピペしてください。

事前準備

# for centos
sudo yum install -y  wget vim git htop tmux unzip

# for ubuntu
sudo apt-get install -y wget vim git htop tmux unzip

# common
cat << 'EOF' > $HOME/.bashrc 
alias ls='ls -G'
alias la="ls -a"
alias ll="ls -al"

if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi
EOF

dotfilesもDropboxのpublicフォルダに置いておいて、とってくる。vimrcに関しては普段のものと分けている。プラグインは不要だったり、Macの今のマシンに依存した設定とかも混ざってたりするので。

curl https://dl.dropboxusercontent.com/..../dotfiles/gitconfig > ~/.gitconfig
curl https://dl.dropboxusercontent.com/..../dotfiles/tmux.conf > ~/.tmux.conf
curl https://dl.dropboxusercontent.com/..../dotfiles/vimrc > ~/.vimrc

Python3のインストール

参考: * https://github.com/pyenv/pyenv/wiki/common-build-problems * https://devguide.python.org/setup/

# for centos
sudo yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel
sudo yum install -y python-devel gcc

# for ubuntu
sudo apt-get install -y python3-dev gcc build-essential
sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev

# common
mkdir python && cd python
wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tgz
tar zxvf Python-3.7.0.tgz
cd Python-3.7.0
sudo ./configure --enable-optimizations --prefix=/opt/python-3.7.0
make
sudo make install
mkdir $HOME/bin; cd $HOME/bin
ln -s /opt/python-3.7.0/bin/python3.7 .
cd $HOME
source $HOME/.bashrc
python3.7 -m venv venv
source venv/bin/activate

/etc/ 以下をgitで管理

# .gitignore
*
*/

コンテスト中はnginxとかmysqlの設定ファイルを結構変更するので、.gitignore は全てignoreするように設定しておいて、管理したいものだけ git add -f nginx/nginx.conf みたいにする。


uWSGIを使う

Pythonで実装されてるgunicornよりも、Cで実装されてるuWSGI使ったほうが基本的に速いらしい。

$ uwsgi --http :9090 --wsgi-file app.py --callable app

UNIXドメインソケットを使うなら↓のように書き換える

$ uwsgi --socket /tmp/uwsgi.sock --wsgi-file app.py --callable app


Nginxを使う

nginx.confを以下のようにする。syntaxがおかしいかは、 nginx -t と入力すればチェックしてくれる。

worker_processes  8;

events {
  worker_connections  1024;
}

http {
  upstream app {
    server 127.0.0.1:8080;
  }

  server {
    location / {
      proxy_set_header Host $host;
      proxy_pass http://app;
    }
  }
}

UNIXドメインソケットを使う

最初動かなかったのでerror logを確認したら、Permission deniedだった。userをrootに変えた。

user  root;
worker_processes  16;

error_log  /var/log/nginx/error.log;

pid        /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format with_time '$remote_addr - $remote_user [$time_local] '
                         '"$request" $status $body_bytes_sent '
                         '"$http_referer" "$http_user_agent" $request_time';
    access_log /var/log/nginx/access.log with_time;

    server {
        location / {
            proxy_set_header Host $host;
            uwsgi_pass unix:///tmp/uwsgi.sock;
            include uwsgi_params;
        }
    }

    include /etc/nginx/conf.d/*.conf;
}

ログを調べる

ログのフォーマットを上のコードのように指定しておくと kataribe というツールが使える。インストールの方法はgithubのreleasesからlinux版のURLをコピーしてから、wgetとunzipするだけ。Goの良さが出てますね。

$ cat /var/log/nginx/access.log | ./kataribe [-f kataribe.toml]

Nginxのアクセスログawkとかで処理しやすいので、コマンド繋げて調べてみても良いかもしれない。

# cat /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -n


Redisを使う

Pythonでは redis-py を使えばいい。pip install redis で入る。 更新の無いデータとかはRedisに乗せていった。

uWSGIとRedisをUNIXドメインソケットでつなぐ

/etc/redis/redis.conf のbindをコメントアウトして、unixsocketとunixsocketpermのコメントアウトを外す。あとは redisモジュールの接続を以下のように書き換える。

r = redis.Redis(host='localhost', port=6379, db=0)

r = redis.Redis(unix_socket_path='/tmp/redis.sock') 

ただ、permission deniedと怒られたから設定ファイルの unixsocketpermをとりあえず777にした。セキュリティ的に問題あるかもだけどコンテストなので無視。


Pythonのコードのプロファイリング

この記事が簡潔でよかった。cProfileでもPyCallGraphでも良さそう。 Webアプリならリクエストオブジェクトがいるけど、bottleのコードなら↓みたいにすればcProfileがつかえる。

bottle.request.user = {
    "id": 3657,
    "account_name": "edwardo3657",
    "nick_name": "タダハル",
    "email": "edwardo3657@isucon.net",
}
bottle.TEMPLATE_PATH.insert(0,'/home/isucon/webapp/python/views/')
cProfile.run("app.get_index()", sort='tottime')

ちなみにPYTHONPATH指定して起動するのは↓みたいにやればよかった

$ PYTHONPATH=/home/isucon/webapp/python/ bin/ipython


サービスの管理

色々あって混乱したので使ったものをいくつかメモ。まだあまり詳しくないけど、supervisordとかもよさそう。

systemd

  • 一覧: systemctl list-units
  • 起動停止: start, stop
  • 再起動: restart, reload (reloadした方が出来るだけrequestさばいてからやってくれるとか)
  • 自動起動: enable, disable
  • 自動起動か確認: is-enabled

service

System V系

$ service --status-all
$ service nginx status  # /etc/init.d/nginx status と一緒
$ service nginx restart

runits

runitsはsvコマンドで制御するっぽいけど、パスが通ってなかったのとどこにあるのか分からなかった。 このページみたいに、.../supervice/status に文字を送ればいいっぽい。 停止は d

[root@samlab gitlab]# echo d > service/nginx/supervise/control
[root@samlab gitlab]# netstat -apn | grep :80
tcp        0      0 127.0.0.1:8080              0.0.0.0:*                   LISTEN      21075/unicorn maste


知っとくと便利だったコマンド

雑にメモ

  • ポートを占有しているプロセスを特定。
[root@samlab etc]# netstat -apn | grep :80
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN      18583/nginx
tcp        0      0 127.0.0.1:8080              0.0.0.0:*                   LISTEN      21075/unicorn maste
  • ps auxf で親子関係まで分かる
  • find . -name nginx.conf
  • scp -i 秘密鍵 <username>@<hostname>:<file_path> .

マスタリングNginx

マスタリングNginx