从 0 到 1 掌控云原生部署:Java 项目的 Docker 容器化与 K8s 集群实战指南

引言:容器化部署为何成为 Java 开发的必选项

在 Java 开发领域,部署方式正经历着从传统虚拟机到容器化的革命性转变。根据 2024 年 JetBrains 开发者调查,超过 78% 的企业级 Java 项目已采用容器化部署,其中 Kubernetes 成为容器编排的绝对主导者,市场占有率高达 83%。

这种转变并非偶然。传统 Java 应用部署面临着环境一致性差、资源利用率低、扩展能力弱等痛点:开发环境运行正常的代码,到了测试环境却频频报错;为应对流量峰值不得不长期维持大量闲置服务器;系统出现故障时,手动恢复耗时费力。

Docker 容器化技术与 Kubernetes 集群管理的结合,为这些问题提供了完美解决方案:

  • 环境一致性:容器封装了应用及其所有依赖,确保 "一次构建,到处运行"
  • 资源高效利用:容器比虚拟机更轻量,能在相同硬件上运行更多应用实例
  • 弹性伸缩:根据负载自动调整实例数量,平衡性能与成本
  • 故障自愈:自动检测并替换故障实例,提高系统可用性
  • 简化部署流程:实现从代码提交到生产部署的自动化流水线

本文将带领你完成 Java 项目的容器化之旅,从基础的 Docker 镜像构建,到复杂的 Kubernetes 集群部署,再到高级的自动伸缩和故障自愈配置,全方位掌握云原生部署技术栈。

一、Docker 容器化基础:从理论到实践

1.1 Docker 核心概念与工作原理

Docker 采用了操作系统级虚拟化技术,通过隔离用户空间实现应用的独立运行。其核心组件包括:

  • 镜像 (Image):包含运行应用所需的代码、运行时、库、环境变量和配置文件的不可变模板
  • 容器 (Container):镜像的运行实例,是一个独立的可执行软件包
  • Docker 引擎:创建和管理容器的核心软件,包括 Docker 守护进程和命令行客户端
  • 仓库 (Registry):存储和分发 Docker 镜像的仓库,如 Docker Hub、阿里云容器仓库等
  • 网络 (Network):实现容器间通信的网络模型
  • 卷 (Volume):用于持久化存储容器数据的机制

Docker 的工作流程可概括为:

1.2 准备一个可容器化的 Java 项目

我们将以一个 Spring Boot 应用为例,演示容器化过程。首先创建项目结构并添加核心依赖:

pom.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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>docker-k8s-demo</artifactId>
    <version>1.0.0</version>
    <name>docker-k8s-demo</name>
    <description>A demo project for Docker and Kubernetes deployment</description>

    <properties>
        <java.version>17</java.version>
        <lombok.version>1.18.30</lombok.version>
        <springdoc.version>2.1.0</springdoc.version>
        <fastjson2.version>2.0.45</fastjson2.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Actuator (用于健康检查和监控) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>

        <!-- Swagger3 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>

        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- MySQL Driver -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- FastJSON2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>

        <!-- Test -->
        <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>
                    <!-- 构建分层jar,优化Docker镜像构建 -->
                    <layers>
                        <enabled>true</enabled>
                    </layers>
                </configuration>
            </plugin>

            <!-- 编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

应用主类

复制代码
package com.example.dockerk8sdemo;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;

/**
 * 应用主类
 *
 * @author ken
 */
@SpringBootApplication
@MapperScan("com.example.dockerk8sdemo.mapper")
@OpenAPIDefinition(info = @Info(title = "Docker-K8s Demo API", version = "1.0", description = "演示API"))
@Slf4j
public class DockerK8sDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DockerK8sDemoApplication.class, args);
    }

    /**
     * 应用启动完成事件
     */
    @EventListener(ApplicationReadyEvent.class)
    public void onApplicationReady() {
        log.info("Application started successfully");
        // 打印环境信息,用于验证容器环境
        String javaVersion = System.getProperty("java.version");
        String userName = System.getenv("USER_NAME");
        log.info("Running with Java version: {}", javaVersion);
        log.info("USER_NAME environment variable: {}", userName);
    }
}

配置文件

复制代码
# application.yml
spring:
  profiles:
    active: ${SPRING_PROFILES_ACTIVE:default}
  datasource:
    url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:demo}?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: ${DB_USERNAME:root}
    password: ${DB_PASSWORD:root}
    driver-class-name: com.mysql.cj.jdbc.Driver

# 应用配置
server:
  port: ${PORT:8080}
  servlet:
    context-path: /api

# Actuator配置(暴露健康检查和监控端点)
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  endpoint:
    health:
      show-details: always
      probes:
        enabled: true
  metrics:
    export:
      prometheus:
        enabled: true

# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
  global-config:
    db-config:
      id-type: auto

健康检查控制器

复制代码
package com.example.dockerk8sdemo.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * 健康检查和演示控制器
 *
 * @author ken
 */
@RestController
@RequestMapping("/health")
@RequiredArgsConstructor
@Tag(name = "健康检查", description = "应用健康状态检查接口")
@Slf4j
public class HealthController implements HealthIndicator {

    private final Random random = new Random();

    /**
     * 自定义健康检查端点
     */
    @Override
    public Health health() {
        // 实际应用中可以检查数据库连接、缓存等资源
        int errorCode = check();
        if (errorCode != 0) {
            return Health.down()
                    .withDetail("Error Code", errorCode)
                    .withDetail("Message", "Some resources are not available")
                    .build();
        }
        return Health.up()
                .withDetail("Message", "Application is running normally")
                .withDetail("Version", "1.0.0")
                .build();
    }

    /**
     * 简单的健康检查逻辑
     */
    private int check() {
        // 模拟健康检查,99%概率返回健康
        return random.nextInt(100) < 99 ? 0 : 1;
    }

    /**
     * 测试接口
     */
    @GetMapping("/test")
    @Operation(summary = "测试接口", description = "用于验证服务是否正常运行")
    public Map<String, Object> test() {
        Map<String, Object> result = new HashMap<>(4);
        result.put("status", "success");
        result.put("message", "Service is running");
        result.put("instance", System.getenv("HOSTNAME")); // 在容器中会显示容器ID
        result.put("timestamp", System.currentTimeMillis());
        log.info("Test endpoint called");
        return result;
    }

    /**
     * 模拟负载的接口
     */
    @GetMapping("/load/{seconds}")
    @Operation(summary = "模拟负载", description = "用于测试自动伸缩功能")
    public Map<String, Object> simulateLoad(
            @Parameter(description = "负载持续时间(秒)") @PathVariable int seconds) {
        long endTime = System.currentTimeMillis() + seconds * 1000L;
        log.info("Simulating load for {} seconds", seconds);
        
        // 消耗CPU
        while (System.currentTimeMillis() < endTime) {
            Math.sqrt(random.nextDouble());
        }
        
        Map<String, Object> result = new HashMap<>(3);
        result.put("status", "success");
        result.put("message", "Load simulation completed");
        result.put("durationSeconds", seconds);
        return result;
    }
}

1.3 编写高效的 Dockerfile

Dockerfile 是构建 Docker 镜像的蓝图,一个优化的 Dockerfile 能显著减小镜像体积并提高构建速度。以下是针对 Java 应用的优化 Dockerfile:

复制代码
# 多阶段构建:第一阶段编译打包
FROM maven:3.9.6-eclipse-temurin-17 AS builder

# 设置工作目录
WORKDIR /app

# 复制pom.xml和依赖文件,利用缓存
COPY pom.xml .
COPY src ./src

# 编译打包,跳过测试以加快构建
RUN mvn clean package -DskipTests

# 提取Spring Boot分层
RUN java -Djarmode=layertools -jar target/*.jar extract

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

# 添加非root用户,增强安全性
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 设置工作目录
WORKDIR /app

# 复制分层文件
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./

# 切换到非root用户
USER appuser

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
  CMD wget -q --spider http://localhost:8080/api/health || exit 1

# 启动命令
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

# 元数据标签
LABEL maintainer="ken"
LABEL version="1.0.0"
LABEL description="Docker-K8s Demo Application"

这个 Dockerfile 采用了多项优化技术:

  1. 多阶段构建:将编译环境和运行环境分离,最终镜像只包含运行所需文件
  2. 分层构建:利用 Spring Boot 的分层功能,将不常变化的依赖与频繁变化的应用代码分离,提高缓存利用率
  3. 使用轻量基础镜像:采用 Alpine 版本的 JRE,比完整版小很多
  4. 非 root 用户运行:增强容器安全性,避免潜在的权限问题
  5. 健康检查:内置健康检查命令,便于容器平台监控应用状态
  6. 适当的元数据:添加标签信息,提高镜像可维护性

1.4 构建并测试 Docker 镜像

构建镜像命令:

复制代码
# 构建镜像,指定标签
docker build -t docker-k8s-demo:1.0.0 .

# 查看构建的镜像
docker images | grep docker-k8s-demo

运行容器测试:

复制代码
# 简单运行
docker run -d -p 8080:8080 --name demo-app docker-k8s-demo:1.0.0

# 带环境变量运行
docker run -d \
  -p 8080:8080 \
  --name demo-app \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DB_HOST=mysql \
  -e DB_USERNAME=root \
  -e DB_PASSWORD=secret \
  docker-k8s-demo:1.0.0

# 查看容器日志
docker logs -f demo-app

# 测试接口
curl http://localhost:8080/api/health/test

# 停止并删除容器
docker stop demo-app
docker rm demo-app

1.5 推送镜像到仓库

为了在 Kubernetes 集群中使用镜像,需要将其推送到容器仓库:

复制代码
# 登录Docker Hub(如果使用其他仓库,如阿里云容器仓库,替换为相应地址)
docker login

# 标记镜像(格式:仓库地址/用户名/镜像名:标签)
docker tag docker-k8s-demo:1.0.0 yourusername/docker-k8s-demo:1.0.0

# 推送镜像
docker push yourusername/docker-k8s-demo:1.0.0

# 如果使用私有仓库
docker tag docker-k8s-demo:1.0.0 private-registry.example.com/demo/docker-k8s-demo:1.0.0
docker push private-registry.example.com/demo/docker-k8s-demo:1.0.0

二、Kubernetes 核心概念与集群搭建

2.1 Kubernetes 核心组件与架构

Kubernetes(简称 K8s)是一个开源的容器编排平台,能够自动化容器的部署、扩展和管理。其核心架构如下:

核心组件说明:

  • 控制平面 (Control Plane)

    • API Server:所有操作的统一入口,提供 RESTful API
    • etcd:分布式键值存储,保存集群的所有状态
    • Scheduler:负责 Pod 的调度,决定将 Pod 部署到哪个节点
    • Controller Manager:运行各种控制器进程,如节点控制器、副本控制器等
    • Cloud Controller Manager:与云服务提供商集成的控制器
  • 节点 (Node)

    • Kubelet:在每个节点上运行,确保容器按照 Pod 规范运行
    • Kube-proxy:网络代理,维护节点网络规则
    • 容器运行时:负责运行容器的软件,如 containerd

Kubernetes 的核心对象包括:

  • Pod:最小部署单元,包含一个或多个容器
  • Service:定义 Pod 的访问方式,提供固定访问点
  • Deployment:管理 Pod 和 ReplicaSet,支持滚动更新
  • StatefulSet:用于管理有状态应用
  • ConfigMap/Secret:配置管理
  • Namespace:提供资源隔离
  • Ingress:管理外部访问

2.2 搭建本地 Kubernetes 集群

对于开发和测试,我们可以使用 Minikube 搭建本地单节点集群:

复制代码
# 安装Minikube(Windows使用Chocolatey,macOS使用Homebrew)
# macOS:
brew install minikube

# 启动集群(指定容器运行时为containerd,内存2GB)
minikube start --driver=docker --container-runtime=containerd --memory=2048

# 检查集群状态
minikube status

# 安装kubectl(Kubernetes命令行工具)
# macOS:
brew install kubectl

# 验证kubectl配置
kubectl config view

# 查看节点信息
kubectl get nodes

# 查看集群组件
kubectl get pods -n kube-system

对于生产环境,建议使用 kubeadm 搭建多节点集群,或使用云服务商提供的托管 Kubernetes 服务(如 EKS、GKE、AKS、阿里云 ACK 等)。

2.3 kubectl 命令行工具基础

kubectl 是与 Kubernetes 集群交互的主要工具,常用命令:

复制代码
# 查看资源
kubectl get pods          # 查看所有Pod
kubectl get services      # 查看所有Service
kubectl get deployments   # 查看所有Deployment
kubectl get namespaces    # 查看所有Namespace

# 查看详细信息
kubectl describe pod <pod-name>
kubectl describe deployment <deployment-name>

# 查看日志
kubectl logs <pod-name>
kubectl logs -f <pod-name>  # 实时查看日志

# 执行命令
kubectl exec -it <pod-name> -- /bin/sh

# 创建资源
kubectl create -f <yaml-file>

# 应用配置(创建或更新资源)
kubectl apply -f <yaml-file>

# 删除资源
kubectl delete pod <pod-name>
kubectl delete -f <yaml-file>

# 查看集群信息
kubectl cluster-info

# 切换命名空间
kubectl config set-context --current --namespace=<namespace-name>

三、在 Kubernetes 部署 Java 应用

3.1 编写 Kubernetes 配置文件

Kubernetes 使用 YAML 或 JSON 格式的配置文件定义资源。我们需要创建以下配置文件来部署 Java 应用:

1. 命名空间配置(namespace.yaml)

复制代码
apiVersion: v1
kind: Namespace
metadata:
  name: demo-app
  labels:
    name: demo-app

2. 配置文件(configmap.yaml)

复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: demo-app-config
  namespace: demo-app
data:
  # 应用配置
  SPRING_PROFILES_ACTIVE: "prod"
  SERVER_PORT: "8080"
  
  # 日志配置
  LOG_LEVEL: "INFO"
  LOG_PATTERN: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

3. 密钥配置(secret.yaml)

复制代码
apiVersion: v1
kind: Secret
metadata:
  name: demo-app-secret
  namespace: demo-app
type: Opaque
data:
  # 注意:这里的值需要Base64编码
  DB_USERNAME: cm9vdA==  # root的Base64编码
  DB_PASSWORD: c2VjcmV0  # secret的Base64编码

生成 Base64 编码的命令:echo -n "value" | base64

4. 部署配置(deployment.yaml)

复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-app
  namespace: demo-app
  labels:
    app: demo-app
spec:
  # 初始副本数
  replicas: 2
  
  # 选择器,用于匹配Pod模板
  selector:
    matchLabels:
      app: demo-app
  
  # 策略配置
  strategy:
    # 滚动更新策略
    rollingUpdate:
      maxSurge: 1        # 可以超出期望副本数的最大数量
      maxUnavailable: 0  # 更新过程中允许不可用的最大副本数
    type: RollingUpdate
  
  # Pod模板
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      # 容器配置
      containers:
      - name: demo-app
        # 镜像地址(替换为你的镜像仓库地址)
        image: yourusername/docker-k8s-demo:1.0.0
        imagePullPolicy: Always
        
        # 端口配置
        ports:
        - containerPort: 8080
          name: http
          protocol: TCP
        
        # 环境变量配置
        env:
        # 从ConfigMap获取环境变量
        - name: SPRING_PROFILES_ACTIVE
          valueFrom:
            configMapKeyRef:
              name: demo-app-config
              key: SPRING_PROFILES_ACTIVE
        - name: SERVER_PORT
          valueFrom:
            configMapKeyRef:
              name: demo-app-config
              key: SERVER_PORT
        # 从Secret获取环境变量
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: demo-app-secret
              key: DB_USERNAME
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: demo-app-secret
              key: DB_PASSWORD
        # 直接指定环境变量
        - name: DB_HOST
          value: mysql-service
        - name: DB_PORT
          value: "3306"
        - name: DB_NAME
          value: demo
        
        # 资源限制
        resources:
          requests:
            memory: "256Mi"
            cpu: "200m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        
        # 健康检查
        livenessProbe:
          httpGet:
            path: /api/health
            port: 8080
          initialDelaySeconds: 60  # 启动后多久开始检查
          periodSeconds: 30        # 检查间隔
          timeoutSeconds: 3        # 超时时间
          failureThreshold: 3      # 失败多少次视为不健康
        
        readinessProbe:
          httpGet:
            path: /api/health/test
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
        
        # 启动探针(确保应用完全启动)
        startupProbe:
          httpGet:
            path: /api/health
            port: 8080
          failureThreshold: 30
          periodSeconds: 10

5. 服务配置(service.yaml)

复制代码
apiVersion: v1
kind: Service
metadata:
  name: demo-app-service
  namespace: demo-app
spec:
  selector:
    app: demo-app
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  type: ClusterIP  # 集群内部访问

6. ingress 配置(ingress.yaml)- 可选

复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-app-ingress
  namespace: demo-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - host: demo-app.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: demo-app-service
            port:
              number: 80

3.2 部署应用到 Kubernetes 集群

执行以下命令部署应用:

复制代码
# 创建命名空间
kubectl apply -f namespace.yaml

# 创建配置和密钥
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml

# 部署应用
kubectl apply -f deployment.yaml

# 创建服务
kubectl apply -f service.yaml

# (可选)创建Ingress
# 首先需要安装Ingress控制器:minikube addons enable ingress
kubectl apply -f ingress.yaml

# 查看部署状态
kubectl get deployments -n demo-app
kubectl get pods -n demo-app
kubectl get services -n demo-app

# 查看部署详情
kubectl describe deployment demo-app -n demo-app

# 查看Pod日志
kubectl logs -f <pod-name> -n demo-app

3.3 访问 Kubernetes 中的应用

有多种方式可以访问 Kubernetes 集群中的应用:

  1. 端口转发

    将本地端口8080转发到集群中的服务

    kubectl port-forward -n demo-app service/demo-app-service 8080:80

    现在可以通过http://localhost:8080访问应用

    curl http://localhost:8080/api/health/test

  2. 使用 Minikube 隧道(针对 NodePort 或 LoadBalancer 类型服务)

    在另一个终端启动隧道

    minikube tunnel

    获取服务地址

    kubectl get service demo-app-service -n demo-app

  3. 通过 Ingress 访问

    添加主机映射(Windows在C:\Windows\System32\drivers\etc\hosts,Linux/macOS在/etc/hosts)

    echo "$(minikube ip) demo-app.local" | sudo tee -a /etc/hosts

    访问应用

    curl http://demo-app.local/api/health/test

3.4 部署 MySQL 数据库

为了让我们的 Java 应用能够正常工作,需要部署一个 MySQL 数据库:

mysql-configmap.yaml

复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
  namespace: demo-app
data:
  MYSQL_DATABASE: "demo"
  MYSQL_ROOT_HOST: "%"

mysql-secret.yaml

复制代码
apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: demo-app
type: Opaque
data:
  MYSQL_ROOT_PASSWORD: c2VjcmV0  # secret的Base64编码

mysql-deployment.yaml

复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  namespace: demo-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.3.0
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: MYSQL_ROOT_PASSWORD
        - name: MYSQL_DATABASE
          valueFrom:
            configMapKeyRef:
              name: mysql-config
              key: MYSQL_DATABASE
        - name: MYSQL_ROOT_HOST
          valueFrom:
            configMapKeyRef:
              name: mysql-config
              key: MYSQL_ROOT_HOST
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
        resources:
          requests:
            memory: "256Mi"
            cpu: "200m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping", "-u", "root", "-p$(MYSQL_ROOT_PASSWORD)"]
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command: ["mysqladmin", "ping", "-u", "root", "-p$(MYSQL_ROOT_PASSWORD)"]
          initialDelaySeconds: 5
          periodSeconds: 5
      volumes:
      - name: mysql-data
        persistentVolumeClaim:
          claimName: mysql-pvc

mysql-pvc.yaml

复制代码
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
  namespace: demo-app
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

mysql-service.yaml

复制代码
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
  namespace: demo-app
spec:
  selector:
    app: mysql
  ports:
  - port: 3306
    targetPort: 3306
  clusterIP: None  # Headless Service

部署 MySQL:

复制代码
kubectl apply -f mysql-configmap.yaml
kubectl apply -f mysql-secret.yaml
kubectl apply -f mysql-pvc.yaml
kubectl apply -f mysql-deployment.yaml
kubectl apply -f mysql-service.yaml

# 查看MySQL部署状态
kubectl get pods -n demo-app | grep mysql

四、实现服务的自动部署与滚动更新

4.1 CI/CD 流水线集成

实现自动部署的关键是建立 CI/CD 流水线。我们以 GitHub Actions 为例,演示如何配置自动构建 Docker 镜像并部署到 Kubernetes:

创建.github/workflows/deploy.yml 文件

复制代码
name: Build and Deploy to Kubernetes

# 触发条件:main分支有push或pull request合并
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    # 检出代码
    - uses: actions/checkout@v4
    
    # 设置JDK 17
    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'
        cache: maven
    
    # 构建项目
    - name: Build with Maven
      run: mvn clean package -DskipTests
    
    # 设置Docker Buildx
    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    # 登录到Docker Hub
    - name: Login to DockerHub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}
    
    # 构建并推送Docker镜像
    - name: Build and push
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: ${{ secrets.DOCKERHUB_USERNAME }}/docker-k8s-demo:latest,${{ secrets.DOCKERHUB_USERNAME }}/docker-k8s-demo:${{ github.sha }}
    
    # 只有在main分支push时才部署到K8s
    - name: Deploy to Kubernetes
      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
      uses: steebchen/kubectl@v4
      with:
        config: ${{ secrets.KUBE_CONFIG_DATA }}  # 存储在GitHub Secrets中的kubeconfig
        command: apply -f k8s/

配置说明:

  1. 首先需要在 GitHub 仓库的 Secrets 中添加:

    • DOCKERHUB_USERNAME:Docker Hub 用户名
    • DOCKERHUB_TOKEN:Docker Hub 访问令牌
    • KUBE_CONFIG_DATA:base64 编码的 kubeconfig 文件内容
  2. 生成 KUBE_CONFIG_DATA 的命令:

    cat ~/.kube/config | base64

  3. 这个流水线会:

    • 当代码推送到 main 分支或有 PR 合并到 main 分支时触发
    • 构建 Java 项目
    • 构建 Docker 镜像并推送到 Docker Hub
    • 只有在 main 分支的 push 事件才会部署到 Kubernetes

4.2 Kubernetes 滚动更新策略

Kubernetes 的 Deployment 资源提供了滚动更新功能,确保应用更新过程中不中断服务。我们在前面的 deployment.yaml 中已经配置了滚动更新策略:

复制代码
strategy:
  rollingUpdate:
    maxSurge: 1        # 可以超出期望副本数的最大数量(绝对值或百分比)
    maxUnavailable: 0  # 更新过程中允许不可用的最大副本数
  type: RollingUpdate

手动触发滚动更新的方法:

复制代码
# 方法1:修改镜像版本
kubectl set image deployment/demo-app demo-app=yourusername/docker-k8s-demo:1.0.1 -n demo-app

# 方法2:使用kubectl apply更新配置
kubectl apply -f deployment.yaml

# 查看更新状态
kubectl rollout status deployment/demo-app -n demo-app

# 查看更新历史
kubectl rollout history deployment/demo-app -n demo-app

# 回滚到上一版本
kubectl rollout undo deployment/demo-app -n demo-app

# 回滚到指定版本
kubectl rollout undo deployment/demo-app --to-revision=2 -n demo-app

滚动更新的工作流程:

4.3 蓝绿部署与金丝雀发布

除了滚动更新,Kubernetes 还支持蓝绿部署和金丝雀发布等高级部署策略。

蓝绿部署:同时维护两个相同的环境(蓝环境和绿环境),新版本部署到非生产环境,测试通过后切换流量:

复制代码
# 创建新版本的Deployment(绿环境)
kubectl apply -f deployment-v2.yaml

# 测试新版本
kubectl port-forward -n demo-app deployment/demo-app-v2 8081:8080

# 切换Service指向新版本
kubectl patch service demo-app-service -n demo-app -p '{"spec":{"selector":{"app":"demo-app-v2"}}}'

# 如果出现问题,切换回旧版本
kubectl patch service demo-app-service -n demo-app -p '{"spec":{"selector":{"app":"demo-app"}}}'

金丝雀发布:先将少量流量导向新版本,验证无误后逐步增加流量比例:

复制代码
# 部署新版本,副本数较少
kubectl apply -f deployment-canary.yaml

# 通过Ingress控制流量分配(需要支持权重的Ingress控制器,如NGINX Ingress)
kubectl apply -f ingress-canary.yaml

ingress-canary.yaml 示例:

复制代码
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-app-primary
  namespace: demo-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - host: demo-app.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: demo-app-service
            port:
              number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-app-canary
  namespace: demo-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"  # 10%流量到金丝雀版本
spec:
  rules:
  - host: demo-app.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: demo-app-canary-service
            port:
              number: 80

五、实现服务的弹性伸缩

5.1 Kubernetes HPA(Horizontal Pod Autoscaler)

Horizontal Pod Autoscaler(HPA)可以根据 CPU 利用率、内存使用率或自定义指标自动调整 Pod 的数量。

1. 首先确保已部署 metrics-server

复制代码
# 安装metrics-server
minikube addons enable metrics-server

# 验证metrics-server是否运行
kubectl get pods -n kube-system | grep metrics-server

2. 创建 HPA 配置(hpa.yaml)

复制代码
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: demo-app-hpa
  namespace: demo-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: demo-app
  minReplicas: 2        # 最小副本数
  maxReplicas: 10       # 最大副本数
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70  # CPU利用率目标值
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80  # 内存利用率目标值
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60  # 扩容稳定窗口
      policies:
      - type: Percent
        value: 50                     # 每次扩容50%
        periodSeconds: 60             # 扩容间隔
    scaleDown:
      stabilizationWindowSeconds: 300 # 缩容稳定窗口(更长,避免频繁波动)
      policies:
      - type: Percent
        value: 30                     # 每次缩容30%
        periodSeconds: 120            # 缩容间隔

3. 部署 HPA

复制代码
kubectl apply -f hpa.yaml

# 查看HPA状态
kubectl get hpa -n demo-app
kubectl describe hpa demo-app-hpa -n demo-app

5.2 基于自定义指标的弹性伸缩

除了 CPU 和内存,Kubernetes 还支持基于自定义指标的弹性伸缩,如请求数、队列长度等。

1. 部署 Prometheus 和 Prometheus Adapter

复制代码
# 添加Helm仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 安装Prometheus
helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring --create-namespace

2. 配置 Prometheus Adapter:创建 custom-metrics-config.yaml:

复制代码
rules:
  default: false
  external:
  - seriesQuery: 'http_server_requests_seconds_count{job!=""}'
    resources:
      overrides:
        kubernetes_namespace:
          resource: namespace
        kubernetes_pod_name:
          resource: pod
    name:
      matches: "^(.*)_count"
      as: "${1}_per_second"
    metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>)'

使用 Helm 安装 Prometheus Adapter:

复制代码
helm install prometheus-adapter prometheus-community/prometheus-adapter \
  -n monitoring \
  --set configMap.create=true \
  --set configMap.content=$(cat custom-metrics-config.yaml)

3. 创建基于自定义指标的 HPA

复制代码
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: demo-app-hpa-custom
  namespace: demo-app
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: demo-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: http_server_requests_seconds_per_second
      target:
        type: AverageValue
        averageValue: "100"  # 每个Pod平均每秒100个请求
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
      - type: Percent
        value: 50
        periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 30
        periodSeconds: 120

5.3 测试弹性伸缩功能

使用以下方法测试弹性伸缩功能:

  1. 增加负载测试 CPU 触发扩容

    在一个终端中持续发送请求

    while true; do curl http://localhost:8080/api/health/load/1; sleep 0.1; done

    在另一个终端监控HPA和Pod

    watch -n 5 "kubectl get hpa -n demo-app; echo; kubectl get pods -n demo-app"

  2. 观察扩容过程:当 CPU 利用率超过目标值(70%)时,HPA 会自动增加 Pod 数量。

  3. 停止负载测试观察缩容

    停止发送请求后,等待缩容稳定窗口(300秒)

    watch -n 5 "kubectl get hpa -n demo-app; echo; kubectl get pods -n demo-app"

当负载降低后,HPA 会在稳定窗口后逐渐减少 Pod 数量到最小副本数。

六、实现服务的故障自愈

6.1 Kubernetes 的自愈机制

Kubernetes 提供了多种机制确保服务的高可用性和故障自愈:

  1. Pod 健康检查:通过 livenessProbe、readinessProbe 和 startupProbe 检测 Pod 状态
  2. 自动重启:当容器崩溃时,Kubelet 会根据 restartPolicy 自动重启容器
  3. 节点故障处理:当节点故障时,控制器会在健康节点上重新创建 Pod
  4. 副本集维护:ReplicaSet 控制器确保实际副本数与期望副本数一致

6.2 配置 Pod 的重启策略和健康检查

在 deployment.yaml 中配置重启策略和健康检查:

复制代码
spec:
  template:
    spec:
      restartPolicy: Always  # 总是重启失败的容器
      containers:
      - name: demo-app
        # ...其他配置...
        
        # 存活探针:检测容器是否运行正常,失败则重启
        livenessProbe:
          httpGet:
            path: /api/health
            port: 8080
          initialDelaySeconds: 60  # 应用启动时间较长,延迟开始检查
          periodSeconds: 30        # 检查间隔
          timeoutSeconds: 3        # 超时时间
          failureThreshold: 3      # 连续3次失败视为不健康
        
        # 就绪探针:检测容器是否可以接收请求,失败则从Service移除
        readinessProbe:
          httpGet:
            path: /api/health/test
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 3
          failureThreshold: 3
        
        # 启动探针:确保应用完全启动
        startupProbe:
          httpGet:
            path: /api/health
            port: 8080
          failureThreshold: 30     # 最多检查30次
          periodSeconds: 10        # 每10秒检查一次

重启策略(restartPolicy)可选值:

  • Always:总是重启失败的容器(默认值)
  • OnFailure:只有当容器以非零退出码终止时才重启
  • Never:从不重启容器

6.3 配置 Pod 的反亲和性与节点亲和性

为了提高服务的可用性,可以配置 Pod 的反亲和性,确保 Pod 分散在不同节点上:

复制代码
spec:
  template:
    spec:
      affinity:
        # 反亲和性:避免同一应用的Pod调度到同一节点
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - demo-app
            topologyKey: "kubernetes.io/hostname"
        
        # 节点亲和性:优先调度到具有特定标签的节点
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: environment
                operator: In
                values:
                - production
          - weight: 50
            preference:
              matchExpressions:
              - key: hardware
                operator: In
                values:
                - high-performance

6.4 测试故障自愈功能

测试 Kubernetes 的故障自愈能力:

  1. 手动删除 Pod

    查看当前Pod

    kubectl get pods -n demo-app

    删除一个Pod

    kubectl delete pod <pod-name> -n demo-app

    观察是否会自动创建新的Pod

    kubectl get pods -n demo-app

  2. 模拟容器崩溃

    进入容器

    kubectl exec -it <pod-name> -n demo-app -- /bin/sh

    在容器内杀死应用进程

    ps aux | grep java
    kill -9 <java-pid>

    退出容器,观察Pod状态

    exit
    kubectl get pods -n demo-app

  3. 模拟节点故障(仅在多节点集群中可行):

    标记节点不可调度

    kubectl cordon <node-name>

    排空节点

    kubectl drain <node-name> --ignore-daemonsets

    观察Pod是否迁移到其他节点

    kubectl get pods -n demo-app -o wide

    恢复节点

    kubectl uncordon <node-name>

在以上测试中,Kubernetes 都会自动恢复 Pod 到期望状态,体现了其强大的故障自愈能力。

七、监控与日志管理

7.1 部署 Prometheus 和 Grafana 监控

使用 Helm 部署 Prometheus 和 Grafana:

复制代码
# 添加Helm仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 创建命名空间
kubectl create namespace monitoring

# 安装kube-prometheus-stack
helm install prometheus prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --set grafana.service.type=NodePort \
  --set prometheus.service.type=NodePort

# 查看部署状态
kubectl get pods -n monitoring

# 获取Grafana访问地址
minikube service prometheus-grafana -n monitoring --url

配置 Java 应用暴露 Prometheus 指标:

我们的 Spring Boot 应用已经添加了 actuator 和 prometheus 依赖,只需在 application.yml 中配置:

复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    export:
      prometheus:
        enabled: true

在 Grafana 中导入 Spring Boot 应用监控面板(ID:12856),可以查看 JVM、内存、CPU、请求数等指标。

7.2 集中式日志管理

部署 ELK(Elasticsearch, Logstash, Kibana)栈或 EFK(Elasticsearch, Fluentd, Kibana)栈进行日志集中管理:

复制代码
# 安装EFK
helm repo add elastic https://helm.elastic.co
helm repo update

# 安装Elasticsearch
helm install elasticsearch elastic/elasticsearch \
  --namespace logging \
  --create-namespace \
  --set replicas=1 \
  --set minimumMasterNodes=1 \
  --set resources.requests.cpu=1 \
  --set resources.requests.memory=2Gi \
  --set resources.limits.cpu=1 \
  --set resources.limits.memory=2Gi

# 安装Kibana
helm install kibana elastic/kibana --namespace logging

# 安装Fluentd
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install fluentd bitnami/fluentd --namespace logging \
  --set elasticsearch.host=elasticsearch-master \
  --set elasticsearch.port=9200

配置 Java 应用输出 JSON 格式日志,在 logback-spring.xml 中:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>spanId</includeMdcKeyName>
            <fieldNames>
                <timestamp>timestamp</timestamp>
                <message>message</message>
                <logger>logger</logger>
                <thread>thread</thread>
                <level>level</level>
            </fieldNames>
            <timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

添加 logstash-logback-encoder 依赖:

复制代码
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.4</version>
</dependency>

八、生产环境最佳实践与优化

8.1 资源限制与请求配置

为所有容器设置合理的资源限制和请求是生产环境的基本要求:

复制代码
resources:
  requests:
    memory: "256Mi"  # 容器需要的最小内存
    cpu: "200m"      # 容器需要的最小CPU(1000m = 1核)
  limits:
    memory: "512Mi"  # 容器允许使用的最大内存
    cpu: "500m"      # 容器允许使用的最大CPU

设置资源限制的好处:

  • 防止单个容器耗尽节点资源
  • 帮助调度器做出更好的调度决策
  • 提高集群资源利用率
  • 避免节点级别的资源竞争

8.2 安全最佳实践

  1. 使用非 root 用户运行容器:在 Dockerfile 中创建并使用普通用户

  2. 限制容器权限

    securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    allowPrivilegeEscalation: false
    capabilities:
    drop: ["ALL"]

  3. 使用 PodSecurityContext

    podSecurityContext:
    seccompProfile:
    type: RuntimeDefault

  4. 配置网络策略:限制 Pod 间通信

  5. 定期更新基础镜像:修复已知漏洞

  6. 使用镜像拉取密钥:限制对私有仓库的访问

  7. 加密敏感数据:使用 Secret 存储敏感信息,并考虑使用 SealedSecrets 或 Vault

8.3 备份与灾难恢复

  1. 备份 etcd 数据

    创建备份

    kubectl -n kube-system exec -it <etcd-pod-name> -- etcdctl
    --endpoints=https://127.0.0.1:2379
    --cacert=/etc/kubernetes/pki/etcd/ca.crt
    --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt
    --key=/etc/kubernetes/pki/apiserver-etcd-client.key
    snapshot save /backup/etcd-snapshot.db

    复制备份到本地

    kubectl cp -n kube-system <etcd-pod-name>:/backup/etcd-snapshot.db ./etcd-snapshot.db

  2. 使用 Velero 进行集群备份

    安装Velero

    velero install
    --provider aws
    --plugins velero/velero-plugin-for-aws:v1.6.0
    --bucket my-backup-bucket
    --secret-file ./credentials-velero
    --use-volume-snapshots=false
    --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio:9000

    创建备份

    velero backup create demo-app-backup --include-namespaces demo-app

    查看备份

    velero backup get

    恢复备份

    velero restore create --from-backup demo-app-backup

  3. 多区域部署:对于关键业务,考虑跨区域部署以提高可用性

8.4 性能优化建议

  1. 使用适当的容器运行时:containerd 比 Docker 更轻量,性能更好

  2. 优化节点配置

    • 关闭 Swap
    • 配置适当的内核参数
    • 使用高性能网络插件(如 Calico)
  3. 优化 Pod 调度

    • 使用节点亲和性将 Pod 调度到合适的节点
    • 使用 Pod 反亲和性避免单点故障
    • 考虑使用 PriorityClass 确保关键 Pod 优先调度
  4. 使用本地存储:对于有状态应用,考虑使用本地 SSD 提高性能

  5. 配置资源自动伸缩:根据实际负载调整资源分配

  6. 使用 Service Mesh:如 Istio,提供更精细的流量控制和监控

九、总结与展望

通过容器化和 Kubernetes,我们不仅解决了传统部署的诸多痛点,更构建了一个弹性、可靠、可扩展的应用平台,为业务的快速迭代和持续创新提供了坚实基础。

相关推荐
森林猿3 小时前
docker-compose-kafka 4.1.0
docker·容器·kafka
Gss7774 小时前
Docker 容器核心知识总结
docker·容器
罗技1234 小时前
Docker 启动 Easysearch 时自定义初始密码的几种方式
运维·docker·容器
码路工人5 小时前
附录B:kubectl 命令速查表 - Kubernetes 集群管理必备指南
docker·云原生·容器
码路工人6 小时前
附录A:常用 Docker 命令速查表
docker·云原生·容器
zcz16071278217 小时前
Docker Compose 搭建 LNMP 环境并部署 WordPress 论坛
android·adb·docker
java之迷14 小时前
Windows环境下,源码启动+本地部署和启动开源项目Ragflow失败SRE模块
windows·docker·开源
致宏Rex16 小时前
Docker 实战教程(7) | 镜像管理和仓库操作
运维·docker·容器
罗技12316 小时前
不用每次都改 `easysearch.yml` 也能改启动参数 —— 用 Docker 环境变量搞定一切
docker·容器·eureka