Docker進階
一. 數據卷(volume)
1.1 爲什麼需要數據卷
docker鏡像是由多個文件系統(只讀層)疊加而成,當我們啓動一個容器的時候,docker的服務端會加載鏡像的只讀層,並在最頂層創建一個可讀寫層。當運行的容器修改現有的文件,該文件會從只讀層拷貝到讀寫層,其實並沒有影響到鏡像本身,依然存在於鏡像中。當我們刪除掉容器,容器運行時的數據都會丟失,當我們通過鏡像重新run一個容器,該容器還是會回到最初的狀態。那麼問題來了,我們該如何保存我們運行中的數據了?這個問題有兩種解決方法:a. 我們可以定期的將我們的容器通過commit的方式生成一個鏡像;b.通過數據捲來實現。很明顯我們不可能不停的commit來生成鏡像,有些朋友可能會說你這麼說的意思是通過數據捲來實現唄,可我都不知道什麼是數據卷,怎麼知道它好用了?那麼接下來我們就開始來介紹數據卷。
1.2 什麼是數據卷
數據卷就是容器內部的數據目錄直接映射到宿主機上的目錄上,無論在宿主機還是容器內對數據的修改在另外一方都是可見的。
1.3 數據卷的使用
1.3.1 命令的使用
通過前面篇章中使用的centos鏡像來講解,其實也就一個命令,我們的工作也就是圍繞着該命令來講解:docker run -it -v /dataVolumn:/containerDataVolumn centos
至於其他的命令在這裏不作過多的解釋,只解釋一下 -v /dataVolumn:/containerDataVolumn 這個命令的意思,-v是綁定將容器中的目錄掛載到宿主機的目錄上,/dataVolumn:/containerDataVolumn中冒號前的路徑是指自動在宿主機上創建的目錄名(不用我們手動去創建),冒號後的路徑是指在容器中自動創建的目錄名。
1.3.2數據卷的創建與測試
我們前面提到過“論在宿主機還是容器內對數據的修改在另外一方都是可見的”,那麼本小節我們回來測試這個問題。
A.我們在容器中 /containerDataVolumn 目錄下創建一個container.txt文件,並寫入內容,命令:echo “hello world” > container.txt
在宿主機的 /dataVolumn目錄下會查看到有container.txt文件,並查看內容,如下圖所示:
B.在宿主機的 /dataVolumn目錄下新建 host.txt文件,並寫入內容,命令爲:echo “welcome” > host.txt
在容器的/containerDataVolumn目錄下會看到host.txt文件,並查看內容,如下圖所示:
C.刪除掉容器,查看宿主機 /dataVolumn目錄,文件並沒有丟失
D.我們可以通過 docker inspect 容器ID 命令查看容器的信息,其中hostConfig.binds可以查看到綁定信息,如下圖所示:
1.3.3 數據卷的其他創建方式
通過上面的方式創建數據卷的時候,我們每次在運行鏡像的時候都需要去指定宿主機目錄和容器目錄,不便於維護與遷移,給大家舉個例子:例如我們的日誌文件是存放在容器中的 /cloud-project/logs目錄下,而且在項目的配置文件中也是指定到該目錄下,對應到我們的宿主機是/mycloud-project/logs目錄,如果說由於項目發佈啓動的時候,運維人員寫錯了目錄名,那將是很大的問題。所以我們在生成鏡像文件的時候就指定數據卷的目錄豈不是更好。
具體操作是,我們根據Dockerfile目錄中通過VOLUMN指定數據卷的位置,至於什麼是Dockerfile,在後面的篇章中會講解。
A. 新建一個空的目錄:mkdir my-dockerfile
B. 新建Dockerfile文件
C.在Dockerfile中添加如下內容:
FROM centos
VOLUMN ["/containerDataVolumn"]
CMD /bin/bash
D.執行命令 docker build -t mycentos:me . 生成名爲mycentos,tag爲me的新的鏡像文件。注意:最後的一個點不能省略,它不是結束的句號(我在這裏栽了很大的跟頭)!!!
E. 根據mycentos:me這個鏡像啓動一個容器,觀察根目錄下會生成 containerDataVolumn文件夾,如下圖所示:
F. 那麼如何查看,容器中的數據卷目錄對應的宿主機的目錄呢?上一小節我們講過,可以通過 docker inspect 容器ID 命令查看,結果如下圖所示:
二. Dockerfile
2.1 Dockerfile是什麼
Dockerfile是docker中鏡像文件的的描述文件,說的直白點就是鏡像文件到底是由什麼東西一步步構成的。例如我們在淘寶上買了一件商品,但是這個商品需要組裝才能使用,於是賣家就給了你一張圖紙,你就按照圖紙一步一步的組裝起來,然後就成了你所需要的樣子。那麼Dockerfile就是這張圖紙,鏡像文件就是你需要的商品。Dockerfile名字可以隨便命名,但是不建議你這做,還是按照規範來使用,首字母要大寫。下面給出我們前幾個章節使用到的ubuntu爲例,來看看它的Dockerfile是怎麼樣的,如下圖所示:
2.2 Dockerfile、鏡像、容器
Dockerfile:是鏡像的構建文件,描述的鏡像是一步步怎麼來的。
鏡像:是通過Dockerfile做出來的,包含操作系統基礎文件和軟件運行環境,它使用分層的存儲方式。
容器:是運行起來的鏡像,簡單理解,Docker鏡像相當於程序,容器相當於進程。
2.3 Dockerfile的基本語法及執行流程
2.3.1 Dockerfile的基本語法
a.每個保留字必須放在每一行的開頭,可以大寫也可以小寫,但是建議大寫;
b.指令按照順序,從上往下依次執行;
c.#表示註解;
d.每條執行指令都會創建一個新的鏡像,並且執行提交。
2.3.2 Dockerfile的執行流程
a.docker從基礎鏡像中執行一個容器;
b.執行一條指令對鏡像進行修改;
c.執行docker commit命令,提交新的鏡像;
d.在基於剛剛提交的鏡像運行一個新的容器;
e.執行Dockerfile中的下一條指令,按照b、c、d依次循環下去,知道所有的指令執行完畢。
2.3.3 演示講解
FROM centos #指定要生成的鏡像的基礎鏡像,開頭第一句話必須也只能是FROM
MAINTAINER [email protected] #指定作者是誰
RUN yum install -y vim #執行 yum install -y vim 命令,安裝vim
RUN yum install -y net-tools #執行 yum install -y net-tools, 安裝net-tools工具
WORKDIR /dev/ #啓動容器後,如果啓動交互模式,直接進入到哪個目錄
CMD ["/bin/bash"] #啓動容器的時候,進入到/bin/bash這種命令行
如上代碼所示,FROM、MAINTAINER、RUN、WORKDIR、CMD均爲關鍵字,按照標準的規範需要大寫;FROM centos表示我們的基礎鏡像是centos,然後會啓動centos這個容器,執行第二行代碼又會生成新的鏡像文件,然後提交,周而復始。
2.4 Dockerfile關鍵字
關鍵字 | 作用 |
---|---|
FROM | 指定基礎鏡像 |
MAINTAINER | 作者的信息 |
RUN | 執行什麼命令 |
EXPOSE | 容器對外暴露的端口 |
WORKDIR | 進入到容器後進入到哪個目錄 |
ENV | 配置環境變量 |
ADD | 將文件拷貝到鏡像中並解壓 |
COPY | 將文件拷貝到鏡像中 |
VOLUME | 配置數據卷 |
CMD | 容器啓動時候執行的命令 |
ENTRYPOINT | 容器啓動時候執行的命令 |
A.ADD指令,我們現在定義這樣一個Dockerfile,代碼如下所示:
FROM ubuntu:18.04
MAINTAINER [email protected]
RUN mkdir /datas
ADD jdk-8u60-linux-x64.tar.gz /datas/
WORKDIR /datas/
CMD ["/bin/bash"]
首先必須將jdk-8u60-linux-x64.tar.gz文件拷貝到Dockerfile同級目錄下,如下圖所示:
執行命令:docker build -t myubuntu . 構建myubuntu鏡像,如下圖所示:
然後啓動容器,進入到 /datas/目錄下,會發現 jdk1.8.0_60 目錄,表示add命令還執行了解壓命令,如下圖所示:
B.COPY命令,我們定義一個Dockerfile, 代碼如下:
FROM centos
MAINTAINER [email protected]
RUN mkdir /datas
ADD jdk-8u60-linux-x64.tar.gz /datas/
WORKDIR /datas/
CMD ["/bin/bash"]
執行命令:docker build -t myubuntu . 構建myubuntu鏡像。
然後啓動容器,進入到 /datas/目錄下,會看到jdk-8u60-linux-x64.tar.gz文件,並沒有解壓,如下圖所示:
C.ENV命令,配置環境變量,我們還是用上面提到的jdk爲例,做過Java開發的朋友都知道,jdk需要配置環境變量,Dockerfile的內容如下所示:
FROM ubuntu
MAINTAINER [email protected]
RUN mkdir -p /datas/
ADD jdk-8u60-linux-x64.tar.gz /datas/
ENV JAVA_HOME=/datas/jdk1.8.0_60 #配置JAVA_HOME
ENV PATH=$JAVA_HOME/bin:$PATH #配置PATH
CMD ["/bin/bash"]
當我們容器啓動後,我們輸入:java -version命令,就能看到我們熟悉的內容了。
D.CMD關鍵字,在鏡像構建階段不執行,在容器啓動階段執行(而我們的RUN關鍵字定義的指令在容器構建階段就會執行,請記住它與CMD的區別)。如果一個Dockerfile中有多個CMD命令,後面的會覆蓋前面的,說白了只有最後一個生效,如下代碼和註釋:
............省略............
CMD echo "<<<<<<<<<<<nice to meet you>>>>>>>>>>"
CMD /bin/bash
CMD echo "==========How are you?=============" #當容器啓動的時候只有該行代碼會執行,會將前兩行代碼覆蓋
CMD還有一個問題,就是當我們使用 docker run命令的時候,我們可以在整個docker命令的最後加上其他額外的命令,那麼額外的命令會覆蓋Dockerfile中所有的CMD命令,例如我們執行如下命令:docker run -it centos ls / -l,最終的結果如下圖所示:
E. ENTRYPOINT指令,和CMD命令差不多,如果一個Dockerfile中有多個ENTRYPOINT,只有最後一個生效。但是他們還是有區別的,如果ENTRYPOINT後面有CMD,當以exec的方式運行的時候,CMD的值會作爲參數給ENTRYPOINT,可能很多人看到這句話不大理解,那麼我們來兩個例子:
例一:
FROM centos
MAINTAINER [email protected]
RUN mkdir -p /datas/
ENTRYPOINT ["echo", "hello"]
CMD ["world"] #會將world作爲echo hello的參數,最後的命令其實爲echo hello world
例二:
FROM centos
MAINTAINER [email protected]
RUN mkdir -p /datas/
ENTRYPOINT ["echo", "hello"]
總結:當我們理解了CMD和ENTRYPOINT兩個命令的區別後,以後在使用的過程中就不會出現各種問題了。
三. docker-compose
在開發一個應用的時候,我們需要其他的很多東西,例如數據庫,nginx,網站應用等很多的環境,而docker又推崇的是每一個容器只運行一個進程,那麼我們勢必得很多的容器,那麼我們得通過docker build命令一個個的構建鏡像,然後在通過docker run命令啓動一個個容器,那麼當我們修改了應用後,我們又得去重複上面的操作。而且容器與容器之間存在着很多的依賴,我們在操作的時候還得去考慮先啓動哪個容器,在啓動另外一個容器,這些操作和步驟都得花上大量的精力。那麼docker-compose的出現就是爲了解決這個問題。
docker-compose是一種容器編排技術,我們可以編寫一個docker-compose.yml文件,在文件中編排好我們的服務,只用通過一個命令即可搞定所有的工作。
3.1 安裝docker-compose
A. 運行如下命令獲取最新版的docker-compose
curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
B. 給二進制文件加上執行權限
chmod +x /usr/local/bin/docker-compose
如果docker-compose命令無效,可以給這個文件創建一個在 /usr/bin 目錄下的一個軟連接,如下所示:
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
C. 驗證docker-compose是否安裝成功
docker-compose --version
3.2 docker-compose.yml的編寫
version: "3.7"
services:
mysql:
image: mysql:5.7.26
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=miller
network_mode: "host"
volumes:
- "/datas/db:/var/lib/mysql"
- "/docker-compose/mysql/my.cnf:/etc/my.cnf"
eureka-server:
build:
context: ./eureka-server
dockerfile: Dockerfile
ports:
- "8761:8761"
network_mode: "host"
provider:
build:
context: ./provider
dockerfile: Dockerfile
ports:
- "6666:6666"
network_mode: "host"
depends_on:
- "mysql"
command: ["./wait-for-it.sh", "mysql:3306"]
consumer:
container_name: consumer
build:
context: ./consumer
network_mode: "host"
ports:
- "8080:8080"
version
指定docker-compose文件的版本,對應的版本信息如下:
services
定義服務。
image
指定基礎鏡像。
ports
指定對外開放的端口。
environment
配置環境變量。
network_mode
網絡模式,默認爲bridge(橋接)模式。
volumes
指定數據卷。
context
要構建的鏡像的上下文,說白了就是相對於docker-compose.yml文件的位置。
dockerfile
指定Dockerfile文件的名字,如果名字爲Dockerfile的話,不用指定。
depends_on
指定容器所依賴的另外一個容器的服務名,但是並不會等待所依賴的容器啓動纔去啓動這個容器。
3.3 啓動
docker-compose up: 如果沒有構建過鏡像,首先會構建鏡像,然後啓動容器。
docker-compose up --build: 無論鏡像是否存在,首先會構建鏡像,然後啓動容器。
docker-compose start [service…]: 啓動已經存在的容器。
3.4 wait-for-it.sh使用
地址:https://github.com/vishnubob/wait-for-it
./wait-for-it.sh www.google.com:80 -- echo "google is up"
如果連接上谷歌的服務器,輸出“google is up”
四. Docker的工程化實踐
A. 創建spring-boot的工程。
在主類上加上@SpringBootApplication, 來標註該類爲一個Spring-boot項目的啓動類。
B. 編寫代碼。
C. 將spring-boot項目打包後的jar包上傳到ubuntu的/spring-boot/目錄下。
D. 執行:docker run -it -v /spring-boot:/jarDir -p 8088:8080 mcr.microsoft.com/java/jre:8u192-zulu-alpine /bin/sh -c “java -jar /jarDir/spring-boot-1.0-SNAPSHOT.jar” 啓動我們的docker的jre容器,然後運行我們java程序。
E. docker run -it -e MYSQL_ROOT_PASSWORD=123456 -p 3305:3306 mysql:5.7.26,啓動mysql的容器。
F. 將mysql容器的數據通過數據卷的方式映射到宿主的目錄下。
五.springcloud 容器部署例子
1.Eureka Dockerfile
#基於哪個鏡像
FROM lwieske/java-8
#將本地文件夾掛載到當前容器
VOLUME /tmp
ADD eureka-0.0.1-SNAPSHOT.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JAVA_OPTS=""
#聲明暴露的端口
EXPOSE 8761
#配置容器啓動後執行的命令
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
2.Provider Dockerfile
#基於哪個鏡像
FROM lwieske/java-8
#將本地文件夾掛載到當前容器
VOLUME /tmp
#賦值文件到容器
ADD my-cloud-provider-1.0-SNAPSHOT.jar app.jar
COPY wait-for-it.sh /
RUN sh -c 'touch /app.jar'
ENV JAVA_OPTS=""
#聲明暴露的端口
EXPOSE 6002
#配置容器啓動後執行的命令
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar" ]
3.其它模塊類似
4.docker-compose.yml文件的編寫(容器編排)
version: "3.7"
services:
eureka:
build:
context: ./eureka
ports:
- "8761:8761"
network_mode: "host"
provider:
build:
context: ./provider
ports:
- "6002:6002"
network_mode: "host"
depends_on:
- "mysql"
command: ["./wait-for-it.sh","mysql:3306"]
mysql:
image: mysql:5.7.26
ports:
- "3305:3306"
environment:
- MYSQL_ROOT_PASSWORD=123456
volumes:
- "/mydir/mysqldate/mysql:/var/lib/mysql"
consumer:
build:
context: ./consumer
ports:
- "8080:8080"
network_mode: "host"
network_mode: “host”:讓不同容器間可以相互通信。
5.服務器結構圖如下
(注:provider 目錄下還有一個wait-for-it.sh文件)