前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【干货】浅尝TensorFlow on Kubernetes

【干货】浅尝TensorFlow on Kubernetes

作者头像
杨振涛
发布2019-04-19 17:12:08
4300
发布2019-04-19 17:12:08
举报
文章被收录于专栏:vivo互联网技术vivo互联网技术

TensorFlow github 70K+ stars, Kubernetes github 27K+ stars, 两个都是在各自领域的霸主,本文从TensorFlow running in Kubernetes的角度,对两者的整合进行梳理和思考,看看能擦出什么样的火花。

1

Distributed TensorFlow

2016年4月TensorFlow发布了0.8版本宣布支持分布式计算,这个特性,我们称之为Distributed TensorFlow。

这是非常重要的一个特性,因为在人工智能/大数据领域,训练数据的size通常会大到让人瞠目结舌,比如Google Brain实验室今年发表的论文《OUTRAGEOUSLY LARGE NEURAL NETWORKS: THE SPARSELY-GATED MIXTURE-OF-EXPERTS LAYER》中提到,下图中MOE Layer Model可以达到680亿个Parameters的规模,如此复杂的模型,如果只能单机训练,那耗时难于接受。通过Distributed TensorFlow,可以利用众多服务器构建TensorFlow Cluster来极大提高训练效率。

关于Distributed TensorFlow的更多内容,请参考官方内容www.tensorflow.org/deplopy/distributed,这里只给出Distributed TensorFlow结构图:

2

Why TensorFlow on Kubernetes

Distributed TensorFlow虽然提供了分布式能力,可以利用服务器集群加快训练,但是还有许多缺点,比如资源无法隔离、PS进程遗留问题等等,而这些正是Kubernetes所擅长的地方。下图是总结的你需要将TensorFlow运行在Kubernetes上的理由:

对于我们来说,前期最大的用户痛点就是算法团队使用的HDFS Read性能不及预期,经过网上查找资料及我们自己简单的对比测试,发现***GlusterFS***可能是最适合我们的分布式存储了。因此在我们的TensorFlow on Kubernetes项目中使用GlusterFS来存放训练数据,worker将从GlusterFS中读取训练数据进行计算。

