mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 10:51:29 +00:00
Merge pull request #116377 from kinvolk/rata/userns
KEP-127: user namespace support for stateless pods
This commit is contained in:
commit
6a111bebe2
@ -816,10 +816,6 @@ func (adc *attachDetachController) GetPodVolumeDir(podUID types.UID, pluginName,
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adc *attachDetachController) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) {
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (adc *attachDetachController) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
func (adc *attachDetachController) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -393,10 +393,6 @@ func (expc *expandController) GetPodsDir() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (expc *expandController) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) {
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (expc *expandController) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string {
|
func (expc *expandController) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -55,10 +55,6 @@ func (ctrl *PersistentVolumeController) GetPodVolumeDir(podUID types.UID, plugin
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctrl *PersistentVolumeController) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) {
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctrl *PersistentVolumeController) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
func (ctrl *PersistentVolumeController) GetPodPluginDir(podUID types.UID, pluginName string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
"k8s.io/kubernetes/pkg/kubelet/sysctl"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/token"
|
"k8s.io/kubernetes/pkg/kubelet/token"
|
||||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/userns"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/util"
|
"k8s.io/kubernetes/pkg/kubelet/util"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/util/manager"
|
"k8s.io/kubernetes/pkg/kubelet/util/manager"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/util/queue"
|
"k8s.io/kubernetes/pkg/kubelet/util/queue"
|
||||||
@ -908,7 +909,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
|
|||||||
StateDirectory: rootDirectory,
|
StateDirectory: rootDirectory,
|
||||||
})
|
})
|
||||||
klet.shutdownManager = shutdownManager
|
klet.shutdownManager = shutdownManager
|
||||||
klet.usernsManager, err = MakeUserNsManager(klet)
|
klet.usernsManager, err = userns.MakeUserNsManager(klet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1256,7 +1257,7 @@ type Kubelet struct {
|
|||||||
shutdownManager nodeshutdown.Manager
|
shutdownManager nodeshutdown.Manager
|
||||||
|
|
||||||
// Manage user namespaces
|
// Manage user namespaces
|
||||||
usernsManager *usernsManager
|
usernsManager *userns.UsernsManager
|
||||||
|
|
||||||
// Mutex to serialize new pod admission and existing pod resizing
|
// Mutex to serialize new pod admission and existing pod resizing
|
||||||
podResizeMutex sync.Mutex
|
podResizeMutex sync.Mutex
|
||||||
|
@ -104,6 +104,11 @@ func (kl *Kubelet) GetPodDir(podUID types.UID) string {
|
|||||||
return kl.getPodDir(podUID)
|
return kl.getPodDir(podUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListPodsFromDisk gets a list of pods that have data directories.
|
||||||
|
func (kl *Kubelet) ListPodsFromDisk() ([]types.UID, error) {
|
||||||
|
return kl.listPodsFromDisk()
|
||||||
|
}
|
||||||
|
|
||||||
// getPodDir returns the full path to the per-pod directory for the pod with
|
// getPodDir returns the full path to the per-pod directory for the pod with
|
||||||
// the given UID.
|
// the given UID.
|
||||||
func (kl *Kubelet) getPodDir(podUID types.UID) string {
|
func (kl *Kubelet) getPodDir(podUID types.UID) string {
|
||||||
|
@ -426,10 +426,6 @@ func (kl *Kubelet) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.Us
|
|||||||
return kl.usernsManager.GetOrCreateUserNamespaceMappings(pod)
|
return kl.usernsManager.GetOrCreateUserNamespaceMappings(pod)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kl *Kubelet) getHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) {
|
|
||||||
return kl.usernsManager.getHostIDsForPod(pod, containerUID, containerGID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GeneratePodHostNameAndDomain creates a hostname and domain name for a pod,
|
// GeneratePodHostNameAndDomain creates a hostname and domain name for a pod,
|
||||||
// given that pod's spec and annotations or returns an error.
|
// given that pod's spec and annotations or returns an error.
|
||||||
func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) {
|
func (kl *Kubelet) GeneratePodHostNameAndDomain(pod *v1.Pod) (string, string, error) {
|
||||||
|
@ -54,6 +54,15 @@ func (m *kubeGenericRuntimeManager) applyPlatformSpecificContainerConfig(config
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config.Linux = cl
|
config.Linux = cl
|
||||||
|
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.UserNamespacesStatelessPodsSupport) {
|
||||||
|
if cl.SecurityContext.NamespaceOptions.UsernsOptions != nil {
|
||||||
|
for _, mount := range config.Mounts {
|
||||||
|
mount.UidMappings = cl.SecurityContext.NamespaceOptions.UsernsOptions.Uids
|
||||||
|
mount.GidMappings = cl.SecurityContext.NamespaceOptions.UsernsOptions.Gids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kubelet
|
package userns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -49,11 +49,11 @@ const maxPods = 1024
|
|||||||
const mapReInitializeThreshold = 1000
|
const mapReInitializeThreshold = 1000
|
||||||
|
|
||||||
type userNsPodsManager interface {
|
type userNsPodsManager interface {
|
||||||
getPodDir(podUID types.UID) string
|
GetPodDir(podUID types.UID) string
|
||||||
listPodsFromDisk() ([]types.UID, error)
|
ListPodsFromDisk() ([]types.UID, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type usernsManager struct {
|
type UsernsManager struct {
|
||||||
used *allocator.AllocationBitmap
|
used *allocator.AllocationBitmap
|
||||||
usedBy map[types.UID]uint32 // Map pod.UID to range used
|
usedBy map[types.UID]uint32 // Map pod.UID to range used
|
||||||
removed int
|
removed int
|
||||||
@ -86,8 +86,8 @@ const mappingsFile = "userns"
|
|||||||
|
|
||||||
// writeMappingsToFile writes the specified user namespace configuration to the pod
|
// writeMappingsToFile writes the specified user namespace configuration to the pod
|
||||||
// directory.
|
// directory.
|
||||||
func (m *usernsManager) writeMappingsToFile(pod types.UID, userNs userNamespace) error {
|
func (m *UsernsManager) writeMappingsToFile(pod types.UID, userNs userNamespace) error {
|
||||||
dir := m.kl.getPodDir(pod)
|
dir := m.kl.GetPodDir(pod)
|
||||||
|
|
||||||
data, err := json.Marshal(userNs)
|
data, err := json.Marshal(userNs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -119,8 +119,8 @@ func (m *usernsManager) writeMappingsToFile(pod types.UID, userNs userNamespace)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readMappingsFromFile reads the user namespace configuration from the pod directory.
|
// readMappingsFromFile reads the user namespace configuration from the pod directory.
|
||||||
func (m *usernsManager) readMappingsFromFile(pod types.UID) ([]byte, error) {
|
func (m *UsernsManager) readMappingsFromFile(pod types.UID) ([]byte, error) {
|
||||||
dir := m.kl.getPodDir(pod)
|
dir := m.kl.GetPodDir(pod)
|
||||||
fstore, err := utilstore.NewFileStore(dir, &utilfs.DefaultFs{})
|
fstore, err := utilstore.NewFileStore(dir, &utilfs.DefaultFs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -128,8 +128,8 @@ func (m *usernsManager) readMappingsFromFile(pod types.UID) ([]byte, error) {
|
|||||||
return fstore.Read(mappingsFile)
|
return fstore.Read(mappingsFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeUserNsManager(kl userNsPodsManager) (*usernsManager, error) {
|
func MakeUserNsManager(kl userNsPodsManager) (*UsernsManager, error) {
|
||||||
m := usernsManager{
|
m := UsernsManager{
|
||||||
// Create a bitArray for all the UID space (2^32).
|
// Create a bitArray for all the UID space (2^32).
|
||||||
// As a by product of that, no index param to bitArray can be out of bounds (index is uint32).
|
// As a by product of that, no index param to bitArray can be out of bounds (index is uint32).
|
||||||
used: allocator.NewAllocationMap((math.MaxUint32+1)/userNsLength, "user namespaces"),
|
used: allocator.NewAllocationMap((math.MaxUint32+1)/userNsLength, "user namespaces"),
|
||||||
@ -141,17 +141,12 @@ func MakeUserNsManager(kl userNsPodsManager) (*usernsManager, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second block will be used for phase II. Don't assign that range for now.
|
|
||||||
if _, err := m.used.Allocate(1); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not bother reading the list of pods if user namespaces are not enabled.
|
// do not bother reading the list of pods if user namespaces are not enabled.
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
||||||
return &m, nil
|
return &m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
found, err := kl.listPodsFromDisk()
|
found, err := kl.ListPodsFromDisk()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return &m, nil
|
return &m, nil
|
||||||
@ -171,7 +166,7 @@ func MakeUserNsManager(kl userNsPodsManager) (*usernsManager, error) {
|
|||||||
|
|
||||||
// recordPodMappings registers the range used for the user namespace if the
|
// recordPodMappings registers the range used for the user namespace if the
|
||||||
// usernsConfFile exists in the pod directory.
|
// usernsConfFile exists in the pod directory.
|
||||||
func (m *usernsManager) recordPodMappings(pod types.UID) error {
|
func (m *UsernsManager) recordPodMappings(pod types.UID) error {
|
||||||
content, err := m.readMappingsFromFile(pod)
|
content, err := m.readMappingsFromFile(pod)
|
||||||
if err != nil && err != utilstore.ErrKeyNotFound {
|
if err != nil && err != utilstore.ErrKeyNotFound {
|
||||||
return err
|
return err
|
||||||
@ -187,7 +182,7 @@ func (m *usernsManager) recordPodMappings(pod types.UID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isSet checks if the specified index is already set.
|
// isSet checks if the specified index is already set.
|
||||||
func (m *usernsManager) isSet(v uint32) bool {
|
func (m *UsernsManager) isSet(v uint32) bool {
|
||||||
index := int(v / userNsLength)
|
index := int(v / userNsLength)
|
||||||
return m.used.Has(index)
|
return m.used.Has(index)
|
||||||
}
|
}
|
||||||
@ -195,7 +190,7 @@ func (m *usernsManager) isSet(v uint32) bool {
|
|||||||
// allocateOne finds a free user namespace and allocate it to the specified pod.
|
// allocateOne finds a free user namespace and allocate it to the specified pod.
|
||||||
// The first return value is the first ID in the user namespace, the second returns
|
// The first return value is the first ID in the user namespace, the second returns
|
||||||
// the length for the user namespace range.
|
// the length for the user namespace range.
|
||||||
func (m *usernsManager) allocateOne(pod types.UID) (firstID uint32, length uint32, err error) {
|
func (m *UsernsManager) allocateOne(pod types.UID) (firstID uint32, length uint32, err error) {
|
||||||
if m.numAllocated >= maxPods {
|
if m.numAllocated >= maxPods {
|
||||||
return 0, 0, fmt.Errorf("limit on count of pods with user namespaces exceeded (limit is %v, current pods with userns: %v)", maxPods, m.numAllocated)
|
return 0, 0, fmt.Errorf("limit on count of pods with user namespaces exceeded (limit is %v, current pods with userns: %v)", maxPods, m.numAllocated)
|
||||||
}
|
}
|
||||||
@ -222,7 +217,7 @@ func (m *usernsManager) allocateOne(pod types.UID) (firstID uint32, length uint3
|
|||||||
}
|
}
|
||||||
|
|
||||||
// record stores the user namespace [from; from+length] to the specified pod.
|
// record stores the user namespace [from; from+length] to the specified pod.
|
||||||
func (m *usernsManager) record(pod types.UID, from, length uint32) (err error) {
|
func (m *UsernsManager) record(pod types.UID, from, length uint32) (err error) {
|
||||||
if length != userNsLength {
|
if length != userNsLength {
|
||||||
return fmt.Errorf("wrong user namespace length %v", length)
|
return fmt.Errorf("wrong user namespace length %v", length)
|
||||||
}
|
}
|
||||||
@ -262,7 +257,7 @@ func (m *usernsManager) record(pod types.UID, from, length uint32) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Release releases the user namespace allocated to the specified pod.
|
// Release releases the user namespace allocated to the specified pod.
|
||||||
func (m *usernsManager) Release(podUID types.UID) {
|
func (m *UsernsManager) Release(podUID types.UID) {
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -273,7 +268,7 @@ func (m *usernsManager) Release(podUID types.UID) {
|
|||||||
m.releaseWithLock(podUID)
|
m.releaseWithLock(podUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *usernsManager) releaseWithLock(pod types.UID) {
|
func (m *UsernsManager) releaseWithLock(pod types.UID) {
|
||||||
v, ok := m.usedBy[pod]
|
v, ok := m.usedBy[pod]
|
||||||
if !ok {
|
if !ok {
|
||||||
klog.V(5).InfoS("pod user namespace allocation not present", "podUID", pod)
|
klog.V(5).InfoS("pod user namespace allocation not present", "podUID", pod)
|
||||||
@ -285,7 +280,7 @@ func (m *usernsManager) releaseWithLock(pod types.UID) {
|
|||||||
m.numAllocated--
|
m.numAllocated--
|
||||||
m.removed++
|
m.removed++
|
||||||
|
|
||||||
_ = os.Remove(filepath.Join(m.kl.getPodDir(pod), mappingsFile))
|
_ = os.Remove(filepath.Join(m.kl.GetPodDir(pod), mappingsFile))
|
||||||
|
|
||||||
if m.removed%mapReInitializeThreshold == 0 {
|
if m.removed%mapReInitializeThreshold == 0 {
|
||||||
n := make(map[types.UID]uint32)
|
n := make(map[types.UID]uint32)
|
||||||
@ -298,7 +293,7 @@ func (m *usernsManager) releaseWithLock(pod types.UID) {
|
|||||||
m.used.Release(int(v / userNsLength))
|
m.used.Release(int(v / userNsLength))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *usernsManager) parseUserNsFileAndRecord(pod types.UID, content []byte) (userNs userNamespace, err error) {
|
func (m *UsernsManager) parseUserNsFileAndRecord(pod types.UID, content []byte) (userNs userNamespace, err error) {
|
||||||
if err = json.Unmarshal([]byte(content), &userNs); err != nil {
|
if err = json.Unmarshal([]byte(content), &userNs); err != nil {
|
||||||
err = fmt.Errorf("can't parse file: %w", err)
|
err = fmt.Errorf("can't parse file: %w", err)
|
||||||
return
|
return
|
||||||
@ -338,7 +333,7 @@ func (m *usernsManager) parseUserNsFileAndRecord(pod types.UID, content []byte)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *usernsManager) createUserNs(pod *v1.Pod) (userNs userNamespace, err error) {
|
func (m *UsernsManager) createUserNs(pod *v1.Pod) (userNs userNamespace, err error) {
|
||||||
firstID, length, err := m.allocateOne(pod.UID)
|
firstID, length, err := m.allocateOne(pod.UID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -371,7 +366,7 @@ func (m *usernsManager) createUserNs(pod *v1.Pod) (userNs userNamespace, err err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace
|
// GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace
|
||||||
func (m *usernsManager) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.UserNamespace, error) {
|
func (m *UsernsManager) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimeapi.UserNamespace, error) {
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -431,7 +426,7 @@ func (m *usernsManager) GetOrCreateUserNamespaceMappings(pod *v1.Pod) (*runtimea
|
|||||||
// CleanupOrphanedPodUsernsAllocations reconciliates the state of user namespace
|
// CleanupOrphanedPodUsernsAllocations reconciliates the state of user namespace
|
||||||
// allocations with the pods actually running. It frees any user namespace
|
// allocations with the pods actually running. It frees any user namespace
|
||||||
// allocation for orphaned pods.
|
// allocation for orphaned pods.
|
||||||
func (m *usernsManager) CleanupOrphanedPodUsernsAllocations(pods []*v1.Pod, runningPods []*kubecontainer.Pod) error {
|
func (m *UsernsManager) CleanupOrphanedPodUsernsAllocations(pods []*v1.Pod, runningPods []*kubecontainer.Pod) error {
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -448,7 +443,7 @@ func (m *usernsManager) CleanupOrphanedPodUsernsAllocations(pods []*v1.Pod, runn
|
|||||||
}
|
}
|
||||||
|
|
||||||
allFound := sets.NewString()
|
allFound := sets.NewString()
|
||||||
found, err := m.kl.listPodsFromDisk()
|
found, err := m.kl.ListPodsFromDisk()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -479,68 +474,3 @@ func (m *usernsManager) CleanupOrphanedPodUsernsAllocations(pods []*v1.Pod, runn
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getHostIDsForPod if the pod uses user namespaces, takes the uid and gid
|
|
||||||
// inside the container and returns the host UID and GID those are mapped to on
|
|
||||||
// the host. If containerUID/containerGID is nil, then it returns the host
|
|
||||||
// UID/GID for ID 0 inside the container.
|
|
||||||
// If the pod is not using user namespaces, as there is no mapping needed, the
|
|
||||||
// same containerUID and containerGID params are returned.
|
|
||||||
func (m *usernsManager) getHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) {
|
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
|
||||||
return containerUID, containerGID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if pod == nil || pod.Spec.HostUsers == nil || *pod.Spec.HostUsers == true {
|
|
||||||
return containerUID, containerGID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping, err := m.GetOrCreateUserNamespaceMappings(pod)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error getting pod user namespace mapping: %w", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uid, err := hostIDFromMapping(mapping.Uids, containerUID)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error getting host UID: %w", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gid, err := hostIDFromMapping(mapping.Gids, containerGID)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("Error getting host GID: %w", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return &uid, &gid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostIDFromMapping(mapping []*runtimeapi.IDMapping, containerId *int64) (int64, error) {
|
|
||||||
if len(mapping) == 0 {
|
|
||||||
return 0, fmt.Errorf("can't use empty user namespace mapping")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none is requested, root inside the container is used
|
|
||||||
id := int64(0)
|
|
||||||
if containerId != nil {
|
|
||||||
id = *containerId
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range mapping {
|
|
||||||
if m == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
firstId := int64(m.ContainerId)
|
|
||||||
lastId := firstId + int64(m.Length) - 1
|
|
||||||
|
|
||||||
// The id we are looking for is in the range
|
|
||||||
if id >= firstId && id <= lastId {
|
|
||||||
// Return the host id for this container id
|
|
||||||
return int64(m.HostId) + id - firstId, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf("ID: %v not present in pod user namespace", id)
|
|
||||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package kubelet
|
package userns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -25,18 +25,17 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
|
||||||
pkgfeatures "k8s.io/kubernetes/pkg/features"
|
pkgfeatures "k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testUserNsPodsManager struct {
|
type testUserNsPodsManager struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *testUserNsPodsManager) getPodDir(podUID types.UID) string {
|
func (m *testUserNsPodsManager) GetPodDir(podUID types.UID) string {
|
||||||
return "/tmp/non-existant-dir.This-is-not-used-in-tests"
|
return "/tmp/non-existant-dir.This-is-not-used-in-tests"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *testUserNsPodsManager) listPodsFromDisk() ([]types.UID, error) {
|
func (m *testUserNsPodsManager) ListPodsFromDisk() ([]types.UID, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,8 +46,7 @@ func TestUserNsManagerAllocate(t *testing.T) {
|
|||||||
m, err := MakeUserNsManager(testUserNsPodsManager)
|
m, err := MakeUserNsManager(testUserNsPodsManager)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, true, m.isSet(0), "m.isSet(0) should be true")
|
assert.Equal(t, true, m.isSet(0*65536), "m.isSet(0) should be true")
|
||||||
assert.Equal(t, true, m.isSet(1), "m.isSet(1) should be true")
|
|
||||||
|
|
||||||
allocated, length, err := m.allocateOne("one")
|
allocated, length, err := m.allocateOne("one")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -173,133 +171,3 @@ func TestUserNsManagerParseUserNsFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserNsManagerHostIDFromMapping(t *testing.T) {
|
|
||||||
// mapping []*runtimeapi.IDMapping, containerId *int64
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
success bool
|
|
||||||
containerId int64 // -1 means a nil ptr will be used.
|
|
||||||
expHostId int64
|
|
||||||
m []*runtimeapi.IDMapping
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "one basic mapping",
|
|
||||||
success: true,
|
|
||||||
containerId: -1,
|
|
||||||
expHostId: 0,
|
|
||||||
m: []*runtimeapi.IDMapping{
|
|
||||||
{
|
|
||||||
HostId: 0,
|
|
||||||
ContainerId: 0,
|
|
||||||
Length: userNsLength,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one unprivileged mapping",
|
|
||||||
success: true,
|
|
||||||
containerId: -1,
|
|
||||||
expHostId: userNsLength * 2,
|
|
||||||
m: []*runtimeapi.IDMapping{
|
|
||||||
{
|
|
||||||
HostId: userNsLength * 2,
|
|
||||||
ContainerId: 0,
|
|
||||||
Length: userNsLength,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one unprivileged mapping random id",
|
|
||||||
success: true,
|
|
||||||
containerId: 3,
|
|
||||||
expHostId: userNsLength*2 + 3,
|
|
||||||
m: []*runtimeapi.IDMapping{
|
|
||||||
{
|
|
||||||
HostId: userNsLength * 2,
|
|
||||||
ContainerId: 0,
|
|
||||||
Length: userNsLength,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "two unprivileged mapping",
|
|
||||||
success: true,
|
|
||||||
containerId: 0,
|
|
||||||
expHostId: userNsLength*2 + 0,
|
|
||||||
m: []*runtimeapi.IDMapping{
|
|
||||||
{
|
|
||||||
HostId: userNsLength * 2,
|
|
||||||
ContainerId: 0,
|
|
||||||
Length: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
HostId: userNsLength*2 + 10,
|
|
||||||
ContainerId: 1,
|
|
||||||
Length: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "two unprivileged mapping - random id",
|
|
||||||
success: true,
|
|
||||||
containerId: 1,
|
|
||||||
expHostId: userNsLength*2 + 10,
|
|
||||||
m: []*runtimeapi.IDMapping{
|
|
||||||
{
|
|
||||||
HostId: userNsLength * 2,
|
|
||||||
ContainerId: 0,
|
|
||||||
Length: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
HostId: userNsLength*2 + 10,
|
|
||||||
ContainerId: 1,
|
|
||||||
Length: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "two unprivileged mapping - not mapped user",
|
|
||||||
success: false,
|
|
||||||
containerId: 3,
|
|
||||||
m: []*runtimeapi.IDMapping{
|
|
||||||
{
|
|
||||||
HostId: userNsLength * 2,
|
|
||||||
ContainerId: 0,
|
|
||||||
Length: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
HostId: userNsLength*2 + 1,
|
|
||||||
ContainerId: 1,
|
|
||||||
Length: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no mappings",
|
|
||||||
success: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range cases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
var containerId *int64
|
|
||||||
if tc.containerId != -1 {
|
|
||||||
containerId = &tc.containerId
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := hostIDFromMapping(tc.m, containerId)
|
|
||||||
if (tc.success && err != nil) || (!tc.success && err == nil) {
|
|
||||||
t.Fatalf("%v: expected success: %v - got error: %v", tc.name, tc.success, err)
|
|
||||||
}
|
|
||||||
if !tc.success && err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if id != tc.expHostId {
|
|
||||||
t.Errorf("expected: %v - got: %v", tc.expHostId, id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -128,16 +128,6 @@ func (kvh *kubeletVolumeHost) GetPodsDir() string {
|
|||||||
return kvh.kubelet.getPodsDir()
|
return kvh.kubelet.getPodsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHostIDsForPod if the pod uses user namespaces, takes the uid and gid
|
|
||||||
// inside the container and returns the host UID and GID those are mapped to on
|
|
||||||
// the host. If containerUID/containerGID is nil, then it returns the host
|
|
||||||
// UID/GID for ID 0 inside the container.
|
|
||||||
// If the pod is not using user namespaces, as there is no mapping needed, the
|
|
||||||
// same containerUID and containerGID params are returned.
|
|
||||||
func (kvh *kubeletVolumeHost) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) {
|
|
||||||
return kvh.kubelet.getHostIDsForPod(pod, containerUID, containerGID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kvh *kubeletVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string {
|
func (kvh *kubeletVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName string, volumeName string) string {
|
||||||
dir := kvh.kubelet.getPodVolumeDir(podUID, pluginName, volumeName)
|
dir := kvh.kubelet.getPodVolumeDir(podUID, pluginName, volumeName)
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
|
@ -334,13 +334,6 @@ type KubeletVolumeHost interface {
|
|||||||
WaitForCacheSync() error
|
WaitForCacheSync() error
|
||||||
// Returns hostutil.HostUtils
|
// Returns hostutil.HostUtils
|
||||||
GetHostUtil() hostutil.HostUtils
|
GetHostUtil() hostutil.HostUtils
|
||||||
// GetHostIDsForPod if the pod uses user namespaces, takes the uid and
|
|
||||||
// gid inside the container and returns the host UID and GID those are
|
|
||||||
// mapped to on the host. If containerUID/containerGID is nil, then it
|
|
||||||
// returns the host UID/GID for ID 0 inside the container.
|
|
||||||
// If the pod is not using user namespaces, as there is no mapping needed, the
|
|
||||||
// same containerUID and containerGID params are returned.
|
|
||||||
GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AttachDetachVolumeHost is a AttachDetach Controller specific interface that plugins can use
|
// AttachDetachVolumeHost is a AttachDetach Controller specific interface that plugins can use
|
||||||
|
@ -123,10 +123,6 @@ func (f *fakeVolumeHost) GetPodsDir() string {
|
|||||||
return filepath.Join(f.rootDir, "pods")
|
return filepath.Join(f.rootDir, "pods")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeVolumeHost) GetHostIDsForPod(pod *v1.Pod, containerUID, containerGID *int64) (hostUID, hostGID *int64, err error) {
|
|
||||||
return containerUID, containerGID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string {
|
func (f *fakeVolumeHost) GetPodVolumeDir(podUID types.UID, pluginName, volumeName string) string {
|
||||||
return filepath.Join(f.rootDir, "pods", string(podUID), "volumes", pluginName, volumeName)
|
return filepath.Join(f.rootDir, "pods", string(podUID), "volumes", pluginName, volumeName)
|
||||||
}
|
}
|
||||||
|
@ -682,35 +682,10 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
|
|||||||
resizeOptions.DeviceStagePath = deviceStagePath
|
resizeOptions.DeviceStagePath = deviceStagePath
|
||||||
}
|
}
|
||||||
|
|
||||||
// No mapping is needed for hostUID/hostGID if userns is not used.
|
|
||||||
// Therefore, just assign the container users to host UID/GID.
|
|
||||||
hostUID := util.FsUserFrom(volumeToMount.Pod)
|
|
||||||
hostGID := fsGroup
|
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesStatelessPodsSupport) {
|
|
||||||
// Without userns hostUID/GID was the user inside the container too.
|
|
||||||
containerUID, containerGID := hostUID, hostGID
|
|
||||||
|
|
||||||
kvh, ok := og.GetVolumePluginMgr().Host.(volume.KubeletVolumeHost)
|
|
||||||
if !ok {
|
|
||||||
msg := fmt.Errorf("volume host does not implement KubeletVolumeHost interface")
|
|
||||||
eventErr, detailedErr := volumeToMount.GenerateError("MountVolume type assertion error", msg)
|
|
||||||
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This pod _might_ use userns. GetHostIDsForPod() will give us the right
|
|
||||||
// UID/GID to use for this pod (no matter if the pod uses userns or not).
|
|
||||||
hostUID, hostGID, err = kvh.GetHostIDsForPod(volumeToMount.Pod, containerUID, containerGID)
|
|
||||||
if err != nil {
|
|
||||||
msg := fmt.Sprintf("MountVolume.GetHostIDsForPod failed to find host ID in user namespace (UID: %v GID: %v)", containerUID, containerGID)
|
|
||||||
eventErr, detailedErr := volumeToMount.GenerateError(msg, err)
|
|
||||||
return volumetypes.NewOperationContext(eventErr, detailedErr, migrated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute mount
|
// Execute mount
|
||||||
mountErr := volumeMounter.SetUp(volume.MounterArgs{
|
mountErr := volumeMounter.SetUp(volume.MounterArgs{
|
||||||
FsUser: hostUID,
|
FsUser: util.FsUserFrom(volumeToMount.Pod),
|
||||||
FsGroup: hostGID,
|
FsGroup: fsGroup,
|
||||||
DesiredSize: volumeToMount.DesiredSizeLimit,
|
DesiredSize: volumeToMount.DesiredSizeLimit,
|
||||||
FSGroupChangePolicy: fsGroupChangePolicy,
|
FSGroupChangePolicy: fsGroupChangePolicy,
|
||||||
SELinuxLabel: volumeToMount.SELinuxLabel,
|
SELinuxLabel: volumeToMount.SELinuxLabel,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -222,6 +222,10 @@ message Mount {
|
|||||||
bool selinux_relabel = 4;
|
bool selinux_relabel = 4;
|
||||||
// Requested propagation mode.
|
// Requested propagation mode.
|
||||||
MountPropagation propagation = 5;
|
MountPropagation propagation = 5;
|
||||||
|
// UidMappings specifies the runtime UID mappings for the mount.
|
||||||
|
repeated IDMapping uidMappings = 6;
|
||||||
|
// GidMappings specifies the runtime GID mappings for the mount.
|
||||||
|
repeated IDMapping gidMappings = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDMapping describes host to container ID mappings for a pod sandbox.
|
// IDMapping describes host to container ID mappings for a pod sandbox.
|
||||||
|
Loading…
Reference in New Issue
Block a user