Deno 的部署和运维是构建生产级应用的重要环节。了解如何正确部署和运维 Deno 应用程序可以确保应用的稳定性和可维护性。
部署概述
Deno 应用可以部署到多种环境,包括传统服务器、容器化平台、云服务和边缘计算平台。
Docker 部署
1. 基础 Dockerfile
dockerfile# 使用官方 Deno 镜像 FROM denoland/deno:1.38.0 # 设置工作目录 WORKDIR /app # 复制依赖文件 COPY deno.json ./ # 缓存依赖 RUN deno cache src/main.ts # 复制源代码 COPY . . # 暴露端口 EXPOSE 8000 # 运行应用 CMD ["deno", "run", "--allow-net", "--allow-env", "src/main.ts"]
2. 多阶段构建
dockerfile# 构建阶段 FROM denoland/deno:1.38.0 AS builder WORKDIR /app COPY . . # 编译为可执行文件 RUN deno compile --allow-net --allow-env --output=app src/main.ts # 运行阶段 FROM debian:bullseye-slim WORKDIR /app # 从构建阶段复制可执行文件 COPY /app/app . # 暴露端口 EXPOSE 8000 # 运行应用 CMD ["./app"]
3. 生产环境 Dockerfile
dockerfile# 构建阶段 FROM denoland/deno:1.38.0 AS builder WORKDIR /app # 安装依赖 COPY deno.json ./ RUN deno cache src/main.ts # 复制源代码 COPY . . # 运行测试 RUN deno test --allow-all # 编译为可执行文件 RUN deno compile \ --allow-net \ --allow-env \ --allow-read \ --output=app \ src/main.ts # 运行阶段 FROM debian:bullseye-slim WORKDIR /app # 安装必要的运行时依赖 RUN apt-get update && \ apt-get install -y ca-certificates && \ rm -rf /var/lib/apt/lists/* # 创建非 root 用户 RUN useradd -m -u 1000 deno # 从构建阶段复制可执行文件 COPY /app/app . # 更改所有者 RUN chown -R deno:deno /app # 切换到非 root 用户 USER deno # 暴露端口 EXPOSE 8000 # 健康检查 HEALTHCHECK \ CMD curl -f http://localhost:8000/health || exit 1 # 运行应用 CMD ["./app"]
Kubernetes 部署
1. Deployment 配置
yamlapiVersion: apps/v1 kind: Deployment metadata: name: deno-app labels: app: deno-app spec: replicas: 3 selector: matchLabels: app: deno-app template: metadata: labels: app: deno-app spec: containers: - name: deno-app image: your-registry/deno-app:latest ports: - containerPort: 8000 env: - name: PORT value: "8000" - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: database-url resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8000 initialDelaySeconds: 5 periodSeconds: 5
2. Service 配置
yamlapiVersion: v1 kind: Service metadata: name: deno-app-service spec: selector: app: deno-app ports: - protocol: TCP port: 80 targetPort: 8000 type: LoadBalancer
3. ConfigMap 和 Secret
yaml# ConfigMap apiVersion: v1 kind: ConfigMap metadata: name: app-config data: PORT: "8000" LOG_LEVEL: "info" --- # Secret apiVersion: v1 kind: Secret metadata: name: app-secrets type: Opaque data: database-url: <base64-encoded-url> api-key: <base64-encoded-key>
云平台部署
1. Deno Deploy
Deno Deploy 是 Deno 官方的边缘计算平台。
typescript// main.ts import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; const handler = async (req: Request): Promise<Response> => { const url = new URL(req.url); if (url.pathname === "/") { return new Response("Hello from Deno Deploy!", { headers: { "Content-Type": "text/plain" }, }); } return new Response("Not Found", { status: 404 }); }; await serve(handler, { port: 8000 });
部署步骤:
- 创建 Deno Deploy 账户
- 连接 GitHub 仓库
- 配置部署设置
- 自动部署
2. Vercel 部署
json// vercel.json { "version": 2, "builds": [ { "src": "src/main.ts", "use": "@vercel/deno" } ], "routes": [ { "src": "/(.*)", "dest": "/src/main.ts" } ] }
3. Railway 部署
toml# railway.toml [build] builder = "NIXPACKS" [deploy] startCommand = "deno run --allow-net --allow-env src/main.ts" [env] PORT = "8000"
进程管理
1. 使用 PM2
javascript// ecosystem.config.js module.exports = { apps: [{ name: 'deno-app', script: 'deno', args: 'run --allow-net --allow-env src/main.ts', instances: 'max', exec_mode: 'cluster', autorestart: true, watch: false, max_memory_restart: '1G', env: { NODE_ENV: 'production', PORT: 8000 } }] };
bash# 安装 PM2 npm install -g pm2 # 启动应用 pm2 start ecosystem.config.js # 查看状态 pm2 status # 查看日志 pm2 logs # 重启应用 pm2 restart deno-app # 停止应用 pm2 stop deno-app
2. 使用 Systemd
ini# /etc/systemd/system/deno-app.service [Unit] Description=Deno Application After=network.target [Service] Type=simple User=deno WorkingDirectory=/app Environment="PORT=8000" Environment="DATABASE_URL=postgres://..." ExecStart=/usr/local/bin/deno run --allow-net --allow-env /app/src/main.ts Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
bash# 启用服务 sudo systemctl enable deno-app # 启动服务 sudo systemctl start deno-app # 查看状态 sudo systemctl status deno-app # 查看日志 sudo journalctl -u deno-app -f # 重启服务 sudo systemctl restart deno-app
监控和日志
1. 日志管理
typescript// logger.ts import { getLogger, setup, handlers } from "https://deno.land/std@0.208.0/log/mod.ts"; await setup({ handlers: { console: new handlers.ConsoleHandler("INFO"), file: new handlers.FileHandler("INFO", { filename: "./logs/app.log", formatter: "{levelName} {datetime} {msg}", }), }, loggers: { default: { level: "INFO", handlers: ["console", "file"], }, }, }); export const logger = getLogger();
2. 健康检查
typescript// health.ts import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; let isHealthy = true; let isReady = false; // 模拟启动时间 setTimeout(() => { isReady = true; }, 5000); const handler = async (req: Request): Promise<Response> => { const url = new URL(req.url); if (url.pathname === "/health") { return new Response(JSON.stringify({ status: isHealthy ? "ok" : "error" }), { headers: { "Content-Type": "application/json" }, status: isHealthy ? 200 : 503, }); } if (url.pathname === "/ready") { return new Response(JSON.stringify({ ready: isReady }), { headers: { "Content-Type": "application/json" }, status: isReady ? 200 : 503, }); } return new Response("Not Found", { status: 404 }); }; await serve(handler, { port: 8000 });
3. 指标收集
typescript// metrics.ts class MetricsCollector { private metrics: Map<string, number> = new Map(); private counters: Map<string, number> = new Map(); increment(name: string, value: number = 1) { const current = this.counters.get(name) || 0; this.counters.set(name, current + value); } gauge(name: string, value: number) { this.metrics.set(name, value); } timing(name: string, duration: number) { const timings = this.metrics.get(`${name}_timings`) || []; timings.push(duration); this.metrics.set(`${name}_timings`, timings); } getMetrics(): Record<string, any> { return { counters: Object.fromEntries(this.counters), gauges: Object.fromEntries(this.metrics), }; } } export const metrics = new MetricsCollector();
性能优化
1. 连接池
typescript// connection-pool.ts class ConnectionPool<T> { private pool: T[] = []; private maxConnections: number; private factory: () => Promise<T>; constructor(maxConnections: number, factory: () => Promise<T>) { this.maxConnections = maxConnections; this.factory = factory; } async acquire(): Promise<T> { if (this.pool.length > 0) { return this.pool.pop()!; } return await this.factory(); } release(connection: T) { if (this.pool.length < this.maxConnections) { this.pool.push(connection); } } }
2. 缓存策略
typescript// cache.ts class Cache { private cache: Map<string, { value: any; expires: number }> = new Map(); private ttl: number; constructor(ttl: number = 60000) { this.ttl = ttl; } set(key: string, value: any, ttl?: number) { const expires = Date.now() + (ttl || this.ttl); this.cache.set(key, { value, expires }); } get(key: string): any | null { const item = this.cache.get(key); if (!item) { return null; } if (Date.now() > item.expires) { this.cache.delete(key); return null; } return item.value; } clear() { this.cache.clear(); } cleanup() { const now = Date.now(); for (const [key, item] of this.cache.entries()) { if (now > item.expires) { this.cache.delete(key); } } } }
安全最佳实践
1. 环境变量管理
typescript// config.ts interface Config { port: number; databaseUrl: string; apiKey: string; logLevel: string; } function loadConfig(): Config { const port = parseInt(Deno.env.get("PORT") || "8000"); const databaseUrl = Deno.env.get("DATABASE_URL"); const apiKey = Deno.env.get("API_KEY"); const logLevel = Deno.env.get("LOG_LEVEL") || "info"; if (!databaseUrl) { throw new Error("DATABASE_URL environment variable is required"); } if (!apiKey) { throw new Error("API_KEY environment variable is required"); } return { port, databaseUrl, apiKey, logLevel, }; } export const config = loadConfig();
2. 权限最小化
bash# 只授予必要的权限 deno run --allow-net --allow-env src/main.ts # 限制网络访问范围 deno run --allow-net=api.example.com src/main.ts # 限制文件访问 deno run --allow-read=/app/data src/main.ts
CI/CD 集成
1. GitHub Actions
yaml# .github/workflows/deploy.yml name: Deploy on: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Deno uses: denoland/setup-deno@v1 with: deno-version: v1.38.0 - name: Run tests run: deno test --allow-all - name: Lint run: deno lint - name: Format check run: deno fmt --check deploy: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build Docker image run: docker build -t deno-app:${{ github.sha }} . - name: Push to registry run: | echo ${{ secrets.REGISTRY_PASSWORD }} | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin docker push deno-app:${{ github.sha }}
2. GitLab CI
yaml# .gitlab-ci.yml stages: - test - build - deploy test: stage: test image: denoland/deno:1.38.0 script: - deno test --allow-all - deno lint - deno fmt --check build: stage: build image: docker:latest services: - docker:dind script: - docker build -t deno-app:$CI_COMMIT_SHA . - docker push deno-app:$CI_COMMIT_SHA deploy: stage: deploy image: alpine:latest script: - kubectl set image deployment/deno-app deno-app=deno-app:$CI_COMMIT_SHA only: - main
故障排查
1. 常见问题
问题:应用启动失败
bash# 检查日志 deno run --log-level=debug src/main.ts # 检查权限 deno info src/main.ts
问题:内存泄漏
typescript// 定期检查内存使用 setInterval(() => { const usage = Deno.memoryUsage(); console.log("Memory usage:", usage); }, 60000);
问题:性能下降
typescript// 使用性能分析 import { performance } from "https://deno.land/std@0.208.0/node/performance.ts"; const start = performance.now(); // 执行操作 const duration = performance.now() - start; console.log(`Operation took ${duration}ms`);
最佳实践
- 容器化部署:使用 Docker 确保环境一致性
- 健康检查:实现健康检查端点
- 日志记录:记录关键操作和错误
- 监控指标:收集和监控应用指标
- 自动化部署:使用 CI/CD 自动化部署流程
- 权限最小化:只授予必要的权限
- 资源限制:设置合理的资源限制
- 备份策略:定期备份重要数据
Deno 的部署和运维需要综合考虑多个方面,通过合理的规划和实施,可以构建稳定、可靠的生产级应用。