爲了讓更多的應用上 OpenShift,首先需要對應用打包。淺羽這幾天就遇到一個 Django 應用,仍然跑在屁眼通紅 Python 2.7 下,並且使用 Celery 實作異步並且通過 RabbitMQ 通訊。那麼遇到這種問題,首先肯定想到的是把 Django (uwsgi) 和 RabbitMQ 分離爲兩個容器。學習了一個 Celery 知識之後,發現 Celery Worker 也應該可以分到別的容器去,相互之間通過 RabbitMQ 通訊調度,於是容器化的基本架構就定下來了。
打包 Docker 其實不是很難的事情,不過因爲不熟悉 Django 的架構與使用,淺羽還是踩了不少坑。
(後面還有:Python 依賴安裝、uwsgi 設定、Django 設定)
首先,Python 的官方 Docker 鏡像有好幾種 Base(甚至還有 Windows Server Core),考慮到鏡像的大小、定製方便和習慣,淺羽採用的是 python:2-alpine3.8 鏡像。項目採用的是 Pipfile 即 pipenv 管理依賴,於是需要先從 pip 安裝一個。考慮到後續寫執行指令稿的方便問題,原先 pipenv 整合的 virtualenv 就顯得過於麻煩了,反正一個容器滿足一個應用的依賴就好了,於是可以從 Pipfile 部署到全局:
# pipenv lock
# pipenv install --system --deploy
但是這樣又有一個問題:部署的過程中看不到裝了什麼包的什麼版本。雖然依賴裏應該鎖好版本,而且只會在 docker build 的過程中安裝,但是總給人一種不安心感;加 –verbose 參數,又太過於嚇人(不明白的話試試就知道了)。於是換一個解決方法:
# pipenv lock -r | pip install --no-cache-dir -r /dev/stdin
這樣就能夠滿足看到安裝過程的奇怪願望,又不至於太嚇人了。
接下來,Django 自己可以 runserver,但是作爲生產部署的話還是應該用 wsgi 的方式執行;如果 Docker 來調試的話,……誰幹得出來這種事情?所以還需要安裝 uwsgi 並且設定好 Nginx 反代。在 Alpine Linux 下,可以直接通過包管理器安裝:
# apk add --no-cache nginx uwsgi uwsgi-python
接着要寫 uwsgi 的設定檔:
[uwsgi]
# Django-related settings
socket = /tmp/app.sock
# the base directory (full path)
chdir = /var/www/
# Django s wsgi file
plugin = python
pythonpath = /usr/local/lib/python2.7/site-packages/
module = app.wsgi:application
# process-related settings
# master
master = true
# maximum number of worker processes
processes = 4
# ... with appropriate permissions - may be needed
uid = nginx
gid = www-data
# clear environment on exit
vacuum = true
其中兩點特別需要注意的,也是淺羽踩到的坑:第一是需要設定 plugin,載入 python 擴展(對應 Python 2);第二是需要設定好 pythonpath,到全局的包安裝位置,否則可能會導致 uwsgi 帶起 Django 的時候出現 ModuleNotFoundError。
設定完成之後,發現 Django 居然還起不來,在 Django 包內部出現 SyntaxError: invalid syntax。仔細排查之後,發現 Django 2.0 已經不支援 Python 2,正在使用的是最後支援 Python 2 的 1.11 版本;安裝的 django-filter 組件同時支援 Django 1.11 和 Django 2,但只能運行在 Python 3 上,於是出現語法錯誤。鎖定 django-filter 的版本到 “<2.0” 解決問題。
隨後又出現 ImportError: Could not import ‘rest_framework.filters.DjangoFilterBackend’ for API setting ‘DEFAULT_FILTER_BACKENDS’. AttributeError: ‘module’ object has no attribute ‘DjangoFilterBackend’,搜尋到一個 django-shop 的 issue 之後,鎖定 djangorestframework 到 “==3.6.3” 解決問題。
最後,記得 Django 項目需要在 settings.py 中設定:
ALLOWED_HOSTS = [‘*’]
否則可能會出現拒絕訪問的問題。設定好 Nginx:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /var/www;
sendfile on;
client_max_body_size 32M;
keepalive_timeout 0;
large_client_header_buffers 4 16k;
location /static {
alias /var/www/static/dist;
}
location / {
include /etc/nginx/uwsgi_params;
uwsgi_pass unix:/tmp/app.sock;
}
}
最後設定好容器入口:在 App 容器中,需要運行
nginx
uwsgi --ini /path/to/uwsgi.ini
帶起應用並且建立反代;另外同一份鏡像的容器中,運行
cd /path/to/django/project/
celery worker -A app -B -n localhost -l info --uid `id -u nginx`
以執行 Celery Worker。然後完成剩下的入口指令稿,應該就沒問題了。
最後執行容器,App 與各個 Celery Worker 之間透過 RabbitMQ 溝通,所以只需要額外設定好一個 RabbitMQ(也有 Alpine-based 的容器!),並且連接各個容器與 RabbitMQ 即可,其他容器之間不需要 link。
發佈留言