淺談k8s+docker 資源監控

寫在前面

最近在研究docker集羣(kubernetes)的監控,爲了完全弄清楚,簡單看了一點源碼。這裏分享一下我學到的東西。html

docker api: stats

首先是docker的api,stats的具體使用場景如:node

http://$dockerip:2375/containers/$containerid/stats

能夠獲取docker機器上某一個容器的狀態,該請求的response會持續的寫響應報文回來(每秒一次)linux

http://$dockerip:2375/containers/$containerid/stats?stream=false

參數stream默認是true,設爲false後,response只寫一次。git

docker中該api對應的操做,就至關於docker stats $CID 這條命令,它調用到了ContainerStats()方法(位於docker項目目錄的/daemon/stats.go 第19行),函數中啓動了一個收集器:github

daemon.SubscribeToContainerStats(name)

收集器定時寫數據到update變量中。
而後啓動一個協程讀數據,獲取數據的途徑包括:docker

update := v.(*execdriver.ResourceStats)

json

nwStats, err := daemon.getNetworkStats(name)

能夠看到網絡狀態是另外讀的,而cpu,內存狀態在哪讀呢?咱們一步步跳轉,看到在這裏:
/daemon/execdriver/driver_linux.go 112行:api

func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error)

在這個函數裏咱們看獲得,其實所謂的獲取容器狀態,就是讀文件而已。咱們舉一個已經在docker運行的容器來講:網絡

cat /run/docker/execdriver/native/$containerID/state.json

這裏面記錄了一個cgroup_paths字段,他的值是一個路徑,經過cstats, err := mgr.GetStats()程序才真正拿到了監控數據,如cpu的狀態信息,存儲在一個以下的路徑中:app

cd /sys/fs/cgroup/cpu,cpuacct/system.slice/docker-9b479ceaf3bef1caee2c37dfdc982a968144700a2c42991b238b938788a8a91f.scope

又好比內存的大體信息存在:

cat /sys/fs/cgroup/memory/system.slice/docker-9b479ceaf3bef1caee2c37dfdc982a968144700a2c42991b238b938788a8a91f.scope/memory.stat

具體裏面放了什麼數據,你們就本身看咯。

還剩下一個數據,也是咱們討論的重點:網絡IO。

咱們回到/daemon/stats.go
看看函數getNetworkStats(name string):
每一個docker容器建立後都會又docker建立一個網卡,網卡名以veth開頭,後面是一串隨機生成的十六進制碼。
咱們只要找到該網卡,而後,像上文的cpu,內存狀態獲取的方法同樣,去找文件就好了。
然而這個網卡名彷佛是存在內存中,至少我沒有看到docker將這個網卡名存到別的地方。
代碼中:
nw, err := daemon.netController.NetworkByID(c.NetworkSettings.NetworkID)
能夠看出獲取了網卡(network),爾後有一個Statistics()方法,咱們繼續跟蹤會發現,docker調用了一個組件包:
github.com/docker/libcontainer
其中有statistics方法,並執行了一個cat的命令(該組件包的/sandbox/interface_linux.go 第174行):
data, err := exec.Command("cat", netStatsFile).Output()

是的,又是讀文件。
此次讀的文件在:/sys/class/net/目錄下,
咱們進入該目錄能夠看到有許多子目錄,其中就包括了docker啓動容器時生成的網卡。

我的猜想:docker在內存中保存了容器到網卡的映射關係。經過這個映射關係能夠找到網卡的統計數據(數據由內核生成,記錄在機器的文件中)
咱們能夠看到以下的數據:
clipboard.png

將這些數據統一塊兒來,就是docker stats 這條命令乾的事。

kubernetes如何調用上述的監控功能

kubernetes的監控採用了cAdvisor組件。由於kubernetes中記錄了容器的信息(可是沒有記錄容器-網卡的映射關係),因此運行在節點上的cAdvisor不須要經過docker stats去獲取容器的cpu和內存使用數據。
而網絡IO數據呢?

咱們知道k8s部署運行一個容器是會先生成一個pause容器。
是的,網絡IO都記錄在pause容器中。這裏你們能夠在本身的k8s環境中驗證。

因此只要咱們獲取某容器對應的pause容器的containerID,咱們就能夠用如上的方式去抓到網絡IO。

由於cAdvisor並非爲k8s專門設計的,不會特意在獲取網絡IO時去遍歷查找容器對應的pause容器。因此當前的cAdvisor沒有辦法獲取容器的網絡IO。

因此若是在使用k8s集羣,想要加入網絡IO監控功能的時候,能夠從k8s自帶的cAdvisor入手。

cAdvisor

上面講到cAdvisor,那麼cAdvisor是如何獲取網絡的IO的呢?
首先,在k8s(1.0.6版本)的/pkg/kubelet/cadvisor/cadvisor_linux.go中51行,New(port int)方法,這是kubelet調用cAdvisor的入口。實例化一個cAdvisorClient,並執行他的Start()方法,咱們能夠進入cAdvisor項目中找到該方法(github.com/google/cadvisor/manager/manager.go 195行)。

咱們看到有一句

err := docker.Register(self, self.fsInfo)

先mark之。繼續看:

glog.Infof("Starting recovery of all containers")
err = self.detectSubcontainers("/")

這裏程序程序檢查全部在運行的容器,同內存中記錄的容器做比較,有新的容器就新建一個相關的處理機制:一個Container的結構(有減小的,就刪除),而後執行cont.Start() (github.com/google/cadvisor/manager/manager.go 766行)

持續追蹤咱們就能夠知道每秒它收集一次監控信息,收集方法即

stats, statsErr := c.handler.GetStats()

(/cadvisor/manager/container.go 401行)
handler是一個接口。咱們得知道當時是如何給他賦值的。這裏不贅述了,咱們直接找關鍵的方法:

func (self *dockerContainerHandler) GetStats() (*info.ContainerStats, error)

(cadvisor/container/docker/handler.go 第305行)

它又調用了:

func GetStats(cgroupManager cgroups.Manager, networkInterfaces []string) (*info.ContainerStats, error)

(cadvisor/container/libcontainer/helpers.go 第77行)

這裏就是真正獲取監控信息的地方,它引入了libcontainer包中的同名方法,

最終是導入了"github.com/docker/libcontainer/cgroups"這個第三方包中的方法。是否是很熟悉?對就是上文中提到的docker stats獲取網絡IO用到的包。咱們到這個包裏找到函數:
(/libcontainer/cgroups/fs/apply_raw.go 第148行)

func (m *Manager) GetStats() (*cgroups.Stats, error) {
    m.mu.Lock()
    defer m.mu.Unlock()
    stats := cgroups.NewStats()
    for name, path := range m.Paths {
        //m.Paths中包括了cpu,memory,network等字段,根據這些字段找到相應的目錄
        sys, ok := subsystems[name]
        if !ok || !cgroups.PathExists(path) {
            continue
        }
        //讀目錄裏的文件獲取監控信息
        if err := sys.GetStats(path, stats); err != nil {
            return nil, err
        }
    }

    return stats, nil
}

咱們能夠看到libcontainer/cgroups/fs目錄下有cpu.go,memory.go等文件,每個文件中都有一個集成了GetStats()的結構,咱們能夠獲取到任何數據。

咱們再回到cadvisor項目中cadvisor/container/libcontainer/helpers.go 第77行,

往下看:

// TODO(rjnagal): Use networking stats directly from libcontainer.
    stats.Network.Interfaces = make([]info.InterfaceStats, len(networkInterfaces))
    for i := range networkInterfaces {
        interfaceStats, err := sysinfo.GetNetworkStats(networkInterfaces[i])
        if err != nil {
            return stats, err
        }
        stats.Network.Interfaces[i] = interfaceStats
    }

這裏官方還用了別的手段再去找network的數據,雖然不知道是處於什麼理由,也無論這裏是去找哪一個系統文件看狀態,可是這樣依然是拿不到數據的。由於根本沒有找到容器對應的網卡。這裏找網卡的方法在cadvisor/container/docker/handler.go 第311行:

var networkInterfaces []string
    if len(config.Networks) > 0 {
        // ContainerStats only reports stat for one network device.
        // TODO(vmarmol): Handle multiple physical network devices.
        for _, n := range config.Networks {
            // Take the first non-loopback.
            if n.Type != "loopback" {
                networkInterfaces = []string{n.HostInterfaceName}
                break
            }
        }
    }
    stats, err := containerLibcontainer.GetStats(self.cgroupManager, networkInterfaces)

很顯然這裏是要改進的(在k8s的環境下)。

注:隨着版本更新,新版本的cAdvisor採用github.com/opencontainers包。詳見:github.com/opencontainers/runc/libcontainer/container_linux.go line 151:
另外,cadvisor獲取網絡監控數據的正確途徑應該是:
監控pause容器,在其Pid對應的/proc/$pid/net/dev文件中便可獲得容器內部網絡監控數據

磁盤監控

cadvisor在主函數初始化時,經過一些init方法在內存中構建了機器上的文件系統的結構(有哪些磁盤、分別掛載目錄是啥、maj和min號是啥)。隨後,cadvisor會在監控機器的性能時,分爲磁盤使用狀況的監控和磁盤讀寫狀況的監控。

磁盤使用狀況的監控

cadvisor會執行syscall.Statfs方法,經過linux系統調用,得到磁盤的總大小、可用空間、inodes總數和使用數等信息

磁盤讀寫狀況的監控

經過讀取機器的/proc/diskstats文件內容,形如:

cat /proc/diskstats 
 254       0 vda 48560 0 994278 64304 2275295 70286 18962312 6814364 0 1205480 6877464
 254       1 vda1 48177 0 991034 64008 1865714 70286 18962304 6777592 0 1170880 6840740
 254      16 vdb 700 0 4955 284 0 0 0 0 0 284 284

經過這些數據計算出各個存儲設備的讀寫次數、讀寫速度。(這裏文件內容釋義和計算方式能夠參見http://www.udpwork.com/item/1...

暴露接口

當咱們調用cadvisor的接口:
/api/v2.1/machinestats 時,就能從返回的json結構中找到filesystem字段,包含了磁盤讀寫性能和磁盤使用狀況的監控數據

缺陷

上文提到,cadvisor在初始化階段就生成了機器文件系統的抽象結構,可是這個結構不會動態檢查和更新,當機器上動態掛載了一個數據盤(好比使用ceph rbd作pod的pv),cadvisor不會感知,這個新掛的盤的監控數據也沒法被感知。

目前社區暫時沒有對這個功能進行優化

應用

說了這麼多,其實給你們在實現k8s容器網絡IO監控這個需求時提供一點想法:1.從cadvisor入手,找到對應pause容器的containerID,那麼就能夠用libcontainer包去獲取到數據(也即用docker stats的方式)。2.從docker入手,建立容器時記錄網卡信息,可否提供一個接口,根據容器名找到對應網卡名,那麼cadvisor只要經過這個docker API獲得網卡名,就能夠本身讀文件獲取網絡IO。

相關文章
相關標籤/搜索