Xin chào các bạn đồng nghiệp trong hành trình khám phá Docker! Chào mừng quay trở lại với chuỗi bài viết “Roadmap Docker”. Sau khi đã cùng nhau tìm hiểu về Container Là Gì, sự khác biệt giữa Container, Máy ảo và Bare Metal, các khái niệm nền tảng như OCI, các kỹ năng Linux cốt lõi (Linux căn bản, Package Managers, Người dùng và Quyền hạn, Lệnh Shell, Shell Scripting), hiểu về phát triển web trong môi trường container, kiến trúc ứng dụng, các công nghệ nền tảng như Namespaces, Cgroups, UnionFS, và cả cài đặt Docker, Docker Engine. Chúng ta cũng đã đi sâu vào lưu trữ dữ liệu bền vững với Volume và Bind Mounts, sử dụng image bên thứ ba an toàn, chạy cơ sở dữ liệu trong Docker, sử dụng Docker cho môi trường kiểm thử, đóng gói công cụ dòng lệnh, viết Dockerfile tốt hơn, hiểu về Docker Caching, tối ưu image và bảo mật, làm quen với Docker Hub và Registry, gắn tag cho image, và quan trọng nhất là làm quen với lệnh docker run
cơ bản và quản lý ứng dụng đa container với Docker Compose.
Việc xây dựng một Docker Image hiệu quả chỉ là bước đầu tiên. Để biến image đó thành một ứng dụng đang chạy, có thể tương tác với thế giới bên ngoài, truy cập tài nguyên, và hoạt động theo đúng ý muốn, chúng ta cần cấu hình container tại thời điểm chạy (runtime). Đây chính là lúc sức mạnh thực sự của lệnh docker run
được thể hiện qua vô số các tùy chọn mà nó cung cấp. Bài viết này sẽ đi sâu vào các tùy chọn cấu hình runtime quan trọng nhất mà mọi DevOps Engineer, đặc biệt là các bạn junior, cần nắm vững để triển khai container một cách hiệu quả, an toàn và đáng tin cậy.
Mục lục
Quản Lý Mạng Cho Container: Liên Kết Với Thế Giới Bên Ngoài
Một trong những nhu cầu cơ bản nhất của container là khả năng giao tiếp, dù là với người dùng cuối, các dịch vụ khác trên mạng nội bộ, hay các container khác. Docker cung cấp các tùy chọn mạnh mẽ để quản lý mạng tại runtime.
Ánh Xạ Cổng (Port Mapping)
Theo mặc định, các cổng mà ứng dụng bên trong container lắng nghe sẽ không thể truy cập được từ bên ngoài host Docker. Tùy chọn -p
hoặc --publish
cho phép chúng ta “ánh xạ” một cổng trên host Docker tới một cổng bên trong container.
docker run -d -p 8080:80 nginx
Ví dụ trên chạy một container Nginx ở chế độ detached (chạy nền, -d
) và ánh xạ cổng 80 bên trong container tới cổng 8080 trên host. Người dùng có thể truy cập Nginx thông qua http://<Địa chỉ IP Host>:8080
.
Bạn có thể ánh xạ nhiều cổng cùng lúc:
docker run -d -p 8080:80 -p 8443:443 nginx
Docker cũng hỗ trợ ràng buộc ánh xạ cổng với một địa chỉ IP cụ thể trên host:
docker run -d -p 127.0.0.1:8080:80 nginx
Chỉ cho phép truy cập từ localhost trên host Docker.
Kết Nối Với Mạng Docker (Docker Networks)
Ngoài việc ánh xạ cổng ra host, container thường cần giao tiếp với nhau hoặc với các dịch vụ khác trong môi trường Docker. Tùy chọn --network
cho phép container tham gia vào một mạng Docker cụ thể.
# Tạo một mạng bridge tùy chỉnh
docker network create my_bridge_network
# Chạy container database trong mạng này
docker run -d --name my_db --network my_bridge_network postgres
# Chạy container ứng dụng trong mạng này, có thể truy cập my_db bằng tên
docker run -d --name my_app --network my_bridge_network my_app_image
Việc sử dụng mạng tùy chỉnh giúp cải thiện khả năng phân giải tên (các container trong cùng mạng có thể truy cập nhau bằng tên container) và cô lập mạng giữa các nhóm container khác nhau.
Docker hỗ trợ nhiều driver mạng khác nhau như bridge
(mặc định), host
(container chia sẻ stack mạng với host – cẩn thận khi sử dụng), overlay
(cho swarm mode), và macvlan
.
Lưu Trữ Dữ Liệu Bền Vững: Giữ Lại Trạng Thái
Theo thiết kế, lớp ghi của container là ephemeral (không bền vững). Khi container bị xóa, mọi thay đổi trong lớp này sẽ mất đi. Để dữ liệu tồn tại lâu hơn vòng đời của container, chúng ta cần sử dụng các cơ chế lưu trữ bền vững. Chúng ta đã thảo luận chi tiết về Lưu trữ Dữ liệu Bền vững và sự khác biệt giữa Volume Mounts và Bind Mounts trong các bài trước. Tại runtime, chúng ta sử dụng tùy chọn -v
hoặc --volume
(cú pháp cũ) hoặc --mount
(cú pháp mới, được khuyến khích) để gắn các volume hoặc bind mount vào container.
Volume Mounts
Volume là cơ chế ưu tiên để lưu trữ dữ liệu Docker. Docker quản lý vòng đời và vị trí của volume trên host. Sử dụng tùy chọn --mount
với type=volume
:
# Tạo một volume
docker volume create my_app_data
# Gắn volume vào container
docker run -d --name my_app_with_volume --mount type=volume,source=my_app_data,target=/app/data my_app_image
Dữ liệu được ghi vào /app/data
bên trong container sẽ được lưu trữ trong volume my_app_data
trên host và tồn tại ngay cả khi container bị xóa.
Bind Mounts
Bind Mount cho phép gắn trực tiếp một thư mục hoặc tập tin từ host vào container. Điều này hữu ích cho việc phát triển (gắn mã nguồn) hoặc cấu hình (gắn tập tin cấu hình).
# Gắn thư mục hiện tại trên host vào /app bên trong container
docker run -d --name my_app_dev --mount type=bind,source="$(pwd)",target=/app my_app_image
# Gắn một tập tin cấu hình cụ thể
docker run -d --name my_app_config --mount type=bind,source="/path/to/host/config.yml",target="/app/config.yml" my_app_image
Lưu ý: Cú pháp -v
cũ cũng hỗ trợ cả volume và bind mount nhưng cú pháp --mount
rõ ràng và linh hoạt hơn, đặc biệt với các tùy chọn phụ (ví dụ: readonly
).
# Cú pháp -v cho volume
docker run -d -v my_app_data:/app/data my_app_image
# Cú pháp -v cho bind mount
docker run -d -v /path/to/host/config.yml:/app/config.yml my_app_image
Thiết Lập Biến Môi Trường: Cấu Hình Linh Hoạt
Biến môi trường là một cách phổ biến và hiệu quả để truyền cấu hình vào ứng dụng, đặc biệt trong môi trường container hóa. Chúng cho phép bạn thay đổi hành vi của ứng dụng mà không cần rebuild image. Tùy chọn -e
hoặc --env
được sử dụng để thiết lập biến môi trường tại runtime.
docker run -d -e DATABASE_URL="postgres://user:pass@host:port/dbname" -e API_KEY="mysecretkey" my_app_image
Bạn có thể cung cấp nhiều biến môi trường bằng cách sử dụng nhiều cờ -e
. Nếu giá trị biến môi trường chứa khoảng trắng hoặc ký tự đặc biệt, hãy đặt nó trong dấu ngoặc kép.
Docker cũng cho phép đọc biến môi trường từ một tập tin bằng tùy chọn --env-file
. Điều này rất tiện lợi khi có nhiều biến cần thiết.
# File .env
# DATABASE_URL=postgres://user:pass@host:port/dbname
# API_KEY=mysecretkey
docker run -d --env-file .env my_app_image
Giới Hạn Tài Nguyên: Kiểm Soát Hiệu Năng và Chi Phí
Container chia sẻ kernel của host nhưng có thể bị giới hạn tài nguyên nhờ các công nghệ như Cgroups (mà chúng ta đã nhắc đến trong bài Namespaces, Cgroups, UnionFS). Việc giới hạn tài nguyên là cần thiết để ngăn chặn một container “ăn” hết tài nguyên của host, ảnh hưởng đến các container hoặc dịch vụ khác.
CPU
Bạn có thể giới hạn mức sử dụng CPU bằng các tùy chọn như --cpus
, --cpu-shares
, --cpu-quota
, --cpu-period
, --cpuset-cpus
.
# Giới hạn container sử dụng tối đa 0.5 core CPU
docker run -d --cpus="0.5" my_cpu_intensive_app
# Giới hạn container sử dụng tối đa 1.5 core CPU
docker run -d --cpus="1.5" my_another_app
--cpu-shares
(hoặc -c
) là một giá trị “trọng số”. Nếu container A có shares là 1024 và container B có shares là 512, thì khi CPU bị cạnh tranh, container A sẽ nhận được gấp đôi thời gian CPU so với container B.
Bộ Nhớ (Memory)
Giới hạn bộ nhớ là cực kỳ quan trọng để tránh tình trạng OOM (Out Of Memory) Killer của host tiêu diệt các tiến trình quan trọng khác. Các tùy chọn chính là --memory
(hoặc -m
) và --memory-swap
.
# Giới hạn container sử dụng tối đa 512MB RAM
docker run -d --memory="512m" my_memory_hungry_app
# Giới hạn container sử dụng tối đa 1GB RAM (không có swap)
docker run -d --memory="1g" --memory-swap="1g" my_app_no_swap
# Giới hạn container sử dụng tối đa 1GB RAM + 1GB Swap
docker run -d --memory="1g" --memory-swap="2g" my_app_with_swap
Nếu --memory-swap
lớn hơn --memory
, phần chênh lệch sẽ là giới hạn swap. Nếu --memory-swap
bằng --memory
, swap sẽ bị vô hiệu hóa.
Cấu Hình An Toàn: Giảm Thiểu Rủi Ro
An toàn là yếu tố không thể bỏ qua khi chạy container. Docker cung cấp nhiều tùy chọn runtime để tăng cường bảo mật.
Chạy Với Người Dùng/Nhóm Cụ Thể (–user)
Theo mặc định, container chạy với người dùng được xác định trong Dockerfile (qua lệnh USER). Tuy nhiên, bạn có thể override điều này tại runtime bằng tùy chọn --user
(hoặc -u
). Điều này đặc biệt quan trọng để tránh chạy các tiến trình bên trong container với quyền root trên host (dù chỉ là ảo hóa qua user namespace, nhưng vẫn nên tránh). Chúng ta đã tìm hiểu về Người Dùng và Quyền hạn trong Linux, kiến thức này rất hữu ích ở đây.
# Chạy container với UID 1000 và GID 1000
docker run -d --user 1000:1000 my_app_image
# Chạy container với người dùng có tên 'appuser' được định nghĩa trong image
docker run -d --user appuser my_app_image
Luôn cố gắng chạy container với người dùng không phải root có ít đặc quyền nhất có thể.
Chế Độ Đặc Quyền (–privileged)
Tùy chọn --privileged
cấp cho container tất cả các đặc quyền trên host. Container chạy ở chế độ privileged gần như có quyền truy cập vào tất cả các thiết bị của host và có thể thực hiện mọi thứ mà tiến trình root trên host có thể làm. Tuyệt đối KHÔNG sử dụng tùy chọn này trong môi trường production trừ khi bạn hiểu rõ ràng và có lý do chính đáng. Nó phá vỡ sự cô lập mà container mang lại.
# KHÔNG NÊN sử dụng tùy tiện!
docker run -d --privileged my_risky_app
Giới Hạn Đặc Quyền (Capabilities, Seccomp, AppArmor/SELinux)
Thay vì sử dụng --privileged
, Docker cho phép bạn cấp hoặc bỏ bớt các “capabilities” của Linux cho container (--cap-add
, --cap-drop
). Capabilities chia nhỏ các đặc quyền của root thành các đơn vị nhỏ hơn, cho phép kiểm soát granular hơn.
# Chạy container mà bỏ tất cả capabilities mặc định trừ NET_RAW (cho phép sniff gói tin)
docker run -d --cap-drop ALL --cap-add NET_RAW my_network_tool
# Bỏ capability cho phép thay đổi thời gian hệ thống
docker run -d --cap-drop SYS_TIME my_app_image
Docker cũng tích hợp với Seccomp (Secure Computing Mode), AppArmor và SELinux để cung cấp các lớp bảo mật bổ sung, giới hạn các syscall mà container có thể thực hiện hoặc kiểm soát truy cập tài nguyên dựa trên profile.
# Áp dụng một profile seccomp tùy chỉnh
docker run -d --security-opt seccomp=/path/to/seccomp/profile.json my_app_image
# Áp dụng một profile AppArmor
docker run -d --security-opt apparmor=my_apparmor_profile my_app_image
Hiểu và sử dụng các tùy chọn bảo mật này là dấu hiệu của một DevOps Engineer chuyên nghiệp.
Chính Sách Khởi Động Lại (–restart)
Điều gì xảy ra khi container dừng lại? Nó có nên tự động khởi động lại không? Chính sách khởi động lại điều khiển hành vi này.
no
: Không tự động khởi động lại (mặc định).on-failure[:max-retries]
: Chỉ khởi động lại nếu container thoát với mã lỗi khác 0. Có thể giới hạn số lần thử.unless-stopped
: Khởi động lại trừ khi container bị dừng thủ công (bằngdocker stop
). Sẽ khởi động lại cả khi host khởi động lại.always
: Luôn luôn khởi động lại container nếu nó dừng, ngay cả khi thoát thành công. Sẽ khởi động lại cả khi host khởi động lại.
# Luôn khởi động lại container
docker run -d --restart always my_app_image
# Khởi động lại tối đa 5 lần nếu container thất bại
docker run -d --restart on-failure:5 my_worker_image
Chính sách khởi động lại là một phần quan trọng của việc đảm bảo ứng dụng luôn khả dụng.
Quản Lý Log (–log-driver)
Xem log của container là cách chính để debug và giám sát. Theo mặc định, Docker sử dụng driver json-file
lưu log vào file trên host. Tuy nhiên, Docker hỗ trợ nhiều logging driver khác nhau để gửi log tới các hệ thống tập trung như Syslog, Journald, Gelf, Fluentd, AWS CloudWatch, Google Cloud Logging, v.v.
# Sử dụng driver syslog
docker run -d --log-driver syslog my_app_image
# Sử dụng driver gelf và cấu hình endpoint
docker run -d --log-driver gelf --log-opt gelf-address=udp://192.168.1.100:12201 my_app_image
Chọn logging driver phù hợp giúp tích hợp container logs vào pipeline giám sát và phân tích log hiện có của bạn.
Đặt Tên Container (–name)
Mặc định, Docker sẽ tạo ra một tên ngẫu nhiên cho container (ví dụ: keen_kalam
). Tuy nhiên, việc đặt tên rõ ràng bằng tùy chọn --name
giúp dễ dàng tham chiếu và quản lý container hơn bằng các lệnh khác như docker stop
, docker start
, docker logs
, docker inspect
.
docker run -d --name my_web_server nginx
docker logs my_web_server
docker stop my_web_server
Tên container phải là duy nhất trên host Docker.
Kiểm Tra Sức Khỏe (–health-cmd, –health-interval, v.v.)
Docker có thể tự động kiểm tra “sức khỏe” của container bằng cách chạy một lệnh cụ thể định kỳ. Kết quả của lệnh này (mã thoát 0 là healthy, khác 0 là unhealthy) cho phép Docker và các hệ thống điều phối (như Docker Swarm, Kubernetes) biết trạng thái thực sự của ứng dụng bên trong, không chỉ dựa vào trạng thái tiến trình.
docker run -d --name my_healthy_app \
--health-cmd "curl -f http://localhost/health || exit 1" \
--health-interval=1m \
--health-timeout=3s \
--health-retries=3 \
--health-start-period=5s \
my_app_image
--health-cmd
: Lệnh chạy để kiểm tra sức khỏe.--health-interval
: Khoảng thời gian giữa các lần kiểm tra.--health-timeout
: Thời gian tối đa chờ lệnh kiểm tra hoàn thành.--health-retries
: Số lần thất bại liên tiếp tối đa trước khi đánh dấu là unhealthy.--health-start-period
: Thời gian ban đầu sau khi container khởi động, trong đó các thất bại của health check không tính vào số lần thử lại (cho phép ứng dụng khởi động hoàn toàn).
Health checks là rất quan trọng trong môi trường tự động hóa và điều phối.
Tổng Kết Các Tùy Chọn Runtime Quan Trọng
Dưới đây là bảng tổng hợp một số tùy chọn docker run
phổ biến và quan trọng:
Tùy Chọn | Mục Đích | Ví Dụ docker run |
---|---|---|
-d |
Chạy container ở chế độ detached (nền) | docker run -d my_image |
-p / --publish |
Ánh xạ cổng từ host tới container | -p 8080:80 |
--network |
Kết nối container với mạng Docker cụ thể | --network my_bridge |
-v / --volume |
Gắn Volume hoặc Bind Mount (cú pháp cũ) | -v my_data:/app/data hoặc -v /host/src:/app/src |
--mount |
Gắn Volume, Bind Mount hoặc Tmpfs (cú pháp mới) | --mount type=volume,source=my_vol,target=/app/data |
-e / --env |
Thiết lập biến môi trường | -e DATABASE_URL=... |
--env-file |
Đọc biến môi trường từ tập tin | --env-file .env |
--cpus |
Giới hạn mức sử dụng CPU | --cpus="0.5" |
--memory / -m |
Giới hạn bộ nhớ RAM | --memory="1g" |
--restart |
Thiết lập chính sách khởi động lại container | --restart always |
--name |
Đặt tên cho container | --name web_server |
-u / --user |
Chạy container với người dùng/nhóm cụ thể | -u 1000:1000 |
--privileged |
Chạy container ở chế độ đặc quyền (cẩn trọng) | --privileged |
--cap-add / --cap-drop |
Thêm/bỏ capabilities Linux | --cap-drop ALL --cap-add NET_RAW |
--log-driver |
Chọn logging driver | --log-driver syslog |
--health-cmd |
Lệnh kiểm tra sức khỏe | --health-cmd "curl ..." |
--entrypoint |
Override ENTRYPOINT được định nghĩa trong Dockerfile | --entrypoint /bin/bash |
--volume-driver |
Chọn volume driver (ít dùng trực tiếp với volume) | --volume-driver local |
Kết Hợp Các Tùy Chọn và Chuyển Sang Docker Compose
Trong thực tế, khi chạy các ứng dụng phức tạp, bạn sẽ cần kết hợp nhiều tùy chọn runtime cùng lúc. Ví dụ, một ứng dụng web có thể cần ánh xạ cổng, kết nối với mạng database, thiết lập biến môi trường, giới hạn bộ nhớ và CPU, và sử dụng health check.
docker run -d \
--name my_full_app \
-p 8080:80 \
--network my_app_network \
-e DATABASE_URL="postgres://user:pass@my_db:5432/dbname" \
--mount type=volume,source=app_data,target=/app/data \
--memory="1g" \
--cpus="0.75" \
--restart unless-stopped \
--health-cmd "curl -f http://localhost/health || exit 1" \
my_app_image
Như bạn thấy, lệnh docker run
có thể trở nên rất dài và phức tạp khi quản lý nhiều container cùng lúc. Đây là lúc Docker Compose phát huy tác dụng. Docker Compose cho phép bạn định nghĩa cấu hình runtime của toàn bộ ứng dụng (bao gồm nhiều service/container) trong một tập tin YAML duy nhất (docker-compose.yml
), làm cho việc quản lý trở nên dễ dàng và có thể tái sử dụng. Chúng ta đã có bài viết giới thiệu về Quản lý Ứng dụng Đa Container với Docker Compose, và giờ bạn đã hiểu sâu hơn về các tùy chọn cấu hình mà bạn sẽ đặt trong file Compose đó.
Kết Luận
Việc nắm vững các tùy chọn cấu hình thời gian chạy cho container Docker là một kỹ năng thiết yếu cho mọi DevOps Engineer. Từ việc quản lý mạng và lưu trữ dữ liệu bền vững, đến việc thiết lập biến môi trường, giới hạn tài nguyên, tăng cường bảo mật, và kiểm soát hành vi khởi động lại hay kiểm tra sức khỏe, mỗi tùy chọn đều đóng góp vào việc triển khai container một cách hiệu quả và đáng tin cậy.
Lệnh docker run
là công cụ cơ bản nhất để làm điều này. Mặc dù với các ứng dụng phức tạp hơn, bạn sẽ chuyển sang sử dụng Docker Compose hoặc các hệ thống điều phối như Kubernetes, nhưng kiến thức về các tùy chọn runtime của docker run
vẫn là nền tảng vững chắc, giúp bạn hiểu cách container hoạt động và cấu hình chúng ở mức độ sâu hơn.
Hãy dành thời gian thực hành với các tùy chọn khác nhau. Đọc kỹ tài liệu chính thức của Docker về lệnh docker run
(docker run --help
là một khởi đầu tốt!). Càng thành thạo việc cấu hình runtime, bạn càng có thể xây dựng và quản lý các ứng dụng container hóa mạnh mẽ và linh hoạt hơn.
Trong các bài viết tiếp theo của chuỗi “Roadmap Docker”, chúng ta sẽ tiếp tục khám phá các khía cạnh nâng cao hơn của Docker và hệ sinh thái container. Hẹn gặp lại!