Trong kỷ nguyên của điện toán đám mây và tự động hóa, Docker đã trở thành công cụ cốt lõi giúp các kỹ sư phần mềm xây dựng, đóng gói và triển khai ứng dụng một cách linh hoạt, nhanh chóng và đồng nhất trên nhiều môi trường khác nhau. Việc hiểu và thành thạo Docker không chỉ giúp nâng cao hiệu suất làm việc mà còn là tiêu chí tuyển dụng quan trọng trong các vị trí DevOps, Backend Developer, QA, hay System Administrator. Chính vì thế, các câu hỏi phỏng vấn Docker xuất hiện ngày càng phổ biến trong các buổi đánh giá kỹ thuật.
Đọc bài viết này để tham khảo câu trả lời cho:
- Câu hỏi phỏng vấn Docker cơ bản
- Câu hỏi phỏng vấn Docker trung cấp
- Câu hỏi phỏng vấn Docker nâng cao
Các nhóm câu hỏi phỏng vấn Docker phổ biến
Tùy theo cấp độ kinh nghiệm, nhà tuyển dụng sẽ khai thác ứng viên theo những nhóm kiến thức chính như:
- Nhóm kiến thức cơ bản: Container là gì, sự khác biệt giữa Image và Container, cách viết Dockerfile, sử dụng Volume, Network,…
- Nhóm thao tác và vận hành: chạy Container với tham số cụ thể, dùng Docker Compose, debug khi Container gặp lỗi, giới hạn tài nguyên,…
- Nhóm nâng cao & thực chiến: triển khai CI/CD với Docker, bảo mật Container, sử dụng multi-stage build, kiểm tra lỗ hổng bảo mật trong Image,…
Việc chuẩn bị kỹ các câu hỏi phỏng vấn Docker không chỉ giúp bạn tự tin vượt qua vòng phỏng vấn, mà còn trang bị nền tảng vững chắc để làm việc trong các hệ thống hiện đại, nơi Docker là một phần không thể thiếu trong chuỗi DevOps.
Câu hỏi phỏng vấn Docker cơ bản (Beginner Level)
Docker là gì? So sánh Docker và máy ảo
Docker là một nền tảng mã nguồn mở giúp đóng gói, phân phối và chạy ứng dụng trong các Container nhẹ. Thay vì tạo một máy ảo hoàn chỉnh, Docker sử dụng cơ chế ảo hóa ở tầng hệ điều hành (OS-level virtualization).
Các Container chia sẻ kernel hệ điều hành của máy chủ, giúp khởi động nhanh và sử dụng tài nguyên hiệu quả hơn so với VM. Docker thường được sử dụng để triển khai ứng dụng một cách nhất quán trên nhiều môi trường khác nhau, từ máy chủ local đến các dịch vụ đám mây.
So sánh Docker và máy ảo (Virtual Machine):
Tiêu chí | Docker Container | Máy ảo (Virtual Machine) |
Mức độ ảo hóa | Ảo hóa tầng hệ điều hành, chia sẻ kernel với host | Ảo hóa toàn bộ phần cứng bằng hypervisor, chạy OS riêng |
Hiệu năng & tốc độ | Khởi động gần như ngay lập tức, sử dụng tài nguyên khi cần | Khởi động lâu hơn, chiếm tài nguyên cố định (CPU, RAM, disk) |
Kích thước | Nhỏ gọn (chỉ bao gồm thư viện và runtime cần thiết) | Lớn hơn nhiều do chứa cả hệ điều hành đầy đủ |
Độ cô lập | Tách biệt process level nhưng dùng chung kernel → độ cô lập thấp hơn | Độc lập hoàn toàn qua kernel riêng → an toàn hơn |
Tính di động (Portability) | Chạy được nhiều Container trên nhiều host khác nhau mà không thay đổi hệ điều hành | VM chạy ổn định trên nhiều hệ thống, nhưng yêu cầu hypervisor tương thích |
Tài nguyên dùng | Chỉ sử dụng tài nguyên khi cần, rất linh hoạt | Cần cấu hình cố định tài nguyên cho từng VM |
Ứng dụng phù hợp | Ứng dụng microservices, CI/CD, dev/test môi trường nhanh nhẹ | Môi trường cần hệ điều hành riêng biệt, ứng dụng legacy, phân lập cao |
Hãy giải thích sự khác biệt giữa Docker Image và Docker Container
Docker Image là một bản mẫu bất biến (immutable blueprint) chứa tất cả những thành phần cần thiết để chạy một ứng dụng, bao gồm mã nguồn, thư viện phụ thuộc, runtime, và các thiết lập cấu hình. Image được tạo ra từ Dockerfile hoặc tải từ các kho lưu trữ như Docker Hub và được lưu trữ dưới dạng các layers (lớp) sử dụng Union File System. Tuy nhiên, Image không thể tự chạy được, nó chỉ cung cấp cơ sở để tạo ra Docker Container.
Docker Container là một thể hiện (instance) đang chạy của một Docker Image. Nó là môi trường thực thi ứng dụng hoàn chỉnh, được tách biệt với hệ điều hành chủ (host OS) thông qua công nghệ ảo hóa ở cấp hệ điều hành sử dụng Linux namespaces và cgroups. Khi Container được khởi chạy từ Image, nó hoạt động như một thực thể sống, có thể thay đổi trạng thái trong quá trình vận hành, chẳng hạn như ghi dữ liệu, cập nhật file, hoặc cài đặt thêm gói mới. Tuy nhiên, những thay đổi này chỉ tồn tại trong Container đang chạy và không ảnh hưởng đến Image gốc do Container sử dụng writable layer riêng biệt.
Phân biệt Docker Image và Docker Container:
Khía cạnh | Docker Image | Docker Container |
Bản chất | Template bất biến (blueprint) | Thực thi ứng dụng (runtime) |
Khả năng thay đổi | Không thể cập nhật trực tiếp | Có thể thay đổi, tạo file, sửa cấu hình |
Vai trò sử dụng | Làm cơ sở để tạo Container | Nơi ứng dụng thực thi |
Lưu trữ | Layers chỉ đọc (read-only) | Read-only layers + writable layer |
Mối quan hệ | Image dùng để tạo Container | Container là instance của Image |
Số lượng tạo được | Một Image → nhiều Container | Nhiều Container từ một Image |
Trạng thái | Tĩnh (static) | Động (dynamic) |
So sánh Docker run và Docker start
Lệnh docker run
thường được sử dụng để tạo và khởi động một Container mới từ một Image đã chỉ định. Lệnh này đồng thời thực hiện hai bước:
- Tạo một layer ghi (writable layer) mới dựa trên Image
- Sau đó khởi động Container cùng với các tùy chọn như đặt tên, gắn Volume, thiết lập biến môi trường, hoặc mapping port.
Nhớ đó, docker run
giúp cấu hình Container một cách linh hoạt khi lần đầu khởi tạo.
Ngược lại, docker start
chỉ được dùng để khởi động lại một Container đã tồn tại và đang ở trạng thái dừng (stopped). Nó không tạo mới Container và sẽ sử dụng lại trạng thái filesystem cũng như các cấu hình trước đó. Lệnh này có thể kết hợp các tùy chọn như -i
để bật chế độ interactive hoặc -a
để gắn đầu ra (attach output) đến terminal.
So sánh docker run
và docker start
:
Tiêu chí | Docker run | Docker start |
Tạo Container | Có. Nó tạo một Container mới từ Image | Không, chỉ dùng lại Container đã tạo trước |
Khởi động | Có. Nó tạo và chạy cùng lúc | Có, nhưng chỉ chạy Container đang ở trạng thái stopped |
Tuỳ chọn cấu hình | Hỗ trợ nhiều flag như --name , -p , -v , -e | Hạn chế, chủ yếu dùng các flag như -i , -a |
Tốc độ thực thi | Chậm hơn vì phải tạo Container mới | Nhanh hơn vì khởi chạy lại Container đã có sẵn |
Trạng thái và dữ liệu | Bắt đầu từ trạng thái sạch, không giữ lại dữ liệu cũ | Giữ nguyên filesystem, log, trạng thái trước đó |
Container ID | Tạo Container ID mới | Sử dụng Container ID đã tồn tại |
Ứng dụng tiêu biểu | Khởi tạo Container mới với cấu hình mới | Resume Container đã dừng mà không muốn mất dữ liệu |
Dockerfile là gì? Dockerfile dùng để làm gì?
Dockerfile là một tệp văn bản chứa các lệnh viết theo một ngôn ngữ riêng (DSL) giúp đặc tả từng bước trong quá trình xây dựng một Docker Image từ đầu.
- Mỗi dòng lệnh trong Dockerfile như
FROM
,RUN
,COPY
,ENV
,CMD
,… được Docker daemon thực thi tuần tự để ghép các lớp (layers) tạo nên một Image hoàn chỉnh. - Mỗi lệnh (instruction) tạo ra một layer mới, và Docker sử dụng cơ chế cache (caching mechanism) để tăng tốc quá trình build bằng cách tái sử dụng những layer không thay đổi.
Vì vậy, Dockerfile được coi như “mã nguồn cấu trúc” của Image, giúp đảm bảo sự nhất quán và khả năng tái tạo lại môi trường mọi lúc mọi nơi:
- Mục đích chính của Dockerfile là tự động hóa quá trình tạo Image một cách có thể tái sử dụng, dễ chia sẻ và linh động. Nhờ đó, chúng ta có thể khởi tạo một Container mới với môi trường giống hệt nhau trên mọi hệ thống, từ máy phát triển đến các máy chủ sản xuất.
- Dockerfile cũng hỗ trợ các workflow như CI/CD, cho phép xây dựng Image tự động khi code thay đổi và đảm bảo các Container được tạo ra hoạt động theo đúng cấu hình đặt trước.
- Ngoài ra, việc sử dụng Dockerfile giúp tạo ra các Images có kích thước tối ưu thông qua các best practices như multi-stage builds và tối ưu hóa layer.
Đọc chi tiết: Dockerfile là gì: Hướng dẫn viết Dockerfile theo cấu trúc chuẩn
CMD và ENTRYPOINT trong Dockerfile có gì khác nhau?
CMD là lệnh mặc định để thực thi khi Container được khởi động mà không có lệnh CLI cung cấp thêm. Có thể là:
shell form (CMD command param1 param2)
Hoặc:
exec form (CMD ["executable", "param1", "param2"]),
CMD chủ yếu dùng để cung cấp các command và tham số mặc định cho Image. Khi chạy docker run và truyền lệnh cụ thể, lệnh đó sẽ ghi đè hoàn toàn CMD từ Dockerfile. Quan trọng là trong shell form, command sẽ chạy với /bin/sh -c
, trong khi exec form chạy trực tiếp executable mà không qua shell.
ENTRYPOINT được thiết kế để định nghĩa lệnh có tính cố định mà Container luôn thực thi khi khởi động, bất kể khi chúng ta có cung cấp lệnh CLI hay không. ENTRYPOINT có thể ở dạng:
exec (ENTRYPOINT ["executable", "param1"])
Hoặc:
shell (ENTRYPOINT command param1)
ENTRYPOINT chỉ bị ghi đè khi sử dụng flag –entrypoint trong docker run. ENTRYPOINT với exec form sẽ chạy như PID 1 process, có thể nhận và xử lý signals một cách chính xác. Từ đó giúp đảm bảo điều kiện hoạt động cố định và đáng tin cậy cho Container.
Khi cả hai chỉ thị cùng xuất hiện trong Dockerfile, ENTRYPOINT đóng vai trò là chương trình chính được chạy, còn CMD cung cấp bộ tham số mặc định cho ENTRYPOINT.
Ví dụ:
- Khi
ENTRYPOINT ["echo", "Hello"]
vàCMD ["World"]
, thì lệnh mặc định sẽ là echo Hello World. - Khi chạy lệnh docker run Image KodeKloud!, phần “World” từ CMD sẽ bị ghi đè, và Container sẽ hiển thị Hello KodeKloud!. ENTRYPOINT vẫn luôn thực thi, trong khi CMD có thể linh hoạt bị thay đổi bằng đối số CLI.
So sánh CMD và ENTRYPOINT:
Tiêu chí | CMD | ENTRYPOINT |
Mục tiêu | Lệnh/chạy mặc định dễ bị ghi đè | Lệnh/core command cố định khi Container khởi động |
Ghi đè tại runtime? | Có, nó thêm args vào docker run sẽ ghi đè | Không, trừ khi dùng flag –entrypoint |
PID 1 process | Có thể không phải PID 1 nếu dùng shell form | Luôn là PID 1 với exec form |
Dùng kết hợp với nhau? | Có, CMD làm tham số cho ENTRYPOINT | Có, ENTRYPOINT xác định process; CMD đưa arg mặc định |
Tính linh hoạt | Cao, dùng phù hợp khi cần command thay đổi | Ổn định. Container luôn chạy chính xác lệnh đã định |
Signal handling | Có thể không nhận signals đúng cách với shell form | Nhận và xử lý signals chính xác với exec form |
Syntax form ưu tiên | Exec form: CMD ["app", "arg"] | Exec form: ENTRYPOINT ["app", "arg"] |
Volumes trong Docker là gì? Khác gì với Bind Mounts?
Volumes là một cơ chế lưu trữ dữ liệu do Docker quản lý hoàn toàn. Khi Volume được tạo, Docker sẽ tự động tạo một thư mục trên host (thường là ở /var/lib/docker/Volumes/) để lưu trữ dữ liệu. Các Container có thể gắn và sử dụng Volume đó mà không bị phụ thuộc vào cấu trúc filesystem của host. Volume giúp lưu dữ liệu tồn tại độc lập với vòng đời của Container – kể cả khi Container bị xóa, dữ liệu vẫn còn nguyên vẹn.
Volumes có performance tốt hơn bind mounts trên Windows và macOS do được tối ưu hóa bởi Docker Desktop. Ngoài ra, Docker cho phép quản lý Volume qua CLI hoặc API, khiến việc backup, restore và chia sẻ Volume giữa nhiều Container trở nên thuận tiện hơn.
Bind Mounts là cách trực tiếp gắn một thư mục hoặc file hiện có trên hệ thống host vào bên trong Container theo đường dẫn tuyệt đối. Khi sử dụng bind mount, Container truy cập trực tiếp filesystem host, mọi thay đổi từ Container hoặc host sẽ đồng thời phản ánh lên nhau.
Tuy bind mounts rất tiện cho phát triển (ví dụ đồng bộ code realtime) nhưng chúng phụ thuộc rất nhiều vào cấu trúc trên host và thường không phù hợp khi di chuyển giữa các máy khác nhau. Bind mounts cũng có thể gây ra vấn đề về bảo mất vì Container có thể truy cập trực tiếp vào filesystem của host.
Ngoài ra, Docker còn hỗ trợ tmpfs mounts – dạng lưu trữ tạm thời trong RAM, phù hợp cho dữ liệu nhạy cảm hoặc tệp tạm thời không cần lưu trữ lâu dài (persist).
So sánh Volumes và Bind Mounts:
Tiêu chí | Volumes | Bind Mounts |
Được quản lý bởi Docker | Có – nằm trong thư mục do Docker điều hành | Không – dùng filesystem host, người dùng tự chỉ định path |
Độc lập với host cấu trúc | Có – không phụ thuộc vào cấu trúc thư mục của host | Không – phải xác định rõ host path chính xác |
Backup & migration | Dễ dàng sao lưu, di chuyển qua CLI hay API của Docker | Phức tạp hơn, phụ thuộc vào host |
Chia sẻ giữa Container | Có thể chia sẻ Volume giữa nhiều Container đồng thời | Có thể chia sẻ, nhưng phụ thuộc vào quyền và cấu trúc host |
Mục đích sử dụng tiêu biểu | Lưu dữ liệu ứng dụng (DB, logs…), dùng trong production | Phát triển real-time: code, config, logs dev, test |
Làm cách nào để xóa tất cả Container đang stopped?
Để xóa toàn bộ Docker Container đã dừng (stopped), tôi dùng các cách sau:
Cách nhanh và đơn giản nhất: docker Container prune
Lệnh này sẽ xóa tất cả Container có trạng thái Exited hoặc Created, được hỏi xác nhận trước khi thực hiện.
Nếu muốn bỏ qua bước hỏi xác nhận, thêm -f
để chạy lệnh mà không cần prompt.
Có thể thêm filters để kiểm soát chính xác hơn: docker Container prune --filter "until=24h"
để xóa Containers stopped trong 24h qua. Đây là cách hiệu quả giúp dọn dẹp nhanh môi trường Docker không còn dùng đến.
Cách kiểm soát cụ thể bằng lệnh dòng
Lọc và xóa các Container dừng theo cách thủ công:
- Bước 1: Liệt kê các Container đã dừng:
docker ps --filter "status=exited" --filter "status=dead" -q
- Bước 2: Xóa từng Container hoặc nhiều Container một lúc:
docker rm <Container_id>
hoặc xóa tất cả:
docker rm $(docker ps --filter "status=exited" -q)
hoặc sử dụng xargs:
docker ps --filter "status=exited" -q | xargs docker rm
Cách này giúp kiểm soát chính xác những Container nào được xóa và tránh xóa nhầm.
Cách dọn dẹp toàn diện với docker system prune
Xóa Containers stopped, Networks unused, Images dangling và build cache:
docker system prune
Thêm -a để xóa luôn Images không được sử dụng:
docker system prune -a
Thêm –Volumes để xóa luôn anonymous Volumes
docker system prune --Volumes
Khi nào nên dùng Docker Compose?
Docker Compose là một công cụ giúp định nghĩa và quản lý một ứng dụng gồm nhiều Container thông qua một file cấu hình YAML duy nhất (thường là docker-Compose.yml). Thay vì chạy từng Container bằng nhiều lệnh docker run, tôi có thể khởi động toàn bộ hệ thống gồm dịch vụ web, database, cache… chỉ với một lệnh docker Compose up.
Đây là cách tiếp cận lý tưởng khi tôi cần phát triển hoặc thử nghiệm các ứng dụng đa thành phần một cách nhanh chóng, nhất quán và dễ kiểm soát.
Những tình huống nên dùng Docker Compose:
- Môi trường phát triển local (Development)
Compose cực kỳ hữu ích cho lập trình viên khi cần chạy nhiều dịch vụ cùng lúc. Ví dụ ứng dụng Node.js kết nối với Redis hoặc MySQL. Tất cả cấu hình về service, Network, Volume đều định nghĩa trong file YAML và dễ dàng chia sẻ cùng đồng đội. Chỉ cần clone repository và chạy lệnh docker Compose up, môi trường dev đã sẵn sàng.
- CI/CD và kiểm thử tự động (Automated Testing)
Compose cho phép dựng và phá môi trường kiểm thử một cách tự động trong pipeline CI/CD. Tôi có thể tạo môi trường test riêng biệt, chạy xong thì dẹp sạch bằng lệnh docker Compose down, thuận tiện cho các bài kiểm thử end-to-end hoặc integration tests.
- Triển khai trên một server đơn (Single Host Deployments)
Docker Compose vẫn phù hợp với những ứng dụng nhỏ hoặc môi trường staging/production chạy trên một máy chủ duy nhất. Khi các cấu hình riêng cho production (như port, volume) được đưa vào file Compose, tôi có thể quản lý toàn bộ stack chỉ bằng một file YAML với docker compose up và docker compose down.
Lưu ý quan trọng: Docker Compose không phải là công cụ orchestration cho môi trường production multi-host. Với hệ thống lớn, hãy dùng Kubernetes, Docker Swarm, hoặc các nền tảng Container orchestration khác.
Làm thế nào để build một Image từ Dockerfile?
Để tạo một Docker Image từ Dockerfile, tôi sử dụng lệnh docker build cùng với thư mục chứa Dockerfile và nội dung cần đóng gói.
Cú pháp cơ bản như sau:
docker build -t tên-Image:tag .
Trong đó:
- Tham số
-t
giúp gán tên và tag cho Image, ví dụmyapp:latest
. - Dấu . ở cuối câu lệnh chỉ đến build context – thư mục chứa Dockerfile và các file cần thiết để build Image, như mã nguồn, cấu hình,…
Điểm quan trọng là toàn bộ build context sẽ được gửi đến Docker daemon, nên tôi luôn sử dụng .dockerignore
để loại trừ những file không cần thiết. Nếu không chỉ định tag, Docker mặc định gán tag là latest.
Quy trình bên trong
Khi chạy lệnh, Docker sẽ đọc Dockerfile, lấy base Image (nếu cần), và thực thi từng bước như COPY, RUN, WORKDIR… tạo thành các lớp (layers) để ghép thành Image hoàn chỉnh.
Docker sử dụng layer caching để tăng tốc độ build – các layers không thay đổi sẽ được tái sử dụng từ cache. Image này sau đó có thể dùng để chạy Container.
Một số tùy chọn build nâng cao mà tôi hay dùng
# Build với Dockerfile khác tên hoặc vị trí khác
docker build -f Dockerfile.prod -t myapp:prod .
# Build với build arguments
docker build --build-arg NODE_ENV=production -t myapp:prod .
# Build multi-stage và chỉ build đến stage cụ thể
docker build --target production -t myapp:prod .
# Build không sử dụng cache
docker build --no-cache -t myapp:latest .
Quản lý tag & push lên registry
Sau khi build, tôi có thể gán thêm tag mới cho Image bằng lệnh docker tag.
Ví dụ:
docker tag myapp:latest username/myapp:v1.0
Điều này giúp đặt tên rõ ràng và quản lý phiên bản dễ dàng.
Sau khi Image đã được tag phù hợp, push Image lên Docker Hub hoặc registry khác bằng lệnh:
docker push username/myapp:v1.0
Tất nhiên, cần đăng nhập (docker login) trước khi push Image lên registry.
Docker Hub là gì? Giải thích quy trình để push Image lên đó?
Docker Hub là một đăng ký (registry) công cộng dùng để lưu trữ và phân phối Docker Image qua Internet. Đây là nơi mà người dùng có thể push Image lên hoặc pull Image từ đó, hỗ trợ cả repository ở chế độ public hoặc private. Docker Hub là registry mặc định mà Docker CLI truy cập khi dùng lệnh như docker pull hoặc docker push, trừ khi người dùng chỉ định registry khác.
Khi cần chia sẻ Docker Image đến cộng đồng hoặc môi trường triển khai, tôi thực hiện các bước sau:
Bước 1: Đăng ký tài khoản & tạo repository
- Tạo tài khoản miễn phí trên Docker Hub
- Tạo repository mới, chọn public hoặc private tùy mục đích.
- Có thể bật Automated Builds để Docker Hub tự build Image từ GitHub/Bitbucket khi code thay đổi.
Bước 2: Gắn tag cho Image phù hợp namespace
Image cần được tag theo định dạng <dockerhub-username>/<repository-name>:<tag>
để Docker Hub có thể xử lý đúng.
Ví dụ: alice/myapp:v1.0. Nếu không chỉ định tag, Docker mặc định dùng latest
# Tag Image hiện có
docker tag myapp:latest alice/myapp:v1.0
docker tag myapp:latest alice/myapp:latest
Bước 3: Đăng nhập trên CLI
Trước khi push, cần thực hiện docker login và nhập username/password Docker Hub để xác thực tài khoản. Từ phiên bản mới, Docker khuyến nghị sử dụng Personal Access Token thay vì password trực tiếp để tăng bảo mật.
Bước 4: Push Image lên Docker Hub
Sau khi login và tag đúng, tôi chạy lệnh sau để push Image:
docker push <dockerhub-username>/<repository-name>:<tag>
Docker sẽ upload Image theo từng layer đến repository tương ứng. Docker sử dụng layer deduplication – chỉ upload các layers chưa tồn tại trên registry, giúp tiết kiệm bandwidth và thời gian. Nếu bạn có nhiều tag cần push, có thể dùng flag -a để push tất cả cùng lúc
Bước 5: Kiểm tra thành công
Sau khi lệnh docker push chạy xong, tôi vào giao diện Docker Hub để xác nhận Image đã hiển thị trong repository với đúng tag như mong muốn. Docker Hub cũng cung cấp vulnerability scanning để kiểm tra các lỗ hổng bảo mất trong Image.
Câu hỏi phỏng vấn Docker trung cấp (Intermediate Level)
Mô tả cách cấu hình ghi nhật ký cho các vùng chứa Docker để thu thập và phân tích nhật ký ứng dụng
Trong môi trường production hoặc staging, tôi sẽ xây dựng một hệ thống logging tập trung cho các Container Docker gồm hai phần chính: cấu hình logging driver trên Docker và forwarding log tới hệ thống phân tích bên ngoài.
Chọn và cấu hình Docker Logging Driver
Mặc định Docker sử dụng driver json‑file, lưu log dưới dạng JSON vào file trên host (đường dẫn thường là /var/lib/docker/Containers/<Container-id>/<Container-id>-json.log). Tuy nhiên trong môi trường production tôi ưu tiên chuyển sang driver như local, syslog, fluentd, hoặc gelf, tùy theo hệ thống logging đã sử dụng.
Ví dụ, để đặt local làm driver mặc định và bật log rotation:
{
"log-driver": "local",
"log-opts": {
"max-size": "20m",
"max-file": "5",
"compress": "true"
}
}
Sau đó restart Docker daemon để áp dụng cấu hình mới. Nếu cần override cho Container riêng, có thể sử dụng flag --log-driver
và --log-opt
trong lệnh docker run
.
Thiết lập logging ứng dụng trong Container
Ứng dụng chạy bên trong Container nên ghi log vào stdout/stderr, sử dụng thư viện logging chuẩn như logback, logrus, hay Python logging. Việc này giúp Docker dễ dàng bắt log và chuyển tiếp nhờ logging driver đã cấu hình.
Tôi cũng chuẩn hóa format log (ví dụ JSON có fields timestamp, level, message, metadata Container-id, service, môi trường…), giúp dễ phân tích sau này.
Chuyển log đến hệ thống trung tâm
Để phân tích tập trung, tôi sẽ forward log từ các Container ra hệ thống chuyên dụng như ELK Stack (Elasticsearch/Logstash/Kibana), Splunk, Fluentd, Sematext, hoặc Syslog server, tùy theo quy mô và hạ tầng hiện tại.
Có thể dùng các mô hình triển khai sau:
- Logging driver tích hợp plugin (syslog, fluentd, gelf…) để gửi trực tiếp log từ Docker đến dịch vụ thu thập log.
- Sidecar hoặc Container chuyên dụng: chạy một Container chuyên thu thập log từ các Volume hoặc Docker API rồi gửi đến log aggregator.
- File-based shipping và agent: sử dụng agent như Logstash, Logagent hoặc Datadog Agent để đọc file log trên host hoặc Volume và forward đi nơi khác.
- Thiết lập quy tắc quản lý log và phân tích
- Bật log rotation để tránh log chiếm quá nhiều disk và đảm bảo retention policy (giữ log theo ngày hoặc kích thước).
- Dùng structured logging (JSON) cùng levels rõ ràng (INFO, WARN, ERROR), phân loại theo service, Container để dễ filter và search.
- Triển khai dashboard (Grafana/Kibana) để theo dõi real-time, alert khi lỗi xảy ra hoặc khi log Volume bất thường.
Làm sao để tối ưu Dockerfile để giảm dung lượng Image?
Để tối ưu Dockerfile, tôi thường kết hợp nhiều chiến lược để tạo ra Image gọn nhẹ, nhanh chóng và bảo mật hơn.
Dưới đây là một số chiến lược hiệu quả:
Chọn base Image nhẹ (Minimal Base Image)
Khởi đầu với một Image nền tối giản như Alpine, Distroless, hoặc các bản “-slim”, giúp giảm kích thước ngay từ bước đầu.
Ví dụ, <node:alpine> chỉ chiếm khoảng 60 MB trong khi bản Ubuntu gốc dễ đạt đến 600 MB. Distroless Images thậm chí còn nhỏ hơn và an toàn hơn vì không chứa shell và package managers.
Sử dụng multi-stage builds
Với multi-stage builds, bạn có thể dùng một Image lớn (có build tools) chỉ trong giai đoạn xây dựng, rồi chỉ copy phần cần thiết vào Image cuối cùng. Kết quả là Image sản xuất sẽ gọn nhẹ hơn rất nhiều.
Tối ưu thứ tự layers và tận dụng cache
Đặt các instructions ít thay đổi lên trước (như dependency installation) và các instructions hay thay đổi xuống cuối (như source code copy). Docker sẽ cache các layers không thay đổi, giúp build nhanh hơn.
Giảm số layer – tối ưu các câu lệnh trong Dockerfile
Mỗi RUN, COPY, ADD tạo một layer, tôi sẽ gộp nhiều lệnh thành một RUN duy nhất, ví dụ: RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* thay vì chia nhỏ thành nhiều câu lệnh riêng biệt.
Ví dụ:
# Cách tối ưu
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
wget \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
Dùng .dockerignore để loại bỏ file không cần thiết
Giống như .gitignore, file .dockerignore chỉ định tập tin hoặc thư mục không được copy vào build context, từ đó giảm dung lượng và tăng tốc build. Đây là cách tối ưu hữu hiệu để tránh đưa vô những file thừa như logs, thư mục build, hoặc node_modules không cần.
Ví dụ .dockerignore:
node_modules
npm-debug.log
.git
.gitignore
*.md
.env
coverage/
.nyc_output
Loại bỏ file tạm, cache, và phụ thuộc dư thừa
Trong quá trình cài đặt package, hãy dùng các tuỳ chọn như --no‑install‑recommends
(với apt), --no-cache
(với apk) hoặc xóa cache ngay trong cùng một RUN để không lưu lại file thừa trong Image cuối cùng.
Sử dụng các công cụ security scanning và dependency analysis
Cụ thể, tôi sử dụng:
- docker scan, Trivy, hoặc Clair để kiểm tra vulnerabilities trong Image.
- Loại bỏ các packages không cần thiết nhằm giảm bề mặt tấn công và dung lượng Image.
- Thường xuyên cập nhật base image để nhận bản vá bảo mật mới nhất.
Ví dụ một Dockerfile tối ưu:
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app/myapp /app/
CMD ["./myapp"]
Hoặc ví dụ khác:
FROM node:alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM node:alpine
WORKDIR /app
COPY --from=builder /app/dist /app/dist
CMD ["node", "dist/index.js"]
Hai ví dụ này thể hiện rõ cách kết hợp multi-stage build, chọn base Image nhẹ, và chỉ copy phần cần thiết vào Image cuối cùng.
Multi-stage build là gì? Khi nào nên dùng?
Multi-stage build là một tính năng trong Docker cho phép sử dụng nhiều giai đoạn (stage) trong một Dockerfile duy nhất. Mỗi giai đoạn được khởi tạo bằng lệnh FROM mới và có thể dùng một base Image khác nhau. Do đó, chỉ copy những phần cần thiết từ các giai đoạn trước vào giai đoạn cuối cùng, nhờ vậy tạo ra Image nhỏ gọn, không bao gồm toàn bộ công cụ và phụ thuộc chỉ dùng trong build.
Tôi thường sử dụng multi-stage build trong những trường hợp sau:
- Ứng dụng biên dịch (compiled languages):
Khi cần build các ứng dụng như Go, C++, Java…, multi-stage giúp tách biệt giai đoạn có công cụ biên dịch và runtime environment, chỉ giữ lại binary cuối cùng trong Image sản xuất.
- Ứng dụng script/JS cần đóng gói:
Với Node.js, Python hay Ruby, tôi có thể build/minify code trong một stage rồi chỉ chuyển file cuối vào Image nhẹ, tránh để thư viện dev hoặc công cụ đóng gói vào production.
- Tách biệt dependencies cho development và production:
Cài đặt dev dependencies (testing tools, build tools) trong build stage và chỉ copy production artifacts sang final stage.
- Đảm bảo an toàn và giảm rủi ro:
Kỹ thuật này giúp loại bỏ các công cụ build ra khỏi Image cuối, giảm vùng tấn công (attack surface) và khả năng tồn tại lỗi bảo mật không cần thiết. Final Image không chứa source code, build tools, hoặc dữ liệu nhạy cảm.
- Dễ quản lý và tái sử dụng:
Có thể đặt tên (AS <stage-name>) cho từng giai đoạn và tái sử dụng lại nếu cần, giúp Dockerfile rõ ràng, dễ bảo trì và tránh viết nhiều Dockerfile riêng cho dev/prod.
- Build nhiều biến thể từ một Dockerfile:
Có thể target specific stages bằng –target flag, ví dụ build development Image và production Image từ cùng một Dockerfile.
Ví dụ minh hoạ:
# Giai đoạn build
FROM golang:1.24 AS build
WORKDIR /src
COPY main.go .
RUN go build -o /bin/hello
# Giai đoạn sản phẩm tối giản
FROM scratch
COPY --from=build /bin/hello /bin/hello
CMD ["/bin/hello"]
Trong ví dụ này, Docker chỉ giữ lại file nhị phân hello trong Image cuối, loại bỏ mọi thành phần build tool ban đầu.
Làm sao chia sẻ dữ liệu giữa hai Container?
Trong môi trường Docker, việc chia sẻ dữ liệu giữa các Container là một nhu cầu phổ biến khi triển khai các ứng dụng nhiều thành phần. Có hai cách chính để thực hiện điều này: sử dụng Docker Volume, Bind Mounts, tmpfs mounts hoặc liên kết thông qua một Container trung gian giữ Volume (còn gọi là data Volume Container).
- Sử dụng Docker Volume để chia sẻ dữ liệu
Docker Volume là phương pháp tiêu chuẩn và an toàn để lưu trữ dữ liệu dùng chung giữa các Container. Tôi chỉ cần tạo một Volume, sau đó mount Volume đó vào nhiều Container với cùng một đường dẫn.
Ví dụ:
docker Volume create shared-data
Sau đó, khi khởi động Container, mount Volume như sau:
docker run -v shared-data:/data --name Container1 my-Image
docker run -v shared-data:/data --name Container2 my-Image
Cả hai Container lúc này sẽ truy cập được thư mục /data có nội dung giống nhau. Đây là phương pháp được khuyến khích vì đảm bảo tính nhất quán và độc lập với hệ thống file host.
- Dùng một Container trung gian làm “Volume Container”
Một cách khác là tạo một Container chỉ để giữ Volume (gọi là data-only Container) và các Container khác sẽ sử dụng –Volumes-from để gắn kết Volume từ Container này.
Ví dụ:
docker create -v /shared-data --name data-Container busybox
docker run --Volumes-from data-Container my-Image
docker run --Volumes-from data-Container another-Image
Cách này hữu ích khi tôi muốn quản lý tập trung dữ liệu mà không cần tạo Volume riêng lẻ, tuy nhiên phương pháp này ít phổ biến hơn trong các phiên bản Docker mới, nơi Docker Volume được tối ưu hơn.
Khi nào nên sử dụng?
- Dùng Docker Volume khi tôi muốn dữ liệu được lưu trữ ổn định, dễ quản lý và chia sẻ rõ ràng giữa nhiều Container.
- Dùng Volume Container trong các hệ thống cũ hoặc cần một giải pháp đơn giản, không cần tạo Volume thủ công.
Khác biệt giữa docker exec và docker attach là gì?
Trong thực tế, cả hai lệnh đều cho phép tôi tương tác với Container đang chạy, nhưng chúng phục vụ mục đích khác nhau:
docker exec: Chạy lệnh mới trong Container đang hoạt động
Lệnh docker exec cho phép thực thi một tiến trình mới bên trong Container mà không ảnh hưởng đến tiến trình chính. Nó đặc biệt hữu ích khi muốn kiểm tra trạng thái hệ thống, sửa lỗi hoặc thực hiện các tác vụ quản trị như:
docker exec -it my_Container /bin/bash
# Hoặc chạy một lệnh cụ thể
docker exec my_Container ls -la /app
docker exec -u root my_Container apt-get update
Câu lệnh trên sẽ mở một shell mới bên trong Container my_Container, tách biệt hoàn toàn với tiến trình chính đang chạy trong Container.
Các options quan trọng:
-i
(interactive): Giữ STDIN mở-t
(tty): Cấp phát pseudo-TTY-u
(user): Chỉ định user để chạy command-w
(workdir): Chỉ định working directory-e
(env): Set environment variables
Ưu điểm:
- Không làm gián đoạn tiến trình chính của Container.
- Có thể mở nhiều session đồng thời.
- An toàn cho việc chẩn đoán hoặc vận hành hệ thống.
- Khi thoát session (exit), Container vẫn tiếp tục chạy bình thường.
docker attach: Kết nối vào tiến trình chính đang chạy
Ngược lại, docker attach sẽ kết nối trực tiếp vào tiến trình chính của Container. Điều này có nghĩa là người dùng sẽ thấy đúng output đang chạy của Container và mọi thao tác nhập vào sẽ ảnh hưởng trực tiếp tới ứng dụng chính.
Ví dụ:
docker attach my_Container
Nếu Container đang chạy một ứng dụng console (như Python, Node.js…), chúng ta sẽ tương tác trực tiếp với nó. Tuy nhiên, nếu không cẩn thận, việc đóng session có thể dừng luôn Container, đặc biệt nếu không tách rời đúng cách.
Hạn chế:
- Có thể gây ngắt tiến trình nếu thoát sai cách (ví dụ nhấn Ctrl+C).
- Không thể mở nhiều session cùng lúc an toàn như docker exec.
Bảng tóm tắt so sánh:
Đặc điểm | Docker exec | Docker attach |
Mục đích | Chạy lệnh mới trong Container | Kết nối vào tiến trình chính |
Process target | Tạo process mới (PID khác) | Kết nối tới PID 1 |
Ảnh hưởng đến Container | Không | Có thể làm dừng Container nếu thoát sai |
Nhiều session | Có | Hạn chế, không an toàn khi nhiều session |
Dùng khi nào? | Quản trị, debug, kiểm tra | Giám sát tiến trình chính |
Docker Network là gì? Có những loại nào?
Docker Network là một hệ thống mạng ảo mà Docker cung cấp để cho phép các Container kết nối và trao đổi dữ liệu với nhau, hoặc với hệ thống bên ngoài. Thay vì để từng Container phải thiết lập mạng riêng biệt, Docker tạo ra các mạng logic để Container có thể “giao tiếp” mà không cần biết cụ thể địa chỉ IP của nhau.
Mỗi khi chạy một Container, Docker sẽ tự động gán nó vào một mạng mặc định, hoặc có thể chỉ định mạng cụ thể theo nhu cầu. Docker cung cấp built-in DNS resolution, cho phép Containers tìm thấy nhau bằng tên thay vì IP address.
Docker hỗ trợ nhiều kiểu mạng khác nhau, phù hợp với các kịch bản sử dụng khác nhau. Dưới đây là các loại phổ biến:
Loại mạng | Mô tả | Khi sử dụng | Cú pháp |
Bridge | Mạng mặc định của Docker. Container trong cùng bridge network có thể giao tiếp với nhau, nhưng tách biệt với network khác. | Ứng dụng độc lập, chạy trên một máy chủ duy nhất. | docker network create –driver bridge my-bridge-network |
Host | Container chia sẻ stack mạng với máy chủ, không cần port mapping. | Cần hiệu năng cao hoặc tránh NAT. | docker run –network host nginx |
Overlay | Kết nối Container trên nhiều máy Docker khác nhau, sử dụng VXLAN tạo tunnels. | Hệ thống microservices phân tán, Docker Swarm. | docker network create –driver overlay my-overlay-network |
None | Container không kết nối mạng, chỉ có loopback interface (localhost). | Muốn kiểm soát hoàn toàn cấu hình mạng. | docker run –network none alpine |
Macvlan | Gán địa chỉ MAC và IP trực tiếp từ mạng vật lý cho Container, giúp nó như một máy độc lập trong LAN. | Container cần tương tác trực tiếp với mạng nội bộ như một máy riêng biệt. | docker network create -d macvlan … (cần config thêm) |
Làm sao để gắn log Container vào hệ thống log bên ngoài?
Docker cho phép Container ghi log thông qua logging drivers – một cơ chế linh hoạt để gửi log tới các đích khác nhau. Bằng cách cấu hình logging driver phù hợp, chúng ta có thể chuyển log Container ra hệ thống log tập trung như syslog, Fluentd, hoặc log file để bên ngoài đọc và xử lý tiếp.
Từ phiên bản Docker 20.10 trở đi, Docker hỗ trợ dual logging – tức là cho phép một Container cùng lúc sử dụng 2 driver log. Điều này rất hữu ích nếu tôi muốn:
- Vẫn theo dõi log trực tiếp qua docker logs
- Đồng thời gửi log đến dịch vụ log như Fluentd hoặc Logstash
Tôi cấu hình ngay trong lệnh docker run:
docker run \
--log-driver=local \
--log-driver=syslog \
--log-opt syslog-address=tcp://192.168.1.100:514 \
my-app
Không phải phiên bản Docker nào cũng hỗ trợ dual logging, do đó tôi luôn đảm bảo đang dùng bản mới nhất và đã bật tính năng này trong cấu hình daemon.
Khi kết nối log Container với hệ thống ngoài, tôi thêm các tùy chọn log như:
- Địa chỉ log server (syslog-address, fluentd-address…)
- Gán tag cho Container để dễ phân loại log
- Định dạng log JSON hoặc plaintext để phù hợp với hệ thống phân tích
Ví dụ với Fluentd:
docker run \
--log-driver=fluentd \
--log-opt tag="app.myapp" \
--log-opt fluentd-address=localhost:24224 \
my-app
Cách xử lý khi Image build bị lỗi không rõ nguyên nhân?
Đây là một câu hỏi phỏng vấn Docker nhằm kiểm tra khả năng xử lý sự cố và tư duy debug của ứng viên.
Dưới đây là những cách tiếp cận hiệu quả khi build Image bị lỗi nhưng log không nói rõ nguyên nhân.
Thêm log chi tiết bằng --progress=plain
và --no-cache
Khi gặp lỗi mơ hồ, điều đầu tiên nên làm là xây dựng lại Image từ đầu và ép Docker hiển thị đầy đủ thông tin: DOCKER_BUILDKIT=1 docker build –progress=plain –no-cache -t my-Image .
Trong đó:
--progress=plain
: hiển thị chi tiết các bước đang chạy--no-cache
: tránh dùng cache để phát hiện lỗi chính xác hơn- Tách nhỏ các lệnh trong Dockerfile
Một trong những nguyên nhân khiến lỗi build trở nên khó xác định là do viết quá nhiều lệnh trong một RUN. Thay vì:
RUN apt update && apt install -y curl && curl http://example.com/install.sh | bash
Hãy tách thành:
RUN apt update
RUN apt install -y curl
RUN curl http://example.com/install.sh | bash
Việc chia nhỏ giúp bạn biết chính xác bước nào gây lỗi và dễ tái kiểm tra hơn.
Kiểm tra kỹ môi trường build
Nhiều lỗi build xảy ra không phải vì Dockerfile sai, mà vì môi trường hệ thống có vấn đề như:
- Thiếu kết nối mạng
- DNS lỗi (thử đổi sang 8.8.8.8)
- Thiếu tài nguyên (RAM, ổ cứng)
- Phiên bản Docker không tương thích
Tôi thường kiểm tra bằng cách build trên một môi trường khác (ví dụ: môi trường CI/CD, server mới…) để so sánh.
In debug hoặc chạy shell trung gian
Một mẹo hay để debug là chèn các lệnh in trạng thái hoặc tạm dừng giữa quá trình build, ví dụ:
RUN echo "Tới đây vẫn ổn" && sleep 10
Hoặc bạn có thể build một Image trung gian, sau đó chạy shell bên trong để kiểm tra từng bước:
docker run -it --entrypoint /bin/sh Image-trung-gian
Bám sát best practices của Docker
- Dùng Docker BuildKit để tăng tốc và debug tốt hơn
- Viết Dockerfile có cấu trúc rõ ràng, hạn chế COPY hay RUN quá nhiều file/lệnh không cần thiết
- Không hard-code giá trị môi trường, thay vào đó dùng biến ENV hoặc ARG
Docker Compose khác gì so với Swarm?
Docker Compose được thiết kế để giúp các developer dễ dàng cấu hình và khởi chạy nhiều Container liên kết với nhau chỉ với một file YAML duy nhất (docker-Compose.yml). Đây là giải pháp lý tưởng cho:
- Môi trường local hoặc staging
- Phát triển ứng dụng có nhiều service (web, database, redis…)
- Dễ quản lý nhờ file cấu hình đơn giản
- Không yêu cầu cluster hay phân tán
- Single-host deployments
Ví dụ, tôi có thể chạy toàn bộ hệ thống chỉ bằng một lệnh: docker-Compose up
Docker Swarm là công cụ orchestration (điều phối) của Docker, cho phép triển khai các Container trên nhiều node (máy chủ) trong một cụm phân tán (cluster). Docker Swarm mode được tích hợp sẵn trong Docker Engine từ version 1.12. Đây là giải pháp phù hợp khi cần:
- Triển khai trên môi trường production có quy mô lớn
- Đảm bảo high availability (sẵn sàng cao) và load balancing
- Tự động scale (mở rộng hoặc thu hẹp số lượng Container)
- Quản lý trạng thái Container (recovery khi có node lỗi)
- Service discovery và internal load balancing
- Cập nhật cuốn chiếu với zero downtime
Swarm sử dụng các lệnh như docker swarm init, docker service create, docker node ls… để quản lý các tài nguyên trong cụm.
Bảng so sánh Docker Compose và Swarm:
Tiêu chí | Docker Compose | Docker Swarm |
Mục đích chính | Phát triển local | Triển khai phân tán (cluster) |
Quy mô triển khai | Một máy | Nhiều máy (multi-host) |
Tính sẵn sàng cao (HA) | Không hỗ trợ | Có tích hợp |
Load balancing | External load balancer cần thiết | Built-in load balancing |
Khả năng mở rộng tự động | Không | Có |
Quản lý trạng thái Container | Không | Có giám sát và khôi phục |
Độ phức tạp cấu hình | Thấp | Trung bình – cao |
Khi nào nên chọn Compose, khi nào dùng Swarm?
- Dùng Docker Compose khi bạn làm việc với một nhóm nhỏ, đang phát triển ứng dụng hoặc test hệ thống trên một máy.
- Dùng Docker Swarm khi hệ thống cần triển khai thực tế trên nhiều node, yêu cầu khả năng phục hồi, mở rộng và phân phối tải.
Làm sao để gỡ Container chiếm port 80?
Trong quá trình làm việc với Docker, việc bị chiếm mất port 80 – cổng mặc định cho các ứng dụng web – là lỗi phổ biến gây ra xung đột khi khởi chạy Container mới. Để xử lý tình huống này hiệu quả, cần biết cách xác định Container đang chiếm port 80 và gỡ bỏ hoặc dừng nó một cách an toàn. Đây là cách tôi thường làm:
Bước 1: Xác định Container đang sử dụng port 80
Trước tiên, tôi kiểm tra Container nào đang chiếm port 80 bằng cách sử dụng lệnh:
docker ps
Tìm trong cột PORTS dòng nào có 0.0.0.0:80->… hoặc :::80->…, ví dụ: 0.0.0.0:80->80/tcp
Lưu lại Container ID hoặc tên Container trong dòng đó để xử lý ở bước tiếp theo.
Bước 2: Dừng Container đang chiếm port
Sau khi xác định được Container, tôi dừng nó bằng lệnh:
docker stop <Container_id hoặc Container_name>
Ví dụ:
docker stop web_server
Lệnh này sẽ giải phóng port 80, cho phép chạy Container mới mà không bị xung đột.
Bước 3 (tuỳ chọn): Xoá Container nếu không còn sử dụng
Nếu tôi chắc chắn không cần Container đó nữa, có thể xoá nó hoàn toàn bằng lệnh:
docker rm <Container_id hoặc Container_name>
Để kết hợp cả hai bước (dừng và xoá), tôi có thể dùng một lệnh duy nhất:
docker rm -f <Container_id hoặc Container_name>
Một số kinh nghiệm của tôi:
- Không nên xoá Container nếu đang dùng trong production, cần kiểm tra kỹ trước khi thao tác.
- Nếu Container khởi động lại tự động sau khi dừng, có thể nó được thiết lập với restart policy, cần xoá hoặc thay đổi thiết lập đó.
- Trong một số trường hợp, dịch vụ khác không thuộc Docker (như Apache/Nginx cài sẵn) cũng có thể chiếm port 80. Khi đó nên dùng: sudo lsof -i :80 để xác định chính xác tiến trình (process) nào đang chiếm dụng port.
Docker Compose v2 và v3 khác nhau điểm gì?
Sự khác biệt chủ yếu nằm ở mục tiêu sử dụng và tính năng hỗ trợ:
Docker Compose v2 được thiết kế cho môi trường local hoặc single-host. Nó hỗ trợ:
- Mạng lưới riêng (bridge): Sử dụng bridge để kết nối các Container và có tên dịch vụ làm hostname.
- Named Volumes: Hỗ trợ khai báo Volume dễ dàng thông qua file YAML.
- Không hỗ trợ Swarm: Đây là phiên bản chủ yếu dùng cho môi trường phát triển hoặc hệ thống một máy chủ.
- Giá trị nâng cao khác: Tích hợp depends_on, cấu hình môi trường cpu_shares, mem_limit
- Hỗ trợ đa loại mạng và build context dễ dàng với các phiên bản như 2.3, 2.4. bridge network, named volumes, và các cấu hình tài nguyên (CPU, RAM) ngay trong service.
Phiên bản này phù hợp cho phát triển và thử nghiệm hơn là production phân tán.
Docker Compose v3 hướng đến triển khai production phân tán trên Docker Swarm với các tính năng như:
- Tương thích với Swarm mode: Thêm phần deploy để mô tả replica, scaling, cấu hình cập nhật, phù hợp với triển khai production phân tán.
- Giảm một số cấu hình V2: Loại bỏ các thuộc tính như Volumes_from, cpu_shares, extends,… Đây là bước hướng đến tiêu chuẩn hóa khâu deploy.
- Phân biệt rõ giữa phát triển và deploy: Các cấu hình tài nguyên (CPU, mem) được chuyển vào mục deploy, chỉ có hiệu lực trong swarm. Khi dùng docker-Compose, phần này sẽ bị bỏ qua
- Docker Documentation.
- Hỗ trợ mạng overlay và stacks: Phù hợp khi bạn cần triển khai hệ thống microservices vừa chạy trên nhiều node, vừa cần cân bằng tải và high availability.
Bảng so sánh Docker Compose v2 và Docker Compose v3:
Tiêu chí | Docker Compose v2 | Docker Compose v3 |
Mục tiêu chính | Local hoặc single-host development | Production, clustering với Docker Swarm |
Hỗ trợ Volumes_from | Có | Bỏ |
Cấu hình tài nguyên | Ngay trong dịch vụ (cpu, mem…) | Trong deploy chỉ dành cho swarm |
Networking | Bridge mặc định, dễ cấu hình | Overlay và stack phù hợp với phân tán |
Cấu hình mở rộng | Giản tiện cho trường hợp đơn giản | Đầy đủ cho production |
Câu hỏi phỏng vấn Docker nâng cao (Advanced Level)
Hãy chia sẻ kinh nghiệm của bạn khi sử dụng Docker Swarm hoặc Kubernetes để điều phối các Container Docker. Ưu và nhược điểm của từng giải pháp là gì, và khi nào bạn nên chọn giải pháp này thay vì giải pháp kia?
Trong thực tế, Docker Swarm và Kubernetes đều là những hệ thống orchestration phổ biến, mỗi nền tảng hướng tới từng mục tiêu sử dụng khác nhau.
Docker Swarm – Tinh giản & Nhanh chóng
Ưu điểm:
- Dễ triển khai và kết hợp hoàn hảo với Docker CLI và Docker Compose, hỗ trợ nhanh các kịch bản phát triển hoặc môi trường test nhỏ.
- Gọn nhẹ, ít overhead, phù hợp khi bạn muốn khởi tạo cluster đơn giản và dễ quản lý.
- Khả năng scale cơ bản, cập nhật cuốn chiếu và triển khai với cú pháp Docker quen thuộc.
- Tích hợp sẵn vào Docker Engine, không cần cài đặt thêm components
- Service discovery và load balancing tự động
- Quản lý secrets và configs đơn giản
Nhược điểm:
- Tính năng hạn chế, đặc biệt thiếu nhiều hỗ trợ cho môi trường phức tạp như autoscaling, network policy, hay khả năng mở rộng quy mô.
- Môi trường hỗ trợ cộng đồng nhỏ, thiếu nhiều công cụ mở rộng so với Kubernetes.
Kinh nghiệm thực tế: Tôi đánh giá Swarm rất phù hợp cho ứng dụng nhỏ hoặc các hệ thống nhỏ lẻ, tuy nhiên Docker đã ngừng active development cho Swarm mode kể từ năm 2019.
Kubernetes – Toàn diện & Mạnh mẽ
Ưu điểm:
- Hỗ trợ tự động scale, tự phục hồi, cấu hình rollout/rollback, quản lý trạng thái cluster rất tốt
- Mạng và lưu trữ đa dạng, từ CNI plugins, DNS service, cho tới PersistentVolumes phục vụ ứng dụng stateful
- Hệ sinh thái mạnh mẽ, cộng đồng đông đảo, hàng tá công cụ tích hợp và dịch vụ cloud-managed (GKE, EKS, v.v.)
- RBAC phong phú và mô hình bảo mật
- Hỗ trợ các workload phức tạp: scheduling nâng cao với node affinity, taints, tolerations; StatefulSets cho ứng dụng stateful; Custom Resource Definitions (CRDs) cho khả năng mở rộng
- Hệ sinh thái giám sát và logging toàn diện
Nhược điểm:
- Khó setup, quản lý và học tập, đòi hỏi nhiều kiến thức về cluster và DeVops
- Chi phí vận hành cao hơn, cần đội ngũ hạ tầng hỗ trợ vận hành production
- Overhead phức tạp cho ứng dụng nhỏ
- Ngốn tài nguyên (nhất là cho control plane)
- Mất nhiều thời gian để thành thạo
Kinh nghiệm thực tế: Tôi bắt đầu với Swarm vì đơn giản, nhưng khi hệ thống phát triển, tôi đã chuyển sang Kubernetes để hưởng lợi từ tính tự động và khả năng mở rộng.
Khi nên chọn Docker Swarm, khi nào chọn Kubernetes?
Chọn Docker Swarm khi:
- Cần khởi động cluster nhanh, setup đơn giản.
- Dự án nhỏ, không cần autoscaling hay orchestrator phức tạp.
- Đội ngũ không có nhiều kinh nghiệm Kubernetes và muốn tập trung vào phát triển.
Chọn Kubernetes khi:
- Hệ thống cần mở rộng, có cấu trúc microservices phức tạp, cần tích hợp CI/CD, tự động scale và phục hồi.
- Mong muốn tận dụng hệ sinh thái tools, RBAC, Network policy và storage mạnh mẽ.
- Có sẵn đội ngũ có khả năng vận hành cluster phức tạp.
Một số phương pháp hay nhất để viết Dockerfile là gì?
Khi viết Dockerfile, tôi luôn áp dụng một số best practices để giữ Image nhẹ, bảo mật, và dễ duy trì. Cụ thể:
Chọn base Image chính thức, nhẹ và phù hợp
Khởi đầu Dockerfile bằng một base Image nguồn gốc rõ ràng (như từ Docker Hub official) và ưu tiên phiên bản nhẹ như alpine, -slim, hoặc distroless. Điều này không chỉ giảm kích thước Image mà còn bớt rủi ro bảo mật từ thư viện thừa.
Áp dụng multi-stage builds để sạch và nhỏ gọn
Sử dụng multi-stage build để tách giai đoạn build đầy đủ công cụ (compiler, dependencies…) khỏi Image cuối cùng. Chỉ copy những thành phần thực sự cần thiết vào Image deploy. Điều này giúp Image nhẹ và sạch hơn.
Giảm số layers bằng cách kết hợp các lệnh
Mỗi instruction như RUN
, COPY
, ADD
tạo một layer riêng. Gộp nhiều lệnh với && trong một RUN, sử dụng WORKDIR thay vì mkdir/cd. Điều này giúp giảm số lớp, tăng tốc build và giảm kích thước ảnh.
Tận dụng caching và thứ tự diễn giải Dockerfile
Sắp xếp các lệnh theo thứ tự thay đổi ít → thay đổi nhiều (ví dụ: install dependencies trước, copy source sau) để Docker tận dụng cache tối ưu khi rebuild, giúp tiết kiệm thời gian.
Dùng file .dockerignore để loại trừ nội dung không cần
Tạo .dockerignore để tránh copy các file không cần thiết (như node_modules, .git, logs, file tạm…) vào build context. Việc này giúp giảm kích thước context và tăng tốc build.
Loại bỏ file tạm và cache trong cùng một layer
Sau khi cài đặt gói hoặc dependencies, clean cache ngay trong cùng câu lệnh RUN để tránh lưu lại file tạm trong layer hiện tại.
Không chạy Container dưới quyền root
Nếu các Container mặc định chạy với quyền root sẽ tiềm ẩn rủi ro bảo mật. Tôi thường tạo người dùng không phải root và chuyển sang quyền đó bằng USER trong Dockerfile.
Sử dụng tags cố định và scan bảo mật thường xuyên
Tôi không dùng tag latest, mài thường chỉ định rõ tag/version (ví dụ nginx:1.23.1) để có khả năng truy vết Image, đồng thời tích hợp quét lỗ hổng (Docker Scan, Trivy…) trong CI/CD để hạn chế rủi ro bảo mật.
Ghi chú, tổ chức và tái sử dụng các stage
- Thêm các ghi chú (comment) ngay trong Dockerfile để giúp người khác hiểu logic nhanh hơn.
- Nếu có phần logic “common” giữa nhiều Image, dùng reusable stage để tránh lặp lại (AS builder).
Tối ưu hiệu suất build qua layering, base Image phù hợp, và caching
Việc chọn đúng base Image, sắp xếp layer hợp lý, tận dụng cache và sử dụng multi-stage builds có thể giảm thời gian build và kích thước Image.
Tóm tắt nhanh:
Mục tiêu | Best Practice |
Giảm kích thước Image | Chọn base Image nhẹ, dùng multi-stage, xóa cache |
Tăng tốc độ build | Tối ưu thứ tự lệnh, tận dụng Docker cache |
Bảo mật và minh bạch | Dùng user không root, tag cụ thể, scan định kỳ |
Dễ duy trì & debug | Viết rõ ràng, dùng .dockerignore, tổ chức stage |
Làm thế nào để giới hạn CPU và RAM cho một Container?
Tôi thường giới hạn tài nguyên cho Container bằng các tham số CPU và bộ nhớ ngay khi chạy docker run hoặc trong Docker Compose. Cách tiếp cận như sau:
Giới hạn CPU:
--cpus
: Giới hạn số lượng CPU (có thể dùng phần thập phân). Ví dụ,--cpus=2
cho phép Container dùng tối đa 2 lõi CPU.--cpuset-cpus
: Ràng buộc Container chỉ được phép sử dụng một hoặc một nhóm lõi nhất định, chẳng hạn “0-2” hay “1,3”.--cpu-shares
: Đặt ưu tiên CPU tương đối khi có nhiều Container cạnh tranh tài nguyên (mặc định là 1024; số cao hơn sẽ được ưu tiên hơn).--cpu-period
và--cpu-quota
: Cho phép kiểm soát chi tiết hơn bằng thời gian chế độ CFS. Ví dụ, để giới hạn Container sử dụng 50% CPU, bạn có thể thiết lập--cpu-period=100000 --cpu-quota=50000
.
Giới hạn bộ nhớ RAM
--memory
(-m
): Giới hạn chặt chẽ về lượng RAM Container có thể sử dụng (ví dụ: “512m”). Đây là hard limit được áp dụng bởi hệ thống.--memory-swap
: Quy định tổng bộ nhớ (RAM + swap) mà Container có thể sử dụng. Nếu bạn không muốn sử dụng swap, hãy đặt--memory-swap
trùng với--memory
.--memory-reservation
: Soft limit, sẽ được kích hoạt khi hệ thống có sự cạnh tranh tài nguyên; nó thấp hơn--memory
và chỉ là giới hạn “mềm”.--kernel-memory
: Giới hạn bộ nhớ kernel. Cần dùng cẩn trọng vì nếu thiếu sẽ ảnh hưởng đến hoạt động của Container và host.--oom-kill-disable
: Vô hiệu hóa OOM killer cho Container (chỉ nên dùng khi đã set memory limit).--memory-swappiness
: Kiểm soát mức độ sử dụng swap (giá trị từ 0-100).
Sau khi thiết lập giới hạn, tôi kiểm tra hiệu quả bằng:
docker stats
Lệnh này hiển thị CPU% RAM sử dụng/giới hạn cũng như I/O—giúp tôi xác nhận xem các tùy chọn đã được áp dụng đúng hay chưa.
Ví dụ minh hoạ:
docker run -d \
--cpus="1.5" \
--memory="512m" \
--memory-swap="1g" \
nginx
Lệnh này sẽ khởi tạo một Container Nginx, giới hạn sử dụng tối đa 1.5 CPU và 512 MB RAM, với tổng bộ nhớ RAM + swap không vượt quá 1 GB.
Hoặc nếu dùng Docker Compose (phiên bản 3+):
services:
app:
Image: myapp
cpus: "0.5"
mem_limit: 512m
mem_reservation: 256m
Làm sao bảo mật Image Docker trước khi deploy?
Để đảm bảo Image Docker an toàn trước khi đưa vào môi trường production, tôi thường cần triển khai một loạt biện pháp bảo mật xuyên suốt vòng đời của Image. Dưới đây là những bước hiệu quả nhất:
Chọn Image nguồn đáng tin cậy và gọn nhẹ
- Ưu tiên sử dụng các Image chính thức từ Docker Hub hoặc nhà cung cấp được xác minh. Những Image này thường được cập nhật thường xuyên và kiểm thử nghiêm ngặt.
- Sử dụng các Image base tối giản như Alpine hoặc distroless giúp giảm bề mặt tấn công đáng kể.
- Sử dụng multi-stage builds để giảm kích thước Image và loại bỏ các build dependencies không cần thiết.
Quét lỗ hổng bảo mật định kỳ
- Tích hợp công cụ như Trivy, Grype hoặc Docker Scan vào pipeline CI/CD để quét Image và phát hiện các lỗ hổng bảo mật ngay trong giai đoạn build.
- Quét thường xuyên và cập nhật ngay khi có bản vá bảo mật mới.
Chạy Container với quyền hạn tối thiểu
- Không chạy Container dưới quyền root; tạo người dùng không phải root bằng chỉ thị USER trong Dockerfile để giảm thiểu nguy cơ leo thang quyền hạn.
- Giảm các Linux capabilities không cần thiết bằng cách sử dụng –cap-drop ALL và chỉ thêm những gì cần thiết.
Áp dụng cơ chế bảo vệ runtime
- Cho phép tuỳ chọn –read-only để áp dụng chế độ chỉ đọc cho hệ thống file; chỉ mount những Volume cần ghi (như logs) ra ngoài.
- Sử dụng tùy chọn –security-opt no-new-privileges:true để ngăn Container không leo thang quyền.
Quản lý secrets an toàn
- Tuyệt đối không nhúng mật khẩu, API keys vào Dockerfile hoặc Image. Thay vào đó, dùng Docker Secrets, Vault, hoặc AWS Secrets Manager để quản lý và inject secrets vào Container khi chạy.
- Sử dụng .dockerignore để tránh copy các file nhạy cảm vào Image.
- Tránh sử dụng biến môi trường cho dữ liệu nhạy cảm – thay vào đó dùng secrets mounting.
Chứng thực và kiểm tra nguồn gốc Image
- Bật Docker Content Trust (DCT) để đảm bảo Image chỉ được pull từ nguồn tin cậy có signature hợp lệ.
- Sử dụng SBOM (Software Bill of Materials) và ký mã để tăng mức độ minh bạch và kiểm chứng tính toàn vẹn của Image.
Gia cố bảo mật host
- Không để Docker daemon socket (/var/run/docker.sock) dễ truy cập – chỉ cho phép người dùng cần thiết mới được truy cập.
- Bảo trì host, cập nhật kernel và Docker engine định kỳ; và có thể chạy Docker ở chế độ rootless để nâng cao an toàn.
Giám sát và audit
- Sử dụng Docker Bench for Security để kiểm tra cấu hình hệ thống Docker và host nhằm đảm bảo tuân thủ best practices.
- Theo dõi runtime Container bằng các công cụ như Falco hoặc sysdig để phát hiện bất thường.
Docker Content Trust là gì?
Docker Content Trust (DCT) là một cơ chế bảo mật được tích hợp trong Docker, cho phép sử dụng chữ ký số để xác thực nguồn gốc và tính toàn vẹn của Image khi thực hiện thao tác pull hoặc push với Docker registry. Điều này giúp đảm bảo rằng Image được sử dụng thực sự đến từ người phát hành đáng tin cậy, chưa bị giả mạo hoặc thay đổi không mong muốn.
Cách hoạt động của Docker Content Trust
- Người phát hành (publisher) có thể ký Image với các khóa kỹ thuật số trước khi push lên registry.
- Khi Docker Content Trust được bật, các thao tác như docker pull, run, hoặc push sẽ chỉ cho phép các Image được ký hợp lệ, giúp ngăn chặn pull, run hoặc build các Image không đáng tin cậy.
- Có thể kích hoạt DCT bằng cách đặt biến môi trường trong shell:
export DOCKER_CONTENT_TRUST=1
Khi bật, Docker client chỉ hiển thị và tương tác với các Image đã được ký; các Image không có chữ ký sẽ bị ẩn hoặc từ chối
DCT sử dụng một hệ thống khoá khóa (keys) để quản lý việc ký và xác thực:
- Root key: khóa gốc giữ vai trò kiểm soát cao nhất, thường được bảo mật ngoại tuyến.
- Targets (repository) key: dùng để ký Image tag.
- Snapshot & Timestamp keys: đảm bảo tính không thay đổi và thời gian xác thực.
- Delegation keys: cho phép ủy quyền ký cho các thành phần hoặc cá nhân khác, giúp quản lý phân quyền linh hoạt.
DCT được xây dựng dựa trên Notary project, cung cấp framework bảo mật cho việc xuất bản và xác thực content.
Khi nào nên dùng Docker Content Trust?
- Khi tôi muốn đảm bảo chỉ triển khai Image từ nguồn an toàn và được xác thực, rất quan trọng trong môi trường sản xuất hoặc CI/CD.
- Khi tổ chức cần kiểm soát chặt chẽ và đảm bảo không sử dụng Image đã bị thay đổi hoặc đăng bởi nguồn không xác thực.
Làm sao kiểm tra lỗ hổng bảo mật trong Image?
Việc quét lỗ hổng (vulnerabilities) trước khi đưa Docker Image vào môi trường sản xuất là một thực hành không thể bỏ qua để bảo vệ hệ thống an toàn.
Các cách thường dùng:
Sử dụng bộ quét tích hợp trong Container Registry
Nếu tôi dùng một registry hỗ trợ khả năng quét tự động (như Oracle Cloud Infrastructure Registry, Harbor, Amazon ECR, Google Container Registry), tôi chỉ cần kích hoạt tính năng Image Scanning cho repository.
Sau đó, bất cứ lần nào tôi docker push, Image đó sẽ được quét ngay để tìm lỗ hổng dựa trên các dữ liệu CVE. Những scan này có thể tự động chạy lại khi có dữ liệu CVE mới, và tôi có thể xem báo cáo chi tiết về mức độ rủi ro và lịch sử scan trong thời gian dài.
Dùng công cụ quét lỗ hổng độc lập như Trivy, Clair, Anchore, Grype…
Có nhiều công cụ mạnh mẽ để scan Image ngay từ local trước khi đưa đến registry:
- Trivy: Nhỏ nhẹ, dễ sử dụng và tích hợp CI/CD nhanh chóng; phát hiện lỗ hổng ở cả hệ điều hành và dependencies ứng dụng.
- Clair: Quét statically từ Image và cho kết quả chi tiết theo từng layer, thường tích hợp cùng các registry (ví dụ Harbor).
- Anchore Engine hoặc Grype: Cho phép thêm chính sách bảo mật tùy chỉnh, phù hợp với quy trình tự động hóa và kiểm soát nội bộ.
Tích hợp quét vào pipeline CI/CD
Một cách hay là đưa bước quét lỗ hổng vào quy trình CI/CD. Ví dụ như với GitLab CI, tôi có thể lưu hình ảnh, đẩy lên registry, và tự động quét trước khi Deploy.
Ngoài ra, Docker Hub cũng cung cấp khả năng static scanning (như với Docker Scout), để phát hiện lỗ hổng khi tôi push Image vào repository, tôi chỉ cần bật tính năng này trong cài đặt repository.
Tóm tắt các phương pháp chính:
Phương pháp | Mô tả |
Registry có scan tích hợp | Kích hoạt Image Scanning để tự động quét sau push |
Công cụ quét offline (Trivy, Clair…) | Dùng trực tiếp trên local hoặc như một bước trong CI |
CI/CD tự động | Tích hợp quét vào pipeline để chặn Image không an toàn |
Docker Scout | Công cụ scanning mới từ Docker với gợi ý bằng AI |
Quét khi push lên Docker Hub | Dùng tính năng static scanning hoặc Docker Scout |
Mô tả cách triển khai CI/CD với Docker + GitLab
Tôi sẽ kết hợp Docker cùng GitLab CI/CD để tự động hóa toàn bộ quy trình từ khi mã nguồn thay đổi đến khi đưa ứng dụng lên môi trường đích. Điểm nổi bật là sử dụng Docker-in-Docker (DinD), cho phép pipeline xây dựng Image Docker ngay bên trong môi trường runner.
Bước 1: Chuẩn bị môi trường
- Sử dụng GitLab Runner với Docker executor là điều kiện cần để chạy các job Docker.
- Trong file .gitlab-ci.yml, tôi định nghĩa stages như build, test, security và deploy.
Bước 2: Cấu hình .gitlab-ci.yml để xây dựng và push Image
Một ví dụ config mạch lạc trong GitLab:
stages:
- build
- security
- deploy
variables:
DOCKER_Image: $CI_REGISTRY_Image:$CI_COMMIT_SHA
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
build:
Image: docker:24.0.7
stage: build
services:
- docker:24.0.7-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $DOCKER_Image .
- docker tag $DOCKER_Image $CI_REGISTRY_Image:latest
- docker push $DOCKER_Image
- docker push $CI_REGISTRY_Image:latest
security_scan:
Image: aquasec/trivy:latest
stage: security
script:
- trivy Image --exit-code 1 --severity HIGH,CRITICAL $DOCKER_Image
allow_failure: false
deploy:
Image: docker:24.0.7
stage: deploy
script:
- docker run -d --name myapp -p 80:80 $DOCKER_Image
only:
- main
Giải thích chi tiết:
- Sử dụng Image Docker CLI (docker:24.0.7 – phiên bản stable).
- Khai báo service docker:24.0.7-dind để chạy Docker daemon bên trong job.
- Thêm stage security để quét lỗ hổng bảo mật.
- Thêm stage deploy để triển khai ứng dụng.
Bước 3: Quản lý tags và version
Sử dụng biến $CI_COMMIT_SHA
để đánh tag Image theo commit, giúp truy vết chính xác Image ứng với mỗi lần build.
Push thêm tag latest
cho phiên bản mới nhất.
Có thể sử dụng semantic versioning với GitLab tags:
$CI_REGISTRY_Image:$CI_COMMIT_TAG.
Bước 4: Những best practices cần lưu ý
Hạn chế dùng Docker-in-Docker ở chế độ privileged: Vì có thể mở các lỗ hổng bảo mật, nên cân nhắc sử dụng Docker rootless hoặc các giải pháp như BuildKit nếu phù hợp.
Tối ưu hiệu năng và bảo mật pipeline:
- Chạy các job song song để giảm thời gian chờ.
- Cấu hình caching hợp lý để tăng tốc quá trình xây dựng.
- Sử dụng Docker layer caching và GitLab CI cache.
- Quản lý secret với GitLab CI/CD variables.
- Thiết lập triển khai theo từng môi trường (staging, production)
Bạn sẽ xử lý thế nào khi Container crash sau khi restart liên tục?
Khi gặp tình huống Container crash lặp lại, tôi xử lý theo các bước sau:
Bước 1: Kiểm tra log để xác định nguyên nhân:
Trước hết, tôi cần xem log lỗi của Container để biết ứng dụng đang crash vì lý do gì (lỗi cấu hình, thiếu file, sai command,…) bằng lệnh:
docker logs <Container_id>
Nếu Container restart nhanh, tôi có thể xem log từ phiên bản trước đó bằng lệnh:
docker logs --since 5s <Container_id>
Bước 2: Kiểm tra exit code để hiểu nguyên nhân crash:
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.ExitCode}}
Exit code 0: Thành công
Exit code 1: Lỗi chung
Exit code 125: Docker daemon error
Exit code 126: Container command không thể thực thi
Exit code 127: Container command không tìm thấy
Exit code 137: Container bị kill (SIGKILL) – thường do OOM
Bước 3: Kiểm tra chính sách restart
Docker cho phép nhiều chế độ restart (--restart flag
), có thể là always
, on-failure
, unless-stopped
,…
Nếu dùng always, Container sẽ liên tục khởi động lại ngay cả khi lỗi không nghiêm trọng hay cấu hình sai. Việc kiểm tra và điều chỉnh lại policy (ví dụ chuyển sang on-failure) để ngăn restart không phục vụ troubleshooting là cần thiết.
Bước 4: Chạy Container ở chế độ foreground (tty interactive) để debug
Trong một số trường hợp, Container vẫn ngắt vì tiến trình chính kết thúc ngay, như dùng script rồi exit mà không giữ shell mở.
Giải pháp là bật tty để giữ Container sống tạm thời: tty: true
Điều này giúp vào inspect Container bằng docker attach và quan sát ứng dụng hoạt động ra sao.
Bước 5: Cải thiện ENTRYPOINT/CMD để xử lý tín hiệu đúng
Có thể việc Container crash là do xử lý STOP/TERM không đúng. Code hoặc script trong Container có thể không bắt tín hiệu dừng, dẫn đến việc bị kill đột ngột (exit code 137) và các restart policy lại kick in.
Giải pháp là:
- Sử dụng công cụ như tini hoặc xử lý tín hiệu trong shell script để dừng Container đúng cách.
- Thiết lập
--stop-timeout
dài hơn để Container có thời gian cleanup trước khi bị kill. - Xây dựng lại Container nếu nghi ngờ bị lỗi build hoặc metadata
Có trường hợp Container bị hỏng (corrupted) do build lỗi hoặc metadata Docker lưu nhầm. Khi đó, cách tốt nhất là:
- Xóa Container cũ.
- Build lại Image mới và tạo Container mới.
- Có thể sử dụng docker system prune để dọn metadata rác.
Làm thế nào để cập nhật Image mà không gây downtime?
Để triển khai bản cập nhật mà không làm gián đoạn dịch vụ, tôi áp dụng một số chiến lược hiệu quả như rolling update, blue‑green deployment hay sử dụng công cụ hỗ trợ như Docker Compose hoặc Docker Swarm/Kubernetes. Dưới đây là cách thực hiện:
Rolling Update với Docker Compose (version 3+)
Trong file docker-Compose.yml, tôi có thể cấu hình triển khai theo lớp, đảm bảo rằng Container mới chỉ được khởi động khi Container cũ đã hoạt động ổn.
Ví dụ:
version: '3.8'
services:
web:
Image: myapp:latest
deploy:
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
ports:
- "80:80"
Trong đó:
- parallelism: 1: chỉ cập nhật từng Container một.
- delay: 10s: chờ một khoảng thời gian giữa các bước để tránh xung đột.
Blue-Green Deployment
Xây dựng hai phiên bản môi trường: Blue (phiên bản hiện tại) và Green (phiên bản mới). Cách làm như sau:
- Triển khai Image mới vào môi trường Green.
- Kiểm tra phiên bản Green hoạt động ổn định.
- Chuyển lưu lượng traffic từ Blue sang Green (có thể qua reverse proxy như Nginx hoặc cân bằng tải).
- Khi Green đã hoạt động ổn định, gỡ bỏ hoặc tạm ngừng phiên bản Blue.
Điều này đảm bảo quá trình cập nhật diễn ra suôn sẻ, không làm gián đoạn dịch vụ.
Canary Deployment (Triển khai dần)
Đây là cách tiếp cận chuyển đổi traffic một cách từ từ, bắt đầu với một phần nhỏ người dùng sẽ đi qua phiên bản mới, theo dõi hiệu suất, sau đó tăng dần. Mô hình này giúp phát hiện sớm lỗi và giảm rủi ro khi cập nhật.
Cân bằng tải (Load Balancing)
Dù dùng Swarm hoặc Kubernetes, tôi cũng áp dụng load balancer ở phía trước các Container để điều phối traffic. Khi deploy bản mới, Container mới có thể nhận traffic từ load balancer, trong khi Container cũ vẫn xử lý yêu cầu hiện tại, đảm bảo không gián đoạn người dùng.
Lưu trữ phiên bản cũ để có thể rollback
Luôn giữ Container cũ hoạt động cho đến khi tôi chắc chắn rằng Container mới chạy ổn định. Việc này giúp dễ dàng rollback nếu có sự cố, và cho phép kiểm soát quá trình cập nhật chặt chẽ hơn.
Làm sao phân biệt Image gốc (official) và Image tùy chỉnh từ Docker Hub?
Docker Official Images là những Image được Docker trực tiếp curate, có badge “Official Image”, thường là nền tảng đáng tin cậy để bắt đầu xây dựng hệ thống. Chúng có mô tả rõ ràng, được duy trì bởi một nhóm chuyên trách (Docker, upstream maintainers và cộng đồng), và thường xuyên được cập nhật để đảm bảo an toàn cũng như chất lượng cao.
Những Image tùy chỉnh thường do người dùng hoặc tổ chức tự build từ Dockerfile hoặc dựa trên Official Image. chúng được tạo với mục đích cá nhân hóa nội dung, thêm cấu hình, phần mềm riêng hoặc điều chỉnh phù hợp với môi trường sử dụng cụ thể.
Ưu điểm: linh hoạt, có thể tối ưu theo nhu cầu. Nhưng hạn chế là tính minh bạch thấp, dễ bị lỗi bảo mật nếu không được kiểm tra kỹ lưỡng, và có thể thiếu tài liệu rõ ràng.
Bảng so sánh nhanh:
Tiêu chí | Docker Official Image | Image tùy chỉnh (Custom Image) |
Nguồn gốc | Docker kiểm duyệt, badge “Official” | Tự build hoặc từ người dùng khác |
Chất lượng & Bảo mật | Cao, cập nhật thường xuyên | Tùy thuộc vào quy trình build và kiểm thử của người tạo |
Tài liệu & hỗ trợ | Rộng rãi, có tài liệu hỗ trợ | Ít khi có, nếu không do chính tác giả cung cấp |
Tính minh bạch | Rõ ràng, repository Dockerfiles công khai | Có thể opaque, cần tự kiểm tra |
Tùy chỉnh theo nhu cầu | Hạn chế, chỉ dùng như nền tảng | Cao, có thể thêm cấu hình đặc thù |
Kinh nghiệm thực tế:
- Bắt đầu với Docker Official Image nếu tôi cần một nền tảng ổn định, được duy trì và có độ tin cậy cao.
- Tùy chỉnh khi cần thiết, ví dụ khi cần thêm phần mềm, config hoặc công cụ riêng (như curl, cấu trúc thư mục tùy chỉnh…). Khi đó, tôi tự build Image dựa trên Official, và đảm bảo có Dockerfile rõ ràng cùng quy trình kiểm thử thích hợp
Làm thế nào để lưu log của Container vào tập tin?
Để lưu log từ Container Docker vào một tệp trên hệ thống, tôi sử dụng một số phương pháp phổ biến, tuỳ vào mục đích sử dụng và môi trường triển khai:
Redirect log dữ liệu qua lệnh docker logs
Cách đơn giản nhất: sử dụng lệnh docker logs để lấy toàn bộ output (stdout và stderr), rồi chuyển hướng vào file:
docker logs <Container_name_or_id> > Container.log
Hoặc để theo dõi logs đầu ra theo thời gian thực và lưu:
docker logs -f <Container_name_or_id> > Container_follow.log
Tôi cũng có thể lưu cả stdout lẫn stderr cùng lúc bằng &>: docker logs <Container> &> Container_all.log
Để lưu logs với timestamp và filter theo thời gian:
docker logs --timestamps --since="2024-01-01" <Container> > Container_timestamped.log
Cấu hình Docker logging driver để viết log vào file
Tôi cho Docker tự động lưu log vào file với –log-driver và tuỳ chọn –log-opt. Ví dụ sử dụng driver json-file và bật log rotation để quản lý kích thước log:
docker run -d \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
your_Image
Lệnh này sẽ lưu log vào các file JSON tự động xoay khi đạt kích thước tối đa. Logs được lưu tại đường dẫn như:
/var/lib/docker/Containers/<Container-id>/<Container-id>-json.log
Ghi log trực tiếp trong câu lệnh khởi động ứng dụng
Tôi redirect output nội bộ ngay trong Dockerfile bằng lệnh:
docker run my-Image bash -c "myapp >> /path/to/logs/app.log 2>&1"
Hoặc dùng entrypoint script để đảm bảo tiến trình chính là PID 1 và log ghi vào tệp một cách gọn gàng:
#!/bin/sh
exec >/log/stdout.log
exec 2>/log/stderr.log
exec "$@"
Kinh nghiệm thực tế:
- Lệnh docker logs rất tiện khi chỉ cần xuất log một lần hoặc theo dõi tạm thời.
- Cấu hình logging driver giúp lưu log dài hạn, có rotation, và tự động hóa giám sát.
- Redirect trực tiếp trong Container phù hợp khi tôi muốn kiểm soát chính xác file log ngay từ đầu.
Làm sao để kiểm soát version của Image trong môi trường staging và production?
Để đảm bảo tính nhất quán và kiểm soát chặt chẽ giữa môi trường staging và production, tôi thường áp dụng các chiến lược sau:
- Tag theo phiên bản rõ ràng, tránh dùng latest
Hạn chế sử dụng tag latest vì không phản ánh rõ ràng Image nào đang được dùng. Thay vào đó, sử dụng version cụ thể (ví dụ: 1.2.0, dev-abc123) hoặc theo commit hash:
REV=$(git rev-parse --short HEAD)
docker build -t myapp:$REV .
docker push myapp:$REV
- Promote Image qua từng môi trường thay vì rebuild
Sau khi test ở staging, thay vì build lại, tôi retag và push lại Image sang môi trường production:
docker tag myapp:staging-abc123 myapp:prod-abc123
docker push myapp:prod-abc123
Cách làm này đảm bảo production chạy đúng Image đã được kiểm thử ở staging.
- Áp dụng CI/CD để kiểm soát version và triển khai theo pipeline
Thay vì thao tác thủ công, nên sử dụng CI/CD pipeline (như GitLab CI, GitHub Actions…) để tự động hóa quá trình build – tag – test – promote Image.
Ví dụ:
- Khi merge vào branch develop, pipeline sẽ build Image và tag là staging-{commit} → push lên staging registry.
- Khi merge vào main, pipeline retag lại Image đó là prod-{commit} → push sang production registry.
Pipeline giúp đảm bảo Image dùng ở production luôn đồng nhất với Image đã được kiểm thử ở staging, giảm thiểu rủi ro và đảm bảo traceability.
- Sử dụng Image digest để đảm bảo tính bất biến tuyệt đối
Ngoài tag, có thể sử dụng digest (SHA256) để đảm bảo production luôn dùng đúng Image đã kiểm thử:
docker pull myapp@sha256:<digest>
Digest giúp ngăn tình trạng Image bị thay đổi sau khi đã tag, phù hợp với yêu cầu bảo mật và tính ổn định.
Tóm tắt:
Môi trường | Cách quản lý Image |
Staging | Tag theo commit/version, đẩy lên registry staging |
Production | Retag từ staging hoặc deploy theo digest đã kiểm thử |
Cả hai env | Tích hợp CI/CD để tự động hóa versioning và kiểm soát |
Kết luận
Hy vọng rằng bộ 30+ câu hỏi phỏng vấn Docker mới nhất trên đây đã giúp bạn nắm vững kiến thức cốt lõi và chuẩn bị tự tin hơn cho buổi phỏng vấn sắp tới. Từ các khái niệm cơ bản đến kỹ thuật triển khai CI/CD, bảo mật Image và xử lý sự cố Container, mọi nội dung đều được chọn lọc kỹ lưỡng nhằm đáp ứng yêu cầu tuyển dụng thực tế. Hãy bắt đầu luyện tập ngay hôm nay để sẵn sàng chinh phục mọi câu hỏi phỏng vấn Docker một cách hiệu quả nhất.