-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f0ae7f8
Showing
2 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |