[TOC]
作為一個開發人員,我們也需要去了解如何配置服務器。不僅僅因為它可以幫助我們更好地理解 Web 開發,而且有時候很多 Bug 都是因為服務器環境引起的——如臭名昭著地編碼問題。
* 一些簡單的 Ops 技能。
* 了解服務器的相關軟件
* 搭建運行 Web 應用的服務器
* 自動化部署應用
為了即時的完成工作,你是不是放棄了很多東西,比如質量? 測試是很重要的一個環節,不僅可以為我們保證代碼的質量,而且還可以為我們以后的重構提供基礎條件。
作為一個在敏捷團隊里工作的開發人員,初次意識到在國內大部分的開發人員是不寫測試的時候,我還是有點詫異。
盡管沒有寫測試可以在初期走得很快,但是在后期就會遇到一堆麻煩事。傳統的思維下,我們會認為一個人會在一家公司工作很久。而這件事在最近幾年里變化得特別快,特別是在信息技術高速發展的今天。人們可以從不同的地方得到哪里缺人,從一個地方到另外一個地方也變得異常的快,這就意味著人員流動是常態。
而代碼盡管還在,但是卻會隨著人員流動而出現更多的問題。這時如果代碼是有有效的測試,那么則可以幫助系統更好地被理解。
## 隔離與運行環境
為了將我們的應用部署到服務器上,我們需要為其配置一個運行環境。從底層到頂層有這樣的運行環境及容器:
1. 隔離硬件:虛擬機
2. 隔離操作系統:容器虛擬化
3. 隔離底層:Servlet 容器
4. 隔離依賴版本:虛擬環境
5. 隔離運行環境:語言虛擬機
6. 隔離語言:DSL
實現上這是一個請求的處理過程,一個 HTTP 請求會先到達你的主機。如果你的主機上運行著多個虛擬機實例,那么請求就會來到這個虛擬機上。又或者是如果你是在 Docker 這一類容器里運行你的程序的話,那么也會先到達 Docker。隨后這個請求就會交由 HTTP 服務器來處理,如 Apache、Nginx,這些 HTTP 服務器再將這些請求交由對應的應用或腳本來處理。隨后將交由語言底層的指令來處理。

Docker Tomcat
不同的環境有不同的選擇,當然也可以結合在一起。不過,從理論上來說在最外層還是應該有一個真機的,但是我想大家都有這個明確的概念,就不多解釋了。
### 隔離硬件:虛擬機
在虛擬機技術出現之前,為了運行不同用戶的應用程序,人們需要不同的物理機才能實現這樣的需求。對于 Web 應用程序來說,有的用戶的網站訪問量少消耗的系統資源也少,有的用戶的網站訪問量大消耗的系統資源也多。雖然有不同的服務器類型可以選擇,然而對于多數的訪問少的用戶來說他們需要支付同樣的費用。這聽上去相當的不合理,并且也浪費了大量的資源。并且對于系統管理員來說,管理這些系統也不是一件容易的事。在過去硬件技術革新特別快,讓操作系統運行在不同的機器上也不是一件容易的事。
> 虛擬機(Virtual Machine)指通過軟件模擬的具有完整硬件系統功能的、運行在一個完全隔離環境中的完整計算機系統。
這是一個很有意思的技術,它可以讓我們在一個主機上同時運行幾個不同的操作系統。我們可以為這幾個操作系統使用不同的硬件,在這之上的應用可以使用不同的技術棧來運行,并且從理論上互相不影響。其架構如下圖所示:

虛擬機
借助于虛擬機技術,當我們需要更多的資源的時候,創建一個新的虛擬機就行了。同時,由于這些虛擬機上運行的是同樣的操作系統,并且可以使用相同的配置,我們只需要編寫一些腳本就可以實現其自動化。當我們的物聯機發生問題時,我們也可以很快將虛擬機遷移或恢復到另外的宿主機。
### 隔離操作系統:容器虛擬化
對于大部分的開發團隊來說,直接開發基于虛擬機的自動化工具不是一件容易的事,并且他從使用成本上來說比較高。這時候我們就需要一些更輕量級的工具容器——它可以提供輕量級的虛擬化,以便隔離進程和資源,而且不需要提供指令解釋機制以及全虛擬化的其他復雜性。并且,它從啟動速度上來說更快。
#### LXC
在介紹 Docker 之前,我們還是稍微提一下 LXC。因為在過去我有一些使用 LXC 的經歷,讓我覺得 LXC 很贊。
> LXC,其名稱來自 Linux 軟件容器(Linux Containers)的縮寫,一種操作系統層虛擬化(Operating system–level virtualization)技術,為 Linux 內核容器功能的一個用戶空間接口。它將應用軟件系統打包成一個軟件容器(Container),內含應用軟件本身的代碼,以及所需要的操作系統核心和庫。通過統一的名字空間和共用 API 來分配不同軟件容器的可用硬件資源,創造出應用程序的獨立沙箱運行環境,使得 Linux 用戶可以容易的創建和管理系統或應用容器。
我們可以將之以上面說到的虛擬機作一個簡單的對比,其架構圖如下所示:

LXC vs VM
我們會發現虛擬機中多了一層 Hypervisor——運行在物理服務器和操作系統之間,它可以讓多個操作系統和應用共享一套基礎物理硬件。這一層級可以協調訪問服務器上的所有物理設備和虛擬機,然而由于這一層級的存在,它也將消耗更多的能量。據愛立信研究院和阿爾托大學發表的論文表示:Docker、LXC 與 Xen、KVM 在完成相同的工作時要少消耗10%的能耗。
LXC 主要是利用 cgroups 與 namespace 的功能,來向提供應用軟件一個獨立的操作系統運行環境。cgroups(即Control Groups)是 Linux 內核提供的一種可以限制、記錄、隔離進程組所使用的物理資源的機制。而由 namespace 來責任隔離控制。
與虛擬機相比,LXC 隔離性方面有所不足,這就意味著在實現可移植部署會遇到一些困難。這時候,我們就需要 Docker 來提供一個抽象層,并提供一個管理機制。
#### Docker
> Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的容器中,然后發布到任何流行的 Linux 機器上,也可以實現虛擬化。Docker 可以自動化打包和部署任何應用、創建一個輕量級私有 PaaS 云、搭建開發測試環境、部署可擴展的 Web 應用等。
構建出 Docker 的 Container 是一個很有意思的過程。在這一個過程中,首先我們需要一個 base images,這個基礎鏡像不僅包含了一個基礎系統,如 Ubuntu、Debian。他還包含了一系列的模塊,如初始化進程、SSH 服務、syslog-ng 等等的一些工具。由上面原內容構建了一個基礎鏡像,隨后的修改都將于這個鏡像,我們可以用它生成新的鏡像,一層層的往上疊加。而用戶的進程運行在 writeable 的 layer 中。

Docker Container
從上圖中我們還可以發現一點: Docker 容器是建立在 Aufs 基礎上的。AUFS 是一種 Union File System,它可以把不同的目錄掛載到同一個虛擬文件系統下。它的目的就是為了實現上圖的增量遞增的過程,同時又不會影響原有的目錄。即如下的流程如下:

AUFS 層
其增量的過程和我們使用 Git 的過程中有點像,除了在最開始的時候會有一個鏡像層。隨后我們的修改都可以保存下來,并且當下次我們提交修改的時候,我們也可以在舊有的提交上運行。
因此,Docker 與 LXC 的差距就如下如圖所示:

LXC 與 Docker
LXC 時每個虛擬機只能是一個虛擬機,而 Docker 則是一系列的虛擬機。
### 隔離底層:Servlet 容器
在上面的例子里我們已經隔離開了操作系統的因素,接著我們還需要解決操作系統、開發環境引起的差異。早期開發 Web 應用時,人們使用 CGI 技術,它可以讓一個客戶端,從網頁瀏覽器向執行在網絡服務器上的程序請求數據。并且 CGI 程序可以用任何腳本語言或者是完全獨立編程語言實現,只要這個語言可以在這個系統上運行。而這樣的腳本語言在多數情況下是依賴于系統環境的,特別是針對于 C++ 這一類的編譯語言來說,在不同的操作系統中都需要重新編譯。
而 Java 的 Servlet 則是另外一種有趣的存在,它是一種**獨立于平臺和協議**的服務器端的 Java 應用程序,可以生成動態的 Web 頁面。
#### Tomcat
在開發 Java Web 應用的過程中,我們在開發環境使用 Jetty 來運行我們的服務,而在生產環境使用 Tomcat 來運行。他們都是 Servlet 容器,可以在其上面運行著同一個 Servlet 應用。Servlet 是指由 Java 編寫的服務器端程序,它們是為響應 Web 應用程序上下文中的 HTTP 請求而設計的。它是應用服務器中位于組件和平臺之間的接口集合。
Tomcat 服務器是一個免費的開放源代碼的 Web 應用服務器。它運行時占用的系統資源小,擴展性好,支持負載平衡與郵件服務等開發應用系統常用的功能。除此,它還是一個 Servlet 和 JSP 容器,獨立的 Servlet 容器是 Tomcat 的默認模式。其架構如下圖所示:

Tomcat架構
Servlet 被部署在應用服務器中,并由容器來控制其生命周期。在運行時由 Web 服務器軟件處理一般請求,并把 Servlet 調用傳遞給“容器”來處理。并且 Tomcat 也會負責對一些靜態資源的處理。
### 隔離依賴版本:虛擬環境
對于 Java 這一類的編譯語言來說,不存在太多語言運行帶來的問題。而對于動態語言來說就存在這樣的問題,如 Ruby、Python、Node.js 等等,這一個問題主要集中于開發環境。當然如果你在一個服務器上運行著幾個不同的應用來說,也會存在這樣的問題。這一類的工具在 Python 里有 VirtualEnv,在 Ruby 里有 RVM、Rbenv,在 Node.js 里有 NVM。
下圖是使用 VirtualEnv 時的不同幾個應用的架構圖:

VirtualEnv
如下所示,在不同的虛擬環境里,我們可以使用不同的依賴庫。在這上面構建不同的應用,也可以使用不同的 Python 版本來構建系統。通常來說,這一類的工具主要用于本地的開發環境。
### 隔離運行環境:語言虛擬機
最后一個要介紹的可能就是更加抽象的,但是也是更加實用的一個,JVM 就是這方面的一個代表。在我們的編程生涯里,我們很容易就會遇到跨平臺問題——即我們在我們的開發機器上開發的軟件,在我們的產品環境的機器上就沒有辦法運行。特別是當我們使用 Mac OS 或者 Windows 機器上開發了我們的應用,然后我們需要在 Linux 系統上運行,就會遇到各種問題。并且當我們使用了一個需要重新編譯的庫時,這種問題就更加麻煩。
如下圖所示的是 JVM 的架構示意圖

JVM
JVM 是一種用于計算設備的規范,它是一個虛構出來的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。它可以實現“編寫一次,到處運行”。
換句話來說,它在底層實現了環境隔離,它屏蔽了與具體操作系統平臺相關的信息,使得 Java 程序只需生成在 Java 虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。
基于此,只要其他編程語言的編譯器能生成正確 Java bytecode 文件,這個語言也能實現在 JVM 上運行。如下圖所示的是基于 JVM 的 Jython 語言的架構圖:

Jython
其底層是基于 JVM,而編寫時則是用 Python 語言,并且他可以使用 Java 的模塊來編程。
常見擁有同樣架構的工具,還有 MySQL,如下圖是所示的是 MySQL 的架構圖:

MySQL
MySQL 在最頂層提供了一個名為 SQL 的查詢語言,這個查詢語言只能用于查詢數據庫,然而它卻是一種更高級的用法。它不像通用目的語言那樣目標范圍涵蓋一切軟件問題,而是專門針對某一特定問題的計算機語言,即領域特定語言。
### 隔離語言:DSL
這是一門特別有意思也特別值得期待的技術,但是實現它并不是一件容易的事。
作為討論隔離環境的一部分,我們只看外部 DSL。內部 DSL 與外部 DSL 最大的區別在于:外部 DSL 近似于創建了一種新的語法和語義的全新語言。如下圖所示是兩中 DSL 的一種對比:

內部 DSL 和外部 DSL
在這樣的外部 DSL 里,我們有自己的語法、自己的解析器、類型檢測器等等。最簡單且最常用的 DSL 就是 Markdown,如下圖所示:

Markdown
如果我們可以將我們的業務邏輯寫成 DSL,那么我們就不需要擔心底層語言的變動過多地影響原有的業務邏輯。換句話說,這相當于創建了我們自己的語言隔離環境,我們不需要思考用何種語言來實用我們的業務。
## LNMP 架構
> LNMP 是一個基于 CentOS/Debian 編寫的 Nginx、PHP、MySQL、phpMyAdmin、eAccelerator 一鍵安裝包。可以在 VPS、獨立主機上輕松的安裝 LNMP 生產環境。
由于在前面我們已經介紹過了數據庫和編程語言,這里我們就只介紹 LN 兩項
### GNU/Linux
GNU 工程創始于一九八四年,旨在開發一個完整 GNU 系統。GNU這個名字是“GNU’s Not Unix!”的遞歸首字母縮寫詞。“GNU” 的發音為 g’noo,只有一個音節,發音很像 “grew”,但需要把其中的 r 音替換為 n 音。類 Unix 操作系統是由一系列應用程序、系統庫和開發工具構成的 軟件集合 , 并加上用于資源分配和硬件管理的內核。
Linux 是一種自由和開放源碼的類 UNIX 操作系統內核。目前存在著許多不同的 Linux 發行版,可安裝在各種各樣的電腦硬件設備,從手機、平板電腦、路由器和影音游戲控制臺,到桌上型電腦,大型電腦和超級電腦。Linux 是一個領先的操作系統內核,**世界上運算最快的10臺超級電腦運行的都是基于 Linux 內核的操作系統**。
Linux 操作系統也是自由軟件和開放源代碼發展中最著名的例子。只要遵循 GNU 通用公共許可證,任何人和機構都可以自由地使用 Linux 的所有底層源代碼,也可以自由地修改和再發布。**嚴格來講,Linux 這個詞本身只表示 Linux 內核,但在實際上人們已經習慣了用 Linux 來形容整個基于 Linux 內核,并且使用 GNU 工程各種工具和數據庫的操作系統(也被稱為 GNU/Linux)**。通常情況下,Linux 被打包成供桌上型電腦和服務器使用的 Linux 發行版本。一些流行的主流 Linux 發行版本,包括 Debian(及其衍生版本 Ubuntu),Fedora 和 openSUSE 等。 Linux 得名于電腦業余愛好者 Linus Torvalds。
### HTTP 服務器
> Web 服務器一般指網站服務器,是指駐留于因特網上某種類型計算機的程序,可以向瀏覽器等 Web 客戶端提供文檔,也可以放置網站文件,讓全世界瀏覽;可以放置數據文件,讓全世界下載。
目前最主流的三個 Web 服務器是 Apache、Nginx、IIS。
#### Apache
Apache 是世界使用排名第一的 Web 服務器軟件。它可以運行在幾乎所有廣泛使用的計算機平臺上,由于其跨平臺和安全性被廣泛使用,是最流行的Web 服務器端軟件之一。它快速、可靠并且可通過簡單的 API 擴充,將 Perl/Python 等解釋器編譯到服務器中。
#### Nginx
Nginx 是一款輕量級的 Web 服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器,并在一個 BSD-like 協議下發行。由俄羅斯的程序設計師 Igor Sysoev 所開發,供俄國大型的入口網站及搜索引擎 Rambler(俄文:Рамблер)使用。其特點是占有內存少,并發能力強,事實上 Nginx 的并發能力確實在同類型的網頁服務器中表現較好,中國大陸使用 Nginx 網站用戶有:百度、新浪、網易、騰訊等。
#### IIS
Internet Information Services(IIS,互聯網信息服務),是由微軟公司提供的基于運行 Microsoft Windows 的互聯網基本服務。最初是Windows NT 版本的可選包,隨后內置在 Windows 2000、Windows XP Professional 和 Windows Server 2003 一起發行,但在 Windows XP Home 版本上并沒有 IIS。
#### 代理服務器
> 代理服務器(Proxy Server)是一種重要的服務器安全功能,它的工作主要在開放系統互聯(OSI)模型的會話層,從而起到防火墻的作用。代理服務器大多被用來連接 INTERNET(國際互聯網)和 Local Area Network(局域網)。
## Web 緩存
Web 緩存是顯著提高 Web 站點的性能最有效的方法之一。主要有:
* 數據庫端緩存
* 應用層緩存
* 前端緩存
* 客戶端緩存
不同的緩存類型適用于不同的環境下使用。
### 數據庫端緩存
這個可以用以“空間換時間”來說。比如建一個表來存儲另外一個表某個類型的數據的總條數,在每次更新數據的時候同時更新數據表和統計條數的表。在需要獲取某個類型的數據的條數的時候,就不需要 select count 去查詢,直接查詢統計表就可以了,這樣可以提高查詢的速度和數據庫的性能。
### 應用層緩存
應用層緩存這塊跟開發人員關系最大,也是平時經常接觸的。
* 緩存數據庫的查詢結果,減少數據的壓力。這個在大型網站是必須做的。
* 緩存磁盤文件的數據。比如常用的數據可以放到內存,不用每次都去讀取磁盤,特別是密集計算的程序,比如中文分詞的詞庫。
* 緩存某個耗時的計算操作,比如數據統計。
應用層緩存的架構也可以分幾種:
* 嵌入式,也就是緩存和應用在同一個機器。比如單機的文件緩存,java 中用 hashMap 來緩存數據等等。這種緩存速度快,沒有網絡消耗。
* 分布式緩存,把緩存的數據獨立到不同的機器,通過網絡來請求數據,比如常用的 memcache 就是這一類。
分布式緩存一般可以分為幾種:
* 按應用切分數據到不同的緩存服務器,這是一種比較簡單和實用的方式。
* 按照某種規則(hash,路由等等)把數據存儲到不同的緩存服務器
* 代理模式,應用在獲取數據的時候都由代理透明的處理,緩存機制有代理服務器來處理
### 前端緩存
我們這里說的前端緩存可以理解為一般使用的 cdn 技術,利用 squid 等做前端緩沖技術,主要還是針對靜態文件類型,比如圖片、css、js、html 等靜態文件。
### 客戶端緩存
瀏覽器端的緩存,可以讓用戶請求一次之后,下一次不在從服務器端請求數據,直接從本地緩存讀取,可以減輕服務器負擔也可以加快用戶的訪問速度。
### HTML5 離線緩存
application cahce 是將大部分圖片資源、js、css 等靜態資源放在 manifest 文件配置中。當頁面打開時通過 manifest 文件來讀取本地文件或是請求服務器文件。
離線訪問對基于網絡的應用而言越來越重要。雖然所有瀏覽器都有緩存機制,但它們并不可靠,也不一定總能起到預期的作用。HTML5 使用 ApplicationCache 接口可以解決由離線帶來的部分難題。前提是你需要訪問的 Web 頁面至少被在線訪問過一次。
## 可配置
讓我們寫的 Web 應用可配置是一項很有挑戰性,也很實用的技能。
起先,我們在本地開發的時候為本地創建了一套環境,也創建了本地的配置。接著我們需要將我們的包部署到測試環境,也生成了測試環境的相應配置。這其中如果有其他的環境,我們也需要創建相應的環境。最后,我們還需要為產品環境創建全新的配置。
下圖是 Druapl 框架的部署流:

Drupal Deployment Flow
在不同的環境下,他們使用不同的 Content。這些 Content 的內容不僅僅可以是一些系統相當的配置,也可以是一些不同環境下的 UI 等等。而在這其中也會涉及到一些比較復雜的知識,下面只是做一些簡單的介紹。
### 環境配置
最常見的例子就是我們需要在不同的環境有不同的配置。大原則就是我們不能直接使用產品的環境測試,因此我們就需要為不同的環境配置不同的數據庫:
* 開發環境。即開發者用于開發的環境,大部分的數據都是由我們自己注入的,在開發的過程中我們也會添加一些數據。
* 集成測試環境/測試環境。和開發環境一樣,這些數據也是由我們注入的,而這些數據主要是為于測試目的。當我們的應用出現Bug的時候,我們可能就需要添加新的測試及其測試數據。
* 模擬環境(Stageing)。在軟件最終發布前,開發或者設計人員對軟件進行調整后可以及時預覽改變的測試環境,這個環境更接近于產品最終發布后的運行環境。因此,這個環境的數據一般來說就是產品環境的一些舊數據——可能是幾個月前,幾年前的數據。
* 產品環境。即線上環境,都是真實的用戶數據。
因此從理論上來說,我們就需要4~5個不同的數據庫配置。而這些不同的數據庫配置并不代表著他們使用的是相同的數據庫。我們可以在本地環境使用 SQLite,而在我們的產品環境使用 MySQL。不過,最好的情況是我們應該使用同一個配置。這樣當出現問題的時候,我們也很容易排查、
而除了數據庫配置之外,我們還有一些其他配置。因此針對于不同的環境的配置最好獨立地寫在不同的文件里。并且這些配置最好可以以文件名來區分,如針對于開發環境,就是`dev.config.js`,針對于測試環境就是?`test.config.js`。
因此,為了實現不同的環境使用不同的配置,我們就需要有一個變更控制。如果我們只有相應的配置,而沒有對應的運行機制那就有問題了。
### 運行機制
當我們的應用程序在服務器上運行得好好的時候,我們可能就不想因為修改配置而去重啟機器,這時候我們就需要配置熱加載。即我們修改配置后,不需要重啟服務即可以使用新的配置。對應的還有一種,便是我們需要重啟機器才能實現配置。
無論是哪種方式都需要修改配置來實現。而在我們使用的過程中熱加載可能需要消耗一些系統資源,因為我們的系統需要不斷地讀取配置的狀態并對其進行判斷。并且如果我們的應用運行在多個機器上的時候,我們可能需要一個個的上支個性。而如果我們是冷啟動的話,我們就可以考慮使用自動部署的方式來完成。
對應的,我們也需要在我們的代碼中實現判斷這些配置的邏輯。
### 功能開關
當我們上線了我們的新功能的時候,這時候如果有個 Bug,那么我們是下線么?要知道這個版本里面包含了很多的 Bug 修復。如果在這個設計這個新功能的時候,我們有一個可配置和 Toogle,那么我們就不需要下線了。只需要切的這個toggle,就可以解決問題了。
對于有多套環境的開發來說,如果我們針對不同的環境都有不同的配置,那么這個靈活的開發會幫助我們更好的開發。
#### Feature Toggle
它是一種允許控制線上功能開啟或者關閉的方式,通常會采取配置文件的方式來控制。其過程如下圖所示:

Feature Toggle
當我們需要 A 功能的時候,我們就只需要把 A 功能的開關打開。當我們需要 B 功能,而不需要 A 功能的時候,我們就可以把相應的功能關掉。像在 Java 里的 Spring 框架,就可以用 PropertyPlaceHolder 來做相似的事。使用 bean 文件創建一個 properties
~~~
<util:properties id="myProps" location="WEB-INF/config/prop.properties"/>
~~~
然后向注入這個值:
~~~
@Value("#{myProps['message']}")
~~~
我們就可以直接判斷這個值是否是真,從而顯示這個內容。
~~~
<spring:eval expression="@myProps.message" var="messageToggle"/>
<c:if test="${messageToggle eq true}">
message
</c:if>
~~~
這是一種很實用,而且很有趣的技術。
參考書籍:**《配置管理最佳實踐》**
## 自動化部署
優化我們開發流程有一個很重要的步驟就是:讓部署自動化。通過部署自動化,我們可以大大縮減我們的開發周期,加快軟件交付流程。下圖是一個自動化部署的流程圖:

自動化部署
從下圖中我們可以得到下面的五個步驟:
* 獲取源碼
* 獲取依賴
* 構建軟件包
* 生成/上傳安裝包
* 目標平臺安裝/配置
這個過程可能和之前的 Web 項目構建過程差不多,然而卻多了好幾步。
在前面的章節里,我們已經使用了版本管理系統來管理我們的源碼。因此,在這里對于獲取源碼的介紹就比較簡單了——我們只需要在我們的 CI(持續集成)服務器上使用?`git clone`?這一類的方法來獲取我們的源碼即可。
### 依賴與包倉庫
獲取完源碼后,我們就需要開始下載軟件包依賴。無論是 Python、Ruby、Java,還是 JavaScript 都需要這樣的一個過程。軟件開發已經從大教堂式的開發走向了集市——開源軟件改變了這一切。

大教堂與集市
過去我們需要大系統的內部構建我們使用的依賴,現在我們更多地借助于外部的庫來實現這些功能。這也意味著,如果在這一個節點里出現了意外——軟件被刪除,那么這個系統將陷入癱瘓的狀態。如之前在 NPM 圈發生了“一個 17 行的模塊引發的血案”——即 left-pad 工具模塊被作者從 NPM 上撤下,所有直接或者間接依賴這個模塊的 NPM 的軟件包都掛掉了。因為我們依賴于公有的包服務,所以系統便嚴重依賴于外部條件。
這時候一種簡單、有效的方案就是搭建自己的包服務。如使用 Java 技術棧的項目,就會使用 Nexus 搭建自己的 Maven 私有服務。我們的軟件依賴包將會依賴于我們自己的服務,此時會產生的主要問題可能就是:我們的軟件包不是最新的。但是對于追求穩定的項目來說,這個并不是必須的需求,反而這也是一個優勢。
### 構建軟件包
在一些編譯型語言里,在我們運行包測試后,我們將會得到一個軟件包。如 Jar 包,它是 Java 中所特有一種壓縮文檔。Jar 包無法直接安裝使用,雖然我們可以直接運行這個 Jar 包,但是我們需要通過一些手段將這個 Jar 包拷貝到我們的服務器上,然后運行。在特定的時候,我們還需要修改配置才能完成我們的工作。
因此,使用 RPM 或者 DEB 包會是一種更好的選擇。RPM 全稱是 Red Hat Package Manager(Red Hat包管理器),它工作于 Red Hat Linux 以及其它 Linux 和 UNIX 系統,可被任何人使用。如下圖是 RPM 包的構建過程:

RPM Build Process
要構建一個標準的 RPM 包,我們需要創建 .spec文件,這個文件包含軟件打包的全部信息——如包的 Summary、Name、Version、Copyright、Vendor 等等。在產生完這一個配置文件后,執行 rpmbuild 命令,系統會按照步驟生成目標 RPM 包。
### 上傳和安裝軟件包
生成對應的軟件包后,我們就可以將其上傳到 Koji 上,它是 Fedora 社區的編譯系統。如下圖所示:

RPM Build Process
如果我們已經對我們的所有目標操作系統配置過,即配置好了軟件源,那么我們就可以直接在我們的服務器上使用包管理工具安裝,如`yum install`。