持續部署編排的另類選擇:使用Node-RED進行容器化部署

在這裏插入圖片描述
Node-RED是在2013年IBM開源的應用於物聯網的流編排引擎,但是也不僅限於物聯網,這篇文章選取容器化應用持續交付的一個示例來進行說明Node-RED的使用方式。

場景說明

持續集成執行完畢之後,容器化的應用已經存儲在Harbor鏡像私庫中,持續部署的時候需要拉取鏡像、啓動容器,這本來是非常簡單的事情,但是需要考慮到主要是各種異常的分支的對應方法,比如:

  • 拉取鏡像:鏡像私庫連接失敗和成功的處理
  • 啓動容器:確認是否存在容器,如果不存在使用docker run生成,如果存在,首先停止此容器,然後刪除,停止失敗和刪除失敗都需要進行異常處理,停止並刪除成功之後啓動容器
  • 結果確認: 確認容器是否啓動,根據結果輸出成功或失敗信息

持續部署的邏輯流程

普通邏輯下,這個示例流程可能是這樣的:

Created with Raphaël 2.2.0開始拉取鏡像鏡像拉取成功?容器已經存在?容器已經停止?刪除容器容器已刪除?啓動容器容器成功啓動?顯示容器啓動成功信息顯示容器啓動失敗信息顯示容器刪除失敗信息停止運行中的容器容器成功停止?顯示容器停止失敗信息顯示鏡像拉取失敗信息yesnoyesnoyesnoyesnoyesnoyesno

當然,在實際的使用中有很多種方法會將上述這個流程變得非常簡化,比如簡化這個過程,如果不需要對中間結果進行確認的話,可以將所有的條件全部去除,直接上來先強制停止->強制刪除之前的容器和鏡像->強制拉取->強制啓動,或者使用kubernetes提供的滾動升級的機制都會非常簡單,但是這裏主要是用於檢驗持續部署引擎中流水線可視化編排的能力,是否能夠很輕鬆和簡單地實現這個流程是需要確認的。

Jenkins vs Node-RED

實際上這並不是可以進行比較的兩個東西,但是僅限於本文中示例說明的這個場景,似乎還真有可對比的地方。Jenkins提供可以DSL語言支持的流水線,代碼及流水線,如果把流水線也看作“流”的表現形式和結果,在這個角度上是有類似的地方的,在本文指定的場景中實際上只有一個流編排引擎的能力:條件分支。

Jenkins的編排能力

Jenkins的DSL裏面支持基於Stage的條件分支和並行的編排需求,但是可視化編排引擎能力稍差,比如BlueOcean插件可以在很簡單地程度上進行可視化編排,但是目前還達不到易用的程度,但是毫無疑問,條件分支的支持在Jenkins中沒有任何問題,比如下圖爲使用Jenkins中的when語句進行條件分支的示例流水線的執行情況:
在這裏插入圖片描述
注:詳細可參看https://blog.csdn.net/liumiaocn/article/details/92817179
更多Jenkins相關的使用技巧:點擊此處

Node-RED的編排能力

條件分支

更多Node-RED相關的使用技巧:點擊此處

使用Node-RED進行編排

與Docker的結合

可以使用Node-RED的插件或者直接拷貝docker客戶端至Node-RED容器中即可對容器進行操作,這裏使用後一種方式,詳細的方法可參看下文:

環境準備

  • Node-RED
    以容器方式啓動Node-RED服務,啓動命令如下所示:

啓動命令:docker run -it -p 1880:1880 -v $PWD/data:/data -e DOCKER_HOST=tcp://192.168.31.242:2375 -e TZ=Asia/Shanghai --name nodered -d nodered/node-red:1.0.4

注:DOCKER_HOST環境變量的設定請根據自己機器的配置進行設定

示例實現

參照前面一些Node-RED系列的文章可以非常容易的實現如下的可視化編排, 拉取鏡像也直接拉取一個nginx的版本,這裏已1.13爲例進行拉取和啓動:
在這裏插入圖片描述

