ops: add isolated production deployment

This commit is contained in:
lunanting 2026-09-12 16:53:18 +08:00
parent 088a24c3a9
commit a85acabde5
8 changed files with 210 additions and 1 deletions

5
backend/.dockerignore Normal file
View File

@ -0,0 +1,5 @@
node_modules
.env
npm-debug.log*
coverage
runtime

View File

@ -0,0 +1,16 @@
FROM node:22-bookworm-slim
ENV NODE_ENV=production
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --no-audit --no-fund
COPY . .
EXPOSE 4090
CMD ["node", "src/index.js"]

View File

@ -0,0 +1,14 @@
# 生产部署(Docker)
生产实例使用三个隔离容器:MySQL 系统数据库、Node.js 后端和部署注册服务。MySQL、后端与注册服务只映射到服务器回环地址,公网仅由 Nginx 暴露 `ck.yydd.ai:8443`。
服务器目录约定:
- `/opt/cangku/app`:仓库代码和前端构建结果
- `/opt/cangku/shared`:权限为 `0700` 的环境配置
- `/opt/cangku/data`:MySQL 和部署注册服务持久数据
- `/www/wwwroot/cangku-acme`:Let's Encrypt HTTP-01 校验目录
真实密码、JWT 密钥、部署 Key 和第三方数据库配置不得写入仓库。`mysql.env`、`backend.env` 和 `bootstrap.env` 只在服务器生成并保持 `0600` 权限。
SSH 隧道账号只接受由注册服务 CA 签发的短期证书,只允许转发到服务器 `127.0.0.1:3307`,并关闭终端、TTY、代理转发和密码认证。生产主机的 SSH 管理账号使用独立密钥,关闭密码登录。

View File

@ -0,0 +1,79 @@
name: cangku
services:
mysql:
image: mysql:8.0.39
container_name: cangku-mysql
restart: unless-stopped
env_file:
- /opt/cangku/shared/mysql.env
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
ports:
- 127.0.0.1:3307:3306
volumes:
- /opt/cangku/data/mysql:/var/lib/mysql
- ../../backend/database/init.sql:/docker-entrypoint-initdb.d/001-init.sql:ro
- ../../backend/database/summary_tables.sql:/docker-entrypoint-initdb.d/002-summary-tables.sql:ro
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent"]
interval: 5s
timeout: 5s
retries: 30
start_period: 30s
networks:
- cangku-internal
backend:
build:
context: ../../backend
dockerfile: Dockerfile.production
container_name: cangku-backend
restart: unless-stopped
env_file:
- /opt/cangku/shared/backend.env
ports:
- 127.0.0.1:4090:4090
volumes:
- /opt/cangku/data/backend:/app/runtime
depends_on:
mysql:
condition: service_healthy
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:4090/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
interval: 10s
timeout: 5s
retries: 18
start_period: 30s
networks:
- cangku-internal
bootstrap:
build:
context: ../../remote-access/server
dockerfile: Dockerfile.production
container_name: cangku-bootstrap
restart: unless-stopped
env_file:
- /opt/cangku/shared/bootstrap.env
ports:
- 127.0.0.1:4092:4092
volumes:
- /opt/cangku/data/bootstrap:/var/lib/cangku-bootstrap
- /etc/ssh/ssh_host_ed25519_key.pub:/etc/ssh/ssh_host_ed25519_key.pub:ro
depends_on:
mysql:
condition: service_healthy
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:4092/health', timeout=3)"]
interval: 10s
timeout: 5s
retries: 12
start_period: 10s
networks:
- cangku-internal
networks:
cangku-internal:
name: cangku-internal

View File

@ -0,0 +1,69 @@
server {
listen 80;
server_name ck.yydd.ai;
location ^~ /.well-known/acme-challenge/ {
root /www/wwwroot/cangku-acme;
default_type text/plain;
}
location / {
return 308 https://$host:8443$request_uri;
}
}
server {
listen 8443 ssl http2;
server_name ck.yydd.ai;
ssl_certificate /etc/letsencrypt/live/ck.yydd.ai/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ck.yydd.ai/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:CangkuTLS:10m;
ssl_session_timeout 1d;
root /opt/cangku/app/frontend/dist;
index index.html;
client_max_body_size 100m;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
location = /health {
proxy_pass http://127.0.0.1:4090/health;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location = /v1/enroll {
limit_except POST { deny all; }
proxy_pass http://127.0.0.1:4092/v1/enroll;
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 https;
}
location /api/ {
proxy_pass http://127.0.0.1:4090;
proxy_http_version 1.1;
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 https;
}
location /cubejs-api/ {
proxy_pass http://127.0.0.1:4090;
proxy_http_version 1.1;
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 https;
}
location / {
try_files $uri $uri/ /index.html;
}
}

View File

@ -0,0 +1,15 @@
FROM python:3.12-slim-bookworm
RUN apt-get update \
&& apt-get install -y --no-install-recommends openssh-client ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY bootstrap_service.py docker-entrypoint.sh ./
RUN chmod 0755 /app/docker-entrypoint.sh
EXPOSE 4092
ENTRYPOINT ["/app/docker-entrypoint.sh"]

View File

@ -0,0 +1,12 @@
#!/bin/sh
set -eu
ca_key=${BOOTSTRAP_SSH_CA_KEY:-/var/lib/cangku-bootstrap/ssh_user_ca}
if [ ! -f "$ca_key" ]; then
mkdir -p "$(dirname "$ca_key")"
ssh-keygen -q -t ed25519 -N '' -C cangku-bootstrap-user-ca -f "$ca_key"
fi
chmod 0600 "$ca_key"
chmod 0644 "$ca_key.pub"
exec python /app/bootstrap_service.py serve

View File

@ -1,7 +1,6 @@
Match User cangku-tunnel
AuthenticationMethods publickey
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowTcpForwarding local
PermitOpen 127.0.0.1:3307
GatewayPorts no