Docker容器化实战:将你的SpringBoot应用一键打包部署,告别环境不一致的烦恼!#第一部分

本文提供完整的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容器化的全套技能:

核心收获

  1. 环境标准化: 通过Docker实现开发、测试、生产环境的一致性
  2. 自动化流程: 完整的构建、测试、部署自动化脚本
  3. 生产就绪: 包含监控、日志、安全加固的生产级配置
  4. 最佳实践: 遵循Docker和SpringBoot的最佳实践

关键特性

  • 🔄 持续集成: 自动化构建和测试流程
  • 🐳 容器化: 完整的Dockerfile和多阶段构建
  • 🚀 一键部署: 简单的部署命令即可完成环境部署
  • 📊 全面监控: 集成Prometheus和Grafana监控
  • 🔒 安全加固: 非root用户运行和安全配置

后续

  1. 设置CI/CD流水线实现自动化部署
  2. 配置告警和自动扩缩容
  3. 实施安全扫描和漏洞管理

现在可以告别环境不一致的烦恼,享受Docker容器化带来的便利和可靠性!

相关推荐
天南星5 小时前
23种设计模式-深度讲解-7. 装饰器模式 (Decorator)
后端·设计模式
初级程序员Kyle5 小时前
Java复习日记 - 第一天:重拾Java基础
后端
oak隔壁找我5 小时前
SpringBoot 开发必备基础工具类实现(纯JWT认证,无SpringSecurity)
java·后端
张较瘦_5 小时前
Springboot | 初识Springboot 从“手动做饭”到“点外卖”的编程革命
java·spring boot·后端
间彧5 小时前
举例说明混合使用CAS和传统锁机制的成功案例
后端
间彧5 小时前
在高并发场景下,如何评估是使用CAS还是传统锁机制更合适?
后端
间彧5 小时前
在高并发场景下,如何量化评估何时应该从CAS切换到传统锁机制?
后端
oak隔壁找我5 小时前
SpringBoot 整合 Minio 和 FastDFS 实现分布式文件存储
java·后端
间彧5 小时前
CAS技术原理与应用详解
后端