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. 解决方案概述

  1. 为了http://{pod_name}.{svc_name}.{namespace}.svc.cluster.local这类有规则的域名可以在本地识别,靠hosts文件是很麻烦的(不支持通配符,需要一个个配),我们需要本地dns,所以非dnsmasq莫属

  2. 域名识别出来后,我们需要反向代理或者重写,毫无疑问,用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作为备选方案,这篇笔记仅仅为了记录下其过程。