Spring Cloud Zookeeper 升级为Spring Cloud Kubernetes

news2024/11/26 13:37:46

这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

背景

现有的微服务是使用的Spring Cloud Zookeeper这一套,实际应用在Kubernetes中部署并不需要额外的注册中心,本身Kubernetes自己就支持,所以打算替换到Zookeeper 替换为Spring Cloud Kubernetes

替换

1. 删除Spring Cloud Zookeeper相关依赖

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

2. 添加 Spring Cloud Kubernetes 相关依赖

	<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-kubernetes-client-all</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-kubernetes-client-loadbalancer</artifactId>
    </dependency>

版本我这里使用的最新版本,2.1.4

3. 解决port没有命名的bug

由于最新版本有bug,就是service.yaml中如果没有定义port的name会报错,所以这里我们采用修改源码方式去解决

问题详情可以参考我之前发的博文

直接创建一个包名为:org.springframework.cloud.kubernetes.client.discovery
创建类KubernetesInformerDiscoveryClient 代码如下

package org.springframework.cloud.kubernetes.client.discovery;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import io.kubernetes.client.extended.wait.Wait;
import io.kubernetes.client.informer.SharedInformer;
import io.kubernetes.client.informer.SharedInformerFactory;
import io.kubernetes.client.informer.cache.Lister;
import io.kubernetes.client.openapi.models.V1EndpointAddress;
import io.kubernetes.client.openapi.models.V1EndpointPort;
import io.kubernetes.client.openapi.models.V1Endpoints;
import io.kubernetes.client.openapi.models.V1Service;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesDiscoveryProperties;
import org.springframework.cloud.kubernetes.commons.discovery.KubernetesServiceInstance;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

/**
 *@author : wh
 *@date : 2022/10/27 14:36
 *@description:
 */
public class KubernetesInformerDiscoveryClient implements DiscoveryClient, InitializingBean {

	private static final Log log = LogFactory.getLog(KubernetesInformerDiscoveryClient.class);

	private static final String PRIMARY_PORT_NAME_LABEL_KEY = "primary-port-name";

	private static final String HTTPS_PORT_NAME = "https";

	private static final String UNSET_PORT_NAME = "<unset>";

	private static final String HTTP_PORT_NAME = "http";

	private final SharedInformerFactory sharedInformerFactory;

	private final Lister<V1Service> serviceLister;

	private final Supplier<Boolean> informersReadyFunc;

	private final Lister<V1Endpoints> endpointsLister;

	private final KubernetesDiscoveryProperties properties;

	private final String namespace;

	public KubernetesInformerDiscoveryClient(String namespace, SharedInformerFactory sharedInformerFactory,
			Lister<V1Service> serviceLister, Lister<V1Endpoints> endpointsLister,
			SharedInformer<V1Service> serviceInformer, SharedInformer<V1Endpoints> endpointsInformer,
			KubernetesDiscoveryProperties properties) {
		this.namespace = namespace;
		this.sharedInformerFactory = sharedInformerFactory;

		this.serviceLister = serviceLister;
		this.endpointsLister = endpointsLister;
		this.informersReadyFunc = () -> serviceInformer.hasSynced() && endpointsInformer.hasSynced();

		this.properties = properties;
	}

	@Override
	public String description() {
		return "Kubernetes Client Discovery";
	}

	@Override
	public List<ServiceInstance> getInstances(String serviceId) {
		Assert.notNull(serviceId, "[Assertion failed] - the object argument must not be null");

		if (!StringUtils.hasText(namespace) && !properties.isAllNamespaces()) {
			log.warn("Namespace is null or empty, this may cause issues looking up services");
		}

		V1Service service = properties.isAllNamespaces() ? this.serviceLister.list().stream()
				.filter(svc -> serviceId.equals(svc.getMetadata().getName())).findFirst().orElse(null)
				: this.serviceLister.namespace(this.namespace).get(serviceId);
		if (service == null || !matchServiceLabels(service)) {
			// no such service present in the cluster
			return new ArrayList<>();
		}

		Map<String, String> svcMetadata = new HashMap<>();
		if (this.properties.getMetadata() != null) {
			if (this.properties.getMetadata().isAddLabels()) {
				if (service.getMetadata() != null && service.getMetadata().getLabels() != null) {
					String labelPrefix = this.properties.getMetadata().getLabelsPrefix() != null
							? this.properties.getMetadata().getLabelsPrefix() : "";
					service.getMetadata().getLabels().entrySet().stream()
							.filter(e -> e.getKey().startsWith(labelPrefix))
							.forEach(e -> svcMetadata.put(e.getKey(), e.getValue()));
				}
			}
			if (this.properties.getMetadata().isAddAnnotations()) {
				if (service.getMetadata() != null && service.getMetadata().getAnnotations() != null) {
					String annotationPrefix = this.properties.getMetadata().getAnnotationsPrefix() != null
							? this.properties.getMetadata().getAnnotationsPrefix() : "";
					service.getMetadata().getAnnotations().entrySet().stream()
							.filter(e -> e.getKey().startsWith(annotationPrefix))
							.forEach(e -> svcMetadata.put(e.getKey(), e.getValue()));
				}
			}
		}

		V1Endpoints ep = this.endpointsLister.namespace(service.getMetadata().getNamespace())
				.get(service.getMetadata().getName());
		if (ep == null || ep.getSubsets() == null) {
			// no available endpoints in the cluster
			return new ArrayList<>();
		}

		Optional<String> discoveredPrimaryPortName = Optional.empty();
		if (service.getMetadata() != null && service.getMetadata().getLabels() != null) {
			discoveredPrimaryPortName = Optional
					.ofNullable(service.getMetadata().getLabels().get(PRIMARY_PORT_NAME_LABEL_KEY));
		}
		final String primaryPortName = discoveredPrimaryPortName.orElse(this.properties.getPrimaryPortName());

		return ep.getSubsets().stream().filter(subset -> subset.getPorts() != null && subset.getPorts().size() > 0) // safeguard
				.flatMap(subset -> {
					Map<String, String> metadata = new HashMap<>(svcMetadata);
					List<V1EndpointPort> endpointPorts = subset.getPorts();
					if (this.properties.getMetadata() != null && this.properties.getMetadata().isAddPorts()) {
						endpointPorts.forEach(p -> metadata.put(StringUtils.hasText(p.getName()) ? p.getName() : UNSET_PORT_NAME,
								Integer.toString(p.getPort())));
					}
					List<V1EndpointAddress> addresses = subset.getAddresses();
					if (addresses == null) {
						addresses = new ArrayList<>();
					}
					if (this.properties.isIncludeNotReadyAddresses()
							&& !CollectionUtils.isEmpty(subset.getNotReadyAddresses())) {
						addresses.addAll(subset.getNotReadyAddresses());
					}

					final int port = findEndpointPort(endpointPorts, primaryPortName, serviceId);
					return addresses.stream()
							.map(addr -> new KubernetesServiceInstance(
									addr.getTargetRef() != null ? addr.getTargetRef().getUid() : "", serviceId,
									addr.getIp(), port, metadata, false, service.getMetadata().getNamespace(),
									service.getMetadata().getClusterName()));
				}).collect(Collectors.toList());
	}

	private int findEndpointPort(List<V1EndpointPort> endpointPorts, String primaryPortName, String serviceId) {
		if (endpointPorts.size() == 1) {
			return endpointPorts.get(0).getPort();
		}
		else {
			Map<String, Integer> ports = endpointPorts.stream().filter(p -> StringUtils.hasText(p.getName()))
					.collect(Collectors.toMap(V1EndpointPort::getName, V1EndpointPort::getPort));
			// This oneliner is looking for a port with a name equal to the primary port
			// name specified in the service label
			// or in spring.cloud.kubernetes.discovery.primary-port-name, equal to https,
			// or equal to http.
			// In case no port has been found return -1 to log a warning and fall back to
			// the first port in the list.
			int discoveredPort = ports.getOrDefault(primaryPortName,
					ports.getOrDefault(HTTPS_PORT_NAME, ports.getOrDefault(HTTP_PORT_NAME, -1)));

			if (discoveredPort == -1) {
				if (StringUtils.hasText(primaryPortName)) {
					log.warn("Could not find a port named '" + primaryPortName + "', 'https', or 'http' for service '"
							+ serviceId + "'.");
				}
				else {
					log.warn("Could not find a port named 'https' or 'http' for service '" + serviceId + "'.");
				}
				log.warn(
						"Make sure that either the primary-port-name label has been added to the service, or that spring.cloud.kubernetes.discovery.primary-port-name has been configured.");
				log.warn("Alternatively name the primary port 'https' or 'http'");
				log.warn("An incorrect configuration may result in non-deterministic behaviour.");
				discoveredPort = endpointPorts.get(0).getPort();
			}
			return discoveredPort;
		}
	}

	@Override
	public List<String> getServices() {
		List<V1Service> services = this.properties.isAllNamespaces() ? this.serviceLister.list()
				: this.serviceLister.namespace(this.namespace).list();
		return services.stream().filter(this::matchServiceLabels).map(s -> s.getMetadata().getName())
				.collect(Collectors.toList());
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.sharedInformerFactory.startAllRegisteredInformers();
		if (!Wait.poll(Duration.ofSeconds(1), Duration.ofSeconds(this.properties.getCacheLoadingTimeoutSeconds()),
				() -> {
					log.info("Waiting for the cache of informers to be fully loaded..");
					return this.informersReadyFunc.get();
				})) {
			if (this.properties.isWaitCacheReady()) {
				throw new IllegalStateException(
						"Timeout waiting for informers cache to be ready, is the kubernetes service up?");
			}
			else {
				log.warn(
						"Timeout waiting for informers cache to be ready, ignoring the failure because waitForInformerCacheReady property is false");
			}
		}
		log.info("Cache fully loaded (total " + serviceLister.list().size()
				+ " services) , discovery client is now available");
	}

	private boolean matchServiceLabels(V1Service service) {
		if (log.isDebugEnabled()) {
			log.debug("Kubernetes Service Label Properties:");
			if (this.properties.getServiceLabels() != null) {
				this.properties.getServiceLabels().forEach((key, value) -> log.debug(key + ":" + value));
			}
			log.debug("Service " + service.getMetadata().getName() + " labels:");
			if (service.getMetadata() != null && service.getMetadata().getLabels() != null) {
				service.getMetadata().getLabels().forEach((key, value) -> log.debug(key + ":" + value));
			}
		}
		// safeguard
		if (service.getMetadata() == null) {
			return false;
		}
		if (properties.getServiceLabels() == null || properties.getServiceLabels().isEmpty()) {
			return true;
		}
		return properties.getServiceLabels().keySet().stream()
				.allMatch(k -> service.getMetadata().getLabels() != null
						&& service.getMetadata().getLabels().containsKey(k)
						&& service.getMetadata().getLabels().get(k).equals(properties.getServiceLabels().get(k)));
	}

}

4. 修改路由转发

				builder.routes()
                .route(r -> r.path("/ms/test/**")
                        .filters(f -> f.stripPrefix(1))
                        .uri("lb://test-service"))

原先使用的是zookeeper上的服务名进行转发的,如果pod上面的服务名和之前zookeeper上面注册的名字不一致,就需要改一下路由的服务名

部署

然后再k8s中重新部署服务

报错

在这里插入图片描述

可以看到这里是pod没有权限调用k8s相关的api,授权就好了。使用RBAC权限处理

  1. 创建role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
  - apiGroups: [""]
    resources: ["pods","configmaps"]
    verbs: ["get", "watch", "list"]

  1. 创建ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: config-reader
  namespace: default
  1. 绑定Role和ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-reader
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: pod-reader
subjects:
  - kind: ServiceAccount
    name: config-reader
    namespace: default
  1. 在deployment中指定上面的ServiceAccount

参考博客

重新部署启动就会发现是无缝切换的

总结

总得来说切换比较简单,基本是无缝的!这样就不用依赖外部的注册中心了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/4271.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10道不得不会的 Java容器 面试题

博主介绍&#xff1a; &#x1f680;自媒体 JavaPub 独立维护人&#xff0c;全网粉丝15w&#xff0c;csdn博客专家、java领域优质创作者&#xff0c;51ctoTOP10博主&#xff0c;知乎/掘金/华为云/阿里云/InfoQ等平台优质作者、专注于 Java、Go 技术领域和副业。&#x1f680; 最…

FFmpeg内存IO模式

ffmpeg 支持从网络流 或者本地文件读取数据&#xff0c;然后拿去丢给解码器解码&#xff0c;但是有一种特殊情况&#xff0c;就是数据不是从网络来的&#xff0c;也不在本地文件里面&#xff0c;而是在某块内存里面的。 这时候 av_read_frame() 函数怎样才能从内存把 AVPacket…

TensorFlow的GPU使用相关设置整理

前言 TensorFlow是一个在机器学习和深度学习领域被广泛使用的开源软件库&#xff0c;用于各种感知和语言理解任务的机器学习。 默认情况下&#xff0c;TensorFlow 会映射进程可见的所有 GPU&#xff08;取决于 CUDA_VISIBLE_DEVICES&#xff09;的几乎全部内存。这是为了减少内…

国考省考行测:问题型材料主旨分析,有问题有对策,主旨是对策,有问题无对策,要合理引申对策

国考省考行测&#xff1a;问题型材料主旨分析&#xff0c;有问题有对策&#xff0c;主旨是对策&#xff0c;有问题无对策&#xff0c;要合理引申对策 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考…

【Linux】Linux背景、环境的搭建以及用XShell实现远程登陆

目录Linux 背景Linux环境搭建Linux远程登陆Linux 背景 肯尼斯蓝汤普森最早用汇编语言创建了UNIX系统&#xff0c;后来与他的好“基友”丹尼斯里奇&#xff08;C语言之父&#xff09;&#xff0c;他们两个一同用C语言重新写了UNIX系统&#xff0c;但是操作系统的使用是需要收费…

ActiveState Platform - November 2022

ActiveState Platform - November 2022 ActiveState平台定期更新新的、修补的和版本化的软件包和语言。 Python 3.10.7、3.9.14、3.8.14-解决了许多安全问题的点发布。 Python C库-ibxml 2.10.3、libxslt 1.1.37、libexpat 2.4.9、zlib 1.2.13、curl 7.85.0和sqlite3 3.39.4&am…

Python添加水印简简单单,三行代码教你批量添加

环境使用: Python 3.8Pycharm 如何配置pycharm里面的python解释器? 选择file(文件) >>> setting(设置) >>> Project(项目) >>> python interpreter(python解释器)点击齿轮, 选择add添加python安装路径 pycharm如何安装插件? 选择file(文件) …

使用Python PyQt5完成残缺棋盘覆盖仿真作业

摘要&#xff1a;本文内容是关于如何实现残缺棋盘覆盖仿真软件&#xff0c;算法课作业要求设计开发一个残缺棋盘覆盖仿真软件。使用”分治算法“求解问题&#xff0c;Python编程语言实现功能&#xff1b;使用PyQt5和Python热力图实现界面和仿真效果展示。 1 残缺棋盘覆盖仿真作…

[Linux打怪升级之路]-yun安装和gcc的使用

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 本期学习目标&am…

Java:外包Java项目有什么好处?

Java已经成为众多解决方案的通用开发语言&#xff0c;包括web应用、游戏、软件开发等等。超过710万全球的Java程序员都在忙着为业界下一个最好的应用程序编码。 随着企业努力在当今的全球市场中保持竞争力&#xff0c;对Java项目外包的需求不断增加。 以下是你的企业通过外包Ja…

python基于PHP+MySQL的论坛管理系统

互联网给了我们一个互通互信的途径,但是如何能够更加高效的进行各种问题的分享和交流是很多人关心的问题,市面上比较知名的一些分享交流平台也很多,比如百度的贴吧,知乎等高质量内容分享平台,本系统是一个类似这样的论坛分享系统 随着互联网的发展人们分享和交流的分享也变的越…

leetcode刷题(128)——1575. 统计所有可行路径,动态规划解法

leetcode刷题&#xff08;127&#xff09;——1575. 统计所有可行路径&#xff0c;DFS解法 给你一个 互不相同 的整数数组&#xff0c;其中 locations[i] 表示第 i 个城市的位置。同时给你 start&#xff0c;finish 和 fuel 分别表示出发城市、目的地城市和你初始拥有的汽油总…

【CSS】CSS字体样式【CSS基础知识详解】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 本文章收录于专栏 【CSS】 【CSS专栏】已发布文章 &#x1f4c1;【CSS基础认知】 &#x1f4c1;【CSS选择器全解指南】 本文目录【CS…

物联网感知-光纤光栅传感器技术

一、光纤光栅传感技术 光纤光栅是利用光纤材料的光敏性&#xff0c;通过紫外光曝光的方法将入射光相干场图样写入纤芯&#xff0c;将周期性微扰作用于光纤纤芯&#xff0c;在纤芯内产生沿纤芯轴向的折射率周期性变化&#xff0c;从而形成永久性空间的相位光栅&#xff0c;其作用…

MySQL数据库的基本操作及存储引擎的使用

大家好呀&#xff01;我是猿童学&#x1f435;&#xff0c;最近在学习Mysql数据库&#xff0c;给初学者分享一些知识&#xff0c;也是学习的总结&#xff0c;关注我将会不断地更新数据库知识&#xff0c;也欢迎大家指点一二&#x1f339;。 目录 一、常用的MySQL语句 二、创建…

使用ThinkMusic网站源码配合cpolar,发布本地音乐网站

1、前言 在我们的日常生活中&#xff0c;音乐已经成为不可或缺的要素之一&#xff0c;听几首喜欢的音乐&#xff0c;能让原本糟糕的心情变得好起来。虽然现在使用电脑或移动电子设备听歌都很方便&#xff0c;但难免受到诸多会员或VIP限制&#xff0c;难免让我们回想起音乐网站…

【JavaScript】常用内置对象——数组(Array)对象

文章目录什么是数组创建数组访问数组数组常用方法和属性投票传送门什么是数组 数组&#xff08;Array&#xff09;是最基本的集合类型&#xff0c;由于JavaScript是弱类型语言&#xff0c;因此JavaScript的数组和大多数语言的数组有所区别。在大多数语言中&#xff0c;当声明一…

ubuntu 20.04 qemu u-boot-2022.10 开发环境搭建

开发环境 ubuntu 20.04 VMware Workstation Pro 16 基于qemu&#xff08;模拟器&#xff09;&#xff0c;vexpress-a9 平台 搭建 u-boot-2022.10 (当前最新版本&#xff09; 准备工作 u-boot下载&#xff0c;下载最新稳定版本&#xff0c;当前为 u-boot-2022.10&#xff0…

代码随想录49——动态规划:121买卖股票的最佳时机、122买卖股票的最佳时机II

文章目录1.121买卖股票的最佳时机1.1.题目1.2.解答1.2.1.贪心算法1.2.2.动态规划2.122买卖股票的最佳时机II2.1.题目2.2.解答1.121买卖股票的最佳时机 参考&#xff1a;代码随想录&#xff0c;121买卖股票的最佳时机&#xff1b;力扣题目链接 1.1.题目 1.2.解答 1.2.1.贪心算…

第七节:类和对象【一】【java】

目录 &#x1f9fe;1. 面向对象的初步认知 1.1 什么是面向对象 1.2 面向对象与面向过程 &#x1f4d5;2. 类定义和使用 2.1 简单认识类 2.2 类的定义格式 2.3 课堂练习 &#x1f392;3. 类的实例化 3.1 什么是实例化 3.2 类和对象的说明 3.3练习 &#x1f9fe;1. 面…