Files
vps_setup/apisix-stack/README.MD
T
2026-06-18 00:01:35 +07:00

15 KiB

Chỉnh sysctl trên máy chủ Host (máy Linux chạy Docker) để TCP Stack hoạt động tốt hơn.

Cho phép tái sử dụng các socket đang ở trạng thái TIME_WAIT

sudo sysctl -w net.ipv4.tcp_tw_reuse=1

Tăng hàng đợi kết nối (Backlog)

sudo sysctl -w net.core.somaxconn=65535

Lưu lại cấu hình (để khi reboot không mất)

echo "net.ipv4.tcp_tw_reuse=1" | sudo tee -a /etc/sysctl.conf echo "net.core.somaxconn=65535" | sudo tee -a /etc/sysctl.conf


Hệ Thống Cổng Kết Nối API Trung Gian (API Gateway) Cho Dynamics 365 Dataverse

Tài liệu này hướng dẫn cách triển khai, cấu hình và vận hành hệ thống API Gateway tự host (Self-hosted) sử dụng Apache APISIX để cung cấp (expose) API của Microsoft Dynamics 365 Dataverse bảo mật cho bên thứ ba. Hệ thống tích hợp sẵn cụm quản lý log tập trung siêu nhẹ: Grafana Loki + Promtail + Grafana.

📌 Kiến Trúc Hệ Thống

[Bên Thứ 3] ---> (Port: 9080 kèm API Key) ---> [APISIX Gateway] ---> (OAuth2 Token) ---> [Dataverse Web API]
                                                      |
                                           (Docker Stdout JSON Log)
                                                      |
                                                 [Promtail] ---> [Grafana Loki] <--- [Grafana UI]

🛠️ Danh Sách Thành Phần & Cổng Kết Nối

Thành phần Cổng công khai (Host Port) Vai trò Tài khoản mặc định
APISIX Gateway 9080 (HTTP) / 9180 (Admin API) Xử lý traffic, giới hạn lượt gọi, gán token bảo mật -
APISIX Dashboard 9090 Giao diện cấu hình Route, Plugin trực quan admin / admin
Grafana 9300 Giao diện truy vấn và giám sát log thời gian thực admin / admin
Loki 9100 Cơ sở dữ liệu lưu trữ log tập trung (Siêu nhẹ) -
etcd Không expose (Chỉ chạy nội bộ) Cơ sở dữ liệu phân tán lưu cấu hình của APISIX Bảo mật nội bộ container

🚀 Hướng Dẫn Triển Khai Nhanh

Bước 1: Chuẩn bị cấu trúc thư mục

Tạo cấu trúc thư mục trên máy chủ của bạn như sau:

apisix-stack/
├── docker-compose.yml
├── apisix_conf/
│   ├── config.yaml
│   ├── dashboard_conf.yaml
│   └── schema.json
├── promtail_conf/
│   └── promtail-config.yaml
└── grafana_conf/
    └── provisioning/
        ├── datasources/
        │   └── datasources.yaml
        └── dashboards/
            ├── dashboards.yaml
            └── apisix_performance.json

Bước 2: Khởi chạy cụm Docker

Tại thư mục gốc (apisix-stack/), chạy lệnh sau để tải và chạy các dịch vụ:

docker compose up -d

⚙️ Cấu Hình Kết Nối Dynamics 365 Dataverse

Bước 1: Đăng ký Ứng dụng trên Microsoft Entra ID (Azure AD)

  1. Truy cập vào Microsoft Entra ID > App registrations > New registration.
  2. Đặt tên (ví dụ: APISIX-Dataverse-Proxy) và bấm Register.
  3. Lưu lại thông tin: Application (client) ID và Directory (tenant) ID.
  4. Vào mục Certificates & secrets > New client secret > Tạo một Secret mới và sao chép lại Value của secret này.
  5. Vào mục API permissions > Add a permission > Chọn Dynamics CRM > Tích chọn user_impersonation và bấm Add permissions. Sau đó bấm Grant admin consent cho tenant của bạn.

