お名前ドットコムVPSへ移行への道3-Docker+Nginx+Djangoまで

※ 当サイトではアフィリエイト広告を利用しています。リンクは広告リンクも含みます。

この記事は約19分で読めます。
広告

お名前.com VPS 今回はNginxをDocker-composeでインストールするのがメインです。
NginxがインストールできたらDjangoを動かしてみます。
Nginxのログも見てみます。
このシリーズの最終目標はDocker+WAF+Nginx+WordPress+Djangoです。
あくまでも私は、こうやってみたということで動作やセキュリティの保証はできません。
記事内でkikuichigevps.comのリンクがありますが、
記事を書いた時点から設定を変えているためアクセスしても記事通りの表示にはなりません。
お名前.comのVPSにデプロイしたDjangoアプリ

環境
パソコン Windows11
VPS OS Ubuntu 20.04.3 LTS
こちらの続きです。

広告

Docker+Nginx

今回のNginxの設定は、セキュリティ対策はまったく考慮しないで、
つなぐことしか考えていませんのでご了承ください。
この記事ではNginxのimageを使用しますが
今シリーズ↓で最終的なimageはNginxではありません。

お名前ドットコムVPSへ移行への道

2024/4お名前.comのレンタルサーバーからVPSへ移行したときの記録になります。

ドメインにアクセスしたらNginxのWelcome to nginx!
サブドメインにアクセスしたらDjangoのThe install worked successfully! Congratulations!画面を
表示させてみます。

こちら↓を参考にしてください。

前回作ったDjangoのコンテナを削除します。
sudo docker stop コンテナID
つくるときに--rm付けたので、これで削除できるはず、できない場合は
sudo docker rm コンテナID
余計なものが残っていないように実行。イメージは消えない。
sudo docker system prune

前回作ったdockerのディレクトリに移動
ブリッジネットワークを作ります
sudo docker network create --driver bridge shared

mkdir shared
cd shared

Nginxのコンテナを作ります
Vscodeを立ち上げsharedフォルダを開く
以下ファイルを新規作成
docker-compose.yml
conf.d/default.conf
version: '3.9'
services:

  proxy_prod:
    image: nginx:latest
    container_name: 'proxy'
    volumes:
      - ./conf.d/:/etc/nginx/conf.d
    ports:
      - "80:80"
    restart: always
    environment:
      TZ: Asia/Tokyo
networks:
  default:
    name: shared
    external: true

今まで設定していなかったが
environment: TZ: Asia/Tokyo
でNginxのログを日本時間に変更できた。

server {
    listen       80;
    listen  [::]:80;
    server_name  "";
    location / {
        root /usr/share/nginx/html;
        index  index.html index.htm;
    }
}
server {
    listen       80;
    listen  [::]:80;

    server_name  "django2.kikuichigevps.com";

    location / {
        proxy_pass   http://django2_web_run_9ea11b0af0c7:8000;
        proxy_set_header Host $host;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /var/www/htdocs;
    }
}

今まで書いてなかったけど ipv6 も追加した。listen [::]:80;
karinopassはDjangoのコンテナ名です。
後でDjnagoのコンテナを作ってからコンテナ名を調べて書き直すので最初は何でも大丈夫です。

server_nameで処理するところ(location)を変えているが、
よく調べてないのでserver_nameの設定の仕方は、これでいいかは分からない。

sharedディレクトリで以下実行してコンテナを作ります。
sudo docker-compose up -d 
目次へ

NginxとDjangoをつなげる

前回作ったDjangoのdocker-compose.ymlを変更します。

version: "3.9"

services:
  web:
    container_name: 'django2.kikuichigevps.com'
    build: .
    volumes:
      - .:/code
    expose:
      - 8000
    restart: always
networks:
  default:
      name: shared
      external: true

container_nameは、上のproxyで作った時は、コンテナ名proxyになっていましたが、
こちらはcontainer_nameで指定した名前にならないで自動的に名前がつきます。
多分、どこかに設定が残っていてダブらないように名前を変えてしまうのだと思います。

#コンテナ作成、コンテナ起動、中に入る
sudo docker-compose run web bash
#実行
python manage.py runserver 0.0.0.0:8000
Vsコードのsharedを開いてるほうで
sudo docker ps
でDjangoのコンテナ名を調べ
conf.d/default.conf(前述)のproxy_passをdjangoのコンテナ名に書き換えます。
proxy_set_header Host $host;は
プロクシーサーバに送られるリクエストヘッダの フィールドを再定義です。
$host変数はクライアントがアクセスしようとしているサーバー(VPS)になってました。
これを付けないとDjangoに渡されるHostがDjangoのコンテナ名になって
urlに'_'を使っているというエラーをDjangoが出します。

#Nginxのコンテナ再起動
sudo docker restart proxy
でconf.d/default.confの変更が反映されます。

docker inspect shared
で今、作ったネットワークに含まれるコンテナが見れます。
Djangoのコンテナ名が入っているか確認。
ドメインにアクセスしたらNginxのWelcome to nginx!
サブドメインにアクセスしたらDjangoのThe install worked successfully! Congratulations!画面が表示させれます。
目次へ

git cloneで既存のレポジトリを持ってきてみた

新たなコンテナを作る場合

dockerディレクトリの下に
mkdir ac_web(適当な名前)
cd ac_web
git clone https://アクセストークン@github.com/ユーザーname/レポジトリ名.git
Docker、docker-compose.ymlを作る。
requirements.txtは、今シリーズでは今までDjangoしか書かなかったがGit cloneしたものに書き換えます。
runした後にコンテナの中でpip install -r requirements.txtしてもいいですが、
コンテナを消すと、またやらなければなりません。
docker-compose up -dは、うまくいかないので
Djangoのコンテナ2個目をdocker-compose up -dしたあとにexecで中に入ろうとするとエラーになるのでdocker-compose up -dは使わず。(1個目はexecで入れる)
sudo docker-compose run web bashを使います。

Nginxのlocationのproxyにコンテナ名を書きます。
docker-compose runだと、やるたびにコンテナ名が変わるので、
その都度proxy_passを書き直します。
直接関係ないですが以下変更しました。
(今シリーズでは今まで書いてないかもしれないですが
WordPressで使っていたのでDjangoも使います。)
proxy_set_header X-Forwarded-Proto https;#常にhttps
→proxy_set_header X-Forwarded-Proto $scheme; #クライアントが実際に使用したプロトコルが設定される

同一コンテナにプロジェクトを追加する場合

dockerディレクトリの下で
git clone https://アクセストークン@github.com/ユーザーname/レポジトリ名.git
コンテナの中に入る
sudo docker exec -it --user root コンテナ名 bash
cd cloneしたプロジェクトのディレクトリ
python manage.py runserver 0.0.0.0:ポートの設定は
後述する下の「1つのコンテナで複数のDjangoプロジェクトを動かす」を見てください。
gunicornではなくpython manage.py runserverを使う理由はメモリを節約するためです。
確認していませんが恐らくgunicornを使うと
メモリをたくさん使うのではないかと思います。(worckerが複数できるため。以下参照)

Djangoアプリを移植したとき発生したエラー

アプリでformでPOSTすると403-1

formからPOSTすると
「アクセス禁止 (403)
CSRF検証に失敗したため、リクエストは中断されました。」が出た場合。

対策
参考:https://note.com/ym202110/n/nfc51f4f7d71f
settings.pyに以下追加でokでした。
# CSRF検証のため信頼できるオリジンを指定(本番環境ではドメイン名を指定する)
CSRF_TRUSTED_ORIGINS = [
    'https://ドメイン',
]

アプリでformでPOSTすると403-2

上で直してもDjango REST frameworkでPOSTすると403になり
開発者ツールのプレビュータブを見ると
detail: "CSRF Failed: CSRF token from the 'X-Csrftoken' HTTP header has incorrect length."になってた。

Nginxのlocation設定で
proxy_set_header X-CSRFToken $cookie_csrftoken;
を追加したら直った。
その前に
proxy_set_header X-CSRFToken $http_cookie;
を試したが、この状態で上のエラーが出てることに気づいた。その前は、2つともなしの状態。

気づいた経緯はRenderで動いている同じアプリと比較した。
具体的には開発者ツールで該当する通信を右クリックして「cURL(bash)としてコピー」で比較したらcookieの欄が違ったので気づいた。

Debug=Falseにすると500エラーが出る

