卷2:第18章 Puppet part 2

2018-02-24 15:55 更新

18.3. 組件分析

Agent (代理)

在 Puppet 運(yùn)行過(guò)程之中,第一個(gè)提到的組件就是?agent?進(jìn)程。歷史上,這是一個(gè)獨(dú)立的稱為puppedd?的可執(zhí)行程序,但在 2.6 中,我們把 Puppet 變成了一個(gè)唯一的可執(zhí)行程序,現(xiàn)在,可以使用?puppet agent?來(lái)調(diào)用它,和 Git 的使用方式很類似。這個(gè)代理本身的功能不多,主要是用于實(shí)現(xiàn)上面圖中客戶端的工作流的配置和代碼。

Facter

在 agent 之后的下一個(gè)組件是一個(gè)稱為 Facter 的外部工具,這是一個(gè)用于檢測(cè)本機(jī)信息的非常簡(jiǎn)單的工具。這些信息包括操作系統(tǒng)、IP 地址、主機(jī)名等,但 Facter 非常易于擴(kuò)展,很多機(jī)構(gòu)在使用中會(huì)增加它們自己的插件來(lái)檢測(cè)一些個(gè)性化信息。Facter 發(fā)現(xiàn)的信息會(huì)由 agent 發(fā)送給服務(wù)器,之后,服務(wù)器就接過(guò)了接力棒,繼續(xù)工作流。

外部節(jié)點(diǎn)分類器

在服務(wù)端,第一個(gè)遇到的組件稱為外部節(jié)點(diǎn)分類器(External Node Classifier),簡(jiǎn)稱為 ENC。ENC 接受主機(jī)名作為輸入,返回一個(gè)包含對(duì)應(yīng)主機(jī)的高級(jí)配置信息的數(shù)據(jù)結(jié)構(gòu)。ENC 通常是一個(gè)獨(dú)立的服務(wù)或程序:可能是一個(gè)其他的開源項(xiàng)目,比如 Puppet Dashboard 或 Foreman,或者是繼承的已有的數(shù)據(jù)存儲(chǔ),比如 LDAP。ENC 的目的是,確定一臺(tái)主機(jī)從功能上屬于那些類,以及需要用哪些參數(shù)來(lái)配置這些類。比如,一個(gè)給定的主機(jī)可能屬于?debian?和?webserver?類,datacenter?參數(shù)應(yīng)該設(shè)置為?atlanta。

注意,在 Puppet 2.7 中,ENC 不是一個(gè)必選組件,用戶可以在 Puppet 代碼中直接指定節(jié)點(diǎn)的配置。大約在 Puppet 項(xiàng)目開始兩年之后,ENC 才被添加進(jìn)來(lái),因?yàn)槲覀冮_始意識(shí)到,對(duì)節(jié)點(diǎn)的功能進(jìn)行劃分和配置節(jié)點(diǎn)從本質(zhì)上說(shuō)是不同的兩件事,將它們劃分到兩個(gè)不同的程序中可能比擴(kuò)展語(yǔ)言來(lái)支持兩種功能要更合理。盡管不是必須,但 ENC 仍然是推薦配置,并且將來(lái)某天可能會(huì)成為必選組件(到那時(shí),Puppet 會(huì)提供一個(gè)擁有足夠必備功能的 ENC,不必?fù)?dān)心)。

一旦服務(wù)器收到 ENC 的分類信息,或是來(lái)自 Facter (經(jīng)過(guò) agent)的系統(tǒng)信息后,它會(huì)將所有信息綁定到一個(gè)節(jié)點(diǎn)對(duì)象上,并將它送入編譯器。

編譯器(Compiler)

如前所述,Puppet 有一個(gè)自定義的語(yǔ)言,用于指定系統(tǒng)配置。它的編譯起實(shí)際上有三個(gè)部分:一個(gè) Yacc 風(fēng)格的分析器生成器和一個(gè)定制的詞法分析器;一組用于創(chuàng)建我們的抽象語(yǔ)法樹(AST)的類;以及 Compiler 類,它用于處理所有這些類,以及實(shí)現(xiàn)編譯器作為系統(tǒng)的一部分所提供的API的函數(shù)之間的交互。