Bước 2: Tạo Application User trong Dataverse

  1. Truy cập vào Power Platform Admin Center.
  2. Chọn Môi trường (Environment) của bạn > Settings > Users + permissions > Application users.
  3. Bấm New app user > Add an app > Chọn ứng dụng bạn vừa đăng ký ở Bước 1.
  4. Chọn Business unit và gán Security Role phù hợp để giới hạn quyền truy cập bảng dữ liệu (Table).

Bước 3: Cấu hình Route trên APISIX Dashboard

Truy cập vào giao diện quản lý APISIX tại địa chỉ: http://<IP-Cua-Ban>:9090.

1. Tạo Consumer (Cấp API Key cho bên thứ ba)

  • Vào mục Consumers > Bấm Create.
  • Đặt tên định danh (Username), ví dụ: doi-tac-A.
  • Ở phần Plugin, bật plugin key-auth và điền một chuỗi bí mật vào ô key (ví dụ: partner-secret-key-2026).

2. Tạo Route (Cấu hình luồng đi của API)

  • Vào mục Routes > Bấm Create.
  • Định nghĩa đường dẫn (Request): Path: /ssm-vn-dev/*
  • Cấu hình Đích đến (Upstream): Chọn Custom Upstream. Node URL: https://tngredev.crm5.dynamics.com (Port: 443).
  • Cấu hình Plugins:
    • Cấu hình Plugins xác thực (Chọn một trong hai phương án sau tùy theo nhu cầu):
      • Phương án A: Sử dụng OpenID Connect (Azure AD / Microsoft Entra ID) để bảo mật API đầu vào
        • Bật plugin openid-connect để APISIX đóng vai trò xác thực Token từ Client gửi lên:
          • client_id: <Mã Application (client) ID>
          • client_secret: <Mã Client Secret Value>
          • discovery: https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration
          • bearer_only: true (Chỉ chấp nhận Bearer Token ở header Authorization, không tự động redirect sang trang web login)
      • Phương án B: Xác thực Client bằng API Key (key-auth)
        • Bật plugin key-auth trên cấu hình Route. Giá trị cấu hình rất đơn giản, chỉ cần truyền một đối tượng rỗng:
          "key-auth": {}
          
          (Mặc định APISIX sẽ tìm header tên là apikey gửi từ Client. Nếu bạn muốn đổi tên header tùy biến, ví dụ X-API-Key, bạn nhập: {"header": "X-API-Key"}).
        • Lưu ý: Vì plugin openid-connect trong APISIX chỉ hỗ trợ xác thực đầu vào (Incoming Auth), nếu bạn muốn APISIX tự động lấy Token từ Azure AD (qua Client Credentials Flow) để đính kèm khi gọi sang Dynamics 365 Dataverse (Outbound Token Injection), bạn cần sử dụng một cơ chế bổ trợ (như viết Custom Lua Script trong plugin serverless-pre-function hoặc dùng service phụ qua plugin forward-auth).
    • Bật plugin rate-limiting: Thiết lập giới hạn request phù hợp để bảo vệ Dataverse.

🔑 Hướng dẫn cấu hình chi tiết Phương án B (API Key + Auto Inject Dynamics 365 Token)

Phương án này cho phép Client chỉ cần truyền API Key đơn giản khi gọi Gateway (qua header apikey của plugin key-auth), sau đó APISIX Gateway sẽ tự động thực hiện Client Credentials Flow với Azure AD để lấy Access Token, cache lại trong bộ nhớ RAM của Gateway và đính kèm vào header Authorization: Bearer <token> trước khi chuyển tiếp request tới Dynamics 365.

Bước 1: Khai báo bộ nhớ cache dùng chung (lua_shared_dict)

Để lưu trữ và tái sử dụng token (hạn dùng của Microsoft token là 1 tiếng), tránh việc mỗi request gửi lên lại phải gọi API xin token mới làm chậm hệ thống, ta cần cấp phát bộ nhớ cache trong apisix_conf/config.yaml:

nginx_config:
  custom_lua_shared_dict:
    oauth_cache: 1m  # Cấp 1MB bộ nhớ để cache access token

Bước 2: Thiết lập Route với plugin key-auth và serverless-pre-function

Dưới đây là lệnh curl cấu hình Route /ssm-vn-dev/* tích hợp xác thực API Key đầu vào và tự lấy/đính kèm Token đầu ra thông qua Custom Lua Script:

curl -i http://localhost:9180/apisix/admin/routes/ssm-vn-dev-route \
  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
  -H 'Content-Type: application/json' \
  -X PUT \
  -d '{
    "uri": "/ssm-vn-dev/*",
    "name": "ssm-vn-dev-route",
    "plugins": {
      "key-auth": {},
      "serverless-pre-function": {
        "phase": "access",
        "functions": [
          "return function(conf, ctx) local core = require(\"apisix.core\"); local http = require(\"resty.http\"); local cache = ngx.shared.oauth_cache; if not cache then core.log.error(\"oauth_cache not defined in config.yaml\"); return end; local token = cache:get(\"dataverse_token\"); if not token then local httpc = http.new(); httpc:set_timeout(5000); local tenant_id = \"<YOUR_TENANT_ID>\"; local client_id = \"<YOUR_CLIENT_ID>\"; local client_secret = \"<YOUR_CLIENT_SECRET>\"; local scope = \"https://<YOUR_ORG>.crm.dynamics.com/.default\"; local body = \"grant_type=client_credentials&client_id=\" .. ngx.escape_uri(client_id) .. \"&client_secret=\" .. ngx.escape_uri(client_secret) .. \"&scope=\" .. ngx.escape_uri(scope); local res, err = httpc:request_uri(\"https://login.microsoftonline.com/\" .. tenant_id .. \"/oauth2/v2.0/token\", { method = \"POST\", body = body, headers = { [\"Content-Type\"] = \"application/x-www-form-urlencoded\" }, ssl_verify = false }); if not res or res.status ~= 200 then core.log.error(\"Failed to fetch Azure AD token: \", err or (res and res.body)); return core.response.exit(500, { error = \"Auth failed to upstream provider\" }) end; local data, decode_err = core.json.decode(res.body); if not data or not data.access_token then core.log.error(\"Failed to decode token response\"); return core.response.exit(500, { error = \"Invalid token response\" }) end; token = data.access_token; local expires_in = data.expires_in or 3600; cache:set(\"dataverse_token\", token, expires_in - 60) end; ngx.req.set_header(\"Authorization\", \"Bearer \" .. token) end"
        ]
      },
      "rate-limiting": {
        "count": 100,
        "time_window": 60,
        "rejected_code": 429
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "tngredev.crm5.dynamics.com:443": 1
      },
      "pass_host": "node",
      "scheme": "https"
    }
  }'

Important

Giải thích tham số cấu hình Upstream:

  • pass_host: "node": Bắt buộc cấu hình trường này để APISIX ghi đè giá trị header Host gửi tới Dynamics 365 trùng khớp với node URL (<YOUR_ORG>.crm.dynamics.com). Nếu thiếu, Dynamics 365 sẽ từ chối xử lý request vì host header không khớp.
  • Các tham số <YOUR_TENANT_ID>, <YOUR_CLIENT_ID>, <YOUR_CLIENT_SECRET>, <YOUR_ORG> trong đoạn mã Lua cần được thay thế bằng thông tin môi trường Azure AD và Dynamics 365 thực tế của bạn.

Chi tiết kịch bản script Lua chạy trong APISIX:

  1. Kiểm tra Cache: Truy cập vào ngx.shared.oauth_cache tìm xem có token dataverse_token nào chưa hết hạn không.
  2. Nếu chưa có: Kích hoạt một cuộc gọi HTTP POST không đồng bộ (non-blocking) tới Endpoint của Azure AD sử dụng thư viện resty.http.
  3. Lưu trữ: Giải mã JSON trả về, lấy Access Token và lưu lại vào cache với thời gian sống (TTL) ngắn hơn thời hạn thật của token 60 giây (để tránh rủi ro token hết hạn trong lúc đang truyền tin).
  4. Gán Header: Ghi đè header Authorization: Bearer <token> bằng lệnh ngx.req.set_header.

📊 Hướng Dẫn Giám Sát Và Xem Log Tập Trung (Grafana Loki)

Note

Hệ thống đã được cấu hình tự động kết nối (provisioning) giữa Grafana và Loki làm Data Source mặc định, đồng thời tự động import sẵn Dashboard theo dõi hiệu năng APISIX. Bạn không cần phải cấu hình thủ công sau khi khởi chạy container.

Bước 1: Xem Dashboard giám sát hiệu năng APISIX (Real-time Dashboard)

  1. Truy cập vào Grafana UI: http://<IP-Cua-Ban>:9300 (Tài khoản mặc định: admin / admin).
  2. Vào phần Dashboards từ menu bên trái.
  3. Chọn dashboard có tên là APISIX Gateway Performance (trong thư mục APISIX API Gateway).
  4. Tại đây bạn sẽ thấy các biểu đồ trực quan hóa thời gian thực được phân tích từ log của Loki:
    • Request Rate (RPS): Tốc độ xử lý request mỗi giây.
    • Average Response Time (Latency): Thời gian phản hồi trung bình (Độ trễ).
    • HTTP Status Distribution: Phân bố mã trạng thái phản hồi (2xx, 3xx, 4xx, 5xx).
    • Data Egress (Throughput): Băng thông dữ liệu trả về từ Gateway.
    • APISIX Gateway Logs: Dòng log thời gian thực chạy trực tiếp bên dưới.

Bước 2: Xem log thời gian thực (Live Stream Log) trong Grafana Explore

  1. Bấm vào biểu tượng Explore (Hình la bàn bên thanh công cụ trái).
  2. Đảm bảo đã chọn Data Source là Loki.
  3. Tại ô nhập câu lệnh truy vấn (Query) nhập:
    {container="apisix-gateway"}
    
  4. Bấm Run query và chọn Live để bắt đầu theo dõi dòng log thời gian thực.

🛡️ Cấu hình ghi Log truy cập tự động kèm Request/Response Body sang Loki

Mặc định, log stdout của APISIX Gateway không chứa nội dung Request/Response Body để tối ưu hiệu năng. Nếu bạn muốn tự động ghi nhận lại toàn bộ Payload (Request/Response Body) gửi từ bên thứ ba và đẩy trực tiếp sang Loki, thực hiện cấu hình dưới đây:

Hướng dẫn thiết lập qua APISIX Admin API

(Lưu ý: Vì phiên bản APISIX Dashboard (v3.0.1) ra đời trước khi plugin loki-logger có schema hiển thị đồ họa trên UI, nên plugin này sẽ không hiển thị trong danh sách Plugin Market của giao diện Dashboard. Tuy nhiên, APISIX Gateway (v3.11.0) hỗ trợ hoàn toàn plugin này. Bạn hãy cấu hình trực tiếp qua Admin API bằng lệnh curl dưới đây để kích hoạt ghi log toàn cục:) Chạy lệnh curl sau trực tiếp trên VPS để cấu hình plugin loki-logger toàn cục:

curl -i http://localhost:9180/apisix/admin/global_rules/1 \
  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
  -H 'Content-Type: application/json' \
  -X PUT \
  -d '\''{
    "plugins": {
      "loki-logger": {
        "endpoint_addrs": ["http://loki:3100"],
        "include_req_body": true,
        "include_resp_body": true,
        "log_labels": {
          "job": "apisix-gateway"
        }
      }
    }
  }'\''

📊 Xem và truy vấn Payload log (Request/Response Body) trong Grafana

  1. Truy cập Grafana UI (http://<IP-Cua-Ban>:9300) > Explore.
  2. Chọn Data Source là Loki.
  3. Nhập câu lệnh LogQL sau để lọc log được gửi trực tiếp từ plugin loki-logger:
    {job="apisix-gateway"}
    
  4. Để định dạng (parse) log JSON và hiển thị trực quan các trường Request/Response, bạn có thể thêm bộ phân tích cú pháp bằng cách nhập truy vấn sau:
    {job="apisix-gateway"} | json
    
  5. Click vào bất kỳ dòng log nào bên dưới kết quả. Bạn sẽ thấy các trường dữ liệu được phân tích cấu trúc, bao gồm:
    • request.body: Chứa toàn bộ nội dung Request payload từ Client.
    • response.body hoặc resp_body: Chứa toàn bộ nội dung Response payload trả về từ phía Dataverse.