mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +00:00
Merge pull request #482 from monnand/cadvisor-update-2
Add code in client package to use kubelet's API to retrieve data from cAdvisor
This commit is contained in:
commit
02b06a2f2f
91
pkg/client/containerinfo.go
Normal file
91
pkg/client/containerinfo.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContainerInfoGetter interface {
|
||||||
|
GetContainerInfo(host, podID, containerID string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
|
GetMachineInfo(host string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPContainerInfoGetter struct {
|
||||||
|
Client *http.Client
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *HTTPContainerInfoGetter) getContainerInfo(host, path string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
|
var body io.Reader
|
||||||
|
if req != nil {
|
||||||
|
content, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
body = bytes.NewBuffer(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
request, err := http.NewRequest(
|
||||||
|
"GET",
|
||||||
|
fmt.Sprintf("http://%v/stats/%v",
|
||||||
|
net.JoinHostPort(host, strconv.Itoa(self.Port)),
|
||||||
|
path,
|
||||||
|
),
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := self.Client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("trying to get info for %v from %v; received status %v",
|
||||||
|
path, host, response.Status)
|
||||||
|
}
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
var cinfo info.ContainerInfo
|
||||||
|
err = decoder.Decode(&cinfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &cinfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *HTTPContainerInfoGetter) GetContainerInfo(host, podID, containerID string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
|
return self.getContainerInfo(
|
||||||
|
host,
|
||||||
|
fmt.Sprintf("%v/%v", podID, containerID),
|
||||||
|
req,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *HTTPContainerInfoGetter) GetMachineInfo(host string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
||||||
|
return self.getContainerInfo(host, "", req)
|
||||||
|
}
|
171
pkg/client/containerinfo_test.go
Normal file
171
pkg/client/containerinfo_test.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/cadvisor/info"
|
||||||
|
itest "github.com/google/cadvisor/info/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testHTTPContainerInfoGetter(
|
||||||
|
req *info.ContainerInfoRequest,
|
||||||
|
cinfo *info.ContainerInfo,
|
||||||
|
podID string,
|
||||||
|
containerID string,
|
||||||
|
status int,
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
expectedPath := "/stats"
|
||||||
|
if len(podID) > 0 && len(containerID) > 0 {
|
||||||
|
expectedPath = path.Join(expectedPath, podID, containerID)
|
||||||
|
}
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if status != 0 {
|
||||||
|
w.WriteHeader(status)
|
||||||
|
}
|
||||||
|
if strings.TrimRight(r.URL.Path, "/") != strings.TrimRight(expectedPath, "/") {
|
||||||
|
t.Fatalf("Received request to an invalid path. Should be %v. got %v",
|
||||||
|
expectedPath, r.URL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
var receivedReq info.ContainerInfoRequest
|
||||||
|
err := decoder.Decode(&receivedReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Note: This will not make a deep copy of req.
|
||||||
|
// So changing req after Get*Info would be a race.
|
||||||
|
expectedReq := req
|
||||||
|
// Fill any empty fields with default value
|
||||||
|
expectedReq = expectedReq.FillDefaults()
|
||||||
|
if !reflect.DeepEqual(expectedReq, &receivedReq) {
|
||||||
|
t.Errorf("received wrong request")
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
err = encoder.Encode(cinfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
hostURL, err := url.Parse(ts.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
parts := strings.Split(hostURL.Host, ":")
|
||||||
|
|
||||||
|
port, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
containerInfoGetter := &HTTPContainerInfoGetter{
|
||||||
|
Client: http.DefaultClient,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
|
||||||
|
var receivedContainerInfo *info.ContainerInfo
|
||||||
|
if len(podID) > 0 && len(containerID) > 0 {
|
||||||
|
receivedContainerInfo, err = containerInfoGetter.GetContainerInfo(parts[0], podID, containerID, req)
|
||||||
|
} else {
|
||||||
|
receivedContainerInfo, err = containerInfoGetter.GetMachineInfo(parts[0], req)
|
||||||
|
}
|
||||||
|
if status == 0 || status == http.StatusOK {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("received unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !receivedContainerInfo.Eq(cinfo) {
|
||||||
|
t.Error("received unexpected container info")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil {
|
||||||
|
t.Error("did not receive expected error.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPContainerInfoGetterGetContainerInfoSuccessfully(t *testing.T) {
|
||||||
|
req := &info.ContainerInfoRequest{
|
||||||
|
NumStats: 10,
|
||||||
|
NumSamples: 10,
|
||||||
|
}
|
||||||
|
req = req.FillDefaults()
|
||||||
|
cinfo := itest.GenerateRandomContainerInfo(
|
||||||
|
"dockerIDWhichWillNotBeChecked", // docker ID
|
||||||
|
2, // Number of cores
|
||||||
|
req,
|
||||||
|
1*time.Second,
|
||||||
|
)
|
||||||
|
testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", 0, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPContainerInfoGetterGetMachineInfoSuccessfully(t *testing.T) {
|
||||||
|
req := &info.ContainerInfoRequest{
|
||||||
|
NumStats: 10,
|
||||||
|
NumSamples: 10,
|
||||||
|
}
|
||||||
|
req = req.FillDefaults()
|
||||||
|
cinfo := itest.GenerateRandomContainerInfo(
|
||||||
|
"dockerIDWhichWillNotBeChecked", // docker ID
|
||||||
|
2, // Number of cores
|
||||||
|
req,
|
||||||
|
1*time.Second,
|
||||||
|
)
|
||||||
|
testHTTPContainerInfoGetter(req, cinfo, "", "", 0, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPContainerInfoGetterGetContainerInfoWithError(t *testing.T) {
|
||||||
|
req := &info.ContainerInfoRequest{
|
||||||
|
NumStats: 10,
|
||||||
|
NumSamples: 10,
|
||||||
|
}
|
||||||
|
req = req.FillDefaults()
|
||||||
|
cinfo := itest.GenerateRandomContainerInfo(
|
||||||
|
"dockerIDWhichWillNotBeChecked", // docker ID
|
||||||
|
2, // Number of cores
|
||||||
|
req,
|
||||||
|
1*time.Second,
|
||||||
|
)
|
||||||
|
testHTTPContainerInfoGetter(req, cinfo, "somePodID", "containerNameInK8S", http.StatusNotFound, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPContainerInfoGetterGetMachineInfoWithError(t *testing.T) {
|
||||||
|
req := &info.ContainerInfoRequest{
|
||||||
|
NumStats: 10,
|
||||||
|
NumSamples: 10,
|
||||||
|
}
|
||||||
|
req = req.FillDefaults()
|
||||||
|
cinfo := itest.GenerateRandomContainerInfo(
|
||||||
|
"dockerIDWhichWillNotBeChecked", // docker ID
|
||||||
|
2, // Number of cores
|
||||||
|
req,
|
||||||
|
1*time.Second,
|
||||||
|
)
|
||||||
|
testHTTPContainerInfoGetter(req, cinfo, "", "", http.StatusNotFound, t)
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.3 (2014-07-14)
|
||||||
|
- Add support for systemd systems.
|
||||||
|
- Fixes for UI with InfluxDB storage driver.
|
||||||
|
|
||||||
## 0.1.2 (2014-07-10)
|
## 0.1.2 (2014-07-10)
|
||||||
- Added Storage Driver concept (flag: storage_driver), default is the in-memory driver
|
- Added Storage Driver concept (flag: storage_driver), default is the in-memory driver
|
||||||
- Implemented InfluxDB storage driver
|
- Implemented InfluxDB storage driver
|
||||||
|
@ -110,32 +110,7 @@ func TestGetContainerInfo(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We cannot use DeepEqual() to compare them directly,
|
if !returned.Eq(cinfo) {
|
||||||
// because json en/decoded time may have precision issues.
|
t.Error("received unexpected ContainerInfo")
|
||||||
if !reflect.DeepEqual(returned.ContainerReference, cinfo.ContainerReference) {
|
|
||||||
t.Errorf("received unexpected container ref")
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(returned.Subcontainers, cinfo.Subcontainers) {
|
|
||||||
t.Errorf("received unexpected subcontainers")
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(returned.Spec, cinfo.Spec) {
|
|
||||||
t.Errorf("received unexpected spec")
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(returned.StatsPercentiles, cinfo.StatsPercentiles) {
|
|
||||||
t.Errorf("received unexpected spec")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, expectedStats := range cinfo.Stats {
|
|
||||||
returnedStats := returned.Stats[i]
|
|
||||||
if !expectedStats.Eq(returnedStats) {
|
|
||||||
t.Errorf("received unexpected stats")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, expectedSample := range cinfo.Samples {
|
|
||||||
returnedSample := returned.Samples[i]
|
|
||||||
if !expectedSample.Eq(returnedSample) {
|
|
||||||
t.Errorf("received unexpected sample")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,14 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/cgroups"
|
"github.com/docker/libcontainer/cgroups"
|
||||||
"github.com/docker/libcontainer/cgroups/fs"
|
"github.com/docker/libcontainer/cgroups/fs"
|
||||||
|
"github.com/docker/libcontainer/cgroups/systemd"
|
||||||
"github.com/fsouza/go-dockerclient"
|
"github.com/fsouza/go-dockerclient"
|
||||||
"github.com/google/cadvisor/container"
|
"github.com/google/cadvisor/container"
|
||||||
"github.com/google/cadvisor/info"
|
"github.com/google/cadvisor/info"
|
||||||
@ -98,8 +100,11 @@ func (self *dockerContainerHandler) splitName() (string, string, error) {
|
|||||||
if nestedLevels > 0 {
|
if nestedLevels > 0 {
|
||||||
// we are running inside a docker container
|
// we are running inside a docker container
|
||||||
upperLevel := strings.Repeat("../../", nestedLevels)
|
upperLevel := strings.Repeat("../../", nestedLevels)
|
||||||
//parent = strings.Join([]string{parent, upperLevel}, "/")
|
parent = filepath.Join(upperLevel, parent)
|
||||||
parent = fmt.Sprintf("%v%v", upperLevel, parent)
|
}
|
||||||
|
// Strip the last "/"
|
||||||
|
if parent[len(parent)-1] == '/' {
|
||||||
|
parent = parent[:len(parent)-1]
|
||||||
}
|
}
|
||||||
return parent, id, nil
|
return parent, id, nil
|
||||||
}
|
}
|
||||||
@ -237,7 +242,15 @@ func (self *dockerContainerHandler) GetStats() (stats *info.ContainerStats, err
|
|||||||
Parent: parent,
|
Parent: parent,
|
||||||
Name: id,
|
Name: id,
|
||||||
}
|
}
|
||||||
s, err := fs.GetStats(cg)
|
|
||||||
|
// TODO(vmarmol): Use libcontainer's Stats() in the new API when that is ready.
|
||||||
|
// Use systemd paths if systemd is being used.
|
||||||
|
var s *cgroups.Stats
|
||||||
|
if systemd.UseSystemd() {
|
||||||
|
s, err = systemd.GetStats(cg)
|
||||||
|
} else {
|
||||||
|
s, err = fs.GetStats(cg)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ FROM busybox:ubuntu-14.04
|
|||||||
MAINTAINER kyurtsever@google.com dengnan@google.com vmarmol@google.com jason@swindle.me
|
MAINTAINER kyurtsever@google.com dengnan@google.com vmarmol@google.com jason@swindle.me
|
||||||
|
|
||||||
# Get cAdvisor binaries.
|
# Get cAdvisor binaries.
|
||||||
ADD http://storage.googleapis.com/cadvisor-bin/cadvisor /usr/bin/cadvisor
|
ADD http://storage.googleapis.com/cadvisor-bin/cadvisor-0.1.3 /usr/bin/cadvisor
|
||||||
RUN chmod +x /usr/bin/cadvisor
|
RUN chmod +x /usr/bin/cadvisor
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y -q --no-install-recommends pkg-config l
|
|||||||
# Get the lcmtfy and cAdvisor binaries.
|
# Get the lcmtfy and cAdvisor binaries.
|
||||||
ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/lmctfy /usr/bin/lmctfy
|
ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/lmctfy /usr/bin/lmctfy
|
||||||
ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0
|
ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0
|
||||||
ADD http://storage.googleapis.com/cadvisor-bin/cadvisor-0.1.2 /usr/bin/cadvisor
|
ADD http://storage.googleapis.com/cadvisor-bin/cadvisor-0.1.3 /usr/bin/cadvisor
|
||||||
RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor
|
RUN chmod +x /usr/bin/lmctfy && chmod +x /usr/bin/cadvisor
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
@ -110,6 +110,52 @@ type ContainerInfo struct {
|
|||||||
StatsPercentiles *ContainerStatsPercentiles `json:"stats_summary,omitempty"`
|
StatsPercentiles *ContainerStatsPercentiles `json:"stats_summary,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContainerInfo may be (un)marshaled by json or other en/decoder. In that
|
||||||
|
// case, the Timestamp field in each stats/sample may not be precisely
|
||||||
|
// en/decoded. This will lead to small but acceptable differences between a
|
||||||
|
// ContainerInfo and its encode-then-decode version. Eq() is used to compare
|
||||||
|
// two ContainerInfo accepting small difference (<10ms) of Time fields.
|
||||||
|
func (self *ContainerInfo) Eq(b *ContainerInfo) bool {
|
||||||
|
|
||||||
|
// If both self and b are nil, then Eq() returns true
|
||||||
|
if self == nil {
|
||||||
|
return b == nil
|
||||||
|
}
|
||||||
|
if b == nil {
|
||||||
|
return self == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For fields other than time.Time, we will compare them precisely.
|
||||||
|
// This would require that any slice should have same order.
|
||||||
|
if !reflect.DeepEqual(self.ContainerReference, b.ContainerReference) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(self.Subcontainers, b.Subcontainers) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(self.Spec, b.Spec) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(self.StatsPercentiles, b.StatsPercentiles) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, expectedStats := range b.Stats {
|
||||||
|
selfStats := self.Stats[i]
|
||||||
|
if !expectedStats.Eq(selfStats) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, expectedSample := range b.Samples {
|
||||||
|
selfSample := self.Samples[i]
|
||||||
|
if !expectedSample.Eq(selfSample) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (self *ContainerInfo) StatsAfter(ref time.Time) []*ContainerStats {
|
func (self *ContainerInfo) StatsAfter(ref time.Time) []*ContainerStats {
|
||||||
n := len(self.Stats) + 1
|
n := len(self.Stats) + 1
|
||||||
for i, s := range self.Stats {
|
for i, s := range self.Stats {
|
||||||
|
@ -15,4 +15,4 @@
|
|||||||
package info
|
package info
|
||||||
|
|
||||||
// Version of cAdvisor.
|
// Version of cAdvisor.
|
||||||
const VERSION = "0.1.2"
|
const VERSION = "0.1.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user