Triển khai Zero-port với Cloudflare Tunnels trên Kubernetes

Khám phá cách triển khai ứng dụng Kubernetes an toàn ra Internet mà không cần mở Port (Zero-trust) bằng Cloudflare Tunnel, thay thế cho Ingress Controller và Load Balancer truyền thống.

Triển khai Zero-port với Cloudflare Tunnels trên Kubernetes

Trước đây, mình thường xuyên mở các dịch vụ ra ngoài Internet theo cách truyền thống: mở Port 443 trên Router, trỏ nó đến LoadBalancer của một Ingress Controller, thiết lập thêm vài rule fail2ban và hy vọng không có sự cố bảo mật nào xảy ra. Tuy nhiên, mỗi lần thêm dịch vụ mới là một lần mình phải lo lắng về việc bề mặt tấn công bị mở rộng — từ các cuộc tấn công SSH Brute Force, các công cụ dò quét lỗ hổng rác, cho đến vô số cảnh báo làm nhiễu log hệ thống.

Sau đó, mình quyết định đưa mọi thứ ra sau Cloudflare Tunnels. Kể từ đó, Router của mình không còn bất kỳ một inbound port nào được mở nữa.

Có nhiều bài hướng dẫn cách chạy cloudflared dưới dạng Docker Container hoặc cài đặt systemd service trên một máy ảo (VM). Cách đó hoạt động tốt. Nhưng nếu bạn cần tính ổn định cao, có tính năng theo dõi sức khỏe, xoay vòng mã bí mật, hay đơn giản là bạn đang dùng Kubernetes và không muốn quản lý thêm một phương pháp deploy thủ công nào nữa thì đây không phải là cách hay.

Mình chạy Cloudflare Tunnels dưới dạng một Kubernetes DaemonSet trong cụm Homelab của mình. Nó hoạt động cực kỳ mượt mà, tự động phục hồi sau lỗi và tích hợp hoàn hảo với hệ thống hạ tầng có sẵn. Dưới đây là lý do tại sao phương pháp này lại tối ưu hơn so với các cách triển khai truyền thống và hướng dẫn chi tiết để bạn tự thiết lập.

1. Cloudflare Tunnels là gì?

Cloudflare Tunnels thiết lập một kết nối một chiều từ hạ tầng của bạn ra mạng lưới Edge của Cloudflare. Thay vì phải mở Port để Internet kết nối vào và phải loay hoay với tường lửa, các dịch vụ của bạn sẽ tự chủ động tạo ra một kết nối liên tục hướng ra ngoài tới Cloudflare.

Khi có ai đó truy cập vào tên miền của bạn, Cloudflare sẽ định tuyến request đó đi qua đường hầm để vào dịch vụ nội bộ của bạn.

Lợi ích bảo mật cực lớn: Không mở Inbound Port, không Port Forwarding, không phơi bày trực tiếp máy chủ ra Internet. Hacker không thể scan IP hay dò lỗ hổng vì hệ thống hoàn toàn “tàng hình”. Tất cả những gì chúng thấy chỉ là hệ thống máy chủ của Cloudflare.

