Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
fstab committed May 11, 2018
0 parents commit f0ae7f8
Show file tree
Hide file tree
Showing 2 changed files with 311 additions and 0 deletions.
137 changes: 137 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
CIFS Flexvolume Plugin for Kubernetes
=====================================

Driver for [CIFS][1] (SMB, Samba, Windows Share) network filesystems as [Kubernetes volumes][2].

Background
----------

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.

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].

Installing
----------

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`.

On the Kubernetes master and on each Kubernetes node run the following commands:

```bash
mkdir -p '/usr/libexec/kubernetes/kubelet-plugins/volume/exec/fstab~cifs'
cd '/usr/libexec/kubernetes/kubelet-plugins/volume/exec/fstab~cifs'
curl -L -O https://raw.githubusercontent.com/fstab/cifs/master/cifs
chmod 755 cifs
```

The `cifs` script requires a few executables to be available on each host system:

* `mount.cifs`, on Ubuntu this is in the [cifs-utils][10] package.
* `jq`, on Ubuntu this is in the [jq][11] package.
* `mountpoint`, on Ubuntu this is in the [util-linux][12] package.
* `base64`, on Ubuntu this is in the [coreutils][13] package.

To check if the installation was successful, run the following command:

```bash
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/fstab~cifs/cifs init
```

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.

Running
-------

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:

```bash
echo -n username | base64
echo -n password | base64
```

Then, create a file `secret.yml` and use the ouput of the above commands as username and password:

```yaml
apiVersion: v1
kind: Secret
metadata:
name: cifs-secret
namespace: default
type: fstab/cifs
data:
username: 'ZXhhbXBsZQ=='
password: 'bXktc2VjcmV0LXBhc3N3b3Jk'
```
Apply the secret:
```bash
kubectl apply -f secret.yml
```

You can check if the secret was installed successfully using `kubectl describe secret cifs-secret`.

Next, create a file `pod.yml` with a test pod (replace `//server/share` with the network path of your CIFS share):

```yaml
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- name: busybox
image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
volumeMounts:
- name: test
mountPath: /data
volumes:
- name: test
flexVolume:
driver: "fstab/cifs"
fsType: "cifs"
secretRef:
name: "cifs-secret"
options:
networkPath: "//server/share"
mountOptions: "dir_mode=0755,file_mode=0644,noperm"
```
Start the pod:
```yaml
kubectl apply -f pod.yml
```

You can verify that the volume was mounted successfully using `kubectl describe pod busybox`.

Testing
-------

If everything is fine, start a shell inside the container to see if it worked:

```bash
kubectl exec -ti busybox /bin/sh
```

Inside the container, you should see the CIFS share mounted to `/data`.

[1]: https://en.wikipedia.org/wiki/Server_Message_Block
[2]: https://kubernetes.io/docs/concepts/storage/volumes/
[3]: https://kubernetes.io/docs/concepts/storage/volumes/
[4]: https://aws.amazon.com/ebs
[5]: https://azure.microsoft.com/en-us/services/storage/unmanaged-disks/
[6]: https://hetzner.cloud
[7]: https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md
[8]: https://github.com/fstab/cifs
[9]: https://github.com/kubernetes/community/blob/master/contributors/devel/flexvolume.md#prerequisites
[10]: https://packages.ubuntu.com/bionic/cifs-utils
[11]: https://packages.ubuntu.com/bionic/jq
[12]: https://packages.ubuntu.com/bionic/util-linux
[13]: https://packages.ubuntu.com/bionic/coreutils
[14]: https://kubernetes.io/docs/concepts/configuration/secret/
174 changes: 174 additions & 0 deletions cifs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#!/bin/bash

set -u

# ====================================================================
# Example configuration:
# ====================================================================
# --------------------------------------------------------------------
# secret.yml:
# --------------------------------------------------------------------
# apiVersion: v1
# kind: Secret
# metadata:
# name: cifs-secret
# namespace: default
# type: fstab/cifs
# data:
# username: 'ZXhhbXBsZQo='
# password: 'c2VjcmV0Cg=='
#
# --------------------------------------------------------------------
# pod.yml:
# --------------------------------------------------------------------
# apiVersion: v1
# kind: Pod
# metadata:
# name: busybox
# namespace: default
# spec:
# containers:
# - name: busybox
# image: busybox
# command:
# - sleep
# - "3600"
# imagePullPolicy: IfNotPresent
# volumeMounts:
# - name: test
# mountPath: /data
# volumes:
# - name: test
# flexVolume:
# driver: "fstab/cifs"
# fsType: "cifs"
# secretRef:
# name: "cifs-secret"
# options:
# networkPath: "//example-server/backup"
# mountOptions: "dir_mode=0755,file_mode=0644,noperm"
# --------------------------------------------------------------------

# Uncomment the following lines to see how this plugin is called:
# echo >> /tmp/cifs.log
# date >> /tmp/cifs.log
# echo "$@" >> /tmp/cifs.log

init() {
assertBinaryInstalled mount.cifs cifs-utils
assertBinaryInstalled jq jq
assertBinaryInstalled mountpoint util-linux
assertBinaryInstalled base64 coreutils
echo '{ "status": "Success", "message": "The fstab/cifs flexvolume plugin was initialized successfully", "capabilities": { "attach": false } }'
exit 0
}

assertBinaryInstalled() {
binary="$1"
package="$2"
if ! which "$binary" > /dev/null ; then
errorExit "Failed to initialize the fstab/cifs flexvolume plugin. $binary command not found. Please install the $package package."
fi
}

errorExit() {
if [[ $# -ne 1 ]] ; then
echo '{ "status": "Failure", "message": "Unknown error in the fstab/cifs flexvolume plugin." }'
else
jq -Mcn --arg message "$1" '{ "status": "Failure", "message": $message }'
fi
exit 1
}

doMount() {
if [[ -z ${1:-} || -z ${2:-} ]] ; then
errorExit "cifs mount: syntax error. usage: cifs mount <mount dir> <json options>"
fi
mountPoint="$1"
shift
json=$(printf '%s ' "${@}")
if ! jq -e . > /dev/null 2>&1 <<< "$json" ; then
errorExit "cifs mount: syntax error. invalid json: '$json'"
fi
networkPath="$(jq --raw-output -e '.networkPath' <<< "$json" 2>/dev/null)"
if [[ $? -ne 0 ]] ; then
errorExit "cifs mount: option networkPath missing in flexvolume configuration."
fi
mountOptions="$(jq --raw-output -e '.mountOptions' <<< "$json" 2>/dev/null)"
if [[ $? -ne 0 ]] ; then
errorExit "cifs mount: option mountOptions missing in flexvolume configuration."
fi
cifsUsernameBase64="$(jq --raw-output -e '.["kubernetes.io/secret/username"]' <<< "$json" 2>/dev/null)"
if [[ $? -ne 0 ]] ; then
errorExit "cifs mount: username not found. the flexVolume definition must contain a secretRef to a secret with username and password."
fi
cifsPasswordBase64="$(jq --raw-output -e '.["kubernetes.io/secret/password"]' <<< "$json" 2>/dev/null)"
if [[ $? -ne 0 ]] ; then
errorExit "cifs mount: password not found. the flexVolume definition must contain a secretRef to a secret with username and password."
fi
cifsUsername="$(base64 --decode <<< "$cifsUsernameBase64" 2>/dev/null)"
if [[ $? -ne 0 ]] ; then
errorExit "cifs mount: username secret is not base64 encoded."
fi
cifsPassword="$(base64 --decode <<< "$cifsPasswordBase64" 2>/dev/null)"
if [[ $? -ne 0 ]] ; then
errorExit "cifs mount: password secret is not base64 encoded."
fi
if ! mkdir -p "$mountPoint" > /dev/null 2>&1 ; then
errorExit "cifs mount: failed to create mount directory: '$mountPoint'"
fi
if [[ $(mountpoint "$mountPoint") = *"is a mountpoint"* ]] ; then
errorExit "cifs mount: there is already a filesystem mounted under the mount directory: '$mountPoint'"
fi
if [[ ! -z $(ls -A "$mountPoint" 2>/dev/null) ]] ; then
errorExit "cifs mount: mount directory is not an empty directory: '$mountPoint'"
fi

result=$(mount -t cifs "$networkPath" "$mountPoint" -o "username=$cifsUsername,password=$cifsPassword,$mountOptions" 2>&1)
if [[ $? -ne 0 ]] ; then
errorExit "cifs mount: failed to mount the network path: $result"
fi
echo '{ "status": "Success" }'
exit 0
}

doUnmount() {
if [[ -z ${1:-} ]] ; then
errorExit "cifs unmount: syntax error. usage: cifs unmount <mount dir>"
fi
mountPoint="$1"
if [[ $(mountpoint "$mountPoint") != *"is a mountpoint"* ]] ; then
errorExit "cifs unmount: no filesystem mounted under directory: '$mountPoint'"
fi
result=$(umount "$mountPoint" 2>&1)
if [[ $? -ne 0 ]] ; then
errorExit "cifs unmount: failed to unmount the network path: $result"
fi
echo '{ "status": "Success" }'
exit 0
}

not_supported() {
echo '{ "status": "Not supported" }'
exit 1
}

command=${1:-}
if [[ -n $command ]]; then
shift
fi

case "$command" in
init)
init "$@"
;;
mount)
doMount "$@"
;;
unmount)
doUnmount "$@"
;;
*)
not_supported "$@"
;;
esac

0 comments on commit f0ae7f8

Please sign in to comment.