https://hub.docker.com/r/itsthenetwork/nfs-server-alpine

https://sueboy.blogspot.com/2019/11/kubernetes-nodeport.html

ConfigMap

  
apiVersion: v1  
kind: ConfigMap  
metadata:  
  name: nfs  
  namespace: nfs  
data:  
  exports: |  
    {{SHARED_DIRECTORY}} {{PERMITTED}}({{READ_ONLY}},fsid=0,{{SYNC}},no_subtree_check,no_auth_nlm,insecure,no_root_squash)  
  nfsd: |  
    #!/bin/bash  
  
    # Make sure we react to these signals by running stop() when we see them - for clean shutdown  
    # And then exiting  
    trap "stop; exit 0;" SIGTERM SIGINT  
  
    stop()  
    {  
      # We're here because we've seen SIGTERM, likely via a Docker stop command or similar  
      # Let's shutdown cleanly  
      echo "SIGTERM caught, terminating NFS process(es)..."  
      /usr/sbin/exportfs -uav  
      /usr/sbin/rpc.nfsd 0  
      pid1=`pidof rpc.nfsd`  
      pid2=`pidof rpc.mountd`  
      # For IPv6 bug:  
      pid3=`pidof rpcbind`  
      kill -TERM $pid1 $pid2 $pid3 > /dev/null 2>&1  
      echo "Terminated."  
      exit  
    }  
      
    # Check if the SHARED_DIRECTORY variable is empty  
    if [ -z "${SHARED_DIRECTORY}" ]; then  
      echo "The SHARED_DIRECTORY environment variable is unset or null, exiting..."  
      exit 1  
    else  
      echo "Writing SHARED_DIRECTORY to /etc/exports file"  
      /bin/sed -i "s@{{SHARED_DIRECTORY}}@${SHARED_DIRECTORY}@g" /etc/exports  
    fi  
  
    # This is here to demonsrate how multiple directories can be shared. You  
    # would need a block like this for each extra share.  
    # Any additional shares MUST be subdirectories of the root directory specified  
    # by SHARED_DIRECTORY.  
  
    # Check if the SHARED_DIRECTORY_2 variable is empty  
    if [ ! -z "${SHARED_DIRECTORY_2}" ]; then  
      echo "Writing SHARED_DIRECTORY_2 to /etc/exports file"  
      echo "{{SHARED_DIRECTORY_2}} {{PERMITTED}}({{READ_ONLY}},{{SYNC}},no_subtree_check,no_auth_nlm,insecure,no_root_squash)" >> /etc/exports  
      /bin/sed -i "s@{{SHARED_DIRECTORY_2}}@${SHARED_DIRECTORY_2}@g" /etc/exports  
    fi  
  
    # Check if the PERMITTED variable is empty  
    if [ -z "${PERMITTED}" ]; then  
      echo "The PERMITTED environment variable is unset or null, defaulting to '*'."  
      echo "This means any client can mount."  
      /bin/sed -i "s/{{PERMITTED}}/*/g" /etc/exports  
    else  
      echo "The PERMITTED environment variable is set."  
      echo "The permitted clients are: ${PERMITTED}."  
      /bin/sed -i "s/{{PERMITTED}}/"${PERMITTED}"/g" /etc/exports  
    fi  
  
    # Check if the READ_ONLY variable is set (rather than a null string) using parameter expansion  
    if [ -z ${READ_ONLY+y} ]; then  
      echo "The READ_ONLY environment variable is unset or null, defaulting to 'rw'."  
      echo "Clients have read/write access."  
      /bin/sed -i "s/{{READ_ONLY}}/rw/g" /etc/exports  
    else  
      echo "The READ_ONLY environment variable is set."  
      echo "Clients will have read-only access."  
      /bin/sed -i "s/{{READ_ONLY}}/ro/g" /etc/exports  
    fi  
  
    # Check if the SYNC variable is set (rather than a null string) using parameter expansion  
    if [ -z "${SYNC+y}" ]; then  
      echo "The SYNC environment variable is unset or null, defaulting to 'async' mode".  
      echo "Writes will not be immediately written to disk."  
      /bin/sed -i "s/{{SYNC}}/async/g" /etc/exports  
    else  
      echo "The SYNC environment variable is set, using 'sync' mode".  
      echo "Writes will be immediately written to disk."  
      /bin/sed -i "s/{{SYNC}}/sync/g" /etc/exports  
    fi  
  
    # Partially set 'unofficial Bash Strict Mode' as described here: http://redsymbol.net/articles/unofficial-bash-strict-mode/  
    # We don't set -e because the pidof command returns an exit code of 1 when the specified process is not found  
    # We expect this at times and don't want the script to be terminated when it occurs  
    set -uo pipefail  
    IFS=$'\n\t'  
  
    # This loop runs till until we've started up successfully  
    while true; do  
  
      # Check if NFS is running by recording it's PID (if it's not running $pid will be null):  
      pid=`pidof rpc.mountd`  
  
      # If $pid is null, do this to start or restart NFS:  
      while [ -z "$pid" ]; do  
        echo "Displaying /etc/exports contents:"  
        cat /etc/exports  
        echo ""  
  
        # Normally only required if v3 will be used  
        # But currently enabled to overcome an NFS bug around opening an IPv6 socket  
        echo "Starting rpcbind..."  
        /sbin/rpcbind -w  
        echo "Displaying rpcbind status..."  
        /sbin/rpcinfo  
  
        # Only required if v3 will be used  
        # /usr/sbin/rpc.idmapd  
        # /usr/sbin/rpc.gssd -v  
        # /usr/sbin/rpc.statd  
  
        echo "Starting NFS in the background..."  
        /usr/sbin/rpc.nfsd --debug 8 --no-udp --no-nfs-version 2 --no-nfs-version 3  
        echo "Exporting File System..."  
        if /usr/sbin/exportfs -rv; then  
          /usr/sbin/exportfs  
        else  
          echo "Export validation failed, exiting..."  
          exit 1  
        fi  
        echo "Starting Mountd in the background..."These  
        /usr/sbin/rpc.mountd --debug all --no-udp --no-nfs-version 2 --no-nfs-version 3  
    # --exports-file /etc/exports  
  
        # Check if NFS is now running by recording it's PID (if it's not running $pid will be null):  
        pid=`pidof rpc.mountd`  
  
        # If $pid is null, startup failed; log the fact and sleep for 2s  
        # We'll then automatically loop through and try again  
        if [ -z "$pid" ]; then  
          echo "Startup of NFS failed, sleeping for 2s, then retrying..."  
          sleep 2  
        fi  
  
      done  
  
      # Break this outer loop once we've started up successfully  
      # Otherwise, we'll silently restart and Docker won't know  
      echo "Startup successful."  
      break  
  
    done  
  
    while true; do  
  
      # Check if NFS is STILL running by recording it's PID (if it's not running $pid will be null):  
      pid=`pidof rpc.mountd`  
      # If it is not, lets kill our PID1 process (this script) by breaking out of this while loop:  
      # This ensures Docker observes the failure and handles it as necessary  
      if [ -z "$pid" ]; then  
        echo "NFS has failed, exiting, so Docker can restart the container..."  
        break  
      fi  
  
      # If it is, give the CPU a rest  
      sleep 1  
  
    done  
  
    sleep 1  
    exit 1  
  bashrc: |  
    # General Aliases  
    alias apk='apk --progress'  
    alias ll="ls -ltan"  
  
    alias hosts='cat /etc/hosts'  
    alias ..="cd .."  
    alias ...="cd ../.."  
    alias ....="cd ../../.."  
    alias untar="tar xzvkf"  
    alias mv="mv -nv"  
    alias cp="cp -i"  
    alias ip4="ip -4 addr"  
    alias ip6="ip -6 addr"  
  
    COL_YEL="\[\e[1;33m\]"  
    COL_GRA="\[\e[0;37m\]"  
    COL_WHI="\[\e[1;37m\]"  
    COL_GRE="\[\e[1;32m\]"  
    COL_RED="\[\e[1;31m\]"  
  
    # Bash Prompt  
    if test "$UID" -eq 0 ; then  
        _COL_USER=$COL_RED  
        _p=" #"  
    else  
        _COL_USER=$COL_GRE  
        _p=">"  
    fi  
    COLORIZED_PROMPT="${_COL_USER}\u${COL_WHI}@${COL_YEL}\h${COL_WHI}:\w${_p} \[\e[m\]"  
    case $TERM in  
        *term | rxvt | screen )  
            PS1="${COLORIZED_PROMPT}\[\e]0;\u@\h:\w\007\]" ;;  
        linux )  
            PS1="${COLORIZED_PROMPT}" ;;  
        * )   
            PS1="\u@\h:\w${_p} " ;;  
    esac  

Deployment

Different is ln /exports/exports /etc/exports
ConfigMAP /etc have read-only problem

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: nfs-service  
  namespace: nfs  
spec:  
  replicas: 1  
  selector:  
    matchLabels:  
      app: nfs-service  
  template:  
    metadata:  
      labels:  
        app: nfs-service  
    spec:  
      #restartPolicy: Always  
      volumes:  
        - name: exports  
          configMap:  
            name: nfs  
            items:  
              - key: exports  
                path: exports  
                mode: 0744  
        - name: nfsd  
          configMap:  
            name: nfs  
            items:  
              - key: nfsd  
                path: nfsd.sh  
                mode: 0744  
        - name: bashrc  
          configMap:  
            name: nfs  
            items:  
              - key: bashrc  
                path: .bashrc  
                mode: 0744  
        - name: nfsshare  
          emptyDir: {}    
      containers:  
        - name: nfs-server-container  
          image: alpine:latest  
          securityContext:  
            privileged: true  
          command:  
            - /bin/sh  
            - -c  
            - |  
              echo nameserver 8.8.8.8 >> /etc/resolv.conf  
              apk add --no-cache --update --verbose nfs-utils bash iproute2   
              rm -rf /var/cache/apk /tmp /sbin/halt /sbin/poweroff /sbin/reboot   
              mkdir -p /var/lib/nfs/rpc_pipefs /var/lib/nfs/v4recovery   
              echo "rpc_pipefs    /var/lib/nfs/rpc_pipefs rpc_pipefs      defaults        0       0" >> /etc/fstab   
              echo "nfsd  /proc/fs/nfsd   nfsd    defaults        0       0" >> /etc/fstab   
              rm /etc/exports   
              cp /exports/exports /etc/exports   
              cp /nfsd/nfsd.sh /usr/bin/nfsd.sh  
              cp /bashrc/.bashrc /root/.bashrc  
              chmod +x /usr/bin/nfsd.sh  
              /usr/bin/nfsd.sh  
          env:  
            - name: SHARED_DIRECTORY  
              value: "/nfsshare"  
          volumeMounts:  
            - name: exports  
              mountPath: /exports  
              #readOnly: true  
            - name: nfsd  
              mountPath: /nfsd  
            - name: bashrc  
              mountPath: /bashrc   
            - name: nfsshare  
              mountPath: /nfsshare 

k8s ConfigMap mountPath need becarful. like this example exports and nfsd are /etc & /usr/bin. This two directory already have file inside. When you director mountPath: /etc or mountPath: /usr/bin . k8s replace this two path to other direcotry. ls -al /etc or /usr/bin that only get exports file and nfsd.sh file. So better way is mountPath usually different directory. In shell: Use Copy that copy exprots file and nfsd.sh file to docker image correct directory.

Service

  
kind: Service  
apiVersion: v1  
metadata:  
  name: nfs-service  
  namespace: nfs  
spec:  
  selector:  
    app: nfs-service  
  type: NodePort  
  ports:  
    # Open the ports required by the NFS server  
    # Port 2049 for TCP  
    - name: tcp-2049  
      port: 2049  
      targetPort: 2049  
      protocol: TCP  
      nodePort: 32049  
  
    # Port 111 for UDP  
    - name: udp-111  
      port: 111  
      protocol: UDP  

======nfs v4 client mount
debain
https://blog.gtwang.org/linux/nfsv4/

Centos
https://computingforgeeks.com/configure-nfsv3-and-nfsv4-on-centos-7/

  
$ sudo yum -y install nfs-utils  
$ sudo systemctl start rpcbind   
$ sudo systemctl enable rpcbind  
  
mount -v -o port=32049 -o vers=4 -t nfs 192.168.99.118:/ /tmp/tnfs/  
nano /tmp/tnfs/tt  # save some text. Then check nfs pod /nfsshare have tt and tt context