Bạn chỉ cần cấu hình tên miền nào sẽ trỏ vào dịch vụ nội bộ nào (Ví dụ: blog.techcoban.comhttp://my-blog-service:3000). Mọi thứ phức tạp còn lại như SSL, bảo vệ DDoS hay Caching sẽ do Cloudflare lo liệu.

2. Tại sao chọn Kubernetes thay vì VM hoặc Docker Compose?

Mình bắt đầu với cloudflared chạy qua docker-compose. Cách này ổn, nhưng mỗi khi cập nhật cấu hình, mình phải SSH vào host, sửa file Compose rồi khởi động lại container. Chưa kể đến việc khi máy chủ khởi động lại, tỷ lệ tunnel hoạt động thành công chỉ là 50/50. Tính năng Health Check cũng rất hạn chế, chỉ xoay quanh việc “Container có đang chạy hay không?”.

Sau đó, mình thử dùng systemd service trên máy ảo (VM). Đáng tin cậy hơn, nhưng lại đẻ thêm một con VM cần phải bảo trì, cập nhật OS định kỳ, và mình vẫn bị “mù dở” về trạng thái thực sự của Tunnel (ngoài việc ngồi đọc log).

Kubernetes đã mang đến chính xác những gì mình cần:

  • Liveness Probes chất lượng: Daemon cloudflared cung cấp một endpoint /metrics. Kubernetes Liveness Probes sẽ ping vào đó vài giây một lần. Nếu nó không phản hồi, Pod sẽ tự động restart. Điều này giúp phát hiện ra các lỗi “ảo” — khi Process vẫn đang chạy nhưng kết nối tới Cloudflare thì đã chết.
  • Quản lý Secrets an toàn: Tunnel Token được lưu trữ dưới dạng Kubernetes Secret thay vì nằm trơ trọi trong file Config hay biến môi trường (Environment Variable) của Compose. Nó tương thích hoàn hảo với mọi hệ thống quản lý Secret mà bạn dùng như Sealed Secrets, External Secrets Operator, hoặc mã hóa etcd.
  • Cấu hình khai báo (Declarative Config): Mình triển khai GitOps thông qua ArgoCD. Các manifest của Cloudflare Tunnel nằm chung trong một repository Git cùng với hạ tầng. Mọi thay đổi đều được Review qua Pull Request và Deploy tự động. Không còn cảnh SSH sửa cấu hình tay.
  • Tự động phục hồi (Auto-recovery): Nếu một Pod crash hoặc một Node “sập”, Kubernetes sẽ tự khởi động lại Pod hoặc chuyển lịch sang Node khác. Mình sử dụng DaemonSet (mỗi Node chạy một Tunnel Pod), nghĩa là nếu mình có 3 Node thì sẽ tự động có 3 Tunnels chạy song song.
  • Resource Limits & Metrics: Mình thiết lập giới hạn RAM/CPU cho Pod, sau đó dùng Prometheus để cào (scrape) các thông số từ endpoint /metrics. Mình có thể theo dõi lưu lượng truy cập, số lượng kết nối và trạng thái của Tunnel trên Grafana cùng với toàn bộ hệ thống.

Tóm lại: Mình không cần phải quản trị Tunnel nữa. Khi thêm một Node mới, Tunnel tự động bật. Khi Drain một Node để bảo trì, Traffic tự động chuyển hướng qua các Tunnel ở Node khác.

3. Cấu hình DaemonSet

Dưới đây là manifest YAML thực tế mình đang sử dụng. Nó khá đơn giản nhưng mọi thành phần đều có lý do của nó.

---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: cloudflared
  namespace: infrastructure
spec:
  selector:
    matchLabels:
      app: cloudflared
  template:
    metadata:
      labels:
        app: cloudflared
    spec:
      containers:
        - name: cloudflared
          image: cloudflare/cloudflared:latest
          args:
            - tunnel
            - --no-autoupdate
            - --metrics
            - 0.0.0.0:60123
            - run
            - --token
            - $(CLOUDFLARE_TOKEN)
          livenessProbe:
            httpGet:
              path: /metrics
              port: metrics
            failureThreshold: 3
            initialDelaySeconds: 5
            periodSeconds: 5
          ports:
            - containerPort: 60123
              name: metrics
          env:
            - name: CLOUDFLARE_TOKEN
              valueFrom:
                secretKeyRef:
                  name: cloudflared-credentials
                  key: token
      volumes:
        - name: cloudflared-credentials
          secret:
            secretName: cloudflared-credentials
            items:
              - key: token
                path: token

Giải thích các phần quan trọng:

  • DaemonSet vs Deployment: DaemonSet đảm bảo luôn có chính xác 1 Pod chạy trên mỗi Node. Đối với Tunnels, bạn sẽ có sẵn tính năng dự phòng (Redundancy) mà không cần lo set replica đếm tay. Hạ tầng của Cloudflare sẽ tự động Load Balance giữa các Tunnel đang chạy này.
  • Cờ --no-autoupdate: Kubernetes đã lo việc quản lý version của Image rồi, do đó ta không muốn cloudflared tự động update phiên bản. Điều này giúp hệ thống ổn định và nằm trong tầm kiểm soát của GitOps.
  • Cờ --metrics: Mở endpoint metrics ở port 60123 để trả về các thông số về sức khỏe Tunnel, kết nối, dung lượng. Kubernetes dùng port này để health check, còn Prometheus cào dữ liệu để Monitoring.
  • Liveness Probe: Cứ 5 giây một lần, Kubernetes gọi HTTP vào /metrics. Nếu 3 lần liên tiếp bị lỗi (failureThreshold: 3), Pod sẽ tự khởi động lại. Cài đặt initialDelaySeconds: 5 giúp Tunnel có đủ thời gian khởi động kết nối trước khi bị check.
  • Quản lý Secret: Tunnel Token được load từ Kubernetes Secret. Bạn không bao giờ nên hardcode nó vào file cấu hình.

Mẫu file Secret sẽ trông như sau:

apiVersion: v1
kind: Secret
metadata:
  name: cloudflared-credentials
  namespace: infrastructure
  labels:
    app: cloudflared
type: Opaque
stringData:
  token: "your-tunnel-token-here"

Lưu ý bảo mật: Trong môi trường thực tế, mình không lưu Secret thật lên Git mà dùng External Secrets Operator. Tuy nhiên với Homelab, bạn hoàn toàn có thể chạy tay lệnh kubectl create secret... rồi bỏ file token ra khỏi version control.

4. Hướng dẫn triển khai từng bước

Đây là các bước để bạn có thể mang hệ thống này lên cụm Kubernetes của riêng mình:

Bước 1: Tạo Cloudflare Tunnel

  1. Đăng nhập vào Cloudflare Dashboard.
  2. Chuyển đến mục Zero Trust → Networks → Tunnels.
  3. Bấm Create a tunnel.
  4. Chọn loại là Cloudflared và đặt tên cho nó (ví dụ: homelab-k8s).
  5. Sau khi tạo, Cloudflare sẽ cấp cho bạn một chuỗi Token. Hãy lưu lại chuỗi này.

Bước 2: Cấu hình định tuyến

Ngay trên Cloudflare Dashboard, bạn hãy cấu hình các tên miền Public (Public Hostnames) trỏ về dịch vụ nội bộ. Ví dụ:

  • blog.techcoban.comhttp://blog-service.apps.svc.cluster.local:3000
  • api.techcoban.comhttp://api-service.apps.svc.cluster.local:8080

Lưu ý: Tunnels sẽ sử dụng Kubernetes DNS (như Coredns) để truy cập đến các service này thông qua FQDN.

Bước 3: Tạo Secret chứa Token

Mở terminal và chạy lệnh tạo Secret (thay giá trị của bạn vào mục your-tunnel-token-here):

kubectl create secret generic cloudflared-credentials \
  --from-literal=token="your-tunnel-token-here" \
  --namespace=infrastructure

(Nếu bạn dùng namespace khác, hãy thay thế tham số --namespace cho phù hợp).

Bước 4: Triển khai DaemonSet

Lưu cấu hình YAML phần trên thành một file tên là cloudflared-daemonset.yaml và áp dụng vào Kubernetes:

kubectl apply -f cloudflared-daemonset.yaml

Bước 5: Kiểm tra hoạt động

Kiểm tra xem các Pod đã chạy thành công hay chưa:

kubectl get pods -n infrastructure -l app=cloudflared

Bạn sẽ thấy số lượng Pod bằng với số lượng Node và đang ở trạng thái Running. Tiếp tục kiểm tra log để đảm bảo đã kết nối thành công tới Edge của Cloudflare:

kubectl logs -n infrastructure -l app=cloudflared --tail=20

Nếu log báo Tunnel đã Register thành công và đang phục vụ các route, hãy thử mở trình duyệt truy cập vào tên miền Public của bạn. Nếu trang web hiện lên, xin chúc mừng, bạn đã hoàn tất!

5. Tổng kết

Sau một thời gian sử dụng đây là những ưu điểm mà mình tâm đắc nhất:

  • Không mở Port ở Router (Zero-port): Mạng nhà/công ty của mình không cần có luật Port Forwarding nào cả. Kết nối duy nhất là từ Tunnel trỏ ra ngoài Cloudflare. Hacker thậm chí còn không biết IP gốc của máy chủ.
  • Dự phòng lỗi hoàn hảo: Nếu một Node lỗi hoặc Pod gặp trục trặc, Kubernetes khởi động lại nó hoặc chuyển hướng traffic sang các Tunnels chạy trên Node khác. Mình chưa bao giờ gặp thời gian downtime liên quan đến Tunnel.
  • Hoạt động mượt mà với GitOps: Cấu hình Tunnel nằm gọn gàng trong Git cùng toàn bộ hạ tầng. Triển khai cập nhật bằng Pull Request cực chuyên nghiệp và gọn gàng.
  • Monitoring chuyên nghiệp: Sử dụng Prometheus scrape cổng Metrics giúp mình theo dõi toàn bộ sức khỏe Tunnel và lưu lượng bằng Grafana, kết hợp cài đặt cảnh báo khi có hiện tượng bất thường.
  • Quản lý vòng đời Secret an toàn: Tách bạch Token ra khỏi Config thông qua Kubernetes Secret giúp việc xoay vòng token dễ dàng và bảo mật hơn rất nhiều so với Docker Compose.

Nếu bạn đang chạy Kubernetes và muốn public ứng dụng mà không cần lo lắng về việc mở cổng trên tường lửa, thì phương pháp dùng Cloudflare Tunnels làm DaemonSet này là sự lựa chọn số một. Nó dễ thiết lập hơn việc cài đặt Reverse Proxy tự kéo chứng chỉ Let’s Encrypt, an toàn hơn nhiều so với việc Forward Port, và đáng tin cậy hơn hẳn việc chạy các file Script hoặc Docker lẻ tẻ trên một con VM!

Bình luận

Bài viết liên quan