# 在 Openshift 上建立應用並驗證掛載 NFS Storage 請在 NFS Server 的主機中執行 Step 1~4 的操作命令,Step 6 請在能夠執行 oc 命令管理 OpenShift 的主機操作 本篇文章的 NFS Server 裝在 RHEL 9.6 上 :::warning :::spoiler {state="open"} 目錄 [TOC] ::: ## Step1: 安裝 NFS Server ``` sudo dnf install -y nfs-utils ``` ## Step2: 設定儲存空間 ``` # 1. 在 VM 新增一顆硬碟專門給 NFS Server 使用 # 2. 檢查新的硬碟名稱 $ lsblk ... NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:16 0 70G 0 disk # 3. 將硬碟名稱設為變數 $ D="/dev/sda" # 4. 使用 parted 建立 GPT 分割區,從 1 MiB 到磁碟尾,並標記為 LVM 用途 $ sudo parted -s ${D} mklabel gpt $ sudo parted -s ${D} unit MiB mkpart primary 1 100% # 5. 將剛建立的分割區初始化為 LVM PV $ sudo pvcreate ${D}1 # 6. 建立名為 vg-nfs 的 VG $ sudo vgcreate vg-nfs ${D}1 # 7. 從 VG 中建立一 LV,利用全部剩餘空間並命名為 lv-nfs $ sudo lvcreate -l +100%FREE -n lv-nfs /dev/vg-nfs # 8. 格式化 LV 為 XFS 檔案系統,以提供穩健且高效的儲存 $ sudo mkfs.xfs /dev/vg-nfs/lv-nfs # 9. 建立 NFS Server 要共享給 Client 的目錄 $ sudo mkdir -p /data/nfs # 10. 修改目錄權限 $ sudo chown -R 65534:65534 /data/nfs # 11. 將目錄設為開機自動掛載 lv 裝置上 $ echo '/dev/vg-nfs/lv-nfs /data/nfs xfs defaults 0 0' | sudo tee -a /etc/fstab # 12. 將目錄掛載到 lv 裝置上 $ sudo mount -a && sudo systemctl daemon-reload # 13. 設定共享目錄 $ echo '/data/nfs 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)' | sudo tee /etc/exports # 14. 設定 NFS Server 使用 4.2 版本,以匯出分享 SELinux context。無適合的版本 client 端掛載時會出現 mount.nfs: Protocol not supported 的訊息。 $ echo 'RPCNFSDARGS="-V 4.2"' | sudo tee /etc/sysconfig/nfs # 15. 查看目前啟動的 nfs 版本,因 server 指定使用 4.2,若出現 -4.2 表示 nfs server 沒有成功啟動。 $ sudo cat /proc/fs/nfsd/versions +3 +4 +4.1 +4.2 ``` ## Step3: 啟動 NFS Server ``` sudo systemctl enable --now nfs-server sudo exportfs -arv ``` --- ## Step4: 設定 NFS Server 使用 fsid ``` # 1. 先手動 resizze vm 硬碟的儲存空間 # 2. 使用 parted 將第 1 區擴展到磁碟剩餘最大空間(自動確認、低互動) $ sudo parted -s -a optimal /dev/sda "resizepart 1 100%" # 3. 設定 LVM 更新 PV 已可使用空間 $ sudo pvresize /dev/sda1 # 4. 檢查目前 PV 與剩餘空間狀態 $ sudo pvs PV VG Fmt Attr PSize PFree /dev/sda1 vg-nfs lvm2 a-- <80.00g 10.00g /dev/sdb2 rhel lvm2 a-- <69.00g 0 # 5. 在 vg-nfs 卷組中創建新的 5G 的 LV lv-share1 $ sudo lvcreate -L 5G -n lv-share1 /dev/vg-nfs # 6. 顯示所有 LV 與大小資訊 $ sudo lvs LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert root rhel -wi-ao---- <69.00g lv-nfs vg-nfs -wi-ao---- <70.00g lv-share1 vg-nfs -wi-ao---- 5.00g # 7. 在新的 LV 上建立 XFS 檔案系統 $ sudo mkfs.xfs /dev/vg-nfs/lv-share1 # 8. 建立掛載點資料夾 $ sudo mkdir -p /data/nfs/share1 # 9. 設定資料夾所有者為 nobody:nobody (UID/GID 65534),通常對應 NFS anonymous user $ sudo chown -R 65534:65534 /data/nfs/share1 # 10. 將新 LV 自動掛載設定加入 fstab $ echo '/dev/vg-nfs/lv-share1 /data/nfs/share1 xfs defaults 0 0' | sudo tee -a /etc/fstab # 11. 掛載全部 fstab 條目,並重載 systemd daemon $ sudo mount -a; sudo systemctl daemon-reload # 12. 驗證 /dev/sda 與 LVM 的結構 $ lsblk /dev/sda NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS sda 8:0 0 80G 0 disk └─sda1 8:1 0 80G 0 part ├─vg--nfs-lv--nfs 253:1 0 70G 0 lvm /data/nfs └─vg--nfs-lv--share1 253:2 0 5G 0 lvm /data/nfs/share1 # 13. 編輯 /etc/exports,NFSv4 範例設定,使用 fsid 唯一識別 $ sudo nano /etc/exports /data/nfs 192.168.1.0/24(rw,fsid=0,sync,no_subtree_check,no_root_squash) /data/nfs/share1 192.168.1.0/24(rw,fsid=1,sync,no_subtree_check,no_root_squash) # 14. 重載 NFS server 配置並立即生效 export $ sudo systemctl reload nfs-server $ sudo exportfs -arv exporting 192.168.1.0/24:/data/nfs/share1 exporting 192.168.1.0/24:/data/nfs ``` --- ## Step5: 在其他機器測試掛載 ``` $ mkdir ~/test $ sudo mount -t nfs -o hard,nfsvers=4.2 192.168.1.90:/ ~/test/ $ ls -l ~/test/ total 0 drwxr-xr-x. 2 nfsnobody nfsnobody 6 Jun 18 07:35 share1 $ sudo umount ~/test ``` ## Step6: 在 OCP 中建應用測試掛載 ### 啟用 NFS Client Provisioner ``` # 1. 使用 NFS Client provisioner 建立動態供應儲存的 template,並指定 NFS Server 和共享目錄位置 $ oc process -f https://raw.githubusercontent.com/openshift-examples/external-storage-nfs-client/main/openshift-template-nfs-client-provisioner.yaml \ -p NFS_SERVER=192.168.1.90 \ -p NFS_PATH=/ | oc apply -f - # 2. 建立本地目錄以儲存 StorageClass 等 YAML 定義 $ mkdir ~/test-mssql # 3. 建立一個新的 StorageClass,使用 pathPattern 區分命名空間與自訂儲存目錄路徑 $ cat <<EOF | tee ~/test-mssql/sc-nfs-share1.yaml | oc apply -f - apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage-share1 provisioner: redhat-emea-ssa-team/hetzner-ocp4 parameters: archiveOnDelete: "false" pathPattern: "share1/\${.PVC.namespace}/\${.PVC.annotations.nfs.io/storage-path}" EOF # 4. 確認新的 StorageClass 已建立 $ oc get sc NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE managed-nfs-storage (default) redhat-emea-ssa-team/hetzner-ocp4 Delete Immediate false 30m managed-nfs-storage-share1 redhat-emea-ssa-team/hetzner-ocp4 Delete Immediate false 3h24m # 5. 建立一個新的專案用來部署 MSSQL Server $ oc new-project mssql # 6. 建立密碼的 secret 給 MSSQL Server 的使用者 $ oc create secret generic mssql --from-literal=SA_PASSWORD="1qaz@WSX" # 7. 建立 PersistentVolumeClaim,並使用剛建立的 NFS StorageClass $ cat <<EOF | tee ~/test-mssql/mssql-pvc.yaml | oc apply -f - kind: PersistentVolumeClaim apiVersion: v1 metadata: name: mssql-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 8Gi storageClassName: managed-nfs-storage-share1 EOF # 8. 驗證 PVC 是否成功綁定 PV $ oc get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE mssql-data Bound pvc-e00bad5b-9a86-4344-94d3-49c7bde4b8e1 8Gi RWO managed-nfs-storage-share1 <unset> 52s # 9. 建立 MSSQL Server 的 Deployment 與 Service,並將資料掛載到 NFS PVC $ cat <<EOF | tee ~/test-mssql/mssql-deployment.yaml | oc apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: mssql-deployment spec: selector: matchLabels: app: mssql replicas: 1 strategy: type: Recreate template: metadata: labels: app: mssql spec: terminationGracePeriodSeconds: 10 containers: - name: mssql image: mcr.microsoft.com/mssql/server:2017-CU8-ubuntu ports: - containerPort: 1433 env: - name: MSSQL_PID value: "Developer" - name: ACCEPT_EULA value: "Y" - name: SA_PASSWORD valueFrom: secretKeyRef: name: mssql key: SA_PASSWORD volumeMounts: - name: mssqldb mountPath: /var/opt/mssql volumes: - name: mssqldb persistentVolumeClaim: claimName: mssql-data --- apiVersion: v1 kind: Service metadata: name: mssql spec: selector: app: mssql ports: - protocol: TCP port: 1433 targetPort: 1433 EOF # 10. 確認 Pod 是否成功建立並執行中 $ oc get pods NAME READY STATUS RESTARTS AGE mssql-deployment-7578c99777-bq7dw 1/1 Running 0 3s # 11. 檢查 NFS Server 上是否已產生 SQL Server 的儲存目錄 $ ls -l /data/nfs/share1/ total 0 drwxrwxrwx. 6 root root 59 Jun 18 15:00 mssql # 12. 檢查 MSSQL Server 儲存目錄內部結構,包含 data/log/secrets/system 等子目錄 $ ls -al /data/nfs/share1/mssql/ total 4 drwxrwxrwx. 6 root root 59 Jun 18 15:00 . drwxr-xr-x. 4 nobody nobody 34 Jun 18 14:58 .. drwxr-xr-x. 2 1000750000 root 156 Jun 18 15:00 data drwxr-xr-x. 2 1000750000 root 4096 Jun 18 15:00 log drwxr-xr-x. 2 1000750000 root 25 Jun 18 15:00 secrets drwxr-xr-x. 5 1000750000 root 74 Jun 18 15:00 .system ``` #### 清理環境 ``` $ oc delete -R -f ~/test-mssql/ $ oc delete project mssql $ sudo rm -rf ~/test-mssql/ # execute the following command on NFS Server $ sudo rm -rf /data/nfs/share1/mssql/ ``` ### 不啟用 StorageClass ``` # 1. 建立本機目錄以儲存 YAML 定義 $ mkdir ~/test-mssql-manually/ # 2. 建立新的專案(namespace)mssql $ oc new-project mssql # 3. 建立 secret,用於 MSSQL 的 SA 密碼 $ oc create secret generic mssql --from-literal=SA_PASSWORD="1qaz@WSX" # 4. 建立 PVC 與手動對應的 PV,使用 NFS 掛載 RWX 模式 $ cat <<EOF | tee ~/test-mssql-manually/mssql-storage.yaml | oc apply -f - kind: PersistentVolumeClaim apiVersion: v1 metadata: name: mssql-data spec: accessModes: - ReadWriteMany resources: requests: storage: 5Gi storageClassName: "" volumeMode: Filesystem --- apiVersion: v1 kind: PersistentVolume metadata: name: mssql-data-pv spec: capacity: storage: 5Gi volumeMode: Filesystem accessModes: - ReadWriteMany mountOptions: - hard - nfsvers=4.2 nfs: path: /share1 server: 192.168.1.90 claimRef: namespace: mssql name: mssql-data EOF # 5. 確認 PVC 已成功綁定 PV $ oc get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE mssql-data Bound mssql-data-pv 5Gi RWX <unset> 10s # 6. 建立 Deployment 與 Service,掛載 NFS 作為資料目錄 $ cat <<EOF | tee ~/test-mssql-manually/mssql-deployment.yaml | oc apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: mssql-deployment spec: selector: matchLabels: app: mssql replicas: 1 strategy: type: Recreate template: metadata: labels: app: mssql spec: terminationGracePeriodSeconds: 10 containers: - name: mssql image: mcr.microsoft.com/mssql/server:2017-CU8-ubuntu ports: - containerPort: 1433 env: - name: MSSQL_PID value: "Developer" - name: ACCEPT_EULA value: "Y" - name: SA_PASSWORD valueFrom: secretKeyRef: name: mssql key: SA_PASSWORD volumeMounts: - name: mssqldb mountPath: /var/opt/mssql volumes: - name: mssqldb persistentVolumeClaim: claimName: mssql-data --- apiVersion: v1 kind: Service metadata: name: mssql spec: selector: app: mssql ports: - protocol: TCP port: 1433 targetPort: 1433 EOF # 7. 檢查 Pod 狀態(第一次會失敗) $ oc get pods NAME READY STATUS RESTARTS AGE mssql-deployment-7578c99777-qcvgv 0/1 Error 2 (30s ago) 32s # 8. 查看失敗原因:目錄建立失敗,權限錯誤 (Errno 13) $ oc logs mssql-deployment-7578c99777-qcvgv /opt/mssql/bin/sqlservr: Error: The secrets directory [var/opt/mssql/secrets/] could not be created. Errno [13] # 9. 確認 app container 是以哪個 UID 執行 $ oc get pods mssql-deployment-7578c99777-qcvgv -o yaml | grep -i runasuser runAsUser: 1000780000 # 10. 修正 NFS 路徑權限,將目錄擁有者改為指定 UID $ sudo chown -R 1000780000 /data/nfs/share1/ # 11. 重啟 Deployment 讓 Pod 重建並應用正確的目錄權限 $ oc rollout restart deployment mssql-deployment # 12. 確認 Pod 成功運行 $ oc get pods NAME READY STATUS RESTARTS AGE mssql-deployment-9fcdd78f8-7d8r8 1/1 Running 0 3s # 13. 驗證 NFS Server 上的目錄內容是否成功建立 $ ls -l /data/nfs/share1/ total 4 drwxr-xr-x. 2 1000780000 root 156 Jun 18 15:20 data drwxr-xr-x. 2 1000780000 root 4096 Jun 18 15:20 log drwxr-xr-x. 2 1000780000 root 25 Jun 18 15:20 secrets ``` #### 清理環境 ``` $ oc delete -R -f ~/test-mssql-manually/ $ oc delete project mssql $ sudo rm -rf ~/test-mssql-manually/ # execute the following command on NFS Server $ sudo rm -rf /data/nfs/share1/*/ $ sudo rm -rf /data/nfs/share1/.system ```