# 在 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
```