上篇文章《Docker快速入門(一)》介紹了docker的基本概念和image的相關操做,本篇將進一步介紹image,容器和Dockerfile。php
(1)Docker 把應用程序及其依賴,打包在 image 文件裏面。
(2)只有經過這個image文件,才能生成 Docker 容器。image 文件能夠看做是容器的模板。Docker 根據 image 文件生成容器的實例。
(3)同一個 image 文件,能夠生成多個同時運行的容器實例。
(4)image 是二進制文件。實際開發中,一個 image 文件每每經過繼承另外一個 image 文件,加上一些個性化設置而生成。
(5)image 文件是通用的,一臺機器的 image 文件拷貝到另外一臺機器,照樣可使用。
(6)通常來講,爲了節省時間,咱們應該儘可能使用別人製做好的 image 文件,而不是本身製做。即便要定製,也應該基於別人的 image 文件進行加工,而不是從零開始製做。
(7)爲了方便共享,image 文件製做完成後,能夠上傳到網上的倉庫。Docker 的官方倉庫 Docker Hub 是最重要、最經常使用的 image 倉庫。此外,出售本身製做的 image 文件也是能夠的。html
下面咱們舉例介紹幾個命令:node
docker container run hello-world
該命令會根據 image 文件,生成一個正在運行的容器實例。
注意:docker container run命令具備自動抓取 image 文件的功能。若是發現本地沒有指定的 image 文件,就會從倉庫自動抓取。所以,前面的docker image pull命令並非必需的步驟。
若是運行成功,以下:python
[@sjs_123_183 ~]# docker container run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://cloud.docker.com/ For more examples and ideas, visit: https://docs.docker.com/engine/userguide/ [@sjs_123_183 ~]#
輸出以上內容後,hello world就會中止運行,容器自動終止。有些容器提供服務,並不會自動終止,如Ubuntu的image:mysql
[@sjs_123_183 ~]# docker container run -it ubuntu bash root@cd902f829884:/# root@cd902f829884:/# pwd / root@cd902f829884:/#
這時候,咱們打開另外一個終端,經過:linux
docker container ls
能夠看到本機正在運行的容器實例:nginx
[@sjs_123_183 ~]# docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cd902f829884 ubuntu "bash" 50 seconds ago Up 50 seconds nifty_pike
此時經過git
docker container kill [CONTAINER ID]
該命令能夠終止正在運行的容器。以下:golang
[@sjs_123_183 ~]# docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cd902f829884 ubuntu "bash" 4 minutes ago Up 4 minutes nifty_pike [@sjs_123_183 ~]# docker container kill cd9 cd9
image 文件生成的容器實例,自己也是一個文件,稱爲容器文件。也就是說,一旦容器生成,就會同時存在兩個文件: image 文件和容器文件。並且關閉容器並不會刪除容器文件,只是容器中止運行而已。web
docker container kill [CONTAINER ID] # 列出正在運行的容器 docker container ls --all # 列出本機全部容器,包括終止運行的容器
以下:
[@sjs_123_183 ~]# docker container ls --all CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cd902f829884 ubuntu "bash" 6 minutes ago Exited (137) About a minute ago nifty_pike 3ca03ca1cd7c hello-world "/hello" 11 minutes ago Exited (0) 11 minutes ago laughing_booth d225defb10db ubuntu "bash" 13 minutes ago Exited (137) 13 minutes ago fervent_galileo 77182065e27d ubuntu "bash" 16 minutes ago Exited (127) 15 minutes ago ecstatic_kilby ebf4e2421f51 hello-world "/hello" 20 minutes ago Exited (0) 20 minutes ago heuristic_albattani 081ccb2d6eed nginx "nginx -g 'daemon of…" 4 weeks ago Exited (0) 4 weeks ago adoring_poincare fea01895c580 hello-world "/hello" 4 weeks ago Exited (0) 4 weeks ago vibrant_goldwasser
命令的輸出結果之中,包括容器的 ID, 即CONTAINER ID。不少地方都須要提供這個 ID,好比上一節終止容器運行的docker container kill命令。
終止運行的容器文件,依然會佔據硬盤空間,可使用docker container rm [CONTAINER ID]命令刪除。參見:
[@sjs_123_183 ~]# docker container ls --all CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cd902f829884 ubuntu "bash" 6 minutes ago Exited (137) About a minute ago nifty_pike 3ca03ca1cd7c hello-world "/hello" 11 minutes ago Exited (0) 11 minutes ago laughing_booth d225defb10db ubuntu "bash" 13 minutes ago Exited (137) 13 minutes ago fervent_galileo 77182065e27d ubuntu "bash" 16 minutes ago Exited (127) 15 minutes ago ecstatic_kilby ebf4e2421f51 hello-world "/hello" 20 minutes ago Exited (0) 20 minutes ago heuristic_albattani 081ccb2d6eed nginx "nginx -g 'daemon of…" 4 weeks ago Exited (0) 4 weeks ago adoring_poincare fea01895c580 hello-world "/hello" 4 weeks ago Exited (0) 4 weeks ago vibrant_goldwasser [@sjs_123_183 ~]# docker container rm 3ca ebf 3ca ebf [@sjs_123_183 ~]# docker container ls --all CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cd902f829884 ubuntu "bash" 10 minutes ago Exited (137) 5 minutes ago nifty_pike d225defb10db ubuntu "bash" 17 minutes ago Exited (137) 17 minutes ago fervent_galileo 77182065e27d ubuntu "bash" 20 minutes ago Exited (127) 19 minutes ago ecstatic_kilby 081ccb2d6eed nginx "nginx -g 'daemon of…" 4 weeks ago Exited (0) 4 weeks ago adoring_poincare fea01895c580 hello-world "/hello" 4 weeks ago Exited (0) 4 weeks ago vibrant_goldwasser
上面的例子是咱們經過容器ID,刪除兩個hello-world容器文件。
學會使用 image 文件之後,接下來的問題就是,如何能夠生成 image 文件?若是你要推廣本身的軟件,勢必要本身製做 image 文件。這就須要用到 Dockerfile 文件。它是一個文本文件,用來配置 image。Docker 根據 該文件生成二進制的 image 文件。鏡像的定製實際上就是定製每一層所添加的配置、文件。若是咱們能夠把每一層修改、安裝、構建、操做的命令都寫入一個腳本,用這個腳原本構建、定製鏡像,能夠重複的使用、鏡像構建透明。Dockerfile 是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,所以每一條指令的內容,就是描述該層應當如何構建。
以 nginx 鏡像爲例,此次咱們使用 Dockerfile 來定製。
mkdir -p /search/odin/xnginx # 新建一個目錄 cd /search/odin/xnginx # cd 到該目錄下 touch Dockerfile # 建立一個叫Dockerfile的文件
在Dockerfile中寫入下面兩行,保存退出:
FROM nginx RUN echo '<h1>Hello, Docker! Hello, xnginx!</h1>' > /usr/share/nginx/html/index.html
這個Dockerfile很簡單,涉及到了兩條指令,FROM
和 RUN。
(1)FROM指定基礎鏡像
定製鏡像,那必定是以一個鏡像爲基礎,在其上進行定製。就像咱們以前運行了一個 nginx 鏡像的容器,再進行修改同樣,基礎鏡像是必須指定的。
而 FROM 就是指定基礎鏡像,所以一個 Dockerfile 中 FROM 是必備的指令,而且必須是第一條指令。
在 Docker Store 上有很是多的高質量的官方鏡像,有能夠直接拿來使用的服務類的鏡像,如 nginx、redis、mongo、mysql、httpd、php、tomcat 等;也有一些方便開發、構建、運行各類語言應用的鏡像,如 node、openjdk、python、ruby、golang 等。能夠在其中尋找一個最符合咱們最終目標的鏡像爲基礎鏡像進行定製。
若是沒有找到對應服務的鏡像,官方鏡像中還提供了一些更爲基礎的操做系統鏡像,如 ubuntu、debian、centos、fedora、alpine 等,這些操做系統的軟件庫爲咱們提供了更廣闊的擴展空間。
除了選擇現有鏡像爲基礎鏡像外,Docker 還存在一個特殊的鏡像,名爲 scratch。這個鏡像是虛擬的概念,並不實際存在,它表示一個空白的鏡像。
FROM scratch ...
以 scratch 爲基礎鏡像的話,意味着你不以任何鏡像爲基礎,接下來所寫的指令將做爲鏡像第一層開始存在。
不以任何系統爲基礎,直接將可執行文件複製進鏡像的作法並不罕見,好比 swarm、coreos/etcd。對於 Linux 下靜態編譯的程序來講,並不須要有操做系統提供運行時支持,所需的一切庫都已經在可執行文件裏了,所以直接 FROM scratch 會讓鏡像體積更加小巧。使用 Go 語言 開發的應用不少會使用這種方式來製做鏡像,這也是爲何有人認爲 Go 是特別適合容器微服務架構的語言的緣由之一。
(2)RUN 執行命令
RUN 指令是用來執行命令行命令的。因爲命令行的強大能力,RUN 指令在定製鏡像時是最經常使用的指令之一。其格式有兩種:
格式一: shell 格式:RUN <命令>,就像直接在命令行中輸入的命令同樣。剛纔寫的 Dockerfile 中的 RUN 指令就是這種格式。 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html 格式二: exec 格式:RUN ["可執行文件", "參數1", "參數2"],這更像是函數調用中的格式。
RUN 就像 Shell 腳本同樣能夠執行命令,不少初學者在寫Dockerfile的時候會像Shell 腳本同樣把每一個命令對應一個 RUN,好比這樣:
FROM debian:jessie RUN apt-get update RUN apt-get install -y gcc libc6-dev make RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" RUN mkdir -p /usr/src/redis RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 RUN make -C /usr/src/redis RUN make -C /usr/src/redis install
Dockerfile 中每個指令都會創建一層,RUN 也不例外。每個 RUN 的行爲,就和剛纔咱們手工創建鏡像的過程同樣:新創建一層,在其上執行這些命令,執行結束後,commit 這一層的修改,構成新的鏡像。
而上面的這種寫法,建立了 7 層鏡像。這是徹底沒有意義的,並且不少運行時不須要的東西,都被裝進了鏡像裏,好比編譯環境、更新的軟件包等等。結果就是產生很是臃腫、很是多層的鏡像,不只僅增長了構建部署的時間,也很容易出錯。
正確的寫法是:
FROM debian:jessie RUN buildDeps='gcc libc6-dev make' \ && apt-get update \ && apt-get install -y $buildDeps \ && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \ && mkdir -p /usr/src/redis \ && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \ && make -C /usr/src/redis \ && make -C /usr/src/redis install \ && rm -rf /var/lib/apt/lists/* \ && rm redis.tar.gz \ && rm -r /usr/src/redis \ && apt-get purge -y --auto-remove $buildDeps
(1)以前全部的命令只有一個目的,就是編譯、安裝 redis 可執行文件。
所以沒有必要創建不少層,這只是一層的事情。這裏沒有使用不少個 RUN 對一一對應不一樣的命令,而是僅僅使用一個 RUN 指令,並使用 && 將各個所需命令串聯起來。將以前的 7 層,簡化爲了 1 層。在撰寫 Dockerfile 的時候,要常常提醒本身,這並非在寫 Shell 腳本,而是在定義每一層該如何構建。
(2)這裏爲了格式化還進行了換行。
Dockerfile 支持 Shell 類的行尾添加 \ 的命令換行方式,以及行首 # 進行註釋的格式。良好的格式,好比換行、縮進、註釋等,會讓維護、排障更爲容易,這是一個比較好的習慣。
(3)還能夠看到這一組命令的最後添加了清理工做的命令,刪除了爲了編譯構建所須要的軟件,清理了全部下載、展開的文件,而且還清理了 apt 緩存文件。
這是很重要的一步,咱們以前說過,鏡像是多層存儲,每一層的東西並不會在下一層被刪除,會一直跟隨着鏡像。所以鏡像構建時,必定要確保每一層只添加真正須要添加的東西,任何無關的東西都應該清理掉。
不少人初學 Docker 製做出了很臃腫的鏡像的緣由之一,就是忘記了每一層構建的最後必定要清理掉無關文件。
到目前爲止,咱們明白了這個 Dockerfile 的內容,接下來就讓咱們構建這個鏡像吧。在 Dockerfile
文件所在目錄執行:
docker build -t nginx:v2 .
詳細以下:
[@sjs_123_183 xnginx]# docker build -t nginx:v2 . Sending build context to Docker daemon 2.048kB Step 1/2 : FROM nginx ---> c5c4e8fa2cf7 Step 2/2 : RUN echo '<h1>Hello, Docker! Hello, xnginx!</h1>' > /usr/share/nginx/html/index.html ---> Running in e955070ac2c9 Removing intermediate container e955070ac2c9 ---> 1beca7b40dee Successfully built 1beca7b40dee Successfully tagged nginx:v2 [@sjs_123_183 xnginx]#
從命令的輸出結果中,咱們能夠清晰的看到鏡像的構建過程。
在 Step 2 中,如同咱們以前所說的那樣,RUN 指令啓動了一個容器 e955070ac2c9,執行了所要求的命令,並最後提交了這一層 1beca7b40dee,隨後刪除了所用到的這個容器 e955070ac2c9。
這裏咱們使用了 docker build 命令進行鏡像構建。其格式爲:
docker build [選項] <上下文路徑/URL/->
這裏咱們指定了最終鏡像的名稱 -t nginx:v2,如今讓咱們啓動本身構建的容器:
docker run --name webserver -d -p 80:80 nginx:v2
這條命令會用 nginx 鏡像啓動一個容器,命名爲 webserver,而且映射了 80 端口,這樣咱們能夠用curl 命令去訪問這個 nginx 服務器。詳情以下:
[@sjs_123_183 ~]# docker run --name webserver -d -p 80:80 nginx:v2 a9f012a96d98262bffd30286c9d23dfe929b032c2149fa67105d29ddde71b763 [@sjs_123_183 ~]# curl http://127.0.0.1:80 <h1>Hello, Docker! Hello, xnginx!</h1> [@sjs_123_183 ~]#
經過瀏覽器也是能夠訪問的,若是你是本機的瀏覽器須要訪問:http://localhost。此處因爲我在遠程的linux上,只須要訪問ip便可。
細心的同窗可能已經注意到docker build 命令最後有一個 . 號。. 表示當前目錄,而 Dockerfile 就在當前目錄,所以很多初學者覺得這個路徑是在指定 Dockerfile 所在路徑,這麼理解實際上是不許確的。
前文已經說到docker build 的命令格式,這個 . 號是指定上下文路徑。那麼什麼是上下文呢?
(1)首先咱們要理解 docker build 的工做原理。Docker 在運行時分爲 Docker 引擎(也就是服務端守護進程)和客戶端工具。Docker 的引擎提供了一組 REST API,被稱爲 Docker Remote API,而如 docker 命令這樣的客戶端工具,則是經過這組 API 與 Docker 引擎交互,從而完成各類功能。
所以,雖然表面上咱們好像是在本機執行各類 docker 功能,但實際上,一切都是使用的遠程調用形式在服務端(Docker 引擎)完成。也由於這種 C/S 設計,讓咱們操做遠程服務器的 Docker 引擎變得垂手可得。
(2)當咱們進行鏡像構建的時候,並不是全部定製都會經過 RUN 指令完成,常常會須要將一些本地文件複製進鏡像,好比經過 COPY 指令、ADD 指令等。而 docker build 命令構建鏡像,其實並不是在本地構建,而是在服務端,也就是 Docker 引擎中構建的。那麼在這種客戶端/服務端的架構中,如何才能讓服務端得到本地文件呢?
這就引入了上下文的概念。當構建的時候,用戶會指定構建鏡像上下文的路徑,docker build 命令得知這個路徑後,會將路徑下的全部內容打包,而後上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下文包後,展開就會得到構建鏡像所需的一切文件。
若是在 Dockerfile
中這麼寫:
COPY ./package.json /app/
這並非要複製執行 docker build 命令所在的目錄下的 package.json,也不是複製 Dockerfile 所在目錄下的 package.json,而是複製 上下文(context) 目錄下的 package.json。
所以,COPY 這類指令中的源文件的路徑都是相對路徑。這也是初學者常常會問的爲何 COPY ../package.json /app 或者 COPY /opt/xxxx /app 沒法工做的緣由,由於這些路徑已經超出了上下文的範圍,Docker 引擎沒法得到這些位置的文件。若是真的須要那些文件,應該將它們複製到上下文目錄中去。
如今就能夠理解剛纔的命令 docker build -t nginx:v2 . 中的這個 .,其實是在指定上下文的目錄,docker build 命令會將該目錄下的內容打包交給 Docker 引擎以幫助構建鏡像。
在docker build命令的輸出內容中有這麼一句:
Sending build context to Docker daemon 2.048kB
這實際上就是發送上下文的過程。
(1)理解構建上下文對於鏡像構建是很重要的,避免犯一些不該該的錯誤。
好比有些初學者在發現 COPY /opt/xxxx /app 不工做後,因而乾脆將 Dockerfile 放到了硬盤根目錄去構建,結果發現 docker build 執行後,在發送一個幾十 GB 的東西,極爲緩慢並且很容易構建失敗。那是由於這種作法是在讓 docker build 打包整個硬盤,這顯然是使用錯誤。
(2)正確的作法是,將 Dockerfile 置於一個空目錄下,或者項目根目錄下。
若是該目錄下沒有所需文件,那麼應該把所需文件複製一份過來。若是目錄下有些東西確實不但願構建時傳給 Docker 引擎,那麼能夠用 .gitignore 同樣的語法寫一個 .dockerignore,該文件是用於剔除不須要做爲上下文傳遞給 Docker 引擎的。
(3)那麼爲何會有人誤覺得 . 是指定 Dockerfile 所在目錄呢?這是由於在默認狀況下,若是不額外指定 Dockerfile 的話,會將上下文目錄下的名爲 Dockerfile 的文件做爲 Dockerfile。這只是默認行爲,實際上 Dockerfile 的文件名並不要求必須爲 Dockerfile,並且並不要求必須位於上下文目錄中,好比能夠用 -f ../Dockerfile.php 參數指定某個文件做爲 Dockerfile。固然,通常你們習慣性的會使用默認的文件名 Dockerfile,以及會將其置於鏡像構建上下文目錄中。