--- # Deploy nfs-subdir-external-provisioner on OpenShift, backed by either: # (a) an external NFS server (set nfs_provisioner_external_server / nfs_provisioner_external_path) # (b) an in-cluster NFS server pod backed by an LVMS RWO PVC (default) # # Architecture (in-cluster mode): # - NFS server StatefulSet: backs exports with an LVMS RWO PVC # - Service: exposes NFS server at a stable ClusterIP # - nfs-subdir-external-provisioner: creates PVs on-demand under the export path # - StorageClass: "nfs-client" with ReadWriteMany support # # Architecture (external mode, nfs_provisioner_external_server != ""): # - In-cluster NFS server is NOT deployed # - nfs-subdir-external-provisioner points directly at the external NFS share # - StorageClass: "nfs-client" with ReadWriteMany support # # The in-cluster NFS server requires privileged SCC on OpenShift (kernel NFS). # All tasks are idempotent (kubernetes.core.k8s state: present). # ------------------------------------------------------------------ # Step 1: Namespace and RBAC # ------------------------------------------------------------------ - name: Create NFS provisioner namespace kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Namespace metadata: name: "{{ nfs_provisioner_namespace }}" - name: Create NFS server ServiceAccount kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: ServiceAccount metadata: name: nfs-server namespace: "{{ nfs_provisioner_namespace }}" when: nfs_provisioner_external_server | length == 0 - name: Create NFS provisioner ServiceAccount kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: ServiceAccount metadata: name: nfs-provisioner namespace: "{{ nfs_provisioner_namespace }}" - name: Create ClusterRole to use privileged SCC (NFS server) kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: nfs-server-scc rules: - apiGroups: [security.openshift.io] resources: [securitycontextconstraints] verbs: [use] resourceNames: [privileged] when: nfs_provisioner_external_server | length == 0 - name: Bind privileged SCC ClusterRole to NFS server ServiceAccount kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: nfs-server-scc subjects: - kind: ServiceAccount name: nfs-server namespace: "{{ nfs_provisioner_namespace }}" roleRef: kind: ClusterRole name: nfs-server-scc apiGroup: rbac.authorization.k8s.io when: nfs_provisioner_external_server | length == 0 - name: Create ClusterRole to use hostmount-anyuid SCC (NFS provisioner) kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: nfs-provisioner-scc rules: - apiGroups: [security.openshift.io] resources: [securitycontextconstraints] verbs: [use] resourceNames: [hostmount-anyuid] - name: Bind hostmount-anyuid SCC ClusterRole to NFS provisioner ServiceAccount kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: nfs-provisioner-scc subjects: - kind: ServiceAccount name: nfs-provisioner namespace: "{{ nfs_provisioner_namespace }}" roleRef: kind: ClusterRole name: nfs-provisioner-scc apiGroup: rbac.authorization.k8s.io - name: Create ClusterRole for NFS provisioner kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: nfs-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] - name: Bind ClusterRole to NFS provisioner ServiceAccount kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: run-nfs-provisioner subjects: - kind: ServiceAccount name: nfs-provisioner namespace: "{{ nfs_provisioner_namespace }}" roleRef: kind: ClusterRole name: nfs-provisioner-runner apiGroup: rbac.authorization.k8s.io - name: Create Role for leader election kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: leader-locking-nfs-provisioner namespace: "{{ nfs_provisioner_namespace }}" rules: - apiGroups: [""] resources: [endpoints] verbs: [get, list, watch, create, update, patch] - name: Bind leader election Role kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: leader-locking-nfs-provisioner namespace: "{{ nfs_provisioner_namespace }}" subjects: - kind: ServiceAccount name: nfs-provisioner namespace: "{{ nfs_provisioner_namespace }}" roleRef: kind: Role name: leader-locking-nfs-provisioner apiGroup: rbac.authorization.k8s.io # ------------------------------------------------------------------ # Step 2: NFS server backing storage and StatefulSet (in-cluster mode only) # ------------------------------------------------------------------ - name: Create NFS server backing PVC kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-server-data namespace: "{{ nfs_provisioner_namespace }}" spec: accessModes: - ReadWriteOnce resources: requests: storage: "{{ nfs_provisioner_storage_size }}" storageClassName: "{{ nfs_provisioner_storage_class }}" when: nfs_provisioner_external_server | length == 0 - name: Deploy NFS server StatefulSet kubernetes.core.k8s: state: present definition: apiVersion: apps/v1 kind: StatefulSet metadata: name: nfs-server namespace: "{{ nfs_provisioner_namespace }}" spec: replicas: 1 selector: matchLabels: app: nfs-server serviceName: nfs-server template: metadata: labels: app: nfs-server spec: serviceAccountName: nfs-server containers: - name: nfs-server image: "{{ nfs_provisioner_server_image }}" ports: - name: nfs containerPort: 2049 - name: mountd containerPort: 20048 - name: rpcbind containerPort: 111 securityContext: privileged: true volumeMounts: - name: nfs-data mountPath: "{{ nfs_provisioner_export_path }}" volumes: - name: nfs-data persistentVolumeClaim: claimName: nfs-server-data when: nfs_provisioner_external_server | length == 0 - name: Create NFS server Service kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Service metadata: name: nfs-server namespace: "{{ nfs_provisioner_namespace }}" spec: selector: app: nfs-server ports: - name: nfs port: 2049 - name: mountd port: 20048 - name: rpcbind port: 111 when: nfs_provisioner_external_server | length == 0 # ------------------------------------------------------------------ # Step 3: Wait for in-cluster NFS server to be ready (in-cluster mode only) # ------------------------------------------------------------------ - name: Wait for NFS server to be ready kubernetes.core.k8s_info: api_version: apps/v1 kind: StatefulSet namespace: "{{ nfs_provisioner_namespace }}" name: nfs-server register: __nfs_provisioner_server_status until: >- __nfs_provisioner_server_status.resources | length > 0 and (__nfs_provisioner_server_status.resources[0].status.readyReplicas | default(0)) >= 1 retries: "{{ __nfs_provisioner_wait_retries }}" delay: 10 when: nfs_provisioner_external_server | length == 0 # ------------------------------------------------------------------ # Step 4: Resolve NFS server address, then deploy nfs-subdir-external-provisioner # ------------------------------------------------------------------ - name: Set NFS server address (external) ansible.builtin.set_fact: __nfs_provisioner_server_addr: "{{ nfs_provisioner_external_server }}" __nfs_provisioner_server_path: "{{ nfs_provisioner_external_path }}" when: nfs_provisioner_external_server | length > 0 - name: Retrieve in-cluster NFS server ClusterIP kubernetes.core.k8s_info: api_version: v1 kind: Service namespace: "{{ nfs_provisioner_namespace }}" name: nfs-server register: __nfs_provisioner_svc when: nfs_provisioner_external_server | length == 0 - name: Set NFS server address (in-cluster) ansible.builtin.set_fact: __nfs_provisioner_server_addr: "{{ __nfs_provisioner_svc.resources[0].spec.clusterIP }}" __nfs_provisioner_server_path: "{{ nfs_provisioner_export_path }}" when: nfs_provisioner_external_server | length == 0 - name: Deploy nfs-subdir-external-provisioner kubernetes.core.k8s: state: present definition: apiVersion: apps/v1 kind: Deployment metadata: name: nfs-provisioner namespace: "{{ nfs_provisioner_namespace }}" spec: replicas: 1 selector: matchLabels: app: nfs-provisioner strategy: type: Recreate template: metadata: labels: app: nfs-provisioner spec: serviceAccountName: nfs-provisioner containers: - name: nfs-provisioner image: "{{ nfs_provisioner_image }}" env: - name: PROVISIONER_NAME value: "{{ nfs_provisioner_name }}" - name: NFS_SERVER value: "{{ __nfs_provisioner_server_addr }}" - name: NFS_PATH value: "{{ __nfs_provisioner_server_path }}" volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes volumes: - name: nfs-client-root nfs: server: "{{ __nfs_provisioner_server_addr }}" path: "{{ __nfs_provisioner_server_path }}" # ------------------------------------------------------------------ # Step 5: Create StorageClass # ------------------------------------------------------------------ - name: Create NFS StorageClass kubernetes.core.k8s: state: present definition: apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: "{{ nfs_provisioner_storage_class_name }}" provisioner: "{{ nfs_provisioner_name }}" parameters: archiveOnDelete: "false" reclaimPolicy: Delete volumeBindingMode: Immediate # ------------------------------------------------------------------ # Step 6: Wait for provisioner to be ready # ------------------------------------------------------------------ - name: Wait for NFS provisioner deployment to be ready kubernetes.core.k8s_info: api_version: apps/v1 kind: Deployment namespace: "{{ nfs_provisioner_namespace }}" name: nfs-provisioner register: __nfs_provisioner_deploy_status until: >- __nfs_provisioner_deploy_status.resources | length > 0 and (__nfs_provisioner_deploy_status.resources[0].status.readyReplicas | default(0)) >= 1 retries: "{{ __nfs_provisioner_wait_retries }}" delay: 10 - name: Display NFS provisioner summary ansible.builtin.debug: msg: - "NFS provisioner deployment complete!" - " Namespace : {{ nfs_provisioner_namespace }}" - " NFS server : {{ __nfs_provisioner_server_addr }}:{{ __nfs_provisioner_server_path }}" - " Mode : {{ 'external' if nfs_provisioner_external_server | length > 0 else 'in-cluster (LVMS-backed)' }}" - " StorageClass : {{ nfs_provisioner_storage_class_name }} (ReadWriteMany)"