Tự động hóa triển khai ứng dụng FastAPI với Docker và GitHub Actions
Trong môi trường phát triển hiện đại, việc chuyển đổi từ một script Python đơn giản sang một ứng dụng dịch vụ web chuyên nghiệp là bước ngoặt quan trọng đối với bất kỳ kỹ sư phần mềm nào. FastAPI đã trở thành lựa chọn hàng đầu cho các dự án backend yêu cầu hiệu năng cao và khả năng phản hồi nhanh chóng, nhờ vào kiến trúc không chặn (async) và tự động tạo tài liệu API thông minh. Tuy nhiên, một ứng dụng FastAPI chỉ thực sự có giá trị khi nó được đóng gói trong một container Docker để đảm bảo tính nhất quán giữa môi trường phát triển và sản xuất, đồng thời được tự động hóa quy trình triển khai thông qua CI/CD. Bài viết này sẽ hướng dẫn bạn xây dựng một pipeline tự động hóa hoàn chỉnh: từ viết code Python, đóng gói vào Docker, cho đến việc build và push image lên Docker Hub mỗi khi bạn push code mới lên GitHub.
Thiết lập môi trường và cấu trúc dự án cơ bản
Trước khi đi vào phần tự động hóa, chúng ta cần có một ứng dụng FastAPI mẫu chuẩn. Cấu trúc dự án tốt là nền tảng cho việc đóng gói container hiệu quả. Chúng ta sẽ tạo một thư mục dự án và bên trong đó cần có file mã nguồn chính, file định nghĩa môi trường biến, file cấu hình Docker và file pipeline GitHub Actions.
Đầu tiên, hãy khởi tạo thư mục dự án và tạo file mã nguồn chính với tên là main.py. Trong file này, chúng ta sẽ định nghĩa một ứng dụng đơn giản có hai endpoint: một endpoint chào mừng và một endpoint trả về dữ liệu JSON.
mkdir fastapi-deploy-demo
cd fastapi-deploy-demo
Sau khi đã vào thư mục, chúng ta tạo file main.py với nội dung dưới đây. Lưu ý rằng chúng ta sử dụng thư viện uvicorn để chạy ứng dụng vì đây là server ASGI chuẩn mà FastAPI khuyến nghị sử dụng trong môi trường sản xuất.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="FastAPI Auto Deploy Demo")
class Item(BaseModel):
name: str
price: float
@app.get("/")
def read_root():
return {"message": "Hello from Dockerized FastAPI", "version": "1.0.0"}
@app.post("/items/")
def create_item(item: Item):
return {"item_name": item.name, "item_price": item.price, "status": "success"}
Cấu hình môi trường và yêu cầu phụ thuộc
Để Python có thể tìm thấy các thư viện mà bạn đã sử dụng trong code, chúng ta cần tạo file yêu cầu. Tuy nhiên, trong quy trình đóng gói Docker hiện đại, chúng ta thường không cần file requirements.txt cứng nhắc nếu sử dụng phương pháp Multi-stage build, nhưng để đảm bảo tính minh bạch và dễ quản lý, chúng ta vẫn nên có file này để liệt kê các thư viện cần thiết.
Hãy tạo file requirements.txt chứa các gói cần thiết cho FastAPI, Uvicorn, Pydantic và một số thư viện hỗ trợ khác.
cat > requirements.txt << EOF
fastapi==0.104.1
uvicorn[standard]==0.24.0
pydantic==2.5.0
python-multipart==0.0.6
EOF
Bên cạnh đó, chúng ta cần file .env để chứa các biến môi trường. Đây là nơi chúng ta có thể định nghĩa các cấu hình nhạy cảm hoặc các thông số tùy chỉnh như cổng chạy server. File này sẽ được sử dụng trong quá trình chạy container để mount vào, giúp tách biệt code và cấu hình.
cat > .env << EOF
APP_ENV=production
LOG_LEVEL=info
EOF
Hệ thống quản lý tập tin của Docker thường có file .dockerignore để loại bỏ các file không cần thiết (như thư mục ảo venv, file log, file .git) khỏi quá trình build. Việc này giúp giảm kích thước layer của image và tăng tốc độ build.
cat > .dockerignore << EOF
__pycache__
.venv
venv
.env
.git
*.pyc
*.md
EOF
Viết file Dockerfile tối ưu cho FastAPI
File Dockerfile là trái tim của quá trình container hóa. Đối với ứng dụng Python, đặc biệt là FastAPI chạy trên Uvicorn, chúng ta nên sử dụng chiến lược Multi-stage build. Phương pháp này giúp tạo ra một image cuối cùng rất nhỏ gọn (chỉ khoảng 20-40MB) bằng cách sử dụng image có đầy đủ Python và các công cụ build cho giai đoạn đầu, và chỉ copy các file cần thiết vào image nhẹ ở giai đoạn sau.
Chúng ta sẽ sử dụng image python:3.11-slim làm base vì nó nhẹ hơn so với bản đầy đủ nhưng vẫn đủ mạnh để chạy FastAPI.
FROM python:3.11-slim as builder
# Cài đặt các công cụ cần thiết để build wheel
RUN apt-get update && apt-get install -y \
build-essential \
gcc \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy file yêu cầu và cài đặt thư viện vào thư mục build
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# Giai đoạn thứ hai: Image cuối cùng cho runtime
FROM python:3.11-slim
WORKDIR /app
# Cài đặt runtime cần thiết (chỉ cần uvicorn và các thư viện đã build sẵn)
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy các thư viện đã build từ giai đoạn builder vào
COPY --from=builder /root/.local /root/.local
# Đảm bảo PYTHONPATH trỏ đến thư mục local của root để tìm library
ENV PATH=/root/.local/bin:$PATH
# Copy code ứng dụng vào image
COPY . .
# Tạo user không root để tăng bảo mật
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
# Mở cổng mặc định của FastAPI/Uvicorn
EXPOSE 8000
# Lệnh chạy mặc định
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Trong file Dockerfile trên, chúng ta đã thực hiện một số bước quan trọng: sử dụng hai giai đoạn (builder và runtime) để giảm kích thước, cài đặt các thư viện vào thư mục user local để tránh xung đột với system packages, tạo một user mới (appuser) để không chạy container dưới quyền root (nguyên tắc bảo mật cơ bản), và cuối cùng là định nghĩa lệnh CMD để khởi động server.
Thiết lập quy trình CI/CD với GitHub Actions
Để tự động hóa việc đóng gói và đẩy code lên kho lưu trữ container, chúng ta sẽ sử dụng GitHub Actions. Bạn cần tạo một thư mục có cấu trúc .github/workflows trong gốc dự án của mình. Inside thư mục này, chúng ta sẽ tạo file workflow.yaml.
Workflow này sẽ được kích hoạt mỗi khi bạn thực hiện lệnh push vào nhánh master (hoặc main). Các bước trong workflow bao gồm: lấy mã nguồn, đăng nhập vào Docker Hub (sử dụng secret key), build image Docker, tag image với commit hash để phân biệt phiên bản, và cuối cùng là push image lên Docker Hub.
mkdir -p .github/workflows
Sau đó, tạo file deploy.yml với nội dung chi tiết dưới đây. Lưu ý rằng bạn cần thay thế DOCKERHUB_USERNAME và DOCKERHUB_PASSWORD bằng các bí mật (secrets) đã được cấu hình trong GitHub Settings -> Secrets and variables.
name: Deploy FastAPI to Docker Hub
on:
push:
branches:
- main
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/fastapi-demo
tags: |
type=sha,prefix=
type=raw,value=latest
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
Trong file workflow trên, bước "Login to Docker Hub" sử dụng token thay vì password trực tiếp để tăng tính bảo mật. Bạn cần tạo Personal Access Token (PAT) trên Docker Hub với quyền "Push" và "Read". Bước "Extract metadata" giúp tự động tạo tag cho image dựa trên commit hash của lần push hiện tại, đồng thời luôn giữ một tag "latest" để dễ dàng gọi sau này. Việc sử dụng cache (cache-from, cache-to) giúp tăng tốc đáng kể thời gian build nếu các thư viện phụ thuộc không thay đổi.
Cách kiểm tra và vận hành sau khi build
Sau khi bạn đã cấu hình xong GitHub Actions và push code lên GitHub, hãy vào tab "Actions" của repository để xem quy trình tự động hóa đang chạy. Khi trạng thái chuyển sang màu xanh lá cây (Success), nghĩa là image Docker đã được build thành công và đẩy lên Docker Hub.
Để kiểm tra xem container có chạy được không, bạn có thể pull image vừa tạo về máy tính của mình (hoặc server production) và chạy thử. Đảm bảo rằng bạn đã cài đặt Docker Desktop hoặc Docker Engine trên máy.
docker pull your_username/fastapi-demo:latest
Sau khi pull xong, hãy chạy container với lệnh dưới đây. Chúng ta sẽ map cổng 8000 của container sang cổng 8000 của máy chủ để có thể truy cập từ trình duyệt.
docker run -d -p 8000:8000 your_username/fastapi-demo:latest
Lúc này, bạn có thể mở trình duyệt và truy cập vào địa chỉ http://localhost:8000/docs để xem tài liệu API tự động được FastAPI tạo ra. Nếu thấy giao diện Swagger UI hiện ra, chúc mừng bạn đã thành công. Bạn cũng có thể thử gọi endpoint bằng curl hoặc Postman để đảm bảo logic backend hoạt động bình thường.
curl http://localhost:8000/
Lưu ý quan trọng khi triển khai vào sản xuất
Quá trình tự động hóa ở trên là nền tảng tốt, nhưng để đưa vào môi trường sản xuất thực tế, bạn cần lưu ý thêm một số điểm. Thứ nhất, về vấn đề bảo mật, không bao giờ lưu token hay mật khẩu trực tiếp trong file code hoặc file Dockerfile. Luôn sử dụng GitHub Secrets hoặc các hệ thống quản lý bí mật như HashiCorp Vault. Thứ hai, đối với ứng dụng FastAPI, mặc dù Uvicorn rất tốt, nhưng trong môi trường product, bạn thường cần một reverse proxy phía trước như Nginx hoặc Traefik để xử lý SSL/TLS, cân bằng tải và caching.
Hơn nữa, khi chạy trong Docker, ứng dụng Python nên được cấu hình để chạy với nhiều worker (thông qua gunicorn hoặc uvicorn-worker) nếu bạn đang xử lý các tác vụ nặng, vì mặc định FastAPI chỉ chạy 1 worker. Tuy nhiên, đối với các tác vụ I/O bound như gọi API bên ngoài, việc sử dụng async đúng cách của FastAPI thường đủ mạnh để xử lý lượng lớn request mà không cần tăng số lượng worker quá nhiều.
Đừng quên cấu hình logging. Trong môi trường container, log sẽ được stdout/stderr. Bạn nên cấu hình FastAPI để ghi log vào stdout và sử dụng các công cụ như Loki, ELK stack hoặc CloudWatch để thu thập và phân tích log này sau này. Việc quản lý log trong container là yếu tố then chốt để debug sự cố khi hệ thống gặp lỗi trên server thật.
Kết luận
Việc kết hợp FastAPI, Docker và GitHub Actions tạo nên một quy trình phát triển phần mềm hiện đại, linh hoạt và hiệu quả. Bằng cách tự động hóa quy trình build và deploy, bạn không chỉ tiết kiệm được rất nhiều thời gian mà còn giảm thiểu các lỗi do con người gây ra trong quá trình thao tác thủ công. Kiến trúc Multi-stage build giúp bạn sở hữu những image container nhẹ nhàng, nhanh chóng khởi động, trong khi GitHub Actions đảm bảo rằng mọi thay đổi trong code đều được kiểm thử và đưa vào kho lưu trữ container một cách tức thì.
Hành trình từ một script Python đơn giản đến một dịch vụ web được container hóa và tự động triển khai là bước đệm quan trọng để bạn tiếp cận với các mô hình DevOps và Cloud Native. Hy vọng hướng dẫn chi tiết này sẽ giúp bạn tự tin thiết lập môi trường phát triển chuyên nghiệp cho các dự án FastAPI của mình. Hãy tiếp tục khám phá, thử nghiệm và tối ưu hóa pipeline của bạn để phù hợp nhất với quy mô và nhu cầu thực tế của dự án.