Docker 是一個構建,發佈和運行應用程序的開放平臺。Docker 以容器爲資源分隔和調度的基本單位,容器封裝了整個項目運行時所須要的全部環境,經過 Docker 你能夠將應用程序與基礎架構分離,像管理應用程序同樣管理基礎架構,以便快速完成項目的部署與交付。java
Docker 使用 Go 語言進行開發,基於 Linux 內核的 cgroup,namespace,以及 AUFS 類的 Union FS 等技術,對進程進行封裝隔離,屬於操做系統層面的虛擬化技術。最初實現是基於 LXC,從 0.7 版本之後開始去除 LXC,轉而使用自行開發的 libcontainer,從 1.11 開始,則進一步演進爲使用 runC 和 containerd。linux
下圖體現了 Docker 和傳統虛擬化方式的不一樣之處:傳統虛擬機技術是虛擬出一套硬件後,在其上運行一個完整操做系統,再在該系統上運行所需應用進程;而 Docker 容器內的應用進程則是直接運行於宿主的內核,容器內沒有本身的內核,並且也沒有進行硬件虛擬,所以要比傳統虛擬機更爲輕便。git
Docker 使用 client-server 架構, Docker 客戶端將命令發送給 Docker 守護進程,後者負責構建,運行和分發 Docker 容器。 Docker 客戶端和守護程序使用 REST API,經過 UNIX 套接字或網絡接口進行通訊。核心概念以下:github
Docker 鏡像(Image)是一個特殊的文件系統,包含了程序運行時候所須要的資源和環境。鏡像不包含任何動態數據,其內容在構建以後也不會被改變。spring
由於鏡像包含操做系統完整的 root
文件系統,其體積每每是龐大的,所以在 Docker 設計時,充分利用 Union FS (聯合文件系統)的技術,將其設計爲分層存儲的架構,因此一個鏡像其實是由多層文件系統聯合組成。鏡像構建時,會一層層構建,前一層是後一層的基礎;每一層構建完就不會再發生改變,後一層上的任何改變只發生在本身這一層。好比,刪除前一層文件的操做,實際不是真的刪除前一層的文件,而是僅在當前層標記爲該文件已刪除。在最終容器運行的時候,雖然不會看到這個文件,可是實際上該文件會一直跟隨鏡像。所以,在構建鏡像的時候,須要額外當心,每一層儘可能只包含該層須要添加的東西,任何額外的東西應該在該層構建結束前清理掉。docker
分層存儲的特徵使得鏡像的複用、定製變的更爲容易。甚至能夠用以前構建好的鏡像做爲基礎層,而後進一步添加新的層,以定製本身所需的內容,構建新的鏡像。shell
鏡像(Image)和容器(Container)的關係,就像是面向對象程序設計中的 類
和 實例
同樣,鏡像是靜態的定義,容器是鏡像運行時的實體,容器能夠被建立、啓動、中止、刪除、暫停等。centos
容器的實質是進程,但與直接在宿主執行的進程不一樣,容器進程運行在屬於本身的、獨立的命名空間中。所以容器能夠擁有本身的 root
文件系統、本身的網絡配置、本身的進程空間,甚至本身的用戶 ID 空間。容器內的進程運行在一個隔離的環境裏,使用起來,就好像是在一個獨立於宿主的系統下操做同樣,這種特性使得容器封裝的應用比直接在宿主運行更加安全。緩存
前面講過鏡像使用的是分層存儲,容器也是如此。每個容器運行時,是以鏡像爲基礎層,在其上建立一個當前容器的存儲層,咱們能夠稱這個爲容器運行時讀寫而準備的存儲層稱爲 容器存儲層。容器存儲層的生存週期和容器同樣,容器消亡時,容器存儲層也隨之消亡。所以,任何保存於容器存儲層的信息都會隨容器刪除而丟失。安全
按照 Docker 最佳實踐的要求,容器不該該向其存儲層內寫入任何數據,容器存儲層要保持無狀態化。全部的文件寫入操做,都應該使用數據卷(Volume)、或者綁定宿主目錄,在這些位置的讀寫會跳過容器存儲層,直接對宿主(或網絡存儲)發生讀寫,其性能和穩定性更高。數據卷的生存週期獨立於容器,容器消亡,數據卷不會消亡,所以,使用數據卷後,容器刪除或者從新運行以後,數據都不會丟失。
鏡像構建完成後,能夠很容易的在當前宿主機上運行,但若是須要在其它服務器上使用這個鏡像,就須要一個集中的存儲、分發鏡像的服務,這就是鏡像倉庫(Registry)。Docker Hub 是 Docker 官方提供的鏡像公有倉庫,提供了大量經常使用軟件的鏡像,固然出於安全和保密的須要,你也能夠構建本身的私有倉庫。
Docker daemon(dockerd)負責監聽 Docker API 請求並管理 Docker 對象,如鏡像,容器,網絡和卷,守護程序彼此之間也能夠進行通信。
Docker 客戶端(docker)是用戶與 Docker 交互的主要方式。當你使用 docker run 等命令時,客戶端會將這些命令發送到 dockerd,dockerd 負責將其執行。一個 Docker客戶端能夠與多個 dockerd 進行通信。
Docker 提供了大量命令用於管理鏡像、容器和服務,命令的統一使用格式爲:docker [OPTIONS] COMMAND
,其中 OPTIONS 表明可選參數。須要注意的是 Docker 命令的執行通常都須要獲取 root 權限,這是由於 Docker 的命令行工具 docker 與 docker daemon 是同一個二進制文件,docker daemon 負責接收並執行來自 docker 的命令,它的運行須要 root 權限。全部經常使用命令及其使用場景以下:
從官方鏡像倉庫 Docker Hub 查找指定名稱的鏡像。經常使用參數爲 --no-trunc
,表明顯示完整的鏡像信息。
列出全部頂層鏡像的相關信息。經常使用參數以下:
從官方倉庫下載鏡像,:TAG
爲鏡像版本,不加則默認下載最新版本。
刪除指定版本的鏡像,不加 :TAG
則默認刪除鏡像的最新版本。若是有基於該鏡像的容器存在,則該鏡像沒法直接刪除,此時可使用參數 -f
,表明強制刪除。rmi 命令支持批量刪除,多個鏡像名之間使用空格分隔。若是想要刪除全部鏡像,則可使用命令 docker rmi -f $(docker images -qa)
。
run 是 docker 中最爲核心的一個命令,用於新建並啓動容器,其擁有衆多可用參數,可使用 docker run --help
查看全部可用參數。經常使用參數以下:
-i
結合使用,表示使用僞終端與容器進行交互;-v
參數同時掛載多個 volume。volume 的格式爲:[host-dir]:[container-dir]:[rw:ro]
,[rw:ro]
用於指定數據卷的模式,rw
表明讀寫模式,ro
表明只讀模式。hostPort:containerPort
,經過端口的暴露,可讓外部主機可以訪問容器內的應用。列出當前全部正在運行的容器。經常使用參數以下:
與容器啓動、中止相關的命令爲:docker start|restart|stop|kill 容器名或ID
,start 命令用於啓動已有的容器,restart 用於重啓正在運行的容器,stop 用於中止正在運行的容器,kill 用於強制中止容器。
想要進入正在運行的容器,與容器主進程交互,有如下兩種經常使用方法:
想要退出正在運行的容器,有如下兩種經常使用方法:
刪除已中止的容器,經常使用參數爲-f
,表示強制刪除容器,即使容器還在運行。想要刪除全部容器,可使用 docker rm -f $(docker ps -aq)
命令。
可使用 docker inspect [OPTIONS] NAME|ID [NAME|ID...]
查看容器或者鏡像的詳細信息,想要查看指定的信息,可使用 -- format
參數來指定輸出的模板格式,示例以下:
docker inspect --format='{{.NetworkSettings}}' 32cb3ace3279
複製代碼
可使用 docker logs [OPTIONS] CONTAINER
查看容器中進程的運行日誌,經常使用參數以下:
dockerfile 是 Docker 用來構建鏡像的文本文件,包含自定義的指令和格式,能夠經過 build 命令從 dockerfile 中構建鏡像,命令格式爲:docker build [OPTIONS] PATH | URL | -
。
dockerfile 描述了組裝鏡像的步驟,其中每條指令都是單獨執行的。除了 FROM 指令,其餘的每一條指令都會在上一條指令所生成鏡像的基礎上執行,執行完後會生成一個新的鏡像層,新鏡像層覆蓋在原來的鏡像之上從而造成新的鏡像。爲了提升鏡像構建的速度, Docker Daemon 會緩存構建過程當中的中間鏡像。在構建鏡像時,它會將 dockerfile 中下一條指令和基礎鏡像的全部子鏡像作比較,若是有一個子鏡像是由相同的指令生成的,則命中緩存,直接使用該鏡像,而不用再生成一個新的鏡像。經常使用指令以下:
FROM 指令用於指定基礎鏡像,所以全部的 dockerfile 都必須使用 FROM 指令開頭。FROM 指令能夠出現屢次,這樣會構建多個鏡像,每一個鏡像建立完成後,Docker 命令行界面會輸出該鏡像的 ID。經常使用指令格式爲:FROM <image>[:<tag>] [AS <name>]
。
MAINTAINER 指令能夠用來設置做者名稱和郵箱,目前 MAINTAINER 指令被標識爲廢棄,官方推薦使用 LABEL 代替。
LABEL 指令能夠用於指定鏡像相關的元數據信息。格式爲:LABEL <key>=<value> <key>=<value> <key>=<value> ...
。
ENV 指令用於聲明環境變量,聲明好的環境變量能夠在後面的指令中引用,引用格式爲 $variable_name
或 ${variable_name}
。經常使用格式有如下兩種:
ENV <key> <value>
:用於設置單個環境變量;ENV <key>=<value> ...
:用於一次設置多個環境變量。EXPOSE 用於指明容器對外暴露的端口號,格式爲:EXPOSE <port> [<port>/<protocol>...]
,您能夠指定端口是偵聽 TCP 仍是 UDP,若是未指定協議,則默認爲 TCP。
WORKDIR 用於指明工做目錄,它能夠屢次使用。若是指明的是相對路徑,則它將相對於上一個WORKDIR指令的路徑。示例以下:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd # 此時pwd爲:/a/b/c
複製代碼
COPY 指令的經常使用格式爲:COPY <src>... <dest>
,用於將指定路徑中的文件添加到新的鏡像中,拷貝的目標路徑能夠不存在,程序會自動建立。
ADD 指令的經常使用格式爲:COPY <src>... <dest>
,做用與 COPY 指令相似,但功能更爲強大,例如 Src
支持文件的網絡地址,且若是 Src
指向的是壓縮文件,ADD 在複製完成後還會自動進行解壓。
RUN 指令會在前一條命令建立出的鏡像基礎上再建立一個容器,並在容器中運行命令,在命令結束後提交該容器爲新的鏡像。它支持如下兩種格式:
RUN <command>
(shell 格式)RUN ["executable", "param1", "param2"]
(exec 格式)使用 shell 格式時候,命令經過 /bin/sh -c
運行,而當使用 exec 格式時,命令是直接運行的,容器不調用 shell 程序,這意味着不會發生正常的 shell 處理。例如,RUN ["echo","$HOME"]
不會對 $HOME
執行變量替換,此時正確的格式應爲:RUN ["sh","-c","echo $HOME"]
。下面的 CMD 指令也存在一樣的問題。
CMD ["executable","param1","param2"]
(exec 格式, 首選)CMD ["param1","param2"]
(做爲 ENTRYPOINT 的默認參數)CMD command param1 param2
(shell 格式)CMD 指令提供容器運行時的默認值,這些默認值能夠是一條指令,也能夠是一些參數。一個 dockerfile 中能夠有多條 CMD 指令,但只有最後一條 CMD 指令有效。CMD 指令與 RUN 指令的命令格式相同,但做用不一樣:RUN 指令是在鏡像的構建階段用於產生新的鏡像;而 CMD 指令則是在容器的啓動階段默認將 CMD 指令做爲第一條執行的命令,若是用戶在 docker run 時指定了新的命令參數,則會覆蓋 CMD 指令中的命令。
ENTRYPOINT 指令 支持如下兩種格式:
ENTRYPOINT ["executable", "param1", "param2"]
(exec 格式,首先)ENTRYPOINT command param1 param2
(shell 格式)ENTRYPOINT 指令 和 CMD 指令相似,均可以讓容器在每次啓動時執行相同的命令。但不一樣的是 CMD 後面能夠是參數也能夠是命令,而 ENTRYPOINT 只能是命令;另外 docker run 命令提供的運行參數能夠覆蓋 CMD,但不能覆蓋 ENTRYPOINT ,這意味着 ENTRYPOINT 指令上的命令必定會被執行。以下 dockerfile 片斷:
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
複製代碼
當執行 docker run -it image
後,此時輸出爲 hello world
,而當你執行 docker run -it image spring
,此時 CMD 中的參數會被覆蓋,此時輸出爲hello spring
。
生產環境中的大多數項目一般都部署在 Linux 服務器上,這裏咱們從基礎的 Linux 鏡像開始,並將咱們的項目(這裏以 Spring Boot 項目爲例)一塊兒打包構建成爲一個完整的可執行的鏡像。首先須要建立Dockerfile文件,其內容以下:
# 以官方倉庫的centos鏡像爲基礎開始建立
FROM centos
# 做者信息
MAINTAINER heibaiying@heibaiying.com
# 把JDK安裝包拷貝到容器中並自動進行解壓
ADD jdk-8u211-linux-x64.tar.gz /usr/java/
# 拷貝項目Jar包到容器中
COPY spring-boot-base.jar /usr/app/
# 配置Java環境變量
ENV JAVA_HOME /usr/java/jdk1.8.0_211
ENV JRE_HOME ${JAVA_HOME}/jre
ENV CLASSPATH .:${JAVA_HOME}/lib:${JRE_HOME}/lib
ENV PATH ${JAVA_HOME}/bin:$PATH
# 項目啓動命令
ENTRYPOINT ["java", "-jar", "/usr/app/spring-boot-base.jar"]
複製代碼
將 JDK 安裝包,Spring Boot 項目的 Jar 包以及 Dockerfile 文件放在同一個目錄,而後執行下面鏡像構建命令:
docker build -t spring-boot-base-java:latest .
複製代碼
鏡像構建完成後,可使用如下命令進行啓動:
docker run -it -p 8080:8080 spring-boot-base-java
複製代碼
這裏爲了觀察到啓動效果,因此使用交互的方式啓動,實際部署時可使用-d
參數來後臺啓動,輸出以下:
上面的項目咱們是基於最基礎的 Centos 鏡像開始構建,但因爲 Docker Hub 上已經提供了 JDK 的鏡像,咱們也能夠選擇從 JDK 鏡像開始構建,此時構建過程更加簡單。構建步驟和上面的徹底一致,只是 Dockerfile 的內容有所不一樣,以下:
# 因爲只須要運行環境,這裏咱們直接以官方倉庫的jre鏡像爲基礎開始建立
FROM openjdk:8u212-jre
# 做者信息
MAINTAINER heibaiying@heibaiying.com
# 拷貝項目Jar包到容器中
COPY spring-boot-base.jar /usr/app/
# 項目啓動命令
ENTRYPOINT ["java", "-jar", "/usr/app/spring-boot-base.jar"]
複製代碼
更多文章,歡迎訪問 [全棧工程師手冊] ,GitHub 地址:github.com/heibaiying/…