Kubernetes:如何设置 VolumeMount 用户组和文件权限

Kubernetes: how to set VolumeMount user group and file permissions

我正在 运行使用 kops 在 AWS 上建立一个 Kubernetes 集群。我已将一个 EBS 卷安装到容器上,它在我的应用程序中是可见的,但它是只读的,因为我的应用程序没有 运行 作为 root。如何以 root 以外的用户身份挂载 PersistentVolumeClaimVolumeMount 似乎没有任何选项来控制挂载路径的用户、组或文件权限。

这是我的部署 yaml 文件:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: notebook-1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: notebook-1
    spec:
      volumes:
      - name: notebook-1
        persistentVolumeClaim:
          claimName: notebook-1
      containers:
      - name: notebook-1
        image: jupyter/base-notebook
        ports:
        - containerPort: 8888
        volumeMounts:
        - mountPath: "/home/jovyan/work"
          name: notebook-1

Pod 安全上下文支持设置 fsGroup,它允许您设置拥有该卷的组 ID,以及谁可以写入它。文档中的示例:

apiVersion: v1
kind: Pod
metadata:
  name: hello-world
spec:
  containers:
  # specification of the pod's containers
  # ...
  securityContext:
    fsGroup: 1234

有关此的更多信息here

对于k8s 1.10+版本,添加了runAsGroup,它与fsGroup类似,但工作方式不同。

可以在此处跟踪实施情况:https://github.com/kubernetes/features/issues/213

我最终得到了一个 initContainer 与主容器相同的 volumeMount 来设置适当的权限,在我的例子中,是自定义 Grafana 图像。

当 pod 中的容器 运行 宁作为 root 以外的用户并且需要对已安装卷的写入权限时,这是必需的。

initContainers:
- name: take-data-dir-ownership
  image: alpine:3
  # Give `grafana` user (id 472) permissions a mounted volume
  # https://github.com/grafana/grafana-docker/blob/master/Dockerfile
  command:
  - chown
  - -R
  - 472:472
  - /var/lib/grafana
  volumeMounts:
  - name: data
    mountPath: /var/lib/grafana

更新: 请注意,在没有 -R(递归)标志的情况下 运行 chown 可能就足够了,因为权限通常会在 pod 重新启动时保持不变。如果卷中有大量文件,这将是可取的,因为处理所有文件需要时间(取决于为 initContainer 设置的 resources 限制)。

这是 Kubernetes Deployments/StatefulSets 面临的挑战之一,当您必须 运行 作为非根用户在容器内进行处理时。但是,当您将卷安装到 pod 时,它总是在 root:root 的许可下安装。

因此,非root用户必须有权访问要读写数据的文件夹。

请按照以下步骤操作。

  1. 创建用户组并在 Dockerfile 中分配组 ID。
  2. 使用用户 ID 创建用户并添加到 Dockerfile 中的组。
  3. 递归更改用户进程想要的文件夹的所有权read/write。
  4. 在 pod spec 上下文的 Deployment/StatefulSet 中添加以下行。

    spec:
      securityContext:
        runAsUser: 1099
        runAsGroup: 1099
        fsGroup: 1099
    

运行AsUser

指定对于 Pod 中的任何容器,所有进程 运行 用户 ID 1099。

运行同组

为 Pod 的任何容器内的所有进程指定主组 ID 1099。

如果省略此字段,容器的主要组 ID 将为 root(0)

当指定 runAsGroup 时,创建的任何文件也将归用户 1099 和组 1099 所有。

fsGroup

指定附加的任何卷的所有者将成为组 ID 1099 的所有者。

在其下创建的任何文件都将具有 nonrootgroup:nonrootgroup.

的权限

在实际容器启动前initcontainer更改文件系统权限运行

这里是 elastic search pod 的例子

initContainers:
      - command:
        - sh
        - -c
        - chown -R 1000:1000 /usr/share/elasticsearch/data
        - sysctl -w vm.max_map_count=262144
        - chgrp 1000 /usr/share/elasticsearch/data
        image: busybox:1.29.2
        imagePullPolicy: IfNotPresent
        name: set-dir-owner
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:                         #Volume mount path
        - mountPath: /usr/share/elasticsearch/data
          name: elasticsearch-data

更改容器中的用户组

spec:
      containers:
      securityContext:
          privileged: true
          runAsUser: 1000

请参考本期:https://github.com/kubernetes/kubernetes/issues/2630

如果是emptydir,可以使用spec中的securityContext

spec:
  securityContext:
    runAsUser: 1000
    fsGroup: 1000
containers: ...

如果卷是 hostpath,则 initContainer 可用于卷中的 chown 路径:

initContainers:
    - name: example-c
      image: busybox:latest
      command: ["sh","-c","mkdir -p /vol-path && chown -R 1000:1000 /vol-path"]
      resources:
        limits:
          cpu: "1"
          memory: 1Gi
      volumeMounts:
        - name: vol-example
          mountPath: /vol-path

经过几次迭代后,我最终使用了

{{- $root := . }}
...
      initContainers:
      - name: volume-mount-hack
        image: busybox
        command: ["sh", "-c", "find /data -user root -exec chown 33:33 {} \;"]
        volumeMounts:
{{- range $key,$val := .Values.persistence.mounts }}
        - name: data
          mountPath: /data/{{ $key }}
          subPath: {{ $root.Values.projectKey }}/{{ $key }}
{{- end }}

与其他解决方案相比,它更加简洁且可配置。此外,它 更快 - find 命令仅更改实际上属于 root 用户的 files/directories 的权限。

当您装载包含大量文件的卷时,这会对您的容器产生重大影响 boot/load 次(几秒甚至几分钟!)。

尝试比较

的执行时间

chown www-data:www-data ./ -R

find /data -user root -exec chown 33:33 {} \;

你可能会感到惊讶!

  • 在 minikube 中,它在 运行 initContainers 之后作为 root 用户工作,通过设置 runAsUser: 0
    initContainers:
      - name: change-ownership-container
        image: busybox
        command: ["/bin/chown","-R","1000:1000", "/home/jovyan/work"]
        securityContext:
          runAsUser: 0
          privileged: true
        volumeMounts:
        - name: notebook-data
          mountPath: /home/jovyan/work 

所以整个 Yaml 文件看起来像这样

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: jupyter
  labels:
    release: jupyter
spec:
  replicas:
  updateStrategy:
    type: RollingUpdate
  serviceName: jupyter-headless
  podManagementPolicy: Parallel
  selector:
    matchLabels:
      release: jupyter
  template:
    metadata:
      labels:
        release: jupyter
      annotations:
    spec:
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      securityContext:
        runAsUser: 1000
        fsGroup: 1000
      containers:
      - name: jupyter
        image: "jupyter/base-notebook:ubuntu-20.04"
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 8888
          protocol: TCP
        - name: blockmanager
          containerPort: 7777
          protocol: TCP
        - name: driver
          containerPort: 2222
          protocol: TCP
        volumeMounts:
        - name: notebook-data
          mountPath: /home/jovyan/work
        resources:
          limits:
            cpu: 200m
            memory: 300Mi
          requests:
            cpu: 100m
            memory: 200Mi
      initContainers:
      - name: change-ownership-container
        image: busybox
        command: ["/bin/chown","-R","1000:1000", "/home/jovyan/work"]
        securityContext:
          runAsUser: 0
          privileged: true
        volumeMounts:
        - name: notebook-data
          mountPath: /home/jovyan/work
      volumes:
      - name: notebook-data
        persistentVolumeClaim:
          claimName: jupyter-pvc  

在我的例子中,我使用 scratch 作为基本图像并将用户设置为 65543。我需要对目录的写入权限。我是通过使用 emptyDir volume,

spec:
  containers:
    ...
    volumeMounts:
      - mountPath: /tmp
        name: tmp
        # readOnly: true
  volumes:
  - name: tmp
    emptyDir: {}

For people using configmap as file inside pod

我正在从 configmap 加载数据作为 pod 容器内的文件,这是我的清单:

#./script-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: script-cm
  labels:
    app: script
data:
  data-script: |
    #!/bin/bash
    set -e
    echo "some script commands"
#./deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name:  script
  namespace: default
  labels:
    app:  script
spec:
  selector:
    matchLabels:
      app: script
  replicas: 1
  template:
    metadata:
      labels:
        app:  script
    spec:              
      restartPolicy: Always
      containers:
      - name:  script-container
        image:  ubuntu:20.04
        resources: {}
        volumeMounts:
        - name: influxdb-provisioning
          mountPath: /docker-entrypoint-initdb.d/data.sh           
          subPath: data.sh     

      volumes:
        - name: script-bind
          configMap:
            name: script-cm
            items:
              - key: data-script
                path: data.sh         
                mode: 0777 

如您所见,我正在按照 k8s docs 将配置映射绑定到 pod,mode: 0777 允许我在该特定文件上提供 execution permissions,您也可以 运行 以下命令使用 kubectl explain 获得更好的想法:

kubectl explain deployment.spec.template.spec.volumes.configMap.items.mode

Make sure to put the right permissions instead of 0777 since it's not recommended especially on sensitive data!