Skip to content

Commit 2d64ce5

Browse files
author
Kubernetes Submit Queue
authored
Merge pull request kubernetes#53440 from jsafrane/mount-container4-10-03
Automatic merge from submit-queue (batch tested with PRs 54005, 55127, 53850, 55486, 53440). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Containerized mount utilities This is implementation of kubernetes/community#589 @tallclair @vishh @dchen1107 PTAL @kubernetes/sig-node-pr-reviews **Release note**: ```release-note Kubelet supports running mount utilities and final mount in a container instead running them on the host. ```
2 parents 560a310 + 1ddc6eb commit 2d64ce5

File tree

10 files changed

+709
-6
lines changed

10 files changed

+709
-6
lines changed

pkg/features/kube_features.go

+7
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ const (
169169
//
170170
// Enable nodes to exclude themselves from service load balancers
171171
ServiceNodeExclusion utilfeature.Feature = "ServiceNodeExclusion"
172+
173+
// owner: @jsafrane
174+
// alpha: v1.9
175+
//
176+
// Enable running mount utilities in containers.
177+
MountContainers utilfeature.Feature = "MountContainers"
172178
)
173179

174180
func init() {
@@ -201,6 +207,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
201207
ExpandPersistentVolumes: {Default: false, PreRelease: utilfeature.Alpha},
202208
CPUManager: {Default: false, PreRelease: utilfeature.Alpha},
203209
ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha},
210+
MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
204211

205212
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
206213
// unintentionally on either side:

