Image, layer, container: đóng gói order-service

4 câu hỏi~25 phútTừ khóa: works on my machine, image, container, layer, build cache, registry, tag, docker run/ps/logs/exec

Mình muốn học Docker, Kubernetes và Helm.

Được. Học theo đúng thứ tự, đừng học song song:

Ứng dụng Spring Boot
       ↓
1. Docker      "Đóng gói app thành container"
       ↓
2. Kubernetes  "Chạy và quản lý nhiều container"
       ↓
3. Helm        "Đóng gói và quản lý Kubernetes YAML"

Chương này là Docker. Ta dùng một project chạy xuyên suốt thay vì ví dụ nginx rời rạc: order-service (Spring Boot 3, Java 17) dùng PostgreSQL và Redis. Cuối chương, cả ba chạy bằng một lệnh docker compose up.

1. Bài toán: "works on my machine"

Bạn có order-service.jar. Trên laptop chạy ngon:

java -jar target/order-service.jar

Đưa sang server khác thì phải lo:

Server
 ├── Java đã cài chưa?
 ├── Java version đúng chưa? (17 hay 11?)
 ├── config đúng chưa?
 ├── timezone, locale, font, thư viện OS?
 └── java -jar order-service.jar

2. Vấn đề

Laptop của bạn                    Server
──────────────                    ──────
Java 17                           Java ???
Spring Boot        ──────►        Config ???
order-service.jar                 Environment ???

3. Giải pháp: Image và Container

Docker đóng gói cả môi trường chạy app thành một Image:

Docker Image: order-service:1.0
┌──────────────────────┐
│ Linux userland       │
│ Java 17 (JRE)        │
│ order-service.jar    │
│ startup command      │
└──────────────────────┘

Từ Image, ta tạo Container:

          IMAGE
            │
       docker run
            │
      ┌─────┼─────┐
      ▼     ▼     ▼
    app-1 app-2 app-3

Giống tư duy Java (không giống hoàn toàn về kỹ thuật, nhưng dễ nhớ):

Java                  Docker
Class                 Image
  │ new                 │ docker run
  ▼                     ▼
Object                Container

Layer. Image không phải một file khổng lồ. Nó là chồng các layer read-only, mỗi lệnh trong Dockerfile (FROM, COPY, RUN...) tạo một layer. Container thêm một writable layer mỏng ở trên cùng:

┌─────────────────────────────┐
│ writable layer (container)  │  ← mất khi docker rm
├─────────────────────────────┤
│ COPY order-service.jar      │  layer 3  (đổi mỗi lần build)
├─────────────────────────────┤
│ WORKDIR /app                │  layer 2
├─────────────────────────────┤
│ eclipse-temurin:17-jre      │  layer 1  (base image, dùng chung)
└─────────────────────────────┘

Registry. Nơi lưu và phát hành image: Docker Hub, GitHub Container Registry, Azure Container Registry, ECR... Server chỉ cần docker pull là có đúng image bạn đã build.

Developer ──docker push──► Registry ──docker pull──► Server / Kubernetes

4. Code từng bước

Bước 1: Chạy thử image có sẵn

docker pull nginx
docker run nginx

Image nginx được tải về, Docker tạo một container từ image đó.

Bước 2: Dockerfile tối thiểu cho order-service (chi tiết ở bài 16.2)

order-service/
├── src/
├── pom.xml
├── target/
│   └── order-service.jar
└── Dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/order-service.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

Bước 3: Build image

mvn clean package
docker build -t order-service:1.0 .
docker images
REPOSITORY       TAG
order-service    1.0

Lúc này chỉ có IMAGE, chưa có app nào đang chạy.

Bước 4: Chạy container và các lệnh cơ bản

# chạy nền (-d), đặt tên, map port host 8080 → container 8080
docker run -d --name order-1 -p 8080:8080 order-service:1.0

docker ps                 # container đang chạy
docker ps -a              # cả container đã stop
docker logs order-1       # xem log
docker logs -f order-1    # theo dõi log realtime
docker exec -it order-1 sh   # chạy thêm process sh bên trong container
docker stop order-1       # RUNNING → STOPPED, container vẫn còn
docker start order-1      # STOPPED → RUNNING
docker rm order-1         # xóa container, image vẫn còn
curl http://localhost:8080/actuator/health

Chạy nhiều container từ cùng một image:

docker run -d -p 8081:8080 order-service:1.0
docker run -d -p 8082:8080 order-service:1.0
docker run -d -p 8083:8080 order-service:1.0

5. Cách hoạt động

Browser
   │ GET localhost:8080/orders/1
   ▼
Host port 8080
   │ Docker port mapping (-p 8080:8080)
   ▼
┌───────────────── Container ─────────────────┐
│ java -jar app.jar  (main process)           │
│      └── Spring Boot                        │
│            └── Embedded Tomcat :8080        │
│                  └── DispatcherServlet      │
│                        └── OrderController  │
└─────────────────────────────────────────────┘

Docker không thay thế Tomcat. Docker chỉ tạo môi trường chứa process Java đó. Container sống khi main process còn sống: Java chết thì container stop.

6. So sánh

