1. 程式人生 > >Python服務Debian打包新思路

Python服務Debian打包新思路

此文已由作者張耕源授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


Debian 打包一直是比較冷僻的技術,大部分同學都不會接觸到它。 但是我們 Debian 伺服器上安裝的各種軟體服務,都是通過各種打包工具製作出來的安裝包部署到伺服器上的。


Debian 打包雖然比較煩瑣複雜,但是它提供了比較健全的一整套軟體部署、安裝、升級、維護的流程, 並有一系列與之配套的自動化工具,可以避免人工操作可能出現各種遺漏、錯誤,特別是在大規模部署時基本不可能人工操作。


我們雲端計算使用的 Openstack 基礎服務,也是通過自己從頭製作安裝包、上傳到 Debian 倉庫、並最終通過 puppet 等自動化工具實現服務的部署、更新。


之前我們一直採用 Debian 官方的流程對 Openstack 的 Python 服務打包,但是在幾年的實踐中發現了各種無法解決的問題, 不得不自己另外實施一套全新的打包方案。


本文主要介紹 Debian 新打包方案的起因、原理、流程。


起因


我們以前使用了很長時間的 Debian 社群官方的 Openstack 服務打包方案,中間還嘗試過一段時間的 virtualenv 打包方案,各自都有較大的問題,詳見下面。


社群打包方案


原來我們從 Debian 社群 Openstack 專案打包倉庫 fork 出來的自己做一些修改和 backport 然後打包的方案, 所有服務及其依賴的 Python 模組都通過 Debian 的 deb 格式安裝包安裝,這樣存在一個主要問題:Debian 官方倉庫中的 Python 模組版本更新太慢了。


比如常用的 Python 資料庫第三方模組 SQLAlchemy ,在 Python 的官方 PyPI 中已經更新到了 1.1.4 版本,但在 Debian Wheezy 的倉庫中僅有 0.7.8 版本,差了4個大版本。


我們在使用 Openstack 服務中發現的一些資料庫相關問題,本來是簡單的升級 SQLAlchemy 版本就能搞定的,由於這個問題變得很難解決。


這個問題直接導致我們很難升級一些有問題的 Python 模組依賴版本;有些需要的模組甚至根本沒有 Debian 安裝包,引進新模組、功能比較困難;升級 Openstack 服務的大版本基本不可能,後續也基本不可能從 Debian Wheezy 升級到 Jessie 了,影響非常大。


virtualenv 打包方案


在發現社群官方的打包方案的嚴重問題後,我們後面也嘗試了一段時間通過 Python virtualenv 虛擬環境打包的方式, 即在一個 virtualenv 虛擬環境中通過 Python pip 工具安裝相關 Python 依賴並將整個安裝了服務與依賴的 virtualenv 環境打包成 Debian 安裝包。


這個方案在後續使用中也發現很多問題,最大的問題還是本質上 virtualenv 並不能真正隔離系統的 Python 環境和自身虛擬環境的 Python 環境,最終導致服務各種詭異錯誤。


我們這裡可以看一個例子


$ virtualenv test$ source test/bin/activate>>> import sys>>> sys.path
['','/home/stanzgy/workspace/test/lib/python2.7','/home/stanzgy/workspace/test/lib/python2.7/plat-linux2','/home/stanzgy/workspace/test/lib/python2.7/lib-tk','/home/stanzgy/workspace/test/lib/python2.7/lib-old','/home/stanzgy/workspace/test/lib/python2.7/lib-dynload','/usr/lib/python2.7','/usr/lib/python2.7/plat-linux2','/usr/lib/python2.7/lib-tk','/home/stanzgy/workspace/test/local/lib/python2.7/site-packages','/home/stanzgy/workspace/test/lib/python2.7/site-packages']>>> import json>>> json.__file__'/usr/lib/python2.7/json/__init__.pyc'>>> import _json>>> _json.__file__'/home/stanzgy/workspace/test/lib/python2.7/lib-dynload/_json.so'

在這個例子中,我們建立了一個虛擬環境 test ,並嘗試在虛擬環境 import Python 自帶的 json 模組,結果發現引用的模組地址事實上是作業系統而不是虛擬環境的。


從 sys.path 的結果可以看到,虛擬環境中的 Python import 模組時會嘗試先從虛擬環境中的 Python PATH 搜尋,然後會嘗試從系統的 Python PATH 搜尋。如果 import 的模組二次引用其他的 Python 模組實現,則可能導致系統的 Python 模組和虛擬環境中的 Python 模組交叉使用的情況。


在上面的例子中,可以看到 json 模組和實現其部分功能的 _json 模組分別屬於系統和虛擬環境。如果系統和虛擬環境中的 Python 版本、模組版本不一致,則很容易導致服務出現問題,並且最重導致 Python 程序本身崩潰,並且很難除錯、查詢原因。


virtualenv 打包方案從原理上並不可靠。


新 Debian 打包方案

需求

我們 Openstack 服務 Debian 打包在現有基礎上的需求主要有三點:


  1. 能自由指定、更新 Python 依賴模組版本

  2. 不同 Openstack 服務之間的 Python 環境互相隔離

  3. Openstack 服務的 Python 環境和系統的 Python 環境隔離


社群的方案三點都不滿足,virtualenv 方案只滿足第一、二點。


原理

新打包的流程比較複雜,但原理用一句話就能描述清楚: 每次打包獨立編譯 Python ,編譯時通過設定 RPATH 變數實現隔離效果。


