公眾號首發、歡迎關注
一、前言
為什么還學Docker的容器編排?
kubernetes幾年前就是容器編排的龍頭老大了,感覺上想學容器編排,是不是可以直接去學學k8s了呢?
其實我是學了一陣k8s之后折回頭實踐使用一下Docker容器編排的,因為在學k8s的過程中難免總是和Docker的容器編排做對比。所以不學學Docker Swarm,怎么知道K8S才是最好用、最強大的容器編排工具呢?
所以整理筆記 記錄實戰Docker Compose和Docker Swarm
二、Docker Compose
2.1、簡介
Docker Compose 是Docker提供的定義、運行多個Docker應用容器的工具,我們通過Docker Compose定義配置好應用服務后,通過一條簡單的命令就能根據這個配置創建出配置中描述的容器。
Docker Compose可以運行在生產環境、測試環境和開發環境中。
使用Docker Compose需要有這三個基礎的步驟:
-
使用Dockerfile定義你的應用環境。
-
編寫docker-compose.yml 定義你的服務,目的是為了讓Dockerfile定義的容器可以一起運行。
version: '2.0' services: # 描述我們的應用 web: build: . ports: - "5000:5000" volumes: - .:/code - logvolume01:/var/log links: # 在啟動前先啟動redis - redis redis: image: redis volumes: # 持久化相關掛載卷 logvolume01: {}
-
執行
docker-compose up
命令,Compose就會開始運行你描述的整個app。
參考:官方文檔:https://docs.docker.com/compose/
2.2、下載安裝
# 將docker-compose下載安裝到 /usr/local/bin 目錄下
curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# docker-compse是一個二進制可執行的文件,所以給他可執行權限
sudo chmod +x /usr/local/bin/docker-compose
2.3、小實驗
在這個小實驗中,我們將使用Docker Compose安裝一個簡單的Java Web應用。
Java Web使用SpringBoot極速構建,提供一個Restful方法如下:
每次調用它都將redis里面的key=money的值+1后返回~
編寫Dockerfile,這個dockerfile可以讓我們將應用都jar包打包成 docker image
編寫docker compose。這個描述文件就是在告訴docker-compose按照什么都規則去啟動構建容器。
將 Dockerfile、docker-compose.yml、jar包都上傳到服務器上~
為什么這里寫服務名,而不寫ip?
因為docker容器啟動后會隨機分配給它ip,舉個例子:比如你有兩個鏡像A、B,那先如果先啟動A,那他的ip可能是:172.16.0.2 。再啟動B他的ip就可能是 172.16.0.3。 反之:如果你先啟動的是容器B,那他的ip可能是172.16.0.3,而不是我們一開始說的172.16.0.2
所以這里寫服務名是一個明智的選擇,因為如果我們在這里固定寫一個ip的話,萬一哪天這個容器掛了,被重新拉起之后他的ip變了,那我們的web應用豈不是找不到這個redis了?所以用不會服務名替換ip。很明智。
容器啟動后,同屬一個網絡下(默認一般是橋接)的容器彼此能根據對方的name彼此ping通。
使用docker-compose時,他會為我們新創建一個虛擬網卡。app中的容器啟動后都會加入這個虛擬網絡中。
對這塊知識有疑問,可以看看這篇筆記:https://www.cnblogs.com/ZhuChangwu/p/13689736.html
web項目的配置文件如下圖:
redis的host並不是某一個ip哦,而是一個服務名~
構建啟動:應用服務
這其實是一個比較激動人心的事情。可以想一下,原來學docker的時候,我們直接從docker-hub上下載鏡像,然后: docker run 使用鏡像。
后來我們學着通過volume將容器中應用的配置文件掛載到宿主機實現簡單的定制化配置,然后docker run 啟動容器
再后來我們學習了Dockerfile,自定義鏡像,實現了將我們本地打包好的應用做成鏡像,然后docker run 啟動容器。
到現在,我們下載安裝好docker后,不用手動下載任何鏡像,只要編寫Dockerfile描述我們打包好后到程序應該做成什么鏡像,通過docker-compose.yml 描述組成app的各個容器(可以是Dockerfile描述的鏡像、本地已經存在的鏡像、或者遠程倉庫中的鏡像)之間有什么依賴關系。誰先啟動,誰后啟動。然后執行 docker-compose up命令,一鍵部署app。
# 一鍵部署
[root@VM-0-6-centos myServer]# docker-compose up
# docker-compose為我們創建了一叫做: myserver_default的網絡
Creating network "myserver_default" with the default driver
# 構建web模塊時,發現他depend_on redis,所以先拉去redis鏡像
Pulling redis (redis:)...
latest: Pulling from library/redis
d121f8d1c412: Pull complete
2f9874741855: Pull complete
d92da09ebfd4: Pull complete
bdfa64b72752: Pull complete
e748e6f663b9: Pull complete
eb1c8b66e2a1: Pull complete
Digest: sha256:1cfb205a988a9dae5f025c57b92e9643ec0e7ccff6e66bc639d8a5f95bba928c
Status: Downloaded newer image for redis:latest
# 開始構建docker-compose.yml中指定的web模塊
Building web
Step 1/5 : FROM java:8
8: Pulling from library/java
5040bd298390: Pull complete
fce5728aad85: Pull complete
76610ec20bf5: Pull complete
60170fec2151: Pull complete
e98f73de8f0d: Pull complete
11f7af24ed9c: Pull complete
49e2d6393f32: Pull complete
bb9cdec9c7f3: Pull complete
Digest: sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d
Status: Downloaded newer image for java:8
---> d23bdf5b1b1b
Step 2/5 : COPY *.jar /app.jar
---> 15421d081257
Step 3/5 : EXPOSE 8888
---> Running in 18f52319a82d
Removing intermediate container 18f52319a82d
---> 86cb853b5711
Step 4/5 : CMD ["--server.port=8888"]
---> Running in 147d797d5848
Removing intermediate container 147d797d5848
---> 323a85aa9c61
Step 5/5 : ENTRYPOINT ["java","-jar","/app.jar"]
---> Running in 6988142d65d4
Removing intermediate container 6988142d65d4
---> 245e7675226d
Successfully built 245e7675226d
Successfully tagged myserver_web:latest
WARNING: Image for service web was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
# 為我們的webapp成功創建了兩個容器
Creating myserver_redis_1 ... done
Creating myserver_web_1 ... done
Attaching to myserver_redis_1, myserver_web_1
# 先啟動redis
redis_1 | 1:C 21 Sep 2020 12:27:54.827 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
redis_1 | 1:C 21 Sep 2020 12:27:54.827 # Redis version=6.0.8, bits=64, commit=00000000, modified=0, pid=1, just started
redis_1 | 1:C 21 Sep 2020 12:27:54.827 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
redis_1 | 1:M 21 Sep 2020 12:27:54.828 * Running mode=standalone, port=6379.
redis_1 | 1:M 21 Sep 2020 12:27:54.828 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
redis_1 | 1:M 21 Sep 2020 12:27:54.828 # Server initialized
redis_1 | 1:M 21 Sep 2020 12:27:54.828 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
redis_1 | 1:M 21 Sep 2020 12:27:54.828 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never').
redis_1 | 1:M 21 Sep 2020 12:27:54.828 * Ready to accept connections
# 啟動SpringBoot
web_1 |
web_1 | . ____ _ __ _ _
web_1 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
web_1 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
web_1 | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
web_1 | ' |____| .__|_| |_|_| |_\__, | / / / /
web_1 | =========|_|==============|___/=/_/_/_/
web_1 | :: Spring Boot :: (v2.3.4.RELEASE)
web_1 |
web_1 | 2020-09-21 12:27:56.777 INFO 1 --- [ main] com.changwu.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT on b66d5414a322 with PID 1 (/app.jar started by root in /)
web_1 | 2020-09-21 12:27:56.780 INFO 1 --- [ main] com.changwu.DemoApplication : No active profile set, falling back to default
服務啟動后可以驗證一下 docker-compose up命令是否構建起了我們的服務
停止服務:
- 在yml文件所在目錄執行:
docker-compose stop
- CTRL + C
docker-compose up 運行起所有的容器后,我們得到的是一個project,注意這里的這個project依然是一個單機的應用,相對於拆分前來說,現在的project是一個容器化后的單機應用。 (Docker提供的集群化部署方案在下面的章節~)
官方Demo:https://docs.docker.com/compose/gettingstarted/
2.4、小實驗的細節
docker-compose up 成功執行后,可以看到他根據我們配置文件描述,為我們自動下載了redis鏡像、java鏡像、以及根據Dockerfile創建鏡像
查看當前正在運行的容器(通過 docker-compose up 為我們自動運行起來的容器):
正在運行的容器的命名規則:目錄名_鏡像名_副本數
docker-compose up 命令還為我們創建了一個叫做 composetest_default的網絡,整個項目中的容器都加入到這個網絡中,這個網絡支持我們使用 服務名訪問到容器。是實現負載均衡的前提。
查看網絡的詳情:下面的兩個容器在同一個網絡下,
在同一個網絡下的容器,彼此是可以通過對方的服務名訪問到對方,如下:
2.5、Compose file的編寫規則
參考:https://docs.docker.com/compose/compose-file/
三、Docker Swarm
3.1、簡介
Docker Engine 1.12誕生了Swarm。Docker的Swarm讓我們可以通過一個或者多個Docker Engine組建起一個集群,沒錯Swarm就是Docker公司推出的集群編排工具。
使用docker swarm構建起的集群架構如下圖:
主要存在兩種角色:Manager節點和Worker節點,Manager和Woker本質上也都是Docker容器。
什么是Node?
大家都在說一個集群由多個節點組成,這個Node究竟是什么呢?
其實可以把這個節點理解成下載有Docker軟件的服務器,通過Docker Swarm 會將我們啟動的容器運行在某個Node中的Docker里面。
然后我感覺也可以把Node直接理解成某個服務器上運行的Docker實例。意思是你可以在一個服務器上啟動多個Docker軟件。當然這其實就和服務容器化以及分布式部署追求的那種容錯性有出入~,畢竟都放在一個服務器上,萬一服務器掛了,所有Docker實例,所有容器也都掛了。
Manager:
Manager主要掌控集群的管理任務
- 維持集群的狀態
- 服務的調度(所謂調度就是通過一定的算法,讓容器在合適的Node上啟動起來)
manager之間彼此通信,我們針對整個集群的操作都要通過manager節點下發。
Worker:
worker節點是swarm集群中普通節點,他們會被Docker Swarm調度Woker節點上面運行起用戶指定的容器。
參考:https://docs.docker.com/engine/swarm/
3.2、注意點
我們使用Docker Swarm做了什么?
不要忽略一件事,使用docker swarm 為我們提供的命令,歸根結底是為了搭建起一個swarm 集群,往這個集群中添加Node,從這個集群中砍掉Node。
搭建起集群之后下一步才是使用集群,使用集群使用的是另一套命令: docker service
3.3、環境搭建
初始環境
查看docker swarm 命令:
初始化一個Docker Swarm 集群
通過 --advertise-addr 指定的ip可以是公網ip,也可以是私網ip。
如何將一個普通Node加入到一個集群中?
然后當我們成功初始化一個集群時,他會提示我們一條docker swarm join --token命令,通過這個命令我們可以讓一個Node加入到以當前節點為Manager的去群中。
也可以該命令生成加入令牌:docker swarm join-token worker
如何查看當前集群各個節點的狀態?
在manager節點執行如下命令:
在普通節點執行 docker node ls 會報錯
如何將一個Manager Node加入到一個集群中?
使用該命令獲取加入令牌:docker swarm join-token manager
該命令只能從manager節點使用。
創建swarm集群后,swarm為我們自動創建了新的網絡~
3.4、Raft一致性協議
manager節點組成的集群使用了Raft算法,要求集群中多數以上的節點存活,集群才可以使用。他的本意是想讓集群中存在3台及以上的manager,這樣即使有manager掛了,整個集群依然是不影響使用的。
因此:如果你只有一個mananger節點,那集群就是不可用,你也不能通過manager下發任何任務。如果你有三台manager,即使掛了一個manager,因為還有半數以上的manager存在,集群依然可用。如果你說我的集群中就有兩個manager,可不可用呢?答案是:可用,這種情況和集群中有三個manager,然后掛了一個manager一樣,但是沒意義。為啥呢?因為他根本沒有任何容錯性。再掛一個manager,集群就不可用了。
下一篇筆記,就好好研究一下Reft協議
3.5、彈性擴容、縮容
創建服務的命令:
通過docker service啟動一個服務
只有在manager節點上才能使用這個命令。
docker run
和這個docker service
挺相似的,但是通過docker service
啟動容器具有擴容、縮容的能力
查看啟動的容器
思考:
我們在集群中的manager節點通過上面的命令啟動一個服務。這其實就是Docker中容器的調度,或者也能說它是簡單的編排。docker service create 命令本意上是在集群中啟動一個nginx服務。這個nginx服務也就是一個docker容器,這個docker容器,會被manager隨機在四個Node中的某一個Node上啟動起來!
具體是哪個Node? 可以登陸上這幾個服務器,通過 docker ps 查
動態擴容:
如下,增加我們的nginx的容器的副本數
對於WebServer來無論它訪問哪台服務器的ip都能訪問到nginx,而且無論這台服務器上的Docker中是否運行中nginx服務。docker swarm對內部的容器運行做了一層屏障,讓很多服務以一個整體的形式存在。
理解:服務的概念
這里只是在表象上理解一下服務的概念~不涉及底層實現,但是有助於捋清思路。
我們在上面通過 docker service 命令啟動一個服務,什么服務呢,通過--name指定一個叫做 mynginx的服務 ,服務中運行的鏡像為nginx,如果本地沒有這個鏡像,他會去遠程拉取。服務啟動后,docker swarm會在集群中隨機找一個合適的Node運行redis容器,這個Redis容器屬於mynginx服務。
后來我們又用 docker service update 增加服務的副本數,就比如我們上面將服務的副本數增加到3,docker swarm就會讓整個集群中再找合適的Node,然后啟動新的service。
如下圖,WebServer去連接Redis服務,這個Redis服務實際上就是存在於由Docker Swarm搭建起的集群中的Redis服務,並且它可能有多個副本。
對於WebServer來說,他不知道DockerSwarm集群中有多少服務的副本,對他來說,它只是在連接一個Redis實例,而不知道它連接的實際上是一個多分片集群。
Docker Swarm集群為WebServer提供Redis服務使用,WebServer可以認為Swarm集群是一個實例,然后WebServer只需要使用Swarm集群為他提供的Redis服務,而不關心Swarm集群中有多少個服務副本。
動態縮容:
動態縮容,就是通過 --replicas 指定一個比原來副本數小的數量即可
動態擴縮容的另一個命令:
移除服務:
服務被移除后,所以的服務副本都會消失,所有的相關容器也會被關閉
3.6、使用的感受
感覺上 Docker Swarm的調度設計是容器為核心的。
為啥這樣說呢?
從上面的使用上來看,首先是拆解App,於是我們將應用拆分成不同容器。那不同容器之間是需要通過網絡通信的,相應的DockerSwarm的實現是:Docker Swarm集群會構建一個igress網絡(它是一個擁有負載均衡能力的overlay網絡)所有的Node都加入到這個扁平化網絡中,實現了網絡中的各個Node直接通過對方的服務名就能ping通互聯。
有了這樣的基礎后,我們就能使用Docker Swarm提供的擴容、所容的調度能力
當我們需要Docker Swarm為我們擴容時,它會根據自己的調度算法去找一個合適的Node然后去一個一個的運行起我的容器,也就是說它的思路是:為容器找一個合適的節點運行起來
spread: 默認策略,盡量均勻分布,找容器數少的結點調度
binpack: 和spread相反,盡量把一個結點占滿再用其他結點
random: 隨機
這種調度思想和k8s的編排思想是不一樣的。
當然說起k8s難免會冒出海量的新概念,這里就不展開了。
簡單來說:kubernates的編排思想是這樣的,它根本不關系底層的容器是Docker還是其他的容器技術,它站在更高的角度上,允許讓用戶以yaml的方式去描述自己的應用,去描述哪幾個鏡像啟動后應在綁定在一起(在一個Pod里,而不是像Docker Swarm那樣,由Docker Swarm去為單個容器找一個合適的Node),允許用戶指定Pod的數量,還為用戶提供了 網關、監控、備份、水平擴展、滾動更新、保持指定數量的副本數、負載均衡等功能。