【技术解决方案】企业如何从SpringBoot应用平滑迁移到云原生K8s平台

好久不见了,小伙伴们,你们最近还好吗?有没有想我,反正我是想你们了~

因为有好多小伙伴是因为我从开发转到Devops、云计算的,我已经看到你们后台的留言,没回是我的错,最近有些私人的事情。

今天得以有时间,写篇文章划划水,我看好多身边的小伙伴们粉丝都突破几十万了,这也得以大家一直坚持输出技术文章得以回报吧。近期,大环境不好,我看很多朋友都转型了,转产品、运营、cto、创业,hhh,希望你们越来越好。好了,煽情到此结束,开篇~

从SpringBoot单体架构到微服务架构的进阶,引入了Spring Cloud框架(奈飞、阿里、腾讯、百度等),Spring Cloud提供的治理措施为了我们解决诸多麻烦(注册、发现、配置、熔断、限流、事务、RPC等),但是代码的侵入性也让我们在后续的架构迭代出现了很多问题,随着SpringBoot的升级,我们不得不考虑相关组件的兼容性问题,你说我们的架构师和运维同学们是不是要双手挠头,头发掉光光。

从云原生步入正轨,我就立刻写了和云原生相关的诸多文章,其中提到的问题,也不断的在各个企业中出现。近年来,Kubernetes已经成为了pass平台的标准。伴随而来的还有容器化、服务网格、无服务等。

这里我提一句和本文不太相关的事,Amazon的流媒体平台Prime Video在2023年3月22日发布了一篇技术博客《规模化Prime Video的音视频监控服务,成本降低90%》,副标题:“从分布式微服务架构到单体应用程序的转变有助于实现更高的规模、弹性和降低成本”。
/
说实话,对我触动还挺大的,我也在思考,是不是微服务被人们玩坏了,从技术解决方案,慢慢变成了技术人员满足自己技术欲的一种炫技手段。
/
这就像我以前文章提到的一件事:Istio刚步入市场,我朋友的老板就让他把公司spring cloud架构向服务网格迁移,细思极恐,好变态。
/
得了,这个话题留给大家去思考吧。

完了,思路断了,那就直接以Spring Cloud Alibaba作为切入点开始划分域写了:

在K8S上部署Spring Cloud Alibaba

简要:直接在K8s上部署Nacos、Sentinel等组件,然后将应用发布到K8s上,这里需注意不能将Nacos服务部署在K8s外,因为集群外的IP空间和K8s内的IP空间会出现通信问题,这也是很多小白会犯的错误。

在Kubernetes上部署Spring Cloud Alibaba应用程序,需要遵循以下步骤:

  1. 在本地构建应用程序并将其打包成docker镜像。
  2. 将这个Docker镜像上传到Docker Hub或其他Docker仓库中。
  3. 编写Kubernetes的deployment和service配置文件,用于定义容器的运行和暴露服务的方式。可以使用YAML格式来编写这些配置文件。
  4. 部署这些配置文件到Kubernetes集群中,以便启动应用程序并将其暴露为可访问的网络服务。

在部署Spring Cloud Alibaba应用程序时,还可以使用Helm Chart来简化部署过程。Helm Chart是一个Kubernetes应用程序的打包和发布工具,可以自动化部署和管理Kubernetes应用程序的生命周期。

在部署Spring Cloud Alibaba应用程序时,还需要考虑一些特定的配置:

  1. 配置Nacos注册中心:Nacos是Spring Cloud Alibaba中使用的服务注册和发现组件。在Kubernetes上部署应用程序时,可以将Nacos作为一个独立的容器部署,并通过环境变量或配置文件指定应用程序连接到该注册中心。
  2. 配置Config Server:Config Server是Spring Cloud Alibaba中使用的配置中心组件。在Kubernetes上部署应用程序时,可以将Config Server作为一个独立的容器部署,并通过环境变量或配置文件指定应用程序连接到该配置中心。
  3. 配置服务网关:服务网关是Spring Cloud Alibaba中使用的API网关组件。在Kubernetes上部署应用程序时,可以将服务网关作为一个独立的容器部署,并通过环境变量或配置文件指定应用程序连接到该网关。
  4. 配置日志和监控:为了方便管理和监控应用程序,可以将日志和监控系统集成到Kubernetes集群中。例如,可以使用Prometheus和Grafana来收集和可视化应用程序的性能数据和指标。

注意事项:

  1. 网络连接:在Kubernetes集群中,容器之间可以通过网络进行通信。因此,在部署应用程序时,要确保应用程序能够正确地访问其他组件(例如Nacos注册中心、Config Server等)。
  2. 资源管理:在Kubernetes中,可以为每个容器分配CPU和内存等资源。要确保为各个容器分配适当的资源,以避免资源不足或浪费等问题。
  3. 部署策略:Kubernetes提供了多种部署策略,例如rolling update、blue-green deployment等。要选择适当的部署策略来实现高可用性和零停机升级等需求。
  4. 安全性:Kubernetes是一个强大的平台,但也需要考虑安全性方面的问题。例如,要确保应用程序可以受到有效的防火墙保护,以避免网络攻击和未经授权的访问。

在Kubernetes上部署Spring Cloud Kubernetes

说实话,这个框架我个人觉得很鸡肋,我觉得Spring Cloud官方是存在危机感了,赶紧搞点和K8s贴边的,但是你看看有谁在用这个框架,你在用么,hhh。

Spring Cloud K8S包括了DiscoveryClient和Config两大模块,由于K8S本身支持Service概念,所以可以直接借助K8S平台本身基于Service的服务注册发现机制,将代码中的Spring Cloud K8S Discovery Client完全移除。如此在本地开发环境通过应用URL进行相互调用,在线上K8S环境同样通过URL(对应K8S Service)进行调用,二者都是通过直接指定URL进行调用(行为一致),在线上K8S环境通过指定Service URL(如:http://serviceName.namespace:8080),底层的服务注册发现、服务转发等均有K8S平台来完成,代码端从此不再需要关注服务注册发现、负载均衡(由K8S底层代理模式决定)等问题。
此种方式下,代码端仅依赖Spring Cloud K8S Config模块,本地开发和线上环境均通过URL进行服务间相互调用。

如此看来,Spring Cloud Kubernetes就变成了Spring Cloud Config,但是,这不是还是具有很强的代码侵入么,而且又做不了熔断 限流 事务。那怎么办嘛,后面说。。。

在Kubernetes上部署Spring Boot应用

继上面的问题,如果我们把Spring Cloud K8S Config也移除掉,是不是就不依托Spring Cloud了~

SpringBoot自身提供了自定义config/目录的方案,config/目录需位于执行java启动命令的工作目录下,且config/目录内的配置优先级高于jar内配置的优先级,此种方式不需要依赖独立的配置中心。

而在K8S平台中ConfigMap本身就是用于管理配置的,并支持挂载到容器中的指定目录,ConfigMap中的内容发生改变时,也会近实时地更新容器内挂载的文件内容。如此在K8S环境中通过ConfigMap定义线上环境的配置,然后通过挂载到应用运行容器的config/目录下,即可达到K8S平台上的外部配置ConfigMap替换jar包内配置的效果。此种方式充分利用了SpringBoot自身的外部配置特性,对应用代码没有任何改动,也无需集成配置中心组件(如Spring Cloud K8S Config、Spring Cloud Alibaba Config等),而在线上利用K8S ConfigMap挂载到容器目录的方式完成了外部配置的替换。

下面我会举一个ConfigMap配置案例来说明如何创建和使用ConfigMap对象。

假设您的Spring Boot应用程序需要以下配置信息:

数据库连接URL:jdbc:mysql://localhost:3306/mydb
数据库用户名:myuser
数据库密码:mypassword

您可以将这些配置信息保存到一个YAML文件中,例如:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb
    username: myuser
    password: mypassword

然后,您可以使用以下命令将该YAML文件转换为一个ConfigMap对象:

kubectl create configmap myapp-config --from-file=application.yml

其中,myapp-config是ConfigMap对象的名称,application.yml是包含配置信息的YAML文件。

接下来,您可以在Deployment YAML文件中引用该ConfigMap对象,并将其挂载到容器中。例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp-container
        image: myapp:latest
        envFrom:
        - configMapRef:
            name: myapp-config
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: config-volume
          mountPath: /config
      volumes:
      - name: config-volume
        configMap:
          name: myapp-config

在这个例子中,我们定义了一个名为myapp-config的ConfigMap对象,并在Deployment YAML文件中引用它。我们还将该ConfigMap对象作为环境变量引入到应用程序容器中,并将其挂载到一个名为config-volume的卷中。

通过这样的配置,您就可以使用Kubernetes的ConfigMap对象来管理Spring Boot应用程序的配置信息,并且在部署时可以轻松地修改和更新这些配置信息。

使用config/目录定义外部配置文件这种方式,在config/目录下的配置文件发生改变时,默认应用端是不支持动态刷新的。若想支持外部配置的动态刷新,可通过集成spring-cloud-context组件,该组件结合spring-boot-starter-actuator组件,在外部配文件发生变更后,可手动调用POST /actuator/refresh端点触发配置的动态刷新,此方式会对Spring Environment、标记@ConfigurationProperties的属性类及标记@RefreshScope(包含@Value属性)的类进行动态更新,从而达到刷新配置的效果。若想支持在外部配置发生变更时自动刷新应用配置,可通过监听config/目录下的配置文件是否发生变更,若发生变更则调用Spring Cloud Context的配置刷新方法ContextRefresher.refresh()以刷新应用的配置。此种集成Spring Cloud Context及创建config/目录以支持配置动态刷新完全是Spring框架自身的特性,仅依赖文件系统,不依赖K8S平台,即便后续切换到不同PasS平台也完全没有关系。监听配置目录及触发配置变更的核心代码如下:

/**
 * 配置监听属性
 */
@Data
@ConfigurationProperties(prefix = ConfigMonitorProps.PREFIX)
public class ConfigMonitorProps {
    
    

    /**
     * 配置前缀
     */
    public static final String PREFIX = "osmium.config.monitor";

    /**
     * 是否启用配置监听
     */
    private Boolean enabled = true;

    /**
     * 外部配置文件所在的文件夹<br/>
     * 注:推荐将配置文件统一放到单独的config文件下,且位于运行java命令的工作目录下,如此Spring会默认读取config文件夹下的配置文件,且config文件下的配置优先级高于jar包内(classpath)配置文件
     */
    private String dirPath;
}

---

/**
 * 配置监听、通知服务
 *
 */
public class WatchNotifyService {
    
    

    private static final Logger log = LoggerFactory.getLogger(WatchNotifyService.class);

    private ConfigMonitorProps configMonitorProps;

    private ContextRefresher contextRefresher;

    public WatchNotifyService(ConfigMonitorProps configMonitorProps, ContextRefresher contextRefresher) {
    
    
        this.configMonitorProps = configMonitorProps;
        this.contextRefresher = contextRefresher;
    }

    @PostConstruct
    void initStartWatch() {
    
    
        new Thread(() -> {
    
    
            startWatch();
        }, "CONFIG-WATCHING-THREAD").start();
    }


    /**
     * 启动配置文件夹监听
     */
    public void startWatch() {
    
    
        try {
    
    
            if (!StringUtils.hasText(this.configMonitorProps.getDirPath())) {
    
    
                log.warn("Stop Config Directory Watching because the dir-path is blank!");
                return;
            }

            //监听目录
            Path path = Paths.get(this.configMonitorProps.getDirPath());
            if (!Files.isDirectory(path)) {
    
    
                log.warn("Stop Config Directory Watching because the dir-path: {} is not a directory!", this.configMonitorProps.getDirPath());
                return;
            }

            log.info("Start Config Directory Watching in dir: '{}'", this.configMonitorProps.getDirPath());
            //创建一个文件系统的监听服务
            WatchService watchService = FileSystems.getDefault().newWatchService();

            //为该文件夹注册监听,监听新建、修改、删除事件。只能为文件夹(目录)注册监听,不能为单个文件注册监听
            path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);

            //编写事件处理
            while (true) {
    
    
                //拉取一个WatchKey。当触发监听的事件时,就会产生一个WatchKey,此WatchKey封装了事件信息。
                WatchKey watchKey = watchService.take();

                //使用循环是因为这一个WatchKey中可能有多个文件变化了,比如Ctrl+A全选,然后删除,只触发了一个WatchKey,但有多个文件变化了
                for (WatchEvent event : watchKey.pollEvents()) {
    
    
                    log.info("WATCHING {}/{} - {}", this.configMonitorProps.getDirPath(), event.context(), event.kind());
                }

                //刷新配置
                this.refreshConfig();

                //虽然是while()循环,但WatchKey和ByteBuffer一样,使用完要重置状态,才能继续用。
                //如果不重置,WatchKey使用一次过后就不能再使用,即只能监听到一次文件变化。
                watchKey.reset();
            }
        } catch (Throwable ex) {
    
    
            log.error("Config Directory Watching Exception!", ex);
        }
    }

    /**
     * 刷新配置
     *
     * @return 被刷新的属性名
     */
    public Set<String> refreshConfig() {
    
    
        log.info("START REFRESH CONTEXT CONFIG");
        //更新Spring Cloud配置
        Set<String> refreshKeys = this.contextRefresher.refresh();
        return refreshKeys;
    }
}

综上,在代码中我们仅仅只是搭建了一个SpringBoot应用,无需依赖各种Spring Cloud实现方案,应用间的相互调用使用URL,而外部配置的替换遵循SpringBoot自带的config/目录形式,而在线上K8S环境则通过K8S Service支持应用服务的注册发现,通过K8S ConfigMap挂载config/目录的形式支持外部配置的替换。此种方式也是笔者推崇的Spring应用向K8S迁移的方式,此种方式使得代码端更清爽,不必依赖繁重的Spring Cloud框架,也不用考虑后续Spring Cloud框架的升级,开发人员可以更专注于业务功能的实现,而微服务架构相关的服务注册发现、配置管理都可以交由K8S平台完成,做到了代码端即不依赖微服务框架(Spring Cloud)、也不依赖于线上K8S环境,而线上K8S平台完全以非侵入的方式来完成微服务框架的工作。此种方式中,K8S平台可以很好的解决服务注册发现、服务负载均衡、外部配置管理的问题,但如果有分布式事务、熔断、限流、服务追踪等需求,还是要在代码层面解决,目前K8S平台暂不提供相关功能。

此种方式中,我们依赖K8S平台做了很多功能,可能有的人会顾虑架构的实现过多依赖于K8S,强绑定到了K8S平台。K8S作为目前云原生时代的PasS平台事实上的标准,即便后续出现了替代品,那想必能替代K8S的必定强于K8S,K8S能做到的它应该都能做到甚至做的更多、更好。并且此种方式中我们并没有像Spring Cloud K8S强耦合K8S平台专有的API,而是已非侵入代码的方式、URL、文件目录等形式,保证了代码端可以在完全不依赖K8S平台的环境下运行,做到了没有强绑定K8S平台,但是K8S平台又能为我们的应用架构锦上添花,即便后续切换到了不同PaaS平台,也可以利用相似的机制保证应用的正常运行。

附操作步骤详解:

  1. 创建Docker镜像:您可以使用Dockerfile定义一个可重复构建的镜像。其中包括设置环境变量、安装依赖项和运行应用程序等步骤。例如,以下是一个基于openjdk:8-jdk-alpine的简单示例Dockerfile:

    FROM openjdk:8-jdk-alpine
    VOLUME /tmp
    ARG JAR_FILE=target/*.jar
    COPY ${
          
          JAR_FILE} app.jar
    ENTRYPOINT ["java","-jar","/app.jar"]
    

    在这个例子中,我们从openjdk:8-jdk-alpine基础镜像开始,并将其命名为“app”,然后将项目的可执行jar文件复制到容器中并设置它为默认入口点。

  2. 将Docker镜像推送到容器注册表:一旦您创建了Docker镜像,您需要将它们推送到一个容器注册表中,以便在Kubernetes集群中部署它们。常用的容器注册表包括Docker Hub、Google Container Registry(GCR)和Amazon Elastic Container Registry(ECR)。您可以使用docker push命令将镜像推送到注册表中。

  3. 创建Kubernetes Deployment对象:Deployment对象是一个Kubernetes资源对象,它描述了要在集群中运行的Pod和它们的副本数等信息。在编写Deployment对象时,您需要指定以下内容:

    1. 要部署的Docker镜像名称和版本。
    2. Pod的副本数目。
    3. 应用程序容器的资源限制和请求,例如CPU和内存。
    4. 应用程序容器的环境变量和命令行参数等。

    以下是一个示例Deployment YAML文件:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-app-deployment
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: my-app
      template:
        metadata:
          labels:
            app: my-app
        spec:
          containers:
          - name: my-app-container
            image: myregistry/my-app:v1.0.0
            resources:
              limits:
                cpu: "1"
                memory: 512Mi
              requests:
                cpu: "0.5"
                memory: 256Mi
            env:
            - name: DB_URL
              value: jdbc:mysql://db-server:3306/mydb
    

    在这个例子中,我们定义了一个名为my-app-deployment的Deployment对象,它指定了要部署的应用程序容器的镜像名称和版本、Pod的副本数、资源限制和请求,以及DB_URL环境变量。

  4. 创建Kubernetes Service对象:Service对象是另一个Kubernetes资源对象,它公开了Deployment对象中运行的Pod的网络端点。在编写Service对象时,您需要指定以下内容:
    Service的类型(ClusterIP、NodePort或LoadBalancer)。

    1. 要暴露的端口号和协议。
    2. 将服务映射到后端Pod的标签选择器。

    以下是一个示例Service YAML文件:

    apiVersion: v1
    kind: Service
    metadata:
      name: my-app-service
    spec:
      type: ClusterIP
      selector:
        app: my-app
      ports:
      - name: http
        port: 8080
        protocol: TCP
        targetPort: 8080
    

    在这个例子中,我们定义了一个名为my-app-service的Service对象,它将端口号8080映射到后端Pod的端口8080,并通过标签选择器将服务路由到匹配app=my-app的Pod。

  5. 部署应用程序:最后,您可以使用kubectl命令行工具将Deployment和Service对象部署到Kubernetes集群中。您可以使用以下命令创建Deployment和Service对象:

    kubectl apply -f deployment.yaml
    kubectl apply -f service.yaml
    

    这条命令将读取deployment.yaml和service.yaml文件并将它们应用到您的Kubernetes集群中。一旦部署成功,您可以使用kubectl命令行工具查看Pod、Deployment和Service对象的状态:

    kubectl get pods
    kubectl get deployments
    kubectl get services
    

    这些命令将返回当前在Kubernetes集群中运行的所有Pod、Deployment和Service对象的状态信息。

  6. 注意事项

    1. 如果您的Spring Boot应用程序依赖于其他服务或数据库,您需要确保这些服务和数据库也在Kubernetes集群中运行,并且已经设置好了相应的环境变量和端口映射。
    2. 在创建Deployment和Service对象之前,您需要先连接到Kubernetes集群并获得足够的权限。
    3. 如有必要,您可以使用Kubernetes的配置管理工具(如ConfigMap和Secret)来管理您的应用程序的配置信息,这些配置信息可以在容器启动时自动加载。
    4. 如果您遇到任何问题,在调试阶段可以使用kubectl logs命令来查看Pod的日志输出,以确定应用程序是否成功启动。
  7. 最佳实践

    1. 存储管理:在部署Spring Boot应用程序时,通常需要将应用程序的数据持久化存储到共享存储中。Kubernetes支持多种存储插件,包括本地存储、云存储和网络存储等。您可以使用PersistentVolumeClaim(PVC)来声明一个动态或静态的存储卷,并将其挂载到Pod中。
    2. 自动扩展:在高负载场景下,您可能需要自动扩展Pod的副本数以满足用户需求。Kubernetes支持水平自动扩展(HPA),您可以定义一个HPA对象并根据CPU利用率或内存利用率自动扩展Pod的副本数。
    3. 发布策略:在应用程序更新时,您可能需要控制发布的版本数量和速度。Kubernetes支持多种发布策略,包括滚动升级、蓝绿部署和金丝雀发布等。您可以根据自己的需求选择合适的发布策略。
    4. 监控和日志记录:在生产环境中,您需要对应用程序进行监控和日志记录,以便及时发现和解决问题。Kubernetes提供了多种监控和日志记录工具,包括Prometheus、Grafana和EFK Stack等。您可以将这些工具集成到Kubernetes集群中,并为每个Pod设置标准的日志记录输出格式。
    5. 安全:在部署Spring Boot应用程序时,您需要考虑安全问题,包括网络安全、数据安全和身份验证等。Kubernetes提供了多种安全机制,包括网络策略、Pod安全上下文和服务账户等。您可以使用这些机制来保护您的应用程序和集群中的敏感数据。
    6. 总之,将Spring Boot应用程序部署到Kubernetes集群中需要一定的技术和经验,但是它能够提高应用程序的可伸缩性、容错性和弹性。如果您想深入学习Kubernetes和Spring Boot,建议您阅读官方文档、参加在线课程或加入社区论坛,与其他开发者分享经验和资源。
  8. 常用的工具和技术

    1. Helm:Helm是一个Kubernetes的包管理器,它提供了简化部署、升级和管理Kubernetes应用程序的工具。Helm使用Chart文件来描述应用程序的依赖关系和配置信息,并可以自动安装和升级这些应用程序。
    2. Skaffold:Skaffold是一个Kubernetes的开发工具,它可以让您在本地构建、测试和部署应用程序,并将其同步到远程Kubernetes集群中。Skaffold支持多种构建工具和镜像仓库,并可以自动检测代码变更并重新构建和部署应用程序。
    3. Istio:Istio是一个服务网格框架,它提供了流量控制、故障恢复和监控等功能,可以帮助您更有效地管理和保护Kubernetes中的微服务。Istio使用Envoy代理来拦截网络请求,并可以为每个服务设置独立的安全策略和路由规则。
    4. Kustomize:Kustomize是Kubernetes的另一个配置管理工具,它允许您以声明性方式定义Kubernetes对象,并支持灵活的模板和覆盖机制。Kustomize还可以从多个文件或目录中组合和应用配置,并可以自动更新对象版本和标签。
    5. DevSpace:DevSpace是一个Kubernetes的开发工具,它可以让您在本地开发、测试和部署Kubernetes应用程序,同时支持多种语言和框架。DevSpace使用Helm Chart来描述应用程序的依赖关系和配置信息,并可以自动构建和部署应用程序。
    6. 总之,这些工具和技术可以帮助您更轻松地部署、管理和保护Spring Boot应用程序在Kubernetes集群中的运行。如果您想深入学习这些工具和技术,建议您阅读官方文档、参加在线课程或尝试使用示例应用程序进行实践。

方案对比分析

请添加图片描述

综合考量,以上3种方式中:

  1. 最推荐方式3:在K8S上部署SpringBoot应用,由K8S平台负责服务注册发现、服务间负载均衡、外部配置管理
  2. 坚决不推荐方式2:在K8S上部署Spring Cloud K8S,不要给自己找麻烦,夹在Spring Cloud和K8S之间进退两难,建议直接切换到方式3:在K8S上部署SpringBoot应用
  3. 若已采用了方式1:在K8S上部署Spring Cloud Alibaba,同样建议切换到方式3:在K8S上部署SpringBoot应用,做减法远比做加法更容易,切换后会发现负担更小了,神清气爽…

拥抱Service Mesh

K8S平台能帮我们以不侵入代码的方式解决微服务架构下的服务注册发现、服务负载均衡、外部配置管理的问题,但是微服务架构下还包括许多其他的需求,例如熔断、限流、服务追踪等,在K8S平台之上我们还可以选择另一种方式:服务网格(Istio)。

Service Mesh服务网格(如Istio) 可以简单理解为K8S容器管理平台之上的微服务管理平台,不侵入代码(跨编程语言),通过Sidecar(伴生容器)的形式将原本微服务框架中的基础功能(服务注册发现、服务路由、流量分发、熔断、限流、监控、安全等)提取到基础设施层,但额外引入的Sidecar会增加集群的资源损耗、请求延时等。

比如我之前经历过的一次架构决策,当时整个团队刚刚从Spring升级到SpringBoot生态,我们并没有选择Spring Cloud框架,而是直接跨过了Spring Cloud,选择了K8S + ServiceMesh Istio的架构方案。

之所以选择K8s + Istio,主要出于以下几个方面考虑:

Service Mesh不侵入代码,对我们的代码改动量最小(仅在需要支持分布式服务追踪功能时改造Http调用工具 - 透传追踪请求头)
Service Mesh - Istio可以满足我们对服务注册发现、服务多版本路由、Http接口级别的熔断、服务追踪、更细粒度的负载均衡策略等的基础需求
我们的开发团队不需要大规模修改之前的代码(无需纠结集成Spring Cloud框架及后续框架的升级),可以更多的关注核心业务功能的开发
Istio可以满足我们灰度发布的需求(如根据用户ID分发流量到不同版本的服务)
除了后端Java技术栈,我们还有前端NodeJs技术栈、Python技术栈,同样可以借助Istio实现跨开发语言的微服务架构的基础功能
拥有富有经验的运维和架构团队
整个迁移过程对开发团队几乎无感,由运维团队负责K8S的搭建与运维,然后由架构团队、运维团队协作完成Devops流水线编排、Istio服务编排规则的制定。整个迁移过程历时将近3个月,拥有极陡峭的学习曲线,对运维与架构团队也是提出了一定的挑战,好在最终顺利完成迁移工作。此次迁移过程可以理解为一次拥抱未来的尝试,虽并不是当下最稳妥的方式,但对于开发团队来说是成本相对较低的方式。

关于Devops

之前见过在代码中通过Maven插件做Docker镜像构建、K8S部署的,此种方式使得我们代码构建的生命周期和Devops功能相耦合,并且其对构建环境也是有特定要求的(如依赖docker命令、kubectl命令等),可能还会在代码库中暴露相关凭证信息(如Harbor账号密码、kubeconfig配置等),开发者很难在本地开发环境对其进行调试,且增加了维护成本。还有就是通过自定义sh脚本的方式,此种方式虽不与代码构建的生命周期相绑定,但同样存在对特定执行环境的要求、暴露相关凭证的风险,同时不具备行业共识性,同样的脚本换到新团队肯定是要重新进行测试、评估以确认其可行性的。可以参见之前的思路,我们已经尽可能将原来耦合在代码里的微服务架构的功能都转移到K8S Pass平台,那我们是否也可以将耦合在代码中的Devops功能也抽取到云原生时代所推崇的Devops平台呢?笔者是推荐将Devops相关的功能都交由专用的Devops平台来做的,例如使用Jenkins平台,所有的凭证(Docker凭证、K8s凭证、Gitlab凭证)都在Jenkins平台进行管理,可由Devops工程师在相应代码库中(或者独立的代码库中)建立单独的文件夹维护Jenkins流水线Pipeline脚本、Dockerfile、K8S部署脚本等,此种方式可以将我们的代码和Devops解耦,所有和Devops相关的流程、运行环境都交给标准的Devops平台来完成,如此也有助于在团队内部形成通用的Devops构建流程,达成共识,形成复用。

最佳实践:

  1. 环境准备:确定需要使用的工具、平台和基础设施,并配置相应的环境。
  2. 代码管理:使用版本控制系统(如 Git)管理代码库,确保团队成员能够共享和协作开发代码,并记录每个版本的更改。
  3. 自动化构建:使用自动化构建工具(如 Jenkins)来自动化构建、测试和部署软件。
  4. 测试与质量保证:使用自动化测试工具(如 Selenium)来进行功能测试、性能测试和安全测试,并保证软件质量符合要求。
  5. 部署与发布:使用自动化部署工具(如 Ansible)将软件部署到生产环境中,并确保无缝地发布新版本。
  6. 监控与日志管理:使用监控和日志管理工具(如 Nagios 和 ELK Stack)监视生产环境中的应用程序和基础设施,并快速解决问题。
  7. 反馈与优化:通过持续反馈和改进过程,优化 DevOps 流程和工具,以提高软件开发和运营的效率和质量。

这些步骤并不是顺序执行的,通常会进行交叉迭代。它们共同构成了一个完整的 DevOps
流程,能够帮助团队更快、更可靠地构建和交付高质量的软件产品。

Serverless最佳实践

  1. 函数设计:将函数设计成小而简单,以便它们能够更快地启动并响应请求。避免在函数中包含大量代码或与其他服务进行复杂的集成。
  2. 性能优化:使用适当的内存和CPU配置来优化函数性能,并使用异步调用和缓存技术来提高效率。
  3. 安全性:确保函数中没有敏感信息泄露,并使用AWS Identity and Access Management (IAM) 来管理对资源的访问控制。
  4. 监控日志:使用CloudWatch Metrics和Logs来收集和监视函数运行状况,并及时处理异常情况。
  5. 自动扩展:根据负载需求自动扩展函数以提高可用性,并使用AWS Lambda的别名功能和版本管理来管理部署的代码。
  6. 成本优化:使用Lambda按需计费模型来最小化成本,并使用云原生技术来优化资源利用率。
  7. 代码管理:使用版本控制系统来管理和维护函数代码,并使用自动化部署工具来简化部署过程。
  8. 使用AWS Lambda Layers:将共享代码和资源分离到AWS Lambda Layers中,以便可以跨多个函数重复使用。这样可以减少函数代码量,提高可重用性,并降低维护成本。
  9. 选择适当的事件源:在使用Serverless架构时,需要明确事件源,并针对不同的事件源选择最合适的服务。例如,Amazon S3触发器可以用于处理对象创建、删除或更新事件,而API网关可以用于处理RESTful API请求。
  10. 避免冷启动:Lambda函数会在第一次运行时进行冷启动,这可能会导致延迟。为了避免这种情况,可以使用预热机制来提前加载函数代码和资源,以确保在实际调用时可以立即响应请求。
  11. 实现故障转移和恢复:在使用Serverless架构时,必须考虑故障转移和恢复策略。为了确保业务连续性,在某些情况下,需要将数据备份到其他区域或存储库中。
  12. 进行压力测试:在部署Serverless应用程序之前,请进行足够的压力测试,并确定您的应用程序是否能够承受负载峰值。可以使用AWS Lambda压力测试工具来模拟真实负载并监视应用程序的性能。
  13. 使用适当的存储服务:在Serverless架构中,您可以使用多种AWS存储服务,例如Amazon DynamoDB、Amazon S3和Amazon RDS等。使用适当的存储服务可提高应用程序性能和可靠性。
  14. 优化网络连接:对于需要访问网络资源的Lambda函数,优化网络连接是至关重要的。可以使用VPC来确保Lambda函数能够安全地访问其他资源,也可以缓存请求以减少网络流量。
  15. 避免不必要的计算:避免执行不必要的计算操作,例如加载大型库或处理不必要的数据。这些操作可能会浪费计算资源并降低应用程序性能。
  16. 实现日志聚合和分析:为了更好地理解应用程序性能和行为,可以将Lambda函数日志聚合到一个单独的日志存储库中,并使用日志分析工具来监视应用程序行为。
  17. 自动化部署和测试:使用CI/CD工具链自动化部署和测试Serverless应用程序。这有助于确保代码质量和一致性,并减少部署错误的风险。
  18. 安全性最佳实践:除了控制IAM权限外,还可以使用AWS Web Application Firewall (WAF) 来过滤和阻止恶意WEB流量攻击。另外,可以使用AWS Shield和AWS Firewall Manager保护您的架构,这将为您的网络提供全面的DDoS防御。
  19. 遵循AWS最佳实践:AWS有许多最佳实践,这些最佳实践是基于AWS丰富的经验和客户反馈制定的。您可以参考AWS官方文档,以确保您的Serverless应用程序符合AWS建议的最佳实践。
  20. 使用标准化的API规范:使用标准化的API规范(例如OpenAPI)来定义和文档化API,并使用自动化生成工具来生成代码、测试、部署和监视API。
  21. 优先考虑事件驱动处理:因为Serverless应用程序适合处理事件驱动型工作负载,所以应该优先采用事件驱动模式进行设计和开发。这种方式可以简化代码复杂性,并实现高度并行处理,从而提高系统性能和可扩展性。
  22. 模块化设计:将函数构建成可重用的模块,并使用适当的设计模式来分离关注点和降低耦合度。这种方式可以简化代码维护和部署,并提高代码复用率和代码质量。
  23. 考虑移动互联网和物联网场景:Serverless应用程序也适用于移动互联网和物联网场景。通过集成AWS IoT Core和Amazon API Gateway,您可以轻松地连接和管理设备,并在物联网和移动应用程序之间进行无缝协作。
  24. 避免Serverless“陷阱”:虽然Serverless是一种灵活、可扩展的架构,但是在使用Serverless时,也需要避免一些“陷阱”,例如过度依赖第三方服务、过度复杂化函数逻辑等。

参考文献:
https://spring.io/projects/spring-cloud https://kubernetes.io/ https://istio.io/ 罗小爬EX

猜你喜欢

转载自blog.csdn.net/CSDN_SAVIOR/article/details/130918039
今日推荐