從 0 開始的微服務架構:(五)代碼給你,看如何用Docker支撐微服務
很好的一篇文章,全面、系統。
雖然已經紅了很久,但是“微服務架構”正變得越來越重要,也將繼續火下去。各個公司與技術人員都在分享微服務架構的相關知識與實踐經驗,但我們發現,目前網上的這些相關文章中,要麽上來就是很有借鑒意義的幹貨,要麽就是以高端的專業術語來講述何為微服務架構。就是沒有一個做到成熟地將技術傳播出來,同時完美地照顧“初入微服務領域人員”,從 0 開始,采用通俗易懂的語言去講解微服務架構的系列。所以,我們邀請青柳雲的蘇槐與 InfoQ 一起共建微服務架構專題“Re:從 0 開始的微服務架構”,為還沒有入門該領域的技術人員開路,也幫助微服務架構老手溫故知新。
這是專題的第五篇文章,看如何用 Docker 支撐微服務。
專題文章傳送
- Re:重識微服務架構
- 快速快速體驗微服務架構?
- 微服務架構 API 的開發與治理
- 如何保障微服務架構下的數據一致性
非常巧合,2013 年 3 月 Docker 發布 0.1 版本,時隔一年(2014 年 3 月)Martin Fowler 給出了微服務架構的定義。經過幾年的發展,兩個毫不相幹的技術最終走到了一起:使用 Docker 來支撐微服務架構的開發和運維。
我們甚至可以說,沒有 Docker 的蓬勃發展,就沒有微服務架構的落地與開花。SoundCloud 的 Phil Calcado 最近卻在 Twitter 上說了這樣的話:
“Microservices came to be because of containers” is a myth. We were already doing microservices on fvcking Weblogic。
起初,我也很好奇他為什麽這麽說,後來才發現他說的是”fvcking Weblogic”,並且早在 15 年接受 InfoQ 采訪的時候,他已坦誠有更好的方法來實現微服務架構的構建流水線:“那是我第一次意識到我們搞砸了,原來有簡單漸進的方式能夠解決研發環境快速構建、基本的監控以及應用快速部署的問題,而且完全沒必要構建自己的系統”。詳情見:
http://www.infoq.com/cn/news/2015/03/soundcloud-microservices
各位怎麽看?
不談這個。在前幾篇文章中,我們已經聊到微服務架構的優勢、劣勢,什麽時候該選擇微服務架構,使用哪些技術和組件來開發微服務,微服務 API 如何治理等等。
同時,前面也提到使用基於 Docker 的微服務平臺來支撐微服務的自動化開發和運維,但是並沒有深入。那麽,這篇文章就來詳細聊聊怎麽使用 Docker 來提高微服務的開發效率和運維效率。
這篇文章的閱讀對象是希望借助 Docker 實施輕量級微服務架構卻不知道如何下手的朋友,所以,我還是延續前文的風格,盡量以淺顯的語言來從以下幾個方面進行介紹。
- Docker 核心概念
介紹 Docker 的鏡像、容器以及鏡像倉庫概念。
- 為什麽使用 Docker 實施微服務架構
介紹使用 Docker 實施微服務架構的優勢。
- 必須了解的 Docker 知識
介紹鏡像構建、容器創建、容器編排、集群管理、文件存儲、容器網絡、容器監控、容器日誌。
-
快速運行一個微服務架構 Demo
包含註冊中心(Eureka)、調用鏈(DCTrace)、Service A、Service B、網關(Zuul)和 Demo 門戶
Docker 核心概念
鏡像(Image)
我們可以認為:鏡像 = 操作系統 + 運行環境 + 應用程序。譬如,我們可以將 Centos7 操作系統、JVM 和 Java 應用程序做成一個鏡像。我們交付的軟件不再是 zip 包或者 war 包,而是鏡像。Docker 鏡像技術實現了應用程序運行環境與主機環境的無關性。
容器(Container)
容器是鏡像的運行態。通過 docker run 命令即可快速的基於鏡像創建一個或多個容器。
鏡像倉庫(Registry)
在項目或者產品的不斷叠代過程中,應用的各版本鏡像存儲在鏡像倉庫中,類似於代碼倉庫 for source code 或 Maven 倉庫 for Jar file。
為什麽使用 docker 實施微服務架構
在前文介紹如何快速搭建一個微服務架構時提到,我們需要使用自動化的構建和監控工具來解決微服務架構運維難的問題。之前由於篇幅問題,並沒有把微服務的運維問題展開來談。所以,這裏我們就來詳細看看 Docker 為微服務開發和運維帶來了什麽樣的好處。
環境依賴隔離
我們經常會碰到一種現象,"應用在我的機器上跑的好好的,部署到生產環境就有出問題了"。Docker 隔離了應用對環境的要求,它將應用依賴的底層庫或者組件制作成鏡像,從而保證了開發、測試、生產環境的一致性。
計算資源隔離
Docker 之前,在同一臺機器上部署多個同構甚至異構應用是非常困難的,不同的應用依賴的底層庫有可能沖突,不同應用之間可能會搶占 CPU 和內存等計算資源。Docker 利用 Linux 的名稱空間 (Namesaces)、控制組 (Contorl groups)、Union 文件系統和容器格式 (Container format) 實現了資源(例如 CPU、內存、IO 等)的隔離,保證同一臺主機上的多個應用不會互相搶占資源。
更高的計算資源利用率
在微服務架構下,系統從單體程序拆成了多個獨立部署的程序,每個程序處於獨立的 Web 容器中,必然增加了對計算資源的需求。Docker 可以幫助抵消這個弊端。
基於資源隔離的特性,我們就可以在主機上部署多個應用,把主機的每個角落的計算資源都利用起來。所以,使用 Docker 後,我們可以提高 5~10 倍的計算資源利用率。
例如原來我們將應用部署在一個 4 核 8G 的主機上,而這個應用真實需要的內存只有 1GB,我們就可以在這臺主機上部署 7 個類似的應用(還有 1GB 留給操作系統)。對我們公司來說,這種利用率的提高在開發測試環境上尤為明顯。
遷移方便
Docker 之前,一個版本發布時,交付物是一個程序包加一份環境配置的文檔。現在,交付物是一個 Docker 鏡像,這個鏡像裏,已經包含了最新版本的程序包和修改過的運行環境。真正上線時,一條命令就可以把最新版本的程序發布起來。這對於需要半夜上線的同學是絕對的救星。
版本管理更便捷
之前,對於版本的管理,更多考慮的源代碼級的,比如開個 Branch 或打個 Tag。現在,版本是一個包含了運行環境和程序包的鏡像。在上線失敗的時候,可以很快回滾到之前的版本。
編排的支持
微服務架構下,一個系統包含多個程序包,而多個程序包之間是有依賴關系的。Docker 編排工具可以幫助管理這些依賴關系,從而達到一鍵創建整個系統的目的。
運維更快速
Docker 除了能夠方便地管理應用系統,還能夠方便地管理 DB、Redis、MQ 這些中間件。除了能夠快速地創建和啟停這些中間件,我們可以基於系統的需求把對這些中間件的優化配置一起做到鏡像裏。中間件研發人員交會的成果不再是一個基礎安裝文件加一堆配置說明,而是一個標準化的鏡像。
環境可重建
以前,運行環境是由基礎環境加一堆配置文檔組成的,如果管理稍有不慎,文檔和真實環境就會不一致。在 Docker 中,使用 Dockerfile 來創建運行環境,每一個對環境的修改都是一條 Dockerfile 命令,所以,運行環境的創建也變得程序化和標準化。從而能夠快速地重建所需要的運行環境,進一步保證開發、測試、生產環境的一致性。
就目前來看,Docker 能夠支持除.net 以外的幾乎所有開發語言。對於.net core,2016 年 6 月發布 1.0 版本,2017 年 8 月發布 2.0 版本,應該說微軟還是在這上面花了不少力氣。只是,還沒有聽到身邊的朋友有誰把.net 項目遷到.net core 上面去。也許還需時日來觀察和沈澱,我們也做了一個基於.net core 的 demo 放到了青柳雲鏡像裏,持續關註。如果哪位朋友有在生產上使用了.net core,請不吝賜教。
必須了解的 Docker 知識
Docker 的理念為“Build, Ship and Run Any App, Anywhere”,通過容器和鏡像的特性讓 DevOps 變得容易,但 Docker 的前景,更在於支持分布式、服務化設計,實現一系列可獨立開發、獨立部署和獨立擴展的服務組合,以保證業務的靈活性和穩定性。
當前 AWS、微軟、阿裏雲、IBM、Redhat、VMware、華為、Intel 等各大公有雲和私有雲提供商都不約而同地大力投資 Docker,實際上就是認可了這樣的趨勢。
利用 Docker 搭建微服務架構,就需要了解一些必需的 Docker 知識,比如鏡像構建、容器創建、容器編排、集群管理、文件存儲、容器網絡、容器監控、容器日誌。
拿一個包含 ABC 組件的微服務系統為例,我們會利用持續集成工具(例如 Jenkins)創建鏡像,並將鏡像推送到鏡像倉庫中(例如 Docker Registry,Harbor),再利用編排工具(例如 Docker Compose)創建並啟動容器。
容器啟動後,ABC 組件就會隨著容器一起啟動,這時就需要考慮 ABC 組件的數據文件如何持久化存儲,分布在不同主機上的組件如何網絡通信(Docker 容器默認不能跨主機通信),容器資源使用情況如何監控,容器日誌如何查看,等等。
下面,就來簡要介紹一下這些知識。
鏡像構建
最簡單的方法,一條命令就可以從鏡像倉庫(Docker registry,類似於代碼倉庫或 Maven 倉庫)裏拉取指定版本的鏡像到本地。例如 docker pull mysql:5.6,5.6 就是鏡像的版本號。
除了從鏡像倉庫裏拉到現成的鏡像,還可以使用 Dockerfile 來創建自定義的鏡像。
由於國內訪問直接訪問 Docker hub 網速比較慢,拉取鏡像的時間就會比較長。我們可以從一些國內的鏡像倉庫上拉取,或者配置阿裏的鏡像加速來拉取,或者自已搭建一個鏡像中心。
- 網易鏡像中心: https://c.163.com/hub#/m/home/
- 阿裏開發者中心: https://dev.aliyun.com/
- 自建鏡像中心: Docker registry 或 Harbor
另外,對於鏡像,還有一個需要了解的概念就是層。對於一個鏡像來說,是分為很多層的,每一條 Dockerfile 命令都會創建一個新的層,但是如果這條命令產生的結果和之前執行過的 Dockerfile 命令產生的結果相同,Docker 就會復用之前已經創建的層。
例如,Dockerfile 裏有三個步驟:拉取 CentOS,安裝 Tomcat,上傳程序包。那麽,在創建新的程序包時,由於 CentOS 和 Tomcat 沒有變化,所以本次鏡像創建只會為“上傳程序包”這一步驟創建新層。
這也就為什麽第二次執行 Dockerfile 會比之前的執行快很多的原因。所以,我們就需要盡量把引起鏡像層變化的步驟放到 Dockerfile 的後半部分。
容器創建
如果把鏡像類比成 Java 的類,容器就是基於類(鏡像)創建出來的對象。鏡像創建好了,一條命令就可以基於鏡像創建多個容器實例:
docker run -d --name=helloworld helloworld:2.0
容器編排
微服務架構下,一個系統有 N 多的組件,用戶中心、配置中心、註冊中心、業務組件、數據庫、緩存服務等等等。我們如果希望一鍵創建和啟動這些組件,就需要容器編排工具,例如 Google 的 Kubernetes,Docker 原生的 Docker Compose。兩者相比,Kubernetes 提供了更多的高級功能,而 Docker Compose 相對來說更易於使用。
下面以代碼示例來使用 Docker Compose 創建 Spring Cloud Eureka 和 MySQL。後面的 Demo 中還會再次講到。
集群管理
多數情況下,出於性能和高可用的考慮,都需要將系統部署於多臺主機上(多個 Docker),那麽手工地選擇主機並在主機上一個個地創建容器是不現實的。
Kubernetes 和 Docker Swarm 都很好地解決了這個問題。Kubernetes 的功能更完善,資源調度、服務發現、運行監控、擴容縮容、負載均衡、灰度升級、失敗冗余、容災恢復、DevOps 等樣樣精通,可實現大規模、分布式、高可用的 Docker 集群。而 Swarm 的優勢則是 Docker 原廠打造,並且暴露的 API 與 Docker API 一致,使用起來更簡便、可控。
文件存儲
要將應用或數據庫放在容器裏運行,並且有效利用容器的快速創建、快速銷毀、快速擴展運行實例的優勢,一個前提條件就是要保證容器的 無狀態性,也就意味著要將應用程序或數據庫產生的數據文件放到容器外面。Docker 提供了四種方法持久化應用數據。
- 掛載宿主機文件
在創建容器的時候,可以添加“-v” 參數來添加掛載目錄。譬如,運行 MySQL 鏡像時,我們需要將 MySQL 中的數據目錄掛在到物理磁盤上,執行命令
docker run --name=mysql -d -p3306:3306 -v /data/mysql/data:/var/lib/mysql mysql:5.6
此命令將容器中 MySQL 數據目錄“/var/lib/mysql”掛在到宿主機“/data/mysql/data”目錄上。
註意:使用“-v”參數一定要牢記,宿主機掛載目錄中的文件會覆蓋容器中同名文件。如果不需要分布式存儲,建議使用這種簡單方式實現數據持久化。
- 添加數據卷
這種數據持久化方式也是通過“-v”參數實現數據掛在到物理機器上。與第一種唯一不同的是,不需要指定宿主機掛載的目錄。
譬如,運行 MySQL 鏡像,執行命令
docker run --name=mysql -d -p3306:3306 -v /var/lib/data mysql:5.6
無需指定宿主機目錄。我們可以使用“docker inspect ContainerName" 查看掛載目錄在物理機器的實際位置。
註意:卸載容器後,數據卷不會自動刪除。這種數據持久化方式,一般不太建議使用。
- 使用數據卷容器
如果多個容器之間需要共享數據,譬如,容器 A 需要使用容器 B 的數據。docker 還提供了容器數據卷方式。首先,我們需要創建數據卷容器 B:
docker run --name=containerB -v /dbstore training/postgres
然後,在創建容器 A 時,使用 --volumes-from 參數掛載數據卷容器 B:
docker run --name=containerA --volumes-from containerB training/postgres
這樣,在容器 A 中就可以訪問 /dbstore 數據目錄了。這種數據持久化方式,我們一般也很少使用。
- 第三方存儲插件
Docker 支持第三方存儲方式以插件形式接入 Docker。采用這個方式,存儲不依賴宿主機。Docker 支持非常多的存儲插件,譬如:Contiv、Convoy、Netshare、Azure 等等。
- Contiv:目前支持分布式存儲 Ceph 與 NFS 文件系統。
- Convoy:支持 devicemapper、nfs 文件系統。
- Netshare:支持 NFS 3/4,AWS EFS 和 CIFS 文件系統。
- Azure File Storae plugin:支持微軟 Azure 文件系統。
微服務應用中,應用之間需要跨多主機共享文件,建議使用第三方存儲插件方式擴展存儲。
簡而言之,最簡單的方法就是將本地目錄掛載到容器中,並將本地目錄映射到文件存儲服務器,從而實現多容器實例共享相同的文件數據。
容器網絡
容器網絡可以算是 Docker 中最復雜的部分了,Docker 內置了 none、bridge、host 三種方式的網絡驅動。
- none:創建容器時,如果指定網絡為 none,那麽此容器內將沒有網絡。我們使用第三方網絡插件時,創建容器時選擇為 none 類型網絡驅動。
- bridge:創建容器時,如果不指定網絡驅動,Docker 默認采用此方式。
- host:如果容器使用 host 類型網絡,容器與宿主機處在同一網絡空間(Network Namespace)中,這種方式,數據包無需轉換,其網絡性能基本等同於物理機網絡的性能。
上面介紹的幾種網絡,只是針對單主機而言。下面介紹兩種 跨多主機通訊 的 SDN 網絡。
- Calico
Calico 是純三層實現的網絡通訊技術。主要利用 Linux 路由表和 iptables 實現網絡轉發與隔離。Calico 相比於其他網絡技術,其性能更接近物理網絡。但是,Calico 對物理網絡是有入侵的。
要實現三層網絡,首要實現路由發現。Calico 通過 BGP 協議實現路由發現。但是,不是所有的路由設備都支持 BGP 協議。國內,阿裏雲 ECS 多實例之間也不支持 BGP 路由發現。在雲服務環境中,如果不支持 BGP 協議,Calico 提供了隧道模式,但是,其性能遠不如前者。
- Overlay
Overlay 容器間跨主機通訊原理:Overlay 是將容器發送數據包封裝到三層網絡中,通過 UDP 發送到其他宿主上(譬如,M),宿主機 M 收到此數據包後交由 VTMP 拆包並轉發到對應的容器中。因為需要做裝包與拆包,所以其性能不如 Calico 網絡。
Overlay 網絡下的容器與外部網絡通訊,還是走橋接網絡,但是這個網絡不是 docker0,而是 docker_gwbridge。相比 Calico 網絡,Overlay 性能比較差。
但是,由於 docker1.9 版本已經將 Overlay 網絡做了跨主機通訊網絡,在後續升級、維護方面比較具有優勢(強大與火熱的 Docker 社區支持)。
在微服務實施中,如果容器間不需要跨主機通訊的,建議使用 bridge 與 host 網絡。如果需要跨主機通訊的,網絡性能要求比較高,建議使用 Calico 網絡;網絡性能無特殊要求,建議使用 Overlay 網絡。
容器監控
為容器分配了 CPU、內存等計算資源後,自然需要時刻監控容器的資源使用情況,這當然也可以借助開源工具來完成,例如 Google 的 Cadvisor 和 Prometheus。
我們沒有對 Prometheus 做過研究,有興趣的朋友可以去試試看。對於 Cadvisor,雖然它提供了圖形化界面來查看資源使用情況,但沒有提供告警功能。
所以,我們使用 Cadvisor + InfluxDB + Grafana 來查看容器使用情況,並將 InfluxDB 數據源接入到自研的監控平臺中,實現告警功能。
容器日誌
Docker 容器日誌來源於容器的標準輸出流(stdout)與標準錯誤流(stderr)。在微服務開發中,使用 log4j、slf4j、logback 等日誌組件時,默認都會將日誌輸出到文件和標準輸出流中。
通過 Docker 容器方式部署後,對於重要的日誌文件,在創建容器時,通過“-v” 參數將日誌文件掛在到宿主機磁盤上永久保存。如果將日誌輸入到標準輸出流,我們可以通過“docker logs”命令查看日誌。
註意:如果通過 docker logs 查看的日誌量很大時(超過 1GB),將會使 Docker 出現假死現象。所以,在使用 docker log 命令時,我們建議加上 --tail 參數,指定查看最後多少行日誌信息。
過多的日誌將會占用大量的磁盤空間,在創建容器時,可以通過參數“--log-driver”與“--log-opt”指定容器產生的日誌文件數已經文件大小。譬如
docker run --name=mysql -d ---log-drive=json-file --log-opt=max-size=100m;max-file=1 mysql:5.6
至此,我們簡要介紹了使用 Docker 支撐微服務架構開發需要用到的功能和註意的事項。下面,我們就用一個 Demo 來完成一個實例的演練。
快速運行一個微服務架構 Demo
在開始之前,可以通過地址 http://msa.qingliuyun.com/ 來查看 Demo 的效果。
請求處理流程:
1 . 內部服務 A 和 B 啟動後,將自身服務地址註冊到註冊中心(Eureka)。
2 . API 網關從註冊中心拉取服務 A 和 B 的服務地址。
3 . 客戶端(Client)調用 API 網關(API Gateway)。
4 . API 網關將調用請求轉發給內部服務 A(Service A)。
5 . 內部服務 A 接受到請求後,將請求轉發給服務 B(Service B)處理。
6 . 內部服務 B 返回處理結果。內部服務 B 將請求結果返回給客戶端。
考慮到本篇文章的篇幅問題,我們將 Demo 源代碼上傳到了
https://github.com/qingliuyun/msa-demo
有興趣的同學可以去看看,非常簡單。
同時,也準備了一篇文檔來介紹 Demo 的兩種安裝方法:
- 基於 Docker + Docker Comopose;
- 基於青柳雲微服務平臺;
鏈接地址:
http://docs.qingliuyun.com/pro/images/quick_start/kuai-su-da-jian-wei-fu-wu-jia-gou-zu-jian.html
文檔中包含了安裝過程中需要的數據庫初始化腳本和 Docker Compose 腳本文件。
從 0 開始的微服務架構:(五)代碼給你,看如何用Docker支撐微服務