當前位置:首頁 » 操作系統 » docker源碼分析

docker源碼分析

發布時間: 2023-05-10 07:30:21

A. docker 源碼分析 怎麼用

本文根據docker官方給出的docker代碼編譯環境搭建指團歲南做更深入的分手或謹析。官方給出的指導比較簡單,但是由於國內的網路問題經常會編譯失敗,了解了編譯步驟後,也可以結合自身遇到的網路問題進行畢基「規避」。

B. 《Docker源碼分析》epub下載在線閱讀全文,求百度網盤雲資源

《Docker源碼分析》(孫宏亮)電子書網盤下載免費在線閱讀

鏈接:

提取碼:UMRF

書名:Docker源碼分析

豆瓣評分:6.4

作者:孫宏亮

出版社:機械工業出版社

出版年:2015-8-1

頁數:264

內容簡介

本書是一本引導讀者深入了解Docker實現原理的技術普及讀物,主要目標是通過對Docker架構和源代碼的詳細講解和解剖,幫助讀者對Docker的底層實現有一個全面的理解。

作者通過大量的流程圖和代碼片段對Docker的架構、Docker的重要模塊,特別是對Swarm、Machine和Compose這三個模塊進行了詳細介紹和深度剖析,無論是Docker的使用者還是開發者,通過閱讀此書都可以對Docker有更深刻的理解,能夠更好的使用或者開發Docker。

作者簡介

孫宏亮

碩士,浙江大學畢業,現為DaoCloud軟體工程師,主要負責企業級容器雲平台的研發工作。數年來一直從事雲計算、PaaS領域的研究與實踐,是國內較早一批接觸Docker的先行者,同時也是Docker技術的推廣者。

C. 如何系統地學習Docker

1Docker 技術可謂是近年最火熱的技術之一,鋪天蓋地的技術論壇和各種講座,大家都在分享關於如何容器化及如何使用Docker優化自己運維和開發流程的經驗。隨著Docker技術的逐漸普及,使用Docker已經不再是一個難題。現在更加重要的是生產環境容器化的最佳實踐,段姿另外就是容器的編排框架之爭。但是,對於技術人員來說,除去Docker 外表的繁華外,什麼是容器,容器到底是怎麼創建的,容器底層的技術探秘也是非常重要的。2014年開始接觸 Docker的時候,經歷了從最初的新奇—感嘆竟然還有Docker 這樣的好工具,到逐漸熟悉Docker的各種功能,嘗試在生產環境中使用Docker技術的過程。但是,每每被人問到:「Docker技術到底是怎麼實現的呢」我只能粗粗淺淺地說:「Docker是使用linux Kernel的Namespace 和 Cgroups實現的一種容器技術。」那麼,什麼是Namespace,什麼是Cgroups,Docker是怎麼使用它們的,容器到底是怎麼一步步被創建出來的.問到這些,我就會支支吾吾地不知所以。螞橡由此可見,了解容器技術的底層技術,然後明白它們是如何工作的,尤為重要,這些才是整個容器技術的基石,掌握了這些基石才能更加容易地向上攀登。

2從docker的用途上來考慮,看docker能否解決你工作中遇到的問題。例如在實際開發過程中,經常遇到的持續集成問題,軟體開發,測試,部署,如何做成自動化的。配合github Jenkins和docker實現自動化部署,系統持續集成。docker最重要的是編排,如何合理有效穩定的管理各個容器,調度容器。可以看看k8s,自己動手搭建握物絕跑跑看。docker本身並沒有引入新的技術,都是在linux原有的基礎上做的融合。可以關注docker源碼分析。UCloud也支持Docker,推出了UDocker產品。UCloud - 專業雲計算服務商。

D. 如何學習Docker

如何學習Docker
對於在校學生而言,應該如何去學習docker?畢竟學校沒有具體的應用需求作為引導,所以應該如何去研究Docker?還有,Docker的源代碼有沒有必要去研究?

首先我說明下,我是一位在浙江大學VLIS實驗室雲計算項目組的學生,使用過Docker,研究過Docker及其源碼,也定製過Docker。

對於學生如何學習Docker,我認為首先要看一下學生個人的知識背景、能利用的資源資源、以及個人興趣和發展方向。

1.學習Docker,如果沒有雲計算的基本知識,以及內核的基本知識,那麼學習並理解起來會稍吃力。作為容器,Docker容器的優勢在哪,不足在哪,最好了解容器的實現是怎樣的(簡單了解);擁有鏡像管理,Docker又該如何體現軟體開發,集成,部署,發布,再迭代的軟體生命周期管理優勢。以上兩點我認為最為關鍵,有這兩方面的認識勢必會對之後的工作幫助巨大。

2.關於學習資源,起碼的硬體設施總是要有的。Docker及其生態的發展很快,不使用純理論肯定收效甚微。另外,資源還包括Docker官方,各大電子媒體平台,技術論壇,開源社區等,往往大拿的觀點能點破自己的困惑,或者讓自己知道哪方面的認識還很欠缺,以及讓自己少走很多的彎路。

3.個人興趣的話,歸結為強扭的瓜不甜。起碼應該認同Docker的設計價值,以及Docker的未來潛力,當然有依據的批判Docker並帶動大家的思考,也是深切關注的表現。

4.個人發展方向,我認為如果需要把Docker當作軟體生命周期管理工具的話,那用好Docker最為重要,API及命令的理解與使用是必需的。如果專注系統設計方面,那麼除Docker以上的知識與經驗之外,若有Docker源碼的學習與理解,那麼這些肯定會讓你的Docker水平提高一個層次。
2014-11-21 8 0

xds2000
學習Docker,最大的好處是跟進新技術發展方向。我覺得在校生應該沒有多少硬性需求在Docker的研究上,這也是為什麼學校沒做具體應用要求的原因。最實際的做法是看一些Docker使用案例,自己實踐出一些經驗應該會再以後的社會實踐中起到作用。

研究docker的源代碼,應該到你下定決心從事雲計算方面的事業或者研究,那麼你就需要以研究者的身份去做仔細的源碼分析的工作。
2014-11-21 3 0

劉勃GTDer

我作為參加工作的過來人來說,我認為只有你真正參加工作後,在工作中學習跟有意義,畢竟Docker知識雲計算其中的一個軟體平台而已,說不來等你畢業了,新的技術出現Docker不一定是唯一選擇。
作為學生了解新技術確實無可厚非,一定要能把理論轉化為生產力才是正道。
2014-12-15 3 0

9lives - 愛生活,愛雲計算。

學習任何一個開源新技術,首先問自己幾個問題:
1. 為什要學習它?
2. 學習它需要了解哪些相關知識點?
3. 如何快速學習?
4. 該技術的使用場景是什麼?

拿我個人的學習經驗來舉例(本人之前比較了解OpenStack)

為什要學習docker?
回答:
docker是輕量級虛擬化技術,docker使linux容器技術的應用更加簡單和標准化
docker的速度很快,容器啟動時毫秒級的
docker將開發和運維職責分清
docker解決了依賴地獄問題
docker支持幾乎所有操作系統
docker有著飛速發展的生態圈
很多IT巨頭逐漸加入和支持
學習它需要了解哪些相關知識點?
回答:
雲計算概念相關(restapi, 微服務,OpenStack)
Linux 系統管理(軟體包管理,用戶管理,進程管理等)
Linux 內核相關(Cgroup, namespace 等)
Linux 文件系統和存儲相關(AUFS,BRFS,devicemapper 等)
Linux 網路(網橋,veth,iptables等)
Linux安全相關(Appmor,Selinux 等)
Linux進程管理(Supervisord,Systemd etc)
Linux容器技術(LXC等)
開發語言(Python, GO,Shell 等)

3.如何快速學習?
回答:個人體會最好有一個實際的需求或項目來邊實踐邊學習,入門可以參考(第一本docker書)寫的不錯,非常適合入門。除此之外,閱讀牛人的blog比如官方blog http://blog.docker.com/
最後,參與社區互動也是很好的學習方式。

該技術的使用場景是什麼? 回答:docker非常適用於dev/test CI/CD 場景,用完就扔。還有就是PasS了。

歡迎大家討論。
2015-05-21 3 0

西弗爾 - 要麼牛逼,要麼滾蛋

你好!我也是在校的學生,也在自己學習docker,多多交流啊!
2015-05-23 1 1

田浩浩 - wizmacau developer
https:// github.com /llitfkitfk/docker-tutorial-cn/
BTW: 熟讀docker文檔
2014-11-21 0 0

tuxknight
樓上各位說的都很好,我再補充一點:
找份相關的實習工作
2015-07-22 0 0

lancer
工作和研究是兩個方向我個人認為,工作需要通過你的實際效能為企業帶來經濟效益,而研究的話可以專注某個點。但是研究離不開工作,因為工作可以讓你更好的理會技術帶來的價值,以及如何提供更好的服務,用戶使用場景需要那些技術的突破。有了這些認識,然後更加專注的研究某個技術點,這樣或許可以說技術和商業是分不開的。
2015-08-25 0 0

綠劍色影
docker現在十分火熱,值得學習一下。

E. libcontainer位於哪個目錄

libcontainer 是Docker中用於容器管理的包,它基於Go語言實現,通過管理namespaces、cgroups、capabilities以及文件系統來進行容器控制。你可以使用libcontainer創建容器,並對容器進行生命周期管理。

容器是一個可管理的執行環境,與主機系統共享內核,可與系統中的其他容器進行隔離。

在2013年Docker剛發布的時候,它是一款基於LXC的開源容器管理引擎。把LXC復雜的容器創建與使用方式簡化為Docker自己的一套命令體系。隨著Docker的不斷發展,它開始有了更為遠大的目標,那就是反向定義容器的實現標准,將底層實現都抽象化到libcontainer的介面。這就意味著,底層容器的實現方式變成了一種可變的方案,無論是使用namespace、cgroups技術抑或是使用systemd等其他方案,只要實現了libcontainer定義的一組介面,Docker都可以運行。這也為Docker實現全面的跨平台帶來了可能。

1.libcontainer 特性
目前版本的libcontainer,功能實現上涵蓋了包括namespaces使用、cgroups管理、Rootfs的配置啟動、默認的Linux capability許可權集、以及進程運行的環境變數配置。內核版本最低要求為2.6,最好是3.8,這與內核對namespace的支持有關。 目前除user namespace不完全支持以外,其他五個namespace都是默認開啟的,通過clone系統調用進行創建。

1.1 建立文件系統
文件系統方面,容器運行需要rootfs。所有容器中要執行的指令,都需要包含在rootfs(在Docker中指令包含在其上疊加的鏡像層也可以執行)所有掛載在容器銷毀時都會被卸載,因為mount namespace會在容器銷毀時一同消失。為了容器可以正常執行命令,以下文件系統必須在容器運行時掛載到rootfs中。

當容器沖仿的文件系統剛掛載完畢時,/dev文件系統會被一系列設備節點所填充,所以rootfs不應該管理/dev文件系統下的設備節點,libcontainer會負責處理並正確啟動這些設備。設備及其許可權模式如下。

容器支持偽終端TTY,當用戶使用時,就會建立/dev/console設備。其他終端支持設備,如/dev/ptmx則是宿主機的/dev/ptmx 鏈接。容器中指向宿主機 /dev/null的IO也會被重定向到容器內的 /dev/null設備。當/proc掛載完成後,/dev/中與IO相關的鏈接也會建立,如下表。

pivot_root 則用於改變進程的根目錄,這樣可以有效的將進程式控制制在我們建立的rootfs中。如果rootfs是基於ramfs的(不支持pivot_root),那麼會在mount時使用MS_MOVE標志位加上chroot來頂替。 當文件系統創建完畢後,umask許可權被重新設置回0022。

1.2 資源管理
在《Docker背後的內核知識:cgroups資源隔離》一文中已經提到,Docker使用cgroups進行資源管理與限制,包括設備、內存、CPU、輸入輸出等。 目前除網路外所有內核支持的子系統都被加入到libcontainer的管理中,所以libcontainer使用cgroups原生支持的統計信息作為資源管理的監控展示。 容器中運行的第一個進程init,必須在初始化開始前放置到指定的cgroup目錄中,這樣就能防止初始型判旦化完成後運行的其他用戶指令逃逸出cgroups的控制。父子進程的同步則通過管道來完成,在隨後的運行時初始化中會進行展開描述。

1.3 可配置的容器安全
容器安全一直是被廣泛探討的話題,使用namespace對進程進行隔離是容器安全的基礎,遺憾的是,usernamespace由於設計上的復雜性,還沒有被libcontainer完全支持。 libcontainer目前可通過配置capabilities、SELinux、apparmor 以及seccomp進行一定的安全防範,目前除seccomp以外都有一份默認的配置項提供給用戶作為參考。 在本系列的後續文章中,我們將對容器安全進行更深入的探討,敬請期待。

1.4 運行時與初始化進程
在容器創建過程中,父進程需要與容器的init進程進行同步通信,通信的方式則通過卜擾向容器中傳入管道來實現。當init啟動時,他會等待管道內傳入EOF信息,這就給父進程完成初始化,建立uid/gid映射,並把新進程放進新建的cgroup一定的時間。 在libcontainer中運行的應用(進程),應該是事先靜態編譯完成的。libcontainer在容器中並不提供任何類似Unix init這樣的守護進程,用戶提供的參數也是通過exec系統調用提供給用戶進程。通常情況下容器中也沒有長進程存在。 如果容器打開了偽終端,就會通過p2把console作為容器的輸入輸出(STDIN, STDOUT, STDERR)對象。 除此之外,以下4個文件也會在容器運行時自動生成。 * /etc/hosts * /etc/resolv.conf * /etc/hostname * /etc/localtime

1.5 在運行著的容器中執行新進程
用戶也可以在運行著的容器中執行一條新的指令,就是我們熟悉的docker exec功能。同樣,執行指令的二進制文件需要包含在容器的rootfs之內。 通過這種方式運行起來的進程會隨容器的狀態變化,如容器被暫停,進程也隨之暫停,恢復也隨之恢復。當容器進程不存在時,進程就會被銷毀,重啟也不會恢復。

1.6 容器熱遷移(Checkpoint & Restore)
目前libcontainer已經集成了CRIU作為容器檢查點保存與恢復(通常也稱為熱遷移)的解決方案,應該在不久之後就會被Docker使用。也就是說,通過libcontainer你已經可以把一個正在運行的進程狀態保存到磁碟上,然後在本地或其他機器中重新恢復當前的運行狀態。這個功能主要帶來如下幾個好處。

伺服器需要維護(如系統升級、重啟等)時,通過熱遷移技術把容器轉移到別的伺服器繼續運行,應用服務信息不會丟失。
對於初始化時間極長的應用程序來說,容器熱遷移可以加快啟動時間,當應用啟動完成後就保存它的檢查點狀態,下次要重啟時直接通過檢查點啟動即可。
在高性能計算的場景中,容器熱遷移可以保證運行了許多天的計算結果不會丟失,只要周期性的進行檢查點快照保存就可以了。
要使用這個功能,需要保證機器上已經安裝了1.5.2或更高版本的criu工具。不同Linux發行版都有criu的安裝包,你也可以在CRIU官網上找到從源碼安裝的方法。我們將會在nsinit的使用中介紹容器熱遷移的使用方法。 CRIU(Checkpoint/Restore In Userspace)由OpenVZ項目於2005年發起,因為其涉及的內核系統繁多、代碼多達數萬行,其復雜性與向後兼容性都阻礙著它進入內核主線,幾經周折之後決定在用戶空間實現,並在2012年被Linus加並入內核主線,其後得以快速發展。 你可以在CRIU官網查看其原理,簡單描述起來可以分為兩部分,一是檢查點的保存,其中分為3步。

收集進程與其子進程構成的樹,並凍結所有進程。
收集任務(包括進程和線程)使用的所有資源,並保存。
清理我們收集資源的相關寄生代碼,並與進程分離。
第二部分自然是恢復,分為4步。

讀取快照文件並解析出共享的資源,對多個進程共享的資源優先恢復,其他資源則隨後需要時恢復。
使用fork恢復整個進程樹,注意此時並不恢復線程,在第4步恢復。
恢復所有基礎任務(包括進程和線程)資源,除了內存映射、計時器、證書和線程。這一步主要打開文件、准備namespace、創建socket連接等。
恢復進程運行的上下文環境,恢復剩下的其他資源,繼續運行進程。
至此,libcontainer的基本特性已經預覽完畢,下面我們將從使用開始,一步步深入libcontainer的原理。

2. nsinit與libcontainer的使用
俗話說,了解一個工具最好的入門方式就是去使用它,nsinit就是一個為了方便不通過Docker就可以直接使用libcontainer而開發的命令行工具。它可以用於啟動一個容器或者在已有的容器中執行命令。使用nsinit需要有 rootfs 以及相應的配置文件。

2.1 nsinit的構建
使用nsinit需要rootfs,最簡單最常用的是使用Docker busybox,相關配置文件則可以參考sample_configs目錄,主要配置的參數及其作用將在配置參數一節中介紹。拷貝一份命名為container.json文件到你rootfs所在目錄中,這份文件就包含了你對容器做的特定配置,包括運行環境、網路以及不同的許可權。這份配置對容器中的所有進程都會產生效果。 具體的構建步驟在官方的README文檔中已經給出,在此為了節省篇幅不再贅述。 最終編譯完成後生成nsinit二進制文件,將這個指令加入到系統的環境變數,在busybox目錄下執行如下命令,即可使用,需要root許可權。 nsinit exec --tty --config container.json /bin/bash 執行完成後會生成一個以容器ID命名的文件夾,上述命令沒有指定容器ID,默認名為」nsinit」,在「nsinit」文件夾下會生成一個state.json文件,表示容器的狀態,其中的內容與配置參數中的內容類似,展示容器的狀態。

2.2 nsinit的使用
目前nsinit定義了9個指令,使用nsinit -h就可以看到,對於每個單獨的指令使用--help就能獲得更詳細的使用參數,如nsinit config --help。 nsinit這個命令行工具是通過cli.go實現的,cli.go封裝了命令行工具需要做的一些細節,包括參數解析、命令執行函數構建等等,這就使得nsinit本身的代碼非常簡潔明了。具體的命令功能如下。

config:使用內置的默認參數加上執行命令時用戶添加的部分參數,生成一份容器可用的標准配置文件。
exec:啟動容器並執行命令。除了一些共有的參數外,還有如下一些獨有的參數。
--tty,-t:為容器分配一個終端顯示輸出內容。
--config:使用配置文件,後跟文件路徑。
--id:指定容器ID,默認為nsinit。
--user,-u:指定用戶,默認為「root」.
--cwd:指定當前工作目錄。
--env:為進程設置環境變數。
init:這是一個內置的參數,用戶並不能直接使用。這個命令是在容器內部執行,為容器進行namespace初始化,並在完成初始化後執行用戶指令。所以在代碼中,運行nsinit exec後,傳入到容器中運行的實際上是nsinit init,把用戶指令作為配置項傳入。
oom:展示容器的內存超限通知。
pause/unpause:暫停/恢復容器中的進程。
stats:顯示容器中的統計信息,主要包括cgroup和網路。
state:展示容器狀態,就是讀取state.json文件。
checkpoint:保存容器的檢查點快照並結束容器進程。需要填--image-path參數,後面是檢查點保存的快照文件路徑。完整的命令示例如下。 nsinit checkpoint --image-path=/tmp/criu

restore:從容器檢查點快照恢復容器進程的運行。參數同上。

總結起來,nsinit與Docker execdriver進行的工作基本相同,所以在Docker的源碼中並不會涉及到nsinit包的調用,但是nsinit為libcontainer自身的調試和使用帶來了極大的便利。

3. 配置參數解析
no_pivot_root :這個參數表示用rootfs作為文件系統掛載點,不單獨設置pivot_root。
parent_death_signal: 這個參數表示當容器父進程銷毀時發送給容器進程的信號。
pivot_dir:在容器root目錄中指定一個目錄作為容器文件系統掛載點目錄。
rootfs:容器根目錄位置。
readonlyfs:設定容器根目錄為只讀。
mounts:設定額外的掛載,填充的信息包括原路徑,容器內目的路徑,文件系統類型,掛載標識位,掛載的數據大小和許可權,最後設定共享掛載還是非共享掛載(獨立於mount_label的設定起作用)。
devices:設定在容器啟動時要創建的設備,填充的信息包括設備類型、容器內設備路徑、設備塊號(major,minor)、cgroup文件許可權、用戶編號、用戶組編號。
mount_label:設定共享掛載還是非共享掛載。
hostname:設定主機名。
namespaces:設定要加入的namespace,每個不同種類的namespace都可以指定,默認與父進程在同一個namespace中。
capabilities:設定在容器內的進程擁有的capabilities許可權,所有沒加入此配置項的capabilities會被移除,即容器內進程失去該許可權。
networks:初始化容器的網路配置,包括類型(loopback、veth)、名稱、網橋、物理地址、IPV4地址及網關、IPV6地址及網關、Mtu大小、傳輸緩沖長度txqueuelen、Hairpin Mode設置以及宿主機設備名稱。
routes:配置路由表。
cgroups:配置cgroups資源限制參數,使用的參數不多,主要包括允許的設備列表、內存、交換區用量、CPU用量、塊設備訪問優先順序、應用啟停等。
apparmor_profile:配置用於SELinux的apparmor文件。
process_label:同樣用於selinux的配置。
rlimits:最大文件打開數量,默認與父進程相同。
additional_groups:設定gid,添加同一用戶下的其他組。
uid_mappings:用於User namespace的uid映射。
gid_mappings:用戶User namespace的gid映射。
readonly_paths:在容器內設定只讀部分的文件路徑。
MaskPaths:配置不使用的設備,通過綁定/dev/null進行路徑掩蓋。
4. libcontainer實現原理
在Docker中,對容器管理的模塊為execdriver,目前Docker支持的容器管理方式有兩種,一種就是最初支持的LXC方式,另一種稱為native,即使用libcontainer進行容器管理。在孫宏亮的《Docker源碼分析系列》中,Docker Deamon啟動過程中就會對execdriver進行初始化,會根據驅動的名稱選擇使用的容器管理方式。 雖然在execdriver中只有LXC和native兩種選擇,但是native(即libcontainer)通過介面的方式定義了一系列容器管理的操作,包括處理容器的創建(Factory)、容器生命周期管理(Container)、進程生命周期管理(Process)等一系列介面,相信如果Docker的熱潮一直像如今這般洶涌,那麼不久的將來,Docker必將實現其全平台通用的宏偉藍圖。本節也將從libcontainer的這些抽象對象開始講解,與你一同解開Docker容器管理之謎。在介紹抽象對象的具體實現過程中會與Docker execdriver聯系起來,讓你充分了解整個過程。

4.1 Factory 對象
Factory對象為容器創建和初始化工作提供了一組抽象介面,目前已經具體實現的是Linux系統上的Factory對象。Factory抽象對象包含如下四個方法,我們將主要描述這四個方法的工作過程,涉及到具體實現方法則以LinuxFactory為例進行講解。

Create():通過一個id和一份配置參數創建容器,返回一個運行的進程。容器的id由字母、數字和下劃線構成,長度范圍為1~1024。容器ID為每個容器獨有,不能沖突。創建的最終返回一個Container類,包含這個id、狀態目錄(在root目錄下創建的以id命名的文件夾,存state.json容器狀態文件)、容器配置參數、初始化路徑和參數,以及管理cgroup的方式(包含直接通過文件操作管理和systemd管理兩個選擇,默認選cgroup文件系統管理)。
Load():當創建的id已經存在時,即已經Create過,存在id文件目錄,就會從id目錄下直接讀取state.json來載入容器。其中的參數在配置參數部分有詳細解釋。
Type():返回容器管理的類型,目前可能返回的有libcontainer和lxc,為未來支持更多容器介面做准備。
StartInitialization():容器內初始化函數。
這部分代碼是在容器內部執行的,當容器創建時,如果New不加任何參數,默認在容器進程中運行的第一條命令就是nsinit init。在execdriver的初始化中,會向reexec注冊初始化器,命名為native,然後在創建libcontainer以後把native作為執行參數傳遞到容器中執行,這個初始化器創建的libcontainer就是沒有參數的。
傳入的參數是一個管道文件描述符,為了保證在初始化過程中,父子進程間狀態同步和配置信息傳遞而建立。
不管是純粹新建的容器還是已經創建的容器執行新的命令,都是從這個入口做初始化。
第一步,通過管道獲取配置信息。
第二步,從配置信息中獲取環境變數並設置為容器內環境變數。
若是已經存在的容器執行新命令,則只需要配置cgroup、namespace的Capabilities以及AppArmor等信息,最後執行命令。
若是純粹新建的容器,則還需要初始化網路、路由、namespace、主機名、配置只讀路徑等等,最後執行命令。
至此,容器就已經創建和初始化完畢了。

4.2 Container 對象
Container對象主要包含了容器配置、控制、狀態顯示等功能,是對不同平台容器功能的抽象。目前已經具體實現的是Linux平台下的Container對象。每一個Container進程內部都是線程安全的。因為Container有可能被外部的進程銷毀,所以每個方法都會對容器是否存在進行檢測。

ID():顯示Container的ID,在Factor對象中已經說過,ID很重要,具有唯一性。
Status():返回容器內進程是運行狀態還是停止狀態。通過執行「SIG=0」的KILL命令對進程是否存在進行檢測。
State():返回容器的狀態,包括容器ID、配置信息、初始進程ID、進程啟動時間、cgroup文件路徑、namespace路徑。通過調用Status()判斷進程是否存在。
Config():返回容器的配置信息,可在「配置參數解析」部分查看有哪些方面的配置信息。
Processes():返回cgroup文件cgroup.procs中的值,在Docker背後的內核知識:cgroups資源限制部分的講解中我們已經提過,cgroup.procs文件會羅列所有在該cgroup中的線程組ID(即若有線程創建了子線程,則子線程的PID不包含在內)。由於容器不斷在運行,所以返回的結果並不能保證完全存活,除非容器處於「PAUSED」狀態。
Stats():返回容器的統計信息,包括容器的cgroups中的統計以及網卡設備的統計信息。Cgroups中主要統計了cpu、memory和blkio這三個子系統的統計內容,具體了解可以通過閱讀「cgroups資源限制」部分對於這三個子系統統計內容的介紹來了解。網卡設備的統計則通過讀取系統中,網路網卡文件的統計信息文件/sys/class/net/<EthInterface>/statistics來實現。
Set():設置容器cgroup各子系統的文件路徑。因為cgroups的配置是進程運行時也會生效的,所以我們可以通過這個方法在容器運行時改變cgroups文件從而改變資源分配。
Start():構建ParentProcess對象,用於處理啟動容器進程的所有初始化工作,並作為父進程與新創建的子進程(容器)進行初始化通信。傳入的Process對象可以幫助我們追蹤進程的生命周期,Process對象將在後文詳細介紹。
啟動的過程首先會調用Status()方法的具體實現得知進程是否存活。
創建一個管道(詳見Docker初始化通信——管道)為後期父子進程通信做准備。
配置子進程cmd命令模板,配置參數的值就是從factory.Create()傳入進來的,包括命令執行的工作目錄、命令參數、輸入輸出、根目錄、子進程管道以及KILL信號的值。
根據容器進程是否存在確定是在已有容器中執行命令還是創建新的容器執行命令。若存在,則把配置的命令構建成一個exec.Cmd對象、cgroup路徑、父子進程管道及配置保留到ParentProcess對象中;若不存在,則創建容器進程及相應namespace,目前對user namespace有了一定的支持,若配置時加入user namespace,會針對配置項進行映射,默認映射到宿主機的root用戶,最後同樣構建出相應的配置內容保留到ParentProcess對象中。通過在cmd.Env寫入環境變數_libcontainer_INITTYPE來告訴容器進程採用的哪種方式啟動。
執行ParentProcess中構建的exec.Cmd內容,即執行ParentProcess.start(),具體的執行過程在Process部分介紹。
最後如果是新建的容器進程,還會執行狀態更新函數,把state.json的內容刷新。
Destroy():首先使用cgroup的freezer子系統暫停所有運行的進程,然後給所有進程發送SIGKIL信號(如果沒有使用pid namespace就不對進程處理)。最後把cgroup及其子系統卸載,刪除cgroup文件夾。
Pause():使用cgroup的freezer子系統暫停所有運行的進程。
Resume():使用cgroup的freezer子系統恢復所有運行的進程。
NotifyOOM():為容器內存使用超界提供只讀的通道,通過向cgroup.event_control寫入eventfd(用作線程間通信的消息隊列)和cgroup.oom_control(用於決定內存使用超限後的處理方式)來實現。
Checkpoint():保存容器進程檢查點快照,為容器熱遷移做准備。通過使用CRIU的SWRK模式來實現,這種模式是CRIU另外兩種模式CLI和RPC的結合體,允許用戶需要的時候像使用命令行工具一樣運行CRIU,並接受用戶遠程調用的請求,即傳入的熱遷移檢查點保存請求,傳入文件形式以Google的protobuf協議保存。
Restore():恢復檢查點快照並運行,完成容器熱遷移。同樣通過CRIU的SWRK模式實現,恢復的時候可以傳入配置文件設置恢復掛載點、網路等配置信息。
至此,Container對象中的所有函數及相關功能都已經介紹完畢,包含了容器生命周期的全部過程。

TIPs: Docker初始化通信——管道
libcontainer創建容器進程時需要做初始化工作,此時就涉及到使用了namespace隔離後的兩個進程間的通信。我們把負責創建容器的進程稱為父進程,容器進程稱為子進程。父進程clone出子進程以後,依舊是共享內存的。但是如何讓子進程知道內存中寫入了新數據依舊是一個問題,一般有四種方法。

發送信號通知(signal)
對內存輪詢訪問(poll memory)
sockets通信(sockets)
文件和文件描述符(files and file-descriptors)
對於Signal而言,本身包含的信息有限,需要額外記錄,namespace帶來的上下文變化使其不易理解,並不是最佳選擇。顯然通過輪詢內存的方式來溝通是一個非常低效的做法。另外,因為Docker會加入network namespace,實際上初始時網路棧也是完全隔離的,所以socket方式並不可行。 Docker最終選擇的方式就是打開的可讀可寫文件描述符——管道。 Linux中,通過pipe(int fd[2])系統調用就可以創建管道,參數是一個包含兩個整型的數組。調用完成後,在fd[1]端寫入的數據,就可以從fd[0]端讀取。

// 需要加入頭文件: #include // 全局變數: int fd[2]; // 在父進程中進行初始化: pipe(fd); // 關閉管道文件描述符 close(checkpoint[1]);

調用pipe函數後,創建的子進程會內嵌這個打開的文件描述符,對fd[1]寫入數據後可以在fd[0]端讀取。通過管道,父子進程之間就可以通信。通信完畢的奧秘就在於EOF信號的傳遞。大家都知道,當打開的文件描述符都關閉時,才能讀到EOF信號,所以libcontainer中父進程先關閉自己這一端的管道,然後等待子進程關閉另一端的管道文件描述符,傳來EOF表示子進程已經完成了初始化的過程。

4.3 Process 對象
Process 主要分為兩類,一類在源碼中就叫Process,用於容器內進程的配置和IO的管理;另一類在源碼中叫ParentProcess,負責處理容器啟動工作,與Container對象直接進行接觸,啟動完成後作為Process的一部分,執行等待、發信號、獲得pid等管理工作。 ParentProcess對象,主要包含以下六個函數,而根據」需要新建容器」和「在已經存在的容器中執行」的不同方式,具體的實現也有所不同。

已有容器中執行命令

pid(): 啟動容器進程後通過管道從容器進程中獲得,因為容器已經存在,與Docker Deamon在不同的pid namespace中,從進程所在的namespace獲得的進程號才有意義。
start(): 初始化容器中的執行進程。在已有容器中執行命令一般由docker exec調用,在execdriver包中,執行exec時會引入nsenter包,從而調用其中的C語言代碼,執行nsexec()函數,該函數會讀取配置文件,使用setns()加入到相應的namespace,然後通過clone()在該namespace中生成一個子進程,並把子進程通過管道傳遞出去,使用setns()以後並沒有進入pid namespace,所以還需要通過加上clone()系統調用。
開始執行進程,首先會運行C代碼,通過管道獲得進程pid,最後等待C代碼執行完畢。
通過獲得的pid把cmd中的Process替換成新生成的子進程。
把子進程加入cgroup中。
通過管道傳配置文件給子進程。
等待初始化完成或出錯返回,結束。
新建容器執行命令

pid():啟動容器進程後通過exec.Cmd自帶的pid()函數即可獲得。
start():初始化及執行容器命令。
開始運行進程。
把進程pid加入到cgroup中管理。
初始化容器網路。(本部分內容豐富,將從本系列的後續文章中深入講解)
通過管道發送配置文件給子進程。
等待初始化完成或出錯返回,結束。
實現方式類似的一些函數

**terminate() **:發送SIGKILL信號結束進程。
**startTime() **:獲取進程的啟動時間。
signal():發送信號給進程。
wait():等待程序執行結束,返回結束的程序狀態。
Process對象,主要描述了容器內進程的配置以及IO。包括參數Args,環境變數Env,用戶User(由於uid、gid映射),工作目錄Cwd,標准輸入輸出及錯誤輸入,控制終端路徑consolePath,容器許可權Capabilities以及上述提到的ParentProcess對象ops(擁有上面的一些操作函數,可以直接管理進程)。

5. 總結
本文主要介紹了Docker容器管理的方式libcontainer,從libcontainer的使用到源碼實現方式。我們深入到容器進程內部,感受到了libcontainer較為全面的設計。總體而言,libcontainer本身主要分為三大塊工作內容,一是容器的創建及初始化,二是容器生命周期管理,三則是進程管理,調用方為Docker的execdriver。容器的監控主要通過cgroups的狀態統計信息,未來會加入進程追蹤等更豐富的功能。另一方面,libcontainer在安全支持方面也為用戶盡可能多的提供了支持和選擇。遺憾的是,容器安全的配置需要用戶對系統安全本身有足夠高的理解,user namespace也尚未支持,可見libcontainer依舊有很多工作要完善。但是Docker社區的火熱也自然帶動了大家對libcontainer的關注,相信在不久的將來,libcontainer就會變得更安全、更易用。

F. 如何編譯Docker源碼

本文根據docker官方給出的docker代碼編譯環境搭建指南做更深入的分析。官方給出的指導比較簡單,但是由於國內的網路問題經常會編譯失敗,了解了編譯步驟後,也可以結合自身遇到的網路問題進行「規避」。
docker的編譯環境實際上是創建一個docker容器,在容器中對代碼進行編譯。 如果想快速的查看編譯環境搭建指導,而不關注環境搭建的機制和細節,可以直接跳到最後一章「總結」。

前提
機器上已經安裝了docker,因為編譯環境是個docker容器,所以要事先有docker(daemon),後面會創建個編譯環境容器,在容器裡面編譯代碼。本文中使用物理機,物理機上運行著docker (daemon)。
機器(物理機)上安裝了git 。 後續使用git下載docker源碼
機器(物理機)上安裝了make。
下載ubuntu 14.04的docker鏡像

下載docker源碼
git clone
會把代碼下載到當前目錄下,後面會把代碼拷貝到容器中。

編譯前分析
官方給的編譯方法是make build 和 make binary等。下面先分析Makefile,看懂Makefile後,編譯環境的准備流程就比較清楚了。

Makefile
在下載的docker源碼中可以看到它的Makefile,Makefile中比較關鍵的幾個參數:
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)") DOCKER_MOUNT 表示創建容器時的mount參數。因為編譯環境是一個容器,在後續的步驟中啟動容器時使用DOCKER_MOUNT參數,會將物理機上的目錄mount給容器容器,容器中該目錄是編譯生成docker二進制文件的目錄。
DOCKER_FLAGS := docker run --rm -i --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) 這是後面創建docker容器時的命令行的一部分,其中包含了前面的DOCKER_MOUNT參數。
DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH)) 這是docker image參數,鏡像的名字是docker-dev,以當前git中docker版本作為tag名。這個鏡像是在make build一步做出來的。
DOCKER_RUN_DOCKER := $(DOCKER_FLAGS) "$(DOCKER_IMAGE)" 創建docker容器的命令行,組合了前面的DOCKER_FLAGS 和 DOCKER_IMAGE 。 從命令行中可以看出,啟動容器使用的參數有 --rm -i --privileged,使用了一些環境變數,還有使用了-v參數把物理機上目錄mount給容器,在容器中編譯好二進制文件後放到該目錄中,在物理機上就能獲得docker二進制文件。啟動的的docker 容器鏡像名字是docker-dev。下文會介紹docker-dev鏡像是怎麼來的。
由於官方給出的「構建編譯環境」的方法是執行 make build,下面在Makefile中看到build分支是這樣的:

make build時會調用 docker build -t "$(DOCKER_IMAGE)" . 去製作一個叫做DOCKER_IMAGE的鏡像。
進行源碼編譯的方式是執行 make binary來編譯代碼,在Makefile中make binary的分支如下:

make binary除了進行 make build以外,會執行$(DOCKER_RUN_DOCKER),即上文提到的docker run命令行。由於執行過了build,會build出來docker-dev鏡像,所以在docker run時直接使用前面build出來的鏡像。docker run時的命令行參數是hack/make.sh binary。make binary的過程實際上是創建一個容器,在容器中執行hack/make.sh binary腳本。接下來會詳細介紹make build和make binary所做的內容。
make build
根據官方的指導,先執行make build來搭建編譯環境。上面分析了,make build實際上是製作了一個鏡像,這個鏡像里會包含編譯代碼所需的環境。下面來介紹下這個鏡像。

Dockerfile
在和Makefile相同的目錄下(源碼的根目錄),有Dockerfile。執行make build 相當於調用docker build,使用的就是該Dockerfile。Dockerfile中的幾個主要步驟(有些步驟這里略過):
FROM ubuntu:14.04 使用ubuntu 14.04作為基礎鏡像;在宿主機上,要事先下載好ubuntu 14.04鏡像。
安裝一些編譯需要的軟體;
用git下載lvm2源碼,並編譯安裝;
下載並安裝GO 1.5.1;
安裝GO相關的tools 可以做code coverage test 、 go lint等代碼檢查
安裝registry和notary server;
安裝docker-py 後面跑集成測試用的
將物理機的contrib/download-frozen-image.sh 腳本拷貝到鏡像中/go/src/github.com/docker/docker/contrib/
運行contrib/download-frozen-image.sh 製作鏡像 實際上這一步只是下載了3個鏡像的tar文件。注意:docker build相當於創建一個臨時的容器(在臨時的容器中執行Dockerfile中的每一步,最後在保存成鏡像),「運行contrib/download-frozen-image.sh 製作鏡像」這個動作出現在Dockerfile中,相當於在docker build所創建的臨時的容器中下載docker鏡像,有docker-in-docker容器嵌套的概念。下一小節會對download-frozen-image.sh腳本做詳細分析。
ENTRYPOINT ["hack/dind"] 做出來的鏡像,使用它啟動的容器可以自動運行源碼目錄中的hack/dind腳本。 dind這個腳本是a wrapper script which allows docker to be run inside a docker container 。後面的小節會對hack/dind腳本做詳細的分析。
COPY . /go/src/github.com/docker/docker 把物理機上的docker源碼文件打入到鏡像中
download-frozen-image.sh腳本
上一小節里提到,在Dockerfile中,有一步會調用contrib/download-frozen-image.sh ,它主要作用是下載3個鏡像的tar包,供後續docker load。在Dockerfile中的調用方式如下:

download-frozen-image.sh腳本中會依次解析參數,其中/docker-frozen-images作為base dir,後面下載的東西全放到這里。之後的3個參數是鏡像,裡麵包含了鏡像名(例如busybox)、鏡像tag(例如latest)、鏡像id(例如),後面會在循環中依次下載這3個鏡像的tar文件。
download-frozen-image.sh腳本中會通過curl從registry上獲取如下信息:
token:獲取token,後面curl獲取的其他信息時都需要使用token。例如本例中 token='signature=,repository="library/busybox",access=read'
ancestryJson:把鏡像相關聯的歷史層次的id也都獲取到,因為每一層的tar都需要下載。本例中 ancestryJson='["", ""]'
這里可以看到這個鏡像只有2層,兩層的id這里都列了出來。 每個鏡像包含的層數不同,例如。第三個鏡像jess/unshare共有10層。
VERSION、json、tar: 每一層鏡像id的目錄下,都下載這3個文件,其中VERSION文件內容目前都是「1.0」,json文件是該層鏡像的json文件,tar文件是該層鏡像的真正內容,以.tar保存。
下載好的各層鏡像目錄結構如下:
$ls

$tree

hack/dind腳本
在Dockerfile中,ENTRYPOINT ["hack/dind"] ,表示在鏡像啟動後,運行該腳本,下面分析一下這個腳本的功能。
腳本在代碼根目錄下的hack目錄中,作者對腳本的描述是 DinD: a wrapper script which allows docker to be run inside a docker container.
就是可以在docker容器中創建docker容器。它就做了一個事,那就是在容器中創建好cgroup目錄,並把各個cgroup子系統mount上來。
為了方便理解,我們可以先看看物理機。在宿主機上如果創建docker容器,需要宿主機上必須事先mount cgroup子系統,因為cgroup是docker容器的一個依賴。同理docker-in-docker也要求外層的docker容器中有cgroup子系統,dind腳本在容器啟動後,先去/proc/1/cgroup中獲取cgroup子系統,然後依次使用mount命令,將cgroup mount上來,例如mount -n -t cgroup -o "cpuset" cgroup "/cgroup/cpuset"
最終在運行make build後,會製作出一個叫docker-dev的鏡像。
make binary
執行make binary 就可以編譯出docker二進制文件。編譯出來的二進制文件在源碼目錄下的bundles/1.10.0-dev/binary/docker-1.10.0-dev ,其中還包含md5和sha256文件。

Makefile中的binary
Makefile中關於make binary流程是

先執行build,即上一節介紹的,製作docker-dev編譯環境鏡像。
再執行DOCKER_RUN_DOCKER,創建容器,DOCKER_RUN_DOCKER就是執行docker run,使用docker-dev鏡像啟動容器,並且會mount -v 將容器生成二進制文件的路徑與宿主機共享。DOCKER_RUN_DOCKER在「編譯前分析」一章中有介紹。啟動的容器運行的命令行是 hack/make.sh binary 。docker run完整的形式如下:
docker run --rm -i --privileged -e BUILDFLAGS -e DOCKER_CLIENTONLY -e DOCKER_DEBUG -e DOCKER_EXECDRIVER -e DOCKER_EXPERIMENTAL -e DOCKER_REMAP_ROOT -e DOCKER_GRAPHDRIVER -e DOCKER_STORAGE_OPTS -e DOCKER_USERLANDPROXY -e TESTDIRS -e TESTFLAGS -e TIMEOUT -v "/home/mu/src/docker/docker/bundles:/go/src/github.com/docker/docker/bundles" -t "docker-dev:master" hack/make.sh binary
hack/make.sh腳本
上一節提到的make binary中創建的容器啟動命令是hack/make.sh binary,運行容器中的(docker源碼目錄下的)hack/make.sh腳本,參數為binary。
make.sh中根據傳入的參數組裝後續編譯用的flags(BUILDFLAGS),最後根據傳入的參數依次調用 hack/make/目錄下對應的腳本。例如我們的操作中傳入的參數只有一個binary。那麼在make.sh的最後,會調用hack/make/binary腳本。
hack/make/binary腳本中,就是直接調用go build進行編譯了,其中會使用BUILDFLAGS LDFLAGS LDFLAGS_STATIC_DOCKER等編譯選項。
如果最終生成的docker二進制文件不在bundles/1.10.0-dev/binary/目錄下,那麼可能是編譯參數BINDDIR設置的不正確,可以在執行make binary時增加BINDDIR參數,例如
make BINDDIR=. binary , 將BINDDIR設置為當前目錄。
總結
編譯步驟總結:
1、編譯前在物理機上安裝好make、git,並下載好docker代碼。下載好ubuntu:14.04鏡像
2、執行make build 。這步執行完會在物理機上創建出一個docker-dev的鏡像。
3、執行make binary 。 這步會使用docker-dev鏡像啟動一個容器,在容器中編譯docker代碼。編譯完成後在物理機上直接可以看到二進制文件。默認二進制文件在 bundles/1.10.0-dev/binary/目錄下
4、docker代碼里有很多test,可以使用此套編譯環境執行test,例如 make test 。 更多參數可以看Makefile
搭建環境心得:
1、在make build時,使用Dockerfile創建製作鏡像,這個鏡像有40多層,其中一層失敗就會導致整個build過程失敗。由於Dockerfile中很多步驟是要連到國外的網站去下載東西,很容易失敗。好在docker build有cache機制,如果前面的層成功了,下次重新build時會使用cache跳過,節省了很多時間。所以如果make build中途失敗(一般是由於國內連國外的網路原因),只要重新執行make build就會在上次失敗的地方繼續,多試幾次可以成功。
2、如果其他人已經build出了docker-dev鏡像,可以把它下載到自己的環境上。這樣在自己make build時,會跳過那些已經在本地存在的層,可以節省時間。
3、每一次編譯會自動刪除掉前面已經生成的二進制文件,所以不用擔心二進制文件不是最新的問題。

G. 五、Docker Server 的創建(摘自《Docker源碼分析》)

Docker 架構中,Docker Server 是 Docker Daemon 的重要組成部分。Docker Server 最主要的功能是:

Docker Daemon 最後環節創建的並運行的serveapi的job 就是讓守護進程提供API訪問服務

使 Docker Server 監聽某一指定地址,接受該地址上的請求,並對以上請求路由轉發至相應的處理函數 Handler 處。從實現的角度來看,ListenAndServe 主要實現了設置一個服務於 HTTP 的 server,該 server 將監聽指定地址上的請求,並對春穗告請求做特定的協議檢查,最終完成請求的路由與分發。

路由實例的作用是:負責Docker Server 對請求進行路由以及分發。實現過程中,主要兩個步驟:
第一,創建全新的router 路由實例;
第二,為router 實例添族前加路由記錄。

Listener 是一種面向流協議的通用網路監聽模塊。
在創建 Listener 之前,先判斷 Docker Server 允許的協議,若協議為 fd 形式,則直接通過 ServeFd 來服務請求;若協議不為 fd 形式,則繼續往下執行。

Docker Server 同樣需要創建一個 Server 對象來運行 HTTP 服務端

創建扒明 http.Server 實例之後,Docker Server 立即啟動 API 服務,使 Docker Server 開始在 Listener 監聽實例 l 上接受請求,並對於每一個請求都生成一個新的 goroutine 來做專屬服務。對於每一個請求,goroutine 會讀取請求,查詢路由表中的路由記錄項,找到匹配的路由記錄,最終調用路由記錄中的執行 Handler,執行完畢後,goroutine 對請求返回響應信息。

H. 微服務架構:基於微服務和Docker容器技術的PaaS雲平台架構設計

基於微服務架構和Docker容器技術的PaaS雲平台建設目標是給我們的開發人員提供一套服務快速開發、部署、運維管理、持續開發持續集成的流程。平台提供基礎設施、中間件、數據服務、雲伺服器等資源,開發人員只需要開發業務代碼並提交到平台代碼庫,做一些必要的配置,系統會自動構建、部署,實現應用的敏捷開發、快速迭代。在系統架構上,PaaS雲平台主要分為微服務架構、Docker容器技術、DveOps三部分,這篇文章重點介紹微服務架構的實施。

如果想學習Java工程化、高性能及分布式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135,群里有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家。

實施微服務需要投入大量的技術力量來開發基礎設施,這對很多公司來說顯然是不現實的,別擔心,業界已經有非常優秀的開源框架供我們參考使用。目前業界比較成熟的微服務框架有Netflix、Spring Cloud和阿里的Dubbo等。Spring Cloud是基於Spring Boot的一整套實現微服務的框架,它提供了開發微服務所需的組件,跟Spring Boot一起使用的話開發微服務架構的雲服務會變的很方便。Spring Cloud包含很多子框架,其中Spring Cloud Netflix是其中的一套框架,在我們的微服務架構設計中,就使用了很多Spring Cloud Netflix框架的組件。Spring Cloud Netflix項目的時間還不長,相關的文檔資料很少,博主當時研究這套框架啃了很多英文文檔,簡直痛苦不堪。對於剛開始接觸這套框架的同學,要搭建一套微服務應用架構,可能會不知道如何下手,接下來介紹我們的微服務架構搭建過程以及 需要那些 框架或組件來支持微服務架構。

為了直接明了的展示微服務架構的組成及原理,畫了一張系統架構圖,如下:

從上圖可以看出,微服務訪問大致路徑為:外部請求 → 負載均衡 → 服務網關(GateWay)→ 微服務 → 數據服務/消息服務。服務網關和微服務都會用到服務注冊和發現來調用依賴的其他服務,各服務集群都能通過配置中心服務來獲得配置信息。

服務網關(GateWay)

網關是外界系統(如:客戶端瀏覽器、移動設備等)和企業內部系統之間的一道門,所有的客戶端請求通過網關訪問後台服務。為了應對高並發訪問,服務網關以集群形式部署,這就意味著需要做負載均衡,我們採用了亞馬遜EC2作為虛擬雲伺服器,採用ELB(Elastic Load Balancing)做負載均衡。EC2具有自動配置容量功能,當用戶流量達到尖峰,EC2可以自動增加更多的容量以維持虛擬主機的性能。ELB彈性負載均衡,在多個實例間自動分配應用的傳入流量。為了保證安全性,客戶端請求需要使用https加密保護,這就需要我們進行SSL卸載,使用Nginx對加密請求進行卸載處理。外部請求經過ELB負載均衡後路由到GateWay集群中的某個GateWay服務,由GateWay服務轉發到微服務。服務網關作為內部系統的邊界,它有以下基本能力:

1、動態路由:動態的將請求路由到所需要的後端服務集群。雖然內部是復雜的分布式微服務網狀結構,但是外部系統從網關看就像是一個整體服務,網關屏蔽了後端服務的復雜性。

2、限流和容錯:為每種類型的請求分配容量,當請求數量超過閥值時拋掉外部請求,限制流量,保護後台服務不被大流量沖垮;黨內部服務出現故障時直接在邊界創建一些響應,集中做容錯處理,而不是將請求轉發到內部集群,保證用戶良好的體驗。

3、身份認證和安全性控制:對每個外部請求進行用戶認證,拒絕沒有通過認證的請求,還能通過訪問模式分析,實現反爬蟲功能。

4、監控:網關可以收集有意義的數據和統計,為後台服務優化提供數據支持。

5、訪問日誌:網關可以收集訪問日誌信息,比如訪問的是哪個服務?處理過程(出現什麼異常)和結果?花費多少時間?通過分析日誌內容,對後台系統做進一步優化。

我們採用Spring Cloud Netflix框架的開源組件Zuul來實現網關服務。Zuul使用一系列不同類型的過濾器(Filter),通過重寫過濾器,使我們能夠靈活的實現網關(GateWay)的各種功能。

如果想學習Java工程化、高性能及分布式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135,群里有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家。

服務注冊與發現

由於微服務架構是由一系列職責單一的細粒度服務構成的網狀結構,服務之間通過輕量機制進行通信,這就引入了服務注冊與發現的問題,服務的提供方要注冊報告服務地址,服務調用放要能發現目標服務。我們的微服務架構中使用了Eureka組件來實現服務的注冊與發現。所有的微服務(通過配置Eureka服務信息)到Eureka伺服器中進行注冊,並定時發送心跳進行 健康 檢查,Eureka默認配置是30秒發送一次心跳,表明服務仍然處於存活狀態,發送心跳的時間間隔可以通過Eureka的配置參數自行配置,Eureka伺服器在接收到服務實例的最後一次心跳後,需要等待90秒(默認配置90秒,可以通過配置參數進行修改)後,才認定服務已經死亡(即連續3次沒有接收到心跳),在Eureka自我保護模式關閉的情況下會清除該服務的注冊信息。所謂的自我保護模式是指,出現網路分區、Eureka在短時間內丟失過多的服務時,會進入自我保護模式,即一個服務長時間沒有發送心跳,Eureka也不會將其刪除。自我保護模式默認為開啟,可以通過配置參數將其設置為關閉狀態。

Eureka服務以集群的方式部署(在博主的另一篇文章中詳細介紹了Eureka集群的部署方式),集群內的所有Eureka節點會定時自動同步微服務的注冊信息,這樣就能保證所有的Eureka服務注冊信息保持一致。那麼在Eureka集群里,Eureka節點是如何發現其他節點的呢?我們通過DNS伺服器來建立所有Eureka節點的關聯,在部署Eureka集群之外還需要搭建DNS伺服器。

當網關服務轉發外部請求或者是後台微服務之間相互調用時,會去Eureka伺服器上查找目標服務的注冊信息,發現目標服務並進行調用,這樣就形成了服務注冊與發現的整個流程。Eureka的配置參數數量很多,多達上百個,博主會在另外的文章里詳細說明。

微服務部署

微服務是一系列職責單一、細粒度的服務,是將我們的業務進行拆分為獨立的服務單元,伸縮性好,耦合度低,不同的微服務可以用不同的語言開發,每一個服務處理的單一的業務。微服務可以劃分為前端服務(也叫邊緣服務)和後端服務(也叫中間服務),前端服務是對後端服務做必要的聚合和剪裁後暴露給外部不同的設備(PC、Phone等),所有的服務啟動時都會到Eureka伺服器進行注冊,服務之間會有錯綜復雜的依賴關系。當網關服務轉發外部請求調用前端服務時,通過查詢服務注冊表就可以發現目標服務進行調用,前端服務調用後端服務時也是同樣的道理,一次請求可能涉及到多個服務之間的相互調用。由於每個微服務都是以集群的形式部署,服務之間相互調用的時候需要做負載均衡,因此每個服務中都有一個LB組件用來實現負載均衡。

微服務以鏡像的形式,運行在Docker容器中。Docker容器技術讓我們的服務部署變得簡單、高效。傳統的部署方式,需要在每台伺服器上安裝運行環境,如果我們的伺服器數量龐大,在每台伺服器上安裝運行環境將是一項無比繁重的工作,一旦運行環境發生改變,就不得不重新安裝,這簡直是災難性的。而使用Docker容器技術,我們只需要將所需的基礎鏡像(jdk等)和微服務生成一個新的鏡像,將這個最終的鏡像部署在Docker容器中運行,這種方式簡單、高效,能夠快速部署服務。每個Docker容器中可以運行多個微服務,Docker容器以集群的方式部署,使用Docker Swarm對這些容器進行管理。我們創建一個鏡像倉庫用來存放所有的基礎鏡像以及生成的最終交付鏡像,在鏡像倉庫中對所有鏡像進行管理。

服務容錯

微服務之間存在錯綜復雜的依賴關系,一次請求可能會依賴多個後端服務,在實際生產中這些服務可能會產生故障或者延遲,在一個高流量的系統中,一旦某個服務產生延遲,可能會在短時間內耗盡系統資源,將整個系統拖垮,因此一個服務如果不能對其故障進行隔離和容錯,這本身就是災難性的。我們的微服務架構中使用了Hystrix組件來進行容錯處理。Hystrix是Netflix的一款開源組件,它通過熔斷模式、隔離模式、回退(fallback)和限流等機制對服務進行彈性容錯保護,保證系統的穩定性。

1、熔斷模式:熔斷模式原理類似於電路熔斷器,當電路發生短路時,熔斷器熔斷,保護電路避免遭受災難性損失。當服務異常或者大量延時,滿足熔斷條件時服務調用方會主動啟動熔斷,執行fallback邏輯直接返回,不會繼續調用服務進一步拖垮系統。熔斷器默認配置服務調用錯誤率閥值為50%,超過閥值將自動啟動熔斷模式。服務隔離一段時間以後,熔斷器會進入半熔斷狀態,即允許少量請求進行嘗試,如果仍然調用失敗,則回到熔斷狀態,如果調用成功,則關閉熔斷模式。

2、隔離模式:Hystrix默認採用線程隔離,不同的服務使用不同的線程池,彼此之間不受影響,當一個服務出現故障耗盡它的線程池資源,其他的服務正常運行不受影響,達到隔離的效果。例如我們通過andThreadPoolKey配置某個服務使用命名為TestThreadPool的線程池,實現與其他命名的線程池隔離。

3、回退(fallback):fallback機制其實是一種服務故障時的容錯方式,原理類似Java中的異常處理。只需要繼承HystixCommand並重寫getFallBack()方法,在此方法中編寫處理邏輯,比如可以直接拋異常(快速失敗),可以返回空值或預設值,也可以返回備份數據等。當服務調用出現異常時,會轉向執行getFallBack()。有以下幾種情況會觸發fallback:

1)程序拋出非HystrixBadRequestExcepption異常,當拋出HystrixBadRequestExcepption異常時,調用程序可以捕獲異常,沒有觸發fallback,當拋出其他異常時,會觸發fallback;

2)程序運行超時;

3)熔斷啟動;

4)線程池已滿。

4、限流: 限流是指對服務的並發訪問量進行限制,設置單位時間內的並發數,超出限制的請求拒絕並fallback,防止後台服務被沖垮。

Hystix使用命令模式HystrixCommand包裝依賴調用邏輯,這樣相關的調用就自動處於Hystrix的彈性容錯保護之下。調用程序需要繼承HystrixCommand並將調用邏輯寫在run()中,使用execute()(同步阻塞)或queue()(非同步非阻塞)來觸發執行run()。

動態配置中心

微服務有很多依賴配置,某些配置參數在服務運行期間可能還要動態修改,比如:根據訪問流量動態調整熔斷閥值。傳統的實現信息配置的方法,比如放在xml、yml等配置文件中,和應用一起打包,每次修改都要重新提交代碼、打包構建、生成新的鏡像、重新啟動服務,效率太低,這樣顯然是不合理的,因此我們需要搭建一個動態配置中心服務支持微服務動態配置。我們使用Spring Cloud的configserver服務幫我們實現動態配置中心的搭建。我們開發的微服務代碼都存放在git伺服器私有倉庫裡面,所有需要動態配置的配置文件存放在git伺服器下的configserver(配置中心,也是一個微服務)服務中,部署到Docker容器中的微服務從git伺服器動態讀取配置文件的信息。當本地git倉庫修改代碼後push到git伺服器倉庫,git服務端hooks(post-receive,在服務端完成代碼更新後會自動調用)自動檢測是否有配置文件更新,如果有,git服務端通過消息隊列給配置中心(configserver,一個部署在容器中的微服務)發消息,通知配置中心刷新對應的配置文件。這樣微服務就能獲取到最新的配置文件信息,實現動態配置。

以上這些框架或組件是支撐實施微服務架構的核心,在實際生產中,我們還會用到很多其他的組件,比如日誌服務組件、消息服務組件等等,根據業務需要自行選擇使用。在我們的微服務架構實施案例中,參考使用了很多Spring Cloud Netflix框架的開源組件,主要包括Zuul(服務網關)、Eureka(服務注冊與發現)、Hystrix(服務容錯)、Ribbon(客戶端負載均衡)等。這些優秀的開源組件,為我們實施微服務架構提供了捷徑。

如果想學習Java工程化、高性能及分布式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java高級交流:854630135,群里有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家。

熱點內容
雙面警長第一季ftp 發布:2025-05-16 11:41:20 瀏覽:663
php取數組第一個 發布:2025-05-16 11:30:58 瀏覽:423
解調演算法 發布:2025-05-16 11:21:09 瀏覽:136
python密碼暴力破解 發布:2025-05-16 11:13:28 瀏覽:592
倒角刀編程 發布:2025-05-16 11:12:55 瀏覽:350
資料庫的酸性 發布:2025-05-16 11:03:17 瀏覽:124
phpmysql長連接 發布:2025-05-16 10:51:50 瀏覽:734
android橫屏全屏 發布:2025-05-16 10:47:43 瀏覽:475
伺服器直鏈下載搭建 發布:2025-05-16 10:47:38 瀏覽:176
編譯不成功怎麼辦 發布:2025-05-16 10:35:54 瀏覽:613