¿Por qué falla mi app cuando un usuario no root es propietario de la vía de acceso de montaje de almacenamiento de archivos NFS?

Solucionar problemas de montaje de NFS sin privilegios de root para el almacenamiento de archivos.

Infraestructura clásica

Después de añadir almacenamiento NFS a su despliegue, el despliegue de su contenedor falla. Al recuperar los registros del contenedor, podría ver errores como los siguientes. El pod falla y queda atascado en un ciclo de recarga.

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

De forma predeterminada, los usuarios no root no tienen permiso de escritura en la vía de acceso de montaje de volumen para el almacenamiento respaldado por NFS. Algunas imágenes comunes de app, como por ejemplo Jenkins y Nexus3, especifican un usuario no root que posee la vía de acceso de montaje en Dockerfile.

Cuando se crea un contenedor desde este Dockerfile, la creación del contenedor falla debido a permisos insuficientes del usuario no root en la vía de acceso de montaje. Para conceder permisos de escritura, puede modificar el archivo Dockerfile para añadir temporalmente al usuario no root al grupo de usuarios root antes de que cambie los permisos de la ruta de montaje, o bien utilizar un contenedor init.

Si utilizas un gráfico de Helm para implementar la imagen, edita la implementación de Helm para utilizar un contenedor init.

Al incluir un en init contenedor tu implementación, puedes otorgar a un usuario que no sea root, especificado en tu Dockerfile, permisos de escritura para la ruta de montaje del volumen dentro del contenedor.

El contenedor init se inicia antes de que se inicie el contenedor de tu aplicación. El contenedor init crea la ruta de montaje del volumen dentro del propio contenedor, cambia la ruta de montaje para que sea propiedad del usuario correcto (que no sea root) y se cierra. A continuación, se inicia el contenedor de la app con el usuario no root que debe escribir en la vía de acceso de montaje. Dado que la vía de acceso ya es propiedad del usuario no root, la escritura en la vía de acceso de montaje se realiza correctamente. Si no quieres utilizar un init contenedor, puedes modificar el archivo Dockerfile para añadir acceso de usuario no root al almacenamiento de archivos NFS.

Antes de empezar:

Para utilizar un contenedor init con el fin de conceder a un usuario que no sea root permisos de escritura en la ruta de montaje del volumen:

  1. Abra el Dockerfile para la app y obtenga el ID de usuario (UID) y el ID de grupo (GID) del usuario al que desea dar permiso de escritura en la vía de acceso de montaje del volumen. En el ejemplo de un Dockerfile de Jenkins, la información es la siguiente:

    • UID: 1000
    • GID: 1000

    Dockerfile de ejemplo

    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. Añada almacenamiento persistente a la app creando una reclamación de volumen persistente (PVC). En este ejemplo se utiliza la clase de almacenamiento ibmc-file-bronze. Para consultar las clases de almacenamiento disponibles, ejecute 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. Cree la PVC.

    oc apply -f mypvc.yaml
    
  4. En tu archivo .yaml de implementación, añade el init contenedor. Incluya el UID y el GID que ha recuperado anteriormente.

    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
    

    Revise el siguiente ejemplo de despliegue 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. Cree el pod y monte la PVC en el pod.

    oc apply -f my-pod.yaml
    

    ¿Se está fallando el contenedor init ? Debido a que Red Hat OpenShift establece restricciones de contexto de seguridad restringidas, es posible que vea un error como chown: /opt/ibm-ucd/server/ext_lib: Operation not permitted. Para el despliegue, utilice una SCC que permita operaciones chown e inténtelo de nuevo.

  6. Verifique que el volumen se ha montado correctamente en el pod. Anote el nombre de pod y la vía de acceso de Containers/Mounts.

    oc describe pod <my-pod>
    

    Salida de ejemplo

    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. Inicie la sesión en el pod utilizando el nombre de pod que ha anotado anteriormente.

    oc exec -it <my-pod-123456789> /bin/bash
    
  8. Verifique los permisos de la vía de acceso de montaje del contenedor. En el ejemplo, la vía de acceso de montaje es /var/jenkins_home.

    ls -ln /var/jenkins_home
    

    Salida de ejemplo

    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
    

    Esta salida muestra que el GID y el UID del Dockerfile (en este ejemplo, 1000 y 1000) poseen la vía de acceso de montaje dentro del contenedor.