1 起因
事件的起因是这样的,我们在微服务改造的过程中,选择将服务注册到eureka中,开发的时候还好,使用场景是这样的:
- 在idea中启动服务,成功注册到eureka,关闭服务,eureka成功注销该服务实例
- java -jar方式启动服务,成功注册到eureka中,ctrl-c停止服务,eureka成功注销该服务实例
有一天,在服务器上部署服务的时候,我们选择了docker启所有的服务,预料之外的事情发生了:
docker run 服务成功注册到eureka,docker stop之后,eureka却没有将该服务实例注销, 那就docker rm 将容器删除,eureka中这个服务实例还是存在,这样每docker run一下,eureka中就会多注册一个实例,就多了好多僵尸实例。
eureka有问题?还是docker启服务有问题?
我docker容器都干掉了,eureka中还存在这有点儿说不过去吧
2 手动下线
eureka不能自动给下线我就手动delete一下吧
DELETE http://{ip}:{port}/eureka/apps/{appName}/{inatanceId}
3 改实例ID
但是这样就是你每重启一下就得删一下,很麻烦。发现容器启的实例id都有个特点,containerId:服务名称:服务端口号,物理机上启的主机ip:服务名称:服务端口号
那就eureka.instance.instance-id=${spring.application.name}:${server.port}
定制一下实例id,这样docker run的服务端口相同的话,实例id就会相同,比如都是rms:8080,这样docker run多少次也不会有那么多僵尸实例了,后起的会把前边儿启的同id实例挤掉。
但是这也并没有从实质上解决自动下线问题,到底是为什么呢?
4 优雅的停止容器
直到看到一个知识点儿你真的优雅的停止容器了吗?不就是docker stop吗,其实:
当你发出Docker stop命令时,Docker会很好地要求进程停止,如果进程在10秒内没有关闭,它将强制终止进程。
docker stop命令首先尝试通过向容器中的根进程(pid 1)发送sigterm信号来停止正在运行的容器。如果进程在超时期间没有退出,则发送SIGKID信号。
虽然进程可以选择忽略sigterm,但sigkill直接进入内核,内核将终止进程。这个过程根本看不到信号。
使用Docker Stop时,唯一可以控制的是Docker守护进程在发送sigkill之前等待的秒数:docker stop --time=30 foo
这才慌然大悟,启容器,进入容器,杀进程:
这里如果kill -9 同样不能正常下线,原因看图,-9 也是发送的SIGKILL
忽然想到java -jar 方式起的服务可以正常注册到eureka,ctrl-c可以正常停止进程使eureka成功注销该实例,如果我nohup java -jar启的服务,就不能ctrl-c了
只能kill掉进程了,贯用的kill -9 pid,发现这样杀死的服务实例,确实eureka中也没有下线。这样就与docker stop服务实例不能正常注销的原因一致了。
看来不能高兴的太早,发现docker stop指定时间不好使:
docker stop --time=80 rms-consul
等呀等,真的等了80s强制退出,eureka并没有注销该实例,感觉这种方式不太好把握进程停止真正需要的时间,也可能这种方式就是无效。
到底应该如何优雅的停止容器?https://www.ctl.io/developers/blog/post/gracefully-stopping-docker-containers/
试了一圈还是这好使:
5 docker 不能正常停止服务是因为没有正确的开始
说好的优雅停止容器,为啥还是不能优雅的解决问题呢?到目前为止其实就是正常的kill解决了问题,继续找原因。找到dockerfile
ENTRYPOINT [ "sh", "-c", "/start.sh"]
$ cat start.sh
java -jar /apps/rms.jar
这个问题是shell script接收SIGTERM信号,而没有发送给通过shell脚本生成的java进程,因此spring boot无法正常退出。
解决办法:
通过运行exec命令,它将代替shell进程把SIGTERM传播到spring boot。
ENTRYPOINT [ "sh", "-c", "exec java -jar /apps/rms.jar"]
再次
docker build...
docker run...
docker stop rms
居然成功了,eureka也能成功注销该容器实例了
再试一下上边儿不生效的优雅停止容器方式也成功了,如:
docker kill -s SIGTERM rms
还是蛮优雅的嘛。
其实说来说去,就是只要能让spring boot优雅退出,eureka就能让该实例优雅注销。
另外spring cloud consul也是如此。