如何在Kubernetes中模拟"--volumes-from"功能

54

我正在寻找一种模式,允许在Kubernetes中运行的同一Pod上运行的两个容器之间共享卷。

我的用例是: 我有一个运行在docker容器内的Ruby on Rails应用程序。 docker镜像包含位于/app/<app-name>/public目录中的静态资源,并且我需要从同一Pod中同时运行的nginx容器中访问这些资源。

在 'vanilla' docker 中,我会使用--volumes-from标志来共享此目录:

docker run --name app -v /app/<app-dir>/public <app-image>
docker run --volumes-from app nginx

阅读此文档后:https://github.com/GoogleCloudPlatform/kubernetes/blob/master/docs/volumes.md 我尝试了以下内容(仅列出相关条目):

spec:
  containers:
    - image: <app-image>
      name: <app-name>
      volumeMounts:
        - mountPath: /app/<app-name>/public
          name: assets
    - image: nginx
      name: nginx
      volumeMounts:
        - mountPath: /var/www/html
          name: assets
          readOnly: true
    volumes:
      - name: assets
        hostPath:
          path: /tmp/assets

但是:

  • 尽管节点上的/tmp/assets存在,但是它是空的
  • 应用程序容器内的/app/<app-name>/public也是空的

为了解决这个问题,我打算在应用程序容器启动时填充共享目录(简单地将/app/<app-name>/public/*复制到共享目录中),但我真的不喜欢这个想法。

问题:如何在Kubernetes中模拟--volumes-from,或者如果没有直接的对应项,如何在同一Pod中运行的一个容器向另一个容器共享文件?

apiVersion: v1beta3

Client Version: version.Info{Major:"0", Minor:"17", GitVersion:"v0.17.0", GitCommit:"82f8bdac06ddfacf493a9ed0fedc85f5ea62ebd5", GitTreeState:"clean"}
Server Version: version.Info{Major:"0", Minor:"17", GitVersion:"v0.17.0", GitCommit:"82f8bdac06ddfacf493a9ed0fedc85f5ea62ebd5", GitTreeState:"clean"}
6个回答

51

[更新-2016-8] 在最新的Kubernetes版本中,您可以使用一个名为init-container的非常好的功能,来替换下面我回答中的postStart部分,从而确保容器顺序。

apiVersion: v1
kind: Pod
metadata:
  name: javaweb-2
spec:
  initContainers:
  - name: war
    image: resouer/sample:v2
    command: ["cp", "/sample.war", "/app"]
    volumeMounts:
    - mountPath: /app
      name: app-volume
  containers:
  - name: tomcat
    image: resouer/mytomcat:7.0
    command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
    volumeMounts:
    - mountPath: /root/apache-tomcat-7.0.42-v2/webapps
      name: app-volume
    ports:
    - containerPort: 8080
      hostPort: 8001
  volumes:
  - name: app-volume
    emptyDir: {}

注意: initContainer仍然是一个beta功能,因此该yaml的工作版本实际上像这样: http://kubernetes.io/docs/user-guide/production-pods/#handling-initialization,请注意pod.beta.kubernetes.io/init-containers部分。

---翻译内容开始---

实际上,你可以使用容器生命周期处理程序来控制你想要与其他容器共享的文件/目录。例如:

---
apiVersion: v1
kind: Pod
metadata:
    name: server
spec:
    restartPolicy: OnFailure
    containers:
    - image: resouer/sample:v2
      name: war
      lifecycle:
        postStart:
          exec:
            command:
              - "cp"
              - "/sample.war"
              - "/app"
      volumeMounts:
      - mountPath: /app
        name: hostv1 
    - name: peer
      image: busybox
      command: ["tail", "-f", "/dev/null"]
      volumeMounts:
      - name: hostv2
        mountPath: /app/sample.war
    volumes:
    - name: hostv1
      hostPath:
          path: /tmp
    - name: hostv2
      hostPath:
          path: /tmp/sample.war
请查看我的 gist 获取更多详细信息: https://gist.github.com/resouer/378bcdaef1d9601ed6aa 当然您也可以使用 emptyDir。这样,war容器可以将其 /sample.war 共享到对等容器中,而不会干扰对等的/app目录。
如果我们可以容忍 /app 被覆盖,那么就会简单得多:
---
apiVersion: v1
kind: Pod
metadata:
  name: javaweb-2
spec:
  restartPolicy: OnFailure
  containers:
  - image: resouer/sample:v2
    name: war
    lifecycle:
      postStart:
        exec:
          command:
            - "cp"
            - "/sample.war"
            - "/app"
    volumeMounts:
    - mountPath: /app
      name: app-volume
  - image: resouer/mytomcat:7.0
    name: tomcat
    command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
    volumeMounts:
    - mountPath: /root/apache-tomcat-7.0.42-v2/webapps
      name: app-volume
    ports:
    - containerPort: 8080
      hostPort: 8001 
  volumes:
  - name: app-volume
    emptyDir: {}

@aronchick的回答很有价值,但是你的回答对我的使用情况最好,所以我接受了它,谢谢。 - cthulhu
我看到这里有一些问题:1. 当两个Pod在同一个节点上运行时会发生什么?2. 在滚动更新中会发生什么?当两个不同版本的Pod在同一个节点上运行时,它们会覆盖彼此的源代码。 - Alex
1
@Alex,我只是举了hostDir的例子,你可以使用emptyDIr,这样就不会有覆盖问题了。请看我发布的gist。 - harryz
感谢提供的配置示例;我现在可以让容器将应用程序包移动到持久卷中。但是,如何防止 Openshift/Kubernetes 认为容器已崩溃因为它完成了复制并退出了呢?看起来我没有完全遵循解决方案。 - Zhao Li
我正在使用Kubernetes 1.4版本,但是initContainers字段无法被识别为正确的字段。我需要激活一些beta/alpha功能才能使其正常工作吗?在官方文档中我找不到相关信息,但我找到了这个链接:http://kubernetes.io/docs/user-guide/production-pods/#handling-initialization - think01
显示剩余3条评论

10
现在的答案是 - 你无法直接在 Kubernetes 中将数据从一个容器移动到另一个容器。以下是来自 Kubernetes 问题讨论中的一些线程: 然而,我可以建议您尝试替代设计方案,以获得更好的效果。
  1. 如果您的资产在容器启动时已被锁定,您可以使用类似于gitRepo卷的东西,在进入emptyDir时将其复制过去,这意味着您不必移动内容,只需直接下载到共享目录即可。
  2. 如果您的资产在构建容器时已被锁定,最好在那个时候使用Docker COPY命令进行复制。
  3. 如果您确实希望坚持您正在使用的方式,那么您需要将内容复制到emptyDir卷中,该卷专门为您寻找的内容而设计(除了不需要复制它)。
NFS[1]卷也可以解决您的问题,但可能过于复杂。
此外,我建议这两个服务存在于不同的pod中,这样您就可以单独对它们进行扩展。如果需要,您可以创建一个服务端点来在它们之间通信。
[1] https://github.com/GoogleCloudPlatform/kubernetes/blob/master/examples/nfs/nfs-web-pod.yaml

2

来自未来的最新消息:

现在有一个适用于Docker卷的FlexVol插件:https://github.com/dims/docker-flexvol

截至目前,FlexVol仍然是一个alpha功能,所以请注意。


1
Kubernetes有自己的卷类型,以下是最常用的卷类型:
  1. emptyDir
  2. secret
  3. gitRepo
  4. hostPath(类似于--volumes-from)
  5. config Maps
  6. 持久存储(由云平台提供的存储磁盘)
您可以在这里找到更多关于Kubernetes卷的信息-https://kubernetes.io/docs/concepts/storage/volumes/ hostpath卷的一个示例:
apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
      # this field is optional
      type: Directory

hostpath会将主机/节点目录挂载到容器目录。Pod内的多个容器可以使用不同或相同的卷。您需要在每个容器中提及它。 hostPath卷与Pod生命周期无关,但它在节点和Pod之间创建了紧密耦合,应避免使用hostPath。


如果您将在给定主机上运行多个Pod实例,则这似乎可能存在问题。当然,您可以这样做,但这是一个重要的限制需要注意。我尚未证明这种担忧是有效的,但我想分享一下,以防其他人正在评估选项。如果我错了,请告诉我。 - Freedom_Ben

0
我刚刚在Kubernetes中发现了shareProcessNamespace,并且觉得这非常适合这个使用案例。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: datasharing
spec:
  selector:
    matchLabels:
      app: datasharing
  template:
    metadata:
      labels:
        app: datasharing
    spec:
      shareProcessNamespace: true
      containers:
        - name: data
          image: ubuntu
          imagePullPolicy: IfNotPresent
          command: ["/bin/bash", "-c"]
          args: ["trap 'echo signal;exit 0' SIGINT; sleep infinity"]
          resources:
            limits:
              memory: "128Mi"
              cpu: "500m"
          lifecycle:
            postStart:
              exec:
                command:
                  - /bin/sh
                  - -c
                  - mkdir -p /DATA && touch /DATA/empty_test_file
        - name: nginx
          image: nginx:stable
          imagePullPolicy: IfNotPresent
          resources:
            limits:
              memory: "128Mi"
              cpu: "500m"
          ports:
            - containerPort: 8080
          env:
            - name: GET_DATA_PROC_DIR
              value: 'find /proc -maxdepth 1 -type d -regex "/proc/[0-9]*" | head -2 | tail -1'
          lifecycle:
            postStart:
              exec:
                command:
                  - /bin/sh
                  - -c
                  - ln -s $(eval $GET_DATA_PROC_DIR)/root/DATA /mnt/DATA

在这个例子中,我们有一个数据容器,只需将一个空文件放入/DATA,另一个容器(在本例中为nginx)通过在/proc中找到第二个PID,创建一个指向该目录的symlink
这样做的原因是数据容器是容器数组中的第一个,并且必须具有shareProcessNamespace中的第二个PID

0
如果您使用的是Docker v17.0.5或更高版本,您可以使用多阶段构建在构建时从一个容器复制文件到另一个容器。这是一个关于高级功能的很好的入门指南https://medium.com/@tonistiigi/advanced-multi-stage-build-patterns-6f741b852fae
我使用它将静态资源从我的后端容器复制到Nginx代理的方式是:
ARG API_BACKEND_CONTAINER="api:backend"
FROM $API_BACKEND_CONTAINER as source

FROM nginx:mainline-alpine

ARG NGINX_ROOT=/usr/share/nginx/html/
COPY --from=source  /var/share/api/static/ ${NGINX_ROOT}

很棒的一件事是,由于API_BACKEND_CONTAINER是一个构建参数,我能够传递最新API构建的标签。

2
这种方法有一个很大的缺点,即无法独立部署容器的新镜像,也无法构建用于重复使用并且其行为可能不同的通用镜像。例如,我构建了一个nginx镜像来为各个rails应用程序提供静态资产服务。我必须为每个rails应用程序构建不同版本的nginx镜像并一起部署它们。尽管存在这个非常不幸的限制,但目前看来这似乎是最好的解决方案(而且非常聪明 :-))。 - Freedom_Ben

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接