iT邦幫忙

0

[Release-as-Knowledge-02] 從 LABEL 開始 · R2K Level 1 Identify

  • 分享至 

  • xImage
  •  

從 LABEL 開始

R2K Level 1(Identify · 身份層)的入門機制 ── 5 行 Dockerfile 改變支援團隊的世界

Release-as-Knowledge(R2K · 軟體發布即知識傳遞)系列・篇 2 / 6


上一篇我們看到 Mary 半小時拼湊一個 80% 正確的答案。

從這篇開始,我們進入解法。

解法的第一塊磚很樸素 : 一個你可能寫過幾百次但從沒認真看待的 Dockerfile 指令:LABEL

這個指令存在了十年。多數團隊只用它放 maintainer email 跟 build 日期。但如果用對方式,它會變成 Release-as-Knowledge 整套機制的地基


先正名:這套方法叫 Release-as-Knowledge

在進入技術細節前,讓我先把上篇結尾留下的概念講清楚。

我把「軟體發布同時是知識傳遞」這個 thesis 命名為 Release-as-Knowledge,簡稱 R2K,中文「軟體發布即知識傳遞」。

跟 Infrastructure-as-Code 同樣的命名結構 : X-as-Y 表示「把 X 當成 Y 來管理」。
IaC 把基礎建設當成可版本化的程式碼。
R2K 把軟體發布當成可結構化的知識傳遞。

R2K 不是一個工具、不是一個產品。是一個漸進採用的階梯,有 4 個 Level:

Level 名稱(中/英) 主要機制 達成成本
L1 身份層 / Identify OCI 標準 LABEL + dev.releaseasknowledge.* 1-2 週
L2 資產層 / Trust /r2k/ snapshots + index.yaml 2-3 週
L3 變更層 / Understand Change Manifest(Mode A/B) 6-8 週
L4 分享層 / Share OCI Referrers + badge 4 週

像 SLSA 那樣,每一級都可以單獨宣告達成。組織可以說「我們是 R2K Level 2」,就像說「我們是 SLSA Level 3」一樣。

這篇講的是 R2K Level 1 : 身份層(Identify)


LABEL 是什麼,為什麼被低估

LABEL 是 Dockerfile 的一個指令,把 key-value 嵌進 image 的 config blob。

最簡單的形式:

LABEL maintainer="ops@yourco.com"
LABEL com.yourco.version="1.0.0"

這段一寫,build 出來的 image 就帶著這些 metadata。任何能讀 image 的工具(Docker、Harbor、Trivy、Cosign)都能拿到。

為什麼被低估?因為大多數團隊只用它存 metadata 給人看,而不是給機器查

它的真正能力是:讓任何 image scanner 不用 pull image 就能拿到結構化資料

但靜態 LABEL 有個問題 : version="1.0.0" 寫死,每次 build 出來都一樣。
我們要的是每次 build 都帶不同的 git commit、build ID、測試結果

這就是 build-arg 機制。


R2K Level 1 的三組 LABEL namespace

R2K Level 1 在 image 上掛兩組必要 + 一組選用的 LABEL:

組別 namespace 由誰定義 角色
OCI 標準 org.opencontainers.image.* OCI Image Spec 任何 container 工具原生支援
R2K 規格 dev.releaseasknowledge.* R2K v1 給 R2K 工具鏈用
Vendor 擴充 com.yourco.*(反向域名) 各自團隊 自家業務欄位(case_type、license_modules...)

兩組必要 namespace 都要掛,不引入新工具的前提下讓 image 同時符合 OCI 慣例與 R2K compliant。OCI label 餵給「現成的 container 生態」,R2K label 餵給「R2K 工具鏈」。

Figure 1 · 三組 LABEL namespace 分工


build-arg:讓 LABEL 動態起來

# syntax=docker/dockerfile:1.6
FROM eclipse-temurin:17-jre-alpine

# CI 在 build 時動態傳入
ARG APP_VERSION=unknown
ARG GIT_COMMIT=unknown
ARG GIT_BRANCH=unknown
ARG GIT_TAG=
ARG BUILD_TIME=unknown
ARG BUILD_ID=unknown
ARG TEST_PASSED=0
ARG TEST_FAILED=0
ARG CASE_TYPE=standard

# === OCI 標準 labels(業界慣例,scanner 都認)===
LABEL org.opencontainers.image.title="payment-service" \
      org.opencontainers.image.version="${APP_VERSION}" \
      org.opencontainers.image.revision="${GIT_COMMIT}" \
      org.opencontainers.image.created="${BUILD_TIME}" \
      org.opencontainers.image.source="https://github.com/yourco/payment-service" \
      org.opencontainers.image.vendor="Your Org" \
      org.opencontainers.image.licenses="Apache-2.0"

