diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f66bbe54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +nfs-client-provisioner diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..f2725559 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# v2.0.1 +- Add support for ARM (Raspberry PI). Image at `quay.io/external_storage/nfs-client-provisioner-arm`. (https://github.com/kubernetes-incubator/external-storage/pull/275) + +# v2.0.0 +- Fix issue 149 - nfs-client-provisioner create folder with 755, not 777 (https://github.com/kubernetes-incubator/external-storage/pull/150) + +# v1 +- Initial release \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..453a0331 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +# Copyright 2016 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifeq ($(REGISTRY),) + REGISTRY = quay.io/external_storage/ +endif +ifeq ($(VERSION),) + VERSION = latest +endif +IMAGE = $(REGISTRY)nfs-client-provisioner:$(VERSION) +IMAGE_ARM = $(REGISTRY)nfs-client-provisioner-arm:$(VERSION) +MUTABLE_IMAGE = $(REGISTRY)nfs-client-provisioner:latest +MUTABLE_IMAGE_ARM = $(REGISTRY)nfs-client-provisioner-arm:latest + +all: build image build_arm image_arm + +container: build image build_arm image_arm + +build: + CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o docker/x86_64/nfs-client-provisioner ./cmd/nfs-client-provisioner + +build_arm: + CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -a -ldflags '-extldflags "-static"' -o docker/arm/nfs-client-provisioner ./cmd/nfs-client-provisioner + +image: + docker build -t $(MUTABLE_IMAGE) docker/x86_64 + docker tag $(MUTABLE_IMAGE) $(IMAGE) + +image_arm: + docker run --rm --privileged multiarch/qemu-user-static:register --reset + docker build -t $(MUTABLE_IMAGE_ARM) docker/arm + docker tag $(MUTABLE_IMAGE_ARM) $(IMAGE_ARM) + +push: + docker push $(IMAGE) + docker push $(MUTABLE_IMAGE) + docker push $(IMAGE_ARM) + docker push $(MUTABLE_IMAGE_ARM) diff --git a/OWNERS b/OWNERS index 96a4ec4a..3c445941 100644 --- a/OWNERS +++ b/OWNERS @@ -4,3 +4,4 @@ approvers: - wongma7 - jsafrane - kmova + - jackielii diff --git a/cmd/nfs-client-provisioner/provisioner.go b/cmd/nfs-client-provisioner/provisioner.go new file mode 100644 index 00000000..b6f7c816 --- /dev/null +++ b/cmd/nfs-client-provisioner/provisioner.go @@ -0,0 +1,192 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "k8s.io/kubernetes/pkg/apis/core/v1/helper" + + "github.com/golang/glog" + "github.com/kubernetes-sigs/sig-storage-lib-external-provisioner/controller" + "k8s.io/api/core/v1" + storage "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +const ( + provisionerNameKey = "PROVISIONER_NAME" +) + +type nfsProvisioner struct { + client kubernetes.Interface + server string + path string +} + +const ( + mountPath = "/persistentvolumes" +) + +var _ controller.Provisioner = &nfsProvisioner{} + +func (p *nfsProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) { + if options.PVC.Spec.Selector != nil { + return nil, fmt.Errorf("claim Selector is not supported") + } + glog.V(4).Infof("nfs provisioner: VolumeOptions %v", options) + + pvcNamespace := options.PVC.Namespace + pvcName := options.PVC.Name + + pvName := strings.Join([]string{pvcNamespace, pvcName, options.PVName}, "-") + + fullPath := filepath.Join(mountPath, pvName) + glog.V(4).Infof("creating path %s", fullPath) + if err := os.MkdirAll(fullPath, 0777); err != nil { + return nil, errors.New("unable to create directory to provision new pv: " + err.Error()) + } + os.Chmod(fullPath, 0777) + + path := filepath.Join(p.path, pvName) + + pv := &v1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: options.PVName, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy, + AccessModes: options.PVC.Spec.AccessModes, + MountOptions: options.MountOptions, + Capacity: v1.ResourceList{ + v1.ResourceName(v1.ResourceStorage): options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)], + }, + PersistentVolumeSource: v1.PersistentVolumeSource{ + NFS: &v1.NFSVolumeSource{ + Server: p.server, + Path: path, + ReadOnly: false, + }, + }, + }, + } + return pv, nil +} + +func (p *nfsProvisioner) Delete(volume *v1.PersistentVolume) error { + path := volume.Spec.PersistentVolumeSource.NFS.Path + pvName := filepath.Base(path) + oldPath := filepath.Join(mountPath, pvName) + if _, err := os.Stat(oldPath); os.IsNotExist(err) { + glog.Warningf("path %s does not exist, deletion skipped", oldPath) + return nil + } + // Get the storage class for this volume. + storageClass, err := p.getClassForVolume(volume) + if err != nil { + return err + } + // Determine if the "archiveOnDelete" parameter exists. + // If it exists and has a false value, delete the directory. + // Otherwise, archive it. + archiveOnDelete, exists := storageClass.Parameters["archiveOnDelete"] + if exists { + archiveBool, err := strconv.ParseBool(archiveOnDelete) + if err != nil { + return err + } + if !archiveBool { + return os.RemoveAll(oldPath) + } + } + + archivePath := filepath.Join(mountPath, "archived-"+pvName) + glog.V(4).Infof("archiving path %s to %s", oldPath, archivePath) + return os.Rename(oldPath, archivePath) + +} + +// getClassForVolume returns StorageClass +func (p *nfsProvisioner) getClassForVolume(pv *v1.PersistentVolume) (*storage.StorageClass, error) { + if p.client == nil { + return nil, fmt.Errorf("Cannot get kube client") + } + className := helper.GetPersistentVolumeClass(pv) + if className == "" { + return nil, fmt.Errorf("Volume has no storage class") + } + class, err := p.client.StorageV1().StorageClasses().Get(className, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return class, nil +} + +func main() { + flag.Parse() + flag.Set("logtostderr", "true") + + server := os.Getenv("NFS_SERVER") + if server == "" { + glog.Fatal("NFS_SERVER not set") + } + path := os.Getenv("NFS_PATH") + if path == "" { + glog.Fatal("NFS_PATH not set") + } + provisionerName := os.Getenv(provisionerNameKey) + if provisionerName == "" { + glog.Fatalf("environment variable %s is not set! Please set it.", provisionerNameKey) + } + + // Create an InClusterConfig and use it to create a client for the controller + // to use to communicate with Kubernetes + config, err := rest.InClusterConfig() + if err != nil { + glog.Fatalf("Failed to create config: %v", err) + } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + glog.Fatalf("Failed to create client: %v", err) + } + + // The controller needs to know what the server version is because out-of-tree + // provisioners aren't officially supported until 1.5 + serverVersion, err := clientset.Discovery().ServerVersion() + if err != nil { + glog.Fatalf("Error getting server version: %v", err) + } + + clientNFSProvisioner := &nfsProvisioner{ + client: clientset, + server: server, + path: path, + } + // Start the provision controller which will dynamically provision efs NFS + // PVs + pc := controller.NewProvisionController(clientset, provisionerName, clientNFSProvisioner, serverVersion.GitVersion) + pc.Run(wait.NeverStop) +} diff --git a/deploy/class.yaml b/deploy/class.yaml new file mode 100644 index 00000000..4d3b4805 --- /dev/null +++ b/deploy/class.yaml @@ -0,0 +1,7 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: managed-nfs-storage +provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME' +parameters: + archiveOnDelete: "false" diff --git a/deploy/deployment-arm.yaml b/deploy/deployment-arm.yaml new file mode 100644 index 00000000..6ee30150 --- /dev/null +++ b/deploy/deployment-arm.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nfs-client-provisioner + labels: + app: nfs-client-provisioner + # replace with namespace where provisioner is deployed + namespace: default +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: nfs-client-provisioner + template: + metadata: + labels: + app: nfs-client-provisioner + spec: + serviceAccountName: nfs-client-provisioner + containers: + - name: nfs-client-provisioner + image: quay.io/external_storage/nfs-client-provisioner-arm:latest + volumeMounts: + - name: nfs-client-root + mountPath: /persistentvolumes + env: + - name: PROVISIONER_NAME + value: fuseim.pri/ifs + - name: NFS_SERVER + value: 10.10.10.60 + - name: NFS_PATH + value: /ifs/kubernetes + volumes: + - name: nfs-client-root + nfs: + server: 10.10.10.60 + path: /ifs/kubernetes diff --git a/deploy/deployment.yaml b/deploy/deployment.yaml new file mode 100644 index 00000000..0ec9c4ed --- /dev/null +++ b/deploy/deployment.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nfs-client-provisioner + labels: + app: nfs-client-provisioner + # replace with namespace where provisioner is deployed + namespace: default +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: nfs-client-provisioner + template: + metadata: + labels: + app: nfs-client-provisioner + spec: + serviceAccountName: nfs-client-provisioner + containers: + - name: nfs-client-provisioner + image: quay.io/external_storage/nfs-client-provisioner:latest + volumeMounts: + - name: nfs-client-root + mountPath: /persistentvolumes + env: + - name: PROVISIONER_NAME + value: fuseim.pri/ifs + - name: NFS_SERVER + value: 10.10.10.60 + - name: NFS_PATH + value: /ifs/kubernetes + volumes: + - name: nfs-client-root + nfs: + server: 10.10.10.60 + path: /ifs/kubernetes diff --git a/deploy/objects/README.md b/deploy/objects/README.md new file mode 100644 index 00000000..2003d611 --- /dev/null +++ b/deploy/objects/README.md @@ -0,0 +1 @@ +The objects in this directory are the same as in the parent except split up into one file per object for certain users' convenience. diff --git a/deploy/objects/class.yaml b/deploy/objects/class.yaml new file mode 100644 index 00000000..4d3b4805 --- /dev/null +++ b/deploy/objects/class.yaml @@ -0,0 +1,7 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: managed-nfs-storage +provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME' +parameters: + archiveOnDelete: "false" diff --git a/deploy/objects/clusterrole.yaml b/deploy/objects/clusterrole.yaml new file mode 100644 index 00000000..d8564a7a --- /dev/null +++ b/deploy/objects/clusterrole.yaml @@ -0,0 +1,17 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: nfs-client-provisioner-runner +rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "update", "patch"] diff --git a/deploy/objects/clusterrolebinding.yaml b/deploy/objects/clusterrolebinding.yaml new file mode 100644 index 00000000..0e949a27 --- /dev/null +++ b/deploy/objects/clusterrolebinding.yaml @@ -0,0 +1,12 @@ +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: run-nfs-client-provisioner +subjects: + - kind: ServiceAccount + name: nfs-client-provisioner + namespace: default +roleRef: + kind: ClusterRole + name: nfs-client-provisioner-runner + apiGroup: rbac.authorization.k8s.io diff --git a/deploy/objects/deployment-arm.yaml b/deploy/objects/deployment-arm.yaml new file mode 100644 index 00000000..9cced7e5 --- /dev/null +++ b/deploy/objects/deployment-arm.yaml @@ -0,0 +1,32 @@ +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: nfs-client-provisioner +spec: + replicas: 1 + strategy: + type: Recreate + template: + metadata: + labels: + app: nfs-client-provisioner + spec: + serviceAccountName: nfs-client-provisioner + containers: + - name: nfs-client-provisioner + image: quay.io/external_storage/nfs-client-provisioner-arm:latest + volumeMounts: + - name: nfs-client-root + mountPath: /persistentvolumes + env: + - name: PROVISIONER_NAME + value: fuseim.pri/ifs + - name: NFS_SERVER + value: 10.10.10.60 + - name: NFS_PATH + value: /ifs/kubernetes + volumes: + - name: nfs-client-root + nfs: + server: 10.10.10.60 + path: /ifs/kubernetes diff --git a/deploy/objects/deployment.yaml b/deploy/objects/deployment.yaml new file mode 100644 index 00000000..1b793676 --- /dev/null +++ b/deploy/objects/deployment.yaml @@ -0,0 +1,32 @@ +kind: Deployment +apiVersion: extensions/v1beta1 +metadata: + name: nfs-client-provisioner +spec: + replicas: 1 + strategy: + type: Recreate + template: + metadata: + labels: + app: nfs-client-provisioner + spec: + serviceAccountName: nfs-client-provisioner + containers: + - name: nfs-client-provisioner + image: quay.io/external_storage/nfs-client-provisioner:latest + volumeMounts: + - name: nfs-client-root + mountPath: /persistentvolumes + env: + - name: PROVISIONER_NAME + value: fuseim.pri/ifs + - name: NFS_SERVER + value: 10.10.10.60 + - name: NFS_PATH + value: /ifs/kubernetes + volumes: + - name: nfs-client-root + nfs: + server: 10.10.10.60 + path: /ifs/kubernetes diff --git a/deploy/objects/role.yaml b/deploy/objects/role.yaml new file mode 100644 index 00000000..28721e82 --- /dev/null +++ b/deploy/objects/role.yaml @@ -0,0 +1,8 @@ +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: leader-locking-nfs-client-provisioner +rules: + - apiGroups: [""] + resources: ["endpoints"] + verbs: ["get", "list", "watch", "create", "update", "patch"] diff --git a/deploy/objects/rolebinding.yaml b/deploy/objects/rolebinding.yaml new file mode 100644 index 00000000..b5faf2d8 --- /dev/null +++ b/deploy/objects/rolebinding.yaml @@ -0,0 +1,13 @@ +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: leader-locking-nfs-client-provisioner +subjects: + - kind: ServiceAccount + name: nfs-client-provisioner + # replace with namespace where provisioner is deployed + namespace: default +roleRef: + kind: Role + name: leader-locking-nfs-client-provisioner + apiGroup: rbac.authorization.k8s.io diff --git a/deploy/objects/serviceaccount.yaml b/deploy/objects/serviceaccount.yaml new file mode 100644 index 00000000..edead9ad --- /dev/null +++ b/deploy/objects/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nfs-client-provisioner diff --git a/deploy/rbac.yaml b/deploy/rbac.yaml new file mode 100644 index 00000000..85a76d02 --- /dev/null +++ b/deploy/rbac.yaml @@ -0,0 +1,65 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: nfs-client-provisioner + # replace with namespace where provisioner is deployed + namespace: default +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: nfs-client-provisioner-runner +rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "update", "patch"] +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: run-nfs-client-provisioner +subjects: + - kind: ServiceAccount + name: nfs-client-provisioner + # replace with namespace where provisioner is deployed + namespace: default +roleRef: + kind: ClusterRole + name: nfs-client-provisioner-runner + apiGroup: rbac.authorization.k8s.io +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: leader-locking-nfs-client-provisioner + # replace with namespace where provisioner is deployed + namespace: default +rules: + - apiGroups: [""] + resources: ["endpoints"] + verbs: ["get", "list", "watch", "create", "update", "patch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: leader-locking-nfs-client-provisioner + # replace with namespace where provisioner is deployed + namespace: default +subjects: + - kind: ServiceAccount + name: nfs-client-provisioner + # replace with namespace where provisioner is deployed + namespace: default +roleRef: + kind: Role + name: leader-locking-nfs-client-provisioner + apiGroup: rbac.authorization.k8s.io diff --git a/deploy/test-claim.yaml b/deploy/test-claim.yaml new file mode 100644 index 00000000..9f7038bd --- /dev/null +++ b/deploy/test-claim.yaml @@ -0,0 +1,12 @@ +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: test-claim + annotations: + volume.beta.kubernetes.io/storage-class: "managed-nfs-storage" +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Mi diff --git a/deploy/test-pod.yaml b/deploy/test-pod.yaml new file mode 100644 index 00000000..e5e7b7fe --- /dev/null +++ b/deploy/test-pod.yaml @@ -0,0 +1,21 @@ +kind: Pod +apiVersion: v1 +metadata: + name: test-pod +spec: + containers: + - name: test-pod + image: gcr.io/google_containers/busybox:1.24 + command: + - "/bin/sh" + args: + - "-c" + - "touch /mnt/SUCCESS && exit 0 || exit 1" + volumeMounts: + - name: nfs-pvc + mountPath: "/mnt" + restartPolicy: "Never" + volumes: + - name: nfs-pvc + persistentVolumeClaim: + claimName: test-claim diff --git a/docker/arm/Dockerfile b/docker/arm/Dockerfile new file mode 100644 index 00000000..ba60cc59 --- /dev/null +++ b/docker/arm/Dockerfile @@ -0,0 +1,18 @@ +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM hypriot/rpi-alpine:3.6 +RUN apk update --no-cache && apk add ca-certificates +COPY nfs-client-provisioner /nfs-client-provisioner +ENTRYPOINT ["/nfs-client-provisioner"] diff --git a/docker/x86_64/Dockerfile b/docker/x86_64/Dockerfile new file mode 100644 index 00000000..0c5fd100 --- /dev/null +++ b/docker/x86_64/Dockerfile @@ -0,0 +1,18 @@ +# Copyright 2017 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM alpine:3.6 +RUN apk update --no-cache && apk add ca-certificates +COPY nfs-client-provisioner /nfs-client-provisioner +ENTRYPOINT ["/nfs-client-provisioner"]