ByteNoteByteNote

字节笔记本

2026年2月19日

cloudflare-worker-tunnel-mysql-example:安全连接 Worker 与自建 MySQL 的完整方案

本文介绍 cloudflare-worker-tunnel-mysql-example,一个通过 Cloudflare Tunnel 安全连接 Cloudflare Worker 与自建 MySQL 数据库的开源示例项目。该项目解决了将数据库暴露在互联网上的安全风险问题,提供了一套完整的隧道加密连接方案。

项目简介

cloudflare-worker-tunnel-mysql-example 是一个由 Brett Scott 开发的开源项目,旨在演示如何通过 Cloudflare Tunnel 建立 Cloudflare Worker 与自建 MySQL 数据库之间的安全连接。该项目在 GitHub 上已获得 66+ stars,是对 Cloudflare 官方 worker-mysql 模板的重要改进,解决了官方模板无法与 Cloudflare Tunnel 和 Cloudflare Access 配合使用的问题。

在传统的架构中,将数据库直接暴露在互联网上存在严重的安全隐患,恶意攻击者会扫描开放的数据库端口并尝试暴力破解。通过使用 Cloudflare Tunnel,可以将数据库隐藏在私有网络中,仅通过加密隧道对外提供服务,大大提升安全性。

核心特性

  • Wrangler 2.0 支持:使用最新的 Cloudflare Workers CLI 工具
  • TypeScript 编写:提供完整的类型支持
  • 本地开发支持:支持在本地环境中进行开发和测试
  • 自托管 MySQL 数据库:支持本地或远程 MySQL 数据库
  • Docker 化部署:使用 Docker Compose 管理 cloudflared 和 MySQL 服务
  • Cloudflare Access 集成:通过 Service Token 保护隧道访问
  • ES Module 模式:采用最新的模块系统(替代 Service Workers)
  • 环境变量加密:敏感信息通过 Wrangler Secrets 安全存储

技术栈

  • Cloudflare Workers:边缘计算平台
  • Cloudflare Tunnel (cloudflared):安全隧道服务
  • Cloudflare Access:身份验证和访问控制
  • MySQL 8.0:关系型数据库
  • Docker & Docker Compose:容器化部署
  • TypeScript:类型安全的 JavaScript 超集
  • Wrangler:Cloudflare Workers 开发工具

架构原理

text
┌─────────────────┐         ┌──────────────────┐         ┌─────────────────┐
│  Cloudflare     │ ──────▶ │  Cloudflare      │ ──────▶ │  Cloudflared    │
│  Worker         │  HTTPS  │  Tunnel          │  TCP    │  (Docker)       │
│                 │         │                  │         │                 │
│  + Service      │         │  + Access        │         │  + MySQL        │
│    Token Auth   │         │    Token Verify  │         │    (Docker)     │
└─────────────────┘         └──────────────────┘         └─────────────────┘

工作流程:

  1. Cloudflare Worker 携带 Service Token 发起请求
  2. Cloudflare Tunnel 验证 Token 并转发请求
  3. cloudflared 容器将请求转发到 MySQL 容器
  4. MySQL 执行查询并返回结果

前置要求

1. Docker

安装 Docker 用于本地运行 cloudflared 和 MySQL。Mac/Windows 用户推荐使用 Docker Desktop。

2. Node Version Manager (NVM)

bash
nvm install
nvm use

3. Cloudflare 账户

需要在 Cloudflare Dashboard 中添加一个网站域名。

安装指南

