0

Series Docker & Nginx cho Laravel #1: Kiến trúc tách biệt (Decoupling) và Cấu hình "Người gác cổng"

Sau khi đã trang bị "Lõi nhớ siêu tốc" Redis và hệ thống giám sát ELK Stack, hạ tầng Backend của chúng ta đã có một nội tại cực kỳ sung mãn. Tuy nhiên, để hệ thống AFC Metro có thể hứng chịu hàng ngàn lượt kết nối đồng thời từ các thiết bị ngoại vi (như máy bán vé TVM hay cổng kiểm soát FAM) gửi về mà không bị nghẽn mạng, chúng ta cần một "Người gác cổng" thực thụ.

Đó chính là Nginx.

Nhiều người chỉ coi Nginx là một công cụ để chạy code PHP lên trình duyệt. Nhưng dưới góc độ kiến trúc Senior, Nginx là lớp lá chắn đầu tiên, chặn đứng các request rác, phân tải và tối ưu hóa tài nguyên trước khi bất kỳ dòng code Laravel nào kịp chạy. Hôm nay, chúng ta sẽ xây dựng cổng thành này bằng Docker!

1. Sai lầm của kiến trúc "Nhồi nhét" (Monolithic Container)

Lỗi kinh điển nhất khi đưa Laravel lên Docker là tìm một Image có sẵn chứa cả Nginx, PHP và đôi khi cả... MySQL chung vào một Container duy nhất.

Cách làm này vi phạm nghiêm trọng nguyên tắc Separation of Concerns (Tách biệt mối quan tâm) của DevOps:

  • Nếu Nginx bị sập, toàn bộ tiến trình PHP cũng chết theo.
  • Bạn không thể scale (nhân bản) riêng rẽ. Nếu lượng truy cập API đồng bộ giao dịch đột nhiên tăng vọt, bạn chỉ muốn tăng thêm số lượng worker PHP để tính toán, chứ không cần thêm Nginx. Nếu nhét chung, bạn phải nhân bản toàn bộ, cực kỳ lãng phí RAM.

Chuẩn Senior: Kiến trúc chuẩn bắt buộc phải chia thành 2 Container giao tiếp với nhau qua mạng nội bộ:

  1. Container Nginx (Web Server): Chỉ làm nhiệm vụ nhận Request, phục vụ file tĩnh (hình ảnh, css, js) cực nhanh, và làm Proxy chuyển tiếp (Forward) các Request cần xử lý logic sang cho PHP.
  2. Container PHP-FPM (App Server): Trái tim xử lý nghiệp vụ Laravel. Hoàn toàn không biết Internet là gì, chỉ ngồi đợi Nginx ném việc vào qua cổng 9000.

2. Thiết kế bản vẽ Docker Compose

Hãy tạo file docker-compose.yml để định nghĩa hai container này. Điểm mấu chốt ở đây là việc chia sẻ Volume thư mục mã nguồn.

version: '3.8'

services:
  # 1. Container xử lý code PHP (App Server)
  app:
    build:
      context: .
      dockerfile: Dockerfile.php
    container_name: afc_php_fpm
    restart: unless-stopped
    working_dir: /var/www/html
    volumes:
      # Map toàn bộ source code Laravel vào trong container
      - ./:/var/www/html
    networks:
      - afc_network

  # 2. Container Nginx (Web Server)
  web:
    image: nginx:alpine
    container_name: afc_nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      # Nginx BẮT BUỘC phải nhìn thấy thư mục public của Laravel để phục vụ file tĩnh
      - ./:/var/www/html
      # Nạp file cấu hình Nginx chuẩn vào container
      - ./nginx/conf.d/:/etc/nginx/conf.d/
    depends_on:
      - app
    networks:
      - afc_network

networks:
  afc_network:
    driver: bridge

Lưu ý: Nginx cần được map đúng thư mục chứa source code y hệt như Container PHP. Tại sao? Vì khi có một Request xin file tĩnh (ví dụ: logo-metro.png), Nginx sẽ tự mình xuống thư mục public tìm và trả về luôn với tốc độ cực nhanh, không thèm gọi hay làm phiền đến PHP.

3. Cấu hình Nginx chuẩn Security & Performance

Tiếp theo, tạo một thư mục nginx/conf.d trong project của bạn, và tạo file default.conf bên trong nó. Đây chính là "bộ luật" của người gác cổng.

server {
    listen 80;
    server_name localhost;
    
    # Trỏ thẳng vào thư mục public của Laravel
    root /var/www/html/public;
    index index.php index.html;

    # Ghi log chuẩn xác
    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    # 1. BẢO MẬT: Chặn truy cập vào các file ẩn (như .env, .git)
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    # 2. XỬ LÝ ĐỊNH TUYẾN (Routing) CHO LARAVEL
    location / {
        # Luật quan trọng nhất: Thử tìm file tĩnh. Nếu không có file tĩnh, 
        # chuyển toàn bộ request về index.php để Laravel phân luồng Router
        try_files $uri $uri/ /index.php?$query_string;
    }

    # 3. GIAO TIẾP VỚI PHP-FPM
    location ~ \.php$ {
        # Đảm bảo file index.php có tồn tại trước khi đẩy đi
        try_files $uri =404;

        # Trỏ cổng chuyển việc sang Container "app" (PHP-FPM) ở cổng 9000
        fastcgi_pass app:9000;
        
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Tối ưu Timeout để tránh 504 Gateway Timeout khi xử lý file lớn
        fastcgi_read_timeout 60s;
        fastcgi_send_timeout 60s;
    }

    # 4. TỐI ƯU HIỆU NĂNG CHO FILE TĨNH (Cache Client)
    location ~* \.(jpg|jpeg|gif|png|css|js|ico|svg|woff2|woff|ttf)$ {
        expires 365d;
        access_log off;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
}

4. Giải mã các chốt chặn trong cấu hình

Bản cấu hình trên đã bao gồm những best-practices khắt khe nhất:

  • Chốt chặn bảo mật (location ~ /. { deny all; }): Một trong những lỗi sơ đẳng và nguy hiểm nhất khiến hàng loạt server bị hack là để lộ file .env. Đoạn cấu hình này chỉ thị Nginx "thẳng tay tàn sát" (trả về 403 Forbidden) bất kỳ request nào cố tình truy cập vào các file bắt đầu bằng dấu chấm, tiêu diệt nguy cơ từ trong trứng nước.
  • Trái tim của Laravel (try_files $uri $uri/ /index.php?$query_string;): Mọi URL ảo mà bạn định nghĩa trong file routes/api.php hay routes/web.php thực chất không tồn tại trên ổ cứng. Nhờ câu lệnh này, Nginx gom tất cả những URL đó và nhét vào cổng chính index.php để Laravel tự lo liệu.
  • Offload File Tĩnh (location ~* .(jpg...): Khi màn hình máy bán vé TVM tải giao diện, nó cần hình ảnh và CSS. Nginx sẽ đính kèm header Cache-Control lên tới 1 năm, ép thiết bị trạm tự lưu (cache) file đó vào bộ nhớ. Lần sau, thiết bị sẽ không cần gọi lên Server xin lại file nữa, giúp giảm đến 70% băng thông tải mạng.

Kết thúc Bài 1, chúng ta đã thiết lập thành công mô hình Client-Server hiện đại. Kẻ gửi request giờ đây không còn được phép "chạm mặt" trực tiếp với Laravel nữa, mọi thứ đều phải bước qua xác của Nginx. Dữ liệu tĩnh được bóc tách xử lý riêng, còn nghiệp vụ logic được đóng gói kín đáo bên trong Container PHP.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí