2019年10月1日 星期二

將 AWS ECS 的 log 導向 Splunk

花了好多天的時間在研究,然後終於稍微搞清楚想要將 AWS ECS 上的應用程式的 log 導去 Splunk 時,需要注意的地方有哪些,以及它導出時是怎麼做的。不過在紀錄之前要先提一下,這篇當中不會提到如何建置 Splunk 服務,因為在我的狀況中 Splunk 是其他公司內的團隊已經建好的。

ECS Service 的 Log

首先先談一下為什麼會有想要把 ECS 的 log 導去 Splunk 的需求呢?因為在 ECS 上的服務基本上應該都會想要用 auto-scaling,但 auto-scale 之後帶來的困擾就是 log 四散各地,而且更重要的是 log 會在 container 裡,一旦 container 因為 scale-in 關閉的話,log 就跟著一起消失了。因此,我們自然會想要把 log 導出來存到其他地方去。當然我們是可以選擇 AWS CloudWatch,只是這邊的選擇是 Splunk,因為 Splunk 提供了比較強大的 log 搜尋功能(當然也比較貴就是了)。

Docker Log Driver

AWS ECS 是基於 Docker 的服務,因此腦筋自然就動到了如何利用 Docker 自身的功能來達成這件事?而 Docker 官方的解答就是透過 Docker Log Driver。Docker 預設的 Log Driver 是 json-file,也就是 Docker 裡的 log 會被寫成一個 JSON 格式的檔案,讓我們之後可以透過 docker logs 指令來查閱那些 log。而我們這裡的目標是要把 log 輸出到 Splunk,因此會需要使用的自然是 Splunk 的 Log Driver [1] 了。

談到這裡,也許腦袋比較靈活的人會先想到一個問題:「container 裡有這麼多資訊,Docker Log 會紀錄哪裡的資訊?」沒錯,這其實就是最重要的問題,也是害我花了幾天的時間搞到死魚眼的問題,因為我一直沒有搞清楚 Docker Log 到底是在看哪裡的 log…..。參考 Docker 的官方文件 [2],其實它是有某種程度提供了完整的描述,不過可能需要對 Linux 系統再熟悉一點吧,我當初看這段描述時並沒有意會到最重要的事情…。

By default, docker logs or docker service logs shows the command’s output just as it would appear if you ran the command interactively in a terminal. UNIX and Linux commands typically open three I/O streams when they run, called STDIN, STDOUT, and STDERR. STDIN is the command’s input stream, which may include input from the keyboard or input from another command. STDOUT is usually a command’s normal output, and STDERR is typically used to output error messages. By default, docker logs shows the command’s STDOUT and STDERR.

從官方文件的描述可以看到,docker logs 會輸出 STDOUTSTDERR 的訊息,換句話說,我們只要把訊息送進 STDOUT 或者 STDERR,我們就能夠從 docker logs 看到那些訊息,也就能期待 Docker Log Driver 幫我們把訊息轉送到 Splunk。但接下來還有個問題,在 Linux 中所有 process 都有自己的 STDOUTSTDERR,哪個才是 Docker Log Driver 會觀察的對象?這個問題目前我還沒直接從文件上看到,是公司同事提醒說 docker logs 只會看 main process 輸出的 STDOUTSTDERR。而何謂 main process 則可以參考到 [3]。

A container’s main running process is the ENTRYPOINT and/or CMD at the end of the Dockerfile.

這樣看下來大概就可以搞懂整個 Docker Log Driver 的運作形式了。當我用 Dockerfile 啟動 Docker,main process 就會留在 Dockerfile 中的最後一個指令上,也就是 main process 就是讓 Docker 維持運行的進入點。

Dockerfile 與 Docker Logs 的關聯

接下來回到主題,當我想要把 log 輸出到 Docker Log Driver 時,具體來說我該做什麼呢?很明確地,我需要讓 log 出現在 container 的 main process 的 STDOUT 裡。舉例來說,假設我寫了一個 Java 程式,程式本身不會結束(例如是個 Spring Boot Application,所以有內嵌 Embedded Web Server),那麼我只要在 Dockerfile 的尾巴寫上執行 Java 的指令,就可以達成這個目的了:

FROM openjdk:8-alpine
...(略)...
RUN java -jar my-spring-boot-app.jar

但如果原本我的最後一行程式是像是 service tomcat start 這種會開背景程序的指令,那麼問題就略為麻煩一點~目前我採取的策略是用 tail  來保證。例如在最後一行執行一個 script,然後 script 的內容是這樣:

while :
do
    tail -F <path_to_log_file> | while read line; echo $line; done
done

也就是說,我的背景程序假設它會輸出所有的 log 到某個檔案,那我就在 Dockerfile 中啟動那個背景服務後,最後開一個 tail 的程序一直去看那個檔案,並且把看到的東西都印出來,這樣就能讓檔案的內容一直被輸出到 main process 的 STDOUT 了。

參考資料
  1. Splunk logging driver
  2. View logs for a container or service
  3. Run multiple services in a container
  4. Docker? Amazon ECS? Splunk? How they now all seamlessly work together
  5. Looping through the content of a file in Bash

沒有留言: