1 Star 0 Fork 0

sunshanpeng / blog

Create your Gitee Account
Explore and code with more than 5 million developers,Free private repositories !:)
Sign up
This repository doesn't specify license. Without author's permission, this code is only for learning and cannot be used for other purposes.
Clone or download
在k8s中使用eureka的几种姿势.md 6.71 KB
Copy Edit Web IDE Raw Blame History
sunshanpeng authored 2019-09-12 18:10 . k8s

前言

在Kubernetes中,service通过label关联pod,使用service的clusterIp即可访问到对应pod。

如果觉得clusterIp不好记,可以用coredns将service name解析成service的clusterIp,通过访问service name来访问对应的pod。

通过coredns+service,可以在单个kubernetes集群中无需其他依赖即可实现服务发现。

但由于某些其他原因我们需要使用额外的注册中心eureka,本文列举了几种注册方式和注意事项。

使用Pod IP注册

因为Pod的IP是跟随生命周期的,重新发布以后IP就变更了。

但是eureka的缓存机制相当长,重新发布后服务消费者很长一段时间会访问之前的Pod IP。

要解决这个问题有以下几个优化点:

优雅停止Pod

如果Pod被强杀掉,就不能立即从Eureka Server下线,直到Eureka Server的EvictionTask 来定时清理过期的客户端,默认60秒清理一次,清理掉之前还能被其他客户端发现并调用。

kubernetes提供了生命周期回调函数,会在容器被终止前调用preStop,执行停止Java进程的命令

        lifecycle:
          preStop:
            exec:
              command: ["/bin/bash", "-c", "ps -ef | grep jav[a] | awk 'NR==1{print $2}' | xargs kill"]

因为遵循一个容器只运行一个进程的原则,所以可以直接查找Java的pid然后kill,也可以用pidof命令查找Java的pid。

缩短服务发现时间

Eureka客户端默认30秒的缓存时间,ribbon默认30秒的缓存时间,加上Eureka Server的三级缓存机制readOnlyCache也有30秒的缓存时间,在开启Eureka Server的自我保护模式时,即使客户端从Eureka Server下线了,极端情况下也有可能在90秒以后才能在其他客户端的缓存中清除。

可以通过设置deployment中的环境变量修改默认的缓存时间:

eureka client缓存(单位:秒)

eureka.client.registryFetchIntervalSeconds = 10

ribbon缓存(单位:秒)

ribbon.ServerListRefreshInterval = 10

关闭Eureka Server的readOnlyCache(可选)

因为readWriteCacheMap用的LoadingCache有读写锁,使用readOnlyCacheMap可以增加吞吐量,中小集群可以关闭readOnlyCacheMap

关闭readWriteCacheMap

eureka.server.use-read-only-response-cache = false

也可以缩短readOnlyCache的刷新时间:

eureka.server.response-cache-update-interval-ms = 10

强制下线

即使优雅停止Pod、缩短客户端和Eureka Server的缓存时间,但是Eureka客户端还是有几率请求到已下线的Pod。

Eureka提供了强制下线的接口,可以先从Eureka Server强制下线,等待缓存时间过期再停止Pod。

client强制下线接口:

POST /pause
POST /service-registry

比如:

curl -X POST "http://localhost:$MANAGEMENT_PORT/pause"
curl -X POST "http://localhost:$MANAGEMENT_PORT/service-registry/instance-status" -H "Content-Type: text/plain; charset=utf-8" -d "OUT_OF_SERVICE"

Eureka Server强制下线接口:

PUT /eureka/apps/${appId}/${ip:port}/status?value=OUT_OF_SERVICE

比如:

curl -X PUT http://eureka:8080/eureka/apps/EUREKA-1/192.168.0.10:8080/status?value=OUT_OF_SERVICE

关于强制下线可以看一下参考里的文章。

具体配置

实践配置为Eureka客户端缓存10秒,ribbon缓存10秒,不关闭readOnlyCache(缓存30秒),加起来缓存时间50秒。

将停止脚本放在指定目录,并在preStop调用。

脚本逻辑:1.强制下线当前实例;2.暂停总缓存时间;3.停止服务。

配置环境变量:

         - name: eureka.client.registryFetchIntervalSeconds # Eureka客户端缓存时间
           value: "10"
         - name: ribbon.ServerListRefreshInterval #ribbon缓存时间
           value: "10"
         - name: MANAGEMENT_PORT #management.port的端口
           value: "${management.port}"
         - name: SLEEP_SECOND #总缓存时间
           value: "50"
         - name: management.endpoints.web.exposure.include #启用并暴露service-registry
           value: service-registry

preStop

        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "/app/script/stop.sh"]

stop.sh

# 如果没有管理端口直接下线
if [ -z $MANAGEMENT_PORT ];then
  echo "MANAGEMENT_PORT is empty, kill java"
  ps -ef | grep jav[a] | awk 'NR==1{print $2}' | xargs kill
  exit 1
fi

# 默认暂停时间
if [ -z $SLEEP_SECOND ];then
  echo "SLEEP_SECOND is empty, set 50"
  SLEEP_SECOND=50
fi

# 从EUREKA下线
echo "curl pause $MANAGEMENT_PORT"
# curl -X POST "http://localhost:$MANAGEMENT_PORT/pause"
curl -X "POST" "http://localhost:$MANAGEMENT_PORT/service-registry/instance-status" -H "Content-Type: text/plain; charset=utf-8" -d "OUT_OF_SERVICE"

# 等待SLEEP_SECOND秒,SLEEP_SECOND = 客户端缓存时间(eureka+ribbon)+ readOnlyCache时间
echo ""
echo "sleep $SLEEP_SECOND s"
sleep $SLEEP_SECOND

# 停止服务
echo "kill java"
ps -ef | grep jav[a] | awk 'NR==1{print $2}' | xargs kill

使用Service的Cluster IP注册

Service的Cluster IP是一个虚拟IP,会经kube-proxy把流量负载均衡到后端endpoint。

而且Service的Cluster IP是固定的,除非主动删除后重建,不然可以一直固定。

使用Cluster IP等于使用了Kubernetes的服务发现,当Pod不可用时就会从service的endpoint列表中移除,流量就不会再负载均衡到该Pod。

配置方式(properties):

eureka.instance.ipAddress = ${Cluster IP}
eureka.instance.nonSecurePort = ${port} #service的port

使用Service注册虽然可以自动过滤下线的服务,但是ribbon内部使用keepalive进行会话保持,因为是客户端负载均衡所以A服务的A1实例所有请求都会经过service转发到同一个B服务的B1实例,从而出现负载不均衡的情况

使用Service的NodePort注册

默认情况下Pod的IP只能在本集群内的Node上访问,非本集群的服务器访问不了。

可以通过写路由表或者BGP来发布Pod路由。

NodePort是Pod对外提供访问的一种方式,使用Host IP+NodePort可以简单实现跨集群及虚拟机之间的服务互相访问。

配置方式(环境变量):

         - name: eureka.instance.ipAddress
           valueFrom:
             fieldRef:
               fieldPath: status.hostIP
         - name: eureka.instance.nonSecurePort
           value: "${NodePort}"        

NodePort方式本质也是Service,所以也存在负载均衡的情况。

另外管理NodePort也是件麻烦事。

参考

http://www.itmuch.com/spring-cloud-sum/how-to-unregister-service-in-eureka/

Comment ( 0 )

Sign in for post a comment

1
https://gitee.com/sunshanpeng/blog.git
git@gitee.com:sunshanpeng/blog.git
sunshanpeng
blog
blog
master

Search