前言
在个人的项目部署中,我们常常面临以下挑战:
- 业务应用数量众多,全部部署至云服务器成本高昂
- 需要在本地环境运行服务,但缺乏固定公网 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
前端页面渲染
前端服务处理请求并返回页面内容,最终在用户浏览器中完成渲染展示


3845

被折叠的 条评论
为什么被折叠?



