Tìm hiểu cách mở (expose) và xuất bản (publish) cổng trong Docker để cho phép giao tiếp giữa các container của bạn và thế giới bên ngoài. Hướng dẫn này bao quát mọi thứ từ cấu hình Dockerfile và cờ khi chạy đến điều phối bằng Docker Compose và kỹ thuật khắc phục sự cố.
Khi làm việc với các container Docker, một trong những khía cạnh quan trọng nhất tôi từng gặp là kích hoạt giao tiếp mạng giữa các container và thế giới bên ngoài. Nếu không cấu hình cổng đúng cách, ứng dụng đóng gói trong container sẽ bị cô lập và không thể truy cập. Trong bài viết này, tôi sẽ hướng dẫn bạn quy trình mở cổng trong Docker, một kỹ năng nền tảng cho bất kỳ ai làm việc với ứng dụng chạy trong container.
Hướng dẫn này được viết riêng cho các nhà phát triển phần mềm, kỹ sư DevOps và chuyên gia CNTT cần làm cho ứng dụng trong container có thể truy cập bởi người dùng, các dịch vụ khác hoặc hệ thống bên ngoài. Dù bạn đang triển khai web server, API hay cơ sở dữ liệu trong container, hiểu về mở cổng là điều thiết yếu để xây dựng hệ thống dựa trên Docker hoạt động hiệu quả.
Kết thúc bài viết, bạn sẽ nắm rõ cách hoạt động của mạng Docker, các phương pháp khác nhau để mở cổng, và những kỹ thuật thực tiễn để áp dụng các khái niệm này vào dự án của riêng bạn.
Trước khi đi vào chi tiết kỹ thuật, điều quan trọng là hiểu "mở cổng" có nghĩa gì trong hệ sinh thái Docker.
Nguyên tắc cô lập mạng của container
Container Docker được thiết kế với cô lập là nguyên tắc cốt lõi. Sự cô lập này đạt được thông qua các tính năng của nhân Linux như namespaces và cgroups. Namespaces cung cấp cô lập tiến trình, đảm bảo tiến trình trong một container không thể nhìn thấy hoặc tương tác với tiến trình ở container khác hay hệ thống host. Còn cgroups kiểm soát phân bổ tài nguyên, giới hạn mức CPU, bộ nhớ và các tài nguyên khác mà container có thể sử dụng.
Về mặt mạng, mỗi container nhận một network namespace riêng, kèm theo giao diện Ethernet ảo (cặp veth). Mặc định, giao diện này kết nối đến mạng cầu (bridge) của Docker, cho phép các container liên lạc với nhau trong khi vẫn cô lập với mạng của host. Hãy hình dung mỗi container có một địa chỉ mạng riêng tư của mình, vô hình với thế giới bên ngoài.
Ở cấu hình mặc định, một container Docker bị sandbox hoàn toàn. Dịch vụ chạy bên trong có thể giao tiếp với nhau, nhưng không thể truy cập từ bên ngoài container, kể cả từ máy host. Đây là lúc cần đến việc mở cổng.
Expose vs publish: khác biệt về mặt khái niệm
Khi cấu hình cổng trong Docker, bạn sẽ gặp hai khái niệm liên quan nhưng khác nhau: expose (mở) và publish (xuất bản) cổng.
Expose (mở) cổng chủ yếu là tính năng phục vụ tài liệu. Khi bạn mở một cổng trong Docker, về bản chất bạn đang thêm siêu dữ liệu vào image, cho biết ứng dụng trong container lắng nghe trên các cổng cụ thể.
Tuy nhiên, mở cổng không thực sự làm cho cổng đó có thể truy cập từ bên ngoài container. Nó đóng vai trò như tài liệu cho người dùng image của bạn.
Publish (xuất bản) cổng mới là hành động thực sự làm cho dịch vụ trong container có thể truy cập từ bên ngoài. Xuất bản tạo ánh xạ giữa một cổng trên máy host và một cổng trong container. Khi bạn xuất bản cổng, Docker cấu hình mạng của host để chuyển tiếp lưu lượng từ cổng host được chỉ định đến cổng tương ứng trong container.
Nên dùng mỗi cách khi nào:
Dùng expose khi bạn xây dựng image để người dùng Docker khác sử dụng, nhằm ghi rõ ứng dụng dùng những cổng nào.
Dùng publish khi bạn cần hệ thống bên ngoài (bao gồm host) truy cập các dịch vụ chạy trong container.
Kết hợp cả hai để vừa có tài liệu đầy đủ vừa đảm bảo chức năng, đặc biệt trong môi trường sản xuất.
Cách mở cổng trong Docker
Giờ chúng ta đã nắm khái niệm, hãy xem khía cạnh thực hành của việc mở cổng Docker trong các ngữ cảnh khác nhau.
Chiến lược khai báo trong Dockerfile
Cách cơ bản nhất để mở một cổng là dùng chỉ thị EXPOSE trong Dockerfile. Điều này khai báo các cổng mà container được thiết kế để sử dụng.
Trong ví dụ này, tôi chỉ định container sẽ dùng cổng 80 và 443, vốn là chuẩn cho lưu lượng HTTP (Hypertext Transfer Protocol) và HTTPS (Hypertext Transfer Protocol Secure). Bạn cũng có thể gộp thành một chỉ thị duy nhất:
Khi chỉ định cổng, tốt nhất nên kèm giao thức, TCP (Transmission Control Protocol) hoặc UDP (User Datagram Protocol) nếu ứng dụng của bạn dùng một giao thức cụ thể:
Nếu không chỉ rõ giao thức, mặc định sẽ là TCP. Điều này phù hợp với hầu hết ứng dụng web, nhưng các dịch vụ như DNS (Domain Name System) hoặc máy chủ trò chơi thường cần UDP.
Về bảo mật, tôi khuyến nghị chỉ mở những cổng mà ứng dụng của bạn thực sự cần. Mỗi cổng được mở đều có thể trở thành bề mặt tấn công, nên việc tối thiểu hóa số cổng mở tuân theo nguyên tắc đặc quyền tối thiểu.
Quản lý cổng khi chạy container
Trong khi chỉ thị EXPOSE ghi lại các cổng mà container sử dụng, để làm cho các cổng này có thể truy cập từ host hoặc máy khác, bạn cần xuất bản chúng khi chạy container.
Cách ràng buộc rõ ràng một cổng trong container với một cổng cụ thể trên host:
Cờ -p (hoặc --publish) cho phép bạn ràng buộc một cổng hoặc dải cổng trong container với host.
Lệnh này ánh xạ cổng 80 bên trong container tới cổng 8081 trên host. Sau khi chạy, bạn có thể truy cập web server bằng cách truy cập http://localhost:8081 trong trình duyệt.
Để tiện cho giai đoạn phát triển, Docker cung cấp cờ -P (chữ P hoa) hoặc --publish-all, tự động xuất bản tất cả các cổng đã mở tới các cổng số lớn ngẫu nhiên trên host:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a7c53d9413bf image "/docker-.…"10 s ago Up 9 s 0.0.0.0:49153->80/tcp, :::49153->80/tcp wizardlĐược hỗ trợ bởi
Ở đây, cổng 80 trong container được ánh xạ tới cổng 49153 trên host.
Khi làm việc với các dịch vụ dùng cả TCP và UDP trên cùng một cổng (như máy chủ DNS), bạn cần chỉ định giao thức khi xuất bản:
docker run -p53:53/tcp -p53:53/udp dns-serverĐược hỗ trợ bởi
Điều phối bằng Docker Compose
Với ứng dụng nhiều container, Docker Compose cung cấp cách cấu hình mạng và cổng dễ quản lý hơn.
Trong tệp YAML của Docker Compose, bạn có thể chỉ định ánh xạ cổng dưới khóa ports cho từng dịch vụ:
services:web:image: my-image
ports:-"8081:80"-"443:443"api:build: ./api
ports:-"3000:3000"Được hỗ trợ bởi
Docker Compose phân biệt quan trọng giữa ports và expose. Phần ports tạo ánh xạ cổng đã xuất bản có thể truy cập từ bên ngoài, trong khi expose chỉ làm cho cổng đó khả dụng với các dịch vụ liên kết trong cùng mạng Compose:
services:web:image: my-image
ports:-"8081:80"database:image: postgres
expose:-"5432"Được hỗ trợ bởi
Trong ví dụ này, dịch vụ web có thể truy cập từ host tại cổng 8081, còn cơ sở dữ liệu PostgreSQL chỉ có thể truy cập bởi các dịch vụ khác trong cùng tệp Compose, không trực tiếp từ host.
Để cấu hình linh hoạt, đặc biệt giữa các môi trường khác nhau, tôi có thể dùng biến môi trường trong ánh xạ cổng:
services:web:image: my-image
ports:-"${WEB_PORT:-8081}:80"Được hỗ trợ bởi
Cú pháp này cho phép tôi chỉ định cổng host qua biến môi trường (WEB_PORT), mặc định là 8081 nếu không đặt.
Mở cổng Docker - Ví dụ thực hành
Hãy cùng đi qua một ví dụ hoàn chỉnh về mở cổng Docker cho một ứng dụng web với backend cơ sở dữ liệu.
Giả sử chúng ta xây dựng một ứng dụng web đơn giản gồm dịch vụ API Python Flask và cơ sở dữ liệu PostgreSQL. Dịch vụ API lắng nghe trên cổng 5000, còn PostgreSQL dùng cổng mặc định 5432.
Đây là ví dụ ứng dụng Flask đơn giản (app.py). Ứng dụng sẽ tạo cơ sở dữ liệu myapp và lấy dữ liệu từ bảng items khi bảng này được tạo trong cơ sở dữ liệu.
from flask import Flask, jsonify
import os
import psycopg2
app = Flask(__name__)# Database connection parameters from environment variables
DB_HOST = os.environ.get('DB_HOST','db')
DB_NAME = os.environ.get('DB_NAME','myapp')
DB_USER = os.environ.get('DB_USER','postgres')
DB_PASS = os.environ.get('DB_PASSWORD','postgres')@app.route('/')defindex():return jsonify({'message':'API is running'})@app.route('/items')defget_items():# Connect to the PostgreSQL database
conn = psycopg2.connect(
host=DB_HOST,
database=DB_NAME,
user=DB_USER,
password=DB_PASS
)# Create a cursor and execute a query
cur = conn.cursor()
cur.execute('SELECT id, name FROM items')# Fetch results and format as list of dictionaries
items =[{'id': row[0],'name': row[1]}for row in cur.fetchall()]# Close connections
cur.close()
conn.close()return jsonify(items)if __name__ =='__main__':# Run the Flask app, binding to all interfaces (important for Docker)
app.run(host='0.0.0.0', port=5000)Được hỗ trợ bởi
Giờ tôi cần tạo Dockerfile cho dịch vụ API Python:
FROM python:3.9-slimWORKDIR /app# Install dependenciesCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt# Copy application codeCOPY . .# Expose the port Flask runs onEXPOSE 5000# Command to run the applicationCMD ["python", "app.py"]Được hỗ trợ bởi
Cuối cùng, tôi sẽ tạo tệp Docker Compose để điều phối cả hai dịch vụ:
Bây giờ, tôi có thể truy cập API tại http://localhost:5000, nhưng phiên bản PostgreSQL không thể truy cập trực tiếp từ bên ngoài mạng Docker, đây là thực hành bảo mật tốt cho dịch vụ cơ sở dữ liệu. Tuy nhiên, bạn có thể truy cập PostgreSQL từ container đang chạy:
docker compose exec db psql -U postgres -d myappĐược hỗ trợ bởi
Lệnh này sẽ truy cập CLI của PostgreSQL, nơi tôi có thể xem cơ sở dữ liệu và tạo bảng items. Khi đã tạo, tôi có thể xem dữ liệu tại http://localhost:5000/items
Cách mở nhiều cổng Docker
Nếu ứng dụng Python của tôi cần mở nhiều cổng, tôi có thể chỉ định tất cả trong Dockerfile:
FROM python:3.9-slimWORKDIR /appCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txtCOPY . .EXPOSE 5000 8081 9090CMD ["python", "app.py"]Được hỗ trợ bởi
Dockerfile này mở:
Cổng 5000 cho ứng dụng Flask chính (hiện đang hoạt động)
Cổng 8081 cho một bảng điều khiển giám sát tiềm năng
Cổng 9090 cho một giao diện gỡ lỗi trong tương lai
Lưu ý rằng dù tôi đã mở các cổng 8081 và 9090 trong Dockerfile như một dạng tài liệu, ứng dụng Flask hiện tại chỉ được cấu hình lắng nghe trên cổng 5000. Các cổng mở thêm cho thấy nơi có thể triển khai chức năng trong tương lai.
Cách xuất bản các cổng đã mở
Để xuất bản các cổng đã mở trong Dockerfile, tôi dùng cờ -p hoặc -P khi chạy container:
Lệnh này ánh xạ cổng 5000 trong container (đã mở trong Dockerfile và là nơi Flask lắng nghe) tới cổng 8081 trên host. Sau khi chạy, tôi có thể truy cập ứng dụng Flask bằng cách vào http://localhost:8081 trong trình duyệt.
Tôi cũng có thể dùng cờ -P để tự động xuất bản tất cả các cổng đã mở tới các cổng ngẫu nhiên trên host:
5000/tcp ->0.0.0.0:32768
8081/tcp ->0.0.0.0:32769
9090/tcp ->0.0.0.0:32770Được hỗ trợ bởi
Lệnh này hiển thị tất cả ánh xạ cổng cho một container cụ thể. Ví dụ, nếu ứng dụng Flask của tôi chỉ lắng nghe trên cổng 5000 nhưng tôi đã mở nhiều cổng trong Dockerfile, docker port sẽ cho tôi biết chính xác cổng host nào được ánh xạ tới cổng nào trong container. Vì vậy, nếu cổng 5000 được ánh xạ tới 32768, tôi có thể truy cập ứng dụng Flask tại http://localhost:32768
Kỹ thuật chẩn đoán và khắc phục sự cố
Ngay cả khi đã cấu hình cẩn thận, các vấn đề vẫn có thể phát sinh với ánh xạ cổng Docker. Dưới đây là một số kỹ thuật tôi dùng để chẩn đoán và giải quyết các vấn đề thường gặp.
Kiểm tra ánh xạ cổng
Bước đầu tiên để chẩn đoán sự cố liên quan đến cổng là xác minh các ánh xạ đã được thiết lập đúng. Tôi dùng các lệnh sau:
Liệt kê tất cả container đang chạy cùng ánh xạ cổng:
Lệnh docker inspect cung cấp thông tin chi tiết, bao gồm cài đặt mạng. Để tập trung vào chi tiết mạng, hãy dùng:
docker inspect --format='{{json .NetworkSettings.Ports}}'[container_id]| jqĐược hỗ trợ bởi
Nếu bạn cần phân tích lưu lượng sâu hơn, các công cụ như tcpdump và netstat có thể rất hữu ích. Trước tiên, hãy đảm bảo chúng được cài trong container. Nếu container chưa có sẵn các công cụ này, bạn có thể cài đặt bằng lệnh sau:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 000.0.0.0:5000 0.0.0.0:* LISTEN
tcp 00127.0.0.11:42265 0.0.0.0:* LISTEN
udp 00127.0.0.11:34267 0.0.0.0:*Được hỗ trợ bởi
Ví dụ này cho thấy ứng dụng Flask đang lắng nghe trên mọi giao diện tại cổng 5000.
Để giám sát lưu lượng vào ra trên giao diện mạng chính của container (thường là eth0):
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytesĐược hỗ trợ bởi
Lệnh này cho phép bạn truy vết lưu lượng và xác định liệu yêu cầu có tới được container hay gói tin bị rơi hoặc định tuyến sai.
Phân tích quy tắc iptables
Docker tận dụng iptables để quản lý NAT (Network Address Translation) và kiểm soát luồng lưu lượng giữa host và container. Nó tự động chèn các quy tắc để chuyển tiếp cổng host tới đúng IP và cổng của container, giúp truy cập dịch vụ trong container một cách liền mạch.
Trên host Linux thuần, bạn có thể kiểm tra các quy tắc này bằng các lệnh như:
Lệnh này cho thấy Docker ánh xạ lưu lượng đến trên các cổng host cụ thể sang các đầu mút trong container như thế nào. Dựa trên tệp Docker Compose ở phần trước, bạn sẽ thấy các cổng được chuyển tiếp:
DNAT chuyển hướng lưu lượng từ host tới container.
RETURN đảm bảo định tuyến đúng cho lưu lượng nội bộ mạng Docker.
Tuy nhiên, tùy môi trường của bạn (ví dụ Docker Desktop trên Windows hoặc WSL2), Docker có thể xử lý chuyển tiếp cổng khác đi, và các quy tắc iptables có thể không hiển thị hoặc không thể chỉnh sửa từ trong hệ thống. Trong trường hợp này, bạn có thể kiểm tra chuyển tiếp cổng bằng lệnh sau:
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp LISTEN 04096 *:5000 *:*
tcp LISTEN 04096 *:8081 *:*
tcp LISTEN 04096 *:9090 *:* Được hỗ trợ bởi
Mẹo khắc phục sự cố:
Xác nhận ánh xạ cổng với docker ps và docker port [container].
Kiểm tra xem tường lửa hệ thống host hoặc phần mềm bảo mật có đang chặn cổng hay không.
Dùng log của container và các công cụ mạng như netstat và tcpdump để xác minh hành vi mạng của ứng dụng.
Kết luận
Mở cổng trong Docker là kỹ năng nền tảng giúp bắc cầu giữa sự cô lập của container và khả năng sử dụng thực tế. Xuyên suốt bài viết, tôi đã giải thích mô hình mạng của Docker hoạt động ra sao, sự khác biệt giữa mở và xuất bản cổng, và đưa ra ví dụ thực hành cho nhiều kịch bản.
Ghi nhớ các điểm chính sau:
Dùng EXPOSE trong Dockerfile để ghi rõ ứng dụng dùng những cổng nào
Dùng -p hoặc -P khi chạy container để cho phép truy cập các cổng đó từ bên ngoài
Tận dụng Docker Compose để quản lý các ứng dụng nhiều container phức tạp
Dùng công cụ chẩn đoán để khắc phục sự cố khi phát sinh
Thành thạo các khái niệm và kỹ thuật này sẽ giúp bạn thiết kế ứng dụng chạy trong container vững chắc hơn, giao tiếp hiệu quả cả bên trong lẫn với thế giới bên ngoài. Dù bạn phát triển ứng dụng web đơn giản hay kiến trúc microservices phức tạp, quản lý cổng đúng cách luôn là yếu tố then chốt để thành công.
Khi container tiếp tục thống lĩnh chiến lược triển khai ứng dụng hiện đại, khả năng quản lý mạng và cổng Docker một cách tự tin sẽ vẫn là kỹ năng thiết yếu trong bộ công cụ của mọi lập trình viên.
Mở cổng trong Docker là cách ghi lại (document) các cổng mà ứng dụng trong container lắng nghe, nhưng không làm cho cổng đó có thể truy cập từ bên ngoài container.
Làm sao để cổng của container Docker có thể truy cập từ máy host?⌄
Bạn xuất bản cổng bằng cờ -p với docker run hoặc phần ports trong Docker Compose, ánh xạ một cổng trên host tới cổng trong container.
Sự khác nhau giữa `EXPOSE` và `-p` trong Docker là gì?⌄
EXPOSE được dùng trong Dockerfile để khai báo cổng phục vụ tài liệu, còn -p dùng để xuất bản và ánh xạ các cổng đó cho truy cập từ bên ngoài.
Tôi có thể mở nhiều cổng cho một container Docker không?⌄
Có. Bạn có thể mở nhiều cổng bằng nhiều chỉ thị EXPOSE trong Dockerfile hoặc chỉ định nhiều cổng trong phần ports của Docker Compose.
Làm sao để khắc phục sự cố ánh xạ cổng Docker?⌄
Dùng các lệnh như docker ps, docker port, docker inspect, và các công cụ mạng trong container (ví dụ netstat, tcpdump) để xác minh ánh xạ cổng và luồng lưu lượng.
Với vai trò Nhà sáng lập Martin Data Solutions và Nhà khoa học dữ liệu/ Kỹ sư ML & AI tự do, tôi sở hữu danh mục năng lực đa dạng về Hồi quy, Phân loại, Xử lý ngôn ngữ tự nhiên (NLP), LLM, RAG, Mạng nơ-ron, Phương pháp tổ hợp (Ensemble) và Thị giác máy tính.
Phát triển thành công nhiều dự án ML end-to-end, bao gồm làm sạch dữ liệu, phân tích, xây dựng mô hình và triển khai trên AWS và GCP, mang lại các giải pháp có tác động và khả năng mở rộng.
Xây dựng các ứng dụng web tương tác, có khả năng mở rộng bằng Streamlit và Gradio cho nhiều bài toán trong các ngành khác nhau.
Giảng dạy và cố vấn cho học viên về khoa học dữ liệu và phân tích, thúc đẩy sự phát triển nghề nghiệp của họ thông qua phương pháp học tập cá nhân hóa.
Thiết kế nội dung khóa học cho các ứng dụng retrieval-augmented generation (RAG) được điều chỉnh theo yêu cầu doanh nghiệp.
Viết các blog kỹ thuật AI & ML có sức ảnh hưởng, bao gồm các chủ đề như MLOps, cơ sở dữ liệu vector và LLM, đạt được mức độ tương tác đáng kể.
Trong mỗi dự án tôi đảm nhận, tôi luôn áp dụng các thực hành cập nhật trong kỹ thuật phần mềm và DevOps như CI/CD, linting mã, định dạng, giám sát mô hình, theo dõi thí nghiệm và xử lý lỗi mạnh mẽ. Tôi cam kết cung cấp các giải pháp hoàn chỉnh, chuyển hóa những hiểu biết từ dữ liệu thành chiến lược thực tiễn giúp doanh nghiệp phát triển và khai thác tối đa giá trị của khoa học dữ liệu, học máy và AI.
Khám phá sâu rộng các kỹ năng nâng cao và công cụ tiên tiến nhất đang cách mạng hóa vai trò của kỹ sư dữ liệu ngày nay thông qua chương trình đào tạo Chuyên gia Kỹ sư Dữ liệu của chúng tôi.
Khi làm việc với các container Docker, một trong những khía cạnh quan trọng nhất tôi từng gặp là kích hoạt giao tiếp mạng giữa các container và thế giới bên ngoài. Nếu không cấu hình cổng đúng cách, ứng dụng đóng gói trong container sẽ bị cô lập và không thể truy cập. Trong bài viết này, tôi sẽ hướng dẫn bạn quy trình mở cổng trong Docker, một kỹ năng nền tảng cho bất kỳ ai làm việc với ứng dụng chạy trong container.
Hướng dẫn này được viết riêng cho các nhà phát triển phần mềm, kỹ sư DevOps và chuyên gia CNTT cần làm cho ứng dụng trong container có thể truy cập bởi người dùng, các dịch vụ khác hoặc hệ thống bên ngoài. Dù bạn đang triển khai web server, API hay cơ sở dữ liệu trong container, hiểu về mở cổng là điều thiết yếu để xây dựng hệ thống dựa trên Docker hoạt động hiệu quả.
Kết thúc bài viết, bạn sẽ nắm rõ cách hoạt động của mạng Docker, các phương pháp khác nhau để mở cổng, và những kỹ thuật thực tiễn để áp dụng các khái niệm này vào dự án của riêng bạn.
Nếu bạn mới làm quen với Docker, hãy cân nhắc tham gia một trong các khóa học của chúng tôi như Giới thiệu về Docker, Container hóa và Ảo hóa với Docker và Kubernetes, Docker trình độ trung cấp, hoặc Các khái niệm về Container hóa và Ảo hóa.
Mở cổng trong Docker là gì?
Trước khi đi vào chi tiết kỹ thuật, điều quan trọng là hiểu "mở cổng" có nghĩa gì trong hệ sinh thái Docker.
Nguyên tắc cô lập mạng của container
Container Docker được thiết kế với cô lập là nguyên tắc cốt lõi. Sự cô lập này đạt được thông qua các tính năng của nhân Linux như namespaces và cgroups. Namespaces cung cấp cô lập tiến trình, đảm bảo tiến trình trong một container không thể nhìn thấy hoặc tương tác với tiến trình ở container khác hay hệ thống host. Còn cgroups kiểm soát phân bổ tài nguyên, giới hạn mức CPU, bộ nhớ và các tài nguyên khác mà container có thể sử dụng.
Về mặt mạng, mỗi container nhận một network namespace riêng, kèm theo giao diện Ethernet ảo (cặp veth). Mặc định, giao diện này kết nối đến mạng cầu (bridge) của Docker, cho phép các container liên lạc với nhau trong khi vẫn cô lập với mạng của host. Hãy hình dung mỗi container có một địa chỉ mạng riêng tư của mình, vô hình với thế giới bên ngoài.
Ở cấu hình mặc định, một container Docker bị sandbox hoàn toàn. Dịch vụ chạy bên trong có thể giao tiếp với nhau, nhưng không thể truy cập từ bên ngoài container, kể cả từ máy host. Đây là lúc cần đến việc mở cổng.
Expose vs publish: khác biệt về mặt khái niệm
Khi cấu hình cổng trong Docker, bạn sẽ gặp hai khái niệm liên quan nhưng khác nhau: expose (mở) và publish (xuất bản) cổng.
Expose (mở) cổng chủ yếu là tính năng phục vụ tài liệu. Khi bạn mở một cổng trong Docker, về bản chất bạn đang thêm siêu dữ liệu vào image, cho biết ứng dụng trong container lắng nghe trên các cổng cụ thể.
Tuy nhiên, mở cổng không thực sự làm cho cổng đó có thể truy cập từ bên ngoài container. Nó đóng vai trò như tài liệu cho người dùng image của bạn.
Publish (xuất bản) cổng mới là hành động thực sự làm cho dịch vụ trong container có thể truy cập từ bên ngoài. Xuất bản tạo ánh xạ giữa một cổng trên máy host và một cổng trong container. Khi bạn xuất bản cổng, Docker cấu hình mạng của host để chuyển tiếp lưu lượng từ cổng host được chỉ định đến cổng tương ứng trong container.
Nên dùng mỗi cách khi nào:
Cách mở cổng trong Docker
Giờ chúng ta đã nắm khái niệm, hãy xem khía cạnh thực hành của việc mở cổng Docker trong các ngữ cảnh khác nhau.
Chiến lược khai báo trong Dockerfile
Cách cơ bản nhất để mở một cổng là dùng chỉ thị
EXPOSEtrong Dockerfile. Điều này khai báo các cổng mà container được thiết kế để sử dụng.Trong ví dụ này, tôi chỉ định container sẽ dùng cổng
80và443, vốn là chuẩn cho lưu lượng HTTP (Hypertext Transfer Protocol) và HTTPS (Hypertext Transfer Protocol Secure). Bạn cũng có thể gộp thành một chỉ thị duy nhất:Khi chỉ định cổng, tốt nhất nên kèm giao thức, TCP (Transmission Control Protocol) hoặc UDP (User Datagram Protocol) nếu ứng dụng của bạn dùng một giao thức cụ thể:
Nếu không chỉ rõ giao thức, mặc định sẽ là TCP. Điều này phù hợp với hầu hết ứng dụng web, nhưng các dịch vụ như DNS (Domain Name System) hoặc máy chủ trò chơi thường cần UDP.
Về bảo mật, tôi khuyến nghị chỉ mở những cổng mà ứng dụng của bạn thực sự cần. Mỗi cổng được mở đều có thể trở thành bề mặt tấn công, nên việc tối thiểu hóa số cổng mở tuân theo nguyên tắc đặc quyền tối thiểu.
Quản lý cổng khi chạy container
Trong khi chỉ thị
EXPOSEghi lại các cổng mà container sử dụng, để làm cho các cổng này có thể truy cập từ host hoặc máy khác, bạn cần xuất bản chúng khi chạy container.Cách ràng buộc rõ ràng một cổng trong container với một cổng cụ thể trên host:
Cờ
-p(hoặc--publish) cho phép bạn ràng buộc một cổng hoặc dải cổng trong container với host.Lệnh này ánh xạ cổng 80 bên trong container tới cổng 8081 trên host. Sau khi chạy, bạn có thể truy cập web server bằng cách truy cập
http://localhost:8081trong trình duyệt.Để tiện cho giai đoạn phát triển, Docker cung cấp cờ
-P(chữ P hoa) hoặc--publish-all, tự động xuất bản tất cả các cổng đã mở tới các cổng số lớn ngẫu nhiên trên host:Để biết các cổng host nào đã được gán, tôi có thể dùng lệnh
docker ps:Kết quả sẽ trông như sau:
Ở đây, cổng 80 trong container được ánh xạ tới cổng 49153 trên host.
Khi làm việc với các dịch vụ dùng cả TCP và UDP trên cùng một cổng (như máy chủ DNS), bạn cần chỉ định giao thức khi xuất bản:
Điều phối bằng Docker Compose
Với ứng dụng nhiều container, Docker Compose cung cấp cách cấu hình mạng và cổng dễ quản lý hơn.
Trong tệp YAML của Docker Compose, bạn có thể chỉ định ánh xạ cổng dưới khóa
portscho từng dịch vụ:Docker Compose phân biệt quan trọng giữa
portsvàexpose. Phầnportstạo ánh xạ cổng đã xuất bản có thể truy cập từ bên ngoài, trong khiexposechỉ làm cho cổng đó khả dụng với các dịch vụ liên kết trong cùng mạng Compose:Trong ví dụ này, dịch vụ
webcó thể truy cập từ host tại cổng 8081, còn cơ sở dữ liệu PostgreSQL chỉ có thể truy cập bởi các dịch vụ khác trong cùng tệp Compose, không trực tiếp từ host.Để cấu hình linh hoạt, đặc biệt giữa các môi trường khác nhau, tôi có thể dùng biến môi trường trong ánh xạ cổng:
Cú pháp này cho phép tôi chỉ định cổng host qua biến môi trường (
WEB_PORT), mặc định là 8081 nếu không đặt.Mở cổng Docker - Ví dụ thực hành
Hãy cùng đi qua một ví dụ hoàn chỉnh về mở cổng Docker cho một ứng dụng web với backend cơ sở dữ liệu.
Giả sử chúng ta xây dựng một ứng dụng web đơn giản gồm dịch vụ API Python Flask và cơ sở dữ liệu PostgreSQL. Dịch vụ API lắng nghe trên cổng
5000, còn PostgreSQL dùng cổng mặc định5432.Đây là ví dụ ứng dụng Flask đơn giản (
app.py). Ứng dụng sẽ tạo cơ sở dữ liệumyappvà lấy dữ liệu từ bảngitemskhi bảng này được tạo trong cơ sở dữ liệu.Tiếp theo, tôi sẽ tạo tệp
requirements.txt:Giờ tôi cần tạo Dockerfile cho dịch vụ API Python:
Cuối cùng, tôi sẽ tạo tệp Docker Compose để điều phối cả hai dịch vụ:
Trong cấu hình này:
5000, giúp có thể truy cập từ máy host.5432nhưng không xuất bản, nên chỉ các dịch vụ khác trong mạng Compose mới có thể truy cập.5432.Để chạy ứng dụng này, dùng:
Bây giờ, tôi có thể truy cập API tại
http://localhost:5000, nhưng phiên bản PostgreSQL không thể truy cập trực tiếp từ bên ngoài mạng Docker, đây là thực hành bảo mật tốt cho dịch vụ cơ sở dữ liệu. Tuy nhiên, bạn có thể truy cập PostgreSQL từ container đang chạy:Lệnh này sẽ truy cập CLI của PostgreSQL, nơi tôi có thể xem cơ sở dữ liệu và tạo bảng
items. Khi đã tạo, tôi có thể xem dữ liệu tạihttp://localhost:5000/itemsCách mở nhiều cổng Docker
Nếu ứng dụng Python của tôi cần mở nhiều cổng, tôi có thể chỉ định tất cả trong Dockerfile:
Dockerfile này mở:
5000cho ứng dụng Flask chính (hiện đang hoạt động)8081cho một bảng điều khiển giám sát tiềm năng9090cho một giao diện gỡ lỗi trong tương laiLưu ý rằng dù tôi đã mở các cổng
8081và9090trong Dockerfile như một dạng tài liệu, ứng dụng Flask hiện tại chỉ được cấu hình lắng nghe trên cổng5000. Các cổng mở thêm cho thấy nơi có thể triển khai chức năng trong tương lai.Cách xuất bản các cổng đã mở
Để xuất bản các cổng đã mở trong Dockerfile, tôi dùng cờ
-phoặc-Pkhi chạy container:Lệnh này ánh xạ cổng
5000trong container (đã mở trong Dockerfile và là nơi Flask lắng nghe) tới cổng8081trên host. Sau khi chạy, tôi có thể truy cập ứng dụng Flask bằng cách vàohttp://localhost:8081trong trình duyệt.Tôi cũng có thể dùng cờ -P để tự động xuất bản tất cả các cổng đã mở tới các cổng ngẫu nhiên trên host:
Để kiểm tra cổng nào đã được xuất bản, tôi có thể dùng:
Để có thông tin chi tiết hơn, tôi có thể dùng:
Lệnh này hiển thị tất cả ánh xạ cổng cho một container cụ thể. Ví dụ, nếu ứng dụng Flask của tôi chỉ lắng nghe trên cổng
5000nhưng tôi đã mở nhiều cổng trong Dockerfile, docker port sẽ cho tôi biết chính xác cổng host nào được ánh xạ tới cổng nào trong container. Vì vậy, nếu cổng5000được ánh xạ tới 32768, tôi có thể truy cập ứng dụng Flask tạihttp://localhost:32768Kỹ thuật chẩn đoán và khắc phục sự cố
Ngay cả khi đã cấu hình cẩn thận, các vấn đề vẫn có thể phát sinh với ánh xạ cổng Docker. Dưới đây là một số kỹ thuật tôi dùng để chẩn đoán và giải quyết các vấn đề thường gặp.
Kiểm tra ánh xạ cổng
Bước đầu tiên để chẩn đoán sự cố liên quan đến cổng là xác minh các ánh xạ đã được thiết lập đúng. Tôi dùng các lệnh sau:
Liệt kê tất cả container đang chạy cùng ánh xạ cổng:
Lấy thông tin chi tiết về một container cụ thể:
Kiểm tra ánh xạ cổng cho một container cụ thể:
Lệnh docker inspect cung cấp thông tin chi tiết, bao gồm cài đặt mạng. Để tập trung vào chi tiết mạng, hãy dùng:
Nếu bạn cần phân tích lưu lượng sâu hơn, các công cụ như
tcpdumpvànetstatcó thể rất hữu ích. Trước tiên, hãy đảm bảo chúng được cài trong container. Nếu container chưa có sẵn các công cụ này, bạn có thể cài đặt bằng lệnh sau:Để xem ứng dụng đang lắng nghe trên những cổng nào bên trong container:
Ví dụ này cho thấy ứng dụng Flask đang lắng nghe trên mọi giao diện tại cổng
5000.Để giám sát lưu lượng vào ra trên giao diện mạng chính của container (thường là eth0):
Lệnh này cho phép bạn truy vết lưu lượng và xác định liệu yêu cầu có tới được container hay gói tin bị rơi hoặc định tuyến sai.
Phân tích quy tắc iptables
Docker tận dụng
iptablesđể quản lý NAT (Network Address Translation) và kiểm soát luồng lưu lượng giữa host và container. Nó tự động chèn các quy tắc để chuyển tiếp cổng host tới đúng IP và cổng của container, giúp truy cập dịch vụ trong container một cách liền mạch.Trên host Linux thuần, bạn có thể kiểm tra các quy tắc này bằng các lệnh như:
Lệnh này cho thấy Docker ánh xạ lưu lượng đến trên các cổng host cụ thể sang các đầu mút trong container như thế nào. Dựa trên tệp Docker Compose ở phần trước, bạn sẽ thấy các cổng được chuyển tiếp:
Trong đó:
DNATchuyển hướng lưu lượng từ host tới container.RETURNđảm bảo định tuyến đúng cho lưu lượng nội bộ mạng Docker.Tuy nhiên, tùy môi trường của bạn (ví dụ Docker Desktop trên Windows hoặc WSL2), Docker có thể xử lý chuyển tiếp cổng khác đi, và các quy tắc
iptablescó thể không hiển thị hoặc không thể chỉnh sửa từ trong hệ thống. Trong trường hợp này, bạn có thể kiểm tra chuyển tiếp cổng bằng lệnh sau:Mẹo khắc phục sự cố:
docker psvàdocker port [container].netstatvàtcpdumpđể xác minh hành vi mạng của ứng dụng.Kết luận
Mở cổng trong Docker là kỹ năng nền tảng giúp bắc cầu giữa sự cô lập của container và khả năng sử dụng thực tế. Xuyên suốt bài viết, tôi đã giải thích mô hình mạng của Docker hoạt động ra sao, sự khác biệt giữa mở và xuất bản cổng, và đưa ra ví dụ thực hành cho nhiều kịch bản.
Ghi nhớ các điểm chính sau:
EXPOSEtrong Dockerfile để ghi rõ ứng dụng dùng những cổng nào-phoặc-Pkhi chạy container để cho phép truy cập các cổng đó từ bên ngoàiThành thạo các khái niệm và kỹ thuật này sẽ giúp bạn thiết kế ứng dụng chạy trong container vững chắc hơn, giao tiếp hiệu quả cả bên trong lẫn với thế giới bên ngoài. Dù bạn phát triển ứng dụng web đơn giản hay kiến trúc microservices phức tạp, quản lý cổng đúng cách luôn là yếu tố then chốt để thành công.
Khi container tiếp tục thống lĩnh chiến lược triển khai ứng dụng hiện đại, khả năng quản lý mạng và cổng Docker một cách tự tin sẽ vẫn là kỹ năng thiết yếu trong bộ công cụ của mọi lập trình viên.
Để tiếp tục học, hãy xem các tài nguyên sau: