Featured image of post 从公网访问个人网站(三)——使用Cloudflare Tunnel反向代理Docker容器

从公网访问个人网站(三)——使用Cloudflare Tunnel反向代理Docker容器

使用Cloudflare Tunnel配置反向代理以便从公网访问自己的服务器或网站

缘起

为了能够从公网访问我在自己服务器上搭建的网站和部署的服务,我之前的做法是:

  1. 使用动态域名解析服务(DDNS)来将我的公网IP地址和域名绑定起来。
  2. 然后使用Nginx配置反向代理,将域名指向我服务器上的服务。
  3. 最后用acme来获取SSL证书,实现HTTPS访问。

后来,为了将反向代理也容器化,我转向了Traefik,将第2步和第3步合并了起来。算是一个更加方便的解决方案。以上这些可以参考我之前发布的文章:

以上的解决方案已经算是比较好用了,但依然依赖于公网IP地址的稳定性,以及端口的开放情况。如果在公司或学校等网络环境下部署,可能会遇到IP地址不稳定或端口被封禁的问题。很长一段时间内,我都认为这个问题很难解决。

最近,我终于发现了一个比较好的解决方案:使用Cloudflare Tunnel。这个方案可以让你在没有公网IP地址的情况下,或者在端口被封禁的情况下,以及网络被防火墙限制的情况下,依然从公网访问你的服务。

前置条件

Cloudflare Tunnel基本概念

简介

Cloudflare Tunnel(之前称为Argo Tunnel)是Cloudflare提供的一项服务,它可以让你在没有公网IP地址的情况下,将你的服务暴露到公网。它通过在你的服务器上运行一个轻量级的代理程序,将你的服务通过Cloudflare的网络进行转发,从而实现从公网访问你的服务。

工作原理

Cloudflare Tunnel的工作原理可以参考官方文档,简单来讲是这样的,只要你的电脑能访问Cloudflare的DNS服务器,就可以建立一条从Cloudflare到你电脑的隧道。在声明将你的域名解析到这个隧道上之后,Cloudflare会将所有关于你的域名的请求通过这个隧道转发到和这个隧道连接的电脑上。同样地,你电脑上的服务也可以通过这个隧道将响应发送出去。这样就实现了从公网访问你的服务。

当用户在公网访问某个服务时,具体过程可参见下图:

Cloudflare Tunnel工作原理

  1. 客户在浏览器中输入你的域名,域名解析到Cloudflare的DNS服务器。
  2. Cloudflare的服务器将请求转发到你建立的Cloudflare Tunnel。这个Tunnel靠你电脑上的cloudflared程序来维持。
  3. cloudflared将请求转发给你设置的反向代理服务(如Traefik或Nginx)。
  4. 反向代理服务将请求转发到你电脑上的具体服务(如NextCloud、Jellyfin等)。
  5. 具体服务处理请求后,将响应通过上述反向的过程发送回给做出请求的客户。

可见,上述过程只要求你的电脑能够访问Cloudflare的服务器,而不需要公网IP地址或开放端口,也不管你的网络是否被防火墙限制。

Cloudflare Tunnel的使用

开启Cloudflare的Zero Trust服务

Cloudflare Tunnel是Cloudflare Zero Trust服务的一部分,因此你需要先开启Cloudflare的Zero Trust服务。

登录Cloudflare账号后,左侧菜单栏中找到“Zero Trust”,点击进入。

首次开启会让你选择一个套餐,对于我这个个人用户而言,免费套餐就够用了。填入信用卡的付款信息开启即可。

创建一个Tunnel

在Zero Trust页面,左侧边栏有一个Networks选项,展开后里面有个Tunnels选项,点击即可打开Tunnels管理页面:

Cloudflare Tunnels

你可以点击Create a tunnel按钮来创建一个新的Tunnel,但这里不推荐这么创建,因为这样创建后还需要将配置挪到服务器上。我们选择在服务器上直接创建,步骤如下:

1. 在服务器上安装cloudflared

首先我们在服务器上安装cloudflared程序。cloudflared是Cloudflare提供的一个命令行工具,用于创建和管理Cloudflare Tunnel。具体安装方法参见官方文档。这里简单列一下:

  • 在Debian系Linux系统(Ubuntu等)上:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # Add cloudflare gpg key
    sudo mkdir -p --mode=0755 /usr/share/keyrings
    curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
    
    # Add this repo to your apt repositories
    echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
    
    # install cloudflared
    sudo apt-get update && sudo apt-get install cloudflared
    
  • 在RedHat系Linux系统(fedora等)上:

    1
    2
    
    sudo dnf config-manager --add-repo https://pkg.cloudflare.com/cloudflared-ascii.repo
    sudo dnf install cloudflared
    

2. 在服务器上创建Tunnel

  1. 在安装好cloudflared后,我们首先需要登录一下Cloudflare账号。在服务器上运行

    1
    
    cloudflared tunnel login
    

    会给你输出一个链接,复制这个链接到浏览器即可登录Cloudflare账号。登录后会让你选择你想要绑定到Tunnel上的域名,选择域名即可。

  2. 然后在服务器上运行

    1
    
    cloudflared tunnel create my-tunnel
    

    其中my-tunnel是你要创建的Tunnel的名称,你可以根据需要修改。运行后会输出一个Tunnel的ID,记下来。

    同时,cloudflared会在你的服务器上创建一个记录这个Tunnel的JSON文件,位置一般在~/.cloudflared/目录下,文件名是Tunnel ID。这个文件很重要,里面存储了连接这个Tunnel所需的凭证。

  3. 这时你可以登录Cloudflare网页端查看Tunnel是否创建成功。在Zero Trust -> Networks -> Tunnels页面,你应该能看到刚才创建的Tunnel:

    Cloudflare Tunnel

    注意,这时我们只创建了这个Tunnel,还没有连接它,因此它处于inactive的状态。

3. 连接Tunnel

这里分别以Docker和k3s为例,说明如何将部署的服务连接到Tunnel。

Docker
  1. 前置条件

    这里假设

    • 你已经安装了docker和docker compose,
    • 使用Traefik来做反向代理,
    • 并创建了一个所有容器共享的网络traefik-net

    具体过程可参考“从公网访问个人网站(二)——Traefik反向代理配置”

  2. 创建cloudflared容器

    在你的Docker项目目录下(假设是~/docker),创建一个新的项目文件夹cloudflared,项目结构如下:

    1
    2
    3
    4
    
    cloudflared/
        ├── docker-compose.yml
        ├── .env
        └── config.yml
    

    docker-compose.yml文件内容如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    version: '3.8'
    
    services:
      cloudflared:
        image: cloudflare/cloudflared:2025.5.0
        container_name: cloudflared
        command: tunnel --config /etc/cloudflared/config.yaml run
        volumes:
          - ./config.yaml:/etc/cloudflared/config.yaml:ro
          - ${CREDENTIAL_FILE}:/etc/cloudflared/creds/credentials.json:ro
        restart: unless-stopped
        networks:
          - traefik-net
    
    networks:
      traefik-net:
        external: true
    

    这里的my-tunnel是你之前创建的Tunnel的名称。CREDENTIAL_FILE是你之前创建的Tunnel时生成的凭证文件的路径,通常是~/.cloudflared/<Tunnel ID>.json。你可以在.env文件中定义这个变量:

    .env文件内容如下:

    1
    
    CREDENTIAL_FILE=<User Home Path>/.cloudflared/<Tunnel ID>.json
    

    除此之外,我们还需要一个配置文件config.yml

    1
    2
    3
    4
    5
    6
    7
    8
    
    tunnel: li-tunnel
    credentials-file: /etc/cloudflared/creds/credentials.json
    metrics: 0.0.0.0:2000
    no-autoupdate: true
    ingress:
      - hostname: "*.jinli.li"
        service: http://traefik:80
      - service: http_status:404
    
    • 这里的hostname是你要通过Tunnel访问的域名,这里我让所有以jinli.li结尾的域名都通过这个li-tunnel访问。
    • service是指向Traefik容器的服务地址。注意这里的traefik:80是指Traefik容器的名称和端口。
  3. 启动cloudflared容器cloudflared目录下运行以下命令来启动cloudflared容器:

    1
    
    docker-compose up -d
    

    这时你可以在Zero Trust -> Networks -> Tunnels页面看到Tunnel的状态变为active,表示Tunnel已经连接成功:

    Cloudflare Tunnel Active

  4. 配置DNS记录

    在Cloudflare的DNS管理页面,删除掉之前关于*.jinli.li的DNS记录。

    然后回到服务器命令行,运行

    1
    
    cloudflared tunnel route dns li-tunnel "*.jinli.li"
    

    这条命令创建了一个wildcard DNS记录,将所有以jinli.li结尾的域名都指向这个Tunnel。

    当然,你也可以只指定某个具体的域名,例如:

    1
    
    cloudflared tunnel route dns li-tunnel "nextcloud.jinli.li"
    

    或者在Cloudflare的网页端DNS管理界面来创建DNS记录,选择CNAME类型,指向<Tunnel ID>.cfargotunnel.com,其中<Tunnel ID>是你之前创建的Tunnel的ID。具体可参见官方文档。但不知为啥,我这么创建后依然无法访问域名,上面用命令行创建的就没问题。

    注意:如果你之后不需要再用命令行来创建DNS记录了,那么在服务器用包管理工具安装的这个cloudflared程序就没啥用了,你可以将其卸载。之前创建的Tunnel将由Docker里容器化的cloudflared程序维护。

  5. 测试访问

    现在你就可以通过浏览器访问你的域名了,例如https://nextcloud.jinli.li。如果你之前已经部署了NextCloud服务,并在Traefik中配置反向代理,那么就可以直接访问了。

