Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use sidecar container to execute rbd command #13691

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/swagger-spec/v1.json
Expand Up @@ -12262,6 +12262,10 @@
"readOnly": {
"type": "boolean",
"description": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: http://releases.k8s.io/HEAD/examples/rbd/README.md#how-to-use-it"
},
"sidecar": {
"type": "string",
"description": "Optional: Sidecar is the sidecar container's name"
}
}
},
Expand Down Expand Up @@ -12765,6 +12769,14 @@
"tty": {
"type": "boolean",
"description": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false."
},
"stdout": {
"type": "boolean",
"description": "Whether this container should send stdout. Default is false."
},
"stderr": {
"type": "boolean",
"description": "Whether this container should send stderr. Default is false."
}
}
},
Expand Down
1 change: 1 addition & 0 deletions examples/rbd/README.md
Expand Up @@ -51,6 +51,7 @@ Once you have installed Ceph and new Kubernetes, you can create a pod based on m
- *secretName*: The name of the authentication secrets. If provided, *secretName* overrides *keyring*. Note, see below about how to create a secret.
- *fsType*: The filesystem type (ext4, xfs, etc) that formatted on the device.
- *readOnly*: Whether the filesystem is used as readOnly.
- *sidecar*: The name of the sidecar container that can run *rbd* command.

# Use Ceph Authentication Secret

Expand Down
3 changes: 3 additions & 0 deletions pkg/api/deep_copy_generated.go
Expand Up @@ -238,6 +238,8 @@ func deepCopy_api_Container(in Container, out *Container, c *conversion.Cloner)
}
out.Stdin = in.Stdin
out.TTY = in.TTY
out.Stdout = in.Stdout
out.Stderr = in.Stderr
return nil
}

Expand Down Expand Up @@ -1568,6 +1570,7 @@ func deepCopy_api_RBDVolumeSource(in RBDVolumeSource, out *RBDVolumeSource, c *c
out.SecretRef = nil
}
out.ReadOnly = in.ReadOnly
out.Sidecar = in.Sidecar
return nil
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/api/types.go
Expand Up @@ -568,6 +568,8 @@ type RBDVolumeSource struct {
// Optional: Defaults to false (read/write). ReadOnly here will force
// the ReadOnly setting in VolumeMounts.
ReadOnly bool `json:"readOnly,omitempty"`
// Optional: Sidecar is the sidecar container's name
Sidecar string `json:"sidecar,omitempty"`
}

// CinderVolumeSource represents a cinder volume resource in Openstack.
Expand Down Expand Up @@ -793,6 +795,12 @@ type Container struct {
// and shouldn't be used for general purpose containers.
Stdin bool `json:"stdin,omitempty"`
TTY bool `json:"tty,omitempty"`
// Whether this container should send stdout.
// Default is false.
Stdout bool `json:"stdout,omitempty"`
// Whether this container should send stderr.
// Default is false.
Stderr bool `json:"stderr,omitempty"`
}

// Handler defines a specific action that should be taken
Expand Down
6 changes: 6 additions & 0 deletions pkg/api/v1/conversion_generated.go
Expand Up @@ -261,6 +261,8 @@ func convert_api_Container_To_v1_Container(in *api.Container, out *Container, s
}
out.Stdin = in.Stdin
out.TTY = in.TTY
out.Stdout = in.Stdout
out.Stderr = in.Stderr
return nil
}

Expand Down Expand Up @@ -1745,6 +1747,7 @@ func convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource,
out.SecretRef = nil
}
out.ReadOnly = in.ReadOnly
out.Sidecar = in.Sidecar
return nil
}

Expand Down Expand Up @@ -2663,6 +2666,8 @@ func convert_v1_Container_To_api_Container(in *Container, out *api.Container, s
}
out.Stdin = in.Stdin
out.TTY = in.TTY
out.Stdout = in.Stdout
out.Stderr = in.Stderr
return nil
}

Expand Down Expand Up @@ -4147,6 +4152,7 @@ func convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *RBDVolumeSource, out
out.SecretRef = nil
}
out.ReadOnly = in.ReadOnly
out.Sidecar = in.Sidecar
return nil
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/api/v1/deep_copy_generated.go
Expand Up @@ -253,6 +253,8 @@ func deepCopy_v1_Container(in Container, out *Container, c *conversion.Cloner) e
}
out.Stdin = in.Stdin
out.TTY = in.TTY
out.Stdout = in.Stdout
out.Stderr = in.Stderr
return nil
}