編譯器要處理的最復(fù)雜的事情是,大部分 Puppet 配置代碼是在第一次被引用時(shí)才延遲加載的(減少加載時(shí)間,同時(shí)避免缺少一些實(shí)際不必要的依賴資源時(shí)產(chǎn)生的無(wú)關(guān)日志),這意味著不會(huì)有顯式的調(diào)用來(lái)加載并分析代碼。

Puppet 的解析器使用了一個(gè)使用開源的?Racc?構(gòu)建的正常的?Yacc 風(fēng)格的解析器生成器??刹恍业氖牵?Puppet 項(xiàng)目開始時(shí),沒有可用的詞法分析器生成器,所以只好使用了一個(gè)定制的詞法分析器。

因?yàn)槲覀冊(cè)?Puppet 中使用了 AST,所以 Puppet 語(yǔ)法中的每個(gè)語(yǔ)句都可以被求值為 Puppet AST 類的一個(gè)實(shí)例(Puppet::Parser::AST::Statement),這些 AST 實(shí)例不會(huì)被直接執(zhí)行操作,而會(huì)被放入一個(gè)語(yǔ)法樹之中,被一起執(zhí)行。當(dāng)一個(gè)服務(wù)器為很多不同節(jié)點(diǎn)服務(wù)時(shí),使用 AST 會(huì)帶來(lái)一些性能上的收益,因?yàn)檫@樣可以一次解析,多次編譯。同時(shí)這也給了我們一個(gè)機(jī)會(huì),來(lái)對(duì) AST 進(jìn)行一些內(nèi)?。╥ntrospection),讓我們得到一些額外的信息和能力,如果直接解析執(zhí)行是無(wú)法得到這些的。

在 Puppet 項(xiàng)目開始時(shí),可參考的 AST 的例子并不多,這部分已經(jīng)經(jīng)過(guò)了很多的演化,發(fā)展到現(xiàn)在,我們的形式看起來(lái)是比較獨(dú)一無(wú)二的。我們不會(huì)直接針對(duì)整個(gè)配置生成一個(gè)單獨(dú)的AST,相反,我們創(chuàng)建很多小的 AST,按照名字切開。比如,如下代碼:

class ssh {
    package { ssh: ensure => present }
}

會(huì)創(chuàng)建一個(gè)新的 AST,包含一個(gè)?Puppet::Parser::AST::Resource?實(shí)例,并將這個(gè) AST 命名為 "ssh",存儲(chǔ)在存儲(chǔ)這個(gè)特定環(huán)境的所有類的哈希表中。(這里略過(guò)了構(gòu)建類的細(xì)節(jié),不過(guò)對(duì)于這里的討論來(lái)說(shuō),這些是不必要的。)

給定 AST 和(來(lái)自 ENC 的)Node 對(duì)象,編譯器取出 node 對(duì)象(如果存在的話)指定的類,查找并進(jìn)行求值。在這個(gè)求值的過(guò)程中,編譯器構(gòu)建了不同不同的域的樹,每個(gè)類有自己的作用域。這意味著 Puppet 是動(dòng)態(tài)作用域的:如果一個(gè) class include 了另一個(gè)類,那么里面的類就可以訪問(wèn)外面類的變量。這的確是一個(gè)噩夢(mèng),我們正在著手消除這個(gè)問(wèn)題。

作用域樹是臨時(shí)數(shù)據(jù)結(jié)構(gòu),一旦編譯完成就會(huì)被釋放,但編譯的輸出也隨著編譯的過(guò)程逐漸完成。我們把這個(gè)輸出產(chǎn)品稱為 Catalog(目錄),但它實(shí)際是一張資源和它們的關(guān)系構(gòu)成的圖。變量、控制結(jié)構(gòu)或是函數(shù)都不會(huì)存在在 catalog 之中,catalog 是純數(shù)據(jù),并可以被轉(zhuǎn)化為 JSON、YAML 或其他各種格式。

在編譯過(guò)程中,我們會(huì)創(chuàng)建一些包含(containment)關(guān)系,一個(gè)類"包含(contains)"類中定義的所有資源(比如, 前面的例子中,ssh 類包含 ssh 包)。類可以包含一個(gè)定義,而這個(gè)定義本身也可以包含一個(gè)或多個(gè)定義,或其他獨(dú)立的資源。一個(gè) catalog 傾向于一個(gè)扁平的、彼此無(wú)連接的圖:很多類,每個(gè)都有少數(shù)的幾個(gè)層次。