K3s
  1. 前置条件

    这里假设你已经安装了K3s,并且使用Traefik作为Ingress Controller。具体过程可参考“Homelab(1):使用Kubernetes(K8s)或K3s自建家庭集群”

  2. 将Tunnel凭证secret化

    之前创建Tunnel时cloudflared生成的凭证文件保存在~/.cloudflared/<Tunnel ID>.json,我们需要将这个文件转换为Kubernetes的Secret。

    在服务器上运行以下命令:

    1
    
    kubectl create secret generic cloudflared-credentials --from-file=credentials.json=<User Home Path>/.cloudflared/<Tunnel ID>.json
    

    其中<User Home Path>是你的用户主目录路径,<Tunnel ID>是你之前创建的Tunnel的ID。

  3. 创建cloudflared Deployment和Service

    所需的manifest文件如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: cloudflared
    spec:
      selector:
        matchLabels:
          app: cloudflared
      replicas: 1
      template:
        metadata:
          labels:
            app: cloudflared
        spec:
          containers:
          - name: cloudflared
            image: cloudflare/cloudflared:2025.5.0
            args:
            - tunnel
            - --config
            - /etc/cloudflared/config/config.yaml
            - run
            livenessProbe:
              httpGet:
                path: /ready
                port: 2000
              failureThreshold: 1
              initialDelaySeconds: 10
              periodSeconds: 10
            volumeMounts:
            - name: config
              mountPath: /etc/cloudflared/config
              readOnly: true
            - name: creds
              mountPath: /etc/cloudflared/creds
              readOnly: true
          volumes:
          - name: creds
            secret:
              secretName: li-tunnel-credentials
          - name: config
            configMap:
              name: cloudflared
              items:
              - key: config.yaml
                path: config.yaml
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: cloudflared
    data:
      config.yaml: |
        tunnel: li-tunnel
        credentials-file: /etc/cloudflared/creds/credentials.json
        metrics: 0.0.0.0:2000
        no-autoupdate: true
        ingress:
        - hostname: "*.jinli.li"
          service: http://traefik.kube-system.svc.cluster.local:80
        - service: http_status:404    
    

    配置的含义跟Docker里的类似,这里不再赘述。

  4. 部署cloudflared

    将上述manifest文件保存为cloudflared.yaml,然后在服务器上运行以下命令来部署cloudflared:

    1
    
    kubectl apply -f cloudflared.yaml
    

    这时你可以在Zero Trust -> Networks -> Tunnels页面看到Tunnel的状态变为active,表示Tunnel已经连接成功。

    如果没有,那么你需要检查cloudflared是否部署成功。

  5. 配置DNS记录与测试访问

    这两步跟上述在Docker中使用cloudflared的步骤(第3步和第4步)一样。

comments powered by Disqus