云端的Hadoop集群

Hadoop是开源的分布式计算框架,它支持著名的MapReduce编程范式,是目前最流行的大数据处理框架。除了提供分布式存储服务HDFS和MapReduce的支持,Hadoop生态还整合了HBase、ZooKeeper、Hive等项目,在数据仓库、数据挖掘、人工智能等领域都有广阔的应用场景。 环境准备 为了搭建分布式Hadoop集群,我们需要至少一台Master节点和一台Slave节点,在UOS创建如下主机。 为了保证集群内主机网络互通,我们需要创建私有网络,保证所有节点都在此网络中。 搭建集群 我们以最新的Ubuntu 15.04为例,部署Hadoop 2.7.1集群模式。 第一步安装Oracle Java 7,注意Hadoop 2.7已经不支持Java 6了。 sudo apt-get purge -y openjdk* add-apt-repository ppa:webupd8team/java apt-get update -y apt-get install -y oracle-java7-installer 然后修改.bashrc配置JAVA_HOME。 export JAVA_HOME=/usr/lib/jvm/java-7-oracle/ export JRE_HOME=$JAVA_HOME/jre export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin 为了模拟线上环境,我们会创建hadoop用户。 sudo useradd -m hadoop -s /bin/bash sudo passwd hadoop sudo adduser hadoop sudo 安装Hadoop的过程非常简单,只需下载编译好的压缩包即可。 wget http://mirror.bit.edu.cn/apache/hadoop/common/stable2/hadoop-2.7.1.tar.gz tar xzvf ./hadoop-2.7.1.tar.gz sudo mv hadoop-2.7.1 /usr/lib/hadoop 并且配置HADOOP_HOME。 export HADOOP_HOME=/usr/lib/hadoop export PATH=$PATH :$HADOOP_HOME/bin 修改配置 修改/etc/hosts。 192.168.0.2 hadoop-master 192.168.0.5 hadoop-slave 修改/usr/lib/hadoop/etc/hadoop/slaves。 hadoop-slave 修改/usr/lib/hadoop/etc/hadoop/core-site.xml。 <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://hadoop-master:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>file:/usr/lib/hadoop/tmp</value> <description>Abase for other temporary directories.</description> </property> </configuration> 修改/usr/lib/hadoop/etc/hadoop/hdfs-site.xml。 <configuration> <property> <name>dfs.namenode.secondary.http-address</name> <value>hadoop-master:50090</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>file:/usr/lib/hadoop/tmp/dfs/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>file:/usr/lib/hadoop/tmp/dfs/data</value> </property> <property> <name>dfs.replication</name> <value>1</value> </property> </configuration> 修改/usr/lib/hadoop/etc/hadoop/mapred-site.xml。 <configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration> 修改/usr/lib/hadoop/etc/hadoop/yarn-site.xml。 <configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>hadoop-master</value> </property> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> </configuration> 启动服务 启动Hadoop服务命令非常简单,执行下面四行命令接口。 cd /usr/lib/hadoop/ bin/hdfs namenode -format sbin/start-dfs.sh sbin/start-yarn.sh 可以通过下面的命令来测试,或者访问 http://master:8088/cluster bin/hdfs dfs -mkdir -p /user/hadoop bin/hdfs dfs -put etc/hadoop input bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.1.jar grep input output 'dfs[a-z.]+' bin/hdfs dfs -cat output/* 总结 虚拟化和云计算让IT基础设施更加触手可得,借助IaaS平台就能在十分钟内搭建Hadoop集群,真正做到让大数据开发像家庭作业一样简单。 UOS致力于为企业和开发者提供高性能的云主机和云存储服务,在UOS搭建大数据服务,让您的数据发掘更大的价值。 关于作者: 陈迪豪,UnitedStack有云的基础架构工程师,目前专注于Docker、OpenStack社区。Docker监控管理工具Seagull项目作者,开源电子书《理解Linux进程》作者。
了解详情

大数据看过来,UOS上的分布式Spark集群

随着移动互联网和物联网的兴起,大数据领域出现了两大新贵,传统的离线大数据处理框架Hadoop和统一迭代计算、流式计算和图计算的Spark。 对于Hadoop我们并不需要过多的介绍,经典的MapReduce编程范式已经流行了近十年,但它的使用很大程度上仅限于离线计算,于是出现了Storm、Hive、Impala等项目来弥补它在流式计算和在线查询上的劣势。大量生态项目的涌入让Hadoop学习越来越复杂,并且很多公司都在重复研究和实现数据挖掘、人工智能的算法,对真正的数据工程师或数学家来说这是难以驾驭的大数据处理服务。 这是Spark应运而生,诞生于伯克利大学的AMPLab,通过RDD的设计创新性地实现了迭代计算、流式计算、图计算等大数据处理接口,并且提供比Hadoop快10倍到100倍的性能。无论从功能来看,还是从性能来看,Spark无疑将获得大部分大数据开发者的青睐。Spark提供了Java、Scala和Python的开发接口,三行代码即可实现经典的WordCount分布式应用,因此快速了解和学习Spark也是当代程序员的基本要求。

UOS支持

为了满足公司内部和公有云用户的大数据开发需求,我们提供的UOS平台必须支持用户在上面运行Spark集群。 UOS通过KVM虚拟化技术为开发者提供了按需分配的虚拟机,在需要分析数据时可以一键创建虚拟机集群,目前UOS基于Ceph统一存储虚拟机启动时间控制在10秒以内。 建立Spark集群还需要打通网络,我们基于Neutron实现的SDN可以支持用户自由配置网络和IP地址,最简单的配置是让Spark集群的服务器都在同一个私有网络中。 除此以外,我们全SSD的块设备也能为用户提供高性能的云硬盘存储服务。由于Ceph是分布式存储,有人担心这是否会影响Spark的性能,实际上Spark的创新之处在于设计了基于内存的RDD,才能在性能上远超Hadoop,从Spark官方文档看出Spark的性能在内存足够的情况下只限于网络。

搭建Spark集群

目前UOS公有云已经有使用Spark的大数据用户,我们通过他的分享来快速搭建自己的分布式Spark集群。 要搭建Spark计算集群,我们需要创建三台服务器,系统使用Ubuntu或CentOS都是支持的,并且让绑定在同一个私有网络上。 然后我们需要安装Java、Scala和Spark,安装步骤虽然有些繁琐,但在Ubuntu虚拟机上已经测试通过,用户也可以选择合适的版本(注意最新的Spark 1.5需要搭配Scala 2.10.x)。 ## Install Java 7 sudo apt-get purge -y openjdk* add-apt-repository ppa:webupd8team/java apt-get update -y apt-get install -y oracle-java7-installer ## Install Scala 2.10.5 wget http://downloads.typesafe.com/scala/2.10.5/scala-2.10.5.tgz tar xzvf ./scala-2.10.5.tgz mv scala-2.10.5 /usr/lib/ ## Install Spark 1.5 wget http://d3kbcqa49mib13.cloudfront.net/spark-1.5.0-bin-hadoop2.6.tgz tar xzvf ./spark-1.5.0-bin-hadoop2.6.tgz mv ./spark-1.5.0-bin-hadoop2.6/ /usr/lib/spark-1.5.0/ 安装上述的软件后,记得在.bashrc或.zshrc添加以下环境变量,否则找不到Java或Spark命令。 export JAVA_HOME=/usr/lib/jvm/java-7-oracle/ export JRE_HOME=$JAVA_HOME/jre export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$PATH:$JAVA_HOME/bin export SCALA_HOME=/usr/lib/scala-2.10.5 export PATH=$PATH:$SCALA_HOME/bin export SPARK_HOME=/usr/lib/spark-1.5.0 export PATH=$PATH:$SPARK_HOME/bin 可以说把包下载下来就可以直接运行Spark了,但为了配置集群模式,我们还需要配置slaves文件和spark-env.sh,添加从节点和主节点的IP地址。 cp /usr/lib/spark-1.5.0/conf/slaves.template /usr/lib/spark-1.5.0/conf/slaves 42.62.101.170 42.62.101.103 cp /usr/lib/spark-1.5.0/conf/spark-env.sh.template /usr/lib/spark-1.5.0/conf/spark-env.sh export JAVA_HOME=/usr/lib/jvm/java-7-oracle/ export SCALA_HOME=/usr/lib/scala-2.10.5 export SPARK_MASTER_IP=10.250.7.235 由于不能说的原因下载Java和Spark都比较耗时,可以使用UOS的虚拟机快照功能,直接克隆一个虚拟机,这样就无需重复上述的配置了。然后只需要在主节点执行start-all.sh就可以启动集群了,为了允许主节点ssh到从节点启动服务,我们需要把主节点的ssh key手动添加到从节点上。服务启动后,在8080端口就可以看到久违的Spark管理界面。 要测试Spark服务也很简单,在主节点运行/usr/lib/spark-1.5.0/bin/pyspark,然后快速创建一个RDD,执行一下count操作,马上可以得到分布式计算后的运算结果。 rdd = sc.parallelize([1, 2, 3]) rdd.count()

总结

最后总结下分布式大数据并行处理框架Spark,这是令很多数据工程师和程序员痴迷的大数据框架,它同时提供了迭代计算、流式计算、图计算等接口,并且内置大量机器学习、数据挖掘算法,让任何人都能快速学习和参与到大数据开发来。 UOS是基于OpenStack的高性能云服务提供商,也提供了对大数据服务和Spark的支持,希望通过我们的平台能让更多人用上Spark,通过我们的教程让更多开发者了解大数据。 关于作者: 陈迪豪,UnitedStack有云的基础架构工程师,目前专注于Docker、OpenStack社区。Docker监控管理工具Seagull项目作者,开源电子书《理解Linux进程》作者。
了解详情

通过demo学习OpenStack开发所需的基础知识 — API服务(2)

本文会重点讲解OpenStack中使用的API开发框架的使用。但是本文的目的并不是覆盖这些框架的使用细节,而是通过说明重要的部分,降低初学者的入门的门槛。框架的使用细节都可以从文档中找到。说明一下,除非特殊说明,本文中的相对路径都是相对于项目源码目录的相对路径

Paste + PasteDeploy + Routes + WebOb

我们在API服务(1)中已经提到了,这个框架只在早期开始的项目中使用,新的项目都已经转到Pecan框架了。但是,早期的项目都是比较核心的项目,因此我们还是要学会如何使用这个框架。我们会以Keystone项目为例,来说明如何阅读使用这个框架的开发的API代码。

重点在于确定URL路由

RESTful API程序的主要特点就是URL path会和功能对应起来。这点从API文档就可以看得出来,比如用户管理的功能一般都放在/user这个路径下。因此,看一个RESTful API程序,一般都是看它实现了哪些URL path,以及每个path对应了什么功能,这个一般都是由框架的URL路由功能负责的。所以,熟悉一个RESTful API程序的重点在于确定URL路由。本章所说的这个框架对于初学者的难点也是如何确定URL路由。

WSGI入口和中间件

作为基础知识,你需要先了解一下WSGI的相关概念,可以参考这篇文章WSGI简介

WSGI入口

API服务(1)中提到了WSGI可以使用Apache进行部署,也可以使用eventlet进行部署。Keystone项目同时提供了这两种方案的代码,也就是我们要找的WSGI的入口。 Keystone项目在httpd/目录下,存放了可以用于Apache服务器部署WSGI服务的文件。其中,wsgi-keystone.conf是一个mod_wsgi的示例配置文件,keystone.py则是WSGI应用程序的入口文件。httpd/keystone.py也就是我们要找的入口文件之一。这个文件的内容很简单:
import os

from keystone.server import wsgi as wsgi_server

name = os.path.basename(__file__)

application = wsgi_server.initialize_application(name)
文件中创建了WSGI入口需要使用的application对象。 keystone-all命令则是采用eventlet来进行部署时的入口,可以从setup.cfg文件按中确定keystone-all命令的入口:
[entry_points]
console_scripts =
    keystone-all = keystone.cmd.all:main
    keystone-manage = keystone.cmd.manage:main
