Docker 容器化概述
为了简化部署流程、提高环境一致性和便于扩展,我们为辣评项目实现了完整的 Docker 容器化方案。本文详细记录了 Docker 镜像构建、容器编排和部署的完整过程。
容器化目标
- 简化部署流程
- 保证环境一致性
- 便于横向扩展
- 提高资源利用率
- 简化运维管理
Docker 镜像构建
多阶段构建 Dockerfile
# Dockerfile
# 多阶段构建,减小最终镜像体积
# 阶段1:构建前端
FROM node:18-alpine AS frontend-builder
WORKDIR /app/admin-vue
# 复制前端依赖文件
COPY admin-vue/package*.json ./
# 安装依赖
RUN npm install --registry=https://registry.npmmirror.com
# 复制前端源码
COPY admin-vue/ ./
# 构建前端
RUN npm run build
# 阶段2:构建后端
FROM golang:1.21-alpine AS backend-builder
WORKDIR /app
# 安装构建依赖
RUN apk add --no-cache git
# 复制 Go 模块文件
COPY go.mod go.sum ./
# 下载依赖
RUN go mod download
# 复制后端源码
COPY cmd/ ./cmd/
COPY VERSION ./
# 构建后端
RUN cd cmd/server && \
CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o /app/laping-server main.go
# 阶段3:最终镜像
FROM alpine:latest
# 安装运行时依赖
RUN apk --no-cache add \
ca-certificates \
tzdata \
sqlite \
&& rm -rf /var/cache/apk/*
# 设置时区
ENV TZ=Asia/Shanghai
# 创建应用目录
WORKDIR /app
# 从构建阶段复制文件
COPY --from=backend-builder /app/laping-server .
COPY --from=frontend-builder /app/admin-vue/dist ./frontend
COPY config.json.example ./config.json
COPY VERSION .
# 创建数据目录
RUN mkdir -p /app/data /app/uploads
# 设置权限
RUN chmod +x /app/laping-server
# 暴露端口
EXPOSE 8888
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8888/api/health || exit 1
# 启动应用
CMD ["./laping-server"]
构建脚本
#!/bin/bash
# docker-build.sh
set -e
echo "========================================="
echo "辣评 Docker 镜像构建脚本"
echo "========================================="
# 读取版本号
VERSION=$(cat VERSION 2>/dev/null || echo "latest")
echo "版本号:$VERSION"
# 镜像名称
IMAGE_NAME="laping"
IMAGE_TAG="${IMAGE_NAME}:${VERSION}"
IMAGE_LATEST="${IMAGE_NAME}:latest"
# 构建镜像
echo ""
echo "构建 Docker 镜像..."
docker build -t ${IMAGE_TAG} -t ${IMAGE_LATEST} .
# 显示镜像信息
echo ""
echo "镜像构建完成:"
docker images | grep ${IMAGE_NAME}
echo ""
echo "========================================="
echo "构建完成!"
echo "镜像标签:"
echo " - ${IMAGE_TAG}"
echo " - ${IMAGE_LATEST}"
echo ""
echo "运行命令:"
echo " docker run -d -p 8888:8888 --name laping ${IMAGE_TAG}"
echo "========================================="
docker-compose 配置
基础配置
# docker-compose.yml
version: '3.8'
services:
laping:
build:
context: .
dockerfile: Dockerfile
image: laping:latest
container_name: laping-server
restart: unless-stopped
ports:
- "8888:8888"
volumes:
# 数据持久化
- ./data:/app/data
- ./uploads:/app/uploads
# 配置文件
- ./config.json:/app/config.json:ro
# 日志
- ./logs:/app/logs
environment:
- TZ=Asia/Shanghai
- GIN_MODE=release
networks:
- laping-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8888/api/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 10s
networks:
laping-network:
driver: bridge
生产环境配置
# docker-compose.prod.yml
version: '3.8'
services:
laping:
image: laping:1.5.0
container_name: laping-prod
restart: always
ports:
- "8888:8888"
volumes:
- /opt/laping/data:/app/data
- /opt/laping/uploads:/app/uploads
- /opt/laping/config.json:/app/config.json:ro
- /opt/laping/logs:/app/logs
environment:
- TZ=Asia/Shanghai
- GIN_MODE=release
networks:
- laping-network
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
# Nginx 反向代理(可选)
nginx:
image: nginx:alpine
container_name: laping-nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- ./nginx/logs:/var/log/nginx
depends_on:
- laping
networks:
- laping-network
networks:
laping-network:
driver: bridge
Nginx 反向代理
Nginx 配置
# nginx/nginx.conf
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;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss
application/rss+xml font/truetype font/opentype
application/vnd.ms-fontobject image/svg+xml;
# 上游服务器
upstream laping_backend {
server laping:8888;
}
# HTTP 服务器
server {
listen 80;
server_name your-domain.com;
# 重定向到 HTTPS
return 301 https://$server_name$request_uri;
}
# HTTPS 服务器
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL 证书
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# SSL 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# 客户端最大上传大小
client_max_body_size 50M;
# 反向代理到后端
location / {
proxy_pass http://laping_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;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 静态文件缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://laping_backend;
expires 1y;
add_header Cache-Control "public, immutable";
}
# 健康检查
location /health {
proxy_pass http://laping_backend/api/health;
access_log off;
}
}
}
环境变量管理
.env 文件
# .env
# 应用配置
APP_ENV=production
APP_PORT=8888
# 数据库配置
DB_PATH=/app/data/laping.db
# JWT 配置
JWT_SECRET=your-secret-key-change-in-production
JWT_EXPIRE_HOURS=24
JWT_REFRESH_HOURS=168
# 管理员配置
ADMIN_USERNAME=admin
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=change-this-password
# 日志配置
LOG_LEVEL=info
LOG_PATH=/app/logs
docker-compose 使用环境变量
# docker-compose.yml
version: '3.8'
services:
laping:
image: laping:${VERSION:-latest}
container_name: laping-${APP_ENV:-dev}
ports:
- "${APP_PORT:-8888}:8888"
environment:
- APP_ENV=${APP_ENV}
- JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRE_HOURS=${JWT_EXPIRE_HOURS}
- LOG_LEVEL=${LOG_LEVEL}
env_file:
- .env
部署最佳实践
部署脚本
#!/bin/bash
# deploy.sh
set -e
echo "========================================="
echo "辣评 Docker 部署脚本"
echo "========================================="
# 检查 Docker 和 docker-compose
if ! command -v docker &> /dev/null; then
echo "错误:未安装 Docker"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
echo "错误:未安装 docker-compose"
exit 1
fi
# 读取版本号
VERSION=$(cat VERSION 2>/dev/null || echo "latest")
echo "部署版本:$VERSION"
# 停止旧容器
echo ""
echo "停止旧容器..."
docker-compose down
# 备份数据
echo ""
echo "备份数据..."
BACKUP_DIR="./backups/$(date +%Y%m%d%H%M%S)"
mkdir -p ${BACKUP_DIR}
if [ -d "./data" ]; then
cp -r ./data ${BACKUP_DIR}/
echo "数据已备份到:${BACKUP_DIR}"
fi
# 拉取最新镜像(如果使用远程镜像)
# echo ""
# echo "拉取最新镜像..."
# docker-compose pull
# 构建镜像(如果本地构建)
echo ""
echo "构建镜像..."
docker-compose build
# 启动容器
echo ""
echo "启动容器..."
docker-compose up -d
# 等待服务启动
echo ""
echo "等待服务启动..."
sleep 5
# 检查服务状态
echo ""
echo "检查服务状态..."
docker-compose ps
# 检查健康状态
echo ""
echo "检查健康状态..."
for i in {1..10}; do
if curl -f http://localhost:8888/api/health > /dev/null 2>&1; then
echo "✓ 服务健康检查通过"
break
fi
if [ $i -eq 10 ]; then
echo "✗ 服务健康检查失败"
docker-compose logs --tail=50
exit 1
fi
echo "等待服务就绪... ($i/10)"
sleep 3
done
# 显示日志
echo ""
echo "最近日志:"
docker-compose logs --tail=20
echo ""
echo "========================================="
echo "部署完成!"
echo "访问地址:http://localhost:8888"
echo ""
echo "常用命令:"
echo " 查看日志:docker-compose logs -f"
echo " 停止服务:docker-compose down"
echo " 重启服务:docker-compose restart"
echo "========================================="
更新脚本
#!/bin/bash
# update.sh
set -e
echo "========================================="
echo "辣评 Docker 更新脚本"
echo "========================================="
# 拉取最新代码
echo "拉取最新代码..."
git pull
# 读取新版本号
NEW_VERSION=$(cat VERSION)
echo "新版本:$NEW_VERSION"
# 备份当前数据
echo ""
echo "备份数据..."
BACKUP_DIR="./backups/$(date +%Y%m%d%H%M%S)"
mkdir -p ${BACKUP_DIR}
cp -r ./data ${BACKUP_DIR}/ 2>/dev/null || true
cp ./config.json ${BACKUP_DIR}/ 2>/dev/null || true
# 停止旧容器
echo ""
echo "停止旧容器..."
docker-compose down
# 构建新镜像
echo ""
echo "构建新镜像..."
docker-compose build
# 启动新容器
echo ""
echo "启动新容器..."
docker-compose up -d
# 清理旧镜像
echo ""
echo "清理旧镜像..."
docker image prune -f
echo ""
echo "========================================="
echo "更新完成!"
echo "新版本:$NEW_VERSION"
echo "========================================="
监控和日志
日志查看
# 查看实时日志
docker-compose logs -f
# 查看特定服务日志
docker-compose logs -f laping
# 查看最近 100 行日志
docker-compose logs --tail=100
# 查看带时间戳的日志
docker-compose logs -f --timestamps
容器监控
# 查看容器状态
docker-compose ps
# 查看容器资源使用
docker stats laping-server
# 查看容器详细信息
docker inspect laping-server
# 进入容器
docker exec -it laping-server sh
故障排查
常见问题
1. 容器无法启动
# 查看详细日志
docker-compose logs laping
# 检查配置文件
docker-compose config
# 检查端口占用
netstat -tulpn | grep 8888
2. 数据库文件权限问题
# 修复权限
sudo chown -R 1000:1000 ./data
3. 内存不足
# 调整资源限制
deploy:
resources:
limits:
memory: 2G
总结
Docker 容器化方案为辣评项目提供了标准化、可复制的部署方式。通过多阶段构建、docker-compose 编排和完善的部署脚本,我们实现了简单、可靠的部署流程。
关键成果
- 多阶段构建减小镜像体积
- docker-compose 简化容器编排
- Nginx 反向代理支持 HTTPS
- 完善的部署和更新脚本
- 健康检查和监控机制
技术亮点
- 多阶段构建优化
- 数据持久化
- 环境变量管理
- 日志和监控
- 自动化部署
部署优势
- 环境一致性
- 快速部署
- 易于扩展
- 简化运维
- 提高可靠性
Docker 容器化方案为辣评项目的部署和运维带来了显著的便利,也为后续的微服务化和云原生改造奠定了基础。