部署上线 #

一、部署准备 #

1.1 环境变量 #

.env.production:

env
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
JWT_SECRET=your-production-secret-key
DATABASE_URL=postgresql://user:pass@host:5432/db

1.2 生产配置 #

src/config/index.js:

javascript
module.exports = {
    port: parseInt(process.env.PORT, 10) || 3000,
    host: process.env.HOST || '0.0.0.0',
    env: process.env.NODE_ENV || 'development',
    
    isProduction: process.env.NODE_ENV === 'production',
    isDevelopment: process.env.NODE_ENV === 'development'
};

二、PM2部署 #

2.1 安装PM2 #

bash
npm install -g pm2

2.2 PM2配置 #

ecosystem.config.js:

javascript
module.exports = {
    apps: [{
        name: 'hapi-app',
        script: './src/index.js',
        instances: 'max',
        exec_mode: 'cluster',
        autorestart: true,
        watch: false,
        max_memory_restart: '1G',
        env: {
            NODE_ENV: 'development',
            PORT: 3000
        },
        env_production: {
            NODE_ENV: 'production',
            PORT: 3000
        },
        error_file: './logs/error.log',
        out_file: './logs/out.log',
        log_file: './logs/combined.log',
        time: true
    }]
};

2.3 PM2命令 #

bash
pm2 start ecosystem.config.js --env production
pm2 stop hapi-app
pm2 restart hapi-app
pm2 delete hapi-app
pm2 logs hapi-app
pm2 monit
pm2 save
pm2 startup

三、Docker部署 #

3.1 Dockerfile #

dockerfile
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm ci --only=production

COPY . .

EXPOSE 3000

USER node

CMD ["node", "src/index.js"]

3.2 docker-compose.yml #

yaml
version: '3.8'

services:
    app:
        build: .
        ports:
            - "3000:3000"
        environment:
            - NODE_ENV=production
            - PORT=3000
            - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
        depends_on:
            - db
            - redis
        restart: always

    db:
        image: postgres:14-alpine
        environment:
            - POSTGRES_USER=postgres
            - POSTGRES_PASSWORD=password
            - POSTGRES_DB=myapp
        volumes:
            - postgres_data:/var/lib/postgresql/data
        restart: always

    redis:
        image: redis:7-alpine
        volumes:
            - redis_data:/data
        restart: always

    nginx:
        image: nginx:alpine
        ports:
            - "80:80"
            - "443:443"
        volumes:
            - ./nginx.conf:/etc/nginx/nginx.conf
            - ./ssl:/etc/nginx/ssl
        depends_on:
            - app
        restart: always

volumes:
    postgres_data:
    redis_data:

3.3 Docker命令 #

bash
docker build -t hapi-app .
docker run -p 3000:3000 hapi-app
docker-compose up -d
docker-compose down
docker-compose logs -f

四、Nginx配置 #

4.1 nginx.conf #

nginx
events {
    worker_connections 1024;
}

http {
    upstream hapi_app {
        server app:3000;
    }

    server {
        listen 80;
        server_name example.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl;
        server_name example.com;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;

        location / {
            proxy_pass http://hapi_app;
            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;
        }

        location /health {
            proxy_pass http://hapi_app/health;
            access_log off;
        }
    }
}

五、CI/CD配置 #

5.1 GitHub Actions #

.github/workflows/deploy.yml:

yaml
name: Deploy

on:
    push:
        branches: [main]

jobs:
    deploy:
        runs-on: ubuntu-latest
        
        steps:
            - uses: actions/checkout@v3
            
            - name: Setup Node.js
              uses: actions/setup-node@v3
              with:
                  node-version: '18'
                  cache: 'npm'
            
            - name: Install dependencies
              run: npm ci
            
            - name: Run tests
              run: npm test
            
            - name: Build Docker image
              run: docker build -t hapi-app .
            
            - name: Deploy to server
              uses: appleboy/ssh-action@master
              with:
                  host: ${{ secrets.HOST }}
                  username: ${{ secrets.USERNAME }}
                  key: ${{ secrets.SSH_KEY }}
                  script: |
                      cd /app
                      git pull
                      docker-compose down
                      docker-compose up -d --build

六、健康检查 #

6.1 健康检查路由 #

javascript
server.route({
    method: 'GET',
    path: '/health',
    options: {
        auth: false
    },
    handler: async (request, h) => {
        const health = {
            status: 'ok',
            timestamp: new Date().toISOString(),
            uptime: process.uptime(),
            memory: process.memoryUsage(),
            env: process.env.NODE_ENV
        };
        
        return health;
    }
});

server.route({
    method: 'GET',
    path: '/health/ready',
    options: {
        auth: false
    },
    handler: async (request, h) => {
        try {
            await checkDatabaseConnection();
            await checkRedisConnection();
            
            return { status: 'ready' };
        } catch (error) {
            return h.response({ status: 'not ready', error: error.message }).code(503);
        }
    }
});

七、日志管理 #

7.1 日志配置 #

javascript
const fs = require('fs');
const path = require('path');

const logDir = path.join(__dirname, '../logs');

if (!fs.existsSync(logDir)) {
    fs.mkdirSync(logDir, { recursive: true });
}

server.events.on('response', (request) => {
    const log = {
        timestamp: new Date().toISOString(),
        method: request.method,
        path: request.path,
        status: request.response.statusCode,
        duration: request.info.responded - request.info.received,
        ip: request.info.remoteAddress
    };
    
    console.log(JSON.stringify(log));
});

7.2 日志轮转 #

bash
npm install rotating-file-stream
javascript
const rfs = require('rotating-file-stream');

const accessLog = rfs.createStream('access.log', {
    path: path.join(__dirname, 'logs'),
    size: '10M',
    interval: '1d',
    compress: 'gzip'
});

八、监控告警 #

8.1 进程监控 #

javascript
setInterval(() => {
    const memoryUsage = process.memoryUsage();
    const cpuUsage = process.cpuUsage();
    
    if (memoryUsage.heapUsed > 500 * 1024 * 1024) {
        console.warn('High memory usage:', memoryUsage);
    }
}, 60000);

8.2 错误追踪 #

javascript
process.on('uncaughtException', (error) => {
    console.error('Uncaught Exception:', error);
    process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('Unhandled Rejection:', reason);
});

server.events.on({ name: 'request', channels: 'error' }, (request, event) => {
    console.error('Request error:', event.error);
});

九、性能优化 #

9.1 启用压缩 #

javascript
const server = Hapi.server({
    port: 3000,
    compression: {
        minBytes: 1024
    }
});

9.2 启用缓存 #

javascript
server.method('getData', async (key) => {
    return await fetchData(key);
}, {
    cache: {
        expiresIn: 60 * 1000,
        generateTimeout: 100
    }
});

9.3 连接池 #

javascript
const { Pool } = require('pg');

const pool = new Pool({
    max: 20,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 2000
});

十、部署检查清单 #

10.1 检查项 #

  • [ ] 环境变量配置
  • [ ] 数据库连接
  • [ ] Redis连接
  • [ ] 日志配置
  • [ ] 错误处理
  • [ ] 健康检查
  • [ ] HTTPS配置
  • [ ] 监控告警
  • [ ] 备份策略
  • [ ] 回滚方案

10.2 部署命令 #

bash
pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup

十一、总结 #

部署要点:

工具 用途
PM2 进程管理
Docker 容器化部署
Nginx 反向代理
GitHub Actions CI/CD

恭喜你完成Hapi.js完全指南的学习!

最后更新:2026-03-28