setup.cfg文件的entry_points部分可以看出,keystone-all的入口是keystone/cmd/all.py文件中的main()函数,这个函数的内容也很简单:
def main():
    eventlet_server.run(possible_topdir)
main()函数的主要作用就是启动一个eventlet_server,配置文件从possible_topdir中查找。因为eventlet的部署方式涉及到eventlet库的使用方法,本文不再展开说明。读者可以在学会确定URL路由后再回来看这个代码。下面,继续以httpd/keystone.py文件作为入口来说明如何阅读代码。

Paste和PasteDeploy

httpd/keystone.py中调用的initialize_application(name)函数载入了整个WSGI应用,这里主要用到了Paste和PasteDeploy库。
def initialize_application(name):
    ...
    def loadapp():
        return keystone_service.loadapp(
            'config:%s' % config.find_paste_config(), name)

    _unused, application = common.setup_backends(
        startup_application_fn=loadapp)
    return application
上面是删掉无关代码后的initialize_application()函数。config.find_paste_config()用来查找PasteDeploy需要用到的WSGI配置文件,这个文件在源码中是etc/keystone-paste.ini文件,如果在线上环境中,一般是/etc/keystone-paste.initkeystone_service.loadapp()函数内部则调用了paste.deploy.loadapp()函数来加载WSGI应用,如何加载则使用了刚才提到的keystone-paste.ini文件,这个文件也是看懂整个程序的关键。

name很关键

在上面的代码中我们可以看到,name这个变量从httpd/keystone.py文件传递到initialize_application()函数,又被传递到keystone_service.loadapp()函数,最终被传递到paste.deploy.loadapp()函数。那么,这个name变量到底起什么作用呢?先把这个问题放在一边,我们后面再来解决它。

paste.ini

使用Paste和PasteDeploy模块来实现WSGI服务时,都需要一个paste.ini文件。这个文件也是Paste框架的精髓,这里需要重点说明一下这个文件如何阅读。 paste.ini文件的格式类似于INI格式,每个section的格式为[type:name]。这里重要的是理解几种不同type的section的作用。
  • composite: 这种section用于将HTTP请求分发到指定的app。
  • app: 这种section表示具体的app。
  • filter: 实现一个过滤器中间件。
  • pipeline: 用来把把一系列的filter串起来。
上面这些section是在keystone的paste.ini中用到的,下面详细介绍一下如何使用。这里需要用到WSGIMiddleware(WSGI中间件)的知识,可以在WSGI简介这篇文章中找到。 section composite 这种section用来决定如何分发HTTP请求。Keystone的paste.ini文件中有两个composite的section:
[composite:main]
use = egg:Paste#urlmap
/v2.0 = public_api
/v3 = api_v3
/ = public_version_api

