# 6

## ch6 部署

## 持續集成, CI, Continuous Integration

CI 能夠保證新提交的代碼 和已有代碼進行集成，從而讓所有人保持同步。 CI server 會檢測到代碼 已提交並簽出，然後花些時間驗證代碼可以 編譯 & 通過測試。

### 如何在 微服務、CI 構建、源代碼 三者之間，建立起合適的映射呢?

#### 最簡單的做法

先把所有東西放在一起，我們有一個巨大的代碼庫，裡面包含全部的代碼，並且只有一個構建。 這個代碼庫的任何一個 commit 都會觸發構建，在這個構建中，我們會運行所有的驗證、測試。運行所有跟微服務有關的驗證。 每個構建會產生 構建號相同的物件。

優點：需要關心的代碼庫較少、概念上的構建比較簡單、開發者的工作也被簡化，只要提交就好了

缺點：如果僅僅修改一行，所有的其他服務也要進行驗證和構建，但事實上或許並不需要這樣做，所以我們花費了不必要的時間，而這會影響 CI 的週期，也會影響從開發到上線的速度。 更糟糕的是，我不知道那些構建物應該被重新部署、那些不應該。 很慘的是，如果有一行構建失敗，在那個構建修復以前，其他的代碼都無法提交，倘若多個團隊都在同一個巨大的代碼庫，真的是超歡樂的。

#### 上述的變體

保留一個代碼庫，但是存在多個CI，會分別映射到代碼庫的不同目錄。 如果目錄結構定義的合理，就會很容易把一個目錄映射到一個構建中。

雙面刃：可以簡化 檢出/檢入 的流程，也可以讓你覺得同時提交多個服務的修改很容易，從而做出將多個服務耦合在一起的修改。

#### 每個微服務都有自己的CI

這樣就可以在微服務部署到生產環境之前，先做一個快速的驗證。 這裡的每個微服務都有自己的代碼庫，分別與對應的CI綁定。 當進行修改時，可以只運行相關的構建和測試，並且只會得到一個需要部署的構建，代碼庫和團隊的匹配程度也更高。

缺點：跨微服務的修改會更難，但相比單塊代碼庫 & 單塊建構流程 所帶來的問題，這應該比較好解決。(使用命令行腳本)

### 構建流水線和持續交付

把一個構建 分成很多個階段 是很有價值的。 將各個不同的類型分開，例如測試分成：單元測試、整合測試。 畢竟單元測試都沒過，整合測試也沒有測的必要。 將構建分解成多個階段，也就是所謂的 **構建流水線**。

**構建流水線** 可以很好的追蹤軟體的構建進度：每完成一個階段，就離終點更近一步。 構建物會在整個構建的第一個環節被生成，然後被用在整個流水線中。 隨著構建物通過不同的階段，我們越來越確認軟體的品質。

### CD, Continuous Delivery, 持續交付

基於上述的概念，CD 能夠將每次提交是否有達到上線的要求，並將信息回饋給我們。它會把每次提交 當成是發布版本來對待。

#### 流水線的範例

```
編譯&快速測試 --->  耗時測試  --->  用戶驗收測試  ---> 性能測試  --->  生產環境
 (單元測試)        (整合測試)       (UI測試)
```

使用真正重視 CD 概念的工具，利用對整個軟體上線過程建立流程，使軟體品質的可視化得到極大的改善，這可以大幅減少發布之間的間隔。

在微服務的世界，我們希望服務之間彼此獨立，所以擁有各自獨立的 CI，會是比較好的做法。

### 不可避免的例外

**每個微服務一個構建** 基本上在大多數情況下都是合理的，那麼 .. 是否有例外呢?

當一個團隊剛開始啟動一個新項目，對於服務的邊界還沒有這麼明確時，可以先將初始服務都先放在一起。

在最開始的階段，經常會發生跨服務邊界的修改，時常會有某些內容 移入/移出 某個服務，這個階段 把服務都放在同一個構建，可以節省跨服務的成本。

### 平台特定的構建物

#### 技術的構建物

Java JAR、WAR Ruby gem Python egg

從微服務的角度看，有些技術中，只有構建物本身是不夠的。 可能還需要 Apache or Nginx 中的進程管理器。 所以為了部署、啟動 這些構建物，需要安裝、配置一些環境，接著才能啟動構建物。 例如 Puppet、Chef 這樣的自動化配置管理工具，就可以很好的解決這個問題。

另一個問題是 不同技術的構建物 都不相同，如何利用自動化去對不同構建物的底層進行部署，讓事情從複雜變得簡單。

### 操作系統的構建物

為了避免多種技術帶來的問題，可以用 作業系統 支持的操作方式。 RedGat or CentOS --> RPM Ubuntu --> deb Windows --> MSI

使用OS特定構建物的好處是，不用考慮底層用什麼技術，就可以完成安裝。這些系統操作工具也可以進行 卸載&查詢，也可以自動幫我們做很多事情。

缺點：剛開始編寫構建腳本的過程可能會比較困難 Linux --> FPM Windows --> NuGet

**如果在Linux 上工作，很建議用 基於OS的軟件包管理工具 來進行部署。**

### 定制化鏡像

#### 使用自動化配置管理工具的一個問題，就是花費大量時間去運行腳本、進行配置。

有時候不希望讓機器運行時間過長，所以會定時去關閉、開啟新的實體。

而且這些安裝的時間，也會使CI無法得到快速的反饋。

(藍/綠部署 可以讓我們老版本不下線的同時，去部署新版本)

為了減少啟動時間，有一種方式就是創建虛擬機鏡像。 可以先在該鏡像內處理好一定程度的環境、工具，之後要部署時，只需要根據該鏡像創建一個實體，然後在上面安裝最新的版本就好了。

只要核心依賴沒有改變，那麼就可以一直用這個鏡像。

缺點：構建鏡像會花費大量時間，而這個問題可以用 Docker 這種容器技術去避免。

Packer ([http://www.packer.io/](http://www.packer.io/)) 可以用來簡化這個創建過程。這個工具提供 VMWare、AWS、Rackspcace雲、Digital Ocean、Vagrant 這些支持，而且在Linux、Windows都可以使用。 而且可以用喜歡的工具 (Chef、Ansible、Puppet)，去做同一配套中，不同平台的鏡像。 也可以開發和測試用 Vagrant，生產環境用 AWS

### 不可變服務器

將 config 也納入版本控制，我們可以自動化重建服務，甚至重建整個環境。 但如果在部署完成之後，有人登入到機器上，對配置做了一些修改呢? 這會導致機器上的實際配置 和 Source Code 的配置不一致，就個問題就叫做 **配置飄移**。

為了避免這個問題，可以禁止對任何運行中的 server 做手動修改。(可以在創建鏡像的過程中，將其設定為禁止SSH) 相反，不論多小的修改，都需要經過構建流水線的過程。

任何能簡化工作的措施都值得嘗試。

### 環境

當軟體在CD流水線的不同階段時，也會被部署到不同的環境中。 我們的微服務構建物從頭到尾都是一樣的，但環境不同。 至少他們的主機是隔離的、配置也不一樣。

但真實情況往往會更複雜得多 ... 舉個例子，我們的生產環境可能會包括兩個 資料中心 的多台主機，使用 負載平衡 來管理，而測試環境可能會把所有服務運行在同一台機器上。 而這樣環境之間的不同，可能會引起一些問題。

從筆記本到UAT，最終再到生產環境，我們希望前面的那些環境可以不斷靠近生產環境，這樣就可以更快的碰到環境差異導致的問題。 這是必須去做權衡的，雖然重建生產環境所消耗的時間和代價會讓人望而卻步。

**生產環境 和 快速反饋 之間的平衡不是一成不變，應該按照那些產生的bug 和反饋時間去做調整。**

### 服務配置

有一種方法是 只創建一個 構建物，並將配置單獨管理。 這針對的是每個環境 一個屬性文件，或者是傳入到安裝過程的一些參數。

還有一個應對大量微服務 比較流行的方法，就是使用專用系統來配置服務。(ch11)

### 服務與主機之間的映射

#### 單主機多服務

優點：簡化開發人員的工作、

缺點：監控變困難、服務之間互相影響 資源分配、可能會演變成無法獨立部署、對於團隊的自治性不利。

#### 每個主機一個服務

這種模型避免了前者的問題，並簡化監控和錯誤恢復。 這種方式也可以減少潛在的單點故障。 一台主機當機，只會影響一個服務(雖然在虛擬化平台上不一定真的是這樣) (ch11 會提到更多伸縮和故障方面的討論)

我們也很容易獨立於其他服務的對某一個服務進行擴展，安全性措施也可以更有目的的在更小範圍內進行。

更重要的是 我們可以採取不同的部署技術，比如前面提到的 **基於鏡像的部署** 和 **不可變服務器模式**

我們已經為 微服務架構 帶來了很多複雜性。 接下來我們要尋找更多複雜性的根源 \~

如果沒有一個適合的 PaaS 平台，這個模型已經很好的降低複雜性。

優點：降低系統複雜性、簡化問題排查、更容易擴展和部署

缺點：主機數量的增加，也會引入很多的隱式代價。盡管如此，作者仍然認為這是比較適合微服務架構的模型。 下面會提到如何降低管理大量主機所帶來的額外負擔。

### PaaS , Platform-as-a-Service, 平台即服務

Heroku 就是一個黃金級的 PaaS，不僅能夠管理服務的運行，還能用簡單的方式提供資料庫的服務。

當 PaaS 正常工作時，他表現得很好。但當出現問題時，我們通常沒辦法通過操作底層系統去解決問題，而這就是我們必須做出的取捨。

因為平台通常都是為了滿足比較通用的需求，所以當我們的應用程式越不標準，就越難和 PaaS 進行工作。

### 自動化

如果都用手動管理所有的事情，當主機數量增加，就會產生更多的管理開銷，更何況單主機單服務會需要很多主機。

所以我們需要將主機控制、服務部署、服務監控、日誌收集等工作自動化\~

### 從物理機到虛擬機

管理大量主機的關鍵之一，就是利用VMWare這樣的傳統虛擬化技術，或者AWS，去減少管理主機的開銷。

#### 傳統的虛擬化技術

如果我們想把每個服務部署在獨立的主機上，為什麼不把物理設備切小塊呢?

但就像是抽屜的隔板，VMWare 就是那些隔板，也是需要成本、花費的，當一台機器切割了越多主機，VMWare就會吃越多資源，那服務本身能用的資源總和就越少。

#### Vagrant

Vagrant 是一個很有用的部署平台，通常在開發和測試的時候使用，而非生產環境。 Vagrant 可以在你的筆記本創建一個虛擬的雲，底層使用標準的虛擬化技術(通常是VirtualBox，但也可以使用其他平台) 可以使用文字檔來定義一系列的虛擬機，也可以在其中定義網路配置&鏡像等信息。 也可以把這個文字檔提交到代碼庫，與團隊其他成員共享。

優點： 這些工具可以幫助我們在本地機器上 輕鬆的創建出類似生產環境的模擬。 也可以同時創建多個VM，通過關掉幾台來測試故障模式，也可以把本地目錄映射到虛擬機，這樣改完就可以馬上看到效果。 對於使用AWS的團隊，使用Vagrant 帶來的快速反饋也有不少好處。

缺點： 開發機上有很多額外的資源消耗。 如果一個服務占用一台虛擬機，可能會很難在本地機器上搭建整個系統。 結果就是為了開發和測試，可能會把其中的一些依賴打掉，讓事情比較容易控制。

#### Linux 容器

相比起 hypervisor 隔離&控制 虛擬主機的方法，Linux 容器 可以直接創建一個隔離的 進程空間，進而在這個空間運行其他進程。

在Linux上，進程必須用戶來控制，根據權限有不同的能力。 進程可以創建其他進程。 Linux 內核 就是在維護這個進程樹。

Linux 容器 擴展了這個想法，每個容器都像是一個系統進程樹的子樹： 最常用的 LXC，其他還有 Solaris Zones、Open VZ。

因為必須共享內核，因為進程樹存在於內核，所以我們可以主機操作Ubuntu，容器中運行 CentOS，因為只要內核相同就可以了。

優點：啟動很快，而且可以更好對容器進行資源分配，也比較容易去調整底層。對資源的利用更有效率。

缺點：需要用某種方式 把外界的請求 對應到內部的容器中。 (利用 IPTable 來配置端口映射，從而直接將容器暴露給外界) 容器並不是完全隔離，有些文檔和已知方法都介紹了，某些容器中的進程，有可能會跳出該容器與其他容器的進程，或者和底層發生干擾。 (如果想要更高的隔離性，應該使用虛擬機)

### Docker

Docker 是構建在極輕量級容器之上的平台。 它可以幫忙處理大多數跟容器管理有關的事情。 你可以在 Docker 中創建和部署應用，這些基於容器的應用 與 VM世界中的鏡像很類似。 Docker 也可以管理容器的配置，並處理一些網路問題，甚至還可以存儲應用程序的版本 (registry)

Docker 還可以緩解更多服務進行本地開發&測試的問題，我們可以將許多個Docker 實例 運行在 Vagrant 的 單個VM上，而非原來的 一個VM 對 一個服務。 當使用完後，就可以使用 Vagrant 去銷毀 Docker 平台本身。 並使用 Docker 來快速配置每個服務。

Docker 有很多相關的技術，例如 CoreOS 就是一個專門為 Docker 設計的操作系統。 它是一個被裁剪過的 Linux OS，僅提供有限的功能來保證 Docker 的運行，因此它也只需要較少的資源，從而把更多的資源留給容器。 它甚至沒有debs、RPM 這樣的工具，因為所有軟件都被裝在一個獨立的 Docker 應用程式內，並只在自己的容器內運行。

**Docker 本身並不能解決所有問題，它只是一個很簡單的 PaaS** 你需要搭配適合的工具，來幫助你跨多台機器去管理 Docker 上的服務。 最近 Google 的開源工具 Kubernetes 和 CoreOS 集群技術 能夠提供幫助。

另一個有趣的工具是 Deis ([http://deis.io](http://deis.io)) ，它試圖在Docker 之上，提供一個類似 Heroku 那樣的 PaaS。

### 一個部署接口

不管部署的底層平台 or 構建物是什麼，用一個統一的接口來部署給定的服務，都是一個很關鍵的實踐。 從本地開發 \~ 生產環境，盡量讓它們一致，就可以讓問題及早發現。

參數化的命令行調用 例如： `$ deploy artifact=catalog environment=local version=local`

Fabric：Python Boto：AWS Capistrano：Ruby PowerShell：Windows

### 環境定義

可以把環境定義想像成 微服務 到 計算、網路、存儲資源 之間的映射。

1. 改變環境配置的大小，去應對不同的測試，使資源充分利用
2. 能夠在不同環境設置不同憑證(credential)。敏感環境的憑證會被存儲到不同的程式庫，這些代碼只有少數人可以訪問。
3. 默認狀況下，如果一個服務有多台節點需要配置，會自動為其創建負載平衡。

構建一個類似的系統的工作量很大。 **這些代價基本上都需要在前期付出， 但是做好以後，可以很好的管理部署的複雜性。** 我希望將來你不需要自己做這些事情\~ Terraform 是來自 Hashicorp 的一個很新的工具，它可以幫你做上述的事情。

### 小結

專注於保持 **服務能夠獨立於其他服務 進行部署** 的能力，無論採用什麼技術，都要確保能提供這樣的能力。 我傾向於一個服務一個代碼，對於 **每個微服務一個CI** 這件事情，不僅僅是傾向，而是非常堅持。 因為只有這樣，才能實現獨立部署。

**將每個服務 放到單獨的 主機/容器 中。** 看看類似 LXC or Docker 這樣的替代技術，如何簡化對多個服務的管理。 但記住，不論選用什麼樣的技術，都要確保可以 **自動化**。 如果採用的技術不能自動化，就去選一個可以新的可以自動化的技術吧 \~ (使用 AWS 這樣的平台，能夠在進行自動化時，提供許多便利。)

確保能理解 **部署的技術** 會對 **開發人員** 產生怎樣的影響，並確保他們也能感受到。 **創建工具 對任何給定服務到不同環境的自動部署 提供服務** 是非常重要的！ 他對開發、測試、微運人員，都能提供很大的幫助。