這種圖的一個(gè)別扭的方面是,它還包含了“依賴(dependency)”關(guān)系,比如一個(gè)服務(wù)依賴于一個(gè)包(可能因?yàn)榘惭b了包才能創(chuàng)建服務(wù)),但這些依賴關(guān)系是由資源的參數(shù)指定的,而非圖結(jié)構(gòu)的邊。我們的圖類(由于歷史原因,稱為?SimpleGraph)不支持在同一張圖里同時(shí)有“包含”邊和“依賴”邊,所以,我們不得不為了不同的需求在它們之間來(lái)回轉(zhuǎn)換。

事務(wù) (Transaction)

一旦 catalog 完全構(gòu)建好了(假設(shè)沒有失敗),就會(huì)送給 Transaction。在一個(gè)區(qū)分客戶機(jī)和服務(wù)器的系統(tǒng)中,Transaction 運(yùn)行在客戶機(jī)上,如圖 18.2,它通過(guò) HTTP 協(xié)議下載 Catalog。

Puppet 的 transaction 類提供了實(shí)際進(jìn)行系統(tǒng)修改操作的框架,而我們討論過(guò)的其他東西都構(gòu)建于其上或是進(jìn)行對(duì)象的分發(fā)傳遞。和數(shù)據(jù)庫(kù)之類的一般系統(tǒng)中的事務(wù)不同 Puppet transaction 的行為并不具有原子性等特征。

transaction 的工作相當(dāng)直接:在圖中按照各種關(guān)系指定的順序進(jìn)行遍歷,并確保各種資源保持同步。正如上面提到的,它不得不將圖從包含邊(比如?Class[ssh]?包含?Package[ssh])轉(zhuǎn)換為依賴邊(比如?Service[ssh]?依賴于?Package[ssh]),然后對(duì)圖進(jìn)行標(biāo)準(zhǔn)的拓?fù)渑判?,按順序選擇每種資源。

對(duì)于給定的資源,我們進(jìn)行簡(jiǎn)單的三步操作:獲取資源的當(dāng)前狀態(tài),與期望的狀態(tài)進(jìn)行比較,進(jìn)行必要的改動(dòng),以滿足期望。比如,有如下代碼:

file { "/etc/motd":
    ensure => file,
    content => "Welcome to the machine",
    mode => 644
}

transaction 會(huì)檢查?/etc/motd?的內(nèi)容,如果和指定的狀態(tài)不匹配的話,會(huì)修復(fù)不一致的地方。如果 /etc/motd 是一個(gè)目錄,那么它會(huì)備份其中的文件,再刪除目錄,然后創(chuàng)建一個(gè)內(nèi)容和權(quán)限都符合要求的文件。

進(jìn)行操作的過(guò)程實(shí)際上是通過(guò)一個(gè)簡(jiǎn)單的?ResourceHarness?類來(lái)控制的,這個(gè)類定義了事務(wù)和資源之間的接口。這樣做可以減少類之間的連接,并可以讓他們互相獨(dú)立地進(jìn)行修改。

資源抽象層 (Resource Abstraction Layer)

事務(wù)類是 Puppet 完成工作的核心,但所有的工作實(shí)際都是由資源抽象層(RAL)來(lái)完成的,從架構(gòu)上講,這一層也是 Puppet 中最有意思的組件。

RAL 是 Puppet 中創(chuàng)建的第一個(gè)組件,與語(yǔ)言部分不同,這部分對(duì)用戶能做的事情進(jìn)行了清晰的定義。RAL 的工作就是定義一個(gè)資源究竟是什么,要實(shí)現(xiàn)一個(gè)資源需要在系統(tǒng)中進(jìn)行什么操作,而 Puppet 語(yǔ)言正是用來(lái)操作由 RAL 建模的資源的。正因如此,RAL 也是系統(tǒng)中最重要和最難改動(dòng)的組件。我們希望能修改 RAL 中的很多東西,而且也在過(guò)去的多年中進(jìn)行了很多重大改進(jìn)(最難的莫過(guò)于增加 Provider 了),但是在 RAL 中,還是有很多工作需要在日后慢慢修改。

