OCI上のDjangoアプリをNginx+uWSGI+SSL (Let’s Encrypt)で公開

# はじめに
Django + Nginx + uWSGI + SSLのネタは三番煎じかもしれませんが、実際にトライしてみるとつまずく箇所がいくつかあったので、その備忘録として本記事を書きました（なので、特に問題の起きなかったOCIとDjangoのセットアップの説明は割愛しています）。

今回使用するサーバー（Oracle Cloud Infrastructure）、ドメイン、証明書の発行はすべて無料です。

# 環境
環境は[Oracle Cloud Infrastructure(OCI)の仮想マシン](https://www.oracle.com/jp/cloud/compute/virtual-machines/)の無料枠で作成しました。イメージはOracle-Linux-7.9-2021.10.20-0、シェイプはVM.Standard.E2.1.Microです。

```
$ cat /etc/oracle-release 
Oracle Linux Server release 7.9
```

# システムの構成
![system_overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/9af7213e-d928-b7f0-3ec0-9ec8c0302935.png)

- NginxはフリーかつオープンソースなWebサーバで、サーバー上の静的コンテンツを配信します。
- DjangoはPythonで実装されたWebアプリケーションフレームワークです。
- uWSGIとはPythonでWebサービスを動かすためのアプリケーションサーバです。PythonのWebアプリケーションとWebサーバー間とのやり取りの規約であるWSGI (Web Server Gateway Interface)に準拠したアプリケーション（Djangoなど）であれば使うことができます。DjangoのアプリケーションとNginx（Webサーバー）は直接対話できないので、uWSGIがその間を取り持ちます。

# 無料のドメインの取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/26cb0691-b1ef-d5fc-a852-a8588b427c3c.png)

最終的にSSL化まで行うため、はじめにドメインを取得しておきます。[Freenom](http://www.freenom.com/ja/index.html?lang=ja)から5種類のドメイン(.tk/.ml/.ga/.cf/.gq)が無料で取得できます。2021/11/13現在、ドメインの有効期限は取得日から三か月でした。以降、`example.tk`というドメインを取得した前提で話を進めます。


# Nginxのインストール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/e9c926e2-ec06-c689-9d8a-9c3aa9c53d8e.png)


Yumのリポジトリファイルを作成します。

```/etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
```

インストールします。

```
sudo yum install -y nginx
```

バージョンを確認します。

```
$ nginx -v
nginx version: nginx/1.20.1
```

# Firewallの設定
外部からサーバーのポートにアクセスするために`firewall-cmd`で必要なポートを登録しておきます。今回登録するのは`http`と`https`と`8000/tcp`（uWSGIの検証用）です。OCIの方でもセキュリティリストからポートを開放しておく必要があります。

```
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --permanent --add-port=8000/tcp
sudo firewall-cmd --reload
```


# uWSGIでDjangoのアプリケーションを起動
今回は`django_project`という名前のDjangoプロジェクトがあらかじめ作成されていることとします。

はじめに、Djangoのアプリケーションが正しく起動するか検証します。`runserver`を使って、8000番ポートで起動してみます。

```
python manage.py runserver 0.0.0.0:8000
```

ここで以下のようなSQLiteのエラーが出たら

```
django.core.exceptions.ImproperlyConfigured: SQLite 3.9.0 or later is required (found 3.7.17).
```

「[Django2.2で開発サーバー起動時にSQLite3のエラーが出た場合の対応](https://qiita.com/rururu_kenken/items/8202b30b50e3bfa75821)」の記事を参考にして、解決してください。最終的に`http://example.tk:8000`からDjangoのアプリケーションにアクセスできれば成功です。

続いて、uWSGIでDjangoのアプリケーションを起動します。
pipでuWSGIをインストールします。

```
pip install uwsgi
```

Djangoのプロジェクト下の`uwsgi_params`を作成します。
https://github.com/nginx/nginx/blob/master/conf/uwsgi_params

```uwsgi_params
uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;
```

以下のコマンドで起動します。ここではブラウザなどを通じてデバッグするために`--http`を使っていますが、Nginxと通信するときはUnix socket（`--socket`）を使用します。

```
uwsgi --http :8000 --module /path/to/your/django_project.wsgi
```



# certbotを用いた証明書の取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344018/ed69d15e-a590-c427-e438-f6188a463cbc.png)

今回はLet's Encryptという認証局に証明書を発行してもらいます。その際にLet's Encryptとのやりとりや検証用ファイルの用意をしてくれるのがCertbotです。

普通であれば、はじめにcertbotをインストールするのに必要なEPELリポジトリを準備します。しかし、Oracle Linux 7用の`ol7_developer_EPEL`は限りなくオリジナルのEPELに近いため、ほとんどの場合EPELの追加インストールは不要とのことです。そこで`ol7_developer_EPEL`が有効になっているか確認します。

```
$ sudo yum repolist all | grep -i epel
ol7_developer_EPEL/x86_64               Oracle Linux 7Server EPE disabled
```

無効になっていなければ、有効にします。

```
sudo yum-config-manager --enable ol7_developer_EPEL
```

再び有効になっているか確認します。

```
$ sudo yum repolist all | grep -i epel
ol7_developer_EPEL/x86_64               Oracle Linux 7Server EPE enabled: 40,406
```

EPELの準備ができたら、certbotとpython2-certbot-nginx(Nginx向けの追加機能)のインストールを行います。

```
$ sudo yum -y install certbot python2-certbot-nginx
```

はじめに、Nginxの設定ファイルである`/etc/nginx/conf.d/default.conf`内のドメイン名を`localhost`から`example.tk`に変更しておきます。

```
sudo sed -i 's/localhost/example.tk/g' /etc/nginx/conf.d/default.conf
```

以下のコマンドを実行すると対話形式で証明書の発行が行えます。さらに、`--nginx`オプションをつけることで`/etc/nginx/conf.d/default.conf`がよしなに自動編集されます。

```
sudo certbot --nginx -d example.tk
```


# uWSGIとNginxを連携

はじめに、Djangoの静的ファイルを準備します。
静的ファイルを収集するディレクトリを`django_project/static/`に指定する設定を`setttings.py`に記述します。

```django_project/settings.py
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
```

静的ファイルを`static`ディレクトリに集めます。

```
python manage.py collectstatic
```

次にNginxの設定を行います。
念のため、`default.conf`を編集する前にオリジナルの設定ファイルのバックアップを作っておきます。

```
sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.copy
```

`/etc/nginx/conf.d/default.conf`を編集します。

```/etc/nginx/conf.d/default.conf（変更前）
server {
    server_name  example.tk;

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

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

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

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.tk/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.tk/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = example.tk) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen       80;
    server_name  example.tk;
    return 404; # managed by Certbot


}
```

主な変更点は以下の通りです。

- uWSGIとUnix socketを用いて通信する設定を追記
- Djangoのstaticパスを指定
- `location /`をDjangoのプロジェクトに変更

`/path/to/your/`と`example.tk`と`djang_project`は自分の環境に合わせて変更してください。

```/etc/nginx/conf.d/default.conf（変更後）
upstream django {
    server unix:///path/to/your/django_project.sock; # for a file socket
}

# configuration of the server
server {
    server_name example.tk; # substitute your machine's IP address or FQDN
    charset     utf-8;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # Django media
    location /media  {
        alias /path/to/your/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /path/to/your/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     /path/to/your/uwsgi_params; # the uwsgi_params file you installed
    }

    listen      443 ssl;
    ssl_certificate /etc/letsencrypt/live/example.tk/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.tk/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = example.tk) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen       80;
    server_name  example.tk;
    return 404; # managed by Certbot

}
```

`/etc/nginx/conf.d/default.conf`を`/etc/nginx/nginx.conf`内でインクルードするように設定します。

```/etc/nginx/nginx.conf
http {
...
    include /etc/nginx/conf.d/default.conf;
...
}
```

Nginxを（再）起動します。

```
sudo systemctl start nginx
```

uWSGIでDjangoのアプリケーションを起動します。

```
uwsgi --socket django_project.sock --module django_project.wsgi --chmod-socket=664
```

`https://example.tk`からDjangoのアプリケーションにアクセスできたら成功です。


iniファイルから起動する場合は、以下の通りです。

```uwsgi.ini
[uwsgi]

# Django-related settings
# the base directory (full path)
chdir           = /path/to/your/django_project
# Django's wsgi file
module          = django_project.wsgi

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 10
# the socket (use the full path to be safe
socket          = /path/to/your/django_project/django_project.sock
# ... with appropriate permissions - may be needed
chmod-socket    = 664
# clear environment on exit
#vacuum          = true
```

```
uwsgi --ini uwsgi.ini
```



# 参考
- [仮想マシン - Oracle Cloud Infrastructure](https://www.oracle.com/jp/cloud/compute/virtual-machines/)
- [OCI Always Free Compute (Oracle Linux・CentOS 7) に nginx をインストールする](https://neos21.net/blog/2020/02/06-03.html)
- [無料のドメインを取得する](https://qiita.com/teekay/items/135dc67e39f24997019e)
- [CentOS7にnginxとcertbotを導入してHTTPS環境をさくっと作るの巻](https://blog.trippyboy.com/2020/centos/centos7%E3%81%ABnginx%E3%81%A8certbot%E3%82%92%E5%B0%8E%E5%85%A5%E3%81%97%E3%81%A6https%E7%92%B0%E5%A2%83%E3%82%92%E3%81%95%E3%81%8F%E3%81%A3%E3%81%A8%E4%BD%9C%E3%82%8B%E3%81%AE%E5%B7%BB/#CentOS7%E3%81%B8%E3%81%AEcertbot%E3%81%AE%E5%B0%8E%E5%85%A5)
- [あらためてEPELリポジトリの使い方をまとめてみた](https://qiita.com/yamada-hakase/items/fdf9c276b9cae51b3633)
- [Setting up Django and your web server with uWSGI and nginx](https://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html)
- [Django2.2で開発サーバー起動時にSQLite3のエラーが出た場合の対応](https://qiita.com/rururu_kenken/items/8202b30b50e3bfa75821)