Debug=Falseにすると500エラーが出るが原因が分からないので表示するようにしました。
原因の特定するときだけ使って、最終的には戻したほうがいい。
参考:https://www.mrrhp.com/ja/djangonote-7-errorhandling
追加部分
urls.py
# views.py をインポート
from . import views
from django.conf.urls import handler500
# 自作の 500 エラーハンドラー。
handler500 = views.my_error_handler

view.py
def my_error_handler(request, *args, **kw):
    import sys
    from django.views import debug
    from django.http import HttpResponse
    error_html = debug.technical_500_response(request, *sys.exc_info()).content
    return HttpResponse(error_html)

Debug=Trueと同じように確認できた。
Missing staticfiles manifest entry for 'website/main.css'が出る
STATIC_ROOT '/code/ac_web2ren/staticfiles'に設定されているところに
ちゃんとgit cloneした時点からmain.cssがあるので問題ないと思っていた。

なのでpython manage.py collectstaticをやる必要がないと思っていたが
改めてpython manage.py collectstaticをやったら、うまくいった。

1つのコンテナで複数のDjangoプロジェクトを動かす

コンテナ作成と起動

一応動いただけで、これで問題ないか分かりません。

Djangoのコンテナでプロジェクトごとにpython manage.py runserver 0.0.0.0:8000の
8000を8001、8002と変更して起動します。
起動するときは各プロジェクトにcdで移動してから実行します。
その際、python manage.py runserverを実行するとコマンドが入力できなくなるので、
実効できる方法3つを書いておきます。
1,python manage.py runserver 0.0.0.0:8000を実行したVsコードはリモート接続を切断し
新たにVsコードを立ち上げれば、runserverを実行したまま、
新たに別のポートのpython manage.py runserver 0.0.0.0:8001を実行できます。

2,またはVsコードのターミナルウィンドウで+をクリックしてbashをクリックすると
コマンド入力できるターミナルが立ち上がります。

3,python manage.py runserver 0.0.0.0:8000 &
というように末尾に&を付けるとバックグラウンドで実行されます。
ただrunserverの起動画面が出て、いつもと同じように入力できなくなりますが
CTRL+Cをするとrunserverは止まらず入力ができる状態になります。
Djangoのコンテナのdocker-compose.ymlはポートをexposeしておきます。
services:
  web:
    build: .
    volumes:
      - .:/code
    expose:
      - 8000
      - 8001
      - 8002
実行後ps aux
でプロセスを見ると2つプロセスができている。これはなぜか気になったのでGeminiに聞いた。
root     1535656  0.0  0.2  55952  2296 pts/2    S    04:32   0:02 python manage.py runserver 0.0.0.0:8001
root     1536651  2.7  5.2 430912 51452 pts/2    Sl   04:35   4:02 /usr/local/bin/python manage.py runserver 0.0.0.0:8001

以下の理由により、ps auxコマンドで2つのプロセスが表示されています。

1. プロセスフォーク

python manage.py runserver 0.0.0.0:8001コマンドを実行すると、最初のプロセス(PID 1535656)がフォークされます。フォークされた子プロセス(PID 1536651)は、親プロセスと同じコマンドを実行しますが、異なるPIDを持ちます。

2. 異なる実行状態

ps auxコマンドの出力が示すように、2つのプロセスは異なる実行状態にあります。

  • 親プロセス(PID 1535656): 状態 S (sleep) は、親プロセスがI/O待ち状態であることを示します。
  • 子プロセス(PID 1536651): 状態 Sl (sleeping in a long-term wait) は、子プロセスが長時間I/O待ち状態であることを示します。

3. 2つのプロセスの役割

  • 親プロセス: 親プロセスは、子プロセスの監視と管理を担当します。
  • 子プロセス: 子プロセスは、実際のDjangoサーバーを実行します。

4. 正常な動作

2つのプロセスが存在することは、python manage.py runserver 0.0.0.0:8001コマンドが正常に実行されていることを示しています。

