CentOS8にDjango+Reactで作成したアプリをリリースしてみた

はじめに

Django+Reactで作成したアプリをリリースしてみたので、その道のりをまとめてみました。
Django公式を参考しています。
今回作ったアプリの詳細は記載していませんが、所々(モジュールのインストール等)でアプリ固有の手順が入っています。

仮想環境の作成

venvで今回デプロイするアプリ用の環境を用意します。

$ python3.6 -m venv mysite

仮想環境の有効化

$ source ~/mysite/bin/activate

または

$ . ~/mysite/bin/activate

モジュールのインストール

$ sudo dnf groupinstall 'development tools'
$ sudo dnf install python3-devel

今回のアプリに使用したモジュールをインストールします。

$ pip install Django
$ pip install django-cors-headers
$ pip install uwsgi
$ pip install tensorflow
$ pip install Keras
$ pip install numpy
$ pip install Pillow

プログラムの配置

– React関連のファイル:/home/user/mysite/src/client
– Djangoのファイル:/home/user/mysite/src/server
に置いてあるものとします。

以下のコマンドで
– React関連のファイル:/var/www/mysite
– Django関連のファイル:/usr/bin/mysite
にコピーします。

$ sudo mkdir -p /var/www/mysite
$ sudo cp -r /home/user/mysite/src/client/* /var/www/mysite

$ sudo mkdir -p /usr/bin/mysite
$ sudo cp -r /home/user/mysite/src/server/* /usr/bin/mysite

nginxの設定

インストール

$ sudo dnf -y install nginx
$ sudo systemctl enable --now nginx

設定

uwsgi_paramsをコピー
$ sudo cp /etc/nginx/uwsgi_params /usr/bin/mysite/
nginx.confの編集
$ cd /etc/nginx
$ sudo vi nginx.conf

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx; // 実行ユーザを変更したい場合は書き換えが必要
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /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;
    include /etc/nginx/sites-enabled/*;  // 追加

    server {
        listen       80 default_server;
        listen       [::]:80 default_server;
        server_name  ababa.ddo.jp;
        charset      utf-8;
        
        root /var/www/mysite;
        index index.html;

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

        location / {
          try_files $uri /index.html;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

# Settings for a TLS enabled server.
#
#    server {
#        listen       443 ssl http2 default_server;
#        listen       [::]:443 ssl http2 default_server;
#        server_name  _;
#        root         /usr/share/nginx/html;
#
#        ssl_certificate "/etc/pki/nginx/server.crt";
#        ssl_certificate_key "/etc/pki/nginx/private/server.key";
#        ssl_session_cache shared:SSL:1m;
#        ssl_session_timeout  10m;
#        ssl_ciphers PROFILE=SYSTEM;
#        ssl_prefer_server_ciphers on;
#
#        # Load configuration files for the default server block.
#        include /etc/nginx/default.d/*.conf;
#
#        location / {
#        }
#
#        error_page 404 /404.html;
#            location = /40x.html {
#        }
#
#        error_page 500 502 503 504 /50x.html;
#            location = /50x.html {
#        }
#    }

}
mysite_nginx.confの編集
$ sudo mkdir /etc/nginx/sites-available
$ sudo mkdir /etc/nginx/sites-enabled
$ cd /etc/nginx/sites-available
$ sudo vi mysite_nginx.conf
# mysite_nginx.conf

# the upstream component nginx needs to connect to
upstream django {
    server unix:///usr/bin/mysite/webapi/webapi.sock; # for a file socket
    # server 127.0.0.1:8001; # for a web port socket (we'll use this first)
}

# configuration of the server
server {
    # the port your site will be served on
    listen      8000;
    # the domain name it will serve for
    server_name localhost; # substitute your machine's IP address or FQDN
    charset     utf-8;

    # max upload size
    client_max_body_size 75M;   # adjust to taste

    # Django media
    location /media  {
        alias /usr/bin/mysite/webapi/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /usr/bin/mysite/webapi/static; # your Django project's static files - amend as required
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass  django;
        include     /usr/bin/mysite/uwsgi_params; # the uwsgi_params file you installed
    }
} 
権限付与
sudo chown -R nginx /var/lib/nginx
シンボリックリンクの設定
$ cp /etc/nginx/sites-available/mysite_nginx.conf /home/user/mysite
$ sudo ln -s /home/user/mysite/mysite_nginx.conf /etc/nginx/sites-enabled/

静的ファイルのデプロイ

$ sudo cd /usr/bin/mysite/webapi/
$ sudo vi settings.py

以下を追記します。

STATIC_ROOT = os.path.join(BASE_DIR, "static/")

staticフォルダとmediaフォルダを作成します。

$ sudo mkdir /usr/bin/mysite/webapi/static
$ sudo mkdir /usr/bin/mysite/webapi/media

$ python manage.py collectstatic
$ sudo systemctl restart nginx

Djangoアプリケーションを実行

$ cd /usr/bin/mysite
$ sudo vi mysite_uwsgi.ini
# mysite_uwsgi.ini file
[uwsgi]

# Django-related settings
# the base directory (full path)
chdir           = /usr/bin/mysite/webapi
# Django's wsgi file
module          = webapi.wsgi
# the virtualenv (full path)
home            = /home/user/mysite

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 10
# the socket (use the full path to be safe
socket          = /usr/bin/mysite/mysite.sock
# ... with appropriate permissions - may be needed
# chmod-socket    = 664
# clear environment on exit
vacuum          = true
uwsgi --ini mysite_uwsgi.ini --chmod-socket=666

システム全体にuWSGIをインストール

$ deactivate
$ sudo pip install uwsgi

emperorモードでの実行

$ sudo mkdir /etc/uwsgi
$ sudo mkdir /etc/uwsgi/vassals

# シンボリックリンクを作成
$ sudo ln -s /usr/bin/mysite/mysite_uwsgi.ini /etc/uwsgi/vassals/


# 実行
$ uwsgi --emperor /etc/uwsgi/vassals --uid www-data --gid www-data

システムの起動時にuWSGIを起動

Djangoのページには「/etc/rc.local」を使用した自動起動の方法が紹介されていますが、現在は非推奨となっているためsystemdを使用しました。
以下の記事が詳しいです。

systemdを用いたプログラムの自動起動

/etc/systemd/system/mysite.serviceの作成

$ cd /etc/systemd/system
$ sudo vi mysite.service

[Unit]
Description=mysiteのサービス
After=local-fs.target
ConditionPathExists=/opt/mysite/bin

[Service]
ExecStart=/opt/mysite/bin/startup.sh
Restart=no
Type=simple

[Install]
WantedBy=multi-user.target

$ sudo chown root:root mysite.service
$ sudo chmod 644 mysite.service

/opt/mysite/bin/startup.sh

$ sudo mkdir -p /opt/mysite/bin
$ sudo chmod 755 /opt/mysite/bin
$ cd /opt/mysite/bin

$ sudo vi startup.sh
#!/bin/bash
uwsgi --emperor /etc/uwsgi/vassals --uid my-user --gid my-user

$ sudo chown root:root /opt/mysite/bin/startup.sh
$ sudo chmod 755 /opt/mysite/bin/startup.sh

起動

$ sudo systemctl daemon-reload

$ sudo systemctl enable mysite.service
$ sudo systemctl start mysite.service

$ sudo systemctl status mysite.service

HTTPS化

$ sudo mkdir -p /opt/mysite/bin
$ cd /usr/local
$ git clone https://github.com/certbot/certbot
$ cd certbot
$ sudo mkdir -p /opt/mysite/bin
$ ./certbot-auto certonly --standalone -t

パッケージのインストールを聞かれるので、「y」。

メールアドレスを指定します。

Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): my-mail@example.com

ライセンスに同意します。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A

メールアドレスを登録していいか聞かれているので、お好きなほうを選択します。
ここではNoを選択しました。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N

登録したいドメインを入力します。

Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'
to cancel): ababa.ddo.jp

以下のように表示されれば成功です。

 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/ababa.ddo.jp/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/ababa.ddo.jp/privkey.pem
   Your cert will expire on 2020-08-26. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot-auto
   again. To non-interactively renew *all* of your certificates, run
   "certbot-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

以下のように表示された場合、nginxを停止してやり直しば成功するはずです。

Problem binding to port 80: Could not bind to IPv4 or IPv6.

nginxの設定ファイルを修正し、nginxを再起動します。

server {
    listen       80 default_server;
    listen       [::]:80 default_server;
    server_name  ababa.ddo.jp;
    
    location ^~ /.well-known/acme-challenge/ {
        default_type "text/plain";
        root         /var/www/mysite;
    }
    
    return 301 https://$host$request_uri;
}

server {
    listen  443 ssl;
    server_name     ababa.ddo.jp;
    ssl on;
    ssl_certificate         /etc/letsencrypt/live/ababa.ddo.jp/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/ababa.ddo.jp/privkey.pem;
}

証明書の更新

$ sudo crontab -e

// 毎週月曜日に更新する
0 0 * * 1 /usr/local/certbot/certbot-auto renew && /bin/systemctl reload nginx