ImageContainer
Bản chấtTemplate read-only gồm các layerInstance đang chạy + writable layer
Tạo bằngdocker build / docker pulldocker run
Số lượngMột imageNhiều container từ cùng image
Xóadocker rmidocker rm (image không ảnh hưởng)
Lưu ởLocal + RegistryChỉ trên máy đang chạy

7. Khi nào nên dùng / không nên dùng

Trong docker build -t, -t có ý nghĩa gì?

-t = tag: đặt tên và version cho image.

docker build -t order-service:1.0 .
    │
    ├── -t order-service:1.0
    │      │             │
    │      │             └── tag/version = 1.0
    │      └──────────────── image name = order-service
    │
    └── . = build context (folder hiện tại)

Không ghi tag thì Docker hiểu là latest:

docker build -t order-service .     # = order-service:latest
docker run order-service            # = docker run order-service:latest

Một image có thể có nhiều tag, và tag là thứ ta dùng khi deploy:

order-service:1.2  ← version cũ
order-service:1.3  ← version mới
                     Kubernetes: image: order-service:1.3

Khi push lên registry, tên đầy đủ có cả host registry:

docker tag order-service:1.3 myregistry.azurecr.io/order-service:1.3
docker push myregistry.azurecr.io/order-service:1.3

Lưu ý production: đừng deploy bằng latest. latest chỉ là một cái tên, không có nghĩa "mới nhất", và nó có thể bị ghi đè. Dùng tag cố định (version hoặc git SHA) để biết chính xác đang chạy bản nào và rollback được.

Còn dấu . là build context: folder mà Docker được phép đọc file khi COPY. Phần này nói kỹ ở bài sau.

Vậy mình docker rm container thì image có mất không? Data trong container thì sao?

Bạn hiểu gần đúng, chỉ cần tách rõ ba thứ:

IMAGE order-service:1.0      ✅ vẫn còn
     │
     ├── old Container A     ❌ đã docker rm
     └── new Container B     ✅ docker run lại

Với order-service thì không sao vì app stateless. Nhưng nếu Postgres chạy trong container mà không có volume, docker rm là mất sạch bảng orders. Đó là lý do có Volume (bài 16.4).

Mindset quan trọng trước khi sang K8s: container là thứ tạm thời, thay thế được. Hỏng thì xóa, tạo lại từ image.

Phỏng vấn hỏi Docker image, container, layer thì trả lời sao?

Q1. Image khác container thế nào?

🇻🇳 Tiếng Việt:

Image là template read-only, chứa ứng dụng và runtime dependency, ví dụ JRE 17 và file jar. Container là một instance đang chạy từ image, có thêm một writable layer riêng. Từ một image em chạy được nhiều container. Xóa container không ảnh hưởng image.

🇬🇧 English:

A Docker image is a read-only template that contains the application and its runtime dependencies, for example a JRE and the JAR file. A container is a running instance of that image with its own thin writable layer. One image can start many containers, and removing a container does not affect the image.

Q2. Layer và build cache là gì?

🇻🇳 Tiếng Việt:

Mỗi lệnh trong Dockerfile tạo một layer read-only. Layer được dùng chung giữa các image và được cache khi build. Nếu một layer đổi thì nó và mọi layer phía sau phải build lại, nên em đặt phần ít đổi như base image và dependency lên trước, source code xuống sau.

🇬🇧 English:

Each Dockerfile instruction creates a read-only layer. Layers are shared between images and cached during builds. When a layer changes, it and all following layers are rebuilt, so I put rarely changing parts like the base image and dependencies first and the source code last.

Q3. Tag và registry dùng thế nào khi deploy?

🇻🇳 Tiếng Việt:

CI build image, gắn tag cố định như version hoặc git SHA, rồi push lên registry như ACR hoặc ECR. Server hoặc Kubernetes pull đúng tag đó. Em tránh dùng latest ở production vì nó có thể bị ghi đè, khó biết đang chạy bản nào và khó rollback.

🇬🇧 English:

The CI pipeline builds the image, tags it with an immutable tag such as the version or git SHA, and pushes it to a registry like ACR or ECR. Servers or Kubernetes then pull exactly that tag. I avoid latest in production because it can be overwritten, which makes it hard to know what is running and to roll back.

Cách nhớ ngắn gọn


Tự kiểm tra

"Works on my machine" được Docker giải quyết thế nào?

Đóng gói cả runtime (JRE, thư viện OS) cùng app vào image, nên máy nào chạy image đó cũng có cùng môi trường.

docker build -t order-service . tạo image có tag gì?

order-service:latest, vì không ghi tag thì mặc định là latest.

Sửa một dòng code rồi build lại, layer nào được cache?

Các layer phía trên lệnh COPY source/jar (base image, WORKDIR, dependency) được cache; từ layer COPY thay đổi trở xuống phải build lại.

docker stop và docker rm khác nhau thế nào?

stop chuyển container sang STOPPED, vẫn start lại được. rm xóa container và writable layer của nó.

docker exec -it order-1 sh có phải SSH không?

Không. Docker chạy thêm process sh bên trong container đang chạy. Thoát shell thì process chính (java) vẫn chạy.