RPATH 是 Python 編譯時設定的變數,效果是硬編碼指定並限制程式執行時動態連結庫的的搜尋路徑,類似 LD_LIBRARY_PATH。關於它的詳細資訊和討論可以參考 WikipediaDebian Wiki


我們每個服務都使用不同的RPATH變數編譯 Python 後,相當於每個服務都安裝在一個獨立的 Python 隔離環境裡,使用各自獨立的執行時動態連結庫搜尋路徑。這樣每個服務既可以隨意更新修改自己的 Python 依賴模組版本、也避免了之前 virtualenv 方案存在的嚴重的系統環境隔離問題,解決了上面的三點需求。


下面是一個採用了新打包方案的 Openstack 服務的 Python 環境。


>>> import sys>>> sys.path
['','/srv/stack/nova/lib/python27.zip','/srv/stack/nova/lib/python2.7','/srv/stack/nova/lib/python2.7/plat-linux2','/srv/stack/nova/lib/python2.7/lib-tk','/srv/stack/nova/lib/python2.7/lib-old','/srv/stack/nova/lib/python2.7/lib-dynload','/srv/stack/nova/lib/python2.7/site-packages']>>> import json>>> json.__file__'/srv/stack/nova/lib/python2.7/json/__init__.py'>>> import _json>>> _json.__file__'/srv/stack/nova/lib/python2.7/lib-dynload/_json.so'

可以看到它有獨立的 Python sys.path 路徑、並且不存在和系統的 Python 交叉呼叫的問題。


流程

新 Debian 打包方案的流程,可簡單描述為:


  1. 指定 RPATH,編譯、安裝 Python

  2. 使用新編譯的 Python pip 安裝依賴

  3. 安裝 Python 服務到新編譯好的 Python 獨立環境

  4. 將上面建立的整個 Python 獨立環境打包

  5. 處理其他配置檔案、啟動指令碼等


下面為每個步驟的詳細說明


編譯 Python

所有專案編譯 Python 使用同一個 build-python.sh 指令碼


#!/bin/bash...export PROJECT_PREFIX=${PROJECT_PREFIX:-/srv/stack}export PROJECT_BASE=$PROJECT_PREFIX/$PROJECTexport PYTHON_FILE=${PYTHON_FILE:-Python-2.7.12.tar.xz}# Get python tarballCUR_DIR=$PWDTEMP_DIR=$(mktemp -d /tmp/pybuild.XXXX)cd $TEMP_DIRwget $PYTHON_URL/$PYTHON_FILEmkdir -p py27 && tar Jxf $PYTHON_FILE -C py27 --strip-components=1cd py27# Compile pythonDPKG_CPPFLAGS=$(dpkg-buildflags --get CFLAGS)
DPKG_CFLAGS=$(dpkg-buildflags --get CPPFLAGS)
DPKG_LDFLAGS=$(dpkg-buildflags --get LDFLAGS)

CFLAGS="$DPKG_CFLAGS $DPKG_CPPFLAGS" \
LDFLAGS="$DPKG_LDFLAGS,-rpath=$PROJECT_BASE/lib" \
./configure \
    --prefix=$PROJECT_BASE \
    --enable-shared \
    --enable-unicode=ucs2 \
    --with-ensurepip=install \
    --enable-ipv6 \
    --with-dbmliborder=bdb:gdbm \
    --with-fpectl \
    --with-system-expat \
    --with-system-ffi

make
make installcd $CUR_DIRrm -rf $TEMP_CIR

這個指令碼會自動下載 Python 2.7.12 ,並指定 /srv/stack/${PROJECT} 為每個 Openstack 專案的 $BASE 目錄,/srv/stack/${PROJECT}/lib 為其 RPATH 目錄,設定一些編譯選項後編譯安裝 Python 到 $BASE 目錄。

安裝 Python 依賴

目前新打包的 Python 依賴在上面編譯 Python 後通過 pip 安裝。


export REQUIREMENT_FILE=debian/deb-requirements.txt# install pip requirements inside the bootstrap system$(PROJECT_PREFIX)/$(PROJECT)/bin/pip install \
    -U -r $(CURDIR)/$(REQUIREMENT_FILE)

每個 Openstack 專案可以在其專案根目錄下 $REQUIREMENT_FILE 中指定符合 python-pip 格式的 Python 依賴,這個檔案通常是通過 pip freeze 生成的。


安裝服務

在 Python 依賴安裝成功後,安裝專案本身到獨立 Python 環境中。

# install the project inside the bootstrap system$(PROJECT_PREFIX)/$(PROJECT)/bin/python setup.py clean \
    build --executable "$(PROJECT_PREFIX)/$(PROJECT)/bin/python" install

需要注意的是,在安裝專案時需要指定我們自己編譯的 Python 路徑 --executable "$(PROJECT_PREFIX)/$(PROJECT)/bin/python" 。


其他

完成上面的步驟後,剩下只需要走正常的 Debian 打包流程,處理分包、配置檔案等即可。


結語

我們使用的這個新 Python 服務 Debian 打包方案,既能享受到 Debian 包管理系統的各種便利和自動化,又能自由使用 Python PyPI 中的各種最新模組,兼顧了兩者的優點又避開了各自的缺點。它已經在我們的測試環境穩定運行了幾個月,趨於穩定,希望它後續能在我們服務安裝部署中更好的服務我們。


免費體驗雲安全(易盾)內容安全、驗證碼等服務

更多網易技術、產品、運營經驗分享請點選


相關文章:
【推薦】 什麼是高防伺服器?
【推薦】 讓App飛久一點