Docker: execution labs
Kiểm chứng bằng evidence thay vì chỉ đọc lý thuyết: image layers và build cache, supply chain, process signals, resource limits, read-only filesystem và service networking.
Lab A · Multi-stage và reproducible image
Objective
Chứng minh multi-stage build tách build dependencies khỏi runtime image, hiểu cache invalidation, kiểm tra supply-chain artifacts và nhận diện nguồn làm image digest không reproducible.
Prerequisites
- Một ứng dụng Java/Maven tạo được
target/app.jar. - Docker với BuildKit; có quyền inspect image history và chạy công cụ SBOM/CVE scanner phù hợp môi trường.
- Ghi lại exact base-image reference đang dùng; nếu cần reproducibility mạnh, pin bằng digest thay vì chỉ dùng mutable tag.
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /src
COPY pom.xml .
RUN mvn -B dependency:go-offline
COPY src src
RUN mvn -B -DskipTests package
FROM eclipse-temurin:21-jre
RUN useradd --system --uid 10001 app
COPY --from=build /src/target/app.jar /app/app.jar
USER 10001
ENTRYPOINT ["java","-jar","/app/app.jar"]
Steps
- Build lần đầu từ trạng thái cold, sau đó build lại khi không đổi input. Lưu duration và các layer được reuse.
- Chỉ đổi source code rồi build lại; sau đó đổi
pom.xmlhoặc dependency input và build lại. So sánh layer nào bị invalidated. - Đo image size và đọc
docker image history. Generate SBOM và scan CVE cho final image. - Inspect final image/layers để xác nhận source tree, Maven cache và secrets không bị copy vào runtime stage.
- Build hai lần với pinned inputs; so image digest. Nếu khác nhau, truy nguồn nondeterminism như mutable base image, build timestamp, archive ordering, generated metadata hoặc dependency thay đổi.
Failure injection & verification
- Cache invalidation: chạm vào source file nhưng không đổi dependency file; dependency-resolution layer nên còn cache hit. Đổi dependency file; layer đó và các layer phía sau phải rebuild.
- Supply chain: cố tình thêm một file bí mật giả vào build context rồi kiểm tra final image không chứa nó. Không dùng secret thật.
- Runtime minimization: xác nhận final image không có Maven/toolchain chỉ cần ở build stage.
- Reproducibility: ghi digest của hai build và phân loại mọi khác biệt trước khi kết luận “reproducible”.
Expected evidence
| Evidence | Điều cần chứng minh |
|---|---|
| Cold/cache/no-cache timings + build log | Cache reuse và invalidation boundary đúng như Dockerfile ordering. |
| Image size + history | Runtime image không kéo theo build tool/layer không cần thiết. |
| SBOM + CVE report | Biết runtime artifact chứa gì và scanner phát hiện gì ở thời điểm kiểm tra. |
| Digest comparison | Pinned inputs chưa đủ nếu build output còn timestamp/metadata nondeterministic. |
Lab B · PID 1 và graceful shutdown
Objective
Quan sát trực tiếp signal path khi container dừng, khác biệt giữa exec-form và shell wrapper, và chứng minh application có ngừng nhận việc mới trước khi hoàn tất in-flight work.
Prerequisites
- Application có endpoint hoặc consumer tạo request/job kéo dài đủ lâu để quan sát shutdown.
- Log rõ thời điểm nhận SIGTERM, readiness/intake off, in-flight count và process exit.
- Nếu app spawn child process, có cách quan sát process tree trong container.
Steps
- Chạy một request/consumer dài, rồi gửi
docker stop --time 20 <container>. - So exec-form
ENTRYPOINT ["java","-jar","/app/app.jar"]với shell wrapper không dùngexec. - Quan sát timeline: SIGTERM đến process nào, lúc nào readiness/intake dừng, in-flight work có hoàn tất không và exit code là gì.
- Inject process con; kiểm tra child termination và zombie/reaping nếu application spawn child.
Failure injection & verification
- Shell-wrapper failure: chạy wrapper dạng
sh -c "java ..."khôngexec. Xác nhận Java process không phải PID 1 và shutdown có thể kéo tới timeout. - Grace window too short: đặt stop timeout nhỏ hơn thời gian job. Xác nhận khi grace period hết, work bị cắt và recovery/idempotency path xử lý được.
- Child process: spawn child rồi stop container; xác nhận không để child/zombie tồn tại ngoài assumptions của app.
Expected evidence
- Process tree cho exec-form và shell-wrapper.
- Timestamp của SIGTERM, intake/readiness stop, request/job completion và container exit.
- Exit code ở graceful case và forced-kill case.
- Assertion rằng request mới bị từ chối/drained trong khi in-flight request được xử lý theo policy.
Lab C · Resource limits
Objective
Hiểu memory/CPU limits ở cấp container, cách JVM và thread/connection pools phản ứng dưới pressure, và thu thập evidence để phân biệt OOM, throttling, GC pressure và saturation.
Prerequisites
- Workload có thể tạo memory allocation và CPU load có kiểm soát.
- Có JVM logs/metrics tối thiểu cho heap, GC, threads; có Docker/container state và host/container CPU-memory metrics.
- Không chạy failure injection trên môi trường production dùng chung.
docker run --memory=512m --cpus=0.5 app:test
Steps
- Đặt JVM heap không chiếm toàn bộ 512 MiB để còn headroom cho metaspace, thread stacks, direct/native memory và runtime overhead.
- Inject allocation đến khi OOM/failure; lưu container state, exit code và JVM/container logs.
- Tạo CPU load để quan sát throttling, latency và GC khi container chỉ có 0.5 CPU.
- So unbounded threads/connections với bounded pools dưới cùng resource limit.
Failure injection & verification
- Memory pressure: tăng allocation theo bậc; phân biệt Java heap OOM với container/kernel OOM kill bằng JVM log, container state và exit evidence.
- CPU throttling: giữ request rate ổn định rồi giảm CPU quota; quan sát p95/p99 latency, queue depth và GC behavior.
- Concurrency amplification: cho downstream chậm, so unbounded thread/connection growth với bounded pool + rejection/backpressure.
Expected evidence
| Signal | Câu hỏi cần trả lời |
|---|---|
| Memory usage / JVM heap / native headroom | Failure do heap đầy, native memory, hay container limit? |
| Container exit state + logs | Process tự fail hay bị OOM killer kết thúc? |
| CPU throttling + latency | Quota thấp làm latency/GC/queue tăng đến mức nào? |
| Threads/connections/queue depth | Bounded pool có ngăn saturation lan sang downstream không? |
Lab D · Read-only và networking
Objective
Chứng minh application chạy được với immutable root filesystem, chỉ mở đúng write paths cần thiết, dùng service DNS thay vì hard-code localhost, và recover đúng khi database connection bị stale sau restart.
Prerequisites
- Application + database chạy được bằng Compose hoặc user-defined Docker network.
- Biết rõ các path application thật sự cần ghi: temp, cache, upload hoặc runtime state.
- Có health/readiness check và timeout/retry metrics cho DB connection.
Steps
- Chạy container với read-only root filesystem; cấp
tmpfshoặc volume chỉ cho các thư mục thực sự cần write. - Test bind mount ownership/UID với non-root user và kiểm tra secret file permissions.
- Chạy app + DB bằng Compose service DNS; application kết nối bằng service name, không hard-code
localhost. - Restart DB để tạo stale connection; kiểm tra connect/read timeout, retry/backoff và connection-pool recovery.
# Ví dụ minh họa; điều chỉnh path theo ứng dụng thật
docker run --read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m app:test
Failure injection & verification
- Unexpected write: cố ghi vào một path không được cấp write permission; operation phải fail rõ ràng, không silently đổi sang path khác.
- UID mismatch: mount thư mục host có owner không phù hợp; xác nhận app fail theo expected permission model rồi sửa ownership/mode thay vì chạy root.
- DNS/network: thay container IP bằng service restart; app vẫn phải resolve service name mới thay vì phụ thuộc IP cũ.
- DB restart: restart database khi app đang hoạt động; stale connection phải bị phát hiện, timeout bounded và pool tạo connection mới.
Expected evidence
- Command/Compose config cho read-only root + explicit writable mounts.
- UID/GID và permission test cho bind mount/secret.
- DNS/service-name resolution trước và sau container recreation.
- DB restart timeline: first failure, timeout, retry/backoff, reconnect và readiness recovery.
tmpfs dùng memory của container, vì vậy write-heavy temp data cũng phải nằm trong capacity model.Deliverables
- Docker/BuildKit version, Dockerfile và build timings.
- Image history, SBOM và scan report.
- SIGTERM timeline và in-flight assertions.
- OOM/throttle evidence và filesystem/network tests.
- Mỗi failure injection có expected behavior, observed behavior, operational signal và recovery/rollback note.