[Infrastructure as Code (IaC)] Use Pulumi kubernetes (K8S) Helm Chart to deploy nfs-server-provisioner

nfs-server-provisioner

NFS Server Provisioner is an out-of-tree dynamic provisioner for Kubernetes. You can use it to quickly & easily deploy shared storage that works almost anywhere.

This chart will deploy the Kubernetes external-storage projects nfs provisioner. This provisioner includes a built in NFS server, and is not intended for connecting to a pre-existing NFS server. If you have a pre-existing NFS Server, please consider using the NFS Client Provisioner [charts/stable/nfs-client-provisioner at master · helm/charts · GitHub - https://github.com/helm/charts/tree/master/stable/nfs-client-provisioner] instead.

This article is about how to use Pulumi, kubernetes (K8S) provider, Helm Chart and TypeScript SDK to deploy nfs-server-provisioner within Kubernetes (K8S).

Pulumi - Modern Infrastructure as Code - https://www.pulumi.com/ is a Modern Infrastructure as Code (IaC) to create, deploy, and manage infrastructure on any cloud using familiar programming languages and tools.

Pulumi’s Cloud Native SDK makes it easy to target any Kubernetes environment to provision a cluster, configure and deploy applications, and update them as required.

Helm Chart is a component representing a collection of resources described by an arbitrary Helm Chart.

The Chart can be fetched from any source that is accessible to the helm command line. Values in the values.yml file can be overridden using ChartOpts.values (equivalent to --set or having multiple values.yml files). Objects can be transformed arbitrarily by supplying callbacks to ChartOpts.transformations.


As of Nov 13, 2020, charts in this repo will no longer be updated. For more information, see the Helm Charts Deprecation and Archive Notice, and Update.


You can use Azure Helm mirror - http://mirror.azure.cn/kubernetes/charts/ to replace https://kubernetes-charts.storage.googleapis.com

Prerequisites

Usage

Pulumi New

Create the workspace directory.

1
2
3
$ mkdir -p col-example-pulumi-typescript-nfs-server-provisioner

$ cd col-example-pulumi-typescript-nfs-server-provisioner

Pulumi login into local file system.

1
2
3
$ pulumi login file://.
Logged in to cloudolife as cloudolife (file://.)
or visit https://pulumi.com/docs/reference/install/ for manual instructions and release notes.

Pulumi new a project with kubernetes-typescript SDK.

1
$ pulumi new kubernetes-typescript

The above command will create some files within the current directory.

1
2
3
4
5
6
7
8
tree . -L 1
.
├── node_modules/
├── package.json
├── package.json.lock
├── Pulumi.dev.yaml
├── Pulumi.yaml
└── main.ts

Install js-yaml package to load and parse yaml file.

1
$ npm i js-yaml

Pulumi Configuration

Configure Kubernetes

By default, Pulumi will look for a kubeconfig file in the following locations, just like kubectl:

  • The environment variable: $KUBECONFIG,

  • Or in current user’s default kubeconfig directory: ~/.kube/config

If the kubeconfig file is not in either of these locations, Pulumi will not find it, and it will fail to authenticate against the cluster. Set one of these locations to a valid kubeconfig file, if you have not done so already.

Configure Values.yaml

Edit values.yaml and replace content within {{ }}.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# values.yaml


# nfs-server-provisioner-chart/values.yaml at main · raphaelmonrouzeau/nfs-server-provisioner-chart
# https://github.com/raphaelmonrouzeau/nfs-server-provisioner-chart/blob/main/values.yaml

# The following is a recommended configuration example when another storage class exists to provide persistence:
persistence:
enabled: true

## Persistent Volume Storage Class
## If defined, storageClassName: <storageClass>
## If set to "-", storageClassName: "", which disables dynamic provisioning
## If undefined (the default) or set to null, no storageClassName spec is
## set, choosing the default provisioner. (gp2 on AWS, standard on
## GKE, AWS & OpenStack)
##
storageClass: {{ .Values.other.storageClass }}
size: 200Gi

storageClass:
## Set StorageClass as the default StorageClass
## Ignored if storageClass.create is false
defaultClass: true

mountOptions:
- vers=4.1
- nolock,tcp,noresvport

nodeSelector:
kubernetes.io/hostname: {{ .Values.node.hostname }}

See and modify main.ts file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// main.ts

import * as pulumi from "@pulumi/pulumi";

import * as k8s from "@pulumi/kubernetes";
import * as kx from "@pulumi/kubernetesx";

const yaml = require('js-yaml');
const fs = require('fs');

const nameNfsServerProvisioner = "nfs-server-provisioner"

// kubernetes.core/v1.Namespace | Pulumi
// https://www.pulumi.com/docs/reference/pkg/kubernetes/core/v1/namespace/
const namespaceNfsServerProvisioner = new k8s.core.v1.Namespace(nameNfsServerProvisioner, {
metadata: {
name: nameNfsServerProvisioner,
},
})

const values = yaml.safeLoad(fs.readFileSync("./values.yaml", 'utf8'))

// charts/stable/nfs-server-provisioner at master · helm/charts · GitHub
// https://github.com/helm/charts/tree/master/stable/nfs-server-provisioner
const charNameNfsServerProvisioner = "nfs-server-provisioner"

// kubernetes.helm/v3.Chart | Pulumi
// https://www.pulumi.com/docs/reference/pkg/kubernetes/helm/v3/chart/
const charNfsServerProvisioner = new k8s.helm.v3.Chart(charNameNfsServerProvisioner, {
chart: charNameNfsServerProvisioner,
version: "1.3.0",
fetchOpts:{
// nfs-server-provisioner 1.3.0 · raphael/raphael
// https://artifacthub.io/packages/helm/raphael/nfs-server-provisioner

// raphaelmonrouzeau/nfs-server-provisioner-chart: An Helm chart for kubernetes-sigs/nfs-ganesha-server-and-external-provisioner
// https://github.com/raphaelmonrouzeau/nfs-server-provisioner-chart
repo: "https://raphaelmonrouzeau.github.io/charts/repository/",
},
namespace: namespaceNfsServerProvisioner.metadata.name,
values: values,
});

Pulumi Up

Run pulumi up to create the namespace and pods.

1
$ pulumi up

See pods about nfs-server-provisioner.

1
2
3
$ kubectl get pods -n nfs-server-provisioner
NAME READY STATUS RESTARTS AGE
nfs-server-provisioner-64cb485747-vm67z 1/1 Running 1 (4d21h ago) 20d

check the StorageClass status.

1
2
3
$ kubectl get sc
NAME PROVISIONER
nfs (default) cluster.local/nfs-server-provisioner Delete Immediate true 20d

Pulumi Destroy

Destroy all resources created by Pulumi.

1
$ pulumi destroy

Advantage

Without others storage class.

The following is a recommended configration example for running on bare metal with a hostPath volume. Need to do:

  • specify nodeSelector with Your Node Host Name or Label.

  • create a Persistence Volume manually.

The following is a recommended configuration example when another storage class does not exist to provide persistence:

Edit values.yaml and replace content within < and >.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# cat values.yaml

# nfs-server-provisioner-chart/values.yaml at main · raphaelmonrouzeau/nfs-server-provisioner-chart
# https://github.com/raphaelmonrouzeau/nfs-server-provisioner-chart/blob/main/values.yaml

# The following is a recommended configration example for running on bare metal or not exist the others StorageClass.
storageClass:
## Set StorageClass as the default StorageClass
## Ignored if storageClass.create is false
defaultClass: true

mountOptions:
- vers=4.1
- nolock,tcp,noresvport

nodeSelector:
kubernetes.io/hostname: {{ .Values.node.hostname }}

In this configuration, a PersistentVolume must be created for each replica to use. Installing the Helm chart, and then inspecting the PersistentVolumeClaim’s created will provide the necessary names for your PersistentVolume’s to bind to.

Otherwise Pod nfs-server-provisioner-0 will fail with error pod has unbound immediate PersistentVolumeClaims.

An example of the necessary PersistentVolume:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: data-nfs-server-provisioner-0
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /srv/volumes/data-nfs-server-provisioner-0
claimRef:
namespace: nfs-server-provisioner
name: data-nfs-server-provisioner-0

Apply pv.yaml to creat a PersistentVolume.

1
$ kubectl apply -f pv.yaml

Then, check the PersistentVolume status.

1
2
3
$ kubectl get pv data-nfs-server-provisioner-0 -o wide
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE VOLUMEMODE
data-nfs-server-provisioner-0 200Gi RWO Retain Bound nfs-server-provisioner/data-nfs-server-provisioner-0 7d23h Filesystem

FAQs

bad option; for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount. helper program

1
MountVolume.SetUp failed for volume "pvc-c10001c32-f2dd-4f8c-8d19-bd8c2e01b5cf" : mount failed: exit status 32 Mounting command: mount Mounting arguments: -t nfs -o nolock,tcp,noresvport,vers=4.1 bad option; for several filesystems (e.g. nfs, cifs) you might need a /sbin/mount.<type> helper program.

Install nfs packages to solve that issue.

1
2
3
4
5
# Debian, Ubuntu
$ apt-get install nfs-common

# CentOS
$ yum install nfs-utils

Unable to attach or mount volumes: unmounted volumes=[config storage xxxxxx-volume], unattached volumes=[config storage xxxxxx-volume]: timed out waiting for the condition

Modify mountOptions to vers=4.1 and nolock,tcp,noresvport to fix that issue.

1
2
3
4
5
6
7
# values.yaml

nfs:

mountOptions:
- vers=4.1
- nolock,tcp,noresvport

References

[1] [raphaelmonrouzeau/nfs-server-provisioner-chart: An Helm chart for kubernetes-sigs/nfs-ganesha-server-and-external-provisioner - https://github.com/raphaelmonrouzeau/nfs-server-provisioner-chart]

[2] [nfs-server-provisioner 1.3.0 · raphael/raphael - https://artifacthub.io/packages/helm/raphael/nfs-server-provisioner]

[3] kubernetes.helm/v3.Chart | Pulumi - https://www.pulumi.com/docs/reference/pkg/kubernetes/helm/v3/chart/

[4] Kubernetes Getting Started | Pulumi - https://www.pulumi.com/docs/get-started/kubernetes/

[5] Pulumi - Modern Infrastructure as Code - https://www.pulumi.com/

[6] Kubernetes - https://kubernetes.io/

[7] TypeScript: Typed JavaScript at Any Scale. - https://www.typescriptlang.org/

[8] Helm - https://helm.sh/