上一章描述了如何通过docker搭建一个简单的kubernetes集群,有了k8s,就可以用它来玩点其他东西;本文通过搭建简单的springboot项目,演示如何通过kubernetes进行服务注册,旨在使用K8S中自身的服务发现功能,不使用其他的服务发现组件,通过 Spring 的 spring-cloud-kubernetes 来搭建SpringCloud项目。
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
如何处理服务发现?
并为一组 Pod 提供相同的 DNS 名
, 并且可以在它们之间进行负载均衡。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 上。
接下来就使用kubernetes的服务发现功能,不另外使用其他服务注册组件,搭建Spring Cloud 微服务架构。
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'
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
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
FROM openjdk:8-jdk-alpine
ENV jarName="service-provider.jar"
COPY build/libs/$jarName $jarName
ENTRYPOINT java -Duser.timezone=GMT+8 -jar $jarName
FROM openjdk:8-jdk-alpine
ENV jarName="service-consumer.jar"
COPY build/libs/$jarName $jarName
ENTRYPOINT java -Duser.timezone=GMT+8 -jar $jarName
@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();
}
@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";
}
}
./gradlew build
docker build -t $APPLICATION_NAME:$VERSION .
# 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
两个项目分别根据模板中的注释描述编写yaml文件,其中,ports内配置的nodePort的类型,为了方便直接通过本机访问, 例如:
service-consumer-deploy.yaml
service-provider-deploy.yaml
使用
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
K8S其实已经维护了服务实例列表,每个服务对应的Endpoint也保存在K8S集群etcd数据库中,所以spring-cloud-kubernetes所做的工作,就是在服务调用时,找到找到服务的Endpoint,从而完成服务调用。我们发现spring-cloud-kubernetes也是通过实现DiscoveryClient接口,实现类KubernetesDiscoveryClient,具体源码这里就不叙述了。
kubectl port-forward podsNAME 外部端口:内部服务端口
#例如
kubectl port-forward podsNAME 80:30001
#例如
kubectl port-forward podsNAME :30001 # 随机分配
本来想说的是下篇解答,但是想了一下也没几个字描述,昨晚失败的主要原因是:feign没有找到可用的服务。k8s下,不借用其他服务发现的组件,那么默认是从etcd下走的dns解析,所以解决这个问题就显而易见了。
@FeignClient(name = "service-provider", url = "http://service-provider")
public interface ProviderServiceClient {
@GetMapping("/provider-hello")
String sayHello();
}
@SneakyThrows
@GetMapping("/provider-hello")
public String sayHello(HttpServletRequest request){
Thread.sleep(2000);
System.out.println("======收到服务调用请求=====");
return "hello world");
}
kubectl apply -f service-provider-deploy.yaml