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也是如此。