Skip to content

Commit f0ae7f8

Browse files
committed
first commit
0 parents  commit f0ae7f8

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed

README.md

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
CIFS Flexvolume Plugin for Kubernetes
2+
=====================================
3+
4+
Driver for [CIFS][1] (SMB, Samba, Windows Share) network filesystems as [Kubernetes volumes][2].
5+
6+
Background
7+
----------
8+
9+
Docker containers running in Kubernetes have an ephemeral file system: Once a container is terminated, all files are gone. In order to store persistent data in Kubernetes, you need to mount a [Persistent Volume][3] into your container. Kubernetes has built-in support for network filesystems found in the most common cloud providers, like [Amazon's EBS][4], [Microsoft's Azure disk][5], etc. However, some cloud hosting services, like the [Hetzner cloud][6], provide network storage using the CIFS (SMB, Samba, Windows Share) protocol, which is not natively supported in Kubernetes.
10+
11+
Fortunately, Kubernetes provides [Flexvolume][7], which is a plugin mechanism enabling users to write their own drivers. There are a few flexvolume drivers for CIFS out there, but for different reasons none of them seemed to work for me. So I wrote my own, which can be found on [github.com/fstab/cifs][8].
12+
13+
Installing
14+
----------
15+
16+
The flexvolume plugin is a single shell script named [cifs][8]. This shell scripted must be available in `/usr/libexec/kubernetes/kubelet-plugins/volume/exec/fstab~cifs/` on the Kubernetes master and on each of the Kubernetes nodes. The directory name `fstab~cifs` will be [mapped][9] to the Flexvolume driver name `fstab/cifs`.
17+
18+
On the Kubernetes master and on each Kubernetes node run the following commands:
19+
20+
```bash
21+
mkdir -p '/usr/libexec/kubernetes/kubelet-plugins/volume/exec/fstab~cifs'
22+
cd '/usr/libexec/kubernetes/kubelet-plugins/volume/exec/fstab~cifs'
23+
curl -L -O https://raw.githubusercontent.com/fstab/cifs/master/cifs
24+
chmod 755 cifs
25+
```
26+
27+
The `cifs` script requires a few executables to be available on each host system:
28+
29+
* `mount.cifs`, on Ubuntu this is in the [cifs-utils][10] package.
30+
* `jq`, on Ubuntu this is in the [jq][11] package.
31+
* `mountpoint`, on Ubuntu this is in the [util-linux][12] package.
32+
* `base64`, on Ubuntu this is in the [coreutils][13] package.
33+
34+
To check if the installation was successful, run the following command:
35+
36+
```bash
37+
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/fstab~cifs/cifs init
38+
```
39+
40+
It should output a JSON string containing `"status": "Success"`. This command is also run by Kubernetes itself when the cifs plugin is detected on the file system.
41+
42+
Running
43+
-------
44+
45+
The plugin takes the CIFS username and password from a [Kubernetes Secret][14]. To create the secret, you first have to convert your username and password to base64 encoding:
46+
47+
```bash
48+
echo -n username | base64
49+
echo -n password | base64
50+
```
51+
52+
Then, create a file `secret.yml` and use the ouput of the above commands as username and password:
53+
54+
```yaml
55+
apiVersion: v1
56+
kind: Secret
57+
metadata:
58+
name: cifs-secret
59+
namespace: default
60+
type: fstab/cifs
61+
data:
62+
username: 'ZXhhbXBsZQ=='
63+
password: 'bXktc2VjcmV0LXBhc3N3b3Jk'
64+
```
65+
66+
Apply the secret:
67+
68+
```bash
69+
kubectl apply -f secret.yml
70+
```
71+
72+
You can check if the secret was installed successfully using `kubectl describe secret cifs-secret`.
73+
74+
Next, create a file `pod.yml` with a test pod (replace `//server/share` with the network path of your CIFS share):
75+
76+
```yaml
77+
apiVersion: v1
78+
kind: Pod
79+
metadata:
80+
name: busybox
81+
namespace: default
82+
spec:
83+
containers:
84+
- name: busybox
85+
image: busybox
86+
command:
87+
- sleep
88+
- "3600"
89+
imagePullPolicy: IfNotPresent
90+
volumeMounts:
91+
- name: test
92+
mountPath: /data
93+
volumes:
94+
- name: test
95+
flexVolume:
96+
driver: "fstab/cifs"
97+
fsType: "cifs"
98+
secretRef:
99+
name: "cifs-secret"
100+
options:
101+
networkPath: "//server/share"
102+
mountOptions: "dir_mode=0755,file_mode=0644,noperm"
103+
```
104+
105+
Start the pod:
106+
107+
```yaml
108+
kubectl apply -f pod.yml
109+
```
110+
111+
You can verify that the volume was mounted successfully using `kubectl describe pod busybox`.
112+
113+
Testing
114+
-------
115+
116+
If everything is fine, start a shell inside the container to see if it worked:
117+
118+
```bash
119+
kubectl exec -ti busybox /bin/sh
120+
```
121+
122+
Inside the container, you should see the CIFS share mounted to `/data`.
123+
124+
[1]: https://en.wikipedia.org/wiki/Server_Message_Block
125+
[2]: https://kubernetes.io/docs/concepts/storage/volumes/
126+
[3]: https://kubernetes.io/docs/concepts/storage/volumes/
127+
[4]: https://aws.amazon.com/ebs
128+
[5]: https://azure.microsoft.com/en-us/services/storage/unmanaged-disks/
129+
[6]: https://hetzner.cloud
130+
[7]: https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md
131+
[8]: https://github.com/fstab/cifs
132+
[9]: https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md#prerequisites
133+
[10]: https://packages.ubuntu.com/bionic/cifs-utils
134+
[11]: https://packages.ubuntu.com/bionic/jq
135+
[12]: https://packages.ubuntu.com/bionic/util-linux
136+
[13]: https://packages.ubuntu.com/bionic/coreutils
137+
[14]: https://kubernetes.io/docs/concepts/configuration/secret/

cifs

+174
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/bin/bash
2+
3+
set -u
4+
5+
# ====================================================================
6+
# Example configuration:
7+
# ====================================================================
8+
# --------------------------------------------------------------------
9+
# secret.yml:
10+
# --------------------------------------------------------------------
11+
# apiVersion: v1
12+
# kind: Secret
13+
# metadata:
14+
# name: cifs-secret
15+
# namespace: default
16+
# type: fstab/cifs
17+
# data:
18+
# username: 'ZXhhbXBsZQo='
19+
# password: 'c2VjcmV0Cg=='
20+
#
21+
# --------------------------------------------------------------------
22+
# pod.yml:
23+
# --------------------------------------------------------------------
24+
# apiVersion: v1
25+
# kind: Pod
26+
# metadata:
27+
# name: busybox
28+
# namespace: default
29+
# spec:
30+
# containers:
31+
# - name: busybox
32+
# image: busybox
33+
# command:
34+
# - sleep
35+
# - "3600"
36+
# imagePullPolicy: IfNotPresent
37+
# volumeMounts:
38+
# - name: test
39+
# mountPath: /data
40+
# volumes:
41+
# - name: test
42+
# flexVolume:
43+
# driver: "fstab/cifs"
44+
# fsType: "cifs"
45+
# secretRef:
46+
# name: "cifs-secret"
47+
# options:
48+
# networkPath: "//example-server/backup"
49+
# mountOptions: "dir_mode=0755,file_mode=0644,noperm"
50+
# --------------------------------------------------------------------
51+
52+
# Uncomment the following lines to see how this plugin is called:
53+
# echo >> /tmp/cifs.log
54+
# date >> /tmp/cifs.log
55+
# echo "$@" >> /tmp/cifs.log
56+
57+
init() {
58+
assertBinaryInstalled mount.cifs cifs-utils
59+
assertBinaryInstalled jq jq
60+
assertBinaryInstalled mountpoint util-linux
61+
assertBinaryInstalled base64 coreutils
62+
echo '{ "status": "Success", "message": "The fstab/cifs flexvolume plugin was initialized successfully", "capabilities": { "attach": false } }'
63+
exit 0
64+
}
65+
66+
assertBinaryInstalled() {
67+
binary="$1"
68+
package="$2"
69+
if ! which "$binary" > /dev/null ; then
70+
errorExit "Failed to initialize the fstab/cifs flexvolume plugin. $binary command not found. Please install the $package package."
71+
fi
72+
}
73+
74+
errorExit() {
75+
if [[ $# -ne 1 ]] ; then
76+
echo '{ "status": "Failure", "message": "Unknown error in the fstab/cifs flexvolume plugin." }'
77+
else
78+
jq -Mcn --arg message "$1" '{ "status": "Failure", "message": $message }'
79+
fi
80+
exit 1
81+
}
82+
83+
doMount() {
84+
if [[ -z ${1:-} || -z ${2:-} ]] ; then
85+
errorExit "cifs mount: syntax error. usage: cifs mount <mount dir> <json options>"
86+
fi
87+
mountPoint="$1"
88+
shift
89+
json=$(printf '%s ' "${@}")
90+
if ! jq -e . > /dev/null 2>&1 <<< "$json" ; then
91+
errorExit "cifs mount: syntax error. invalid json: '$json'"
92+
fi
93+
networkPath="$(jq --raw-output -e '.networkPath' <<< "$json" 2>/dev/null)"
94+
if [[ $? -ne 0 ]] ; then
95+
errorExit "cifs mount: option networkPath missing in flexvolume configuration."
96+
fi
97+
mountOptions="$(jq --raw-output -e '.mountOptions' <<< "$json" 2>/dev/null)"
98+
if [[ $? -ne 0 ]] ; then
99+
errorExit "cifs mount: option mountOptions missing in flexvolume configuration."
100+
fi
101+
cifsUsernameBase64="$(jq --raw-output -e '.["kubernetes.io/secret/username"]' <<< "$json" 2>/dev/null)"
102+
if [[ $? -ne 0 ]] ; then
103+
errorExit "cifs mount: username not found. the flexVolume definition must contain a secretRef to a secret with username and password."
104+
fi
105+
cifsPasswordBase64="$(jq --raw-output -e '.["kubernetes.io/secret/password"]' <<< "$json" 2>/dev/null)"
106+
if [[ $? -ne 0 ]] ; then
107+
errorExit "cifs mount: password not found. the flexVolume definition must contain a secretRef to a secret with username and password."
108+
fi
109+
cifsUsername="$(base64 --decode <<< "$cifsUsernameBase64" 2>/dev/null)"
110+
if [[ $? -ne 0 ]] ; then
111+
errorExit "cifs mount: username secret is not base64 encoded."
112+
fi
113+
cifsPassword="$(base64 --decode <<< "$cifsPasswordBase64" 2>/dev/null)"
114+
if [[ $? -ne 0 ]] ; then
115+
errorExit "cifs mount: password secret is not base64 encoded."
116+
fi
117+
if ! mkdir -p "$mountPoint" > /dev/null 2>&1 ; then
118+
errorExit "cifs mount: failed to create mount directory: '$mountPoint'"
119+
fi
120+
if [[ $(mountpoint "$mountPoint") = *"is a mountpoint"* ]] ; then
121+
errorExit "cifs mount: there is already a filesystem mounted under the mount directory: '$mountPoint'"
122+
fi
123+
if [[ ! -z $(ls -A "$mountPoint" 2>/dev/null) ]] ; then
124+
errorExit "cifs mount: mount directory is not an empty directory: '$mountPoint'"
125+
fi
126+
127+
result=$(mount -t cifs "$networkPath" "$mountPoint" -o "username=$cifsUsername,password=$cifsPassword,$mountOptions" 2>&1)
128+
if [[ $? -ne 0 ]] ; then
129+
errorExit "cifs mount: failed to mount the network path: $result"
130+
fi
131+
echo '{ "status": "Success" }'
132+
exit 0
133+
}
134+
135+
doUnmount() {
136+
if [[ -z ${1:-} ]] ; then
137+
errorExit "cifs unmount: syntax error. usage: cifs unmount <mount dir>"
138+
fi
139+
mountPoint="$1"
140+
if [[ $(mountpoint "$mountPoint") != *"is a mountpoint"* ]] ; then
141+
errorExit "cifs unmount: no filesystem mounted under directory: '$mountPoint'"
142+
fi
143+
result=$(umount "$mountPoint" 2>&1)
144+
if [[ $? -ne 0 ]] ; then
145+
errorExit "cifs unmount: failed to unmount the network path: $result"
146+
fi
147+
echo '{ "status": "Success" }'
148+
exit 0
149+
}
150+
151+
not_supported() {
152+
echo '{ "status": "Not supported" }'
153+
exit 1
154+
}
155+
156+
command=${1:-}
157+
if [[ -n $command ]]; then
158+
shift
159+
fi
160+
161+
case "$command" in
162+
init)
163+
init "$@"
164+
;;
165+
mount)
166+
doMount "$@"
167+
;;
168+
unmount)
169+
doUnmount "$@"
170+
;;
171+
*)
172+
not_supported "$@"
173+
;;
174+
esac

0 commit comments

Comments
 (0)