加快軟件部署 - Docker Swarm 教程

已發表: 2022-03-11

除非您一直住在集裝箱內,否則您可能聽說過集裝箱。 該行業一直在從持久性基礎設施轉向臨時基礎設施,而容器在這一轉變過程中是方正的。 原因很簡單:雖然容器確實可以幫助開發團隊快速啟動和運行,但它們更有潛力徹底改變運營的面貌。

但這究竟是什麼樣子的呢? 當您準備好在本地運行容器或在幾台服務器上手動運行容器時會發生什麼? 在一個理想的世界裡,你只想把你的應用程序扔到一個服務器集群上,然後說“運行它!”

謝天謝地,這就是我們今天所處的位置。

在本文中,我們將探討 Docker Swarm 是什麼,以及它必須提供的一些出色功能。 然後我們將看看實際使用 Swarm 模式並部署到一個 swarm 是什麼樣子的,我們將用一些示例來說明使用已部署的 swarm 的日常操作。 絕對建議您了解 Docker 和容器的基本知識,但如果您不熟悉容器,可以先查看這篇出色的博客文章。

什麼是 Docker Swarm?

Docker Swarm 模式徽標

在我們深入創建和部署到我們的第一個 swarm 之前,了解 Docker Swarm 是什麼會很有幫助。 Docker 本身已經存在多年,今天大多數人認為它是一個容器運行時。 但實際上,Docker 由許多不同的部分組成,它們一起工作。 例如,該容器運行時部分由兩個較小的組件處理,稱為 runC 和 containerd。 隨著 Docker 的發展和回饋社區,他們發現創建這些更小的組件是增長和快速添加功能的最佳方式。 因此,我們現在有 SwarmKit 和 Swarm 模式,它直接內置在 Docker 中。

Docker Swarm 是一個容器編排引擎。 在高層次上,它需要在不同主機上運行多個 Docker 引擎,並允許您一起使用它們。 用法很簡單:將您的應用程序聲明為服務堆棧,並讓 Docker 處理其餘部分。 服務可以是從應用程序實例到數據庫的任何東西,也可以是 Redis 或 RabbitMQ 等實用程序。 如果您曾經在開發中使用過 docker-compose,這聽起來應該很熟悉,因為它是完全相同的概念。 事實上,堆棧聲明實際上只是一個docker-compose.yml文件,具有 3.1 版本的語法。 這意味著您可以使用類似的(並且在許多情況下相同的)組合配置進行開發和集群部署,但是我在這裡有點超前了。 當您在 Swarm 模式下擁有 Docker 實例時會發生什麼?

Docker Swarm 模式將服務分組到堆棧中。

不要從木筏上掉下來

在 Swarm 世界中,我們有兩種類型的節點(服務器):管理器和工作器。 重要的是要記住,經理也是工人,他們只是有額外的責任讓事情繼續運轉。 每個 swarm 都從一個指定為領導者的管理節點開始。 從那裡,只需運行一個命令即可將節點安全地添加到集群中。

由於實現了 Raft 算法,Swarm 具有很高的可用性。 我不會詳細介紹 Raft,因為已經有一個關於它如何工作的很棒的教程,但這裡有一個大體思路:領導節點不斷地與其他管理節點檢查並同步它們的狀態。 為了“接受”狀態更改,管理節點要達成共識,這發生在大多數節點確認狀態更改時。

這樣做的美妙之處在於,管理節點可以在不影響群體共識的情況下偶爾下降。 如果狀態更改達成共識,我們知道它保證存在於大多數管理器節點上,並且即使當前領導者失敗也會持續存在。

假設我們有三個管理節點,分別名為 A、B 和 C。當然,A 是我們無畏的領導者。 好吧,有一天,一個短暫的網絡錯誤使 A 離線,只剩下 B 和 C。 在很長一段時間(幾百毫秒)沒有收到 A 的消息後,B 和 C 等待一個隨機生成的時間段,然後將自己進行選舉並通知對方。 當然,第一個上選的,在這種情況下,會當選。 在此示例中,B 成為新的領導者,並且恢復了法定人數。 但是,情節轉折:當 A 重新上線時會發生什麼? 它會認為它仍然是領導者,對吧? 每次選舉都有一個與之相關的任期,所以 A 實際上是在第一任期當選的。一旦 A 重新上線並開始命令 B 和 C,他們就會好心地讓它知道 B 是第二任期的領導者,並且A將下台。

當然,同樣的過程在更大的範圍內起作用。 您可以擁有三個以上的管理器節點。 不過,我會添加另一個快速說明。 每個 Swarm 只能承受特定數量的經理損失。 一群 n 個管理器節點可以失去(n-1)/2管理器而不會失去法定人數。 這意味著對於三個管理器群,您可能會失去一個,對於五個管理器,您可能會失去兩個,等等。其根本原因回到了多數共識的想法,並且在您進行生產時絕對要牢記這一點。

任務調度和協調

到目前為止,我們已經確定我們的經理非常善於保持同步。 偉大的! 但他們實際上在做什麼? 還記得我說過你將一堆服務部署到 Swarm 嗎? 當你聲明你的服務時,你向 Swarm 提供了關於你希望你的服務如何運行的重要信息。 這包括參數,例如您想要每個服務的副本數量、副本應該如何分佈、它們是否應該只在某些節點上運行等等。

部署服務後,管理人員的工作就是確保繼續滿足您設置的任何部署要求。 假設您部署了一個 Nginx 服務並指定應該有三個副本。 管理器將看到沒有容器正在運行,並將三個容器均勻地分佈在可用節點上。

不過,更酷的是,如果一個容器發生故障(或整個節點下線),Swarm 將自動在其餘節點上創建容器以彌補差異。 如果您說要運行三個容器,那麼您將運行三個容器,而 Swarm 處理所有細節。 另外——這是一個很大的優勢——擴大或縮小規模就像給 Swarm 一個新的複制設置一樣容易。

服務發現和負載平衡

我想從最後一個例子中指出一個重要但微妙的細節:如果 Swarm 在它選擇的節點上智能地啟動容器,我們不一定知道這些容器將在哪裡運行。 起初這聽起來可能很嚇人,但它實際上是 Swarm 最強大的功能之一。

繼續同樣的 Nginx 示例,假設我們告訴 Docker 這些容器應該公開端口 80。如果您將瀏覽器指向在端口 80 上運行該容器的節點,您將看到該容器的內容。 那裡沒有驚喜。 但可能令人驚訝的是,如果您將請求發送到未運行該容器的節點,您仍然會看到相同的內容! 這裡發生了什麼事?

Swarm 實際上是使用入口網絡將您的請求發送到運行該容器的可用節點,並且同時對其進行負載平衡。 因此,如果您向同一個節點發出三個請求,您很可能會訪問三個不同的容器。 只要您知道 swarm 中單個節點的 IP,就可以訪問其中運行的任何內容。 相反,這使您可以將負載均衡器(例如 ELB)指向 swarm 中的所有節點,而無需擔心在哪裡運行什麼。

它不會停留在外部連接上。 在同一堆棧上運行的服務有一個覆蓋網絡,可以讓它們相互通信。 無需在代碼中硬編碼 IP 地址,您只需使用服務名稱作為要連接的主機名即可。 例如,如果您的應用程序需要與名為“redis”的 Redis 服務通信,它可以簡單地使用“redis”作為主機名,Swarm 會將其請求路由到適當的容器。 而且因為這在使用 docker-compose 的開發和使用 Docker Swarm 的生產中無縫工作,所以在部署應用程序時無需擔心一件事。

Docker Swarm 模式的路由網格將請求路由到適當的容器,即使它們在不同節點上運行也是如此。

滾動更新

如果您在操作中,當產品更新出現嚴重錯誤時,您可能會經歷恐慌發作。 這可能是錯誤的代碼更新,甚至只是配置錯誤,但突然生產停止了! 很可能老闆不會在意任何一種方式。 他們只會知道這是你的錯。 好吧,別擔心,Swarm 也支持這個。

更新服務時,您可以定義一次應該更新多少個容器,以及如果新容器開始失敗應該發生什麼。 在某個閾值之後,Swarm 可以停止更新或(從 Docker 17.04 開始)將容器回滾到之前的鏡像和設置。 不用擔心明天早上必須給你的老闆帶杯咖啡。

安全

最後但並非最不重要的一點是,Docker Swarm 具有開箱即用的強大安全功能。 當一個節點加入 swarm 時,它會使用一個令牌,該令牌不僅可以驗證自己,還可以驗證它是否正在加入您認為的 swarm。 從那一刻起,節點之間的所有通信都使用相互 TLS 加密進行。 這種加密全部由 Swarm 自動配置和管理,因此您無需擔心更新證書和其他典型的安全問題。 當然,如果您想強制密鑰輪換,則有一個命令。

Docker Swarm 默認是安全的。

最新版本的 Docker Swarm 還帶有內置的秘密管理。 這使您可以將密鑰和密碼等秘密安全地部署到需要它們的服務,並且只部署到需要它們的服務。 當您為服務提供密鑰時,該服務的容器將在其文件系統中安裝一個特殊文件,其中包含密鑰的值。 不言而喻,但這比使用環境變量安全得多,後者是傳統方法。

潛入蟲群

如果你和我一樣,你會渴望加入並體驗所有這些功能! 所以事不宜遲,讓我們開始吧!

Docker Swarm 示例應用程序

我創建了一個非常基本的 Flask 應用程序來展示使用 Docker Swarm 的強大功能和易用性。 Web 應用程序只顯示一個頁面,告訴您哪個容器為您的請求提供了服務,總共提供了多少個請求,以及“秘密”數據庫密碼是什麼。

它分為三個服務:實際的 Flask 應用程序、一個 Nginx 反向代理和一個 Redis 密鑰庫。 在每個請求上,應用程序都會增加 Redis 中的num_requests鍵,因此無論您點擊哪個 Flask 實例,您都會看到反映的請求的正確數量。

如果您想“查看”正在發生的事情,所有源代碼都可以在 GitHub 上找到。

與 Docker 一起玩!

學習本教程時,您可以隨意使用自己的服務器,但如果您想直接加入,我強烈建議您使用 play-with-docker.com。這是一個由一些 Docker 開發人員運行的站點,可讓您啟動多個網絡預先安裝了 Docker 的節點。 它們將在四個小時後關閉,但這對於這個示例來說已經足夠了!

創建一個 Swarm

好的,我們開始吧! 繼續在 PWD(play-with-docker)中創建三個實例,或者在您最喜歡的 VPS(虛擬專用服務器)服務中啟動三個服務器,並在所有這些服務器上安裝 Docker 引擎。 請記住,您始終可以創建一個圖像並在將來添加節點時重新使用它。 管理節點和工作節點之間在軟件方面沒有區別,因此您不需要維護兩個不同的圖像。

還在轉? 別擔心,我等著。 好的,現在我們將創建我們的第一個管理器和領導節點。 在您的第一個實例中,初始化一個 swarm:

 docker swarm init --advertise-addr <node ip here>

<node_ip_here>替換為您節點的 IP 地址。 在 PWD 上,IP 地址顯示在頂部,如果您使用自己的 VPS,只要可以從網絡中的其他節點訪問,請隨意使用服務器的私有 IP 地址。

你現在有一群! 不過,這是一個非常無聊的集群,因為它只有一個節點。 讓我們繼續添加其他節點。 您會注意到,當您運行init時,它會顯示一條長消息,說明如何使用加入令牌。 我們不會使用那個,因為它會使其他節點成為工作節點,我們希望他們成為管理者。 讓我們通過在第一個節點上運行它來獲取管理器的加入令牌:

 docker swarm join-token manager

複製生成的命令並在第二個和第三個節點上運行它。 看哪,一個三節點集群! 讓我們驗證我們所有的節點是否真的存在。 docker node ls命令將列出我們 swarm 中的所有節點。 您應該看到如下內容:

 $ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS su1bgh1uxqsikf1129tjhg5r8 * node1 Ready Active Leader t1tgnq38wb0cehsry2pdku10h node3 Ready Active Reachable wxie5wf65akdug7sfr9uuleui node2 Ready Active Reachable

請注意我們的第一個節點如何在 ID 旁邊有一個星號。 這只是告訴我們這是我們當前連接的節點。 我們還可以看到這個節點當前是Leader,如果有什麼事情發生,其他節點都是Reachable。

花點時間體會一下這有多簡單,讓我們部署我們的第一個應用程序!

裝運它!

就在此時,業務開發團隊向客戶承諾,他們的新應用程序將在一小時內部署並準備就緒! 典型的,我知道。 但不要擔心,我們幾乎不需要那麼多時間,因為它是使用 Docker 構建的! 開發人員很友好地將他們的docker-compose文件借給我們:

 version: '3.1' services: web: image: lsapan/docker-swarm-demo-web command: gunicorn --bind 0.0.0.0:5000 wsgi:app deploy: replicas: 2 secrets: - db_password nginx: image: lsapan/docker-swarm-demo-nginx ports: - 8000:80 deploy: mode: global redis: image: redis deploy: replicas: 1 secrets: db_password: external: true

我們稍後會分解它,但現在還沒有時間。 讓我們部署它! 繼續在您的第一個節點上創建一個名為docker-compose.yml的文件,並使用上面的配置填充它。您可以使用echo "<pasted contents here>" > docker-compose.yml

通常,我們可以直接部署它,但我們的配置提到我們使用一個名為db_password的秘密,所以讓我們快速創建該秘密:

 echo "supersecretpassword" | docker secret create db_password -

偉大的! 現在我們需要做的就是告訴 Docker 使用我們的配置:

 docker stack deploy -c docker-compose.yml demo

當你運行這個命令時,你會看到 Docker 創建了我們定義的三個服務: webnginxredis 。 但是,因為我們將堆棧命名為 demo,所以我們的服務實際上命名為demo_webdemo_nginxdemo_redis 。 我們可以通過運行docker service ls命令來查看我們正在運行的服務,它應該顯示如下內容:

 $ docker service ls ID NAME MODE REPLICAS IMAGE PORTS cih6u1t88vx7 demo_web replicated 2/2 lsapan/docker-swarm-demo-web:latest u0p1gd6tykvu demo_nginx global 3/3 lsapan/docker-swarm-demo-nginx:latest *:8000->80/ tcp wa1gz80ker2g demo_redis replicated 1/1 redis:latest

瞧! Docker 已將我們的圖像下載到適當的節點並為我們的服務創建容器。 如果您的副本尚未滿負荷,請稍等片刻,然後再次檢查。 Docker 可能仍在下載圖像。

眼見為實

不過,不要相信我的話(或 Docker 的話)。 讓我們嘗試連接到我們的應用程序。 我們的服務配置告訴 Docker 在端口 8000 上公開 NGINX。如果您使用的是 PWD,頁面頂部應該有一個藍色鏈接,上面寫著“8000”。 PWD 實際上已經自動檢測到我們在該端口上運行了一項服務! 單擊它,它會將您路由到端口 8000 上的選定節點。如果您滾動自己的服務器,只需導航到端口 8000 上的服務器 IP 之一。

您將看到一個風格精美的屏幕,為您提供一些基本信息:

由創建的堆棧提供的內容

記下哪個容器為您的請求提供服務,然後刷新頁面。 有可能變了。 但為什麼? 好吧,我們告訴 Docker 創建我們的 Flask 應用程序的兩個副本,並將請求分發到這兩個實例。 你第二次碰巧撞到了另一個容器。 您還會注意到請求數量增加了,因為兩個 Flask 容器都在與我們指定的單個 Redis 實例進行通信。

隨意嘗試從任何節點訪問端口 8000。 您仍會被正確路由到該應用程序。

揭開魔法的神秘面紗

在這一點上,一切正常,希望你發現這個過程是無痛的! 讓我們仔細看看那個docker-compose.yml文件,看看我們實際上告訴了 Docker 什麼。 在高層次上,我們看到我們已經定義了三個服務: webnginxredis 。 就像普通的 compose 文件一樣,我們為 Docker 提供了一個用於每個服務的映像,以及一個運行命令。 對於nginx ,我們還指定主機上的端口 8000 應該映射到容器中的端口 80。 到目前為止,所有這些都是標準的撰寫語法。

這裡的新功能是部署密鑰和秘密密鑰。 這些鍵會被docker-compose忽略,因此它們不會影響您的開發環境,但會被docker stack使用。 讓我們看看網絡服務。 很簡單,我們告訴 Docker 我們想運行兩個副本我們的 Flask 應用程序。 我們還讓 Docker 知道 Web 服務需要db_password密碼。 這確保了容器將有一個名為/run/secrets/db_password的文件,其中包含密鑰的值。

向下移動到 Nginx,我們可以看到部署模式設置為global 。 默認值(在 web 中隱式使用)是replicated ,這意味著我們將指定我們想要多少副本。 當我們指定global時,它告訴 Docker 集群中的每個節點都應該運行一個服務實例。 再次運行docker service ls ,你會注意到nginx有三個副本,一個用於我們 swarm 中的每個節點。

最後,我們指示 Docker 在 swarm 的某處運行單個 Redis 實例。 在哪裡都沒有關係,因為我們的 Web 容器在請求 Redis 主機時會自動路由到它。

每天使用 Swarm

恭喜您將您的第一個應用程序部署到 Docker Swarm! 讓我們花點時間回顧一下您將使用的一些常用命令。

檢查你的 Swarm

需要檢查您的服務嗎? 嘗試docker service lsdocker service ps <service name> 。 前者向您顯示每個服務的高級概述,後者為您提供有關為指定服務運行的每個容器的信息。 當您想查看哪些節點正在運行服務時,該功能特別有用。

滾動更新

當您準備好更新應用程序時怎麼辦? 好吧,關於docker stack deploy很酷的一點是它實際上也會將更新應用到現有堆棧。 假設您已將一個新的 Docker 映像推送到您的存儲庫。 實際上,您可以只運行您第一次使用的相同部署命令,您的 swarm 將下載並部署新映像。

當然,您可能並不總是希望更新堆棧中的每個服務。 我們也可以在服務級別執行更新。 假設我最近更新了我的網​​絡服務的圖像。 我可以發出這個命令來更新我所有的 web 容器:

 docker service update \ --image lsapan/docker-swarm-demo-web:latest \ demo_web

該命令的另一個好處是,如果您在原始配置中指定它應該應用滾動更新,它將應用滾動更新。 即使您沒有這樣做,您也可以將標誌傳遞給更新,以指示它執行滾動更新,如下所示:

 docker service update \ --image lsapan/docker-swarm-demo-web:latest \ --update-parallelism 1 --update-delay 30s \ demo_web

這將一次更新一個容器,在兩次更新之間等待 30 秒。

向上或向下擴展服務

擁有兩個 Web 容器很棒,但您知道哪個更好嗎? 有十個! 在 swarm 中向上和向下擴展服務非常簡單:

 docker service scale demo_web=10

運行該命令並檢查docker service ps demo_web的輸出。 您會看到我們現在有十個容器,其中八個是剛剛啟動的。 如果你有興趣,你也可以回到 web 應用程序,刷新幾次頁面,看看你現在得到的不僅僅是原來的兩個容器 ID。

刪除堆棧和服務

您的堆棧和服務已部署和擴展,太棒了! 但是現在您想讓它們離線。 這可以通過相應的rm命令來完成。 要刪除我們的演示堆棧,請運行以下命令:

 docker stack rm demo

或者,如果您只想刪除一個服務,只需使用:

 docker service rm demo_web

排水節點

還記得我們之前運行docker node ls來檢查 swarm 中的節點嗎? 它提供了關於每個節點的大量信息,包括其可用性。 默認情況下,節點是Active ,這意味著它們是運行容器的公平遊戲。 但是,有時您可能需要使節點暫時脫機以執行維護。 當然,您可以將其關閉,然後群會恢復,但是給 Moby(Docker 鯨魚)一點通知是很好的。

這就是耗盡節點的用武之地。當您將節點標記為Drain時,Docker Swarm 會將其上運行的任何容器委託給其他節點,並且在您將其可用性更改回Active之前,它不會啟動該節點上的任何容器。

假設我們要耗盡node1 。 我們可以運行:

 docker node update --availability drain node1

簡單! 當您準備好讓它恢復工作時:

 docker node update --availability active node1

包起來

正如我們所見,Docker 與 Swarm 模式相結合,使我們能夠比以往更高效、更可靠地部署應用程序。 值得一提的是,Docker Swarm 絕不是唯一的容器編排引擎。 事實上,它是年輕人中的一員。 Kubernetes 存在的時間更長,而且肯定會在更多的生產應用程序中使用。 也就是說,Swarm 是 Docker 官方開發的,他們每天都在努力添加更多的功能。 無論您選擇使用哪個,請保持容器化!