【笔记】对于部署在Kubernetes里的Spring Cloud,如何进行本地调试?
文章目录
1. 故事背景
随着业务的发展,单体臃肿的项目严重阻碍进度,朝着微服务的方向进行重构,因为后台的技术栈是Java,所以微服务框架的第一选择是Spring Cloud。经过一系列的折腾,我们把Spring Cloud部署在k8s集群里(到目前为止,我都觉得这两个玩意功能有点重合,虽然领域不一样),这期间遇到几个关键的问题。
> 首先,对于Spring Cloud,部署需要选择Statefulset,因为这样可以有稳定的网络标识(服务名)
Eureka客户端会根据服务名,获取对应Endpoints
,缓存在本地,在调用时,会从Endpoints
中取一个进行调用。如问题所描述,这个Endpoint
在k8s集群里通过DNS是认识的,但本地并不认识。
> 研发怎么调试?难道注册中心、配置中心、所有的微服务都要本地启动?
之所以采用微服务的架构,肯定是因为规模上来了,拆分出来的微服务绝不会少。而微服务整个架构内容实在太多,包括注册中心、配置中心、网关、各种业务微服务。如果本地调试时,全部在本地启动,是不现实的。为了可以本地调试,我们大致需要解决如下问题:
1. k8s集群里,pod的网络都是内网的
可以使用Ingress
或者Service NodePort
模式,让开发环境在本地也可以访问到,我这里使用ingress
,规则是{service_name}.foo.com
,如:base.foo.com
2. 需要把上面提到的稳定的网络标识在本地也可以识别
那些稳定的网络标识,只存在于k8s集群里(dns),在研发本地是不存在的
# 按照我的需求,需要把
http://{pod_name}.{svc_name}.{namespace}.svc.cluster.local
# 重写至
{service_name}.cloud.foo.com
# 实例
http://java-ms-base-test-0.java-ms-base-test.java-ms.svc.cluster.local
👇
http://base.cloud.foo.com
# 其中foo是顶级域名
# base是微服务名,从{svc_name}中提取java-ms-{service_name}-test
> 最后,如果你也有类似的问题,不妨可以看看我的解决方案
部署Spring Cloud,为什么使用Statefulset,可自行了解一下
2. 提前准备
- 本次试验是在
MacOS High Sierra(version 10.13.3)
系统
3. 解决方案概述
为了
http://{pod_name}.{svc_name}.{namespace}.svc.cluster.local
这类有规则的域名可以在本地识别,靠hosts
文件是很麻烦的(不支持通配符,需要一个个配),我们需要本地dns,所以非dnsmasq
莫属域名识别出来后,我们需要反向代理或者重写,毫无疑问,用
nginx
4. dnsmasq
1. 安装
brew install dnsmasq
2. 配置 dnsmasq.conf
# 逻辑:把域名后缀svc.cluster.local均由本地处理
echo 'listen-address=127.0.0.1' >> $(brew --prefix)/etc/dnsmasq.conf
echo 'address=/svc.cluster.local/127.0.0.1' >> $(brew --prefix)/etc/dnsmasq.conf
echo 'strict-order' >> $(brew --prefix)/etc/dnsmasq.conf
3. 配置 /etc/resolver/local
# 因为域名最末端的后缀是local,所以文件命名为local
sudo mkdir -v /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/local'
4. 启动dnsmasq
sudo brew services start dnsmasq
5. 加入开机自启动
sudo cp -v $(brew --prefix dnsmasq)/homebrew.mxcl.dnsmasq.plist /Library/LaunchDaemons
sudo launchctl load -w /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
5. nginx
1. 安装
brew install nginx
2. 配置
# vim /usr/local/etc/nginx/nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# 必须指定
resolver 127.0.0.1;
server {
# 把所有微服务的端口都加上
listen 8880;
listen 8881;
listen 8882;
listen 8883;
listen 8884;
listen 8885;
listen 8886;
listen 8887;
listen 8888;
listen 8889;
server_name *.svc.cluster.local;
if ($host ~* (.*)\.eureka-(.*)-test\.java-ms\.svc\.cluster\.local$) {
set $eureka_service_name $2;
}
# 从{svc_name}中找到微服务名称
if ($host ~* (.*)\.java-ms-(.*)-test\.java-ms\.svc\.cluster\.local$) {
set $eureka_service_name $2;
}
# 反向代理至微服务域名
location / {
proxy_pass http://$eureka_service_name.cloud.foo.com$request_uri;
}
}
}
3. 刷新配置
sudo nginx -s reload
6. 总结
现在,你可以调试某一个微服务了,而不需要全部都在本地启动。
建议:这种操作只在测试环境进行,生产环境不要直接暴露微服务的地址,要通过网关
虽然测试成功了,但通过Spring Cloud + k8s 并不是最佳方式(个人觉得),我因为一个原因一直感觉很难受。
在没有使用Spring Cloud前,k8s里Serverless应用(deployment)的滚动更新是可以做到服务不中断(哪怕只有一个副本),因为TA是先启动一个新的,启动成功后,再替换掉旧的。
为了使用Spring Cloud,不能用deployment了,得用statefuelset(因为需要稳定的网络标识),TA滚动更新的方式是先干掉旧的,然后再启动一个新的(可能是因为网络标识唯一,没办法像deployment那样做)
尽管如此,其实在k8s集群里,这两种部署方式都是可以做到服务不中断的(不大了来两个副本),问题在于Eureka Client是把Enpoints缓存在本地,隔一段时间再同步一次新的(譬如:30秒),在更新应用的时候,其中一个网络标识会被干掉,本地命中就这样会服务会出现中断。(k8s里不会,因为service同步移除掉不可用的pod)。
想了很久,找了很久,在这个方案上没有很好的办法,后面不小心发现了Istio(Service Mesh)
这个东东!经过肤浅的了解后,我决定尝试一下,成功的话,就可以抛弃Spring Cloud了,哈哈哈哈!!!
最后Spring Cloud作为备选方案,这篇笔记仅仅为了记录下其过程。
文章作者 xifan
上次更新 2018-10-06