版本提示

本文记录的是 2025 年写作时的懒猫微服开发环境。lzc-cli 命令、lzc-build.yml / lzc-manifest.yml 字段、基础镜像、OIDC 环境变量、路由方式和应用商店流程都可能随系统与 SDK 更新而变化。动手前请先对照当前官方开发文档,并以本机命令帮助、实际日志和最终生成的配置为准。

写 Web 躲不开跨域。先说清楚它到底是什么:

跨域问题(Cross-Origin)本质上是浏览器的同源策略(Same-Origin Policy, SOP)在发挥作用:

同源指“协议 + 域名(或 IP)+ 端口”三要素完全一致。只要三者有任何一个不同,就被视为跨域。

为什么浏览器要限制跨域?

  • 安全:阻止一个站点随意读取或修改另一个站点的敏感资源(如 Cookie、LocalStorage、DOM),避免 XSS、CSRF 等攻击链被无限放大。
  • 隔离:让不同网站在沙盒里各自运行,互不干扰。

同源策略只在浏览器环境生效;后端服务之间(如服务器 A 请求服务器 B)并没有 SOP 的限制。

场景描述是否受限
fetch('https://api.foo.com') 从 https://www.bar.com 发出协议、域名不同受限
http://example.com:3000 调用 http://example.com:4000端口不同受限

⚠️ 用 Nginx/OpenResty 并不会“自动”解决 CORS。

  • 你可以把前端请求代理到后端 API,使浏览器认为请求仍在同一域名下,达到“变同源”的效果。
  • 或者直接在后端/代理层加 CORS 响应头,两种方式都可以。

懒猫微服上使用的是 OpenResty。它是一个功能完整的 Web 应用服务器,集成了 Nginx 核心、大量第三方 Nginx 模块及相关依赖,因此配置方式和 Nginx 基本一致。

以我之前比赛做的项目为例,这个是 Nginx 作为网关,监听 80 端口,然后反向代理到 Next.js 和 Flask。

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
services:
nginx:
build:
context: .
dockerfile: Dockerfile
ports:
- "80:80"
depends_on:
- next-app
- backend
restart: unless-stopped

next-app:
image: smart-shopping-app
container_name: next-frontend
expose:
- "3000"
environment:
- NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1
restart: unless-stopped

backend:
image: shoppingassistant-backend
container_name: backend-app
expose:
- "5005"
restart: unless-stopped

Nginx 配置如下,通过七层转发把根路径代理到前端,把 /api 代理到后端。这样前端的 Axios 请求仍然访问当前域名下的 /api,浏览器看到的是同源请求,也就避开了跨域问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 80;
server_name localhost;

location / {
proxy_pass http://next-app:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

location /api {
proxy_pass http://backend:5005;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

懒猫微服里的 OpenResty 也是一样的思路,好处是不用再自己寻找基础镜像,直接写入配置文件就能使用。

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
lzc-sdk-version: "0.1"
name: APP Proxy Test
package: cloud.lazycat.app.app-proxy-test
version: 0.0.1
application:
routes:
- /=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80
subdomain: app-proxy-test #
services:
app-proxy:
image: registry.lazycat.cloud/app-proxy:v0.1.0
setup_script: |
cat <<'EOF' > /etc/nginx/conf.d/default.conf
server {
listen 80;
server_name _;

# 静态页面
location / {
root /usr/local/openresty/nginx/html;
index index.html index.htm;
}

# API 反向代理,保留 /api 前缀
location /api/ {
proxy_pass http://flask:5000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
EOF
flask:
image: registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c

Flask 使用的是我之前制作的镜像,可以先用下面的命令在本地运行:

docker run -p 5005:5000 cloudsmithy/flask-demo:latest

然后通过 lzc-cli appstore copy-image cloudsmithy/flask-demo 把镜像复制到懒猫镜像仓库,得到 registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c。

通过 setup_script 写入和 Nginx 类似的配置文件,应用启动时就能使用这份代理配置。

1
2
3
4
5
# 打包成 LPK
lzc-cli project build -o release.lpk

# 在线安装 LPK
lzc-cli app install release.lpk

可以看到这是 OpenResty 的主页,然后访问 https://app-proxy-test.micro.heiyu.space/api/,也能返回 Flask 容器的 “Hello from multi-arch Flask Docker in production mode!”。
image-20250704120859972

如果你想把根路由直接代理到容器,也可以使用这个办法。它一般用于通过反向代理访问内网服务,内网后端使用 HTTP 也可以。这个环境变量应该是懒猫提供的快捷方式,不要和前面的自定义配置文件混用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lzc-sdk-version: "0.1"
name: APP Proxy Test
package: cloud.lazycat.app.app-proxy-test
version: 0.0.1
application:
routes:
- /=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80
subdomain: app-proxy-test #
services:
app-proxy:
image: registry.lazycat.cloud/app-proxy:v0.1.0
environment:
- UPSTREAM="http://flask:5000"
flask:
image: registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c

有时候还会使用 BASIC_AUTH_HEADER 字段,让 Nginx/OpenResty 自动添加 Basic Authentication 请求头。除了代理自己的容器,也可以用它代理外部服务。

先用 echo -n "user:password" | base64 生成 Base64 字符串,再按 Basic <base64> 的格式填写 BASIC_AUTH_HEADER。这里的 Base64 不是加密,配置文件和日志中不要使用真实生产密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lzc-sdk-version: "0.1"
name: APP Proxy Test
package: cloud.lazycat.app.app-proxy-test
version: 0.0.1
application:
routes:
- /=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80
subdomain: app-proxy-test #
services:
app-proxy:
image: registry.lazycat.cloud/app-proxy:v0.1.0
environment:
- UPSTREAM="https://xxx:9200/"
- BASIC_AUTH_HEADER="Basic YWRt46YzssdsfFlOk="
flask:
image: registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c

另外也支持多域名解析,这个在传统的线下机房比较常见,而云上基本上还是 7 层基于路由转发,比如第一种,我也更加熟悉第一种。

这个方案就是增加 secondary_domains 字段,再把后端单独暴露出来,这样就可以通过子域名访问后端。

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
lzc-sdk-version: "0.1"
name: APP Proxy Test
package: cloud.lazycat.app.app-proxy-test
version: 0.0.1
application:
routes:
- /=http://app-proxy.cloud.lazycat.app.app-proxy-test.lzcapp:80
subdomain: app-proxy-test # 应用列表里默认打开的域名
secondary_domains:
- flask
services:
app-proxy:
image: registry.lazycat.cloud/app-proxy:v0.1.0
setup_script: |
cat <<'EOF' > /etc/nginx/conf.d/default.conf
server {
server_name app-proxy-test.*;
location / {
root /usr/local/openresty/nginx/html;
index index.html index.htm;
}
}

server {
server_name flask.*;

location / {
proxy_pass http://flask:5000;
}
}
EOF
flask:
image: registry.lazycat.cloud/u04123229/cloudsmithy/flask-demo:c14689303facd82c

忘机山人二维码

扫码关注「忘机山人」