# === R2K Level 1 · Identify ===
LABEL dev.releaseasknowledge.version="1.0" \
      dev.releaseasknowledge.level="1" \
      dev.releaseasknowledge.commit="${GIT_COMMIT}" \
      dev.releaseasknowledge.branch="${GIT_BRANCH}" \
      dev.releaseasknowledge.tag="${GIT_TAG}" \
      dev.releaseasknowledge.build-time="${BUILD_TIME}" \
      dev.releaseasknowledge.repo="https://github.com/yourco/payment-service" \
      dev.releaseasknowledge.spec.url="https://enjtorian.github.io/release-as-knowledge/zh-tw/"

# === Vendor 擴充(反向域名命名空間,跟其他工具不衝突)===
LABEL com.yourco.psp.case_type="${CASE_TYPE}" \
      com.yourco.psp.build_id="${BUILD_ID}" \
      com.yourco.psp.test_summary="passed=${TEST_PASSED},failed=${TEST_FAILED}"

WORKDIR /app
COPY target/app.jar /app/app.jar
ENTRYPOINT ["java","-jar","/app/app.jar"]

CI 端的 build 指令長這樣:

docker build \
  --build-arg APP_VERSION="2.4.1" \
  --build-arg GIT_COMMIT="$(git rev-parse HEAD)" \
  --build-arg GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" \
  --build-arg GIT_TAG="$(git tag --points-at HEAD | paste -sd, -)" \
  --build-arg BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --build-arg BUILD_ID="${CI_BUILD_ID}" \
  --build-arg TEST_PASSED="$(jq '.passed' test-report.json)" \
  --build-arg TEST_FAILED="$(jq '.failed' test-report.json)" \
  --build-arg CASE_TYPE="enterprise" \
  -t harbor.yourco.com/products/payment-service:2.4.1 .

這就是 R2K Level 1 的全部入口。 5-15 行 Dockerfile 修改、CI 多幾個 --build-arg

Figure 4 · build-arg → LABEL → scanner 端到端流


命名規範:三組怎麼分工

每個 LABEL key 該放哪一組?簡單原則:

org.opencontainers.image.* : 業界已經有定義的 metadata(version、revision、source、created、title、description、vendor、licenses、authors...)。
能用 OCI 標準的就用 OCI 標準,別發明同義詞。所有 scanner 都認(Trivy、Grype、Cosign、Harbor 內建掃描)。

dev.releaseasknowledge.* : R2K 規格自己的事。例如:

  • version ── 這顆 image 遵循的 R2K 規格版本
  • level ── 自我宣告達到的 R2K Level(1 / 2 / 3 / 4
  • commit / branch / tag ── source git 三元組;commit 必填,branch / tag 強烈建議。tag 多個用逗號分隔;無 tag 則省略。
  • build-time ── R2K manifest 的 build 時間(RFC 3339)
  • snapshot.path / snapshot.index ── L2 才會填(見篇 3)
  • diff.mode / diff.from ── L3 才會填(見篇 4)
  • spec.url ── 採用的 R2K 規格文件位置

com.yourco.* : Vendor 自家業務欄位,標準沒涵蓋的:case_typelicense_modulestest_summarybuild_id
用反向域名(像 Java package 命名)避免跟其他組織的 LABEL key 衝突。

R2K Manifesto 第 4 原則「Standards over invention」在這裡體現 ── 三層命名空間清楚分工,OCI 已有的不重做、R2K 規格只管自己的事、vendor 擴充走自己的 prefix


讀取極快:Harbor v2 API 的兩個 call

LABEL 為什麼比其他機制好?因為讀取極便宜

任何 scanner 透過 Harbor v2 API 兩個 HTTP call 就能拿到所有 LABEL:

GET /v2/<repo>/manifests/<tag>
└─→ 拿到 config descriptor 的 digest, ~ 2 KB

GET /v2/<repo>/blobs/<config-digest>
└─→ 拿到 image config JSON,內含 LABEL Map, ~ 5 KB

加總不到 10 KB、毫秒級回應。沒有 pull image、沒有解壓 layer、沒有任何重操作

實際數字:

單一 image 索引掃描: ~50 ms
1000 個 image 全掃: ~50 秒
傳輸總量: ~7 MB

這個成本可以每小時跑一次都不傷 Harbor。

對比一下「pull 整個 image 才能讀 metadata」這個替代方案:

單一 image (150 MB): ~8 秒
1000 個 image: 130 分鐘
傳輸總量: 150 GB

效能差 20000 倍。這就是為什麼 LABEL 該是任何 image inventory 的第一站。

Figure 2 · Harbor v2 API 索引掃描 vs 整 image pull


90% 的查詢秒回

有了 inventory 表,Mary 的場景立刻改變。

她打開 PSP UI,搜「Customer A 的 payment-service」,看到:

✓ 客戶部署版本: 2.3.4
✓ R2K Level: 2 (此 image 已附 snapshot)
✓ 最近 build: 2026-04-28 (commit abc1234)
✓ 測試摘要: 1247 passed, 0 failed
✓ Case type: enterprise (含 retail 模組 + audit 模組)
✓ Build pipeline: GitLab CI #4231

1 秒之內,Mary 拿到她以前花 5 分鐘從 4 個系統拼湊的資料。

這不是新發明的查詢介面 : 是一個 PostgreSQL view 加一個 React table。資料源是 image 自己。
工程師不用維護額外文件、PM 不用更新 Confluence、support 不用問人 : 資料就在 image 裡


R2K Level 1 達成標準

要宣告達到 R2K Level 1,你的 production image 必須:

  • [ ] OCI 標準 LABEL 完整 : 至少 versionrevisioncreatedtitlesource
  • [ ] R2K 規格 LABEL 必填 : dev.releaseasknowledge.{version, level, commit, build-time},強烈建議補 branch / tag
  • [ ] Vendor 擴充用反向域名命名空間 : com.yourco.* 形式(如有)
  • [ ] CI 用 build-arg 動態注入 : 不是寫死的 1.0.0
  • [ ] External scanner 可秒掃 : 1000 個 image 應該 < 1 分鐘掃完
  • [ ] Inventory 持久化 : 至少存進 DB / 搜尋引擎,不只 ad-hoc 查 API

達到這 6 條,你就是 R2K Level 1。可以公開宣告、放在工程 blog、寫在 RFP 裡。

成本:1-2 個工程週(Dockerfile 改一次、CI 改一次、scanner 寫一次)。

Figure 3 · R2K Level 1 達成 checklist


為什麼這還不夠

LABEL 有一個明顯限制 : 容量

實務上塞個版本號、commit、test 摘要 OK。塞完整 SBOM 或測試報告就不行 : 一個 LABEL 應該保持在幾百 bytes 以內。整個 image config blob 也不該超過 100 KB。

所以 LABEL 解決了「身份層」:

  • ✅ 「這個 image 是什麼版本?」
  • ✅ 「這個 image 測試有 fail 嗎?」
  • ✅ 「這個 image 屬於什麼 case_type?」
  • ✅ 「這個 image 達到 R2K 第幾級?」

但解決不了:

  • ❌ 「給我完整的 SBOM」
  • ❌ 「給我 OpenAPI spec」
  • ❌ 「給我 DB schema 快照」
  • ❌ 「給我這次發布的 Change Manifest」

這就是 R2K Level 2(Trust)要處理的事 : 下一篇我們來看 /r2k/ snapshot + index.yaml 怎麼補上深度查詢


收尾

LABEL 看起來很樸素。但它是 R2K 整套機制的入口

絕大多數團隊已經在用 LABEL,只是沒用對方式。用 OCI 標準 + R2K 規格 namespace + 反向域名 vendor 擴充 + build-arg 動態注入 : 這四件事就讓 LABEL 從「沒人看的 metadata」變成「秒回 90% 查詢的身份層」。

這個轉變不需要新工具、不需要新平台、不需要說服老闆 : 只需要 5-15 行 Dockerfile 修改 + CI 多幾個 --build-arg

這就是 R2K Level 1 的承諾:最小變更、最大回報、最快達成

LABEL 是 image 的目錄頁 : 所有 image 都該有,所有查詢都該先看這裡。

下篇我們進 R2K Level 2(Trust) : 當資料量變大、當你需要塞完整 SBOM、OpenAPI spec、DB schema,LABEL 不夠用,但 OCI 還有另一個機制:metadata layer,並用 /r2k/index.yaml 當入口清單。


R2K 系列導覽

  • 篇 1:支援工程師的 30 分鐘
  • 篇 2:從 LABEL 開始 : R2K Level 1 Identify ← 你在這
  • 篇 3:Two-Tier Metadata Pattern + index.yaml : R2K Level 2 Trust
  • 篇 4:Change Manifest : R2K Level 3 Understand(Mode A/B)
  • 篇 5:Mary 的下個版本 : R2K-aware tooling
  • 篇 6:The R2K Manifesto : 8 條原則

Release-as-Knowledge · R2K · 軟體發布即知識傳遞 · v1 · CC BY 4.0

完整介紹 : https://enjtorian.github.io/release-as-knowledge/zh-tw/
官方網站 : https://www.releaseasknowledge.com


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言