pkg/kubelet/BUILD

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ go_library(
6464
"//pkg/kubelet/kuberuntime:go_default_library",
6565
"//pkg/kubelet/lifecycle:go_default_library",
6666
"//pkg/kubelet/metrics:go_default_library",
67+
"//pkg/kubelet/mountpod:go_default_library",
6768
"//pkg/kubelet/network:go_default_library",
6869
"//pkg/kubelet/pleg:go_default_library",
6970
"//pkg/kubelet/pod:go_default_library",
@@ -269,6 +270,7 @@ filegroup(
269270
"//pkg/kubelet/leaky:all-srcs",
270271
"//pkg/kubelet/lifecycle:all-srcs",
271272
"//pkg/kubelet/metrics:all-srcs",
273+
"//pkg/kubelet/mountpod:all-srcs",
272274
"//pkg/kubelet/network:all-srcs",
273275
"//pkg/kubelet/pleg:all-srcs",
274276
"//pkg/kubelet/pod:all-srcs",

pkg/kubelet/config/defaults.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ limitations under the License.
1717
package config
1818

1919
const (
20-
DefaultKubeletPodsDirName = "pods"
21-
DefaultKubeletVolumesDirName = "volumes"
22-
DefaultKubeletPluginsDirName = "plugins"
23-
DefaultKubeletContainersDirName = "containers"
20+
DefaultKubeletPodsDirName = "pods"
21+
DefaultKubeletVolumesDirName = "volumes"
22+
DefaultKubeletPluginsDirName = "plugins"
23+
DefaultKubeletContainersDirName = "containers"
24+
DefaultKubeletPluginContainersDirName = "plugin-containers"
2425
)

pkg/kubelet/mountpod/BUILD

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["mount_pod.go"],
6+
importpath = "k8s.io/kubernetes/pkg/kubelet/mountpod",
7+
visibility = ["//visibility:public"],
8+
deps = [
9+
"//pkg/kubelet/config:go_default_library",
10+
"//pkg/kubelet/pod:go_default_library",
11+
"//pkg/util/strings:go_default_library",
12+
"//vendor/k8s.io/api/core/v1:go_default_library",
13+
],
14+
)
15+
16+
go_test(
17+
name = "go_default_test",
18+
srcs = ["mount_pod_test.go"],
19+
importpath = "k8s.io/kubernetes/pkg/kubelet/mountpod",
20+
library = ":go_default_library",
21+
deps = [
22+
"//pkg/kubelet/configmap:go_default_library",
23+
"//pkg/kubelet/pod:go_default_library",
24+
"//pkg/kubelet/pod/testing:go_default_library",
25+
"//pkg/kubelet/secret:go_default_library",
26+
"//vendor/github.com/golang/glog:go_default_library",
27+
"//vendor/k8s.io/api/core/v1:go_default_library",
28+
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
29+
"//vendor/k8s.io/client-go/util/testing:go_default_library",
30+
],
31+
)
32+
33+
filegroup(
34+
name = "package-srcs",
35+
srcs = glob(["**"]),
36+
tags = ["automanaged"],
37+
visibility = ["//visibility:private"],
38+
)
39+
40+
filegroup(
41+
name = "all-srcs",
42+
srcs = [":package-srcs"],
43+
tags = ["automanaged"],
44+
visibility = ["//visibility:public"],
45+
)

pkg/kubelet/mountpod/mount_pod.go

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
Copyright 2017 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package mountpod
18+
19+
import (
20+
"encoding/json"
21+
"fmt"
22+
"io/ioutil"
23+
"os"
24+
"path"
25+
26+
"k8s.io/api/core/v1"
27+
"k8s.io/kubernetes/pkg/kubelet/config"
28+
kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
29+
"k8s.io/kubernetes/pkg/util/strings"
30+
)
31+
32+
// Manager is an interface that tracks pods with mount utilities for individual
33+
// volume plugins.
34+
type Manager interface {
35+
GetMountPod(pluginName string) (pod *v1.Pod, container string, err error)
36+
}
37+
38+
// basicManager is simple implementation of Manager. Pods with mount utilities
39+
// are registered by placing a JSON file into
40+
// /var/lib/kubelet/plugin-containers/<plugin name>.json and this manager just
41+
// finds them there.
42+
type basicManager struct {
43+
registrationDirectory string
44+
podManager kubepod.Manager
45+
}
46+
47+
// volumePluginRegistration specified format of the json files placed in
48+
// /var/lib/kubelet/plugin-containers/
49+
type volumePluginRegistration struct {
50+
PodName string `json:"podName"`
51+
PodNamespace string `json:"podNamespace"`
52+
PodUID string `json:"podUID"`
53+
ContainerName string `json:"containerName"`
54+
}
55+
56+
// NewManager returns a new mount pod manager.
57+
func NewManager(rootDirectory string, podManager kubepod.Manager) (Manager, error) {
58+
regPath := path.Join(rootDirectory, config.DefaultKubeletPluginContainersDirName)
59+
60+
// Create the directory on startup
61+
os.MkdirAll(regPath, 0700)
62+
63+
return &basicManager{
64+
registrationDirectory: regPath,
65+
podManager: podManager,
66+
}, nil
67+
}
68+
69+
func (m *basicManager) getVolumePluginRegistrationPath(pluginName string) string {
70+
// sanitize plugin name so it does not escape directory
71+
safePluginName := strings.EscapePluginName(pluginName) + ".json"
72+
return path.Join(m.registrationDirectory, safePluginName)
73+
}
74+
75+
func (m *basicManager) GetMountPod(pluginName string) (pod *v1.Pod, containerName string, err error) {
76+
// Read /var/lib/kubelet/plugin-containers/<plugin name>.json
77+
regPath := m.getVolumePluginRegistrationPath(pluginName)
78+
regBytes, err := ioutil.ReadFile(regPath)
79+
if err != nil {
80+
if os.IsNotExist(err) {
81+
// No pod is registered for this plugin
82+
return nil, "", nil
83+
}
84+
return nil, "", fmt.Errorf("cannot read %s: %v", regPath, err)
85+
}
86+
87+
// Parse json
88+
var reg volumePluginRegistration
89+
if err := json.Unmarshal(regBytes, &reg); err != nil {
90+
return nil, "", fmt.Errorf("unable to parse %s: %s", regPath, err)
91+
}
92+
if len(reg.ContainerName) == 0 {
93+
return nil, "", fmt.Errorf("unable to parse %s: \"containerName\" is not set", regPath)
94+
}
95+
if len(reg.PodUID) == 0 {
96+
return nil, "", fmt.Errorf("unable to parse %s: \"podUID\" is not set", regPath)
97+
}
98+
if len(reg.PodNamespace) == 0 {
99+
return nil, "", fmt.Errorf("unable to parse %s: \"podNamespace\" is not set", regPath)
100+
}
101+
if len(reg.PodName) == 0 {
102+
return nil, "", fmt.Errorf("unable to parse %s: \"podName\" is not set", regPath)
103+
}
104+
105+
pod, ok := m.podManager.GetPodByName(reg.PodNamespace, reg.PodName)
106+
if !ok {
107+
return nil, "", fmt.Errorf("unable to process %s: pod %s/%s not found", regPath, reg.PodNamespace, reg.PodName)
108+
}
109+
if string(pod.UID) != reg.PodUID {
110+
return nil, "", fmt.Errorf("unable to process %s: pod %s/%s has unexpected UID", regPath, reg.PodNamespace, reg.PodName)
111+
}
112+
// make sure that reg.ContainerName exists in the pod
113+
for i := range pod.Spec.Containers {
114+
if pod.Spec.Containers[i].Name == reg.ContainerName {
115+
return pod, reg.ContainerName, nil
116+
}
117+
}
118+
return nil, "", fmt.Errorf("unable to process %s: pod %s/%s has no container named %q", regPath, reg.PodNamespace, reg.PodName, reg.ContainerName)
119+
120+
}
+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
Copyright 2017 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package mountpod
18+
19+
import (
20+
"io/ioutil"
21+
"os"
22+
"path"
23+
"testing"
24+
25+
"github.com/golang/glog"
26+
27+
"k8s.io/api/core/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
utiltesting "k8s.io/client-go/util/testing"
30+
"k8s.io/kubernetes/pkg/kubelet/configmap"
31+
kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
32+
podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing"
33+
"k8s.io/kubernetes/pkg/kubelet/secret"
34+
)
35+
36+
func TestGetVolumeExec(t *testing.T) {
37+
// prepare PodManager
38+
pods := []*v1.Pod{
39+
{
40+
ObjectMeta: metav1.ObjectMeta{
41+
UID: "12345678",
42+
Name: "foo",
43+
Namespace: "bar",
44+
},
45+
Spec: v1.PodSpec{
46+
Containers: []v1.Container{
47+
{Name: "baz"},
48+
},
49+
},
50+
},
51+
}
52+
fakeSecretManager := secret.NewFakeManager()
53+
fakeConfigMapManager := configmap.NewFakeManager()
54+
podManager := kubepod.NewBasicPodManager(
55+
podtest.NewFakeMirrorClient(), fakeSecretManager, fakeConfigMapManager)
56+
podManager.SetPods(pods)
57+
58+
// Prepare fake /var/lib/kubelet
59+
basePath, err := utiltesting.MkTmpdir("kubelet")
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
defer os.RemoveAll(basePath)
64+
regPath := path.Join(basePath, "plugin-containers")
65+
66+
mgr, err := NewManager(basePath, podManager)
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
71+
tests := []struct {
72+
name string
73+
json string
74+
expectError bool
75+
}{
76+
{
77+
"invalid json",
78+
"{{{}",
79+
true,
80+
},
81+
{
82+
"missing json",
83+
"", // this means no json file should be created
84+
false,
85+
},
86+
{
87+
"missing podNamespace",
88+
`{"podName": "foo", "podUID": "87654321", "containerName": "baz"}`,
89+
true,
90+
},
91+
{
92+
"missing podName",
93+
`{"podNamespace": "bar", "podUID": "87654321", "containerName": "baz"}`,
94+
true,
95+
},
96+
{
97+
"missing containerName",
98+
`{"podNamespace": "bar", "podName": "foo", "podUID": "87654321"}`,
99+
true,
100+
},
101+
{
102+
"missing podUID",
103+
`{"podNamespace": "bar", "podName": "foo", "containerName": "baz"}`,
104+
true,
105+
},
106+
{
107+
"missing pod",
108+
`{"podNamespace": "bar", "podName": "non-existing-pod", "podUID": "12345678", "containerName": "baz"}`,
109+
true,
110+
},
111+
{
112+
"invalid uid",
113+
`{"podNamespace": "bar", "podName": "foo", "podUID": "87654321", "containerName": "baz"}`,
114+
true,
115+
},
116+
{
117+
"invalid container",
118+
`{"podNamespace": "bar", "podName": "foo", "podUID": "12345678", "containerName": "invalid"}`,
119+
true,
120+
},
121+
{
122+
"valid pod",
123+
`{"podNamespace": "bar", "podName": "foo", "podUID": "12345678", "containerName": "baz"}`,
124+
false,
125+
},
126+
}
127+
for _, test := range tests {
128+
p := path.Join(regPath, "kubernetes.io~glusterfs.json")
129+
if len(test.json) > 0 {
130+
if err := ioutil.WriteFile(p, []byte(test.json), 0600); err != nil {
131+
t.Errorf("test %q: error writing %s: %v", test.name, p, err)
132+
continue
133+
}
134+
} else {
135+
// "" means no JSON file
136+
os.Remove(p)
137+
}
138+
pod, container, err := mgr.GetMountPod("kubernetes.io/glusterfs")
139+
if err != nil {
140+
glog.V(5).Infof("test %q returned error %s", test.name, err)
141+
}
142+
if err == nil && test.expectError {
143+
t.Errorf("test %q: expected error, got none", test.name)
144+
}
145+
if err != nil && !test.expectError {
146+
t.Errorf("test %q: unexpected error: %v", test.name, err)
147+
}
148+
149+
if err == nil {
150+
// Pod must be returned when the json file was not empty
151+
if pod == nil && len(test.json) != 0 {
152+
t.Errorf("test %q: expected exec, got nil", test.name)
153+
}
154+
// Both pod and container must be returned
155+
if pod != nil && len(container) == 0 {
156+
t.Errorf("test %q: expected container name, got %q", test.name, container)
157+
}
158+
}
159+
}
160+
}

0 commit comments

Comments
 (0)