🇬🇧 Read the English version of this article on tanhdev.com
Answer-first: Triển khai GraphHopper trên Kubernetes chuẩn production yêu cầu cấu hình StatefulSet với SSD PVC lưu trữ OSM PBF và CH cache, cấp phát 768MB JVM Heap cùng 4GB off-heap Direct Memory (mmap), thiết lập Readiness Probe 600s bảo vệ giai đoạn CH pre-processing, và áp dụng chiến lược Blue-Green để cập nhật bản đồ không downtime.
GraphHopper là một cỗ máy định tuyến (routing engine) mã nguồn mở mạnh mẽ — hỗ trợ thuật toán Rút Ngọn Thứ Bậc (Contraction Hierarchies - CH) cho các câu truy vấn phản hồi dưới 1 mili-giây, hồ sơ phương tiện tùy chỉnh (custom vehicle profiles), quy định cấm rẽ (turn restrictions) và tích hợp dữ liệu bản đồ OpenStreetMap. Thách thức lớn nhất đối với các đội ngũ kỹ thuật không phải là thuật toán, mà là việc vận hành và triển khai trên Kubernetes: nạp tập tin OSM PBF dung lượng lớn, tối ưu hóa bộ nhớ JVM, xử lý giai đoạn tiền xử lý đồ thị CH (CH pre-processing), và cập nhật dữ liệu bản đồ mà không gây gián đoạn hệ thống (zero-downtime).
Bài viết này hướng dẫn chi tiết cách triển khai GraphHopper chuẩn production trên Kubernetes với dữ liệu OpenStreetMap. Bạn sẽ nắm được cách thiết lập StatefulSet/Deployment với lưu trữ bền vững (persistent OSM graph files), cấu hình tài nguyên JVM chuẩn xác, thiết lập liveness và readiness probes cho giai đoạn CH pre-processing, và chiến lược cập nhật bản đồ 0% downtime.
Để so sánh các thuật toán định tuyến và tích hợp API, bạn có thể xem bài viết GraphHopper và CARTO: Bộ Máy Điều Phối Lộ Trình Giao Hàng cùng chuyên mục trong series Routing & Geospatial Architecture.
Tại Sao Nên Tự Triển Khai GraphHopper? So Sánh Chi Phí Với Cloud API
Trước khi quyết định tự triển khai hệ thống (self-hosted deployment), hãy so sánh các phương án:
| Hệ Đo Lường | Tự Triển Khai GraphHopper | GraphHopper Cloud API | Google Maps Routes API |
|---|---|---|---|
| Chi phí mỗi truy vấn | Chi phí hạ tầng cố định (Infrastructure) | ~$0.005–0.008/request | $0.005–0.010/request |
| Tại mốc 1 Triệu queries/ngày | ~$200–400/tháng (K8s) | ~$5,000–8,000/tháng | ~$5,000–10,000/tháng |
Trích Xuất Dữ Liệu OSM PBF: Phạm Vi Quốc Gia, Khu Vực Hoặc Bounding Box
Dữ liệu OpenStreetMap được phân phối miễn phí theo định dạng PBF (Protocol Buffer Format). Bạn có thể tải các bản trích xuất bản đồ từ Geofabrik (máy chủ mirror chính).
Bảng Dung Lượng Dữ Liệu Và Yêu Cầu Tài Nguyên
| Khu Vực | Kích Thước File PBF | Bộ Nhớ RAM Cho CH Graph (Car profile) |
|---|---|---|
| Việt Nam | ~170 MB | ~2–3 GB |
| Đông Nam Á (Southeast Asia) | ~1.2 GB | ~15–20 GB |
| Nhật Bản (Japan) | ~1.0 GB | ~12–18 GB |
| Đức (Germany) | ~3.5 GB | ~40–60 GB |
| Toàn Châu Âu (Europe) | ~28 GB | ~300+ GB |
Đối với các ứng dụng logistics hoạt động trong phạm vi một quốc gia, bạn nên tải tệp dữ liệu trích xuất theo quốc gia (country-level extract). Nếu ứng dụng hoạt động trên phạm vi nhiều quốc gia, hãy chọn bản trích xuất theo khu vực (regional extract). Tránh sử dụng tệp dữ liệu toàn cầu (global planet file) (dung lượng nén >90 GB), trừ khi hệ thống máy chủ của bạn được trang bị tối thiểu 512 GB RAM trở lên để phục vụ quá trình tiền xử lý đồ thị (graph pre-processing).
Lệnh Tải Dữ Liệu OSM Data
# Tải dữ liệu bản đồ Việt Nam
wget https://download.geofabrik.de/asia/vietnam-latest.osm.pbf
# Tải dữ liệu khu vực Đông Nam Á (South-East Asia - bao gồm Việt Nam, Thái Lan, Indonesia, Philippines...)
wget https://download.geofabrik.de/asia/south-east-asia-latest.osm.pbf
# Kiểm tra tính toàn vẹn của tệp đã tải (verify integrity)
wget https://download.geofabrik.de/asia/vietnam-latest.osm.pbf.md5
md5sum -c vietnam-latest.osm.pbf.md5
Lưu tệp PBF vào một PersistentVolumeClaim (PVC) trong Kubernetes — tuyệt đối không đóng gói tệp PBF trực tiếp vào trong container image. Tệp OSM có dung lượng rất lớn sẽ làm tăng kích thước của container image không cần thiết và dữ liệu bản đồ cũng cần lưu trữ bền vững qua các lần restart Pod.
Docker Image Của GraphHopper (The GraphHopper Docker Image): Đóng Image Tự Chế Hay Dùng Official Image
GraphHopper cung cấp sẵn Docker image chính thức trên GitHub Container Registry:
docker pull ghcr.io/graphhopper/graphhopper:latest
Đối với môi trường production, bạn nên gắn thẻ (tag) phiên bản cụ thể thay vì dùng latest:
docker pull ghcr.io/graphhopper/graphhopper:10.0
Tùy Chỉnh Cấu Hình Qua ConfigMap
Tạo tập tin cấu hình graphhopper.yml tối giản cho môi trường production:
# graphhopper.yml
graphhopper:
# Đường dẫn tới file OSM PBF bên trong container (gắn qua PVC)
datareader.file: /data/osm/vietnam-latest.osm.pbf
# Thư mục chứa CH graph cache
graph.location: /data/graph-cache
# Các profile phương tiện được hỗ trợ
profiles:
- name: car
vehicle: car
weighting: fastest
- name: motorcycle
vehicle: motorcycle
weighting: fastest
- name: bike
vehicle: bike
weighting: fastest
ch.profiles: car,motorcycle,bike
# Cấu hình Server ports
server:
application_connectors:
- type: http
port: 8989
admin_connectors:
- type: http
port: 8990
import.osm.ignored.highways: ""
Tạo Kubernetes ConfigMap từ tập tin cấu hình:
kubectl create configmap graphhopper-config \
--from-file=graphhopper.yml=./graphhopper.yml \
-n logistics
Kiến Trúc Kubernetes PersistentVolume Cho Dữ Liệu OSM Graph
GraphHopper yêu cầu hai (2) thư mục chính cần lưu trữ lâu dài (persistent directories):
- Thư mục chứa OSM PBF (
/data/osm/): Chứa tập tin dữ liệu bản đồ OSM thô. - Thư mục Graph Cache (
/data/graph-cache/): Chứa các tập tin CH graph đã được xử lý xong (lưu trữ lại qua các lần khởi động lại Pod để tránh phải build lại từ đầu).
flowchart TD
PVC["PersistentVolumeClaim: graphhopper-data (30Gi RWO)"]
PVC --> OSM["/data/osm/vietnam-latest.osm.pbf"]
PVC --> GRAPH["/data/graph-cache/ (CH graphs)"]
POD["GraphHopper Pod"]
POD --> PVC
POD --> CM["ConfigMap: graphhopper-config"]
SVC["Service: graphhopper-svc :8989"] --> POD
Cấu Hình PersistentVolumeClaim
# graphhopper-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: graphhopper-data
namespace: logistics
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 30Gi # Áp dụng cho Việt Nam: OSM PBF (~170MB) + CH graphs (~3GB) + bộ nhớ đệm
storageClassName: ssd # Sử dụng SSD để tăng tốc độ nạp đồ thị
Phương Pháp Tải File OSM PBF Vào PVC
Sử dụng Pod tạm thời để sao chép file OSM vào PVC:
# Tạo Pod uploader tạm thời
kubectl run osm-uploader --image=alpine --restart=Never \
--overrides='{"spec":{"volumes":[{"name":"data","persistentVolumeClaim":{"claimName":"graphhopper-data"}}],"containers":[{"name":"uploader","image":"alpine","command":["sleep","3600"],"volumeMounts":[{"name":"data","mountPath":"/data"}]}]}}' \
-n logistics
# Chờ Pod sẵn sàng
kubectl wait --for=condition=ready pod/osm-uploader -n logistics
# Sao chép file OSM PBF vào PVC
kubectl cp vietnam-latest.osm.pbf logistics/osm-uploader:/data/osm/vietnam-latest.osm.pbf
# Dọn dẹp Pod tạm
kubectl delete pod osm-uploader -n logistics
Tối Ưu RAM Và JVM (RAM and JVM Tuning): Thiết Lập Requests/Limits Cho CH Graphs Trên Kubernetes
Nguyên nhân chính khiến GraphHopper bị sập hoặc gặp sự cố trên Kubernetes chính là lỗi Out Of Memory (OOMKill) của JVM.
Yêu Cầu Bộ Nhớ (Memory Requirements)
GraphHopper nạp toàn bộ dữ liệu đồ thị CH (Contraction Hierarchies) vào bộ nhớ RAM bằng cơ chế memory-mapped files qua MappedByteBuffer của Java. Nguồn bộ nhớ này nằm ngoài vùng Heap (off-heap memory) — không tính vào dung lượng Heap JVM (-Xmx) nhưng lại chiếm dụng giới hạn bộ nhớ (memory limit) của container.
Ví dụ với dữ liệu bản đồ Việt Nam cho các profile xe hơi (car), xe máy (motorcycle), xe đạp (bike):
- Bộ nhớ JVM Heap (
-Xmx): 512MB–1GB (phục vụ xử lý rác GC overhead và tính toán thuật toán routing) - Bộ nhớ CH Graph mmap (off-heap): ~2–3 GB
- Giới hạn bộ nhớ Container (Memory Limit):
-Xmx+ dung lượng Graph + 512MB buffer đệm = 4–5 GB
Cấu Hình JVM Configuration
Thiết lập các tham số JVM thông qua biến môi trường JAVA_OPTS:
env:
- name: JAVA_OPTS
value: >-
-Xmx768m
-Xms256m
-XX:+UseG1GC
-XX:MaxDirectMemorySize=4g
-XX:+ExitOnOutOfMemoryError
-Djava.nio.file.spi.DefaultFileSystemProvider=sun.nio.fs.UnixFileSystemProvider
Ý nghĩa các cờ cấu hình quan trọng:
-Xmx768m: Giới hạn bộ nhớ JVM Heap (dùng cho tính toán route và GC metadata)-XX:MaxDirectMemorySize=4g: Giới hạn dung lượng Direct Memory ngoài Heap (phục vụ mmap dữ liệu đồ thị)-XX:+ExitOnOutOfMemoryError: Buộc JVM thoát ngay khi gặp lỗi OOM thay vì rơi vào trạng thái degraded — giúp Kubernetes chủ động restart Pod một cách sạch sẽ và tức thì
Cấu Hình Tài Nguyên (Requests và Limits) Trên K8s
resources:
requests:
memory: "4Gi" # Đảm bảo Node có đủ bộ nhớ trống tối thiểu
cpu: "1000m" # 1 vCPU dành cho xử lý truy vấn CH (tùy chỉnh dựa theo lượng query rate)
limits:
memory: "5Gi" # 4GB graph mmap + 768MB heap + 512MB buffer
cpu: "4000m" # Cho phép bùng nổ (bursting) CPU trong quá trình xử lý CH pre-processing lúc khởi động
Lựa Chọn StatefulSet Hay Deployment Trên Kubernetes
GraphHopper yêu cầu dữ liệu PersistentVolume ở chế độ ReadWriteOnce (đơn ghi - single writer). Do đó trên Kubernetes, bạn không thể gắn 2 replicas của GraphHopper cùng lúc vào một PVC tại cùng một thời điểm.
Nên dùng StatefulSet khi:
- Mỗi replica cần một PVC riêng biệt (ví dụ từng node GraphHopper xử lý dữ liệu cho từng khu vực riêng).
- Cần định danh Pod cố định (stable pod names) cho mục đích service discovery.
Nên dùng Deployment khi:
- Bạn chỉ chạy 1 replica duy nhất (mô hình logistics phổ biến).
- Bạn sử dụng lưu trữ chia sẻ
ReadWriteMany(như NFS hay Ceph) hỗ trợ nhiều replicas đọc đồng thời.
Dưới đây là file manifest StatefulSet hoàn chỉnh cho môi trường production:
# graphhopper-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: graphhopper
namespace: logistics
spec:
serviceName: graphhopper
replicas: 1
selector:
matchLabels:
app: graphhopper
template:
metadata:
labels:
app: graphhopper
spec:
containers:
- name: graphhopper
image: ghcr.io/graphhopper/graphhopper:10.0
args: ["start", "/config/graphhopper.yml"]
ports:
- name: http
containerPort: 8989
- name: admin
containerPort: 8990
env:
- name: JAVA_OPTS
value: "-Xmx768m -Xms256m -XX:+UseG1GC -XX:MaxDirectMemorySize=4g -XX:+ExitOnOutOfMemoryError"
resources:
requests:
memory: "4Gi"
cpu: "1000m"
limits:
memory: "5Gi"
cpu: "4000m"
volumeMounts:
- name: data
mountPath: /data
- name: config
mountPath: /config
# Readiness Probe: GraphHopper chỉ báo sẵn sàng khi đã hoàn thành giai đoạn CH pre-processing
readinessProbe:
httpGet:
path: /health
port: 8990
initialDelaySeconds: 60 # Thời gian chờ ban đầu để graph bắt đầu khởi tạo
periodSeconds: 15
failureThreshold: 40 # 40 * 15s = 10 phút tối đa cho giai đoạn CH pre-processing
# Liveness Probe: Khởi động lại Pod nếu JVM bị treo hoặc ngưng hoạt động
livenessProbe:
httpGet:
path: /health
port: 8990
initialDelaySeconds: 300 # 5 phút chờ ban đầu cho lần khởi tạo CH pre-processing đầu tiên
periodSeconds: 30
failureThreshold: 3
volumes:
- name: config
configMap:
name: graphhopper-config
# Template PVC tự động cho từng replica
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 30Gi
storageClassName: ssd
Cập Nhật Dữ Liệu Bản Đồ OSM Không Downtime (Zero-Downtime Map Updates)
Dữ liệu OSM cần được cập nhật thường xuyên để cập nhật tuyến đường mới, biển cấm hoặc thay đổi luồng giao thông. Tần suất cập nhật trong hệ thống logistics thường diễn ra hàng tuần (Geofabrik cập nhật dữ liệu quốc gia hàng tuần).
Chiến Lược Blue-Green Cập Nhật Graph
Do chế độ ReadWriteOnce của PVC ngăn việc hai instance GraphHopper ghi chung vào một ổ đĩa, chúng ta sử dụng chiến lược Blue-Green:
flowchart LR
SVC["Service: graphhopper-svc"] -->|phân tuyến route tới| ACTIVE["StatefulSet: graphhopper-blue (ACTIVE)"]
STANDBY["StatefulSet: graphhopper-green (UPDATING)"]
subgraph "Quy Trình Cập Nhật"
DOWNLOAD["Tải file OSM PBF mới"] --> COPY["Sao chép vào PVC của green"]
COPY --> RESTART["Khởi động lại graphhopper-green"]
RESTART --> WAIT["Chờ Readiness Probe"]
WAIT --> SWITCH["Patch Service selector trỏ sang green"]
SWITCH --> CLEANUP["Dọn dẹp Blue cũ"]
end
Phương pháp này yêu cầu duy trì 2 StatefulSet với 2 PVC riêng biệt (Active và Standby). Quy trình cập nhật tự động bằng CronJob:
- Tải tập tin OSM PBF mới.
- Sao chép vào PVC của StatefulSet Standby.
- Khởi động lại Pod Standby và chờ Readiness Probe vượt qua.
- Thay đổi Service Selector để trỏ sang Pod Standby mới.
- Xóa bỏ Pod Active cũ hoặc chuyển thành Standby mới.
Để áp dụng quy trình GitOps quản lý cả CronJob và StatefulSet, hãy xem GitOps Ở Quy Mô Lớn (GitOps at Scale): Kubernetes & ArgoCD Cho Microservices.
Cấu Hình Health Probes Và Readiness Gates: Xử Lý Quá Trình CH Pre-Processing
Điểm hay mắc lỗi nhất là cấu hình initialDelaySeconds quá ngắn cho Readiness Probe. GraphHopper thực hiện các bước Khởi động:
- Tải tập tin OSM PBF (lần đầu: 2–5 phút cho bản đồ Việt Nam).
- Xây dựng đồ thị Contraction Hierarchies (lần đầu: 5–15 phút).
- Ghi dữ liệu CH graph ra đĩa.
- Nạp dữ liệu CH graph vào bộ nhớ (các lần khởi động tiếp theo: 1–3 phút).
Trong các lần khởi động lại sau (khi CH graph đã được cache sẵn), thời gian khởi động chỉ tốn 1–3 phút. Tuy nhiên ở lần đầu tiên khi đọc file OSM mới, quá trình có thể kéo dài 15–20 phút.
Vì vậy, cấu hình Readiness Probe nên dùng initialDelaySeconds: 60 kết hợp failureThreshold: 40 (tổng thời gian chờ tối đa 600 giây). Cấu hình Liveness Probe sử dụng initialDelaySeconds: 300 để tránh việc Kubernetes khởi động lại Pod trong khi đang thực hiện CH pre-processing.
Giám Sát GraphHopper Trên K8s Với Prometheus Và Grafana
Cổng Admin của GraphHopper phơi các chỉ số theo định dạng Dropwizard. Bạn có thể sử dụng Prometheus JMX Exporter hoặc cấu hình Prometheus ServiceMonitor:
# Cấu hình ServiceMonitor cho Prometheus Operator
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: graphhopper
namespace: logistics
spec:
selector:
matchLabels:
app: graphhopper
endpoints:
- port: admin
path: /metrics
interval: 30s
Các chỉ số quan trọng cần cảnh báo (Alerting Metrics):
graphhopper_routing_requests_total: Tổng lượng request routing (cảnh báo khi tỷ lệ lỗi > 0).graphhopper_routing_time_seconds: Độ trễ P99 tính toán route (cảnh báo nếu > 200ms).jvm_memory_used_bytes{area="heap"}: Dung lượng JVM Heap đã dùng (cảnh báo nếu > 85%-Xmx).process_resident_memory_bytes: Dung lượng RSS thực tế của tiến trình (cảnh báo khi tiến trình áp sát giới hạn memory limit của container).
Những Câu Hỏi Thường Gặp (FAQ)
GraphHopper tiêu tốn bao nhiêu RAM để phục vụ mạng lưới giao thông Việt Nam?
Với dữ liệu bản đồ Việt Nam và các profile car + motorcycle + bike, GraphHopper cần khoảng 3–4 GB off-heap memory cho dữ liệu đồ thị mmap, cộng thêm 512MB–1GB cho JVM Heap. Nên thiết lập giới hạn Memory Limit trên Kubernetes là 5 GB để đảm bảo an toàn. Với khu vực Đông Nam Á (Thái Lan, Indonesia, Philippines…), dung lượng RAM yêu cầu là 15–20 GB.
Có thể chạy GraphHopper trên Kubernetes với nhiều replicas không?
Có, nhưng có ràng buộc. Nếu dùng PVC ở chế độ ReadWriteOnce, bạn không thể mount 1 PVC cho nhiều Pods cùng lúc. Để scale ngang nhiều replicas, bạn cần dùng đĩa lưu trữ ReadWriteMany (NFS, Ceph) hoặc triển khai mỗi replica một StatefulSet/PVC riêng biệt tương ứng với từng khu vực địa lý.
Làm sao để cập nhật dữ liệu bản đồ OSM mà không bị downtime?
Sử dụng mô hình Blue-Green: duy trì 2 StatefulSet (Active và Standby). Bạn tiến hành tải file OSM mới và build lại đồ thị trên StatefulSet Standby. Khi Standby đã sẵn sàng (Readiness Probe chuyển sang Ready), tiến hành cập nhật Kubernetes Service Selector để chuyển hướng toàn bộ traffic sang Standby.
❓ Câu Hỏi Thường Gặp (FAQ)
Q1: Tự Tổ Chức Triển Khai GraphHopper trên Kubernetes với Dữ liệu OSM giải quyết vấn đề cốt lõi nào trong kiến trúc hệ thống?
Step-by-step guide to deploying GraphHopper on Kubernetes with OpenStreetMap data: Docker image, PVC for OSM PBF files, RAM tuning, and health probes.
Q2: Những lưu ý quan trọng nhất khi triển khai thực tế là gì?
Cần chú trọng phân tầng ranh giới trách nhiệm (bounded context), thiết lập cơ chế fallback dự phòng, và giám sát chặt chẽ qua metrics OpenTelemetry để phát hiện sớm các điểm nghẽn.
Q3: Làm sao để kiểm thử và đánh giá hiệu quả sau khi áp dụng?
Áp dụng kiểm thử tải (load test), benchmark độ trễ P95/P99 trước và sau triển khai, kết hợp tracing phân tán để xác minh tính ổn định dưới tải cao.
