前言

在个人的项目部署中,我们常常面临以下挑战:

  • 业务应用数量众多,全部部署至云服务器成本高昂
  • 需要在本地环境运行服务,但缺乏固定公网 IP
  • 希望使用自定义域名提供专业服务访问体验

针对这些痛点,我们采用云服务器中转 + 内网穿透的方案:

  • 利用云服务商提供的 ECS 实例与公网 IP 作为流量入口
  • 通过 FRP 工具建立安全的内网穿透通道
  • 将实际业务服务部署在内网环境,享受本地部署的灵活性与成本优势
  • 结合自有域名,为用户提供统一的专业访问入口

一、内网穿透

1. 准备一台云服务器

先去阿里云的高校计划白嫖300元优惠券,开通一年2c2g的ECS服务器选择按量计费的IP服务。

或者其他腾讯云、京东云厂商的便宜服务器都行。

2. 云服务器安装frp工具

基本原理:

  • 公网服务器 (云服务器):具有固定公网IP,运行 frp 的服务端 frps。
  • 内网服务器/设备:位于 NAT 或防火墙后,运行 frp 的客户端 frpc。
  • 通信流程:frpc 主动连接 frps 建立控制通道,当外部用户访问 frps 的某个端口时,frps 会将请求通过已建立的通道转发给 frpc,再由 frpc 转发给内网的特定服务。

接下来创建完实例登录控制台,打开远程控制界面。安装frp工具

下载 frp:从 frp GitHub Releases 下载对应平台的二进制文件

上传并解压 frp

tar -xzf frp_0.52.3_linux_amd64.tar.gz
cd frp_0.52.3_linux_amd64

编辑服务端配置文件 frps.ini

[common]
# frp 服务端监听的端口,客户端通过此端口连接
bind_port = 7000

# 可选:Web 管理界面端口
dashboard_port = 7500
# 管理界面的用户名和密码
dashboard_user = admin
dashboard_pwd = password

# 可选:身份验证令牌,建议设置以增强安全性
token = yourtoken

# 可选:设置最大并发连接数
max_pool_count = 100
# HTTP 和 HTTPS 代理端口
vhost_http_port = 80
vhost_https_port = 443

启动 frp 服务端

./frps -c frps.ini

设置开机自启 (Systemd)

创建 systemd 服务文件:sudo vim /etc/systemd/system/frps.service

ExecStart的路径要修改成你自己的路径

[Unit]
Description=Frp Server Service
After=network.target

[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/path/to/frps -c /path/to/frps.ini

[Install]
WantedBy=multi-user.target

启用服务

sudo systemctl daemon-reload
sudo systemctl enable frps
sudo systemctl start frps

开启防火墙,在控制台的安全组里面开启防火墙放行(如果安装了宝塔面板,宝塔里面也要开启)

3. 内网服务器配置frp

上传并解压 frp (同服务端步骤)

编辑客户端配置文件 frpc.ini

[common]
server_addr = 101.205.73.227
server_port = 7000
token = yourtoken

# 所有 HTTP 流量都代理到 Nginx
[web-http]
type = http
local_ip = 127.0.0.1
local_port = 80
custom_domains = zs4m.cn,www.zs4m.cn,api.zs4m.cn

# 所有 HTTPS 流量都代理到 Nginx  
[web-https]
type = https
local_ip = 127.0.0.1
local_port = 443
custom_domains = zs4m.cn,www.zs4m.cn,api.zs4m.cn

# SSH 服务
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

创建服务文件:sudo vim /etc/systemd/system/frpc.service

[Unit]
Description=Frp Client Service
After=network.target

[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/path/to/frpc -c /path/to/frpc.ini

[Install]
WantedBy=multi-user.target

启用服务:

sudo systemctl daemon-reload
sudo systemctl enable frpc
sudo systemctl start frpc

访问 Dashboard:http://x.x.x.x:7500 (使用设置的用户名密码登录)

HTTP 服务测试:访问域名需要先配置好DNS解析和nginx

curl http://zs4m.cn

二、域名解析

1. 购买一个域名并且备案

直接搜索域名,各个厂商都有提供,随机挑选一个就可,1块钱一年

在哪个云服务商购买的服务器,就在哪个服务商进行备案阿里备案

配置域名解析:

获取SSL证书,各个云厂商也有提供。子域名需要认证多个,点击下载key和pem

2. nginx代理

在云服务器上我们配置了80,443端口的http流量都会转到内网服务器上,内网服务器再根据域名接收,不同的子域名对应不同的端口,需要nginx来做ssl解密和转发。这里使用docker来安装运行,后续所有服务也运行在docker上。

 1) 安装docker

curl -fsSL https://get.docker.com | bash
sudo systemctl enable docker
sudo systemctl start docker

配置代理:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://mirror.ccs.tencentyun.com",
    "https://hub-mirror.c.163.com",
    "https://registry.docker-cn.com"
  ]
}
EOF

重启docker:

sudo systemctl daemon-reload
sudo systemctl restart docker

2) 安装portainer

Portainer 是一个轻量级、开源的容器管理图形化界面,专门用于管理 Docker 和 Kubernetes 环境。它的核心理念是让容器管理变得简单,即使是对命令行不熟悉的开发人员或运维人员也能轻松上手。

docker pull portainer/portainer
docker run -d --restart=always --name portainer -p 9009:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer

访问:http://ip:9009

3)安装nginx

 创建网站目录和文件

# 创建项目目录
mkdir -p ~/docker/nginx/zs4m.cn/html
cd ~/docker/nginx/zs4m.cn

# 创建网站首页
cat > html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
    <title>欢迎访问 zs4m.cn</title>
    <meta charset="utf-8">
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .container { max-width: 800px; margin: 0 auto; }
        .header { background: #f5f5f5; padding: 20px; border-radius: 5px; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🚀 欢迎访问 zs4m.cn!</h1>
            <p>网站运行在内网服务器的 Docker 容器中</p>
            <p>通过云服务器 frp 中转访问</p>
        </div>
        <div>
            <h2>系统信息</h2>
            <p>服务器时间: <span id="time"></span></p>
            <p>访问者IP: <span id="ip"></span></p>
        </div>
    </div>
    <script>
        document.getElementById('time').textContent = new Date().toLocaleString();
        // 简单的IP显示(实际需要后端支持)
        fetch('https://api.ipify.org?format=json')
            .then(response => response.json())
            .then(data => document.getElementById('ip').textContent = data.ip)
            .catch(() => document.getElementById('ip').textContent = '无法获取');
    </script>
</body>
</html>
EOF

创建 Nginx 配置文件:

mkdir -p conf

default.conf

# 主站点 HTTP 重定向
server {
    listen 80;
    server_name zs4m.cn www.zs4m.cn;
    return 301 https://$server_name$request_uri;
}

# 主站点 HTTPS
server {
    listen 443 ssl;
    server_name zs4m.cn www.zs4m.cn;
    
    client_max_body_size 100M;
    
    # SSL 证书配置
    ssl_certificate /etc/nginx/ssl/zs4m.cn.crt;
    ssl_certificate_key /etc/nginx/ssl/zs4m.cn.key;
    
    # SSL 安全配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # 安全头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;

    # 代理到前端服务
    location / {
        proxy_pass http://cheeseai-web:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        
        proxy_connect_timeout 30s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }

    # 健康检查
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }

    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

# API 站点 HTTP 重定向
server {
    listen 80;
    server_name api.zs4m.cn;
    return 301 https://$server_name$request_uri;
}

# API 站点 HTTPS
server {
    listen 443 ssl;
    server_name api.zs4m.cn;
    
    client_max_body_size 100M;
    
    # 使用相同的 SSL 证书
    ssl_certificate /etc/nginx/ssl/api.zs4m.cn.crt;
    ssl_certificate_key /etc/nginx/ssl/api.zs4m.cn.key;
    
    # SSL 安全配置(与主站相同)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # 安全头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;

    # 代理到 API 服务
    location / {
        proxy_pass http://192.168.0.46:8090;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        
        # API 特定的超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # API 健康检查
    location /health {
        proxy_pass http://192.168.0.46:8090/health;
        proxy_set_header Host $host;
        access_log off;
    }

    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

创建主配置文件

# 创建主配置文件
cat > conf/nginx.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;
    gzip on;

    # 包含虚拟主机配置
    include /etc/nginx/conf.d/*.conf;
}
EOF

使用docker部署nginx

cd /home/vvv/docs/dev-ops/nginx

cat > docker-compose.yml << 'EOF'
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    container_name: nginx_zs4m
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"  # 新增 HTTPS 端口映射
    volumes:
      - ./html:/usr/share/nginx/html
      - ./conf/nginx.conf:/etc/nginx/nginx.conf
      - ./conf/default.conf:/etc/nginx/conf.d/default.conf
      - ./logs:/var/log/nginx
      - ./cache:/var/cache/nginx
      - ./ssl:/etc/nginx/ssl  # 挂载 SSL 证书目录
    networks:
      - dev-ops_my-network

networks:
  dev-ops_my-network:
    external: true
    name: dev-ops_my-network
EOF

启动

# 在项目目录下启动服务
cd ~/docker/nginx/zs4m.cn
docker-compose up -d

# 检查容器状态
docker ps
docker logs nginx_zs4m

# 测试本地访问
curl http://localhost

添加ssl配置,在ssl目录下面上传从云服务器证书管理下载的key和pem,将pem重命名为crt后缀

# 创建 SSL 配置目录
mkdir -p ssl

三、web服务

1. docker运行

前端服务已经准备好,直接build

docker build --platform linux/amd64  -t mumu/cheeseai-web:1.0 .
# 停止并重新启动
docker-compose down
docker-compose up -d

# 检查状态
docker-compose ps

# 查看日志
docker logs nginx_zs4m
services:
  ai-agent-station-front:
    image: mumu/cheeseai-agent-front:1.0
    container_name: ai-agent-station-front-app
    ports:
      - "3002:3002"
    restart: unless-stopped
    networks:
      - my-network
    environment:
      - NODE_ENV=production
      - NEXT_PUBLIC_API_HOST_URL=https://api.zs4m.cn
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://localhost:3002" ]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

2. 访问服务

当用户在浏览器访问 https://zs4m.cn 时,完整的请求处理流程如下:

HTTPS 请求接收
用户浏览器向云服务器 443 端口发起 HTTPS 请求

FRP 服务端转发
云服务器上的 FRP 服务端(frps)接收请求,通过已建立的隧道将请求转发至内网服务器

FRP 客户端处理
内网服务器上的 FRP 客户端(frpc)接收转发来的请求

Nginx 反向代理
FRP 客户端将请求发送至本地 Nginx 服务的 443 端口,Nginx 作为反向代理将请求转发至实际的前端服务端口 3002

前端页面渲染
前端服务处理请求并返回页面内容,最终在用户浏览器中完成渲染展示

更多推荐