Allow configurable Kubelet net image for isolated networks

Public access to the DockerHub is not guaranteed in all environments,
add a flag to the kubelet that allows it to use a different image (like
one on a private registry) as well as only pull the first time the
image is needed.

Fixes #1545
pull/6/head
Clayton Coleman 2014-10-02 14:58:58 -04:00
parent 1dda3072a4
commit 6881db64a9
4 changed files with 118 additions and 41 deletions

View File

@ -57,6 +57,7 @@ var (
address = flag.String("address", "127.0.0.1", "The address for the info server to serve on (set to 0.0.0.0 or \"\" for all interfaces)") address = flag.String("address", "127.0.0.1", "The address for the info server to serve on (set to 0.0.0.0 or \"\" for all interfaces)")
port = flag.Uint("port", master.KubeletPort, "The port for the info server to serve on") port = flag.Uint("port", master.KubeletPort, "The port for the info server to serve on")
hostnameOverride = flag.String("hostname_override", "", "If non-empty, will use this string as identification instead of the actual hostname.") hostnameOverride = flag.String("hostname_override", "", "If non-empty, will use this string as identification instead of the actual hostname.")
networkContainerImage = flag.String("network_container_image", kubelet.NetworkContainerImage, "The image that network containers in each pod will use.")
dockerEndpoint = flag.String("docker_endpoint", "", "If non-empty, use this for the docker endpoint to communicate with") dockerEndpoint = flag.String("docker_endpoint", "", "If non-empty, use this for the docker endpoint to communicate with")
etcdServerList util.StringList etcdServerList util.StringList
rootDirectory = flag.String("root_dir", defaultRootDir, "Directory path for managing kubelet files (volume mounts,etc).") rootDirectory = flag.String("root_dir", defaultRootDir, "Directory path for managing kubelet files (volume mounts,etc).")
@ -159,6 +160,7 @@ func main() {
cadvisorClient, cadvisorClient,
etcdClient, etcdClient,
*rootDirectory, *rootDirectory,
*networkContainerImage,
*syncFrequency, *syncFrequency,
float32(*registryPullQPS), float32(*registryPullQPS),
*registryBurst) *registryBurst)

View File

