此文已由做者張耕源受權網易雲社區發佈。
html
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。python
Debian 打包一直是比較冷僻的技術,大部分同窗都不會接觸到它。 可是咱們 Debian 服務器上安裝的各類軟件服務,都是經過各類打包工具製做出來的安裝包部署到服務器上的。linux
Debian 打包雖然比較煩瑣複雜,可是它提供了比較健全的一整套軟件部署、安裝、升級、維護的流程, 並有一系列與之配套的自動化工具,能夠避免人工操做可能出現各類遺漏、錯誤,特別是在大規模部署時基本不可能人工操做。web
咱們雲計算使用的 Openstack 基礎服務,也是經過本身從頭製做安裝包、上傳到 Debian 倉庫、並最終經過 puppet 等自動化工具實現服務的部署、更新。數據庫
以前咱們一直採用 Debian 官方的流程對 Openstack 的 Python 服務打包,可是在幾年的實踐中發現了各類沒法解決的問題, 不得不本身另外實施一套全新的打包方案。json
本文主要介紹 Debian 新打包方案的原由、原理、流程。bootstrap
咱們之前使用了很長時間的 Debian 社區官方的 Openstack 服務打包方案,中間還嘗試過一段時間的 virtualenv 打包方案,各自都有較大的問題,詳見下面。安全
原來咱們從 Debian 社區 Openstack 項目打包倉庫 fork 出來的本身作一些修改和 backport 而後打包的方案, 全部服務及其依賴的 Python 模塊都經過 Debian 的 deb 格式安裝包安裝,這樣存在一個主要問題:Debian 官方倉庫中的 Python 模塊版本更新太慢了。bash
好比經常使用的 Python 數據庫第三方模塊 SQLAlchemy ,在 Python 的官方 PyPI 中已經更新到了 1.1.4 版本,但在 Debian Wheezy 的倉庫中僅有 0.7.8 版本,差了4個大版本。服務器
咱們在使用 Openstack 服務中發現的一些數據庫相關問題,原本是簡單的升級 SQLAlchemy 版本就能搞定的,因爲這個問題變得很難解決。
這個問題直接致使咱們很難升級一些有問題的 Python 模塊依賴版本;有些須要的模塊甚至根本沒有 Debian 安裝包,引進新模塊、功能比較困難;升級 Openstack 服務的大版本基本不可能,後續也基本不可能從 Debian Wheezy 升級到 Jessie 了,影響很是大。
在發現社區官方的打包方案的嚴重問題後,咱們後面也嘗試了一段時間經過 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 打包方案從原理上並不可靠。
咱們 Openstack 服務 Debian 打包在現有基礎上的需求主要有三點:
能自由指定、更新 Python 依賴模塊版本
不一樣 Openstack 服務之間的 Python 環境互相隔離
Openstack 服務的 Python 環境和系統的 Python 環境隔離
社區的方案三點都不知足,virtualenv 方案只知足第1、二點。
新打包的流程比較複雜,但原理用一句話就能描述清楚: 每次打包獨立編譯 Python ,編譯時經過設置 RPATH 變量實現隔離效果。
RPATH 是 Python 編譯時設置的變量,效果是硬編碼指定並限制程序運行時動態連接庫的的搜索路徑,相似 LD_LIBRARY_PATH。關於它的詳細信息和討論能夠參考 Wikipedia 和 Debian 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 打包方案的流程,可簡單描述爲:
指定 RPATH,編譯、安裝 Python
使用新編譯的 Python pip 安裝依賴
安裝 Python 服務到新編譯好的 Python 獨立環境
將上面建立的整個 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 後經過 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 中的各類最新模塊,兼顧了二者的優勢又避開了各自的缺點。它已經在咱們的測試環境穩定運行了幾個月,趨於穩定,但願它後續能在咱們服務安裝部署中更好的服務咱們。
更多網易技術、產品、運營經驗分享請點擊。