
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: debug
2.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 List findServices(){
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条)