Skip to content

Commit db6eab3

Browse files
Use hard-coded kubelet path for s3-plugin container (#656)
*Issue #577 * **Description** On MicroK8s (snap install), kubelet uses `/var/snap/microk8s/common/var/lib/kubelet` as its root directory. In CSI Driver v2, Unix socket paths built from this base exceed Linux's 108-byte `UNIX_PATH_MAX` limit, causing bind() / connect() to fail with invalid argument. The driver either fails to register with kubelet or fails to communicate with Mountpoint pods, making S3 mounts impossible. This affects any Kubernetes distribution with a long kubelet path. **Fix** Hard-code the container-side mountPath to `/var/lib/kubelet `regardless of the host path. The host path remains configurable via `node.kubeletPath` (used as the volume source), but inside the container all paths are short. Replace the single `KUBELET_PATH` env var with: - `CONTAINER_KUBELET_PATH`=`/var/lib/kubelet `— path as seen inside the container - `HOST_KUBELET_PATH` — path as seen on the host (from node.kubeletPath) Add `KubeletHostPathToContainerPath()` to translate host paths (received from kubelet RPCs) to container paths in both `NodePublishVolume` and `NodeUnpublishVolume`. **Verification** Tested on Ubuntu 22.04 with MicroK8s (snap, channel 1.32) and node.kubeletPath=/var/snap/microk8s/common/var/lib/kubelet. Without the fix, the driver fails to register as reported in Issue #577 — the registration socket path exceeds Linux's 108-byte limit. With the fix — driver registers successfully and all 3 node containers are running: ``` $ microk8s kubectl get pods -n kube-system | grep s3-csi s3-csi-controller-558c76774b-4mc8f 1/1 Running 0 5m s3-csi-node-spmkn 3/3 Running 0 5m ``` ``` $ microk8s kubectl logs -n kube-system -l app=s3-csi-node -c node-driver-registrar I0322 14:02:26.423604 1 main.go:145] "Version" version="v2.16.0-eksbuild.1" I0322 14:02:26.428943 1 node_register.go:56] "Starting Registration Server" socketPath="/registration/s3.csi.aws.com-reg.sock" I0322 14:02:26.429167 1 node_register.go:66] "Registration Server started" socketPath="/registration/s3.csi.aws.com-reg.sock" I0322 14:02:26.965157 1 main.go:102] "Received NotifyRegistrationStatus call" status="plugin_registered:true" ``` S3 mount works — workload pod writes to the bucket: ``` $ microk8s kubectl exec s3-app -- ls -la /data/ $ aws s3 ls tadiwaom-s3-test/ ... 2026-03-22 21:30:34 26 Sun Mar 22 21:30:32 UTC 2026.txt ``` **Testing** Manual Testing with E2E tests passing: (rosa was not set up in my CI) https://github.com/tadiwa-aizen/mountpoint-s3-csi-driver/actions/runs/23405969976 By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. --------- Signed-off-by: Tadiwa Magwenzi <tadiwaom@amazon.com> Co-authored-by: Yerzhan Mazhkenov <20302932+yerzhan7@users.noreply.github.com>
1 parent 53033ca commit db6eab3

9 files changed

Lines changed: 91 additions & 43 deletions

File tree

charts/aws-mountpoint-s3-csi-driver/templates/node.yaml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,17 @@ spec:
8282
- --v={{ .Values.node.logLevel }}
8383
env:
8484
- name: CSI_ENDPOINT
85-
value: unix:{{ trimSuffix "/" .Values.node.kubeletPath }}/plugins/s3.csi.aws.com/csi.sock
86-
- name: KUBELET_PATH
87-
value: {{ .Values.node.kubeletPath }}
85+
value: unix:/var/lib/kubelet/plugins/s3.csi.aws.com/csi.sock
8886
- name: CSI_NODE_NAME
8987
valueFrom:
9088
fieldRef:
9189
fieldPath: spec.nodeName
9290
- name: HOST_PLUGIN_DIR
93-
value: {{ trimSuffix "/" .Values.node.kubeletPath }}/plugins/s3.csi.aws.com/
91+
value: /var/lib/kubelet/plugins/s3.csi.aws.com/
92+
- name: CONTAINER_KUBELET_PATH
93+
value: /var/lib/kubelet
94+
- name: HOST_KUBELET_PATH
95+
value: {{ .Values.node.kubeletPath }}
9496
- name: SUPPORT_LEGACY_SYSTEMD_MOUNTS
9597
value: "{{ .Values.supportLegacySystemDMounts }}"
9698
- name: MOUNTPOINT_NAMESPACE
@@ -121,7 +123,7 @@ spec:
121123
{{- end }}
122124
volumeMounts:
123125
- name: kubelet-dir
124-
mountPath: {{ .Values.node.kubeletPath }}
126+
mountPath: /var/lib/kubelet
125127
mountPropagation: Bidirectional
126128
ports:
127129
- name: healthz

deploy/kubernetes/base/node.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,16 @@ spec:
6262
env:
6363
- name: CSI_ENDPOINT
6464
value: unix:/var/lib/kubelet/plugins/s3.csi.aws.com/csi.sock
65-
- name: KUBELET_PATH
66-
value: /var/lib/kubelet
6765
- name: CSI_NODE_NAME
6866
valueFrom:
6967
fieldRef:
7068
fieldPath: spec.nodeName
7169
- name: HOST_PLUGIN_DIR
7270
value: /var/lib/kubelet/plugins/s3.csi.aws.com/
71+
- name: CONTAINER_KUBELET_PATH
72+
value: /var/lib/kubelet
73+
- name: HOST_KUBELET_PATH
74+
value: /var/lib/kubelet
7375
- name: SUPPORT_LEGACY_SYSTEMD_MOUNTS
7476
value: "true"
7577
- name: MOUNTPOINT_NAMESPACE

pkg/driver/node/mounter/pod_mounter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func NewPodMounter(
7979
s3paCache: s3paCache,
8080
credProvider: credProvider,
8181
mount: mount,
82-
kubeletPath: util.KubeletPath(),
82+
kubeletPath: util.ContainerKubeletPath(),
8383
mountSyscall: mountSyscall,
8484
bindMountSyscall: bindMountSyscall,
8585
kubernetesVersion: kubernetesVersion,

pkg/driver/node/mounter/pod_mounter_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func setup(t *testing.T) *testCtx {
7878
parentDir, err := filepath.EvalSymlinks(filepath.Dir(kubeletPath))
7979
assert.NoError(t, err)
8080
kubeletPath = filepath.Join(parentDir, filepath.Base(kubeletPath))
81-
t.Setenv("KUBELET_PATH", kubeletPath)
81+
t.Setenv("CONTAINER_KUBELET_PATH", kubeletPath)
8282

8383
// Chdir to `kubeletPath` so `mountoptions.{Recv, Send}` can use relative paths to Unix sockets
8484
// to overcome `bind: invalid argument`.

pkg/driver/node/mounter/pod_unmounter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func NewPodUnmounter(
4949
return &PodUnmounter{
5050
nodeID: nodeID,
5151
mount: mount,
52-
kubeletPath: util.KubeletPath(),
52+
kubeletPath: util.ContainerKubeletPath(),
5353
podWatcher: podWatcher,
5454
credProvider: credProvider,
5555
}

pkg/driver/node/mounter/pod_unmounter_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func TestHandleS3PodAttachmentUpdate(t *testing.T) {
193193
parentDir, err := filepath.EvalSymlinks(filepath.Dir(kubeletPath))
194194
assert.NoError(t, err)
195195
kubeletPath = filepath.Join(parentDir, filepath.Base(kubeletPath))
196-
t.Setenv("KUBELET_PATH", kubeletPath)
196+
t.Setenv("CONTAINER_KUBELET_PATH", kubeletPath)
197197
t.Chdir(kubeletPath)
198198

199199
sourceMountDir := mounter.SourceMountDir(kubeletPath)
@@ -337,7 +337,7 @@ func TestCleanupDanglingMounts(t *testing.T) {
337337
parentDir, err := filepath.EvalSymlinks(filepath.Dir(kubeletPath))
338338
assert.NoError(t, err)
339339
kubeletPath = filepath.Join(parentDir, filepath.Base(kubeletPath))
340-
t.Setenv("KUBELET_PATH", kubeletPath)
340+
t.Setenv("CONTAINER_KUBELET_PATH", kubeletPath)
341341
t.Chdir(kubeletPath)
342342

343343
sourceMountDir := mounter.SourceMountDir(kubeletPath)

pkg/driver/node/node.go

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"maps"
2222
"os"
2323
"strconv"
24-
"strings"
2524

2625
"github.com/container-storage-interface/spec/lib/go/csi"
2726
"google.golang.org/grpc/codes"
@@ -39,8 +38,6 @@ import (
3938
"github.com/awslabs/mountpoint-s3-csi-driver/pkg/util"
4039
)
4140

42-
var kubeletPath = util.KubeletPath()
43-
4441
var (
4542
nodeCaps = []csi.NodeServiceCapability_RPC_Type{
4643
csi.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP,
@@ -96,13 +93,17 @@ func (ns *S3NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePubl
9693
return nil, status.Error(codes.InvalidArgument, "Bucket name not provided")
9794
}
9895

99-
target := req.GetTargetPath()
100-
if len(target) == 0 {
96+
targetHost := req.GetTargetPath()
97+
if len(targetHost) == 0 {
10198
return nil, status.Error(codes.InvalidArgument, "Target path not provided")
10299
}
103100

104-
if !strings.HasPrefix(target, kubeletPath) {
105-
klog.Errorf("NodePublishVolume: target path %q is not in kubelet path %q. This might cause mounting issues, please ensure you have correct kubelet path configured.", target, kubeletPath)
101+
// Translate target path from host format to container format if needed.
102+
// Example error: Failed to translate target path "/opt/kubernetes/kubelet/pods/abcd-1234/volumes/kubernetes.io~csi/s3-pv/mount":
103+
// path "/opt/kubernetes/kubelet/pods/abcd-1234/volumes/kubernetes.io~csi/s3-pv/mount" does not start with host kubelet path "/var/lib/kubelet"
104+
targetContainer, err := util.KubeletHostPathToContainerPath(targetHost)
105+
if err != nil {
106+
return nil, status.Errorf(codes.InvalidArgument, "Failed to translate target path %q: %v", targetHost, err)
106107
}
107108

108109
volCap := req.GetVolumeCapability()
@@ -190,15 +191,15 @@ func (ns *S3NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePubl
190191
}
191192
}
192193

193-
klog.V(4).Infof("NodePublishVolume: mounting %s at %s with options %v", bucket, target, args.SortedList())
194+
klog.V(4).Infof("NodePublishVolume: mounting %s at %s with options %v", bucket, targetContainer, args.SortedList())
194195

195196
credentialCtx := credentialProvideContextFromPublishRequest(req, args)
196197

197-
if err := ns.Mounter.Mount(ctx, bucket, target, credentialCtx, args, fsGroup); err != nil {
198-
os.Remove(target)
199-
return nil, status.Errorf(codes.Internal, "Could not mount %q at %q: %v", bucket, target, err)
198+
if err := ns.Mounter.Mount(ctx, bucket, targetContainer, credentialCtx, args, fsGroup); err != nil {
199+
os.Remove(targetContainer)
200+
return nil, status.Errorf(codes.Internal, "Could not mount %q at %q: %v", bucket, targetContainer, err)
200201
}
201-
klog.V(4).Infof("NodePublishVolume: %s was mounted", target)
202+
klog.V(4).Infof("NodePublishVolume: %s was mounted", targetContainer)
202203

203204
return &csi.NodePublishVolumeResponse{}, nil
204205
}
@@ -211,32 +212,37 @@ func (ns *S3NodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUn
211212
return nil, status.Error(codes.InvalidArgument, "Volume ID not provided")
212213
}
213214

214-
target := req.GetTargetPath()
215-
if len(target) == 0 {
215+
targetHost := req.GetTargetPath()
216+
if len(targetHost) == 0 {
216217
return nil, status.Error(codes.InvalidArgument, "Target path not provided")
217218
}
218219

219-
mounted, err := ns.Mounter.IsMountPoint(target)
220+
targetContainer, err := util.KubeletHostPathToContainerPath(targetHost)
221+
if err != nil {
222+
return nil, status.Errorf(codes.InvalidArgument, "Failed to translate target path %q: %v", targetHost, err)
223+
}
224+
225+
mounted, err := ns.Mounter.IsMountPoint(targetContainer)
220226
if err != nil && os.IsNotExist(err) {
221-
klog.V(4).Infof("NodeUnpublishVolume: target path %s does not exist, skipping unmount", target)
227+
klog.V(4).Infof("NodeUnpublishVolume: target path %s does not exist, skipping unmount", targetContainer)
222228
return &csi.NodeUnpublishVolumeResponse{}, nil
223229
} else if err != nil && mount.IsCorruptedMnt(err) {
224-
klog.V(4).Infof("NodeUnpublishVolume: target path %s is corrupted: %v, will try to unmount", target, err)
230+
klog.V(4).Infof("NodeUnpublishVolume: target path %s is corrupted: %v, will try to unmount", targetContainer, err)
225231
mounted = true
226232
} else if err != nil {
227-
return nil, status.Errorf(codes.Internal, "Could not unmount %q: %v", target, err)
233+
return nil, status.Errorf(codes.Internal, "Could not unmount %q: %v", targetContainer, err)
228234
}
229235
if !mounted {
230-
klog.V(4).Infof("NodeUnpublishVolume: target path %s not mounted, skipping unmount", target)
236+
klog.V(4).Infof("NodeUnpublishVolume: target path %s not mounted, skipping unmount", targetContainer)
231237
return &csi.NodeUnpublishVolumeResponse{}, nil
232238
}
233239

234240
credentialCtx := credentialCleanupContextFromUnpublishRequest(req)
235241

236-
klog.V(4).Infof("NodeUnpublishVolume: unmounting %s", target)
237-
err = ns.Mounter.Unmount(ctx, target, credentialCtx)
242+
klog.V(4).Infof("NodeUnpublishVolume: unmounting %s", targetContainer)
243+
err = ns.Mounter.Unmount(ctx, targetContainer, credentialCtx)
238244
if err != nil {
239-
return nil, status.Errorf(codes.Internal, "Could not unmount %q: %v", target, err)
245+
return nil, status.Errorf(codes.Internal, "Could not unmount %q: %v", targetContainer, err)
240246
}
241247

242248
return &csi.NodeUnpublishVolumeResponse{}, nil

pkg/driver/node/node_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func TestNodePublishVolume(t *testing.T) {
4848
Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
4949
},
5050
}
51-
targetPath = "/target/path"
51+
targetPath = "/var/lib/kubelet/target/path"
5252
)
5353
testCases := []struct {
5454
name string
@@ -379,7 +379,7 @@ func TestNodePublishVolumeForPodMounter(t *testing.T) {
379379
var (
380380
volumeId = "test-volume-id"
381381
bucketName = "test-bucket-name"
382-
targetPath = "/target/path"
382+
targetPath = "/var/lib/kubelet/target/path"
383383
)
384384
testCases := []struct {
385385
name string
@@ -602,7 +602,7 @@ func TestNodePublishVolumeMaxCacheSizeInjection(t *testing.T) {
602602
var (
603603
volumeId = "test-volume-id"
604604
bucketName = "test-bucket-name"
605-
targetPath = "/target/path"
605+
targetPath = "/var/lib/kubelet/target/path"
606606
)
607607

608608
testCases := []struct {
@@ -792,7 +792,7 @@ func TestNodePublishVolumeMaxCacheSizeInjection(t *testing.T) {
792792
func TestNodeUnpublishVolume(t *testing.T) {
793793
var (
794794
volumeId = "test-volume-id"
795-
targetPath = "/target/path"
795+
targetPath = "/var/lib/kubelet/target/path"
796796
)
797797
testCases := []struct {
798798
name string

pkg/util/kubelet.go

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,53 @@
11
package util
22

3-
import "os"
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
)
48

59
const defaultKubeletPath = "/var/lib/kubelet"
610

7-
// KubeletPath returns path of the kubelet.
8-
// It looks for `KUBELET_PATH` variable, and returns a default path if its not defined.
9-
func KubeletPath() string {
10-
kubeletPath := os.Getenv("KUBELET_PATH")
11+
// ContainerKubeletPath returns the kubelet path as seen from inside the container.
12+
// It looks for `CONTAINER_KUBELET_PATH` variable, and returns a default path if its not defined.
13+
func ContainerKubeletPath() string {
14+
kubeletPath := os.Getenv("CONTAINER_KUBELET_PATH")
1115
if kubeletPath == "" {
1216
return defaultKubeletPath
1317
}
1418
return kubeletPath
1519
}
20+
21+
// HostKubeletPath returns the kubelet path as seen on the host.
22+
// It looks for `HOST_KUBELET_PATH` variable, and returns a default path if its not defined.
23+
func HostKubeletPath() string {
24+
kubeletPath := os.Getenv("HOST_KUBELET_PATH")
25+
if kubeletPath == "" {
26+
return defaultKubeletPath
27+
}
28+
return kubeletPath
29+
}
30+
31+
// KubeletHostPathToContainerPath translates a path from host kubelet path to container kubelet path.
32+
// This is useful when the kubelet path on the host is different from the kubelet path in the container.
33+
// For example, in MicroK8s, the host path is /var/snap/microk8s/common/var/lib/kubelet
34+
// but the container sees it as /var/lib/kubelet.
35+
func KubeletHostPathToContainerPath(path string) (string, error) {
36+
hostPath := HostKubeletPath()
37+
containerPath := ContainerKubeletPath()
38+
39+
// If paths are the same, no translation needed
40+
if hostPath == containerPath {
41+
if strings.HasPrefix(path, containerPath) {
42+
return path, nil
43+
}
44+
return "", fmt.Errorf("path %q does not start with kubelet path %q", path, containerPath)
45+
}
46+
47+
// If the path starts with the host kubelet path, replace it with the container path
48+
if strings.HasPrefix(path, hostPath) {
49+
return strings.Replace(path, hostPath, containerPath, 1), nil
50+
}
51+
52+
return "", fmt.Errorf("path %q does not start with host kubelet path %q", path, hostPath)
53+
}

0 commit comments

Comments
 (0)