引言

容器化和Kubernetes已成为现代应用部署的标准。作为Java开发者,掌握Docker和Kubernetes不仅能让应用部署更高效,还能大幅提升系统的可伸缩性和可靠性。本文将带你从Docker基础到Kubernetes高级特性,全面掌握Java应用的容器化部署。

Docker容器化深度实践

1.1 优化Java应用Docker镜像

# 多阶段构建优化Dockerfile
# 第一阶段:构建阶段
FROM maven:3.8.6-openjdk-17 AS builder

# 设置工作目录
WORKDIR /app

# 复制pom文件并下载依赖(利用Docker缓存)
COPY pom.xml .
RUN mvn dependency:go-offline -B

# 复制源代码并构建
COPY src ./src
RUN mvn clean package -DskipTests

# 第二阶段:运行时阶段
FROM eclipse-temurin:17-jre-alpine

# 安装必要的工具
RUN apk add --no-cache tini curl

# 设置Java优化参数
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
ENV JAVA_TOOL_OPTIONS="-Xmx512m -Xms256m"

# 创建非root用户运行应用
RUN addgroup -S spring && adduser -S spring -G spring
USER spring

# 设置工作目录
WORKDIR /app

# 从构建阶段复制jar文件
COPY --from=builder --chown=spring:spring /app/target/*.jar app.jar

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

# 使用tini作为init进程
ENTRYPOINT ["/sbin/tini", "--"]

# 启动应用
CMD java $JAVA_OPTS -jar app.jar

# 暴露端口
EXPOSE 8080

# 标签信息
LABEL maintainer="dev-team@company.com"
LABEL version="1.0.0"
LABEL description="Spring Boot Application"
# 针对不同环境的Dockerfile模板
# 开发环境Dockerfile
FROM eclipse-temurin:17-jdk-alpine as development

WORKDIR /app
COPY . .
RUN ./mvnw dependency:go-offline -B

CMD ["./mvnw", "spring-boot:run"]

# 生产环境Dockerfile
FROM eclipse-temurin:17-jre-alpine as production

RUN addgroup -S spring && adduser -S spring -G spring
USER spring

WORKDIR /app
COPY --from=development /app/target/*.jar app.jar

ENTRYPOINT ["java", "-jar", "/app/app.jar"]

1.2 Docker Compose编排实战

# docker-compose.yml - 完整微服务栈
version: '3.8'

services:
  # MySQL数据库
  mysql:
    image: mysql:8.0
    container_name: app-mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./config/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10

  # Redis缓存
  redis:
    image: redis:7-alpine
    container_name: app-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

  # Nacos服务发现
  nacos:
    image: nacos/nacos-server:2.2.0
    container_name: app-nacos
    environment:
      - MODE=standalone
      - SPRING_DATASOURCE_PLATFORM=mysql
      - MYSQL_SERVICE_HOST=mysql
      - MYSQL_SERVICE_DB_NAME=nacos_dev
      - MYSQL_SERVICE_PORT=3306
      - MYSQL_SERVICE_USER=root
      - MYSQL_SERVICE_PASSWORD=${MYSQL_ROOT_PASSWORD}
    ports:
      - "8848:8848"
    depends_on:
      mysql:
        condition: service_healthy
    networks:
      - app-network

  # 用户服务
  user-service:
    build:
      context: ./user-service
      dockerfile: Dockerfile
      target: production
    container_name: user-service
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/user_db
      - SPRING_REDIS_HOST=redis
      - SPRING_CLOUD_NACOS_SERVER_ADDR=nacos:8848
    ports:
      - "8081:8080"
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
      nacos:
        condition: service_started
    networks:
      - app-network
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
        reservations:
          memory: 256M
          cpus: '0.25'

  # 订单服务
  order-service:
    build:
      context: ./order-service
      dockerfile: Dockerfile
    container_name: order-service
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - USER_SERVICE_URL=http://user-service:8080
    ports:
      - "8082:8080"
    depends_on:
      - user-service
    networks:
      - app-network

  # API网关
  api-gateway:
    build:
      context: ./api-gateway
      dockerfile: Dockerfile
    container_name: api-gateway
    ports:
      - "8080:8080"
    depends_on:
      - user-service
      - order-service
    networks:
      - app-network

  # 监控栈
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - app-network

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards
    networks:
      - app-network

volumes:
  mysql_data:
  redis_data:

networks:
  app-network:
    driver: bridge

1.3 Java应用容器化最佳实践

// 容器感知的Spring Boot配置
@Configuration
@Slf4j
public class ContainerAwareConfiguration {
    
    @Bean
    @Profile("docker")
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer() {
        return factory -> {
            // 在容器环境中优化Web服务器配置
            if (factory instanceof TomcatServletWebServerFactory) {
                customizeTomcat((TomcatServletWebServerFactory) factory);
            }
        };
    }
    
    private void customizeTomcat(TomcatServletWebServerFactory factory) {
        factory.addConnectorCustomizers(connector -> {
            // 优化Tomcat连接器配置
            connector.setProperty("maxThreads", "200");
            connector.setProperty("minSpareThreads", "20");
            connector.setProperty("connectionTimeout", "5000");
        });
        
        log.info("Tomcat配置已针对容器环境优化");
    }
    
    @Bean
    @Profile("docker")
    public DataSource dataSource(Environment env) {
        HikariConfig config = new HikariConfig();
        
        // 容器环境下的数据库连接池优化
        config.setJdbcUrl(env.getProperty("spring.datasource.url"));
        config.setUsername(env.getProperty("spring.datasource.username"));
        config.setPassword(env.getProperty("spring.datasource.password"));
        config.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
        
        // 连接池优化配置
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30000);
        config.setIdleTimeout(600000);
        config.setMaxLifetime(1800000);
        config.setLeakDetectionThreshold(60000);
        
        return new HikariDataSource(config);
    }
    
    // 容器健康检查端点
    @Component
    @Endpoint(id = "container")
    @Slf4j
    public class ContainerHealthEndpoint {
        
        @ReadOperation
        public Map<String, Object> health() {
            Map<String, Object> health = new HashMap<>();
            
            // 检查内存使用
            Runtime runtime = Runtime.getRuntime();
            long maxMemory = runtime.maxMemory();
            long usedMemory = runtime.totalMemory() - runtime.freeMemory();
            double memoryUsage = (double) usedMemory / maxMemory;
            
            health.put("memoryUsage", String.format("%.2f%%", memoryUsage * 100));
            health.put("maxMemory", formatMemory(maxMemory));
            health.put("usedMemory", formatMemory(usedMemory));
            
            // 检查CPU核心数
            health.put("availableProcessors", runtime.availableProcessors());
            
            // 检查文件系统
            File root = new File("/");
            health.put("diskFree", formatMemory(root.getFreeSpace()));
            health.put("diskTotal", formatMemory(root.getTotalSpace()));
            
            log.debug("容器健康检查: {}", health);
            return health;
        }
        
        private String formatMemory(long bytes) {
            return String.format("%.2f MB", bytes / 1024.0 / 1024.0);
        }
    }
}

// 容器环境检测工具
@Component
@Slf4j
public class ContainerEnvironmentDetector {
    
    private final boolean runningInContainer;
    
    public ContainerEnvironmentDetector() {
        this.runningInContainer = detectContainerEnvironment();
        log.info("运行环境: {}", runningInContainer ? "容器" : "物理机/虚拟机");
    }
    
    public boolean isRunningInContainer() {
        return runningInContainer;
    }
    
    private boolean detectContainerEnvironment() {
        // 检查常见的容器环境指标
        return checkCGroup() || checkDockerEnv() || checkKubernetesEnv();
    }
    
    private boolean checkCGroup() {
        try {
            File cgroup = new File("/proc/1/cgroup");
            if (cgroup.exists()) {
                String content = Files.readString(cgroup.toPath());
                return content.contains("docker") || content.contains("kubepods");
            }
        } catch (IOException e) {
            log.debug("检查cgroup失败", e);
        }
        return false;
    }
    
    private boolean checkDockerEnv() {
        return System.getenv("DOCKER_CONTAINER") != null ||
               new File("/.dockerenv").exists();
    }
    
    private boolean checkKubernetesEnv() {
        return System.getenv("KUBERNETES_SERVICE_HOST") != null ||
               System.getenv("KUBERNETES_PORT") != null;
    }
    
    // 获取容器资源限制
    public ContainerResources getContainerResources() {
        return new ContainerResources(
            getMemoryLimit(),
            getCpuLimit(),
            getCpuQuota()
        );
    }
    
    private Long getMemoryLimit() {
        try {
            // 从cgroup读取内存限制
            Path memoryLimitPath = Paths.get("/sys/fs/cgroup/memory/memory.limit_in_bytes");
            if (Files.exists(memoryLimitPath)) {
                String content = Files.readString(memoryLimitPath).trim();
                return Long.parseLong(content);
            }
        } catch (Exception e) {
            log.debug("读取内存限制失败", e);
        }
        return null;
    }
    
    private Long getCpuLimit() {
        try {
            // 从cgroup读取CPU限制
            Path cpuPeriodPath = Paths.get("/sys/fs/cgroup/cpu/cpu.cfs_period_us");
            Path cpuQuotaPath = Paths.get("/sys/fs/cgroup/cpu/cpu.cfs_quota_us");
            
            if (Files.exists(cpuPeriodPath) && Files.exists(cpuQuotaPath)) {
                long period = Long.parseLong(Files.readString(cpuPeriodPath).trim());
                long quota = Long.parseLong(Files.readString(cpuQuotaPath).trim());
                
                if (quota > 0 && period > 0) {
                    return Math.round((double) quota / period);
                }
            }
        } catch (Exception e) {
            log.debug("读取CPU限制失败", e);
        }
        return null;
    }
    
    private Long getCpuQuota() {
        try {
            Path cpuQuotaPath = Paths.get("/sys/fs/cgroup/cpu/cpu.cfs_quota_us");
            if (Files.exists(cpuQuotaPath)) {
                return Long.parseLong(Files.readString(cpuQuotaPath).trim());
            }
        } catch (Exception e) {
            log.debug("读取CPU配额失败", e);
        }
        return null;
    }
    
    public static class ContainerResources {
        private final Long memoryLimit;
        private final Long cpuLimit;
        private final Long cpuQuota;
        
        public ContainerResources(Long memoryLimit, Long cpuLimit, Long cpuQuota) {
            this.memoryLimit = memoryLimit;
            this.cpuLimit = cpuLimit;
            this.cpuQuota = cpuQuota;
        }
        
        // getters...
    }
}

Kubernetes部署实战

2.1 Kubernetes基础资源配置

# namespace.yaml - 命名空间配置
apiVersion: v1
kind: Namespace
metadata:
  name: java-app
  labels:
    name: java-app
    environment: production
---
# configmap.yaml - 应用配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: java-app
data:
  application.yml: |
    server:
      port: 8080
      tomcat:
        max-threads: 200
        min-spare-threads: 20
    
    spring:
      datasource:
        hikari:
          maximum-pool-size: 20
          minimum-idle: 5
          connection-timeout: 30000
      jpa:
        show-sql: false
        hibernate:
          ddl-auto: validate
      redis:
        timeout: 2000
        lettuce:
          pool:
            max-active: 20
            max-idle: 10
            min-idle: 5
    
    management:
      endpoints:
        web:
          exposure:
            include: health,info,metrics,prometheus
      endpoint:
        health:
          show-details: always
          probes:
            enabled: true
  logback-spring.xml: |
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
        <root level="INFO">
            <appender-ref ref="CONSOLE"/>
        </root>
    </configuration>
---
# secret.yaml - 敏感信息配置
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: java-app
type: Opaque
data:
  database-password: c3VwZXJzZWNyZXRwYXNzd29yZA==  # base64编码
  redis-password: cmVkaXNwYXNzd29yZA==
  jwt-secret: anN0c2VjcmV0a2V5MTIzNDU2Nzg=

2.2 Deployment高级配置

# deployment.yaml - 用户服务部署
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
  namespace: java-app
  labels:
    app: user-service
    version: v1.0.0
spec:
  replicas: 3
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8080"
        prometheus.io/path: "/actuator/prometheus"
    spec:
      # 亲和性调度
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - user-service
              topologyKey: kubernetes.io/hostname
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: disktype
                operator: In
                values:
                - ssd
      # 资源限制
      containers:
      - name: user-service
        image: registry.company.com/user-service:1.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
          protocol: TCP
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "kubernetes"
        - name: JAVA_OPTS
          value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
        - name: MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE
          value: "health,info,metrics,prometheus"
        envFrom:
        - configMapRef:
            name: app-config
        - secretRef:
            name: app-secrets
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        # 健康检查
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
        startupProbe:
          httpGet:
            path: /actuator/health/startup
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 30
        # 生命周期钩子
        lifecycle:
          preStop:
            exec:
              command: ["sh", "-c", "sleep 30"]
        # 安全上下文
        securityContext:
          runAsNonRoot: true
          runAsUser: 1000
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
            - ALL
        volumeMounts:
        - name: config-volume
          mountPath: /app/config
          readOnly: true
        - name: tmp-volume
          mountPath: /tmp
      volumes:
      - name: config-volume
        configMap:
          name: app-config
      - name: tmp-volume
        emptyDir: {}
      # 服务账户
      serviceAccountName: java-app-sa
      # 镜像拉取密钥
      imagePullSecrets:
      - name: registry-credentials
---
# service.yaml - 服务暴露
apiVersion: v1
kind: Service
metadata:
  name: user-service
  namespace: java-app
  labels:
    app: user-service
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "8080"
spec:
  selector:
    app: user-service
  ports:
  - name: http
    port: 8080
    targetPort: 8080
    protocol: TCP
  - name: metrics
    port: 8081
    targetPort: 8081
    protocol: TCP
  type: ClusterIP
---
# ingress.yaml - 入口配置
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: user-service-ingress
  namespace: java-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: 10m
spec:
  ingressClassName: nginx
  rules:
  - host: users.api.company.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 8080
  tls:
  - hosts:
    - users.api.company.com
    secretName: tls-secret

2.3 Kubernetes Operator模式

// 自定义资源定义 - Java应用配置
public class JavaAppSpec {
    private String version;
    private int replicas;
    private Resources resources;
    private ProbeConfig livenessProbe;
    private ProbeConfig readinessProbe;
    private Map<String, String> env;
    private List<String> jvmArgs;
    
    // getters and setters...
    
    public static class Resources {
        private String memoryRequest;
        private String cpuRequest;
        private String memoryLimit;
        private String cpuLimit;
        
        // getters and setters...
    }
    
    public static class ProbeConfig {
        private String path;
        private int initialDelay;
        private int period;
        private int timeout;
        
        // getters and setters...
    }
}

// 自定义控制器
@Component
@Slf4j
public class JavaAppOperator {
    
    @Autowired
    private KubernetesClient kubernetesClient;
    
    @EventListener
    public void onJavaAppCreated(GenericKubernetesResource resource) {
        String name = resource.getMetadata().getName();
        String namespace = resource.getMetadata().getNamespace();
        
        log.info("处理Java应用创建事件: {}/{}", namespace, name);
        
        try {
            // 解析自定义资源
            JavaAppSpec spec = parseSpec(resource);
            
            // 创建或更新Deployment
            createOrUpdateDeployment(namespace, name, spec);
            
            // 创建或更新Service
            createOrUpdateService(namespace, name, spec);
            
            // 创建或更新Ingress(如果需要)
            if (spec.isExposePublic()) {
                createOrUpdateIngress(namespace, name, spec);
            }
            
            log.info("Java应用部署完成: {}/{}", namespace, name);
            
        } catch (Exception e) {
            log.error("处理Java应用失败: {}/{}", namespace, name, e);
        }
    }
    
    private JavaAppSpec parseSpec(GenericKubernetesResource resource) {
        // 解析自定义资源规范
        ObjectMapper mapper = new ObjectMapper();
        return mapper.convertValue(resource.getAdditionalProperties().get("spec"), JavaAppSpec.class);
    }
    
    private void createOrUpdateDeployment(String namespace, String name, JavaAppSpec spec) {
        Deployment deployment = new DeploymentBuilder()
            .withNewMetadata()
                .withName(name)
                .withNamespace(namespace)
                .withLabels(createLabels(name, spec.getVersion()))
            .endMetadata()
            .withNewSpec()
                .withReplicas(spec.getReplicas())
                .withNewSelector()
                    .withMatchLabels(createLabels(name, spec.getVersion()))
                .endSelector()
                .withNewTemplate()
                    .withNewMetadata()
                        .withLabels(createLabels(name, spec.getVersion()))
                    .endMetadata()
                    .withNewSpec()
                        .withContainers(createContainer(name, spec))
                        .withSecurityContext(createSecurityContext())
                    .endSpec()
                .endTemplate()
                .withStrategy(createUpdateStrategy())
            .endSpec()
            .build();
        
        kubernetesClient.apps().deployments()
            .inNamespace(namespace)
            .resource(deployment)
            .createOrReplace();
    }
    
    private Container createContainer(String name, JavaAppSpec spec) {
        return new ContainerBuilder()
            .withName(name)
            .withImage("registry.company.com/" + name + ":" + spec.getVersion())
            .withPorts(new ContainerPortBuilder()
                .withContainerPort(8080)
                .withProtocol("TCP")
                .build())
            .withEnv(createEnvVars(spec))
            .withResources(createResourceRequirements(spec.getResources()))
            .withLivenessProbe(createProbe(spec.getLivenessProbe()))
            .withReadinessProbe(createProbe(spec.getReadinessProbe()))
            .withSecurityContext(createContainerSecurityContext())
            .build();
    }
    
    private List<EnvVar> createEnvVars(JavaAppSpec spec) {
        List<EnvVar> envVars = new ArrayList<>();
        
        // 添加JVM参数
        if (spec.getJvmArgs() != null) {
            envVars.add(new EnvVarBuilder()
                .withName("JAVA_OPTS")
                .withValue(String.join(" ", spec.getJvmArgs()))
                .build());
        }
        
        // 添加自定义环境变量
        if (spec.getEnv() != null) {
            spec.getEnv().forEach((key, value) -> 
                envVars.add(new EnvVarBuilder()
                    .withName(key)
                    .withValue(value)
                    .build())
            );
        }
        
        return envVars;
    }
    
    private Map<String, String> createLabels(String name, String version) {
        Map<String, String> labels = new HashMap<>();
        labels.put("app", name);
        labels.put("version", version);
        labels.put("managed-by", "java-app-operator");
        return labels;
    }
    
    // 其他创建方法...
}

Helm Chart包管理

3.1 Helm Chart结构设计

# Chart.yaml
apiVersion: v2
name: java-springboot-app
description: A Helm chart for Spring Boot Java applications
type: application
version: 1.0.0
appVersion: "1.0.0"

dependencies:
  - name: mysql
    version: 9.7.0
    repository: https://charts.bitnami.com/bitnami
    condition: mysql.enabled
  - name: redis
    version: 17.3.0
    repository: https://charts.bitnami.com/bitnami
    condition: redis.enabled
  - name: prometheus
    version: 15.0.0
    repository: https://prometheus-community.github.io/helm-charts
    condition: monitoring.enabled
# values.yaml - 配置默认值
# 应用配置
app:
  name: "user-service"
  version: "1.0.0"
  replicaCount: 3
  image:
    repository: "registry.company.com/user-service"
    tag: "latest"
    pullPolicy: IfNotPresent
  service:
    type: ClusterIP
    port: 8080
    metricsPort: 8081
  ingress:
    enabled: true
    className: "nginx"
    hosts:
      - host: "users.api.company.com"
        paths:
          - path: "/"
            pathType: Prefix
    tls: []
  # Java配置
  java:
    opts: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"
    jmx:
      enabled: false
      port: 1099
  # 资源限制
  resources:
    requests:
      memory: "512Mi"
      cpu: "250m"
    limits:
      memory: "1Gi"
      cpu: "500m"
  # 健康检查
  livenessProbe:
    path: "/actuator/health/liveness"
    initialDelaySeconds: 60
    periodSeconds: 10
    timeoutSeconds: 5
  readinessProbe:
    path: "/actuator/health/readiness"
    initialDelaySeconds: 30
    periodSeconds: 5
    timeoutSeconds: 3
  # 环境变量
  env:
    SPRING_PROFILES_ACTIVE: "kubernetes"
    MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: "health,info,metrics,prometheus"

# 数据库配置
mysql:
  enabled: true
  auth:
    rootPassword: "mysql-root-password"
    database: "user_db"
    username: "app_user"
    password: "app_password"
  primary:
    persistence:
      enabled: true
      size: "8Gi"
    resources:
      requests:
        memory: "256Mi"
        cpu: "100m"

# Redis配置
redis:
  enabled: true
  auth:
    password: "redis-password"
  master:
    persistence:
      enabled: true
      size: "4Gi"
    resources:
      requests:
        memory: "128Mi"
        cpu: "100m"

# 监控配置
monitoring:
  enabled: true
  prometheus:
    scrapeInterval: "30s"
  grafana:
    enabled: true
    adminPassword: "admin"

# 自动伸缩配置
autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80
  targetMemoryUtilizationPercentage: 80
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "java-springboot-app.fullname" . }}
  labels:
    {{- include "java-springboot-app.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.app.replicaCount }}
  {{- end }}
  revisionHistoryLimit: {{ .Values.app.revisionHistoryLimit | default 3 }}
  strategy:
    {{- toYaml .Values.app.strategy | nindent 4 }}
  selector:
    matchLabels:
      {{- include "java-springboot-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "java-springboot-app.selectorLabels" . | nindent 8 }}
        {{- with .Values.app.podLabels }}
        {{ toYaml . | nindent 8 }}
        {{- end }}
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        {{- with .Values.app.podAnnotations }}
        {{ toYaml . | nindent 8 }}
        {{- end }}
    spec:
      {{- with .Values.app.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "java-springboot-app.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.app.podSecurityContext | nindent 8 }}
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.app.securityContext | nindent 12 }}
          image: "{{ .Values.app.image.repository }}:{{ .Values.app.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.app.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.app.service.port }}
              protocol: TCP
            - name: metrics
              containerPort: {{ .Values.app.service.metricsPort }}
              protocol: TCP
          env:
            {{- range $key, $value := .Values.app.env }}
            - name: {{ $key }}
              value: {{ $value | quote }}
            {{- end }}
            {{- if .Values.app.java.jmx.enabled }}
            - name: JAVA_TOOL_OPTIONS
              value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port={{ .Values.app.java.jmx.port }} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false"
            {{- end }}
          envFrom:
            - configMapRef:
                name: {{ include "java-springboot-app.fullname" . }}-config
            - secretRef:
                name: {{ include "java-springboot-app.fullname" . }}-secrets
          livenessProbe:
            httpGet:
              path: {{ .Values.app.livenessProbe.path }}
              port: http
            initialDelaySeconds: {{ .Values.app.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.app.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.app.livenessProbe.timeoutSeconds }}
            failureThreshold: {{ .Values.app.livenessProbe.failureThreshold | default 3 }}
          readinessProbe:
            httpGet:
              path: {{ .Values.app.readinessProbe.path }}
              port: http
            initialDelaySeconds: {{ .Values.app.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.app.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.app.readinessProbe.timeoutSeconds }}
            failureThreshold: {{ .Values.app.readinessProbe.failureThreshold | default 3 }}
          resources:
            {{- toYaml .Values.app.resources | nindent 12 }}
          volumeMounts:
            - name: config
              mountPath: /app/config
              readOnly: true
      volumes:
        - name: config
          configMap:
            name: {{ include "java-springboot-app.fullname" . }}-config
      {{- with .Values.app.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.app.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.app.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

高级部署策略

4.1 蓝绿部署与金丝雀发布

# blue-green-deployment.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: user-service
  namespace: java-app
spec:
  # 目标部署引用
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: user-service
  # 服务引用
  service:
    port: 8080
    targetPort: 8080
    # 流量策略
    trafficPolicy:
      tls:
        # mTLS配置
        mode: ISTIO_MUTUAL
  # 分析配置
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      threshold: 99
      interval: 1m
    - name: request-duration
      threshold: 500
      interval: 1m
    - name: error-rate
      threshold: 1
      interval: 1m
    # 自定义指标
    webhooks:
      - name: "load-test"
        type: pre-rollout
        url: http://flagger-loadtester.test/
        timeout: 5m
        metadata:
          type: "cmd"
          cmd: "hey -z 1m -q 10 -c 2 http://user-service-canary.java-app:8080/actuator/health"
      - name: "analysis"
        type: rollout
        url: http://analyzer-service/
        metadata:
          query: "max_over_time(application_requests_total{job='user-service', status=~'2..'}[1m])"
// 部署策略管理器
@Component
@Slf4j
public class DeploymentStrategyManager {
    
    @Autowired
    private KubernetesClient kubernetesClient;
    
    /**
     * 执行蓝绿部署
     */
    public void blueGreenDeployment(String deploymentName, String namespace, 
                                   String newImage, String newVersion) {
        log.info("开始蓝绿部署: {}/{} -> {}", namespace, deploymentName, newImage);
        
        try {
            // 1. 创建绿色部署
            String greenDeploymentName = deploymentName + "-green";
            createGreenDeployment(greenDeploymentName, namespace, newImage, newVersion);
            
            // 2. 等待绿色部署就绪
            waitForDeploymentReady(greenDeploymentName, namespace);
            
            // 3. 切换服务流量
            switchServiceTraffic(deploymentName, greenDeploymentName, namespace);
            
            // 4. 清理蓝色部署
            cleanupBlueDeployment(deploymentName, namespace);
            
            log.info("蓝绿部署完成: {}/{}", namespace, deploymentName);
            
        } catch (Exception e) {
            log.error("蓝绿部署失败: {}/{}", namespace, deploymentName, e);
            rollbackBlueGreenDeployment(deploymentName, namespace);
        }
    }
    
    /**
     * 执行金丝雀发布
     */
    public void canaryDeployment(String deploymentName, String namespace,
                                String newImage, String newVersion, 
                                int initialTrafficPercent) {
        log.info("开始金丝雀发布: {}/{} -> {} ({}%)", 
            namespace, deploymentName, newImage, initialTrafficPercent);
        
        try {
            // 1. 创建金丝雀部署
            String canaryDeploymentName = deploymentName + "-canary";
            createCanaryDeployment(canaryDeploymentName, namespace, newImage, newVersion);
            
            // 2. 逐步增加流量
            for (int trafficPercent = initialTrafficPercent; trafficPercent <= 100; trafficPercent += 10) {
                adjustTrafficSplit(deploymentName, canaryDeploymentName, namespace, trafficPercent);
                
                // 等待一段时间观察指标
                Thread.sleep(300000); // 5分钟
                
                // 检查应用健康状态
                if (!isDeploymentHealthy(canaryDeploymentName, namespace)) {
                    log.warn("金丝雀部署不健康,回滚");
                    rollbackCanaryDeployment(deploymentName, namespace);
                    return;
                }
                
                log.info("金丝雀流量已调整到: {}%", trafficPercent);
            }
            
            // 3. 完成发布
            completeCanaryDeployment(deploymentName, canaryDeploymentName, namespace);
            
            log.info("金丝雀发布完成: {}/{}", namespace, deploymentName);
            
        } catch (Exception e) {
            log.error("金丝雀发布失败: {}/{}", namespace, deploymentName, e);
            rollbackCanaryDeployment(deploymentName, namespace);
        }
    }
    
    private void createGreenDeployment(String name, String namespace, String image, String version) {
        // 基于现有部署创建绿色部署
        Deployment existing = kubernetesClient.apps().deployments()
            .inNamespace(namespace)
            .withName(name.replace("-green", ""))
            .get();
        
        Deployment greenDeployment = new DeploymentBuilder(existing)
            .editMetadata()
                .withName(name)
                .withResourceVersion(null)
            .endMetadata()
            .editSpec()
                .editTemplate()
                    .editMetadata()
                        .withLabels(updateLabels(existing.getSpec().getTemplate().getMetadata().getLabels(), version))
                    .endMetadata()
                    .editSpec()
                        .editContainer(0)
                            .withImage(image)
                        .endContainer()
                    .endSpec()
                .endTemplate()
            .endSpec()
            .build();
        
        kubernetesClient.apps().deployments()
            .inNamespace(namespace)
            .resource(greenDeployment)
            .create();
    }
    
    private Map<String, String> updateLabels(Map<String, String> labels, String version) {
        Map<String, String> newLabels = new HashMap<>(labels);
        newLabels.put("version", version);
        newLabels.put("deployment-color", "green");
        return newLabels;
    }
    
    private void waitForDeploymentReady(String deploymentName, String namespace) throws InterruptedException {
        int maxWaitTime = 600; // 10分钟
        int waited = 0;
        
        while (waited < maxWaitTime) {
            Deployment deployment = kubernetesClient.apps().deployments()
                .inNamespace(namespace)
                .withName(deploymentName)
                .get();
            
            if (deployment != null && deployment.getStatus() != null &&
                deployment.getStatus().getReadyReplicas() != null &&
                deployment.getStatus().getReadyReplicas().equals(deployment.getSpec().getReplicas())) {
                log.info("部署就绪: {}/{}", namespace, deploymentName);
                return;
            }
            
            Thread.sleep(10000); // 等待10秒
            waited += 10;
            log.info("等待部署就绪: {}/{} (已等待{}秒)", namespace, deploymentName, waited);
        }
        
        throw new RuntimeException("部署就绪超时: " + deploymentName);
    }
    
    // 其他方法实现...
}

