1. 程式人生 > 實用技巧 >Docker基礎入門

Docker基礎入門

一、安裝

環境:Centos7

1、解除安裝舊版本

較舊的Docker版本稱為dockerdocker-engine。如果已安裝這些程式,請解除安裝它們以及相關的依賴項。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2、使用儲存庫安裝

注意:官方給了三種安裝方式,這裡我們選擇最常用的儲存庫安裝

在新主機上首次安裝Docker Engine之前,需要設定Docker儲存庫。之後可以從儲存庫安裝和更新Docker。

sudo yum install -y yum-utils

# 注意:此處是大坑,可以自行換成阿里的源
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    
# 我們用阿里的    
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
 
# 進行安裝(如果不換源,速度會很慢)
sudo yum install docker-ce docker-ce-cli containerd.io

3、啟動以及驗證

systemctl start docker   # 啟動
systemctl status docker  #  檢視狀態
docker version  # 檢視版本
docker run hello-world   #  測試

4、設定加速器(阿里雲)

注意:裡面的地址每個人都有專屬的地址。

開啟阿里雲官網->控制檯->搜尋容器映象服務->右下角找到映象加速器

sudo mkdir -p /etc/docker

tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

docker run hello-world

5、解除安裝

# 解除安裝Docker Engine,CLI和Containerd軟體包
sudo yum remove docker-ce docker-ce-cli containerd.io

# 主機上的映像,容器,卷或自定義配置檔案不會自動刪除。要刪除所有影象,容器和卷
sudo rm -rf /var/lib/docker

二、Docker三要素

1、倉庫(Repository)

倉庫:集中存放映象的場所。

注意:倉庫(Repository)和倉庫註冊伺服器(Registry)是有區別的,倉庫註冊伺服器往往存放著多個倉庫,每個倉庫中又包含多個映象,每個映象有不同的標籤(tag)

倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。

最大的公開倉庫是Docker Hub(https://hub.docker.com),存放了數量龐大的映象供使用者下載,國內的公開倉庫包括阿里雲、網易雲等。

2、映象(Image)

一個只讀模板,用來建立Docker容器,一個映象可以建立很多個容器。

容器與映象的關係類似於面向物件程式設計中的物件和類

Docker 面向物件
容器 物件
映象

3、容器 (Container)

獨立執行的一個或一組應用。

容器使用映象建立的執行例項。

它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的,保證安全的平臺。

可以把容器看作是一個簡易版的Linux環境(包括root使用者許可權,程序空間,使用者空間和網路空間等等)和執行在啟動的應用程式。

容器的定義和映象幾乎一模一樣,也是一堆層的統一視角,唯一區別在於容器的最上面那一層是可讀可寫的。

三、簡單瞭解底層原理

1、Docker是怎麼工作的

Docker是一個Client-Server結構的系統,Docker守護程序執行在主機上,然後通過Socket連線從客戶端訪問,守護程序從客戶端接收命令並管理執行在主機上的容器。

2、Docker為什麼比VM快?

(1)Docker有著比虛擬機器更少的抽象層,由於Docker不需要Hypervisor實現硬體資源虛擬化,執行在Docker容器上的程式直接使用的都是實際物理機的硬體資源,因此在CPU、記憶體利用率上Docker將會在效率上有明顯的優勢。

(2)Docker利用的是宿主機的核心,而不需要Guest OS。因此當新建一個容器時,Docker不需要和虛擬機器一樣重疊載入一個作業系統核心,從而避免引尋、載入作業系統核心,返回比較費時費資源的過程。當新建一個虛擬機器時,虛擬機器軟體需要載入Guest OS,這個新建過程是分鐘級別的,而Docker由於直接利用宿主機的作業系統,因此新建一個Docker容器只需幾秒鐘。

Docker容器 虛擬機器(VM)
作業系統 與宿主機共享OS 宿主機OS上執行虛擬機器OS
儲存大小 映象小,便於儲存與傳輸 映象龐大(vmdk,vdi等)
執行效能 幾乎無額外效能損失 作業系統額外的CPU、記憶體消耗
移植性 輕便、靈活,適應於Linux 笨重,與虛擬化技術耦合度高
硬體親和性 面向軟體開發者 面向硬體運維者

四、相關命令

1、幫助命令

docker version  # 檢視docker版本資訊
docker info  # 詳細說明
docker --help  # 幫助命令

2、映象命令

(1)列出本地映象
docker images [OPTIONS] [REPOSITORY[:TAG]]

# OPTIONS說明:
	-a: 列出本地所有的映象(含中間映像層)
    -q: 只顯示映象ID
    --digests: 顯示映象的摘要資訊
    --no-trunc: 顯示完整的映象資訊

# 各個選項說明:
# REPOSITORY:表示映象的倉庫源
# TAG:映象的標籤
# IMAGE ID:映象ID
# CREATED:映象建立時間
# SIZE:映象大小	

同一倉庫源可以有多個TAG,代表這個倉庫源的不同版本,我們使用REPOSITORY:TAG來定義不同的映象,如果不指定一個映象的版本標籤,例如我們只使用ubuntu,docker將預設使用ubuntu:latest映象。

(2)查詢映象
docker search [options] 某個xxx映象名字  # 會在https://hub.docker.com上去查詢

docker search mysql --filter=STARS=3000  # 搜尋星數大於等於3000的映象

# OPTIONS說明:
# --no-trunc: 顯示完整的映象描述	
# -s:列出點贊數不小於指定值的映象
# --automated: 只列出automated build型別的映象
(3)獲取映象
docker pull 映象名字[:TAG]  # 如果不寫TAG,則預設獲取latest,此時從我們配置的阿里雲上獲取映象

docker pull mysql  # 獲取最新版本的mysql
Using default tag: latest
latest: Pulling from library/mysql
852e50cd189d: Pull complete   # 分卷下載
29969ddb0ffb: Pull complete 
a43f41a44c48: Pull complete 
5cdd802543a3: Pull complete 
b79b040de953: Pull complete 
938c64119969: Pull complete 
7689ec51a0d9: Pull complete 
a880ba7c411f: Pull complete 
984f656ec6ca: Pull complete 
9f497bce458a: Pull complete 
b9940f97694b: Pull complete 
2f069358dc96: Pull complete 
Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest  
# docker pull mysql 等價於docker pull docker.io/library/mysql:latest 

docker pull mysql:5.7  # 下載指定版本的映象
5.7: Pulling from library/mysql
852e50cd189d: Already exists  # 聯合檔案系統,已經存在的不會去重複下載
29969ddb0ffb: Already exists 
a43f41a44c48: Already exists 
5cdd802543a3: Already exists 
b79b040de953: Already exists 
938c64119969: Already exists 
7689ec51a0d9: Already exists 
36bd6224d58f: Pull complete 
cab9d3fa4c8c: Pull complete 
1b741e1c47de: Pull complete 
aac9d11987ac: Pull complete 
Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
(4)刪除映象
# 刪除單個映象
docker rmi -f 映象名/ID  # 如果是映象名,後面不帶TAG,則預設刪除latest

# 刪除多個映象
docker rmi -f 映象名1 映象名2 ...
docker rmi -f id1 id2 ...     // 注意:兩種方式不能混用

# 刪除全部映象
docker rmi -f $(docker images -aq)   

3、容器命令

​ 有映象才能建立容器,這是根本前提(下載一個Centos映象演示)

(1) 新建並啟動容器(互動式)
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run -it --name mycentos centos  // 如果不指定別名,系統會自動分配

# OPTIONS說明:
# --name 容器新名字:為容器指定一個名稱
# -d:後臺執行容器,並返回容器ID,即啟動守護式容器
# -i: 以互動模式執行容器,通常與-t同時使用
# -t: 為容器重新分配一個偽輸入終端,通常與-i同時使用
# -P: 隨機埠對映
# -p: 指定埠對映有以下四種格式
		ip:hostPort:containerPort
		ip::containerPort
		hostPort:containerPort
		containerPort

# 測試
[root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash  # 啟動並進入容器
[root@783cb2f26230 /]# ls   # 在容器內檢視
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@783cb2f26230 /]# exit   # 退出容器
exit
[root@iz2zeaj5c9isqt1zj9elpbz ~]# 

(2)列出當前所有正在執行的容器
docker ps [OPTIONS]  # 不帶OPTIONS,則只列出正在執行的容器

# OPTIONS說明(常用):
# -a:列出當前所有正在執行的容器以及歷史上執行過的
# -l:顯示最近建立的容器
# -n: 顯示最近n個建立的容器
# -q: 靜默模式,只顯示容器編號
# --no-trunc:不截斷輸出
(3)退出容器
exit  // 直接關閉並退出容器

重新開啟一個終端,執行docker ps -l,會返回剛才我們建立的容器資訊,並且STATUS會提示已經退出。

那麼可不可以在互動式,不關閉容器的情況下,暫時退出,一會又可以回來呢?

Ctrl + P + Q

# 執行後,我們將退出容器,回到宿主機,使用docker ps -l,我們會發現這個剛才退出的容器STATUS是Up狀態。
(4)啟動容器
docker start [OPTIONS] CONTAINER [CONTAINER...]

# 同時可以啟動多個容器,容器名和ID可以混用

# OPTION說明(常用):
# -i : 進入互動式,此時只能進入一個容器

進入上面我們退出但是依然存活的容器

docker start -i 186ae928f07c
(5)重啟容器
docker restart [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死容器之前等待停止的時間(預設為10)
(6) 停止容器
docker stop [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死前等待停止的時間(預設為10)

docker kill [OPTIONS] CONTAINER [CONTAINER...]  // 強制關閉(相當於拔電源)

# OPTIONS說明:
# -s: 傳送到容器的訊號(預設為“KILL”)
(7)刪除容器
docker rm [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -f :強制刪除,不管是否在執行

# 上面的命令可以刪除一個或者多個,但是想全部刪除所有的容器,那麼我們要把全部的容器名或者ID都寫一遍嗎?

docker rm -f $(docker ps -aq)  # 刪除所有
docker ps -aq | xargs docker rm -f
(8) 啟動守護式容器
docker run -d 容器名/容器ID

說明:我們使用docker ps -a命令檢視,會發現剛才啟動的容器以及退出了,這是為什麼呢?

很重要的要說明一點:Docker容器後臺執行,必須要有一個前臺程序,容器執行的命令如果不是那些一直掛起的命令(比如執行top,tail),就會自動退出。

這就是Docker的機制問題,比如我們現在執行WEB容器,以Nginx為例,正常情況下,我們配置啟動服務只需要啟動響應的service即可,例如service nginx start,但是這樣做,Nginx為後臺程序模式執行,導致Docker前臺沒有執行的應用。這樣的容器後臺啟動後,會立即自殺因為它覺得它沒事可做。所以最佳的解決方案是將要執行的程式以前臺程序的形式執行。

那麼如何讓守護式容器不自動退出呢?我們可以執行一直掛起的命令。

docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
(9) 檢視容器日誌
docker logs [OPTIONS] CONTAINER

# OPTION說明:
# -t 加入時間戳
# -f 跟隨最新的日誌列印
# --tail 顯示最後多少條
(10) 檢視容器內的程序
docker top CONTAINER 
(11) 檢視容器內部細節
docker inspect CONTAINER
(12) 進入正在執行的容器並以命令列互動
  • exec在容器中開啟新的終端,並且可以啟動新的程序
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]  

docker exec -it CONTAINER /bin/bash   // 進入容器內部並互動

# 隔山打牛
docker exec -it 3d00a0a2877e ls -l /tmp 

# 3d00a0a2877e是我上面跑的守護式容器
# 進入容器,執行ls -l /tmp 命令後將結果返回給我宿主機,而使用者的介面還是停留在宿主機,沒有進入容器內部
  • attach直接進入容器啟動命令的終端,不會啟動新的程序
docker attach CONTAINER

# 注意,進入上面那個守護式容器後,我們會看到還是每隔兩秒列印hello Negan,而且不能退出,只能重新開啟一個終端,執行docker kill 命令
(13)容器與主機間的檔案拷貝
docker cp CONTAINER:SRC_PATH DEST_PATH  # 把容器內的檔案拷貝到宿主機
docker cp 90bd03598dd4:123.txt ~

docker cp SRC_PATH CONTAINER:DEST_PATH  # 把宿主機上的檔案拷貝到容器
docker cp 12345.txt 90bd03598dd4:~

練習

練習一:Docker部署Nginx

# 查詢一個nginx映象
docker search nginx
# 下載映象
docker pull nginx
# 啟動
docker run -d --name nginx01 -p 3344:80 nginx  # -p 3344(宿主機):80(容器埠)
# 測試(瀏覽器訪問)
123.56.243.64:3344 

五、視覺化

1、portainer

Docker圖形介面管理工具,提供一個後臺面板供我們操作。

docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer

六、Docker映象

1、映象是什麼

映象是一種輕量級、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體,它包含執行某個軟體所需的所有內容,包括程式碼、執行時、庫、環境變數和配置檔案。

所有應用直接打包docker映象,就可以直接跑起來。

如何獲取映象
  • 從遠端操作下載
  • 朋友拷貝
  • 自己製作一個映象DockerFile

2、映象載入原理

(1)聯合檔案系統

UnionFS(聯合檔案系統):Union檔案系統是一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改作為一次提交來一層一層疊加,同時可以將不同目錄掛在到同一個虛擬檔案系統下。聯合檔案系統是Docker映象的基礎,映象可以通過分層來進行繼承,基於基礎映象(沒有父映象),可以製作各種具體的應用映象。

特性:一次同時載入多個檔案系統,但從外面看起來,只能看到一個檔案系統,聯合載入會把各層檔案系統疊加起來,這樣最終的檔案系統會包含所有底層的檔案和目錄。

(2)Docker映象載入原理

Docker的映象實際上是由一層一層的檔案系統組成,也就是上面所說的聯合檔案系統。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引導載入kernel,Linux剛啟動時會載入bootfs檔案系統,在Docker映象最底層是bootfs。這一層與我們典型的Linux/Unix系統是一樣的,包含boot載入器和核心。當boot載入完成之後整個核心就都在記憶體中,此時記憶體的使用權已由bootfs轉交給核心,此時系統也會解除安裝bootfs。

rootfs(root file system),在bootfs之上,包含的就是典型Linux系統中的/dev,/proc,/bin,/etc等標準目錄和檔案。rootfs就是各種不同的作業系統發行版,比如Ubuntu,CentOS等等。

對於一個精簡的OS,rootfs可以很小,只需要包含最近本的命令,工具和程式庫就可以,因為底層直接用Host的kernel,自己只需要提供rootfs就可以,由此可見對於不同的Linux發行版,bootfs基本是一致的,rootfs會有差別,因此不同的發行版可以公用bootfs。

3、分層理解

(1)分層的映象

我們下載一個映象,注意觀察下載的日誌輸出,可以看到是一層一層在下載。

為什麼Docker映象採用這種分層的結構?

最大的好處,我覺得莫過於資源共享了,比如有多個映象從相同的Base映象構建而來,那麼宿主機只需要在磁碟上保留一份Base映象,同時記憶體中也只需要載入一份Base映象,這樣就可以為所有的容器服務了,而且映象每一層都可以被共享。

(2)理解

所有的Docker映象都始於一個基礎映象層,當進行修改或者增加新內容時,就會在當前映象層之上,建立新的映象層。假如基於Ubuntu Linux 16.04建立一個新的映象,這就是新映象的第一層;如果在該映象中新增Python包,就會在基礎映象之上建立第二個映象層,如果繼續新增一個安全補丁,就會建立第三個映象層。

再新增額外的映象層同時,映象始終保持是當前所有映象的組合。

注意:Docker映象都是隻讀的,當容器啟動時,一個新的可寫層被載入到映象的頂部。

​ 這一層就是我們通常說的容器層,容器之下的都叫映象層。

4、commit

docker commit 提交容器成為一個新的映象

docker commit -m="提交的描述資訊" -a="作者" 容器id 目標映象名:[TAG]

七、容器資料卷

如果資料在容器中,那麼我們容器刪除,資料就會丟失!

需求:資料需要持久化。

MySQL,容器刪了,刪庫跑路?Docker容器中產生的資料,需要同步到本地。

這就是卷技術,目錄的掛載,將我們容器內的目錄,掛載到Linux上。

1、使用資料卷

  • 直接使用命令來掛載
docker run -it -v 主機目錄:容器內目錄  # 雙向繫結,一方改變另一方自動改變

docker run -it -v /home/ceshi:/home centos /bin/bash

docker inspect d2343e9d338a  # 進行檢視

/*
...
 "Mounts": [   # 掛載 -v 
            {
                "Type": "bind",
                "Source": "/home/ceshi",  # 主機內的路徑
                "Destination": "/home",   # 容器內的路徑
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...
*/

2、實戰:安裝MySQL

思考:MySQL的資料持久化問題

docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7

3、具名掛載和匿名掛載

(1) 、匿名掛載
docker run -d -P --name nginx01 -v /etc/nginx nginx  # 只指定了容器內的路徑

docker volume ls  # 檢視所有volume的情況
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a  # 匿名掛載
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049

# 這種就是匿名掛載,我們在-v只寫了容器內的路徑,沒有寫容器外的路徑。
(2)、具名掛載
docker run -d -P -v juming:/etc/nginx --name nginx02 nginx   # 具名掛載

docker volume ls
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
local               juming  # 我們上面起的名字

# 通過-v 卷名:容器內路徑

# 所有的docker容器內的卷,沒有指定目錄的情況下,都在/var/lib/docker/volumes目錄下。
docker inspect juming  
[
    {
        "CreatedAt": "2020-12-09T00:22:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/juming/_data",
        "Name": "juming",
        "Options": null,
        "Scope": "local"
    }
]

我們通過具名掛載可以方便的找到我們掛載的卷,大多情況下我們都會使用具名掛載。

(3)、拓展
-v 容器內路徑  # 匿名掛載
-v 卷名:容器內路徑 # 具名掛載
-v /宿主機路徑:容器內路徑  # 指定路徑掛載

# 通過 -V 容器內路徑:ro rw改變讀寫許可權
ro readonly # 只讀
rw readwrite # 可讀可寫

# 一旦設定了只讀,容器對我們掛載出來的內容就有限定了。檔案只能通過宿主機來操作,容器無法操作。
docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx 

4、資料卷容器

容器間的資訊同步。

docker run -it -v /home --name c1 centos /bin/bash   # 啟動c1,作為一個父容器(指定容器內掛載目錄)
docker run -it --name c2 --volumes-from c1 centos /bin/bash  # 啟動c2,掛載c1,此時在兩個容器中/home中的操作就會同步


# 下面的容器是我們自己建立的,可以參考下面dockerfile進行構建
docker run -it --name docker01 negan/centos   # 啟動一個容器作為父容器(被掛載的容器)  
docker run -it --name docker02 --volumes-from docker01 negan/centos  #啟動容器2,掛載容器1 
docker run -it --name docker03 --volumes-from docker02 negan/centos  #啟動容器3,掛載容器2
# 上面的容器3也可以直接掛載到容器1上,然後我們進入任意一個容器,進入掛載卷volume01/volume02,進行操作,容器間的資料會自動同步。

結論:

容器之間配置資訊的傳遞,資料卷容器的宣告週期一直持續到沒有容器使用為止。

但是一旦持久到了本地,本地資料不會被刪除。

八、DockerFile

1、初識DockerFile

DockerFile就是用來構造docker映象的構建檔案,命令引數指令碼。通過這個指令碼,可以生成映象。映象是一層一層的,指令碼是一個個命令,每個命令就是一層。

# dockerfile1內容,所有命令都是大寫

FROM centos   # 基於centos

VOLUME ["volume01","volume02"]   # 掛載資料卷(匿名掛載)

CMD echo "----end-----"

CMD /bin/bash


# 構建
docker build -f dockerfile1 -t negan/centos .  # -f 指定路徑 -t指定名字,不加tag預設最新
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
 ---> 0d120b6ccaa8
Step 2/4 : VOLUME ["volume01","volume02"]
 ---> Running in 0cfe6b5be6bf
Removing intermediate container 0cfe6b5be6bf
 ---> 396a4a7cfe15
Step 3/4 : CMD echo "----end-----"
 ---> Running in fa535b5581fa
Removing intermediate container fa535b5581fa
 ---> 110d9f93f827
Step 4/4 : CMD /bin/bash
 ---> Running in 557a2bb87d97
Removing intermediate container 557a2bb87d97
 ---> c2c9b92d50ad
Successfully built c2c9b92d50ad
Successfully tagged negan/centos:latest

docker images  # 檢視

2、DokcerFile構建過程

(1)、基礎知識

每個保留關鍵字(指令)都必須是大寫字母

執行從上到下順序執行

#表示註釋

每一個指令都會建立提交一個新的映象層,並提交

DockerFile是面向開發的,我們以後要釋出專案,做映象,就需要編寫dockerfile檔案。

Docker映象逐漸成為企業交付的標準。

(2)、基本命令
FROM  #這個映象的媽媽是誰?(基礎映象,一切從這裡開始構建)
MAINTAINER # 告訴別人誰負責養他?(映象是誰寫的,指定維護者資訊,姓名+郵箱)
RUN  # 你想讓他幹啥?(映象構建的時候需要執行的命令)
ADD  # 給他點創業資金(複製檔案,會自動解壓)
WORKDIR # 映象的工作目錄
VOLUME  # 給他一個存放行李的地方(設定卷,容器內掛載到主機的目錄,匿名掛載)
EXPOSE  # 門牌號是多少?(指定對外埠)
CMD   # 指定這個容器啟動的時候要執行的命令,只有最後一個會生效,可被替代
ENTRYPOINT # 指定這個容器啟動時候要執行的命令,可以追加命令
ONBUILD  # 當構建一個被繼承DockerFile,這時就會執行ONBUILD指令
COPY    # 類似ADD,將我們檔案拷貝到映象中
ENV    # 構建的時候設定環境變數

3、實戰操作

(1)建立一個自己的CentOS
# vim Dockerfile

FROM centos
MAINTAINER Negan<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 構建
docker build -f Dockerfile -t negan/centos .

# 測試
docker run -it negan/centos
[root@ffae1f9eb97e local]# pwd
/usr/local   # 進入的是我們在dockerfile設定的工作目錄

# 檢視映象構建過程
docker history 映象名/ID
(2)CMD與ENTRYPOINT區別

兩者都是容器啟動的時候要執行的命令,CMD命令只有最後一個會生效,後面不支援追加命令,會被替代。ENTRYPOINT不會被替代,可以追加命令。

CMD

# vim cmd
FROM centos
CMD ["ls","-a"]

# 構建
docker build -f cmd -t cmd_test .

# 執行,發現我們的ls -a命令生效
docker run cmd_test
.
..
.dockerenv
bin
dev
etc
......

# 追加命令執行
docker run cmd_test -l
# 丟擲錯誤,不能追加命令,原來的ls -a命令被-l替換,但是-l不是一個有效的命令
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.

# 追加完整的命令
docker run cmd_test ls -al

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:36 .
drwxr-xr-x  1 root root 4096 Dec 10 14:36 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:36 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:36 dev
......

ENTRYPOINT

# vim entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]

# 構建
docker build -f entrypoint -t entrypoint_test .

# 執行
docker run entrypoint_test
.
..
.dockerenv
bin
dev
etc

# 追加命令執行
docker run entrypoint_test -l

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:41 .
drwxr-xr-x  1 root root 4096 Dec 10 14:41 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:41 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:41 dev
drwxr-xr-x  1 root root 4096 Dec 10 14:41 etc
......

4、實戰構建tomcat

(1)環境準備
 ll
total 166472
-rw-r--r-- 1 root root  11437266 Dec  9 16:22 apache-tomcat-9.0.40.tar.gz
-rw-r--r-- 1 root root       641 Dec 10 23:26 Dockerfile
-rw-r--r-- 1 root root 159019376 Dec  9 17:39 jdk-8u11-linux-x64.tar.gz
-rw-r--r-- 1 root root         0 Dec 10 22:48 readme.txt
(2)構建映象
# vim Dockerfile (Dockerfile是官方建議命名)

FROM centos
MAINTAINER Negan<[email protected]>

COPY readme.txt /usr/local/readme.txt

ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
ADD apache-tomcat-9.0.40.tar.gz /usr/local/

RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out

# 構建
docker -t tomcat .
(3)啟動容器
docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat

九、釋出自己的映象

1、docker hub

首先需要在DockerHub上註冊自己的賬號,並且確定這個賬號可以登入。

在我們的伺服器上進行登入,登入成功後提交我們的映象。

# 登入
docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
  -p, --password string   Password
      --password-stdin    Take the password from stdin
  -u, --username string   Username


# 登入成功後推送我們的映象
docker push [OPTIONS] NAME[:TAG]  

Push an image or a repository to a registry

Options:
      --disable-content-trust   Skip image signing (default true)

docker tag tomcat huiyichanmian/tomcat  # 需要改名,推送改名後的(前面加自己的使用者名稱)
docker push huiyichanmian/tomcat

2、阿里雲

登入阿里雲,找到容器映象服務,使用映象倉庫。建立名稱空間,建立映象倉庫。選擇本地倉庫。

具體阿里雲上有特別詳細的步驟,這裡不再贅述了。

十、Docker網路

1、理解docker0

(1)檢視本機網絡卡資訊
ip addr 

# 本地迴環地址
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
       
# 阿里雲內網地址
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286793195sec preferred_lft 286793195sec
    
# docker0地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

(2)檢視容器網絡卡資訊

我們獲取一個tomcat映象,用來測試。

docker run -d -P --name t1 tomcat 

docker exec -it t1 ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# 我們發現容器啟動時候會得到一個eth0@ifxxx的網絡卡,而且ip地址和上面的docker0裡的是在同一網段。
233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
(3)、再次檢視本地網絡卡資訊
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286792020sec preferred_lft 286792020sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
# 我們發現多了一條網絡卡資訊,而且與容器裡面的網絡卡有某種對應關係。(233,234)
234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default

我們重複上面的操作,不難發現我們只要安裝了docker,本地網卡里就會多一個docker0,而且我們每啟動一個容器,docker就會給容器分配一個網絡卡,而且在本地也會多出一個網絡卡資訊,與容器內的相對應。這就是veth_pair技術,就是一對虛擬裝置介面,它們都是成對出現,一段連著協議,一段彼此相連。正因為有了這個特性,我們通常veth_pair充當了一個橋樑,連線各種虛擬網路裝置。

所有的容器不指定網路的情況下,都是由docker0路由的,docker會給我們的容器分配一個預設的ip。

Docker使用的是Linux的橋接,宿主機中docker0是一個Docker容器的網橋。所有的網路介面都是虛擬的。

只要容器刪除,對應的網橋也響應的被刪除。

問題:我們每次重啟容器,容器的ip地址會變,而我們專案中一些配置使用的固定ip,這時候也需要做相應的改變,那麼我們可不可以直接設定服務名呢,下次重啟時候,配置直接找服務名。

我們啟動兩個tomcat,進行測試,互相ping其對應的名字,看是否能通?

docker exec -it t1 ping t2
ping: t2: Name or service not known
# 答案是肯定的,不能識別t2,如何解決?
# 我們使用--link進行連線
docker run -d -P --name t3 --link t2 tomcat

# 我們嘗試使用t3來ping t2
docker exec -it t3 ping t2
# 我們發現竟然通了
PING t2 (172.17.0.3) 56(84) bytes of data.
64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
......

那麼--link做了什麼呢?

docker exec -it t3 cat /etc/hosts  # 我們來檢視t3的hosts檔案

127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	t2 6bf3c12674c8  # 原因在這了,這裡對t2做了標記,當ping t2時候,自動轉到172.17.0.3
172.17.0.4	b6dae0572f93

3、自定義網路

(1)檢視docker網路
docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
afc0c673386f        none                null                local

# bridge 橋接(docker預設)
# host 與主機共享
# none 不配置
(2)容器啟動時的預設網路

docker0是我們預設的網路,不支援域名訪問,可以使用--link打通。

 # 一般我們啟動容器是這樣子,使用的是預設網路,而這個預設網路就是bridge,所以下面兩條命令是相同的
docker run -d -P --name t1 tomcat  

docker run -d -P --name t1 --net bridge tomcat 
(3)建立網路
# --driver bridge 預設,可以不寫
# --subnet 192.168.0.0/16 子網掩碼
# --gateway 192.168.0.1 預設閘道器
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

docker network ls

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
0e98462f3e8e        mynet               bridge              local  # 我們自己建立的網路
afc0c673386f        none                null                local


ip addr

.....
# 我們自己建立的網路
239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
       valid_lft forever preferred_lft forever
.....

啟動兩個容器,使用我們自己建立的網路

docker run -P -d --name t1 --net mynet tomcat
docker run -P -d --name t2 --net mynet tomcat

# 檢視我們自己建立的網路資訊
docker network mynet inspect

# 我們發現我們剛啟動的兩個容器使用的是我們剛建立的網路
......
"Containers": {
            "1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
                "Name": "t2",
                "EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
                "Name": "t1",
                "EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
.......

那麼使用自己建立的網路有什麼好處呢?

我們回到我們以前的問題,就是域名ping不通的問題上。

docker exec -it t1 ping t2
PING t2 (192.168.0.3) 56(84) bytes of data.
64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
......

docker exec -it t2 ping t1
PING t1 (192.168.0.2) 56(84) bytes of data.
64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
......

我們發現域名可以ping通了,那麼說明我們自定義的網路已經幫我們維護好了對應關係。

這樣不同的叢集使用不同的網路,也能保證叢集的安全和健康。

4、網路連通

現在有這樣一個需求,t1,t2使用的是我們自己定義的網路,t3,t4使用的預設的docker0網路。那麼現在t3和t1或者t2能否通訊呢?

我們知道,docker0的預設閘道器是172.17.0.1,mynet預設閘道器是192.168.0.1,他們直接屬於不同的網段,不能進行通訊。那麼現在如何解決上面的問題呢?

那麼mynet能不能給t3分配一個ip地址呢?如果能分配,那麼問題就應該可以得到解決。

docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
      --alias strings           Add network-scoped alias for the container
      --driver-opt strings      driver options for the network
      --ip string               IPv4 address (e.g., 172.30.100.104)
      --ip6 string              IPv6 address (e.g., 2001:db8::33)
      --link list               Add link to another container
      --link-local-ip strings   Add a link-local address for the container
   
  # 一個容器兩個ip地址 
 docker network connect mynet t3   # 將t3加入到mynet網路中
 
 # 檢視mynet資訊
 docker network inspect mynet
 
 "Containers": {
			......
            "d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
                "Name": "t3",  # 我們發現了t3
                "EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
                "MacAddress": "02:42:c0:a8:00:04",
                "IPv4Address": "192.168.0.4/16",
                "IPv6Address": ""
            }
        },
        ......

這時候,t3就能和t1,t2進行通訊了。

5、部署Redis叢集

# 建立網路
docker network create redis --subnet 172.38.0.0/16

# 通過指令碼建立六個redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379 
bind 0.0.0.0
cluster-enabled yes 
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

# 啟動容器
vim redis.py

import os
for i in range(1, 7):
    str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} \
    -v /mydata/redis/node-{}/data:/data \
    -v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf \
    -d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
    os.system(str)

python reidis.py

# 建立叢集
docker exec -it redis-1 /bash/sh  # 進入redis-1容器

redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

# 建立叢集。。。。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   slots: (0 slots) slave
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   slots: (0 slots) slave
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   slots: (0 slots) slave
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.


# 進行測試
redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:160
cluster_stats_messages_pong_sent:164
cluster_stats_messages_sent:324
cluster_stats_messages_ping_received:159
cluster_stats_messages_pong_received:160
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:324

十一、Docker Compose

1、介紹

Compose是Docker官方開源的專案,需要安裝。

Compose是用於定義和執行多容器Docker應用程式的工具。通過Compose,可以使用YAML檔案來配置應用程式的服務。然後,使用一個命令,就可以從配置中建立並啟動所有服務。

使用Compose基本上是一個三步過程:

  1. Dockerfile保證我們的專案在任何地方執行
  2. docker-compose檔案
  3. 啟動

2、快速開始

(1)安裝
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

docker-compose --version
# 安裝成功
# docker-compose version 1.27.4, build 40524192
(2)使用
# 為專案建立目錄
mkdir composetest
cd composetest

# 編寫一段flask程式
vim app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)  # 這裡主機名直接用“redis”,而不是ip地址

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
    
# 編寫requirements.txt檔案(不指定版本,下載最新)
flask
redis

# 編寫Dockerfile檔案
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]


# 編寫docker-compose.yml檔案
# 檔案中定義了兩個服務,web和redis,web是從Dockerfile開始構建,redis使用的是公共映象
version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
  redis:
    image: "redis:alpine"
    
# 執行
docker-compose up 

3、搭建部落格

# 建立並進入目錄
mkdir wordpress && cd wordpress

# 編寫docker-compose.yml檔案來啟動,並建立了一個單獨Mysql例項,具有用於資料永續性的卷掛載
version: "3.3"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      W0RDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
  volumes:
    db_data: {}
    

# 啟動執行
docker-compose up -d

十二、Docker Swarm

1、環境準備

準備四臺伺服器。安裝docker。

2、swarm叢集搭建

docker swarm COMMAND
Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm   # 初始化一個節點(管理節點)
  join        Join a swarm as a node and/or manager  # 加入節點
  join-token  Manage join tokens   # 通過節點token加入
  leave       Leave the swarm    # 離開節點
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm  #更新

# 首先我們初始化一個節點
docker swarm init --advertise-addr + 自己的ip(這裡用內網地址,主要是省錢)
docker swarm init --advertise-addr 172.27.0.4

# 提示我們節點建立成功
Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.

To add a worker to this swarm, run the following command:
	
	# 在其他機器上執行,加入這個節點
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 生成一個管理節點的token,
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

# 我們在機器2上行加入節點的命令
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 我們在機器1上檢視節點資訊
docker node ls
# 我們發現一個管理節點,一個工作節點,狀態都是就緒狀態
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos   Ready     Active         Leader           20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos   Ready     Active                          20.10.0

# 現在將機器3也加入,同樣是work節點
# 到此為止,現在只有機器4沒有加入,這時候我們想把它設定成管理節點。
# 在機器1上執行生成管理節點的命令,在機器4上執行
docker swarm join-token manager
# 生成的命令在機器4上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 此時機器4也是管理節點了
This node joined a swarm as a manager.

# 至此我們可以在機器1或者機器4上進行其他操作了。(只能在管理節點上操作)

3、Raft協議

在前面的步驟,我們已經完成了雙主雙從叢集的搭建。

Raft協議:保證大多數節點存活才能使用。必須大於1,叢集至少大於3臺。

實驗1

將機器1上的docker停止,宕機,現在叢集中只有一個管理節點了。叢集是否可用。

#我們在機器四上檢視節點資訊
docker node ls
# 發現我們的叢集已經不能用了
Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded

# 我們將機器1上的docker重啟,發現叢集又能用了,但是此時機器1上的docker已經不是leader了,leader自動轉給機器4了

實驗2

我們將工作節點離開叢集,檢視叢集資訊

# 我們在機器2上執行
docker swarm leave

# 在機器一上檢視節點資訊
docker node ls
# 我們發現機器2狀態是Down
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos    Ready     Active         Reachable        20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

實驗3

現在我們將機器2也設定成管理節點(此時叢集有三個管理節點),隨機down掉一個管理節點,叢集是否正常執行。

# 在機器2上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377

# 將機器1的docker宕機
systemctl stop docker

# 在機器二上檢視節點資訊
docker node ls
# 叢集正常,且可以看到機器1宕機了
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm     VM-0-4-centos    Ready     Active         Unreachable      20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
vdwcwr3v6qrn6da40zdrjkwmy *   VM-0-7-centos    Ready     Active         Reachable        20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

4、彈性建立服務

docker service COMMAND

Commands:
  create      Create a new service  # 建立一個服務
  inspect     Display detailed information on one or more services  # 檢視服務資訊
  logs        Fetch the logs of a service or task  # 日誌
  ls          List services   # 列表
  ps          List the tasks of one or more services   # 檢視我們的服務
  rm          Remove one or more services  # 刪除服務
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services  # 動態擴縮容
  update      Update a service  # 更新

docker service create -p 8888:80 --name n1 nginx  # 建立一個服務,在叢集中隨機分配
kj0xokbxvf5uw91bswgp1cukf
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

# 給我們的服務增加三個副本
docker service update --replicas 3 n1

# 動態擴縮容
docker service scale n1=10   # 和上面updata一樣,這個方便

docker ps # 檢視

服務,叢集中任意的節點都可以訪問,服務可以有多個副本動態擴縮容實現高可用。

5、使用Docker stack部署部落格

我們現在需要將上面的部落格跑在我們的叢集中,並且要開十個副本。

# 編輯docker-compose.yml檔案
version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     deploy:
       replicas: 10
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}


# 進行啟動
docker stack deploy -c docker-compose.yml wordpress

# 檢視
docker service ls

一、安裝

環境:Centos7

1、解除安裝舊版本

較舊的Docker版本稱為dockerdocker-engine。如果已安裝這些程式,請解除安裝它們以及相關的依賴項。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2、使用儲存庫安裝

注意:官方給了三種安裝方式,這裡我們選擇最常用的儲存庫安裝

在新主機上首次安裝Docker Engine之前,需要設定Docker儲存庫。之後可以從儲存庫安裝和更新Docker。

sudo yum install -y yum-utils

# 注意:此處是大坑,可以自行換成阿里的源
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    
# 我們用阿里的    
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
 
# 進行安裝(如果不換源,速度會很慢)
sudo yum install docker-ce docker-ce-cli containerd.io

3、啟動以及驗證

systemctl start docker   # 啟動
systemctl status docker  #  檢視狀態
docker version  # 檢視版本
docker run hello-world   #  測試

4、設定加速器(阿里雲)

注意:裡面的地址每個人都有專屬的地址。

開啟阿里雲官網->控制檯->搜尋容器映象服務->右下角找到映象加速器

sudo mkdir -p /etc/docker

tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

docker run hello-world

5、解除安裝

# 解除安裝Docker Engine,CLI和Containerd軟體包
sudo yum remove docker-ce docker-ce-cli containerd.io

# 主機上的映像,容器,卷或自定義配置檔案不會自動刪除。要刪除所有影象,容器和卷
sudo rm -rf /var/lib/docker

二、Docker三要素

1、倉庫(Repository)

倉庫:集中存放映象的場所。

注意:倉庫(Repository)和倉庫註冊伺服器(Registry)是有區別的,倉庫註冊伺服器往往存放著多個倉庫,每個倉庫中又包含多個映象,每個映象有不同的標籤(tag)

倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。

最大的公開倉庫是Docker Hub(https://hub.docker.com),存放了數量龐大的映象供使用者下載,國內的公開倉庫包括阿里雲、網易雲等。

2、映象(Image)

一個只讀模板,用來建立Docker容器,一個映象可以建立很多個容器。

容器與映象的關係類似於面向物件程式設計中的物件和類

Docker 面向物件
容器 物件
映象

3、容器 (Container)

獨立執行的一個或一組應用。

容器使用映象建立的執行例項。

它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的,保證安全的平臺。

可以把容器看作是一個簡易版的Linux環境(包括root使用者許可權,程序空間,使用者空間和網路空間等等)和執行在啟動的應用程式。

容器的定義和映象幾乎一模一樣,也是一堆層的統一視角,唯一區別在於容器的最上面那一層是可讀可寫的。

三、簡單瞭解底層原理

1、Docker是怎麼工作的

Docker是一個Client-Server結構的系統,Docker守護程序執行在主機上,然後通過Socket連線從客戶端訪問,守護程序從客戶端接收命令並管理執行在主機上的容器。

2、Docker為什麼比VM快?

(1)Docker有著比虛擬機器更少的抽象層,由於Docker不需要Hypervisor實現硬體資源虛擬化,執行在Docker容器上的程式直接使用的都是實際物理機的硬體資源,因此在CPU、記憶體利用率上Docker將會在效率上有明顯的優勢。

(2)Docker利用的是宿主機的核心,而不需要Guest OS。因此當新建一個容器時,Docker不需要和虛擬機器一樣重疊載入一個作業系統核心,從而避免引尋、載入作業系統核心,返回比較費時費資源的過程。當新建一個虛擬機器時,虛擬機器軟體需要載入Guest OS,這個新建過程是分鐘級別的,而Docker由於直接利用宿主機的作業系統,因此新建一個Docker容器只需幾秒鐘。

Docker容器 虛擬機器(VM)
作業系統 與宿主機共享OS 宿主機OS上執行虛擬機器OS
儲存大小 映象小,便於儲存與傳輸 映象龐大(vmdk,vdi等)
執行效能 幾乎無額外效能損失 作業系統額外的CPU、記憶體消耗
移植性 輕便、靈活,適應於Linux 笨重,與虛擬化技術耦合度高
硬體親和性 面向軟體開發者 面向硬體運維者

四、相關命令

1、幫助命令

docker version  # 檢視docker版本資訊
docker info  # 詳細說明
docker --help  # 幫助命令

2、映象命令

(1)列出本地映象
docker images [OPTIONS] [REPOSITORY[:TAG]]

# OPTIONS說明:
	-a: 列出本地所有的映象(含中間映像層)
    -q: 只顯示映象ID
    --digests: 顯示映象的摘要資訊
    --no-trunc: 顯示完整的映象資訊

# 各個選項說明:
# REPOSITORY:表示映象的倉庫源
# TAG:映象的標籤
# IMAGE ID:映象ID
# CREATED:映象建立時間
# SIZE:映象大小	

同一倉庫源可以有多個TAG,代表這個倉庫源的不同版本,我們使用REPOSITORY:TAG來定義不同的映象,如果不指定一個映象的版本標籤,例如我們只使用ubuntu,docker將預設使用ubuntu:latest映象。

(2)查詢映象
docker search [options] 某個xxx映象名字  # 會在https://hub.docker.com上去查詢

docker search mysql --filter=STARS=3000  # 搜尋星數大於等於3000的映象

# OPTIONS說明:
# --no-trunc: 顯示完整的映象描述	
# -s:列出點贊數不小於指定值的映象
# --automated: 只列出automated build型別的映象
(3)獲取映象
docker pull 映象名字[:TAG]  # 如果不寫TAG,則預設獲取latest,此時從我們配置的阿里雲上獲取映象

docker pull mysql  # 獲取最新版本的mysql
Using default tag: latest
latest: Pulling from library/mysql
852e50cd189d: Pull complete   # 分卷下載
29969ddb0ffb: Pull complete 
a43f41a44c48: Pull complete 
5cdd802543a3: Pull complete 
b79b040de953: Pull complete 
938c64119969: Pull complete 
7689ec51a0d9: Pull complete 
a880ba7c411f: Pull complete 
984f656ec6ca: Pull complete 
9f497bce458a: Pull complete 
b9940f97694b: Pull complete 
2f069358dc96: Pull complete 
Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest  
# docker pull mysql 等價於docker pull docker.io/library/mysql:latest 

docker pull mysql:5.7  # 下載指定版本的映象
5.7: Pulling from library/mysql
852e50cd189d: Already exists  # 聯合檔案系統,已經存在的不會去重複下載
29969ddb0ffb: Already exists 
a43f41a44c48: Already exists 
5cdd802543a3: Already exists 
b79b040de953: Already exists 
938c64119969: Already exists 
7689ec51a0d9: Already exists 
36bd6224d58f: Pull complete 
cab9d3fa4c8c: Pull complete 
1b741e1c47de: Pull complete 
aac9d11987ac: Pull complete 
Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
(4)刪除映象
# 刪除單個映象
docker rmi -f 映象名/ID  # 如果是映象名,後面不帶TAG,則預設刪除latest

# 刪除多個映象
docker rmi -f 映象名1 映象名2 ...
docker rmi -f id1 id2 ...     // 注意:兩種方式不能混用

# 刪除全部映象
docker rmi -f $(docker images -aq)   

3、容器命令

​ 有映象才能建立容器,這是根本前提(下載一個Centos映象演示)

(1) 新建並啟動容器(互動式)
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run -it --name mycentos centos  // 如果不指定別名,系統會自動分配

# OPTIONS說明:
# --name 容器新名字:為容器指定一個名稱
# -d:後臺執行容器,並返回容器ID,即啟動守護式容器
# -i: 以互動模式執行容器,通常與-t同時使用
# -t: 為容器重新分配一個偽輸入終端,通常與-i同時使用
# -P: 隨機埠對映
# -p: 指定埠對映有以下四種格式
		ip:hostPort:containerPort
		ip::containerPort
		hostPort:containerPort
		containerPort

# 測試
[root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash  # 啟動並進入容器
[root@783cb2f26230 /]# ls   # 在容器內檢視
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@783cb2f26230 /]# exit   # 退出容器
exit
[root@iz2zeaj5c9isqt1zj9elpbz ~]# 

(2)列出當前所有正在執行的容器
docker ps [OPTIONS]  # 不帶OPTIONS,則只列出正在執行的容器

# OPTIONS說明(常用):
# -a:列出當前所有正在執行的容器以及歷史上執行過的
# -l:顯示最近建立的容器
# -n: 顯示最近n個建立的容器
# -q: 靜默模式,只顯示容器編號
# --no-trunc:不截斷輸出
(3)退出容器
exit  // 直接關閉並退出容器

重新開啟一個終端,執行docker ps -l,會返回剛才我們建立的容器資訊,並且STATUS會提示已經退出。

那麼可不可以在互動式,不關閉容器的情況下,暫時退出,一會又可以回來呢?

Ctrl + P + Q

# 執行後,我們將退出容器,回到宿主機,使用docker ps -l,我們會發現這個剛才退出的容器STATUS是Up狀態。
(4)啟動容器
docker start [OPTIONS] CONTAINER [CONTAINER...]

# 同時可以啟動多個容器,容器名和ID可以混用

# OPTION說明(常用):
# -i : 進入互動式,此時只能進入一個容器

進入上面我們退出但是依然存活的容器

docker start -i 186ae928f07c
(5)重啟容器
docker restart [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死容器之前等待停止的時間(預設為10)
(6) 停止容器
docker stop [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死前等待停止的時間(預設為10)

docker kill [OPTIONS] CONTAINER [CONTAINER...]  // 強制關閉(相當於拔電源)

# OPTIONS說明:
# -s: 傳送到容器的訊號(預設為“KILL”)
(7)刪除容器
docker rm [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -f :強制刪除,不管是否在執行

# 上面的命令可以刪除一個或者多個,但是想全部刪除所有的容器,那麼我們要把全部的容器名或者ID都寫一遍嗎?

docker rm -f $(docker ps -aq)  # 刪除所有
docker ps -aq | xargs docker rm -f
(8) 啟動守護式容器
docker run -d 容器名/容器ID

說明:我們使用docker ps -a命令檢視,會發現剛才啟動的容器以及退出了,這是為什麼呢?

很重要的要說明一點:Docker容器後臺執行,必須要有一個前臺程序,容器執行的命令如果不是那些一直掛起的命令(比如執行top,tail),就會自動退出。

這就是Docker的機制問題,比如我們現在執行WEB容器,以Nginx為例,正常情況下,我們配置啟動服務只需要啟動響應的service即可,例如service nginx start,但是這樣做,Nginx為後臺程序模式執行,導致Docker前臺沒有執行的應用。這樣的容器後臺啟動後,會立即自殺因為它覺得它沒事可做。所以最佳的解決方案是將要執行的程式以前臺程序的形式執行。

那麼如何讓守護式容器不自動退出呢?我們可以執行一直掛起的命令。

docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
(9) 檢視容器日誌
docker logs [OPTIONS] CONTAINER

# OPTION說明:
# -t 加入時間戳
# -f 跟隨最新的日誌列印
# --tail 顯示最後多少條
(10) 檢視容器內的程序
docker top CONTAINER 
(11) 檢視容器內部細節
docker inspect CONTAINER
(12) 進入正在執行的容器並以命令列互動
  • exec在容器中開啟新的終端,並且可以啟動新的程序
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]  

docker exec -it CONTAINER /bin/bash   // 進入容器內部並互動

# 隔山打牛
docker exec -it 3d00a0a2877e ls -l /tmp 

# 3d00a0a2877e是我上面跑的守護式容器
# 進入容器,執行ls -l /tmp 命令後將結果返回給我宿主機,而使用者的介面還是停留在宿主機,沒有進入容器內部
  • attach直接進入容器啟動命令的終端,不會啟動新的程序
docker attach CONTAINER

# 注意,進入上面那個守護式容器後,我們會看到還是每隔兩秒列印hello Negan,而且不能退出,只能重新開啟一個終端,執行docker kill 命令
(13)容器與主機間的檔案拷貝
docker cp CONTAINER:SRC_PATH DEST_PATH  # 把容器內的檔案拷貝到宿主機
docker cp 90bd03598dd4:123.txt ~

docker cp SRC_PATH CONTAINER:DEST_PATH  # 把宿主機上的檔案拷貝到容器
docker cp 12345.txt 90bd03598dd4:~

練習

練習一:Docker部署Nginx

# 查詢一個nginx映象
docker search nginx
# 下載映象
docker pull nginx
# 啟動
docker run -d --name nginx01 -p 3344:80 nginx  # -p 3344(宿主機):80(容器埠)
# 測試(瀏覽器訪問)
123.56.243.64:3344 

五、視覺化

1、portainer

Docker圖形介面管理工具,提供一個後臺面板供我們操作。

docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer

六、Docker映象

1、映象是什麼

映象是一種輕量級、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體,它包含執行某個軟體所需的所有內容,包括程式碼、執行時、庫、環境變數和配置檔案。

所有應用直接打包docker映象,就可以直接跑起來。

如何獲取映象
  • 從遠端操作下載
  • 朋友拷貝
  • 自己製作一個映象DockerFile

2、映象載入原理

(1)聯合檔案系統

UnionFS(聯合檔案系統):Union檔案系統是一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改作為一次提交來一層一層疊加,同時可以將不同目錄掛在到同一個虛擬檔案系統下。聯合檔案系統是Docker映象的基礎,映象可以通過分層來進行繼承,基於基礎映象(沒有父映象),可以製作各種具體的應用映象。

特性:一次同時載入多個檔案系統,但從外面看起來,只能看到一個檔案系統,聯合載入會把各層檔案系統疊加起來,這樣最終的檔案系統會包含所有底層的檔案和目錄。

(2)Docker映象載入原理

Docker的映象實際上是由一層一層的檔案系統組成,也就是上面所說的聯合檔案系統。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引導載入kernel,Linux剛啟動時會載入bootfs檔案系統,在Docker映象最底層是bootfs。這一層與我們典型的Linux/Unix系統是一樣的,包含boot載入器和核心。當boot載入完成之後整個核心就都在記憶體中,此時記憶體的使用權已由bootfs轉交給核心,此時系統也會解除安裝bootfs。

rootfs(root file system),在bootfs之上,包含的就是典型Linux系統中的/dev,/proc,/bin,/etc等標準目錄和檔案。rootfs就是各種不同的作業系統發行版,比如Ubuntu,CentOS等等。

對於一個精簡的OS,rootfs可以很小,只需要包含最近本的命令,工具和程式庫就可以,因為底層直接用Host的kernel,自己只需要提供rootfs就可以,由此可見對於不同的Linux發行版,bootfs基本是一致的,rootfs會有差別,因此不同的發行版可以公用bootfs。

3、分層理解

(1)分層的映象

我們下載一個映象,注意觀察下載的日誌輸出,可以看到是一層一層在下載。

為什麼Docker映象採用這種分層的結構?

最大的好處,我覺得莫過於資源共享了,比如有多個映象從相同的Base映象構建而來,那麼宿主機只需要在磁碟上保留一份Base映象,同時記憶體中也只需要載入一份Base映象,這樣就可以為所有的容器服務了,而且映象每一層都可以被共享。

(2)理解

所有的Docker映象都始於一個基礎映象層,當進行修改或者增加新內容時,就會在當前映象層之上,建立新的映象層。假如基於Ubuntu Linux 16.04建立一個新的映象,這就是新映象的第一層;如果在該映象中新增Python包,就會在基礎映象之上建立第二個映象層,如果繼續新增一個安全補丁,就會建立第三個映象層。

再新增額外的映象層同時,映象始終保持是當前所有映象的組合。

注意:Docker映象都是隻讀的,當容器啟動時,一個新的可寫層被載入到映象的頂部。

​ 這一層就是我們通常說的容器層,容器之下的都叫映象層。

4、commit

docker commit 提交容器成為一個新的映象

docker commit -m="提交的描述資訊" -a="作者" 容器id 目標映象名:[TAG]

七、容器資料卷

如果資料在容器中,那麼我們容器刪除,資料就會丟失!

需求:資料需要持久化。

MySQL,容器刪了,刪庫跑路?Docker容器中產生的資料,需要同步到本地。

這就是卷技術,目錄的掛載,將我們容器內的目錄,掛載到Linux上。

1、使用資料卷

  • 直接使用命令來掛載
docker run -it -v 主機目錄:容器內目錄  # 雙向繫結,一方改變另一方自動改變

docker run -it -v /home/ceshi:/home centos /bin/bash

docker inspect d2343e9d338a  # 進行檢視

/*
...
 "Mounts": [   # 掛載 -v 
            {
                "Type": "bind",
                "Source": "/home/ceshi",  # 主機內的路徑
                "Destination": "/home",   # 容器內的路徑
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...
*/

2、實戰:安裝MySQL

思考:MySQL的資料持久化問題

docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7

3、具名掛載和匿名掛載

(1) 、匿名掛載
docker run -d -P --name nginx01 -v /etc/nginx nginx  # 只指定了容器內的路徑

docker volume ls  # 檢視所有volume的情況
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a  # 匿名掛載
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049

# 這種就是匿名掛載,我們在-v只寫了容器內的路徑,沒有寫容器外的路徑。
(2)、具名掛載
docker run -d -P -v juming:/etc/nginx --name nginx02 nginx   # 具名掛載

docker volume ls
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
local               juming  # 我們上面起的名字

# 通過-v 卷名:容器內路徑

# 所有的docker容器內的卷,沒有指定目錄的情況下,都在/var/lib/docker/volumes目錄下。
docker inspect juming  
[
    {
        "CreatedAt": "2020-12-09T00:22:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/juming/_data",
        "Name": "juming",
        "Options": null,
        "Scope": "local"
    }
]

我們通過具名掛載可以方便的找到我們掛載的卷,大多情況下我們都會使用具名掛載。

(3)、拓展
-v 容器內路徑  # 匿名掛載
-v 卷名:容器內路徑 # 具名掛載
-v /宿主機路徑:容器內路徑  # 指定路徑掛載

# 通過 -V 容器內路徑:ro rw改變讀寫許可權
ro readonly # 只讀
rw readwrite # 可讀可寫

# 一旦設定了只讀,容器對我們掛載出來的內容就有限定了。檔案只能通過宿主機來操作,容器無法操作。
docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx 

4、資料卷容器

容器間的資訊同步。

docker run -it -v /home --name c1 centos /bin/bash   # 啟動c1,作為一個父容器(指定容器內掛載目錄)
docker run -it --name c2 --volumes-from c1 centos /bin/bash  # 啟動c2,掛載c1,此時在兩個容器中/home中的操作就會同步


# 下面的容器是我們自己建立的,可以參考下面dockerfile進行構建
docker run -it --name docker01 negan/centos   # 啟動一個容器作為父容器(被掛載的容器)  
docker run -it --name docker02 --volumes-from docker01 negan/centos  #啟動容器2,掛載容器1 
docker run -it --name docker03 --volumes-from docker02 negan/centos  #啟動容器3,掛載容器2
# 上面的容器3也可以直接掛載到容器1上,然後我們進入任意一個容器,進入掛載卷volume01/volume02,進行操作,容器間的資料會自動同步。

結論:

容器之間配置資訊的傳遞,資料卷容器的宣告週期一直持續到沒有容器使用為止。

但是一旦持久到了本地,本地資料不會被刪除。

八、DockerFile

1、初識DockerFile

DockerFile就是用來構造docker映象的構建檔案,命令引數指令碼。通過這個指令碼,可以生成映象。映象是一層一層的,指令碼是一個個命令,每個命令就是一層。

# dockerfile1內容,所有命令都是大寫

FROM centos   # 基於centos

VOLUME ["volume01","volume02"]   # 掛載資料卷(匿名掛載)

CMD echo "----end-----"

CMD /bin/bash


# 構建
docker build -f dockerfile1 -t negan/centos .  # -f 指定路徑 -t指定名字,不加tag預設最新
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
 ---> 0d120b6ccaa8
Step 2/4 : VOLUME ["volume01","volume02"]
 ---> Running in 0cfe6b5be6bf
Removing intermediate container 0cfe6b5be6bf
 ---> 396a4a7cfe15
Step 3/4 : CMD echo "----end-----"
 ---> Running in fa535b5581fa
Removing intermediate container fa535b5581fa
 ---> 110d9f93f827
Step 4/4 : CMD /bin/bash
 ---> Running in 557a2bb87d97
Removing intermediate container 557a2bb87d97
 ---> c2c9b92d50ad
Successfully built c2c9b92d50ad
Successfully tagged negan/centos:latest

docker images  # 檢視

2、DokcerFile構建過程

(1)、基礎知識

每個保留關鍵字(指令)都必須是大寫字母

執行從上到下順序執行

#表示註釋

每一個指令都會建立提交一個新的映象層,並提交

DockerFile是面向開發的,我們以後要釋出專案,做映象,就需要編寫dockerfile檔案。

Docker映象逐漸成為企業交付的標準。

(2)、基本命令
FROM  #這個映象的媽媽是誰?(基礎映象,一切從這裡開始構建)
MAINTAINER # 告訴別人誰負責養他?(映象是誰寫的,指定維護者資訊,姓名+郵箱)
RUN  # 你想讓他幹啥?(映象構建的時候需要執行的命令)
ADD  # 給他點創業資金(複製檔案,會自動解壓)
WORKDIR # 映象的工作目錄
VOLUME  # 給他一個存放行李的地方(設定卷,容器內掛載到主機的目錄,匿名掛載)
EXPOSE  # 門牌號是多少?(指定對外埠)
CMD   # 指定這個容器啟動的時候要執行的命令,只有最後一個會生效,可被替代
ENTRYPOINT # 指定這個容器啟動時候要執行的命令,可以追加命令
ONBUILD  # 當構建一個被繼承DockerFile,這時就會執行ONBUILD指令
COPY    # 類似ADD,將我們檔案拷貝到映象中
ENV    # 構建的時候設定環境變數

3、實戰操作

(1)建立一個自己的CentOS
# vim Dockerfile

FROM centos
MAINTAINER Negan<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 構建
docker build -f Dockerfile -t negan/centos .

# 測試
docker run -it negan/centos
[root@ffae1f9eb97e local]# pwd
/usr/local   # 進入的是我們在dockerfile設定的工作目錄

# 檢視映象構建過程
docker history 映象名/ID
(2)CMD與ENTRYPOINT區別

兩者都是容器啟動的時候要執行的命令,CMD命令只有最後一個會生效,後面不支援追加命令,會被替代。ENTRYPOINT不會被替代,可以追加命令。

CMD

# vim cmd
FROM centos
CMD ["ls","-a"]

# 構建
docker build -f cmd -t cmd_test .

# 執行,發現我們的ls -a命令生效
docker run cmd_test
.
..
.dockerenv
bin
dev
etc
......

# 追加命令執行
docker run cmd_test -l
# 丟擲錯誤,不能追加命令,原來的ls -a命令被-l替換,但是-l不是一個有效的命令
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.

# 追加完整的命令
docker run cmd_test ls -al

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:36 .
drwxr-xr-x  1 root root 4096 Dec 10 14:36 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:36 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:36 dev
......

ENTRYPOINT

# vim entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]

# 構建
docker build -f entrypoint -t entrypoint_test .

# 執行
docker run entrypoint_test
.
..
.dockerenv
bin
dev
etc

# 追加命令執行
docker run entrypoint_test -l

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:41 .
drwxr-xr-x  1 root root 4096 Dec 10 14:41 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:41 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:41 dev
drwxr-xr-x  1 root root 4096 Dec 10 14:41 etc
......

4、實戰構建tomcat

(1)環境準備
 ll
total 166472
-rw-r--r-- 1 root root  11437266 Dec  9 16:22 apache-tomcat-9.0.40.tar.gz
-rw-r--r-- 1 root root       641 Dec 10 23:26 Dockerfile
-rw-r--r-- 1 root root 159019376 Dec  9 17:39 jdk-8u11-linux-x64.tar.gz
-rw-r--r-- 1 root root         0 Dec 10 22:48 readme.txt
(2)構建映象
# vim Dockerfile (Dockerfile是官方建議命名)

FROM centos
MAINTAINER Negan<[email protected]>

COPY readme.txt /usr/local/readme.txt

ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
ADD apache-tomcat-9.0.40.tar.gz /usr/local/

RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out

# 構建
docker -t tomcat .
(3)啟動容器
docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat

九、釋出自己的映象

1、docker hub

首先需要在DockerHub上註冊自己的賬號,並且確定這個賬號可以登入。

在我們的伺服器上進行登入,登入成功後提交我們的映象。

# 登入
docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
  -p, --password string   Password
      --password-stdin    Take the password from stdin
  -u, --username string   Username


# 登入成功後推送我們的映象
docker push [OPTIONS] NAME[:TAG]  

Push an image or a repository to a registry

Options:
      --disable-content-trust   Skip image signing (default true)

docker tag tomcat huiyichanmian/tomcat  # 需要改名,推送改名後的(前面加自己的使用者名稱)
docker push huiyichanmian/tomcat

2、阿里雲

登入阿里雲,找到容器映象服務,使用映象倉庫。建立名稱空間,建立映象倉庫。選擇本地倉庫。

具體阿里雲上有特別詳細的步驟,這裡不再贅述了。

十、Docker網路

1、理解docker0

(1)檢視本機網絡卡資訊
ip addr 

# 本地迴環地址
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
       
# 阿里雲內網地址
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286793195sec preferred_lft 286793195sec
    
# docker0地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

(2)檢視容器網絡卡資訊

我們獲取一個tomcat映象,用來測試。

docker run -d -P --name t1 tomcat 

docker exec -it t1 ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# 我們發現容器啟動時候會得到一個eth0@ifxxx的網絡卡,而且ip地址和上面的docker0裡的是在同一網段。
233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
(3)、再次檢視本地網絡卡資訊
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286792020sec preferred_lft 286792020sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
# 我們發現多了一條網絡卡資訊,而且與容器裡面的網絡卡有某種對應關係。(233,234)
234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default

我們重複上面的操作,不難發現我們只要安裝了docker,本地網卡里就會多一個docker0,而且我們每啟動一個容器,docker就會給容器分配一個網絡卡,而且在本地也會多出一個網絡卡資訊,與容器內的相對應。這就是veth_pair技術,就是一對虛擬裝置介面,它們都是成對出現,一段連著協議,一段彼此相連。正因為有了這個特性,我們通常veth_pair充當了一個橋樑,連線各種虛擬網路裝置。

所有的容器不指定網路的情況下,都是由docker0路由的,docker會給我們的容器分配一個預設的ip。

Docker使用的是Linux的橋接,宿主機中docker0是一個Docker容器的網橋。所有的網路介面都是虛擬的。

只要容器刪除,對應的網橋也響應的被刪除。

問題:我們每次重啟容器,容器的ip地址會變,而我們專案中一些配置使用的固定ip,這時候也需要做相應的改變,那麼我們可不可以直接設定服務名呢,下次重啟時候,配置直接找服務名。

我們啟動兩個tomcat,進行測試,互相ping其對應的名字,看是否能通?

docker exec -it t1 ping t2
ping: t2: Name or service not known
# 答案是肯定的,不能識別t2,如何解決?
# 我們使用--link進行連線
docker run -d -P --name t3 --link t2 tomcat

# 我們嘗試使用t3來ping t2
docker exec -it t3 ping t2
# 我們發現竟然通了
PING t2 (172.17.0.3) 56(84) bytes of data.
64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
......

那麼--link做了什麼呢?

docker exec -it t3 cat /etc/hosts  # 我們來檢視t3的hosts檔案

127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	t2 6bf3c12674c8  # 原因在這了,這裡對t2做了標記,當ping t2時候,自動轉到172.17.0.3
172.17.0.4	b6dae0572f93

3、自定義網路

(1)檢視docker網路
docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
afc0c673386f        none                null                local

# bridge 橋接(docker預設)
# host 與主機共享
# none 不配置
(2)容器啟動時的預設網路

docker0是我們預設的網路,不支援域名訪問,可以使用--link打通。

 # 一般我們啟動容器是這樣子,使用的是預設網路,而這個預設網路就是bridge,所以下面兩條命令是相同的
docker run -d -P --name t1 tomcat  

docker run -d -P --name t1 --net bridge tomcat 
(3)建立網路
# --driver bridge 預設,可以不寫
# --subnet 192.168.0.0/16 子網掩碼
# --gateway 192.168.0.1 預設閘道器
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

docker network ls

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
0e98462f3e8e        mynet               bridge              local  # 我們自己建立的網路
afc0c673386f        none                null                local


ip addr

.....
# 我們自己建立的網路
239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
       valid_lft forever preferred_lft forever
.....

啟動兩個容器,使用我們自己建立的網路

docker run -P -d --name t1 --net mynet tomcat
docker run -P -d --name t2 --net mynet tomcat

# 檢視我們自己建立的網路資訊
docker network mynet inspect

# 我們發現我們剛啟動的兩個容器使用的是我們剛建立的網路
......
"Containers": {
            "1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
                "Name": "t2",
                "EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
                "Name": "t1",
                "EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
.......

那麼使用自己建立的網路有什麼好處呢?

我們回到我們以前的問題,就是域名ping不通的問題上。

docker exec -it t1 ping t2
PING t2 (192.168.0.3) 56(84) bytes of data.
64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
......

docker exec -it t2 ping t1
PING t1 (192.168.0.2) 56(84) bytes of data.
64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
......

我們發現域名可以ping通了,那麼說明我們自定義的網路已經幫我們維護好了對應關係。

這樣不同的叢集使用不同的網路,也能保證叢集的安全和健康。

4、網路連通

現在有這樣一個需求,t1,t2使用的是我們自己定義的網路,t3,t4使用的預設的docker0網路。那麼現在t3和t1或者t2能否通訊呢?

我們知道,docker0的預設閘道器是172.17.0.1,mynet預設閘道器是192.168.0.1,他們直接屬於不同的網段,不能進行通訊。那麼現在如何解決上面的問題呢?

那麼mynet能不能給t3分配一個ip地址呢?如果能分配,那麼問題就應該可以得到解決。

docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
      --alias strings           Add network-scoped alias for the container
      --driver-opt strings      driver options for the network
      --ip string               IPv4 address (e.g., 172.30.100.104)
      --ip6 string              IPv6 address (e.g., 2001:db8::33)
      --link list               Add link to another container
      --link-local-ip strings   Add a link-local address for the container
   
  # 一個容器兩個ip地址 
 docker network connect mynet t3   # 將t3加入到mynet網路中
 
 # 檢視mynet資訊
 docker network inspect mynet
 
 "Containers": {
			......
            "d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
                "Name": "t3",  # 我們發現了t3
                "EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
                "MacAddress": "02:42:c0:a8:00:04",
                "IPv4Address": "192.168.0.4/16",
                "IPv6Address": ""
            }
        },
        ......

這時候,t3就能和t1,t2進行通訊了。

5、部署Redis叢集

# 建立網路
docker network create redis --subnet 172.38.0.0/16

# 通過指令碼建立六個redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379 
bind 0.0.0.0
cluster-enabled yes 
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

# 啟動容器
vim redis.py

import os
for i in range(1, 7):
    str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} \
    -v /mydata/redis/node-{}/data:/data \
    -v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf \
    -d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
    os.system(str)

python reidis.py

# 建立叢集
docker exec -it redis-1 /bash/sh  # 進入redis-1容器

redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

# 建立叢集。。。。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   slots: (0 slots) slave
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   slots: (0 slots) slave
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   slots: (0 slots) slave
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.


# 進行測試
redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:160
cluster_stats_messages_pong_sent:164
cluster_stats_messages_sent:324
cluster_stats_messages_ping_received:159
cluster_stats_messages_pong_received:160
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:324

十一、Docker Compose

1、介紹

Compose是Docker官方開源的專案,需要安裝。

Compose是用於定義和執行多容器Docker應用程式的工具。通過Compose,可以使用YAML檔案來配置應用程式的服務。然後,使用一個命令,就可以從配置中建立並啟動所有服務。

使用Compose基本上是一個三步過程:

  1. Dockerfile保證我們的專案在任何地方執行
  2. docker-compose檔案
  3. 啟動

2、快速開始

(1)安裝
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

docker-compose --version
# 安裝成功
# docker-compose version 1.27.4, build 40524192
(2)使用
# 為專案建立目錄
mkdir composetest
cd composetest

# 編寫一段flask程式
vim app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)  # 這裡主機名直接用“redis”,而不是ip地址

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
    
# 編寫requirements.txt檔案(不指定版本,下載最新)
flask
redis

# 編寫Dockerfile檔案
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]


# 編寫docker-compose.yml檔案
# 檔案中定義了兩個服務,web和redis,web是從Dockerfile開始構建,redis使用的是公共映象
version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
  redis:
    image: "redis:alpine"
    
# 執行
docker-compose up 

3、搭建部落格

# 建立並進入目錄
mkdir wordpress && cd wordpress

# 編寫docker-compose.yml檔案來啟動,並建立了一個單獨Mysql例項,具有用於資料永續性的卷掛載
version: "3.3"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      W0RDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
  volumes:
    db_data: {}
    

# 啟動執行
docker-compose up -d

十二、Docker Swarm

1、環境準備

準備四臺伺服器。安裝docker。

2、swarm叢集搭建

docker swarm COMMAND
Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm   # 初始化一個節點(管理節點)
  join        Join a swarm as a node and/or manager  # 加入節點
  join-token  Manage join tokens   # 通過節點token加入
  leave       Leave the swarm    # 離開節點
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm  #更新

# 首先我們初始化一個節點
docker swarm init --advertise-addr + 自己的ip(這裡用內網地址,主要是省錢)
docker swarm init --advertise-addr 172.27.0.4

# 提示我們節點建立成功
Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.

To add a worker to this swarm, run the following command:
	
	# 在其他機器上執行,加入這個節點
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 生成一個管理節點的token,
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

# 我們在機器2上行加入節點的命令
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 我們在機器1上檢視節點資訊
docker node ls
# 我們發現一個管理節點,一個工作節點,狀態都是就緒狀態
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos   Ready     Active         Leader           20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos   Ready     Active                          20.10.0

# 現在將機器3也加入,同樣是work節點
# 到此為止,現在只有機器4沒有加入,這時候我們想把它設定成管理節點。
# 在機器1上執行生成管理節點的命令,在機器4上執行
docker swarm join-token manager
# 生成的命令在機器4上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 此時機器4也是管理節點了
This node joined a swarm as a manager.

# 至此我們可以在機器1或者機器4上進行其他操作了。(只能在管理節點上操作)

3、Raft協議

在前面的步驟,我們已經完成了雙主雙從叢集的搭建。

Raft協議:保證大多數節點存活才能使用。必須大於1,叢集至少大於3臺。

實驗1

將機器1上的docker停止,宕機,現在叢集中只有一個管理節點了。叢集是否可用。

#我們在機器四上檢視節點資訊
docker node ls
# 發現我們的叢集已經不能用了
Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded

# 我們將機器1上的docker重啟,發現叢集又能用了,但是此時機器1上的docker已經不是leader了,leader自動轉給機器4了

實驗2

我們將工作節點離開叢集,檢視叢集資訊

# 我們在機器2上執行
docker swarm leave

# 在機器一上檢視節點資訊
docker node ls
# 我們發現機器2狀態是Down
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos    Ready     Active         Reachable        20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

實驗3

現在我們將機器2也設定成管理節點(此時叢集有三個管理節點),隨機down掉一個管理節點,叢集是否正常執行。

# 在機器2上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377

# 將機器1的docker宕機
systemctl stop docker

# 在機器二上檢視節點資訊
docker node ls
# 叢集正常,且可以看到機器1宕機了
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm     VM-0-4-centos    Ready     Active         Unreachable      20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
vdwcwr3v6qrn6da40zdrjkwmy *   VM-0-7-centos    Ready     Active         Reachable        20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

4、彈性建立服務

docker service COMMAND

Commands:
  create      Create a new service  # 建立一個服務
  inspect     Display detailed information on one or more services  # 檢視服務資訊
  logs        Fetch the logs of a service or task  # 日誌
  ls          List services   # 列表
  ps          List the tasks of one or more services   # 檢視我們的服務
  rm          Remove one or more services  # 刪除服務
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services  # 動態擴縮容
  update      Update a service  # 更新

docker service create -p 8888:80 --name n1 nginx  # 建立一個服務,在叢集中隨機分配
kj0xokbxvf5uw91bswgp1cukf
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

# 給我們的服務增加三個副本
docker service update --replicas 3 n1

# 動態擴縮容
docker service scale n1=10   # 和上面updata一樣,這個方便

docker ps # 檢視

服務,叢集中任意的節點都可以訪問,服務可以有多個副本動態擴縮容實現高可用。

5、使用Docker stack部署部落格

我們現在需要將上面的部落格跑在我們的叢集中,並且要開十個副本。

# 編輯docker-compose.yml檔案
version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     deploy:
       replicas: 10
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}


# 進行啟動
docker stack deploy -c docker-compose.yml wordpress

# 檢視
docker service ls

一、安裝

環境:Centos7

1、解除安裝舊版本

較舊的Docker版本稱為dockerdocker-engine。如果已安裝這些程式,請解除安裝它們以及相關的依賴項。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2、使用儲存庫安裝

注意:官方給了三種安裝方式,這裡我們選擇最常用的儲存庫安裝

在新主機上首次安裝Docker Engine之前,需要設定Docker儲存庫。之後可以從儲存庫安裝和更新Docker。

sudo yum install -y yum-utils

# 注意:此處是大坑,可以自行換成阿里的源
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    
# 我們用阿里的    
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
 
# 進行安裝(如果不換源,速度會很慢)
sudo yum install docker-ce docker-ce-cli containerd.io

3、啟動以及驗證

systemctl start docker   # 啟動
systemctl status docker  #  檢視狀態
docker version  # 檢視版本
docker run hello-world   #  測試

4、設定加速器(阿里雲)

注意:裡面的地址每個人都有專屬的地址。

開啟阿里雲官網->控制檯->搜尋容器映象服務->右下角找到映象加速器

sudo mkdir -p /etc/docker

tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

docker run hello-world

5、解除安裝

# 解除安裝Docker Engine,CLI和Containerd軟體包
sudo yum remove docker-ce docker-ce-cli containerd.io

# 主機上的映像,容器,卷或自定義配置檔案不會自動刪除。要刪除所有影象,容器和卷
sudo rm -rf /var/lib/docker

二、Docker三要素

1、倉庫(Repository)

倉庫:集中存放映象的場所。

注意:倉庫(Repository)和倉庫註冊伺服器(Registry)是有區別的,倉庫註冊伺服器往往存放著多個倉庫,每個倉庫中又包含多個映象,每個映象有不同的標籤(tag)

倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。

最大的公開倉庫是Docker Hub(https://hub.docker.com),存放了數量龐大的映象供使用者下載,國內的公開倉庫包括阿里雲、網易雲等。

2、映象(Image)

一個只讀模板,用來建立Docker容器,一個映象可以建立很多個容器。

容器與映象的關係類似於面向物件程式設計中的物件和類

Docker 面向物件
容器 物件
映象

3、容器 (Container)

獨立執行的一個或一組應用。

容器使用映象建立的執行例項。

它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的,保證安全的平臺。

可以把容器看作是一個簡易版的Linux環境(包括root使用者許可權,程序空間,使用者空間和網路空間等等)和執行在啟動的應用程式。

容器的定義和映象幾乎一模一樣,也是一堆層的統一視角,唯一區別在於容器的最上面那一層是可讀可寫的。

三、簡單瞭解底層原理

1、Docker是怎麼工作的

Docker是一個Client-Server結構的系統,Docker守護程序執行在主機上,然後通過Socket連線從客戶端訪問,守護程序從客戶端接收命令並管理執行在主機上的容器。

2、Docker為什麼比VM快?

(1)Docker有著比虛擬機器更少的抽象層,由於Docker不需要Hypervisor實現硬體資源虛擬化,執行在Docker容器上的程式直接使用的都是實際物理機的硬體資源,因此在CPU、記憶體利用率上Docker將會在效率上有明顯的優勢。

(2)Docker利用的是宿主機的核心,而不需要Guest OS。因此當新建一個容器時,Docker不需要和虛擬機器一樣重疊載入一個作業系統核心,從而避免引尋、載入作業系統核心,返回比較費時費資源的過程。當新建一個虛擬機器時,虛擬機器軟體需要載入Guest OS,這個新建過程是分鐘級別的,而Docker由於直接利用宿主機的作業系統,因此新建一個Docker容器只需幾秒鐘。

Docker容器 虛擬機器(VM)
作業系統 與宿主機共享OS 宿主機OS上執行虛擬機器OS
儲存大小 映象小,便於儲存與傳輸 映象龐大(vmdk,vdi等)
執行效能 幾乎無額外效能損失 作業系統額外的CPU、記憶體消耗
移植性 輕便、靈活,適應於Linux 笨重,與虛擬化技術耦合度高
硬體親和性 面向軟體開發者 面向硬體運維者

四、相關命令

1、幫助命令

docker version  # 檢視docker版本資訊
docker info  # 詳細說明
docker --help  # 幫助命令

2、映象命令

(1)列出本地映象
docker images [OPTIONS] [REPOSITORY[:TAG]]

# OPTIONS說明:
	-a: 列出本地所有的映象(含中間映像層)
    -q: 只顯示映象ID
    --digests: 顯示映象的摘要資訊
    --no-trunc: 顯示完整的映象資訊

# 各個選項說明:
# REPOSITORY:表示映象的倉庫源
# TAG:映象的標籤
# IMAGE ID:映象ID
# CREATED:映象建立時間
# SIZE:映象大小	

同一倉庫源可以有多個TAG,代表這個倉庫源的不同版本,我們使用REPOSITORY:TAG來定義不同的映象,如果不指定一個映象的版本標籤,例如我們只使用ubuntu,docker將預設使用ubuntu:latest映象。

(2)查詢映象
docker search [options] 某個xxx映象名字  # 會在https://hub.docker.com上去查詢

docker search mysql --filter=STARS=3000  # 搜尋星數大於等於3000的映象

# OPTIONS說明:
# --no-trunc: 顯示完整的映象描述	
# -s:列出點贊數不小於指定值的映象
# --automated: 只列出automated build型別的映象
(3)獲取映象
docker pull 映象名字[:TAG]  # 如果不寫TAG,則預設獲取latest,此時從我們配置的阿里雲上獲取映象

docker pull mysql  # 獲取最新版本的mysql
Using default tag: latest
latest: Pulling from library/mysql
852e50cd189d: Pull complete   # 分卷下載
29969ddb0ffb: Pull complete 
a43f41a44c48: Pull complete 
5cdd802543a3: Pull complete 
b79b040de953: Pull complete 
938c64119969: Pull complete 
7689ec51a0d9: Pull complete 
a880ba7c411f: Pull complete 
984f656ec6ca: Pull complete 
9f497bce458a: Pull complete 
b9940f97694b: Pull complete 
2f069358dc96: Pull complete 
Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest  
# docker pull mysql 等價於docker pull docker.io/library/mysql:latest 

docker pull mysql:5.7  # 下載指定版本的映象
5.7: Pulling from library/mysql
852e50cd189d: Already exists  # 聯合檔案系統,已經存在的不會去重複下載
29969ddb0ffb: Already exists 
a43f41a44c48: Already exists 
5cdd802543a3: Already exists 
b79b040de953: Already exists 
938c64119969: Already exists 
7689ec51a0d9: Already exists 
36bd6224d58f: Pull complete 
cab9d3fa4c8c: Pull complete 
1b741e1c47de: Pull complete 
aac9d11987ac: Pull complete 
Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
(4)刪除映象
# 刪除單個映象
docker rmi -f 映象名/ID  # 如果是映象名,後面不帶TAG,則預設刪除latest

# 刪除多個映象
docker rmi -f 映象名1 映象名2 ...
docker rmi -f id1 id2 ...     // 注意:兩種方式不能混用

# 刪除全部映象
docker rmi -f $(docker images -aq)   

3、容器命令

​ 有映象才能建立容器,這是根本前提(下載一個Centos映象演示)

(1) 新建並啟動容器(互動式)
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run -it --name mycentos centos  // 如果不指定別名,系統會自動分配

# OPTIONS說明:
# --name 容器新名字:為容器指定一個名稱
# -d:後臺執行容器,並返回容器ID,即啟動守護式容器
# -i: 以互動模式執行容器,通常與-t同時使用
# -t: 為容器重新分配一個偽輸入終端,通常與-i同時使用
# -P: 隨機埠對映
# -p: 指定埠對映有以下四種格式
		ip:hostPort:containerPort
		ip::containerPort
		hostPort:containerPort
		containerPort

# 測試
[root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash  # 啟動並進入容器
[root@783cb2f26230 /]# ls   # 在容器內檢視
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@783cb2f26230 /]# exit   # 退出容器
exit
[root@iz2zeaj5c9isqt1zj9elpbz ~]# 

(2)列出當前所有正在執行的容器
docker ps [OPTIONS]  # 不帶OPTIONS,則只列出正在執行的容器

# OPTIONS說明(常用):
# -a:列出當前所有正在執行的容器以及歷史上執行過的
# -l:顯示最近建立的容器
# -n: 顯示最近n個建立的容器
# -q: 靜默模式,只顯示容器編號
# --no-trunc:不截斷輸出
(3)退出容器
exit  // 直接關閉並退出容器

重新開啟一個終端,執行docker ps -l,會返回剛才我們建立的容器資訊,並且STATUS會提示已經退出。

那麼可不可以在互動式,不關閉容器的情況下,暫時退出,一會又可以回來呢?

Ctrl + P + Q

# 執行後,我們將退出容器,回到宿主機,使用docker ps -l,我們會發現這個剛才退出的容器STATUS是Up狀態。
(4)啟動容器
docker start [OPTIONS] CONTAINER [CONTAINER...]

# 同時可以啟動多個容器,容器名和ID可以混用

# OPTION說明(常用):
# -i : 進入互動式,此時只能進入一個容器

進入上面我們退出但是依然存活的容器

docker start -i 186ae928f07c
(5)重啟容器
docker restart [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死容器之前等待停止的時間(預設為10)
(6) 停止容器
docker stop [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死前等待停止的時間(預設為10)

docker kill [OPTIONS] CONTAINER [CONTAINER...]  // 強制關閉(相當於拔電源)

# OPTIONS說明:
# -s: 傳送到容器的訊號(預設為“KILL”)
(7)刪除容器
docker rm [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -f :強制刪除,不管是否在執行

# 上面的命令可以刪除一個或者多個,但是想全部刪除所有的容器,那麼我們要把全部的容器名或者ID都寫一遍嗎?

docker rm -f $(docker ps -aq)  # 刪除所有
docker ps -aq | xargs docker rm -f
(8) 啟動守護式容器
docker run -d 容器名/容器ID

說明:我們使用docker ps -a命令檢視,會發現剛才啟動的容器以及退出了,這是為什麼呢?

很重要的要說明一點:Docker容器後臺執行,必須要有一個前臺程序,容器執行的命令如果不是那些一直掛起的命令(比如執行top,tail),就會自動退出。

這就是Docker的機制問題,比如我們現在執行WEB容器,以Nginx為例,正常情況下,我們配置啟動服務只需要啟動響應的service即可,例如service nginx start,但是這樣做,Nginx為後臺程序模式執行,導致Docker前臺沒有執行的應用。這樣的容器後臺啟動後,會立即自殺因為它覺得它沒事可做。所以最佳的解決方案是將要執行的程式以前臺程序的形式執行。

那麼如何讓守護式容器不自動退出呢?我們可以執行一直掛起的命令。

docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
(9) 檢視容器日誌
docker logs [OPTIONS] CONTAINER

# OPTION說明:
# -t 加入時間戳
# -f 跟隨最新的日誌列印
# --tail 顯示最後多少條
(10) 檢視容器內的程序
docker top CONTAINER 
(11) 檢視容器內部細節
docker inspect CONTAINER
(12) 進入正在執行的容器並以命令列互動
  • exec在容器中開啟新的終端,並且可以啟動新的程序
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]  

docker exec -it CONTAINER /bin/bash   // 進入容器內部並互動

# 隔山打牛
docker exec -it 3d00a0a2877e ls -l /tmp 

# 3d00a0a2877e是我上面跑的守護式容器
# 進入容器,執行ls -l /tmp 命令後將結果返回給我宿主機,而使用者的介面還是停留在宿主機,沒有進入容器內部
  • attach直接進入容器啟動命令的終端,不會啟動新的程序
docker attach CONTAINER

# 注意,進入上面那個守護式容器後,我們會看到還是每隔兩秒列印hello Negan,而且不能退出,只能重新開啟一個終端,執行docker kill 命令
(13)容器與主機間的檔案拷貝
docker cp CONTAINER:SRC_PATH DEST_PATH  # 把容器內的檔案拷貝到宿主機
docker cp 90bd03598dd4:123.txt ~

docker cp SRC_PATH CONTAINER:DEST_PATH  # 把宿主機上的檔案拷貝到容器
docker cp 12345.txt 90bd03598dd4:~

練習

練習一:Docker部署Nginx

# 查詢一個nginx映象
docker search nginx
# 下載映象
docker pull nginx
# 啟動
docker run -d --name nginx01 -p 3344:80 nginx  # -p 3344(宿主機):80(容器埠)
# 測試(瀏覽器訪問)
123.56.243.64:3344 

五、視覺化

1、portainer

Docker圖形介面管理工具,提供一個後臺面板供我們操作。

docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer

六、Docker映象

1、映象是什麼

映象是一種輕量級、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體,它包含執行某個軟體所需的所有內容,包括程式碼、執行時、庫、環境變數和配置檔案。

所有應用直接打包docker映象,就可以直接跑起來。

如何獲取映象
  • 從遠端操作下載
  • 朋友拷貝
  • 自己製作一個映象DockerFile

2、映象載入原理

(1)聯合檔案系統

UnionFS(聯合檔案系統):Union檔案系統是一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改作為一次提交來一層一層疊加,同時可以將不同目錄掛在到同一個虛擬檔案系統下。聯合檔案系統是Docker映象的基礎,映象可以通過分層來進行繼承,基於基礎映象(沒有父映象),可以製作各種具體的應用映象。

特性:一次同時載入多個檔案系統,但從外面看起來,只能看到一個檔案系統,聯合載入會把各層檔案系統疊加起來,這樣最終的檔案系統會包含所有底層的檔案和目錄。

(2)Docker映象載入原理

Docker的映象實際上是由一層一層的檔案系統組成,也就是上面所說的聯合檔案系統。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引導載入kernel,Linux剛啟動時會載入bootfs檔案系統,在Docker映象最底層是bootfs。這一層與我們典型的Linux/Unix系統是一樣的,包含boot載入器和核心。當boot載入完成之後整個核心就都在記憶體中,此時記憶體的使用權已由bootfs轉交給核心,此時系統也會解除安裝bootfs。

rootfs(root file system),在bootfs之上,包含的就是典型Linux系統中的/dev,/proc,/bin,/etc等標準目錄和檔案。rootfs就是各種不同的作業系統發行版,比如Ubuntu,CentOS等等。

對於一個精簡的OS,rootfs可以很小,只需要包含最近本的命令,工具和程式庫就可以,因為底層直接用Host的kernel,自己只需要提供rootfs就可以,由此可見對於不同的Linux發行版,bootfs基本是一致的,rootfs會有差別,因此不同的發行版可以公用bootfs。

3、分層理解

(1)分層的映象

我們下載一個映象,注意觀察下載的日誌輸出,可以看到是一層一層在下載。

為什麼Docker映象採用這種分層的結構?

最大的好處,我覺得莫過於資源共享了,比如有多個映象從相同的Base映象構建而來,那麼宿主機只需要在磁碟上保留一份Base映象,同時記憶體中也只需要載入一份Base映象,這樣就可以為所有的容器服務了,而且映象每一層都可以被共享。

(2)理解

所有的Docker映象都始於一個基礎映象層,當進行修改或者增加新內容時,就會在當前映象層之上,建立新的映象層。假如基於Ubuntu Linux 16.04建立一個新的映象,這就是新映象的第一層;如果在該映象中新增Python包,就會在基礎映象之上建立第二個映象層,如果繼續新增一個安全補丁,就會建立第三個映象層。

再新增額外的映象層同時,映象始終保持是當前所有映象的組合。

注意:Docker映象都是隻讀的,當容器啟動時,一個新的可寫層被載入到映象的頂部。

​ 這一層就是我們通常說的容器層,容器之下的都叫映象層。

4、commit

docker commit 提交容器成為一個新的映象

docker commit -m="提交的描述資訊" -a="作者" 容器id 目標映象名:[TAG]

七、容器資料卷

如果資料在容器中,那麼我們容器刪除,資料就會丟失!

需求:資料需要持久化。

MySQL,容器刪了,刪庫跑路?Docker容器中產生的資料,需要同步到本地。

這就是卷技術,目錄的掛載,將我們容器內的目錄,掛載到Linux上。

1、使用資料卷

  • 直接使用命令來掛載
docker run -it -v 主機目錄:容器內目錄  # 雙向繫結,一方改變另一方自動改變

docker run -it -v /home/ceshi:/home centos /bin/bash

docker inspect d2343e9d338a  # 進行檢視

/*
...
 "Mounts": [   # 掛載 -v 
            {
                "Type": "bind",
                "Source": "/home/ceshi",  # 主機內的路徑
                "Destination": "/home",   # 容器內的路徑
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...
*/

2、實戰:安裝MySQL

思考:MySQL的資料持久化問題

docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7

3、具名掛載和匿名掛載

(1) 、匿名掛載
docker run -d -P --name nginx01 -v /etc/nginx nginx  # 只指定了容器內的路徑

docker volume ls  # 檢視所有volume的情況
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a  # 匿名掛載
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049

# 這種就是匿名掛載,我們在-v只寫了容器內的路徑,沒有寫容器外的路徑。
(2)、具名掛載
docker run -d -P -v juming:/etc/nginx --name nginx02 nginx   # 具名掛載

docker volume ls
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
local               juming  # 我們上面起的名字

# 通過-v 卷名:容器內路徑

# 所有的docker容器內的卷,沒有指定目錄的情況下,都在/var/lib/docker/volumes目錄下。
docker inspect juming  
[
    {
        "CreatedAt": "2020-12-09T00:22:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/juming/_data",
        "Name": "juming",
        "Options": null,
        "Scope": "local"
    }
]

我們通過具名掛載可以方便的找到我們掛載的卷,大多情況下我們都會使用具名掛載。

(3)、拓展
-v 容器內路徑  # 匿名掛載
-v 卷名:容器內路徑 # 具名掛載
-v /宿主機路徑:容器內路徑  # 指定路徑掛載

# 通過 -V 容器內路徑:ro rw改變讀寫許可權
ro readonly # 只讀
rw readwrite # 可讀可寫

# 一旦設定了只讀,容器對我們掛載出來的內容就有限定了。檔案只能通過宿主機來操作,容器無法操作。
docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx 

4、資料卷容器

容器間的資訊同步。

docker run -it -v /home --name c1 centos /bin/bash   # 啟動c1,作為一個父容器(指定容器內掛載目錄)
docker run -it --name c2 --volumes-from c1 centos /bin/bash  # 啟動c2,掛載c1,此時在兩個容器中/home中的操作就會同步


# 下面的容器是我們自己建立的,可以參考下面dockerfile進行構建
docker run -it --name docker01 negan/centos   # 啟動一個容器作為父容器(被掛載的容器)  
docker run -it --name docker02 --volumes-from docker01 negan/centos  #啟動容器2,掛載容器1 
docker run -it --name docker03 --volumes-from docker02 negan/centos  #啟動容器3,掛載容器2
# 上面的容器3也可以直接掛載到容器1上,然後我們進入任意一個容器,進入掛載卷volume01/volume02,進行操作,容器間的資料會自動同步。

結論:

容器之間配置資訊的傳遞,資料卷容器的宣告週期一直持續到沒有容器使用為止。

但是一旦持久到了本地,本地資料不會被刪除。

八、DockerFile

1、初識DockerFile

DockerFile就是用來構造docker映象的構建檔案,命令引數指令碼。通過這個指令碼,可以生成映象。映象是一層一層的,指令碼是一個個命令,每個命令就是一層。

# dockerfile1內容,所有命令都是大寫

FROM centos   # 基於centos

VOLUME ["volume01","volume02"]   # 掛載資料卷(匿名掛載)

CMD echo "----end-----"

CMD /bin/bash


# 構建
docker build -f dockerfile1 -t negan/centos .  # -f 指定路徑 -t指定名字,不加tag預設最新
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
 ---> 0d120b6ccaa8
Step 2/4 : VOLUME ["volume01","volume02"]
 ---> Running in 0cfe6b5be6bf
Removing intermediate container 0cfe6b5be6bf
 ---> 396a4a7cfe15
Step 3/4 : CMD echo "----end-----"
 ---> Running in fa535b5581fa
Removing intermediate container fa535b5581fa
 ---> 110d9f93f827
Step 4/4 : CMD /bin/bash
 ---> Running in 557a2bb87d97
Removing intermediate container 557a2bb87d97
 ---> c2c9b92d50ad
Successfully built c2c9b92d50ad
Successfully tagged negan/centos:latest

docker images  # 檢視

2、DokcerFile構建過程

(1)、基礎知識

每個保留關鍵字(指令)都必須是大寫字母

執行從上到下順序執行

#表示註釋

每一個指令都會建立提交一個新的映象層,並提交

DockerFile是面向開發的,我們以後要釋出專案,做映象,就需要編寫dockerfile檔案。

Docker映象逐漸成為企業交付的標準。

(2)、基本命令
FROM  #這個映象的媽媽是誰?(基礎映象,一切從這裡開始構建)
MAINTAINER # 告訴別人誰負責養他?(映象是誰寫的,指定維護者資訊,姓名+郵箱)
RUN  # 你想讓他幹啥?(映象構建的時候需要執行的命令)
ADD  # 給他點創業資金(複製檔案,會自動解壓)
WORKDIR # 映象的工作目錄
VOLUME  # 給他一個存放行李的地方(設定卷,容器內掛載到主機的目錄,匿名掛載)
EXPOSE  # 門牌號是多少?(指定對外埠)
CMD   # 指定這個容器啟動的時候要執行的命令,只有最後一個會生效,可被替代
ENTRYPOINT # 指定這個容器啟動時候要執行的命令,可以追加命令
ONBUILD  # 當構建一個被繼承DockerFile,這時就會執行ONBUILD指令
COPY    # 類似ADD,將我們檔案拷貝到映象中
ENV    # 構建的時候設定環境變數

3、實戰操作

(1)建立一個自己的CentOS
# vim Dockerfile

FROM centos
MAINTAINER Negan<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 構建
docker build -f Dockerfile -t negan/centos .

# 測試
docker run -it negan/centos
[root@ffae1f9eb97e local]# pwd
/usr/local   # 進入的是我們在dockerfile設定的工作目錄

# 檢視映象構建過程
docker history 映象名/ID
(2)CMD與ENTRYPOINT區別

兩者都是容器啟動的時候要執行的命令,CMD命令只有最後一個會生效,後面不支援追加命令,會被替代。ENTRYPOINT不會被替代,可以追加命令。

CMD

# vim cmd
FROM centos
CMD ["ls","-a"]

# 構建
docker build -f cmd -t cmd_test .

# 執行,發現我們的ls -a命令生效
docker run cmd_test
.
..
.dockerenv
bin
dev
etc
......

# 追加命令執行
docker run cmd_test -l
# 丟擲錯誤,不能追加命令,原來的ls -a命令被-l替換,但是-l不是一個有效的命令
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.

# 追加完整的命令
docker run cmd_test ls -al

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:36 .
drwxr-xr-x  1 root root 4096 Dec 10 14:36 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:36 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:36 dev
......

ENTRYPOINT

# vim entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]

# 構建
docker build -f entrypoint -t entrypoint_test .

# 執行
docker run entrypoint_test
.
..
.dockerenv
bin
dev
etc

# 追加命令執行
docker run entrypoint_test -l

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:41 .
drwxr-xr-x  1 root root 4096 Dec 10 14:41 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:41 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:41 dev
drwxr-xr-x  1 root root 4096 Dec 10 14:41 etc
......

4、實戰構建tomcat

(1)環境準備
 ll
total 166472
-rw-r--r-- 1 root root  11437266 Dec  9 16:22 apache-tomcat-9.0.40.tar.gz
-rw-r--r-- 1 root root       641 Dec 10 23:26 Dockerfile
-rw-r--r-- 1 root root 159019376 Dec  9 17:39 jdk-8u11-linux-x64.tar.gz
-rw-r--r-- 1 root root         0 Dec 10 22:48 readme.txt
(2)構建映象
# vim Dockerfile (Dockerfile是官方建議命名)

FROM centos
MAINTAINER Negan<[email protected]>

COPY readme.txt /usr/local/readme.txt

ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
ADD apache-tomcat-9.0.40.tar.gz /usr/local/

RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out

# 構建
docker -t tomcat .
(3)啟動容器
docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat

九、釋出自己的映象

1、docker hub

首先需要在DockerHub上註冊自己的賬號,並且確定這個賬號可以登入。

在我們的伺服器上進行登入,登入成功後提交我們的映象。

# 登入
docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
  -p, --password string   Password
      --password-stdin    Take the password from stdin
  -u, --username string   Username


# 登入成功後推送我們的映象
docker push [OPTIONS] NAME[:TAG]  

Push an image or a repository to a registry

Options:
      --disable-content-trust   Skip image signing (default true)

docker tag tomcat huiyichanmian/tomcat  # 需要改名,推送改名後的(前面加自己的使用者名稱)
docker push huiyichanmian/tomcat

2、阿里雲

登入阿里雲,找到容器映象服務,使用映象倉庫。建立名稱空間,建立映象倉庫。選擇本地倉庫。

具體阿里雲上有特別詳細的步驟,這裡不再贅述了。

十、Docker網路

1、理解docker0

(1)檢視本機網絡卡資訊
ip addr 

# 本地迴環地址
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
       
# 阿里雲內網地址
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286793195sec preferred_lft 286793195sec
    
# docker0地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

(2)檢視容器網絡卡資訊

我們獲取一個tomcat映象,用來測試。

docker run -d -P --name t1 tomcat 

docker exec -it t1 ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# 我們發現容器啟動時候會得到一個eth0@ifxxx的網絡卡,而且ip地址和上面的docker0裡的是在同一網段。
233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
(3)、再次檢視本地網絡卡資訊
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286792020sec preferred_lft 286792020sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
# 我們發現多了一條網絡卡資訊,而且與容器裡面的網絡卡有某種對應關係。(233,234)
234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default

我們重複上面的操作,不難發現我們只要安裝了docker,本地網卡里就會多一個docker0,而且我們每啟動一個容器,docker就會給容器分配一個網絡卡,而且在本地也會多出一個網絡卡資訊,與容器內的相對應。這就是veth_pair技術,就是一對虛擬裝置介面,它們都是成對出現,一段連著協議,一段彼此相連。正因為有了這個特性,我們通常veth_pair充當了一個橋樑,連線各種虛擬網路裝置。

所有的容器不指定網路的情況下,都是由docker0路由的,docker會給我們的容器分配一個預設的ip。

Docker使用的是Linux的橋接,宿主機中docker0是一個Docker容器的網橋。所有的網路介面都是虛擬的。

只要容器刪除,對應的網橋也響應的被刪除。

問題:我們每次重啟容器,容器的ip地址會變,而我們專案中一些配置使用的固定ip,這時候也需要做相應的改變,那麼我們可不可以直接設定服務名呢,下次重啟時候,配置直接找服務名。

我們啟動兩個tomcat,進行測試,互相ping其對應的名字,看是否能通?

docker exec -it t1 ping t2
ping: t2: Name or service not known
# 答案是肯定的,不能識別t2,如何解決?
# 我們使用--link進行連線
docker run -d -P --name t3 --link t2 tomcat

# 我們嘗試使用t3來ping t2
docker exec -it t3 ping t2
# 我們發現竟然通了
PING t2 (172.17.0.3) 56(84) bytes of data.
64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
......

那麼--link做了什麼呢?

docker exec -it t3 cat /etc/hosts  # 我們來檢視t3的hosts檔案

127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	t2 6bf3c12674c8  # 原因在這了,這裡對t2做了標記,當ping t2時候,自動轉到172.17.0.3
172.17.0.4	b6dae0572f93

3、自定義網路

(1)檢視docker網路
docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
afc0c673386f        none                null                local

# bridge 橋接(docker預設)
# host 與主機共享
# none 不配置
(2)容器啟動時的預設網路

docker0是我們預設的網路,不支援域名訪問,可以使用--link打通。

 # 一般我們啟動容器是這樣子,使用的是預設網路,而這個預設網路就是bridge,所以下面兩條命令是相同的
docker run -d -P --name t1 tomcat  

docker run -d -P --name t1 --net bridge tomcat 
(3)建立網路
# --driver bridge 預設,可以不寫
# --subnet 192.168.0.0/16 子網掩碼
# --gateway 192.168.0.1 預設閘道器
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

docker network ls

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
0e98462f3e8e        mynet               bridge              local  # 我們自己建立的網路
afc0c673386f        none                null                local


ip addr

.....
# 我們自己建立的網路
239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
       valid_lft forever preferred_lft forever
.....

啟動兩個容器,使用我們自己建立的網路

docker run -P -d --name t1 --net mynet tomcat
docker run -P -d --name t2 --net mynet tomcat

# 檢視我們自己建立的網路資訊
docker network mynet inspect

# 我們發現我們剛啟動的兩個容器使用的是我們剛建立的網路
......
"Containers": {
            "1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
                "Name": "t2",
                "EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
                "Name": "t1",
                "EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
.......

那麼使用自己建立的網路有什麼好處呢?

我們回到我們以前的問題,就是域名ping不通的問題上。

docker exec -it t1 ping t2
PING t2 (192.168.0.3) 56(84) bytes of data.
64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
......

docker exec -it t2 ping t1
PING t1 (192.168.0.2) 56(84) bytes of data.
64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
......

我們發現域名可以ping通了,那麼說明我們自定義的網路已經幫我們維護好了對應關係。

這樣不同的叢集使用不同的網路,也能保證叢集的安全和健康。

4、網路連通

現在有這樣一個需求,t1,t2使用的是我們自己定義的網路,t3,t4使用的預設的docker0網路。那麼現在t3和t1或者t2能否通訊呢?

我們知道,docker0的預設閘道器是172.17.0.1,mynet預設閘道器是192.168.0.1,他們直接屬於不同的網段,不能進行通訊。那麼現在如何解決上面的問題呢?

那麼mynet能不能給t3分配一個ip地址呢?如果能分配,那麼問題就應該可以得到解決。

docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
      --alias strings           Add network-scoped alias for the container
      --driver-opt strings      driver options for the network
      --ip string               IPv4 address (e.g., 172.30.100.104)
      --ip6 string              IPv6 address (e.g., 2001:db8::33)
      --link list               Add link to another container
      --link-local-ip strings   Add a link-local address for the container
   
  # 一個容器兩個ip地址 
 docker network connect mynet t3   # 將t3加入到mynet網路中
 
 # 檢視mynet資訊
 docker network inspect mynet
 
 "Containers": {
			......
            "d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
                "Name": "t3",  # 我們發現了t3
                "EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
                "MacAddress": "02:42:c0:a8:00:04",
                "IPv4Address": "192.168.0.4/16",
                "IPv6Address": ""
            }
        },
        ......

這時候,t3就能和t1,t2進行通訊了。

5、部署Redis叢集

# 建立網路
docker network create redis --subnet 172.38.0.0/16

# 通過指令碼建立六個redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379 
bind 0.0.0.0
cluster-enabled yes 
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

# 啟動容器
vim redis.py

import os
for i in range(1, 7):
    str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} \
    -v /mydata/redis/node-{}/data:/data \
    -v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf \
    -d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
    os.system(str)

python reidis.py

# 建立叢集
docker exec -it redis-1 /bash/sh  # 進入redis-1容器

redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

# 建立叢集。。。。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   slots: (0 slots) slave
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   slots: (0 slots) slave
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   slots: (0 slots) slave
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.


# 進行測試
redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:160
cluster_stats_messages_pong_sent:164
cluster_stats_messages_sent:324
cluster_stats_messages_ping_received:159
cluster_stats_messages_pong_received:160
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:324

十一、Docker Compose

1、介紹

Compose是Docker官方開源的專案,需要安裝。

Compose是用於定義和執行多容器Docker應用程式的工具。通過Compose,可以使用YAML檔案來配置應用程式的服務。然後,使用一個命令,就可以從配置中建立並啟動所有服務。

使用Compose基本上是一個三步過程:

  1. Dockerfile保證我們的專案在任何地方執行
  2. docker-compose檔案
  3. 啟動

2、快速開始

(1)安裝
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

docker-compose --version
# 安裝成功
# docker-compose version 1.27.4, build 40524192
(2)使用
# 為專案建立目錄
mkdir composetest
cd composetest

# 編寫一段flask程式
vim app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)  # 這裡主機名直接用“redis”,而不是ip地址

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
    
# 編寫requirements.txt檔案(不指定版本,下載最新)
flask
redis

# 編寫Dockerfile檔案
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]


# 編寫docker-compose.yml檔案
# 檔案中定義了兩個服務,web和redis,web是從Dockerfile開始構建,redis使用的是公共映象
version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
  redis:
    image: "redis:alpine"
    
# 執行
docker-compose up 

3、搭建部落格

# 建立並進入目錄
mkdir wordpress && cd wordpress

# 編寫docker-compose.yml檔案來啟動,並建立了一個單獨Mysql例項,具有用於資料永續性的卷掛載
version: "3.3"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      W0RDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
  volumes:
    db_data: {}
    

# 啟動執行
docker-compose up -d

十二、Docker Swarm

1、環境準備

準備四臺伺服器。安裝docker。

2、swarm叢集搭建

docker swarm COMMAND
Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm   # 初始化一個節點(管理節點)
  join        Join a swarm as a node and/or manager  # 加入節點
  join-token  Manage join tokens   # 通過節點token加入
  leave       Leave the swarm    # 離開節點
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm  #更新

# 首先我們初始化一個節點
docker swarm init --advertise-addr + 自己的ip(這裡用內網地址,主要是省錢)
docker swarm init --advertise-addr 172.27.0.4

# 提示我們節點建立成功
Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.

To add a worker to this swarm, run the following command:
	
	# 在其他機器上執行,加入這個節點
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 生成一個管理節點的token,
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

# 我們在機器2上行加入節點的命令
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 我們在機器1上檢視節點資訊
docker node ls
# 我們發現一個管理節點,一個工作節點,狀態都是就緒狀態
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos   Ready     Active         Leader           20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos   Ready     Active                          20.10.0

# 現在將機器3也加入,同樣是work節點
# 到此為止,現在只有機器4沒有加入,這時候我們想把它設定成管理節點。
# 在機器1上執行生成管理節點的命令,在機器4上執行
docker swarm join-token manager
# 生成的命令在機器4上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 此時機器4也是管理節點了
This node joined a swarm as a manager.

# 至此我們可以在機器1或者機器4上進行其他操作了。(只能在管理節點上操作)

3、Raft協議

在前面的步驟,我們已經完成了雙主雙從叢集的搭建。

Raft協議:保證大多數節點存活才能使用。必須大於1,叢集至少大於3臺。

實驗1

將機器1上的docker停止,宕機,現在叢集中只有一個管理節點了。叢集是否可用。

#我們在機器四上檢視節點資訊
docker node ls
# 發現我們的叢集已經不能用了
Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded

# 我們將機器1上的docker重啟,發現叢集又能用了,但是此時機器1上的docker已經不是leader了,leader自動轉給機器4了

實驗2

我們將工作節點離開叢集,檢視叢集資訊

# 我們在機器2上執行
docker swarm leave

# 在機器一上檢視節點資訊
docker node ls
# 我們發現機器2狀態是Down
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos    Ready     Active         Reachable        20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

實驗3

現在我們將機器2也設定成管理節點(此時叢集有三個管理節點),隨機down掉一個管理節點,叢集是否正常執行。

# 在機器2上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377

# 將機器1的docker宕機
systemctl stop docker

# 在機器二上檢視節點資訊
docker node ls
# 叢集正常,且可以看到機器1宕機了
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm     VM-0-4-centos    Ready     Active         Unreachable      20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
vdwcwr3v6qrn6da40zdrjkwmy *   VM-0-7-centos    Ready     Active         Reachable        20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

4、彈性建立服務

docker service COMMAND

Commands:
  create      Create a new service  # 建立一個服務
  inspect     Display detailed information on one or more services  # 檢視服務資訊
  logs        Fetch the logs of a service or task  # 日誌
  ls          List services   # 列表
  ps          List the tasks of one or more services   # 檢視我們的服務
  rm          Remove one or more services  # 刪除服務
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services  # 動態擴縮容
  update      Update a service  # 更新

docker service create -p 8888:80 --name n1 nginx  # 建立一個服務,在叢集中隨機分配
kj0xokbxvf5uw91bswgp1cukf
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

# 給我們的服務增加三個副本
docker service update --replicas 3 n1

# 動態擴縮容
docker service scale n1=10   # 和上面updata一樣,這個方便

docker ps # 檢視

服務,叢集中任意的節點都可以訪問,服務可以有多個副本動態擴縮容實現高可用。

5、使用Docker stack部署部落格

我們現在需要將上面的部落格跑在我們的叢集中,並且要開十個副本。

# 編輯docker-compose.yml檔案
version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     deploy:
       replicas: 10
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}


# 進行啟動
docker stack deploy -c docker-compose.yml wordpress

# 檢視
docker service ls

一、安裝

環境:Centos7

1、解除安裝舊版本

較舊的Docker版本稱為dockerdocker-engine。如果已安裝這些程式,請解除安裝它們以及相關的依賴項。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2、使用儲存庫安裝

注意:官方給了三種安裝方式,這裡我們選擇最常用的儲存庫安裝

在新主機上首次安裝Docker Engine之前,需要設定Docker儲存庫。之後可以從儲存庫安裝和更新Docker。

sudo yum install -y yum-utils

# 注意:此處是大坑,可以自行換成阿里的源
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    
# 我們用阿里的    
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
 
# 進行安裝(如果不換源,速度會很慢)
sudo yum install docker-ce docker-ce-cli containerd.io

3、啟動以及驗證

systemctl start docker   # 啟動
systemctl status docker  #  檢視狀態
docker version  # 檢視版本
docker run hello-world   #  測試

4、設定加速器(阿里雲)

注意:裡面的地址每個人都有專屬的地址。

開啟阿里雲官網->控制檯->搜尋容器映象服務->右下角找到映象加速器

sudo mkdir -p /etc/docker

tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

docker run hello-world

5、解除安裝

# 解除安裝Docker Engine,CLI和Containerd軟體包
sudo yum remove docker-ce docker-ce-cli containerd.io

# 主機上的映像,容器,卷或自定義配置檔案不會自動刪除。要刪除所有影象,容器和卷
sudo rm -rf /var/lib/docker

二、Docker三要素

1、倉庫(Repository)

倉庫:集中存放映象的場所。

注意:倉庫(Repository)和倉庫註冊伺服器(Registry)是有區別的,倉庫註冊伺服器往往存放著多個倉庫,每個倉庫中又包含多個映象,每個映象有不同的標籤(tag)

倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。

最大的公開倉庫是Docker Hub(https://hub.docker.com),存放了數量龐大的映象供使用者下載,國內的公開倉庫包括阿里雲、網易雲等。

2、映象(Image)

一個只讀模板,用來建立Docker容器,一個映象可以建立很多個容器。

容器與映象的關係類似於面向物件程式設計中的物件和類

Docker 面向物件
容器 物件
映象

3、容器 (Container)

獨立執行的一個或一組應用。

容器使用映象建立的執行例項。

它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的,保證安全的平臺。

可以把容器看作是一個簡易版的Linux環境(包括root使用者許可權,程序空間,使用者空間和網路空間等等)和執行在啟動的應用程式。

容器的定義和映象幾乎一模一樣,也是一堆層的統一視角,唯一區別在於容器的最上面那一層是可讀可寫的。

三、簡單瞭解底層原理

1、Docker是怎麼工作的

Docker是一個Client-Server結構的系統,Docker守護程序執行在主機上,然後通過Socket連線從客戶端訪問,守護程序從客戶端接收命令並管理執行在主機上的容器。

2、Docker為什麼比VM快?

(1)Docker有著比虛擬機器更少的抽象層,由於Docker不需要Hypervisor實現硬體資源虛擬化,執行在Docker容器上的程式直接使用的都是實際物理機的硬體資源,因此在CPU、記憶體利用率上Docker將會在效率上有明顯的優勢。

(2)Docker利用的是宿主機的核心,而不需要Guest OS。因此當新建一個容器時,Docker不需要和虛擬機器一樣重疊載入一個作業系統核心,從而避免引尋、載入作業系統核心,返回比較費時費資源的過程。當新建一個虛擬機器時,虛擬機器軟體需要載入Guest OS,這個新建過程是分鐘級別的,而Docker由於直接利用宿主機的作業系統,因此新建一個Docker容器只需幾秒鐘。

Docker容器 虛擬機器(VM)
作業系統 與宿主機共享OS 宿主機OS上執行虛擬機器OS
儲存大小 映象小,便於儲存與傳輸 映象龐大(vmdk,vdi等)
執行效能 幾乎無額外效能損失 作業系統額外的CPU、記憶體消耗
移植性 輕便、靈活,適應於Linux 笨重,與虛擬化技術耦合度高
硬體親和性 面向軟體開發者 面向硬體運維者

四、相關命令

1、幫助命令

docker version  # 檢視docker版本資訊
docker info  # 詳細說明
docker --help  # 幫助命令

2、映象命令

(1)列出本地映象
docker images [OPTIONS] [REPOSITORY[:TAG]]

# OPTIONS說明:
	-a: 列出本地所有的映象(含中間映像層)
    -q: 只顯示映象ID
    --digests: 顯示映象的摘要資訊
    --no-trunc: 顯示完整的映象資訊

# 各個選項說明:
# REPOSITORY:表示映象的倉庫源
# TAG:映象的標籤
# IMAGE ID:映象ID
# CREATED:映象建立時間
# SIZE:映象大小	

同一倉庫源可以有多個TAG,代表這個倉庫源的不同版本,我們使用REPOSITORY:TAG來定義不同的映象,如果不指定一個映象的版本標籤,例如我們只使用ubuntu,docker將預設使用ubuntu:latest映象。

(2)查詢映象
docker search [options] 某個xxx映象名字  # 會在https://hub.docker.com上去查詢

docker search mysql --filter=STARS=3000  # 搜尋星數大於等於3000的映象

# OPTIONS說明:
# --no-trunc: 顯示完整的映象描述	
# -s:列出點贊數不小於指定值的映象
# --automated: 只列出automated build型別的映象
(3)獲取映象
docker pull 映象名字[:TAG]  # 如果不寫TAG,則預設獲取latest,此時從我們配置的阿里雲上獲取映象

docker pull mysql  # 獲取最新版本的mysql
Using default tag: latest
latest: Pulling from library/mysql
852e50cd189d: Pull complete   # 分卷下載
29969ddb0ffb: Pull complete 
a43f41a44c48: Pull complete 
5cdd802543a3: Pull complete 
b79b040de953: Pull complete 
938c64119969: Pull complete 
7689ec51a0d9: Pull complete 
a880ba7c411f: Pull complete 
984f656ec6ca: Pull complete 
9f497bce458a: Pull complete 
b9940f97694b: Pull complete 
2f069358dc96: Pull complete 
Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest  
# docker pull mysql 等價於docker pull docker.io/library/mysql:latest 

docker pull mysql:5.7  # 下載指定版本的映象
5.7: Pulling from library/mysql
852e50cd189d: Already exists  # 聯合檔案系統,已經存在的不會去重複下載
29969ddb0ffb: Already exists 
a43f41a44c48: Already exists 
5cdd802543a3: Already exists 
b79b040de953: Already exists 
938c64119969: Already exists 
7689ec51a0d9: Already exists 
36bd6224d58f: Pull complete 
cab9d3fa4c8c: Pull complete 
1b741e1c47de: Pull complete 
aac9d11987ac: Pull complete 
Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
(4)刪除映象
# 刪除單個映象
docker rmi -f 映象名/ID  # 如果是映象名,後面不帶TAG,則預設刪除latest

# 刪除多個映象
docker rmi -f 映象名1 映象名2 ...
docker rmi -f id1 id2 ...     // 注意:兩種方式不能混用

# 刪除全部映象
docker rmi -f $(docker images -aq)   

3、容器命令

​ 有映象才能建立容器,這是根本前提(下載一個Centos映象演示)

(1) 新建並啟動容器(互動式)
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run -it --name mycentos centos  // 如果不指定別名,系統會自動分配

# OPTIONS說明:
# --name 容器新名字:為容器指定一個名稱
# -d:後臺執行容器,並返回容器ID,即啟動守護式容器
# -i: 以互動模式執行容器,通常與-t同時使用
# -t: 為容器重新分配一個偽輸入終端,通常與-i同時使用
# -P: 隨機埠對映
# -p: 指定埠對映有以下四種格式
		ip:hostPort:containerPort
		ip::containerPort
		hostPort:containerPort
		containerPort

# 測試
[root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash  # 啟動並進入容器
[root@783cb2f26230 /]# ls   # 在容器內檢視
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@783cb2f26230 /]# exit   # 退出容器
exit
[root@iz2zeaj5c9isqt1zj9elpbz ~]# 

(2)列出當前所有正在執行的容器
docker ps [OPTIONS]  # 不帶OPTIONS,則只列出正在執行的容器

# OPTIONS說明(常用):
# -a:列出當前所有正在執行的容器以及歷史上執行過的
# -l:顯示最近建立的容器
# -n: 顯示最近n個建立的容器
# -q: 靜默模式,只顯示容器編號
# --no-trunc:不截斷輸出
(3)退出容器
exit  // 直接關閉並退出容器

重新開啟一個終端,執行docker ps -l,會返回剛才我們建立的容器資訊,並且STATUS會提示已經退出。

那麼可不可以在互動式,不關閉容器的情況下,暫時退出,一會又可以回來呢?

Ctrl + P + Q

# 執行後,我們將退出容器,回到宿主機,使用docker ps -l,我們會發現這個剛才退出的容器STATUS是Up狀態。
(4)啟動容器
docker start [OPTIONS] CONTAINER [CONTAINER...]

# 同時可以啟動多個容器,容器名和ID可以混用

# OPTION說明(常用):
# -i : 進入互動式,此時只能進入一個容器

進入上面我們退出但是依然存活的容器

docker start -i 186ae928f07c
(5)重啟容器
docker restart [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死容器之前等待停止的時間(預設為10)
(6) 停止容器
docker stop [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死前等待停止的時間(預設為10)

docker kill [OPTIONS] CONTAINER [CONTAINER...]  // 強制關閉(相當於拔電源)

# OPTIONS說明:
# -s: 傳送到容器的訊號(預設為“KILL”)
(7)刪除容器
docker rm [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -f :強制刪除,不管是否在執行

# 上面的命令可以刪除一個或者多個,但是想全部刪除所有的容器,那麼我們要把全部的容器名或者ID都寫一遍嗎?

docker rm -f $(docker ps -aq)  # 刪除所有
docker ps -aq | xargs docker rm -f
(8) 啟動守護式容器
docker run -d 容器名/容器ID

說明:我們使用docker ps -a命令檢視,會發現剛才啟動的容器以及退出了,這是為什麼呢?

很重要的要說明一點:Docker容器後臺執行,必須要有一個前臺程序,容器執行的命令如果不是那些一直掛起的命令(比如執行top,tail),就會自動退出。

這就是Docker的機制問題,比如我們現在執行WEB容器,以Nginx為例,正常情況下,我們配置啟動服務只需要啟動響應的service即可,例如service nginx start,但是這樣做,Nginx為後臺程序模式執行,導致Docker前臺沒有執行的應用。這樣的容器後臺啟動後,會立即自殺因為它覺得它沒事可做。所以最佳的解決方案是將要執行的程式以前臺程序的形式執行。

那麼如何讓守護式容器不自動退出呢?我們可以執行一直掛起的命令。

docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
(9) 檢視容器日誌
docker logs [OPTIONS] CONTAINER

# OPTION說明:
# -t 加入時間戳
# -f 跟隨最新的日誌列印
# --tail 顯示最後多少條
(10) 檢視容器內的程序
docker top CONTAINER 
(11) 檢視容器內部細節
docker inspect CONTAINER
(12) 進入正在執行的容器並以命令列互動
  • exec在容器中開啟新的終端,並且可以啟動新的程序
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]  

docker exec -it CONTAINER /bin/bash   // 進入容器內部並互動

# 隔山打牛
docker exec -it 3d00a0a2877e ls -l /tmp 

# 3d00a0a2877e是我上面跑的守護式容器
# 進入容器,執行ls -l /tmp 命令後將結果返回給我宿主機,而使用者的介面還是停留在宿主機,沒有進入容器內部
  • attach直接進入容器啟動命令的終端,不會啟動新的程序
docker attach CONTAINER

# 注意,進入上面那個守護式容器後,我們會看到還是每隔兩秒列印hello Negan,而且不能退出,只能重新開啟一個終端,執行docker kill 命令
(13)容器與主機間的檔案拷貝
docker cp CONTAINER:SRC_PATH DEST_PATH  # 把容器內的檔案拷貝到宿主機
docker cp 90bd03598dd4:123.txt ~

docker cp SRC_PATH CONTAINER:DEST_PATH  # 把宿主機上的檔案拷貝到容器
docker cp 12345.txt 90bd03598dd4:~

練習

練習一:Docker部署Nginx

# 查詢一個nginx映象
docker search nginx
# 下載映象
docker pull nginx
# 啟動
docker run -d --name nginx01 -p 3344:80 nginx  # -p 3344(宿主機):80(容器埠)
# 測試(瀏覽器訪問)
123.56.243.64:3344 

五、視覺化

1、portainer

Docker圖形介面管理工具,提供一個後臺面板供我們操作。

docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer

六、Docker映象

1、映象是什麼

映象是一種輕量級、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體,它包含執行某個軟體所需的所有內容,包括程式碼、執行時、庫、環境變數和配置檔案。

所有應用直接打包docker映象,就可以直接跑起來。

如何獲取映象
  • 從遠端操作下載
  • 朋友拷貝
  • 自己製作一個映象DockerFile

2、映象載入原理

(1)聯合檔案系統

UnionFS(聯合檔案系統):Union檔案系統是一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改作為一次提交來一層一層疊加,同時可以將不同目錄掛在到同一個虛擬檔案系統下。聯合檔案系統是Docker映象的基礎,映象可以通過分層來進行繼承,基於基礎映象(沒有父映象),可以製作各種具體的應用映象。

特性:一次同時載入多個檔案系統,但從外面看起來,只能看到一個檔案系統,聯合載入會把各層檔案系統疊加起來,這樣最終的檔案系統會包含所有底層的檔案和目錄。

(2)Docker映象載入原理

Docker的映象實際上是由一層一層的檔案系統組成,也就是上面所說的聯合檔案系統。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引導載入kernel,Linux剛啟動時會載入bootfs檔案系統,在Docker映象最底層是bootfs。這一層與我們典型的Linux/Unix系統是一樣的,包含boot載入器和核心。當boot載入完成之後整個核心就都在記憶體中,此時記憶體的使用權已由bootfs轉交給核心,此時系統也會解除安裝bootfs。

rootfs(root file system),在bootfs之上,包含的就是典型Linux系統中的/dev,/proc,/bin,/etc等標準目錄和檔案。rootfs就是各種不同的作業系統發行版,比如Ubuntu,CentOS等等。

對於一個精簡的OS,rootfs可以很小,只需要包含最近本的命令,工具和程式庫就可以,因為底層直接用Host的kernel,自己只需要提供rootfs就可以,由此可見對於不同的Linux發行版,bootfs基本是一致的,rootfs會有差別,因此不同的發行版可以公用bootfs。

3、分層理解

(1)分層的映象

我們下載一個映象,注意觀察下載的日誌輸出,可以看到是一層一層在下載。

為什麼Docker映象採用這種分層的結構?

最大的好處,我覺得莫過於資源共享了,比如有多個映象從相同的Base映象構建而來,那麼宿主機只需要在磁碟上保留一份Base映象,同時記憶體中也只需要載入一份Base映象,這樣就可以為所有的容器服務了,而且映象每一層都可以被共享。

(2)理解

所有的Docker映象都始於一個基礎映象層,當進行修改或者增加新內容時,就會在當前映象層之上,建立新的映象層。假如基於Ubuntu Linux 16.04建立一個新的映象,這就是新映象的第一層;如果在該映象中新增Python包,就會在基礎映象之上建立第二個映象層,如果繼續新增一個安全補丁,就會建立第三個映象層。

再新增額外的映象層同時,映象始終保持是當前所有映象的組合。

注意:Docker映象都是隻讀的,當容器啟動時,一個新的可寫層被載入到映象的頂部。

​ 這一層就是我們通常說的容器層,容器之下的都叫映象層。

4、commit

docker commit 提交容器成為一個新的映象

docker commit -m="提交的描述資訊" -a="作者" 容器id 目標映象名:[TAG]

七、容器資料卷

如果資料在容器中,那麼我們容器刪除,資料就會丟失!

需求:資料需要持久化。

MySQL,容器刪了,刪庫跑路?Docker容器中產生的資料,需要同步到本地。

這就是卷技術,目錄的掛載,將我們容器內的目錄,掛載到Linux上。

1、使用資料卷

  • 直接使用命令來掛載
docker run -it -v 主機目錄:容器內目錄  # 雙向繫結,一方改變另一方自動改變

docker run -it -v /home/ceshi:/home centos /bin/bash

docker inspect d2343e9d338a  # 進行檢視

/*
...
 "Mounts": [   # 掛載 -v 
            {
                "Type": "bind",
                "Source": "/home/ceshi",  # 主機內的路徑
                "Destination": "/home",   # 容器內的路徑
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...
*/

2、實戰:安裝MySQL

思考:MySQL的資料持久化問題

docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7

3、具名掛載和匿名掛載

(1) 、匿名掛載
docker run -d -P --name nginx01 -v /etc/nginx nginx  # 只指定了容器內的路徑

docker volume ls  # 檢視所有volume的情況
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a  # 匿名掛載
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049

# 這種就是匿名掛載,我們在-v只寫了容器內的路徑,沒有寫容器外的路徑。
(2)、具名掛載
docker run -d -P -v juming:/etc/nginx --name nginx02 nginx   # 具名掛載

docker volume ls
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
local               juming  # 我們上面起的名字

# 通過-v 卷名:容器內路徑

# 所有的docker容器內的卷,沒有指定目錄的情況下,都在/var/lib/docker/volumes目錄下。
docker inspect juming  
[
    {
        "CreatedAt": "2020-12-09T00:22:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/juming/_data",
        "Name": "juming",
        "Options": null,
        "Scope": "local"
    }
]

我們通過具名掛載可以方便的找到我們掛載的卷,大多情況下我們都會使用具名掛載。

(3)、拓展
-v 容器內路徑  # 匿名掛載
-v 卷名:容器內路徑 # 具名掛載
-v /宿主機路徑:容器內路徑  # 指定路徑掛載

# 通過 -V 容器內路徑:ro rw改變讀寫許可權
ro readonly # 只讀
rw readwrite # 可讀可寫

# 一旦設定了只讀,容器對我們掛載出來的內容就有限定了。檔案只能通過宿主機來操作,容器無法操作。
docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx 

4、資料卷容器

容器間的資訊同步。

docker run -it -v /home --name c1 centos /bin/bash   # 啟動c1,作為一個父容器(指定容器內掛載目錄)
docker run -it --name c2 --volumes-from c1 centos /bin/bash  # 啟動c2,掛載c1,此時在兩個容器中/home中的操作就會同步


# 下面的容器是我們自己建立的,可以參考下面dockerfile進行構建
docker run -it --name docker01 negan/centos   # 啟動一個容器作為父容器(被掛載的容器)  
docker run -it --name docker02 --volumes-from docker01 negan/centos  #啟動容器2,掛載容器1 
docker run -it --name docker03 --volumes-from docker02 negan/centos  #啟動容器3,掛載容器2
# 上面的容器3也可以直接掛載到容器1上,然後我們進入任意一個容器,進入掛載卷volume01/volume02,進行操作,容器間的資料會自動同步。

結論:

容器之間配置資訊的傳遞,資料卷容器的宣告週期一直持續到沒有容器使用為止。

但是一旦持久到了本地,本地資料不會被刪除。

八、DockerFile

1、初識DockerFile

DockerFile就是用來構造docker映象的構建檔案,命令引數指令碼。通過這個指令碼,可以生成映象。映象是一層一層的,指令碼是一個個命令,每個命令就是一層。

# dockerfile1內容,所有命令都是大寫

FROM centos   # 基於centos

VOLUME ["volume01","volume02"]   # 掛載資料卷(匿名掛載)

CMD echo "----end-----"

CMD /bin/bash


# 構建
docker build -f dockerfile1 -t negan/centos .  # -f 指定路徑 -t指定名字,不加tag預設最新
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
 ---> 0d120b6ccaa8
Step 2/4 : VOLUME ["volume01","volume02"]
 ---> Running in 0cfe6b5be6bf
Removing intermediate container 0cfe6b5be6bf
 ---> 396a4a7cfe15
Step 3/4 : CMD echo "----end-----"
 ---> Running in fa535b5581fa
Removing intermediate container fa535b5581fa
 ---> 110d9f93f827
Step 4/4 : CMD /bin/bash
 ---> Running in 557a2bb87d97
Removing intermediate container 557a2bb87d97
 ---> c2c9b92d50ad
Successfully built c2c9b92d50ad
Successfully tagged negan/centos:latest

docker images  # 檢視

2、DokcerFile構建過程

(1)、基礎知識

每個保留關鍵字(指令)都必須是大寫字母

執行從上到下順序執行

#表示註釋

每一個指令都會建立提交一個新的映象層,並提交

DockerFile是面向開發的,我們以後要釋出專案,做映象,就需要編寫dockerfile檔案。

Docker映象逐漸成為企業交付的標準。

(2)、基本命令
FROM  #這個映象的媽媽是誰?(基礎映象,一切從這裡開始構建)
MAINTAINER # 告訴別人誰負責養他?(映象是誰寫的,指定維護者資訊,姓名+郵箱)
RUN  # 你想讓他幹啥?(映象構建的時候需要執行的命令)
ADD  # 給他點創業資金(複製檔案,會自動解壓)
WORKDIR # 映象的工作目錄
VOLUME  # 給他一個存放行李的地方(設定卷,容器內掛載到主機的目錄,匿名掛載)
EXPOSE  # 門牌號是多少?(指定對外埠)
CMD   # 指定這個容器啟動的時候要執行的命令,只有最後一個會生效,可被替代
ENTRYPOINT # 指定這個容器啟動時候要執行的命令,可以追加命令
ONBUILD  # 當構建一個被繼承DockerFile,這時就會執行ONBUILD指令
COPY    # 類似ADD,將我們檔案拷貝到映象中
ENV    # 構建的時候設定環境變數

3、實戰操作

(1)建立一個自己的CentOS
# vim Dockerfile

FROM centos
MAINTAINER Negan<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 構建
docker build -f Dockerfile -t negan/centos .

# 測試
docker run -it negan/centos
[root@ffae1f9eb97e local]# pwd
/usr/local   # 進入的是我們在dockerfile設定的工作目錄

# 檢視映象構建過程
docker history 映象名/ID
(2)CMD與ENTRYPOINT區別

兩者都是容器啟動的時候要執行的命令,CMD命令只有最後一個會生效,後面不支援追加命令,會被替代。ENTRYPOINT不會被替代,可以追加命令。

CMD

# vim cmd
FROM centos
CMD ["ls","-a"]

# 構建
docker build -f cmd -t cmd_test .

# 執行,發現我們的ls -a命令生效
docker run cmd_test
.
..
.dockerenv
bin
dev
etc
......

# 追加命令執行
docker run cmd_test -l
# 丟擲錯誤,不能追加命令,原來的ls -a命令被-l替換,但是-l不是一個有效的命令
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.

# 追加完整的命令
docker run cmd_test ls -al

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:36 .
drwxr-xr-x  1 root root 4096 Dec 10 14:36 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:36 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:36 dev
......

ENTRYPOINT

# vim entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]

# 構建
docker build -f entrypoint -t entrypoint_test .

# 執行
docker run entrypoint_test
.
..
.dockerenv
bin
dev
etc

# 追加命令執行
docker run entrypoint_test -l

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:41 .
drwxr-xr-x  1 root root 4096 Dec 10 14:41 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:41 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:41 dev
drwxr-xr-x  1 root root 4096 Dec 10 14:41 etc
......

4、實戰構建tomcat

(1)環境準備
 ll
total 166472
-rw-r--r-- 1 root root  11437266 Dec  9 16:22 apache-tomcat-9.0.40.tar.gz
-rw-r--r-- 1 root root       641 Dec 10 23:26 Dockerfile
-rw-r--r-- 1 root root 159019376 Dec  9 17:39 jdk-8u11-linux-x64.tar.gz
-rw-r--r-- 1 root root         0 Dec 10 22:48 readme.txt
(2)構建映象
# vim Dockerfile (Dockerfile是官方建議命名)

FROM centos
MAINTAINER Negan<[email protected]>

COPY readme.txt /usr/local/readme.txt

ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
ADD apache-tomcat-9.0.40.tar.gz /usr/local/

RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out

# 構建
docker -t tomcat .
(3)啟動容器
docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat

九、釋出自己的映象

1、docker hub

首先需要在DockerHub上註冊自己的賬號,並且確定這個賬號可以登入。

在我們的伺服器上進行登入,登入成功後提交我們的映象。

# 登入
docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
  -p, --password string   Password
      --password-stdin    Take the password from stdin
  -u, --username string   Username


# 登入成功後推送我們的映象
docker push [OPTIONS] NAME[:TAG]  

Push an image or a repository to a registry

Options:
      --disable-content-trust   Skip image signing (default true)

docker tag tomcat huiyichanmian/tomcat  # 需要改名,推送改名後的(前面加自己的使用者名稱)
docker push huiyichanmian/tomcat

2、阿里雲

登入阿里雲,找到容器映象服務,使用映象倉庫。建立名稱空間,建立映象倉庫。選擇本地倉庫。

具體阿里雲上有特別詳細的步驟,這裡不再贅述了。

十、Docker網路

1、理解docker0

(1)檢視本機網絡卡資訊
ip addr 

# 本地迴環地址
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
       
# 阿里雲內網地址
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286793195sec preferred_lft 286793195sec
    
# docker0地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

(2)檢視容器網絡卡資訊

我們獲取一個tomcat映象,用來測試。

docker run -d -P --name t1 tomcat 

docker exec -it t1 ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# 我們發現容器啟動時候會得到一個eth0@ifxxx的網絡卡,而且ip地址和上面的docker0裡的是在同一網段。
233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
(3)、再次檢視本地網絡卡資訊
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286792020sec preferred_lft 286792020sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
# 我們發現多了一條網絡卡資訊,而且與容器裡面的網絡卡有某種對應關係。(233,234)
234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default

我們重複上面的操作,不難發現我們只要安裝了docker,本地網卡里就會多一個docker0,而且我們每啟動一個容器,docker就會給容器分配一個網絡卡,而且在本地也會多出一個網絡卡資訊,與容器內的相對應。這就是veth_pair技術,就是一對虛擬裝置介面,它們都是成對出現,一段連著協議,一段彼此相連。正因為有了這個特性,我們通常veth_pair充當了一個橋樑,連線各種虛擬網路裝置。

所有的容器不指定網路的情況下,都是由docker0路由的,docker會給我們的容器分配一個預設的ip。

Docker使用的是Linux的橋接,宿主機中docker0是一個Docker容器的網橋。所有的網路介面都是虛擬的。

只要容器刪除,對應的網橋也響應的被刪除。

問題:我們每次重啟容器,容器的ip地址會變,而我們專案中一些配置使用的固定ip,這時候也需要做相應的改變,那麼我們可不可以直接設定服務名呢,下次重啟時候,配置直接找服務名。

我們啟動兩個tomcat,進行測試,互相ping其對應的名字,看是否能通?

docker exec -it t1 ping t2
ping: t2: Name or service not known
# 答案是肯定的,不能識別t2,如何解決?
# 我們使用--link進行連線
docker run -d -P --name t3 --link t2 tomcat

# 我們嘗試使用t3來ping t2
docker exec -it t3 ping t2
# 我們發現竟然通了
PING t2 (172.17.0.3) 56(84) bytes of data.
64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
......

那麼--link做了什麼呢?

docker exec -it t3 cat /etc/hosts  # 我們來檢視t3的hosts檔案

127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	t2 6bf3c12674c8  # 原因在這了,這裡對t2做了標記,當ping t2時候,自動轉到172.17.0.3
172.17.0.4	b6dae0572f93

3、自定義網路

(1)檢視docker網路
docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
afc0c673386f        none                null                local

# bridge 橋接(docker預設)
# host 與主機共享
# none 不配置
(2)容器啟動時的預設網路

docker0是我們預設的網路,不支援域名訪問,可以使用--link打通。

 # 一般我們啟動容器是這樣子,使用的是預設網路,而這個預設網路就是bridge,所以下面兩條命令是相同的
docker run -d -P --name t1 tomcat  

docker run -d -P --name t1 --net bridge tomcat 
(3)建立網路
# --driver bridge 預設,可以不寫
# --subnet 192.168.0.0/16 子網掩碼
# --gateway 192.168.0.1 預設閘道器
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

docker network ls

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
0e98462f3e8e        mynet               bridge              local  # 我們自己建立的網路
afc0c673386f        none                null                local


ip addr

.....
# 我們自己建立的網路
239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
       valid_lft forever preferred_lft forever
.....

啟動兩個容器,使用我們自己建立的網路

docker run -P -d --name t1 --net mynet tomcat
docker run -P -d --name t2 --net mynet tomcat

# 檢視我們自己建立的網路資訊
docker network mynet inspect

# 我們發現我們剛啟動的兩個容器使用的是我們剛建立的網路
......
"Containers": {
            "1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
                "Name": "t2",
                "EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
                "Name": "t1",
                "EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
.......

那麼使用自己建立的網路有什麼好處呢?

我們回到我們以前的問題,就是域名ping不通的問題上。

docker exec -it t1 ping t2
PING t2 (192.168.0.3) 56(84) bytes of data.
64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
......

docker exec -it t2 ping t1
PING t1 (192.168.0.2) 56(84) bytes of data.
64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
......

我們發現域名可以ping通了,那麼說明我們自定義的網路已經幫我們維護好了對應關係。

這樣不同的叢集使用不同的網路,也能保證叢集的安全和健康。

4、網路連通

現在有這樣一個需求,t1,t2使用的是我們自己定義的網路,t3,t4使用的預設的docker0網路。那麼現在t3和t1或者t2能否通訊呢?

我們知道,docker0的預設閘道器是172.17.0.1,mynet預設閘道器是192.168.0.1,他們直接屬於不同的網段,不能進行通訊。那麼現在如何解決上面的問題呢?

那麼mynet能不能給t3分配一個ip地址呢?如果能分配,那麼問題就應該可以得到解決。

docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
      --alias strings           Add network-scoped alias for the container
      --driver-opt strings      driver options for the network
      --ip string               IPv4 address (e.g., 172.30.100.104)
      --ip6 string              IPv6 address (e.g., 2001:db8::33)
      --link list               Add link to another container
      --link-local-ip strings   Add a link-local address for the container
   
  # 一個容器兩個ip地址 
 docker network connect mynet t3   # 將t3加入到mynet網路中
 
 # 檢視mynet資訊
 docker network inspect mynet
 
 "Containers": {
			......
            "d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
                "Name": "t3",  # 我們發現了t3
                "EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
                "MacAddress": "02:42:c0:a8:00:04",
                "IPv4Address": "192.168.0.4/16",
                "IPv6Address": ""
            }
        },
        ......

這時候,t3就能和t1,t2進行通訊了。

5、部署Redis叢集

# 建立網路
docker network create redis --subnet 172.38.0.0/16

# 通過指令碼建立六個redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379 
bind 0.0.0.0
cluster-enabled yes 
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

# 啟動容器
vim redis.py

import os
for i in range(1, 7):
    str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} \
    -v /mydata/redis/node-{}/data:/data \
    -v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf \
    -d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
    os.system(str)

python reidis.py

# 建立叢集
docker exec -it redis-1 /bash/sh  # 進入redis-1容器

redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

# 建立叢集。。。。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   slots: (0 slots) slave
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   slots: (0 slots) slave
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   slots: (0 slots) slave
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.


# 進行測試
redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:160
cluster_stats_messages_pong_sent:164
cluster_stats_messages_sent:324
cluster_stats_messages_ping_received:159
cluster_stats_messages_pong_received:160
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:324

十一、Docker Compose

1、介紹

Compose是Docker官方開源的專案,需要安裝。

Compose是用於定義和執行多容器Docker應用程式的工具。通過Compose,可以使用YAML檔案來配置應用程式的服務。然後,使用一個命令,就可以從配置中建立並啟動所有服務。

使用Compose基本上是一個三步過程:

  1. Dockerfile保證我們的專案在任何地方執行
  2. docker-compose檔案
  3. 啟動

2、快速開始

(1)安裝
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

docker-compose --version
# 安裝成功
# docker-compose version 1.27.4, build 40524192
(2)使用
# 為專案建立目錄
mkdir composetest
cd composetest

# 編寫一段flask程式
vim app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)  # 這裡主機名直接用“redis”,而不是ip地址

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
    
# 編寫requirements.txt檔案(不指定版本,下載最新)
flask
redis

# 編寫Dockerfile檔案
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]


# 編寫docker-compose.yml檔案
# 檔案中定義了兩個服務,web和redis,web是從Dockerfile開始構建,redis使用的是公共映象
version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
  redis:
    image: "redis:alpine"
    
# 執行
docker-compose up 

3、搭建部落格

# 建立並進入目錄
mkdir wordpress && cd wordpress

# 編寫docker-compose.yml檔案來啟動,並建立了一個單獨Mysql例項,具有用於資料永續性的卷掛載
version: "3.3"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      W0RDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
  volumes:
    db_data: {}
    

# 啟動執行
docker-compose up -d

十二、Docker Swarm

1、環境準備

準備四臺伺服器。安裝docker。

2、swarm叢集搭建

docker swarm COMMAND
Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm   # 初始化一個節點(管理節點)
  join        Join a swarm as a node and/or manager  # 加入節點
  join-token  Manage join tokens   # 通過節點token加入
  leave       Leave the swarm    # 離開節點
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm  #更新

# 首先我們初始化一個節點
docker swarm init --advertise-addr + 自己的ip(這裡用內網地址,主要是省錢)
docker swarm init --advertise-addr 172.27.0.4

# 提示我們節點建立成功
Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.

To add a worker to this swarm, run the following command:
	
	# 在其他機器上執行,加入這個節點
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 生成一個管理節點的token,
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

# 我們在機器2上行加入節點的命令
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 我們在機器1上檢視節點資訊
docker node ls
# 我們發現一個管理節點,一個工作節點,狀態都是就緒狀態
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos   Ready     Active         Leader           20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos   Ready     Active                          20.10.0

# 現在將機器3也加入,同樣是work節點
# 到此為止,現在只有機器4沒有加入,這時候我們想把它設定成管理節點。
# 在機器1上執行生成管理節點的命令,在機器4上執行
docker swarm join-token manager
# 生成的命令在機器4上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 此時機器4也是管理節點了
This node joined a swarm as a manager.

# 至此我們可以在機器1或者機器4上進行其他操作了。(只能在管理節點上操作)

3、Raft協議

在前面的步驟,我們已經完成了雙主雙從叢集的搭建。

Raft協議:保證大多數節點存活才能使用。必須大於1,叢集至少大於3臺。

實驗1

將機器1上的docker停止,宕機,現在叢集中只有一個管理節點了。叢集是否可用。

#我們在機器四上檢視節點資訊
docker node ls
# 發現我們的叢集已經不能用了
Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded

# 我們將機器1上的docker重啟,發現叢集又能用了,但是此時機器1上的docker已經不是leader了,leader自動轉給機器4了

實驗2

我們將工作節點離開叢集,檢視叢集資訊

# 我們在機器2上執行
docker swarm leave

# 在機器一上檢視節點資訊
docker node ls
# 我們發現機器2狀態是Down
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos    Ready     Active         Reachable        20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

實驗3

現在我們將機器2也設定成管理節點(此時叢集有三個管理節點),隨機down掉一個管理節點,叢集是否正常執行。

# 在機器2上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377

# 將機器1的docker宕機
systemctl stop docker

# 在機器二上檢視節點資訊
docker node ls
# 叢集正常,且可以看到機器1宕機了
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm     VM-0-4-centos    Ready     Active         Unreachable      20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
vdwcwr3v6qrn6da40zdrjkwmy *   VM-0-7-centos    Ready     Active         Reachable        20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

4、彈性建立服務

docker service COMMAND

Commands:
  create      Create a new service  # 建立一個服務
  inspect     Display detailed information on one or more services  # 檢視服務資訊
  logs        Fetch the logs of a service or task  # 日誌
  ls          List services   # 列表
  ps          List the tasks of one or more services   # 檢視我們的服務
  rm          Remove one or more services  # 刪除服務
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services  # 動態擴縮容
  update      Update a service  # 更新

docker service create -p 8888:80 --name n1 nginx  # 建立一個服務,在叢集中隨機分配
kj0xokbxvf5uw91bswgp1cukf
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

# 給我們的服務增加三個副本
docker service update --replicas 3 n1

# 動態擴縮容
docker service scale n1=10   # 和上面updata一樣,這個方便

docker ps # 檢視

服務,叢集中任意的節點都可以訪問,服務可以有多個副本動態擴縮容實現高可用。

5、使用Docker stack部署部落格

我們現在需要將上面的部落格跑在我們的叢集中,並且要開十個副本。

# 編輯docker-compose.yml檔案
version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     deploy:
       replicas: 10
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}


# 進行啟動
docker stack deploy -c docker-compose.yml wordpress

# 檢視
docker service ls

一、安裝

環境:Centos7

1、解除安裝舊版本

較舊的Docker版本稱為dockerdocker-engine。如果已安裝這些程式,請解除安裝它們以及相關的依賴項。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2、使用儲存庫安裝

注意:官方給了三種安裝方式,這裡我們選擇最常用的儲存庫安裝

在新主機上首次安裝Docker Engine之前,需要設定Docker儲存庫。之後可以從儲存庫安裝和更新Docker。

sudo yum install -y yum-utils

# 注意:此處是大坑,可以自行換成阿里的源
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    
# 我們用阿里的    
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
 
# 進行安裝(如果不換源,速度會很慢)
sudo yum install docker-ce docker-ce-cli containerd.io

3、啟動以及驗證

systemctl start docker   # 啟動
systemctl status docker  #  檢視狀態
docker version  # 檢視版本
docker run hello-world   #  測試

4、設定加速器(阿里雲)

注意:裡面的地址每個人都有專屬的地址。

開啟阿里雲官網->控制檯->搜尋容器映象服務->右下角找到映象加速器

sudo mkdir -p /etc/docker

tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

docker run hello-world

5、解除安裝

# 解除安裝Docker Engine,CLI和Containerd軟體包
sudo yum remove docker-ce docker-ce-cli containerd.io

# 主機上的映像,容器,卷或自定義配置檔案不會自動刪除。要刪除所有影象,容器和卷
sudo rm -rf /var/lib/docker

二、Docker三要素

1、倉庫(Repository)

倉庫:集中存放映象的場所。

注意:倉庫(Repository)和倉庫註冊伺服器(Registry)是有區別的,倉庫註冊伺服器往往存放著多個倉庫,每個倉庫中又包含多個映象,每個映象有不同的標籤(tag)

倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。

最大的公開倉庫是Docker Hub(https://hub.docker.com),存放了數量龐大的映象供使用者下載,國內的公開倉庫包括阿里雲、網易雲等。

2、映象(Image)

一個只讀模板,用來建立Docker容器,一個映象可以建立很多個容器。

容器與映象的關係類似於面向物件程式設計中的物件和類

Docker 面向物件
容器 物件
映象

3、容器 (Container)

獨立執行的一個或一組應用。

容器使用映象建立的執行例項。

它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的,保證安全的平臺。

可以把容器看作是一個簡易版的Linux環境(包括root使用者許可權,程序空間,使用者空間和網路空間等等)和執行在啟動的應用程式。

容器的定義和映象幾乎一模一樣,也是一堆層的統一視角,唯一區別在於容器的最上面那一層是可讀可寫的。

三、簡單瞭解底層原理

1、Docker是怎麼工作的

Docker是一個Client-Server結構的系統,Docker守護程序執行在主機上,然後通過Socket連線從客戶端訪問,守護程序從客戶端接收命令並管理執行在主機上的容器。

2、Docker為什麼比VM快?

(1)Docker有著比虛擬機器更少的抽象層,由於Docker不需要Hypervisor實現硬體資源虛擬化,執行在Docker容器上的程式直接使用的都是實際物理機的硬體資源,因此在CPU、記憶體利用率上Docker將會在效率上有明顯的優勢。

(2)Docker利用的是宿主機的核心,而不需要Guest OS。因此當新建一個容器時,Docker不需要和虛擬機器一樣重疊載入一個作業系統核心,從而避免引尋、載入作業系統核心,返回比較費時費資源的過程。當新建一個虛擬機器時,虛擬機器軟體需要載入Guest OS,這個新建過程是分鐘級別的,而Docker由於直接利用宿主機的作業系統,因此新建一個Docker容器只需幾秒鐘。

Docker容器 虛擬機器(VM)
作業系統 與宿主機共享OS 宿主機OS上執行虛擬機器OS
儲存大小 映象小,便於儲存與傳輸 映象龐大(vmdk,vdi等)
執行效能 幾乎無額外效能損失 作業系統額外的CPU、記憶體消耗
移植性 輕便、靈活,適應於Linux 笨重,與虛擬化技術耦合度高
硬體親和性 面向軟體開發者 面向硬體運維者

四、相關命令

1、幫助命令

docker version  # 檢視docker版本資訊
docker info  # 詳細說明
docker --help  # 幫助命令

2、映象命令

(1)列出本地映象
docker images [OPTIONS] [REPOSITORY[:TAG]]

# OPTIONS說明:
	-a: 列出本地所有的映象(含中間映像層)
    -q: 只顯示映象ID
    --digests: 顯示映象的摘要資訊
    --no-trunc: 顯示完整的映象資訊

# 各個選項說明:
# REPOSITORY:表示映象的倉庫源
# TAG:映象的標籤
# IMAGE ID:映象ID
# CREATED:映象建立時間
# SIZE:映象大小	

同一倉庫源可以有多個TAG,代表這個倉庫源的不同版本,我們使用REPOSITORY:TAG來定義不同的映象,如果不指定一個映象的版本標籤,例如我們只使用ubuntu,docker將預設使用ubuntu:latest映象。

(2)查詢映象
docker search [options] 某個xxx映象名字  # 會在https://hub.docker.com上去查詢

docker search mysql --filter=STARS=3000  # 搜尋星數大於等於3000的映象

# OPTIONS說明:
# --no-trunc: 顯示完整的映象描述	
# -s:列出點贊數不小於指定值的映象
# --automated: 只列出automated build型別的映象
(3)獲取映象
docker pull 映象名字[:TAG]  # 如果不寫TAG,則預設獲取latest,此時從我們配置的阿里雲上獲取映象

docker pull mysql  # 獲取最新版本的mysql
Using default tag: latest
latest: Pulling from library/mysql
852e50cd189d: Pull complete   # 分卷下載
29969ddb0ffb: Pull complete 
a43f41a44c48: Pull complete 
5cdd802543a3: Pull complete 
b79b040de953: Pull complete 
938c64119969: Pull complete 
7689ec51a0d9: Pull complete 
a880ba7c411f: Pull complete 
984f656ec6ca: Pull complete 
9f497bce458a: Pull complete 
b9940f97694b: Pull complete 
2f069358dc96: Pull complete 
Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest  
# docker pull mysql 等價於docker pull docker.io/library/mysql:latest 

docker pull mysql:5.7  # 下載指定版本的映象
5.7: Pulling from library/mysql
852e50cd189d: Already exists  # 聯合檔案系統,已經存在的不會去重複下載
29969ddb0ffb: Already exists 
a43f41a44c48: Already exists 
5cdd802543a3: Already exists 
b79b040de953: Already exists 
938c64119969: Already exists 
7689ec51a0d9: Already exists 
36bd6224d58f: Pull complete 
cab9d3fa4c8c: Pull complete 
1b741e1c47de: Pull complete 
aac9d11987ac: Pull complete 
Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
(4)刪除映象
# 刪除單個映象
docker rmi -f 映象名/ID  # 如果是映象名,後面不帶TAG,則預設刪除latest

# 刪除多個映象
docker rmi -f 映象名1 映象名2 ...
docker rmi -f id1 id2 ...     // 注意:兩種方式不能混用

# 刪除全部映象
docker rmi -f $(docker images -aq)   

3、容器命令

​ 有映象才能建立容器,這是根本前提(下載一個Centos映象演示)

(1) 新建並啟動容器(互動式)
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run -it --name mycentos centos  // 如果不指定別名,系統會自動分配

# OPTIONS說明:
# --name 容器新名字:為容器指定一個名稱
# -d:後臺執行容器,並返回容器ID,即啟動守護式容器
# -i: 以互動模式執行容器,通常與-t同時使用
# -t: 為容器重新分配一個偽輸入終端,通常與-i同時使用
# -P: 隨機埠對映
# -p: 指定埠對映有以下四種格式
		ip:hostPort:containerPort
		ip::containerPort
		hostPort:containerPort
		containerPort

# 測試
[root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash  # 啟動並進入容器
[root@783cb2f26230 /]# ls   # 在容器內檢視
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@783cb2f26230 /]# exit   # 退出容器
exit
[root@iz2zeaj5c9isqt1zj9elpbz ~]# 

(2)列出當前所有正在執行的容器
docker ps [OPTIONS]  # 不帶OPTIONS,則只列出正在執行的容器

# OPTIONS說明(常用):
# -a:列出當前所有正在執行的容器以及歷史上執行過的
# -l:顯示最近建立的容器
# -n: 顯示最近n個建立的容器
# -q: 靜默模式,只顯示容器編號
# --no-trunc:不截斷輸出
(3)退出容器
exit  // 直接關閉並退出容器

重新開啟一個終端,執行docker ps -l,會返回剛才我們建立的容器資訊,並且STATUS會提示已經退出。

那麼可不可以在互動式,不關閉容器的情況下,暫時退出,一會又可以回來呢?

Ctrl + P + Q

# 執行後,我們將退出容器,回到宿主機,使用docker ps -l,我們會發現這個剛才退出的容器STATUS是Up狀態。
(4)啟動容器
docker start [OPTIONS] CONTAINER [CONTAINER...]

# 同時可以啟動多個容器,容器名和ID可以混用

# OPTION說明(常用):
# -i : 進入互動式,此時只能進入一個容器

進入上面我們退出但是依然存活的容器

docker start -i 186ae928f07c
(5)重啟容器
docker restart [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死容器之前等待停止的時間(預設為10)
(6) 停止容器
docker stop [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死前等待停止的時間(預設為10)

docker kill [OPTIONS] CONTAINER [CONTAINER...]  // 強制關閉(相當於拔電源)

# OPTIONS說明:
# -s: 傳送到容器的訊號(預設為“KILL”)
(7)刪除容器
docker rm [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -f :強制刪除,不管是否在執行

# 上面的命令可以刪除一個或者多個,但是想全部刪除所有的容器,那麼我們要把全部的容器名或者ID都寫一遍嗎?

docker rm -f $(docker ps -aq)  # 刪除所有
docker ps -aq | xargs docker rm -f
(8) 啟動守護式容器
docker run -d 容器名/容器ID

說明:我們使用docker ps -a命令檢視,會發現剛才啟動的容器以及退出了,這是為什麼呢?

很重要的要說明一點:Docker容器後臺執行,必須要有一個前臺程序,容器執行的命令如果不是那些一直掛起的命令(比如執行top,tail),就會自動退出。

這就是Docker的機制問題,比如我們現在執行WEB容器,以Nginx為例,正常情況下,我們配置啟動服務只需要啟動響應的service即可,例如service nginx start,但是這樣做,Nginx為後臺程序模式執行,導致Docker前臺沒有執行的應用。這樣的容器後臺啟動後,會立即自殺因為它覺得它沒事可做。所以最佳的解決方案是將要執行的程式以前臺程序的形式執行。

那麼如何讓守護式容器不自動退出呢?我們可以執行一直掛起的命令。

docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
(9) 檢視容器日誌
docker logs [OPTIONS] CONTAINER

# OPTION說明:
# -t 加入時間戳
# -f 跟隨最新的日誌列印
# --tail 顯示最後多少條
(10) 檢視容器內的程序
docker top CONTAINER 
(11) 檢視容器內部細節
docker inspect CONTAINER
(12) 進入正在執行的容器並以命令列互動
  • exec在容器中開啟新的終端,並且可以啟動新的程序
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]  

docker exec -it CONTAINER /bin/bash   // 進入容器內部並互動

# 隔山打牛
docker exec -it 3d00a0a2877e ls -l /tmp 

# 3d00a0a2877e是我上面跑的守護式容器
# 進入容器,執行ls -l /tmp 命令後將結果返回給我宿主機,而使用者的介面還是停留在宿主機,沒有進入容器內部
  • attach直接進入容器啟動命令的終端,不會啟動新的程序
docker attach CONTAINER

# 注意,進入上面那個守護式容器後,我們會看到還是每隔兩秒列印hello Negan,而且不能退出,只能重新開啟一個終端,執行docker kill 命令
(13)容器與主機間的檔案拷貝
docker cp CONTAINER:SRC_PATH DEST_PATH  # 把容器內的檔案拷貝到宿主機
docker cp 90bd03598dd4:123.txt ~

docker cp SRC_PATH CONTAINER:DEST_PATH  # 把宿主機上的檔案拷貝到容器
docker cp 12345.txt 90bd03598dd4:~

練習

練習一:Docker部署Nginx

# 查詢一個nginx映象
docker search nginx
# 下載映象
docker pull nginx
# 啟動
docker run -d --name nginx01 -p 3344:80 nginx  # -p 3344(宿主機):80(容器埠)
# 測試(瀏覽器訪問)
123.56.243.64:3344 

五、視覺化

1、portainer

Docker圖形介面管理工具,提供一個後臺面板供我們操作。

docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer

六、Docker映象

1、映象是什麼

映象是一種輕量級、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體,它包含執行某個軟體所需的所有內容,包括程式碼、執行時、庫、環境變數和配置檔案。

所有應用直接打包docker映象,就可以直接跑起來。

如何獲取映象
  • 從遠端操作下載
  • 朋友拷貝
  • 自己製作一個映象DockerFile

2、映象載入原理

(1)聯合檔案系統

UnionFS(聯合檔案系統):Union檔案系統是一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改作為一次提交來一層一層疊加,同時可以將不同目錄掛在到同一個虛擬檔案系統下。聯合檔案系統是Docker映象的基礎,映象可以通過分層來進行繼承,基於基礎映象(沒有父映象),可以製作各種具體的應用映象。

特性:一次同時載入多個檔案系統,但從外面看起來,只能看到一個檔案系統,聯合載入會把各層檔案系統疊加起來,這樣最終的檔案系統會包含所有底層的檔案和目錄。

(2)Docker映象載入原理

Docker的映象實際上是由一層一層的檔案系統組成,也就是上面所說的聯合檔案系統。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引導載入kernel,Linux剛啟動時會載入bootfs檔案系統,在Docker映象最底層是bootfs。這一層與我們典型的Linux/Unix系統是一樣的,包含boot載入器和核心。當boot載入完成之後整個核心就都在記憶體中,此時記憶體的使用權已由bootfs轉交給核心,此時系統也會解除安裝bootfs。

rootfs(root file system),在bootfs之上,包含的就是典型Linux系統中的/dev,/proc,/bin,/etc等標準目錄和檔案。rootfs就是各種不同的作業系統發行版,比如Ubuntu,CentOS等等。

對於一個精簡的OS,rootfs可以很小,只需要包含最近本的命令,工具和程式庫就可以,因為底層直接用Host的kernel,自己只需要提供rootfs就可以,由此可見對於不同的Linux發行版,bootfs基本是一致的,rootfs會有差別,因此不同的發行版可以公用bootfs。

3、分層理解

(1)分層的映象

我們下載一個映象,注意觀察下載的日誌輸出,可以看到是一層一層在下載。

為什麼Docker映象採用這種分層的結構?

最大的好處,我覺得莫過於資源共享了,比如有多個映象從相同的Base映象構建而來,那麼宿主機只需要在磁碟上保留一份Base映象,同時記憶體中也只需要載入一份Base映象,這樣就可以為所有的容器服務了,而且映象每一層都可以被共享。

(2)理解

所有的Docker映象都始於一個基礎映象層,當進行修改或者增加新內容時,就會在當前映象層之上,建立新的映象層。假如基於Ubuntu Linux 16.04建立一個新的映象,這就是新映象的第一層;如果在該映象中新增Python包,就會在基礎映象之上建立第二個映象層,如果繼續新增一個安全補丁,就會建立第三個映象層。

再新增額外的映象層同時,映象始終保持是當前所有映象的組合。

注意:Docker映象都是隻讀的,當容器啟動時,一個新的可寫層被載入到映象的頂部。

​ 這一層就是我們通常說的容器層,容器之下的都叫映象層。

4、commit

docker commit 提交容器成為一個新的映象

docker commit -m="提交的描述資訊" -a="作者" 容器id 目標映象名:[TAG]

七、容器資料卷

如果資料在容器中,那麼我們容器刪除,資料就會丟失!

需求:資料需要持久化。

MySQL,容器刪了,刪庫跑路?Docker容器中產生的資料,需要同步到本地。

這就是卷技術,目錄的掛載,將我們容器內的目錄,掛載到Linux上。

1、使用資料卷

  • 直接使用命令來掛載
docker run -it -v 主機目錄:容器內目錄  # 雙向繫結,一方改變另一方自動改變

docker run -it -v /home/ceshi:/home centos /bin/bash

docker inspect d2343e9d338a  # 進行檢視

/*
...
 "Mounts": [   # 掛載 -v 
            {
                "Type": "bind",
                "Source": "/home/ceshi",  # 主機內的路徑
                "Destination": "/home",   # 容器內的路徑
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...
*/

2、實戰:安裝MySQL

思考:MySQL的資料持久化問題

docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7

3、具名掛載和匿名掛載

(1) 、匿名掛載
docker run -d -P --name nginx01 -v /etc/nginx nginx  # 只指定了容器內的路徑

docker volume ls  # 檢視所有volume的情況
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a  # 匿名掛載
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049

# 這種就是匿名掛載,我們在-v只寫了容器內的路徑,沒有寫容器外的路徑。
(2)、具名掛載
docker run -d -P -v juming:/etc/nginx --name nginx02 nginx   # 具名掛載

docker volume ls
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
local               juming  # 我們上面起的名字

# 通過-v 卷名:容器內路徑

# 所有的docker容器內的卷,沒有指定目錄的情況下,都在/var/lib/docker/volumes目錄下。
docker inspect juming  
[
    {
        "CreatedAt": "2020-12-09T00:22:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/juming/_data",
        "Name": "juming",
        "Options": null,
        "Scope": "local"
    }
]

我們通過具名掛載可以方便的找到我們掛載的卷,大多情況下我們都會使用具名掛載。

(3)、拓展
-v 容器內路徑  # 匿名掛載
-v 卷名:容器內路徑 # 具名掛載
-v /宿主機路徑:容器內路徑  # 指定路徑掛載

# 通過 -V 容器內路徑:ro rw改變讀寫許可權
ro readonly # 只讀
rw readwrite # 可讀可寫

# 一旦設定了只讀,容器對我們掛載出來的內容就有限定了。檔案只能通過宿主機來操作,容器無法操作。
docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx 

4、資料卷容器

容器間的資訊同步。

docker run -it -v /home --name c1 centos /bin/bash   # 啟動c1,作為一個父容器(指定容器內掛載目錄)
docker run -it --name c2 --volumes-from c1 centos /bin/bash  # 啟動c2,掛載c1,此時在兩個容器中/home中的操作就會同步


# 下面的容器是我們自己建立的,可以參考下面dockerfile進行構建
docker run -it --name docker01 negan/centos   # 啟動一個容器作為父容器(被掛載的容器)  
docker run -it --name docker02 --volumes-from docker01 negan/centos  #啟動容器2,掛載容器1 
docker run -it --name docker03 --volumes-from docker02 negan/centos  #啟動容器3,掛載容器2
# 上面的容器3也可以直接掛載到容器1上,然後我們進入任意一個容器,進入掛載卷volume01/volume02,進行操作,容器間的資料會自動同步。

結論:

容器之間配置資訊的傳遞,資料卷容器的宣告週期一直持續到沒有容器使用為止。

但是一旦持久到了本地,本地資料不會被刪除。

八、DockerFile

1、初識DockerFile

DockerFile就是用來構造docker映象的構建檔案,命令引數指令碼。通過這個指令碼,可以生成映象。映象是一層一層的,指令碼是一個個命令,每個命令就是一層。

# dockerfile1內容,所有命令都是大寫

FROM centos   # 基於centos

VOLUME ["volume01","volume02"]   # 掛載資料卷(匿名掛載)

CMD echo "----end-----"

CMD /bin/bash


# 構建
docker build -f dockerfile1 -t negan/centos .  # -f 指定路徑 -t指定名字,不加tag預設最新
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
 ---> 0d120b6ccaa8
Step 2/4 : VOLUME ["volume01","volume02"]
 ---> Running in 0cfe6b5be6bf
Removing intermediate container 0cfe6b5be6bf
 ---> 396a4a7cfe15
Step 3/4 : CMD echo "----end-----"
 ---> Running in fa535b5581fa
Removing intermediate container fa535b5581fa
 ---> 110d9f93f827
Step 4/4 : CMD /bin/bash
 ---> Running in 557a2bb87d97
Removing intermediate container 557a2bb87d97
 ---> c2c9b92d50ad
Successfully built c2c9b92d50ad
Successfully tagged negan/centos:latest

docker images  # 檢視

2、DokcerFile構建過程

(1)、基礎知識

每個保留關鍵字(指令)都必須是大寫字母

執行從上到下順序執行

#表示註釋

每一個指令都會建立提交一個新的映象層,並提交

DockerFile是面向開發的,我們以後要釋出專案,做映象,就需要編寫dockerfile檔案。

Docker映象逐漸成為企業交付的標準。

(2)、基本命令
FROM  #這個映象的媽媽是誰?(基礎映象,一切從這裡開始構建)
MAINTAINER # 告訴別人誰負責養他?(映象是誰寫的,指定維護者資訊,姓名+郵箱)
RUN  # 你想讓他幹啥?(映象構建的時候需要執行的命令)
ADD  # 給他點創業資金(複製檔案,會自動解壓)
WORKDIR # 映象的工作目錄
VOLUME  # 給他一個存放行李的地方(設定卷,容器內掛載到主機的目錄,匿名掛載)
EXPOSE  # 門牌號是多少?(指定對外埠)
CMD   # 指定這個容器啟動的時候要執行的命令,只有最後一個會生效,可被替代
ENTRYPOINT # 指定這個容器啟動時候要執行的命令,可以追加命令
ONBUILD  # 當構建一個被繼承DockerFile,這時就會執行ONBUILD指令
COPY    # 類似ADD,將我們檔案拷貝到映象中
ENV    # 構建的時候設定環境變數

3、實戰操作

(1)建立一個自己的CentOS
# vim Dockerfile

FROM centos
MAINTAINER Negan<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 構建
docker build -f Dockerfile -t negan/centos .

# 測試
docker run -it negan/centos
[root@ffae1f9eb97e local]# pwd
/usr/local   # 進入的是我們在dockerfile設定的工作目錄

# 檢視映象構建過程
docker history 映象名/ID
(2)CMD與ENTRYPOINT區別

兩者都是容器啟動的時候要執行的命令,CMD命令只有最後一個會生效,後面不支援追加命令,會被替代。ENTRYPOINT不會被替代,可以追加命令。

CMD

# vim cmd
FROM centos
CMD ["ls","-a"]

# 構建
docker build -f cmd -t cmd_test .

# 執行,發現我們的ls -a命令生效
docker run cmd_test
.
..
.dockerenv
bin
dev
etc
......

# 追加命令執行
docker run cmd_test -l
# 丟擲錯誤,不能追加命令,原來的ls -a命令被-l替換,但是-l不是一個有效的命令
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.

# 追加完整的命令
docker run cmd_test ls -al

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:36 .
drwxr-xr-x  1 root root 4096 Dec 10 14:36 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:36 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:36 dev
......

ENTRYPOINT

# vim entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]

# 構建
docker build -f entrypoint -t entrypoint_test .

# 執行
docker run entrypoint_test
.
..
.dockerenv
bin
dev
etc

# 追加命令執行
docker run entrypoint_test -l

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:41 .
drwxr-xr-x  1 root root 4096 Dec 10 14:41 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:41 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:41 dev
drwxr-xr-x  1 root root 4096 Dec 10 14:41 etc
......

4、實戰構建tomcat

(1)環境準備
 ll
total 166472
-rw-r--r-- 1 root root  11437266 Dec  9 16:22 apache-tomcat-9.0.40.tar.gz
-rw-r--r-- 1 root root       641 Dec 10 23:26 Dockerfile
-rw-r--r-- 1 root root 159019376 Dec  9 17:39 jdk-8u11-linux-x64.tar.gz
-rw-r--r-- 1 root root         0 Dec 10 22:48 readme.txt
(2)構建映象
# vim Dockerfile (Dockerfile是官方建議命名)

FROM centos
MAINTAINER Negan<[email protected]>

COPY readme.txt /usr/local/readme.txt

ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
ADD apache-tomcat-9.0.40.tar.gz /usr/local/

RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out

# 構建
docker -t tomcat .
(3)啟動容器
docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat

九、釋出自己的映象

1、docker hub

首先需要在DockerHub上註冊自己的賬號,並且確定這個賬號可以登入。

在我們的伺服器上進行登入,登入成功後提交我們的映象。

# 登入
docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
  -p, --password string   Password
      --password-stdin    Take the password from stdin
  -u, --username string   Username


# 登入成功後推送我們的映象
docker push [OPTIONS] NAME[:TAG]  

Push an image or a repository to a registry

Options:
      --disable-content-trust   Skip image signing (default true)

docker tag tomcat huiyichanmian/tomcat  # 需要改名,推送改名後的(前面加自己的使用者名稱)
docker push huiyichanmian/tomcat

2、阿里雲

登入阿里雲,找到容器映象服務,使用映象倉庫。建立名稱空間,建立映象倉庫。選擇本地倉庫。

具體阿里雲上有特別詳細的步驟,這裡不再贅述了。

十、Docker網路

1、理解docker0

(1)檢視本機網絡卡資訊
ip addr 

# 本地迴環地址
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
       
# 阿里雲內網地址
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286793195sec preferred_lft 286793195sec
    
# docker0地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

(2)檢視容器網絡卡資訊

我們獲取一個tomcat映象,用來測試。

docker run -d -P --name t1 tomcat 

docker exec -it t1 ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# 我們發現容器啟動時候會得到一個eth0@ifxxx的網絡卡,而且ip地址和上面的docker0裡的是在同一網段。
233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
(3)、再次檢視本地網絡卡資訊
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286792020sec preferred_lft 286792020sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
# 我們發現多了一條網絡卡資訊,而且與容器裡面的網絡卡有某種對應關係。(233,234)
234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default

我們重複上面的操作,不難發現我們只要安裝了docker,本地網卡里就會多一個docker0,而且我們每啟動一個容器,docker就會給容器分配一個網絡卡,而且在本地也會多出一個網絡卡資訊,與容器內的相對應。這就是veth_pair技術,就是一對虛擬裝置介面,它們都是成對出現,一段連著協議,一段彼此相連。正因為有了這個特性,我們通常veth_pair充當了一個橋樑,連線各種虛擬網路裝置。

所有的容器不指定網路的情況下,都是由docker0路由的,docker會給我們的容器分配一個預設的ip。

Docker使用的是Linux的橋接,宿主機中docker0是一個Docker容器的網橋。所有的網路介面都是虛擬的。

只要容器刪除,對應的網橋也響應的被刪除。

問題:我們每次重啟容器,容器的ip地址會變,而我們專案中一些配置使用的固定ip,這時候也需要做相應的改變,那麼我們可不可以直接設定服務名呢,下次重啟時候,配置直接找服務名。

我們啟動兩個tomcat,進行測試,互相ping其對應的名字,看是否能通?

docker exec -it t1 ping t2
ping: t2: Name or service not known
# 答案是肯定的,不能識別t2,如何解決?
# 我們使用--link進行連線
docker run -d -P --name t3 --link t2 tomcat

# 我們嘗試使用t3來ping t2
docker exec -it t3 ping t2
# 我們發現竟然通了
PING t2 (172.17.0.3) 56(84) bytes of data.
64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
......

那麼--link做了什麼呢?

docker exec -it t3 cat /etc/hosts  # 我們來檢視t3的hosts檔案

127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	t2 6bf3c12674c8  # 原因在這了,這裡對t2做了標記,當ping t2時候,自動轉到172.17.0.3
172.17.0.4	b6dae0572f93

3、自定義網路

(1)檢視docker網路
docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
afc0c673386f        none                null                local

# bridge 橋接(docker預設)
# host 與主機共享
# none 不配置
(2)容器啟動時的預設網路

docker0是我們預設的網路,不支援域名訪問,可以使用--link打通。

 # 一般我們啟動容器是這樣子,使用的是預設網路,而這個預設網路就是bridge,所以下面兩條命令是相同的
docker run -d -P --name t1 tomcat  

docker run -d -P --name t1 --net bridge tomcat 
(3)建立網路
# --driver bridge 預設,可以不寫
# --subnet 192.168.0.0/16 子網掩碼
# --gateway 192.168.0.1 預設閘道器
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

docker network ls

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
0e98462f3e8e        mynet               bridge              local  # 我們自己建立的網路
afc0c673386f        none                null                local


ip addr

.....
# 我們自己建立的網路
239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
       valid_lft forever preferred_lft forever
.....

啟動兩個容器,使用我們自己建立的網路

docker run -P -d --name t1 --net mynet tomcat
docker run -P -d --name t2 --net mynet tomcat

# 檢視我們自己建立的網路資訊
docker network mynet inspect

# 我們發現我們剛啟動的兩個容器使用的是我們剛建立的網路
......
"Containers": {
            "1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
                "Name": "t2",
                "EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
                "Name": "t1",
                "EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
.......

那麼使用自己建立的網路有什麼好處呢?

我們回到我們以前的問題,就是域名ping不通的問題上。

docker exec -it t1 ping t2
PING t2 (192.168.0.3) 56(84) bytes of data.
64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
......

docker exec -it t2 ping t1
PING t1 (192.168.0.2) 56(84) bytes of data.
64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
......

我們發現域名可以ping通了,那麼說明我們自定義的網路已經幫我們維護好了對應關係。

這樣不同的叢集使用不同的網路,也能保證叢集的安全和健康。

4、網路連通

現在有這樣一個需求,t1,t2使用的是我們自己定義的網路,t3,t4使用的預設的docker0網路。那麼現在t3和t1或者t2能否通訊呢?

我們知道,docker0的預設閘道器是172.17.0.1,mynet預設閘道器是192.168.0.1,他們直接屬於不同的網段,不能進行通訊。那麼現在如何解決上面的問題呢?

那麼mynet能不能給t3分配一個ip地址呢?如果能分配,那麼問題就應該可以得到解決。

docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
      --alias strings           Add network-scoped alias for the container
      --driver-opt strings      driver options for the network
      --ip string               IPv4 address (e.g., 172.30.100.104)
      --ip6 string              IPv6 address (e.g., 2001:db8::33)
      --link list               Add link to another container
      --link-local-ip strings   Add a link-local address for the container
   
  # 一個容器兩個ip地址 
 docker network connect mynet t3   # 將t3加入到mynet網路中
 
 # 檢視mynet資訊
 docker network inspect mynet
 
 "Containers": {
			......
            "d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
                "Name": "t3",  # 我們發現了t3
                "EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
                "MacAddress": "02:42:c0:a8:00:04",
                "IPv4Address": "192.168.0.4/16",
                "IPv6Address": ""
            }
        },
        ......

這時候,t3就能和t1,t2進行通訊了。

5、部署Redis叢集

# 建立網路
docker network create redis --subnet 172.38.0.0/16

# 通過指令碼建立六個redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379 
bind 0.0.0.0
cluster-enabled yes 
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

# 啟動容器
vim redis.py

import os
for i in range(1, 7):
    str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} \
    -v /mydata/redis/node-{}/data:/data \
    -v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf \
    -d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
    os.system(str)

python reidis.py

# 建立叢集
docker exec -it redis-1 /bash/sh  # 進入redis-1容器

redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

# 建立叢集。。。。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   slots: (0 slots) slave
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   slots: (0 slots) slave
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   slots: (0 slots) slave
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.


# 進行測試
redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:160
cluster_stats_messages_pong_sent:164
cluster_stats_messages_sent:324
cluster_stats_messages_ping_received:159
cluster_stats_messages_pong_received:160
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:324

十一、Docker Compose

1、介紹

Compose是Docker官方開源的專案,需要安裝。

Compose是用於定義和執行多容器Docker應用程式的工具。通過Compose,可以使用YAML檔案來配置應用程式的服務。然後,使用一個命令,就可以從配置中建立並啟動所有服務。

使用Compose基本上是一個三步過程:

  1. Dockerfile保證我們的專案在任何地方執行
  2. docker-compose檔案
  3. 啟動

2、快速開始

(1)安裝
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

docker-compose --version
# 安裝成功
# docker-compose version 1.27.4, build 40524192
(2)使用
# 為專案建立目錄
mkdir composetest
cd composetest

# 編寫一段flask程式
vim app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)  # 這裡主機名直接用“redis”,而不是ip地址

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
    
# 編寫requirements.txt檔案(不指定版本,下載最新)
flask
redis

# 編寫Dockerfile檔案
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]


# 編寫docker-compose.yml檔案
# 檔案中定義了兩個服務,web和redis,web是從Dockerfile開始構建,redis使用的是公共映象
version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
  redis:
    image: "redis:alpine"
    
# 執行
docker-compose up 

3、搭建部落格

# 建立並進入目錄
mkdir wordpress && cd wordpress

# 編寫docker-compose.yml檔案來啟動,並建立了一個單獨Mysql例項,具有用於資料永續性的卷掛載
version: "3.3"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      W0RDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
  volumes:
    db_data: {}
    

# 啟動執行
docker-compose up -d

十二、Docker Swarm

1、環境準備

準備四臺伺服器。安裝docker。

2、swarm叢集搭建

docker swarm COMMAND
Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm   # 初始化一個節點(管理節點)
  join        Join a swarm as a node and/or manager  # 加入節點
  join-token  Manage join tokens   # 通過節點token加入
  leave       Leave the swarm    # 離開節點
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm  #更新

# 首先我們初始化一個節點
docker swarm init --advertise-addr + 自己的ip(這裡用內網地址,主要是省錢)
docker swarm init --advertise-addr 172.27.0.4

# 提示我們節點建立成功
Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.

To add a worker to this swarm, run the following command:
	
	# 在其他機器上執行,加入這個節點
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 生成一個管理節點的token,
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

# 我們在機器2上行加入節點的命令
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 我們在機器1上檢視節點資訊
docker node ls
# 我們發現一個管理節點,一個工作節點,狀態都是就緒狀態
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos   Ready     Active         Leader           20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos   Ready     Active                          20.10.0

# 現在將機器3也加入,同樣是work節點
# 到此為止,現在只有機器4沒有加入,這時候我們想把它設定成管理節點。
# 在機器1上執行生成管理節點的命令,在機器4上執行
docker swarm join-token manager
# 生成的命令在機器4上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 此時機器4也是管理節點了
This node joined a swarm as a manager.

# 至此我們可以在機器1或者機器4上進行其他操作了。(只能在管理節點上操作)

3、Raft協議

在前面的步驟,我們已經完成了雙主雙從叢集的搭建。

Raft協議:保證大多數節點存活才能使用。必須大於1,叢集至少大於3臺。

實驗1

將機器1上的docker停止,宕機,現在叢集中只有一個管理節點了。叢集是否可用。

#我們在機器四上檢視節點資訊
docker node ls
# 發現我們的叢集已經不能用了
Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded

# 我們將機器1上的docker重啟,發現叢集又能用了,但是此時機器1上的docker已經不是leader了,leader自動轉給機器4了

實驗2

我們將工作節點離開叢集,檢視叢集資訊

# 我們在機器2上執行
docker swarm leave

# 在機器一上檢視節點資訊
docker node ls
# 我們發現機器2狀態是Down
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos    Ready     Active         Reachable        20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

實驗3

現在我們將機器2也設定成管理節點(此時叢集有三個管理節點),隨機down掉一個管理節點,叢集是否正常執行。

# 在機器2上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377

# 將機器1的docker宕機
systemctl stop docker

# 在機器二上檢視節點資訊
docker node ls
# 叢集正常,且可以看到機器1宕機了
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm     VM-0-4-centos    Ready     Active         Unreachable      20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
vdwcwr3v6qrn6da40zdrjkwmy *   VM-0-7-centos    Ready     Active         Reachable        20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

4、彈性建立服務

docker service COMMAND

Commands:
  create      Create a new service  # 建立一個服務
  inspect     Display detailed information on one or more services  # 檢視服務資訊
  logs        Fetch the logs of a service or task  # 日誌
  ls          List services   # 列表
  ps          List the tasks of one or more services   # 檢視我們的服務
  rm          Remove one or more services  # 刪除服務
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services  # 動態擴縮容
  update      Update a service  # 更新

docker service create -p 8888:80 --name n1 nginx  # 建立一個服務,在叢集中隨機分配
kj0xokbxvf5uw91bswgp1cukf
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

# 給我們的服務增加三個副本
docker service update --replicas 3 n1

# 動態擴縮容
docker service scale n1=10   # 和上面updata一樣,這個方便

docker ps # 檢視

服務,叢集中任意的節點都可以訪問,服務可以有多個副本動態擴縮容實現高可用。

5、使用Docker stack部署部落格

我們現在需要將上面的部落格跑在我們的叢集中,並且要開十個副本。

# 編輯docker-compose.yml檔案
version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     deploy:
       replicas: 10
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}


# 進行啟動
docker stack deploy -c docker-compose.yml wordpress

# 檢視
docker service ls

一、安裝

環境:Centos7

1、解除安裝舊版本

較舊的Docker版本稱為dockerdocker-engine。如果已安裝這些程式,請解除安裝它們以及相關的依賴項。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2、使用儲存庫安裝

注意:官方給了三種安裝方式,這裡我們選擇最常用的儲存庫安裝

在新主機上首次安裝Docker Engine之前,需要設定Docker儲存庫。之後可以從儲存庫安裝和更新Docker。

sudo yum install -y yum-utils

# 注意:此處是大坑,可以自行換成阿里的源
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    
# 我們用阿里的    
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
 
# 進行安裝(如果不換源,速度會很慢)
sudo yum install docker-ce docker-ce-cli containerd.io

3、啟動以及驗證

systemctl start docker   # 啟動
systemctl status docker  #  檢視狀態
docker version  # 檢視版本
docker run hello-world   #  測試

4、設定加速器(阿里雲)

注意:裡面的地址每個人都有專屬的地址。

開啟阿里雲官網->控制檯->搜尋容器映象服務->右下角找到映象加速器

sudo mkdir -p /etc/docker

tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

docker run hello-world

5、解除安裝

# 解除安裝Docker Engine,CLI和Containerd軟體包
sudo yum remove docker-ce docker-ce-cli containerd.io

# 主機上的映像,容器,卷或自定義配置檔案不會自動刪除。要刪除所有影象,容器和卷
sudo rm -rf /var/lib/docker

二、Docker三要素

1、倉庫(Repository)

倉庫:集中存放映象的場所。

注意:倉庫(Repository)和倉庫註冊伺服器(Registry)是有區別的,倉庫註冊伺服器往往存放著多個倉庫,每個倉庫中又包含多個映象,每個映象有不同的標籤(tag)

倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。

最大的公開倉庫是Docker Hub(https://hub.docker.com),存放了數量龐大的映象供使用者下載,國內的公開倉庫包括阿里雲、網易雲等。

2、映象(Image)

一個只讀模板,用來建立Docker容器,一個映象可以建立很多個容器。

容器與映象的關係類似於面向物件程式設計中的物件和類

Docker 面向物件
容器 物件
映象

3、容器 (Container)

獨立執行的一個或一組應用。

容器使用映象建立的執行例項。

它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的,保證安全的平臺。

可以把容器看作是一個簡易版的Linux環境(包括root使用者許可權,程序空間,使用者空間和網路空間等等)和執行在啟動的應用程式。

容器的定義和映象幾乎一模一樣,也是一堆層的統一視角,唯一區別在於容器的最上面那一層是可讀可寫的。

三、簡單瞭解底層原理

1、Docker是怎麼工作的

Docker是一個Client-Server結構的系統,Docker守護程序執行在主機上,然後通過Socket連線從客戶端訪問,守護程序從客戶端接收命令並管理執行在主機上的容器。

2、Docker為什麼比VM快?

(1)Docker有著比虛擬機器更少的抽象層,由於Docker不需要Hypervisor實現硬體資源虛擬化,執行在Docker容器上的程式直接使用的都是實際物理機的硬體資源,因此在CPU、記憶體利用率上Docker將會在效率上有明顯的優勢。

(2)Docker利用的是宿主機的核心,而不需要Guest OS。因此當新建一個容器時,Docker不需要和虛擬機器一樣重疊載入一個作業系統核心,從而避免引尋、載入作業系統核心,返回比較費時費資源的過程。當新建一個虛擬機器時,虛擬機器軟體需要載入Guest OS,這個新建過程是分鐘級別的,而Docker由於直接利用宿主機的作業系統,因此新建一個Docker容器只需幾秒鐘。

Docker容器 虛擬機器(VM)
作業系統 與宿主機共享OS 宿主機OS上執行虛擬機器OS
儲存大小 映象小,便於儲存與傳輸 映象龐大(vmdk,vdi等)
執行效能 幾乎無額外效能損失 作業系統額外的CPU、記憶體消耗
移植性 輕便、靈活,適應於Linux 笨重,與虛擬化技術耦合度高
硬體親和性 面向軟體開發者 面向硬體運維者

四、相關命令

1、幫助命令

docker version  # 檢視docker版本資訊
docker info  # 詳細說明
docker --help  # 幫助命令

2、映象命令

(1)列出本地映象
docker images [OPTIONS] [REPOSITORY[:TAG]]

# OPTIONS說明:
	-a: 列出本地所有的映象(含中間映像層)
    -q: 只顯示映象ID
    --digests: 顯示映象的摘要資訊
    --no-trunc: 顯示完整的映象資訊

# 各個選項說明:
# REPOSITORY:表示映象的倉庫源
# TAG:映象的標籤
# IMAGE ID:映象ID
# CREATED:映象建立時間
# SIZE:映象大小	

同一倉庫源可以有多個TAG,代表這個倉庫源的不同版本,我們使用REPOSITORY:TAG來定義不同的映象,如果不指定一個映象的版本標籤,例如我們只使用ubuntu,docker將預設使用ubuntu:latest映象。

(2)查詢映象
docker search [options] 某個xxx映象名字  # 會在https://hub.docker.com上去查詢

docker search mysql --filter=STARS=3000  # 搜尋星數大於等於3000的映象

# OPTIONS說明:
# --no-trunc: 顯示完整的映象描述	
# -s:列出點贊數不小於指定值的映象
# --automated: 只列出automated build型別的映象
(3)獲取映象
docker pull 映象名字[:TAG]  # 如果不寫TAG,則預設獲取latest,此時從我們配置的阿里雲上獲取映象

docker pull mysql  # 獲取最新版本的mysql
Using default tag: latest
latest: Pulling from library/mysql
852e50cd189d: Pull complete   # 分卷下載
29969ddb0ffb: Pull complete 
a43f41a44c48: Pull complete 
5cdd802543a3: Pull complete 
b79b040de953: Pull complete 
938c64119969: Pull complete 
7689ec51a0d9: Pull complete 
a880ba7c411f: Pull complete 
984f656ec6ca: Pull complete 
9f497bce458a: Pull complete 
b9940f97694b: Pull complete 
2f069358dc96: Pull complete 
Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest  
# docker pull mysql 等價於docker pull docker.io/library/mysql:latest 

docker pull mysql:5.7  # 下載指定版本的映象
5.7: Pulling from library/mysql
852e50cd189d: Already exists  # 聯合檔案系統,已經存在的不會去重複下載
29969ddb0ffb: Already exists 
a43f41a44c48: Already exists 
5cdd802543a3: Already exists 
b79b040de953: Already exists 
938c64119969: Already exists 
7689ec51a0d9: Already exists 
36bd6224d58f: Pull complete 
cab9d3fa4c8c: Pull complete 
1b741e1c47de: Pull complete 
aac9d11987ac: Pull complete 
Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
(4)刪除映象
# 刪除單個映象
docker rmi -f 映象名/ID  # 如果是映象名,後面不帶TAG,則預設刪除latest

# 刪除多個映象
docker rmi -f 映象名1 映象名2 ...
docker rmi -f id1 id2 ...     // 注意:兩種方式不能混用

# 刪除全部映象
docker rmi -f $(docker images -aq)   

3、容器命令

​ 有映象才能建立容器,這是根本前提(下載一個Centos映象演示)

(1) 新建並啟動容器(互動式)
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run -it --name mycentos centos  // 如果不指定別名,系統會自動分配

# OPTIONS說明:
# --name 容器新名字:為容器指定一個名稱
# -d:後臺執行容器,並返回容器ID,即啟動守護式容器
# -i: 以互動模式執行容器,通常與-t同時使用
# -t: 為容器重新分配一個偽輸入終端,通常與-i同時使用
# -P: 隨機埠對映
# -p: 指定埠對映有以下四種格式
		ip:hostPort:containerPort
		ip::containerPort
		hostPort:containerPort
		containerPort

# 測試
[root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash  # 啟動並進入容器
[root@783cb2f26230 /]# ls   # 在容器內檢視
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@783cb2f26230 /]# exit   # 退出容器
exit
[root@iz2zeaj5c9isqt1zj9elpbz ~]# 

(2)列出當前所有正在執行的容器
docker ps [OPTIONS]  # 不帶OPTIONS,則只列出正在執行的容器

# OPTIONS說明(常用):
# -a:列出當前所有正在執行的容器以及歷史上執行過的
# -l:顯示最近建立的容器
# -n: 顯示最近n個建立的容器
# -q: 靜默模式,只顯示容器編號
# --no-trunc:不截斷輸出
(3)退出容器
exit  // 直接關閉並退出容器

重新開啟一個終端,執行docker ps -l,會返回剛才我們建立的容器資訊,並且STATUS會提示已經退出。

那麼可不可以在互動式,不關閉容器的情況下,暫時退出,一會又可以回來呢?

Ctrl + P + Q

# 執行後,我們將退出容器,回到宿主機,使用docker ps -l,我們會發現這個剛才退出的容器STATUS是Up狀態。
(4)啟動容器
docker start [OPTIONS] CONTAINER [CONTAINER...]

# 同時可以啟動多個容器,容器名和ID可以混用

# OPTION說明(常用):
# -i : 進入互動式,此時只能進入一個容器

進入上面我們退出但是依然存活的容器

docker start -i 186ae928f07c
(5)重啟容器
docker restart [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死容器之前等待停止的時間(預設為10)
(6) 停止容器
docker stop [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死前等待停止的時間(預設為10)

docker kill [OPTIONS] CONTAINER [CONTAINER...]  // 強制關閉(相當於拔電源)

# OPTIONS說明:
# -s: 傳送到容器的訊號(預設為“KILL”)
(7)刪除容器
docker rm [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -f :強制刪除,不管是否在執行

# 上面的命令可以刪除一個或者多個,但是想全部刪除所有的容器,那麼我們要把全部的容器名或者ID都寫一遍嗎?

docker rm -f $(docker ps -aq)  # 刪除所有
docker ps -aq | xargs docker rm -f
(8) 啟動守護式容器
docker run -d 容器名/容器ID

說明:我們使用docker ps -a命令檢視,會發現剛才啟動的容器以及退出了,這是為什麼呢?

很重要的要說明一點:Docker容器後臺執行,必須要有一個前臺程序,容器執行的命令如果不是那些一直掛起的命令(比如執行top,tail),就會自動退出。

這就是Docker的機制問題,比如我們現在執行WEB容器,以Nginx為例,正常情況下,我們配置啟動服務只需要啟動響應的service即可,例如service nginx start,但是這樣做,Nginx為後臺程序模式執行,導致Docker前臺沒有執行的應用。這樣的容器後臺啟動後,會立即自殺因為它覺得它沒事可做。所以最佳的解決方案是將要執行的程式以前臺程序的形式執行。

那麼如何讓守護式容器不自動退出呢?我們可以執行一直掛起的命令。

docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
(9) 檢視容器日誌
docker logs [OPTIONS] CONTAINER

# OPTION說明:
# -t 加入時間戳
# -f 跟隨最新的日誌列印
# --tail 顯示最後多少條
(10) 檢視容器內的程序
docker top CONTAINER 
(11) 檢視容器內部細節
docker inspect CONTAINER
(12) 進入正在執行的容器並以命令列互動
  • exec在容器中開啟新的終端,並且可以啟動新的程序
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]  

docker exec -it CONTAINER /bin/bash   // 進入容器內部並互動

# 隔山打牛
docker exec -it 3d00a0a2877e ls -l /tmp 

# 3d00a0a2877e是我上面跑的守護式容器
# 進入容器,執行ls -l /tmp 命令後將結果返回給我宿主機,而使用者的介面還是停留在宿主機,沒有進入容器內部
  • attach直接進入容器啟動命令的終端,不會啟動新的程序
docker attach CONTAINER

# 注意,進入上面那個守護式容器後,我們會看到還是每隔兩秒列印hello Negan,而且不能退出,只能重新開啟一個終端,執行docker kill 命令
(13)容器與主機間的檔案拷貝
docker cp CONTAINER:SRC_PATH DEST_PATH  # 把容器內的檔案拷貝到宿主機
docker cp 90bd03598dd4:123.txt ~

docker cp SRC_PATH CONTAINER:DEST_PATH  # 把宿主機上的檔案拷貝到容器
docker cp 12345.txt 90bd03598dd4:~

練習

練習一:Docker部署Nginx

# 查詢一個nginx映象
docker search nginx
# 下載映象
docker pull nginx
# 啟動
docker run -d --name nginx01 -p 3344:80 nginx  # -p 3344(宿主機):80(容器埠)
# 測試(瀏覽器訪問)
123.56.243.64:3344 

五、視覺化

1、portainer

Docker圖形介面管理工具,提供一個後臺面板供我們操作。

docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer

六、Docker映象

1、映象是什麼

映象是一種輕量級、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體,它包含執行某個軟體所需的所有內容,包括程式碼、執行時、庫、環境變數和配置檔案。

所有應用直接打包docker映象,就可以直接跑起來。

如何獲取映象
  • 從遠端操作下載
  • 朋友拷貝
  • 自己製作一個映象DockerFile

2、映象載入原理

(1)聯合檔案系統

UnionFS(聯合檔案系統):Union檔案系統是一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改作為一次提交來一層一層疊加,同時可以將不同目錄掛在到同一個虛擬檔案系統下。聯合檔案系統是Docker映象的基礎,映象可以通過分層來進行繼承,基於基礎映象(沒有父映象),可以製作各種具體的應用映象。

特性:一次同時載入多個檔案系統,但從外面看起來,只能看到一個檔案系統,聯合載入會把各層檔案系統疊加起來,這樣最終的檔案系統會包含所有底層的檔案和目錄。

(2)Docker映象載入原理

Docker的映象實際上是由一層一層的檔案系統組成,也就是上面所說的聯合檔案系統。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引導載入kernel,Linux剛啟動時會載入bootfs檔案系統,在Docker映象最底層是bootfs。這一層與我們典型的Linux/Unix系統是一樣的,包含boot載入器和核心。當boot載入完成之後整個核心就都在記憶體中,此時記憶體的使用權已由bootfs轉交給核心,此時系統也會解除安裝bootfs。

rootfs(root file system),在bootfs之上,包含的就是典型Linux系統中的/dev,/proc,/bin,/etc等標準目錄和檔案。rootfs就是各種不同的作業系統發行版,比如Ubuntu,CentOS等等。

對於一個精簡的OS,rootfs可以很小,只需要包含最近本的命令,工具和程式庫就可以,因為底層直接用Host的kernel,自己只需要提供rootfs就可以,由此可見對於不同的Linux發行版,bootfs基本是一致的,rootfs會有差別,因此不同的發行版可以公用bootfs。

3、分層理解

(1)分層的映象

我們下載一個映象,注意觀察下載的日誌輸出,可以看到是一層一層在下載。

為什麼Docker映象採用這種分層的結構?

最大的好處,我覺得莫過於資源共享了,比如有多個映象從相同的Base映象構建而來,那麼宿主機只需要在磁碟上保留一份Base映象,同時記憶體中也只需要載入一份Base映象,這樣就可以為所有的容器服務了,而且映象每一層都可以被共享。

(2)理解

所有的Docker映象都始於一個基礎映象層,當進行修改或者增加新內容時,就會在當前映象層之上,建立新的映象層。假如基於Ubuntu Linux 16.04建立一個新的映象,這就是新映象的第一層;如果在該映象中新增Python包,就會在基礎映象之上建立第二個映象層,如果繼續新增一個安全補丁,就會建立第三個映象層。

再新增額外的映象層同時,映象始終保持是當前所有映象的組合。

注意:Docker映象都是隻讀的,當容器啟動時,一個新的可寫層被載入到映象的頂部。

​ 這一層就是我們通常說的容器層,容器之下的都叫映象層。

4、commit

docker commit 提交容器成為一個新的映象

docker commit -m="提交的描述資訊" -a="作者" 容器id 目標映象名:[TAG]

七、容器資料卷

如果資料在容器中,那麼我們容器刪除,資料就會丟失!

需求:資料需要持久化。

MySQL,容器刪了,刪庫跑路?Docker容器中產生的資料,需要同步到本地。

這就是卷技術,目錄的掛載,將我們容器內的目錄,掛載到Linux上。

1、使用資料卷

  • 直接使用命令來掛載
docker run -it -v 主機目錄:容器內目錄  # 雙向繫結,一方改變另一方自動改變

docker run -it -v /home/ceshi:/home centos /bin/bash

docker inspect d2343e9d338a  # 進行檢視

/*
...
 "Mounts": [   # 掛載 -v 
            {
                "Type": "bind",
                "Source": "/home/ceshi",  # 主機內的路徑
                "Destination": "/home",   # 容器內的路徑
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...
*/

2、實戰:安裝MySQL

思考:MySQL的資料持久化問題

docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7

3、具名掛載和匿名掛載

(1) 、匿名掛載
docker run -d -P --name nginx01 -v /etc/nginx nginx  # 只指定了容器內的路徑

docker volume ls  # 檢視所有volume的情況
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a  # 匿名掛載
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049

# 這種就是匿名掛載,我們在-v只寫了容器內的路徑,沒有寫容器外的路徑。
(2)、具名掛載
docker run -d -P -v juming:/etc/nginx --name nginx02 nginx   # 具名掛載

docker volume ls
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
local               juming  # 我們上面起的名字

# 通過-v 卷名:容器內路徑

# 所有的docker容器內的卷,沒有指定目錄的情況下,都在/var/lib/docker/volumes目錄下。
docker inspect juming  
[
    {
        "CreatedAt": "2020-12-09T00:22:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/juming/_data",
        "Name": "juming",
        "Options": null,
        "Scope": "local"
    }
]

我們通過具名掛載可以方便的找到我們掛載的卷,大多情況下我們都會使用具名掛載。

(3)、拓展
-v 容器內路徑  # 匿名掛載
-v 卷名:容器內路徑 # 具名掛載
-v /宿主機路徑:容器內路徑  # 指定路徑掛載

# 通過 -V 容器內路徑:ro rw改變讀寫許可權
ro readonly # 只讀
rw readwrite # 可讀可寫

# 一旦設定了只讀,容器對我們掛載出來的內容就有限定了。檔案只能通過宿主機來操作,容器無法操作。
docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx 

4、資料卷容器

容器間的資訊同步。

docker run -it -v /home --name c1 centos /bin/bash   # 啟動c1,作為一個父容器(指定容器內掛載目錄)
docker run -it --name c2 --volumes-from c1 centos /bin/bash  # 啟動c2,掛載c1,此時在兩個容器中/home中的操作就會同步


# 下面的容器是我們自己建立的,可以參考下面dockerfile進行構建
docker run -it --name docker01 negan/centos   # 啟動一個容器作為父容器(被掛載的容器)  
docker run -it --name docker02 --volumes-from docker01 negan/centos  #啟動容器2,掛載容器1 
docker run -it --name docker03 --volumes-from docker02 negan/centos  #啟動容器3,掛載容器2
# 上面的容器3也可以直接掛載到容器1上,然後我們進入任意一個容器,進入掛載卷volume01/volume02,進行操作,容器間的資料會自動同步。

結論:

容器之間配置資訊的傳遞,資料卷容器的宣告週期一直持續到沒有容器使用為止。

但是一旦持久到了本地,本地資料不會被刪除。

八、DockerFile

1、初識DockerFile

DockerFile就是用來構造docker映象的構建檔案,命令引數指令碼。通過這個指令碼,可以生成映象。映象是一層一層的,指令碼是一個個命令,每個命令就是一層。

# dockerfile1內容,所有命令都是大寫

FROM centos   # 基於centos

VOLUME ["volume01","volume02"]   # 掛載資料卷(匿名掛載)

CMD echo "----end-----"

CMD /bin/bash


# 構建
docker build -f dockerfile1 -t negan/centos .  # -f 指定路徑 -t指定名字,不加tag預設最新
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
 ---> 0d120b6ccaa8
Step 2/4 : VOLUME ["volume01","volume02"]
 ---> Running in 0cfe6b5be6bf
Removing intermediate container 0cfe6b5be6bf
 ---> 396a4a7cfe15
Step 3/4 : CMD echo "----end-----"
 ---> Running in fa535b5581fa
Removing intermediate container fa535b5581fa
 ---> 110d9f93f827
Step 4/4 : CMD /bin/bash
 ---> Running in 557a2bb87d97
Removing intermediate container 557a2bb87d97
 ---> c2c9b92d50ad
Successfully built c2c9b92d50ad
Successfully tagged negan/centos:latest

docker images  # 檢視

2、DokcerFile構建過程

(1)、基礎知識

每個保留關鍵字(指令)都必須是大寫字母

執行從上到下順序執行

#表示註釋

每一個指令都會建立提交一個新的映象層,並提交

DockerFile是面向開發的,我們以後要釋出專案,做映象,就需要編寫dockerfile檔案。

Docker映象逐漸成為企業交付的標準。

(2)、基本命令
FROM  #這個映象的媽媽是誰?(基礎映象,一切從這裡開始構建)
MAINTAINER # 告訴別人誰負責養他?(映象是誰寫的,指定維護者資訊,姓名+郵箱)
RUN  # 你想讓他幹啥?(映象構建的時候需要執行的命令)
ADD  # 給他點創業資金(複製檔案,會自動解壓)
WORKDIR # 映象的工作目錄
VOLUME  # 給他一個存放行李的地方(設定卷,容器內掛載到主機的目錄,匿名掛載)
EXPOSE  # 門牌號是多少?(指定對外埠)
CMD   # 指定這個容器啟動的時候要執行的命令,只有最後一個會生效,可被替代
ENTRYPOINT # 指定這個容器啟動時候要執行的命令,可以追加命令
ONBUILD  # 當構建一個被繼承DockerFile,這時就會執行ONBUILD指令
COPY    # 類似ADD,將我們檔案拷貝到映象中
ENV    # 構建的時候設定環境變數

3、實戰操作

(1)建立一個自己的CentOS
# vim Dockerfile

FROM centos
MAINTAINER Negan<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 構建
docker build -f Dockerfile -t negan/centos .

# 測試
docker run -it negan/centos
[root@ffae1f9eb97e local]# pwd
/usr/local   # 進入的是我們在dockerfile設定的工作目錄

# 檢視映象構建過程
docker history 映象名/ID
(2)CMD與ENTRYPOINT區別

兩者都是容器啟動的時候要執行的命令,CMD命令只有最後一個會生效,後面不支援追加命令,會被替代。ENTRYPOINT不會被替代,可以追加命令。

CMD

# vim cmd
FROM centos
CMD ["ls","-a"]

# 構建
docker build -f cmd -t cmd_test .

# 執行,發現我們的ls -a命令生效
docker run cmd_test
.
..
.dockerenv
bin
dev
etc
......

# 追加命令執行
docker run cmd_test -l
# 丟擲錯誤,不能追加命令,原來的ls -a命令被-l替換,但是-l不是一個有效的命令
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.

# 追加完整的命令
docker run cmd_test ls -al

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:36 .
drwxr-xr-x  1 root root 4096 Dec 10 14:36 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:36 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:36 dev
......

ENTRYPOINT

# vim entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]

# 構建
docker build -f entrypoint -t entrypoint_test .

# 執行
docker run entrypoint_test
.
..
.dockerenv
bin
dev
etc

# 追加命令執行
docker run entrypoint_test -l

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:41 .
drwxr-xr-x  1 root root 4096 Dec 10 14:41 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:41 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:41 dev
drwxr-xr-x  1 root root 4096 Dec 10 14:41 etc
......

4、實戰構建tomcat

(1)環境準備
 ll
total 166472
-rw-r--r-- 1 root root  11437266 Dec  9 16:22 apache-tomcat-9.0.40.tar.gz
-rw-r--r-- 1 root root       641 Dec 10 23:26 Dockerfile
-rw-r--r-- 1 root root 159019376 Dec  9 17:39 jdk-8u11-linux-x64.tar.gz
-rw-r--r-- 1 root root         0 Dec 10 22:48 readme.txt
(2)構建映象
# vim Dockerfile (Dockerfile是官方建議命名)

FROM centos
MAINTAINER Negan<[email protected]>

COPY readme.txt /usr/local/readme.txt

ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
ADD apache-tomcat-9.0.40.tar.gz /usr/local/

RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out

# 構建
docker -t tomcat .
(3)啟動容器
docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat

九、釋出自己的映象

1、docker hub

首先需要在DockerHub上註冊自己的賬號,並且確定這個賬號可以登入。

在我們的伺服器上進行登入,登入成功後提交我們的映象。

# 登入
docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
  -p, --password string   Password
      --password-stdin    Take the password from stdin
  -u, --username string   Username


# 登入成功後推送我們的映象
docker push [OPTIONS] NAME[:TAG]  

Push an image or a repository to a registry

Options:
      --disable-content-trust   Skip image signing (default true)

docker tag tomcat huiyichanmian/tomcat  # 需要改名,推送改名後的(前面加自己的使用者名稱)
docker push huiyichanmian/tomcat

2、阿里雲

登入阿里雲,找到容器映象服務,使用映象倉庫。建立名稱空間,建立映象倉庫。選擇本地倉庫。

具體阿里雲上有特別詳細的步驟,這裡不再贅述了。

十、Docker網路

1、理解docker0

(1)檢視本機網絡卡資訊
ip addr 

# 本地迴環地址
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
       
# 阿里雲內網地址
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286793195sec preferred_lft 286793195sec
    
# docker0地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

(2)檢視容器網絡卡資訊

我們獲取一個tomcat映象,用來測試。

docker run -d -P --name t1 tomcat 

docker exec -it t1 ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# 我們發現容器啟動時候會得到一個eth0@ifxxx的網絡卡,而且ip地址和上面的docker0裡的是在同一網段。
233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
(3)、再次檢視本地網絡卡資訊
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286792020sec preferred_lft 286792020sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
# 我們發現多了一條網絡卡資訊,而且與容器裡面的網絡卡有某種對應關係。(233,234)
234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default

我們重複上面的操作,不難發現我們只要安裝了docker,本地網卡里就會多一個docker0,而且我們每啟動一個容器,docker就會給容器分配一個網絡卡,而且在本地也會多出一個網絡卡資訊,與容器內的相對應。這就是veth_pair技術,就是一對虛擬裝置介面,它們都是成對出現,一段連著協議,一段彼此相連。正因為有了這個特性,我們通常veth_pair充當了一個橋樑,連線各種虛擬網路裝置。

所有的容器不指定網路的情況下,都是由docker0路由的,docker會給我們的容器分配一個預設的ip。

Docker使用的是Linux的橋接,宿主機中docker0是一個Docker容器的網橋。所有的網路介面都是虛擬的。

只要容器刪除,對應的網橋也響應的被刪除。

問題:我們每次重啟容器,容器的ip地址會變,而我們專案中一些配置使用的固定ip,這時候也需要做相應的改變,那麼我們可不可以直接設定服務名呢,下次重啟時候,配置直接找服務名。

我們啟動兩個tomcat,進行測試,互相ping其對應的名字,看是否能通?

docker exec -it t1 ping t2
ping: t2: Name or service not known
# 答案是肯定的,不能識別t2,如何解決?
# 我們使用--link進行連線
docker run -d -P --name t3 --link t2 tomcat

# 我們嘗試使用t3來ping t2
docker exec -it t3 ping t2
# 我們發現竟然通了
PING t2 (172.17.0.3) 56(84) bytes of data.
64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
......

那麼--link做了什麼呢?

docker exec -it t3 cat /etc/hosts  # 我們來檢視t3的hosts檔案

127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	t2 6bf3c12674c8  # 原因在這了,這裡對t2做了標記,當ping t2時候,自動轉到172.17.0.3
172.17.0.4	b6dae0572f93

3、自定義網路

(1)檢視docker網路
docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
afc0c673386f        none                null                local

# bridge 橋接(docker預設)
# host 與主機共享
# none 不配置
(2)容器啟動時的預設網路

docker0是我們預設的網路,不支援域名訪問,可以使用--link打通。

 # 一般我們啟動容器是這樣子,使用的是預設網路,而這個預設網路就是bridge,所以下面兩條命令是相同的
docker run -d -P --name t1 tomcat  

docker run -d -P --name t1 --net bridge tomcat 
(3)建立網路
# --driver bridge 預設,可以不寫
# --subnet 192.168.0.0/16 子網掩碼
# --gateway 192.168.0.1 預設閘道器
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

docker network ls

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
0e98462f3e8e        mynet               bridge              local  # 我們自己建立的網路
afc0c673386f        none                null                local


ip addr

.....
# 我們自己建立的網路
239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
       valid_lft forever preferred_lft forever
.....

啟動兩個容器,使用我們自己建立的網路

docker run -P -d --name t1 --net mynet tomcat
docker run -P -d --name t2 --net mynet tomcat

# 檢視我們自己建立的網路資訊
docker network mynet inspect

# 我們發現我們剛啟動的兩個容器使用的是我們剛建立的網路
......
"Containers": {
            "1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
                "Name": "t2",
                "EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
                "Name": "t1",
                "EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
.......

那麼使用自己建立的網路有什麼好處呢?

我們回到我們以前的問題,就是域名ping不通的問題上。

docker exec -it t1 ping t2
PING t2 (192.168.0.3) 56(84) bytes of data.
64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
......

docker exec -it t2 ping t1
PING t1 (192.168.0.2) 56(84) bytes of data.
64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
......

我們發現域名可以ping通了,那麼說明我們自定義的網路已經幫我們維護好了對應關係。

這樣不同的叢集使用不同的網路,也能保證叢集的安全和健康。

4、網路連通

現在有這樣一個需求,t1,t2使用的是我們自己定義的網路,t3,t4使用的預設的docker0網路。那麼現在t3和t1或者t2能否通訊呢?

我們知道,docker0的預設閘道器是172.17.0.1,mynet預設閘道器是192.168.0.1,他們直接屬於不同的網段,不能進行通訊。那麼現在如何解決上面的問題呢?

那麼mynet能不能給t3分配一個ip地址呢?如果能分配,那麼問題就應該可以得到解決。

docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
      --alias strings           Add network-scoped alias for the container
      --driver-opt strings      driver options for the network
      --ip string               IPv4 address (e.g., 172.30.100.104)
      --ip6 string              IPv6 address (e.g., 2001:db8::33)
      --link list               Add link to another container
      --link-local-ip strings   Add a link-local address for the container
   
  # 一個容器兩個ip地址 
 docker network connect mynet t3   # 將t3加入到mynet網路中
 
 # 檢視mynet資訊
 docker network inspect mynet
 
 "Containers": {
			......
            "d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
                "Name": "t3",  # 我們發現了t3
                "EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
                "MacAddress": "02:42:c0:a8:00:04",
                "IPv4Address": "192.168.0.4/16",
                "IPv6Address": ""
            }
        },
        ......

這時候,t3就能和t1,t2進行通訊了。

5、部署Redis叢集

# 建立網路
docker network create redis --subnet 172.38.0.0/16

# 通過指令碼建立六個redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379 
bind 0.0.0.0
cluster-enabled yes 
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

# 啟動容器
vim redis.py

import os
for i in range(1, 7):
    str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} \
    -v /mydata/redis/node-{}/data:/data \
    -v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf \
    -d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
    os.system(str)

python reidis.py

# 建立叢集
docker exec -it redis-1 /bash/sh  # 進入redis-1容器

redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

# 建立叢集。。。。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   slots: (0 slots) slave
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   slots: (0 slots) slave
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   slots: (0 slots) slave
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.


# 進行測試
redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:160
cluster_stats_messages_pong_sent:164
cluster_stats_messages_sent:324
cluster_stats_messages_ping_received:159
cluster_stats_messages_pong_received:160
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:324

十一、Docker Compose

1、介紹

Compose是Docker官方開源的專案,需要安裝。

Compose是用於定義和執行多容器Docker應用程式的工具。通過Compose,可以使用YAML檔案來配置應用程式的服務。然後,使用一個命令,就可以從配置中建立並啟動所有服務。

使用Compose基本上是一個三步過程:

  1. Dockerfile保證我們的專案在任何地方執行
  2. docker-compose檔案
  3. 啟動

2、快速開始

(1)安裝
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

docker-compose --version
# 安裝成功
# docker-compose version 1.27.4, build 40524192
(2)使用
# 為專案建立目錄
mkdir composetest
cd composetest

# 編寫一段flask程式
vim app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)  # 這裡主機名直接用“redis”,而不是ip地址

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
    
# 編寫requirements.txt檔案(不指定版本,下載最新)
flask
redis

# 編寫Dockerfile檔案
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]


# 編寫docker-compose.yml檔案
# 檔案中定義了兩個服務,web和redis,web是從Dockerfile開始構建,redis使用的是公共映象
version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
  redis:
    image: "redis:alpine"
    
# 執行
docker-compose up 

3、搭建部落格

# 建立並進入目錄
mkdir wordpress && cd wordpress

# 編寫docker-compose.yml檔案來啟動,並建立了一個單獨Mysql例項,具有用於資料永續性的卷掛載
version: "3.3"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      W0RDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
  volumes:
    db_data: {}
    

# 啟動執行
docker-compose up -d

十二、Docker Swarm

1、環境準備

準備四臺伺服器。安裝docker。

2、swarm叢集搭建

docker swarm COMMAND
Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm   # 初始化一個節點(管理節點)
  join        Join a swarm as a node and/or manager  # 加入節點
  join-token  Manage join tokens   # 通過節點token加入
  leave       Leave the swarm    # 離開節點
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm  #更新

# 首先我們初始化一個節點
docker swarm init --advertise-addr + 自己的ip(這裡用內網地址,主要是省錢)
docker swarm init --advertise-addr 172.27.0.4

# 提示我們節點建立成功
Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.

To add a worker to this swarm, run the following command:
	
	# 在其他機器上執行,加入這個節點
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 生成一個管理節點的token,
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

# 我們在機器2上行加入節點的命令
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 我們在機器1上檢視節點資訊
docker node ls
# 我們發現一個管理節點,一個工作節點,狀態都是就緒狀態
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos   Ready     Active         Leader           20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos   Ready     Active                          20.10.0

# 現在將機器3也加入,同樣是work節點
# 到此為止,現在只有機器4沒有加入,這時候我們想把它設定成管理節點。
# 在機器1上執行生成管理節點的命令,在機器4上執行
docker swarm join-token manager
# 生成的命令在機器4上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 此時機器4也是管理節點了
This node joined a swarm as a manager.

# 至此我們可以在機器1或者機器4上進行其他操作了。(只能在管理節點上操作)

3、Raft協議

在前面的步驟,我們已經完成了雙主雙從叢集的搭建。

Raft協議:保證大多數節點存活才能使用。必須大於1,叢集至少大於3臺。

實驗1

將機器1上的docker停止,宕機,現在叢集中只有一個管理節點了。叢集是否可用。

#我們在機器四上檢視節點資訊
docker node ls
# 發現我們的叢集已經不能用了
Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded

# 我們將機器1上的docker重啟,發現叢集又能用了,但是此時機器1上的docker已經不是leader了,leader自動轉給機器4了

實驗2

我們將工作節點離開叢集,檢視叢集資訊

# 我們在機器2上執行
docker swarm leave

# 在機器一上檢視節點資訊
docker node ls
# 我們發現機器2狀態是Down
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos    Ready     Active         Reachable        20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

實驗3

現在我們將機器2也設定成管理節點(此時叢集有三個管理節點),隨機down掉一個管理節點,叢集是否正常執行。

# 在機器2上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377

# 將機器1的docker宕機
systemctl stop docker

# 在機器二上檢視節點資訊
docker node ls
# 叢集正常,且可以看到機器1宕機了
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm     VM-0-4-centos    Ready     Active         Unreachable      20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
vdwcwr3v6qrn6da40zdrjkwmy *   VM-0-7-centos    Ready     Active         Reachable        20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

4、彈性建立服務

docker service COMMAND

Commands:
  create      Create a new service  # 建立一個服務
  inspect     Display detailed information on one or more services  # 檢視服務資訊
  logs        Fetch the logs of a service or task  # 日誌
  ls          List services   # 列表
  ps          List the tasks of one or more services   # 檢視我們的服務
  rm          Remove one or more services  # 刪除服務
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services  # 動態擴縮容
  update      Update a service  # 更新

docker service create -p 8888:80 --name n1 nginx  # 建立一個服務,在叢集中隨機分配
kj0xokbxvf5uw91bswgp1cukf
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

# 給我們的服務增加三個副本
docker service update --replicas 3 n1

# 動態擴縮容
docker service scale n1=10   # 和上面updata一樣,這個方便

docker ps # 檢視

服務,叢集中任意的節點都可以訪問,服務可以有多個副本動態擴縮容實現高可用。

5、使用Docker stack部署部落格

我們現在需要將上面的部落格跑在我們的叢集中,並且要開十個副本。

# 編輯docker-compose.yml檔案
version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     deploy:
       replicas: 10
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}


# 進行啟動
docker stack deploy -c docker-compose.yml wordpress

# 檢視
docker service ls

一、安裝

環境:Centos7

1、解除安裝舊版本

較舊的Docker版本稱為dockerdocker-engine。如果已安裝這些程式,請解除安裝它們以及相關的依賴項。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2、使用儲存庫安裝

注意:官方給了三種安裝方式,這裡我們選擇最常用的儲存庫安裝

在新主機上首次安裝Docker Engine之前,需要設定Docker儲存庫。之後可以從儲存庫安裝和更新Docker。

sudo yum install -y yum-utils

# 注意:此處是大坑,可以自行換成阿里的源
sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    
# 我們用阿里的    
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
 
# 進行安裝(如果不換源,速度會很慢)
sudo yum install docker-ce docker-ce-cli containerd.io

3、啟動以及驗證

systemctl start docker   # 啟動
systemctl status docker  #  檢視狀態
docker version  # 檢視版本
docker run hello-world   #  測試

4、設定加速器(阿里雲)

注意:裡面的地址每個人都有專屬的地址。

開啟阿里雲官網->控制檯->搜尋容器映象服務->右下角找到映象加速器

sudo mkdir -p /etc/docker

tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://isch1uhg.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

docker run hello-world

5、解除安裝

# 解除安裝Docker Engine,CLI和Containerd軟體包
sudo yum remove docker-ce docker-ce-cli containerd.io

# 主機上的映像,容器,卷或自定義配置檔案不會自動刪除。要刪除所有影象,容器和卷
sudo rm -rf /var/lib/docker

二、Docker三要素

1、倉庫(Repository)

倉庫:集中存放映象的場所。

注意:倉庫(Repository)和倉庫註冊伺服器(Registry)是有區別的,倉庫註冊伺服器往往存放著多個倉庫,每個倉庫中又包含多個映象,每個映象有不同的標籤(tag)

倉庫分為公開倉庫(Public)和私有倉庫(Private)兩種形式。

最大的公開倉庫是Docker Hub(https://hub.docker.com),存放了數量龐大的映象供使用者下載,國內的公開倉庫包括阿里雲、網易雲等。

2、映象(Image)

一個只讀模板,用來建立Docker容器,一個映象可以建立很多個容器。

容器與映象的關係類似於面向物件程式設計中的物件和類

Docker 面向物件
容器 物件
映象

3、容器 (Container)

獨立執行的一個或一組應用。

容器使用映象建立的執行例項。

它可以被啟動、開始、停止、刪除。每個容器都是相互隔離的,保證安全的平臺。

可以把容器看作是一個簡易版的Linux環境(包括root使用者許可權,程序空間,使用者空間和網路空間等等)和執行在啟動的應用程式。

容器的定義和映象幾乎一模一樣,也是一堆層的統一視角,唯一區別在於容器的最上面那一層是可讀可寫的。

三、簡單瞭解底層原理

1、Docker是怎麼工作的

Docker是一個Client-Server結構的系統,Docker守護程序執行在主機上,然後通過Socket連線從客戶端訪問,守護程序從客戶端接收命令並管理執行在主機上的容器。

2、Docker為什麼比VM快?

(1)Docker有著比虛擬機器更少的抽象層,由於Docker不需要Hypervisor實現硬體資源虛擬化,執行在Docker容器上的程式直接使用的都是實際物理機的硬體資源,因此在CPU、記憶體利用率上Docker將會在效率上有明顯的優勢。

(2)Docker利用的是宿主機的核心,而不需要Guest OS。因此當新建一個容器時,Docker不需要和虛擬機器一樣重疊載入一個作業系統核心,從而避免引尋、載入作業系統核心,返回比較費時費資源的過程。當新建一個虛擬機器時,虛擬機器軟體需要載入Guest OS,這個新建過程是分鐘級別的,而Docker由於直接利用宿主機的作業系統,因此新建一個Docker容器只需幾秒鐘。

Docker容器 虛擬機器(VM)
作業系統 與宿主機共享OS 宿主機OS上執行虛擬機器OS
儲存大小 映象小,便於儲存與傳輸 映象龐大(vmdk,vdi等)
執行效能 幾乎無額外效能損失 作業系統額外的CPU、記憶體消耗
移植性 輕便、靈活,適應於Linux 笨重,與虛擬化技術耦合度高
硬體親和性 面向軟體開發者 面向硬體運維者

四、相關命令

1、幫助命令

docker version  # 檢視docker版本資訊
docker info  # 詳細說明
docker --help  # 幫助命令

2、映象命令

(1)列出本地映象
docker images [OPTIONS] [REPOSITORY[:TAG]]

# OPTIONS說明:
	-a: 列出本地所有的映象(含中間映像層)
    -q: 只顯示映象ID
    --digests: 顯示映象的摘要資訊
    --no-trunc: 顯示完整的映象資訊

# 各個選項說明:
# REPOSITORY:表示映象的倉庫源
# TAG:映象的標籤
# IMAGE ID:映象ID
# CREATED:映象建立時間
# SIZE:映象大小	

同一倉庫源可以有多個TAG,代表這個倉庫源的不同版本,我們使用REPOSITORY:TAG來定義不同的映象,如果不指定一個映象的版本標籤,例如我們只使用ubuntu,docker將預設使用ubuntu:latest映象。

(2)查詢映象
docker search [options] 某個xxx映象名字  # 會在https://hub.docker.com上去查詢

docker search mysql --filter=STARS=3000  # 搜尋星數大於等於3000的映象

# OPTIONS說明:
# --no-trunc: 顯示完整的映象描述	
# -s:列出點贊數不小於指定值的映象
# --automated: 只列出automated build型別的映象
(3)獲取映象
docker pull 映象名字[:TAG]  # 如果不寫TAG,則預設獲取latest,此時從我們配置的阿里雲上獲取映象

docker pull mysql  # 獲取最新版本的mysql
Using default tag: latest
latest: Pulling from library/mysql
852e50cd189d: Pull complete   # 分卷下載
29969ddb0ffb: Pull complete 
a43f41a44c48: Pull complete 
5cdd802543a3: Pull complete 
b79b040de953: Pull complete 
938c64119969: Pull complete 
7689ec51a0d9: Pull complete 
a880ba7c411f: Pull complete 
984f656ec6ca: Pull complete 
9f497bce458a: Pull complete 
b9940f97694b: Pull complete 
2f069358dc96: Pull complete 
Digest: sha256:4bb2e81a40e9d0d59bd8e3dc2ba5e1f2197696f6de39a91e90798dd27299b093
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest  
# docker pull mysql 等價於docker pull docker.io/library/mysql:latest 

docker pull mysql:5.7  # 下載指定版本的映象
5.7: Pulling from library/mysql
852e50cd189d: Already exists  # 聯合檔案系統,已經存在的不會去重複下載
29969ddb0ffb: Already exists 
a43f41a44c48: Already exists 
5cdd802543a3: Already exists 
b79b040de953: Already exists 
938c64119969: Already exists 
7689ec51a0d9: Already exists 
36bd6224d58f: Pull complete 
cab9d3fa4c8c: Pull complete 
1b741e1c47de: Pull complete 
aac9d11987ac: Pull complete 
Digest: sha256:8e2004f9fe43df06c3030090f593021a5f283d028b5ed5765cc24236c2c4d88e
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
(4)刪除映象
# 刪除單個映象
docker rmi -f 映象名/ID  # 如果是映象名,後面不帶TAG,則預設刪除latest

# 刪除多個映象
docker rmi -f 映象名1 映象名2 ...
docker rmi -f id1 id2 ...     // 注意:兩種方式不能混用

# 刪除全部映象
docker rmi -f $(docker images -aq)   

3、容器命令

​ 有映象才能建立容器,這是根本前提(下載一個Centos映象演示)

(1) 新建並啟動容器(互動式)
docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run -it --name mycentos centos  // 如果不指定別名,系統會自動分配

# OPTIONS說明:
# --name 容器新名字:為容器指定一個名稱
# -d:後臺執行容器,並返回容器ID,即啟動守護式容器
# -i: 以互動模式執行容器,通常與-t同時使用
# -t: 為容器重新分配一個偽輸入終端,通常與-i同時使用
# -P: 隨機埠對映
# -p: 指定埠對映有以下四種格式
		ip:hostPort:containerPort
		ip::containerPort
		hostPort:containerPort
		containerPort

# 測試
[root@iz2zeaj5c9isqt1zj9elpbz ~]# docker run -it centos /bin/bash  # 啟動並進入容器
[root@783cb2f26230 /]# ls   # 在容器內檢視
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@783cb2f26230 /]# exit   # 退出容器
exit
[root@iz2zeaj5c9isqt1zj9elpbz ~]# 

(2)列出當前所有正在執行的容器
docker ps [OPTIONS]  # 不帶OPTIONS,則只列出正在執行的容器

# OPTIONS說明(常用):
# -a:列出當前所有正在執行的容器以及歷史上執行過的
# -l:顯示最近建立的容器
# -n: 顯示最近n個建立的容器
# -q: 靜默模式,只顯示容器編號
# --no-trunc:不截斷輸出
(3)退出容器
exit  // 直接關閉並退出容器

重新開啟一個終端,執行docker ps -l,會返回剛才我們建立的容器資訊,並且STATUS會提示已經退出。

那麼可不可以在互動式,不關閉容器的情況下,暫時退出,一會又可以回來呢?

Ctrl + P + Q

# 執行後,我們將退出容器,回到宿主機,使用docker ps -l,我們會發現這個剛才退出的容器STATUS是Up狀態。
(4)啟動容器
docker start [OPTIONS] CONTAINER [CONTAINER...]

# 同時可以啟動多個容器,容器名和ID可以混用

# OPTION說明(常用):
# -i : 進入互動式,此時只能進入一個容器

進入上面我們退出但是依然存活的容器

docker start -i 186ae928f07c
(5)重啟容器
docker restart [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死容器之前等待停止的時間(預設為10)
(6) 停止容器
docker stop [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -t :在殺死前等待停止的時間(預設為10)

docker kill [OPTIONS] CONTAINER [CONTAINER...]  // 強制關閉(相當於拔電源)

# OPTIONS說明:
# -s: 傳送到容器的訊號(預設為“KILL”)
(7)刪除容器
docker rm [OPTIONS] CONTAINER [CONTAINER...]

# OPTIONS說明:
# -f :強制刪除,不管是否在執行

# 上面的命令可以刪除一個或者多個,但是想全部刪除所有的容器,那麼我們要把全部的容器名或者ID都寫一遍嗎?

docker rm -f $(docker ps -aq)  # 刪除所有
docker ps -aq | xargs docker rm -f
(8) 啟動守護式容器
docker run -d 容器名/容器ID

說明:我們使用docker ps -a命令檢視,會發現剛才啟動的容器以及退出了,這是為什麼呢?

很重要的要說明一點:Docker容器後臺執行,必須要有一個前臺程序,容器執行的命令如果不是那些一直掛起的命令(比如執行top,tail),就會自動退出。

這就是Docker的機制問題,比如我們現在執行WEB容器,以Nginx為例,正常情況下,我們配置啟動服務只需要啟動響應的service即可,例如service nginx start,但是這樣做,Nginx為後臺程序模式執行,導致Docker前臺沒有執行的應用。這樣的容器後臺啟動後,會立即自殺因為它覺得它沒事可做。所以最佳的解決方案是將要執行的程式以前臺程序的形式執行。

那麼如何讓守護式容器不自動退出呢?我們可以執行一直掛起的命令。

docker run -d centos /bin/sh -c "while true;do echo hello Negan;sleep 2;done"
(9) 檢視容器日誌
docker logs [OPTIONS] CONTAINER

# OPTION說明:
# -t 加入時間戳
# -f 跟隨最新的日誌列印
# --tail 顯示最後多少條
(10) 檢視容器內的程序
docker top CONTAINER 
(11) 檢視容器內部細節
docker inspect CONTAINER
(12) 進入正在執行的容器並以命令列互動
  • exec在容器中開啟新的終端,並且可以啟動新的程序
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]  

docker exec -it CONTAINER /bin/bash   // 進入容器內部並互動

# 隔山打牛
docker exec -it 3d00a0a2877e ls -l /tmp 

# 3d00a0a2877e是我上面跑的守護式容器
# 進入容器,執行ls -l /tmp 命令後將結果返回給我宿主機,而使用者的介面還是停留在宿主機,沒有進入容器內部
  • attach直接進入容器啟動命令的終端,不會啟動新的程序
docker attach CONTAINER

# 注意,進入上面那個守護式容器後,我們會看到還是每隔兩秒列印hello Negan,而且不能退出,只能重新開啟一個終端,執行docker kill 命令
(13)容器與主機間的檔案拷貝
docker cp CONTAINER:SRC_PATH DEST_PATH  # 把容器內的檔案拷貝到宿主機
docker cp 90bd03598dd4:123.txt ~

docker cp SRC_PATH CONTAINER:DEST_PATH  # 把宿主機上的檔案拷貝到容器
docker cp 12345.txt 90bd03598dd4:~

練習

練習一:Docker部署Nginx

# 查詢一個nginx映象
docker search nginx
# 下載映象
docker pull nginx
# 啟動
docker run -d --name nginx01 -p 3344:80 nginx  # -p 3344(宿主機):80(容器埠)
# 測試(瀏覽器訪問)
123.56.243.64:3344 

五、視覺化

1、portainer

Docker圖形介面管理工具,提供一個後臺面板供我們操作。

docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true protainer/portainer

六、Docker映象

1、映象是什麼

映象是一種輕量級、可執行的獨立軟體包,用來打包軟體執行環境和基於執行環境開發的軟體,它包含執行某個軟體所需的所有內容,包括程式碼、執行時、庫、環境變數和配置檔案。

所有應用直接打包docker映象,就可以直接跑起來。

如何獲取映象
  • 從遠端操作下載
  • 朋友拷貝
  • 自己製作一個映象DockerFile

2、映象載入原理

(1)聯合檔案系統

UnionFS(聯合檔案系統):Union檔案系統是一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改作為一次提交來一層一層疊加,同時可以將不同目錄掛在到同一個虛擬檔案系統下。聯合檔案系統是Docker映象的基礎,映象可以通過分層來進行繼承,基於基礎映象(沒有父映象),可以製作各種具體的應用映象。

特性:一次同時載入多個檔案系統,但從外面看起來,只能看到一個檔案系統,聯合載入會把各層檔案系統疊加起來,這樣最終的檔案系統會包含所有底層的檔案和目錄。

(2)Docker映象載入原理

Docker的映象實際上是由一層一層的檔案系統組成,也就是上面所說的聯合檔案系統。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引導載入kernel,Linux剛啟動時會載入bootfs檔案系統,在Docker映象最底層是bootfs。這一層與我們典型的Linux/Unix系統是一樣的,包含boot載入器和核心。當boot載入完成之後整個核心就都在記憶體中,此時記憶體的使用權已由bootfs轉交給核心,此時系統也會解除安裝bootfs。

rootfs(root file system),在bootfs之上,包含的就是典型Linux系統中的/dev,/proc,/bin,/etc等標準目錄和檔案。rootfs就是各種不同的作業系統發行版,比如Ubuntu,CentOS等等。

對於一個精簡的OS,rootfs可以很小,只需要包含最近本的命令,工具和程式庫就可以,因為底層直接用Host的kernel,自己只需要提供rootfs就可以,由此可見對於不同的Linux發行版,bootfs基本是一致的,rootfs會有差別,因此不同的發行版可以公用bootfs。

3、分層理解

(1)分層的映象

我們下載一個映象,注意觀察下載的日誌輸出,可以看到是一層一層在下載。

為什麼Docker映象採用這種分層的結構?

最大的好處,我覺得莫過於資源共享了,比如有多個映象從相同的Base映象構建而來,那麼宿主機只需要在磁碟上保留一份Base映象,同時記憶體中也只需要載入一份Base映象,這樣就可以為所有的容器服務了,而且映象每一層都可以被共享。

(2)理解

所有的Docker映象都始於一個基礎映象層,當進行修改或者增加新內容時,就會在當前映象層之上,建立新的映象層。假如基於Ubuntu Linux 16.04建立一個新的映象,這就是新映象的第一層;如果在該映象中新增Python包,就會在基礎映象之上建立第二個映象層,如果繼續新增一個安全補丁,就會建立第三個映象層。

再新增額外的映象層同時,映象始終保持是當前所有映象的組合。

注意:Docker映象都是隻讀的,當容器啟動時,一個新的可寫層被載入到映象的頂部。

​ 這一層就是我們通常說的容器層,容器之下的都叫映象層。

4、commit

docker commit 提交容器成為一個新的映象

docker commit -m="提交的描述資訊" -a="作者" 容器id 目標映象名:[TAG]

七、容器資料卷

如果資料在容器中,那麼我們容器刪除,資料就會丟失!

需求:資料需要持久化。

MySQL,容器刪了,刪庫跑路?Docker容器中產生的資料,需要同步到本地。

這就是卷技術,目錄的掛載,將我們容器內的目錄,掛載到Linux上。

1、使用資料卷

  • 直接使用命令來掛載
docker run -it -v 主機目錄:容器內目錄  # 雙向繫結,一方改變另一方自動改變

docker run -it -v /home/ceshi:/home centos /bin/bash

docker inspect d2343e9d338a  # 進行檢視

/*
...
 "Mounts": [   # 掛載 -v 
            {
                "Type": "bind",
                "Source": "/home/ceshi",  # 主機內的路徑
                "Destination": "/home",   # 容器內的路徑
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
...
*/

2、實戰:安裝MySQL

思考:MySQL的資料持久化問題

docker run -d -p 13306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7

3、具名掛載和匿名掛載

(1) 、匿名掛載
docker run -d -P --name nginx01 -v /etc/nginx nginx  # 只指定了容器內的路徑

docker volume ls  # 檢視所有volume的情況
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a  # 匿名掛載
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049

# 這種就是匿名掛載,我們在-v只寫了容器內的路徑,沒有寫容器外的路徑。
(2)、具名掛載
docker run -d -P -v juming:/etc/nginx --name nginx02 nginx   # 具名掛載

docker volume ls
DRIVER              VOLUME NAME
local               55050407d8fd052403bbf6ee349aa6268c2ec5c1054dafa678ac5dd31c59217a
local               fd074ffbcea60b7fe65025ebe146e0903e90d9df5122c1e8874130ced5311049
local               juming  # 我們上面起的名字

# 通過-v 卷名:容器內路徑

# 所有的docker容器內的卷,沒有指定目錄的情況下,都在/var/lib/docker/volumes目錄下。
docker inspect juming  
[
    {
        "CreatedAt": "2020-12-09T00:22:54+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/juming/_data",
        "Name": "juming",
        "Options": null,
        "Scope": "local"
    }
]

我們通過具名掛載可以方便的找到我們掛載的卷,大多情況下我們都會使用具名掛載。

(3)、拓展
-v 容器內路徑  # 匿名掛載
-v 卷名:容器內路徑 # 具名掛載
-v /宿主機路徑:容器內路徑  # 指定路徑掛載

# 通過 -V 容器內路徑:ro rw改變讀寫許可權
ro readonly # 只讀
rw readwrite # 可讀可寫

# 一旦設定了只讀,容器對我們掛載出來的內容就有限定了。檔案只能通過宿主機來操作,容器無法操作。
docker run -d -P --name nginx01 -v juming:/etc/nginx:ro nginx 

4、資料卷容器

容器間的資訊同步。

docker run -it -v /home --name c1 centos /bin/bash   # 啟動c1,作為一個父容器(指定容器內掛載目錄)
docker run -it --name c2 --volumes-from c1 centos /bin/bash  # 啟動c2,掛載c1,此時在兩個容器中/home中的操作就會同步


# 下面的容器是我們自己建立的,可以參考下面dockerfile進行構建
docker run -it --name docker01 negan/centos   # 啟動一個容器作為父容器(被掛載的容器)  
docker run -it --name docker02 --volumes-from docker01 negan/centos  #啟動容器2,掛載容器1 
docker run -it --name docker03 --volumes-from docker02 negan/centos  #啟動容器3,掛載容器2
# 上面的容器3也可以直接掛載到容器1上,然後我們進入任意一個容器,進入掛載卷volume01/volume02,進行操作,容器間的資料會自動同步。

結論:

容器之間配置資訊的傳遞,資料卷容器的宣告週期一直持續到沒有容器使用為止。

但是一旦持久到了本地,本地資料不會被刪除。

八、DockerFile

1、初識DockerFile

DockerFile就是用來構造docker映象的構建檔案,命令引數指令碼。通過這個指令碼,可以生成映象。映象是一層一層的,指令碼是一個個命令,每個命令就是一層。

# dockerfile1內容,所有命令都是大寫

FROM centos   # 基於centos

VOLUME ["volume01","volume02"]   # 掛載資料卷(匿名掛載)

CMD echo "----end-----"

CMD /bin/bash


# 構建
docker build -f dockerfile1 -t negan/centos .  # -f 指定路徑 -t指定名字,不加tag預設最新
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM centos
 ---> 0d120b6ccaa8
Step 2/4 : VOLUME ["volume01","volume02"]
 ---> Running in 0cfe6b5be6bf
Removing intermediate container 0cfe6b5be6bf
 ---> 396a4a7cfe15
Step 3/4 : CMD echo "----end-----"
 ---> Running in fa535b5581fa
Removing intermediate container fa535b5581fa
 ---> 110d9f93f827
Step 4/4 : CMD /bin/bash
 ---> Running in 557a2bb87d97
Removing intermediate container 557a2bb87d97
 ---> c2c9b92d50ad
Successfully built c2c9b92d50ad
Successfully tagged negan/centos:latest

docker images  # 檢視

2、DokcerFile構建過程

(1)、基礎知識

每個保留關鍵字(指令)都必須是大寫字母

執行從上到下順序執行

#表示註釋

每一個指令都會建立提交一個新的映象層,並提交

DockerFile是面向開發的,我們以後要釋出專案,做映象,就需要編寫dockerfile檔案。

Docker映象逐漸成為企業交付的標準。

(2)、基本命令
FROM  #這個映象的媽媽是誰?(基礎映象,一切從這裡開始構建)
MAINTAINER # 告訴別人誰負責養他?(映象是誰寫的,指定維護者資訊,姓名+郵箱)
RUN  # 你想讓他幹啥?(映象構建的時候需要執行的命令)
ADD  # 給他點創業資金(複製檔案,會自動解壓)
WORKDIR # 映象的工作目錄
VOLUME  # 給他一個存放行李的地方(設定卷,容器內掛載到主機的目錄,匿名掛載)
EXPOSE  # 門牌號是多少?(指定對外埠)
CMD   # 指定這個容器啟動的時候要執行的命令,只有最後一個會生效,可被替代
ENTRYPOINT # 指定這個容器啟動時候要執行的命令,可以追加命令
ONBUILD  # 當構建一個被繼承DockerFile,這時就會執行ONBUILD指令
COPY    # 類似ADD,將我們檔案拷貝到映象中
ENV    # 構建的時候設定環境變數

3、實戰操作

(1)建立一個自己的CentOS
# vim Dockerfile

FROM centos
MAINTAINER Negan<[email protected]>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 構建
docker build -f Dockerfile -t negan/centos .

# 測試
docker run -it negan/centos
[root@ffae1f9eb97e local]# pwd
/usr/local   # 進入的是我們在dockerfile設定的工作目錄

# 檢視映象構建過程
docker history 映象名/ID
(2)CMD與ENTRYPOINT區別

兩者都是容器啟動的時候要執行的命令,CMD命令只有最後一個會生效,後面不支援追加命令,會被替代。ENTRYPOINT不會被替代,可以追加命令。

CMD

# vim cmd
FROM centos
CMD ["ls","-a"]

# 構建
docker build -f cmd -t cmd_test .

# 執行,發現我們的ls -a命令生效
docker run cmd_test
.
..
.dockerenv
bin
dev
etc
......

# 追加命令執行
docker run cmd_test -l
# 丟擲錯誤,不能追加命令,原來的ls -a命令被-l替換,但是-l不是一個有效的命令
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.

# 追加完整的命令
docker run cmd_test ls -al

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:36 .
drwxr-xr-x  1 root root 4096 Dec 10 14:36 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:36 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:36 dev
......

ENTRYPOINT

# vim entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]

# 構建
docker build -f entrypoint -t entrypoint_test .

# 執行
docker run entrypoint_test
.
..
.dockerenv
bin
dev
etc

# 追加命令執行
docker run entrypoint_test -l

total 56
drwxr-xr-x  1 root root 4096 Dec 10 14:41 .
drwxr-xr-x  1 root root 4096 Dec 10 14:41 ..
-rwxr-xr-x  1 root root    0 Dec 10 14:41 .dockerenv
lrwxrwxrwx  1 root root    7 Nov  3 15:22 bin -> usr/bin
drwxr-xr-x  5 root root  340 Dec 10 14:41 dev
drwxr-xr-x  1 root root 4096 Dec 10 14:41 etc
......

4、實戰構建tomcat

(1)環境準備
 ll
total 166472
-rw-r--r-- 1 root root  11437266 Dec  9 16:22 apache-tomcat-9.0.40.tar.gz
-rw-r--r-- 1 root root       641 Dec 10 23:26 Dockerfile
-rw-r--r-- 1 root root 159019376 Dec  9 17:39 jdk-8u11-linux-x64.tar.gz
-rw-r--r-- 1 root root         0 Dec 10 22:48 readme.txt
(2)構建映象
# vim Dockerfile (Dockerfile是官方建議命名)

FROM centos
MAINTAINER Negan<[email protected]>

COPY readme.txt /usr/local/readme.txt

ADD jdk-8u271-linux-aarch64.tar.gz /usr/local/
ADD apache-tomcat-9.0.40.tar.gz /usr/local/

RUN yum -y install vim

ENV MYPATH /usr/local
WORKDIR $MYPATH

ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.40
ENV CATALINA_BASH /usr/local/apache-tomcat-9.0.40
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080

CMD /usr/local/apache-tomcat-9.0.40/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.40/bin/logs/catalina.out

# 構建
docker -t tomcat .
(3)啟動容器
docker run -d -P --name tomcat01 -v /home/Negan/tomcat/test:/usr/local/apache-tomcat-9.0.40/webapps/test -v /home/Negan/tomcat/logs:/usr/local/apache-tomcat-9.0.40/logs tomcat

九、釋出自己的映象

1、docker hub

首先需要在DockerHub上註冊自己的賬號,並且確定這個賬號可以登入。

在我們的伺服器上進行登入,登入成功後提交我們的映象。

# 登入
docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
  -p, --password string   Password
      --password-stdin    Take the password from stdin
  -u, --username string   Username


# 登入成功後推送我們的映象
docker push [OPTIONS] NAME[:TAG]  

Push an image or a repository to a registry

Options:
      --disable-content-trust   Skip image signing (default true)

docker tag tomcat huiyichanmian/tomcat  # 需要改名,推送改名後的(前面加自己的使用者名稱)
docker push huiyichanmian/tomcat

2、阿里雲

登入阿里雲,找到容器映象服務,使用映象倉庫。建立名稱空間,建立映象倉庫。選擇本地倉庫。

具體阿里雲上有特別詳細的步驟,這裡不再贅述了。

十、Docker網路

1、理解docker0

(1)檢視本機網絡卡資訊
ip addr 

# 本地迴環地址
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
       
# 阿里雲內網地址
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286793195sec preferred_lft 286793195sec
    
# docker0地址
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

(2)檢視容器網絡卡資訊

我們獲取一個tomcat映象,用來測試。

docker run -d -P --name t1 tomcat 

docker exec -it t1 ip addr

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# 我們發現容器啟動時候會得到一個eth0@ifxxx的網絡卡,而且ip地址和上面的docker0裡的是在同一網段。
233: eth0@if234: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
(3)、再次檢視本地網絡卡資訊
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:0c:7b:cb brd ff:ff:ff:ff:ff:ff
    inet 172.24.14.32/18 brd 172.24.63.255 scope global dynamic eth0
       valid_lft 286792020sec preferred_lft 286792020sec
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:5e:2b:4c:05 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
# 我們發現多了一條網絡卡資訊,而且與容器裡面的網絡卡有某種對應關係。(233,234)
234: veth284c2d9@if233: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default

我們重複上面的操作,不難發現我們只要安裝了docker,本地網卡里就會多一個docker0,而且我們每啟動一個容器,docker就會給容器分配一個網絡卡,而且在本地也會多出一個網絡卡資訊,與容器內的相對應。這就是veth_pair技術,就是一對虛擬裝置介面,它們都是成對出現,一段連著協議,一段彼此相連。正因為有了這個特性,我們通常veth_pair充當了一個橋樑,連線各種虛擬網路裝置。

所有的容器不指定網路的情況下,都是由docker0路由的,docker會給我們的容器分配一個預設的ip。

Docker使用的是Linux的橋接,宿主機中docker0是一個Docker容器的網橋。所有的網路介面都是虛擬的。

只要容器刪除,對應的網橋也響應的被刪除。

問題:我們每次重啟容器,容器的ip地址會變,而我們專案中一些配置使用的固定ip,這時候也需要做相應的改變,那麼我們可不可以直接設定服務名呢,下次重啟時候,配置直接找服務名。

我們啟動兩個tomcat,進行測試,互相ping其對應的名字,看是否能通?

docker exec -it t1 ping t2
ping: t2: Name or service not known
# 答案是肯定的,不能識別t2,如何解決?
# 我們使用--link進行連線
docker run -d -P --name t3 --link t2 tomcat

# 我們嘗試使用t3來ping t2
docker exec -it t3 ping t2
# 我們發現竟然通了
PING t2 (172.17.0.3) 56(84) bytes of data.
64 bytes from t2 (172.17.0.3): icmp_seq=1 ttl=64 time=0.099 ms
64 bytes from t2 (172.17.0.3): icmp_seq=2 ttl=64 time=0.066 ms
......

那麼--link做了什麼呢?

docker exec -it t3 cat /etc/hosts  # 我們來檢視t3的hosts檔案

127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	t2 6bf3c12674c8  # 原因在這了,這裡對t2做了標記,當ping t2時候,自動轉到172.17.0.3
172.17.0.4	b6dae0572f93

3、自定義網路

(1)檢視docker網路
docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
afc0c673386f        none                null                local

# bridge 橋接(docker預設)
# host 與主機共享
# none 不配置
(2)容器啟動時的預設網路

docker0是我們預設的網路,不支援域名訪問,可以使用--link打通。

 # 一般我們啟動容器是這樣子,使用的是預設網路,而這個預設網路就是bridge,所以下面兩條命令是相同的
docker run -d -P --name t1 tomcat  

docker run -d -P --name t1 --net bridge tomcat 
(3)建立網路
# --driver bridge 預設,可以不寫
# --subnet 192.168.0.0/16 子網掩碼
# --gateway 192.168.0.1 預設閘道器
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

docker network ls

docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
10684d1bfac9        bridge              bridge              local
19f4854793d7        host                host                local
0e98462f3e8e        mynet               bridge              local  # 我們自己建立的網路
afc0c673386f        none                null                local


ip addr

.....
# 我們自己建立的網路
239: br-0e98462f3e8e: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:b6:a7:b1:96 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.1/16 brd 192.168.255.255 scope global br-0e98462f3e8e
       valid_lft forever preferred_lft forever
.....

啟動兩個容器,使用我們自己建立的網路

docker run -P -d --name t1 --net mynet tomcat
docker run -P -d --name t2 --net mynet tomcat

# 檢視我們自己建立的網路資訊
docker network mynet inspect

# 我們發現我們剛啟動的兩個容器使用的是我們剛建立的網路
......
"Containers": {
            "1993703e0d0234006e1f95e964344d5ce01c90fe114f58addbd426255f686382": {
                "Name": "t2",
                "EndpointID": "f814ccc94232e5bbc4aaed35022dde879743ad9ac3f370600fb1845a862ed3b0",
                "MacAddress": "02:42:c0:a8:00:03",
                "IPv4Address": "192.168.0.3/16",
                "IPv6Address": ""
            },
            "8283df6e894eeee8742ca6341bf928df53bee482ab8a6de0a34db8c73fb2a5fb": {
                "Name": "t1",
                "EndpointID": "e462941f0103b99f696ebe2ab93c1bb7d1edfbf6d799aeaf9a32b4f0f2f08e01",
                "MacAddress": "02:42:c0:a8:00:02",
                "IPv4Address": "192.168.0.2/16",
                "IPv6Address": ""
            }
        },
.......

那麼使用自己建立的網路有什麼好處呢?

我們回到我們以前的問題,就是域名ping不通的問題上。

docker exec -it t1 ping t2
PING t2 (192.168.0.3) 56(84) bytes of data.
64 bytes from t2.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
......

docker exec -it t2 ping t1
PING t1 (192.168.0.2) 56(84) bytes of data.
64 bytes from t1.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.045 ms
......

我們發現域名可以ping通了,那麼說明我們自定義的網路已經幫我們維護好了對應關係。

這樣不同的叢集使用不同的網路,也能保證叢集的安全和健康。

4、網路連通

現在有這樣一個需求,t1,t2使用的是我們自己定義的網路,t3,t4使用的預設的docker0網路。那麼現在t3和t1或者t2能否通訊呢?

我們知道,docker0的預設閘道器是172.17.0.1,mynet預設閘道器是192.168.0.1,他們直接屬於不同的網段,不能進行通訊。那麼現在如何解決上面的問題呢?

那麼mynet能不能給t3分配一個ip地址呢?如果能分配,那麼問題就應該可以得到解決。

docker network connect [OPTIONS] NETWORK CONTAINER

Connect a container to a network

Options:
      --alias strings           Add network-scoped alias for the container
      --driver-opt strings      driver options for the network
      --ip string               IPv4 address (e.g., 172.30.100.104)
      --ip6 string              IPv6 address (e.g., 2001:db8::33)
      --link list               Add link to another container
      --link-local-ip strings   Add a link-local address for the container
   
  # 一個容器兩個ip地址 
 docker network connect mynet t3   # 將t3加入到mynet網路中
 
 # 檢視mynet資訊
 docker network inspect mynet
 
 "Containers": {
			......
            "d8ecec77f7c1e6d26ad0fcf9107cf31bed4b6dd553321b737d14eb2b497794e0": {
                "Name": "t3",  # 我們發現了t3
                "EndpointID": "8796d63c1dd1969549a2d1d46808981a2b0ad725745d794bd3b824f278cec28c",
                "MacAddress": "02:42:c0:a8:00:04",
                "IPv4Address": "192.168.0.4/16",
                "IPv6Address": ""
            }
        },
        ......

這時候,t3就能和t1,t2進行通訊了。

5、部署Redis叢集

# 建立網路
docker network create redis --subnet 172.38.0.0/16

# 通過指令碼建立六個redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379 
bind 0.0.0.0
cluster-enabled yes 
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done

# 啟動容器
vim redis.py

import os
for i in range(1, 7):
    str = "docker run -p 637{}:6379 -p 1637{}:16379 --name redis-{} \
    -v /mydata/redis/node-{}/data:/data \
    -v /mydata/redis/node-{}/conf/redis.conf:/etc/redis/redis.conf \
    -d --net redis --ip 172.38.0.1{} redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf".format(i,i,i,i,i,i)
    os.system(str)

python reidis.py

# 建立叢集
docker exec -it redis-1 /bash/sh  # 進入redis-1容器

redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1

# 建立叢集。。。。
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49 172.38.0.11:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: a89026d4ea211d36ee04f2f3762c6e3cd9692a28 172.38.0.14:6379
   slots: (0 slots) slave
   replicates d63e90423a034f9c42e72cc562706919fd9fc418
S: 53d6196c160385181ff23b15e7bda7d4387b2b17 172.38.0.16:6379
   slots: (0 slots) slave
   replicates 9d1d33301aea7e4cc9eb41ec5404e2199258e94e
M: 9d1d33301aea7e4cc9eb41ec5404e2199258e94e 172.38.0.12:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: bee27443cd5eb6f031115f19968625eb86c8440b 172.38.0.15:6379
   slots: (0 slots) slave
   replicates 875f0a7c696fcd584c4f5a7fd5cc38b343acbc49
M: d63e90423a034f9c42e72cc562706919fd9fc418 172.38.0.13:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.


# 進行測試
redis-cli -c
127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:160
cluster_stats_messages_pong_sent:164
cluster_stats_messages_sent:324
cluster_stats_messages_ping_received:159
cluster_stats_messages_pong_received:160
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:324

十一、Docker Compose

1、介紹

Compose是Docker官方開源的專案,需要安裝。

Compose是用於定義和執行多容器Docker應用程式的工具。通過Compose,可以使用YAML檔案來配置應用程式的服務。然後,使用一個命令,就可以從配置中建立並啟動所有服務。

使用Compose基本上是一個三步過程:

  1. Dockerfile保證我們的專案在任何地方執行
  2. docker-compose檔案
  3. 啟動

2、快速開始

(1)安裝
curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

docker-compose --version
# 安裝成功
# docker-compose version 1.27.4, build 40524192
(2)使用
# 為專案建立目錄
mkdir composetest
cd composetest

# 編寫一段flask程式
vim app.py

import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)  # 這裡主機名直接用“redis”,而不是ip地址

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
    
# 編寫requirements.txt檔案(不指定版本,下載最新)
flask
redis

# 編寫Dockerfile檔案
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]


# 編寫docker-compose.yml檔案
# 檔案中定義了兩個服務,web和redis,web是從Dockerfile開始構建,redis使用的是公共映象
version: "3.9"
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
  redis:
    image: "redis:alpine"
    
# 執行
docker-compose up 

3、搭建部落格

# 建立並進入目錄
mkdir wordpress && cd wordpress

# 編寫docker-compose.yml檔案來啟動,並建立了一個單獨Mysql例項,具有用於資料永續性的卷掛載
version: "3.3"

services:
  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: somewordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress
      
  wordpress:
    depends_on:
      - db
    image: wordpress:latest
    ports:
      - "8000:80"
    restart: always
    environment:
      W0RDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: wordpress
      WORDPRESS_DB_NAME: wordpress
  volumes:
    db_data: {}
    

# 啟動執行
docker-compose up -d

十二、Docker Swarm

1、環境準備

準備四臺伺服器。安裝docker。

2、swarm叢集搭建

docker swarm COMMAND
Commands:
  ca          Display and rotate the root CA
  init        Initialize a swarm   # 初始化一個節點(管理節點)
  join        Join a swarm as a node and/or manager  # 加入節點
  join-token  Manage join tokens   # 通過節點token加入
  leave       Leave the swarm    # 離開節點
  unlock      Unlock swarm
  unlock-key  Manage the unlock key
  update      Update the swarm  #更新

# 首先我們初始化一個節點
docker swarm init --advertise-addr + 自己的ip(這裡用內網地址,主要是省錢)
docker swarm init --advertise-addr 172.27.0.4

# 提示我們節點建立成功
Swarm initialized: current node (yamss133bil4gb59fyangtdmm) is now a manager.

To add a worker to this swarm, run the following command:
	
	# 在其他機器上執行,加入這個節點
    docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 生成一個管理節點的token,
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

# 我們在機器2上行加入節點的命令
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-4yyba7377uz9mfabak6pwu4ci 172.27.0.4:2377

# 我們在機器1上檢視節點資訊
docker node ls
# 我們發現一個管理節點,一個工作節點,狀態都是就緒狀態
ID                            HOSTNAME        STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos   Ready     Active         Leader           20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos   Ready     Active                          20.10.0

# 現在將機器3也加入,同樣是work節點
# 到此為止,現在只有機器4沒有加入,這時候我們想把它設定成管理節點。
# 在機器1上執行生成管理節點的命令,在機器4上執行
docker swarm join-token manager
# 生成的命令在機器4上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377
# 此時機器4也是管理節點了
This node joined a swarm as a manager.

# 至此我們可以在機器1或者機器4上進行其他操作了。(只能在管理節點上操作)

3、Raft協議

在前面的步驟,我們已經完成了雙主雙從叢集的搭建。

Raft協議:保證大多數節點存活才能使用。必須大於1,叢集至少大於3臺。

實驗1

將機器1上的docker停止,宕機,現在叢集中只有一個管理節點了。叢集是否可用。

#我們在機器四上檢視節點資訊
docker node ls
# 發現我們的叢集已經不能用了
Error response from daemon: rpc error: code = DeadlineExceeded desc = context deadline exceeded

# 我們將機器1上的docker重啟,發現叢集又能用了,但是此時機器1上的docker已經不是leader了,leader自動轉給機器4了

實驗2

我們將工作節點離開叢集,檢視叢集資訊

# 我們在機器2上執行
docker swarm leave

# 在機器一上檢視節點資訊
docker node ls
# 我們發現機器2狀態是Down
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm *   VM-0-4-centos    Ready     Active         Reachable        20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

實驗3

現在我們將機器2也設定成管理節點(此時叢集有三個管理節點),隨機down掉一個管理節點,叢集是否正常執行。

# 在機器2上執行
docker swarm join --token SWMTKN-1-3f8p9pq2gp36s6ei0bs9pepqya24n274msin701j9kdt7h3v2z-82pamju7b37aq8e1dcf1xmmng 172.27.0.4:2377

# 將機器1的docker宕機
systemctl stop docker

# 在機器二上檢視節點資訊
docker node ls
# 叢集正常,且可以看到機器1宕機了
ID                            HOSTNAME         STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
yamss133bil4gb59fyangtdmm     VM-0-4-centos    Ready     Active         Unreachable      20.10.0
mfxdgj1pobj0idbl9cesm2xnp     VM-0-7-centos    Down      Active                          20.10.0
vdwcwr3v6qrn6da40zdrjkwmy *   VM-0-7-centos    Ready     Active         Reachable        20.10.0
u3rlqynazrdiz6oaubnuuyqod     VM-0-11-centos   Ready     Active                          20.10.0
im6kk7qd2a3s9g98lydni6udi     VM-0-13-centos   Ready     Active         Leader           20.10.0

4、彈性建立服務

docker service COMMAND

Commands:
  create      Create a new service  # 建立一個服務
  inspect     Display detailed information on one or more services  # 檢視服務資訊
  logs        Fetch the logs of a service or task  # 日誌
  ls          List services   # 列表
  ps          List the tasks of one or more services   # 檢視我們的服務
  rm          Remove one or more services  # 刪除服務
  rollback    Revert changes to a service's configuration
  scale       Scale one or multiple replicated services  # 動態擴縮容
  update      Update a service  # 更新

docker service create -p 8888:80 --name n1 nginx  # 建立一個服務,在叢集中隨機分配
kj0xokbxvf5uw91bswgp1cukf
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

# 給我們的服務增加三個副本
docker service update --replicas 3 n1

# 動態擴縮容
docker service scale n1=10   # 和上面updata一樣,這個方便

docker ps # 檢視

服務,叢集中任意的節點都可以訪問,服務可以有多個副本動態擴縮容實現高可用。

5、使用Docker stack部署部落格

我們現在需要將上面的部落格跑在我們的叢集中,並且要開十個副本。

# 編輯docker-compose.yml檔案
version: '3.3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     deploy:
       replicas: 10
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress
volumes:
    db_data: {}


# 進行啟動
docker stack deploy -c docker-compose.yml wordpress

# 檢視
docker service ls