文章目录
  1. 1. 简介
  2. 2. 示例
    1. 2.1. Hello World
    2. 2.2. 输入输出
    3. 2.3. 安装软件
    4. 2.4. 初始化数据库
    5. 2.5. 两层应用
    6. 2.6. 关系定制化
    7. 2.7. 限定需求资源
    8. 2.8. 节点模板替换
    9. 2.9. 节点模板组
    10. 2.10. YAML宏
    11. 2.11. 传参
    12. 2.12. 取动态值

TOSCA(Topology and Orchestration Specification for Cloud Applications)是由OASIS组织制定的云应用拓扑编排规范。通俗地说,就是制定了一个标准,用来描述云平台上应用的拓扑结构。目前支持XML和YAML,Cloudiy的蓝图就是基于这个规范而来。这个规范比较庞大,本文尽量浓缩了TOSCA的YAML版前两章,以便用尽量少的时间了解尽量多的规范内容。

简介

TOSCA的基本概念只有两个:节点(node)和关系(relationship)。节点有许多类型,可以是一台服务器,一个网络,一个计算节点等等。关系描述了节点之间是如何连接的。举个栗子:一个nodejs应用(节点)部署在(关系)名为host的主机(节点)上。节点和关系都可以通过程序来扩展和实现。

目前它的开源实现有OpenStack (Heat-Translator,Tacker,Senlin),Alien4Cloud,Cloudify等。

示例

Hello World

首先登场的是广大程序猿和攻城狮们都喜闻乐见的Hello World,但是其实里面并没有Hello World,只是比较简单而已。先看下面这段描述文件:

tosca_definitions_version: tosca_simple_yaml_1_0

description: Template for deploying a single server with predefined properties.

topology_template:
  node_templates:
    my_server:
      type: tosca.nodes.Compute
      capabilities:
        host:
          properties:
            num_cpus: 1
            disk_size: 10 GB
            mem_size: 4096 MB
        os:
          properties:
            architecture: x86_64
            type: linux 
            distribution: rhel 
            version: 6.5 

除了TOSCA的版本tosca_definitions_version和描述信息description以外,就是这个topology_template了。这里我们看到有一个名为my_server的节点,它的类型是tosca.nodes.Compute。这个类型预置了两个capabilities信息,一个是host,定义了硬件信息;另一个是os,定义了操作系统信息。

输入输出

再看看下面这个描述文件:

topology_template:
  inputs:
    cpus:
      type: integer
      description: Number of CPUs for the server.
      constraints:
        - valid_values: [ 1, 2, 4, 8 ]

  node_templates:
    my_server:
      type: tosca.nodes.Compute
      capabilities:
        host:
          properties:
            num_cpus: { get_input: cpus }
            mem_size: 2048  MB
            disk_size: 10 GB

  outputs:
    server_ip:
      description: The private IP address of the provisioned server.
      value: { get_attribute: [ my_server, private_address ] }

这里的inputsoutputs分别定义了输入和输出。输入的cpus是在1,2,4和8中的一个整数,而输出的server_ip就是my_server这个节点的private_address也就是私有IP地址。另外一点是TOSCA提供了一些内置函数,在上面这个文件中使用了get_inputget_attribute。输入参数可以通过get_input被使用。

安装软件

第三个描述文件如下:

topology_template:
  inputs:
    # 略

  node_templates:
    mysql:
      type: tosca.nodes.DBMS.MySQL
      properties:
        root_password: { get_input: my_mysql_rootpw }
        port: { get_input: my_mysql_port }
      requirements:
        - host: db_server

    db_server:
      type: tosca.nodes.Compute
      capabilities:
        # 略

我们看到了一个新的节点类型:tosca.nodes.DBMS.MySQL。这个类型允许接收root_passwordport的参数。在requirements里定义了mysql这个节点需要被安装到db_server这个节点上,这就是“关系”。如果只想表明依赖,比如说service_a依赖于service_b,也可以直接用- dependency: service_b来描述。上面文件的拓扑结构如下图:

初始化数据库

第四个描述文件如下:

  node_templates:
    my_db:
      type: tosca.nodes.Database.MySQL
      properties:
        name: { get_input: database_name }
        user: { get_input: database_user }
        password: { get_input: database_password }
        port: { get_input: database_port }
      artifacts:
        db_content:
          file: files/my_db_content.txt
          type: tosca.artifacts.File
      requirements:
        - host: mysql
      interfaces:
        Standard:
          create:
            implementation: db_create.sh
            inputs:
              db_data: { get_artifact: [ SELF, db_content ] }

    mysql:
      type: tosca.nodes.DBMS.MySQL
      properties:
        root_password: { get_input: mysql_rootpw }
        port: { get_input: mysql_port }
      requirements:
        - host: db_server

    db_server:
      # 略

这里的tosca.nodes.Database.MySQL表示一个MySQL数据库的实例。在artifactsdb_content里指定了一个文本文件,而这个文件将被interfaces里的Create所用,为db_create.sh脚本提供数据。Standard表示生命周期,可能会包含configurestartstop等各种操作,而db_create.sh本身是对tosca.nodes.Database.MySQL提供的默认create操作的一个重写。如下图:

两层应用

再来看看第五个描述文件:

  node_templates:
    wordpress:
      type: tosca.nodes.WebApplication.WordPress
      properties:
        context_root: { get_input: context_root }
        admin_user: { get_input: wp_admin_username }
        admin_password: { get_input: wp_admin_password }
        db_host: { get_attribute: [ db_server, private_address ] }
      requirements:
        - host: apache
        - database_endpoint: wordpress_db
      interfaces:
        Standard:
          inputs:
            db_host: { get_attribute: [ db_server, private_address ] }
            db_port: { get_property: [ wordpress_db, port ] }
            db_name: { get_property: [ wordpress_db, name ] }
            db_user: { get_property: [ wordpress_db, user ] }
            db_password: { get_property: [ wordpress_db, password ] }  
    apache:
      type: tosca.nodes.WebServer.Apache
      properties:
        # 略
      requirements:
        - host: web_server
    web_server:
      type: tosca.nodes.Compute
      # 略

    wordpress_db:
      type: tosca.nodes.Database.MySQL
      # 略
    mysql:
      type: tosca.nodes.DBMS.MySQL
      # 略
    db_server:
      type: tosca.nodes.Compute
      # 略

这个文件描述了一个很常见的拓扑结构:mysql里有一个wordpress_db,运行在db_server上;apache部署了一个wordpress,运行在web_server上。wordpress需要wordpress_db

关系定制化

第六个描述文件:

  node_templates:
    wordpress:
      type: tosca.nodes.WebApplication.WordPress
      properties:
        # 略
      requirements:
        - host: apache
        - database_endpoint:
            node: wordpress_db
            relationship: my.types.WordpressDbConnection
    wordpress_db:
      type: tosca.nodes.Database.MySQL
      properties:
        # 略
      requirements:
        - host: mysql
  relationship_templates:
    my.types.WordpressDbConnection:
      type: ConnectsTo
      interfaces:
        Configure:
          pre_configure_source: scripts/wp_db_configure.sh

这里的关注点是relationship里的my.types.WordpressDbConnection。这是一个自定义的关系,在文件的下半部分描述了详细定义。它实际上是一个ConnectsTo类型,为pre_configure_source操作提供了一个自定义脚本。这个定义也可以单独提出一个文件,就像下面这样:

tosca_definitions_version: tosca_simple_yaml_1_0

description: Definition of custom WordpressDbConnection relationship type

relationship_types:
  my.types.WordpressDbConnection:
    derived_from: tosca.relationships.ConnectsTo
    interfaces:
      Configure:
        pre_configure_source: scripts/wp_db_configure.sh

限定需求资源

再看一个描述文件:

  node_templates:
    mysql:
      type: tosca.nodes.DBMS.MySQL
      properties:
        # 略
      requirements:
        - host:
            node_filter:
              capabilities:
                - host:
                    properties:
                      - num_cpus: { in_range: [ 1, 4 ] }
                      - mem_size: { greater_or_equal: 2 GB }
                - os:
                    properties:
                      - architecture: { equal: x86_64 }
                      - type: linux
                      - distribution: ubuntu

需要关注的是node_filter。这里并没有指定mysql在哪个节点上启动,但是指定了一些节点信息,只有符合的节点才能够启动它。也可以抽出来做个模板:

  node_templates:
    mysql:
      type: tosca.nodes.DBMS.MySQL
      properties:
        # 略
      requirements:
        - host: mysql_compute

    mysql_compute:
      type: Compute
      node_filter:
        capabilities:
          - host:
              properties:
                num_cpus: { equal: 2 }
                mem_size: { greater_or_equal: 2 GB }
          - os:
              properties:
                architecture: { equal: x86_64 }
                type: linux
                distribution: ubuntu

数据库也可以使用:

  node_templates:
    my_app:
      type: my.types.MyApplication
      properties:
        admin_user: { get_input: admin_username }
        admin_password: { get_input: admin_password }
        db_endpoint_url: { get_property: [SELF, database_endpoint, url_path ] }         
      requirements:
        - database_endpoint:
            node: my.types.nodes.MyDatabase
            node_filter:
              properties:
                - db_version: { greater_or_equal: 5.5 }

上面指定了数据库的版本。也可以抽出来做个模板:

  node_templates:
    my_app:
      type: my.types.MyApplication
      properties:
        admin_user: { get_input: admin_username }
        admin_password: { get_input: admin_password }
        db_endpoint_url: { get_property: [SELF, database_endpoint, url_path ] }         
      requirements:
        - database_endpoint: my_abstract_database
    my_abstract_database:
      type: my.types.nodes.MyDatabase
      properties:
        - db_version: { greater_or_equal: 5.5 }

节点模板替换

再看一个描述文件:

  node_templates:
    web_app:
      type: tosca.nodes.WebApplication.MyWebApp
      requirements:
        - host: web_server
        - database_endpoint: db

    web_server:
      type: tosca.nodes.WebServer
      requirements:
        - host: server

    server:
      type: tosca.nodes.Compute
      # 略

    db:
      # 这是一个抽象节点
      type: tosca.nodes.Database
      properties:
        user: my_db_user
        password: secret
        name: my_db_name

