第 50 课:项目部署上线
本课目标:掌握CI/CD基础流程,使用GitHub Actions自动化部署,完成Linux服务器部署、Nginx配置、Docker容器化部署。
一、概念讲解
1.1 部署流程概览
开发 → 推送代码 → CI/CD自动构建 → 部署服务器 → 验证上线
┌─────────────────────────────────────────────────────┐
│ 部署流程 │
├──────────┬──────────┬──────────┬──────────┬─────────┤
│ 代码提交 │ 自动构建 │ 镜像打包 │ 服务器部署 │ 健康检查 │
│ Git │ Maven │ Docker │ Docker │ Actuator│
└──────────┴──────────┴──────────┴──────────┴─────────┘
1.2 CI/CD概念
CI(持续集成):
代码合并 → 自动构建 → 自动测试 → 反馈结果
CD(持续交付/部署):
构建产物 → 自动部署到测试环境 → 手动/自动发布到生产环境
1.3 部署架构
生产环境架构:
┌─────────────────────────────────────────────────┐
│ Nginx (80/443) │
│ 反向代理+SSL │
├────────────────────┬────────────────────────────┤
│ / (前端静态资源) │ /api/ (后端API) │
│ Vue构建产物 │ Spring Boot │
├────────────────────┼────────────────────────────┤
│ │ │
│ Docker容器1 │ Docker容器2 │
│ nginx:alpine │ my-app:latest │
└────────────────────┴────────────────────────────┘
│
┌──────────────┴──────────────┐
│ │
┌─────┴─────┐ ┌───────┴─────┐
│ MySQL │ │ Redis │
│ Docker │ │ Docker │
└───────────┘ └─────────────┘
二、语法格式
2.1 GitHub Actions工作流语法
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build
run: mvn clean package -DskipTests
2.2 Docker部署命令
# 构建并启动
docker-compose up -d --build
# 查看状态
docker-compose ps
# 查看日志
docker-compose logs -f app
# 停止服务
docker-compose down
三、代码案例
3.1 GitHub Actions工作流
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [ main ]
env:
IMAGE_NAME: my-app
SERVER_HOST: ${{ secrets.SERVER_HOST }}
SERVER_USER: ${{ secrets.SERVER_USER }}
jobs:
build:
runs-on: ubuntu-latest
steps:
# 1. 检出代码
- name: Checkout
uses: actions/checkout@v4
# 2. 配置JDK
- name: Setup JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
# 3. Maven打包
- name: Build with Maven
run: mvn clean package -DskipTests
# 4. 构建Docker镜像
- name: Build Docker image
run: |
docker build -t $IMAGE_NAME:${{ github.sha }} .
docker tag $IMAGE_NAME:${{ github.sha }} $IMAGE_NAME:latest
# 5. 推送到Docker Hub(可选)
- name: Login to Docker Hub
if: success()
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push to Docker Hub
if: success()
run: |
docker push $IMAGE_NAME:${{ github.sha }}
docker push $IMAGE_NAME:latest
# 6. 部署到服务器
- name: Deploy to server
if: success()
uses: appleboy/ssh-[email protected]
with:
host: $SERVER_HOST
username: $SERVER_USER
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/deploy
docker-compose pull
docker-compose up -d --remove-orphans
docker image prune -f
echo "部署完成!"
3.2 Dockerfile
# Dockerfile
FROM eclipse-temurin:17-jre-jammy
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 设置工作目录
WORKDIR /app
# 复制jar包
COPY target/my-app.jar app.jar
# 切换用户
USER appuser
# 暴露端口
EXPOSE 8080
# JVM优化参数
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-XX:InitialRAMPercentage=50.0", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", \
"app.jar", \
"--spring.profiles.active=prod"]
3.3 docker-compose.yml
version: '3.8'
services:
# 应用服务
app:
build:
context: .
dockerfile: Dockerfile
container_name: my-app
restart: always
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_HOST=mysql
- DB_PORT=3306
- DB_NAME=demo_prod
- DB_USERNAME=root
- DB_PASSWORD=${DB_PASSWORD:-root123}
- REDIS_HOST=redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- app-network
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
# Nginx
nginx:
image: nginx:alpine
container_name: nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
- ./frontend/dist:/usr/share/nginx/html
depends_on:
- app
networks:
- app-network
# MySQL
mysql:
image: mysql:8.0
container_name: mysql-db
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-root123}
MYSQL_DATABASE: demo_prod
MYSQL_CHARSET: utf8mb4
TZ: Asia/Shanghai
volumes:
- mysql-data:/var/lib/mysql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
command: --default-authentication-plugin=caching_sha2_password
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
# Redis
redis:
image: redis:7-alpine
container_name: redis-cache
restart: always
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
networks:
- app-network
volumes:
mysql-data:
redis-data:
networks:
app-network:
driver: bridge
3.4 Nginx配置
# nginx/nginx.conf
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;
# Gzip压缩
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;
# 上游服务
upstream backend {
server app:8080;
}
server {
listen 80;
server_name example.com;
# 前端静态资源
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# API反向代理
location /api/ {
proxy_pass http://backend/api/;
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_connect_timeout 30s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 7d;
add_header Cache-Control "public, no-transform";
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
}
}
}
3.5 部署脚本
#!/bin/bash
# deploy.sh - 一键部署脚本
set -e
echo "=========================================="
echo " 项目自动化部署脚本"
echo "=========================================="
# 配置
APP_NAME="my-app"
DEPLOY_DIR="/opt/deploy"
SQL_DIR="./sql"
# 1. 打包
echo "[1/5] Maven打包..."
mvn clean package -DskipTests
echo "打包完成!"
# 2. 构建前端(如果有)
echo "[2/5] 构建前端..."
if [ -d "./frontend" ]; then
cd frontend
npm install
npm run build
cd ..
echo "前端构建完成!"
fi
# 3. 构建Docker镜像
echo "[3/5] 构建Docker镜像..."
docker build -t $APP_NAME:latest .
echo "镜像构建完成!"
# 4. 停止旧容器
echo "[4/5] 停止旧容器..."
cd $DEPLOY_DIR 2>/dev/null || mkdir -p $DEPLOY_DIR
docker-compose down 2>/dev/null || true
# 5. 启动新容器
echo "[5/5] 启动新容器..."
docker-compose up -d
# 等待启动
echo "等待服务启动..."
sleep 15
# 健康检查
echo "执行健康检查..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/api/actuator/health)
if [ "$HTTP_CODE" = "200" ]; then
echo "=========================================="
echo " 部署成功!服务已启动"
echo " 访问地址: http://$(hostname):80"
echo "=========================================="
else
echo "=========================================="
echo " 部署失败!HTTP状态码: $HTTP_CODE"
echo " 查看日志: docker-compose logs -f"
echo "=========================================="
exit 1
fi
▶ 运行结果:
==========================================
项目自动化部署脚本
==========================================
[1/5] Maven打包...
[INFO] BUILD SUCCESS
打包完成!
[2/5] 构建前端...
[3/5] 构建Docker镜像...
Successfully built abc123def456
Successfully tagged my-app:latest
镜像构建完成!
[4/5] 停止旧容器...
[5/5] 启动新容器...
Creating network "deploy_app-network" with the default driver
Creating deploy_app_1 ... done
Creating deploy_mysql_1 ... done
Creating deploy_redis_1 ... done
Creating deploy_nginx_1 ... done
等待服务启动...
执行健康检查...
==========================================
部署成功!服务已启动
访问地址: http://your-server-ip:80
==========================================
3.6 SQL初始化脚本
-- sql/init.sql
CREATE DATABASE IF NOT EXISTS demo_prod DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE demo_prod;
-- 用户表
CREATE TABLE IF NOT EXISTS sys_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(200) NOT NULL,
role VARCHAR(20) DEFAULT 'USER',
status INT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 商品表
CREATE TABLE IF NOT EXISTS sys_product (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
category_id BIGINT,
status INT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
-- 初始化管理员(密码: admin123,BCrypt加密)
INSERT INTO sys_user (username, email, password, role, status)
VALUES ('admin', '[email protected]',
'$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi',
'ADMIN', 1)
ON DUPLICATE KEY UPDATE username = username;
3.7 服务器初始化脚本
#!/bin/bash
# server-init.sh - 服务器初始化脚本
echo "=== 服务器初始化 ==="
# 1. 更新系统
sudo apt update && sudo apt upgrade -y
# 2. 安装Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
# 3. 安装Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 4. 创建部署目录
sudo mkdir -p /opt/deploy
sudo chown $USER:$USER /opt/deploy
# 5. 配置防火墙
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 22/tcp
sudo ufw --force enable
echo "=== 服务器初始化完成 ==="
▶ 运行结果:
=== 服务器初始化 ===
Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
Get:2 http://archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB]
...
Reading package lists... Done
Building dependency tree... Done
...
[OK] Docker installed successfully
=== 服务器初始化完成 ===
3.8 生产环境配置
# application-prod.yml
server:
port: 8080
tomcat:
max-threads: 200
min-spare-threads: 20
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:demo_prod}?useSSL=true&serverTimezone=Asia/Shanghai
username: ${DB_USERNAME:root}
password: ${DB_PASSWORD:root123}
hikari:
maximum-pool-size: 20
minimum-idle: 5
redis:
host: ${REDIS_HOST:localhost}
port: 6379
jpa:
hibernate:
ddl-auto: none
show-sql: false
logging:
level:
root: INFO
com.example: INFO
management:
endpoints:
web:
exposure:
include: health,info
3.9 监控配置(可选)
# docker-compose.monitoring.yml
version: '3.8'
services:
# Prometheus
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- app-network
# Grafana
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
networks:
- app-network
volumes:
grafana-data:
networks:
app-network:
external: true
# monitoring/prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'spring-boot'
metrics_path: '/api/actuator/prometheus'
static_configs:
- targets: ['app:8080']
四、常见错误
4.1 GitHub Actions SSH连接失败
原因:SSH密钥配置错误或服务器未开放22端口。
解决:
- 生成SSH密钥:
ssh-keygen -t rsa - 将公钥添加到服务器
~/.ssh/authorized_keys - 在GitHub Secrets中配置私钥
4.2 Docker容器启动失败
排查步骤:
# 查看容器状态
docker-compose ps
# 查看日志
docker-compose logs app
# 进入容器调试
docker exec -it my-app bash
4.3 Nginx 502 Bad Gateway
原因:后端服务未启动或连接失败。
解决:检查Docker容器是否运行,网络是否连通。
4.4 数据库连接失败
现象:Communications link failure
解决:
- 检查MySQL容器是否运行
- 检查连接地址是否正确(Docker内用服务名,非localhost)
- 检查用户名密码是否正确
五、课后练习
- 配置GitHub Actions,实现push到main分支时自动构建并部署
- 编写一键部署脚本,包含打包、构建、启动、健康检查全流程
- 配置Nginx实现前端静态资源服务和API反向代理
- 搭建Prometheus+Grafana监控,查看应用运行指标
六、本课小结
| 知识点 | 说明 |
|---|---|
| GitHub Actions | CI/CD自动化工具,支持代码推送后自动构建部署 |
| Docker | 容器化技术,确保开发、测试、生产环境一致 |
| docker-compose | 多容器编排,一键启动应用+数据库+缓存+代理 |
| Nginx | 反向代理+负载均衡+静态资源服务 |
| 部署脚本 | 自动化部署流程,减少人工操作错误 |
| 健康检查 | Actuator端点监控应用状态,确保服务可用 |
本课程持续更新中,欢迎关注!
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_26950675/article/details/166601558



