使用 Elasticsearch 和 cAdvisor 監控 Docker 容器

贊 回覆發表於2016-10-06

如果你正在執行 Swarm 模式的叢集,或者只執行單臺 Docker,你都會有下面的疑問:

我如何才能監控到它們都在幹些什麼?

這個問題的答案是“很不容易”。

你需要監控下面的引數:

  1. 容器的數量和狀態。
  2. 一臺容器是否已經移到另一個節點了,如果是,那是在什麼時候,移動到哪個節點?
  3. 給定節點上執行著的容器數量。
  4. 一段時間內的通訊峰值。
  5. 孤兒卷和網路(LCTT 譯註:孤兒卷就是當你刪除容器時忘記刪除它的卷,這個卷就不會再被使用,但會一直佔用資源)。
  6. 可用磁碟空間、可用 inode 數。
  7. 容器數量與連線在 docker0docker_gwbridge 上的虛擬網路卡數量不一致(LCTT 譯註:當 docker 啟動時,它會在宿主機器上建立一個名為 docker0 的虛擬網路介面)。
  8. 開啟和關閉 Swarm 節點。
  9. 收集並集中處理日誌。

本文的目標是介紹 Elasticsearch + Kibana + cAdvisor 的用法,使用它們來收集 Docker 容器的引數,分析資料併產生視覺化報表。

閱讀本文後你可以發現有一個監控儀表盤能夠部分解決上述列出的問題。但如果只是使用 cAdvisor,有些引數就無法顯示出來,比如 Swarm 模式的節點。

如果你有一些 cAdvisor 或其他工具無法解決的特殊需求,我建議你開發自己的資料收集器和資料處理器(比如 Beats),請注意我不會演示如何使用 Elasticsearch 來集中收集 Docker 容器的日誌。

“你要如何才能監控到 Swarm 模式叢集裡面發生了什麼事情?要做到這點很不容易。” —— @fntlnz

我們為什麼要監控容器?

想象一下這個經典場景:你在管理一臺或多臺虛擬機器,你把 tmux 工具用得很溜,用各種 session 事先設定好了所有基礎的東西,包括監控。然後生產環境出問題了,你使用 tophtopiotopjnettop 各種 top 來排查,然後你準備好修復故障。

現在重新想象一下你有 3 個節點,包含 50 臺容器,你需要在一個地方檢視整潔的歷史資料,這樣你知道問題出在哪個地方,而不是把你的生命浪費在那些字元介面來賭你可以找到問題點。

什麼是 Elastic Stack ?

Elastic Stack 就一個工具集,包括以下工具:

  • Elasticsearch
  • Kibana
  • Logstash
  • Beats

我們會使用其中一部分工具,比如使用 Elasticsearch 來分析基於 JSON 格式的文字,以及使用 Kibana 來視覺化資料併產生報表。

另一個重要的工具是 Beats,但在本文中我們還是把精力放在容器上,官方的 Beats 工具不支援 Docker,所以我們選擇原生相容 Elasticsearch 的 cAdvisor。

cAdvisor 工具負責收集、整合正在執行的容器資料,並匯出報表。在本文中,這些報表被到入到 Elasticsearch 中。

cAdvisor 有兩個比較酷的特性:

  • 它不只侷限於 Docker 容器。
  • 它有自己的 Web 伺服器,可以簡單地顯示當前節點的視覺化報表。

設定測試叢集,或搭建自己的基礎架構

和我以前的文章一樣,我習慣提供一個簡單的指令碼,讓讀者不用花很多時間就能部署好和我一樣的測試環境。你可以使用以下(非生產環境使用的)指令碼來搭建一個 Swarm 模式的叢集,其中一個容器執行著 Elasticsearch。

如果你有充足的時間和經驗,你可以搭建自己的基礎架構 Bring Your Own Infrastructure,BYOI

如果要繼續閱讀本文,你需要:

  • 執行 Docker 程式的一個或多個節點(docker 版本號大於等於 1.12)。
  • 至少有一個獨立執行的 Elasticsearch 節點(版本號 2.4.X)。

重申一下,此 Elasticsearch 叢集環境不能放在生產環境中使用。生產環境也不推薦使用單節點叢集,所以如果你計劃安裝一個生產環境,請參考 Elastic 指南

對喜歡嚐鮮的使用者的友情提示

我就是一個喜歡嚐鮮的人(當然我也已經在生產環境中使用了最新的 alpha 版本),但是在本文中,我不會使用最新的 Elasticsearch 5.0.0 alpha 版本,我還不是很清楚這個版本的功能,所以我不想成為那個引導你們出錯的關鍵。

所以本文中涉及的 Elasticsearch 版本為最新穩定版 2.4.0。

測試叢集部署指令碼

前面已經說過,我提供這個指令碼給你們,讓你們不必費神去部署 Swarm 叢集和 Elasticsearch,當然你也可以跳過這一步,用你自己的 Swarm 模式引擎和你自己的 Elasticserch 節點。

執行這段指令碼之前,你需要:

建立叢集的指令碼

現在萬事俱備,你可以把下面的程式碼拷到 create-cluster.sh 檔案中:

#!/usr/bin/env bash
#
# Create a Swarm Mode cluster with a single master and a configurable number of workers

workers=${WORKERS:-"worker1 worker2"}

#######################################
# Creates a machine on Digital Ocean
# Globals:
#   DO_ACCESS_TOKEN The token needed to access DigitalOcean's API
# Arguments:
#   $1 the actual name to give to the machine
#######################################
create_machine() {
  docker-machine create \
    -d digitalocean \
    --digitalocean-access-token=$DO_ACCESS_TOKEN \
    --digitalocean-size 2gb \
    $1
}

#######################################
# Executes a command on the specified machine
# Arguments:
#   $1     The machine on which to run the command
#   $2..$n The command to execute on that machine
#######################################
machine_do() {
  docker-machine ssh $@
}

main() {

  if [ -z "$DO_ACCESS_TOKEN" ]; then
    echo "Please export a DigitalOcean Access token: https://cloud.digitalocean.com/settings/api/tokens/new"
    echo "export DO_ACCESS_TOKEN=<yourtokenhere>"
    exit 1
  fi

  if [ -z "$WORKERS" ]; then
    echo "You haven't provided your workers by setting the \$WORKERS environment variable, using the default ones: $workers"
  fi

  # Create the first and only master
  echo "Creating the master"

  create_machine master1

  master_ip=$(docker-machine ip master1)

  # Initialize the swarm mode on it
  echo "Initializing the swarm mode"
  machine_do master1 docker swarm init --advertise-addr $master_ip

  # Obtain the token to allow workers to join
  worker_tkn=$(machine_do master1 docker swarm join-token -q worker)
  echo "Worker token: ${worker_tkn}"

  # Create and join the workers
  for worker in $workers; do
    echo "Creating worker ${worker}"
    create_machine $worker
    machine_do $worker docker swarm join --token $worker_tkn $master_ip:2377
  done
}

main $@

賦予它可執行許可權:

chmod +x create-cluster.sh

建立叢集

如檔名所示,我們可以用它來建立叢集。預設情況下這個指令碼會建立一個 master 和兩個 worker,如果你想修改 worker 個數,可以設定環境變數 WORKERS。

現在就來建立叢集吧。

./create-cluster.sh

你可以出去喝杯咖啡,因為這需要花點時間。

最後叢集部署好了。

現在為了驗證 Swarm 模式叢集已經正常執行,我們可以通過 ssh 登入進 master:

docker-machine ssh master1

然後列出叢集的節點:

docker node ls
ID                           HOSTNAME  STATUS  AVAILABILITY  MANAGER STATUS
26fi3wiqr8lsidkjy69k031w2 *  master1   Ready   Active        Leader
dyluxpq8sztj7kmwlzs51u4id    worker2   Ready   Active
epglndegvixag0jztarn2lte8    worker1   Ready   Active

