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


myqrcode

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章