这里的db是一个抽象节点,可以被下面的描述文件所替换:

topology_template:
  inputs:
    db_user:
      type: string
    # 略
  substitution_mappings:
    node_type: tosca.nodes.Database
    capabilities:
      database_endpoint: [ database, database_endpoint ]
  node_templates:
    database:
      type: tosca.nodes.Database
      properties:
        user: { get_input: db_user }
        # 略
      requirements:
        - host: dbms
    dbms:
      type: tosca.nodes.DBMS
      # 略
    server:
      type: tosca.nodes.Compute
      # 略

这里的database_endpoint是由database节点提供的database_endpoint。两个文件联系起来看,表明了上面的web_app不需要管db是什么样子的,有什么拓扑结构,它关心的只是database_endpoint。而下面由databasedbmsserver三个节点组成的模板正好可以提供database_endpoint,从而替换掉db这个抽象节点。另外,这样的替换也支持嵌套。

节点模板组

再看一个描述文件:

  node_templates:
    apache:
      type: tosca.nodes.WebServer.Apache
      properties:
        # 略
      requirements:
        - host: server
    server:
      type: tosca.nodes.Compute
        # 略
  groups:
    webserver_group:
      type: tosca.groups.Root
      members: [ apache, server ]

  policies:
    - my_anti_collocation_policy:
        type: my.policies.anticolocateion
        targets: [ webserver_group ]
        # 可以一起处理

这个例子表明了apacheserver应该是一组的关系。这样它们就可以一起被处理,比如说伸缩。

YAML宏

下面这个描述文件使用了宏来避免重复:

dsl_definitions:
  my_compute_node_props: &my_compute_node_props
    disk_size: 10 GB
    num_cpus: 1
    mem_size: 2 GB

topology_template:
  node_templates:
    my_server:
      type: Compute
      capabilities:
        - host:
            properties: *my_compute_node_props

    my_database:
      type: Compute
      capabilities:
        - host:
            properties: *my_compute_node_props

传参

先看一个描述文件:

  node_templates: 
    wordpress:
      type: tosca.nodes.WebApplication.WordPress
      requirements:
        - database_endpoint: mysql_database
      interfaces:
        Standard:
          inputs:
            wp_db_port: { get_property: [ SELF, database_endpoint, port ] }
          configure:
            implementation: wordpress_configure.sh           
            inputs:
              wp_db_port: { get_property: [ SELF, database_endpoint, port ] }

这个例子有两个inputs,前者指的是为所有操作都声明一个变量,后者指的是为configure这个操作声明一个变量。再看下一个文件:

  node_templates: 
    frontend: 
      type: MyTypes.SomeNodeType    
      attributes: 
        url: { get_operation_output: [ SELF, Standard, create, generated_url ] } 
      interfaces: 
        Standard: 
          create: 
            implementation: scripts/frontend/create.sh
          configure: 
            implementation: scripts/frontend/configure.sh 
            inputs: 
              data_dir: { get_operation_output: [ SELF, Standard, create, data_dir ] }

在这个例子里有两个get_operation_output,前者指的是将create操作的环境变量generated_url设置到url里,后者是将data_dir传递给configure操作。

取动态值

最后一个描述文件:

node_types:
  ServerNode:
    derived_from: SoftwareComponent
    properties:
      notification_port:
        type: integer
    capabilities:
      # 略
  ClientNode:
    derived_from: SoftwareComponent
    properties:
      # 略
    requirements:
      - server:
          capability: Endpoint
          node: ServerNode 
          relationship: ConnectsTo
topology_template:          
  node_templates:
    my_server:
      type: ServerNode 
      properties:
        notification_port: 8000
    my_client:
      type: ClientNode
      requirements:
        - server:
            node: my_server
            relationship: my_connection
  relationship_templates:
    my_connection:
      type: ConnectsTo
      interfaces:
        Configure:
          inputs:
            targ_notify_port: { get_attribute: [ TARGET, notification_port ] }
            # 略

这个例子里,类型为ClientNodemy_clientmy_connection关系的Configure操作上需要notification_port变量。这样的话,当类型为ServerNodemy_server连接过来时,就能取到它的notification_port变量,并设置到targ_notify_port环境变量里。有一点值得注意的是,真实的notification_port可能是8000,也可能不是。所以在这种情况下,不用get_property,而用get_attribute函数。

文章目录
  1. 1. 简介
  2. 2. 示例
    1. 2.1. Hello World
    2. 2.2. 输入输出
    3. 2.3. 安装软件
    4. 2.4. 初始化数据库
    5. 2.5. 两层应用
    6. 2.6. 关系定制化
    7. 2.7. 限定需求资源
    8. 2.8. 节点模板替换
    9. 2.9. 节点模板组
    10. 2.10. YAML宏
    11. 2.11. 传参
    12. 2.12. 取动态值