index - nevermosby/ytdl-bypy GitHub Wiki

开发团队

The Whale Pirate团队, 热衷Docker技术及应用的研究与探索。

开发者

作品介绍

作为程序员,常常会通过各种途径观看YouTube视频,了解最新行业资讯,或学习新技术,可是免不了受到些限制,比如

  • 看到一个挺好的YouTube视频,可是没有机会全部看完
  • 手机端无法便捷流畅地观看YouTube视频
  • 等等。。。

我们开发这个项目正是为了解决这些问题。基于hubot,你只要把你想看的YouTube视频网址以聊天的形式发给它,你就可以不用管了。hubot会帮你:

  • 下载指定视频
  • 把下载好的视频上传保存到国内网盘。
  • 视频下载和上传完成后,hubot会推送通知给你,追踪视频下载情况。

这样的过程更便捷、更智能,分分钟便可完成,借助网盘的客户端,任何设备便可随时播放查看。

Demo Video

实现方式

  • 整体设计

    • 在**首都在线上,创建两个Linux VM,一个在美国地区,另一个在国内(我们使用了北京),并且把它们置于首都在线提供的GPN**直连网络里。 twp-gic-datacenter
    • 在美国地区的VM上下载YouTube视频,通过Docker和**GPN**连接北京地区的VM,上传下载好的视频到国内网盘,这样就可以随时随地观看了。 baidu pan list
    • 整个过程,通过hubot在flowdock上,浏览器和手机客户端都可以触发,支持并发请求。 flowdock video track
  • 下载YouTube视频

    选取了youtube-dl作为下载视频的工具,只要传入YouTube视频地址,即可下载到MP4格式的视频文件。

    把这个下载过程Docker化,即写了相应的Dockerfile,bulid出相应的image,包含了youtube-dl,直接可以调用。为了能够正常下载,使用在美国地区的VM,运行Docker Run这个image。

  • 上传YouTube视频到国内网盘

    有了视频源文件,想要随时随地观看,比较好的方法就是保存在网盘里,最好是国内的,这样能保证下载速度。这次选择了百度网盘(容量大,比较稳定),配合bypy这个python客户端,这样能通过编程方式上传视频到百度盘,使自动化整个上传过程变成可能。

    也把这个上传过程Docker化,写了相应的Dockerfile,build出相应的image,包含了bypy工具,就可以直接使用。为了能够获取更好的上传速度,使用在北京地区的VM,运行Docker Run这个image。

  • 通过Docker Machine/Swarm建立跨host的通信

    Docker在1.9之后,重新实现了network组件,提供了Overlay的网络模式,配合Docker Swarm可以实现跨host的通信,详情参见link

    为了方便管理Docker Swarm,我们使用Docker Machine来创建Docker Swarm,即直接在首都在线提供的VM上,使用Docker Machine创建Docker Host(安装Docker Engine,以及配置它使用TLS验证来运行Docker Engine,这样,可以远程访问这台VM上的Docker Engine)以及Docker Swarm。

    将上面两个Docker Container(下载和上传)配置在通过一个Overlay的network下,这样他们就可以在service层互相通信了。

  • 通过Docker+NFS实现跨host数据共享

    我们需要将下载好的视频上传,就需要打通部署在不同host上,负责下载的Docker Container,和负责上传的Docker Container对于视频数据共享访问。我们选取了NFS(Network File System)这个比较成熟的网络共享的solution,并用Docker Data Container进行封装,实现了跨host的Docker Container级别的数据共享。

  • 通过hubot接受请求

    以上为后端的实现,为了方便地触发下载-上传任务,我们选择使用hubot来接受前台请求,用户可以直接在具有hubot的聊天协作平台(我们使用了flowdock)上,以发消息的方式提交请求。我们使用docker+nodejs搭建了一个web server接受请求并触发下载-上传任务。

如何使用

  • 配置Docker Machine和Docker Swarm
    • Docker Swarm Master
    • Docker Swarm Agent
    • Key-Value Store
  • 在负责下载的Docker Host上
    • 创建Docker Data Container
    • 运行Docker NFS Server Container
    • 运行Docker化hubot Container
    • 准备好负责下载视频的Docker Image
  • 在负责上传的Docker Host上
    • 运行Docker Video Track Container来保存视频信息,推送给hubot所在的协作平台
      • nodejs web server
      • mongodb
    • 运行Docker Video Download-Upload handler Container来处理下载和上传的请求
      • nodejs web server
    • 准备好负责上传视频的Docker Image
  • 完成配置后,就可以使用
    • 负责下载的Docker Host twp-docker-ps
    • 负责上传的Docker Host btwp-docker-ps

实现功能

  1. 以一定格式发送消息给hubot

    @<your hubot name>, gm <youtube video url> <your comments>
    
  2. 下载完成后,hubot将会发消息给请求者,告知下载完成,例如

    {
       "DownloadResult":"201-Request has been created",
       "Message":""
    }

    Hubot交互实例截图:

    Hubot交互过程

  3. 下载,上传整个流程结束后,hubot推送一条关于下载视频的消息,包括url,标题,文件大小,保存位置等。截图如下:

    下载完成后hubot推送消息截图

重点难点

  • 使用Docker Machine整合**首都在线**GIC的虚拟主机

    • 痛点
      1. GIC没有API可以直接创建虚拟主机,无法自动化
      2. GIC不能作为Cloud Privider被Docker Machine直接作为Driver使用
    • 解决
      1. 手动创建虚拟主机,并记下公网IP地址。

      2. 刚开始想使用Docker Machine的Adding a host without a driver模式,通过以下命令添加已有docker engine的虚拟主机:

        # 传入连接远程Docker engine的TLS Credential
        # 必须指定`-d "none"`,否则不能添加成功
        # 你需要将远程的Docker engine的TLS Credential \
        # 手动copy到运行docker machine命令的机器上,非常麻烦!!!
        docker-machine \
        --tls-ca-cert /root/twp001/ca.pem \
        --tls-client-cert /root/twp001/server.pem \
        --tls-client-key /root/twp001/server-key.pem \
        create -d "none" --url=tcp://<ip-address>:2376 twp001

        后来,使用了Docker Machine的Generic Driver,来直接连接虚拟主机,并安装配置Docker engine:

        # 你只需要提供连接远程机器的ssh credential \
        # 就可以创建出一个完整的docker host,适用于GIC所有Linux主机。
        docker-machine \
        create -d generic \
        --generic-ip-address <ip-of-remote-vm> \
        --generic-ssh-user root \
        --generic-ssh-key ~/key/gic-twp twp001

        这样,就可以直接通过Docker Machine来管理多个Docker Host了。 dm list

  • 使用Docker + Overlay + NFS实现跨主机数据共享

    • 痛点
      1. Docker Data Container无法使用--volumes-from+--net进行跨主机共享
      2. 目前的Docker Volume Plugin(Flocker或者Convoy)都需要配合额外的存储平台(Amazon S3或者Openstack Cinder)进行转存,引入后,复杂度较高。
    • 解决 选取成熟、简单的NFS(Network File System)来共享数据。具体就是:
      • 在负责下载的Docker Host上创建一个Docker Data Container,并映射到host上的一个目录,这是下载完视频的存储路径。
        • 命令:

          docker create \
          -v /home/docker/shared-vol:/shared-data \
          --name shared-data-vol busybox
      • 在负责下载的Docker Host上运行一个Docker化的NFS server,并且:
        • 使用--volumes-from将刚刚创建的Data Container加载进来,这样,数据文件可被NFS Server Container访问,并使用NFS mount那个存储路径

        • 使用--net将NFS Server Container加入到跨主机网络中

        • 命令:

          docker run -d --name nfs-server-david \
          --privileged --net=my-net \
          --volumes-from shared-data-vol \
          david/nfs-server /shared-data
      • 在负责上传的Docker Host上按需运行一个Docker化的NFS client,并且:
        • 使用--net将NFS Client Container加入到跨主机网站中,这样,无须知道NFS Server的具体IP地址,只要使用NFS Server的Container名字就可以通信。

        • 命令:

          docker run --privileged --net=my-net \
          -e NFS_SERVER=nfs-server-david \
          -e NFS_SRC=/shared-data \
          -e NFS_TARGET=/shared-data \
          david/youknownothing-upload
      • 因为两台Docker Host通过**GPN**直连了起来,所以整个传输性能相当出色。
  • 使用Docker+nodejs+dockerode实现自动化下载和上传

    • 痛点
      1. 使用Docker命令行运行下载container,无法得知完成状态。这样,就不能准确地运行上传的container
      2. Hubot如何自动化触发下载-上传任务
    • 解决 使用编程的方式(Docker Remote API)来启动负责上传和下载的containers。由于hubot是nodejs实现的,为了交互方便,选取了dockerode这个nodejs版的Docker SDK。
      • 使用dockerode中docker.run 由于需要在docker run的同时,传入各种参数,如环境变量、网络、volumes等,所以选取docker.run来模拟,它几乎提供了所有Docker remote API提供的功能。

      • 命令:

        localDocker.run('david/youknownothing-upload',
            ["bash", "-c", uploadCmd],
            uploadLogStream, 
            {
               "Env" : [
             "NFS_SERVER=nfs-server",
             "NFS_SRC=/shared-data",
             "NFS_TARGET=/shared-data"
           ],
           "HostConfig" : {
              "Privileged" : true,
              "NetworkMode" : "twp",
                  "Binds" : ["/root/.bypy/bypy.json:/root/.bypy/bypy.json:ro"]
            }
            }, {}, function (err, data, container) {
                   if (data.StatusCode != 0) {
                       console.error("Failed to upload the target video: " + targetUrl);
               } else {
                   // do the real stuff
               }
                });

Next to do

  • 修复Bug
    • 获取video size在某些情况下会出错,可能是curl超时导致
  • 把Nodejs Express转成Nodejs Koajs
  • 改进Nodejs里的异步方法(callback)调用模式
  • 支持更多视频链接的下载
  • 支持更多网盘的上传
⚠️ **GitHub.com Fallback** ⚠️