這裏爲了簡單演示,直接在命令行中硬編碼的方式實現容器名稱和映射端口號,啓動容器的設定如下所示:
在這裏插入圖片描述

JSON格式的flow

如下JSON格式的flow,導入之後即可即可使用。

[{"id":"d9908e57.2efd4","type":"inject","z":"c205d2ff.b4322","name":"觸發器","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":60,"wires":[["4af508a3.75bed8"]]},{"id":"4af508a3.75bed8","type":"exec","z":"c205d2ff.b4322","command":"docker pull nginx:1.13","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"拉取鏡像","x":340,"y":60,"wires":[[],[],["8e27643a.f72f68"]]},{"id":"8e27643a.f72f68","type":"switch","z":"c205d2ff.b4322","name":"拉取鏡像成功?","property":"payload.code","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"neq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":540,"y":60,"wires":[["fbc7e47e.300d68","3f88a226.a0e27e"],["d6bf6546.e90028"]]},{"id":"fbc7e47e.300d68","type":"debug","z":"c205d2ff.b4322","name":"拉取鏡像成功結果顯示","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":760,"y":20,"wires":[]},{"id":"d6bf6546.e90028","type":"debug","z":"c205d2ff.b4322","name":"拉取鏡像失敗結果顯示","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":760,"y":100,"wires":[]},{"id":"3f88a226.a0e27e","type":"exec","z":"c205d2ff.b4322","command":"docker ps -a |grep  nginx","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"容器存在性確認","x":160,"y":220,"wires":[[],[],["f04db033.03b9d"]]},{"id":"f04db033.03b9d","type":"switch","z":"c205d2ff.b4322","name":"容器已經存在?","property":"payload.code","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"neq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":360,"y":220,"wires":[["41de4ca4.2bc304"],["370847f2.635c28","de314625.b32708"]]},{"id":"370847f2.635c28","type":"exec","z":"c205d2ff.b4322","command":"docker run -p 8088:80 --name=nginx -d nginx:1.13","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"啓動容器","x":600,"y":200,"wires":[[],[],["fc7c7676.7747b8"]]},{"id":"fc7c7676.7747b8","type":"switch","z":"c205d2ff.b4322","name":"結果判斷","property":"payload.code","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"neq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":800,"y":200,"wires":[["e7f38a59.27c928"],["594e0886.2aa498"]]},{"id":"e7f38a59.27c928","type":"debug","z":"c205d2ff.b4322","name":"顯示容器正常啓動信息","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1000,"y":180,"wires":[]},{"id":"594e0886.2aa498","type":"debug","z":"c205d2ff.b4322","name":"顯示容器啓動失敗信息","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1000,"y":220,"wires":[]},{"id":"de314625.b32708","type":"debug","z":"c205d2ff.b4322","name":"容器不存在","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":610,"y":280,"wires":[]},{"id":"f4e42833.ee4448","type":"exec","z":"c205d2ff.b4322","command":"docker stop nginx;","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"停止容器","x":160,"y":540,"wires":[[],[],["3e4a962a.01903a"]]},{"id":"3e4a962a.01903a","type":"switch","z":"c205d2ff.b4322","name":"容器停止成功?","property":"payload.code","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"neq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":340,"y":540,"wires":[["e5baa05e.433b9","84ae2749.94ee28"],["f5d2345c.4f0d98"]]},{"id":"e5baa05e.433b9","type":"debug","z":"c205d2ff.b4322","name":"顯示容器停止成功信息","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":580,"y":500,"wires":[]},{"id":"f5d2345c.4f0d98","type":"debug","z":"c205d2ff.b4322","name":"顯示容器停止失敗信息","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":580,"y":580,"wires":[]},{"id":"41de4ca4.2bc304","type":"exec","z":"c205d2ff.b4322","command":"docker ps |grep  nginx","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"容器停止狀態確認","x":170,"y":380,"wires":[[],[],["827bee6d.6d1d1"]]},{"id":"827bee6d.6d1d1","type":"switch","z":"c205d2ff.b4322","name":"容器已停止?","property":"payload.code","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"neq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":360,"y":380,"wires":[["db3dca72.6a5d98","f4e42833.ee4448"],["370847f2.635c28","eb12796d.538858"]]},{"id":"db3dca72.6a5d98","type":"debug","z":"c205d2ff.b4322","name":"存在啓動的容器需要停止","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":630,"y":340,"wires":[]},{"id":"eb12796d.538858","type":"debug","z":"c205d2ff.b4322","name":"沒有啓動的容器需要停止","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":630,"y":420,"wires":[]},{"id":"84ae2749.94ee28","type":"exec","z":"c205d2ff.b4322","command":"docker rm nginx;","addpay":false,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"刪除容器","x":140,"y":720,"wires":[[],[],["8515b044.05477"]]},{"id":"8515b044.05477","type":"switch","z":"c205d2ff.b4322","name":"容器刪除成功?","property":"payload.code","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"neq","v":"0","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":320,"y":720,"wires":[["61927cc0.f243e4","370847f2.635c28"],["9b26c2ff.a7d18"]]},{"id":"61927cc0.f243e4","type":"debug","z":"c205d2ff.b4322","name":"顯示容器刪除成功信息","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":560,"y":680,"wires":[]},{"id":"9b26c2ff.a7d18","type":"debug","z":"c205d2ff.b4322","name":"顯示容器刪除失敗信息","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":560,"y":760,"wires":[]}]

結果確認

  • 執行前確認
liumiaocn:~ liumiao$ docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                    PORTS                    NAMES
e290de2ccd69        nodered/node-red:1.0.4   "npm start -- --user…"   11 minutes ago      Up 11 minutes (healthy)   0.0.0.0:1880->1880/tcp   nodered
liumiaocn:~ liumiao$
  • 第一次執行
    在這裏插入圖片描述
    說明:這裏出現了一個很顯眼的錯誤信息,這是因爲使用dokcer ps |grep nginx確認是否有nginx爲名稱的容器在運行中的命令執行的時候沒有發現這樣的容器所產生的,所以這並不是一個問題,exec組件會根據返回值進行狀態顯示而已,結合docker ps命令在宿主機器上可以看到名爲nginx的容器已經啓動起來了
liumiaocn:~ liumiao$ docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                    PORTS                    NAMES
4728e76addcf        nginx:1.13               "nginx -g 'daemon of…"   11 seconds ago      Up 10 seconds             0.0.0.0:8088->80/tcp     nginx
e290de2ccd69        nodered/node-red:1.0.4   "npm start -- --user…"   44 minutes ago      Up 44 minutes (healthy)   0.0.0.0:1880->1880/tcp   nodered
liumiaocn:~ liumiao$ 
  • 第二次執行
    在這裏插入圖片描述
    結合docker ps命令在宿主機器上可以看到nginx容器是啓動狀態,但是Container ID已經發生了變化,說明原有的的nginx容器已經被停止和刪除,然後重新啓動的nginx容器。
liumiaocn:~ liumiao$ docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS                    PORTS                    NAMES
122c01aa206d        nginx:1.13               "nginx -g 'daemon of…"   22 seconds ago      Up 20 seconds             0.0.0.0:8088->80/tcp     nginx
e290de2ccd69        nodered/node-red:1.0.4   "npm start -- --user…"   46 minutes ago      Up 46 minutes (healthy)   0.0.0.0:1880->1880/tcp   nodered
liumiaocn:~ liumiao$ 

總結

這篇文章使用了一個簡單地示例對Node-RED的編排進行了說明,當然在使用的過程中還有很多需要注意的,比如參數的傳入方法,比如異常的處理機制,但是相信這樣一個簡單的示例就能夠說明流編排引擎在持續集成和部署中的作用,鑑於Jenkins目前的可視化編排功能較弱,所以很多平臺都是基於此進行強化,但是後續的CDF時候能夠提供一種標準的DSL的編排標準或者接入現在主流的工具還需要進一步地觀察,這些都是我們在持續集成和部署實踐中需要注意的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章