
# 部署
最終,你終于可以向全世界展示你的應用了。是時候部署它了。這個過程總能讓人感到受挫,因為有太多任務需要完成。同時在部署的過程中你需要做出太多艱難的決定。我們會談論一些關鍵的地方以及我們一些可能的選擇。
## 托管主機
首先,你需要一個服務器。世上服務器提供商成千,但我只取三家。我不會談論如何開始使用它們的服務的細節,因為這超出本書的范圍。相反,我只會談論它們作為Flask應用托管商上的優點。
### Amazon Web Services EC2(因為國情問題,讓我們直接看下一個吧)
Amazon Web Services指的是一套相關的服務,提供商是……~~卓越~~亞馬遜!今日,許多著名的初創公司選擇使用它,所以你或許已經聽過它的大名。AWS服務中我們最關心的是EC2,全稱是Elastic Compute Cloud。EC2的最大的賣點是你能夠獲得虛擬主機,或者說實例(這是AWS官方稱呼),在僅僅幾秒之內。如果你需要快速拓展你的應用,就只需啟動多一點EC2實例給你的應用,并且用一個負載平衡器(load balancer)管理它們。(這時還可以試試AWS Elastic Load Balancer)
對于Flask而言,AWS就是一個常規的虛擬主機。付上一些費用,你可以用你喜歡的Linux發行版啟動它,并安上你的Flask應用。之后你的服務器就起來了。不過它意味著你需要一些系統管理知識。
### Heroku
Heroku是一個應用托管網站,基于諸如EC2的AWS的服務。他們允許你獲得EC2的便利,而無需系統管理經驗。
對于Heroku,你通過`git push`來在它們的服務器上部署代碼。這是非常便利的,如果你不想浪費時間ssh到服務器上,安裝并配置軟件,繼續整個常規的部署流程。這種便利是需要花錢購買的,盡管AWS和Heroku都提供了一定量的免費服務。
> **參見**
> Heroku有一個如何在它們的服務器上部署Flask應用的教程:
> <https://devcenter.heroku.com/articles/getting-started-with-python>
> **注意**
> 管理你自己的數據庫將會花上許多時間,而把它做好也需要一些經驗。通過配置你自己的站點來學習數據庫管理是好的,但有時候你會想要外包給專業團隊來省下時間和精力。Heroku和AWS都提供有數據庫管理服務。我個人還沒試過,但聽說它們不錯。如果你想要保障數據安全以及備份,卻又不想要自己動手,值得考慮一下它們。
> - Heroku Postgres: https://www.heroku.com/postgres
> - Amazon RDS: https://aws.amazon.com/rds/
### Digital Ocean
Digital Ocean是最近出現的EC2的競爭對手。一如EC2,Digital Ocean允許你快速地啟動虛擬主機(在這里叫droplet)。所有的droplet都運行在SSD上,而在EC2,如果你用的是普通服務,你是享受不到這種待遇的。對我而言,最大的賣點是它提供的控制接口比AWS控制面板簡單和容易多了。Digital Ocean是我個人的最愛,我建議你考慮下它。
在Digital Ocean,Flask應用部署方式就跟在EC2一樣。你會得到一個全新的Linux發行版,然后需要安裝你的全套軟件。
## 部署工具
這一節將包括一些為了向別人提供服務,你需要安裝在服務器上的軟件。最基本的是一個前置服務器,用來反向代理請求給一個運行你的Flask應用的應用容器。你通常也需要一個數據庫,所以我們也會略微談論下這方面的內容。
### 應用容器
在開發應用時,本地運行的那個服務器并不能處理真實的請求。當你真的需要向公眾發布你的應用,你需要在應用容器,例如Gunicorn,上運行它。Gunicorn接待請求,并處理諸如線程的復雜事務。
要想使用Gunicorn,需要通過pip安裝`gunicorn`到你的虛擬環境中。運行你的應用只需簡單的命令。為了簡明起見,讓我們假設這就是我們的Flask應用:
_app.py_
```python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello World!"
```
哦,這真簡明扼要。現在,使用Gunicorn來運行它吧,我們只需執行這個命令:
```
(ourapp)$ gunicorn rocket:app
2014-03-19 16:28:54 [62924] [INFO] Starting gunicorn 18.0
2014-03-19 16:28:54 [62924] [INFO] Listening at: http://127.0.0.1:8000 (62924)
2014-03-19 16:28:54 [62924] [INFO] Using worker: sync
2014-03-19 16:28:54 [62927] [INFO] Booting worker with pid: 62927
```
你應該能在 http://127.0.0.1:8000 看到“Hello World!”。
為了在后臺運行這個服務器(也即使它變成守護進程),我們可以傳遞`-D`選項給Gunicorn。這下它會持續運行,即使你關閉了當前的終端會話。
如果我們這么做了,當我們想要關閉服務器時就會困惑于到底應該關閉哪個進程。我們可以讓Gunicorn把進程ID儲存到文件中,這樣如果想要停止或者重啟服務器時,我們可以不用在一大串運行中的進程中搜索它。我們使用`-p <file>`選項來這么做。現在,我們的Gunicorn部署命令是這樣:
```
(ourapp)$ gunicorn rocket:app -p rocket.pid -D
(ourapp)$ cat rocket.pid
63101
```
要想重新啟動或者關閉服務器,我們可以運行對應的命令:
```
(ourapp)$ kill -HUP `cat rocket.pid` # 發送一個SIGHUP信號,終止進程
(ourapp)$ kill `cat rocket.pid`
```
默認下Gunicorn會運行在8000端口。如果這已經被另外的應用占用了,你可以通過添加`-b`選項來指定端口。
```
(ourapp)$ gunicorn rocket:app -p rocket.pid -b 127.0.0.1:7999 -D
```
#### 將Gunicorn擺上前臺
> **注意**
> Gunicorn應該隱藏于反向代理之后。如果你直接讓它監聽來自外網的請求,它很容易成為拒絕服務攻擊的目標。它不應該接受這樣的考驗。只有在debug的情況下你才能把Gunicorn擺上前臺,而且完工之后,切記把它重新隱藏到幕后。 }
如果你像前面說的那樣在服務器上運行Gunicorn,將不能從本地系統中訪問到它。這是因為默認情況下Gunicorn綁定在127.0.0.1。這意味著它僅僅監聽來自服務器自身的連接。所以通常使用一個反向代理來作為外網和Gunicorn服務器的中介。不過,假如為了debug,你需要直接從外網發送請求給Gunicorn,可以告訴Gunicorn綁定0.0.0.0。這樣它就會監聽所有請求。
```
(ourapp)$ gunicorn rocket:app -p rocket.pid -b 0.0.0.0:8000 -D
```
> **注意**
> - 從文檔中可以讀到更多關于運行和部署Gunicorn的信息 : http://docs.gunicorn.org/en/latest/
> - Fabric是一個可以允許你不通過SSH連接到每個服務器上就可以執行部署和管理命令的工具 : http://docs.fabfile.org/en/latest
### Nginx反向代理
反向代理處理公共的HTTP請求,發送給Gunicorn并將響應帶回給發送請求的客戶端。Nginx是一個優秀的客戶端,更何況Gunicorn強烈建議我們使用它。
要想配置Nginx作為運行在127.0.0.1:8000的Gunicorn的反向代理,我們可以在*/etc/nginx/sites-available*下給應用創建一個文件。不如稱之為*exploreflask.com*吧。
_/etc/nginx/sites-available/exploreflask.com_
```
# Redirect www.exploreflask.com to exploreflask.com
server {
server_name www.exploreflask.com;
rewrite ^ http://exploreflask.com/ permanent;
}
# Handle requests to exploreflask.com on port 80
server {
listen 80;
server_name exploreflask.com;
# Handle all locations
location / {
# Pass the request to Gunicorn
proxy_pass http://127.0.0.1:8000;
# Set some HTTP headers so that our app knows where the request really came from
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```
現在在*/etc/nginx/sites-enabled*下創建該文件的符號鏈接,接著重啟Nginx。
```
$ sudo ln -s \
/etc/nginx/sites-available/exploreflask.com \
/etc/nginx/sites-enabled/exploreflask.com
```
你現在應該可以發送請求給Nginx然后收到來自應用的響應。
> **參見**
> Gunicorn文檔中關于配置Nginx的部分會給你更多啟動Nginx的信息:
> <http://docs.gunicorn.org/en/latest/deploy.html#nginx-configuration>
#### ProxyFix
有時,你會遇到Flask不能恰當處理轉發的請求的情況。這也許是因為在Nginx中設置的某些HTTP報文頭部造成的。我們可以使用Werkzeug的ProxyFix來fix轉發請求。
_app.py_
```python
from flask import Flask
# Import the fixer
from werkzeug.contrib.fixers import ProxyFix
app = Flask(__name__)
# Use the fixer
app.wsgi_app = ProxyFix(app.wsgi_app)
@app.route('/')
def index():
return "Hello World!"
```
> **參見**
> 在Werkzeug文檔中可以讀到更多關于ProxyFix的信息:
> <http://werkzeug.pocoo.org/docs/contrib/fixers/#werkzeug.contrib.fixers.ProxyFix>
## 總結
* 你可以把Flask應用托管到AWS EC2, Heroku和Digital Ocean。(譯者注:建議托管到國內的云平臺上)
* Flask應用的基本部署依賴包括一個應用容器(比如Gunicorn)和一個反向代理(比如Nginx)。
* Gunicorn應該退居Nginx幕后并監聽127.0.0.1(內部請求)而非0.0.0.0(外部請求)
* 使用Werkzeug的ProxyFix來處理Flask應用遇到的特定的轉發報文頭部。