背景
為解決日誌在ELK中偶發性丟失問題,需要對應用日誌進行備份,當關鍵日誌缺失後可以對原始日誌進行查詢。應用日誌除了傳ELK,在本地(docker內部)也保存一份原始文件,路徑為/opt/logs並且通過logback相關配置可以對日誌進行自動歸檔。
方案
總體方案
總體方案是在主機上通過本地文件系統找到應用在容器內的日誌文件,藉助rsync進行備份,再通過crontab配置定時任務就能實現日誌的備份。
實現
docker本質就是一個根文件系統(roots)+Linux內核程序,docker內部所有的文件在宿主機上都有對應的映射,通過docker inspect命令可以查看容器在宿主機上文件系統路徑
# docker inspect 84be0c7fd47b --format '{{.GraphDriver.Data.MergedDir }}'
/u01/docker/overlay2/a3226f727cf8217bff0bfc8036201834260a50502c7e6b5eb0672a5305665c0e/merged
我們進入該目錄
root@k8s-node-dev-10:/u01/docker/overlay2/a3226f727cf8217bff0bfc8036201834260a50502c7e6b5eb0672a5305665c0e/merged# ll
total 156444
drwxr-xr-x 1 root root 3488 Oct 19 01:42 ./
drwx--x--- 5 root root 3488 Oct 19 01:42 ../
-rw-r--r-- 1 root root 159961293 Oct 19 01:41 app.jar
drwxr-xr-x 1 root root 3488 May 15 01:13 bin/
drwxr-xr-x 1 root root 3488 Oct 19 01:42 dev/
-rwxr-xr-x 1 root root 0 Oct 19 01:42 .dockerenv*
-rwxrwxrwx 1 root root 1756 May 29 00:59 entrypoint.sh*
drwxr-xr-x 1 root root 3488 Oct 19 01:42 etc/
drwxr-xr-x 2 root root 3488 May 9 2019 home/
drwxr-xr-x 1 root root 3488 May 15 01:15 lib/
drwxr-xr-x 2 root root 3488 May 15 01:13 lib64/
drwxr-xr-x 5 root root 3488 May 9 2019 media/
drwxr-xr-x 2 root root 3488 May 9 2019 mnt/
drwxr-xr-x 1 root root 3488 Oct 19 01:42 opt/
dr-xr-xr-x 2 root root 3488 May 9 2019 proc/
drwx------ 1 root root 3488 Oct 30 06:49 root/
drwxr-xr-x 1 root root 3488 Oct 19 01:42 run/
drwxr-xr-x 1 root root 3488 May 15 01:13 sbin/
drwxr-xr-x 2 root root 3488 May 9 2019 srv/
drwxr-xr-x 2 root root 3488 May 9 2019 sys/
drwxrwxrwt 2 root root 3488 May 9 2019 tmp/
drwxr-xr-x 1 root root 3488 Oct 19 01:42 usr/
drwxr-xr-x 1 root root 3488 May 9 2019 var/
可以發現就是容器內的Linux文件系統,我們可以通過以下腳本,顯示出所有容器在主機上的文件系統路徑
for container in $(docker ps --all --quiet --format '{{ .Names }}'); do
log_path=$(docker inspect $container --format '{{.GraphDriver.Data.MergedDir }}')
echo $log_path
done
由於日誌保存在容器內部的/opt/logs目錄下,對應主機路徑就是log_path/opt/logs目錄,找到文件路徑後,我們需要知道該文件對應的服務名稱,從路徑上我們只能得到一個類似a3226f727cf8217bff0bfc8036201834260a50502c7e6b5eb0672a5305665c0e的ID,那怎麼根據這個ID獲取到服務名稱呢,kubernetes在啓動應用docker容器時,會給容器打上標籤,其中就包含了應用名稱,我們通過inspect命令就可以查看
# docker inspect 84be0c7fd47b
....
"Labels": {
"annotation.io.kubernetes.container.hash": "f44b328d",
"annotation.io.kubernetes.container.ports": "[{\"name\":\"8080tcp2\",\"containerPort\":8080,\"protocol\":\"TCP\"}]",
"annotation.io.kubernetes.container.restartCount": "0",
"annotation.io.kubernetes.container.terminationMessagePath": "/dev/termination-log",
"annotation.io.kubernetes.container.terminationMessagePolicy": "File",
"annotation.io.kubernetes.pod.terminationGracePeriod": "30",
"io.kubernetes.container.logpath": "/var/log/pods/xinyou-application_xinyou-service-message-7745fd9699-drkzg_26e825a2-8f5e-4b5b-aed1-758139fc019c/xinyou-service-message/0.log",
"io.kubernetes.container.name": "xinyou-message-service",
"io.kubernetes.docker.type": "container",
"io.kubernetes.pod.name": "xinyou-message-service-7745fd9699-drkzg",
"io.kubernetes.pod.namespace": "xinyou-application",
"io.kubernetes.pod.uid": "26e825a2-8f5e-4b5b-aed1-758139fc019c",
"io.kubernetes.sandbox.id": "7bc9954a02c7e43a1a21b8cc47e09f86ada8f6e8584c49150c67d77dc13b6832"
}
....
其中io.kubernetes.container.name就是服務名稱,io.kubernetes.pod.name是pod名稱,io.kubernetes.container.name就是命名空間,所以拿到ID後我們可以通過inspect獲取到服務 名稱,有了原始文件路徑,服務名稱後,就可以寫備份腳本
#!/bin/bash
for container in $(docker ps --all --quiet --format '{{ .Names }}'); do
log_path=$(docker inspect $container --format '{{.GraphDriver.Data.MergedDir }}')
log_path=${log_path}/opt/logs/
service_name=$(docker inspect $container --format '{{ index .Config.Labels "io.kubernetes.container.name" }}')
pod_name=$(docker inspect $container --format '{{ index .Config.Labels "io.kubernetes.pod.name" }}')
target_path=/u01/logbackup/$service_name/$pod_name
if [ -d "$log_path" ]; then
echo "sync $service_name/$pod_name from $log_path to $target_path"
mkdir -p $target_path
rsync -av "$log_path" "$target_path" >log_backup.log
fi
done
這裏簡單解釋下腳本內容
-
- 通過
docker ps --all獲取到所有容器列表,因為加了--all命令,所以也包含停止運行的容器,這裏為什麼要包含停止運行的容器,原因是腳本執行是週期性的,假設在執行週期內容器停止運行了,那麼就會失去這段時間的日誌,所以就算是停止運行的容器也要同步日誌。
- 通過
-
- log_path就是日誌在主機上的路徑
-
- service_name就是服務名稱,讀取的是io.kubernetes.container.name這個標籤
-
- target_path備份目標路徑,這裏路徑加上了pod_name,原因是一個主機上可能運行服務的多個副本,如果不加上pod_name,有可能兩個副本的日誌會相互覆蓋,因為日誌名稱是一樣的。
-
- 通過rsync命令進行同步,rsync命令可以實現增量同步,速度和效率都遠遠比cp命令高
-
- 腳本只對包含
opt/logs的容器進行日誌備份,如果有額外情況需要更改腳本
- 腳本只對包含
配置下crontab就可以定時執行腳本進行備份任務
* * * * * /u01/scripts/log_backup.sh
這裏是一分鐘執行一次,這個頻率其實可以更低,因為我們有將停止的容器包含進去,所以頻率低一些也不會有日誌丟失。以下是日誌同步後的日誌備份目錄文件夾分佈情況:
root@k8s-node-dev-10:/u01/logbackup# tree -d
.
├── xinyou-mes
│ └── xinyou-mes-6c5cd57447-qgk29
├── xinyou-gateway
│ └── xinyou-gateway-67bff75986-qsgsq
├── xinyou-bbc-service
│ └── xinyou-bbc-service-7455c4dc86-kj6tp
├── xinyou-ddr-service
│ └── xinyou-ddr-service-c774bbbb7-mh7fk
├── xinyou-iam-service
│ └── xinyou-iam-service-59b844df47-5h5gs
├── xinyou-review-service
│ └── xinyou-review-service-856c5f545c-lxmz6
├── xinyou-sass-oa
│ └── xinyou-sass-oa-77845c55bb-jjmf2
├── xinyou-service-message
│ └── xinyou-service-message-7745fd9699-drkzg
└── xinyou-tables-service
└── xinyou-tables-service-7fd66d4c95-7pcst