Docker日志自動化: ElasticSearch、Logstash、Kibana以及Logspout

jopen 9年前發布 | 180K 次閱讀 Docker

本文主要介紹了如何使用ElasticSearch、Logstash、Kibana和Logspout技術棧來部署自動化的日志系統。
You, too, could Logstash.

快速念五遍這個題目!不過說實話,我其實并不確定該給這篇文章起個什么樣的名字才能確保人們可以找到它。

這篇文章是基于 Evan Hazlett’s article on running the ELK stack in DockerClusterHQ’s article on doing it with Fig/Docker Compose and Flocker這兩篇文章的。本文同時也受到了 Borg paper的影響,它在討論使用『標準棧』做事情的時候提出了我們感興趣的的一些工具。

問題

相信不用我說你也明白為什么要對妥善處理日志,日志可以幫助我們排查錯誤,分析問題。

當然,也有很多日志的解決方案,其中包括Logstash這樣非常可愛的技術。而后又出現了更多實用的技術。

我們都知道日志的重要性,所以,隨著對Docker興趣的增加,讓人們瘋狂的一件事情就是如何在Docker的世界處理日志。

有了Docker,人們突然被迫需要用另一種方式來考慮日志。在部署一個傳統的Linux時,應用程序或者架構記錄日志的方式通常記錄到文件里, 一般(但不一定)會記錄到/var/log目錄下。的確,我有一些關于檢查PHP日志“不錯”(譯者注:原文是帶引號的fond,應該是反語的意思)的回 憶,在那個項目中,日志是放在項目的目錄下,當出現錯誤的時候內置的應用程序日志并不能提供任何有用的東西。我其實并不喜歡把日志拆分成那樣(更多可移動 的部分意味著更難調試),可能更好的實踐是有一個統一訪問日志的方式。的確,有一篇關于這個想法(“統一日志層”)的有趣的 文章,作者是來自 Fluentd的Kiyoto Tamura,這是個類似Logstash的工具。

那對于Docker有什么不同呢?突然間,不同于以往將所有日志放在主機系統的統一位置,如今日志分散在很多不同容器的相互隔離的環境中。啊哦,聽起來好像跟我們想要的剛好相反。

Docker以往處理日志的方法是通過docker logs命令 - Docker捕捉每一個容器進程的STDOUT和STDERR,保存在磁盤上,然后用戶就能使用docker logs <container>來進行查詢。如果用于開發,你只是想往終端屏幕上打印一些輸出并快速得到結果,那么這種方式工作的還不錯;但是當你考 慮在更復雜的環境下使用Docker,或者想要查看更多傳統架構的Unix后臺程序的日志,而這些程序運行在后臺并且日志記錄在容器的內部磁盤上的時候, 麻煩就來了。這種情況下,問題主要是:

  1. 可發現性(discoverability) - 如果容器只是短暫的存在,那么試圖用我想要的日志跟蹤它,并且使用grep來解析它,這種方式好像不是那么有趣;
  2. 日志輪轉(log rotation - 有些服務程序特別健談(譯者注:原文是chatty,意思是這些程序會有很多輸出),亦或是會運行很長時間。那么我們就需要一種方式,當系統運行一段時間 后,對日志進行一次清理,來確保磁盤不會被那些我們不再使用的日志所填充。據我所知,現有的Docker還不支持這種特性。
  3. </ol>
    在這片文章中我暫且不談關于日志輪轉的內容,因為那是另一個罐罐里的蟲子,但是我會在這里描述該技術棧(譯者注:原文是stack,不知道譯成技術棧是否正確)的輪廓,主要是為了緩解處理第一個問題的過程。

    對于那些把日志記錄在容器內部磁盤的進程,如果想要為它們記錄標簽,有很多種方法可以讓它工作。我最喜歡的一種方式——雖然并沒有在這里真正列出來,但在我看來非常有用——是將原始容器日志記錄的目錄作為一個卷( volume), 并且讓其他容器使用--volumes-from選項來繼承這個卷。然后他們就可以使用tail -f /var/log/foo/access.log或一些其他方法來查看日志了。在我看來這促進了一個相當好的關注點的分離,因為你進行監視日志的容器與寫 日志的容器的不同的,另外(并不充分)你可以繞開union文件系統來進行操作(就像操作一個數據庫一樣)。但真的沒有必要在鏡像中跟蹤日志(狀態)。

    那關于discoverability你要做些什么呢?我們將會在Docker內運行一個 ELK stack,并且使用 logspout工 具來自動將容器的日志路由到Logstash。我真心覺得在未來這種方法會應用在很多方面 - 如果你將要運行容器、停止它們、刪除它們或者其它,那么你可能也會對本地事件感興趣,并且會讓容器的生命周期可以追蹤和監視。之后你的基礎設施就可以重新 激活而不再需要人工干預。同樣對于像負載平衡,服務發現等此方法也是可用的,但那將是另一篇文章的內容了。

    方法

    以下是Evan和ClusterHQ的方法,我們將會運行:

    1. 用ElasticSearch來索引收集的日志數據并使它更易于查詢
    2. 使用Logstash作為遠程syslog來收集來自容器的日志
    3. 使用Logspout來向Logstash發送容器的日志
    4. 使用Kibana作為與收集來的數據進行交互的一個漂亮的前端
    5. Cadvisor,一個監視容器資源的指標的儀表盤,這個純屬個人喜好(原文:for kicks)
    6. </ol>
      如果它們聽起來像是會運行嚇人數量的程序,請試著不要那么煩惱 - 我們將會直接使用 Docker Compose來開始這些工作。

      所以,如果你想在家跟隨我一起來做,你可以運行以下命令來開始 (你需要安裝最新版本的Docker和Docker Compose):

      $ git clone https://github.com/nathanleclaire/elk
      $ cd elk
      $ docker-compose -f docker-compose-quickstart.yml up


      這會在從Docker Hub上獲得的容器化好的鏡像上啟動應用程序,而且你的Kibana前端會從80號端口進行獲取而不管主機的DOCKER_HOST指向什么地方。

      對我而言,我喜歡部署(譯者注:原文kick up,不確定是否能譯成部署)一個 DigitalOcean droplet(譯者注:DigitalOcean提供了IaaS服務,droplets是DigitalOcean公司專有的云服務器術語)或者等價的使用 Docker Machine來做這樣的工作,因為拉鏡像所需的帶寬想必比連接你鄰居的WiFi的要求更高一些。如果你也想這么做,那么下面的命令可以創建你自己的服務器(再次強調,確保安裝了最新版本的軟件):

      $ export DIGITALOCEAN_ACCESS_TOKEN=MY_SECRET_API_TOKEN
      $ docker-machine create -d digitalocean \
      --digitalocean-size 4gb \
      --digitalocean-image docker \
      droplet
      ....
      ....
      ....
      To point your Docker client at it, run this in your shell: eval "$(docker-machine env droplet)"
      $ eval "$(docker-machine env droplet)"

      我通常建議使用上文提到的體面結實的服務器,因為這些進程可能會消耗大量的內存。它在本地工作的還不錯,但是pulls操作會有點慢,除非你使用的是光纖。

      如果你不是很喜歡運行不受信任鏡像(或者你僅僅是修改并構建自己的鏡像),沒有問題:發行版中默認的docker-compose.yml是基于構建參數的,所以你可以自己構建鏡像:

      $ docker-compose build
      $ docker-compose up

      當你把容器啟動后,你的終端上會有類似的輸出:

       Docker日志自動化: ElasticSearch、Logstash、Kibana以及Logspout

      你可能會看到關于Logspout未能鏈接到syslog的錯誤,不過這沒什么問題,很正常。該錯誤是因為Logstash容器還沒有啟動。當把它運行之后,上面的錯誤就會停止了。

      現在當你查看啟動了一組容器的宿主機的80號端口時,你應該會看到Kibana的歡迎界面:

       Docker日志自動化: ElasticSearch、Logstash、Kibana以及Logspout

      點擊上圖中箭頭指示的 “Logstash dashboard” 鏈接,或直接訪問<machineIp>/#/dashboard/file/default.json,你會被帶到你新建的Docker日 志架構的頁面(譯者注:真的不想把dashboard譯成儀表盤)!

      我一直在強調,關于容器最基本的“技術棧”在Evan的文章里被直接忽略了,這并沒有什么不好,但是當我自己想要實現這些技術的時候,遇到了一些問題:

      1. Logspout發送的數據格式跟Evan原文中提到的Logstash的grok過濾器有略微的不同/格式中鏡像是需要的,所以:
      2. 在日志中會有很多grok解析失敗的錯誤(這意味著Logstash試圖去匹配一個它知道的日志信息模式卻不能正確解析)。對于它自己來說這并不是什么大問題,但是:
      3. 由 于Logstash是一個被Logspout監視的容器,而Logspout會將Logstash所有的日志轉發給Logstash,這會造成一個瘋狂的 自旋循環并且幾乎會耗盡容器內所有的CPU資源(docker stats,一個非常有用的命令,可以實時報告容器資源的使用情況,我就是用它來捕捉到了上述的問題)。
         Docker日志自動化: ElasticSearch、Logstash、Kibana以及Logspout

        This can't be good.
      4. </ol>
        那一個黑客會做些什么呢?當然是黑(譯者注:這里應該是解決問題的意思)它了!我復制(fork)了Evan的原始的 Dockerfiles/repos并更改了一些東西。第一步,我把所有容器扔進了一個docker-compose.yml文件的服務中,如此以便快速 參考(這樣當我想再次啟動docker技術棧的時候就不必重復輸入所有docker run命令了)。我在 the Logspout Github repo的文檔中注意到你可以為容器指明一個環境變量來讓它的日志不要轉發給Logspout。所以,我在Logstash容器中設置了這個環境變量:LOGSPOUT=ignore。

        讀書也應該會注意到,gliderlabs/logspout鏡像現在想要Docker套接字掛載到/var/run/docker.sock (經典的位置), 而不是以前的/tmp/docker.sock - 在我意識到這點之前,我逐字使用了Evan文章中給出的命令,而這這給我造成了很大的麻煩。所以請注意這個問題!!

        現在這個環境(stack)不再出現那個無限循環來鞭撻我的CPU了。不過還有一個挑戰:那些在日志中grok解析失敗的消息。文章中提供的 Logstash配置例子不能很好地匹配(譯者注:原文jive well,不知道以為匹配是否合適)logspout的輸出。所以,為了更好地掌握發生了什么,我做了在這種情況下所有人都會做事情:閱讀 Logspout源碼。
        沒過多久我偶然發現了這個 block of code related to the syslog adapter:

        func NewSyslogAdapter(route *router.Route) (router.LogAdapter, error) {
            transport, found := router.AdapterTransports.Lookup(route.AdapterTransport("udp"))
            if !found {
                    return nil, errors.New("bad transport: " + route.Adapter)
            }
            conn, err := transport.Dial(route.Address, route.Options)
            if err != nil {
                    return nil, err
            }

            format := getopt("SYSLOG_FORMAT", "rfc5424")     priority := getopt("SYSLOG_PRIORITY", "{{.Priority}}")     hostname := getopt("SYSLOG_HOSTNAME", "{{.Container.Config.Hostname}}")     pid := getopt("SYSLOG_PID", "{{.Container.State.Pid}}")     tag := getopt("SYSLOG_TAG", "{{.ContainerName}}"+route.Options["append_tag"])     structuredData := getopt("SYSLOG_STRUCTURED_DATA", "")     if route.Options["structured_data"] != "" {             structuredData = route.Options["structured_data"]     }     data := getopt("SYSLOG_DATA", "{{.Data}}")

            var tmplStr string     switch format {     case "rfc5424":             tmplStr = fmt.Sprintf("<%s>1 {{.Timestamp}} %s %s %s - [%s] %s\n",                     priority, hostname, tag, pid, structuredData, data)     case "rfc3164":             tmplStr = fmt.Sprintf("<%s>{{.Timestamp}} %s %s[%s]: %s\n",                     priority, hostname, tag, pid, data)     default:             return nil, errors.New("unsupported syslog format: " + format)     }     tmpl, err := template.New("syslog").Parse(tmplStr)     if err != nil {             return nil, err     }     return &SyslogAdapter{             route: route,             conn:  conn,             tmpl:  tmpl,     }, nil</pre>}
        原來在我的例子中Logspout按照 syslog RFC5424標準來轉發日志的(你可以在上面的代碼中查看默認的值)。我花了一些時間在非常好玩的 Logstash grok parse test app上,但之后我很好奇網上是否已經有解決這個問題的現有的資源了。我很快的谷歌了一下,發現了這篇 文章,非常出色的描述了grok parse filter,而這正是我想要的。我僅僅改了部分代碼(比如,我把“app”改為了“containername”)就很快上道了 - 把Logspout的日志解析為有用的數據了。

        我最終的Logstash配置文件看起來是這樣的:

        input {
        tcp {
        port => 5000
        type => syslog
        }
        udp {
        port => 5000
        type => syslog
        }
        }

        filter { if [type] == "syslog" { grok {   match => { "message" => "%{SYSLOG5424PRI}%{NONNEGINT:ver} +(?:%{TIMESTAMP_ISO8601:ts}|-) +(?:%{HOSTNAME:containerid}|-) +(?:%{NOTSPACE:containername}|-) +(?:%{NOTSPACE:proc}|-) +(?:%{WORD:msgid}|-) +(?:%{SYSLOG5424SD:sd}|-|) +%{GREEDYDATA:msg}" } } syslog_pri { } date {   match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ] } if !("_grokparsefailure" in [tags]) {   mutate {     replace => [ "@source_host", "%{syslog_hostname}" ]     replace => [ "@message", "%{syslog_message}" ]   } } mutate {   remove_field => [ "syslog_hostname", "syslog_message", "syslog_timestamp" ] } } }

        output { elasticsearch { host => "elasticsearch" } stdout { codec => rubydebug }</pre>}
        也許還有很大的改進空間,但首先我還須更充分地了解Logstash ;P

        So what?

        現在我得到了有意義的格式的日志了,而且這并沒有對CPU產生多大的影響,太棒了!每當我在那臺主機上運行一個容器,日志就會自動地在 ElasticSearch中被索引并且可以在Kibana中進行查詢!Logstash過濾器負責將原始的系統日志消息解析為更有用的標簽信息。這包括 上文提到過的,來自運行該技術棧的容器的日志(除了Logstash - 不確定如何處理它,或我是否應該擔心它。可能一個附加的配置參數可以讓它僅打印自身的日志到STDOUT而不是所有容器的日志)。想象一下這類自動化容器 日志將會是多么有用,當它處理類似 RancherOS這樣的所有系統服務都運行在Docker容器內部的時候。

        你可以決定在日志信息中顯示哪些字段以便來快速查看你的應用程序產生的日志。這樣就可以實時感受到在容器中發生的事情。Kibana有充分能力和可配置性,允許你通過你所擁有的不同字段來進行排序,查詢以及過濾。

         Docker日志自動化: ElasticSearch、Logstash、Kibana以及Logspout

        Logs which are nice and easy to read and query!

        嘗試在一個配置了ELK技術棧(譯者注:包括Elasticsearch,Logstash和Kibana)的主機上運行一個容器,然后來查看自動顯示在Kibana上的日志(你可能需要刷新你的瀏覽器或點擊Kibana上“刷新”的刷新按鈕)。

        $ docker run -d --name number_spitter debian:jessie bash -c 'for i in {0..2000}; do echo $i; done'


        你現在可以看到日志中大多消息來自number_spitter容器,它很自然地bash滾動條(譯者注:應該是指下圖最下邊的那一行)中在吐出了一串數字。

         Docker日志自動化: ElasticSearch、Logstash、Kibana以及Logspout

        Whaaaaat! The Number Spitter container is so chatty!

        你可以在這些基本的設置之上做很多不可思議的事情。自然地,有一個可以用作容器活動的可視化表示的時間序列圖,可以讓你發現熱點,并快速知道發生了什么,以及為什么會發生。

         Docker日志自動化: ElasticSearch、Logstash、Kibana以及Logspout

        What the hell happened here? I don't know, but I can find out.

        顯示:ElasticSearch容器的真實事件。

        有大量的事情可以并且應該在配置了Logstash之后來進行 - 這里討論的配置文件僅僅是一個開始。有些容器使用它們自己的日志格式,它們需要進一步的進行解析。例如,在上圖中顯示的Kibana容器的“表格式”就有 它自己的時間戳,以及哪些IP地址訪問哪些文件的信息,HTTP響應的狀態碼,等。

        所以,這些附加的信息絕對可以被解析為更有用的結構格式中,而且這些事情應該針對每個應用程序來做(on per-app basis)。同樣,你可以想象一下,當你的消息以更高的規格的機構來呈現,它們可以匹配一個模式來告訴你是否有應用程序從panic中啟動,或者在代碼 執行路徑中遇到了空指針異常,亦或是不能鏈接數據庫等,都會以一定的優先級在日志中表現出來,這應該是一件很酷的事情吧。

        并且,如果Logspout把Docker的事件(我不清楚是否支持這樣的功能,因為我似乎記得看到一些容器的刪除事但并沒有出現其他別的東西) 和/或Docker的后臺日志也轉發給了Logstash 將是一件很巧妙的事情。可能有一種可以替代Logstash的更簡單的方式。

        此外,Docker 1.6 的 log drivers可 能會以稍微不同過的方式來做類似的事情,所以我很好奇當把log driver考慮進來的話這個配置會有什么變化。我不是很清楚Logspout的內部細節,所以也不知道是否可以使用--log-driver=none 參數來禁掉log driver,然后繼續使用Logspout來轉發日志。那將會很酷因為你只需要跟中ElasticSearch中的數據,而不是既有 ElasticSearch又有--log-driver=json格式的數據。

        我也不是很確定Logstash是否支持事件(例如,給一個正在打電話的人發送郵件或短信是否會在有限時間內收到大量的錯誤),那是另一個潛在的 用框(譯者注:北京大學的邵維忠老師告誡我們use case應該譯為用框)(如果這樣的事情還沒有得到很好支持我會感到很吃驚)。想想看,這種事情同時確實強烈地需要松散的集成 - 例如,每次我們關閉一個非接觸訂閱的時候會通知銷售頻道,我們知道這些事情,因為它被有記錄了下來。不過這些跑題了。

        說到跑題,演示程序同時包含了一個 cAdvisor的實例,這是一個非常有用的工具,它可以監視你容器中資源的使用情況。你可以在在你工作的主機的8080號端口訪問它:

         Docker日志自動化: ElasticSearch、Logstash、Kibana以及Logspout

        Pretty graphs for your containers

        好了。我應該馬上開始使用這個嗎?

        如果你想不加任何修改并立即在生產中使用這個配置,那這并不是你所要的,盡管它看起來絕對比“docker run, 可能之后還會用docker logs手動檢查”好很多。需要考慮一些額外的東西,不分先后:

        1. ElasticSearch副本:在多個節點保存數據來支持冗余。因為會發生奇怪的事情,節點會掛掉,理想情況下你的基礎設施應該可以平 滑的處理這類故障。同樣,當你有多個主機,在每個節點配置Logspout實例來向“master”Logstash(我不確定應該如何處理這種潛在的故 障點的冗余)來轉發主機的日志是你需要處理的事情。
        2. 備份(Backing up)和輪轉存儲在ElasticSearch中的日志數據。要實現這個,我確定ClusterHQ(自稱為“container data people”)將會助你一臂之力;)
        3. 確保訪問接口被約束在網絡和用戶級(演示程序把所有的接口都打開了,所以如果你在公有網絡運行它的時候,所有人都是可以看到它并對它做些什么)
        4. 添加容器重啟策略以及監控,來確保服務的健康和正常運行。
        5. 使用低權限用戶來運行容器,這樣可以確保更好的安全性
        6. </ol>
          所以,在現實中使用它的時候仍然有很多事情需要去考慮,但我希望我所講的這些東西會對你有所啟發,就像我幫你在你的頭腦了安裝一些小小齒輪,推動 你完成這項工作。我想現在一個非常小的創業公司都是可以運行這些工具的,而且我很興奮像這樣世界級的工具變得越來越多了。不管是現在還是將來,對一個團隊 來說最終它都會使至關重要的,因為他們需要將使用一個命令來處理多臺機器(SRE)(譯者注:不知道SRE是什么意思), 正是這類工具讓這一目標變得更具可行性。

          結語

          朋友們,趕緊動手去記錄日志吧!!如果你有什么想法或建議,告訴我。我希望看到在關于這類事情的后續文章,可能你就是下一個。

          Until next time, stay sassy Internet!
          Nathan

          原文鏈接:Automating Docker Logging: ElasticSearch, Logstash, Kibana, and Logspout(翻譯:趙軍旺)

          來自:http://dockone.io/article/373

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!