本文提供完整的SpringBoot应用Docker容器化实战指南,从基础概念到高级部署,包含详细的操作步骤和代码示例,帮助您彻底解决环境不一致问题。
环境准备和Docker安装
系统要求检查
在开始Docker化之前,确保系统满足基本要求。
bash
#!/bin/bash
# docker_environment_check.sh - Docker环境检查脚本
set -euo pipefail
# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}=== Docker环境检查开始 ===${NC}"
# 检查操作系统
echo -e "\n${GREEN}1. 操作系统检查${NC}"
if [ -f /etc/os-release ]; then
source /etc/os-release
echo -e " 操作系统: $NAME $VERSION"
echo -e " 内核版本: $(uname -r)"
else
echo -e " ${YELLOW}警告: 无法确定操作系统类型${NC}"
fi
# 检查系统架构
echo -e "\n${GREEN}2. 系统架构检查${NC}"
ARCH=$(uname -m)
echo -e " 系统架构: $ARCH"
# 检查内存
echo -e "\n${GREEN}3. 内存检查${NC}"
MEM_TOTAL=$(free -h | awk 'NR==2{print $2}')
MEM_AVAILABLE=$(free -h | awk 'NR==2{print $7}')
echo -e " 总内存: $MEM_TOTAL"
echo -e " 可用内存: $MEM_AVAILABLE"
# 检查磁盘空间
echo -e "\n${GREEN}4. 磁盘空间检查${NC}"
DISK_SPACE=$(df -h / | awk 'NR==2{print $4}')
echo -e " 根分区可用空间: $DISK_SPACE"
# 检查Docker是否已安装
echo -e "\n${GREEN}5. Docker安装状态检查${NC}"
if command -v docker &> /dev/null; then
DOCKER_VERSION=$(docker --version | cut -d' ' -f3 | tr -d ',')
echo -e " ✅ Docker已安装: $DOCKER_VERSION"
# 检查Docker服务状态
if systemctl is-active --quiet docker; then
echo -e " ✅ Docker服务运行中"
else
echo -e " ${YELLOW}⚠️ Docker服务未运行${NC}"
fi
else
echo -e " ${RED}❌ Docker未安装${NC}"
fi
# 检查Docker Compose
echo -e "\n${GREEN}6. Docker Compose检查${NC}"
if command -v docker-compose &> /dev/null; then
DOCKER_COMPOSE_VERSION=$(docker-compose --version | cut -d' ' -f3 | tr -d ',')
echo -e " ✅ Docker Compose已安装: $DOCKER_COMPOSE_VERSION"
else
echo -e " ${YELLOW}⚠️ Docker Compose未安装${NC}"
fi
# 总结
echo -e "\n${BLUE}=== 环境检查完成 ===${NC}"
echo -e "请根据检查结果安装缺失的组件"
Docker安装脚本
根据不同操作系统安装Docker和Docker Compose。
bash
#!/bin/bash
# install_docker.sh - Docker安装脚本
set -euo pipefail
# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
error_exit() {
echo -e "${RED}错误: $1${NC}" >&2
exit 1
}
# 检测操作系统
detect_os() {
if [ -f /etc/os-release ]; then
source /etc/os-release
OS=$ID
VERSION=$VERSION_ID
else
error_exit "无法检测操作系统"
fi
}
# 安装Docker
install_docker() {
log "开始安装Docker..."
case $OS in
ubuntu|debian)
install_docker_debian
;;
centos|rhel|fedora)
install_docker_centos
;;
*)
error_exit "不支持的操作系统: $OS"
;;
esac
}
# Debian/Ubuntu系统安装Docker
install_docker_debian() {
log "在Debian/Ubuntu系统上安装Docker..."
# 卸载旧版本
sudo apt-get remove -y docker docker-engine docker.io containerd runc || true
# 安装依赖
sudo apt-get update
sudo apt-get install -y \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
# 添加Docker官方GPG密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 添加Docker仓库
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装Docker引擎
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
log "Docker安装完成"
}
# CentOS/RHEL系统安装Docker
install_docker_centos() {
log "在CentOS/RHEL系统上安装Docker..."
# 卸载旧版本
sudo yum remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
# 安装依赖
sudo yum install -y yum-utils
# 添加Docker仓库
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
# 安装Docker引擎
sudo yum install -y docker-ce docker-ce-cli containerd.io
log "Docker安装完成"
}
# 安装Docker Compose
install_docker_compose() {
log "开始安装Docker Compose..."
# 获取最新版本
COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
# 下载并安装
sudo curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 设置执行权限
sudo chmod +x /usr/local/bin/docker-compose
# 创建符号链接
sudo ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose
log "Docker Compose ${COMPOSE_VERSION} 安装完成"
}
# 配置Docker
configure_docker() {
log "配置Docker..."
# 启动Docker服务
sudo systemctl enable docker
sudo systemctl start docker
# 将当前用户添加到docker组(避免每次使用sudo)
sudo usermod -aG docker $USER
# 配置Docker镜像加速器(中国用户)
if [ -f /etc/docker/daemon.json ]; then
sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak
fi
sudo tee /etc/docker/daemon.json > /dev/null << EOF
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com",
"https://registry.docker-cn.com"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"data-root": "/var/lib/docker"
}
EOF
# 重启Docker服务
sudo systemctl daemon-reload
sudo systemctl restart docker
log "Docker配置完成"
}
# 验证安装
verify_installation() {
log "验证Docker安装..."
if docker --version; then
log "✅ Docker安装成功"
else
error_exit "Docker安装失败"
fi
if docker-compose --version; then
log "✅ Docker Compose安装成功"
else
error_exit "Docker Compose安装失败"
fi
# 测试运行容器
log "测试运行Hello World容器..."
if docker run --rm hello-world; then
log "✅ Docker运行测试成功"
else
error_exit "Docker运行测试失败"
fi
}
# 显示使用说明
show_usage() {
cat << EOF
使用说明:
./install_docker.sh [选项]
选项:
--help 显示此帮助信息
--skip-compose 跳过Docker Compose安装
--skip-configure 跳过Docker配置
示例:
./install_docker.sh # 完整安装
./install_docker.sh --skip-compose # 不安装Docker Compose
./install_docker.sh --skip-configure # 不进行配置
EOF
}
# 主函数
main() {
local skip_compose=false
local skip_configure=false
# 解析参数
while [[ $# -gt 0 ]]; do
case $1 in
--skip-compose)
skip_compose=true
shift
;;
--skip-configure)
skip_configure=true
shift
;;
--help)
show_usage
exit 0
;;
*)
error_exit "未知参数: $1"
;;
esac
done
log "开始Docker安装流程..."
# 检测操作系统
detect_os
# 安装Docker
install_docker
# 安装Docker Compose(除非跳过)
if [ "$skip_compose" = false ]; then
install_docker_compose
else
log "跳过Docker Compose安装"
fi
# 配置Docker(除非跳过)
if [ "$skip_configure" = false ]; then
configure_docker
else
log "跳过Docker配置"
fi
# 验证安装
verify_installation
log "🎉 Docker安装全部完成!"
log "请重新登录或执行 'newgrp docker' 以使组权限生效"
log "然后可以运行 'docker ps' 测试权限"
}
# 执行主函数
main "$@"
SpringBoot应用Docker化实战
创建示例SpringBoot应用
首先创建一个完整的SpringBoot应用作为演示。
java
// src/main/java/com/example/demo/DemoApplication.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@RestController
class HealthController {
@GetMapping("/health")
public Map<String, Object> health() {
Map<String, Object> healthInfo = new HashMap<>();
healthInfo.put("status", "UP");
healthInfo.put("timestamp", LocalDateTime.now().toString());
healthInfo.put("service", "SpringBoot Docker Demo");
return healthInfo;
}
@GetMapping("/info")
public Map<String, Object> info() {
Map<String, Object> info = new HashMap<>();
info.put("java.version", System.getProperty("java.version"));
info.put("java.vendor", System.getProperty("java.vendor"));
info.put("os.name", System.getProperty("os.name"));
info.put("os.arch", System.getProperty("os.arch"));
info.put("user.name", System.getProperty("user.name"));
info.put("availableProcessors", Runtime.getRuntime().availableProcessors());
info.put("maxMemory", Runtime.getRuntime().maxMemory() / (1024 * 1024) + " MB");
info.put("totalMemory", Runtime.getRuntime().totalMemory() / (1024 * 1024) + " MB");
return info;
}
}
SpringBoot应用配置文件
properties
# src/main/resources/application.properties
# 应用配置
spring.application.name=springboot-docker-demo
server.port=8080
server.servlet.context-path=/
# 日志配置
logging.level.com.example.demo=INFO
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# 健康检查
management.endpoint.health.enabled=true
management.endpoints.web.exposure.include=health,info
Maven构建配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>springboot-docker-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>SpringBoot Docker Demo</name>
<description>SpringBoot应用Docker容器化演示项目</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<properties>
<java.version>11</java.version>
<docker.image.prefix>springboot-demo</docker.image.prefix>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<!-- Docker镜像构建插件 -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<configuration>
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
Docker化配置和脚本
基础Dockerfile配置
dockerfile
# Dockerfile - SpringBoot应用容器化配置
# 多阶段构建,减小镜像体积
# 第一阶段:构建阶段
FROM maven:3.8.6-openjdk-11 AS builder
# 设置工作目录
WORKDIR /app
# 复制pom文件
COPY pom.xml .
# 下载依赖(利用Docker缓存层)
RUN mvn dependency:go-offline -B
# 复制源代码
COPY src ./src
# 构建应用
RUN mvn clean package -DskipTests
# 第二阶段:运行阶段
FROM openjdk:11-jre-slim
# 设置元数据
LABEL maintainer="your-email@example.com"
LABEL version="1.0.0"
LABEL description="SpringBoot Docker Demo Application"
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 创建应用用户(安全最佳实践)
RUN groupadd -r springboot && useradd -r -g springboot springboot
# 创建应用目录
RUN mkdir -p /app/logs && chown -R springboot:springboot /app
# 切换到应用用户
USER springboot
# 设置工作目录
WORKDIR /app
# 从构建阶段复制jar文件
COPY --from=builder --chown=springboot:springboot /app/target/*.jar app.jar
# 创建健康检查脚本
COPY --chown=springboot:springboot health-check.sh .
# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
ENV SPRING_PROFILES_ACTIVE="docker"
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD ./health-check.sh
# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar app.jar"]
健康检查脚本
bash
#!/bin/bash
# health-check.sh - 应用健康检查脚本
set -e
# 健康检查端点
HEALTH_URL="http://localhost:8080/health"
# 超时时间(秒)
TIMEOUT=5
# 执行健康检查
response=$(curl -s -o /dev/null -w "%{http_code}" --max-time $TIMEOUT $HEALTH_URL || echo "000")
# 检查响应状态码
if [ "$response" = "200" ]; then
echo "Health check: OK"
exit 0
else
echo "Health check: FAILED (HTTP $response)"
exit 1
fi
优化版Dockerfile(针对生产环境)
dockerfile
# Dockerfile.optimized - 生产环境优化版本
FROM eclipse-temurin:11-jre-focal as base
# 安装必要的工具
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
tzdata \
&& rm -rf /var/lib/apt/lists/*
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 创建非root用户
RUN groupadd --gid 1000 springboot && \
useradd --uid 1000 --gid springboot --shell /bin/bash --create-home springboot
# 第二阶段:运行环境
FROM base as runtime
# 设置JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions \
-XX:+UseContainerSupport \
-XX:InitialRAMPercentage=50.0 \
-XX:MaxRAMPercentage=80.0 \
-Djava.security.egd=file:/dev/./urandom"
# 设置应用参数
ENV SPRING_PROFILES_ACTIVE="docker,prod"
ENV SERVER_PORT=8080
# 创建应用目录
WORKDIR /app
# 复制jar文件
COPY --chown=springboot:springboot target/*.jar app.jar
# 复制启动脚本
COPY --chown=springboot:springboot docker-entrypoint.sh .
# 复制健康检查脚本
COPY --chown=springboot:springboot health-check.sh .
# 设置权限
RUN chmod +x docker-entrypoint.sh health-check.sh
# 创建日志目录
RUN mkdir -p logs && chown springboot:springboot logs
# 切换到非root用户
USER springboot
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD ./health-check.sh
# 使用入口点脚本
ENTRYPOINT ["./docker-entrypoint.sh"]
高级启动脚本
bash
#!/bin/bash
# docker-entrypoint.sh - 高级启动脚本
set -e
echo "🚀 启动 SpringBoot 应用..."
# 设置默认JVM参数
if [ -z "$JAVA_OPTS" ]; then
JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"
fi
# 应用特定配置
if [ "$SPRING_PROFILES_ACTIVE" = "prod" ]; then
echo "🔧 生产环境配置激活"
JAVA_OPTS="$JAVA_OPTS -XX:+AlwaysPreTouch -XX:+ExitOnOutOfMemoryError"
fi
# 日志配置
if [ -n "$LOG_LEVEL" ]; then
JAVA_OPTS="$JAVA_OPTS -Dlogging.level.com.example.demo=$LOG_LEVEL"
fi
# 显示启动信息
echo "📋 启动配置:"
echo " 工作目录: $(pwd)"
echo " Java版本: $(java -version 2>&1 | head -1)"
echo " JVM参数: $JAVA_OPTS"
echo " Spring Profiles: $SPRING_PROFILES_ACTIVE"
echo " 应用端口: $SERVER_PORT"
# 等待依赖服务(如果有)
if [ -n "$WAIT_FOR_HOSTS" ]; then
echo "⏳ 等待依赖服务..."
IFS=',' read -ra HOSTS <<< "$WAIT_FOR_HOSTS"
for host in "${HOSTS[@]}"; do
IFS=':' read -ra ADDR <<< "$host"
hostname=${ADDR[0]}
port=${ADDR[1]:=80}
echo " 等待 $hostname:$port..."
while ! nc -z $hostname $port; do
sleep 1
done
echo " ✅ $hostname:$port 就绪"
done
fi
# 启动应用
echo "🎯 启动应用..."
exec java $JAVA_OPTS -jar app.jar "$@"
Docker构建和部署脚本
自动化构建脚本
bash
#!/bin/bash
# build_and_deploy.sh - 自动化构建和部署脚本
set -euo pipefail
# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
# 配置变量
APP_NAME="springboot-docker-demo"
VERSION="1.0.0"
REGISTRY="localhost:5000" # 本地 registry,生产环境替换为实际 registry
DOCKERFILE="Dockerfile"
# 日志函数
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
error_exit() {
echo -e "${RED}错误: $1${NC}" >&2
exit 1
}
# 显示帮助信息
show_help() {
cat << EOF
使用说明: $0 [选项]
选项:
-h, --help 显示此帮助信息
-b, --build 构建Docker镜像
-t, --test 运行测试
-p, --push 推送镜像到仓库
-d, --deploy 部署到环境
-e, --environment 指定环境 (dev/staging/prod)
--version 指定版本号
--no-cache 构建时不使用缓存
示例:
$0 -b -t -p -d -e prod # 完整CI/CD流程
$0 -b --no-cache # 不使用缓存构建
$0 --version 2.0.0 -b -p # 构建并推送特定版本
EOF
}
# 参数解析
parse_arguments() {
local build=false
local test=false
local push=false
local deploy=false
local environment="dev"
local no_cache=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-b|--build)
build=true
shift
;;
-t|--test)
test=true
shift
;;
-p|--push)
push=true
shift
;;
-d|--deploy)
deploy=true
shift
;;
-e|--environment)
environment="$2"
shift 2
;;
--version)
VERSION="$2"
shift 2
;;
--no-cache)
no_cache=true
shift
;;
*)
error_exit "未知参数: $1"
;;
esac
done
# 如果没有指定任何操作,显示帮助
if [ "$build" = false ] && [ "$test" = false ] && [ "$push" = false ] && [ "$deploy" = false ]; then
show_help
exit 0
fi
# 设置变量供后续使用
BUILD=$build
TEST=$test
PUSH=$push
DEPLOY=$deploy
ENVIRONMENT=$environment
NO_CACHE=$no_cache
}
# 前置检查
pre_checks() {
log "执行前置检查..."
# 检查Docker是否可用
if ! command -v docker &> /dev/null; then
error_exit "Docker未安装"
fi
# 检查Docker服务是否运行
if ! docker info &> /dev/null; then
error_exit "Docker服务未运行"
fi
# 检查必要的文件
if [ ! -f "Dockerfile" ]; then
error_exit "Dockerfile不存在"
fi
if [ ! -f "pom.xml" ]; then
error_exit "pom.xml不存在"
fi
log "✅ 前置检查通过"
}
# 构建应用
build_application() {
log "构建SpringBoot应用..."
if [ ! -d "target" ] || [ "$NO_CACHE" = true ]; then
# 清理并重新构建
mvn clean package -DskipTests
else
# 仅打包
mvn package -DskipTests
fi
if [ $? -eq 0 ]; then
log "✅ 应用构建成功"
else
error_exit "应用构建失败"
fi
}
# 构建Docker镜像
build_docker_image() {
log "构建Docker镜像..."
local cache_flag=""
if [ "$NO_CACHE" = true ]; then
cache_flag="--no-cache"
log "不使用缓存构建"
fi
local image_tag="$APP_NAME:$VERSION"
local full_tag="$REGISTRY/$APP_NAME:$VERSION"
# 构建镜像
docker build $cache_flag -t "$image_tag" -t "$full_tag" -f "$DOCKERFILE" .
if [ $? -eq 0 ]; then
log "✅ Docker镜像构建成功: $image_tag"
log "✅ Docker镜像构建成功: $full_tag"
else
error_exit "Docker镜像构建失败"
fi
}
# 运行测试
run_tests() {
log "运行测试..."
# 运行单元测试
mvn test
if [ $? -eq 0 ]; then
log "✅ 单元测试通过"
else
error_exit "单元测试失败"
fi
# 运行容器测试
log "运行容器测试..."
local test_container_name="${APP_NAME}-test-$(date +%s)"
local image_tag="$APP_NAME:$VERSION"
# 启动测试容器
docker run -d --name "$test_container_name" -p 8081:8080 "$image_tag"
# 等待应用启动
log "等待应用启动..."
sleep 30
# 测试健康端点
local health_response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8081/health || echo "000")
if [ "$health_response" = "200" ]; then
log "✅ 健康检查通过"
else
docker logs "$test_container_name"
error_exit "健康检查失败 (HTTP $health_response)"
fi
# 测试信息端点
local info_response=$(curl -s http://localhost:8081/info | grep -q "java.version" && echo "OK" || echo "FAIL")
if [ "$info_response" = "OK" ]; then
log "✅ 信息端点测试通过"
else
error_exit "信息端点测试失败"
fi
# 清理测试容器
docker stop "$test_container_name"
docker rm "$test_container_name"
log "✅ 所有测试通过"
}
# 推送镜像
push_image() {
log "推送镜像到仓库..."
local full_tag="$REGISTRY/$APP_NAME:$VERSION"
# 检查仓库是否可用
if ! docker push "$full_tag" &> /dev/null; then
log "⚠️ 镜像仓库不可用,尝试启动本地registry"
# 启动本地registry(用于演示)
docker run -d -p 5000:5000 --name registry registry:2 || true
sleep 2
fi
# 推送镜像
if docker push "$full_tag"; then
log "✅ 镜像推送成功: $full_tag"
else
error_exit "镜像推送失败"
fi
}
# 部署应用
deploy_application() {
log "部署应用到 $ENVIRONMENT 环境..."
case $ENVIRONMENT in
dev)
deploy_dev
;;
staging)
deploy_staging
;;
prod)
deploy_prod
;;
*)
error_exit "未知环境: $ENVIRONMENT"
;;
esac
}
# 开发环境部署
deploy_dev() {
log "部署到开发环境..."
local container_name="$APP_NAME-dev"
local image_tag="$APP_NAME:$VERSION"
# 停止并删除现有容器
docker stop "$container_name" 2>/dev/null || true
docker rm "$container_name" 2>/dev/null || true
# 启动新容器
docker run -d \
--name "$container_name" \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE="dev,docker" \
-e JAVA_OPTS="-Xms256m -Xmx512m" \
--restart unless-stopped \
"$image_tag"
log "✅ 开发环境部署完成"
log " 应用地址: http://localhost:8080"
log " 健康检查: http://localhost:8080/health"
}
# 生产环境部署
deploy_prod() {
log "部署到生产环境..."
# 使用Docker Compose部署
if [ -f "docker-compose.prod.yml" ]; then
docker-compose -f docker-compose.prod.yml up -d
log "✅ 生产环境部署完成"
else
error_exit "生产环境配置文件不存在"
fi
}
# 主函数
main() {
log "🚀 开始SpringBoot应用Docker化流程"
# 解析参数
parse_arguments "$@"
# 前置检查
pre_checks
# 构建应用
if [ "$BUILD" = true ]; then
build_application
build_docker_image
fi
# 运行测试
if [ "$TEST" = true ]; then
run_tests
fi
# 推送镜像
if [ "$PUSH" = true ]; then
push_image
fi
# 部署应用
if [ "$DEPLOY" = true ]; then
deploy_application
fi
log "🎉 所有操作完成!"
# 显示部署信息
if [ "$DEPLOY" = true ]; then
echo ""
echo "📋 部署摘要:"
echo " 应用名称: $APP_NAME"
echo " 版本: $VERSION"
echo " 环境: $ENVIRONMENT"
echo " 镜像: $REGISTRY/$APP_NAME:$VERSION"
echo " 健康检查: http://localhost:8080/health"
fi
}
# 执行主函数
main "$@"
Docker Compose编排配置
开发环境配置
yaml
# docker-compose.dev.yml
version: '3.8'
services:
springboot-app:
build:
context: .
dockerfile: Dockerfile
image: springboot-docker-demo:dev
container_name: springboot-app-dev
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=dev,docker
- JAVA_OPTS=-Xms256m -Xmx512m -XX:+UseG1GC
- LOG_LEVEL=DEBUG
volumes:
- ./logs:/app/logs
- ./config:/app/config
networks:
- springboot-network
restart: unless-stopped
healthcheck:
test: ["CMD", "./health-check.sh"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# MySQL数据库
mysql-db:
image: mysql:8.0
container_name: mysql-dev
environment:
- MYSQL_ROOT_PASSWORD=rootpassword
- MYSQL_DATABASE=springboot_demo
- MYSQL_USER=appuser
- MYSQL_PASSWORD=apppassword
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- springboot-network
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 10s
retries: 5
# Redis缓存
redis:
image: redis:7-alpine
container_name: redis-dev
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- springboot-network
restart: unless-stopped
command: redis-server --appendonly yes
# 监控工具
prometheus:
image: prom/prometheus:latest
container_name: prometheus-dev
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
networks:
- springboot-network
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: grafana-dev
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
networks:
- springboot-network
restart: unless-stopped
networks:
springboot-network:
driver: bridge
volumes:
mysql_data:
redis_data:
prometheus_data:
grafana_data:
生产环境配置
yaml
# docker-compose.prod.yml
version: '3.8'
services:
springboot-app:
image: ${REGISTRY:-localhost:5000}/springboot-docker-demo:${VERSION:-latest}
container_name: springboot-app-prod
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod,docker
- JAVA_OPTS=-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200
- LOG_LEVEL=INFO
- MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics
volumes:
- app_logs:/app/logs
networks:
- springboot-prod-network
restart: always
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
reservations:
memory: 512M
cpus: '0.5'
healthcheck:
test: ["CMD", "./health-check.sh"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# Nginx负载均衡
nginx:
image: nginx:1.21-alpine
container_name: nginx-prod
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
networks:
- springboot-prod-network
restart: always
depends_on:
- springboot-app
# 数据库(生产环境建议使用外部数据库)
# mysql:
# image: mysql:8.0
# container_name: mysql-prod
# environment:
# - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
# - MYSQL_DATABASE=${DB_NAME}
# - MYSQL_USER=${DB_USER}
# - MYSQL_PASSWORD=${DB_PASSWORD}
# volumes:
# - mysql_prod_data:/var/lib/mysql
# networks:
# - springboot-prod-network
# restart: always
# deploy:
# resources:
# limits:
# memory: 2G
# cpus: '2.0'
networks:
springboot-prod-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
volumes:
app_logs:
mysql_prod_data:
Nginx配置
nginx
# nginx.conf
events {
worker_connections 1024;
}
http {
upstream springboot_app {
server springboot-app:8080;
}
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# 基础配置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 服务器配置
server {
listen 80;
server_name localhost;
# 安全头
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
proxy_pass http://springboot_app;
}
# API路由
location /api/ {
proxy_pass http://springboot_app;
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_send_timeout 30s;
proxy_read_timeout 30s;
}
# 健康检查
location /health {
proxy_pass http://springboot_app/health;
proxy_set_header Host $host;
access_log off;
}
# 根路径
location / {
proxy_pass http://springboot_app;
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;
}
}
}
监控和日志管理
应用监控配置
yaml
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
scrape_configs:
- job_name: 'springboot-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['springboot-app:8080']
scrape_interval: 10s
scrape_timeout: 5s
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
日志管理脚本
bash
#!/bin/bash
# log_manager.sh - 容器日志管理脚本
set -euo pipefail
# 配置
LOG_DIR="./logs"
BACKUP_DIR="./logs/backup"
RETENTION_DAYS=7
CONTAINER_NAME="springboot-app"
# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}
# 创建目录
create_directories() {
mkdir -p "$LOG_DIR"
mkdir -p "$BACKUP_DIR"
}
# 查看实时日志
view_logs() {
log "查看容器日志: $CONTAINER_NAME"
if docker ps --format "table {{.Names}}" | grep -q "$CONTAINER_NAME"; then
docker logs -f "$CONTAINER_NAME"
else
echo -e "${RED}容器 $CONTAINER_NAME 未运行${NC}"
fi
}
# 导出日志
export_logs() {
local export_file="$BACKUP_DIR/logs_$(date +%Y%m%d_%H%M%S).tar.gz"
log "导出容器日志到: $export_file"
if docker ps --format "table {{.Names}}" | grep -q "$CONTAINER_NAME"; then
docker logs "$CONTAINER_NAME" > "$LOG_DIR/container.log" 2>&1
tar -czf "$export_file" -C "$LOG_DIR" .
log "✅ 日志导出完成: $export_file"
else
echo -e "${RED}容器 $CONTAINER_NAME 未运行${NC}"
fi
}
# 清理旧日志
cleanup_old_logs() {
log "清理 $RETENTION_DAYS 天前的日志备份..."
find "$BACKUP_DIR" -name "*.tar.gz" -type f -mtime +$RETENTION_DAYS -delete
log "✅ 旧日志清理完成"
}
# 日志分析
analyze_logs() {
local log_file="$LOG_DIR/container.log"
if [ ! -f "$log_file" ]; then
echo -e "${RED}日志文件不存在: $log_file${NC}"
return 1
fi
log "分析应用日志..."
echo -e "\n${YELLOW}=== 错误统计 ===${NC}"
grep -i "error" "$log_file" | sort | uniq -c | sort -nr | head -10
echo -e "\n${YELLOW}=== 警告统计 ===${NC}"
grep -i "warn" "$log_file" | sort | uniq -c | sort -nr | head -10
echo -e "\n${YELLOW}=== 请求统计 ===${NC}"
grep "HTTP" "$log_file" | awk '{print $NF}' | sort | uniq -c | sort -nr | head -10
echo -e "\n${YELLOW}=== 响应时间分析 ===${NC}"
grep "Completed" "$log_file" | awk '{print $NF}' | \
awk '{
if ($1 < 100) a++;
else if ($1 < 500) b++;
else if ($1 < 1000) c++;
else d++;
}
END {
print " < 100ms: " a;
print "100-500ms: " b;
print "500-1000ms: " c;
print " > 1000ms: " d;
}'
}
# 显示帮助
show_help() {
cat << EOF
使用说明: $0 [命令]
命令:
view 查看实时日志
export 导出日志到文件
cleanup 清理旧日志
analyze 分析日志
help 显示此帮助信息
示例:
$0 view # 查看实时日志
$0 export # 导出日志
$0 analyze # 分析日志
EOF
}
# 主函数
main() {
local command=${1:-help}
create_directories
case $command in
view)
view_logs
;;
export)
export_logs
;;
cleanup)
cleanup_old_logs
;;
analyze)
analyze_logs
;;
help|*)
show_help
;;
esac
}
# 执行主函数
main "$@"
Docker化流程架构图
以下图表展示了完整的SpringBoot应用Docker化流程:
flowchart TD
A[SpringBoot应用代码] --> B[Maven构建]
B --> C[Docker镜像构建]
C --> D[镜像测试验证]
D --> E[镜像推送仓库]
E --> F[容器化部署]
F --> G[监控日志]
G --> H[性能分析]
B --> B1[pom.xml配置]
B --> B2[应用打包]
C --> C1[Dockerfile配置]
C --> C2[多阶段构建]
C --> C3[镜像优化]
D --> D1[健康检查]
D --> D2[功能测试]
D --> D3[性能测试]
E --> E1[Docker Registry]
E --> E2[版本标签]
F --> F1[Docker Compose]
F --> F2[容器编排]
F --> F3[服务发现]
G --> G1[Prometheus监控]
G --> G2[Grafana仪表板]
G --> G3[日志聚合]
H --> H1[性能调优]
H --> H2[资源优化]
H --> H3[自动扩缩容]
style A fill:#3498db,color:#fff
style F fill:#27ae60,color:#fff
style H fill:#e74c3c,color:#fff
style C fill:#9b59b6,color:#fff
高级特性和最佳实践
安全加固配置
dockerfile
# Dockerfile.security - 安全加固版本
FROM eclipse-temurin:11-jre-focal as base
# 安全扫描和更新
RUN apt-get update && \
apt-get upgrade -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 创建非特权用户
RUN groupadd -r springboot -g 1000 && \
useradd -r -u 1000 -g springboot -s /bin/bash -d /app springboot
# 设置安全标签
LABEL security.scan.enabled="true"
LABEL security.compliance="base"
FROM base as runtime
# 设置安全相关的环境变量
ENV JAVA_OPTS="-Xms512m -Xmx1024m \
-Djava.security.egd=file:/dev/./urandom \
-Djava.awt.headless=true \
-Dspring.jmx.enabled=false \
-Dfile.encoding=UTF-8"
# 使用非root用户
USER springboot
WORKDIR /app
# 复制应用程序
COPY --chown=springboot:springboot target/*.jar app.jar
# 设置文件权限
RUN chmod 500 app.jar && \
chmod 400 docker-entrypoint.sh health-check.sh
# 安全配置:只读根文件系统
# 注意:需要写入的目录需要单独挂载卷
# docker run --read-only -v /app/logs ...
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD ./health-check.sh
ENTRYPOINT ["./docker-entrypoint.sh"]
多环境部署脚本
bash
#!/bin/bash
# multi_env_deploy.sh - 多环境部署脚本
set -euo pipefail
# 配置
ENVIRONMENTS=("dev" "staging" "prod")
REGISTRY="my-registry.example.com"
APP_NAME="springboot-docker-demo"
deploy_to_environment() {
local env=$1
local version=$2
echo "🚀 部署到 $env 环境, 版本: $version"
case $env in
dev)
deploy_dev $version
;;
staging)
deploy_staging $version
;;
prod)
deploy_prod $version
;;
*)
echo "❌ 未知环境: $env"
return 1
;;
esac
}
deploy_dev() {
local version=$1
# 开发环境直接使用最新镜像
docker-compose -f docker-compose.dev.yml up -d --force-recreate
echo "✅ 开发环境部署完成"
run_smoke_tests "dev"
}
deploy_staging() {
local version=$1
# 预生产环境部署
export VERSION=$version
docker-compose -f docker-compose.staging.yml up -d --force-recreate
echo "✅ 预生产环境部署完成"
run_integration_tests
}
deploy_prod() {
local version=$1
# 生产环境蓝绿部署
perform_blue_green_deployment $version
echo "✅ 生产环境部署完成"
run_smoke_tests "prod"
}
perform_blue_green_deployment() {
local version=$1
# 确定当前运行的颜色(blue 或 green)
local current_color=$(get_current_deployment_color)
local new_color=$([ "$current_color" = "blue" ] && echo "green" || echo "blue")
echo "🎨 当前部署颜色: $current_color"
echo "🎨 新部署颜色: $new_color"
# 启动新颜色的服务
start_service_with_color $new_color $version
# 等待新服务就绪
wait_for_service_ready $new_color
# 切换流量
switch_traffic $new_color
# 停止旧服务
stop_old_service $current_color
echo "✅ 蓝绿部署完成,当前颜色: $new_color"
}
# 主部署流程
main() {
local version=${1:-latest}
echo "开始多环境部署流程..."
echo "应用版本: $version"
echo "目标环境: ${ENVIRONMENTS[*]}"
for env in "${ENVIRONMENTS[@]}"; do
echo ""
echo "=== 部署到 $env 环境 ==="
# 确认部署(生产环境需要确认)
if [ "$env" = "prod" ]; then
read -p "确认部署到生产环境? (y/N): " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "❌ 生产环境部署取消"
continue
fi
fi
# 执行部署
if deploy_to_environment $env $version; then
echo "✅ $env 环境部署成功"
else
echo "❌ $env 环境部署失败"
# 可以根据策略决定是否继续部署其他环境
fi
done
echo ""
echo "🎉 多环境部署流程完成"
}
# 执行主函数
main "$@"
总结
通过本文的完整指南,已经掌握了SpringBoot应用Docker容器化的全套技能:
核心收获
- 环境标准化: 通过Docker实现开发、测试、生产环境的一致性
- 自动化流程: 完整的构建、测试、部署自动化脚本
- 生产就绪: 包含监控、日志、安全加固的生产级配置
- 最佳实践: 遵循Docker和SpringBoot的最佳实践
关键特性
- 🔄 持续集成: 自动化构建和测试流程
- 🐳 容器化: 完整的Dockerfile和多阶段构建
- 🚀 一键部署: 简单的部署命令即可完成环境部署
- 📊 全面监控: 集成Prometheus和Grafana监控
- 🔒 安全加固: 非root用户运行和安全配置
后续
- 设置CI/CD流水线实现自动化部署
- 配置告警和自动扩缩容
- 实施安全扫描和漏洞管理
现在可以告别环境不一致的烦恼,享受Docker容器化带来的便利和可靠性!