Django -- 從平凡到超凡

第 14 章    部署專案

(1) 雲端運算

∗ 雲端運算的基本型態:

▸ 設備即服務 (Infrastructure as a Service, IaaS)

✶ 提供機器、儲存空間、與網路資源
✶ 使用者需自行安裝作業系統、資料庫管理系統、網路伺服器、及其他應用程式
✶ 範例:Amazon EC2, Google Compute Engine, Rackspace, GoGrid, Microsoft, HP, AT&T, OpSource, ...

▸ 平台即服務 (Platform as a Service, PaaS)

✶ 提供使用者可以開發 Web 系統的平台
✶ 包括硬體(伺服器與硬碟)、作業系統、開發工具、及管理工具
✶ 使用者僅需專注自己所開發的應用程式,其餘有關伺服器、作業系統、資料庫、 或網路等基礎設施問題均交由雲端供應商負責
✶ 範例:Heroku, Google App Engine, Windows Azure, Force.com, Cloud Foundry, PythonAnyWhere, ...

▸ 軟體即服務 (Software as a Service, SaaS)

✶ 一個完整的軟體應用程式,使用者透過瀏覽器即可使用應用程式
✶ 優點:
# 簡單:只需瀏覽器,無需安裝其他軟體
# 成本低:無需購置額外設備
# 可伸縮:用多少付多少
✶ 範例:Salesforce.com, Google Suite, TurboTax, Zoho, QuickBooks, Clarizen, ...

▸ 儲存即服務 (Storage as a Service, StaaS)

✶ 一般型:提供資料儲存服務,使用者可上載、下載、備份,有些提供線上編輯
# 例如:Google drive, Dropbox, Box.net, SugarSync, Mozy, Zmanda, ...
✶ 程式型:提供資料儲存服務,使用者可透過應用程式存取資料
# 例如:Amazon Simple Storage Service (S3), Google cloud storage (GS)

∗ Heroku 的 PaaS 服務

Heroku 於 2007 年 創立並提供 PaaS 服務, 於 2010 年被 Salesforce 併購

▸ 架構在 Amazon EC2 的 IaaS 服務上

▸ 提供各種應用程式架構:Python, Node.js, Ruby, Java, PHP, Go, Scala and Play, Clojure

▸ 提供各種資料庫:Postgres, SQLite3, MySQL (ClearDB)

▸ 可直接利用 Github 將專案部署到 Heroku:按下按鍵就部署完畢

(2) Heroku 的相關設定

∗ 安裝 django-toolbelt

(blogVenv)$ pip install django-toolbelt

▸ 此套件包含:gunicorn, dj-database-url, dj-static

∗ 在專案根目錄新增以下三個檔案:

1. Procfile:指定 Web 伺服器為 gunicorn , 並使用 blog.wsgi 模組為 Python 與伺服器的溝通介面

Procfile
web: gunicorn blog.wsgi --log-file -
註:以上的 blog 是 app 名稱,如果是其他專案,應該修改名稱

2. requirements.txt:指定需安裝的套件清單

✶ 在 專案根目錄 下執行 (blogVenv)$ pip freeze > requirements.txt 指令來產生檔案
requirements.txt
dj-database-url==x.x.x
dj-static==x.x.x
Django==x.x.x
django-toolbelt==x.x.x
gunicorn==x.x.x
psycopg2==x.x.x.x
pytz==x.x
sqlparse==0.3.0
static3==x.x.x
requirements.txt 裡一定要有 Django 項目,如此 Heroku 才能確定是 Django 專案
✶ 每次修改安裝套件,一定要執行 pip freeze > requirements.txt 指令, 以確保雲端的環境和本機端的環境相同

3. runtime.txt:指定應用程式的執行環境所使用的 Python 版本 (依據 Heroku 所支援的版本)

runtime.txt
python-3.7.3
✶ 檢查 Heroku 支援的 Python 版本:https://devcenter.heroku.com/articles/python-runtimes

▸ 專案根目錄內容如下:

blog/
   account/
   article/ 
   blog/
   main/
   populate/  
   manage.py
   Procfile
   requirements.txt
   runtime.txt

(3) 設定 settings.py 與 wsgi.py

▸ 修改設定檔

blog/settings.py
...
DEBUG = True
if 'DYNO' in os.environ:    # Running on Heroku
    DEBUG = False

...

# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases

if DEBUG:   # Running on the development environment
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'blogdb',
            'USER': 'blog',
            'PASSWORD': 'blog',
            'HOST': 'localhost',
            'PORT': '',     # Set to empty string for default.
        }
    }
else:   # Running on Heroku
    # Parse database configuration from $DATABASE_URL
    import dj_database_url
    DATABASES = {'default':dj_database_url.config()}
    # Honor the 'X-Forwarded-Proto' header for request.is_secure()
    SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

...

LOGIN_URL = '/account/login/'

# For Heroku deployment
STATIC_ROOT = 'staticfiles'
if 'DYNO' ...DYNO 是 Heroku 的特有環境變數,藉此判斷專案是在 Heroku 上執行或是在本機端執行,若是在 Heroku 環境則將 DEBUG 設為 False
# DEBUG = False:細節的錯誤訊息不會在使用者瀏覽器顯示,以確保系統安全
if DEBUG: ... else: ...:如果在 Heroku 上執行,則改用 Heroku 的資料庫
✶ 最後設定 STATIC_ROOT = 'staticfiles' ,讓 Heroku 在部署時可順利執行 collectstatic 指令,將所有靜態檔案複製到專案下的 staticfiles 目錄

▸ 修改 wsgi 檔

blog/wsgi.py
...

application = get_wsgi_application()

from blog.settings import DEBUG
if not DEBUG:    # Running on Heroku
    from dj_static import Cling
    application = Cling(get_wsgi_application())
✶ WSGI (Web Server Gateway Interface):網站伺服器閘道介面,是 Python 網頁程式和伺服器溝通的介面
✶ 最後加入:匯入 DEBUG 變數進行判斷,如果是假 (亦即是在 Heroku 執行), 則將 Heroku 的反向代理伺服器 Cling 架在前面,負責處理所有請求,如果是
# 靜態檔案請求,就直接從 staticfiles 目錄裡送出
# 動態網頁請求,就轉給應用程式伺服器 Gunicorn 處理

▸ 在本機安裝 Heroku CLI,之後就可執行 Heroku 所提供的 CLI 指令

✶ Ubuntu:
$ sudo snap install --classic heroku
✶ Windows:
# 至 Heroku 官網 下載 Heroku CLI 套件 (Windows 64-bit installer,檔名:heroku-x64.exe) 並安裝
✶ Mac:
$ brew tap heroku/brew && brew install heroku

(4) 撰寫部署專用的填充程式

∗ 建立生產環境填充程式,內容如下:

populate/production.py

from populate import base
from account.models import User

print('Creating admin account ... ', end='')
User.objects.create_superuser(username='admin', password='xxxxxxxx', email=None, fullName='管理者')
print('done')

▸ 在生產環境中,我們一開始只建立一筆管理者帳號資料,其餘使用者或是文章及留言等測試資料均不建立

▸ 這時候管理者密碼 (上述 xxxxxxxx) 就非常重要了,記得使用安全程度較高的密碼

(5) 將資料庫遷移檔案納入版本控制

▸ 刪除 .gitignore 裡的 00*.py 項目:部署時, 資料庫遷移檔案必須上載至 Heroku ,如此才能在 Heroku 建立或修改資料庫

.gitignore
*~
__pycahe__
*.pyc
00*.py

▸ 如果是第一次部署,可以將所有遷移檔案全部刪除,然後重新執行 makemigrations, 以建立 *初始* 的遷移檔案來進行部署

(6) 上推專案到 Github

... Commit message: Ready to deploy Commit and Push

(7) 部署至 Heroku

∗ 註冊 Heroku 帳號:

▸ 至 Heroku 網站註冊 Pick your development language: Python 驗證電子信箱

∗ 建立 Heroku 專案:

▸ 登入 Heroku,點右上方的 9 個藍色點的圖示,然後點選 Dashboard

Dashboard

▸ 建立新 App

✶ 點右上方「New」按鈕 Create new app App name: <appName>, Region: United States (or Europe) Create App
New app
✶ 註:Heroku 的 app name space 是所有使用者共用,因此不可重複,blog 名稱可能早已被註冊,因此需改用其他名稱 (例如:<appName>blog12345)

∗ 設定專案連結到 Github 帳號並部署

▸ 點 App

Heroku app

▸ 點「Deploy」頁籤 Deployment method: Github, Connect to Github Connect to Github 登入 Github Search for a repository to connect to: blog Search Connect

Connect Github

▸ 開始部署:點「Deploy Branch」

Deploy branch
-----> Python app detected
-----> Installing python-3.7.3
-----> Installing pip
-----> Installing SQLite3
-----> Installing requirements with pip
       Collecting...
         Downloading ...
         ...
       Successfully installed ...
       $ python manage.py collectstatic --noinput
       xxx static files copied to '/tmp/build_.../staticfiles'.
-----> Discovering process types
       Procfile declares types -> web
-----> Compressing...
       Done: xxxM
-----> Launching...
       Released 
       https://<appName>.herokuapp.com/ deployed to Heroku
✶ 以上訊息說明 Heroku 在部署時會執行 collectstatic, 將所有靜態檔案全部複製一份放在專案目錄下的 staticfiles 子目錄 (--noinput:一次完成,使用者不用再輸入任何資料)
✶ 可登入 Heroku 並檢視部署結果 (以下指令 -i 為 Interactive 之意)
$ heroku login -i
heroku: Enter your login credentials
Email [xxx@xxx.xxx]: xxx@xxx.xxx
Password: xxxxxxxx
Logged in as xxx@xxx.xxx
$ heroku run bash -a <appName>
Running bash on ...

$ ls -l
drwx------ 6 u13281 dyno 4096 Oct 23 14:47 article
drwx------ 3 u13281 dyno 4096 Oct 23 14:47 blog
drwx------ 6 u13281 dyno 4096 Oct 23 14:47 main
-rwx------ 1 u13281 dyno  802 Oct 23 14:43 manage.py
drwx------ 2 u13281 dyno 4096 Oct 23 14:43 populate
-rw------- 1 u13281 dyno   36 Oct 23 14:43 Procfile
-rw------- 1 u13281 dyno  125 Oct 23 14:43 requirements.txt
-rw------- 1 u13281 dyno   12 Oct 23 14:43 runtime.txt
drwx------ 5 u13281 dyno 4096 Oct 23 14:47 staticfiles
$ cd staticfiles
$ ls -l
# 其結構如下:
staticfiles/
   admin/
      css/
      fonts/
      img/
      js/
   article/
      css/
   main/
      css/
      img/
      js/
也就是說,collectstatic 將所有 app 的 static/ 目錄裡的內容全部複製到 staticfiles 目錄裡, 這也就是我們在 <app>/static/ 底下還要再建立一個 <app> 目錄的原因
✶ 登出 Heroku:
$ exit

∗ 進行雲端資料庫遷移

▸ 部署完畢,下一個步驟就是進行資料庫遷移:利用遷移檔案 (00*.py) 在雲端建立資料表

$ heroku run python manage.py migrate -a <appName>

Running python manage.py migrate on  ... 
Operations to perform:
  Apply all migrations: ...
Running migrations:
  Applying ... OK
  ...

∗ 啟動 Heroku DYNO

$ heroku ps:scale web=1 -a <appName>

Scaling dynos... done, now running web at 1:Free

∗ 測試:

在瀏覽器 url 輸入 http://<appName>.herokuapp.com/ 確認系統所有功能正常

∗ 接下來執行填充程式:

$ heroku run python -m populate.production -a <appName>

Running python -m populate.production on ⬢ <appName>... up, run.4701 (Free)

Creating admin account...done

∗ 檢查執行紀錄:

$ heroku logs -a <appName>

∗ 查看 Heroku 所配置的資源

▸ 點「Overview」或 「Resource」頁籤,可以看到目前 Heroku 配置一個 Postgres 資料庫以及一個免費 dyno

Resources

∗ 轉址設定

▸ 如果已有網域,可以將該網域導至 Heroku app

▸ 需要先輸入信用卡資料,然後點「Settings」 Domains Add domain Domain name: xxx.xxx.xxx Save changes

∗ 加入插件:Heroku 提供許多插件工具,有些免費、有些需付費

在 Resources 頁籤 FIND MORE ADD-ONS ,常用的 add-on:

▸ 系統效能監控 (Monitoring): New Relic APM

▸ 執行紀錄 (Logging): Papertrail

▸ 錯誤報告 (Errors and Exceptions): Rollbar

∗ 後續部署,系統上線後 (Production stage):

▸ 若程式有修改,則先 Push 至 Github ,再部署至 Heroku 以更新程式

▸ 所有資料庫遷移檔案 (00*.py) 都需要上載到 Github 與 Heroku , 如此 Heroku 才能依據遷移資訊更新資料庫

▸ 若需要修改 Model,程序如下:

✶ 確認只有一個開發者進行 Model 修改
✶ 修改後在本機端執行 Makemigrations 來產生遷移檔案 (00*.py),以便上推到 Github 並部署到 Heroku
✶ Push 至 Github (包含所有遷移檔案 00*.py )
✶ 部署至 Heroku
✶ 在 Heroku 執行 Migrate
✶ 其他開發者 Pull from Github (如果發生資料衝突,需將本機端所有專案資料清除,再重新 Pull )

∗ 註:清除資料庫裡的所有資料 (危險!)

$ heroku pg:reset DATABASE_URL -a <appName>

DYNO 型態與資料庫方案

∗ DYNO 型態

▸ Heroku 提供許多 DYNO 型態

▸ 預設免費 DYNO,規格如下:

✶ 1 個 CPU (分享),512 MB RAM
✶ 30 分鐘內未收到 Request 就開始睡覺,之後收到 Request 後需要花十幾秒鐘時間甦醒
✶ 總 DYNO 時數
- 沒有信用卡:一個帳戶全月 550 小時 (所有 app 加總)
- 有綁定信用卡:1,000 小時

▸ DYNO型態:

https://devcenter.heroku.com/articles/dyno-types#available-dyno-types
Dyno Type Sleeps Professional
Features
Memory
(RAM)
CPU
Share
Dedicated Compute Price
(USD)
free yes no 512MB 1x no 1x-4x 0
hobby no no 512MB 1x no 1x-4x 7
standard-1x no yes 512MB 1x no 1x-4x 25
standard-2x no yes 1024MB 2x no 4x-8x 50
performance-m no yes 2.5GB 100% yes 11x 250
performance-l no yes 14GB 100% yes 46x 500

∗ Database 方案

https://elements.heroku.com/addons/heroku-postgresql#dev

https://devcenter.heroku.com/articles/heroku-postgres-plans

Heroku
Postgres tier
Downtime
Tolerance
Fork Follow Rollback HA Disk
Encryption
Hobby < 4 hr downtime per mo. No No No No No
Standard < 1 hr downtime per mo. Yes Yes 4 days No Yes
Premium < 15 min downtime per mo. Yes Yes 1 week Yes Yes
Private < 15 min downtime per mo. Yes Yes 1 week Yes Yes
Shield < 15 min downtime per mo. Yes Yes 1 week Yes Yes

Plan Name RAM
Size
Row
Limit
Storage
Limit
Connection
Limit
Monthly
Price
hobby-dev 0 10,000 0 20 $0
hobby-basic 0 10,000,000 0 20 $9
Standard0 4 GB no 64 GB 120 $50
Standard2 8 GB no 256 GB 400 $200
Standard3 15 GB no 512 GB 500 $400
Standard4 61 GB no 1 TB 500 $1,400
...

▸ 練習