ps aufで見ると親子関係が分かる。
root     1534750  0.0  0.0   4600   296 pts/2    Ss+  04:29   0:00 bash
root     1535656  0.0  0.2  55952  2296 pts/2    S    04:32   0:02  \_ python manage.py runserver 0.0.0.0:8001
root     1536651  2.6  2.9 430912 28744 pts/2    Sl   04:35   7:27      \_ /usr/local/bin/python manage.py runserver 0.0.0.0:8001
停止させるときはコンテナから出てrootユーザで
ps aux
でpython manage.py runserver 0.0.0.0:800*のPIDを探す。
kill そのPID番号で停止します。
親プロセス(PID 1535656)をkillしたら子プロセス(PID 1536651)もなくなってました。
プロセスが削除されるので、再開したいときはコンテナの中に入って
python manage.py runserver 0.0.0.0:800* &

urlを分離

プロジェクトのurls.pyは先頭に共通した文字を追加して
Nginxのconfのlocationでurlを分離できるようにします。
urlpatterns = [
    path('zundahage/admin/', admin.site.urls),
    path('zundahage/api/', include(router.urls)),
    path('zundahage', TemplateView.as_view(template_name='index.html')),
    path('zundahage/test/',include("article.urls")),
    path('zundahage/hajime/',include("article.urls")),
]
urls.py以外にもfetchなどでurlを使っている場合、
あわせて変更する必要があります。
また静的ファイルstaticが以下のようにうまくいかない場合
 https://django6.kikuichigevps.com/static/toukou/js/chunk-vendors.f589f689.js
は404 not foundになったがsettings.pyの以下変更で直った。
STATIC_URL = '/static/'→STATIC_URL = '/zundahage/static/'
 https://django6.kikuichigevps.com/drakuten/static/toukou/js/chunk-vendors.f589f689.js

また複数のプロジェクトを使う場合
コンテナの中に入って、それぞれ
pip install -r requirements.txtをする必要があるかもしれません。

Nginxのほうは、locationで追加した文字列ごとに共通のコンテナでポートを分ければ
1つのDjangoのコンテナで同時に複数のプロジェクトを動かすことができました。

Nginx(wad-nginxコンテナ)のjikken1.conf
    location / {
        proxy_pass   http://django2_web_run_9ea11b0af0c7:8000;

    location /zundahage {
        proxy_pass   http://django2_web_run_9ea11b0af0c7:8001;
目次へ

Nginxのログ

Nginxのログを見れるようにしておかないとセキュリティ対策できないので見れる方法を色々調べた。

リアルタイムでアクセスが見れた
sudo docker logs コンテナID -f 2>/dev/null
参考;https://global-hack.com/blog/archives/126
追加でGeminiに聞いた
-f オプションは、コンテナが新しいログを出力するたびに、そのログを標準出力に表示します。
2>/dev/null オプションは、標準エラー出力 (stderr) を /dev/null にリダイレクトします。/dev/null は、入力されたデータを破棄する特殊なファイルです。
docker logs コンテナID > log.txt
はログをlog.txtファイルにコピーできる。
Vsコード接続してるときに、このコマンドを実行するとVsコードにlog.txtができる。
VPSのほうはコマンドを実行したディレクトリにできます。
Vsコードでlog.txtを右クリックしダウンロードでローカルにダウンロードできます。

実際に見るとすでに攻撃っぽいアクセスしてきているので準備ができるまで
自分のIP以外ufwでアクセス不可にした。しかしアクセスされてる。
NginxのDockerコンテナで ports: – “80:80″にしているときは、ここはufwが効かないみたい。「docker ufw」で検索するといろいろ出てきます。(ここは要注意)
ということでufwも開放した。
ただDjangoはどうか分からないがWordPressは、
このセキュリティ対策してない初期状態で開放すると攻撃に弱いので
DockerのNginxの開放するポートの番号変えて
WordPressのセキュリティ対策してから80で開放したほうがよさそうだな。

VPS(KVM)サーバーコントロールパネルのIPセキュリティはDockerの設定よりも強い(仮想マシンの外側の設定)みたいなので、使わないときは、ここで削除してつながらないようにしよう。簡単に戻せるし。

所感

Nginxのアクセスログを見ると、
怪しいアクセスが常にあるので改めてセキュリティ対策が重要だと思った。
レンタルサーバーとは違いVPSではセキュリティ対策の手間が、かなりかかりそうで中々、本題に入れない。
目次へ
イチゲをOFUSEで応援する(御質問でもOKです)Vプリカでのお支払いがおすすめです。
MENTAやってます(ichige)

コメント

タイトルとURLをコピーしました