监控与日志管理

5.1 应用监控配置

# service-monitor.yaml - Prometheus监控
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: user-service-monitor
  namespace: java-app
  labels:
    app: user-service
    release: prometheus
spec:
  selector:
    matchLabels:
      app: user-service
  endpoints:
  - port: metrics
    path: /actuator/prometheus
    interval: 30s
    scrapeTimeout: 10s
    relabelings:
    - sourceLabels: [__meta_kubernetes_pod_name]
      targetLabel: pod
    - sourceLabels: [__meta_kubernetes_namespace]
      targetLabel: namespace
  namespaceSelector:
    matchNames:
    - java-app
// 自定义应用监控
@Component
@Slf4j
public class ApplicationMetrics {
    
    private final MeterRegistry meterRegistry;
    private final Counter httpRequestsTotal;
    private final Timer httpRequestDuration;
    private final Gauge memoryUsage;
    private final Counter businessEvents;
    
    public ApplicationMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        // HTTP请求指标
        this.httpRequestsTotal = Counter.builder("http_requests_total")
            .description("Total HTTP requests")
            .tags("application", "user-service")
            .register(meterRegistry);
            
        this.httpRequestDuration = Timer.builder("http_request_duration_seconds")
            .description("HTTP request duration in seconds")
            .tags("application", "user-service")
            .register(meterRegistry);
        
        // 内存使用指标
        this.memoryUsage = Gauge.builder("jvm_memory_usage_ratio")
            .description("JVM memory usage ratio")
            .tags("application", "user-service")
            .register(meterRegistry, this, self -> self.calculateMemoryUsage());
        
        // 业务指标
        this.businessEvents = Counter.builder("business_events_total")
            .description("Total business events")
            .tags("application", "user-service")
            .register(meterRegistry);
    }
    
    public void recordHttpRequest(String method, String path, int status, long duration) {
        httpRequestsTotal.increment();
        
        httpRequestDuration.record(duration, TimeUnit.MILLISECONDS);
        
        // 记录带标签的指标
        Counter.builder("http_requests_detailed")
            .tags("method", method, "path", path, "status", String.valueOf(status))
            .register(meterRegistry)
            .increment();
    }
    
    public void recordBusinessEvent(String eventType) {
        businessEvents.increment();
        
        Counter.builder("business_events_detailed")
            .tags("type", eventType)
            .register(meterRegistry)
            .increment();
    }
    
    private double calculateMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();
        long usedMemory = runtime.totalMemory() - runtime.freeMemory();
        long maxMemory = runtime.maxMemory();
        return (double) usedMemory / maxMemory;
    }
    
    // 数据库连接池监控
    @EventListener
    public void monitorDataSource(DataSourcePoolCreatedEvent event) {
        DataSource dataSource = event.getDataSource();
        if (dataSource instanceof HikariDataSource) {
            HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
            
            Gauge.builder("database_connections_active")
                .description("Active database connections")
                .tags("application", "user-service")
                .register(meterRegistry, hikariDataSource, HikariDataSource::getHikariPoolMXBean)
                .getActiveConnections();
                
            Gauge.builder("database_connections_idle")
                .description("Idle database connections")
                .tags("application", "user-service")
                .register(meterRegistry, hikariDataSource, HikariDataSource::getHikariPoolMXBean)
                .getIdleConnections();
        }
    }
}