安裝 Elasticsearch 和 Kibana

注意,從現在開始所有的命令都執行在主節點 master1 上。

在生產環境中,你可能會把 Elasticsearch 和 Kibana 安裝在一個單獨的、大小合適的例項集合中。但是在我們的實驗中,我們還是把它們和 Swarm 模式叢集安裝在一起。

為了將 Elasticsearch 和 cAdvisor 連通,我們需要建立一個自定義的網路,因為我們使用了叢集,並且容器可能會分佈在不同的節點上,我們需要使用 overlay 網路(LCTT 譯註:overlay 網路是指在不改變現有網路基礎設施的前提下,通過某種約定通訊協議,把二層報文封裝在 IP 報文之上的新的資料格式,是目前最主流的容器跨節點資料傳輸和路由方案)。

也許你會問,“為什麼還要網路?我們不是可以用 link 嗎?” 請考慮一下,自從引入使用者定義網路後,link 機制就已經過時了。

以下內容摘自 Docker 文件

在 Docker network 特性出來以前,你可以使用 Docker link 特性實現容器互相發現、安全通訊。而在 network 特性出來以後,你還可以使用 link,但是當容器處於預設橋接網路或使用者自定義網路時,它們的表現是不一樣的。

現在建立 overlay 網路,名稱為 monitoring:

docker network create monitoring -d overlay

Elasticsearch 容器

docker service create --network=monitoring \
  --mount type=volume,target=/usr/share/elasticsearch/data \
  --constraint node.hostname==worker1 \
  --name elasticsearch elasticsearch:2.4.0

注意 Elasticsearch 容器被限定在 worker1 節點,這是因為它執行時需要依賴 worker1 節點上掛載的卷。

Kibana 容器

docker service create --network=monitoring --name kibana -e ELASTICSEARCH_URL="http://elasticsearch:9200" -p 5601:5601 kibana:4.6.0

如你所見,我們啟動這兩個容器時,都讓它們加入 monitoring 網路,這樣一來它們可以通過名稱(如 Kibana)被相同網路的其他服務訪問。

現在,通過 routing mesh 機制,我們可以使用瀏覽器訪問伺服器的 IP 地址來檢視 Kibana 報表介面。

獲取 master1 例項的公共 IP 地址:

docker-machine ip master1

開啟瀏覽器輸入地址:http://[master1 的 ip 地址]:5601/status

所有專案都應該是綠色:

讓我們接下來開始收集資料!

收集容器的執行資料

收集資料之前,我們需要建立一個服務,以全域性模式執行 cAdvisor,為每個有效節點設定一個定時任務。

這個服務與 Elasticsearch 處於相同的網路,以便於 cAdvisor 可以推送資料給 Elasticsearch。

docker service create --network=monitoring --mode global --name cadvisor \
  --mount type=bind,source=/,target=/rootfs,readonly=true \
  --mount type=bind,source=/var/run,target=/var/run,readonly=false \
  --mount type=bind,source=/sys,target=/sys,readonly=true \
  --mount type=bind,source=/var/lib/docker/,target=/var/lib/docker,readonly=true \
  google/cadvisor:latest \
  -storage_driver=elasticsearch \
  -storage_driver_es_host="http://elasticsearch:9200"

注意:如果你想配置 cAdvisor 選項,參考這裡

現在 cAdvisor 在傳送資料給 Elasticsearch,我們通過定義一個索引模型來檢索 Kibana 中的資料。有兩種方式可以做到這一點:通過 Kibana 或者通過 API。在這裡我們使用 API 方式實現。

我們需要在一個連線到 monitoring 網路的正在執行的容器中執行索引建立命令,你可以在 cAdvisor 容器中拿到 shell,不幸的是 Swarm 模式在開啟服務時會在容器名稱後面附加一個唯一的 ID 號,所以你需要手動指定 cAdvisor 容器的名稱。

