Caddy + gunicorn + django

前言

目前這個部落格是放在東京 Linode 主機上,最近遇到的問題是 nginx 每天都會自動死掉,每天都得要 ssh 進去 restart nginx service,實在有夠麻煩。再加上我有用 Let’s Encrypt,每 90 天都要更新憑證。乾脆一不做二不休的把 web server 換成會自動更新憑證的 caddy,以下這篇記錄轉換的過程。

安裝 caddy

以下參考這份文件

安裝 caddy 很簡單,直接在 shell 下執行:

curl -s https://getcaddy.com | bash -s personal

使用 personal license 來安裝 caddy

然後建立必要的目錄

sudo mkdir /etc/caddy && sudo chown -R root:www-data /etc/caddy

建立空白的 caddy 設定檔

sudo touch /etc/caddy/Caddyfile

建立給 caddy 使用的 ssl 目錄

sudo mkdir /etc/ssl/caddy
sudo chown -R www-data:root /etc/ssl/caddy
sudo chmod 0770 /etc/ssl/caddy

安裝 Caddy 系統服務

從官方 repository 下載 caddy.service

sudo curl -s https://raw.githubusercontent.com/mholt/caddy/master/dist/init/linux-systemd/caddy.service -o /etc/systemd/system/caddy.service

讓 systemd 發現新下載的 caddy.service

sudo systemctl daemon-reload

Enable caddy.service

sudo systemctl enable caddy.service

(Optional) 檢查 Caddy 是否有正確運作

建立 /var/www/index.html

echo '<h1>Hello World!</h1>' | sudo tee /var/www/index.html

編輯 /etc/caddy/Caddyfile 設定檔

http:// {
    root /var/www
    gzip
}

然後啟動 Caddy server

sudo systemctl start caddy

設定 gunicorn

之前用 nginx 當作 web server 的時候,是搭配 uwsgi 來把 request 傳到 django 內。而 Caddy 雖然有 plugin 可以支援 uwsgi,但是需要自己編 Caddy,所以在這裡我改用 gunicorn。

安裝 gunicorn 和 runit

sudo apt install -y runit
sudo -H pip install gunicorn
sudo service runit start

設定 runit,${project_name} 為您的專案名稱

mkdir /etc/sv/${product_name}/
touch /etc/sv/${project_name}/run
chmod u+x /etc/sv/${project_name}/run
vim /etc/sv/${project_name}/run

編輯 /etc/sv/${project_name}/run

#!/bin/sh

# gunicorn 執行檔位置
GUNICORN=/usr/local/bin/gunicorn
# Django 專案位置
ROOT=/var/www/my_django_project
# 執行時的 pid 位置
PID=/tmp/my_django_project.pid

# Django 專案的 wsgi.py 位置
APP=my_django_project.wsgi

if [ -f $PID ]; then rm $PID; fi

cd $ROOT
exec $GUNICORN -c $ROOT/gunicorn.conf.py --pid=$PID $APP --group www-data

再來設定 django 專案,在專案根目錄中新增 gunicorn.conf.py,內容如下

# 用本機的 8000 port
bind = "127.0.0.1:8000"
# gunicorn 的 pid file,和前面 runit 設定檔內相同
pidfile = "/tmp/my_django_project.pid"
# 設定要用幾個 worker 來執行
workers = 1
timeout = 90

再來讓 runit 把 gunicorn 跑起來

ln -s /etc/sv/${project_name} /etc/service/${project_name}

如果到這邊都有做對,當你建立好 symbolic link 後執行 ps -aux | grep gunicorn,就會看到 gunicorn 的 process 跑起來了

設定 Caddy

最後設定 Caddy 把 request 丟給 gunicorn 處理,編輯 /etc/caddy/Caddyfile,加上

my_django_project.fykuan.tw {
    root /var/www/my_django_project
    proxy / 127.0.0.1:8000 {
        transparent
        except /static
    }
    tls ${your_email_address}
}

然後重新啟動 Caddy

sudo service caddy restart

就可以用瀏覽器打開 https://my_django_project.fykuan.tw 試試看了

用 gitlab-ci 自動測試 django 專案

隨著手上的 django 專案越來越大,就會遇到改了 A 功能以後 B 功能爛掉的情況。這時候測試的重要性就顯露出來了。

雖然 django 自己有 Unit Test 功能,但是要每個人在 commit 前都手動跑一次測試有點太勉強,總是可能會有忘記測試,結果 commit 爛掉的 code 上去的機會。所以把測試的工作丟給 gitlab-ci,讓每次 push 的時候 gitlab 都能自動跑 django 的 Unit test,並把測試結果吐到 Slack 上面會是比較方便的作法。

要使用 gitlab-ci 自動測試,需要完成以下步驟:

  • 編寫 django 的 Unit test
  • 安裝/註冊一台或多台 gitlab-ci-runner,用來執行測試
  • 編輯專案根目錄的 .gitlab-ci.yml 設定檔

編寫 django 的 Unit test

可參考 django 關於測試的文件

安裝/註冊 gitlab-ci-runner

  1. 先到 gitlab 專案頁面,點選右上角的齒輪圖示,選取「Runners」。會在這個頁面看到 gitlab 提供給 runner 註冊的 URL 以及 token
  2. 我用來跑 runner 的機器是 Ubuntu,可參考這份文件安裝 gitlab-ci-runner
  3. 裝好後執行 gitlab-ci-runner,會提示您輸入剛剛看到的 URL 和 token
  4. 然後根據您需要設定其他項目,在這邊我選用 docker 環境來執行測試。gitlab-ci 會在 runner 上跑一個 docker 環境,把您的 django 專案丟到這個環境內測試。所以也會需要在 runner 上安裝好 docker。

安裝/註冊成功後,會在 gitlab 的 Runners 頁面看到剛剛註冊的 runner:

2017-05-18 下午4.57.20
設定好的 gitlab-ci-runner 已註冊進 gitlab

編輯 .gitlab-ci.yml

再來編輯 .gitlab-ci.yml 設定檔,這個設定檔定義了 gitlab-ci 要幫你做什麼,範例如下:

# 使用 python 3.4 的 docker image 來建立 docker 環境
image: python:3.4

# 定義 unit test
unit_test:
    script:
        # 執行 ./shell/install.sh 設定系統環境
        - sh ./shell/install.sh
        # 指定 django 設定檔,並執行 django unit test
        - export DJANGO_SETTINGS_MODULE=official_web.settings.test && python3 manage.py test -k
    when: on_success

./shell/install.sh 的內容如下,我在這個檔案定義了 runner 在啟動 docker 之後、執行 django unit test 之前需要做些什麼:

#!/usr/bin/env bash
echo "********************************************"
echo "***************** Install ******************"
echo "********************************************"
# 先更新系統
apt-get -y update

echo "********************************************"
echo "************** Run apt-get *****************"
echo "********************************************"
# 用 apt-get 安裝必要的套件
apt-get -y install ca-certificates python3-dev libssl-dev netcat libmysqlclient-dev python3-pip mysql-client

echo "********************************************"
echo "************* Run pip install **************"
echo "********************************************"
# 根據專案目錄下的 requirements.txt 安裝 python 套件
pip install -r requirements.txt

這樣設定好之後,push 上 gitlab 就會在專案的「Pipelines」→「Builds」看到測試的狀況,如果測試成功就會在這個 Build 及 Pipeline 看到 passed 的標誌:

如果另外還有在 gitlab 設定和 Slack 的整合,Build 的結果也會一起吐到 Slack 上面。

在 docker 中使用 Elasticsearch 作為 django 的 search backend

目前手上的專案用了 django 作為網站的 Framework,而為了確保所有開發人員能夠在自己的開發機上用相同的環境進行開發,所以我們把整個 django 專案用 docker-compose 包裝成容器。

且為了未來專案正式上線時,可以放在 AWS 上,並盡量使用 AWS 的 managed service,所以選擇了 Elasticsearch 來作為 search backend。

這篇文章是把 Elasticsearch 加入 docker-compose 中的 django 專案的筆記。

閱讀更多在 docker 中使用 Elasticsearch 作為 django 的 search backend