总结

🎯 Docker & Kubernetes部署核心要点

  1. 容器化最佳实践

    • 多阶段构建优化镜像大小
    • 非root用户运行增强安全
    • 合理的资源限制和健康检查
  2. Kubernetes部署策略

    • 使用ConfigMap和Secret管理配置
    • 合理的资源请求和限制
    • 多维度健康检查确保应用可用性
  3. 高级部署模式

    • 蓝绿部署实现零停机发布
    • 金丝雀发布降低发布风险
    • Helm标准化应用部署
  4. 监控与可观测性

    • 应用指标暴露和收集
    • 日志聚合和分析
    • 分布式追踪

🚀 生产环境建议

  1. 安全加固

    • 使用Pod安全策略
    • 网络策略限制通信
    • 镜像漏洞扫描
  2. 性能优化

    • 合理的资源配额
    • HPA自动伸缩
    • 节点亲和性调度
  3. 灾难恢复

    • 定期备份ETCD
    • 应用数据备份
    • 跨可用区部署

📋 部署检查表

  • Docker镜像优化和安全扫描
  • Kubernetes资源配置合理
  • 健康检查配置正确
  • 监控告警配置完善
  • 日志收集配置正确
  • 备份策略准备就绪
  • 灾难恢复方案测试
  • 安全策略配置到位

掌握Docker和Kubernetes的Java应用部署,能够让你的应用在云原生环境中获得更好的弹性、可观测性和可靠性。


下一篇预告:《响应式编程实战:WebFlux与RSocket深度解析》

如果觉得本文对你有帮助,请点赞、收藏、关注!欢迎在评论区分享你的容器化部署经验和问题。

更多推荐