Warum schlägt meine App fehl, wenn ein Benutzer ohne Rootberechtigung den Anhängepfad für den NFS-Dateispeicher besitzt?

Fehlerbehebung bei Problemen mit dem Einbinden von Nicht-Root- NFS en für die Dateispeicherung.

Klassische Infrastruktur

Nach dem Hinzufügen von NFS-Speicher zu Ihrer Bereitstellung schlägt die Bereitstellung Ihres Containers fehl. Wenn Sie die Protokolle für Ihren Container abrufen, werden möglicherweise Fehler wie die folgenden angezeigt. Der Pod schlägt fehl und bleibt in einer Neuladeschleife stecken.

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

Standardmäßig haben Benutzer ohne Rootberechtigung keinen Schreibzugriff auf den Datenträgermountpfad für NFS-gestützten Speicher. Einige allgemeine App-Images, wie z. B. Jenkins und Nexus3, geben einen Benutzer ohne Rootberechtigung an, der Eigner des Mountpfads in der Dockerfile ist.

Wenn Sie einen Container aus dieser Dockerfile erstellen, schlägt die Erstellung des Containers aufgrund unzureichender Berechtigungen für den Benutzer ohne Rootberechtigung auf dem Mountpfad fehl. Um Schreibrechte zu gewähren, können Sie die Dockerfile-Datei so anpassen, dass der Nicht-Root-Benutzer vorübergehend zur Root-Benutzergruppe hinzugefügt wird, bevor die Berechtigungen für den Einhängepfad geändert werden, oder Sie können einen init Container verwenden.

Wenn Sie zur Bereitstellung des Images ein Helm-Diagramm verwenden, bearbeiten Sie die Bereitstellung unter Helm so, dass ein Container init verwendet wird.

Wenn Sie einen Container init in Ihre Bereitstellung einbinden, können Sie einem in Ihrer Dockerfile angegebenen Nicht-Root-Benutzer Schreibrechte für den Volume-Mount-Pfad innerhalb des Containers erteilen.

Der Container init wird gestartet, bevor Ihr App-Container gestartet wird. Der Container init erstellt den Einhängepfad für das Volume innerhalb des Containers, ändert den Einhängepfad so, dass er dem richtigen (nicht-root-)Benutzer gehört, und schließt ihn. Anschließend wird Ihr App-Container mit dem Benutzer ohne Rootberechtigung gestartet, der in den Mountpfad schreiben muss. Da der Benutzer ohne Rootberechtigung bereits Eigner des Pfads ist, ist das Schreiben in den Mountpfad erfolgreich. Wenn Sie keinen Container init verwenden möchten, können Sie die Dockerfile-Datei so anpassen, dass der Zugriff auf den Dateispeicher NFS auch für Nicht-Root-Benutzer möglich ist.

Vorbereitende Schritte:

So verwenden Sie einen Container init, um einem Nicht-Root-Benutzer Schreibrechte für den Mount-Pfad des Volumes zu erteilen:

  1. Öffnen Sie die Dockerfile für Ihre App und rufen Sie die Benutzer-ID (UID) und die Benutzer-ID (GID) von dem Benutzer ab, dem Sie Schreibzugriff auf den Datenträgermountpfad erteilen möchten. Im Beispiel einer Jenkins-Dockerfile lautet die Information:

    • Benutzer-ID: 1000
    • Gruppen-ID:1000

    Beispiel-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. Fügen Sie persistenten Speicher zu Ihrer App hinzu, indem Sie einen PVC (Persistent Volume Claim) erstellen. Dieses Beispiel verwendet die Speicherklasse ibmc-file-bronze. Um die verfügbaren Speicherklassen anzuzeigen, führen Sie folgenden Befehl aus: 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. Erstellen Sie den PVC.

    oc apply -f mypvc.yaml
    
  4. Fügen Sie in Ihrer Bereitstellungsdatei .yaml den Container init hinzu. Beziehen Sie die UID und GID ein, die Sie zuvor abgerufen haben.

    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
    

    Sehen Sie sich das folgende Beispiel einer Jenkins-Bereitstellung an.

    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. Erstellen Sie den Pod und hängen Sie den PVC an Ihren Pod an.

    oc apply -f my-pod.yaml
    

    Weist der Container init eine Störung auf? Da Red Hat OpenShift Sicherheitskontexteinschränkungen festlegt, werden möglicherweise Fehler wie z. B. chown: /opt/ibm-ucd/server/ext_lib: Operation not permitted angezeigt. Verwenden Sie für Ihre Bereitstellung eine Sicherheitskontexteinschränkung, die chown-Operationen zulässt, und wiederholen Sie den Versuch.

  6. Überprüfen Sie, ob der Datenträger erfolgreich an Ihren Pod angehängt wurde. Notieren Sie den Namen des Pod und den Containers/Mounts-Pfad.

    oc describe pod <my-pod>
    

    Beispielausgabe

    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. Melden Sie sich beim Pod an, indem Sie den Podnamen verwenden, den Sie zuvor notiert haben.

    oc exec -it <my-pod-123456789> /bin/bash
    
  8. Überprüfen Sie die Berechtigungen des Mountpfads Ihres Containers. In dem Beispiel ist der Mountpfad /var/jenkins_home.

    ls -ln /var/jenkins_home
    

    Beispielausgabe

    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
    

    Diese Ausgabe zeigt, dass die GID und UID von Ihrer Dockerfile (in diesem Beispiel 1000 und 1000) Eigner des Mountpfads innerhalb des Containers sind.