拿到 shell:

docker exec -ti <cadvisor-container-name> sh

建立索引:

curl -XPUT http://elasticsearch:9200/.kibana/index-pattern/cadvisor -d '{"title" : "cadvisor*",  "timeFieldName": "container_stats.timestamp"}'

如果你夠懶,可以只執行下面這一句:

docker exec $(docker ps | grep cadvisor | awk '{print $1}' | head -1) curl -XPUT http://elasticsearch:9200/.kibana/index-pattern/cadvisor -d '{"title" : "cadvisor*",  "timeFieldName": "container_stats.timestamp"}'

把資料彙總成報表

你現在可以使用 Kibana 來建立一份美觀的報表了。但是不要著急,我為你們建了一份報表和一些圖形介面來方便你們入門。

訪問 Kibana 介面 => Setting => Objects => Import,然後選擇包含以下內容的 JSON 檔案,就可以匯入我的配置資訊了:

[
  {
    "_id": "cAdvisor",
    "_type": "dashboard",
    "_source": {
      "title": "cAdvisor",
      "hits": 0,
      "description": "",
      "panelsJSON": "[{\"id\":\"Filesystem-usage\",\"type\":\"visualization\",\"panelIndex\":1,\"size_x\":6,\"size_y\":3,\"col\":1,\"row\":1},{\"id\":\"Memory-[Node-equal->Container]\",\"type\":\"visualization\",\"panelIndex\":2,\"size_x\":6,\"size_y\":4,\"col\":7,\"row\":4},{\"id\":\"memory-usage-by-machine\",\"type\":\"visualization\",\"panelIndex\":3,\"size_x\":6,\"size_y\":6,\"col\":1,\"row\":4},{\"id\":\"CPU-Total-Usage\",\"type\":\"visualization\",\"panelIndex\":4,\"size_x\":6,\"size_y\":5,\"col\":7,\"row\":8},{\"id\":\"Network-RX-TX\",\"type\":\"visualization\",\"panelIndex\":5,\"size_x\":6,\"size_y\":3,\"col\":7,\"row\":1}]",
      "optionsJSON": "{\"darkTheme\":false}",
      "uiStateJSON": "{}",
      "version": 1,
      "timeRestore": false,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"filter\":[{\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}}}]}"
      }
    }
  },
  {
    "_id": "Network",
    "_type": "search",
    "_source": {
      "title": "Network",
      "description": "",
      "hits": 0,
      "columns": [
        "machine_name",
        "container_Name",
        "container_stats.network.name",
        "container_stats.network.interfaces",
        "container_stats.network.rx_bytes",
        "container_stats.network.rx_packets",
        "container_stats.network.rx_dropped",
        "container_stats.network.rx_errors",
        "container_stats.network.tx_packets",
        "container_stats.network.tx_bytes",
        "container_stats.network.tx_dropped",
        "container_stats.network.tx_errors"
      ],
      "sort": [
        "container_stats.timestamp",
        "desc"
      ],
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"highlight\":{\"pre_tags\":[\"@kibana-highlighted-field@\"],\"post_tags\":[\"@/kibana-highlighted-field@\"],\"fields\":{\"*\":{}},\"fragment_size\":2147483647},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "Filesystem-usage",
    "_type": "visualization",
    "_source": {
      "title": "Filesystem usage",
      "visState": "{\"title\":\"Filesystem usage\",\"type\":\"histogram\",\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"defaultYExtents\":false,\"mode\":\"stacked\",\"scale\":\"linear\",\"setYExtents\":false,\"shareYAxis\":true,\"times\":[],\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.filesystem.usage\",\"customLabel\":\"USED\"}},{\"id\":\"2\",\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"machine_name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":false}},{\"id\":\"3\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.filesystem.capacity\",\"customLabel\":\"AVAIL\"}},{\"id\":\"4\",\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.filesystem.device\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{\"vis\":{\"colors\":{\"Average container_stats.filesystem.available\":\"#E24D42\",\"Average container_stats.filesystem.base_usage\":\"#890F02\",\"Average container_stats.filesystem.capacity\":\"#3F6833\",\"Average container_stats.filesystem.usage\":\"#E24D42\",\"USED\":\"#BF1B00\",\"AVAIL\":\"#508642\"}}}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"analyze_wildcard\":true,\"query\":\"*\"}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "CPU-Total-Usage",
    "_type": "visualization",
    "_source": {
      "title": "CPU Total Usage",
      "visState": "{\"title\":\"CPU Total Usage\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.cpu.usage.total\"}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"container_Name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"4\",\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"machine_name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":true}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "memory-usage-by-machine",
    "_type": "visualization",
    "_source": {
      "title": "Memory [Node]",
      "visState": "{\"title\":\"Memory [Node]\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.memory.usage\"}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"machine_name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  },
  {
    "_id": "Network-RX-TX",
    "_type": "visualization",
    "_source": {
      "title": "Network RX TX",
      "visState": "{\"title\":\"Network RX TX\",\"type\":\"histogram\",\"params\":{\"addLegend\":true,\"addTimeMarker\":true,\"addTooltip\":true,\"defaultYExtents\":false,\"mode\":\"stacked\",\"scale\":\"linear\",\"setYExtents\":false,\"shareYAxis\":true,\"times\":[],\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.network.rx_bytes\",\"customLabel\":\"RX\"}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.timestamp\",\"interval\":\"s\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.network.tx_bytes\",\"customLabel\":\"TX\"}}],\"listeners\":{}}",
      "uiStateJSON": "{\"vis\":{\"colors\":{\"RX\":\"#EAB839\",\"TX\":\"#BF1B00\"}}}",
      "description": "",
      "savedSearchId": "Network",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"filter\":[]}"
      }
    }
  },
  {
    "_id": "Memory-[Node-equal->Container]",
    "_type": "visualization",
    "_source": {
      "title": "Memory [Node=>Container]",
      "visState": "{\"title\":\"Memory [Node=>Container]\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"avg\",\"schema\":\"metric\",\"params\":{\"field\":\"container_stats.memory.usage\"}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"container_stats.timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}},{\"id\":\"3\",\"type\":\"terms\",\"schema\":\"group\",\"params\":{\"field\":\"container_Name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}},{\"id\":\"4\",\"type\":\"terms\",\"schema\":\"split\",\"params\":{\"field\":\"machine_name\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\",\"row\":true}}],\"listeners\":{}}",
      "uiStateJSON": "{}",
      "description": "",
      "version": 1,
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"index\":\"cadvisor*\",\"query\":{\"query_string\":{\"query\":\"* NOT container_Name.raw: \\\\\\\"/\\\\\\\" AND NOT container_Name.raw: \\\\\\\"/docker\\\\\\\"\",\"analyze_wildcard\":true}},\"filter\":[]}"
      }
    }
  }
]

這裡還有很多東西可以玩,你也許想自定義報表介面,比如新增記憶體頁錯誤狀態,或者收發包的丟包數。如果你能實現開頭列表處我沒能實現的專案,那也是很好的。

總結

正確監控需要大量時間和精力,容器的 CPU、記憶體、IO、網路和磁碟,監控的這些引數還只是整個監控專案中的滄海一粟而已。

我不知道你做到了哪一階段,但接下來的任務也許是:

  • 收集執行中的容器的日誌
  • 收集應用的日誌
  • 監控應用的效能
  • 報警
  • 監控健康狀態

如果你有意見或建議,請留言。祝你玩得開心。

現在你可以關掉這些測試系統了:

docker-machine rm master1 worker{1,2}

via: https://blog.codeship.com/monitoring-docker-containers-with-elasticsearch-and-cadvisor/

作者:Lorenzo Fontana 譯者:bazz2 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

相關文章