Expand Down Expand Up @@ -1568,6 +1570,7 @@ func deepCopy_v1_RBDVolumeSource(in RBDVolumeSource, out *RBDVolumeSource, c *co
out.SecretRef = nil
}
out.ReadOnly = in.ReadOnly
out.Sidecar = in.Sidecar
return nil
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/api/v1/types.go
Expand Up @@ -582,6 +582,8 @@ type RBDVolumeSource struct {
// Defaults to false.
// More info: http://releases.k8s.io/HEAD/examples/rbd/README.md#how-to-use-it
ReadOnly bool `json:"readOnly,omitempty"`
// Optional: Sidecar is the sidecar container's name
Sidecar string `json:"sidecar,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither the name of this field nor the comment helps me understand what this is or what its intended usage is. Is this a container or an image? If a container, is it Docker's name for the container, or something else? What are you trying to do?

Why is this exposed by the API at all? My interpretation of #13138 is that you wanted a container-based plugin mechanism. Kubelet plugins shouldn't be exposed via the user-facing API.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this exposed by the API at all? My interpretation of #13138 is that you wanted a container-based plugin mechanism. Kubelet plugins shouldn't be exposed via the user-facing API.

+1

This is really the name of an image to used to run the rbd client in a pod. I'm not sure I would call it a sidecar container, since it runs in a dedicated pod. I agree that we should nit expose this information via the API. I like the direction the the factoring in the volume subsystem is going, but the API changes should go away. Instead this should be a Kubelet property.

@rootfs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sidecar is the image name. It is part of the pod API that user can specify so the appropriate container image can be started to execute the command.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the image name need to vary by volume or would a single image name
suffice for all instances of the type?

The new VolumeConfig stuff is meant to easily do the latter. An admin can
configure the image to use.

On Monday, September 21, 2015, Huamin Chen notifications@github.com wrote:

In pkg/api/v1/types.go
#13691 (comment)
:

@@ -582,6 +582,8 @@ type RBDVolumeSource struct {
// Defaults to false.
// More info: http://releases.k8s.io/HEAD/examples/rbd/README.md#how-to-use-it
ReadOnly bool json:"readOnly,omitempty"

  • // Optional: Sidecar is the sidecar container's name
  • Sidecar string json:"sidecar,omitempty"

Sidecar is the image name. It is part of the pod API that user can specify
so the appropriate container image can be started to execute the command.


Reply to this email directly or view it on GitHub
https://github.com/kubernetes/kubernetes/pull/13691/files#r39969486.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i expect image name vary by volume. I use ceph/base in this example. This image is maintained by ceph community. There could also be a glusterfs image to do glusterfs mount, and yet another image for e.g. fibre channel/iSCSI related exec.

}

// CinderVolumeSource represents a cinder volume resource in Openstack.
Expand Down Expand Up @@ -988,6 +990,12 @@ type Container struct {
// Whether this container should allocate a TTY for itself, also requires 'stdin' to be true.
// Default is false.
TTY bool `json:"tty,omitempty"`
// Whether this container should send stdout.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Send it where?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we already have the ability to attach to running containers.

https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/attach.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stdout is to tell if container out is sent to stdout. The attach ability is not used here because the container could just exit after exec, attaching to an exited container just fails.

// Default is false.
Stdout bool `json:"stdout,omitempty"`
// Whether this container should send stderr.
// Default is false.
Stderr bool `json:"stderr,omitempty"`
}

// Handler defines a specific action that should be taken
Expand Down
3 changes: 3 additions & 0 deletions pkg/api/v1/types_swagger_doc_generated.go
Expand Up @@ -144,6 +144,8 @@ var map_Container = map[string]string{
"securityContext": "Security options the pod should run with. More info: http://releases.k8s.io/HEAD/docs/design/security_context.md",
"stdin": "Whether this container should allocate a buffer for stdin in the container runtime. Default is false.",
"tty": "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.",
"stdout": "Whether this container should send stdout. Default is false.",
"stderr": "Whether this container should send stderr. Default is false.",
}

func (Container) SwaggerDoc() map[string]string {
Expand Down Expand Up @@ -1036,6 +1038,7 @@ var map_RBDVolumeSource = map[string]string{
"keyring": "Keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: http://releases.k8s.io/HEAD/examples/rbd/README.md#how-to-use-it",
"secretRef": "SecretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is empty. More info: http://releases.k8s.io/HEAD/examples/rbd/README.md#how-to-use-it",
"readOnly": "ReadOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: http://releases.k8s.io/HEAD/examples/rbd/README.md#how-to-use-it",
"sidecar": "Optional: Sidecar is the sidecar container's name",
}

func (RBDVolumeSource) SwaggerDoc() map[string]string {
Expand Down
Expand Up @@ -233,3 +233,7 @@ func (f *PersistentVolumeRecycler) NewWrapperCleaner(spec *volume.Spec, podUID t
func (f *PersistentVolumeRecycler) GetCloudProvider() cloudprovider.Interface {
return nil
}

func (f *PersistentVolumeRecycler) RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error) {
return nil, fmt.Errorf("RunInContainer not supported by PVClaimBinder's VolumeHost implementation")
}
3 changes: 3 additions & 0 deletions pkg/expapi/deep_copy_generated.go
Expand Up @@ -175,6 +175,8 @@ func deepCopy_api_Container(in api.Container, out *api.Container, c *conversion.
}
out.Stdin = in.Stdin
out.TTY = in.TTY
out.Stdout = in.Stdout
out.Stderr = in.Stderr
return nil
}

Expand Down Expand Up @@ -520,6 +522,7 @@ func deepCopy_api_RBDVolumeSource(in api.RBDVolumeSource, out *api.RBDVolumeSour
out.SecretRef = nil
}
out.ReadOnly = in.ReadOnly
out.Sidecar = in.Sidecar
return nil
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/expapi/v1/conversion_generated.go
Expand Up @@ -190,6 +190,8 @@ func convert_api_Container_To_v1_Container(in *api.Container, out *v1.Container,
}
out.Stdin = in.Stdin
out.TTY = in.TTY
out.Stdout = in.Stdout
out.Stderr = in.Stderr
return nil
}

Expand Down Expand Up @@ -547,6 +549,7 @@ func convert_api_RBDVolumeSource_To_v1_RBDVolumeSource(in *api.RBDVolumeSource,
out.SecretRef = nil
}
out.ReadOnly = in.ReadOnly
out.Sidecar = in.Sidecar
return nil
}

Expand Down Expand Up @@ -957,6 +960,8 @@ func convert_v1_Container_To_api_Container(in *v1.Container, out *api.Container,
}
out.Stdin = in.Stdin
out.TTY = in.TTY
out.Stdout = in.Stdout
out.Stderr = in.Stderr
return nil
}

Expand Down Expand Up @@ -1314,6 +1319,7 @@ func convert_v1_RBDVolumeSource_To_api_RBDVolumeSource(in *v1.RBDVolumeSource, o
out.SecretRef = nil
}
out.ReadOnly = in.ReadOnly
out.Sidecar = in.Sidecar
return nil
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/expapi/v1/deep_copy_generated.go
Expand Up @@ -192,6 +192,8 @@ func deepCopy_v1_Container(in v1.Container, out *v1.Container, c *conversion.Clo
}
out.Stdin = in.Stdin
out.TTY = in.TTY
out.Stdout = in.Stdout
out.Stderr = in.Stderr
return nil
}

Expand Down Expand Up @@ -538,6 +540,7 @@ func deepCopy_v1_RBDVolumeSource(in v1.RBDVolumeSource, out *v1.RBDVolumeSource,
out.SecretRef = nil
}
out.ReadOnly = in.ReadOnly
out.Sidecar = in.Sidecar
return nil
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/kubelet/container/fake_runtime.go
Expand Up @@ -312,3 +312,11 @@ func (f *FakeRuntime) PortForward(pod *Pod, port uint16, stream io.ReadWriteClos
f.CalledFunctions = append(f.CalledFunctions, "PortForward")
return f.Err
}

func (f *FakeRuntime) RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error) {
f.Lock()
defer f.Unlock()

f.CalledFunctions = append(f.CalledFunctions, "RunContainerCommand")
return nil, f.Err
}
2 changes: 2 additions & 0 deletions pkg/kubelet/container/runtime.go
Expand Up @@ -103,6 +103,8 @@ type ContainerCommandRunner interface {
ExecInContainer(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool) error
// Forward the specified port from the specified pod to the stream.
PortForward(pod *Pod, port uint16, stream io.ReadWriteCloser) error
// Runs the command in the container
RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error)
}

// ImagePuller wraps Runtime.PullImage() to pull a container image.
Expand Down
38 changes: 36 additions & 2 deletions pkg/kubelet/dockertools/manager.go
Expand Up @@ -663,8 +663,10 @@ func (dm *DockerManager) runContainer(
WorkingDir: container.WorkingDir,
Labels: labels,
// Interactive containers:
OpenStdin: container.Stdin,
Tty: container.TTY,
OpenStdin: container.Stdin,
Tty: container.TTY,
AttachStdout: container.Stdout,
AttachStderr: container.Stderr,
},
}

Expand Down Expand Up @@ -1871,3 +1873,35 @@ func (dm *DockerManager) doBackOff(pod *api.Pod, container *api.Container, podSt
dm.clearReasonCache(pod, container)
return false
}

// create and start a container then log the output
func (dm *DockerManager) RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error) {
opts, err := dm.generator.GenerateRunContainerOptions(pod, container)
if err != nil {
return nil, err
}

utsMode := ""
if pod.Spec.HostNetwork {
utsMode = "host"
}
netNamespace := ""
if pod.Spec.HostNetwork {
netNamespace = "host"
}

// create and start container
// see https://docs.docker.com/reference/api/docker_remote_api_v1.20/#3-1-inside-docker-run
id, err := dm.runContainer(pod, container, opts, nil, netNamespace, "", utsMode)
if err != nil {
return nil, err
}
defer dm.KillContainerInPod("", container, pod)

var stdout, stderr bytes.Buffer
if err := dm.GetContainerLogs(pod, id, "all", true, &stdout, &stderr); err != nil {
return nil, err
}
out := append(stdout.Bytes(), stderr.Bytes()...)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes sense to concatenate stdout and stderr. How about returning both separately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ncdc yes I like your point. The idea here is to reach the parity with exec.CombinedOutput

return out, nil
}
14 changes: 14 additions & 0 deletions pkg/kubelet/kubelet.go
Expand Up @@ -2786,3 +2786,17 @@ func extractBandwidthResources(pod *api.Pod) (ingress, egress *resource.Quantity
}
return ingress, egress, nil
}

// Runs the command in the container
func (kl *Kubelet) RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error) {
// Mount volumes.
podFullName := kubecontainer.GetPodFullName(pod)
podVolumes, err := kl.mountExternalVolumes(pod)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the mountExternalVolumes call necessary here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kubelet builds volume first then creates the pod. if there is no volume, kubelet will complain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to enforce that the pod is run-once here.

if err != nil {
err = fmt.Errorf("Unable to mount volumes for pod %q: %v; skipping pod", podFullName, err)
return nil, err
}
kl.volumeManager.SetVolumes(pod.UID, podVolumes)

return kl.runner.RunContainerCommand(pod, container, cmd)
}
4 changes: 4 additions & 0 deletions pkg/kubelet/kubelet_test.go
Expand Up @@ -797,6 +797,10 @@ func (f *fakeContainerCommandRunner) PortForward(pod *kubecontainer.Pod, port ui
return nil
}

func (f *fakeContainerCommandRunner) RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error) {
return nil, f.E
}

func TestRunInContainerNoSuchPod(t *testing.T) {
testKubelet := newTestKubelet(t)
kubelet := testKubelet.kubelet
Expand Down
4 changes: 4 additions & 0 deletions pkg/kubelet/lifecycle/handlers_test.go
Expand Up @@ -91,6 +91,10 @@ func (f *fakeContainerCommandRunner) PortForward(pod *kubecontainer.Pod, port ui
return nil
}

func (f *fakeContainerCommandRunner) RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error) {
return []byte{}, nil
}

func TestRunHandlerExec(t *testing.T) {
fakeCommandRunner := fakeContainerCommandRunner{}
handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeCommandRunner, nil)
Expand Down
5 changes: 5 additions & 0 deletions pkg/kubelet/rkt/rkt.go
Expand Up @@ -1143,6 +1143,11 @@ func (r *runtime) ExecInContainer(containerID string, cmd []string, stdin io.Rea
return command.Run()
}

//TODO placeholder
func (r *runtime) RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error) {
return nil, fmt.Errorf("not supported")
}

// findRktID returns the rkt uuid for the pod.
func (r *runtime) findRktID(pod *kubecontainer.Pod) (string, error) {
serviceName := makePodServiceFileName(pod.ID)
Expand Down
4 changes: 4 additions & 0 deletions pkg/kubelet/volumes.go
Expand Up @@ -84,6 +84,10 @@ func (vh *volumeHost) GetCloudProvider() cloudprovider.Interface {
return vh.kubelet.cloud
}

func (vh *volumeHost) RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error) {
return vh.kubelet.RunContainerCommand(pod, container, cmd)
}

func (kl *Kubelet) newVolumeBuilderFromPlugins(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions, mounter mount.Interface) (volume.Builder, error) {
plugin, err := kl.volumePluginMgr.FindPluginBySpec(spec)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions pkg/volume/plugins.go
Expand Up @@ -125,6 +125,9 @@ type VolumeHost interface {

//Get cloud provider from kubelet
GetCloudProvider() cloudprovider.Interface

// Runs the command in the container
RunContainerCommand(pod *api.Pod, container *api.Container, cmd []string) ([]byte, error)
}

// VolumePluginMgr tracks registered plugins.
Expand Down