buildbot
大師Martin Fowler對持續整合(Continuous Integration)是這樣定義的:持續整合是一種軟體開發實踐,即團隊開發成員經常整合他們的工作,通常每個成員每天至少整合一次,也就意味著每天可能會發生多次整合。每次整合都通過自動化的構建(包括編譯,釋出,自動化測試)來驗證,從而儘快地發現整合錯誤。許多團隊發現這個過程可以大大減少整合的問題,讓團隊能夠更快的開發內聚的軟體。 圖片來源於:百度百科
在BuildBot中,可以通過三種方式觸發構建:
- 監測到程式碼庫的變化 ,從而觸發構建
- 通過配置, 定時 觸發構建
- 通過配置,允許 強制 觸發構建
BuildBot有如下優點:
- 跨平臺:可以執行在各種平臺上,實現不同平臺上的測試
- 可以處理各種語言編寫的程式,例如C、Java、Python
- 環境要求低並且配置簡單:僅依賴Python和網路庫Twisted
- 結果的交付方式多,例如Email、webpage、IRC或者其他協議工具
- 通過子類繼承並重寫父類從而靈活的配置
- 很好的實現了 分散式 部署和整合工作
使用BuildBot的公司:
BuildBot整體架構: BuildBot是由一個 buildmaster 和一個或者多個 worker ,通過星型拓撲結構連線而成的。
各個元件的作用:
- Repository :程式碼倉庫,用於程式碼管理和版本控制,目前流行的有SVN、Git等
- BuildMaster:監測程式碼庫上的變化,這些變化可能會引發構建
- Worker:根據BuildMaster下發的Command,執行構建,同時將執行狀態和結果返回給BuildMaster
- Notifiers:在構建的時候,會產生各種各樣的狀態訊息,它們會被髮送到Notifiers
Worker連線:
Worker通常執行在各種不同的平臺上。它們通過TCP協議連線到BuildMaster上。這些TCP連線都是由Worker發起的。BuildMaster下發的Command以及Worker返回的Result都通過這些TCP連線進行傳輸。BuildMaster是叢集的管理者,因此所有Command都是由BuildMaster下發給Worker的。
BuildMaster只提供用於執行構建的指令,並不會提供原始碼。因此,Worker需要去程式碼倉庫獲取原始碼。
BuildMaster架構:
(BuildMaster的各個元件均在master.cfg中配置)
-
ChangeSource:
當監測到VCS上面發生變化時,ChangeSource會建立一個Change物件,提交給各個Scheduler。
ChangeSource既可以通過監聽鉤子指令碼產生的訊息,也可以通過定期輪詢的方式,監測VCS上的變化
-
Scheduler:
決定何時執行構建。Scheduler會收集Change,構造BuildRequest,當Worker可用時,將BuildRequest交付給Builder
-
Builder:
用於精確地控制如何執行構建(其中包含一系列的BuildStep)
-
Status plugin:
通過HTTP、mail、IRC之類的協議,交付與構建結果有關的資訊
(本文假定讀者已經安裝了Python3,本文在Python 3.7.2下測試通過)
1,安裝:
pip install 'buildbot[bundle]'
pip install buildbot-worker
2,建立,並啟動master:
x
mkdir -p ~/tmp
buildbot create-master ~/tmp/master
mv ~/tmp/master/master.cfg.sample ~/tmp/master/master.cfg
buildbot upgrade-master ~/tmp/master
buildbot start ~/tmp/master
# 日誌在:~/tmp/master/twistd.log
3,建立,並啟動worker:
xxxxxxxxxx
mkdir -p ~/tmp
buildbot-worker create-worker ~/tmp/worker localhost example-worker pass
buildbot-worker start ~/tmp/worker
# 日誌在:~/tmp/worker/twistd.log
其中:
-
localhost:執行buildmaster的伺服器的地址。本例中是localhost
-
example-worker/pass:在buildmaster的配置檔案中,有一個 workers列表 ,它是buildmaster所能識別的worker的集合。該列表中的元素是Worker物件,每個Worker物件都有唯一的名字 和 密碼。在worker上配置的worker名稱和密碼,必須與buildmaster中配置的相同。比如:
xxxxxxxxxx
# master.cfg
####### WORKERS
# The 'workers' list defines the set of recognized workers. Each element is
# a Worker object, specifying a unique worker name and password.The same
# worker name and password must be configured on the worker.
c['workers'] = [worker.Worker("example-worker", "pass")]
4,webstatus page:
接下來,訪問: http://localhost:8010 ,可以看到類似下面的web頁面: 點選左側的“Builds”,開啟子選單。然後通過“Builders”,可以看到剛剛啟動的worker,已經連線到master上了。
下面,開始詳細地介紹buildmaster的各個元件。
Builder負責執行某個或一系列行為,這些行為可以是與構建軟體相關的,也可以是任意命令。
需要使用一個worker列表來配置Builder,這些worker用於執行任務。Builder需要的基礎資訊還包括:它要做的事情的列表(這些事情會在被選擇的worker上執行)。在Buildbot中,這個事情列表被表示為一個BuildFactory物件,它本質上是一個步驟序列,每個步驟定義了一個特定的操作或命令。
下面看一個例子:
xxxxxxxxxx
factory = util.BuildFactory()
# 檢出原始碼
factory.addStep(steps.Git(repourl='https://github.com/tim-chow/buildbot-test.git', mode='incremental'))
# 執行測試
factory.addStep(steps.ShellCommand(command=["python", "setup.py", "test"]))
c['builders'] = []
c['builders'].append(
util.BuilderConfig(name="runtests",
workernames=["worker-1"],
factory=factory))
在這個例子中,建立了一個叫 runtests
的Builder,它能夠在 worker-1
上執行。
最重要的是:Builder的名字不能相同,並且必須被新增到 c['builders']
(該值是一個BuilderConfig物件的列表)。
Scheduler在它等待的事件發生時,基於事件資訊,決定是否以及何時執行構建。可以有多個Scheduler。
Scheduler主要包含兩部分資訊:
- 監測哪些事件
- 當監測的事件發生時,觸發哪些Builder
下面看一個例子:
xxxxxxxxxx
# Configure the Schedulers, which decide how to react to incoming changes.In this
# case, just kick off a 'runtests' build
c['schedulers'] = []
c['schedulers'].append(schedulers.SingleBranchScheduler(
name="all",
change_filter=util.ChangeFilter(branch='master'),
treeStableTimer=None,
builderNames=["runtests"]))
c['schedulers'].append(schedulers.ForceScheduler(
name="force",
builderNames=["runtests"]))
第一個Scheduler會接收程式碼倉庫上的變化,但是 只關注 master分支上的變化。換句話說,它只對它感興趣的變化作出反應。當檢測到這樣的變化時,它會執行 runtests
。 treeStableTimer
是為了避免:在頻繁提交時,產生大量的構建請求。
使用第二個Scheduler的時候,可以通過Web UI強制構建 runtests
。
與Builder類似,所有Scheduler都必須被新增到 c["schedulers"]
列表中。
ChangeSource的任務是監測程式碼庫上的變化,並把它們提交給Scheduler。
ChangeSource可以通過多種方式,監測程式碼庫上的變化。比如,週期性地輪詢程式碼庫;或者通過配置(比如,通過被commit觸發的鉤子指令碼),使VCS把變更推送給ChangeSource。
下面看一個例子:
xxxxxxxxxx
c['change_source'] = []
c['change_source'].append(changes.GitPoller(
'https://github.com/tim-chow/buildbot-test.git',
workdir='integrate_buildbot_test', branch='master',
pollInterval=120))
在這個例子中,建立了一個GitPoller型別的ChangeSource,它每隔120秒輪詢一次程式碼庫,獲取master分支上的變化。
與Builder類似,所有ChangeSource都必須被新增到 c["change_source"]
列表中。
BuildMaster可以通過多種方式,將構建狀態傳送給使用者。每個交付方法是一個Reporter Target物件。
下面看一個例子:
xxxxxxxxxx
c['services'] = []
m = reporters.MailNotifier(fromaddr="buildbot@localhost",
extraRecipients=["[email protected]"],
sendToInterestedUsers=False,
builders=["runtests"])
c['services'].append(m)
在這個例子中,BuildMaster會將 runtests
的構建狀態,以郵件的形式傳送給 [email protected]
。
如果想要獲取,更多關於Reporter的細節,請檢視: http://docs.buildbot.net/current/manual/configuration/reporters.html 。
與Builder類似,所有Reporter Target都必須被新增到 c["services"]
列表中。
master.cfg中的其它配置項
xxxxxxxxxx
c['protocols'] = {'pb': {'port': 9989}}
protocols
中包含 與 master和worker之間通訊所使用的協議 有關的資訊。
xxxxxxxxxx
c['title'] = "My Test BuildBot"
c['titleURL'] = "http://timd.cn/python-buildbot/"
title
字串會出現在home頁面的頂部(連結到 titleURL
)。
xxxxxxxxxx
c['buildbotURL'] = "http://192.168.10.102:8013/"
buildbotURL
用於指出web服務的地址。它應該使用 www
中設定的埠。
xxxxxxxxxx
c['www'] = dict(port=8010,
plugins=dict(waterfall_view={}, console_view={}, grid_view={}))
啟用web UI的最小化配置。其中:
-
port
:用於指定接收請求的TCP埠 -
plugins
:用於指定要載入的UI外掛以及它們的配置。這些外掛是獨立安裝的 - 如果想要了解,更多關於web服務的配置,請移步: http://docs.buildbot.net/current/manual/configuration/www.html
xxxxxxxxxx
c['db'] = {
# This specifies what database buildbot uses to store its state.You can leave
# this at its default for all but the largest installations.
'db_url' : "sqlite:///state.sqlite",
}
db_url
用於指定 用來儲存BuildBot的狀態的資料庫 的url。
預設情況下,訪問Web UI是無需認證的。為了安全起見,應該配置認證外掛。每個認證外掛是一個類。
比如:
xxxxxxxxxx
c['www'] = {
# ...
'auth': util.UserPasswordAuth({"timchow": "password"}),
}
如果想要了解,BuildBot支援哪些認證外掛,以及如何自定義認證外掛,請移步: http://docs.buildbot.net/current/manual/configuration/www.html#authentication-plugins 。
接下來學習 授權框架 。
授權框架只對HTTP生效。它根據規則,控制使用者對Rest API的訪問。
授權框架的執行流程如下圖所示:
授權框架是基於角色的:
-
角色:授予使用者的一個標籤
- 一個使用者可以擁有多個角色,一個角色可以被授予給多個使用者
-
Endpoint Matchers:負責建立用於匹配Rest端點的規則。對於某個Rest端點而言,如果某條規則匹配它,那麼在使用者擁有規則中指定的角色的情況下,才能訪問它
-
在下面的例子中,對於強制構建這個Rest端點而言,第一條規則匹配它,因此,在使用者擁有admins角色的情況下,才能訪問它
xxxxxxxxxx
allowRules=[
util.AnyControlEndpointMatcher(role="admins"),
]
-
預設的策略是:對於一個Rest端點,如果沒有任何規則匹配它,那麼允許任何使用者訪問它
-
預設情況下,第一個匹配Rest端點的規則,會阻止接下來的匹配。如果想要繼續檢查其它規則,那麼需要將Matcher的 defaultDeny 引數設定為False
-
-
Role Matchers:負責建立用於匹配授權使用者的規則。對於某個使用者而言,如果某條規則匹配它,那麼它將被授予規則中指定的角色
-
在下面的例子中,對於使用者root而言,第一條規則匹配它,因此,它被授予admins角色;對於使用者Charlie而言,沒有任何規則匹配它,因此,它不會被授予任何角色
xxxxxxxxxx
roleMatchers=[
RolesFromUsername(roles=["admins"], usernames=["root"]),
RolesFromUsername(roles=["developers", "integrators"], usernames=["Alice", "Bob"])
]
-
綜上,如果使用者A想要訪問端點B,那麼首先用Role Matchers中的規則匹配使用者A,最終得到使用者A擁有的所有角色;然後用Endpoint Matchers中的規則匹配端點B,如果某條規則匹配端點B,並且使用者A擁有該規則所指定的角色,則使用者A可以訪問端點B…
配置授權規則的方式,如下所示:
xxxxxxxxxx
authz = util.Authz(
allowRules=[
util.AnyControlEndpointMatcher(role="admins"),
],
roleMatchers=[
util.RolesFromEmails(admins=["[email protected]"])
]
c['www']['authz'] = authz