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

DockerでVPS上にgitlabとjenkinsサーバをたてる

はじめに

研究室で借りているさくらのVPSがあって、研究で使うアプリケーション以外にgitlabとかjenkinsを動かしている。Dockerをローカルでしか動かしたことが無かったので、リスクの低いこういうところで実際にDocker使ってみる。

これまでは AnsibleGalaxyで人気のRole を探して中身も読まずにインストールしちゃっていたけど、さっき触ってたらどうインストールされてるのか混乱したのでDockerで分離できたら良さそうっていうのが2つ目の理由。

準備

環境はさくらのVPSCentOS 6が入ってます。 https://docs.docker.com/installation/centos/ を参考にDockerをインストール。

# yum update
# cat >/etc/yum.repos.d/docker.repo <<-EOF
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/7
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF
# yum -y install docker-io
# service docker start

Gitlab

こちらのDockerイメージが人気なので使ってみる。

2015/11/26 追記

公式のDockerイメージありました...

追記終わり

MySQLとRedisもdockerで管理することにした。

$ docker pull sameersbn/mysql:latest
$ docker pull sameersbn/redis:latest
$ docker pull sameersbn/gitlab:latest
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
sameersbn/mysql     latest              1aa894d249d8        23 hours ago        294.2 MB
sameersbn/gitlab    latest              a87dce14c499        2 days ago          675.7 MB
sameersbn/redis     latest              9e62c2099db1        9 days ago          196.9 MB

MySQL、Redis、Gitlabのコンテナの立ち上げ。passwordのところは適当に置き換えて下さい。

$ docker run \
--name=mysql -d \
-e 'DB_NAME=gitlab_production' \
-e 'DB_USER=gitlab' \
-e 'DB_PASS=password' \
sameersbn/mysql:latest
$ docker run \
--name=redis -d \
sameersbn/redis:latest
$ docker run \
--name=gitlab -d -it -p 10022:22 -p 10080:80 \
-e 'GITLAB_SSH_PORT=10022' \
-e 'GITLAB_PORT=10080' \
-e 'DB_NAME=gitlab_production' \
-e 'DB_USER=gitlab' \
-e 'DB_PASS=password' \
-e 'GITLAB_SECRETS_DB_KEY_BASE=gacH4taC0ceJ7I' \
--link=mysql:mysql \
--link=redis:redisio \
-v $(which docker):/bin/docker sameersbn/gitlab:latest
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                CREATED             STATUS              PORTS                                                   NAMES
2198b686b6dc        sameersbn/gitlab:latest   "/sbin/entrypoint.sh   6 seconds ago       Up 4 seconds        443/tcp, 0.0.0.0:10022->22/tcp, 0.0.0.0:10080->80/tcp   gitlab
105589c338fb        sameersbn/redis:latest    "/sbin/entrypoint.sh   19 minutes ago      Up 19 minutes       6379/tcp                                                redis
52577c0c7aad        sameersbn/mysql:latest    "/sbin/entrypoint.sh   19 minutes ago      Up 19 minutes       3306/tcp                                                mysql

docker runのコマンドが長いのでdocker-compose使ったほうが良さそうだけど、今回はとりあえずこのままにしておく。

Jenkins

Jenkinsが公式にDocker imageを配布しているのでそれを使う。

https://hub.docker.com/_/jenkins/

$ docker pull jenkins
$ docker run \
--name jenkins -d \
-p 10081:8080 jenkins

アクセスして確認したら、ちゃんと動いてくれてました。手軽

おわりに

ポート番号指定するのはあれなのでnginxで gitlab.example.com とか jenkins.example.com とかにひも付けた。試してないけどこの辺は nginx-proxy 使ってもよさそう。

使いたいDocker Imageをyml形式で指定しておけば後は全部セットアップしてるAnsibleのRoleがあれば便利そう。無さそうだったら作ります。

初めてのAnsible

初めてのAnsible