當 NFS 檔案儲存區的掛載路徑由非 root 使用者擁有時,為什麼我的應用程式會失敗?

針對檔案儲存,排除非 root 權限下 NFS 掛載問題。

經典基礎架構

新增 NFS 儲存空間至您的部署之後,您的容器部署失敗。 當您擷取容器的日誌時,可能會看到如下錯誤。 Pod 失敗,並停留在重新載入的循環中。

write-permission
don't have required permission
can't create directory '/bitnami/mariadb/data': Permission denied

預設情況下,非 root 使用者對由 NFS 支援的儲存空間之掛載路徑沒有寫入權限。 部分一般應用程式映像檔(例如 Jenkins 及 Nexus3)會在 Dockerfile 中指定擁有裝載路徑的非 root 使用者。

當您從這個 Dockerfile 建立容器時,會由於非 root 使用者對裝載路徑的權限不足,而造成建立容器失敗。 若要授予寫入權限,您可以修改 Dockerfile,在變更掛載路徑權限之前,暫時將非 root 使用者加入 root 使用者群組;或者使用 init 容器。

若您使用 Helm 圖表來部署該映像檔,請編輯 Helm 部署設定,改為使用 init 容器。

當您在部署中包含一個 init 容器時,可以授予 Dockerfile 中指定的非 root 使用者對容器內卷掛載路徑的寫入權限。

init 容器會在您的應用程式容器啟動之前先啟動。 init 容器會在容器內部建立卷掛載路徑,將掛載路徑的所有權變更為正確的(非 root)使用者,然後關閉。 然後,會使用必須寫入至裝載路徑的非 root 使用者來啟動您的應用程式容器。 因為路徑已由非 root 使用者擁有,所以會成功寫入至裝載路徑。 若您不想使用 init 容器,可以修改 Dockerfile,以讓非 root 使用者能夠存取 NFS 的檔案儲存空間。

開始之前

若要使用 init 儲存器來授與非 root 使用者對磁區裝載路徑的寫入權,請執行下列動作:

  1. 開啟應用程式的 Dockerfile,然後取得您要授與磁區裝載路徑寫入權的使用者的使用者 ID (UID) 及群組 ID (GID)。 在 Jenkins Dockerfile 的範例中,其資訊如下:

    • UID:1000
    • GID:1000

    範例 Dockerfile

    FROM openjdk:8-jdk
    RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
    ARG user=jenkins
    ARG group=jenkins
    ARG uid=1000
    ARG gid=1000
    ARG http_port=8080
    ARG agent_port=50000
    ENV JENKINS_HOME /var/jenkins_home
    ENV JENKINS_SLAVE_AGENT_PORT ${agent_port}
    ...
    
  2. 藉由建立持續性磁區要求 (PVC),將持續性儲存空間新增至應用程式。 這個範例使用 ibmc-file-bronze 儲存空間類別。 若要檢視可用的儲存類別,請執行 oc get sc

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: mypvc
      annotations:
        volume.beta.kubernetes.io/storage-class: "ibmc-file-bronze"
    spec:
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 20Gi
    
  3. 建立 PVC。

    oc apply -f mypvc.yaml
    
  4. 在您的部署檔案 .yaml 中,新增 init 容器。 包括您先前擷取的 UID 及 GID。

    initContainers:
    - name: initcontainer # Or replace the name
      image: alpine:latest
      command: ["/bin/sh", "-c"]
      args:
        - chown <UID>:<GID> /mount; # Replace UID and GID with values from the Dockerfile
      volumeMounts:
      - name: volume # Or you can replace with any name
        mountPath: /mount # Must match the mount path in the args line
    

    檢閱下列範例 Jenkins 部署。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: my-pod
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: jenkins
      template:
        metadata:
          labels:
            app: jenkins
        spec:
          containers:
          - name: jenkins
            image: jenkins
            volumeMounts:
            - mountPath: /var/jenkins_home
              name: volume
          volumes:
          - name: volume
            persistentVolumeClaim:
              claimName: mypvc
          initContainers:
          - name: permissionsfix
            image: alpine:latest
            command: ["/bin/sh", "-c"]
            args:
              - chown 1000:1000 /mount;
            volumeMounts:
            - name: volume
              mountPath: /mount
    
  5. 建立 Pod,並將 PVC 裝載至 Pod。

    oc apply -f my-pod.yaml
    

    init 儲存器是否失敗? 由於 Red Hat OpenShift 設定了受限制的安全上下文限制,您可能會看到如 chown: /opt/ibm-ucd/server/ext_lib: Operation not permitted 之類的錯誤。 針對您的部署,容許 chown 作業的 使用 SCC,然後再試一次。

  6. 驗證磁區已順利裝載至 Pod。 請記下 Pod 名稱及 Containers/Mounts 路徑。

    oc describe pod <my-pod>
    

    輸出範例

    Name:       mypod-123456789
    Namespace:    default
    ...
    Init Containers:
    ...
    Mounts:
        /mount from volume (rw)
        /var/run/secrets/kubernetes.io/serviceaccount from default-token-cp9f0 (ro)
    ...
    Containers:
        jenkins:
        Container ID:
        Image:        jenkins
        Image ID:
        Port:          <none>
        State:        Waiting
            Reason:        PodInitializing
        Ready:        False
        Restart Count:    0
        Environment:    <none>
        Mounts:
            /var/jenkins_home from volume (rw)
            /var/run/secrets/kubernetes.io/serviceaccount from default-token-cp9f0 (ro)
    ...
    Volumes:
        myvol:
        Type:    PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
        ClaimName:    mypvc
        ReadOnly:      false
    
  7. 使用您先前記下的 Pod 名稱來登入 Pod。

    oc exec -it <my-pod-123456789> /bin/bash
    
  8. 驗證容器裝載路徑的許可權。 在此範例中,裝載路徑為 /var/jenkins_home

    ls -ln /var/jenkins_home
    

    輸出範例

    jenkins@mypod-123456789:/$ ls -ln /var/jenkins_home
    total 12
    -rw-r--r-- 1 1000 1000  102 Mar  9 19:58 copy_reference_file.log
    drwxr-xr-x 2 1000 1000 4096 Mar  9 19:58 init.groovy.d
    drwxr-xr-x 9 1000 1000 4096 Mar  9 20:16 war
    

    此輸出顯示 Dockerfile 中的 GID 及 UID(在此範例中是 10001000)擁有容器內的裝載路徑。