<p>I am in the process of setting up a NFS server on my K8S cluster. I want it to act as a NFS server for external entities i.e. client will be from outside the K8S cluster such as VMs.</p>

<p>The port requirements for the Docker image are :</p>

<pre><code>==================================================================
      SERVER STARTUP COMPLETE
==================================================================
----&gt; list of enabled NFS protocol versions: 4.2, 4.1, 4
----&gt; list of container exports:
----&gt;   /exports *(rw,no_subtree_check)
----&gt; list of container ports that should be exposed:
----&gt;   111 (TCP and UDP)
----&gt;   2049 (TCP and UDP)
----&gt;   32765 (TCP and UDP)
----&gt;   32767 (TCP and UDP)
</code></pre>

<p>So I have created a Debian Stretch docker image. When I run it using <code>docker run</code>, I can successfully expose <code>/exports</code> and mount it from other systems.</p>

<pre><code>docker run -v /data:/exports -v /tmp/exports.txt:/etc/exports:ro \
--cap-add SYS_ADMIN -p 2049:2049 -p 111:111 -p 32765:32765 \
-p 32767:32767 8113b6abeac
</code></pre>

<p>The above command spins up my docker container and when I do </p>

<pre><code>mount.nfs4 &lt;DOKCER_HOST_IP&gt;:/exports /mount/
</code></pre>

<p>from another VM, I can successfully mount the volume.</p>

<p>So everything up until here is <strong>A OK</strong>!</p>

<p>Now the task is to deploy this in K8S.</p>

<p>My stateful-set definition is:</p>

<pre><code>kind: StatefulSet
apiVersion: apps/v1
metadata:
  name: nfs-provisioner
spec:
  selector:
    matchLabels:
      app: nfs-provisioner
  serviceName: "nfs-provisioner"
  replicas: 1
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccount: nfs-provisioner
      terminationGracePeriodSeconds: 10
      imagePullSecrets:
      - name: artifactory
      containers:
        - name: nfs-provisioner
          image: repository.hybris.com:5005/test/nfs/nfs-server:1.2
          ports:
            - name: nfs
              containerPort: 2049
            - name: mountd
              containerPort: 20048
            - name: rpcbind
              containerPort: 111
            - name: rpcbind-udp
              containerPort: 111
              protocol: UDP
            - name: filenet
              containerPort: 32767
            - name: filenet-udp
              containerPort: 32767
              protocol: UDP
            - name: unknown
              containerPort: 32765
            - name: unknown-udp
              containerPort: 32765
              protocol: UDP
          securityContext:
            privileged: true
          env:
            - name: SERVICE_NAME
              value: nfs-provisioner
            - name: NFS_EXPORT_0
              value: '/exports *(rw,no_subtree_check)'
          imagePullPolicy: "IfNotPresent"
          volumeMounts:
            - name: export-volume
              mountPath: /exports
      volumes:
        - name: export-volume
          hostPath:
            path: /var/tmp
</code></pre>

<p>As you can see, I have specified all the ports (both TCP and UDP)</p>

<p>And now to expose this to the outside world and not just inside the cluster, my <code>service.yaml</code> file deceleration is :</p>

<pre><code>kind: Service
apiVersion: v1
metadata:
  name: nfs-provisioner
  labels:
    app: nfs-provisioner
spec:
  type: NodePort
  ports:
    - name: nfs
      port: 2049
    - name: mountd
      port: 20048
    - name: rpcbind
      port: 111
    - name: rpcbind-udp
      port: 111
      protocol: UDP
    - name: filenet
      port: 32767
    - name: filenet-udp
      port: 32767
      protocol: UDP
    - name: unknown
      port: 32765
    - name: unknown-udp
      port: 32765
      protocol: UDP
  selector:
    app: nfs-provisioner
</code></pre>

<p>This results in </p>

<pre><code>kubectl get svc
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                                                                                      AGE
nfs-provisioner   NodePort    10.233.43.135   &lt;none&gt;        2049:30382/TCP,20048:31316/TCP,111:32720/TCP,111:32720/UDP,32767:30173/TCP,32767:30173/UDP,32765:31215/TCP,32765:31215/UDP   32m
</code></pre>

<p>Now I try to mount <code>/exports</code> from another node/VM that is external to the 
K8S cluster.</p>

<p>I've tried </p>

<pre><code>mount.nfs4 &lt;K8S_Node_IP&gt;:/exports /mount/
</code></pre>

<p>and I've tried</p>

<pre><code>mount.nfs4 -o port=&lt;NodePort&gt; &lt;K8S_Node_IP&gt;:/exports /mount/
</code></pre>

<p>Ive tried each NodePort one at a time. But none of them work. I get the error :</p>

<pre><code>mount.nfs4 -o port=31316 &lt;K8S_Node_IP&gt;:/exports /mount/
mount.nfs4: mount to NFS server '&lt;K8S_Node_IP&gt;:/exports' failed: RPC Error: Unable to receive
</code></pre>

<p>I'm unsure as to what might be the issue here. Is it that I need to specify all the nodePorts? If so, how can I do that? </p>

<p>The issue here is that all the NodePorts are different as seen externally as from:</p>

<pre><code>----&gt;   111 (TCP and UDP)
----&gt;   2049 (TCP and UDP)
----&gt;   32765 (TCP and UDP)
----&gt;   32767 (TCP and UDP)
</code></pre>

<p>You can try an L4 load balancer that exposes exactly those ports on a given IP address (internal or external) and forwards them to the nodePorts (which is what <code>type=LoadBalancer</code> does too).</p>

<p>Another option is to hard code the NodePorts in your services to match exactly the ones of the containers:</p>

<pre><code>kind: Service
apiVersion: v1
metadata:
  name: nfs-provisioner
  labels:
    app: nfs-provisioner
spec:
  type: NodePort
  ports:
    - name: nfs
      port: 2049
      nodePort: 2049
    - name: mountd
      port: 20048
      nodePort: 20048
    - name: rpcbind
      port: 111
      nodePort: 111
    - name: rpcbind-udp
      port: 111
      nodePort: 111
      protocol: UDP
    - name: filenet
      port: 32767
      nodePort: 32767
    - name: filenet-udp
      port: 32767
      nodePort: 32767
      protocol: UDP
    - name: unknown
      port: 32765
      nodePort: 32765
    - name: unknown-udp
      port: 32765
      nodePort: 32765
      protocol: UDP
  selector:
    app: nfs-provisioner
</code></pre>

<p>You will have to change the nodePort range <a href="https://kubernetes.io/docs/concepts/services-networking/service/#nodeport" rel="nofollow noreferrer">(<code>--service-node-port-range</code>)</a> on the kubelet though. This is so that you can use <code>2049</code> and <code>111</code>.</p>

<p>You can also change the ports that you NFS server listens on for <code>2049</code> (nfs) and <code>111</code> (portmapper) for example, that way you don't have to change <code>--service-node-port-range</code></p>
