|
| 1 | +--- |
| 2 | +title: " [實作筆記] 建立私有 Python Package Registry - 以 ONNX Runtime 為例" |
| 3 | +date: 2025/09/02 14:42:42 |
| 4 | +tags: |
| 5 | + - 實作筆記 |
| 6 | +--- |
| 7 | + |
| 8 | +## 前情提要 |
| 9 | + |
| 10 | +在開發 AI 應用時,我們遇到一些特殊的依賴管理需求,需要私有 Registry |
| 11 | + |
| 12 | +在這個專案中,我們遇到了幾個挑戰: |
| 13 | + |
| 14 | +1. **平台特化需求**:Jetson 平台需要特製的 `onnxruntime-gpu` 版本,PyPI 上沒有現成的 |
| 15 | +2. **版本一致性**:確保所有環境(開發、測試、生產)使用完全相同的依賴版本 |
| 16 | +3. **安全性考量**:避免依賴外部不穩定的來源,降低供應鏈攻擊風險 |
| 17 | +4. **內部套件分發**:團隊開發的內部工具需要有管道分發 |
| 18 | + |
| 19 | +## 架構設計 |
| 20 | + |
| 21 | +整體架構分為三個層次: |
| 22 | + |
| 23 | +```text |
| 24 | +┌───────────────────────┐ |
| 25 | +│ GitLab CI/CD │ → 自動化構建與發布 |
| 26 | +├───────────────────────┤ |
| 27 | +│ Package Registry │ → 私有套件儲存 |
| 28 | +├───────────────────────┤ |
| 29 | +│ Project Dependencies │ → 專案依賴管理 |
| 30 | +└───────────────────────┘ |
| 31 | +``` |
| 32 | + |
| 33 | +## 實作步驟 |
| 34 | + |
| 35 | +### 設定 GitLab Package Registry |
| 36 | + |
| 37 | +首先,在 GitLab 專案中啟用 Package Registry,並建立 Deploy Token: |
| 38 | + |
| 39 | +在 GitLab 專案設定中建立 Deploy Token, Settings → Repository → Deploy Tokens |
| 40 | + |
| 41 | +權限:read_package_registry, write_package_registry |
| 42 | + |
| 43 | +記下 token ID 和 token 值,格式如下: |
| 44 | + |
| 45 | +- Token ID: deploy-token-{ID} |
| 46 | + |
| 47 | +- Token: {TOKEN} |
| 48 | + |
| 49 | +### 取得 Project ID |
| 50 | + |
| 51 | +有三種方式可以找到 GitLab Project ID: |
| 52 | + |
| 53 | +從專案首頁: |
| 54 | + |
| 55 | +進入你的 GitLab 專案首頁 |
| 56 | +Project ID 會顯示在專案名稱下方 |
| 57 | +例如:Project ID: 12345678 |
| 58 | + |
| 59 | +從專案設定頁面: |
| 60 | + |
| 61 | +進入 Settings → General |
| 62 | +在最上方的 "General project settings" 區塊 |
| 63 | +可以看到 Project ID |
| 64 | + |
| 65 | +從 GitLab API: |
| 66 | + |
| 67 | +如果你在 CI/CD pipeline 中,可以直接使用環境變數 $CI_PROJECT_ID |
| 68 | +這個變數會自動帶入當前專案的 ID |
| 69 | + |
| 70 | +### 配置專案的依賴管理 |
| 71 | + |
| 72 | +在 `pyproject.toml` 中設定私有 registry: |
| 73 | + |
| 74 | +```toml |
| 75 | +# 定義私有 index |
| 76 | +[[tool.uv.index]] |
| 77 | +name = "onnx" |
| 78 | +url = "https://gitlab+deploy-token-{TOKEN_ID}:{TOKEN}@gitlab.com/api/v4/projects/{PROJECT_ID}/packages/pypi/simple" |
| 79 | +default = false |
| 80 | + |
| 81 | +# 指定套件來源 |
| 82 | +[tool.uv.sources] |
| 83 | +onnxruntime-gpu = { index = "onnx" } |
| 84 | + |
| 85 | +# 多平台依賴策略 |
| 86 | +dependencies = [ |
| 87 | + # macOS ARM64 使用 CPU 版本 |
| 88 | + "onnxruntime==1.19.0; sys_platform == 'darwin' and platform_machine == 'arm64'", |
| 89 | + # Jetson 使用私有倉庫的 GPU 版本 |
| 90 | + "onnxruntime-gpu; sys_platform == 'linux' and platform_machine == 'aarch64'", |
| 91 | +] |
| 92 | +``` |
| 93 | + |
| 94 | +這裡的關鍵是使用條件依賴,根據不同平台安裝不同版本的套件。 |
| 95 | + |
| 96 | +### 3. 開發者使用私有 Registry |
| 97 | + |
| 98 | +根據專案的安全策略,RD 有幾種方式存取私有 registry: |
| 99 | + |
| 100 | +#### 選項 1:Token 內嵌在 pyproject.toml(簡單但不安全) |
| 101 | + |
| 102 | +如果 `pyproject.toml` 中已經包含完整的認證 URL: |
| 103 | + |
| 104 | +```bash |
| 105 | +# 直接安裝依賴 |
| 106 | +uv sync |
| 107 | + |
| 108 | +# 或使用 pip |
| 109 | +pip install -e . |
| 110 | +``` |
| 111 | + |
| 112 | +#### 選項 2:使用環境變數(推薦) |
| 113 | + |
| 114 | +在 `pyproject.toml` 中使用佔位符: |
| 115 | + |
| 116 | +```toml |
| 117 | +url = "https://deploy-token-{TOKEN_ID}:${GITLAB_TOKEN}@gitlab.com/api/v4/projects/{PROJECT_ID}/packages/pypi/simple" |
| 118 | +``` |
| 119 | + |
| 120 | +RD 需要設定環境變數: |
| 121 | + |
| 122 | +```bash |
| 123 | +export GITLAB_TOKEN=your_deploy_token |
| 124 | +uv sync |
| 125 | +``` |
| 126 | + |
| 127 | +#### 選項 3:使用認證檔案 |
| 128 | + |
| 129 | +設定 pip 或 uv 的認證檔案: |
| 130 | + |
| 131 | +```bash |
| 132 | +# 建立 pip 配置 |
| 133 | +cat > ~/.pip/pip.conf <<EOF |
| 134 | +[global] |
| 135 | +extra-index-url = https://deploy-token-{TOKEN_ID}:{TOKEN}@gitlab.com/api/v4/projects/{PROJECT_ID}/packages/pypi/simple |
| 136 | +EOF |
| 137 | +``` |
| 138 | + |
| 139 | +#### 選項 4:企業內網存取 |
| 140 | + |
| 141 | +如果使用企業內網或 VPN: |
| 142 | + |
| 143 | +```bash |
| 144 | +# 連接 VPN 後直接使用 |
| 145 | +uv sync |
| 146 | +``` |
| 147 | + |
| 148 | +### 4. CI/CD 自動化發布 |
| 149 | + |
| 150 | +設定 GitLab CI 自動構建並推送套件到 registry: |
| 151 | + |
| 152 | +```yaml |
| 153 | +# .gitlab-ci.yml |
| 154 | +variables: |
| 155 | + REGISTRY_PATH: registry.gitlab.com/$GROUP_PATH/$PROJECT_NAME |
| 156 | + |
| 157 | +stages: |
| 158 | + - build |
| 159 | + - publish |
| 160 | + |
| 161 | +build-package: |
| 162 | + stage: build |
| 163 | + script: |
| 164 | + # 構建 Python 套件 |
| 165 | + - pip install build |
| 166 | + - python -m build |
| 167 | + artifacts: |
| 168 | + paths: |
| 169 | + - dist/ |
| 170 | + |
| 171 | +publish-to-registry: |
| 172 | + stage: publish |
| 173 | + dependencies: |
| 174 | + - build-package |
| 175 | + script: |
| 176 | + # 設定認證 |
| 177 | + - pip install twine |
| 178 | + - | |
| 179 | + cat > ~/.pypirc <<EOF |
| 180 | + [distutils] |
| 181 | + index-servers = gitlab |
| 182 | + |
| 183 | + [gitlab] |
| 184 | + repository = https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi |
| 185 | + username = gitlab-ci-token |
| 186 | + password = ${CI_JOB_TOKEN} |
| 187 | + EOF |
| 188 | + |
| 189 | + # 上傳到 GitLab Package Registry |
| 190 | + - python -m twine upload --repository gitlab dist/* |
| 191 | + only: |
| 192 | + - main |
| 193 | +``` |
| 194 | +
|
| 195 | +### 6. Docker 映像支援多架構 |
| 196 | +
|
| 197 | +為了支援不同的硬體平台,使用 buildx 構建多架構映像: |
| 198 | +
|
| 199 | +```yaml |
| 200 | +build-multi-arch: |
| 201 | + stage: build |
| 202 | + script: |
| 203 | + # 設定 buildx |
| 204 | + - docker buildx create --use |
| 205 | + |
| 206 | + # 構建並推送多架構映像 |
| 207 | + - | |
| 208 | + docker buildx build \ |
| 209 | + --platform linux/amd64,linux/arm64 \ |
| 210 | + -t $REGISTRY_PATH:$NEW_TAG \ |
| 211 | + --push . |
| 212 | +``` |
| 213 | +
|
| 214 | +## 常見問題與解決方案 |
| 215 | +
|
| 216 | +### Deploy Token 權限不足? |
| 217 | +
|
| 218 | +確保 Deploy Token 有 `read_package_registry` 和 `write_package_registry` 權限。 |
| 219 | + |
| 220 | +### 套件版本衝突? |
| 221 | + |
| 222 | +使用 `uv` 的 resolution markers 功能,明確指定版本解析策略。 |
| 223 | + |
| 224 | +### 多架構構建失敗? |
| 225 | + |
| 226 | +檢查 Docker buildx 是否正確安裝,並確認基礎映像支援目標架構。 |
| 227 | + |
| 228 | +### Registry URL 格式錯誤? |
| 229 | + |
| 230 | +GitLab PyPI registry URL 格式為: |
| 231 | + |
| 232 | +``` |
| 233 | +https://gitlab.com/api/v4/projects/{PROJECT_ID}/packages/pypi/simple |
| 234 | +``` |
| 235 | +
|
| 236 | +注意將 `{PROJECT_ID}` 替換為實際的專案 ID。 |
| 237 | +
|
| 238 | +## 小結 |
| 239 | +
|
| 240 | +透過建立私有 Package Registry,我們成功解決了: |
| 241 | +
|
| 242 | +- **特製版本管理**:Jetson 平台的特殊 ONNX Runtime 版本需求 |
| 243 | +- **依賴一致性**:所有環境使用相同版本的套件 |
| 244 | +- **安全性提升**:減少對外部來源的依賴 |
| 245 | +- **自動化流程**:CI/CD 自動構建並發布套件 |
| 246 | +
|
| 247 | +這個架構不僅適用於 ONNX Runtime,也可以擴展到其他需要特殊管理的依賴套件。 |
| 248 | +
|
| 249 | +(fin) |
0 commit comments