在編譯器子系統(tǒng)中,我們將資源和資源類型分別進(jìn)行了建模(分別命名為?Puppet::Resource?和 Puppet::Resource::Type)。我們的目標(biāo)是讓這些類也成為 RAL 的核心,不過(guò),目前這兩種行為(資源和類型)被封裝到了同一個(gè)類之中 ——?Puppet::Type。(這個(gè)類的命名十分糟糕,這是因?yàn)槎x這個(gè)類的時(shí)間遠(yuǎn)早于我們開始使用“資源”這個(gè)名詞的時(shí)間,在那時(shí),我們?cè)谥鳈C(jī)間進(jìn)行數(shù)據(jù)通信時(shí),是直接對(duì)內(nèi)存類型進(jìn)行序列化的,事到如今,想要再去重新調(diào)整命名已經(jīng)非常困難了。)

當(dāng)?Puppet::Type?被最早設(shè)計(jì)出來(lái)的時(shí)候,似乎把資源和類型的行為放到同一個(gè)類里是有道理的,畢竟資源是資源類型的實(shí)例。但隨著時(shí)間的推移,越來(lái)越發(fā)現(xiàn),資源及其類型不適合于放在一個(gè)傳統(tǒng)的繼承關(guān)系構(gòu)成的模型里。比如,資源類型定義了資源可以有哪些參數(shù),但不管資源接受哪些參數(shù)(可能全部接受)。這樣,我們的?Puppet::Type?就擁有了類級(jí)別的行為——規(guī)定資源類型的行為,和實(shí)例級(jí)別的行為——規(guī)定資源實(shí)例如何行為。同時(shí),它還負(fù)責(zé)管理注冊(cè)和獲取資源類型的功能,如果你需要 "user" 類型,你可以調(diào)用?Puppet::Type.type(:user).

這種混合的行為導(dǎo)致了?Puppet::Type?不太容易維護(hù)。整個(gè)類有不到 2000 行代碼,但卻在三個(gè)層面上工作 —— 資源、資源類型,和組員類型管理器 —— 這讓它變得難以理解。這就是為什么這個(gè)模塊是重構(gòu)的主要目標(biāo)的原因,不過(guò)它本身更多的是拼接在一起的代碼,而不是面向用戶的設(shè)計(jì),所以,修正它要比直接根據(jù)功能重寫更困難。

除了?Puppet::Type?之外,RAL 中還有兩個(gè)重要的類,其中最有趣的一個(gè)我們稱之為 Provider。在 RAL 剛剛被開發(fā)出來(lái)時(shí),每種資源都是由參數(shù)定義和如何管理它們的代碼混在一起構(gòu)成的。比如,我們要定義 "content" 參數(shù),然后提供一個(gè)方法來(lái)讀取文件的內(nèi)容,以及另一個(gè)用于修改內(nèi)容的方法:

Puppet::Type.newtype(:file) do
    ...
    newproperty(:content) do
        def retrieve
            File.read(@resource[:name])
        end
        def sync
            File.open(@resource[:name], "w") { |f| f.print @resource[:content] }
        end
    end
end

這是個(gè)簡(jiǎn)化的例子(比如我們內(nèi)部實(shí)際使用的校驗(yàn)和,而非讀取全部?jī)?nèi)容),但這里可以大致了解處理思路。

這樣就讓事情變得非常難于管理了,因?yàn)槲覀冃枰獮槊總€(gè)資源類型管理很多不同的屬性。目前 Puppet 支持超過(guò) 30 種包管理工具,這樣,很難在一個(gè) Package 資源類型中去支持所有這些管理工具了。于是,我們提供了一種資源類型和管理相應(yīng)類型的資源的方法之間的一個(gè)清晰接口 —— 實(shí)際上,資源類型是指資源類型的名字和它們支持的屬性。 Provider 為所有的資源類型的屬性定義了 getter 和 setter 方法,以清晰直觀的方式命名。例如,這是一個(gè) provider 實(shí)現(xiàn)上述屬性的代碼示例:

Puppet::Type.newtype(:file) do
    newproperty(:content)
end    
Puppet::Type.type(:file).provide(:posix) do
    def content
        File.read(@resource[:name])
    end
    def content=(str)
        File.open(@resource[:name], "w") { |f| f.print(str) }
    end
end

在這個(gè)簡(jiǎn)單的例子里,似乎還多了一點(diǎn)代碼,但這更易于理解和維護(hù),特別是當(dāng)屬性的數(shù)量或是屬性提供者的數(shù)量變多的時(shí)候。

