之所以會有這個主題,是因為這幾天我給mysql集群前置了乙個keepalived,為了方便也做成了docker映象,丟給k8s來部署。但實際測試時發現,當停止或者刪除keepalived容器後,網絡卡上還殘餘之前keepalived下發的virtual ip。
直接在宿主機上安裝keepalived不會有這個問題。從keepalived原始碼上來看,它會接管linux發給它的sigterm訊號,之後清理現場,包括下發給介面的虛ip,所以問題原因就比較簡單了,容器在退出時,並沒用將sigterm訊號傳遞給keepalived程序,造成keepalived強制退出,最終virtual ip殘留。
要解決這個問題,還需要從容器退出開始談起。先挖個坑,晚上填。
docker容器退出會收到哪些訊號?
不同的命令,收到的訊號不同。
docker stop
預設docker stop會向容器傳送sigterm訊號,並等待10秒;如果10秒後容器沒有退出,則會傳送sigkill訊號,強制殺死該容器。我的keepalived容器就是收到訊號10s後仍然存活,最後被sigkill殺死的,docker ps -a檢視容器的status,可以看到exited (137) 2 hours ago。137=128+9,即退出原因是收到了訊號9(即sigkill)。
如果你的應用在退出時要做的事情比較多,可以docker stop的時候寬限幾天。
docker stop ----time=30 ***
docker kill
預設docker kill會向容器傳送sigkill訊號,即直接殺死容器,不叨叨。不過你可以通過–signal=sig***引數來修改docker kill傳送的訊號。注意docker kill和linux kill命令的預設行為不同,後者預設傳送的是sigterm(類似docker stop)。docker kill和linux kill -9的行為是類似的。
docker rm -f
docker rm -f會傳送sigkill殺死容器,並清理容器痕跡,類似docker kill && docker rm。
應用應該處理哪個訊號?
新寫的應用,建議捕捉sigterm,因為這是docker stop時預設發出的訊號;舊有的應用,則需要一房一價,具體問題具體分析,例如nginx需要sigquit,apache需要sigwinch。keepalived比較友好:
/* terminate handler */
void
sigend(void *v, int sig)
int status;
/* register the terminate thread */
thread_add_terminate_event(master);
if (vrrp_child > 0) {
kill(vrrp_child, sigterm);
waitpid(vrrp_child, &status, wnohang);
if (checkers_child > 0) {
kill(checkers_child, sigterm);
waitpid(checkers_child, &status, wnohang);
/* initialize signal handler */
void
signal_init(void)
signal_handler_init();
signal_set(sighup, sighup, null);
signal_set(sigint, sigend, null);
signal_set(sigterm, sigend, null);
signal_ignore(sigpipe);
不管是sigint還是sigterm,都能sigend走清理。
應用是怎麼接收到訊號的
docker會將訊號傳給pid=1的程序。
換句話說,如果你的應用跑起來時pid不為1,那麼就收不到docker發出來的訊號,也就做不到gracefully退出了。因此,我們的目標就是要讓需要gracefully退出的應用跑在pid=1的位置上。
舉個例子。
先寫個loop.sh。
#!/usr/bin/env bash
trap 'exit 0' sigterm
whiletrue; do :; done
然後是dockerfile。
from ubuntu:trusty
copy loop.sh /
cmd /loop.sh
你可以試著build/run/stop一下,會發現docker stop需要10s才能停掉這個容器,說明loop.sh沒有收到訊號。為什麼呢?我們知道訊號是發給pid=1的程序,那麼來看看容器裡的情況:
docker exec 838 ps aux
user pid %cpu %mem vsz rss tty stat start time command
root 1 0.0 0.0 4460 696 ? ss 05:26 0:00 /bin/sh -c /loop.sh
root 6 99.6 0.0 17968 2716 ? r 05:26 0:29 bash /loop.sh
root 7 0.0 0.0 15580 2156 ? rs 05:26 0:00 ps aux
顯然loop.sh收不到訊號。
dockerfile對於cmd command param1 param2這種形式的命令,具體執行的是bash -c command param1 param2,也就是上面我們看到的結果。
正確的寫法是,使用cmd ["executable","param1","param2"]這種形式,例如上面:
from ubuntu:trusty
copy loop.sh /
cmd [/loop.sh]
這樣容器啟動時,會直接執行loop.sh,不會在外面再套一層bash:
docker exec 638 ps aux
user pid %cpu %mem vsz rss tty stat start time command
root 1 91.8 0.0 17968 2864 ? rs 05:36 0:09 bash /loop.sh
root 6 0.0 0.0 15580 2032 ? rs 05:36 0:00 ps aux
這樣應用就可以拿到docker傳送的sigterm訊號了。
再舉個例子。
比如我的keepalived容器。由於容器啟動時我需要做一些配置修改(keepalived.conf),需要把這些操作放在乙個指令碼裡,所以我的cmd是:cmd ["/opt/entry-point.sh"],在entry-point.sh中啟動keepalived程序。
#!/bin/bash -x
#entry-point.sh
#replace variables in /etc/keepalived/keepalived.conf
/sbin/ipvsadm-restore < /etc/sysconfig/ipvsadm
/usr/sbin/keepalived --dont-fork --log-console --pid /keepalived.pid -d
tail -f /var/log/yum.log
entry-point.sh指令碼的確是pid=1了,但keepalived程序的pid並不是1,因為它和上面的cmd command一樣,是bash -c方式以entry-point.sh的子程序的身份執行的。正確的做法是什麼呢?
要exec,不要fork:
#!/bin/bash -x
#entry-point.sh
#replace variables in /etc/keepalived/keepalived.conf
/sbin/ipvsadm-restore < /etc/sysconfig/ipvsadm
exec /usr/sbin/keepalived --dont-fork --log-console --pid /keepalived.pid -d
此時再來看容器內的程序,keepalived就是pid=1了,這時去docker stop容器,keepalived會捕捉到sigterm訊號,然後清理掉virtual ip,gracefully的退出。
docker exec 576 ps aux
user pid %cpu %mem vsz rss tty stat start time command
root 1 0.0 0.0 111660 4000 ? ss 09:41 0:00 /usr/sbin/keepalived --dont-fork --log-console --pid /keepalived.pid -d
root 18 0.0 0.0 113904 2952 ? s 09:41 0:02 /usr/sbin/keepalived --dont-fork --log-console --pid /keepalived.pid -d
root 19 0.0 0.0 113780 2192 ? s 09:41 0:04 /usr/sbin/keepalived --dont-fork --log-console --pid /keepalived.pid -d
root 24 0.0 0.0 35884 1456 ? rs 13:48 0:00 ps aux
ref:
乙隻垂直的小爬蟲
這只垂直的小爬蟲,使用如下實現 實現的思路很簡單,我從主函式開始簡單敘述一下整個執行流程,第一步 收集需要爬取的url位址,容器我選擇的是concurrentlinkedqueue非阻塞佇列,它底層使用unsafe實現,要的就是它執行緒安全的特性 主函式 如下 static string url 新...
《殺死乙隻知更鳥》讀後感
知更鳥在美國南方是很受歡迎的鳥,只唱歌,對人毫無害處。打獵殺死乙隻知更鳥是一件很糟糕的事,意味著傷害了無辜。知更鳥是天真,善良的代表。書名這樣取,就不難猜出內容來。美國南方小鎮梅科姆,鎮上大部分人都有千絲萬縷的聯絡,低頭抬頭都是熟人,好訊息,壞訊息,各種坊間傳聞很快都會家喻戶曉。斯庫特和哥哥傑姆,父...
乙隻有野心的小爬蟲
這個問題在程式設計師中的爭議很大,這裡不拉開情懷與逼格的爭議。對於大多數人,我的建議是 先暫時使用ide,這可以在學習的過程中讓你的精力主要集中在 的編寫上,對於執行和除錯也非常方便。但是你至少應該會一點vim的基礎操作,這樣可以方便你在伺服器部署 ctrl space 基本的 完成 類 方法 屬性...