关于PS进程遗留问题,TensorFlow社区有很多讨论,但至今没有官方的实现方案,在Kubernetes中,这将比较好解决,在后面的[Thinking](##Thinking)小节中会单独讨论。

3

Integrated Architecture

说明:

• 支持Between-Graph和In-Graph两种replication场景;

• PS Task通过Kubernetes Deployment来部署,Worker Task通过Kubernetes Job来部署,由Kubernetes service和KubeDNS来提供服务发现;

• 每个TensorFlow Cluster都会通过StorageClass来Dynamic Provision PV,事先会先创建好通过Heketi对接Gluster集群的StorageClass;

• GlusterFS集群通过Heketi来暴露rest api与Kubernetes进行交互,关于Heketi的部署,请参考官方文档;

• 每个TensorFlow Cluster会最终创建两个PV,一个用来存放训练数据(挂载到容器内/data,对应TensorFlow --data\_dir配置),一个用来存储训练日志(挂载到容器内/log,对应TensorFlow --log\_path配置);

• 每个用户会对应在Kubernetes中创建一个

namespace;

• 会给每个用户部署一个Jupyter Notebook Deployment和Service,Service通过NodePort暴露到集群外;

• 有一个节点比较特殊,我们称之为User Node,这个节点通过Taint方式,保证会运行Pod,但是会通过kube-proxy来暴露集群内的service,比如上面的Jupyter Notebook service将只允许在这个节点暴露出去;

• User Node节点存放着用户写的python算法,并可以通过http查看和下载这些算法文件,Between-Graph场景下,容器启动后将通过curl下载这些算法文件;

• 会给没用用户创建一个Tensorboard Deployment和Service,Serivce通过NodePort暴露到集群外(同样只能在User Node暴露),Tensorboard Pod会挂着log PV,这样就能得到TensorFlow Graph。

4

Deploy Architecture

整个系统涉及以下核心Components:

• TensorFlow: 1.3.0

• Kubernetes: 1.7.4

• Docker: 1.12.6

• Glusterfs: 3.10.5

• Harbor: 1.1.2

• Contiv netplugin: 0.1-12-23-2016.19-44-42.UTC

• Keepalived: 1.3.5

• Haproxy:1.7.8

• Etcd2: 2.3.7

• Etcd3: 3.2.1

网络方案:contiv netplugin + ovs + vlan.

日志方案:fluentd + Kafka + ES + Kibana.

监控方案:cadvisor + prometheus + Grafana.

CaaS的细节不在这里讨论,其实也是大家非常熟悉的方案了。

5

Demo

大家可以参考Kyle Bai https://github.com/kairen/workshop413,他这里用NFS作为后端存储,需要改成你们自己的存储,大家自己去尝试吧,我这就不一步一步来了,好无聊。

这个Demo,我改成NodePort方式暴露Jupyter Nodebook,登录时输入正确的token即可:

这是一个In-Graph集群,点击master_client.ipynb,可以看到具体的训练算法内容:

点击执行,可以在下面看到输出:

这只是个简单的Demo,实际使用上,自动化生成各个ps, worker, pvc对应的kubernetes yaml,使用域名进行服务发现,不然如果你使用IP的话,可能就需要利用Pod的ProStart Hook来反馈各个Task的IP了,这将比较麻烦。

6

Thinking

Q: PS进程遗留问题,在社区讨论比较多([issue 4173](https://github.com/tensorflow/tensorflow/issues/4713)),结合Kubernetes,我们可以比较简单的来做到回收PS进程的目的。

A: 在DevOps的TaaS模块中,针对每个TensorFlow Cluster都启动一个协程,检查计数器是否达到worker数量(worker是job运行的,down了以后,watch到job successed,则计数器加1),如果等于worker数,则表明训练结束,等待30s后,调用kubernetes apiserver接口将ps deployment/service删除,达到自动回收ps的效果;

Q: worker是无状态的,ps是有状态的,而ps是无法进行checkpoint的,如何进行训练save和restore呢?

A: worker虽然是无状态的,但是tf.train.Saver提供能力在worker上进行checkpoint,大概原理就是逐个从PS task中get Parameters,并进行save持久化。

Q: 怎么让用户指定ps和worker个数等少量参数,自动生成kubernetes yaml?

A: 因为当前我们还没有针对TaaS做前端Portal,所以目前是通过jinja template来自动生成的(可以参考tensorflow/ecosystem/kubernetes),用户只要指定少量参数即可生成ps和worker需要的kubernetes yaml。

比如下面是一个例子,tfcluster_template.yaml.jinja,用户只需关注红色字体的4项即可。

{%- set name = "imagenet" -%}  # 算法名称
{%- set worker_replicas = 3 -%}  # worker数
{%- set ps_replicas = 2 -%}     # ps数
{%- set script = "http://xxx.xx.xx.xxx:80/imagenet/imagenet.py" -%}  # 算法脚本的http下载路径
{%- set image = "tensorflow/tensorflow:1.3.0" -%}
{%- set data_dir = "/data" -%}
{%- set log_dir = "/log" -%}
{%- set port = 2222 -%}
{%- set replicas = {"worker": worker_replicas, "ps": ps_replicas} -%}
{%- macro worker_hosts() -%}
  {%- for i in range(worker_replicas) -%}
    {%- if not loop.first -%},{%- endif -%}
    {{ name }}-worker-{{ i }}:{{ port }}
  {%- endfor -%}
{%- endmacro -%}
{%- macro ps_hosts() -%}
  {%- for i in range(ps_replicas) -%}
    {%- if not loop.first -%},{%- endif -%}
    {{ name }}-ps-{{ i }}:{{ port }}
  {%- endfor -%}
{%- endmacro -%}
{%- for job in ["worker", "ps"] -%}
{%- for i in range(replicas[job]) -%}
kind: Service
apiVersion: v1
metadata:
  name: {{ name }}-{{ job }}-{{ i }}
spec:
  selector:
    name: {{ name }}
    job: {{ job }}
    task: "{{ i }}"
  ports:
  - port: {{ port }}
    targetPort: 2222
{% if job == "worker" %}
---
kind: Job
apiVersion: batch/v1
metadata:
  name: {{ name }}-{{ job }}-{{ i }}
spec:
  replicas: 1
  template:
    metadata:
      labels:
        name: {{ name }}
        job: {{ job }}
        task: "{{ i }}"
    spec:
      containers:
      - name: {{ name }}-{{ job }}-{{ i }}
        image: {{ image }}
        ports:
        - containerPort: 2222
        command: ["/bin/sh", "-c"]
        args:["
            curl {{ script }} -o /opt/{{ name }}.py;
            python /opt/{{ name }}.py \
                   --ps_hosts={{ ps_hosts() }} \
                   --worker_hosts={{ worker_hosts() }} \
                   --job_name={{ job }} \
                   --task_index={{ i }} \
                   --log_path={{ log_dir }} \
                   --data_dir={{ data_dir }} ;"]
        volumeMounts:
        - name: data
          mountPath: {{ data_dir }}
        - name: log
          mountPath: {{ log_dir }}
      restartPolicy: Never
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: {{ name }}-data-pvc
        - name: log
          persistentVolumeClaim:
            claimName: {{ name }}-log-pvc
{% endif %}
{% if job == "ps" %}
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: {{ name }}-{{ job }}-{{ i }}
spec:
  replicas: 1
  template:
    metadata:
      labels:
        name: {{ name }}
        job: {{ job }}
        task: "{{ i }}"
    spec:
      containers:
      - name: {{ name }}-{{ job }}-{{ i }}
        image: {{ image }}
        ports:
        - containerPort: 2222
        command: ["/bin/sh", "-c"]
        args:["
            curl {{ script }} -o /opt/{{ name }}.py;
            python /opt/{{ name }}.py \
                   --ps_hosts={{ ps_hosts() }} \
                   --worker_hosts={{ worker_hosts() }} \
                   --job_name={{ job }} \
                   --task_index={{ i }} \
                   --log_path={{ log_dir }} ;"]
        volumeMounts:
        - name: log
          mountPath: {{ log_dir }}
      restartPolicy: Never
      volumes:
        - name: log
          persistentVolumeClaim:
            claimName: {{ name }}-log-pvc
{% endif %}
---
{% endfor %}
{%- endfor -%}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: {{ name }}-log-pvc
 annotations:
   volume.beta.kubernetes.io/storage-class: glusterfs
spec:
 accessModes:
  - ReadWriteMany
 resources:
   requests:
     storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: {{ name }}-data-pvc
 annotations:
   volume.beta.kubernetes.io/storage-class: glusterfs
spec:
 accessModes:
  - ReadWriteMany
 resources:
   requests:
     storage: 10Gi
---

然后执行 python render_template.py tfcluster_template.yaml.jinja | kubectl apply -f - 完成对应的Between-Graph TensorFlow Cluster的创建和启动。

关于作者

王涛@vivo

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-11-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 vivo互联网技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档