一句話結論:映像來源搬家時,先盤點 chart 裡每個容器;後來自己 build,也要分清楚「加了功能」和「有人持續維護底層」是兩件事。
當初怎麼做
用 Helm 裝 PostgreSQL、Redis,最方便的就是沿用 chart 預設映像。資料庫、metrics exporter、必要時調整目錄權限的初始化容器,都幫你接好了。
直到上游調整映像目錄,原本 chart 指向的來源不再適用。當時先把映像 repository 換成 bitnamilegacy,讓既有版本繼續跑。
以為已經改完,後來卻還要補一個修正:PostgreSQL 和 Redis 的 metrics exporter 沒有一起改來源。
為什麼只改主映像不夠
Bitnami 官方公告 說明,歷史映像搬到 docker.io/bitnamilegacy,Legacy 不再獲得更新或支援,只適合作為過渡。這是當時的遷移背景,不代表現在應該把新部署都指向 Legacy。
一個 chart 通常不只一個 image.repository。主容器改完,metrics sidecar 或 init container 仍可能沿用原來的預設值。
翻回 Git,補修的內容其實只有兩個設定:
# 各自放在 PostgreSQL 與 Redis chart 的 values 裡 # PostgreSQL metrics: image: repository: bitnamilegacy/postgres-exporter # Redis 請放另一份 values,不能把兩個 metrics 合併貼到同一份 # metrics: # image: # repository: bitnamilegacy/redis-exporter 教訓很直接:values 看起來改了,不代表最後產生的每個容器都改了。
手把手:先看渲染結果
這次用本機保存的 PostgreSQL chart 16.4.1 與 Redis chart 20.6.2 重做檢查。以下版本是歷史案例的重現條件,不是新部署的版本建議。
1. 把用到的 image 分開列出
下面是 PostgreSQL 單獨 chart 的 values 節錄,檔名設為 image-migration.yaml:
global: security: allowInsecureImages: true image: repository: bitnamilegacy/postgresql tag: 17.2.0-debian-12-r5 metrics: enabled: true image: repository: bitnamilegacy/postgres-exporter tag: 0.16.0-debian-12-r2 volumePermissions: enabled: true image: repository: bitnamilegacy/os-shell tag: 12-debian-12-r34 這裡特意開啟 volumePermissions,是為了讓初始化容器出現在渲染結果,不表示每個部署都需要開它。
allowInsecureImages 也要講清楚:這版 chart 會拒絕不認得的替代映像,這個設定讓它跳過來源識別檢查;它沒有替映像做安全認證,也不是修復漏洞。官方說明 有這個參數的範圍。
2. 渲染後逐一核對
假設你已經有對應的 chart 壓縮檔,在該檔案所在資料夾執行:
helm template demo ./postgresql-16.4.1.tgz \ -f image-migration.yaml > rendered.yaml rg '^ *image:' rendered.yaml 這次對照結果如下。tag 沒變,變的是 repository:
| 容器 | 只改主映像 | 補齊設定後 |
|---|---|---|
| PostgreSQL | bitnamilegacy/postgresql | bitnamilegacy/postgresql |
| metrics exporter | bitnami/postgres-exporter | bitnamilegacy/postgres-exporter |
| 權限初始化 | bitnami/os-shell | bitnamilegacy/os-shell |
Redis 同樣要看主容器、exporter 與初始化容器。這次也用相同方式渲染確認,但沒有啟動 Redis 或 exporter 做執行期測試。
先維持原 chart 與映像 tag,只調整來源,比同時升 chart、換資料庫版本更容易確認問題在哪。至於 tag 在遠端是否還能拉取,要另外驗證;本次使用本機已存在的基底映像,沒有重做從 Docker Hub 冷拉的測試。
後來為什麼又自己 build
之後需要 PostgreSQL 的 pgvector 擴充。這跟映像目錄搬家是兩個不同需求,只是都碰到同一個映像。
直接換另一個 PostgreSQL 發行映像,還要一起確認 entrypoint、環境變數、資料目錄和執行使用者是否符合原 chart 的期待。當時選擇在原本同一個 Bitnami PostgreSQL 基底上加擴充,把變動縮小。
Dockerfile 可以拆成兩個階段:前面編譯,後面從乾淨的同一基底開始,只拿編譯產物。
ARG BASE=docker.io/bitnamilegacy/postgresql:17.2.0-debian-12-r5 FROM ${BASE} AS build ARG PGVECTOR_VERSION=v0.8.6 USER root RUN apt-get update \ && apt-get install -y --no-install-recommends build-essential git ca-certificates \ && git clone --depth 1 --branch "${PGVECTOR_VERSION}" https://github.com/pgvector/pgvector.git /tmp/pgvector \ && cd /tmp/pgvector \ && make PG_CONFIG=/opt/bitnami/postgresql/bin/pg_config OPTFLAGS="" \ && make PG_CONFIG=/opt/bitnami/postgresql/bin/pg_config OPTFLAGS="" install FROM ${BASE} COPY --from=build /opt/bitnami/postgresql/lib/vector.so /opt/bitnami/postgresql/lib/vector.so COPY --from=build /opt/bitnami/postgresql/share/extension/ /opt/bitnami/postgresql/share/extension/ USER 1001 存成 Dockerfile 後,可以在該資料夾建置:
docker build -t local-postgresql-vector:17.2.0-0.8.6 . PG_CONFIG 要指向這個 PostgreSQL 安裝,避免編到另一套版本。OPTFLAGS="" 則避免預設的 -march=native 把建置機特有的 CPU 指令帶進擴充;這是 pgvector 官方提供的可攜性做法,不等於可以跨 CPU 架構直接使用。
第二個 FROM 很重要:編譯階段安裝的套件不會自動進入最終映像。Docker multi-stage build 文件 說明了這種選擇性複製的方式。
怎麼確認相容
這次重新 build 後,以斷開外部網路的隔離容器和全新的測試 volume 驗證:
- 用原始 PostgreSQL 映像啟動,建立一筆純測試資料。
- 停止並移除舊容器,讓自建映像掛上同一個測試 volume。
- 讀回資料,確認
SHOW data_directory的結果沒變。 - 建立擴充並執行向量運算。
SQL 驗證的核心是:
CREATE EXTENSION vector; SELECT extversion FROM pg_extension WHERE extname = 'vector'; SELECT round(('[1,0,0]'::vector <=> '[0,1,0]'::vector)::numeric, 3); 原始映像沒有可用的 vector 擴充;自建映像回報版本 0.8.6,兩個正交向量的 cosine distance 是 1.000。原先的測試資料仍讀得到,映像的 entrypoint、啟動命令與使用者也維持一致,最終映像內沒有額外留下 gcc。
這驗證的是同一個 PostgreSQL 基底加上擴充,沒有測跨大版本資料格式遷移,也沒有碰正式資料。不能把這個結果延伸成「任意 PostgreSQL 映像都能直接互換」。
換了之後的代價
自建映像沒有讓舊基底恢復更新。編譯階段即使跑了 apt-get update,最終階段仍從原本的 Legacy 基底開始;它增加的是擴充功能,不是替整個執行環境補齊安全更新。
後續仍要維護基底與擴充版本、確認新映像符合 chart 的啟動慣例,並處理映像保存與版本固定。若要更嚴格重現建置,還要進一步固定基底 digest、原始碼 commit 與建置依賴;只固定 tag 不代表完全可重現。
當時先換來源,再沿用原基底加功能,是為了減少同時變動的地方。長期要換到持續維護的映像,仍然是一項獨立工作,不能因為現在能成功啟動就當作已經完成。