實戰Docker容器排程

賜我白日夢發表於2020-09-23

一、前言

為什麼還學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需要有這三個基礎的步驟:

  1. 使用Dockerfile定義你的應用環境。

  2. 編寫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: {}
    
  3. 執行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

image-20200921101140749

# docker-compse是一個二進位制可執行的檔案,所以給他可執行許可權
sudo chmod +x /usr/local/bin/docker-compose

image-20200921102823935

2.3、小實驗

在這個小實驗中,我們將使用Docker Compose安裝一個簡單的Java Web應用。

Java Web使用SpringBoot極速構建,提供一個Restful方法如下:

每次呼叫它都將redis裡面的key=money的值+1後返回~

image-20200923105920940

編寫Dockerfile,這個dockerfile可以讓我們將應用都jar包打包成 docker image

image-20200921193731596

編寫docker compose。這個描述檔案就是在告訴docker-compose按照什麼都規則去啟動構建容器。

image-20200921193916657

將 Dockerfile、docker-compose.yml、jar包都上傳到伺服器上~

image-20200921133820428

為什麼這裡寫服務名,而不寫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哦,而是一個服務名~

image-20200921213615484

構建啟動:應用服務

這其實是一個比較激動人心的事情。可以想一下,原來學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命令是否構建起了我們的服務

image-20200921205042128

停止服務:

  • 在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建立映象

image-20200921212557808

檢視當前正在執行的容器(通過 docker-compose up 為我們自動執行起來的容器):

正在執行的容器的命名規則:目錄名_映象名_副本數

image-20200921212631175

docker-compose up 命令還為我們建立了一個叫做 composetest_default的網路,整個專案中的容器都加入到這個網路中,這個網路支援我們使用 服務名訪問到容器。是實現負載均衡的前提。

image-20200921212853875

檢視網路的詳情:下面的兩個容器在同一個網路下,

image-20200921160226483

在同一個網路下的容器,彼此是可以通過對方的服務名訪問到對方,如下:

image-20200921213508294

2.5、Compose file的編寫規則

參考:https://docs.docker.com/compose/compose-file/

image-20200921164958769

三、Docker Swarm

3.1、簡介

Docker Engine 1.12誕生了Swarm。Docker的Swarm讓我們可以通過一個或者多個Docker Engine組建起一個叢集,沒錯Swarm就是Docker公司退出的叢集編排工具。

使用docker swarm構建起的叢集架構如下圖:

image-20200922185443374

主要存在兩種角色: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、環境搭建

初始環境

image-20200922194031708

檢視docker swarm 命令:

image-20200922194338177

初始化一個Docker Swarm 叢集

image-20200922194826403

通過 --advertise-addr 指定的ip可以是公網ip,也可以是私網ip。

image-20200922195147955

如何將一個普通Node加入到一個叢集中?

然後當我們成功初始化一個叢集時,他會提示我們一條docker swarm join --token命令,通過這個命令我們可以讓一個Node加入到以當前節點為Manager的去群中。

也可以該命令生成加入令牌:docker swarm join-token worker

image-20200922200327887

如何檢視當前叢集各個節點的狀態?

在manager節點執行如下命令:

image-20200922200555545

在普通節點執行 docker node ls 會報錯

image-20200922200642771

如何將一個Manager Node加入到一個叢集中?

使用該命令獲取加入令牌:docker swarm join-token manager

該命令只能從manager節點使用。

image-20200922201346292

建立swarm叢集后,swarm為我們自動建立了新的網路~

image-20200922195602919

3.4、Raft一致性協議

image-20200922185443374

manager節點組成的叢集使用了Raft演算法,要求叢集中多數以上的節點存活,叢集才可以使用。他的本意是想讓叢集中存在3臺及以上的manager,這樣即使有manager掛了,整個叢集依然是不影響使用的。

因此:如果你只有一個mananger節點,那叢集就是不可用,你也不能通過manager下發任何任務。如果你有三臺manager,即使掛了一個manager,因為還有半數以上的manager存在,叢集依然可用。如果你說我的叢集中就有兩個manager,可不可用呢?答案是:可用,這種情況和叢集中有三個manager,然後掛了一個manager一樣,但是沒意義。為啥呢?因為他根本沒有任何容錯性。再掛一個manager,叢集就不可用了。

下一篇筆記,就好好研究一下Reft協議

3.5、彈性擴容、縮容

建立服務的命令:

image-20200922213936596

通過docker service啟動一個服務

只有在manager節點上才能使用這個命令。

docker run 和這個docker service挺相似的,但是通過docker service啟動容器具有擴容、縮容的能力

image-20200922214537522

檢視啟動的容器

image-20200922223135676

思考:

我們在叢集中的manager節點通過上面的命令啟動一個服務。這其實就是Docker中容器的排程,或者也能說它是簡單的編排。docker service create 命令本意上是在叢集中啟動一個nginx服務。這個nginx服務也就是一個docker容器,這個docker容器,會被manager隨機在四個Node中的某一個Node上啟動起來!

具體是哪個Node? 可以登陸上這幾個伺服器,通過 docker ps 查

動態擴容:

如下,增加我們的nginx的容器的副本數

對於WebServer來無論它訪問哪臺伺服器的ip都能訪問到nginx,而且無論這臺伺服器上的Docker中是否執行中nginx服務。docker swarm對內部的容器執行做了一層屏障,讓很多服務以一個整體的形式存在。

image-20200922223504385

理解:服務的概念

這裡只是在表象上理解一下服務的概念~不涉及底層實現,但是有助於捋清思路。

我們在上面通過 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叢集中有多少個服務副本。

image-20200923100004898

動態縮容:

動態縮容,就是通過 --replicas 指定一個比原來副本數小的數量即可

image-20200923101029782

動態擴縮容的另一個命令:

image-20200923101612381

移除服務:

服務被移除後,所以的服務副本都會消失,所有的相關容器也會被關閉

image-20200923101857288

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的數量,還為使用者提供了 閘道器、監控、備份、水平擴充套件、滾動更新、保持指定數量的副本數、負載均衡等功能。

相關文章