全栈开发:服务器、运维与部署

本项目将深入演示全栈开发中的运维部署核心技能:Linux 基础操作、Docker 容器化、多阶段构建、Docker Compose 编排、CI/CD 自动化流水线、Nginx 反向代理与负载均衡。通过一个完整的 Web 应用部署示例,展示从代码提交到上线运行的全流程。


一、UML 建模(Mermaid)

1. 部署架构图

外部服务

云服务器

用户端

宿主机

Docker 容器集群

HTTPS

反向代理

反向代理

构建推送

拉取

用户浏览器

Nginx 容器
反向代理 + 负载均衡

App 容器实例1
Node.js/Python

App 容器实例2
Node.js/Python

Redis 容器
缓存/消息队列

PostgreSQL 容器
数据存储

Docker Engine

Docker Hub
镜像仓库

GitHub Actions
CI/CD

2. CI/CD 流水线

生产服务器 Docker Hub GitHub Actions GitHub 开发者 生产服务器 Docker Hub GitHub Actions GitHub 开发者 git push 触发工作流 Lint 代码检查 运行单元测试 构建 Docker 镜像 推送镜像 SSH 部署命令 docker-compose pull docker-compose up -d 部署完成

3. Nginx 负载均衡配置

Nginx

轮询

轮询

upstream backend

负载均衡算法

app:3000

app:3001


二、项目文件结构组织

我们将创建一个示例项目 deployment-demo,包含一个简单的 Node.js 应用,并展示完整的部署配置。

deployment-demo/
├── .github/
│   └── workflows/
│       └── deploy.yml          # GitHub Actions CI/CD 流水线
├── app/                        # 应用代码
│   ├── src/
│   │   └── index.js            # 简单 Express 应用
│   ├── package.json
│   ├── Dockerfile              # 多阶段构建文件
│   └── .dockerignore
├── nginx/
│   ├── nginx.conf              # Nginx 主配置
│   ├── conf.d/
│   │   └── app.conf            # 站点配置(反向代理、负载均衡)
│   └── ssl/                    # SSL 证书目录
│       ├── fullchain.pem       # 证书(示例)
│       └── privkey.pem         # 私钥(示例)
├── docker-compose.yml          # 多容器编排
├── scripts/
│   ├── deploy.sh               # 部署脚本(SSH 远程执行)
│   └── ssl-renew.sh            # 证书续期脚本(示例)
├── .env.example                # 环境变量模板
└── README.md

三、源代码实现

1. 示例应用(Node.js + Express)

app/package.json

{
  "name": "deployment-demo",
  "version": "1.0.0",
  "description": "Demo app for deployment",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "test": "echo 'Running tests...' && exit 0",
    "lint": "eslint src/"
  },
  "dependencies": {
    "express": "^4.18.2",
    "pg": "^8.11.0",
    "redis": "^4.6.0"
  },
  "devDependencies": {
    "eslint": "^8.40.0"
  }
}

app/src/index.js

const express = require('express');
const { Pool } = require('pg');
const redis = require('redis');

const app = express();
const port = process.env.PORT || 3000;

// 数据库连接
const pool = new Pool({
  host: process.env.DB_HOST || 'postgres',
  user: process.env.DB_USER || 'postgres',
  password: process.env.DB_PASSWORD || 'postgres',
  database: process.env.DB_NAME || 'appdb',
});

// Redis 连接
const redisClient = redis.createClient({
  url: `redis://${process.env.REDIS_HOST || 'redis'}:6379`,
});
redisClient.connect();

app.get('/', (req, res) => {
  res.json({ message: 'Hello from deployment demo!', host: process.env.HOSTNAME });
});

app.get('/health', (req, res) => {
  res.status(200).send('OK');
});

