Add control over container entrypoint

pull/6/head
Paul Morie 2015-03-30 22:56:34 -04:00
parent 9ed87612d0
commit 7628b37d78
23 changed files with 815 additions and 226 deletions

View File

@ -0,0 +1,20 @@
# Copyright 2015 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.
FROM scratch
ADD ep ep
ADD ep ep-2
EXPOSE 8080
ENTRYPOINT ["/ep"]
CMD ["default", "arguments"]

View File

@ -0,0 +1,15 @@
all: push
TAG = 0.1
ep: ep.go
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' ./ep.go
image: ep
sudo docker build -t kubernetes/eptest:$(TAG) .
push: image
sudo docker push kubernetes/eptest:$(TAG)
clean:
rm -f ep

View File

@ -0,0 +1,29 @@
/*
Copyright 2015 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 main
import (
"fmt"
"os"
)
// This program prints the arguments it's passed and exits.
func main() {
args := os.Args
fmt.Printf("%v\n", args)
os.Exit(0)
}

View File

@ -1,4 +1,46 @@
# Container with Kubernetes
# Containers with Kubernetes
## Containers and commands
So far the Pods we've seen have all used the `image` field to indicate what process Kubernetes
should run in a container. In this case, Kubernetes runs the image's default command. If we want
to run a particular command or override the image's defaults, there are two additional fields that
we can use:
1. `Command`: Controls the actual command run by the image
2. `Args`: Controls the arguments passed to the command
### How docker handles command and arguments
Docker images have metadata associated with them that is used to store information about the image.
The image author may use this to define defaults for the command and arguments to run a container
when the user does not supply values. Docker calls the fields for commands and arguments
`Entrypoint` and `Cmd` respectively. The full details for this feature are too complicated to
describe here, mostly due to the fact that the docker API allows users to specify both of these
fields as either a string array or a string and there are subtle differences in how those cases are
handled. We encourage the curious to check out [docker's documentation]() for this feature.
Kubernetes allows you to override both the image's default command (docker `Entrypoint`) and args
(docker `Cmd`) with the `Command` and `Args` fields of `Container`. The rules are:
1. If you do not supply a `Command` or `Args` for a container, the defaults defined by the image
will be used
2. If you supply a `Command` but no `Args` for a container, only the supplied `Command` will be
used; the image's default arguments are ignored
3. If you supply only `Args`, the image's default command will be used with the arguments you
supply
4. If you supply a `Command` **and** `Args`, the image's defaults will be ignored and the values
you supply will be used
Here are examples for these rules in table format
| Image `Entrypoint` | Image `Cmd` | Container `Command` | Container `Args` | Command Run |
|--------------------|------------------|---------------------|--------------------|------------------|
| `[/ep-1]` | `[foo bar]` | <not set> | <not set> | `[ep-1 foo bar]` |
| `[/ep-1]` | `[foo bar]` | `[/ep-2]` | <not set> | `[ep-2]` |
| `[/ep-1]` | `[foo bar]` | <not set> | `[zoo boo]` | `[ep-1 zoo boo]` |
| `[/ep-1]` | `[foo bar]` | `[/ep-2]` | `[zoo boo]` | `[ep-2 zoo boo]` |
## Capabilities

View File

@ -505,8 +505,10 @@ type Container struct {
Name string `json:"name"`
// Required.
Image string `json:"image"`
// Optional: Defaults to whatever is defined in the image.
// Optional: The docker image's entrypoint is used if this is not provided; cannot be updated.
Command []string `json:"command,omitempty"`
// Optional: The docker image's cmd is used if this is not provided; cannot be updated.
Args []string `json:"args,omitempty"`
// Optional: Defaults to Docker's default.
WorkingDir string `json:"workingDir,omitempty"`
Ports []ContainerPort `json:"ports,omitempty"`

View File

@ -532,7 +532,10 @@ func init() {
if err := s.Convert(&in.Image, &out.Image, 0); err != nil {
return err
}
if err := s.Convert(&in.Command, &out.Command, 0); err != nil {
if err := s.Convert(&in.Command, &out.Entrypoint, 0); err != nil {
return err
}
if err := s.Convert(&in.Args, &out.Command, 0); err != nil {
return err
}
if err := s.Convert(&in.WorkingDir, &out.WorkingDir, 0); err != nil {
@ -615,7 +618,10 @@ func init() {
if err := s.Convert(&in.Image, &out.Image, 0); err != nil {
return err
}
if err := s.Convert(&in.Command, &out.Command, 0); err != nil {
if err := s.Convert(&in.Command, &out.Args, 0); err != nil {
return err
}
if err := s.Convert(&in.Entrypoint, &out.Command, 0); err != nil {
return err
}
if err := s.Convert(&in.WorkingDir, &out.WorkingDir, 0); err != nil {

View File

@ -411,9 +411,11 @@ type Container struct {
Name string `json:"name" description:"name of the container; must be a DNS_LABEL and unique within the pod; cannot be updated"`
// Required.
Image string `json:"image" description:"Docker image name"`
// Optional: Defaults to whatever is defined in the image.
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; defaults to entrypoint or command in the image; cannot be updated"`
// Optional: Defaults to Docker's default.
// Optional: The image's entrypoint is used if this is not provided; cannot be updated.
Entrypoint []string `json:"entrypoint:omitempty" description:"entrypoint array; not executed within a shell; the image's entrypoint is used if this is not provided; cannot be updated"`
// Optional: The image's cmd is used if this is not provided; cannot be updated.
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; the image's cmd is used if this is not provided; cannot be updated"`
// Optional: Docker's default is used if this is not provided.
WorkingDir string `json:"workingDir,omitempty" description:"container's working directory; defaults to image's default; cannot be updated"`
Ports []ContainerPort `json:"ports,omitempty" description:"list of ports to expose from the container; cannot be updated"`
Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container; cannot be updated"`

View File

@ -317,13 +317,15 @@ func init() {
if err := s.Convert(&in.Image, &out.Image, 0); err != nil {
return err
}
if err := s.Convert(&in.Command, &out.Command, 0); err != nil {
if err := s.Convert(&in.Command, &out.Entrypoint, 0); err != nil {
return err
}
if err := s.Convert(&in.Args, &out.Command, 0); err != nil {
return err
}
if err := s.Convert(&in.WorkingDir, &out.WorkingDir, 0); err != nil {
return err
}
if err := s.Convert(&in.Ports, &out.Ports, 0); err != nil {
return err
}
@ -403,7 +405,10 @@ func init() {
if err := s.Convert(&in.Image, &out.Image, 0); err != nil {
return err
}
if err := s.Convert(&in.Command, &out.Command, 0); err != nil {
if err := s.Convert(&in.Command, &out.Args, 0); err != nil {
return err
}
if err := s.Convert(&in.Entrypoint, &out.Command, 0); err != nil {
return err
}
if err := s.Convert(&in.WorkingDir, &out.WorkingDir, 0); err != nil {

View File

@ -393,9 +393,11 @@ type Container struct {
Name string `json:"name" description:"name of the container; must be a DNS_LABEL and unique within the pod; cannot be updated"`
// Required.
Image string `json:"image" description:"Docker image name"`
// Optional: Defaults to whatever is defined in the image.
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; defaults to entrypoint or command in the image; cannot be updated"`
// Optional: Defaults to Docker's default.
// Optional: The image's entrypoint is used if this is not provided; cannot be updated.
Entrypoint []string `json:"entrypoint:omitempty" description:"entrypoint array; not executed within a shell; the image's entrypoint is used if this is not provided; cannot be updated"`
// Optional: The image's cmd is used if this is not provided; cannot be updated.
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; the image's cmd is used if this is not provided; cannot be updated"`
// Optional: Docker's default is used if this is not provided.
WorkingDir string `json:"workingDir,omitempty" description:"container's working directory; defaults to image's default; cannot be updated"`
Ports []ContainerPort `json:"ports,omitempty" description:"list of ports to expose from the container; cannot be updated"`
Env []EnvVar `json:"env,omitempty" description:"list of environment variables to set in the container; cannot be updated"`

View File

@ -520,8 +520,10 @@ type Container struct {
Name string `json:"name" description:"name of the container; must be a DNS_LABEL and unique within the pod; cannot be updated"`
// Required.
Image string `json:"image" description:"Docker image name"`
// Optional: Defaults to whatever is defined in the image.
Command []string `json:"command,omitempty" description:"command argv array; not executed within a shell; defaults to entrypoint or command in the image; cannot be updated"`
// Optional: The docker image's entrypoint is used if this is not provided; cannot be updated.
Command []string `json:"command,omitempty" description:"entrypoint array; not executed within a shell; the docker image's entrypoint is used if this is not provided; cannot be updated"`
// Optional: The docker image's cmd is used if this is not provided; cannot be updated.
Args []string `json:"args,omitempty" description:"command array; the docker image's cmd is used if this is not provided; arguments to the entrypoint; cannot be updated"`
// Optional: Defaults to Docker's default.
WorkingDir string `json:"workingDir,omitempty" description:"container's working directory; defaults to image's default; cannot be updated"`
Ports []ContainerPort `json:"ports,omitempty" description:"list of ports to expose from the container; cannot be updated"`

View File

@ -17,7 +17,6 @@ limitations under the License.
package container
import (
"fmt"
"sync"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
@ -62,41 +61,3 @@ func (c *RefManager) GetRef(id string) (ref *api.ObjectReference, ok bool) {
ref, ok = c.containerIDToRef[id]
return ref, ok
}
// fieldPath returns a fieldPath locating container within pod.
// Returns an error if the container isn't part of the pod.
func fieldPath(pod *api.Pod, container *api.Container) (string, error) {
for i := range pod.Spec.Containers {
here := &pod.Spec.Containers[i]
if here.Name == container.Name {
if here.Name == "" {
return fmt.Sprintf("spec.containers[%d]", i), nil
} else {
return fmt.Sprintf("spec.containers{%s}", here.Name), nil
}
}
}
return "", fmt.Errorf("container %#v not found in pod %#v", container, pod)
}
// GenerateContainerRef returns an *api.ObjectReference which references the given container within the
// given pod. Returns an error if the reference can't be constructed or the container doesn't
// actually belong to the pod.
// TODO: Pods that came to us by static config or over HTTP have no selfLink set, which makes
// this fail and log an error. Figure out how we want to identify these pods to the rest of the
// system.
// TODO(yifan): Revisit this function later, for current case it does not need to use RefManager
// as a receiver.
func (c *RefManager) GenerateContainerRef(pod *api.Pod, container *api.Container) (*api.ObjectReference, error) {
fieldPath, err := fieldPath(pod, container)
if err != nil {
// TODO: figure out intelligent way to refer to containers that we implicitly
// start (like the pod infra container). This is not a good way, ugh.
fieldPath = "implicitly required container " + container.Name
}
ref, err := api.GetPartialReference(pod, fieldPath)
if err != nil {
return nil, err
}
return ref, nil
}

View File

@ -1,61 +0,0 @@
/*
Copyright 2015 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 container
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
func TestFieldPath(t *testing.T) {
pod := &api.Pod{Spec: api.PodSpec{Containers: []api.Container{
{Name: "foo"},
{Name: "bar"},
{Name: ""},
{Name: "baz"},
}}}
table := map[string]struct {
pod *api.Pod
container *api.Container
path string
success bool
}{
"basic": {pod, &api.Container{Name: "foo"}, "spec.containers{foo}", true},
"basic2": {pod, &api.Container{Name: "baz"}, "spec.containers{baz}", true},
"emptyName": {pod, &api.Container{Name: ""}, "spec.containers[2]", true},
"basicSamePointer": {pod, &pod.Spec.Containers[0], "spec.containers{foo}", true},
"missing": {pod, &api.Container{Name: "qux"}, "", false},
}
for name, item := range table {
res, err := fieldPath(item.pod, item.container)
if item.success == false {
if err == nil {
t.Errorf("%v: unexpected non-error", name)
}
continue
}
if err != nil {
t.Errorf("%v: unexpected error: %v", name, err)
continue
}
if e, a := item.path, res; e != a {
t.Errorf("%v: wanted %v, got %v", name, e, a)
}
}
}

View File

@ -0,0 +1,59 @@
/*
Copyright 2015 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 container
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
// GenerateContainerRef returns an *api.ObjectReference which references the given container
// within the given pod. Returns an error if the reference can't be constructed or the
// container doesn't actually belong to the pod.
//
// This function will return an error if the provided Pod does not have a selfLink,
// but we expect selfLink to be populated at all call sites for the function.
func GenerateContainerRef(pod *api.Pod, container *api.Container) (*api.ObjectReference, error) {
fieldPath, err := fieldPath(pod, container)
if err != nil {
// TODO: figure out intelligent way to refer to containers that we implicitly
// start (like the pod infra container). This is not a good way, ugh.
fieldPath = "implicitly required container " + container.Name
}
ref, err := api.GetPartialReference(pod, fieldPath)
if err != nil {
return nil, err
}
return ref, nil
}
// fieldPath returns a fieldPath locating container within pod.
// Returns an error if the container isn't part of the pod.
func fieldPath(pod *api.Pod, container *api.Container) (string, error) {
for i := range pod.Spec.Containers {
here := &pod.Spec.Containers[i]
if here.Name == container.Name {
if here.Name == "" {
return fmt.Sprintf("spec.containers[%d]", i), nil
} else {
return fmt.Sprintf("spec.containers{%s}", here.Name), nil
}
}
}
return "", fmt.Errorf("container %#v not found in pod %#v", container, pod)
}

View File

@ -0,0 +1,208 @@
/*
Copyright 2015 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 container
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
func TestFieldPath(t *testing.T) {
pod := &api.Pod{Spec: api.PodSpec{Containers: []api.Container{
{Name: "foo"},
{Name: "bar"},
{Name: ""},
{Name: "baz"},
}}}
table := map[string]struct {
pod *api.Pod
container *api.Container
path string
success bool
}{
"basic": {pod, &api.Container{Name: "foo"}, "spec.containers{foo}", true},
"basic2": {pod, &api.Container{Name: "baz"}, "spec.containers{baz}", true},
"emptyName": {pod, &api.Container{Name: ""}, "spec.containers[2]", true},
"basicSamePointer": {pod, &pod.Spec.Containers[0], "spec.containers{foo}", true},
"missing": {pod, &api.Container{Name: "qux"}, "", false},
}
for name, item := range table {
res, err := fieldPath(item.pod, item.container)
if item.success == false {
if err == nil {
t.Errorf("%v: unexpected non-error", name)
}
continue
}
if err != nil {
t.Errorf("%v: unexpected error: %v", name, err)
continue
}
if e, a := item.path, res; e != a {
t.Errorf("%v: wanted %v, got %v", name, e, a)
}
}
}
func TestGenerateContainerRef(t *testing.T) {
var (
okPod = api.Pod{
TypeMeta: api.TypeMeta{
Kind: "Pod",
APIVersion: "v1beta1",
},
ObjectMeta: api.ObjectMeta{
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
SelfLink: "/api/v1beta1/pods/foo",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "by-name",
},
{},
},
},
}
noSelfLinkPod = okPod
defaultedSelfLinkPod = okPod
)
noSelfLinkPod.ObjectMeta.SelfLink = ""
defaultedSelfLinkPod.ObjectMeta.SelfLink = "/api/v1beta1/pods/ok"
cases := []struct {
name string
pod *api.Pod
container *api.Container
expected *api.ObjectReference
success bool
}{
{
name: "by-name",
pod: &okPod,
container: &api.Container{
Name: "by-name",
},
expected: &api.ObjectReference{
Kind: "Pod",
APIVersion: "v1beta1",
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
FieldPath: ".spec.containers{by-name}",
},
success: true,
},
{
name: "no-name",
pod: &okPod,
container: &api.Container{},
expected: &api.ObjectReference{
Kind: "Pod",
APIVersion: "v1beta1",
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
FieldPath: ".spec.containers[1]",
},
success: true,
},
{
name: "no-selflink",
pod: &noSelfLinkPod,
container: &api.Container{},
expected: nil,
success: false,
},
{
name: "defaulted-selflink",
pod: &defaultedSelfLinkPod,
container: &api.Container{
Name: "by-name",
},
expected: &api.ObjectReference{
Kind: "Pod",
APIVersion: "v1beta1",
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
FieldPath: ".spec.containers{by-name}",
},
success: true,
},
{
name: "implicitly-required",
pod: &okPod,
container: &api.Container{
Name: "net",
},
expected: &api.ObjectReference{
Kind: "Pod",
APIVersion: "v1beta1",
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
FieldPath: "implicitly required container net",
},
success: true,
},
}
for _, tc := range cases {
actual, err := GenerateContainerRef(tc.pod, tc.container)
if err != nil {
if tc.success {
t.Errorf("%v: unexpected error: %v", tc.name, err)
}
continue
}
if !tc.success {
t.Errorf("%v: unexpected success", tc.name)
continue
}
if e, a := tc.expected.Kind, actual.Kind; e != a {
t.Errorf("%v: kind: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.APIVersion, actual.APIVersion; e != a {
t.Errorf("%v: apiVersion: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.Name, actual.Name; e != a {
t.Errorf("%v: name: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.Namespace, actual.Namespace; e != a {
t.Errorf("%v: namespace: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.UID, actual.UID; e != a {
t.Errorf("%v: uid: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.ResourceVersion, actual.ResourceVersion; e != a {
t.Errorf("%v: kind: expected %v, got %v", tc.name, e, a)
}
}
}

View File

@ -52,6 +52,14 @@ type Runtime interface {
// TODO(yifan): Pull/Remove images
}
// Container runner is a narrow interface to consume in the Kubelet
// before there is a full implementation of Runtime.
//
// TODO: eventually include this interface in Runtime
type ContainerRunner interface {
RunContainer(pod *api.Pod, container *api.Container, opts *RunContainerOptions) (string, error)
}
// Pod is a group of containers, with the status of the pod.
type Pod struct {
// The ID of the pod, which can be used to retrieve a particular pod

View File

@ -27,13 +27,10 @@ import (
"math/rand"
"os"
"os/exec"
"path"
"strconv"
"strings"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
"github.com/GoogleCloudPlatform/kubernetes/pkg/credentialprovider"
kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/leaky"
@ -945,104 +942,3 @@ func makeCapabilites(capAdd []api.CapabilityType, capDrop []api.CapabilityType)
}
return addCaps, dropCaps
}
// RunContainer creates and starts a docker container with the required RunContainerOptions.
// On success it will return the container's ID with nil error. During the process, it will
// use the reference and event recorder to report the state of the container (e.g. created,
// started, failed, etc.).
// TODO(yifan): To use a strong type for the returned container ID.
func RunContainer(client DockerInterface, container *api.Container, pod *api.Pod, opts *kubecontainer.RunContainerOptions,
refManager *kubecontainer.RefManager, ref *api.ObjectReference, recorder record.EventRecorder) (string, error) {
dockerName := KubeletContainerName{
PodFullName: kubecontainer.GetPodFullName(pod),
PodUID: pod.UID,
ContainerName: container.Name,
}
exposedPorts, portBindings := makePortsAndBindings(container)
// TODO(vmarmol): Handle better.
// Cap hostname at 63 chars (specification is 64bytes which is 63 chars and the null terminating char).
const hostnameMaxLen = 63
containerHostname := pod.Name
if len(containerHostname) > hostnameMaxLen {
containerHostname = containerHostname[:hostnameMaxLen]
}
dockerOpts := docker.CreateContainerOptions{
Name: BuildDockerName(dockerName, container),
Config: &docker.Config{
Cmd: container.Command,
Env: opts.Envs,
ExposedPorts: exposedPorts,
Hostname: containerHostname,
Image: container.Image,
Memory: container.Resources.Limits.Memory().Value(),
CPUShares: milliCPUToShares(container.Resources.Limits.Cpu().MilliValue()),
WorkingDir: container.WorkingDir,
},
}
dockerContainer, err := client.CreateContainer(dockerOpts)
if err != nil {
if ref != nil {
recorder.Eventf(ref, "failed", "Failed to create docker container with error: %v", err)
}
return "", err
}
// Remember this reference so we can report events about this container
if ref != nil {
refManager.SetRef(dockerContainer.ID, ref)
recorder.Eventf(ref, "created", "Created with docker id %v", dockerContainer.ID)
}
// The reason we create and mount the log file in here (not in kubelet) is because
// the file's location depends on the ID of the container, and we need to create and
// mount the file before actually starting the container.
// TODO(yifan): Consider to pull this logic out since we might need to reuse it in
// other container runtime.
if opts.PodContainerDir != "" && len(container.TerminationMessagePath) != 0 {
containerLogPath := path.Join(opts.PodContainerDir, dockerContainer.ID)
fs, err := os.Create(containerLogPath)
if err != nil {
// TODO: Clean up the previouly created dir? return the error?
glog.Errorf("Error on creating termination-log file %q: %v", containerLogPath, err)
} else {
fs.Close() // Close immediately; we're just doing a `touch` here
b := fmt.Sprintf("%s:%s", containerLogPath, container.TerminationMessagePath)
opts.Binds = append(opts.Binds, b)
}
}
privileged := false
if capabilities.Get().AllowPrivileged {
privileged = container.Privileged
} else if container.Privileged {
return "", fmt.Errorf("container requested privileged mode, but it is disallowed globally.")
}
capAdd, capDrop := makeCapabilites(container.Capabilities.Add, container.Capabilities.Drop)
hc := &docker.HostConfig{
PortBindings: portBindings,
Binds: opts.Binds,
NetworkMode: opts.NetMode,
IpcMode: opts.IpcMode,
Privileged: privileged,
CapAdd: capAdd,
CapDrop: capDrop,
}
if len(opts.DNS) > 0 {
hc.DNS = opts.DNS
}
if len(opts.DNSSearch) > 0 {
hc.DNSSearch = opts.DNSSearch
}
if err = client.StartContainer(dockerContainer.ID, hc); err != nil {
if ref != nil {
recorder.Eventf(ref, "failed",
"Failed to start with docker id %v with error: %v", dockerContainer.ID, err)
}
return "", err
}
if ref != nil {
recorder.Eventf(ref, "started", "Started with docker id %v", dockerContainer.ID)
}
return dockerContainer.ID, nil
}

View File

@ -0,0 +1,146 @@
/*
Copyright 2015 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 dockertools
import (
"fmt"
"os"
"path"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container"
"github.com/fsouza/go-dockerclient"
"github.com/golang/glog"
)
type DockerContainerRunner struct {
Client DockerInterface
Recorder record.EventRecorder
}
func (r *DockerContainerRunner) RunContainer(pod *api.Pod, container *api.Container, opts *kubecontainer.RunContainerOptions) (string, error) {
ref, err := kubecontainer.GenerateContainerRef(pod, container)
if err != nil {
glog.Errorf("Couldn't make a ref to pod %v, container %v: '%v'", pod.Name, container.Name, err)
}
dockerName := KubeletContainerName{
PodFullName: kubecontainer.GetPodFullName(pod),
PodUID: pod.UID,
ContainerName: container.Name,
}
exposedPorts, portBindings := makePortsAndBindings(container)
// TODO(vmarmol): Handle better.
// Cap hostname at 63 chars (specification is 64bytes which is 63 chars and the null terminating char).
const hostnameMaxLen = 63
containerHostname := pod.Name
if len(containerHostname) > hostnameMaxLen {
containerHostname = containerHostname[:hostnameMaxLen]
}
dockerOpts := docker.CreateContainerOptions{
Name: BuildDockerName(dockerName, container),
Config: &docker.Config{
Env: opts.Envs,
ExposedPorts: exposedPorts,
Hostname: containerHostname,
Image: container.Image,
Memory: container.Resources.Limits.Memory().Value(),
CPUShares: milliCPUToShares(container.Resources.Limits.Cpu().MilliValue()),
WorkingDir: container.WorkingDir,
},
}
setEntrypointAndCommand(container, &dockerOpts)
dockerContainer, err := r.Client.CreateContainer(dockerOpts)
if err != nil {
if ref != nil {
r.Recorder.Eventf(ref, "failed", "Failed to create docker container with error: %v", err)
}
return "", err
}
if ref != nil {
r.Recorder.Eventf(ref, "created", "Created with docker id %v", dockerContainer.ID)
}
// The reason we create and mount the log file in here (not in kubelet) is because
// the file's location depends on the ID of the container, and we need to create and
// mount the file before actually starting the container.
// TODO(yifan): Consider to pull this logic out since we might need to reuse it in
// other container runtime.
if opts.PodContainerDir != "" && len(container.TerminationMessagePath) != 0 {
containerLogPath := path.Join(opts.PodContainerDir, dockerContainer.ID)
fs, err := os.Create(containerLogPath)
if err != nil {
// TODO: Clean up the previouly created dir? return the error?
glog.Errorf("Error on creating termination-log file %q: %v", containerLogPath, err)
} else {
fs.Close() // Close immediately; we're just doing a `touch` here
b := fmt.Sprintf("%s:%s", containerLogPath, container.TerminationMessagePath)
opts.Binds = append(opts.Binds, b)
}
}
privileged := false
if capabilities.Get().AllowPrivileged {
privileged = container.Privileged
} else if container.Privileged {
return "", fmt.Errorf("container requested privileged mode, but it is disallowed globally.")
}
capAdd, capDrop := makeCapabilites(container.Capabilities.Add, container.Capabilities.Drop)
hc := &docker.HostConfig{
PortBindings: portBindings,
Binds: opts.Binds,
NetworkMode: opts.NetMode,
IpcMode: opts.IpcMode,
Privileged: privileged,
CapAdd: capAdd,
CapDrop: capDrop,
}
if len(opts.DNS) > 0 {
hc.DNS = opts.DNS
}
if len(opts.DNSSearch) > 0 {
hc.DNSSearch = opts.DNSSearch
}
if err = r.Client.StartContainer(dockerContainer.ID, hc); err != nil {
if ref != nil {
r.Recorder.Eventf(ref, "failed",
"Failed to start with docker id %v with error: %v", dockerContainer.ID, err)
}
return "", err
}
if ref != nil {
r.Recorder.Eventf(ref, "started", "Started with docker id %v", dockerContainer.ID)
}
return dockerContainer.ID, nil
}
func setEntrypointAndCommand(container *api.Container, opts *docker.CreateContainerOptions) {
if len(container.Command) != 0 {
opts.Config.Entrypoint = container.Command
}
if len(container.Args) != 0 {
opts.Config.Cmd = container.Args
}
}

View File

@ -0,0 +1,89 @@
/*
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 dockertools
import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/fsouza/go-dockerclient"
)
func TestSetEntrypointAndCommand(t *testing.T) {
cases := []struct {
name string
container *api.Container
expected *docker.CreateContainerOptions
}{
{
name: "none",
container: &api.Container{},
expected: &docker.CreateContainerOptions{
Config: &docker.Config{},
},
},
{
name: "command",
container: &api.Container{
Command: []string{"foo", "bar"},
},
expected: &docker.CreateContainerOptions{
Config: &docker.Config{
Entrypoint: []string{"foo", "bar"},
},
},
},
{
name: "args",
container: &api.Container{
Args: []string{"foo", "bar"},
},
expected: &docker.CreateContainerOptions{
Config: &docker.Config{
Cmd: []string{"foo", "bar"},
},
},
},
{
name: "both",
container: &api.Container{
Command: []string{"foo"},
Args: []string{"bar", "baz"},
},
expected: &docker.CreateContainerOptions{
Config: &docker.Config{
Entrypoint: []string{"foo"},
Cmd: []string{"bar", "baz"},
},
},
},
}
for _, tc := range cases {
actualOpts := &docker.CreateContainerOptions{
Config: &docker.Config{},
}
setEntrypointAndCommand(tc.container, actualOpts)
if e, a := tc.expected.Config.Entrypoint, actualOpts.Config.Entrypoint; !api.Semantic.DeepEqual(e, a) {
t.Errorf("%v: unexpected entrypoint: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.Config.Cmd, actualOpts.Config.Cmd; !api.Semantic.DeepEqual(e, a) {
t.Errorf("%v: unexpected command: expected %v, got %v", tc.name, e, a)
}
}
}

View File

@ -214,6 +214,10 @@ func NewMainKubelet(
return nil, fmt.Errorf("failed to initialize image manager: %v", err)
}
statusManager := newStatusManager(kubeClient)
containerRunner := &dockertools.DockerContainerRunner{
Client: dockerClient,
Recorder: recorder,
}
klet := &Kubelet{
hostname: hostname,
@ -243,6 +247,7 @@ func NewMainKubelet(
statusManager: statusManager,
cloud: cloud,
nodeRef: nodeRef,
containerRunner: containerRunner,
}
klet.podManager = newBasicPodManager(klet.kubeClient)
@ -359,6 +364,9 @@ type Kubelet struct {
// Syncs pods statuses with apiserver; also used as a cache of statuses.
statusManager *statusManager
// Knows how to run a container in a pod
containerRunner kubecontainer.ContainerRunner
//Cloud provider interface
cloud cloudprovider.Interface
@ -649,7 +657,7 @@ func (kl *Kubelet) generateRunContainerOptions(pod *api.Pod, container *api.Cont
// Run a single container from a pod. Returns the docker container ID
func (kl *Kubelet) runContainer(pod *api.Pod, container *api.Container, podVolumes volumeMap, netMode, ipcMode string) (dockertools.DockerID, error) {
ref, err := kl.containerRefManager.GenerateContainerRef(pod, container)
ref, err := kubecontainer.GenerateContainerRef(pod, container)
if err != nil {
glog.Errorf("Couldn't make a ref to pod %v, container %v: '%v'", pod.Name, container.Name, err)
}
@ -659,13 +667,16 @@ func (kl *Kubelet) runContainer(pod *api.Pod, container *api.Container, podVolum
return "", err
}
// TODO(yifan): Replace with RunContainerInPod, so we can eliminate 'netMode', 'ipcMode'
// by handling the pod infra container in the container runtime's implementation.
id, err := dockertools.RunContainer(kl.dockerClient, container, pod, opts, kl.containerRefManager, ref, kl.recorder)
id, err := kl.containerRunner.RunContainer(pod, container, opts)
if err != nil {
return "", err
}
// Remember this reference so we can report events about this container
if ref != nil {
kl.containerRefManager.SetRef(id, ref)
}
if container.Lifecycle != nil && container.Lifecycle.PostStart != nil {
handlerErr := kl.runHandler(kubecontainer.GetPodFullName(pod), pod.UID, container, container.Lifecycle.PostStart)
if handlerErr != nil {
@ -885,7 +896,7 @@ func (kl *Kubelet) createPodInfraContainer(pod *api.Pod) (dockertools.DockerID,
Image: kl.podInfraContainerImage,
Ports: ports,
}
ref, err := kl.containerRefManager.GenerateContainerRef(pod, container)
ref, err := kubecontainer.GenerateContainerRef(pod, container)
if err != nil {
glog.Errorf("Couldn't make a ref to pod %v, container %v: '%v'", pod.Name, container.Name, err)
}
@ -1036,7 +1047,7 @@ func (kl *Kubelet) getPodInfraContainer(podFullName string, uid types.UID,
func (kl *Kubelet) pullImageAndRunContainer(pod *api.Pod, container *api.Container, podVolumes *volumeMap,
podInfraContainerID dockertools.DockerID) (dockertools.DockerID, error) {
podFullName := kubecontainer.GetPodFullName(pod)
ref, err := kl.containerRefManager.GenerateContainerRef(pod, container)
ref, err := kubecontainer.GenerateContainerRef(pod, container)
if err != nil {
glog.Errorf("Couldn't make a ref to pod %v, container %v: '%v'", pod.Name, container.Name, err)
}

View File

@ -112,6 +112,7 @@ func newTestKubelet(t *testing.T) *TestKubelet {
podManager, fakeMirrorClient := newFakePodManager()
kubelet.podManager = podManager
kubelet.containerRefManager = kubecontainer.NewRefManager()
kubelet.containerRunner = &dockertools.DockerContainerRunner{fakeDocker, fakeRecorder}
return &TestKubelet{kubelet, fakeDocker, mockCadvisor, fakeKubeClient, waitGroup, fakeMirrorClient}
}

View File

@ -140,6 +140,7 @@ func TestRunOnce(t *testing.T) {
t: t,
}
kb.dockerPuller = &dockertools.FakeDockerPuller{}
kb.containerRunner = &dockertools.DockerContainerRunner{kb.dockerClient, kb.recorder}
results, err := kb.runOnce([]api.Pod{
{
ObjectMeta: api.ObjectMeta{

View File

@ -1,5 +1,5 @@
/*
Copyright 2014 Google Inc. All rights reserved.
Copyright 2015 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.

View File

@ -0,0 +1,145 @@
/*
Copyright 2015 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 e2e
import (
"fmt"
"strings"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Docker Containers", func() {
var c *client.Client
BeforeEach(func() {
var err error
c, err = loadClient()
Expect(err).NotTo(HaveOccurred())
})
It("should use the image defaults if command and args are blank", func() {
runEntrypointTest("use defaults", c, entrypointTestPod(), []string{
"[/ep default arguments]",
})
})
It("should be able to override the image's default arguments (docker cmd)", func() {
pod := entrypointTestPod()
pod.Spec.Containers[0].Args = []string{"override", "arguments"}
runEntrypointTest("override arguments", c, pod, []string{
"[/ep override arguments]",
})
})
// Note: when you override the entrypoint, the image's arguments (docker cmd)
// are ignored.
It("should be able to override the image's default commmand (docker entrypoint)", func() {
pod := entrypointTestPod()
pod.Spec.Containers[0].Command = []string{"/ep-2"}
runEntrypointTest("override command", c, pod, []string{
"[/ep-2]",
})
})
It("should be able to override the image's default command and arguments", func() {
pod := entrypointTestPod()
pod.Spec.Containers[0].Command = []string{"/ep-2"}
pod.Spec.Containers[0].Args = []string{"override", "arguments"}
runEntrypointTest("override all", c, pod, []string{
"[/ep-2 override arguments]",
})
})
})
const testContainerName = "test-container"
// Return a prototypical entrypoint test pod
func entrypointTestPod() *api.Pod {
podName := "client-containers-" + string(util.NewUUID())
return &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: podName,
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: testContainerName,
Image: "kubernetes/eptest:0.1",
},
},
RestartPolicy: api.RestartPolicyNever,
},
}
}
// pod must have a container named 'test-container'
func runEntrypointTest(scenarioName string, c *client.Client, pod *api.Pod, expectedOutput []string) {
ns := api.NamespaceDefault
By(fmt.Sprintf("Creating a pod to test %v", scenarioName))
defer c.Pods(ns).Delete(pod.Name)
if _, err := c.Pods(ns).Create(pod); err != nil {
Failf("Failed to create pod: %v", err)
}
// Wait for client pod to complete.
expectNoError(waitForPodSuccess(c, pod.Name, testContainerName))
// Grab its logs. Get host first.
podStatus, err := c.Pods(ns).Get(pod.Name)
if err != nil {
Failf("Failed to get pod to know host: %v", err)
}
By(fmt.Sprintf("Trying to get logs from host %s pod %s container %s: %v",
podStatus.Status.Host, podStatus.Name, podStatus.Spec.Containers[0].Name, err))
var logs []byte
start := time.Now()
// Sometimes the actual containers take a second to get started, try to get logs for 60s
for time.Now().Sub(start) < (60 * time.Second) {
logs, err = c.Get().
Prefix("proxy").
Resource("minions").
Name(podStatus.Status.Host).
Suffix("containerLogs", ns, podStatus.Name, podStatus.Spec.Containers[0].Name).
Do().
Raw()
fmt.Sprintf("pod logs:%v\n", string(logs))
By(fmt.Sprintf("pod logs:%v\n", string(logs)))
if strings.Contains(string(logs), "Internal Error") {
By(fmt.Sprintf("Failed to get logs from host %s pod %s container %s: %v",
podStatus.Status.Host, podStatus.Name, podStatus.Spec.Containers[0].Name, string(logs)))
time.Sleep(5 * time.Second)
continue
}
break
}
for _, m := range expectedOutput {
Expect(string(logs)).To(ContainSubstring(m), "%q in container output", m)
}
}