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操作的一个重写。如下图:
image004.pnguploading.4e448015.gif转存失败重新上传取消

两层应用

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

  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函数

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