本節(jié)開始處曾經(jīng)提到,Transaction 并不直接改動(dòng)系統(tǒng),而是通過(guò) RAL 來(lái)完成的?,F(xiàn)在,我們可以清楚地看到,是 provider 來(lái)進(jìn)行的這想具體工作。事實(shí)上,總體上講,provider 是 Puppet 當(dāng)中,唯一真正直接觸及系統(tǒng)的部分。transaction 請(qǐng)求文件內(nèi)容,provider 就會(huì)為它讀取,transaction 要求文件的內(nèi)容要被改動(dòng),provider 就去改動(dòng)它。注意,盡管如此,provider 從不決定如何影響系統(tǒng) —— 如何影響系統(tǒng)這個(gè)問(wèn)題是由 Transaction 來(lái)決定的,provider 僅僅是執(zhí)行任務(wù)。這種架構(gòu)可以讓 Transaction 能夠完全控制系統(tǒng),卻不需要了解文件、用戶和包這些具體細(xì)節(jié),而且,這個(gè)劃分可以讓 Puppet 可以擁有一個(gè)完全模擬執(zhí)行的模式,在這種模式下,我們可以保證系統(tǒng)完全不會(huì)受到任何影響。

RAL之中的另一個(gè)主要的類型負(fù)責(zé)參數(shù)本身。我們支持三種類型的參數(shù): metaparameters, 這種參數(shù)影響所有資源類型(比如,是否在模擬模式中運(yùn)行);參數(shù),它們是不直接寫入到磁盤上的一些值(比如,是否在查找文件時(shí)進(jìn)入符號(hào)鏈接);還有屬性(property),它們規(guī)范了你要修改磁盤上的資源的哪方面的內(nèi)容(比如文件的內(nèi)容,或者一個(gè)服務(wù)是否在運(yùn)行著)。區(qū)分屬性和參數(shù)的不同十分困難,但你可以這么想,屬性是哪些 provider 中有 getter 和 setter 方法的,這樣就易于分辨了。

報(bào)告 (Reporting)

隨著 transaction 遍歷整張圖,并使用 RAL 來(lái)修改系統(tǒng)的配置,Puppet 同時(shí)也會(huì)同時(shí)生成一份報(bào)告。這份報(bào)告包含了在對(duì)系統(tǒng)應(yīng)用修改的過(guò)程中發(fā)生的事件。這些事件也完整地飯贏了工作進(jìn)行的情況:它們會(huì)在資源改變時(shí)記錄時(shí)間戳,已有的值和新的值,以及所有產(chǎn)生的信息,以及變動(dòng)成功或是失敗(或者實(shí)在模擬運(yùn)行模式)。

這些事件封裝在?ResourceStatus?對(duì)象之中,映射到相應(yīng)的資源。這樣,對(duì)于一個(gè)給定的 Transaction,你可以知道其中運(yùn)行的所有資源,這些改變是否成功,以及你可能希望知道的關(guān)于這些改動(dòng)的元數(shù)據(jù)。

一旦 transaction 完成了,一些基本的性能參數(shù)也會(huì)被計(jì)算出來(lái),并存儲(chǔ)到報(bào)告中,之后發(fā)送給服務(wù)器(如果配置了服務(wù)器的話)。當(dāng)報(bào)告被發(fā)送出去的時(shí)候,配置過(guò)程就完成了,agent 會(huì)回到睡眠模式,或者進(jìn)程退出。

18.4. 基礎(chǔ)架構(gòu)

現(xiàn)在我們已經(jīng)從整體上理解了 Puppet 做了什么,如何做到的,值得再花一點(diǎn)看看其他部分了,這些部分并沒有顯示出什么過(guò)人之處,但對(duì)于完成工作也是十分必要的。

Plugins

Puppet 的一個(gè)突出優(yōu)點(diǎn)是它非常易于擴(kuò)展。在 Puppet 里,至少有 12 類擴(kuò)展,大部分?jǐn)U展都可以被所有人使用。比如,你可以在這些方面寫出你自己的擴(kuò)展:

  • 資源類型和自定義 provider
  • 報(bào)告處理程序,比如存儲(chǔ)報(bào)告到專用的數(shù)據(jù)庫(kù)中
  • Indirector,用于和已有數(shù)據(jù)存儲(chǔ)交互
  • 用于獲取你的主機(jī)上的額外信息的 facts

不過(guò),Puppet 的分布式本質(zhì)意味著 agent 需要某種方式來(lái)取回并加載新的擴(kuò)展。為此,在每次 Puppet 啟動(dòng)之前,第一件事請(qǐng)就是找到可用的服務(wù)器,下載所有 plugin。其中可能包括新的資源類型或 provider,新 facts,或者是新的報(bào)告處理器。

這意味著我們可以在不改動(dòng)核心 Puppet 包的同時(shí),升級(jí)大部分的 Puppet agent 功能。對(duì)于一些自定義的 Puppet 部署,這更是特別有用。

Indirector

到目前為止,你可能已經(jīng)發(fā)現(xiàn),Puppet 的開發(fā)歷史中有一些壞名字的類,而對(duì)于大部分人來(lái)說(shuō),這一個(gè)是最無(wú)法容忍的。Indirector 是一個(gè)極具擴(kuò)展性的控制反轉(zhuǎn)(IoC)框架。控制反轉(zhuǎn)系統(tǒng)允許你講功能的開發(fā)和如何控制使用什么功能獨(dú)立開。在 Puppet 的例子中,這允許我們使用很多插件,來(lái)提供非常不同的功能,比如可以通過(guò) HTTP 訪問(wèn)編譯器,也可以直接在進(jìn)程中加載,這些可以通過(guò)一個(gè)笑得配置改變而不需要修改代碼就可以實(shí)現(xiàn)。換句話說(shuō),按照 Wikipedia 的 “控制反轉(zhuǎn)” 頁(yè)面的描述,Puppet Indirector 是一個(gè)服務(wù)定位器的實(shí)現(xiàn)。所有從一個(gè)類到另一個(gè)類的切換都經(jīng)由 Indirector,通過(guò)一個(gè)標(biāo)準(zhǔn)的類 REST 接口完成(比如,我們支持 find, search, save 以及 destroy 方法),這樣,講 Puppet 從無(wú)服務(wù)器模式切到客戶機(jī)/服務(wù)器模式的操作,很大程度上說(shuō),是一個(gè)配置 agent 使用 HTTP 作為獲取 catalog 的方法,而非直接訪問(wèn) compiler 的問(wèn)題。

因?yàn)樽鳛橐粋€(gè)控制反轉(zhuǎn)框架,Indirector 的配置必須嚴(yán)格地和代碼的路徑分開,這個(gè)類本身非常難于理解,特別是你在 debug 為什么使用給定的代碼路徑時(shí)。

網(wǎng)絡(luò)

Puppet 的原型寫于 2004 年,當(dāng)時(shí)的關(guān)于 RPC 的問(wèn)題是 XMLRPC 與 SOAP 之爭(zhēng)。我們選擇了 XMLRPC,它工作得很好,但是有一個(gè)大部分其他方法都有的問(wèn)題:不鼓勵(lì)在模塊間使用標(biāo)準(zhǔn)接口,而且對(duì)于獲取簡(jiǎn)單的一個(gè)結(jié)果這樣的操作有些過(guò)于復(fù)雜了。因?yàn)?XMLRPC 編碼的需要,導(dǎo)致了幾乎每個(gè)對(duì)象都在內(nèi)存中至少出現(xiàn)兩次,對(duì)于大文件來(lái)說(shuō)代價(jià)很高,我們也為此遇到過(guò)很嚴(yán)重的內(nèi)存問(wèn)題。

從 0.25 發(fā)布開始(開始于 2008 年),我們開始了將網(wǎng)絡(luò)通信向類 REST 模型遷移的過(guò)程,但我們沒有直接修改網(wǎng)絡(luò),而是選擇了一種更復(fù)雜的方案。我們開發(fā)了 Indirector 作為組件間通信的標(biāo)準(zhǔn)框架,然后構(gòu)建了一個(gè) REST 端點(diǎn)作為一個(gè)可選方案。我們用了兩個(gè) Release 來(lái)完整支持 REST,目前還沒有完全完成(從使用 YAML)到使用 JSON 作為序列化方案的轉(zhuǎn)換。我們進(jìn)行 YAML 到 JSON 的轉(zhuǎn)換有兩點(diǎn)主要的原因:首先,Ruby 之中,處理 YAML 非常慢,而 Ruby 處理 JSON 則快很多;其次,大部分 web 應(yīng)用都轉(zhuǎn)向了 JSON,這讓 JSON 看起來(lái)更加可移植一些。當(dāng)然,對(duì)于 Puppet 的情況來(lái)說(shuō),使用 YAML 并非是為了語(yǔ)言間可移植性考慮的,,而且 YAML 配置對(duì)于不同版本的 Puppet 可能都經(jīng)常不兼容,因?yàn)樗举|(zhì)上是用來(lái)序列化 Ruby 內(nèi)部對(duì)象的。

我們的下一個(gè)主版本更新將完全移除 XMLRPC 的支持。

18.5. 經(jīng)驗(yàn)教訓(xùn)

從實(shí)現(xiàn)的角度講,我們對(duì)于 Puppet 中實(shí)現(xiàn)的各種解耦非常自豪:描述語(yǔ)言與 RAL 是完全解耦的,Transaction 無(wú)法直接訪問(wèn)系統(tǒng),而 RAL 本身不會(huì)做任何策略判斷。這些抽象和解耦讓應(yīng)用的開發(fā)者可以更加專注于工作流的開發(fā),并可以獲得很多關(guān)于發(fā)生了什么、為什么發(fā)生的信息。

Puppet 的可擴(kuò)展性和可配置性也是它的一個(gè)主要優(yōu)點(diǎn),任何人都可以在 Puppet 的基礎(chǔ)上,輕易地開發(fā)應(yīng)用而無(wú)需修改其內(nèi)核。我們也使用提供給用戶的同樣的接口來(lái)開發(fā)各種功能。

Puppet 的簡(jiǎn)單和易用性一直是它的主要優(yōu)點(diǎn)。雖然讓它跑起來(lái)還是有點(diǎn)困難,不過(guò)可以強(qiáng)出市場(chǎng)上的其他產(chǎn)品好幾里地了。這些簡(jiǎn)單性是以增加了很多工程量為代價(jià)的,特別是在維護(hù)和更多的設(shè)計(jì)工作方面的工作量,但是,如果能讓用戶可以更加集中在他們的問(wèn)題上,而非工具上的話,這些開銷也是值得的。

Puppet 的可配執(zhí)性是個(gè)非常好的特性,不過(guò)我們做得好像有點(diǎn)過(guò)了。你可以有很多方法來(lái)讓 Puppet 工作起來(lái),并且,在 Puppet 上太容易構(gòu)建工作流了,這在有的時(shí)候可能讓人趕到困窘。我們的一個(gè)短期目標(biāo)是,減少你可以調(diào)整的 Puppet 配置,避免用戶很容易地把 Puppet 調(diào)壞,而且,這樣我們?cè)谏?jí)的時(shí)候也可以考慮更少的邊界情況了。

我們的改變也有些慢了。有很多重構(gòu)都已經(jīng)等待數(shù)年卻仍然沒有進(jìn)行。對(duì)用戶來(lái)說(shuō),短期內(nèi)這意味著更穩(wěn)定的系統(tǒng),但卻更難于維護(hù),而且用戶也更難于向社區(qū)回饋代碼。

最后,我們花費(fèi)了很長(zhǎng)時(shí)間來(lái)認(rèn)識(shí)到,描述我們的設(shè)計(jì)語(yǔ)言的最恰當(dāng)?shù)脑~就是簡(jiǎn)單性。我們現(xiàn)在已經(jīng)開始在考慮簡(jiǎn)單性之外的設(shè)計(jì)目標(biāo)了,我們開始采用更好的決策框架來(lái)決定是否增加或移除特性,開始通過(guò)考慮背后的深層次原因來(lái)做出決定。

18.6. Conclusion

Puppet 是一個(gè)簡(jiǎn)單的系統(tǒng),也是一個(gè)復(fù)雜的系統(tǒng)。它由很多的部分組成,但各個(gè)部分之間的耦合非常松,每個(gè)部分從2005年至今都發(fā)生了很多變化。它是一個(gè)可以用于處理各種配置問(wèn)題的框架,但作為一個(gè)應(yīng)用,它非常簡(jiǎn)單易用。

在未來(lái),我們的成功將依賴于更加堅(jiān)實(shí)、更加簡(jiǎn)單的框架,并讓應(yīng)用在增強(qiáng)能力的同時(shí),保持易用性。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)