MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Spring Cloud 与 Docker 容器化部署

2024-11-145.9k 阅读

Spring Cloud 基础

Spring Cloud 简介

Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。Spring Cloud 并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

服务治理 - Eureka

在微服务架构中,服务治理是非常关键的一环。Eureka 是 Spring Cloud Netflix 中的重要组件,用于实现服务的注册与发现。

Eureka 服务端搭建

首先创建一个 Spring Boot 项目,在 pom.xml 文件中添加 Eureka Server 依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

接着在 application.yml 配置文件中进行如下配置:

server:
  port: 8761
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

然后在启动类上添加 @EnableEurekaServer 注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

这样,一个简单的 Eureka 服务端就搭建好了。启动项目后,访问 http://localhost:8761/ 可以看到 Eureka 的管理界面。

Eureka 客户端注册

对于需要注册到 Eureka 服务端的微服务,同样在 pom.xml 中添加 Eureka Client 依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yml 中配置 Eureka 服务端地址:

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

在启动类上添加 @EnableEurekaClient 注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }
}

启动客户端项目后,在 Eureka 管理界面可以看到该服务已注册。

配置中心 - Spring Cloud Config

在微服务架构中,配置管理是一个复杂的问题。Spring Cloud Config 为微服务架构中的微服务提供了集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。

搭建 Spring Cloud Config Server

创建一个 Spring Boot 项目,在 pom.xml 中添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
</dependency>

application.yml 中配置:

server:
  port: 8888
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/yourusername/config-repo # 配置文件存储的 Git 仓库地址
          search-paths: config-repo
      label: master

在启动类上添加 @EnableConfigServer 注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

客户端使用 Spring Cloud Config

在客户端项目的 pom.xml 中添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
</dependency>

bootstrap.yml 中配置:

spring:
  application:
    name: your-service-name
  cloud:
    config:
      uri: http://localhost:8888
      fail-fast: true
      label: master

这样,客户端就能从配置中心获取配置信息了。例如,可以在 Git 仓库的配置文件中定义如下内容:

your.property=value

在客户端代码中通过 @Value 注解获取配置值:

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConfigController {
    @Value("${your.property}")
    private String propertyValue;

    @GetMapping("/config")
    public String getConfig() {
        return propertyValue;
    }
}

负载均衡 - Ribbon

Ribbon 是一个客户端负载均衡器,它可以在客户端实现负载均衡策略。当一个微服务有多个实例时,Ribbon 可以根据配置的负载均衡策略选择合适的实例进行调用。

在使用 Eureka 的项目中,Ribbon 已经集成在 spring-cloud-starter-netflix-eureka-client 依赖中。例如,有一个服务调用的接口:

import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "service-name")
@RibbonClient(name = "service-name")
public interface ServiceClient {
    @GetMapping("/api")
    String callService();
}

Ribbon 支持多种负载均衡策略,如轮询(RoundRobinRule)、随机(RandomRule)、权重(WeightedResponseTimeRule)等。可以通过配置文件修改负载均衡策略,例如:

service-name:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

服务调用 - Feign

Feign 是一个声明式的 Web 服务客户端,它使得编写 Web 服务客户端变得更加简单。只需要创建一个接口并使用注解来配置它既可完成对 Web 服务的绑定。

引入 Feign 依赖

pom.xml 中添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

创建 Feign 客户端接口

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(name = "service-name")
public interface ServiceClient {
    @GetMapping("/api")
    String callService();
}

使用 Feign 客户端

在服务调用的业务类中注入 Feign 客户端接口并调用方法:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CallController {
    @Autowired
    private ServiceClient serviceClient;

    @GetMapping("/call")
    public String callService() {
        return serviceClient.callService();
    }
}

同时,在启动类上添加 @EnableFeignClients 注解开启 Feign 功能:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

断路器 - Hystrix

在微服务架构中,服务之间的调用可能会因为各种原因失败,如网络故障、服务过载等。Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,通过添加断路器机制来防止级联故障,提高系统的弹性。

引入 Hystrix 依赖

pom.xml 中添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

使用 Hystrix

在需要使用断路器的方法所在的类上添加 @DefaultProperties 注解,并配置默认的 fallback 方法:

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;

@Service
@DefaultProperties(defaultFallback = "defaultFallback")
public class ServiceWithHystrix {
    @HystrixCommand
    public String callService() {
        // 模拟服务调用
        return "Success";
    }

    public String defaultFallback() {
        return "Service is unavailable";
    }
}

在启动类上添加 @EnableHystrix 注解开启 Hystrix 功能:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;

@SpringBootApplication
@EnableHystrix
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Docker 基础

Docker 简介

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。Docker 的理念是构建一次,到处运行(Build once, run anywhere),这极大地简化了应用的部署过程。

Docker 镜像与容器

Docker 镜像

镜像是 Docker 运行容器的基础,它是一个只读的模板。可以把镜像看作是一个软件的安装包,包含了运行该软件所需的所有文件、配置和依赖。例如,官方的 ubuntu 镜像就包含了一个完整的 Ubuntu 操作系统环境。可以通过 docker pull 命令从 Docker 镜像仓库中下载镜像,如:

docker pull ubuntu:latest

这里 ubuntu 是镜像名称,latest 是标签,表示最新版本。

Docker 容器

容器是镜像的运行实例,它是一个可读写的环境。可以将容器看作是正在运行的软件。通过 docker run 命令可以基于镜像启动容器,例如:

docker run -it ubuntu:latest bash

上述命令基于 ubuntu:latest 镜像启动一个交互式的容器,并进入容器的 bash 命令行环境。其中 -i 表示以交互模式运行容器, -t 表示为容器分配一个伪终端。

Dockerfile

Dockerfile 是一个文本文件,包含了一条条的指令,每一条指令构建一层,基于基础镜像,最终构建出一个新的镜像。以下是一个简单的基于 openjdk:8-jdk-alpine 镜像构建 Spring Boot 应用镜像的 Dockerfile 示例:

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

上述 Dockerfile 解释如下:

  1. FROM openjdk:8-jdk-alpine:指定基础镜像为 openjdk:8-jdk-alpine,这是一个轻量级的 OpenJDK 8 运行环境。
  2. VOLUME /tmp:创建一个临时文件挂载点,用于容器内的临时文件存储。
  3. ARG JAR_FILE:定义一个构建参数 JAR_FILE,用于指定要复制到容器中的 Spring Boot 应用 JAR 文件。
  4. COPY ${JAR_FILE} app.jar:将本地的 JAR 文件复制到容器中并命名为 app.jar
  5. ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]:指定容器启动时执行的命令,这里是运行 Spring Boot 应用。

Docker 仓库

Docker 仓库用于存储 Docker 镜像。Docker Hub 是官方的公共镜像仓库,开发者可以在上面找到大量的基础镜像和官方镜像。同时,企业也可以搭建自己的私有镜像仓库,如使用 Harbor 搭建企业级的 Docker 镜像仓库。

推送镜像到 Docker Hub

首先需要在 Docker Hub 上注册账号。然后使用 docker login 命令登录:

docker login

登录成功后,给镜像打标签,标签格式为 username/repository:tag,例如:

docker tag myapp:latest yourusername/myapp:latest

最后使用 docker push 命令推送镜像:

docker push yourusername/myapp:latest

使用私有仓库

如果使用 Harbor 搭建了私有仓库,首先修改 Docker 配置文件(通常在 /etc/docker/daemon.json),添加私有仓库地址:

{
    "insecure-registries": ["your-private-registry-url"]
}

重启 Docker 服务后,就可以使用私有仓库了。同样先登录私有仓库:

docker login your-private-registry-url

然后给镜像打标签并推送:

docker tag myapp:latest your-private-registry-url/yournamespace/myapp:latest
docker push your-private-registry-url/yournamespace/myapp:latest

Spring Cloud 与 Docker 容器化部署

Spring Cloud 微服务容器化

构建 Spring Cloud 微服务镜像

以一个简单的 Spring Cloud Eureka 服务端为例,首先确保项目已经打包成 JAR 文件。然后创建 Dockerfile,内容如下:

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=eureka-server-1.0.0.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

在包含 Dockerfile 的目录下执行构建命令:

docker build -t eureka-server:1.0.0.

这里 -t 用于指定镜像的标签,最后的 . 表示当前目录。

构建 Spring Cloud 配置中心镜像

对于 Spring Cloud Config Server,同样创建 Dockerfile:

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=config-server-1.0.0.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

执行构建命令:

docker build -t config-server:1.0.0.

其他 Spring Cloud 微服务如 Feign 客户端、Hystrix 服务等都可以按照类似的方式构建镜像。

使用 Docker Compose 进行多容器编排

在微服务架构中,通常有多个微服务协同工作。Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具,通过一个 docker-compose.yml 文件就可以定义多个服务,并进行统一的启动、停止和管理。

以下是一个包含 Eureka 服务端、Config 服务端和一个简单微服务的 docker-compose.yml 示例:

version: '3'
services:
  eureka-server:
    image: eureka-server:1.0.0
    ports:
      - 8761:8761
  config-server:
    image: config-server:1.0.0
    ports:
      - 8888:8888
  my-service:
    image: my-service:1.0.0
    ports:
      - 9000:9000
    depends_on:
      - eureka-server
      - config-server

上述 docker-compose.yml 文件定义了三个服务:

  1. eureka-server:使用 eureka-server:1.0.0 镜像,将容器的 8761 端口映射到宿主机的 8761 端口。
  2. config-server:使用 config-server:1.0.0 镜像,将容器的 8888 端口映射到宿主机的 8888 端口。
  3. my-service:使用 my-service:1.0.0 镜像,将容器的 9000 端口映射到宿主机的 9000 端口,并依赖 eureka-serverconfig-server 服务,确保这两个服务先启动。

在包含 docker-compose.yml 文件的目录下执行以下命令启动服务:

docker-compose up -d

-d 参数表示以守护进程模式运行,容器会在后台运行。

容器化部署中的网络与服务发现

在容器化部署中,容器之间需要进行通信。Docker 提供了多种网络模式,如 bridgehostnone 等。在使用 Docker Compose 时,默认会创建一个桥接网络,容器之间可以通过服务名进行通信。

例如,在上述 docker-compose.yml 示例中,my-service 容器可以通过 eureka-serverconfig-server 服务名来访问对应的服务,而不需要使用具体的 IP 地址。这对于动态分配 IP 的容器环境非常方便。

同时,结合 Spring Cloud Eureka 的服务发现机制,容器化的微服务在启动后会注册到 Eureka 服务端。这样,其他微服务可以通过 Eureka 进行服务发现,而不是直接通过容器的 IP 地址进行调用,进一步提高了系统的灵活性和可维护性。

容器化部署的持续集成与持续交付(CI/CD)

在实际开发中,持续集成与持续交付是非常重要的环节。以 GitHub Actions 为例,可以创建一个 .github/workflows/build-and-deploy.yml 文件来实现 Spring Cloud 微服务的容器化构建与部署。

以下是一个简单的示例:

name: Build and Deploy
on:
  push:
    branches:
      - master
jobs:
  build-and-push:
    runs-on: ubuntu - latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Set up JDK 11
        uses: actions/setup-java@v2
        with:
          java-version: '11'
      - name: Build with Maven
        run: mvn clean package -DskipTests
      - name: Login to DockerHub
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}
      - name: Build and push Docker image
        uses: docker/build-push-action@v2
        with:
          context:.
          push: true
          tags: yourusername/your-service:${{ github.sha }}

上述工作流在每次 master 分支有推送时触发。它首先检出代码,设置 JDK 11,使用 Maven 进行打包,然后登录 Docker Hub,最后构建并推送 Docker 镜像到 Docker Hub。

对于部署,可以结合 Kubernetes 等容器编排工具,在目标环境中拉取最新镜像并进行部署,实现持续交付的流程。

容器化部署的监控与日志管理

在容器化部署的 Spring Cloud 微服务系统中,监控和日志管理是保障系统稳定运行的关键。

监控

可以使用 Prometheus 和 Grafana 进行监控。Prometheus 是一个开源的系统监控和报警工具包,它可以收集容器化微服务的各种指标数据,如 CPU 使用率、内存使用率、请求响应时间等。

首先在微服务项目中添加 Prometheus 相关依赖,例如对于 Spring Boot 项目:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

然后配置 Prometheus 服务器来抓取微服务的指标数据。在 prometheus.yml 配置文件中添加如下内容:

scrape_configs:
  - job_name:'spring - cloud - microservices'
    static_configs:
      - targets: ['my - service:9000'] # 替换为实际微服务地址

启动 Prometheus 后,就可以在其界面查看微服务的指标数据。

Grafana 是一个可视化工具,可以与 Prometheus 集成,通过创建仪表盘(Dashboard)来直观展示监控数据。在 Grafana 中添加 Prometheus 数据源,然后导入合适的仪表盘模板,就可以实现对微服务的可视化监控。

日志管理

对于容器化微服务的日志管理,可以使用 ELK 堆栈(Elasticsearch、Logstash、Kibana)或 EFK 堆栈(Elasticsearch、Filebeat、Kibana)。

以 EFK 为例,Filebeat 是一个轻量级的日志采集器,可以部署在每个容器所在的宿主机上,负责收集容器的日志文件。它将收集到的日志发送到 Elasticsearch 进行存储。Elasticsearch 是一个分布式搜索引擎,用于高效存储和检索日志数据。Kibana 则是一个可视化工具,用于在 Elasticsearch 中查询和展示日志数据。

首先在宿主机上安装并配置 Filebeat,例如在 filebeat.yml 中配置:

filebeat.inputs:
  - type: container
    paths:
      - /var/lib/docker/containers/*/*.log
output.elasticsearch:
  hosts: ['elasticsearch:9200']

启动 Filebeat 后,它会将容器日志发送到 Elasticsearch。然后启动 Elasticsearch 和 Kibana,在 Kibana 中创建索引模式并进行日志查询和可视化展示。

通过监控和日志管理,可以及时发现微服务系统中的问题,如性能瓶颈、错误异常等,从而保障系统的稳定运行。

在实际应用中,Spring Cloud 与 Docker 的结合需要根据具体的业务场景和需求进行优化和调整,不断完善系统的架构和部署方案,以实现高效、稳定的微服务架构应用。同时,随着技术的不断发展,新的工具和方法也会不断涌现,需要持续学习和跟进,以保持系统的先进性和竞争力。