Câu hỏi hay gặp lúc nửa đêm: container đang chạy trên máy chủ này được dựng từ commit nào?
Nếu image của bạn không có nhãn, câu trả lời là một chuỗi phỏng đoán — xem tag, đoán ngày, hỏi người khác, mở lịch sử CI. Nếu có nhãn, nó là một lệnh.
Bộ nhãn chuẩn
Đặc tả OCI định nghĩa một tập nhãn với tiền tố org.opencontainers.image.:
ARG SHA
ARG NGAY
LABEL org.opencontainers.image.title="Ung dung vi du" \
org.opencontainers.image.description="Vi du cho bai viet ve nhan OCI" \
org.opencontainers.image.version="1.4.2" \
org.opencontainers.image.revision="${SHA}" \
org.opencontainers.image.created="${NGAY}" \
org.opencontainers.image.source="https://github.com/vi-du/ung-dung" \
org.opencontainers.image.licenses="MIT" \
org.opencontainers.image.vendor="Ca Phe va Code"
Trong đó hai nhãn quan trọng nhất cho việc vận hành là revision (commit SHA) và source (địa chỉ kho mã).
Chi phí: 1 337 byte
| Dung lượng | Layer | |
|---|---|---|
| Không nhãn | 4 094 764 byte | 2 |
| 8 nhãn OCI | 4 096 101 byte | 2 |
Thêm 1 337 byte và không thêm layer nào. Nhãn nằm trong cấu hình image, không phải trong filesystem — đúng như phần 2 đã đo: LABEL hiện 0B trong docker history.
Với giá đó thì không có lý do nào để không gắn nhãn.
Truy vết ngược: ba lệnh
$ docker inspect nh-chay --format '{{.Image}}'
sha256:dec27236cb994fb046a98c42e8df2c386bfdbd372...
$ docker inspect nh-chay --format '{{index .Config.Labels "org.opencontainers.image.revision"}}'
062422ac69077f502ae3319fc4827b70db6fd3ad
$ docker inspect nh-chay --format '{{index .Config.Labels "org.opencontainers.image.source"}}'
https://github.com/vi-du/ung-dung
Từ container đang chạy ra thẳng commit, không cần hỏi ai, không cần tin vào tag. Điều này quan trọng vì phần 4 đã cho thấy tag có thể bị đẩy đè: hai container cùng hiện myapp:1.4.2 có thể đang chạy hai nội dung khác nhau. Nhãn revision thì đi cùng nội dung, không đi cùng tên.
Nhãn cũng phá cache — đặt cuối tệp
revision đổi mỗi lần commit, nên LABEL chứa nó là một điểm mất cache, giống hệt ARG ở phần 13:
| Sau khi đổi SHA | |
|---|---|
LABEL ở đầu Dockerfile |
2,6 s — 1 bước CACHED |
LABEL ở cuối Dockerfile |
0,3 s — 2 bước CACHED |
Vì nhãn không phụ thuộc vào bất cứ thứ gì trong image, luôn đặt khối LABEL ở cuối — sau mọi RUN và COPY. Không mất gì, và giữ được toàn bộ cache phía trên.
Trong multi-stage, chỉ nhãn tầng cuối có tác dụng
FROM alpine:3.20 AS xay
LABEL nhan.o-tang-build="co"
...
FROM alpine:3.20 AS chay
LABEL nhan.o-tang-cuoi="co"
{ "nhan.o-tang-cuoi": "co" }
Nhãn gắn ở tầng build biến mất cùng tầng đó. Đây là lỗi dễ mắc khi chuyển từ Dockerfile một tầng sang multi-stage — khối LABEL nằm nguyên chỗ cũ và không còn tác dụng.
Image của bạn đang mang tên người khác
Nhãn kế thừa từ image nền. Một Dockerfile không khai LABEL nào cả:
FROM nginx:alpine
RUN echo x > /x
ke thua 1 nhan tu anh nen
maintainer = NGINX Docker Maintainers <docker-maint@nginx.com>
Image của bạn tự giới thiệu là do đội NGINX bảo trì. Với maintainer thì chỉ hơi buồn cười, nhưng các image nền khác mang theo cả org.opencontainers.image.source trỏ về kho mã của họ — và khi ai đó truy vết sự cố, họ sẽ đi vào đúng kho mã sai.
Cách chữa là ghi đè tường minh những nhãn quan trọng, kể cả khi bạn không có gì để điền:
LABEL org.opencontainers.image.source="https://github.com/cua-ban/du-an" \
org.opencontainers.image.vendor="Ten cong ty cua ban" \
maintainer=""
Lọc theo nhãn
Nhãn không chỉ để đọc, chúng còn tìm được:
$ docker images --filter "label=org.opencontainers.image.vendor=Ca Phe va Code"
nh:nhan
Và có một loại nhãn thứ hai, gắn lúc chạy, độc lập với nhãn của image:
$ docker run -d --label moi-truong=san-xuat --label doi=ha-tang alpine sleep 60
$ docker ps --filter "label=moi-truong=san-xuat"
nh-rt | alpine:3.20
Nhãn lúc chạy hữu ích cho việc gom nhóm container theo môi trường, theo đội sở hữu, theo dịch vụ — và là cách các công cụ giám sát nhận biết container nào thuộc về ai. Docker Compose tự gắn một bộ nhãn kiểu này cho mọi thứ nó tạo ra, đó là cách docker compose down biết phải xoá cái gì.
Bộ nhãn tối thiểu nên có
# ... moi thu khac o tren ...
ARG SHA=unknown
ARG NGAY=unknown
ARG PHIEN_BAN=dev
LABEL org.opencontainers.image.title="ten-dich-vu" \
org.opencontainers.image.source="https://github.com/to-chuc/kho-ma" \
org.opencontainers.image.revision="${SHA}" \
org.opencontainers.image.version="${PHIEN_BAN}" \
org.opencontainers.image.created="${NGAY}"
Và trong đường ống CI:
docker build \
--build-arg SHA="$(git rev-parse HEAD)" \
--build-arg NGAY="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--build-arg PHIEN_BAN="$(git describe --tags --always)" \
-t myapp:1.4.2 .
Ba giá trị đó là toàn bộ thứ bạn cần để, sáu tháng sau, đi từ một container lạ trên một máy chủ lạ về đúng dòng mã đã sinh ra nó.
BuildKit còn có thể ghi thông tin nguồn gốc chi tiết hơn nhiều dưới dạng provenance — ai dựng, dựng bằng gì, từ nguồn nào — và đó là chủ đề của phần 22.
Thử ba mươi giây
Xem image trên máy bạn có truy vết được không:
docker images -q | head -20 | while read i; do
printf "%-16s %s\n" "$i" \
"$(docker inspect $i --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' 2>/dev/null)"
done
Cột thứ hai trống nghĩa là image đó không nói được nó từ đâu ra. Và nếu bạn thấy maintainer của một tổ chức khác trong image của mình:
docker inspect <anh> --format '{{json .Config.Labels}}'
thì đó là nhãn kế thừa mà bạn chưa ghi đè.
Phần sau xếp hạng từng thủ thuật cắt dung lượng image theo số megabyte thật sự tiết kiệm được.