步骤 1:创建 Cloudflare Tunnel

  1. 访问 Cloudflare Zero Trust Dashboard
  2. 选择 "Access" > "Tunnels"
  3. 点击 "Create a Tunnel"
  4. 输入隧道名称,如 "Database Tunnel - Dev"
  5. 配置 Public Hostname:
    • Subdomain: db-tunnel-dev
    • Domain: 选择你的域名(如 example.com
    • Service: tcp://host.docker.internal:3306

如果 MySQL 不在 Docker 中运行,使用 tcp://127.0.0.1:3306

步骤 2:配置 cloudflared

  1. 登录 Cloudflare:
bash
npm run cloudflared:login
  1. docker-compose.yml 中配置 Tunnel Token:
yaml
cloudflared:
  environment:
    - TUNNEL_TOKEN="你的隧道令牌"
  1. 启动服务:
bash
npm run cloudflared

步骤 3:创建 Service Token

  1. 在 Cloudflare Zero Trust Dashboard 中,进入 "Access" > "Service Auth"
  2. 点击 "Create Service Token"
  3. 记录生成的 CF_CLIENT_IDCF_CLIENT_SECRET

步骤 4:配置 Cloudflare Application

  1. 进入 "Access" > "Applications"
  2. 创建 Self-Hosted 类型应用
  3. 配置应用域名(与隧道相同的域名)
  4. 添加 Policy:
    • Action: "Service Auth"
    • Include: "Service Token"(选择步骤3创建的 Token)

步骤 5:配置 Worker 密钥

bash
npx wrangler secret put CF_CLIENT_ID
npx wrangler secret put CF_CLIENT_SECRET

步骤 6:运行示例

  1. 安装依赖:
bash
npm install
  1. 更新 wrangler.toml 中的 TUNNEL_HOST
toml
[vars]
TUNNEL_HOST = "https://db-tunnel-dev.example.com"
  1. 启动开发服务器:
bash
npm run dev
  1. 访问 http://localhost:8787 测试

核心代码示例

Worker 入口代码

typescript
import { Client } from './driver/mysql'

type Environment = {
  readonly TUNNEL_HOST: string
  readonly CF_CLIENT_ID: string
  readonly CF_CLIENT_SECRET: string
}

export default {
  async fetch(request: Request, env: Environment, ctx: ExecutionContext) {
    let mysqlClient
    try {
      // 创建 MySQL 客户端
      const mysql = new Client()
      mysqlClient = await mysql.connect({
        username: 'user',
        db: 'appdb',
        hostname: env.TUNNEL_HOST,
        password: 'password',
        cfClientId: env.CF_CLIENT_ID,
        cfClientSecret: env.CF_CLIENT_SECRET,
      })

      // 执行查询
      const param = 42
      const result = await mysqlClient.query('SELECT ?;', [param])

      // 关闭连接
      await mysqlClient.close()

      return new Response(JSON.stringify({ result }))

    } catch (e) {
      if (mysqlClient) {
        await (mysqlClient as Client).close()
      }
      return new Response((e as Error).message)
    }
  },
}

Docker Compose 配置

yaml
version: '3.3'
services:
  mysql:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_DATABASE: 'appdb'
      MYSQL_USER: 'user'
      MYSQL_PASSWORD: 'password'
      MYSQL_ROOT_PASSWORD: 'password'
    ports:
      - '3306:3306'
    volumes:
      - my-db:/var/lib/mysql

  cloudflared:
    image: cloudflare/cloudflared:1280-66d1f2750707
    restart: unless-stopped
    command: tunnel run
    environment:
      - TUNNEL_TOKEN=""
    volumes:
      - '~/.cloudflared:/etc/cloudflared'
    depends_on:
      - mysql

volumes:
  my-db:

常见问题与解决方案

1. globalThis 使用问题

错误ERROR: CF_CLIENT_ID is not defined

解决:Module Workers 中不要使用 globalThis,直接使用 env 参数:

typescript
// ❌ 错误
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    globalThis.CF_CLIENT_ID = env.CF_CLIENT_ID
    console.log(globalThis.CF_CLIENT_ID)  // 会报错!
  }
}

// ✅ 正确
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    console.log(env.CF_CLIENT_ID)  // 直接使用 env
  }
}

2. Top-level Await 问题

错误Top-level await is not available in the configured target environment

解决:将顶层 await 包装在立即执行函数中:

typescript
// ❌ 错误
await setup(DEFAULT_CONFIG);

// ✅ 正确
(async () => {
  await setup(DEFAULT_CONFIG);
})();

3. Docker 网络连接问题

错误dial tcp 127.0.0.1:3306: connect: connection refused

解决

  • 如果 MySQL 在 Docker 中:使用 host.docker.internal:3306
  • 如果 MySQL 在宿主机:在 docker-compose.yml 中添加 network_mode: host

安全建议

  1. 防火墙配置:确保 MySQL 端口(3306)不直接暴露在互联网上
  2. 强密码策略:使用复杂的密码并定期更换
  3. 最小权限原则:为 Worker 创建仅具有必要权限的数据库用户
  4. 定期轮换 Token:定期更新 Cloudflare Service Token
  5. 监控日志:定期检查 Cloudflare Access 日志以发现异常访问

生产环境部署

项目支持多环境配置(开发、预发布、生产):

toml
# wrangler.toml
[env.staging]
name = "mysql-tunnel-example-staging"
[env.staging.vars]
TUNNEL_HOST = "https://db-tunnel-staging.example.com"

[env.prod]
name = "mysql-tunnel-example-prod"
[env.prod.vars]
TUNNEL_HOST = "https://db-tunnel-prod.example.com"

部署命令:

bash
# 部署到预发布环境
npm run publish:staging

# 部署到生产环境
npm run publish:prod

项目链接

总结

cloudflare-worker-tunnel-mysql-example 为开发者提供了一套完整的解决方案,用于安全地连接 Cloudflare Worker 和自建数据库。通过 Cloudflare Tunnel 的加密通道和 Access 的身份验证,可以有效保护数据库免受互联网上的恶意攻击。该项目特别适合需要将无服务器函数与传统数据库集成的场景,是边缘计算与私有基础设施结合的优秀实践。

分享: