1、kubernetes Service 概述上一章描述了如何通过docker搭建一个简单的kubernetes集群,有了k8s,就可以用它来玩点其他东西;本文通过搭建简单的springboot项目,演示如何通过kubernetes进行服务注册,旨在使用K8S中自身的服务发现功能,不使用其他的服务发现组件,通过 Spring 的 spring-cloud-kubernetes 来搭建SpringCloud项目。
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
- 这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?其实这个问题放在微服务下来讲的话,就是如何处理服务发现?
- 为了解决这个问题,就要使用到kubernetes的另外一种资源:Service(SVC)。
- SVC服务是Kubernetes里的核心资源对象之一,其实可以理解成我们微服务架构中的一个微服务。SVC一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,在svc的整个生命周期内,Cluster IP不会发生改变。
- Kubernetes使用DNS提供服务注册功能。 Kubernetes 为 Pods 提供自己的 IP 地址,并为一组 Pod 提供相同的 DNS 名, 并且可以在它们之间进行负载均衡。
- 定义SVC:
apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 9376
上述配置创建一个名称为 my-service 的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 app=MyApp 的 Pod 上。
2、项目搭建接下来就使用kubernetes的服务发现功能,不另外使用其他服务注册组件,搭建Spring Cloud 微服务架构。
- 项目结构
- service-consumer
- service-provider
2.1 均采用 gradle 构建springboot项目
- build.gradle
implementation 'org.springframework.boot:spring-boot-starter-web' implementation "org.springframework.cloud:spring-cloud-starter-openfeign:2.2.2.RELEASE" implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-all:1.1.2.RELEASE' implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap:3.0.3' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-ribbon:2.2.6.RELEASE'
2.2 配置文件
- service-consumer -application.yml
server: port: 30001 spring: application: name: service-consumer cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true kubernetes: reload: enabled: true mode: polling period: 5000 logging: level: org.springframework.cloud.gateway: debug org.springframework.cloud.loadbalancer: debug
- service-provider - application.yml
server: port: 30000 spring: application: name: service-provider cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true kubernetes: reload: enabled: true mode: polling period: 5000 logging: level: org.springframework.cloud.gateway: debug org.springframework.cloud.loadbalancer: debug2.3 DockerFile
- service-provider
FROM openjdk:8-jdk-alpine ENV jarName="service-provider.jar" COPY build/libs/$jarName $jarName ENTRYPOINT java -Duser.timezone=GMT+8 -jar $jarName
- service-consumer
FROM openjdk:8-jdk-alpine ENV jarName="service-consumer.jar" COPY build/libs/$jarName $jarName ENTRYPOINT java -Duser.timezone=GMT+8 -jar $jarName2.4 简单的controller
- service-consumer
@Slf4j @RestController @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @RequiredArgsConstructor public class BootstrapApplication { private final DiscoveryClient discoveryClient; private final ProviderServiceClient providerServiceClient; private final RestTemplate restTemplate = new RestTemplate(); public static void main(String[] args) { SpringApplication.run(BootstrapApplication.class, args); } @GetMapping("/feign/hello") public String consumerHello(){ log.info("消费服务:service-consumer"); return providerServiceClient.sayHello(); } @GetMapping("/rest/hello") public String restHello(){ return restTemplate.getForObject("http://service-provider/provider-hello", String.class); } @GetMapping("/rest-port/hello") public String restHelloWithPort(){ return restTemplate.getForObject("http://service-provider:30000/provider-hello", String.class); } @GetMapping("/rest-ip/hello") public String restHelloWithIP(){ return restTemplate.getForObject("http://10.100.235.65:30000/provider-hello", String.class); } @GetMapping("/consumers/services") public ListfindServices(){ log.info("当前注册中心下所有服务"); List services = discoveryClient.getServices(); services.stream().map(discoveryClient::getInstances).forEach(v -> v.forEach(s -> System.out.printf("%s:%s uri:%s%n", s.getHost(), s.getPort(), s.getUri()))); return services; } } @FeignClient(value = "service-provider") public interface ProviderServiceClient { @GetMapping("/provider-hello") String sayHello(); }
- service-provider
@RestController @SpringBootApplication @EnableDiscoveryClient public class BootstrapApplication { public static void main(String[] args) { SpringApplication.run(BootstrapApplication.class, args); } @GetMapping("/provider-hello") public String sayHello(){ return "hello world"; } }2.5编译打包
./gradlew build
docker build -t $APPLICATION_NAME:$VERSION .2.6 构建一个k8s template
# PROJECT_NAME:SpringBoot工程的服务名称 # REPLACE_IMAGE: Docker镜像 # PROJECT_PORT: SpringBoot工程的服务端口 # 定义Deployment apiVersion: apps/v1 kind: Deployment metadata: name: PROJECT_NAME labels: app: PROJECT_NAME spec: replicas: 1 template: metadata: name: PROJECT_NAME labels: app: PROJECT_NAME spec: containers: - name: PROJECT_NAME image: REPLACE_IMAGE ports: - containerPort: PROJECT_PORT imagePullPolicy: IfNotPresent # 镜像仓库,(不指定则使用本地仓库) #imagePullSecrets: # - name: regcred-aliyun restartPolicy: Always selector: matchLabels: app: PROJECT_NAME --- # 定义SVC apiVersion: v1 kind: Service metadata: name: PROJECT_NAME spec: selector: app: PROJECT_NAME ports: - port: PROJECT_PORT #外部映射端口 targetPort: PROJECT_PORT # 服务运行端口 nodePort: PROJECT_PORT # node访问端口 type: NodePort
2.7 构建k8s服务两个项目分别根据模板中的注释描述编写yaml文件,其中,ports内配置的nodePort的类型,为了方便直接通过本机访问, 例如:
service-consumer-deploy.yaml
service-provider-deploy.yaml
2.8 查看构建进度,构建完成后,找到nodeIP访问使用 kubectl apply -f 文件名 构建服务,例如:
kubectl apply -f service-consumer-deploy.yaml kubectl apply -f service-provider-deploy.yaml
kubectl get po kubectl get svc kubectl get node -o wide
- 获取当前注册中心有哪些服务
- 尝试rest template调用 provider服务
- 尝试feign 调用provider服务
- 查看日志
- 查看日志
- 聊一聊原理
K8S其实已经维护了服务实例列表,每个服务对应的Endpoint也保存在K8S集群etcd数据库中,所以spring-cloud-kubernetes所做的工作,就是在服务调用时,找到找到服务的Endpoint,从而完成服务调用。我们发现spring-cloud-kubernetes也是通过实现DiscoveryClient接口,实现类KubernetesDiscoveryClient,具体源码这里就不叙述了。
- 配置pods端口转发
kubectl port-forward podsNAME 外部端口:内部服务端口 #例如 kubectl port-forward podsNAME 80:30001 #例如 kubectl port-forward podsNAME :30001 # 随机分配
- 至于为什么没有调通feign:
-
首先服务发现没有问题,通过DiscoveryClient 调用getServices是可以获取所有服务列表的
-
1、可能是因为内部转发端口应该配置成80,目前是30001,导致走dns之后,默认端口为80,访问失败。
-
2、 feign配置问题
-
3、 下篇解答
-
End.解决feign调用失败:
本来想说的是下篇解答,但是想了一下也没几个字描述,昨晚失败的主要原因是:feign没有找到可用的服务。k8s下,不借用其他服务发现的组件,那么默认是从etcd下走的dns解析,所以解决这个问题就显而易见了。
- 1、在feignClient上加url
@FeignClient(name = "service-provider", url = "http://service-provider") public interface ProviderServiceClient { @GetMapping("/provider-hello") String sayHello(); }
- 2、为了不指定端口,修改映射端口为80,反正每个Cluster IP都是独立的,不会冲突
- 3、 测试:
- 修改provider的代码
@SneakyThrows @GetMapping("/provider-hello") public String sayHello(HttpServletRequest request){ Thread.sleep(2000); System.out.println("======收到服务调用请求====="); return "hello world"); }
- 增加service-provider 的pod数量:
kubectl apply -f service-provider-deploy.yaml
-
等待重启,然后查看pods状态和provider的数量
-
访问: servicer-consumer 访问provider
-
查看日志
pod1:
pod2:
-
多发几次请求:
pod1:
pod2:
完成。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)