PEP 381 – 適用於 PyPI 的鏡像基礎設施
- 作者:
- Tarek Ziadé <tarek at ziade.org>, Martin von Löwis <martin at v.loewis.de>
- 狀態:
- 已撤回
- 類型:
- 標準軌跡 (Standards Track)
- 主題:
- 套件封裝 (Packaging)
- 建立日期:
- 21-Mar-2009
- 公告歷史:
摘要
本 PEP 描述了適用於 PyPI 的鏡像基礎設施。
PEP 撤回
PyPI 主要網路服務於 2013 年 5 月移至 Fastly 快取 CDN 後方:https://mail.python.org/pipermail/distutils-sig/2013-May/020848.html
隨後,這項安排被正式確立為與 PSF 的實物贊助,並且 PSF 也承擔了萬一該贊助安排終止時的風險管理任務。
以前直接在 PyPI 上提供的下載統計資料,現在透過 Google Big Query 間接發布:https://packaging.python.org/guides/analyzing-pypi-package-downloads/
因此,本 PEP 中描述的鏡像提案已不再需要,並已標示為「已撤回」。
原理
PyPI 託管著超過 6000 個專案,並被人們日常用於建構應用程式。特別是像 easy_install 和 zc.buildout 這樣的系統,對 PyPI 進行了大量使用。
對於大量使用 PyPI 的人來說,它可能成為單點故障。人們已開始建立一些鏡像,包括私有和公共的。這些鏡像是活躍鏡像,這表示它們正在瀏覽 PyPI 以進行同步。
為了使系統更可靠,本 PEP 描述了
- PyPI 上的鏡像列示與註冊
- 公共鏡像應維護的頁面。PyPI 將使用這些頁面來獲取點擊數和最後修改日期。
- 鏡像應如何與 PyPI 同步
- 客戶端如何實現故障轉移機制
鏡像列示與註冊
想要鏡像 PyPI 的人向 catalog-SIG 提出提案。當在郵件列表中提出一個鏡像時,會在檢查其是否符合鏡像規則後,手動將其添加到 PyPI 應用程式的鏡像列表中。
鏡像列表以以下形式的主機名稱列表提供
X.pypi.python.org
X 的值是序列 a,b,c,…,aa,ab,…。a.pypi.python.org 是主伺服器;鏡像從 b 開始。CNAME 記錄 last.pypi.python.org 指向最後一個主機名稱。鏡像營運商應使用靜態位址,並提前向 distutils-sig 報告該位址的計畫變更。
新鏡像也顯示在 http://pypi.python.org/mirrors,這是一個提供鏡像列表的易讀頁面。此頁面也解釋了如何註冊新鏡像。
統計資料頁面
PyPI 在 /stats 提供下載統計資料。此頁面由 PyPI 每日計算,透過讀取所有鏡像的本地統計資料並將其加總。
統計資料以每日或每月檔案形式呈現,在 /stats/days 和 /stats/months 下。每個檔案都是 bzip2 格式的檔案,具有以下格式
- YYYY-MM-DD.bz2 用於每日檔案
- YYYY-MM.bz2 用於每月檔案
範例
- /stats/days/2008-11-06.bz2
- /stats/days/2008-11-07.bz2
- /stats/days/2008-11-08.bz2
- /stats/months/2008-11.bz2
- /stats/months/2008-10.bz2
鏡像真實性
在分散式鏡像系統中,客戶端可能希望驗證鏡像副本是真實的。有多種威脅需要考慮
- 中央索引可能遭到破壞
- 中央索引被假定為受信任的,但鏡像可能被篡改。
- 中央索引與終端使用者之間,或鏡像與終端使用者之間的中間人可能篡改資料報。
本規範僅處理第二種威脅。已採取一些措施來偵測中間人攻擊。為偵測第一種攻擊,套件作者需要使用 PGP 金鑰簽署其套件,以便使用者驗證套件來自他們信任的作者。
中央索引在 URL /serverkey 提供 DSA 金鑰,採用由「openssl dsa -pubout」生成的 PEM 格式(即 RFC 3280 SubjectPublicKeyInfo,演算法為 1.3.14.3.2.12)。此 URL 不得被鏡像,且客戶端必須直接從 PyPI 獲取官方伺服器金鑰,或使用隨附於 PyPI 客戶端軟體的副本。鏡像仍應下載金鑰,以偵測金鑰輪換。
對於每個套件,在 /serversig/<package> 提供一個鏡像簽章。這是並行 URL /simple/<package> 的 DSA 簽章,採用 DER 格式,使用 SHA-1 與 DSA(即作為 RFC 3279 Dsa-Sig-Value,由演算法 1.2.840.10040.4.3 建立)。
使用鏡像的客戶端需要執行以下步驟來驗證套件
- 下載 /simple 頁面,並計算其 SHA-1 雜湊值
- 計算該雜湊值的 DSA 簽章
- 下載對應的 /serversig,並將其(逐位元組)與步驟 2 中計算的值進行比較。
- 計算並驗證 (對照 /simple 頁面) 他們從鏡像下載的所有檔案的 MD-5 雜湊值。
驗證演算法的實作可從 https://svn.python.org/packages/trunk/pypi/tools/verify.py 獲取
從中央索引下載時不需要驗證,並應避免以減少計算開銷。
大約每年一次,金鑰將被替換為新的。鏡像必須重新獲取所有 /serversig 頁面。使用鏡像的客戶端需要找到新伺服器金鑰的受信任副本。獲取它的其中一種方法是從 https://pypi.python.org/serverkey 下載。為了偵測中間人攻擊,客戶端需要驗證 SSL 伺服器憑證,該憑證將由 CACert 機構簽署。
鏡像需提供的特殊頁面
鏡像是一個 PyPI 的子集副本,因此它透過複製來提供相同的結構。
- simple: 套件索引的 REST 版本
- packages: 套件,按 Python 版本和字母儲存
- serversig: simple 頁面的簽章
它還需要提供兩個特定元素
- last-modified
- local-stats
最後修改日期
CPAN 使用一種新鮮度日期系統,在該系統中,鏡像的最後同步日期會公開提供。
對於 PyPI,每個鏡像都需要維護一個帶有簡單文字內容的 URL,該內容代表鏡像維護的最後同步日期。
日期以 GMT 時間提供,使用 ISO 8601 格式 [2]。每個鏡像將負責維護其最後修改日期。
此頁面必須位於: /last-modified 並且必須是一個 text/plain 頁面。
本地統計資料
每個鏡像負責計算透過它完成的所有下載。PyPI 使用這項資料來加總所有下載,以便能夠顯示總計。
這些統計資料以類似 CSV 的形式呈現,第一行包含標頭。它需要遵守 PEP 305。基本上,它應該可以由 Python 的 csv 模組讀取。
此檔案中的欄位為
- package: 套件的 distutils ID。
- filename: 已下載的檔案名稱。
- useragent: 下載套件的客戶端 User-Agent。
- count: 下載次數。
內容將如下所示
# package,filename,useragent,count
zc.buildout,zc.buildout-1.6.0.tgz,MyAgent,142
...
計數從鏡像啟動當天開始,並且每天有一個檔案,使用 bzip2 格式壓縮。每個檔案都以日期命名。例如,2008-11-06.bz2 是 2008 年 11 月 6 日的檔案。
然後它們會被放置在名為 days 的資料夾中。例如
- /local-stats/days/2008-11-06.bz2
- /local-stats/days/2008-11-07.bz2
- /local-stats/days/2008-11-08.bz2
此頁面必須位於 /local-stats。
鏡像應如何與 PyPI 同步
一個名為 Simple Index 的鏡像協定由 Martin v. Loewis 和 Jim Fulton 描述並實作,基於 easy_install 的運作方式。本節綜合介紹了它並提供了一些相關連結,以及關於 User-Agent 的一小部分內容。
鏡像協定
鏡像必須減少中央伺服器與鏡像之間傳輸的資料量。為此,它們必須使用 changelog() PyPI XML-RPC 呼叫,並且只重新獲取自上次以來已變更的套件。對於每個套件 P,它們必須複製文件 /simple/P/ 和 /serversig/P。如果中央伺服器上刪除了套件,它們必須刪除該套件和所有相關檔案。為偵測套件檔案的修改,它們可以快取檔案的 ETag,並可以使用 If-none-match 標頭請求跳過它。
每個鏡像工具必須使用描述性的 User-agent 標頭來識別自己。
pep381client 套件 [1] 提供了一個遵循此協定來瀏覽 PyPI 的應用程式。
User-agent 請求標頭
為了能夠區分客戶端在 PyPI 上的操作,所有鏡像軟體都應提供一個特定的使用者代理名稱。
這也適用於所有客戶端,例如
XXX PyPI 上的使用者代理註冊機制?
客戶端如何使用 PyPI 及其鏡像
瀏覽 PyPI 的客戶端應該能夠透過使用 last.pypi.python.org 獲取鏡像列表來使用替代鏡像。
程式碼範例
>>> import socket
>>> socket.gethostbyname_ex('last.pypi.python.org')[0]
'h.pypi.python.org'
迄今為止可以使用此機制的客戶端
- setuptools
- zc.buildout (透過 setuptools)
- pip
故障轉移機制
瀏覽 PyPI 的客戶端應該能夠在 PyPI 或所使用的鏡像沒有回應時,使用故障轉移機制。
由客戶端決定應使用哪個鏡像,也許可以透過考量其地理位置和回應能力。
本 PEP 並未描述此故障轉移機制應如何運作,但強烈建議客戶端嘗試使用最近的鏡像。
迄今為止可以使用此機制的客戶端
- setuptools
- zc.buildout (透過 setuptools)
- pip
額外套件索引
顯然,有些套件不會上傳到 PyPI,無論是因為它們是私有的,還是因為專案維護者運行自己的伺服器,人們可以在那裡獲取專案套件。然而,強烈建議公共套件索引遵循 PyPI 和 Distutils 協定。
換句話說,register 和 upload 命令應與任何現有的套件索引伺服器相容。
迄今為止與 PyPI 和 Distutils 相容的軟體
額外的套件索引不是 PyPI 的鏡像,但它本身可以有一些鏡像。
合併多個索引
當客戶端需要從幾個不同的索引獲取套件時,它應該能夠將它們中的每一個都用作潛在的套件來源。應將不同的索引定義為客戶端尋找套件的排序列表。
每個獨立索引當然可以提供其鏡像列表。
XXX 定義如何獲取任意索引的鏡像主機名稱。
這允許客戶端層級的所有組合,適用於具有所有隱私層級的可靠套件系統。
合併由客戶端處理。
參考文獻
致謝
Georg Brandl。
版權
此文件已歸入公有領域 (public domain)。
來源: https://github.com/python/peps/blob/main/peps/pep-0381.rst
最後修改: 2025-02-01 08:55:40 GMT