[composite:admin]
use = egg:Paste#urlmap
/v2.0 = admin_api
/v3 = api_v3
/ = admin_version_api
在composite seciont中,use是一个关键字,指定处理请求的代码。egg:Paste#urlmap表示到Paste模块的egg-info中去查找urlmap关键字所对应的函数。在virtualenv环境下,是文件/lib/python2.7/site-packages/Paste-2.0.2.dist-info/metadata.json
{
    ...
    "extensions": {
        ...
        "python.exports": {
            "paste.composite_factory": {
                "cascade": "paste.cascade:make_cascade",
                "urlmap": "paste.urlmap:urlmap_factory"
            },
    ...
}
在这个文件中,你可以找到urlmap对应的是paste.urlmap:urlmap_factory,也就是paste/urlmap.py文件中的urlmap_factory()函数。 composite section中其他的关键字则是urlmap_factory()函数的参数,用于表示不同的URL path前缀。urlmap_factory()函数会返回一个WSGI app,其功能是根据不同的URL path前缀,把请求路由给不同的app。以[composite:main]为例:
[composite:main]
use = egg:Paste#urlmap
/v2.0 = public_api       # /v2.0 开头的请求会路由给public_api处理
/v3 = api_v3             # /v3 开头的请求会路由个api_v3处理
/ = public_version_api   # / 开头的请求会路由给public_version_api处理
路由的对象其实就是paste.ini中其他secion的名字,类型必须是app或者pipeline。 section pipeline pipeline是把filter和app串起来的一种section。它只有一个关键字就是pipeline。我们以api_v3这个pipeline为例:
[pipeline:api_v3]
# The last item in this pipeline must be service_v3 or an equivalent
# application. It cannot be a filter.
pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension_v3 s3_extension simple_cert_extension revoke_extension federation_extension oauth1_extension endpoint_filter_extension endpoint_policy_extension service_v3
pipeline关键字指定了很多个名字,这些名字也是paste.ini文件中其他section的名字。请求会从最前面的section开始处理,一直向后传递。pipeline指定的section有如下要求:
  • 最后一个名字对应的section一定要是一个app
  • 非最后一个名字对应的section一定要是一个filter
section filter filter是用来过滤请求和响应的,以WSGI中间件的方式实现。
[filter:sizelimit]
paste.filter_factory = oslo_middleware.sizelimit:RequestBodySizeLimiter.factory
这个是api_v3这个pipeline指定的第一个filter,作用是限制请求的大小。其中的paste.filter_factory表示调用哪个函数来获得这个filter中间件。 section app app表示实现主要功能的应用,是一个标准的WSGI application。
[app:service_v3]
paste.app_factory = keystone.service:v3_app_factory
paste.app_factory表示调用哪个函数来获得这个app。 总结一下 paste.ini中这一大堆配置的作用就是把我们用Python写的WSGI application和middleware串起来,规定好HTTP请求处理的路径。 name是用来确定入口的 上面我们提到了一个问题,就是name变量的作用到底是什么?name变量表示paste.ini中一个section的名字,指定这个section作为HTTP请求处理的第一站。在Keystone的paste.ini中,请求必须先由[composite:main]或者[composite:admin]处理,所以在keystone项目中,name的值必须是main或者admin。 上面提到的httpd/keystone.py文件中,name等于文件名的basename,所以实际部署中,必须把keystone.py重命名为main.py或者admin.py举个例子 一般情况下,从Keystone服务获取一个token时,会使用下面这个API:
POST http://hostname:35357/v3/auth/tokens
我们根据Keystone的paste.ini来说明这个API是如何被处理的:
  1. hostname:35357 这一部分是由Web服务器处理的,比如Apache。然后,请求会被转到WSGI的入口,也就是httpd/keystone.py中的application对象取处理。
  2. application对象是根据paste.ini中的配置来处理的。这里会先由[composite:admin]来处理(一般是admin监听35357端口,main监听5000端口)。
  3. [composite:admin]发现请求的path是/v3开头的,于是就把请求转发给[pipeline:api_v3]去处理,转发之前,会把/v3这个部分去掉。
  4. [pipeline:api_v3]收到请求,path是/auth/tokens,然后开始调用各个filter来处理请求。最后会把请求交给[app:service_v3]进行处理。
  5. [app:service_v3]收到请求,path是/auth/tokens,然后交给最终的WSGI app去处理。
下一步 到此为止,paste.ini中的配置的所有工作都已经做完了。下面请求就要转移到最终的app内部去处理了。前面已经说过了,我们的重点是确定URL路由,那么现在还有一部分的path的路由还没确定,/auth/tokens,这个还需要下一步的工作。

中间件的实现

上面我们提到paste.ini中用到了许多的WSGI中间件,那么这些中间件是如何实现的呢?我们来看一个例子就知道了。
[filter:build_auth_context]
paste.filter_factory = keystone.middleware:AuthContextMiddleware.factory
build_auth_context这个中间件的作用是在WSGI的environ中添加KEYSTONE_AUTH_CONTEXT这个键,包含的内容是认证信息的上下文。实现这个中间件的类继承关系如下:
keystone.middleware.core.AuthContextMiddleware
  -> keystone.common.wsgi.Middleware
    -> keystone.common.wsgi.Application
      -> keystone.common.wsgi.BaseApplication
这里实现的关键主要在前面两个类中。 keystone.common.wsgi.Middleware类实现了__call__()方法,这个就是WSGI中application端被调用时运行的方法。
class Middleware(Application):
    ...
    @webob.dec.wsgify()
    def __call__(self, request):
        try:
            response = self.process_request(request)
            if response:
                return response
            response = request.get_response(self.application)
            return self.process_response(request, response)
        except exceptin.Error as e:
            ...
        ...
__call__()方法实现为接收一个request对象,返回一个response对象的形式,然后使用WebOB模块的装饰器webob.dec.wsgify()将它变成标准的WSGI application接口。这里的request和response对象分别是 webob.Requestwebob.Response。这里,__call__()方法内部调用了self.process_request()方法,这个方法在keystone.middleware.core.AuthContextMiddleware中实现:
class AuthContextMiddleware(wsgi.Middleware):
    ...
    def process_request(self, request):
        ...
        request.environ[authorization.AUTH_CONTEXT_ENV] = auth_context
这个函数会根据功能设计创建auth_context,然后赋值给request.environ['KEYSTONE_AUTH_CONTEXT]`,这样就能通过WSGI application方法的environ传递到下一个WSGI application中去了。

最后的Application

上面我们已经看到了,对于/v3开头的请求,在paste.ini中会被路由到[app:service_v3]这个section,会交给keystone.service:v3_app_factory这个函数生成的application处理。最后这个application需要根据URL path中剩下的部分,/auth/tokens,来实现URL路由。从这里开始,就需要用到Routes模块了。 同样由于篇幅限制,我们只能展示Routes模块的大概用法。Routes模块是用Python实现的类似Rails的URL路由系统,它的主要功能就是把path映射到对应的动作。 Routes模块的一般用法是创建一个Mapper对象,然后调用该对象的connect()方法把path和method映射到一个controller的某个action上,这里controller是一个自定义的类实例,action是表示controller对象的方法的字符串。一般调用的时候还会指定映射哪些方法,比如GET或者POST之类的。 举个例子,来看下keystone/auth/routers.py的代码:
class Routers(wsgi.RoutersBase):

    def append_v3_routers(self, mapper, routers):
        auth_controller = controllers.Auth()

        self._add_resource(
            mapper, auth_controller,
            path='/auth/tokens',
            get_action='validate_token',
            head_action='check_token',
            post_action='authenticate_for_token',
            delete_action='revoke_token',
            rel=json_home.build_v3_resource_relation('auth_tokens'))

    ...
这里调用了自己Keystone自己封装的_add_resource()方法批量为一个/auth/tokens这个path添加多个方法的处理函数。其中,controller是一个controllers.Auth实例,也就是 keystone.auth.controllers.Auth。其他的参数,我们从名称可以猜出其作用是指定对应方法的处理函数,比如get_action用于指定GET方法的处理函数为validate_token。我们再深入一下,看下_add_resource()这个方法的实现:
    def _add_resource(self, mapper, controller, path, rel,
                      get_action=None, head_action=None, get_head_action=None,
                      put_action=None, post_action=None, patch_action=None,
                      delete_action=None, get_post_action=None,
                      path_vars=None, status=json_home.Status.STABLE):
        ...
        if get_action:
            getattr(controller, get_action)  # ensure the attribute exists
            mapper.connect(path, controller=controller, action=get_action,
                           conditions=dict(method=['GET']))
        ...
这个函数其实很简单,就是调用mapper对象的connect方法指定一个path的某些method的处理函数。

Keystone项目的代码结构

Keystone项目把每个功能都分到单独的目录下,比如token相关的功能是放在keystone/token/目录下,assignment相关的功能是放在keystone/assignment/目录下。目录下都一般会有三个文件:routers.py, controllers.py, core.pyrouters.py中实现了URL路由,把URL和controllers.py中的action对应起来;controllers.py中的action调用core.py中的底层接口实现RESTful API承诺的功能。所以,我们要进一步确定URL路由是如何做的,就要看routers.py文件。 注意,这个只是Keystone项目的结构,其他项目即使用了同样的框架,也不一定是这么做的。

Keystone中的路由汇总

每个模块都定义了自己的路由,但是这些路由最终要还是要通过一个WSGI application来调用的。上面已经提到了,在Keystone中,/v3开头的请求最终都会交给keystone.service.v3_app_factory这个函数生成的application来处理。这个函数里也包含了路由最后分发的秘密,我们来看代码:
def v3_app_factory(global_conf, **local_conf):
    ...
    mapper = routes.Mapper()
    ...

    router_modules = [auth,
                      assignment,
                      catalog,
                      credential,
                      identity,
                      policy,
                      resource]
    ...

    for module in router_modules:
        routers_instance = module.routers.Routers()
        _routers.append(routers_instance)
        routers_instance.append_v3_routers(mapper, sub_routers)

    # Add in the v3 version api
    sub_routers.append(routers.VersionV3('public', _routers))
    return wsgi.ComposingRouter(mapper, sub_routers)
v3_app_factory()函数中先遍历了所有的模块,将每个模块的路由都添加到同一个mapper对象中,然后把mapper对象作为参数用于初始化wsgi.ComposingRouter对象,所以我们可以判断,这个wsgi.ComposingRouter对象一定是一个WSGI application,我们看看代码就知道了:
class Router(object):
    """WSGI middleware that maps incoming requests to WSGI apps."""

    def __init__(self, mapper):
        self.map = mapper
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                          self.map)

    @webob.dec.wsgify()
    def __call__(self, req):
        return self._router

    ...

class ComposingRouter(Router):
    def __init__(self, mapper=None, routers=None):
        ...
上述代码证实了我们的猜测。这个ComposingRouter对象被调用时(在其父类Router中实现),会返回一个WSGI application。这个application中则使用了routes模块的中间件来实现了请求路由,在routes.middleware.RoutesMiddleware中实现。这里对path进行路由的结果就是返回各个模块的controllers.py中定义的controller。各个模块的controller都是一个WSGI application,这个你可以通过这些controller的类继承关系看出来。 但是这里只讲到了,routes模块把path映射到了一个controller,但是如何把对path的处理映射到controller的方法呢?这个可以从controller的父类keystone.common.wsgi.Application的实现看出来。这个Application类中使用了environ['wsgiorg.routing_args']中的数据来确定调用controller的哪个方法,这些数据是由上面提到的routes.middleware.RoutesMiddleware设置的。

总结

到这里我们大概把Paste + PasteDeploy + Routes + WebOb这个框架的流程讲了一遍,从本文的长度你就可以看出这个框架有多啰嗦,用起来有多麻烦。下一篇文章我们会讲Pecan框架,我们的demo也将会使用Pecan框架来开发。

参考资源

本文主要提到了Python Paste中的各种库,这些库的相关文档都可以在项目官网找到:http://pythonpaste.org/。 另外,routes库的项目官网是:https://routes.readthedocs.org/en/latest/ 关于作者: 刘陈泓,现任UnitedStack平台开发部开发工程师,主要关注OpenStack的身份认证和计费领域。
了解详情

通过demo学习OpenStack开发所需的基础知识 — API服务(1)

使用OpenStack服务的方式

OpenStack项目作为一个IaaS平台,提供了三种使用方式:
  • 通过Web界面,也就是通过Dashboard(面板)来使用平台上的功能。
  • 通过命令行,也就是通过keystonenovaneutron等命令,或者通过最新的openstack命令来使用各个服务的功能(社区目前的发展目标是使用一个单一的openstack命令替代过去的每个项目一个命令的方式,以后会只存在一个openstack命令)。
  • 通过API,也就是通过各个OpenStack项目提供的API来使用各个服务的功能。
上面提到的三种方式中,通过API这种方式是基础,是其他两种方式可行的基础。 通过Web界面使用OpenStack服务这种方式是通过OpenStack的Horizon项目提供的。Horizon项目是一个Django应用,实现了一个面板功能,包含了前后端的代码(除了Python,还包括了CSS和JS)。Horizon项目主要是提供一种交互界面,它会通过API来和各个OpenStack服务进行交互,然后在Web界面上展示各个服务的状态;它也会接收用户的操作,然后调用各个服务的API来完成用户对各个服务的使用。 通过命令行是用OpenStack服务的方式是由一系列项目来提供的,这些项目一般都命名为python-projectclient,比如python-keystoneclientpython-novaclietn等。这些命令行项目分别对应到各个主要的服务,为用户提供命令行操作界面和Python的SDK。比如python-keystoneclient对应到keystone,为用户提供了keystone这个命令,同时也提供了keyston项目的SDK(其实是在SDK的基础上实现了命令行)。这些client项目提供的SDK其实也是封装了对各自服务的API的调用。由于每个主要项目都有一个自己的命令行工具,社区觉得不好,于是又有了一个新的项目python-openstackclient,用来提供一个统一的命令行工具openstack(命令的名字就叫做openstack),这个工具实现了命令行,然后使用各个服务的client项目提供的SDK来完成对应的操作。 通过API使用OpenStack的方式是由各个服务自己实现的,比如负责计算的nova项目实现了计算相关的API,负责认证的keystone项目实现了认证和授权相关的API。这些API都是有统一的形式的,都是采用了HTTP协议实现的符合REST规范的API。OpenStack中如何实现这些API就是本文重点要将的内容。

基于HTTP协议的RESTful API

REST的全称是Representational State Transfer,中文翻译过来是表征状态转移,是Roy Fielding在他的博士论文**Architectural Styles and the Design of Network-based Software Architectures**提出的一种软件架构风格。可以先到wikipedia页面了解一下这个风格的特点。一般会把满足这种设计风格的API成为RESTful API。由于这种软件设计风格非常适合采用HTTP协议来实现,因此HTTP协议是目前实现RESTful API的主要方案。 OpenStack就是基于HTTP协议和JSON来实现自己的RESTful API(之前OpenStack还有采用XML来表示数据的,现在都已经转到JSON了)。当一个服务要提供API时,它就会启动一个HTTP服务端,用来对外提供RESTful API。 OpenStack的API都是有详细的文档记录的,可以在http://docs.openstack.org/看到所有的API的文档。每个API的文档形式如下: 当然,你可以点开detail看到详细的说明。从上面这个API的文档来看,你会觉得这个和开发网站时使用的GET方法和POST方法差不多,实际上也是差不多的,只不过对HTTP协议的使用方法做了满足REST风格的规定而已。

Python如何实现RESTful API

因为Python能够进行Web开发,所以用来开发RESTful API也就不成问题,这两者的技术基础是一样。在Python下开发RESTful API应用,无非是解决两个问题:
  1. 服务如何部署?
  2. 用什么框架开发?

服务如何部署?

说到Python的Web服务部署这个问题,就不得不提到WSGI。目前Python有两种方式来开发和部署一个Web应用:用WSGI不用WSGI。如果你不了解WSGI,那么你需要先看下另外这篇关于WSGI的文章:WSGI简介。 OpenStack的API服务都是使用WSGI的方式来部署的。在生产环境中部署WSGI,一般会考虑使用Web服务器 + 应用服务器 + 应用(框架)的方案。OpenStack官方推荐的是使用Apache + mod_wsgi的方案,不过这个要换成其他方案也很容易,你也可以选nginx + uWSGI。对于开发调试的目的,有些项目也会提供使用eventlet的单进程部署方案,比如Keystone项目的keystone-all命令。采用eventlet这种异步架构来进行应用开发也是一个比较大的话题,本文不覆盖这方面的内容。 当然,也可以不用WSGI。在Python中,如果不使用WSGI的化,一般开发者会选择一些专门的服务器和框架,比如Tornado,或者最新最潮的aiohttp。不过在OpenStack的项目中我还没见过不使用WSGI的。

用什么框架开发

Python的Web开发框架很多,最出名自然是Django了。基本上,还活跃的框架都支持RESTful API的开发,有些框架还专门为RESTful API的开发提供了便利的功能(比如Pecan),有些框架则通过第三方模块来提供这种便利,比如Django和Flask都有不少和REST相关的第三方库。 对于框架选择,也没有什么特别好的标准,一般都是比较性能、文档、社区是否活跃等。在我看来,选择流行的一般就不会错。

OpenStack中的RESTful API开发

上面已经谈到了OpenStack都是使用WSGI,也提到了部署方式。这一章来说一下OpenStack中使用的框架。 OpenStack项目倾向于不重新发明轮子,一般都会选择现有的库和框架来使用,除非现有的框架不满足需求。因为Web框架的选择很多,而且都满足需求,所以OpenStack项目到目前为止都是使用现成的Web框架。 OpenStack早期的项目并没有使用一个框架,而是使用了几个不同的模块来组合出一个框架:Paste + PasteDeploy + Routes + WebOb,这几个不同的模块分别负责应用的WSGI化、URL路由和请求处理等功能。Nova, Glance, Neutron, Keystone等早期的项目都是使用这样的架构来实现RESTful API的。 早期的这种技术选型带来的好处是"框架"具备足够的灵活性,缺点则是要把这几个模块组合起来实现一个REST服务,需要写很多代码,连WSGI的入口函数都要自己实现(比如Keystone项目的keystone/common/wsgi.py文件中的class Application)。因为灵活性的好处不是很明显,而代码量大的坏处很明显,比如上面那个class Application需要在每个项目中复制一遍,所以社区的新项目就开始使用新的Web框架Pecan。 Pecan是一个基于对象路由的框架,即灵活又简单。Pecan主要实现了URL路由功能,支持RESTful API。Pecan没有实现模板、session管理和ORM等功能,但是这些功能可以通过其他的模块来实现。对于OpenStack来说,Pecan是一个很好的选择,因为OpenStack项目中统一使用sqlalchemy来实现ORM,API的实现也不需要模板功能,安全控制则基于Keystone体系。使用Pecan来开发REST服务,代码量很少,代码结构也清晰。Ceilometer项目就是使用了Pecan。 关于作者: 刘陈泓,现任UnitedStack平台开发部开发工程师,主要关注OpenStack的身份认证和计费领域。
了解详情

通过demo学习OpenStack开发所需的基础知识 — 软件包管理

为什么写这个系列

OpenStack是目前我所知的最大最复杂的基于Python项目。整个OpenStack项目包含了数十个主要的子项目,每个子项目所用到的库也不尽相同。因此,对于Python初学者和未接触过OpenStack项目的人来说,入门的难度相当大。 幸运的是,OpenStack中的项目有很多共同点。比如,它们有相同的代码库结构,都尽可能是用同样的库,配置文件和单元测试的规范也都几乎一样。因此,通过学习这些共通的部分,我们就可以快速掌握多个OpenStack项目。但是,万事开头难,学习这些基础知识总是痛苦的。不过,学习的难点并不在于这些知识点本身有多难理解,而是这些基础知识的应用场景和应用效果对初学者来说都是模糊的。这个系列文章的目的就是帮助有需要的人了解OpenStack中一些常见的知识点。理解过程就是通过动手做一个web application demo来实现的。 这个系列文章会涉及到以下的知识点:
  • 包管理和pbr
  • WSGI, RESTful Service和Pecan框架
  • eventlet
  • SQLAlchymy
  • 单元测试
下面的知识点是不会专门讲的,如果有遇到不会的请自学:
  • git

软件包管理

软件包管理是每个OpenStack项目的基础,其目的是用来将项目代码打包成源码包或者二进制包进行分发。一个项目的代码可能会被打包放到PyPI上,这样你可以通过pip命令安装这个包;也可能会被打包放到项目的软件仓库里,这样你可以通过apt-get install或者yum install来安装这个软件包。 不幸的是,Python在软件包管理十分混乱,至少历史上十分混乱。原因有两个:一是标准库提供的软件包管理功能十分弱,二是官方没有提供统一的软件包管理标准。对于这个领域,我曾经也是混乱的,只知道使用easy_installpip来安装软件包。不过自从看了The Hacker's Guide to Python(《Python高手之路》)之后,算是知道点来龙去脉。

软件打包工具的历史

这里我会讲一下我知道的Python的软件打包工具的历史,我们按照历史顺序来叙述。

distutils (before 2000)

disutils自从1998年起就是Python标准库的一部分了,不过它在2000年就停止了开发。disutils是最早的Python打包工具和标准,也奠定了对Python软件进行打包的一个基本工作方式:使用setup.py文件。来看一个setup.py文件的例子:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from disutils.core import setup

setup(name='webdemo',
      description='A simple web demo.',
      author='author name',
      author_email='author_name@example.com'
      url='http://example.com',
      packages=['webdemo'])
setup.py文件是放在项目根目录下的:
➜ ~/programming/python/webdemo git:(master) ✗ $ ls
LICENSE  README.md  setup.py  webdemo
然后你就可以使用命令python setup.py build来编译包,可以使用python setup.py install来安装这个项目。如果需要帮助,可以通过python setup.py --help-commands来查看支持的命令。

setuptools

disutils停止开发后,setuptools成了继任者。setuptools提供了很多高级功能,包括自动依赖处理、Egg分发格式以及easy_install命令。setuptools的使用方式和disutils差不多,也是以一个setup函数作为入口,只不过该函数来自于setuptools模块,而且支持更多的参数,比如classifiers, setup_requires等,参数更多意味着功能更多。 后来有一段时间setuptools项目发展开始变得缓慢了,就有人从setuptools项目创建了distribute项目。distribute开始支持Python 3等新特性。不过一段时间后,distribute项目又和setuptools项目合并了(2013年3月)。因此,现在已经不存在distribute项目了。 到目前为止,setuptools还是使用最多的打包工具,而且开发很活跃,2015年6月刚刚发布了18.0版本。setuptools项目的文档在:http://pythonhosted.org/setuptools/。OpenStack目前也是使用setuptools库来执行打包操作,我们下面会详细点介绍setuptools工具。

disutils2

在setuptools项目发展的过程中,有一个叫disutils2的项目也在并行开发中,其目的是全面取代Python标准库中的distutils。disutils2的最大改进是将setup函数的参数单独放到一个setup.cfg的文件中(这些成为包的元数据)。不够disutils2这个项目缺点很多,而且没有功能上还不如setuptools项目,所以在2012年的时候,这个项目被废弃了。

distlib

这个是一个新的打包工具,目标也是取代disutils。不过这个项目的开发进展也不快,到2015年才发布了0.2.0版本。目前还未能并入到Python的标准库中。不过可以保持关注。项目文档地址:https://readthedocs.org/projects/distlib/

在OpenStack中使用打包工具

前面已经提到了,OpenStack也是使用setuptools工具来进行打包,不过为了满足OpenStack项目的需求,引入了一个辅助工具pbr来配合setuptools完成打包工作。

pbr (Python Build Reasonableness)

pbr是一个setuptools的扩展工具,被开发出来的主要目的是为了方便使用setuptools,其项目文档地址也在OpenStack官网内:http://docs.openstack.org/developer/pbr/。 先说一下pbr如何使用:
import setuptools

setuptools.setup(setup_requires=['pbr'], pbr=True)
按照上面的方式就可以配置setuptools工具使用pbr来协助完成打包工作。这里的setup_requires参数意思是setup函数在执行之前需要依赖的包的列表。这里的依赖的包的功能可以理解为生成setup的实际参数。你可以看到,当使用pbr的时候,setup函数只有两个参数,然而实际上setuptools.setup函数实际上是disutils.core.setup函数,会接收任何参数,这些参数可以通过在调用时指定,也可以通过所依赖的扩展来生成(比如pbr)。 那么OpenStack社区为啥要开发pbr呢?因为setuptools库使用起来还是有点麻烦,参数太多,而且直接通过指定setup函数的参数的方法实在太不方便了。pbr就是为了方便而生的,它带了了如下的改进:
  1. 使用setup.cfg文件来提供包的元数据。这个是从disutils2学来的。
  2. 基于requirements.txt文件来实现自动依赖安装。requirements.txt文件中包含了一个项目所要依赖的库,这个文件的格式是和pip兼容的。
  3. 利用Sphinx实现文档自动化。
  4. 基于git history自动生成AUTHORS和ChangeLog文件。
  5. 针对git自动创建文件列表。
  6. 基于git tags的版本号管理。

pbr的版本推导

这里重点说明一下基于git tag的版本号管理这个功能。当使用pbr的时候,版本号有两种方式:postversioningpreversioning,postversioning是默认方式。要是用preversioning的方式,则需要设置setup.cfg文件中的[metadata]段的version字段的值。无论采用哪种方式,版本号都是从git的历史推理得到的。pbr使用的版本号标准是Linux/Python Compatible Semantic Versioning 3.0.0,简单的说就是下面这个标准:
Given a version number MAJOR.MINOR.PATCH, increment the:
  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards-compatible manner,
  3. and PATCH version when you make backwards-compatible bug fixes.
pbr的版本推导按照如下的步骤进行(注意,最终版本号才是软件包的版本号):
  1. 如果设置version的值为一个给定的版本号,且这个版本号刚好对应一个tag,则这个值就是最终版本号(注意,这里只有签名的tag才有效)。
  2. 如果不是上面情况,则pbr会找到最近的一个tag,然后为其MINOR值加1得到一个比它大的最小版本号(注意,这个还不是最终版本号)。
  3. 然后pbr会从最近的一个tag开始遍历所有的git commit,并检查每个提交的commit message,在commit message中查找Sem-Ver:这样的行:
  • 如果Sem-Ver的值是bugfix,则会增加版本号中PATCH部分的值。
  • 如果Sem-Ver的值是feature或者deprecation,则会增加版本号中MINOR部分的值。
  • 如果Sem-Ver的值是api-break,则会增加版本号中MAJOR部分的值。
  • 如果Sem-Ver行不存在,则认为值是bugfix
  • 如果Sem-Ver的值不在上面列出的范围内,则会给出警告。
  1. 如果使用的是postversioning的方式,也就是setup.cfg中不指定version的值,则pbr会使用规则3推导出来的值作为目标版本号(只是目标版本号,不是最终版本号)。
  2. 如果使用的是preversioning的方式,也就是setup.cfg中指定了version的值(而且不符合规则1),则会检查指定的version是否高于规则3推导出来的版本号,如果没有,则会抛出异常,如果有,则使用指定的版本号作为目标版本号。
  3. 在得到目标版本号之后,开始计算开发版本号。开发版本号的形式如下:MAJOR.MINOR.PATCH.devN。这里要计算的是devN中的N。这个值等于从最近的git tag开始的提交数量。计算完开发版本号之后,就得到了最终版本号。
总的来说,从上面的规则计算出来的版本号只有两种形式,一种是发布版本号(对应到某个tag),另一种是开发版本号。注意:pbr要求tag都是要签名的,也就是打tag时要使用git tag -a -s X.Y.Z的形式。

setup.cfg和requirements.txt

setup.cfg

由于OpenStack项目都使用了setuptools和pbr来执行打包工作,因此项目的元数据都放在setup.cfg文件中。我们以keystone项目的setup.cfg文件为例来说明这个文件里一般会包含什么内容。以下是写这篇文章时最新的keystone项目的setup.cfg文件的内容(以#开头的是我加的注释):
[metadata]  # 元数据段
name = keystone  # 软件包名称
version = 8.0.0  # 软件包版本号,还可以指定preversoining, postversioning等值,具体的作用看pbr的文档。
summary = OpenStack Identity  # 简介
description-file =  # 指定README文件
    README.rst
author = OpenStack  # 作者
author-email = openstack-dev@lists.openstack.org  # 作者邮件
home-page = http://www.openstack.org/  # 主页
classifier =  # 包的分类,下面具体说
    Environment :: OpenStack
    Intended Audience :: Information Technology
    Intended Audience :: System Administrators
    License :: OSI Approved :: Apache Software License
    Operating System :: POSIX :: Linux
    Programming Language :: Python
    Programming Language :: Python :: 2
    Programming Language :: Python :: 2.7

[files]  # 文件段
packages =  # 包名称
    keystone

[global]  # 全局段
setup-hooks =  # 指定安装hook
    pbr.hooks.setup_hook


[egg_info]  # 指定egg信息
tag_build =
tag_date = 0
tag_svn_revision = 0

[build_sphinx]  # 文档build相关信息
all_files = 1
build-dir = doc/build
source-dir = doc/source

[compile_catalog]
directory = keystone/locale
domain = keystone

[update_catalog]
domain = keystone
output_dir = keystone/locale
input_file = keystone/locale/keystone.pot

[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = keystone/locale/keystone.pot
copyright_holder = OpenStack Foundation
msgid_bugs_address = https://bugs.launchpad.net/keystone

# NOTE(dstanek): Uncomment the [pbr] section below and remove the ext.apidoc
# Sphinx extension when https://launchpad.net/bugs/1260495 is fixed.
[pbr]  # pbr本身的配置
warnerrors = True
autodoc_tree_index_modules = True

[entry_points]  # 指定入口点
console_scripts =  # 指定要生成的可执行文件
    keystone-all = keystone.cmd.all:main
    keystone-manage = keystone.cmd.manage:main

# 下面是其他entry_points内容,主要用于指定不同功能的扩展,和打包无关。
...
(上面有些未注释的部分我目前还不太清楚,后续补充,可以先参考PEP301) 这里说说一下classifier这个参数。这个参数是用来指定一个软件包的分类、许可证、允许运行的操作系统、允许运行的Python的版本的信息。这些信息是在一个叫trove的项目。关于Python和trove的关系,请参考http://stackoverflow.com/questions/9094220/trove-classifiers-definitio...。 你可以在PyPI上找到完整的classifier值列表,地址是:https://pypi.python.org/pypi?%3Aaction=list_classifiers。另外,你也可以通过setuptools的命令来获取这个列表,在项目根目录下执行:python setup.py register --list-classifiers

requirements.txt

这个文件指定了一个项目依赖的包有哪些,并且支出了依赖的包的版本需求,可以看看keystone项目的requirements.txt:
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.

pbr<2.0,>=0.11
WebOb>=1.2.3
eventlet>=0.17.4
greenlet>=0.3.2
PasteDeploy>=1.5.0
Paste
Routes!=2.0,>=1.12.3
cryptography>=0.8.2 # Apache-2.0
six>=1.9.0
SQLAlchemy<1.1.0,>=0.9.7
sqlalchemy-migrate>=0.9.6
stevedore>=1.5.0 # Apache-2.0
passlib
python-keystoneclient>=1.6.0
keystonemiddleware>=1.5.0
oslo.concurrency>=2.1.0 # Apache-2.0
oslo.config>=1.11.0 # Apache-2.0
oslo.messaging!=1.12.0,>=1.8.0 # Apache-2.0
oslo.db>=1.10.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0
oslo.log>=1.2.0 # Apache-2.0
oslo.middleware!=2.0.0,>=1.2.0 # Apache-2.0
oslo.policy>=0.5.0 # Apache-2.0
oslo.serialization>=1.4.0 # Apache-2.0
oslo.service>=0.1.0 # Apache-2.0
oslo.utils>=1.6.0 # Apache-2.0
oauthlib>=0.6
pysaml2>=2.4.0
dogpile.cache>=0.5.3
jsonschema!=2.5.0,<3.0.0,>=2.0.0
pycadf>=0.8.0
msgpack-python>=0.4.0

软件包归档格式

Python的软件包一开始是没有官方的标准分发格式的。比如Java有jar包或者war包作为分发格式,Python则什么都没有。后来不同的工具都开始引入一些比较通用的归档格式。比如,setuptools引入了Egg格式。但是,这些都不是官方支持的,存在元数据和包结构彼此不兼容的问题。因此,为了解决这个问题,PEP 427定义了新的分发包标准,名为Wheel。目前pip和setuptools工具都支持Wheel格式。这里我们简单总结一下常用的分发格式:
  • tar.gz格式:这个就是标准压缩格式,里面包含了项目元数据和代码,可以使用python setup.py sdist命令生成。
  • .egg格式:这个本质上也是一个压缩文件,只是扩展名换了,里面也包含了项目元数据以及源代码。这个格式由setuptools项目引入。可以通过命令python setup.py bdist_egg命令生成。
  • .whl格式:这个是Wheel包,也是一个压缩文件,只是扩展名换了,里面也包含了项目元数据和代码,还支持免安装直接运行。whl分发包内的元数据和egg包是有些不同的。这个格式是由PEP 427引入的。可以通过命令python setup.py bdist_wheel生成。

.egg-info和.dist-info目录

如果你到系统中安装Python库的路径下看看,就能看到很多名称以.egg-info或者以.dist-info结尾的目录。这些目录的内容就是这个库的元数据,是从库的分发包中拷贝出来的。其中.egg-info类型的目录来自于Egg格式的分发包,.dist-info类型的目录来自于Wheel格式的分发包。

软件包的安装

安装工具

上面已经提到了,setuptools项目提供了一个软件包安装工具**esay_install*。easy_install支持从软件归档文件中或者从PyPI上安装软件包,不过这个工具并不好用,比如缺少卸载功能等,因此并不流行,现在更多的都是使用pip工具。 pip项目提供了很好的软件包安装方式,并且已经被包含到Python 3.4中,可以从PyPI、tarball或者Wheel归档中安装和卸载软件按包。关于pip常见的用法,这里就不赘述了(pip install, pip uninstall, pip search, ...)。

安装路径

软件包的安装路径依赖于操作系统、Python版本和安装方式。
  • 在Debian系的系统上(比如Ubuntu)
    • 使用apt-get install从系统软件源安装
      • Python 2.7: /usr/lib/python2.7/dist-packages
      • Python 3.4: /usr/lib/python3.4/dist-packages
    • 使用pip install命令安装
      • Python 2.7: /usr/local/lib/python2.7/dist-packages
      • Python 3.4: /usr/local/lib/python3.4/dist-packages
    • 在virtualenv中使用pip install安装
      • Python 2.7: lib/python2.7/site-packages
      • Python 3.4: lib/python3.4/site-packages
  • 在CentOS系的系统上
    • 使用yum install命令安装
      • Python 2.7: /usr/lib/python2.7/site-packages

以开发模式安装

pip的安装命令可以使用-e选项,用来从本地代码目录或者版本库URL来安装一个开发版本的库。采用这种方式的时候,在安装目录下只会创建一个包含软件包信息的文件,真正的代码不会安装到系统目录下。

webdemo的打包管理

学习过包管理相关的知识后,我们就要以OpenStack的方法来创建一个我们自己的项目。这个项目的名称是webdemo,就是一个简单的web服务器。这个项目会贯穿这个系列文章。在本文中,我们首先要创建webdemo的项目框架并添加软件包管理相关的内容。

项目目录结构

➜ ~/programming/python/webdemo git:(master) ✗ $ tree .
.
├── LICENSE
├── README.md
├── requirement.txt
├── setup.cfg
├── setup.py
└── webdemo
    └── __init__.py

1 directory, 6 files
这个是一个最简单的Python项目目录:
  • 源代码放在子目录webdemo/
  • 然后包含了软件包管理的所需的文件:setup.py, setup.cfg, requirements.txt
  • LICENSE和README

软件包管理相关

首先是setup.py,就是这么简单:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import setuptools


# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
        import multiprocessing  # noqa
except ImportError:
        pass


setuptools.setup(
    setup_requires=['pbr'], pbr=True)
然后是setup.cfg:
[metadata]
name = webdemo
version = 0.0.1
summary = Web Application Demo
description-file = README.md
author = author
author-email = author@example.com
classifier =
    Environment :: Web Environment
    Intended Audience :: Developers
    Intended Audience :: Education
    License :: OSI Approved :: GNU General Public License v2 (GPLv2)
    Operating System :: POSIX :: Linux
    Programming Language :: Python
    Programming Language :: Python :: 2
    Programming Language :: Python :: 2.7

[global]
setup-hooks =
    pbr.hooks.setup_hook

[files]
packages =
    webdemo

[entry_points]
console_scripts =
只包含最基本的信息。接下来是requirements.txt文件:
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.

pbr<2.0,>=0.11
目前只依赖于pbr库。源代码目录下现在只有一个空的init.py文件。我们已经搭建好了这个最简单的项目框架了。首先,我们要把这些代码提交到git库,然后打上tag 0.0.1
➜ ~/programming/python/webdemo git:(master) ✗ $ git log --oneline
697427c Add packaging information
2cbbf4d Initial commit
➜ ~/programming/python/webdemo git:(master) ✗ $ git tag -a -s 0.0.1
➜ ~/programming/python/webdemo git:(master) ✗ $ git tag
0.0.1
然后就可以使用python setup.py sdist命令来生成一个0.0.1版本的源码归档了:
➜ ~/programming/python/webdemo git:(master) ✗ $ python setup.py sdist
running sdist
[pbr] Writing ChangeLog
[pbr] Generating ChangeLog
[pbr] Generating AUTHORS
running egg_info
writing pbr to webdemo.egg-info/pbr.json
writing webdemo.egg-info/PKG-INFO
writing top-level names to webdemo.egg-info/top_level.txt
writing dependency_links to webdemo.egg-info/dependency_links.txt
writing entry points to webdemo.egg-info/entry_points.txt
[pbr] Processing SOURCES.txt
[pbr] In git context, generating filelist from git
warning: no previously-included files found matching '.gitreview'
warning: no previously-included files matching '*.pyc' found anywhere in distribution
writing manifest file 'webdemo.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt

running check
warning: check: missing required meta-data: url

creating webdemo-0.0.1
creating webdemo-0.0.1/webdemo
creating webdemo-0.0.1/webdemo.egg-info
making hard links in webdemo-0.0.1...
hard linking AUTHORS -> webdemo-0.0.1
hard linking ChangeLog -> webdemo-0.0.1
hard linking LICENSE -> webdemo-0.0.1
hard linking README.md -> webdemo-0.0.1
hard linking requirement.txt -> webdemo-0.0.1
hard linking setup.cfg -> webdemo-0.0.1
hard linking setup.py -> webdemo-0.0.1
hard linking webdemo/__init__.py -> webdemo-0.0.1/webdemo
hard linking webdemo.egg-info/PKG-INFO -> webdemo-0.0.1/webdemo.egg-info
hard linking webdemo.egg-info/SOURCES.txt -> webdemo-0.0.1/webdemo.egg-info
hard linking webdemo.egg-info/dependency_links.txt -> webdemo-0.0.1/webdemo.egg-info
hard linking webdemo.egg-info/entry_points.txt -> webdemo-0.0.1/webdemo.egg-info
hard linking webdemo.egg-info/not-zip-safe -> webdemo-0.0.1/webdemo.egg-info
hard linking webdemo.egg-info/pbr.json -> webdemo-0.0.1/webdemo.egg-info
hard linking webdemo.egg-info/top_level.txt -> webdemo-0.0.1/webdemo.egg-info
copying setup.cfg -> webdemo-0.0.1
Writing webdemo-0.0.1/setup.cfg
Creating tar archive
removing 'webdemo-0.0.1' (and everything under it)
➜ ~/programming/python/webdemo git:(master) ✗ $ ls dist
webdemo-0.0.1.tar.gz
➜ ~/programming/python/webdemo git:(master) ✗ $ ls
AUTHORS  ChangeLog  dist  LICENSE  README.md  requirement.txt  setup.cfg  setup.py  webdemo  webdemo.egg-info
验证成功,在dist/目录下生成了一个0.0.1版本的源码归档,同时生成了如下的文件和目录:AUTHORS, ChangeLog, webdemo.egg-info关于作者: 刘陈泓,现任UnitedStack平台开发部开发工程师,主要关注OpenStack的身份认证和计费领域。
了解详情

分享|奔跑吧!OpenStack容器站(二):Docker Kubernetes Neutron下的网络

奔跑吧,OpenStack!容器专场的分享来到第二篇,来自UnitedStack网络工程师秦天欢的《Docker Kubernetes Neutron下的网络》,文末可以下载演讲PPT,观看演讲视频。在正文开始之前,小编想先聊聊关于关于网络的事儿,和全文有关,但也没有完全关系。
  网络的作用一直就是通道,可以类比公路、水管或者是下水道,只是其中流通的内容是数据。这是网络的本质,不论是在传统物理IT层面,还是在云计算以及容器的今天,网络一直都在执行这样的使命。比如,负载均衡其实就是给数据分类提高速度,相当于分出了快车道慢车道;而VPN隧道技术就是在大管子里套了一个小管子,让小管子里的数据更加安全。到了云计算时代,OpenStack孕育出Neutron,在Docker中孕育出libnetwork,他们的作用都是把OpenStack或者是Docker中的数据与其他网络实现互联互通,或者更形象的说法是,修了一个河道,实现了一个池塘(新平台上的数据)与其他池塘(物理网络)之间的沟通。 本次分享的话题《Docker Kubernetes Neutron下的网络》,其实就是讲了三个池塘如何与内外沟通的故事。 下载PPT:docker-k8s-neutron下的网络 视频观看地址:http://www.stuq.org/course/detail/1014 先把三个河道的区别show出来,如图: Docker:
- 提供libnetwork,客户端直接调⽤用libnetwork进⾏行⺴⽹网络相关的配置 Kubernetes:
- 基于分布式协调服务(如etcd/zookeeeper等)提供事件订阅 - 分布式协调服务同时保存元数据 Neutron:
- 数据库保存元数据,数据库在环境就在 - 基于消息进⾏行事件的订阅及响应 一Docker网络 Docker网络是通过Linux Bridge建立网桥的形式来实现数据沟通的,数据如何走有两种模式,不通过外网(池塘内)的,就是容器(紫色)之间通过Docker0实现二层转发,从而实现容器之间的数据通信。另一种方式,是需要容器与外网通信(池塘的水流向外部)的,这种类型相对复杂,其实就是通过一个IP Tables的规则来制作的一个NAT(类似于池塘与大海之间的一个阀门)。如图所示: 特别说明,自从Docker快速发展,已经从一个大项目,发展成各自独立的分支,成为package 。libnetwork 就是Docker里的网络部分。以下是libnetwork的一些概念: 关于三个核心概念: Sandbox:可以理解为namespace,或者传统意义上的虚拟机; Endpoint:连接起虚拟机和网桥的部件,当Sandbox要和外界通信的时候就是通过Endpoint连接到外界的,最简单的情况就是连接到一个Bridge上。 Network:网络,更加贴切点的话可以认为是Neutron中的一个拥有一个subnet的网络。 关于五个编程对象(结构体): * NetworkController。用于获取一个控制器,可以认为通过这个控制器可以对接下来的所有网络操作进行操作。Neutron中并没有这么一个概念,因为Neutron中的网络是由agent通过轮询或者消息的方式来间接操作的,而不是由用户使用docker命令直接在本机进行操作。 * Driver。这里的Driver类似于Neutron中的core_plugin或者是ml2下的各种driver,表示的是底层网络的实现方法。比如有bridge的driver,也有基于vxlan的overlay的driver等等。这个概念和Neutron中的driver概念基本上是一样的。 * Network。这里的Network结构体就是对应的上面的Network,表示建立了一个网络。 * Endpoint。这里的Endpoint结构体就是对应上面的Endpoint,通过这个结构体可以对Endpoint进行操作。 Sandbox。这里的Sandbox结构体就是对应上面的Sandbox,表示建立了一个独立的名字空间。可以类比Nova的虚拟机或者是Kubernetes的Pod,亦或是独立的Docker容器。 一般使用libnetwork的方法,具体的步骤如下图: 1. 获取一个NetworkController对象用于进行下面的操作。获取对象的时候指定Driver。 2. 通过NetworkController对象的NewNetwork()建立一个网络。这里最简单的理解就是现在我们有了一个bridge了。 3. 通过网络的CreateEndpoint()在这个网络上建立Endpoint。这里最简单的理解就是每建立一个Endpoint,我们上面建立的bridge上就会多出一个VIF口等着虚拟机或者Sandbox连上来。假设这里使用的是veth,则veth的一头目前接在了bridge中,另一头还暴露在外面。 4. 调用上面建立的Endpoint的Join方法,提供容器信息,于是libnetwork的代码就会建立一个Sandbox对象(一般这里的Sandbox就是容器的namespace,所以不会重复建立),然后将第三步建立的veth的一头接入到这个Sandbox中,也就是将其放到Sandbox的namespace中。 5. 当Sandbox的生命周期结束时,调用Endpoint的Leave方法使其从这个Network中解绑。简单的说就是将veth从Sandbox的namespace中拿出来回到物理机上。 6. 如果一个Endpoint无用了,则可以调用Delete方法删除。 7. 如果一个Network无用了,则可以调用Delete方法删除。 二Kubernetes网络 当有多台物理机的时候,如何实现通信,这不是Docker解决的,而是通过调用Docker的东西解决的,也就是通过控制器解决,Kubernetes就是其中的一种。假设有四个物理机,有一台物理机是控制节点,其他三台是用来提供容器服务的。物理机之间的通信,首先就是要保持网络互通的,联通的方法很多。下图是用VXLAN连接的示意图: 除了数据流向,Kubernetes中还有一些重要概念,比如:servicekube-proxy以及pause容器。其中,service:⽤用于提供VIP;kube-proxy:负责service相关的iptables规则建⽴立以及提供proxy,因此有人担心kube-proxy会成为瓶颈。pause容器为Pod提供一个⺴络 的Namespace,每个Pod⼀一个。Kubernetes中的服务不是以容器为单位提供,而是以Pod为单位提供的。 三Neutron网络 Neutron是OpenStack网络中的概念,提供了云平台上数据的流动和管理方法,相对于之前两个概念,发展时间较长,也较成熟,自动化程度较高。 Neutron server 收到信息之后,把网络的信息写道数据库中, 发消息给接听的Agent,Agent去数据库查询,去物理机上进行配置。这样的好处是:只要数据库在,信息就可以恢复;Agent可以有很多类型,DHCP、L3、OVS、SR-IOV、各种SDN Agent…… 此时,需要一个重要的概念登场——ML2.The Modular Layer 2 (ml2) plugin是一个CORE PLUGIN,继承NeutronPluginBaseV2 将物理⺴络根据TYPE(VLAN/VXLAN/GRE...)做了规范,因此⽀支持多种Mechanism Driver。翻译过来,就是ML2让不同网络技术(方言)之间可以实现沟通,就是国际会议上面使用的“翻译器”,工作原理如下图: 总结起来,就是网络控制器和调度器一直都在蓬勃发展,但是类似于iptables/NAT、route、namespace 、linux bridge/ovs以及vlan/vxlan/各种tag都是网络的基础组件,他们很少有变化,这也是一个通道能够稳定运行的基础。 关于演讲人:

秦天欢 UnitedStack网络工程师

秦天欢先后在IBM、阿里巴巴从事运维和开发工作,目前专注于Neutron相关的开发,深入研究OpenStack、容器技术及运维自动化等方面的课题。 下载PPT:docker-k8s-neutron下的网络 视频观看地址:http://www.stuq.org/course/detail/1014
了解详情

分享|奔跑吧!OpenStack容器站(一):Docker与缓存服务

奔跑吧,OpenStack!系列上周日继续,这一站,容器!虽然当天与阅兵预演封路,影响了大家出行,但是仍有很多朋友从北京各处赶来,赶到上地云基地,聆听一场OpenStack和Docker的盛宴。甚至有朋友提前一个多小时入场,这让我们非常感动,送给他的除了早鸟奖励还有我们的感激。 关于本次活动的分享我们分为两个部分,今天整理的是由陈迪豪同学分享的《Docker与缓存服务》。本次分享PPT下载:docker_and_cache_service
一UnitedStack和OpenStack对Docker的支持
在2014年9月我们上线了CoreOS镜像,很多开发者都支持CoreOS是第一个也是目前最流行的为容器优化的操作系统,UnitedStack是国内第一个支持CoreOS的IaaS厂商,并且发布了《在UOS上体验CoreOS》和《从运维角度看CoreOS》两篇文章。 在2015年6月我们还上线了Fedora Atomic镜像,这是开源巨头Redhat力推的容器操作系统,我们也是国内首家支持Atomic镜像的基础云服务厂商,并且是国内唯一一家同时支持CoreOS和Atomic系统的服务商。 目前我们也积极与Docker创业公司合作,为Docker爱好者提供最好的基础服务。在未来,我们将基于Magnum项目提供像亚马逊ECS这样的容器服务,打造容器与虚机结合的良好生态。
二基于Docker和Trove实现的缓存服务
缓存服务,就是将缓存实例像水电那样交付给用户,一个基本的缓存服务应该支持通过Web UI或API来管理实例。并且提供启动、停止、重启实例等功能,可以随时添加replication从节点,可以修改实例的配置,可以随时扩容,并且由系统实现监控功能。 我们的UOS缓存服务已经能支持上述功能,我们还做了什么呢?首先将服务的启动时间优化到10秒以内,用户的单个集群可以轻松突破一百万读QPS,并且缓存数据持久化到SSD,而服务的安全由Neutron实现的SDN网络来支持。 前面提到我们的缓存服务是基于Trove和Docker实现的,那么Trove是什么呢?Trove是OpenStack中Database as a Service的服务,它是可拓展的可靠的服务,支持多租户和资源隔离,提供的数据库服务解放了DBA的运维,最重要的是它支持关系型数据库和非关系型数据库 Trove的基本框架包括trove-api、taskmanager和guestmanager,其中用户通过界面或RESTful请求trove-api,然后转发给taskmanager,由taskmanager执行重启缓存实例、修改配置等操作,而真正发送命令重启实例的操作时在虚拟机内部的guestagent。 基于Trove框架,我们很容易就可以实现Cache as a Service了,那么我们是如何结合Docker的呢? 从第一个维度,我们使用Docker容器化所有Trove的进程,通过在容器的隔离我们实现了更灵活的开发环境。我们组内多个开发者使用的Trove代码不同,使用Docker封装后可以快速启停容器,多人开发也能互不干扰。 第二个维度是我们在未来计划实现guestagent container,前面提到过我们使用虚拟机来隔离缓存实例的环境,这样可以带来更安全的资源隔离,但也有更大的overhead。如果使用Docker,可以让缓存服务的启动时间进一步缩短,并且带来更平滑的扩容操作。 对于Trove这个开源项目,我们做了很多工作,其中包括实现的Redis的后端,并且将数据库的监控集成到Ceilometer上,还写了Dockerfile将进程运行在容器内。这些工作,我们未来都会开源到 https://github.com/unitedstack ,欢迎关注。
三未来的工作
最后介绍下我们未来的工作,首先是Dockerize everything,除了我们已经容器化的Trove服务,我们还计划将它所以来Nova、Glance等服务封装在容器内,这样任何开发者都可以在本地运行整个缓存服务了。其次是我们计划使用Docker这样轻量化的资源隔离技术,一旦两面两点都实现了,我们将开发自己的编排工具来调度这些容器服务。 而我们分享的主题Cache as a Service for OpenStack with Trove and Docker也提交到OpenStack Summit的会议上,如果有机会在今年年底在日本东京可以分享下我们的实践与收获。 本次分享PPT下载:docker_and_cache_service 关于作者: 陈迪豪,UnitedStack有云的基础架构工程师,目前专注于Docker、OpenStack社区。Docker监控管理工具Seagull项目作者,开源电子书《理解Linux进程》作者。
了解详情

帅小伙、大叔、高富帅都怎么玩OpenStack?

关于云计算落地有各种各样的猜测,但是真正了解这个过程的却并不多。本文是UnitedStack有云产品VP袁东在云头条的一次分享,把在向云迁移过程中的三类典型用户抽象成具体人物,个性鲜明。虽然都有真是真事儿,但是大家不要较真到底是谁,因为这样的故事在这些行业频繁的上演。也许,帅小伙儿说的就是你? 今天来讲一个帅小伙、大叔和高富帅的故事,这三个人物实际上是我们做的三个典型项目的客户给我留下的感觉。希望通过这三个项目和大家分享一下不同的行业对云计算的思考方式的差异。 帅小伙:来自IDC行业 (故事背景:IDC是个水很深的行业,帅小伙的背后我也不知道有没有背景) 当时这个项目做下来也奇葩,本来人家第二天要跟别人签合同了,硬件都采购完了——然后被我们抢单了。 在和帅小伙交流的时候也很有意思,他们最关心的问题是:云计算这个东西,怎么为他们挣钱!但凡IDC行业转型云计算,出发点几乎都是这一点:怎么用云计算挣钱。把这个问题讲清楚了,单子就到手了。 不过单子到手不算完,正当我们打算弹冠相庆的时候,帅小伙就把一大堆定制化需求糊了我们一脸。果然,这才是做项目的节奏——事情本来就会这么简单嘛。 这一坨定制化需求从按月导出客户对账记录,一直到公网IP的分配,再到各种打折、促销和代金券……搞得我们动物园里面的产品汪和程序猿一阵狗吠猿闹。 等到都坐下来了,我们总结了一下:以帅小伙为代表的IDC客户的核心需求是“运营”! 如果一个云计算系统在满足IDC云化的基础上,能很好的解决“运营”这个痛点,那么基本上就成功了 如果在能够解决他们的定制化需求,那他们估计就乐坏了。 大叔算是制造业的 (故事背景:老国企转型,几十年的历史,比我们年纪都大。大叔老树开新花,赶着互联网+的热潮,也全盘云化。) 大叔的想法,既然搞了,就搞最时髦的——直接上OpenStack!我们就是这样进去的。 这个项目完全是标准方案,直到系统搭好都一帆风顺。但是,就像我们都明白的那样,没有坑的项目,绝对不是好项目。 大叔的项目完全由集成商包办,里面的人际关系估计比OpenStack的MQ交互还复杂。不过这不是重点,重点是不知道集成商从哪里找来的人搭应用,很显然搭应用的和写应用的还不是一伙儿人,估计集成商也找不到写系统的人了。然后悲剧发生了,应用搭在我们的平台上,怎么都不好使:不是测试性能巨差,就是系统崩溃…… 其实都和我们的平台没关系,但是大叔不管这个,大叔的管家(集成商)更不管这个。应用跑不起来、测试过不去,就别想验收拿钱。 结果就是我们从把整个应用平台搞了一遍,从数据库到中间件再到WebLogic,最夸张的时候我们还动手帮他们改应用配置,定位Java程序的Bug。 最后业务跑起来了,验收通过了,大叔满意了,我们的人也都成了业务专家。 小结:大叔代表的传统或者细分一下,制造业。需求实际要的是一站式解决方案。诉求也很明确:你把我的业务支撑起来,我就给你钱。绝不可能说,你做IaaS的,我就跟你验收IaaS,然后你就可以退场了,这类客户只在乎业务。 云计算,很多时候他们并不是很关心。只要你能解决他们的业务,你就赢下了这个市场。 高富帅,银行生产系统的应用 !!!注意哦,是生产系统!虽然肯定不是核心记账系统,但也不是开发测试系统。 (前情交代:这个项目让我见识了什么是真正的高富帅。在这之前,我听说某行搞云计算,光规划搞了半年以上,我还不不太信。经历了这个项目我就懂了,我原来也以为是傻有钱傻有钱,但其实完全不是,听我讲完你就知道了。) 第一次去见这个客户,30人的会议室,18度空调。 一个感觉:热!!! 为什么热呢?因为好多好多人,某大厂牵头这个项目,我们算是合作伙伴,实际上是小弟弟。我们去了包括我在内的俩人,大厂去的人数大概是我们的10倍!高富帅的领导往中间一坐,后面呼啦啦跟着七八口人,包括弱电、机房、线缆……承担各种工种的人,我自始至终都没认全,汗。 三十几口人就伺候一个领导,这个派头我在大叔那里都没见到。我当时以为这个领导大概会很水,不然干嘛带这么多人?不过项目做多了,真是开眼界。这个领导完全和我想的不一样。 人家从弱电怎么布,网络怎么走线,交换机几个口怎么化VLan,问到软件选型 最后连我们的存储是怎么保证MySQL断电时候的事务一致性都搞明白了…还顺便帮我们梳理了一把银行的业务。 绝对的高手! 人家带这么多人,是直接现场安排布置工作的。一天从早到晚,一个数据库大概怎么构建,脉络基本清晰了。基本上对方就是一个人在主导,剩下的30多口人在配合。 小结:这个故事让我们明白了两个道理:高富帅行业不愧钱多,藏龙卧虎。第二就是:关键应用,客户一定会把你的姥姥根给你刨出来。不搞明白你的运作机制,他是不敢上业务在你的平台上的,不然我们丢失的是单子,他丢的是乌纱帽。 总结一下就一句话:IDC看运营;传统企业看业务;银行要的是个放心。 精彩问答 因为问答实在精彩,小编只能摘录部分,并且按照产品、项目和技术进行分类,希望大家看起来清晰一些,一些问题的分类可能并不精准,请多包涵。 产品相关: Q:你们做的银行云案例、传统企业云案例,是不是私有云? A:不是传统私有云,是我们的托管云模式,都有VPN进去的。 Q:VPN进去做管理? A:是的。 Q:托管云也是私有云的一种? A:这个可能大家理解的差异,我们区别的是这个云是不是客户自己维护,客户维护是私有云,我们维护是托管云。 Q:你们的方案是完全开源还是闭源的?客户可以在你们的产品上自己定制开发定制吗? A:完全开源,给源代码。我们有一家IDC客户就是自己定制的,完全看不出是我们的东西,我们都很佩服。 Q:SSD做Cache Pool性能如何?我记得你们一般用3*SSD加SATA。 A:我们的存储分为容量型存储和性能型存储,对于大部分客户都可以满足要求。关于三的倍数是因为我们的存储有三个副本,所以要求是3的倍数。 Q:12个Node为一个Ceph Cluster,这是为啥考虑? A:12个节点不是Ceph Cluster,是我们的一个标准方案。 Q:那每12个Node之间的VM和数据能迁移吗? A:可以的。 Q:假如一个VM在故障域A需要迁移到故障域B,这个时候如何在线迁移? A:底层如果是两套Ceph,现在只能停机迁移。 Q:这样的设计不就形成了很多独立的Pool了吗? A:12Node-100Node是一个Pool,我们这里容灾域不是Pool的概念,是Ceph Crush的概念。 Q:我想了解的事,私有云考验你们的定制化交付能力,你们大概有多少人占比铺在定制化实施上,这个怎么和你们的公有云功能维护平衡? A:公有云是我们为自己运维的一个托管云,跟运维客户的云说一样的。 Q:大叔、高富帅他们之间的区别,用配置就可以解决了,对吗? A:是的,一套代码,没有专有分支,不然维护成本太高了。修一个Bug,各种BackPort。 Q:高富帅、大叔和帅小伙每个都需要定制化,会不会很累? A:除非放弃这个行业,不然这个行业的需求必需满足。 项目相关: Q:既然已经实施了这么多基于OpenStack的云,客户使用反响如何?你们的后续维护情况如何? A:这个问题怎么回答呢?大部分还不错,虽然还存在大大小小的问题,但是从用户的实际购买来看,不断有客户进行扩容。 Q:客户已经现网运行了,会让你们升级吗?比如银行。 A:可以的,因为业务不受影响。 Q:三个故事既各有特点,但是又说明了共性。这些和你们直接对接的应该就是家纺的项目经理。他们一般对内部的业务还是比较了解和熟悉的。说大地,你们按照项目来交付,其实就是弄清楚客户最终的终极需求,要完全的满足和解决他们的需求。 A:我们是在做项目中提炼产品,需求不可能是我们拍脑门想出来的,项目本身就是需求最好的来源,但是我们并不是为了做项目而做项目。 Q:你们的项目和行业做多了,又归纳客户分几类? A:当然有分类,基本就是按照行业分的,然后找行业共性的需求。一段时间内打一个行业。 Q:这三类就是最具代表性的吗? A:算是吧。 Q:可以看出你们这些有的还说有很强的系统集成意味,你们提供开源云技术/软件等服务居多。 A:是的,我们公司就是卖服务的。 Q:银行和政府核心业务现在用的是什么云? A:记账业务都是小机,这个估计可见的未来都上不了云。 Q:每个项目大概从需求沟通到验收,周期大概多长? A:行业差异很大,像帅小伙,一两周就搞定了(不含定制需求)。大叔项目,断断续续几个月吧,主要是人的问题。 Q:听说帅小伙儿那没业务啊? A:帅小伙已经在扩容第二个Region了。 Q:帅小伙儿们IDC云化以后,和公有云服务上比有竞争优势吗? 众筹回答(来自群内的高手): 1.机柜费用省了30%~40%,是占整个云化成本的30%~40%,还是非常惊人的。 2.云化少了搬箱子的工作 3.核心还是资源:带宽、电力,跟云没关系。 4.第一驱动力不是跟公有云用户抢客户,而是留住现有客户。一般客户租用了IDC后,粘性很强,没有特殊原因不会轻易换。但是现在很多客户问IDC要云,没有云就走了,所以......十几个人的IDC云,一年销售额7000万的万都见过,他们有很多客户资源。 5.所以有host private cloud,你不能云化的机器就在你边上,一根网线搞定。 技术相关: Q:基于IOE的应用移植到OpenStack平台会遇到哪些问题? A:这个问题有点大,IOE实际上要分开来看,I如果是小机的话,还说要重写业务吧;O如果是重要应用,比如RAC,还是不要上云了;E的情况最惨,因为ServerSAN对E的冲击最大,大部分情况是可以替换的。 Q:对那些传统开发模式/软件Oracledb、tuxedo、Weblogic的产品/系统,这些都是绕不过去的。 A:J2EE和Weblogic这些我在项目中都碰到过,问题不大。Linux—Based都好说。 Q:Oracle数据库,是跑盘柜还是Ceph? A:如果是Oracle RAC的话,还是跑阵列吧。 Q:单机呢? A:Oracle单机也可以跑在云上,这个问题不大。 Q:用户不会有分层存储的需求吗? A:目前没有遇到。 Q:你们的项目中有没有涉及到物理机接入的? A:有需求,现在我们还做不到管理物理机和管理虚拟机一样,Iron我们也在调研中。但是基本的物理机管理功能是有的,包括自动安装系统之类的功能。 关于分享者 袁冬博士,UnitedStack产品副总;云计算专家,在云计算、虚拟化、分布式系统和企业级应用等方面有丰富的经验;对分布式存储、非结构数据存储和存储虚拟化有深刻地理解,在云存储和企业级存储领域有丰富的研发与实践经验;Ceph等开源存储项目的核心代码贡献者。
了解详情

ArchSummit干货分享:通向企业级的 OpenStack 网络服务

在上周六落幕的ArchSummit深圳站上,Unitedstack有云的网络工程师王为进行了关于OpenStack 网络的主题演讲《通向高可用与分布式的 OpenStack 网络服务》,对 OpeStack 的网络发展做了一个综合性的总结和比较,也解答了对于OpenStack 网络复杂、Neutron 难维护、Overlay 网络性能低下的疑问。
前言
当我们提到 OpenStack 的网络,很多人会望而生畏,说 OpenStack 网络好复杂、Neutron 难以维护、Overlay 网络性能低下…… 这样的印象阻碍了 OpenStack 特别是 Neutron 在企业的部署脚步,事实上从 OpenStack 诞生起,其网络的模型和设计就一直在进化并且保持着高效、快速的迭代,特别是从 Neutron 诞生,Legacy 网络、Provider 网络、L3 HA、L2 Population、DVR、DragonFlow 相继提出,我们看到 Neutron 在其每一个 Cycle 都在向企业级的生产软件靠近,本文将尝试对 OpeStack 的网络发展做一个综合性的总结和比较。
从 Nova-network 说起
我们知道最初 OpenStack 只有 Nova 和 Swift 两个组件,所以 Nova 除了提供基础的计算服务,包括调度、块设备和网络也一并由 Nova 提供,当时的网络是什么样呢?为什么现在还有很多 Superuser 还在使用 Nova-network? 最开始,大家期望中的 OpenStack 网络是什么样的?
  1. 能给虚拟机提供 IP 地址;
  2. 虚拟机在需要时可以连通外网;
  3. 相同网络下的虚拟机之间允许内部通信;
  4. 一些虚拟机还希望能获得一个外网 IP 来对外提供服务。
最后特别对于公有云,租户间还需要保证网络隔离。 基于以上需求,Nova-network 提供了这样的参考模型(VlanManager+MultiHost): 首先,dnsmasq 进程绑定在租户的网桥上,用于提供 DHCP 服务,提供 IP 地址;然后,计算节点上配置默认路由并将一个网口连接至公网,这样虚拟机按默认路由发送的报文将被网桥以节点的默认路由送出,发往公网的接入层;同一租户的网络处于同于 Vlan,通过网桥广播允许其互相通信;不同租户的虚拟机如果则通过节点上的路由表路由到对应网桥并转发(见上图);如果虚拟机需要公网 IP,则可以在计算节点上直接起 NAT 规则对应到相应内网 IP。 整个模型很简单明了,全部使用 Linux 中较为成熟的网络技术, 所有路由选择由本地决定,不依赖某个单点,这个在 Nova-network 中被称为 MultiHost,是Nova-network 的重要特性,所以其一出世就获得了很多人的青睐。 但是 Nova-network 的缺点也是很明显的:
  1. 因为 Vlan 技术固有缺陷,一个 Region 下无法服务太多租户;
  2. 路由实现粗糙,路由决策和 NAT 依赖 IP 地址,所以很难实现Overlap IP,用户的 IP 管理不自由;
  3. 前面说不同租户(其实是不同网络)之间似乎可以在没有公网IP的情况下互香通讯,但这是有条件的,再看前面的图,我们看到如果想在计算节点下做路由决策,让数据包成功封装另一个租户的 Vlan,我们需要这个计算节点拥有另一个租户的网桥,而且因为这个链路的非对称性,对方节点也需要相同的要求。因为 Nova-network 的网桥是按需建立的(不然太多),所以其实这种通信是无法保障的。
最后,Nova-network 提供的网络高级功能很有限,只有基本的安全组,很难满足用户需求,而且将网络紧耦合在计算服务中也不符合云计算的架构,所以社区最终成立了 Neutron 项目。
Neutron 的艰难前行
Neutron 的诞生承载着大家对面向大型云基础设施的网络服务的期望,它在一开始就着手设计了基于 Overlay 网络的网络模型,通过先进的 VxLan 和 NVGRE 协议, Neutron 克服了很多在 Nova-network 中无法解决的网络问题。Overlay 网络是什么的,简单的说,它是一个逻辑网,运行在物理网之上,一般要求物理网 IP 可达,然后通过 UDP 等三层传输协议承载二层,形成 L2 over L3 的模型,这样我们就可以实现突破物理拓扑的任意自定义网络拓扑、Overlap IP 等。 首先针对 Nova-network 面临的几个问题,VxLan、NVGRE 等都支持上千万的租户数量,远远满足一般需求;其次通过 L2 over L3,用户完全实现了自定网络拓扑,没有 IP 地址的限制;不同网络间拥有不用的 VxLan tag,当需要在不同网络下互相通信时,可以通过路由器路由转换 VxLan tag,不再有种种限制。 针对 Nova-network 的高级功能匮乏的问题,借助灵活的网络模型和虚拟路由器的实现,Neutron 拥有自定义路由、VPNaaS、FWaaS 和 LBaaS 等多种高级功能。此外,由于 Neutron 定义良好的北向接口和 Plugin-extension 架构,它可以支持大量厂商的设备,用户拥有彻底的自主选择权,厂商拥有高度的自主开发空间。 既然我们说的这么好,为什么很多人对其都不满意呢?原因也很多:
  1. Neutron 使用了 Namespace、Open vSwitch、网桥、veth、iptables 等技术,其中有些内容,特别是 OVS 对很多人都是比较陌生的,而且在一开始,其稳定性也受人质疑,这让人们有了充分的质疑理由;
  2. 南北向通讯和跨网络通讯都依赖于网络节点,而这个节点在默认的模型下是单点。
  3. Overlay 网络的默认性能并不能让人满意,需要专业工程师或厂商设计方案和调优。
软件的复杂度随着软件功能的丰富和接口的复杂性的上升几乎是必然的,Open vSwitch 的稳定性和性能也一直在提升,所以社区决定要发动力量主要解决第二个问题。 首先是 HA,企业 IT 系统首先关心的,莫过于系统的稳定性,一个可靠的 HA 方案是社区首先考虑的。很多网络服务的高可用都是借助 VRRP 协议的,Neutron 也不例外,通过 Keepalived + conntrackd,实现 Master 和 Slave 共同维护 VIP,一旦 Master 挂掉了,VIP 将自动飘到 Slave 节点上,因为 conntrackd 始终在自动拷贝session 信息,所以理论上可以实现用户的无感知自动切换。 L3 HA 确实实现了高可用,但是东西流量还是没有优化啊,这里面一大原因是 VxLan本来支持组播的,但是 OVS 目前支持有限,我们总是不得把一些无效的 ARP 广播发送出去。比如说下图中,A 的广播包其实只对 3 和 4 有用,但是 2 和 5 也收到了: 如何优化,这里的问题是虚拟机不知道通信对方的位置,可是 Neutron 知道啊,Neutron 数据库中保存着每一个 Port 联接的虚拟机信息、其 IP、MAC 地址、其宿主机信息等等,所以如果有新的虚拟机建立起来,连接了网络,那么 Neutron 就往所有 Agent 发送消息,告诉他们新的 Port 的所有信息,大家就低头检查看看自己是不是也有这个网络的虚拟机,如果有就更新流表规则,将来要请求这个 IP 的 ARP 可以直接回应,如果没有就忽略。这就是 L2 Population 和 ARP responder。 OK,更加优化了一步,但是他也有问题啊,就是
  1. 因为消息是广播的,很耗费资源;
  2. 跨网络的通讯还要依赖于路由器;
  3. 它目前没办法和 L3 HA 共同工作!
为什么它无法和 L3 HA 共同工作呢,因为 L2 Pop 假定了每个 Port 都工作在一个固定的节点上,这样 L2 Pop 才能将 ARP 和 Tunnel 引过去,然而 L3 HA 破坏了这个假设…… Bug 的 report 见 Launchpad 上的 #1365476 ,目前尚未解决……
新的架构
这么说来,Neutron 在企业化道路上真实困难重重啊,怎么办,社区决定不能在旧的架构上修修补补了,让我们真正实现一个分布式虚拟路由(Distributed Virtual Routing 简称 DVR)吧! 由于过去的集中化的虚拟路由 L3 Agent 实现的很完整了,社区决定方案就是将其从单独部署在网络节点转为分布式的部署在所有计算节点上,每个计算节点都有自己的 Router Namespace,就像之前 Nova-network 在各个节点上都有自己的 Gateway 一样。 首先我们看虚拟机绑定公网 IP 情况下的公网流量: 当一个外部的报文需要和虚拟机通信时,首先会发到网桥 br-ex,然后 FIP Namespace 的 fg 设备响应其 ARP,再被路由到 fpr 上,进入 DVR Namespace,这里再通过 iptables 将公网 IP DNAT 为内网地址,发往虚拟机。内部网外部通信也是类似的道理。 对很多用户来说,南北流量不是他们最关心的问题,他们最关心的是东西流量:   因为现在路由器就在计算节点上,所以我们只需要在 Namespace 内完成路由就行了,这和以前在网络节点上是一样的。但是会出现多个计算节点上会存在同一子网的网关,怎么办?解决方案是为每一个计算节点生成唯一的DVR MAC 地址,在对外发出数据包之前,将原有 MAC 替换成 DVR MAC,保证双向通信的正常进行。 OK,我们解决了问题,但也引入了新的问题:
  1. 因为现在 ARP 应答无法跨计算节点,像 Allowed address pair 这样的扩展也无法工作了(回应非 Port 自己本身绑定的 IP 的 ARP 请求)。
  2. IO 路径比较复杂,且充斥着大量虚假 ARP 应答,增加了运维难度。
针对 DVR 的这些问题,社区另一拨人提出一种新的架构,他们称之为 SDN way。那就是我们看到所有流表都是 Neutron 主动下发的,而不是像 OpenFlow 那样首包上送,我们能不能实现一个基于首包上送的反应式控制器呢? 于是 DragonFlow 被提了出来,其特点是未知流量首包上送到控制器,控制器知道一切,下发流表规则,这样东西向通信的其余流量就都可以都直接走到对方计算节点了。 比较有特点去谈的就是这个流表了:   但是这样控制器的性能会成为瓶颈吗?DragonFlow 团队声称的性能提高真的可靠吗?恐怕无论 DVR 还是 DragonFlow 都还是需要真正生产环境的考验。
第三方的发展
前面说到因为 Neutron 的 Plugin-extension 架构,给了厂商良好的自定义空间,所以 Neutron 的第三方解决方案也是层出不穷,这里简单谈谈 NSX 与 Midonet。 NSX 改造自原 Nicira 的 NVP,据 VMware 宣称其应用在了多家云计算系统中,但我们外界所知资料并不是很丰富,下面的图介绍了 NSX 对东西流量的处理,可见与 DVR 有相似之处: 其优点是拥有良好的商业公司支持,缺点是价格高昂、无法自主可控。 再说一个新秀 Midonet,是 Midokura 公司提出的网络解决方案,与今年年中宣布开源,实现了很多企业级的特性,比如 BGP 的支持、Tunnel Zone、DoS抵御隔离的支持等等,但是对我们来说最吸引人的,是其基于 Java 重新设计的全新的软件架构。 Midonet 有几个组件,分别是
  • Cluster:保存集群状态,同步信息,检查外部设备等,依赖于 Zookeeper 和 Cassandra;
  • midonet-util:一些其它模块用到的工具类;
  • midolman:类似于 Neutron 中的 Agent,
  • midonet-api:实现 API;
  • netlink:与内核通信用模块,基于 Linux netlink;
  • odp:于内核的 Open Datapath 通信用模块。
Midonet 充分借助了已有成熟的分布式软件降低自己本身的复杂性,而且只使用了 Open vSwitch 的 Datapath 模块,使转发和控制更加灵活,不失为一个好的设计。但是其企业级服务还需要定制,对社区的部分高级功能也支持有限,这也是它的缺点。
总结
最后我们以一个表格做总结: 注1: NSX 价格还需要额外购买 SNS 支持服务,数据来自http://technodrone.blogspot.com/2015/02/vmware-integrated-openstack-cost.html 注2:“ ✔*”表示支持有限,非全部支持,或数据可能不明确。
会场花絮
关于作者
王为,UnitedStack有云SDN 工程师,专注于虚拟网络和 SDN 方向,OpenStack 等多个开源社区的活跃贡献者。
了解详情

Kubernetes与OpenStack容器服务

UnitedStack有云课堂继续!Kubernetes课程分享了《搭建单机Kubernetes开发环境(1)》和《在UOS搭建Kubernetes集群》,受到大家欢迎,该系列第三篇文章介绍Kubernetes与OpenStack容器服务。 背景介绍 OpenStack作为最流行的IaaS项目吸引了大量开发者的关注,而同样受到广泛关注的还有容器项目Docker。Docker基于Linux容器技术提出了一种新的应用打包方式,开发者在本地开发的程序可以无隙迁移到服务器,并且没有任何环境依赖,加上Google推出的容器调度系统Kubernetes,为开发者搭建基于容器的私有云提供了新的思路。有人不禁怀疑OpenStack提供的虚拟机云服务已经过时了吗?显然没有,虚拟机和容器本质上就是不同层次的虚拟化技术,虚拟机更重也更安全,容器很快但也存在安全隐患。 本文在《搭建单机Kubernetes开发环境(1)》和《在UOS搭建Kubernetes集群(2)》之后,介绍了在OpenStack基础之上搭建的容器服务,同时我们还推出了《Magnum开发体验教程》方便大家学习。 容器服务 继IaaS(Infrastructure as a Servce)、PaaS(Platform as a Service)、SaaS(Software as a Service)之后,最近火了CaaS(Container as a Service),也就是容器服务。 容器服务允许开发者在本地开发,将程序封装到容器内,并通过容器交付,开发者无需考虑底层硬件资源,由调度系统处理应用的分发。这样的好处是解决的开发者的环境依赖问题,当然不可避免得引入了新的问题,例如使用怎样的调度算法、如何监控和管理容器等。Kubernetes的出现给容器服务注入了新的生命,Google拥有十多年的容器使用经验,并100%开源了Kubernetes系统,使用Kubernetes很容器就可以搭建简单的私有容器服务了。前面的文章详细介绍过如果在本地和云平台UOS搭建Kubernetes,无论是裸机还是虚拟机我们都可以实现基于容器的私有云服务。 而容器技术在2015年OpenStack Summit更是受到广泛关注,适时推出了CaaS项目Magnum,而Magnum使用的底层调度系统,正是Google的Kubernetes。 Magnum项目 Magnum项目是Rackspace和Google合作推出的OpenStack项目,与其他OpenStack项目一样,Magnum有着良好的社区基础,项目模块化与代码质量有着很好的保证。通过这次Summit我们知道,Magnum的PTL Adrian Otto与Google工程师有着密切联系,他们也将致力于将Kubernetes整合到OpenStack生态中。 目前Magnum项目不是很成熟,需要解决容器初始化和安全认证的问题,但它提出了在虚拟机和Bare Metal创建容器服务的新思路。利用Magnum,我们可以在OpenStack上一键创建容器集群服务,利用Keystone实现租户的隔离,利用Neutron解决Docker网络问题,得益于OpenStack提供的基础服务,Magnum能够让容器技术在IaaS或者裸机上落地。 对容器服务感兴趣的不妨看看Magnum的演示视频。 未来展望 Docker的出现加速了容器的发展,Kubernetes的出现为容器私有云提供的新的思路,而Magnum的出现让OpenStack成为大一统的基础云平台,通过Magnum甚至可以将Kubernetes和Docker直接部署到物理机上。 UnitedStack作为云服务提供商,很早就关注到Docker和Magnum项目,也会继续关注和支持容器服务,如果需求可以到我们的企业容器服务交流群聊聊 462294386。 关于作者: 陈迪豪,UnitedStack有云的基础架构工程师,目前专注于Docker、OpenStack社区。Docker监控管理工具Seagull项目作者,开源电子书《理解Linux进程》作者。
了解详情