@ -81,7 +81,7 @@ func (f *FakeDockerClient) CreateContainer(c docker.CreateContainerOptions) (*do
// This is not a very good fake. We'll just add this container's name to the list. // This is not a very good fake. We'll just add this container's name to the list.
// Docker likes to add a '/', so copy that behavior. // Docker likes to add a '/', so copy that behavior.
name := "/" + c.Name name := "/" + c.Name
f.ContainerList = append(f.ContainerList, docker.APIContainers{ID: name, Names: []string{name}}) f.ContainerList = append(f.ContainerList, docker.APIContainers{ID: name, Names: []string{name}, Image: c.Config.Image})
return &docker.Container{ID: name}, nil return &docker.Container{ID: name}, nil
} }
@ -138,6 +138,7 @@ func (f *FakeDockerClient) InspectImage(name string) (*docker.Image, error) {
type FakeDockerPuller struct { type FakeDockerPuller struct {
sync.Mutex sync.Mutex
HasImages []string
ImagesPulled []string ImagesPulled []string
// Every pull will return the first error here, and then reslice // Every pull will return the first error here, and then reslice
@ -159,5 +160,15 @@ func (f *FakeDockerPuller) Pull(image string) (err error) {
} }
func (f *FakeDockerPuller) IsImagePresent(name string) (bool, error) { func (f *FakeDockerPuller) IsImagePresent(name string) (bool, error) {
f.Lock()
defer f.Unlock()
if f.HasImages == nil {
return true, nil return true, nil
}
for _, s := range f.HasImages {
if s == name {
return true, nil
}
}
return false, nil
} }

View File

@ -67,6 +67,7 @@ func NewMainKubelet(
cc CadvisorInterface, cc CadvisorInterface,
ec tools.EtcdClient, ec tools.EtcdClient,
rd string, rd string,
ni string,
ri time.Duration, ri time.Duration,
pullQPS float32, pullQPS float32,
pullBurst int) *Kubelet { pullBurst int) *Kubelet {
@ -77,6 +78,7 @@ func NewMainKubelet(
etcdClient: ec, etcdClient: ec,
rootDirectory: rd, rootDirectory: rd,
resyncInterval: ri, resyncInterval: ri,
networkContainerImage: ni,
podWorkers: newPodWorkers(), podWorkers: newPodWorkers(),
runner: dockertools.NewDockerContainerCommandRunner(), runner: dockertools.NewDockerContainerCommandRunner(),
httpClient: &http.Client{}, httpClient: &http.Client{},
@ -92,6 +94,7 @@ func NewIntegrationTestKubelet(hn string, dc dockertools.DockerInterface) *Kubel
hostname: hn, hostname: hn,
dockerClient: dc, dockerClient: dc,
dockerPuller: &dockertools.FakeDockerPuller{}, dockerPuller: &dockertools.FakeDockerPuller{},
networkContainerImage: NetworkContainerImage,
resyncInterval: 3 * time.Second, resyncInterval: 3 * time.Second,
podWorkers: newPodWorkers(), podWorkers: newPodWorkers(),
} }
@ -106,6 +109,7 @@ type Kubelet struct {
hostname string hostname string
dockerClient dockertools.DockerInterface dockerClient dockertools.DockerInterface
rootDirectory string rootDirectory string
networkContainerImage string
podWorkers podWorkers podWorkers podWorkers
resyncInterval time.Duration resyncInterval time.Duration
@ -368,7 +372,7 @@ func (kl *Kubelet) killContainerByID(ID, name string) error {
const ( const (
networkContainerName = "net" networkContainerName = "net"
networkContainerImage = "kubernetes/pause:latest" NetworkContainerImage = "kubernetes/pause:latest"
) )
// createNetworkContainer starts the network container for a pod. Returns the docker container ID of the newly created container. // createNetworkContainer starts the network container for a pod. Returns the docker container ID of the newly created container.
@ -381,12 +385,19 @@ func (kl *Kubelet) createNetworkContainer(pod *Pod) (dockertools.DockerID, error
} }
container := &api.Container{ container := &api.Container{
Name: networkContainerName, Name: networkContainerName,
Image: networkContainerImage, Image: kl.networkContainerImage,
Ports: ports, Ports: ports,
} }
if err := kl.dockerPuller.Pull(networkContainerImage); err != nil { // TODO: make this a TTL based pull (if image older than X policy, pull)
ok, err := kl.dockerPuller.IsImagePresent(container.Image)
if err != nil {
return "", err return "", err
} }
if !ok {
if err := kl.dockerPuller.Pull(container.Image); err != nil {
return "", err
}
}
return kl.runContainer(pod, container, nil, "") return kl.runContainer(pod, container, nil, "")
} }

View File

@ -22,6 +22,7 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"sync" "sync"
"testing" "testing"
"time" "time"
@ -206,6 +207,7 @@ func matchString(t *testing.T, pattern, str string) bool {
func TestSyncPodsCreatesNetAndContainer(t *testing.T) { func TestSyncPodsCreatesNetAndContainer(t *testing.T) {
kubelet, _, fakeDocker := newTestKubelet(t) kubelet, _, fakeDocker := newTestKubelet(t)
kubelet.networkContainerImage = "custom_image_name"
fakeDocker.ContainerList = []docker.APIContainers{} fakeDocker.ContainerList = []docker.APIContainers{}
err := kubelet.SyncPods([]Pod{ err := kubelet.SyncPods([]Pod{
{ {
@ -228,6 +230,57 @@ func TestSyncPodsCreatesNetAndContainer(t *testing.T) {
"list", "list", "create", "start", "list", "inspect", "list", "create", "start"}) "list", "list", "create", "start", "list", "inspect", "list", "create", "start"})
fakeDocker.Lock() fakeDocker.Lock()
found := false
for _, c := range fakeDocker.ContainerList {
if c.Image == "custom_image_name" && strings.HasPrefix(c.Names[0], "/k8s_net") {
found = true
}
}
if !found {
t.Errorf("Custom net container not found: %v", fakeDocker.ContainerList)
}
if len(fakeDocker.Created) != 2 ||
!matchString(t, "k8s_net\\.[a-f0-9]+_foo.test_", fakeDocker.Created[0]) ||
!matchString(t, "k8s_bar\\.[a-f0-9]+_foo.test_", fakeDocker.Created[1]) {
t.Errorf("Unexpected containers created %v", fakeDocker.Created)
}
fakeDocker.Unlock()
}
func TestSyncPodsCreatesNetAndContainerPullsImage(t *testing.T) {
kubelet, _, fakeDocker := newTestKubelet(t)
puller := kubelet.dockerPuller.(*dockertools.FakeDockerPuller)
puller.HasImages = []string{}
kubelet.networkContainerImage = "custom_image_name"
fakeDocker.ContainerList = []docker.APIContainers{}
err := kubelet.SyncPods([]Pod{
{
Name: "foo",
Namespace: "test",
Manifest: api.ContainerManifest{
ID: "foo",
Containers: []api.Container{
{Name: "bar"},
},
},
},
})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
kubelet.drainWorkers()
verifyCalls(t, fakeDocker, []string{
"list", "list", "create", "start", "list", "inspect", "list", "create", "start"})
fakeDocker.Lock()
if !reflect.DeepEqual(puller.ImagesPulled, []string{"custom_image_name", ""}) {
t.Errorf("Unexpected pulled containers: %v", puller.ImagesPulled)
}
if len(fakeDocker.Created) != 2 || if len(fakeDocker.Created) != 2 ||
!matchString(t, "k8s_net\\.[a-f0-9]+_foo.test_", fakeDocker.Created[0]) || !matchString(t, "k8s_net\\.[a-f0-9]+_foo.test_", fakeDocker.Created[0]) ||
!matchString(t, "k8s_bar\\.[a-f0-9]+_foo.test_", fakeDocker.Created[1]) { !matchString(t, "k8s_bar\\.[a-f0-9]+_foo.test_", fakeDocker.Created[1]) {