本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

容器取證分析

在我之前的文章《Kubernetes 中的容器取證檢查點(Alpha)》中,我介紹了 Kubernetes 中的檢查點,以及如何設定和使用它。該功能的名稱是“容器取證檢查點”,但我沒有詳細介紹如何對 Kubernetes 建立的檢查點進行實際分析。在本文中,我將詳細介紹如何分析檢查點。

檢查點仍然是 Kubernetes 中的一項 Alpha 功能,本文旨在提供該功能未來可能如何工作的預覽。

準備工作

有關如何配置 Kubernetes 和底層 CRI 實現以啟用檢查點支援的詳細資訊,請參閱我的文章《Kubernetes 中的容器取證檢查點(Alpha)》。

例如,我準備了一個容器映象(quay.io/adrianreber/counter:blog),我將在本文中對其進行檢查點並進行分析。這個容器允許我在容器中建立檔案,並在記憶體中儲存資訊,我稍後會在檢查點中找到這些資訊。

為了執行該容器,我需要一個 Pod,在此示例中,我使用以下 Pod 清單

apiVersion: v1
kind: Pod
metadata:
  name: counters
spec:
  containers:
  - name: counter
    image: quay.io/adrianreber/counter:blog

這導致一個名為 counter 的容器在一個名為 counters 的 Pod 中執行。

容器執行後,我將對該容器執行以下操作

$ kubectl get pod counters --template '{{.status.podIP}}'
10.88.0.25
$ curl 10.88.0.25:8088/create?test-file
$ curl 10.88.0.25:8088/secret?RANDOM_1432_KEY
$ curl 10.88.0.25:8088

第一次訪問在容器中建立了一個名為 test-file 的檔案,內容為 test-file,第二次訪問將我的秘密資訊(RANDOM_1432_KEY)儲存在容器記憶體中的某個位置。最後一次訪問只是在內部日誌檔案中添加了一行。

在分析檢查點之前的最後一步是告訴 Kubernetes 建立檢查點。如前一篇文章所述,這需要訪問 kubeletcheckpoint API 端點。

對於預設名稱空間中名為 counters 的 Pod 中名為 counter 的容器,kubelet API 端點可在以下位置訪問

# run this on the node where that Pod is executing
curl -X POST "https://:10250/checkpoint/default/counters/counter"

為完整起見,以下 curl 命令列選項是必要的,以使 curl 接受 kubelet 的自簽名證書並授權使用 kubelet checkpoint API

--insecure --cert /var/run/kubernetes/client-admin.crt --key /var/run/kubernetes/client-admin.key

檢查點完成後,檢查點應在 /var/lib/kubelet/checkpoints/checkpoint-<pod-name>_<namespace-name>-<container-name>-<timestamp>.tar 下可用

在本文的以下步驟中,我將使用名稱 checkpoint.tar 來分析檢查點歸檔檔案。

使用 checkpointctl 分析檢查點歸檔檔案

為了獲取有關檢查點容器的一些初始資訊,我使用工具 checkpointctl,如下所示

$ checkpointctl show checkpoint.tar --print-stats
+-----------+----------------------------------+--------------+---------+---------------------+--------+------------+------------+-------------------+
| CONTAINER |              IMAGE               |      ID      | RUNTIME |       CREATED       | ENGINE |     IP     | CHKPT SIZE | ROOT FS DIFF SIZE |
+-----------+----------------------------------+--------------+---------+---------------------+--------+------------+------------+-------------------+
| counter   | quay.io/adrianreber/counter:blog | 059a219a22e5 | runc    | 2023-03-02T06:06:49 | CRI-O  | 10.88.0.23 | 8.6 MiB    | 3.0 KiB           |
+-----------+----------------------------------+--------------+---------+---------------------+--------+------------+------------+-------------------+
CRIU dump statistics
+---------------+-------------+--------------+---------------+---------------+---------------+
| FREEZING TIME | FROZEN TIME | MEMDUMP TIME | MEMWRITE TIME | PAGES SCANNED | PAGES WRITTEN |
+---------------+-------------+--------------+---------------+---------------+---------------+
| 100809 us     | 119627 us   | 11602 us     | 7379 us       |          7800 |          2198 |
+---------------+-------------+--------------+---------------+---------------+---------------+

這已經為我提供了有關檢查點歸檔檔案中檢查點的一些資訊。我可以看到容器的名稱、有關容器執行時和容器引擎的資訊。它還列出了檢查點的大小(CHKPT SIZE)。這主要是檢查點中包含的記憶體頁面的大小,但也有關於容器中所有更改檔案大小的資訊(ROOT FS DIFF SIZE)。

附加引數 --print-stats 解碼檢查點歸檔檔案中的資訊並將其顯示在第二個表(CRIU 轉儲統計資訊)中。此資訊在檢查點建立期間收集,並概述了 CRIU 在檢查容器中的程序時需要多少時間,以及在檢查點建立期間分析和寫入了多少記憶體頁面。

深入挖掘

藉助 checkpointctl,我能夠獲取有關檢查點歸檔檔案的一些高階資訊。為了進一步分析檢查點歸檔檔案,我必須將其提取。檢查點歸檔檔案是一個 tar 歸檔檔案,可以使用 tar xf checkpoint.tar 進行提取。

提取檢查點歸檔檔案將生成以下檔案和目錄

  • bind.mounts - 此檔案包含有關繫結掛載的資訊,在恢復期間需要將所有外部檔案和目錄掛載到正確的位置
  • checkpoint/ - 此目錄包含 CRIU 建立的實際檢查點
  • config.dumpspec.dump - 這些檔案包含在恢復期間所需的容器元資料
  • dump.log - 此檔案包含 CRIU 在檢查點建立期間生成的除錯輸出
  • stats-dump - 此檔案包含 checkpointctl 用於顯示轉儲統計資訊(--print-stats)的資料
  • rootfs-diff.tar - 此檔案包含容器檔案系統中所有更改的檔案

檔案系統更改 - rootfs-diff.tar

進一步分析容器檢查點的第一步是檢視容器中已更改的檔案。這可以透過檢視檔案 rootfs-diff.tar 來完成

$ tar xvf rootfs-diff.tar
home/counter/logfile
home/counter/test-file

現在可以研究容器中更改的檔案

$ cat home/counter/logfile
10.88.0.1 - - [02/Mar/2023 06:07:29] "GET /create?test-file HTTP/1.1" 200 -
10.88.0.1 - - [02/Mar/2023 06:07:40] "GET /secret?RANDOM_1432_KEY HTTP/1.1" 200 -
10.88.0.1 - - [02/Mar/2023 06:07:43] "GET / HTTP/1.1" 200 -
$ cat home/counter/test-file
test-file 

與此容器所基於的容器映象(quay.io/adrianreber/counter:blog)相比,我可以看到檔案 logfile 包含有關容器提供的服務的所有訪問資訊,並且檔案 test-file 正如預期地建立。

藉助 rootfs-diff.tar,可以檢查與容器基礎映象相比建立或更改的所有檔案。

分析檢查點程序 - checkpoint/

目錄 checkpoint/ 包含 CRIU 在檢查容器中的程序時建立的資料。目錄 checkpoint/ 中的內容由不同的映象檔案組成,可以使用作為 CRIU 一部分分發的工具 CRIT 進行分析。

首先,讓我們瞭解一下容器內的程序

$ crit show checkpoint/pstree.img | jq .entries[].pid
1
7
8

此輸出表示我的容器 PID 名稱空間中有三個程序,其 PID 分別為:1、7、8。

這僅僅是從容器 PID 名稱空間內部的角度來看。在恢復期間,這些 PID 將完全重新建立。從容器 PID 名稱空間外部來看,恢復後 PID 將發生變化。

下一步是獲取有關這三個程序的一些附加資訊

$ crit show checkpoint/core-1.img | jq .entries[0].tc.comm
"bash"
$ crit show checkpoint/core-7.img | jq .entries[0].tc.comm
"counter.py"
$ crit show checkpoint/core-8.img | jq .entries[0].tc.comm
"tee"

這意味著我的容器中的三個程序是 bashcounter.py(一個 Python 直譯器)和 tee。有關這些程序的父子關係的詳細資訊,需要分析 checkpoint/pstree.img 中的更多資料。

讓我們將目前收集到的資訊與仍在執行的容器進行比較

$ crictl inspect --output go-template --template "{{(index .info.pid)}}" 059a219a22e56
722520
$ ps auxf | grep -A 2 722520
fedora    722520  \_ bash -c /home/counter/counter.py 2>&1 | tee /home/counter/logfile
fedora    722541      \_ /usr/bin/python3 /home/counter/counter.py
fedora    722542      \_ /usr/bin/coreutils --coreutils-prog-shebang=tee /usr/bin/tee /home/counter/logfile
$ cat /proc/722520/comm
bash
$ cat /proc/722541/comm
counter.py
$ cat /proc/722542/comm
tee

在此輸出中,我首先檢索容器中第一個程序的 PID,然後我在容器執行的系統上查詢該 PID 和子程序。我看到了三個程序,第一個是“bash”,它是容器 PID 名稱空間中的 PID 1。然後我檢視 /proc/<PID>/comm,我可以找到與檢查點映象中完全相同的值。

重要的是要記住,檢查點將包含容器 PID 名稱空間內部的檢視,因為該資訊對於恢復程序很重要。

crit 能夠告訴我們關於容器的最後一個例子是 UTS 名稱空間的資訊

$ crit show checkpoint/utsns-12.img
{
    "magic": "UTSNS",
    "entries": [
        {
            "nodename": "counters",
            "domainname": "(none)"
        }
    ]
}

這告訴我 UTS 名稱空間中的主機名是 counters

對於 CRIU 在檢查點期間收集的每個資源,checkpoint/ 目錄都包含相應的映象檔案,可以使用 crit 進行分析。

檢視記憶體頁

除了可以藉助 CRIT 解碼的 CRIU 資訊外,還有包含 CRIU 寫入磁碟的原始記憶體頁的檔案

$ ls  checkpoint/pages-*
checkpoint/pages-1.img  checkpoint/pages-2.img  checkpoint/pages-3.img

當我最初使用容器時,我將一個隨機金鑰(RANDOM_1432_KEY)儲存在記憶體中的某個位置。讓我們看看我是否能找到它

$ grep -ao RANDOM_1432_KEY checkpoint/pages-*
checkpoint/pages-2.img:RANDOM_1432_KEY

果然,我的資料就在那裡。透過這種方式,我可以輕鬆地檢視容器中程序的所有記憶體頁面的內容,但同樣重要的是要記住,任何可以訪問檢查點歸檔檔案的人都可以訪問容器程序記憶體中儲存的所有資訊。

使用 gdb 進行進一步分析

檢視檢查點映象的另一種可能性是 gdb。CRIU 倉庫包含指令碼 coredump,它可以將檢查點轉換為核心轉儲檔案

$ /home/criu/coredump/coredump-python3
$ ls -al core*
core.1  core.7  core.8

執行 coredump-python3 指令碼會將檢查點映象轉換為容器中每個程序的一個核心轉儲檔案。使用 gdb 我還可以檢視程序的詳細資訊

$ echo info registers | gdb --core checkpoint/core.1 -q

[New LWP 1]

Core was generated by `bash -c /home/counter/counter.py 2>&1 | tee /home/counter/logfile'.

#0  0x00007fefba110198 in ?? ()
(gdb)
rax            0x3d                61
rbx            0x8                 8
rcx            0x7fefba11019a      140667595587994
rdx            0x0                 0
rsi            0x7fffed9c1110      140737179816208
rdi            0xffffffff          4294967295
rbp            0x1                 0x1
rsp            0x7fffed9c10e8      0x7fffed9c10e8
r8             0x1                 1
r9             0x0                 0
r10            0x0                 0
r11            0x246               582
r12            0x0                 0
r13            0x7fffed9c1170      140737179816304
r14            0x0                 0
r15            0x0                 0
rip            0x7fefba110198      0x7fefba110198
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

在此示例中,我可以看到檢查點期間所有暫存器的值,我還可以看到容器 PID 1 程序的完整命令列:bash -c /home/counter/counter.py 2>&1 | tee /home/counter/logfile

總結

藉助容器檢查點,可以在不停止容器且容器不知情的情況下建立正在執行的容器的檢查點。在 Kubernetes 中檢查容器的結果是檢查點歸檔檔案;使用 checkpointctltarcritgdb 等不同工具可以分析檢查點。即使使用 grep 等簡單工具,也可以在檢查點歸檔檔案中找到資訊。

我在本文中展示的如何分析檢查點的不同示例只是一個起點。根據您的要求,可以更詳細地檢視某些內容,但本文旨在為您介紹如何開始分析檢查點。

我如何參與?

你可以透過多種方式聯絡 SIG Node