2014-06-06 23:40:48 +00:00
|
|
|
/*
|
|
|
|
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 kubelet
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2014-07-01 21:05:10 +00:00
|
|
|
"errors"
|
2014-06-06 23:40:48 +00:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2014-07-25 20:16:59 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2014-06-06 23:40:48 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
2014-07-15 18:39:19 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/health"
|
2014-06-30 19:00:14 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
2014-06-06 23:40:48 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
2014-07-15 01:39:30 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
|
2014-06-06 23:40:48 +00:00
|
|
|
"github.com/coreos/go-etcd/etcd"
|
|
|
|
"github.com/fsouza/go-dockerclient"
|
2014-06-25 03:51:57 +00:00
|
|
|
"github.com/golang/glog"
|
2014-06-19 00:31:18 +00:00
|
|
|
"github.com/google/cadvisor/info"
|
2014-06-06 23:40:48 +00:00
|
|
|
)
|
|
|
|
|
2014-07-14 20:12:44 +00:00
|
|
|
const defaultChanSize = 1024
|
|
|
|
|
2014-07-15 23:49:34 +00:00
|
|
|
// taken from lmctfy https://github.com/google/lmctfy/blob/master/lmctfy/controllers/cpu_controller.cc
|
|
|
|
const minShares = 2
|
2014-07-16 09:46:22 +00:00
|
|
|
const sharesPerCPU = 1024
|
|
|
|
const milliCPUToCPU = 1000
|
2014-06-24 23:31:33 +00:00
|
|
|
|
2014-07-10 12:26:24 +00:00
|
|
|
// CadvisorInterface is an abstract interface for testability. It abstracts the interface of "github.com/google/cadvisor/client".Client.
|
2014-06-19 00:31:18 +00:00
|
|
|
type CadvisorInterface interface {
|
2014-07-14 21:48:51 +00:00
|
|
|
ContainerInfo(name string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error)
|
2014-06-19 00:31:18 +00:00
|
|
|
MachineInfo() (*info.MachineInfo, error)
|
|
|
|
}
|
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
// SyncHandler is an interface implemented by Kubelet, for testability
|
|
|
|
type SyncHandler interface {
|
|
|
|
SyncPods([]Pod) error
|
|
|
|
}
|
|
|
|
|
|
|
|
type volumeMap map[string]volume.Interface
|
|
|
|
|
2014-07-22 21:40:59 +00:00
|
|
|
// New creates a new Kubelet for use in main
|
|
|
|
func NewMainKubelet(
|
|
|
|
hn string,
|
|
|
|
dc DockerInterface,
|
|
|
|
cc CadvisorInterface,
|
2014-07-19 00:13:34 +00:00
|
|
|
ec tools.EtcdClient,
|
|
|
|
rd string) *Kubelet {
|
2014-07-22 21:40:59 +00:00
|
|
|
return &Kubelet{
|
|
|
|
hostname: hn,
|
|
|
|
dockerClient: dc,
|
|
|
|
cadvisorClient: cc,
|
|
|
|
etcdClient: ec,
|
2014-07-19 00:13:34 +00:00
|
|
|
rootDirectory: rd,
|
2014-07-18 18:42:47 +00:00
|
|
|
podWorkers: newPodWorkers(),
|
2014-07-22 21:40:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewIntegrationTestKubelet creates a new Kubelet for use in integration tests.
|
|
|
|
// TODO: add more integration tests, and expand parameter list as needed.
|
|
|
|
func NewIntegrationTestKubelet(hn string, dc DockerInterface) *Kubelet {
|
|
|
|
return &Kubelet{
|
|
|
|
hostname: hn,
|
|
|
|
dockerClient: dc,
|
|
|
|
dockerPuller: &FakeDockerPuller{},
|
2014-07-18 18:42:47 +00:00
|
|
|
podWorkers: newPodWorkers(),
|
2014-07-22 21:40:59 +00:00
|
|
|
}
|
2014-07-01 16:15:49 +00:00
|
|
|
}
|
|
|
|
|
2014-07-10 12:26:24 +00:00
|
|
|
// Kubelet is the main kubelet implementation.
|
2014-06-06 23:40:48 +00:00
|
|
|
type Kubelet struct {
|
2014-07-19 00:13:34 +00:00
|
|
|
hostname string
|
|
|
|
dockerClient DockerInterface
|
|
|
|
rootDirectory string
|
2014-07-18 18:42:47 +00:00
|
|
|
podWorkers podWorkers
|
2014-07-15 20:24:41 +00:00
|
|
|
|
|
|
|
// Optional, no events will be sent without it
|
2014-07-22 21:40:59 +00:00
|
|
|
etcdClient tools.EtcdClient
|
2014-07-15 20:24:41 +00:00
|
|
|
// Optional, no statistics will be available if omitted
|
2014-07-22 21:40:59 +00:00
|
|
|
cadvisorClient CadvisorInterface
|
2014-07-15 20:24:41 +00:00
|
|
|
// Optional, defaults to simple implementaiton
|
2014-07-22 21:40:59 +00:00
|
|
|
healthChecker health.HealthChecker
|
2014-07-15 20:24:41 +00:00
|
|
|
// Optional, defaults to simple Docker implementation
|
2014-07-22 21:40:59 +00:00
|
|
|
dockerPuller DockerPuller
|
2014-07-15 20:24:41 +00:00
|
|
|
// Optional, defaults to /logs/ from /var/log
|
2014-07-22 21:40:59 +00:00
|
|
|
logServer http.Handler
|
2014-07-15 20:24:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run starts the kubelet reacting to config updates
|
|
|
|
func (kl *Kubelet) Run(updates <-chan PodUpdate) {
|
2014-07-22 21:40:59 +00:00
|
|
|
if kl.logServer == nil {
|
|
|
|
kl.logServer = http.StripPrefix("/logs/", http.FileServer(http.Dir("/var/log/")))
|
2014-07-15 07:04:30 +00:00
|
|
|
}
|
2014-07-22 21:40:59 +00:00
|
|
|
if kl.dockerPuller == nil {
|
|
|
|
kl.dockerPuller = NewDockerPuller(kl.dockerClient)
|
2014-06-24 23:31:33 +00:00
|
|
|
}
|
2014-07-22 21:40:59 +00:00
|
|
|
if kl.healthChecker == nil {
|
|
|
|
kl.healthChecker = health.NewHealthChecker()
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2014-07-15 20:24:41 +00:00
|
|
|
kl.syncLoop(updates, kl)
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2014-07-18 18:42:47 +00:00
|
|
|
// Per-pod workers.
|
|
|
|
type podWorkers struct {
|
|
|
|
lock sync.Mutex
|
|
|
|
|
|
|
|
// Set of pods with existing workers.
|
|
|
|
workers util.StringSet
|
|
|
|
}
|
|
|
|
|
|
|
|
func newPodWorkers() podWorkers {
|
|
|
|
return podWorkers{
|
|
|
|
workers: util.NewStringSet(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs a worker for "podFullName" asynchronously with the specified "action".
|
|
|
|
// If the worker for the "podFullName" is already running, functions as a no-op.
|
|
|
|
func (self *podWorkers) Run(podFullName string, action func()) {
|
|
|
|
self.lock.Lock()
|
|
|
|
defer self.lock.Unlock()
|
|
|
|
|
|
|
|
// This worker is already running, let it finish.
|
|
|
|
if self.workers.Has(podFullName) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
self.workers.Insert(podFullName)
|
|
|
|
|
|
|
|
// Run worker async.
|
|
|
|
go func() {
|
|
|
|
defer util.HandleCrash()
|
|
|
|
action()
|
|
|
|
|
|
|
|
self.lock.Lock()
|
|
|
|
defer self.lock.Unlock()
|
|
|
|
self.workers.Delete(podFullName)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2014-07-10 12:26:24 +00:00
|
|
|
// LogEvent logs an event to the etcd backend.
|
2014-06-09 03:35:07 +00:00
|
|
|
func (kl *Kubelet) LogEvent(event *api.Event) error {
|
2014-07-22 21:40:59 +00:00
|
|
|
if kl.etcdClient == nil {
|
2014-07-10 12:26:24 +00:00
|
|
|
return fmt.Errorf("no etcd client connection")
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
event.Timestamp = time.Now().Unix()
|
|
|
|
data, err := json.Marshal(event)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var response *etcd.Response
|
2014-07-22 21:40:59 +00:00
|
|
|
response, err = kl.etcdClient.AddChild(fmt.Sprintf("/events/%s", event.Container.Name), string(data), 60*60*48 /* 2 days */)
|
2014-06-06 23:40:48 +00:00
|
|
|
// TODO(bburns) : examine response here.
|
|
|
|
if err != nil {
|
2014-06-25 03:51:57 +00:00
|
|
|
glog.Errorf("Error writing event: %s\n", err)
|
2014-06-06 23:40:48 +00:00
|
|
|
if response != nil {
|
2014-06-29 05:16:26 +00:00
|
|
|
glog.Infof("Response was: %v\n", *response)
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-06-09 20:47:25 +00:00
|
|
|
func makeEnvironmentVariables(container *api.Container) []string {
|
|
|
|
var result []string
|
2014-06-06 23:40:48 +00:00
|
|
|
for _, value := range container.Env {
|
2014-06-09 20:47:25 +00:00
|
|
|
result = append(result, fmt.Sprintf("%s=%s", value.Name, value.Value))
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2014-06-09 20:47:25 +00:00
|
|
|
return result
|
|
|
|
}
|
2014-06-06 23:40:48 +00:00
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
func makeVolumesAndBinds(pod *Pod, container *api.Container, podVolumes volumeMap) (map[string]struct{}, []string) {
|
2014-06-06 23:40:48 +00:00
|
|
|
volumes := map[string]struct{}{}
|
|
|
|
binds := []string{}
|
|
|
|
for _, volume := range container.VolumeMounts {
|
2014-06-19 23:59:48 +00:00
|
|
|
var basePath string
|
2014-07-16 19:32:59 +00:00
|
|
|
if vol, ok := podVolumes[volume.Name]; ok {
|
2014-06-19 23:59:48 +00:00
|
|
|
// Host volumes are not Docker volumes and are directly mounted from the host.
|
2014-07-16 19:32:59 +00:00
|
|
|
basePath = fmt.Sprintf("%s:%s", vol.GetPath(), volume.MountPath)
|
2014-07-15 01:39:30 +00:00
|
|
|
} else if volume.MountType == "HOST" {
|
2014-07-16 19:32:59 +00:00
|
|
|
// DEPRECATED: VolumeMount.MountType will be handled by the Volume struct.
|
2014-06-19 23:59:48 +00:00
|
|
|
basePath = fmt.Sprintf("%s:%s", volume.MountPath, volume.MountPath)
|
|
|
|
} else {
|
2014-07-16 19:32:59 +00:00
|
|
|
// TODO(jonesdl) This clause should be deleted and an error should be thrown. The default
|
|
|
|
// behavior is now supported by the EmptyDirectory type.
|
2014-06-19 23:59:48 +00:00
|
|
|
volumes[volume.MountPath] = struct{}{}
|
2014-07-15 20:24:41 +00:00
|
|
|
basePath = fmt.Sprintf("/exports/%s/%s:%s", GetPodFullName(pod), volume.Name, volume.MountPath)
|
2014-06-19 23:59:48 +00:00
|
|
|
}
|
2014-06-06 23:40:48 +00:00
|
|
|
if volume.ReadOnly {
|
|
|
|
basePath += ":ro"
|
|
|
|
}
|
|
|
|
binds = append(binds, basePath)
|
|
|
|
}
|
2014-06-09 20:47:25 +00:00
|
|
|
return volumes, binds
|
|
|
|
}
|
2014-06-06 23:40:48 +00:00
|
|
|
|
2014-06-09 20:47:25 +00:00
|
|
|
func makePortsAndBindings(container *api.Container) (map[docker.Port]struct{}, map[docker.Port][]docker.PortBinding) {
|
2014-06-06 23:40:48 +00:00
|
|
|
exposedPorts := map[docker.Port]struct{}{}
|
|
|
|
portBindings := map[docker.Port][]docker.PortBinding{}
|
|
|
|
for _, port := range container.Ports {
|
|
|
|
interiorPort := port.ContainerPort
|
|
|
|
exteriorPort := port.HostPort
|
|
|
|
// Some of this port stuff is under-documented voodoo.
|
|
|
|
// See http://stackoverflow.com/questions/20428302/binding-a-port-to-a-host-interface-using-the-rest-api
|
2014-06-16 04:19:35 +00:00
|
|
|
var protocol string
|
2014-07-08 04:32:56 +00:00
|
|
|
switch strings.ToUpper(port.Protocol) {
|
|
|
|
case "UDP":
|
2014-06-16 04:19:35 +00:00
|
|
|
protocol = "/udp"
|
2014-07-08 04:32:56 +00:00
|
|
|
case "TCP":
|
2014-06-16 04:19:35 +00:00
|
|
|
protocol = "/tcp"
|
|
|
|
default:
|
2014-07-08 04:32:56 +00:00
|
|
|
glog.Infof("Unknown protocol '%s': defaulting to TCP", port.Protocol)
|
2014-06-16 04:19:35 +00:00
|
|
|
protocol = "/tcp"
|
|
|
|
}
|
|
|
|
dockerPort := docker.Port(strconv.Itoa(interiorPort) + protocol)
|
2014-06-06 23:40:48 +00:00
|
|
|
exposedPorts[dockerPort] = struct{}{}
|
|
|
|
portBindings[dockerPort] = []docker.PortBinding{
|
2014-06-12 21:09:40 +00:00
|
|
|
{
|
2014-06-06 23:40:48 +00:00
|
|
|
HostPort: strconv.Itoa(exteriorPort),
|
2014-07-09 05:44:15 +00:00
|
|
|
HostIp: port.HostIP,
|
2014-06-06 23:40:48 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2014-06-09 20:47:25 +00:00
|
|
|
return exposedPorts, portBindings
|
|
|
|
}
|
|
|
|
|
2014-07-16 09:46:22 +00:00
|
|
|
func milliCPUToShares(milliCPU int) int {
|
2014-07-29 18:34:16 +00:00
|
|
|
if milliCPU == 0 {
|
|
|
|
// zero milliCPU means unset. Use kernel default.
|
|
|
|
return 0
|
|
|
|
}
|
2014-07-16 09:46:22 +00:00
|
|
|
// Conceptually (milliCPU / milliCPUToCPU) * sharesPerCPU, but factored to improve rounding.
|
|
|
|
shares := (milliCPU * sharesPerCPU) / milliCPUToCPU
|
2014-07-15 23:49:34 +00:00
|
|
|
if shares < minShares {
|
|
|
|
return minShares
|
2014-06-19 12:29:42 +00:00
|
|
|
}
|
2014-07-15 23:49:34 +00:00
|
|
|
return shares
|
2014-06-19 12:29:42 +00:00
|
|
|
}
|
|
|
|
|
2014-07-15 01:39:30 +00:00
|
|
|
func (kl *Kubelet) mountExternalVolumes(manifest *api.ContainerManifest) (volumeMap, error) {
|
|
|
|
podVolumes := make(volumeMap)
|
|
|
|
for _, vol := range manifest.Volumes {
|
2014-07-25 21:17:02 +00:00
|
|
|
extVolume, err := volume.CreateVolumeBuilder(&vol, manifest.ID, kl.rootDirectory)
|
2014-07-15 01:39:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-07-16 19:32:59 +00:00
|
|
|
// TODO(jonesdl) When the default volume behavior is no longer supported, this case
|
|
|
|
// should never occur and an error should be thrown instead.
|
|
|
|
if extVolume == nil {
|
|
|
|
continue
|
2014-07-15 01:39:30 +00:00
|
|
|
}
|
2014-07-16 19:32:59 +00:00
|
|
|
podVolumes[vol.Name] = extVolume
|
2014-07-24 20:45:55 +00:00
|
|
|
err = extVolume.SetUp()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-07-15 01:39:30 +00:00
|
|
|
}
|
|
|
|
return podVolumes, nil
|
|
|
|
}
|
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
// Run a single container from a pod. Returns the docker container ID
|
|
|
|
func (kl *Kubelet) runContainer(pod *Pod, container *api.Container, podVolumes volumeMap, netMode string) (id DockerID, err error) {
|
2014-06-09 20:47:25 +00:00
|
|
|
envVariables := makeEnvironmentVariables(container)
|
2014-07-15 20:24:41 +00:00
|
|
|
volumes, binds := makeVolumesAndBinds(pod, container, podVolumes)
|
2014-06-09 20:47:25 +00:00
|
|
|
exposedPorts, portBindings := makePortsAndBindings(container)
|
|
|
|
|
2014-06-06 23:40:48 +00:00
|
|
|
opts := docker.CreateContainerOptions{
|
2014-07-15 20:24:41 +00:00
|
|
|
Name: buildDockerName(pod, container),
|
2014-06-06 23:40:48 +00:00
|
|
|
Config: &docker.Config{
|
2014-07-15 03:56:18 +00:00
|
|
|
Cmd: container.Command,
|
|
|
|
Env: envVariables,
|
|
|
|
ExposedPorts: exposedPorts,
|
2014-07-10 19:25:57 +00:00
|
|
|
Hostname: container.Name,
|
2014-06-06 23:40:48 +00:00
|
|
|
Image: container.Image,
|
2014-07-30 23:56:50 +00:00
|
|
|
Memory: int64(container.Memory),
|
2014-07-16 09:46:22 +00:00
|
|
|
CpuShares: int64(milliCPUToShares(container.CPU)),
|
2014-06-06 23:40:48 +00:00
|
|
|
Volumes: volumes,
|
|
|
|
WorkingDir: container.WorkingDir,
|
|
|
|
},
|
|
|
|
}
|
2014-07-22 21:40:59 +00:00
|
|
|
dockerContainer, err := kl.dockerClient.CreateContainer(opts)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2014-07-22 21:40:59 +00:00
|
|
|
err = kl.dockerClient.StartContainer(dockerContainer.ID, &docker.HostConfig{
|
2014-06-06 23:40:48 +00:00
|
|
|
PortBindings: portBindings,
|
|
|
|
Binds: binds,
|
2014-06-20 03:30:42 +00:00
|
|
|
NetworkMode: netMode,
|
2014-06-06 23:40:48 +00:00
|
|
|
})
|
2014-07-02 18:21:29 +00:00
|
|
|
return DockerID(dockerContainer.ID), err
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 23:24:20 +00:00
|
|
|
// Kill a docker container
|
2014-07-15 20:24:41 +00:00
|
|
|
func (kl *Kubelet) killContainer(dockerContainer docker.APIContainers) error {
|
2014-07-18 18:42:47 +00:00
|
|
|
glog.Infof("Killing: %s", dockerContainer.ID)
|
2014-07-22 21:40:59 +00:00
|
|
|
err := kl.dockerClient.StopContainer(dockerContainer.ID, 10)
|
2014-07-15 20:24:41 +00:00
|
|
|
podFullName, containerName := parseDockerName(dockerContainer.Names[0])
|
2014-06-09 03:35:07 +00:00
|
|
|
kl.LogEvent(&api.Event{
|
2014-06-06 23:40:48 +00:00
|
|
|
Event: "STOP",
|
|
|
|
Manifest: &api.ContainerManifest{
|
2014-07-15 20:24:41 +00:00
|
|
|
//TODO: This should be reported using either the apiserver schema or the kubelet schema
|
|
|
|
ID: podFullName,
|
2014-06-06 23:40:48 +00:00
|
|
|
},
|
|
|
|
Container: &api.Container{
|
|
|
|
Name: containerName,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-07-19 07:09:43 +00:00
|
|
|
const (
|
|
|
|
networkContainerName = "net"
|
|
|
|
networkContainerImage = "kubernetes/pause:latest"
|
|
|
|
)
|
2014-06-20 03:30:42 +00:00
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
// createNetworkContainer starts the network container for a pod. Returns the docker container ID of the newly created container.
|
|
|
|
func (kl *Kubelet) createNetworkContainer(pod *Pod) (DockerID, error) {
|
2014-06-20 03:30:42 +00:00
|
|
|
var ports []api.Port
|
|
|
|
// Docker only exports ports from the network container. Let's
|
2014-06-20 15:55:02 +00:00
|
|
|
// collect all of the relevant ports and export them.
|
2014-07-15 20:24:41 +00:00
|
|
|
for _, container := range pod.Manifest.Containers {
|
2014-06-20 03:30:42 +00:00
|
|
|
ports = append(ports, container.Ports...)
|
|
|
|
}
|
|
|
|
container := &api.Container{
|
2014-07-19 07:09:43 +00:00
|
|
|
Name: networkContainerName,
|
|
|
|
Image: networkContainerImage,
|
|
|
|
Ports: ports,
|
2014-06-20 03:30:42 +00:00
|
|
|
}
|
2014-07-22 21:40:59 +00:00
|
|
|
kl.dockerPuller.Pull(networkContainerImage)
|
2014-07-15 20:24:41 +00:00
|
|
|
return kl.runContainer(pod, container, nil, "")
|
2014-06-20 03:30:42 +00:00
|
|
|
}
|
|
|
|
|
2014-07-18 18:42:47 +00:00
|
|
|
type empty struct{}
|
|
|
|
|
|
|
|
func (kl *Kubelet) syncPod(pod *Pod, dockerContainers DockerContainers) error {
|
2014-07-15 20:24:41 +00:00
|
|
|
podFullName := GetPodFullName(pod)
|
2014-07-18 18:42:47 +00:00
|
|
|
containersToKeep := make(map[DockerID]empty)
|
|
|
|
killedContainers := make(map[DockerID]empty)
|
2014-07-15 20:24:41 +00:00
|
|
|
|
2014-07-18 18:42:47 +00:00
|
|
|
// Make sure we have a network container
|
2014-07-15 17:26:56 +00:00
|
|
|
var netID DockerID
|
2014-07-15 20:24:41 +00:00
|
|
|
if networkDockerContainer, found := dockerContainers.FindPodContainer(podFullName, networkContainerName); found {
|
2014-07-15 17:26:56 +00:00
|
|
|
netID = DockerID(networkDockerContainer.ID)
|
|
|
|
} else {
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.Infof("Network container doesn't exist, creating")
|
|
|
|
dockerNetworkID, err := kl.createNetworkContainer(pod)
|
2014-06-20 03:30:42 +00:00
|
|
|
if err != nil {
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.Errorf("Failed to introspect network container. (%v) Skipping pod %s", err, podFullName)
|
2014-07-01 05:27:56 +00:00
|
|
|
return err
|
|
|
|
}
|
2014-07-15 17:26:56 +00:00
|
|
|
netID = dockerNetworkID
|
2014-07-01 05:27:56 +00:00
|
|
|
}
|
2014-07-18 18:42:47 +00:00
|
|
|
containersToKeep[netID] = empty{}
|
2014-07-15 20:24:41 +00:00
|
|
|
|
|
|
|
podVolumes, err := kl.mountExternalVolumes(&pod.Manifest)
|
2014-07-16 19:32:59 +00:00
|
|
|
if err != nil {
|
2014-07-24 20:45:55 +00:00
|
|
|
glog.Errorf("Unable to mount volumes for pod %s: (%v) Skipping pod.", podFullName, err)
|
|
|
|
return err
|
2014-07-16 19:32:59 +00:00
|
|
|
}
|
2014-07-15 20:24:41 +00:00
|
|
|
|
2014-08-01 00:35:54 +00:00
|
|
|
podState := api.PodState{}
|
|
|
|
info, err := kl.GetPodInfo(podFullName)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Unable to get pod info, health checks may be invalid.")
|
|
|
|
}
|
|
|
|
netInfo, found := info[networkContainerName]
|
|
|
|
if found && netInfo.NetworkSettings != nil {
|
|
|
|
podState.PodIP = netInfo.NetworkSettings.IPAddress
|
|
|
|
}
|
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
for _, container := range pod.Manifest.Containers {
|
|
|
|
if dockerContainer, found := dockerContainers.FindPodContainer(podFullName, container.Name); found {
|
2014-07-15 17:26:56 +00:00
|
|
|
containerID := DockerID(dockerContainer.ID)
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.Infof("pod %s container %s exists as %v", podFullName, container.Name, containerID)
|
|
|
|
glog.V(1).Infof("pod %s container %s exists as %v", podFullName, container.Name, containerID)
|
2014-07-15 17:26:56 +00:00
|
|
|
|
2014-07-03 05:35:50 +00:00
|
|
|
// TODO: This should probably be separated out into a separate goroutine.
|
2014-08-01 00:35:54 +00:00
|
|
|
healthy, err := kl.healthy(podState, container, dockerContainer)
|
2014-07-03 05:35:50 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.V(1).Infof("health check errored: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
2014-07-15 17:26:56 +00:00
|
|
|
if healthy == health.Healthy {
|
2014-07-18 18:42:47 +00:00
|
|
|
containersToKeep[containerID] = empty{}
|
2014-07-15 17:26:56 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.V(1).Infof("pod %s container %s is unhealthy.", podFullName, container.Name, healthy)
|
2014-07-15 17:26:56 +00:00
|
|
|
if err := kl.killContainer(*dockerContainer); err != nil {
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.V(1).Infof("Failed to kill container %s: %v", dockerContainer.ID, err)
|
2014-07-15 17:26:56 +00:00
|
|
|
continue
|
2014-07-03 05:35:50 +00:00
|
|
|
}
|
2014-07-18 18:42:47 +00:00
|
|
|
killedContainers[containerID] = empty{}
|
2014-07-01 05:27:56 +00:00
|
|
|
}
|
2014-07-15 17:26:56 +00:00
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.Infof("Container doesn't exist, creating %#v", container)
|
2014-07-22 21:40:59 +00:00
|
|
|
if err := kl.dockerPuller.Pull(container.Image); err != nil {
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.Errorf("Failed to pull image: %v skipping pod %s container %s.", err, podFullName, container.Name)
|
2014-07-15 17:26:56 +00:00
|
|
|
continue
|
|
|
|
}
|
2014-07-15 20:24:41 +00:00
|
|
|
containerID, err := kl.runContainer(pod, &container, podVolumes, "container:"+string(netID))
|
2014-07-15 17:26:56 +00:00
|
|
|
if err != nil {
|
|
|
|
// TODO(bburns) : Perhaps blacklist a container after N failures?
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.Errorf("Error running pod %s container %s: %v", podFullName, container.Name, err)
|
2014-07-15 17:26:56 +00:00
|
|
|
continue
|
|
|
|
}
|
2014-07-18 18:42:47 +00:00
|
|
|
containersToKeep[containerID] = empty{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kill any containers in this pod which were not identified above (guards against duplicates).
|
|
|
|
for id, container := range dockerContainers {
|
|
|
|
curPodFullName, _ := parseDockerName(container.Names[0])
|
|
|
|
if curPodFullName == podFullName {
|
|
|
|
// Don't kill containers we want to keep or those we already killed.
|
|
|
|
_, keep := containersToKeep[id]
|
|
|
|
_, killed := killedContainers[id]
|
|
|
|
if !keep && !killed {
|
|
|
|
err = kl.killContainer(*container)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Error killing container: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
2014-07-18 18:42:47 +00:00
|
|
|
|
2014-07-01 05:27:56 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-07-18 18:42:47 +00:00
|
|
|
type podContainer struct {
|
|
|
|
podFullName string
|
|
|
|
containerName string
|
|
|
|
}
|
2014-07-03 01:06:54 +00:00
|
|
|
|
2014-07-25 21:17:02 +00:00
|
|
|
// Stores all volumes defined by the set of pods in a map.
|
2014-07-25 20:16:59 +00:00
|
|
|
func determineValidVolumes(pods []Pod) map[string]api.Volume {
|
|
|
|
validVolumes := make(map[string]api.Volume)
|
|
|
|
for _, pod := range pods {
|
|
|
|
for _, volume := range pod.Manifest.Volumes {
|
2014-07-25 21:17:02 +00:00
|
|
|
identifier := pod.Manifest.ID + "/" + volume.Name
|
2014-07-25 20:16:59 +00:00
|
|
|
validVolumes[identifier] = volume
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return validVolumes
|
|
|
|
}
|
|
|
|
|
2014-07-25 21:17:02 +00:00
|
|
|
// Examines directory structure to determine volumes that are presently
|
|
|
|
// active and mounted. Builds their respective Cleaner type in case they need to be deleted.
|
|
|
|
func (kl *Kubelet) determineActiveVolumes() map[string]volume.Cleaner {
|
|
|
|
activeVolumes := make(map[string]volume.Cleaner)
|
2014-07-25 20:16:59 +00:00
|
|
|
filepath.Walk(kl.rootDirectory, func(path string, info os.FileInfo, err error) error {
|
2014-07-25 21:17:02 +00:00
|
|
|
// Search for volume dir structure : $ROOTDIR/$PODID/volumes/$VOLUMETYPE/$VOLUMENAME
|
2014-07-25 20:16:59 +00:00
|
|
|
var name string
|
|
|
|
var podID string
|
2014-07-25 21:17:02 +00:00
|
|
|
// Extract volume type for dir structure
|
2014-07-25 20:16:59 +00:00
|
|
|
dir := getDir(path)
|
|
|
|
glog.Infof("Traversing filepath %s", path)
|
2014-07-25 21:17:02 +00:00
|
|
|
// Handle emptyDirectory types.
|
2014-07-25 20:16:59 +00:00
|
|
|
if dir == "empty" {
|
|
|
|
name = info.Name()
|
2014-07-25 21:17:02 +00:00
|
|
|
// Retrieve podID from dir structure
|
2014-07-25 20:16:59 +00:00
|
|
|
podID = getDir(filepath.Dir(filepath.Dir(path)))
|
2014-07-25 21:17:02 +00:00
|
|
|
glog.Infof("Found active volume %s of pod %s", name, podID)
|
|
|
|
identifier := podID + "/" + name
|
|
|
|
activeVolumes[identifier] = &volume.EmptyDirectoryCleaner{path}
|
2014-07-25 20:16:59 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return activeVolumes
|
|
|
|
}
|
|
|
|
|
2014-07-25 21:17:02 +00:00
|
|
|
// Utility function to extract only the directory name.
|
2014-07-25 20:16:59 +00:00
|
|
|
func getDir(path string) string {
|
|
|
|
return filepath.Base(filepath.Dir(path))
|
|
|
|
}
|
|
|
|
|
2014-07-25 21:17:02 +00:00
|
|
|
// Compares the map of active volumes to the map of valid volumes.
|
|
|
|
// If an active volume does not have a respective valid volume, clean it up.
|
2014-07-25 20:16:59 +00:00
|
|
|
func (kl *Kubelet) reconcileVolumes(pods []Pod) error {
|
|
|
|
validVolumes := determineValidVolumes(pods)
|
|
|
|
activeVolumes := kl.determineActiveVolumes()
|
2014-07-25 21:17:02 +00:00
|
|
|
glog.Infof("ValidVolumes: %v", validVolumes)
|
|
|
|
glog.Infof("ActiveVolumes: %v", activeVolumes)
|
2014-07-25 20:16:59 +00:00
|
|
|
for name, volume := range activeVolumes {
|
|
|
|
if _, ok := validVolumes[name]; !ok {
|
2014-07-25 21:17:02 +00:00
|
|
|
glog.Infof("Orphaned volume %s found, tearing down volume", name)
|
2014-07-25 20:16:59 +00:00
|
|
|
volume.TearDown()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
// SyncPods synchronizes the configured list of pods (desired state) with the host current state.
|
|
|
|
func (kl *Kubelet) SyncPods(pods []Pod) error {
|
2014-07-22 21:40:59 +00:00
|
|
|
glog.Infof("Desired [%s]: %+v", kl.hostname, pods)
|
2014-07-15 20:24:41 +00:00
|
|
|
var err error
|
2014-07-18 18:42:47 +00:00
|
|
|
desiredContainers := make(map[podContainer]empty)
|
2014-07-01 05:27:56 +00:00
|
|
|
|
2014-07-22 21:40:59 +00:00
|
|
|
dockerContainers, err := getKubeletDockerContainers(kl.dockerClient)
|
2014-07-15 17:26:56 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Error listing containers %#v", dockerContainers)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-07-01 05:27:56 +00:00
|
|
|
// Check for any containers that need starting
|
2014-07-15 20:24:41 +00:00
|
|
|
for i := range pods {
|
2014-07-18 18:42:47 +00:00
|
|
|
pod := &pods[i]
|
|
|
|
podFullName := GetPodFullName(pod)
|
|
|
|
|
|
|
|
// Add all containers (including net) to the map.
|
|
|
|
desiredContainers[podContainer{podFullName, networkContainerName}] = empty{}
|
|
|
|
for _, cont := range pod.Manifest.Containers {
|
|
|
|
desiredContainers[podContainer{podFullName, cont.Name}] = empty{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run the sync in an async manifest worker.
|
|
|
|
kl.podWorkers.Run(podFullName, func() {
|
|
|
|
err := kl.syncPod(pod, dockerContainers)
|
2014-07-01 05:27:56 +00:00
|
|
|
if err != nil {
|
2014-07-15 20:24:41 +00:00
|
|
|
glog.Errorf("Error syncing pod: %v skipping.", err)
|
2014-07-01 05:27:56 +00:00
|
|
|
}
|
2014-07-18 18:42:47 +00:00
|
|
|
})
|
2014-07-01 16:37:45 +00:00
|
|
|
}
|
2014-07-25 21:17:02 +00:00
|
|
|
|
|
|
|
// Remove any orphaned volumes.
|
2014-07-25 20:16:59 +00:00
|
|
|
kl.reconcileVolumes(pods)
|
2014-06-25 23:24:20 +00:00
|
|
|
|
|
|
|
// Kill any containers we don't need
|
2014-07-22 21:40:59 +00:00
|
|
|
existingContainers, err := getKubeletDockerContainers(kl.dockerClient)
|
2014-06-25 23:24:20 +00:00
|
|
|
if err != nil {
|
2014-06-29 05:16:26 +00:00
|
|
|
glog.Errorf("Error listing containers: %v", err)
|
2014-06-25 23:24:20 +00:00
|
|
|
return err
|
|
|
|
}
|
2014-07-18 18:42:47 +00:00
|
|
|
for _, container := range existingContainers {
|
|
|
|
// Don't kill containers that are in the desired pods.
|
|
|
|
podFullName, containerName := parseDockerName(container.Names[0])
|
|
|
|
if _, ok := desiredContainers[podContainer{podFullName, containerName}]; !ok {
|
2014-07-15 17:26:56 +00:00
|
|
|
err = kl.killContainer(*container)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-06-29 05:16:26 +00:00
|
|
|
glog.Errorf("Error killing container: %v", err)
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
// filterHostPortConflicts removes pods that conflict on Port.HostPort values
|
|
|
|
func filterHostPortConflicts(pods []Pod) []Pod {
|
|
|
|
filtered := []Pod{}
|
|
|
|
ports := map[int]bool{}
|
2014-07-08 04:48:47 +00:00
|
|
|
extract := func(p *api.Port) int { return p.HostPort }
|
2014-07-15 20:24:41 +00:00
|
|
|
for i := range pods {
|
|
|
|
pod := &pods[i]
|
|
|
|
if errs := api.AccumulateUniquePorts(pod.Manifest.Containers, ports, extract); len(errs) != 0 {
|
|
|
|
glog.Warningf("Pod %s has conflicting ports, ignoring: %v", GetPodFullName(pod), errs)
|
|
|
|
continue
|
2014-07-08 04:48:47 +00:00
|
|
|
}
|
2014-07-15 20:24:41 +00:00
|
|
|
filtered = append(filtered, *pod)
|
2014-07-08 04:48:47 +00:00
|
|
|
}
|
2014-07-15 20:24:41 +00:00
|
|
|
|
|
|
|
return filtered
|
2014-07-08 04:48:47 +00:00
|
|
|
}
|
|
|
|
|
2014-07-01 20:01:39 +00:00
|
|
|
// syncLoop is the main loop for processing changes. It watches for changes from
|
2014-06-20 16:31:18 +00:00
|
|
|
// four channels (file, etcd, server, and http) and creates a union of them. For
|
2014-06-06 23:40:48 +00:00
|
|
|
// any new change seen, will run a sync against desired state and running state. If
|
|
|
|
// no changes are seen to the configuration, will synchronize the last known desired
|
2014-07-15 20:24:41 +00:00
|
|
|
// state every sync_frequency seconds. Never returns.
|
|
|
|
func (kl *Kubelet) syncLoop(updates <-chan PodUpdate, handler SyncHandler) {
|
2014-06-06 23:40:48 +00:00
|
|
|
for {
|
2014-07-15 20:24:41 +00:00
|
|
|
var pods []Pod
|
2014-06-06 23:40:48 +00:00
|
|
|
select {
|
2014-07-15 20:24:41 +00:00
|
|
|
case u := <-updates:
|
|
|
|
switch u.Op {
|
|
|
|
case SET:
|
2014-07-22 21:40:59 +00:00
|
|
|
glog.Infof("Containers changed [%s]", kl.hostname)
|
2014-07-15 20:24:41 +00:00
|
|
|
pods = u.Pods
|
|
|
|
|
|
|
|
case UPDATE:
|
|
|
|
//TODO: implement updates of containers
|
2014-07-22 21:40:59 +00:00
|
|
|
glog.Infof("Containers updated, not implemented [%s]", kl.hostname)
|
2014-07-15 20:24:41 +00:00
|
|
|
continue
|
2014-06-06 23:40:48 +00:00
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
default:
|
|
|
|
panic("syncLoop does not support incremental changes")
|
2014-07-01 20:01:39 +00:00
|
|
|
}
|
2014-06-21 21:20:35 +00:00
|
|
|
}
|
2014-06-20 16:31:18 +00:00
|
|
|
|
2014-07-15 20:24:41 +00:00
|
|
|
pods = filterHostPortConflicts(pods)
|
|
|
|
|
|
|
|
err := handler.SyncPods(pods)
|
2014-06-06 23:40:48 +00:00
|
|
|
if err != nil {
|
2014-06-29 05:16:26 +00:00
|
|
|
glog.Errorf("Couldn't sync containers : %v", err)
|
2014-06-06 23:40:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-14 21:48:51 +00:00
|
|
|
func getCadvisorContainerInfoRequest(req *info.ContainerInfoRequest) *info.ContainerInfoRequest {
|
|
|
|
ret := &info.ContainerInfoRequest{
|
|
|
|
NumStats: req.NumStats,
|
|
|
|
CpuUsagePercentiles: req.CpuUsagePercentiles,
|
2014-07-30 23:56:50 +00:00
|
|
|
MemoryUsagePercentiles: req.MemoryUsagePercentiles,
|
2014-07-14 21:48:51 +00:00
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2014-07-01 21:05:10 +00:00
|
|
|
// This method takes a container's absolute path and returns the stats for the
|
|
|
|
// container. The container's absolute path refers to its hierarchy in the
|
|
|
|
// cgroup file system. e.g. The root container, which represents the whole
|
|
|
|
// machine, has path "/"; all docker containers have path "/docker/<docker id>"
|
2014-07-14 21:48:51 +00:00
|
|
|
func (kl *Kubelet) statsFromContainerPath(containerPath string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
2014-07-22 21:40:59 +00:00
|
|
|
cinfo, err := kl.cadvisorClient.ContainerInfo(containerPath, getCadvisorContainerInfoRequest(req))
|
2014-06-19 01:26:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-07-14 21:48:51 +00:00
|
|
|
return cinfo, nil
|
2014-06-19 01:26:23 +00:00
|
|
|
}
|
2014-07-01 21:05:10 +00:00
|
|
|
|
2014-07-15 17:26:56 +00:00
|
|
|
// GetPodInfo returns information from Docker about the containers in a pod
|
2014-07-15 20:24:41 +00:00
|
|
|
func (kl *Kubelet) GetPodInfo(podFullName string) (api.PodInfo, error) {
|
2014-07-22 21:40:59 +00:00
|
|
|
return getDockerPodInfo(kl.dockerClient, podFullName)
|
2014-07-15 17:26:56 +00:00
|
|
|
}
|
|
|
|
|
2014-07-16 09:46:22 +00:00
|
|
|
// GetContainerInfo returns stats (from Cadvisor) for a container.
|
2014-07-15 20:24:41 +00:00
|
|
|
func (kl *Kubelet) GetContainerInfo(podFullName, containerName string, req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
2014-07-22 21:40:59 +00:00
|
|
|
if kl.cadvisorClient == nil {
|
2014-07-01 21:05:10 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
2014-07-22 21:40:59 +00:00
|
|
|
dockerContainers, err := getKubeletDockerContainers(kl.dockerClient)
|
2014-07-15 17:26:56 +00:00
|
|
|
if err != nil {
|
2014-07-01 21:05:10 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2014-07-15 20:24:41 +00:00
|
|
|
dockerContainer, found := dockerContainers.FindPodContainer(podFullName, containerName)
|
2014-07-15 17:26:56 +00:00
|
|
|
if !found {
|
|
|
|
return nil, errors.New("couldn't find container")
|
|
|
|
}
|
|
|
|
return kl.statsFromContainerPath(fmt.Sprintf("/docker/%s", dockerContainer.ID), req)
|
2014-07-01 21:05:10 +00:00
|
|
|
}
|
|
|
|
|
2014-07-15 22:40:02 +00:00
|
|
|
// GetRootInfo returns stats (from Cadvisor) of current machine (root container).
|
|
|
|
func (kl *Kubelet) GetRootInfo(req *info.ContainerInfoRequest) (*info.ContainerInfo, error) {
|
2014-07-14 21:48:51 +00:00
|
|
|
return kl.statsFromContainerPath("/", req)
|
2014-07-01 21:05:10 +00:00
|
|
|
}
|
2014-07-03 05:35:50 +00:00
|
|
|
|
2014-07-15 22:40:02 +00:00
|
|
|
func (kl *Kubelet) GetMachineInfo() (*info.MachineInfo, error) {
|
2014-07-22 21:40:59 +00:00
|
|
|
return kl.cadvisorClient.MachineInfo()
|
2014-07-15 22:40:02 +00:00
|
|
|
}
|
|
|
|
|
2014-08-01 00:35:54 +00:00
|
|
|
func (kl *Kubelet) healthy(currentState api.PodState, container api.Container, dockerContainer *docker.APIContainers) (health.Status, error) {
|
2014-07-03 05:35:50 +00:00
|
|
|
// Give the container 60 seconds to start up.
|
2014-07-09 23:53:31 +00:00
|
|
|
if container.LivenessProbe == nil {
|
2014-07-15 18:39:19 +00:00
|
|
|
return health.Healthy, nil
|
2014-07-03 05:35:50 +00:00
|
|
|
}
|
|
|
|
if time.Now().Unix()-dockerContainer.Created < container.LivenessProbe.InitialDelaySeconds {
|
2014-07-15 18:39:19 +00:00
|
|
|
return health.Healthy, nil
|
2014-07-03 05:35:50 +00:00
|
|
|
}
|
2014-07-22 21:40:59 +00:00
|
|
|
if kl.healthChecker == nil {
|
2014-07-15 18:39:19 +00:00
|
|
|
return health.Healthy, nil
|
2014-07-03 05:35:50 +00:00
|
|
|
}
|
2014-08-01 00:35:54 +00:00
|
|
|
return kl.healthChecker.HealthCheck(currentState, container)
|
2014-07-03 05:35:50 +00:00
|
|
|
}
|
2014-07-15 07:04:30 +00:00
|
|
|
|
|
|
|
// Returns logs of current machine.
|
|
|
|
func (kl *Kubelet) ServeLogs(w http.ResponseWriter, req *http.Request) {
|
|
|
|
// TODO: whitelist logs we are willing to serve
|
2014-07-22 21:40:59 +00:00
|
|
|
kl.logServer.ServeHTTP(w, req)
|
2014-07-15 07:04:30 +00:00
|
|
|
}
|