app.get('/db-test', async (req, res) => {
  try {
    const result = await pool.query('SELECT NOW()');
    res.json({ time: result.rows[0].now });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.get('/redis-test', async (req, res) => {
  await redisClient.set('test', 'ok');
  const val = await redisClient.get('test');
  res.json({ redis: val });
});

app.listen(port, () => {
  console.log(`App listening on port ${port}`);
});

2. Dockerfile(多阶段构建)

app/Dockerfile

# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

# 运行阶段
FROM node:18-alpine
WORKDIR /app
# 从构建阶段复制依赖
COPY --from=builder /app/node_modules ./node_modules
COPY . .
# 创建非 root 用户运行
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "src/index.js"]

3. Docker Compose 编排

docker-compose.yml

version: '3.8'

services:
  postgres:
    image: postgres:14-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: appdb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    build: ./app
    environment:
      - PORT=3000
      - DB_HOST=postgres
      - DB_USER=postgres
      - DB_PASSWORD=postgres
      - DB_NAME=appdb
      - REDIS_HOST=redis
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    deploy:
      replicas: 2  # 多实例,用于负载均衡演示(需 swarm 或 compose 版本)
    # 这里仅定义单个服务,负载均衡由 Nginx 实现

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - app
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres_data:
  redis_data:

注意:由于 Docker Compose 默认不支持 deploy.replicas,如需多实例需使用 docker-compose up --scale app=2 或使用 Docker Swarm。上面仅作为示意,实际部署时可通过 docker-compose up --scale app=2 启动两个 app 容器,Nginx 配置 upstream 指向它们。

4. Nginx 配置

nginx/nginx.conf(主配置,包含 gzip 和基础设置)

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
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;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    include /etc/nginx/conf.d/*.conf;
}

nginx/conf.d/app.conf(反向代理与负载均衡)

# 定义后端服务组(负载均衡)
upstream backend {
    # 轮询策略(默认)
    server app:3000;   # 当 docker-compose 有多个 app 实例时,可添加多个
    # server app_2:3000;  # 示例,实际需根据容器名
    # 权重轮询示例
    # server app:3000 weight=3;
    # server app_2:3000 weight=1;

    # 保持会话的 IP 哈希策略
    # ip_hash;
}

server {
    listen 80;
    server_name example.com;  # 替换为实际域名

    # 重定向到 HTTPS(如果有证书)
    # return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # 根路径反向代理
    location / {
        proxy_pass http://backend;
        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;
    }

    # 健康检查端点
    location /health {
        proxy_pass http://backend/health;
        access_log off;
    }

    # 静态资源缓存(示例)
    location /static/ {
        alias /var/www/static/;
        expires 30d;
    }
}

5. CI/CD 流水线(GitHub Actions)

.github/workflows/deploy.yml

name: Deploy to Production

on:
  push:
    branches:
      - main  # 当推送 main 分支时触发

env:
  DOCKER_IMAGE_NAME: your-dockerhub-username/deployment-demo
  DOCKER_TAG: ${{ github.sha }}

jobs:
  test-and-build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 18

      - name: Install dependencies
        run: npm ci
        working-directory: ./app

      - name: Lint code
        run: npm run lint
        working-directory: ./app

      - name: Run tests
        run: npm test
        working-directory: ./app

      - name: Log in to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build Docker image
        run: docker build -t ${{ env.DOCKER_IMAGE_NAME }}:${{ env.DOCKER_TAG }} ./app

      - name: Push Docker image
        run: docker push ${{ env.DOCKER_IMAGE_NAME }}:${{ env.DOCKER_TAG }}

      - name: Tag as latest
        run: |
          docker tag ${{ env.DOCKER_IMAGE_NAME }}:${{ env.DOCKER_TAG }} ${{ env.DOCKER_IMAGE_NAME }}:latest
          docker push ${{ env.DOCKER_IMAGE_NAME }}:latest

  deploy:
    needs: test-and-build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy to server via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/deployment-demo
            docker-compose pull app
            docker-compose up -d --no-deps app
            # 可选:清理旧镜像
            docker image prune -f

6. 部署辅助脚本

scripts/deploy.sh(本地或服务器执行)

#!/bin/bash
# 在服务器上拉取最新镜像并重启服务

set -e

cd /opt/deployment-demo

echo "Pulling latest images..."
docker-compose pull

echo "Restarting services..."
docker-compose up -d

echo "Cleaning up old images..."
docker image prune -f

echo "Deployment completed."

scripts/ssl-renew.sh(使用 Certbot 续期证书)

#!/bin/bash
# 通过 Certbot 自动续期 SSL 证书

# 停止 Nginx 以释放 80/443 端口(如果使用 standalone 模式)
docker-compose stop nginx

# 运行 Certbot
certbot renew --standalone --preferred-challenges http --non-interactive --agree-tos

# 重新启动 Nginx
docker-compose start nginx

echo "SSL certificates renewed."

四、深入解析与最佳实践

1. Docker 多阶段构建

  • 构建阶段:使用完整开发环境安装依赖(npm ci),只保留生产依赖,减少镜像层数。
  • 运行阶段:切换到更小的基础镜像,仅复制必要的文件,避免包含构建工具和源代码中的无关文件。
  • 用户隔离:创建非 root 用户运行应用,增强安全性。
  • 镜像瘦身:最终镜像大小通常从几百 MB 降至几十 MB。

2. Docker Compose 多容器编排

  • 服务依赖:使用 depends_on 结合 condition: service_healthy 确保数据库和缓存启动后再启动应用。
  • 网络隔离:通过自定义网络 app-network 实现服务间通信,无需暴露端口到宿主机。
  • 数据持久化:使用 volumes 挂载数据库和 Redis 数据,防止容器重启后丢失。
  • 环境变量管理:使用 .env 文件或直接写入 docker-compose.ymlenvironment 字段。

3. Nginx 反向代理与负载均衡

  • upstream 定义:支持轮询、加权轮询、IP 哈希等策略。当应用容器数量变化时,需要动态更新 upstream(可用 Consul、Nginx Plus 或重新加载)。
  • SSL 配置:证书使用 Let’s Encrypt 免费证书,通过 Certbot 自动续期。示例中挂载证书文件,实际生产环境建议使用 nginx-proxy 配合 acme-companion 自动化。
  • Gzip 压缩:对常见 MIME 类型开启压缩,减少传输体积。
  • 健康检查:配置 /health 端点,供负载均衡器或监控使用。

4. CI/CD 流水线

  • 分支策略:触发 main 分支的推送,自动执行完整流程。
  • 环境变量与密钥:使用 GitHub Secrets 存储敏感信息(Docker 凭证、服务器 SSH 密钥)。
  • 部署策略
    • 使用 docker-compose pull 拉取新镜像
    • docker-compose up -d 重启服务(如有滚动更新需求可结合 --no-deps
    • 清理旧镜像释放空间
  • 安全加固:部署用户使用 SSH 密钥而非密码,限制权限。

5. Linux 常用命令(部署相关)

  • 进程监控top, htop, ps aux
  • 网络诊断netstat -tulpn, ss -tulpn, curl -I
  • 日志查看tail -f /var/log/nginx/access.log, docker logs container_name
  • 文本处理grep, awk, sed 用于日志分析和配置修改
  • Shell 脚本:使用 #!/bin/bash 编写自动化部署脚本,结合 set -e 使脚本在错误时退出。

6. 证书续期(Let’s Encrypt)

  • Certbot 模式:standalone 需要临时占用 80/443 端口,适用于无 Web 服务器运行的情况;webroot 模式通过已有 Web 服务器验证,更适合生产环境。
  • 自动续期:通过 crontab 定期执行 certbot renew,并在续期后重载 Nginx。
  • Nginx 热重载docker exec nginx nginx -s reload 无需重启容器。

五、总结

本项目通过一个完整的 Web 应用示例,系统性地实践了全栈开发中的服务器运维与部署核心技能:

  • 容器化:Docker 多阶段构建、Docker Compose 编排
  • 反向代理与负载均衡:Nginx 配置 upstream、gzip、SSL
  • CI/CD:GitHub Actions 自动化构建、测试、推送、部署
  • 自动化运维:Shell 脚本、证书续期

全栈开发者通过掌握这些技能,能够独立完成从代码提交到线上运行的完整闭环,并确保应用的高可用性和安全性。实际生产中可根据业务规模选择更高级的编排工具(如 Kubernetes)和 CI/CD 平台(如 GitLab CI、Jenkins),但核心思想与本方案一致。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