容器進程Core Dump處理

本文主要介紹了Core Dump實現容器進程的方法和相關內容。
上篇文章回顧: IPv6入門教程

引子

在咱們調試程序時常常會使用到core dump功能,使用調試器(如gdb)分析其產生的Core Dump文件(如下稱"core文件"),對於排查問題、定位bug堪稱無往不利的利器。當前容器技術使用越發廣泛,線上大量業務使用容器技術部署,那咱們的業務進程在容器環境下core文件是如何產生、與在宿主機中有什麼不一樣呢?本文針對這個問題簡略說明,拋磚引玉。html

什麼是Core文件

Core文件是當一個進程在收到某些信號後終止時產生的文件,其中包含進程終止時刻進程內存的鏡像。咱們可使用gdb從該鏡像中觀察進程終止時處於什麼狀態,用於追蹤排查定位問題。
linux

以下示例,其中/usr/share/core_pipe/test是crash程序,core.29873就是core文件,其中29873是crash進程的PID。git

# gdb /usr/share/core_pipe/test core.29873GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-gitCopyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /usr/share/core_pipe/test...done.
[New LWP 34]

warning: .dynamic section for "/lib64/ld-linux-x86-64.so.2" is not at the expected address (wrong library or version mismatch?)warning: Could not load shared library symbols for /lib64/libc.so.6.
Do you need "set solib-search-path" or "set sysroot"?
Core was generated by `./test'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x0000556039e756aa in main () at main.c:7 7 printf("this is null p %d \n", *intp); (gdb)複製代碼

宿主機進程Core Dumpgithub

在講容器進程Core Dump以前,咱們先來簡單回顧下宿主機進程Core Dump在什麼條件下產生。這裏的宿主機泛指物理機和虛擬主機,宿主機進程特指運行在系統主機Initial Namespace的進程,redis

容器進程特指運行在容器Namespace中的進程。shell

前文說到當進程收到某種信號纔會產生,那麼什麼纔是"某種信號"呢?這些信號大約有十多個,這裏只列出咱們常見比較熟悉的幾個:ubuntu

- SIGQUIT  數值2  從鍵盤輸入 Ctrl+'\'能夠產生此信號

- SIGILL   數值4  非法指令

- SIGABRT  數值6  abort調用

- SIGSEGV  數值11 非法內存訪問

- SIGTRAP  數值5  調試程序時使用的斷點複製代碼

其中SIGSEGV應該是咱們最常接觸的,尤爲是使用C/C++程序的同窗更是常見。bash

main.c
#include <stdio.h>int main(){ int *p = NULL; 
  printf("hello world! \n");
  printf("this will cause core dump p %d", *p);
}複製代碼

使用下面命名編譯便可產生運行時觸發非法內存訪問SIGSEGV信號,其中-g選項是編譯添加調試信息,對於gdb調試很是有用。運維

# gcc -g main.c -o test複製代碼

除了上述產生信號的方法外,使用咱們常用的kill命令能夠很是方便的產生這些信號,另外還有gcore命令能夠在不終止進程的前提下產生core文件。socket

那麼只要進程收到這些信號就必定會有core文件嗎?顯然不是,linux在這方面提供了相關配置。那麼這些配置都有哪些呢?

一、設置Core文件大小上限

ulimit
# ulimit -c 0 // 0表示不產生core文件# ulimit -c 100 // 100表示設置core文件最大爲100k,固然能夠根據本身須要設置,注意設置單位是KB# ulimit -c unlimited // 不限制core文件大小複製代碼

使用上述命令設置core文件大小隻對當前shell環境有效,系統重啓或者退出當前shell設置就會失效,恢復默認設置。

若想永久生效能夠把該shell放到/etc/profile文件中,系統從新啓動初始化環境時會執行其中的命令,該設置就會在全局範圍內生效,達到永久生效的效果。也可使用 source /etc/profile命令當即全局生效。

# echo "unlimit -c unlimited" >> /etc/profile // 配置添加到/etc/profile中# source /etc/profile // 當即生效複製代碼

二、設置Core文件存儲目錄

# echo "/var/core-dir/core-%e-%p-%t" > /proc/sys/kernel/core_pattern複製代碼

該命令能夠控制core文件保存位置和文件名格式。注意須要使用root權限執行,而且存儲路徑必須是絕對路徑,不能是相對路徑

其中%e表示添加用戶程序名稱,%p表示添加程序進程PID,%t表示添加產生core文件時的時間戳,還有其餘一些很是有用的格式,能夠參閱CORE(5) 文檔。

這樣修改後系統重啓後也會消失,一樣也有永久生效的辦法

修改/etc/sysctl.conf文件在其中修改或者添加

/etc/sysctl.conf
kernel.core_pattern = /var/core-dir/core-%e-%p-%t複製代碼

而後執行下面命令配置當即生效,這樣系統重啓後配置依然有效

# sysctl –p /etc/sysctl.conf複製代碼

三、宿主機Core文件小結

宿主機產生core文件可使用以下步驟

1. ulimit -c 命令設置core文件大小2. 修改core_pattern選項配置core文件存儲目錄複製代碼

具有上述兩個條件後,當進程在必定條件core後,就會存儲core文件到咱們配置的目錄中。

Core文件產生流程

大概說一下從進程出現異常到生成core文件的流程,不會涉及太多Linux系統實現細節,具體細節能夠參考相關文檔和linux內核源碼。

  1. 進程運行時發生一個異常,好比非法內存地址訪問(即段錯誤),相應硬件會上報該異常,CPU檢測到該異常時會進入異常處理流程,包括保存當前上下文,跳轉到對應的中斷向量執行入口等

  2. 在異常處理流程中若是判斷該異常發生時是處於用戶態,則該異常只會影響當前進程,此時向用戶態進程發送相應的信號,如段錯誤就會發送SIGSEGV信號

  3. 當用戶態進程被調度時會檢查pending的信號,若是發現pending的信號是SIG_KERNEL_COREDUMP_MASK中的一個,就會進入core文件內容收集存儲流程,而後根據配置(core_pattern等)生成core文件

容器進程Core Dump

那麼若是咱們要收集在容器裏運行的進程的core文件應該如何設置呢?

答案是上述宿主機針對core文件的設置對容器中的進程依然有效。

衆所周知,宿主機上全部的容器都是共享系統內核的,/proc/sys文件下只有一小部分支持namespace隔離,而core_pattern恰巧不支持隔離的,因此不管是從宿主機仍是容器裏修改core_pattern,最終修改的是同一個設置,而且全局生效,不論是對宿主機仍是對容器都是有效的。

通常狀況下每一個容器都有本身的mount namespace,其中的文件系統與宿主機和其餘容器相隔離,那麼在core_pattern指定的core文件存儲目錄是容器中的文件目錄仍是宿主機中呢?不妨推測一二,剛纔咱們已經說過這個core_pattern是全局生效,若是該目錄是針對某個容器的文件目錄,那麼確定是不合理的,由於若是宿主機上進程Core Dump時就會找不到對應的目錄,沒法保存。

實際上有效的core_pattern中的目錄必須是宿主機中的絕對目錄,更準確的描述是宿主機Initial Namespace中的絕對路徑。

另一個問題是,每一個容器都有本身pid namespace,咱們再core_pattern中設置的獲取crash進程的各類信息好比PID,可執行文件名,是容器namespace中的仍是宿主機namespace中的呢?從相關文檔和實驗得知,能夠同時獲取crash進程在容器中的PID(經過%p格式指定)和在宿主機Initial Namespace中的PID(經過%P格式指定),可執行文件名稱(經過%e或%E格式指定)是容器的namespace中的。

之因此形成上述狀況,根本緣由是Core Dump流程中內核進程最後負責處理core文件存儲的,而內核進程運行在宿主機Initial Namespace中,實際上全部的容器進程在宿主機Initial Namespace都有映射,對內核來說,宿主機進程和容器進程能夠統一處理,並無本質區別。

一、使用管道解決容器進程Core Dummp問題

上文中咱們得知了容器進程core文件產生的方法,可是有一個問題就是上述方法的設置是對宿主機和容器內全部的進程都生效的。沒法針對特定容器進程特定設置。好比說咱們但願宿主機進程core文件保存到/var/crash目錄,而對容器的core文件保存在/var/container/crash目錄,或者我要限制某個容器產生core文件的總存儲大小,而不是單個core文件的大小;若是咱們作一個服務平臺對其餘用戶開放Core Dump功能的話,咱們確定還但願獲取一下crash進程的其餘額外信息好比進程當前環境變量、當前用戶、當前進程有效UID和GID、任務名稱屬性;若是咱們但願針對core事件進行統計分析的話,可能還須要各類回調通知等等操做。

顯然上述簡單的設置core文件存儲目錄的方法沒法知足咱們的需求的,那麼咱們還有另一個選擇,就是使用linux的piping技術轉儲core文件。

從linux內核版本2.6.19以後,內核就開支支持在/proc/sys/kernel/core_pattern文件中指定一個管道程序來實際處理core文件存儲。core文件內容會做爲該管道程序的標準輸入傳輸給管道程序,管道程序就接管了接下來的core文件內容的全部處理。以下設置可使用piping技術轉儲core文件

# echo "|/usr/share/core_pipe/core_pipe core -H=%h -p=%p -i=%i -s=%s -c=%c > /proc/sys/kernel/core_pattern
# cat /proc/sys/kernel/core_pattern
|/usr/share/core_pipe/core_pipe core -H=%h -p=%p -i=%i -s=%s  -c=%c複製代碼

其中/usr/share/core_pipe/core_pipe是咱們的管道程序,須要注意的是必須以|開發, |以後必須緊接管道程序的路徑,沒有空格。當有進程core時,就會調用該管道程序進行處理。

咱們能夠開發本身的管道處理程序,從管道程序啓動的參數獲取crash的進程信息,從管道程序的標準輸入獲取core文件的內容。

咱們如今知曉該管道程序何時被調用(進程Core Dump時),那麼管道程序是由誰來調用呢?

既然管道程序是咱們本身開發的,咱們就能夠獲取管道程序的父進程是誰,也就是被誰調用的,經過實驗咱們可一知道父進程的PID是2,當咱們再看該進程的父進程是誰:

# ps -ef -q 2UID PID PPID C STIME TTY TIME CMD
root          2      0  0 Jan20 ?        00:00:00 [kthreadd]複製代碼

進程PID2的父進程是PID 0,而PID 0表明的是linux系統內核idle進程,Linux系統中共有三個特殊進程,分別是idle(PID 0), init(PID 1), kthreadd(PID 2),而kthreadd是全部內核進程的父進程,也就是說咱們的管道程序是做爲內核線程在運行的,運行在內核態,而且在宿主機Initial Namespace中以root用戶身份運行,不在任何容器內。

二、Socket Activation應用到容器進程Core Dummp

上文說了管道程序運行在內核態,並且是在宿主機的Initial Namespace中運行,容器的各類限制對其不起做用,好比core文件大小有可能超過容器的硬盤空間限制。固然咱們管道程序能夠經過crash進程的PID拿到crash進程的容器namespace以及各類cgroup限制,而後針對性處理。這樣顯然對容器極有侵入性,代碼寫起來也不夠優雅。若是處理core文件存儲程序在容器中運行,就能較優雅的解決好這個問題。管道程序已經做爲內核線程運行在宿主機的Initial Namespace了,雖然有辦法能夠動態的加入和退出某個namespace和cgroup,可是考慮的邊界條件多,易出錯,並不優雅。

若是管道程序可以和容器內某個程序進行交互,能夠解決上述問題,同一個宿主機進程通訊的方式有不少,好比共享內存,管道,消息隊列等。可是這裏的兩個程序是分佈在不一樣的namespace中,並且彼此並不知道何時能夠交互,咱們爲了低機率的core文件長時間讓容器內某個進程空跑佔用資源嗎?那麼socket activation技術能夠用來解決這個問題。

socket activation並非一種新技術,其技術理念和原理早就被應用到Linux和MacOS中,關於socket activation技術原理細節又是須要另外一篇的長篇大論,這裏暫且再也不詳述,簡單來講,就是由系統init進程(對於目前大多數linux系統來講是systemd)來爲普通應用進程監聽特定socket,此時應用進程並未啓動,當有鏈接到達該socket後,由init進程接管該鏈接並跟進配置文件啓動相應的應用進程,而後把鏈接傳遞給應用進程來處理,主要好處是當沒有鏈接到達時,應用進程無需常駐後臺空跑耗費系統資源。很是適合像Core Dump這種低頻服務。

咱們能夠設置一個unix socket來把管道程序的文件描述符傳遞到容器內進程,完成傳遞後, 管道程序就能夠退出,由容器內進程處理core文件的存儲。

下面是一個socket activation示例,其中/usr/share/core_pipe/core_pipe是咱們的core 文件處理程序, /run/core_pipe.socket是咱們unix socket文件,存在容器中,該文件咱們在Initial Namespace中的管道程序能夠經過/proc/${crash pid}/root/run/core_pipe.socket拿到,而後與之交互。

core_pipe-forward.socket
# 此爲Unit文件,保存內容爲文件到 /etc/systemd/system/core_pipe-forward.socket
[Unit]
Description=Unix socket for core_pipe crash forwarding
ConditionVirtualization=container

[Socket]
ListenStream=/run/core_pipe.socket
SocketMode=0600Accept=yes
MaxConnections=10Backlog=5PassCredentials=true[Install]
WantedBy=sockets.target複製代碼
# 此爲service文件,保存內容到 /etc/systemd/system/core_pipe-forward.service
[Unit]
Description=Core Pipe crash forwarding receiver
Requires=core_pipe-forward.socket

[Service]
Type=oneshot
ExecStart=/usr/share/core_pipe/core_pipe複製代碼

core_pipe-forward.socket

執行下面命令使得socket生效

# systemctl enable core_pipe-forward.socket
# systemctl start core_pipe-forward.socket
# systemctl status core_pipe-forward.socket複製代碼

上述命令若是是在容器內的init進程不是systemd狀況下會出錯,大多數狀況下容器內的init進程並非systemd,此時能夠退一步使用容器內常駐進程的方式來實現core文件的處理。

總結

本文簡單說明了實現容器進程Core Dump的方法,概況一下主要有三點:

  1. 使用ulimit -c和/proc/sys/kernel/core_pattern設置Core Dump文件大小限制和存儲位置

  2. 使用管道程序加強Core Dump文件處理能力

  3. 使用管道程序和容器內進程結合的方式完成內核態轉到用戶態,在容器內處理Core文件存儲

參考文獻:

CORE(5) man7.org/linux/man-p…

GETRLIMIT(2) man7.org/linux/man-p…

GDB(1) man7.org/linux/man-p…

KILL(2) man7.org/linux/man-p…

SIGNAL(7) man7.org/linux/man-p…

NAMESPACE(7) man7.org/linux/man-p…

BASH(1) man7.org/linux/man-p…

go-systemd github.com/coreos/go-s…

systemd-socket-activation-in-go www.darkcoding.net/software/sy…

Core Dump 流程分析 blog.csdn.net/omnispace/a…

Socket activation in systemd hustcat.github.io/socket-acti…

socket activation 0pointer.de/blog/projec…

文章首發於公衆號「小米運維」,點擊查看原文

相關文章
相關標籤/搜索