OpenDaylight搭建集群

目录

声明
摘要
多节点集群
参考文献

声明

官网的文档一直在变化,可能翻译时的文档跟目前文档有出入,请以官网文档为准。

摘要

集群是使多个进程和程序作为一个整体工作的机制。举例,当在google.com网站上查询资料时,查询请求好像只有一台web服务器在处理。实际上,查询请求是被一个集群中多台相互连接的web服务器处理的。类似地,OpenDaylight也可以有多个实例作为一个整体对外提供服务。

集群的优势:
- 可伸缩性:如果有多个OpenDaylight实例在运行,一般情况下比仅有一台实例会做更多的任务和存储更多的数据。也可以把数据分解成块(片),然后把数据散布到整个集群,或者在集群中的某个实例执行某个操作。

  • 高可用性:如果有多个OpenDaylight实例在运行,其中一个实例宕机,其它实例仍然可以工作和提供服务。

  • 数据持久化:手动重启或者宕机,不会丢失存储在OpenDaylight的任何数据.

下面这部分描述如何搭建集群。

多节点点集群

以下部分描述了如何用OpenDaylight搭建多节点集群。

部署注意事项

实现集群,需要注意如下事项:

  • 推荐最少3台机子来搭建多节点集群。也可以搭建只有两个节点的集群。但是,如果2台中有一台宕机,则集群就不可用。

    注意:这是因为OpenDaylight要求大部分节点需要运行,而一个节点显然不能称作是大部分节点。

  • 集群中的每个实例都需要有一个标识符。OpenDaylight通过使用节点role来达到这个目的。当在akka.conf文件中定义第一个节点的role为member-1时,OpenDaylight就使用member-1来标识该节点。

  • 数据片(译注:这个概念对于文章理解很重要,请查看wiki Share)用于存储OpenDaylight MD-SAL数据库所有数据或者某个片段的数据。例如,一个数据片可以存储所有的inventory数据,而另外一个数据片可以存储所有的topology数据。

    如果没有在modules.conf文件中指定一个module,且在module-shards.conf文件中也没有指定一个分片。默认所有的数据存放在default数据片中(该default分片必须在module-shares.conf文件预先定义好)。每个分片都有一个可配置的replicas。在module-shares.conf中可以指定replicas详情,replicas表示分片位于哪个节点(译注:比如memeber-1,memeber-2)。

  • 如果有一个3节点的集群,想要能够容忍任意节点的宕机,那么每个数据片的副本必须运行在3节点的集群上。(译注:意思是如果想在集群中某台机子宕机的情况下还能正常服务,没有数据缺失,那么你必须把每一个数据片复制到3个节点上)

    注意:这是因为OpenDaylight的集群实现要求大部分数据片都运行才能提供服务。如果只在集群中的2个节点定义数据片副本(译注:即该数据片只有两个复制)且其中一个节点宕机,相应的该数据片无法运作(译注:一个数据片无法成为大部分。虽然还有一个节点正常运行,但是也不运行的意思)

  • 如果有3个节点的集群且某个数据片在所有节点上定义副本,即使该集群只剩下两个节点在运行,该数据片仍然能够运行。注意如果剩下的两个节点又宕机一个,则该数据片将不可操作。

  • 推荐配置多个节点。集群中的某个节点启动,它会发送一个消息给所有的节点。然后该节点会发送一个加入命令给第一个响应它的节点。如果没有节点回应,该节点重复这个过程直到成功建立一个连接或者自己关闭这个过程。

  • 集群中某个节点持续一段时间(默认10秒,可配置)还是不可达后(译注:不能连接),该节点自动下线。一旦节点下线,你需要重新启动它才能再次加入集群。一旦重新启动的节点加入集群,它将自动从主节点同步数据。

集群脚本

OpenDaylight包含一些用于配置集群的脚本。

注意:脚本是位于OpenDaylight distribution/bin 目录下,该脚本在仓库的位置是distribution-karaf/src/main/assembly/bin/

使用集群脚本

这个脚本被用于设置控制器集群中某个节点的集群参数(指akka.conf,module-shares.conf中的参数)。用户重启节点才能生效设置的参数。

注意:这个脚本可以在任意时刻使用,甚至在控制器第一次被启动之前。

用法:

bin/configure_cluster.sh <index> <seed_nodes_list>

index:1..N之间的整数,N是节点的数量。这个表示脚本配置的是哪一个控制器节点。

seed_nodes_list:集群中所有节点组成的ip地址列表,用逗号或者空格分隔。

index指向的IP地址应该是正在执行脚本的节点的ip地址。当在多个节点运行这个脚本时,保证seed_node_list这个参数值一样,而index在1到N之间改变。

可选,数据片可以通过修改“custom_shard_configs.txt”文件,在更多粒度进行配置,该文件和该脚本在同一个目录下。想要更多信息可以查看此文件。

举例:

bin/configure_cluster.sh 2 192.168.0.1 192.168.0.2 192.168.0.3

上面的命令将配置节点2(ip地址192.168.0.2),该节点位于由192.168.0.1 192.168.0.2 192.168.0.3三台机子组成的集群中

搭建多节点集群

译注:这里需要注意一下步骤顺序,第10个步骤需要移到第2个步骤的下面进行,不然你将找不到第3步所说的配置文件。即你需要先启动karaf,然后运行feature:install odl-mdsal-clustering,退出karaf命令行界面后,在继续进行下面的步骤。

为了运行3节点集群的OpenDaylight,执行如下步骤:

首先选择3台机子用于搭建集群。之后在每台机子下做如下操作:

  • 1.拷贝OpenDaylight发布文件zip(译注:linux为tar.bz2)到机子上。

  • 2.解压发布版本文件。

  • 3.打开下面的.conf文件:

    configuration/initial/akka.conf
    configuration/initial/module-shards.conf
  • 4.每个配置文件做如下修改(译注:这里说的是akka.conf这个文件):

    每个文件找到如下行的情况,然后用该配置文件所在机子即OpenDaylight运行所在机子的主机名或者ip地址替换127.0.0.1:

    netty.tcp {
      hostname = "127.0.0.1"

    注意:对于集群中的每个节点该值是不一样的。

  • 5.找到如下所示的行,然后用集群中的所有节点的机子的主机名或ip地址替换127.0.0.1(译注:这里的意思是集群有几个节点,数组中就有几个记录,只是ip做相应的修改):

    cluster {
      seed-nodes = ["akka.tcp://[email protected]:2550"]
  • 6.找到如下部分,给每一个节点指定一个角色名。这里我们用member-1赋值给第一个节点,第二节点用member-2,第三个节点用member-3:

    roles = [
      "member-1"
    ]

    注意:这个步骤,在每个节点应该使用不同的节点名。

  • 7.打开configuration/initial/module-shards.conf文件,修改replicas属性,让每一个数据片复制到所有的节点

    replicas = [
        "member-1",
        "member-2",
        "member-3"
    ]

    仅供参考,可以查看下面给出的==配置文件示例==

  • 8.切换到/bin目录下。

  • 9.运行如下命令:

    JAVA_MAX_MEM=4G JAVA_MAX_PERM_MEM=512m ./karaf
  • 10.在karaf命令行中运行如下命令启用集群功能(译注:这步移到第三步)

    feature:install odl-mdsal-clustering

OpenDaylight此时应该运行在3个节点的集群上。你可以通过任意的节点访问存储在数据库中的数据。

配置文件示例

==akka.conf== 示例文件:

 odl-cluster-data {
     bounded-mailbox {
       mailbox-type = "org.opendaylight.controller.cluster.common.actor.MeteredBoundedMailbox"
       mailbox-capacity = 1000
       mailbox-push-timeout-time = 100ms
     }

     metric-capture-enabled = true

     akka {
       loglevel = "DEBUG"
       loggers = ["akka.event.slf4j.Slf4jLogger"]

       actor {

         provider = "akka.cluster.ClusterActorRefProvider"
         serializers {
                   java = "akka.serialization.JavaSerializer"
                   proto = "akka.remote.serialization.ProtobufSerializer"
                 }

                 serialization-bindings {
                     "com.google.protobuf.Message" = proto

                 }
       }
       remote {
         log-remote-lifecycle-events = off
         netty.tcp {
           hostname = "10.194.189.96"
           port = 2550
           maximum-frame-size = 419430400
           send-buffer-size = 52428800
           receive-buffer-size = 52428800
         }
       }

       cluster {
         seed-nodes = ["akka.tcp://opendaylight-cluster-data@10.194.189.96:2550",
                       "akka.tcp://opendaylight-cluster-data@10.194.189.98:2550",
                       "akka.tcp://opendaylight-cluster-data@10.194.189.101:2550"]

         auto-down-unreachable-after = 10s

         roles = [
           "member-2"
         ]

       }
     }
   }

   odl-cluster-rpc {
     bounded-mailbox {
       mailbox-type = "org.opendaylight.controller.cluster.common.actor.MeteredBoundedMailbox"
       mailbox-capacity = 1000
       mailbox-push-timeout-time = 100ms
     }

     metric-capture-enabled = true

     akka {
       loglevel = "INFO"
       loggers = ["akka.event.slf4j.Slf4jLogger"]

       actor {
         provider = "akka.cluster.ClusterActorRefProvider"

       }
       remote {
         log-remote-lifecycle-events = off
         netty.tcp {
           hostname = "10.194.189.96"
           port = 2551
         }
       }

       cluster {
         seed-nodes = ["akka.tcp://opendaylight-cluster-rpc@10.194.189.96:2551"]

         auto-down-unreachable-after = 10s
       }
     }
   }

==module-shards.conf== 示例文件:

module-shards = [
    {
        name = "default"
        shards = [
            {
                name="default"
                replicas = [
                    "member-1",
                    "member-2",
                    "member-3"
                ]
            }
        ]
    },
    {
        name = "topology"
        shards = [
            {
                name="topology"
                replicas = [
                    "member-1",
                    "member-2",
                    "member-3"
                ]
            }
        ]
    },
    {
        name = "inventory"
        shards = [
            {
                name="inventory"
                replicas = [
                    "member-1",
                    "member-2",
                    "member-3"
                ]
            }
        ]
    },
    {
         name = "toaster"
         shards = [
             {
                 name="toaster"
                 replicas = [
                    "member-1",
                    "member-2",
                    "member-3"
                 ]
             }
         ]
    }
]

集群监控

OpenDaylight通过MBeans暴露数据片信息,可以通过JConsole,VisualVM或者其他JMX客户端来查看数据片信息,也可以通过使用Jolokia的REST API来暴露数据片信息,该API是安装karaf feature ==odl-jolokia==组件才有。

列出所有的可用的MBeans方案的URI:

GET  /jolokia/list

使用下面REST,查询OpenDaylight实例的本地数据片信息。

比如config数据库信息:

GET  /jolokia/read/org.opendaylight.controller:type=DistributedConfigDatastore,Category=ShardManager,name=shard-manager-config

比如operational数据库信息:

GET  /jolokia/read/org.opendaylight.controller:type=DistributedOperationalDatastore,Category=ShardManager,name=shard-manager-operational

以下输出显示了该节点数据片信息:

{
  "request": {
    "mbean": "org.opendaylight.controller:Category=ShardManager,name=shard-manager-operational,type=DistributedOperationalDatastore",
    "type": "read"
  },
  "value": {
    "LocalShards": [
      "member-1-shard-default-operational",
      "member-1-shard-entity-ownership-operational",
      "member-1-shard-topology-operational",
      "member-1-shard-inventory-operational",
      "member-1-shard-toaster-operational"
    ],
    "SyncStatus": true,
    "MemberName": "member-1"
  },
  "timestamp": 1483738005,
  "status": 200
}

当需要进一步的查看来自“LocalShards”列表中的某个分片信息,可以把该分片的完整名称当作URI的一部分,来查询该分片的详细信息。如下是member-1-shard-default-operational信息输出的例子:

{
  "request": {
    "mbean": "org.opendaylight.controller:Category=Shards,name=member-1-shard-default-operational,type=DistributedOperationalDatastore",
    "type": "read"
  },
  "value": {
    "ReadWriteTransactionCount": 0,
    "SnapshotIndex": 4,
    "InMemoryJournalLogSize": 1,
    "ReplicatedToAllIndex": 4,
    "Leader": "member-1-shard-default-operational",
    "LastIndex": 5,
    "RaftState": "Leader",
    "LastCommittedTransactionTime": "2017-01-06 13:19:00.135",
    "LastApplied": 5,
    "LastLeadershipChangeTime": "2017-01-06 13:18:37.605",
    "LastLogIndex": 5,
    "PeerAddresses": "member-3-shard-default-operational: akka.tcp://[email protected]:2550/user/shardmanager-operational/member-3-shard-default-operational, member-2-shard-default-operational: akka.tcp://[email protected]:2550/user/shardmanager-operational/member-2-shard-default-operational",
    "WriteOnlyTransactionCount": 0,
    "FollowerInitialSyncStatus": false,
    "FollowerInfo": [
      {
        "timeSinceLastActivity": "00:00:00.320",
        "active": true,
        "matchIndex": 5,
        "voting": true,
        "id": "member-3-shard-default-operational",
        "nextIndex": 6
      },
      {
        "timeSinceLastActivity": "00:00:00.320",
        "active": true,
        "matchIndex": 5,
        "voting": true,
        "id": "member-2-shard-default-operational",
        "nextIndex": 6
      }
    ],
    "FailedReadTransactionsCount": 0,
    "StatRetrievalTime": "810.5 μs",
    "Voting": true,
    "CurrentTerm": 1,
    "LastTerm": 1,
    "FailedTransactionsCount": 0,
    "PendingTxCommitQueueSize": 0,
    "VotedFor": "member-1-shard-default-operational",
    "SnapshotCaptureInitiated": false,
    "CommittedTransactionsCount": 6,
    "TxCohortCacheSize": 0,
    "PeerVotingStates": "member-3-shard-default-operational: true, member-2-shard-default-operational: true",
    "LastLogTerm": 1,
    "StatRetrievalError": null,
    "CommitIndex": 5,
    "SnapshotTerm": 1,
    "AbortTransactionsCount": 0,
    "ReadOnlyTransactionCount": 0,
    "ShardName": "member-1-shard-default-operational",
    "LeadershipChangeCount": 1,
    "InMemoryJournalDataSize": 450
  },
  "timestamp": 1483740350,
  "status": 200
}

输出信息标识分片状态(主/从,投票中/非投票中)、同级节点信息、从节点信息,分片是否是主节点、其他统计信息和计数信息。

集成团队正在维护一个基于Python开发的工具,这个工具通过Jolokia组件,使用了上文提到MBeans暴露的接口,且systemmetrics项目也提供了一个基于DLUX UI来显示相同的信息。

空间分布式的active/backup设置

节点之间的延迟时间最小的时候OpenDaylight集群工作在最佳状态,实践表明它们应该在同一个数据中心(译注:指在同一个机房)。但是假设所有节点宕机,能够在不同的数据中心实现故障转移更满足需求。为了获的这种效果,在不影响主数据中心节点(译注:这里应该是主数据中心,即同一个机房的所有节点)的延迟时间的方法下,集群可以通过在不同数据中心扩展节点的方式来达到目的。如果这样做,备份数据中心节点必须处在“non-voting” 状态。

在controller项目cluster-admin.yang中定义了一个RPCs,用于修改分片的voting状态的API;该API已经很好地被文档化。该API的概要如下:

注意:除非另外指出,下面的POST请求都被发送到集群中的任意一个节点。

创建一个6个节点集群的active/backup设置(分别位于两个机房,3个活跃和3个备份节点)。这里有一个RPC可以根据给定状态设置指定节点中的所有分片的投票状态:

POST  /restconf/operations/cluster-admin:change-member-voting-states-for-all-shards

这个RPC需要一个节点列表和期望的投票状态作为输入。为了创建备份节点,如下输入示例可以被使用:

{
  "input": {
    "member-voting-state": [
      {
        "member-name": "member-4",
        "voting": false
      },
      {
        "member-name": "member-5",
        "voting": false
      },
      {
        "member-name": "member-6",
        "voting": false
      }
    ]
  }
}

当一个active/backup部署存在时,且备份节点处于non-voting状态,这些被用于从active子集群到backup子集群的故障转移的分片将翻转每一个分片(包含每一个节点,活跃和备份)的投票状态。通过下面的RPC调用可以很容易实现这个过程(无需参数):

POST  /restconf/operations/cluster-admin:flip-member-voting-states-for-all-shards

如果是主投票节点(译注:这里应该指的是active数据中心机房)因意外断电导致宕机,“flip” RPC请求必须被发送到某个非投票状态的备份节点(译注:backup数据中心机房的某个节点)。这种情况下,没有分片进行投票选举。但是这里有一个特例,如果收到RPC请求的这个节点处在non-voting状态,且即将改变为voting状态,且没有主节点,则这个节点将状态改为voting并尝试成为主节点。如果成功,该节点保存状态并复制这些变动信息给其余的节点。

当主中心被修复且你想要自动恢复到之前状态,你必须谨慎对待主中心的恢复。因为次级中心的投票状态被翻转的时候,主中心是处于下线状态,主中心的数据库并没有保存这些变化。如果主中心在这种状态被恢复,主中心的节点将仍然处于投票状态。如果主中心节点连接到次级中心,它们找出次级中心的主节点并同步。但是如果没有发生这种情况,主中心将可能选举出它们自己的主节点,从而被分割出2个集群,这种情况会导致不可预测的结果。因此推荐在恢复主中心时,先清除主中心节点数据库数据(例如,日志和快照目录)。另外一种选择是从次级中心一个最新备份中恢复主中心的数据(详情查阅Backing Up and Restoring the Datastore)

如果想要从集群中温和的移除一个节点也是可以的,查看如下RPC API:

POST  /restconf/operations/cluster-admin:remove-all-shard-replicas

请求的示例数据:

{
  "input": {
    "member-name": "member-1"
  }
}

或者仅仅是移除一个特定的分片:

POST  /restconf/operations/cluster-admin:remove-shard-replica

请求的实例数据:

{
  "input": {
    "shard-name": "default",
    "member-name": "member-2",
    "data-store-type": "config"
  }
}

注意在运行时某个故障节点被移除,也可以添加一个新的节点,不用修改正常节点的配置文件(需要重启)。

POST  /restconf/operations/cluster-admin:add-replicas-for-all-shards

该请求不需要任何请求数据,但是这个RPC需要被发送到新的节点,指示该节点去集群中复制所有分片数据。

注意

虽然集群admin API允许动态地添加和移除分片,但是module-shard.conf和modules.conf文件仍然使用启动时定义的初始化配置。使用API修改并不会存储到这些静态文件中,但是会存到journal中。

参考文献

Shard

Setting Up Clustering

OpenDaylight Lithium-SR2 Cluster集群搭建

Running and testing an OpenDaylight Cluster


你可能感兴趣的:(OpenDayLight,opendaylight,集群)