准备
首先要更新软件源,以 Ubuntu 为例:
$ sudo apt-get update -y
安装 python3
、virtualenv
、nginx
、sqlite3
、openssl
:
$ sudo apt-get install -y python3 nginx sqlite3 openssl
$ python -m pip install virtualenv
之后,创建并进入虚拟环境:
$ cd /path/to/project/
$ virtualenv venv
$ source venv/bin/activate
需要退出时:
(venv) $ deactivate
在虚拟环境中安装 django
、gunicorn
:
(venv) $ pip install django gunicorn
创建项目
如果还没有创建项目:
$ mkdir django-project/
$ django-admin startproject myproject django-project/
$ cd django-project/
$ django-admin startapp myapp
$ python manage.py migrate
$ mkdir -pv myapp/templates/myapp/
现在,你的目录结构大概是这样的:
/home/ubuntu/
│
├── django-project/
│ │
│ ├── myapp/
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── __init__.py
│ │ ├── migrations/
│ │ │ └── __init__.py
│ │ ├── models.py
│ │ ├── templates/
│ │ │ └── myapp/
│ │ ├── tests.py
│ │ └── views.py
│ │
│ ├── myproject/
│ │ ├── asgi.py
│ │ ├── __init__.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ │
│ ├── db.sqlite3
│ └── manage.py
│
└── venv/ ← Virtual environment
接着,编辑 myproject/settings.py
文件,在 INSTALLED_APPS
部分添加刚才我们创建的 myapp
应用:
$ nano myproject/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"myapp",
]
创建主页:
$ nano myapp/templates/myapp/home.html
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>My secure app</title>
</head>
<body>
<p><span id="changeme">Now this is some sweet HTML!</span></p>
<script src="/static/js/greenlight.js"></script>
</body>
</html>
准备渲染工作:
$ nano myapp/views.py
from django.shortcuts import render
def index(request):
return render(request, "myapp/home.html")
添加链接:
$ nano myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
]
$ nano myproject/urls.py
from django.urls import include, path
urlpatterns = [
path("myapp/", include("myapp.urls")),
path("", include("myapp.urls")),
]
SECRET_KEY
准备好一个简单的页面后,我们开始进入正题,编辑 myproject/settings.py
并找到类似下面的内容:
SECRET_KEY = "django-insecure-o6w@a46mx..." # 删除或注释这行
# SECRET_KEY = "django-insecure-o6w@a46mx..." # 删除或注释这行
相应的,使用新的内容替换:
import os
# ...
try:
SECRET_KEY = os.environ["SECRET_KEY"]
except KeyError as e:
raise RuntimeError("Could not find a SECRET_KEY in environment") from e
此时,Django
便知道要去环境变量中寻找 SECRET_KEY
而不是在项目源文件中。
现在我们去创建这个环境变量:
$ echo "export SECRET_KEY='$(openssl rand -hex 40)'" > .DJANGO_SECRET_KEY
$ source .DJANGO_SECRET_KEY
你可以查看这个文件进行校验:
$ cat .DJANGO_SECRET_KEY
export SECRET_KEY='26a2d2ccaf9ef850...'
WSGIServer
$ pwd
/home/ubuntu
$ source env/bin/activate
$ python -m pip install httpie
$ # 发送 GET 请求并且追踪 30 次重定向
$ alias GET='http --follow --timeout 6'
有必要检查一下目前的进展:
$ cd django-project/
$ python manage.py check
System check identified no issues (0 silenced).
$ # 在后台 127.0.0.1:8000
$ nohup python manage.py runserver &
$ jobs -l
[1]+ 43689 Running nohup python manage.py runserver &
现在这个网站还只能在本地访问,我们来成为第一个访客吧!
$ GET :8000/myapp/
HTTP/1.1 200 OK
Content-Length: 182
Content-Type: text/html; charset=utf-8
Date: Sat, 28 Jan 2020 00:11:38 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.10
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>My secure app</title>
</head>
<body>
<p>Now this is some sweet HTML!</p>
</body>
</html>
准备上线
当然,首先你要有一台服务器以及其公网 IP,并完成配置 DNS、域名解析等操作。
例如:
Type Host Value TTL A Record @ 50.19.125.152 Automatic A Record www 50.19.125.152 Automatic
现在,我们先结束前面运行的服务:
$ jobs -l
[1]+ 43689 Running nohup python manage.py runserver &
$ kill 43689
[1]+ Done nohup python manage.py runserver
可以进一步确认:
$ pgrep runserver # 空
$ jobs -l # Empty or 'Done'
$ sudo lsof -n -P -i TCP:8000 -s TCP:LISTEN # 空
$ rm nohup.out
$ nano project/settings.py
# 把下面的域名换成你的域名:
ALLOWED_HOSTS = [".caveops.com"]
# 这样可以同时允许 `www.caveops.com` 与 `caveops.com`。
再次尝试运行:
$ nohup python manage.py runserver '0.0.0.0:8000' &
打开日志 nohup.out
输出:
$ tail -f nohup.out
现在,到浏览器里输入下面的地址试试吧:
http://www.caveops.codes:8000/myapp/
而在刚刚打开的日志中,应该会出现类似下面的内容:
[<date>] "GET /myapp/ HTTP/1.1" 200 182
Gunicorn
现在我们将 WSGIServer
替换为 Gunicorn
吧。
$ pwd
/home/ubuntu/django-project
$ mkdir -pv config/gunicorn/
mkdir: created directory 'config'
mkdir: created directory 'config/gunicorn/'
$ sudo mkdir -pv /var/{log,run}/gunicorn/
mkdir: created directory '/var/log/gunicorn/'
mkdir: created directory '/var/run/gunicorn/'
$ sudo chown -cR ubuntu:ubuntu /var/{log,run}/gunicorn/
changed ownership of '/var/log/gunicorn/' from root:root to ubuntu:ubuntu
changed ownership of '/var/run/gunicorn/' from root:root to ubuntu:ubuntu
创建配置文件:
$ nano config/gunicorn/dev.py
"""Gunicorn *development* config file"""
# Django WSGI application path in pattern MODULE_NAME:VARIABLE_NAME
wsgi_app = "caveops.wsgi:application"
# The granularity of Error log outputs
loglevel = "debug"
# The number of worker processes for handling requests
workers = 2
# The socket to bind
bind = "0.0.0.0:8000"
# Restart workers when code changes (development only!)
reload = True
# Write access and error info to /var/log
accesslog = "/var/log/gunicorn/dev.log"
errorlog = "/var/log/gunicorn/error.log"
enable_stdio_inheritance = True
# Redirect stdout/stderr to log file
capture_output = True
# PID file so you can easily fetch process ID
pidfile = "/var/run/gunicorn/dev.pid"
# Daemonize the Gunicorn process (detach & enter background)
daemon = True
关闭之前运行的服务:
$ jobs -l
[1]+ 26374 Running nohup python manage.py runserver &
$ kill 26374
[1]+ Done nohup python manage.py runserver
在使用 Gunicorn
运行前,别忘了 .DJANGO_SECRET_KEY
哦:
$ pwd
/home/ubuntu/django-project
$ source .DJANGO_SECRET_KEY
$ gunicorn -c config/gunicorn/dev.py
现在可以看看日志了:
$ tail -f /var/log/gunicorn/dev.log
[2020-01-28 01:29:50 +0000] [49457] [INFO] Starting gunicorn 20.1.0
[2020-01-28 01:29:50 +0000] [49457] [DEBUG] Arbiter booted
[2020-01-28 01:29:50 +0000] [49457] [INFO] Listening at: http://0.0.0.0:8000 (49457)
[2020-01-28 01:29:50 +0000] [49457] [INFO] Using worker: sync
[2020-01-28 01:29:50 +0000] [49459] [INFO] Booting worker with pid: 49459
[2020-01-28 01:29:50 +0000] [49460] [INFO] Booting worker with pid: 49460
[2020-01-28 01:29:50 +0000] [49457] [DEBUG] 2 workers
同样的,在浏览器里访问试试:
http://www.caveops.com:8000/myapp/
你会在日志中看到一些新的信息:
113.xx.xx.xx - - [27/Sep/2020:01:28:46 +0000] "GET /myapp/ HTTP/1.1" 200 182
Nginx
首先,在你的服务器管理平台开放
80
端口HTTP
(TCP
) 访问。
启动 Nginx
:
$ sudo systemctl start nginx
$ sudo systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; ...
Active: active (running) since Mon 2020-01-28 01:37:04 UTC; 2min 49s ago
...
到浏览器访问试试:
http://caveops.com/
这是你应该会看到一个 Welcome to nginx
的页面。
而如果你访问前面那个地址,会显示 404 Not Found
:
http://caveops.com/myapp/
创建一个配置文件:
$ nano /etc/nginx/sites-available/caveops
server_tokens off;
access_log /var/log/nginx/supersecure.access.log;
error_log /var/log/nginx/supersecure.error.log;
server {
listen 80;
server_name caveops.com www.caveops.com;
location /favicon.ico {
alias /home/ubuntu/django-project/collectstatic/favicon.ico;
}
location /robots.txt {
alias /home/ubuntu/django-project/collectstatic/robots.txt;
}
location /humans.txt {
alias /home/ubuntu/django-project/collectstatic/humans.txt;
}
location /static {
autoindex on;
alias /home/ubuntu/django-project/collectstatic;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
# don't cache it
proxy_no_cache 1;
# even if cached, don't try to use it
proxy_cache_bypass 1;
}
location /media {
autoindex on;
alias /home/ubuntu/django-project/media/;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
# don't cache it
proxy_no_cache 1;
# even if cached, don't try to use it
proxy_cache_bypass 1;
}
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_pass_header Server;
proxy_redirect off;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 60;
proxy_read_timeout 60;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
# don't cache it
proxy_no_cache 1;
# even if cached, don't try to use it
proxy_cache_bypass 1;
}
}
同时修改默认配置:
$ nano /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
client_max_body_size 200M;
sendfile off;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}
上面的配置中关闭了
Nginx
缓存功能,如果你不需要,请根据情况自行修改:
# /etc/nginx/sites-available/caveops
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
# don't cache it
proxy_no_cache 1;
# even if cached, don't try to use it
proxy_cache_bypass 1;
# /etc/nginx/nginx.conf
sendfile off;
别急,此时这个配置文件还不能使用,我们还需要处理一些 Django
的设置:
$ pwd
/home/ubuntu/django-project
$ mkdir -p static/js
创建一个 JavaScript
文件用来测试:
$ nano static/js/greenlight.js
// Enlarge the #changeme element in green when hovered over
(function () {
"use strict";
function enlarge() {
document.getElementById("changeme").style.color = "green";
document.getElementById("changeme").style.fontSize = "xx-large";
return false;
}
document.getElementById("changeme").addEventListener("mouseover", enlarge);
}());
修改 Django
设置:
$ nano myproject/settings.py
STATIC_URL = "/static/"
# Note: 把下面的域名替换为你的域名
STATIC_ROOT = BASE_DIR / 'collectstatic'
STATICFILES_DIRS = [BASE_DIR / "static"]
MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = '/media/'
同时关闭 DEBUG
设置:
DEBUG = False
创建这个目录:
$ sudo mkdir -pv /var/www/caveops/static/
mkdir: created directory '/var/www/caveops'
mkdir: created directory '/var/www/caveops/static/'
$ sudo chown -cR ubuntu:ubuntu /var/www/caveops/
changed ownership of '/var/www/caveops/static' ... to ubuntu:ubuntu
changed ownership of '/var/www/caveops/' ... to ubuntu:ubuntu
整理静态文件:
$ pwd
/home/ubuntu/django-project
$ source venv/bin/activate
(venv )$ python3 manage.py collectstatic
129 static files copied to '/var/www/caveops/static'.
现在测试一下 Nginx
的配置文件吧:
$ sudo service nginx configtest /etc/nginx/sites-available/caveops
* Testing nginx configuration [ OK ]
重启 Nginx
:
$ sudo systemctl restart nginx
在浏览器访问试试:
http://caveops.com/myapp/
你应当会看到我们编写的网站页面,并且文字被替换成了绿色。也就是说,页面访问与资源文件访问的配置都完成了。
HTTPS
简单的方法
最简单的办法是通过 Cloudfrale,使用 Cloudfrale 的 DNS
并进行域名解析。
请注意,你可能需要在 Cloudfrale 控制台对缓存机制进行设置已达到理想效果。
复杂一些的方法
首先修改 Nginx
配置文件:
$ nano /etc/nginx/nginx.conf
替换下面内容
# File: /etc/nginx/nginx.conf
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# File: /etc/nginx/nginx.conf
ssl_protocols TLSv1.2 TLSv1.3;
确认是否支持 1.3
:
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
现在可以安装 Certbot
了:
$ sudo snap install --classic certbot
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
此时仍要注意开放端口:
Reference | Type | Protocol | Port Range | Source |
---|---|---|---|---|
1 | HTTPS | TCP | 443 | 0.0.0.0/0 |
2 | HTTP | TCP | 80 | 0.0.0.0/0 |
3 | Custom | All | All | security-group-id |
4 | SSH | TCP | 22 | my-laptop-ip-address/32 |
执行下面命令:
$ sudo certbot --nginx --rsa-key-size 4096 --no-redirect
Saving debug log to /var/log/letsencrypt/letsencrypt.log
...
你可能会被要求根据提示进行一些配置,比如输入邮箱等。
当被要求输入域名时,仿照下列格式(逗号分隔)输入:
www.caveops.com,caveops.com
完成后,你会看到类似下面的信息:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/www.caveops.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/www.caveops.com/privkey.pem
This certificate expires on 2020-01-28.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this
certificate in the background.
Deploying certificate
Successfully deployed certificate for caveops.com
to /etc/nginx/sites-enabled/caveops
Successfully deployed certificate for www.caveops.com
to /etc/nginx/sites-enabled/caveops
Congratulations! You have successfully enabled HTTPS
on https://caveops.com and https://www.caveops.com
接下来在 /etc/nginx/site-available/caveops
中 server
中增加一段内容:
server {
# ....
# 增加下面的内容:
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/www.caveops.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.caveops.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
重新加载 Nginx
:
$ sudo systemctl reload nginx
在浏览器访问试试:
https://www.caveops.com/myapp/
此时大部分浏览器都会在地址栏出现一个锁图标,大功告成!
HTTP
重定向到 HTTPS
同样还是修改 Nginx
配置文件:
$ nano /etc/nginx/sites-available/caveops
单独添加下面的内容:
server {
server_name .caveops.com;
listen 80;
return 307 https://$host$request_uri;
}
# 添加上面的内容
server {
# ...
}
再次进行测试:
$ sudo service nginx configtest /etc/nginx/sites-available/supersecure
* Testing nginx configuration [ OK ]
重新载入:
$ sudo systemctl reload nginx
使用 HTTPie
访问:
$ GET --all http://caveops.com/myapp/
HTTP/1.1 307 Temporary Redirect
Connection: keep-alive
Content-Length: 164
Content-Type: text/html
Date: Tue, 28 Jan 2020 02:16:30 GMT
Location: https://caveops.com/myapp/
Server: nginx
<html>
<head><title>307 Temporary Redirect</title></head>
<body bgcolor="white">
<center><h1>307 Temporary Redirect</h1></center>
<hr><center>nginx</center>
</body>
</html>
HTTP/1.1 200 OK
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Tue, 28 Jan 2020 02:16:30 GMT
Referrer-Policy: same-origin
Server: nginx
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>My secure app</title>
</head>
<body>
<p><span id="changeme">Now this is some sweet HTML!</span></p>
<script src="/static/js/greenlight.js"></script>
</body>
</html>