容器運行時介紹:containerd
docker基礎:
1、LXC 提供輕量級的虛擬化,隔離進程和資源。
docker 底層還是LXC(linux containerd) go語言開發
靈活性 輕量級 可互換(即時部署更新升級) 便攜式(便攜部署) 可擴展 可堆疊
docker幾個重要概念: namespace cgroup unionfs
鏡像
:只讀模板,基於基礎鏡像並添加自定義功能來組成。
容器
:容器的本質是進程(獨立網絡配置、獨立進程 獨立的文件系統)
registry倉庫
:存儲docker鏡像的倉庫,dockerhub(docker pull 或 docker run默認來這裏查找鏡像),docker push 推送鏡像到對應倉庫。
容器與虛擬機的區別
: 是否有獨立操作系統,獨立內核。
docker本質是
宿主機一個特殊進程
,docker是通過
namespace
實現資源(網絡、文件系統)隔離,通過
cgroup
實現資源限制(cpu 內存等),通過寫時複製技術(copy-on-write)實現了高效文件操作(如虛擬機磁盤分配了500G,並不是實際佔用物理磁盤500G)
namespace
:命名空間 對文件系統 進程 網絡的隔離。
pid namespace 進程隔離
net namespace 網絡隔離
mut namespace 文件系統隔離
uts namespace 主機名和域名隔離
user namspace 用戶和用戶組隔離(3.8以後內核支持)
ipc namepace ipc資源訪問
cgroups:
control groups
物理資源隔離,cpu 內存 IO磁盤 網絡帶寬,避免搶佔資源。
cgroups 由
七個子系統
組成,cpuset cpu cpuacct bikio devices freezer memory(
linux內核裏內置的子系統
)
路徑在這裏/sys/fs/cgroup/memory
在 CGroup 中,所有的任務就是一個系統的一個進程,而 CGroup 就是一組按照某種標準劃分的進程,在 CGroup 這種機制中,所有的資源控制都是以
CGroup 作爲單位
實現的,每一個進程都可以隨時加入一個 CGroup 也可以隨時退出。
cgroup的API以一個僞文件系統(/sys/fs/cgroup/)實現方式,用戶可以創建和銷燬cgroup,實現數資源再利用。資源管理是以子系統實現的。
unionfs
聯合文件系統
Linux 的命名空間和控制組分別解決了不同資源隔離的問題,前者解決了
進程、網絡以及文件系統的隔離
,後者實現了
CPU、內存等資源的隔離
,但是在 Docker 中還有另一個非常重要的問題需要解決 - 也就是
鏡像
。
鏡像到底是什麼,它又是如何組成和組織的呢?而這其中最重要的概念就是
鏡像層
(Layers)(如下圖)的概念,而鏡像層依賴於一系列的底層技術,比如
文件系統(filesystems)、寫時複製(copy-on-write)、聯合掛載(union mounts)
等。
鏡像是隻讀的,容器是隻寫的,位於鏡像層之上。
鏡像就是由這些層一層一層堆疊起來的,
鏡像中的層只讀
的,當我們運行容器的時候,就可以在這些基礎層之上添加新的可寫層,也就是我們通常說的
容器層
,對於運行中的容器所做的所有更改(比如寫入新文件、修改現有文件、刪除文件)都將
寫入這個容器層
。
容器和鏡像之間的
主要區別
就是容器在
鏡像頂部的一個可寫層
,在容器中的所有操作都會存儲在這個
容器層
中,刪除容器後,容器層也會被刪除,但是
鏡像不會變化
。正因爲每個容器都有
自己的可寫容器
層,所有更改都存儲在
自己的容器層
中,所以多個容器之間可以
共享同一基礎鏡像
的訪問,但仍然具有
自己的數據狀態
。
Docker 使用
存儲驅動程序來管理鏡像層和可寫容器層的內容
,每個
存儲驅動程序
的處理方式不同,但是所有的驅動都使用
可堆疊的鏡像層和寫時複製(Cow)策
略,這些驅動程序管理的這些層其實就是 UnionFS(聯合文件系統),現在 Docker 主要支持的存儲驅動有
aufs、devicemapper、overlay、overlay2、zfs 和 vfs
等等,在新的 Docker 版本中,overlay2 取代了 aufs 成爲了推薦的存儲驅動。
copy-on-write
寫時複製(Cow)策
略:寫時複製是一種
共享和複製文件的策略
,可以最大程度地
提高效率
,如果
文件或目錄位於鏡像的較低層
中,而另一層(包括可寫層)需要
對其進行讀取
訪問,則它直接使用現有文件即可。另一層第一次需要修改文件時(在構建鏡像或運行容器時),將文件複製到該層並進行修改。這樣可以將 I/O 和每個後續層的大小最小化。
也就是需要時再從底層複製到容器層
。
Docker 架構
Docker 使用 C/S (客戶端/服務器)體系的架構,Docker 客戶端與 Docker 守護進程(Dockerd)通信,Docker 守護進程負責構建,運行和分發 Docker 容器。Docker 客戶端和守護進程可以在同一個系統上運行,也可以將 Docker 客戶端連接到遠程 Docker 守護進程。Docker 客戶端和守護進程使用 REST API 通過 UNIX 套接字或網絡接口進行通信。
2、docker安裝:
dccker-ce 社區版本 Centos 7 推薦使用overlay2存儲驅動。
卸載docker版本:
yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate
docker-engine
安裝docker有多種方式:
離線安裝:https://download.docker.com/linux/centos/7/x86_64/stable/Packages/ 下載rpm包直接安裝。
在線安裝:
[root
@localhost ~]# yum install -y yum-utils #安裝依賴包
[root
@localhost ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo #下載state的docker源
查看當前倉庫的可安裝版本:
yum list docker-ce --showduplicates | sort -r
安裝指定版本
: yum install
docker-ce-18.09.9
docker-ce-cli-18.09.9 containerd.io -y
安裝最新版本
:
yum install docker-ce docker-ce-cli containerd.io
安裝完成,修改docker的目錄:
”graph“: "data/docker" 用來指定docker自定義目錄。
"storage-dirver": "overlay2", 更改存儲驅動類型。
"registry-mirrors" 指定鏡像加速器。建議阿里雲賬號。
[root@localhost ~]# cat /etc/docker/daemon.json
{
"storage-dirver": "overlay2",
"registry-mirrors" : [
"https://ot2k4d59.mirror.aliyuncs.com/"
],
"graph": "/data/docker"
}
啓動docker:systemctl daemon-reload
[root@localhost ~]# systemctl daemon-reload #重新加載服務。
[root@localhost ~]# systemctl enable docker
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[root@localhost ~]# systemctl restart docker
測試
:
docker run hello-world
--------------------------------------------------------------------------------------------
docker基本操作
: https://hub.docker.com/
下載鏡像時不給出用戶名\密碼 默認是library官方鏡像。
鏡像是由多層存儲所構成,下載也是一層層去下載,下載過程中給出每一層ID的前12位,下載完成後給出鏡像的sha256,保證鏡像一致性。
刪除鏡像:docker rmi -f hello-world
給鏡像重新打tag:docker tag nginx nginx:test
鏡像導出成一個獨立文件: docker save nginx > /tmp/nginx.tar.gz
導入鏡像:有時候機器無法訪問鏡像,可以使用這種方式:
docker load > /tmp/nginx.tar.gz
運行容器
:docker run -it --rm ubuntu /bin/bash
-i 交互式 -t 終端
--rm 表示容器退出後馬上刪除。
/bin/bash 希望的交互式shell。
docker run時的過程:
docker run時檢查本地是否有鏡像,沒有則從公共倉庫下載。
分配一個文件系統,並在只讀鏡像層外面颳着一層可讀可寫。
從宿主機的網橋接口中橋接一個虛擬接口到容器中。
分配一個IP給容器。
刪除容器:docker rm -f imageid 或者name名稱都可以。
進入容器:
docker exec -it 8d836238d7e3 /bin/bash
啓動或者關閉容器 docker start/start/restart 容器id
啓動一個nginx容器:指定名稱
docker run --name webserver -itd nginx
獲取容器的IP地址:
docker inspect webserver |grep IPAddress
啓動容器並映射本機端口:
docker run --name webserver -d -p 8080:80 nginx 宿主機是8080,容器是80
查看容器的IP地址:
docker inspect webserver |grep IPAddress
docker網絡模式 bridge host container none模式
當 Docker 進程啓動時,會在主機上創建一個名爲
docker0
的虛擬網橋,此主機上啓動的 Docker 容器會連接到這個虛擬網橋上。虛擬網橋的工作方式和物理交換機類似,這樣主機上的所有容器就通過交換機連在了一個二層網絡中。從 docker0 子網中分配一個 IP 給容器使用,並設置 docker0 的 IP 地址爲容器的默認網關。在主機上創建一對虛擬網卡
veth pair
設備,Docker 將 veth pair 設備的一端放在新創建的容器中,並命名爲 eth0(容器的網卡),另一端放在主機中,以
vethxxx
這樣類似的名字命名,並將這個網絡設備加入到 docker0 網橋中。可以通過
brctl show
命令查看:
bridge模式:docker默認模式,使用docker run -p時,實際上是iptables做了NAT規則,實現端口轉發功能,通過iptables -t nat -nvL
可以通過ip link show命令查看到對應的 veth pair 對名稱。
比如我們運行一個測試busybox容器:
docker run -itd --net=bridge --name docker_bridge busybox top
進入到這個容器:docker exec -it docker_bridge /bin/sh
執行命令ifconfig -a
執行命令ip link show 會發現容器內和宿主機網卡是相對應的。20: eth0@if21 ------> 21: vethb9969aa@if20:
自定義網絡
:可以通過自定義創建docker網絡來連接多個容器:
最初是使用
--link
命令,把新容器掛在舊容器下面。ping只能單方面,舊的ping不同新容器,如下:
docker run -itd
--link docker_bri
--name docker_bri1 busybox top
[root@localhost ~]#
docker exec -it docker_bri /bin/sh
/ # ping docker_bri1 #但是ping ip是能通的。
ping: bad address 'docker_bri1'
通過自定義創建網絡方式互聯互通。
docker network create -d bridge my-net
查看創建的網絡: docker network ls
創建容器:docker run -itd --name busybox1 --network mynet bosybox sh
HOST模式
:和宿主機共享網卡。
host模式不會獲得獨立network namespace,而是和宿主機共用一個namespace。使用宿主機的ip和端口。不過其它文件系統和進程都是和宿主機隔離的。
使用方式:在運行時:
--net=host
指定即可。
container模式
:和已經存在一個容器共享網卡。
這個模式指定新創建的容器和已經存在的一個容器共享一個 Network Namespace,而不是和宿主機共享。新創建的容器不會創建自己的網卡,配置自己的 IP,而是和一個指定的容器共享 IP、端口範圍等。同樣,兩個容器除了網絡方面,其他的如文件系統、進程列表等還是隔離的。兩個容器的進程可以通過 lo 網卡設備通信。
在運行容器的時候指定
--net=container:目標容器名
即可。實際上我們後面要學習的 Kubernetes 裏面的
Pod 中容器之間
就是通過 Container 模式鏈接到
pause 容器
上面的,所以容器直接可以通過
localhost
來進行訪問。
NONE模式:自身有namespace的。但是這個容器不配置任何IP及路由信息,後續手動添加。
使用 none模式,Docker 容器擁有自己的 Network Namespace,但是並不爲Docker 容器進行任何網絡配置。也就是說這個 Docker 容器沒有網卡、IP、路由等信息。需要我們自己爲 Docker 容器添加網卡、配置 IP 等。 特殊需求使用。
數據共享與持久化
數據卷(Data Volemes) 不能直接掛載文件。 volume會引起docker目錄膨脹。
掛載主機目錄(Bind mounts) 可以掛載文件,
數據卷是一個可供一個或多個容器使用的特殊目錄。
數據卷在容器自己共享重用
數據卷修改不影響鏡像 數據卷只存在,即使容器被刪除
創建一個數據卷
:docker volume create my-vol
查看數據卷: docker volume ls
查看數據卷詳細信息:docker volume inspect my-vol
啓動一個掛載數據卷的容器:docker run -itd -p 8081:80 --name webweb -v my-vol:/usr/share/nginx/html nginx
-v 宿主機目錄:容器內目錄
數據卷被設計用來持久化數據的,是獨立的,docker不會在容器刪除後自動刪除數據卷。
如果需要刪除容器同時移除數據卷,在刪除容器時使用docker rm -v 這個命令。
如果要清理使用 docker volume prune 刪除全部 刪除單個docker vulume rm
掛載主機目錄
:使用-v或者mount參數來掛載 -v 宿主機目錄:容器目錄
docker run -itd --name webweb -v tmp:/usr/tmp/ nginx
docke exec -it webweb /bin/sh
註釋:容器內路徑權限爲讀寫,指定只讀用 ro -v /tmp:/usr/tmp:ro
路徑必須絕對路徑,目錄不存在會自動創建
掛載宿主機目錄後,容器內操作提示permission denied,通過兩種方式解決。
1、關閉selinux
2、以特權方式啓動容器:docker run -it --privileged=true -v /tmp:/usr/tmp nginx
volume數據卷與 bind mount主機目錄。
volume會引起docker目錄膨脹,既要存鏡像和volume,不要放在系統盤,最好配置到其它盤。
兩者不同:使用-v時,當容器外的目錄是空的的區別。
volume數據卷會將
容器內的目錄拷貝到容器外
目錄。 容器掛載到宿主機裏,
而mount會將
外部目錄覆蓋容器內
目錄。 宿主機掛載到容器裏,
使用時會在容器裏創建臨時目錄,然後用臨時目錄來和宿主機做映射關係。
volume不能掛載配置文件,而mount可以。
---------------------------------------------------------------------------------------------------------------
dockerfile
定製鏡像模板文件 每一行命令對應鏡像的一層
定製鏡像的每一層所添加的配置、文件信息,通過名爲Dockerfile, 修改、安裝 操作的命令每 一行就對應鏡像的一層一行就對應鏡像的一層。很直觀。
定製鏡像:定製nginx鏡像,並定製默認頁爲hello docker
cat Dockerfile
FROM nginx RUN echo 'Hello docker' > /usr/share/nginx/html/index.html
1、FROM 指令
表示以那個鏡像爲基礎進行定製, 必備命令
docker官方提供了很多鏡像,如redis mongo tomcat mysql httpd等及基礎鏡像ubuntu centos debian等。
docker還有一個
特殊鏡像scratc
h 空白鏡像/虛擬鏡像並不實際存在,不以任何鏡像爲基礎,都用自己所寫的內容,鏡像體積更加小巧。
2、RUN指令
用來執行命令 有如下兩種格式:
shell格式: RUN echo 'Hello, docker' > /usr/share/nginx/html/index.html
exec格式: RUN ["可執行文件","參數1","參數2"]
Dockerfile每一個指令都會建立一層,而每一個RUN也會建立一層,可以使用一個RUN,就沒必須要使用多個RUN。如下
UnionFS最大層數不能超過127層。 要不然非常臃腫 非常多的層鏡像
FROM debian:jessie
RUN buildDeps='gcc libc6-dev make wget' \
&& 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
如上就是編譯安裝redis文件,沒有必須建立多層,僅僅使用一個RUN命令,1層。
dockerfile支持shell類行尾添加 \ 的命令行換行方式及首#進行註釋。
最後添加了清理工作的命令,刪除尾編譯下載的軟件及緩存。
鏡像是多層存儲,每一層的東西不會在下一層被刪除的,一直跟隨鏡像。任何無關的東西要清理掉。每一層構建的最後要清理無關文件。
構建鏡像:
docker build -t testnginx .
3、
WORKDIR指令
WORKDIR指令設置dockerfile中的任何RUN CMD ENTRPOING、COPY和ADD指令的工作目錄。
如果WORKDIR指定的目錄不存在,會創建該目錄。
WORKDIR /path/to/workdir 中絕對路徑
使用WORKDIR來代替 RUN cd 的指令最好了。方便閱讀。
4、ADD & COPY指令 推薦使用COPY,ADD 適用於本地軟件拷貝解壓縮。
ADD 和 COPY都是將主機上的資源複製或假如到容器中,在構建鏡像時完成的。
它兩區別在於是否支持從遠程URL獲取資源。
COPY 只支持從主機上覆制資源到容器中。
ADD 不僅支持從主機上覆制,還支持從遠程URL複製資源到容器中。
COPY:COPY指令支持exec和shell兩種格式:
exec格式: cpoy ["src" "dest"] 適合路徑帶中括號的情況
shell格式:COPY src dest
ADD指令:支持從主機上覆制,也支持遠程URL複製到容器中,是copy增強版本,也支持兩種格式:
shell格式: ADD src dest
遠端獲取資源:ADD http://nginx.org/nginx.tar.gz /tmp/nginx 由於ADD不支持遠程資源認證,如果需要驗證則使用RUN wget 或者RUN curl。
支持自動解壓文件: ADD /tmp/nginx.tar.gz /tmp/ 把nginx.tar.gz 解壓到tmp目錄下。
[root@localhost testnginx]# cat Dockerfile
FROM nginx:1.7.9
COPY index.html /usr/share/nginx/html/index.html
docker build
命令構建鏡像,其實並非在本地構建,而是在服務端,也就是 Docker 引擎中構建的。
構建上下文
:在使用docker build 命令後面的點 . 而這個點 .不是表示dockerfile所在路徑,而是表示構建上下文。
之前我們瞭解docker build的原理是是基於CS架構,docker命令行和服務器端docker daemon交互,而構建也不例外,也是把文件傳送到服務端,然後有 dockercrd來負責構建。
鏡像構建時會用到COPY和ADD命令,也就是構建時這些文件也被傳送到docker deamon端去,那麼如何讓服務端
獲取這些文件,這時就用到構建上下文,用戶指定構建上下文路徑,docker build命令知道這個路徑後,會把路徑下
所有內容打包上傳到docker引擎,這樣docker引擎收到上下文就可以構建了。
COPY這類指令中的源文件路徑都是相對路徑,不能寫成../,已經超出上下文路徑的範圍。
一般會將
dockerfile放到一個空目錄下
。把
構建需要的文件複製到這個目錄
下。
如果有不需要的文件,可以寫一個.dockeringonre。
實際上Dockerfile的文件名並不要求是dockerfile,也不要求位於上下文目錄中,可以用-f 來指定dockerfie,如下:
docker build -t nginxtest:v1 -f /tmp/Dockerfile .
docker build -t nginxtest:v1 -f /tmp/Dockerfile /apps/
命令行登錄docker hub: docker login
註銷docker hub:docker logout
給鏡像打個tag:
docker tag testnginx:v1 fenye/nginx:test
推送鏡像:
docker push fenye/nginx:test
#fenye就是用戶名
私有倉庫
:希望局域網內使用,可以創建一個本地倉庫私人使用。
docker-registry
就是官方一個私有倉庫工具。官方也提供registry的鏡像直接運行即可。
默認情況下創建的倉庫路徑在容器內的
/var/lib/registry
目錄下,可通過-v參數來修改鏡像的存放路徑。
docker run -d -p 5000:5000 -v /data/registry:/var/lib/registry --name registry registry:2
創建好私有倉庫後,用tag打標記,並推送到倉庫。倉庫私有地址爲127.0.0.1:500
打標記:
docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:v1
推送鏡像: docker push 127.0.0.1:5000/ubuntu:v1
下載鏡像:docker pull 127.0.0.1:5000/ubuntu:v1
查看鏡像
: curl 127.0.0.1:5000/v2/_catalog
如果不想使用127.0.0.1:5000作爲倉庫地址,想讓局域網內其他人也能推送地址,需要修改成具體的IP。
因爲 Docker 默認不允許非 HTTPS 方式推送鏡像。我們可以通過 Docker 的配置選項來取消這個限制,我們這裏是 CentOS 7 系統,同樣還是編輯文件
/etc/docker/daemon.json
,添加如下內容:
[root@localhost testnginx]# cat /etc/docker/daemon.json
{
"registry-mirrors" : [
"https://sij47yha.mirror.aliyuncs.com"
],
"graph": "/data/docker"
"insecure-registries": [
"192.168.1.100:5000"
]
}
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
其中的insecure-registries就是我們添加的內容,然後
重啓 Docker
之後就可以在局域網內使用我們的私有鏡像倉庫了。
默認的私有鏡像倉庫可以滿足我們的很多需求,但是往往在企業中使用的話還有很安全性方面的功能的缺失,比如權限管理之類的,這對於企業來說是非常重要的,不過
registry 對於權限管理這一塊做了一個對外暴露
的接口,只要我們實現他暴露的安全接口就可以來
實現權限管理相關的接口
,其中註明的
開源鏡像管理軟件
Harbor
就是這類應用的佼佼者,我們還在後面的課程中和大家學習 Harbor 的一個使用。
Dockerfile指令
3、LABEI
標籤 來幫助鏡像記錄作者、版本等。
LABEL
vendor=
"ACME Incorporated"
RUN指令:
不要使用RUN apt-get upgrade,如果基礎鏡像某個包foo需要升級,應該使用 apt -get install foo
將 apt-get update 放在一條單獨的 RUN 聲明中會導致緩存問題以及後續的 apt-get install 失敗。
Docker 發現修改後的
RUN apt-get update 指令和之前的完全一樣
。所以,apt-get update 不會執行,而是使用之前的緩存鏡像。因爲
apt-get update 沒有運行
,後面的 apt-get install 可能安裝的是過時的 curl 和 nginx 版本。
使用
RUN apt-get update && apt-get install -y
可以確保你的 Dockerfiles 每次安裝的都是包的最新的版本,而且這個過程不需要進一步的編碼或額外干預。這項技術叫作緩存破壞。
永遠將RUN apt-get update 和apt-get install 組合一條RUN聲明,如下:
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
# 其他操作
&& rm -rf /var/lib/apt/lists/*
最後一條命令清理掉 apt 緩存 var/lib/apt/lists 可以減小鏡像大小。因爲 RUN 指令的開頭爲 apt-get udpate,包緩存總是會在 apt-get install 之前刷新。
4、EXPOSE指令:
EXPOSET 用於指定容器將要監聽的端口,如apache 是80端口, mongodb是27017端口。
對於外部訪問,用戶在docker run時使用一個-p參數來指定端口。
5、ENV指令:用來爲容器安裝的程序更新PATH環境變量。如下使用:
ENV PATH /usr/local/nginx/bin:PATH 來確保 CMD ["nginx"] 能正常運行。
ENV也用來設置版本號,然後RUN時引用即可。
ENV PG_VERSION 9.3.4
RUN
curl -SL http://example.com/postgres-
$PG_VERSION
.tar.xz | tar -xJC /usr/src/postgress
ENV
PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
6、VOLUME指令:用於暴露數據庫配置文件,使用VOLUME來管理鏡像中可變部分和用戶可以改變的部分。
7、USER指令:如果某個服務不需要特權執行,可使用USER指令切換到非root。
RUN groupadd -r postgres && useradd -r -g postgres postgres
8、WORKDIR指令: 使用絕對路徑。
9、COPY和ADD命令 功能類似,但優先使用COPY,
ADD支持本地tar提前及遠程URL下載文件。特殊要求。
如果將
COPY . /tmp/放置在 RUN 指令
之前,只要 . 目錄中任何一個文件變化,都會導致後續指令的緩存失效。
爲了讓鏡像儘量小,最好
不要使用 ADD 指令從遠程 URL 獲取包
,而是使用
curl 和 wget
。這樣你可以在文件提取完之後
刪掉不再需要的文件
來避免在
鏡像中額外添加一層
。比如儘量避免下面的用法:
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
而是 應該使用下面這種方法 :
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all
上面使用的管道操作,所以沒有中間文件需要刪除。對於其他 不需要 ADD 的自動提取功能的文件或目 錄,你應該使用 COPY。
儘管 ENTRYPOINT 和 CMD 都是在容器裏執行一條命令, 但是他們有一些微妙的區別,在絕大多數情況下, 你只要在這2者之間選擇一個調用就可以,但是我們還是非常有必要來認真瞭解下二者的區別。
CMD指令是容器啓動以後,默認的執行命令,需要重點理解下這個默認的含義,意思就是如果我們執行 docker run沒有指定任何的執行命令或者 Dockerfile 裏面也沒有指定 ENTRYPOINT,那麼就會使用 CMD 指定的執行命令執行了。這也說明了 ENTRYPOINT 纔是容器啓動以後真正要執行的命令。
所以我們經常遇到 cmd會被覆蓋的情況,爲什麼會被覆蓋呢?主要還是因爲 CMD 的定位就是默認,如果不額外指定,那麼纔會執行 CMD 命令,但是如果我們指定了的話那就不會執行 CMD 命令了,也就是說 CMD 會被覆蓋。
CMD 總共有三種用法:
CMD ["executable", "param1", "param2"] # exec 形式
CMD ["param1", "param2"] # 作爲 ENTRYPOINT 的默認參數
CMD command param1 param2 # shell 形式
其中 shell 形式,就是沒有中括號的形式,命令 command 默認是在/bin/sh -c下執行的,比如:
FROM busybox
CMD echo "hello cmd shell form!"
我們將上面的 Dockerfile 打包成 cmdshell 鏡像,然後直接啓動一個容器:
$ docker build -t cmdshell .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox
---> 020584afccce
Step 2/2 : CMD echo "hello cmd shell form!"
---> Running in 651afaddb83d
Removing intermediate container 651afaddb83d
---> d26e4d6d9cdf
Successfully built d26e4d6d9cdf
Successfully tagged cmdshell:latest
$ docker run cmdshell
hello cmd shell form!
對於帶有中括號的 exec 形式,命令沒有在任何 shell 終端環境下,如果我們要執行 shell,必須把 shell 加入到中括號的參數中。將上面的例子修改爲:
FROM busybox
CMD ["/bin/sh", "-c", "echo 'hello cmd exec form!'"]
同樣將上面的 Dockerfile 打包成 cmdexec 鏡像,然後直接啓動一個容器:
$ docker build -t cmdexec .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM busybox
---> 020584afccce
Step 2/2 : CMD ["/bin/sh", "-c", "echo 'hello cmd exec form!'"]
---> Running in 8c72d5e2ce35
Removing intermediate container 8c72d5e2ce35
---> c393bd1ab3c1
Successfully built c393bd1ab3c1
Successfully tagged cmdexec:latest
$ docker run cmdexec
hello cmd exec form!
需要注意,採用 exec 形式,第一個參數必須是命令的全路徑纔行。一個 Dockerfile 如果有多個 CMD,只有最後一個生效,官網推薦採用這種方式。
當然,以上都是體現了 CMD 的
默認
行爲。如果我們在 run 時指定了命令或者有 ENTRYPOINT CMD 就會被覆蓋。比如同樣用上面兩個鏡像,在運行的時候指定一個命令:
$ docker run cmdexec echo 'hello docker'
hello docker
$ docker run cmdshell echo 'hello docker'
hello docker
可以看到,最終容器裏面執行的是 run 命令後面的命令,而不是 CMD 裏面定義的。
根據官方定義來說 ENTRYPOINT纔是用於定義容器啓動以後的執行程序的,允許將鏡像當成命令本身來運行(用 CMD 提供默認選項),從名字也可以理解,是容器的
入口
。ENTRYPOINT 一共有兩種用法:
ENTRYPOINT ["executable", "param1", "param2"] (exec 形式)
ENTRYPOINT command param1 param2 (shell 形式)
對應命令行 exec 模式,也就是帶中括號的。和 CMD 的中括號形式是一致的,但是這裏貌似是在shell的環境下執行的,與cmd有區別。如果 run 命令後面有執行命令,那麼後面的全部都會作爲 ENTRYPOINT 的參數。如果 run 後面沒有額外的命令,但是定義了 CMD,那麼 CMD 的全部內容就會作爲 ENTRYPOINT 的參數,這同時是上面我們提到的 CMD 的第二種用法。所以說 ENTRYPOINT 不會被覆蓋。當然如果要在 run 裏面覆蓋,也是有辦法的,使用--entrypoint參數即可。
比如我們定義如下的 Dockerfile:
FROM busybox
CMD ["I am in cmd exec form"] ENTRYPOINT ["echo"]
將上面的 Dockerfile 打包成鏡像 entrypointest,然後直接運行,不帶任何參數:
$ docker build -t entrypointest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM busybox
---> 020584afccce
Step 2/3 : CMD ["I am in cmd exec form"]
---> Running in 2d7b13b0dfe7
Removing intermediate container 2d7b13b0dfe7
---> 903d739ead9a
Step 3/3 : ENTRYPOINT ["echo"]
---> Running in c61682ea476e
Removing intermediate container c61682ea476e
---> 00b09a578d48
Successfully built 00b09a578d48
Successfully tagged entrypointest:latest
$ docker run entrypointest
I am in cmd exec form
我們可以看到打印的結果是 CMD 裏面指定的內容,也就是默認情況將 CMD 部分作爲 ENTRYPOINT 的參數了。但是如果我們在運行容器的時候如果指定了運行參數呢:
$ docker run entrypointest
I am in run section I am in run section
我們可以看到運行時指定的參數會覆蓋掉 CMD 提供的默認參數,但是默認都是執行的 ENTRYPOINT 裏面的命令。
對於 shell 模式的,任何 run 和 CMD 的參數都無法被傳入到 ENTRYPOINT 裏。官網推薦用上面一種用法。比如我們我們這裏定義一個 Dockerfile 如下:
FROM busybox
CMD ["I am in cmd exec form and entrypoint shell form"]
ENTRYPOINT echo
將上面 Dockerfile 打包成鏡像 entrypointshell,然後直接運行:
$ docker build -t entrypointshell .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM busybox
---> 020584afccce
Step 2/3 : CMD ["I am in cmd exec form and entrypoint shell form"]
---> Running in 2aee7326f4cd
Removing intermediate container 2aee7326f4cd
---> e89cadeeecd3
Step 3/3 : ENTRYPOINT echo
---> Running in f359c8bb5025
Removing intermediate container f359c8bb5025
---> f9d4e1d1b0a0
Successfully built f9d4e1d1b0a0
Successfully tagged entrypointshell:latest
$ docker run entrypointshell
我們可以發現 CMD 的參數並沒有被打印出來,如果在運行的時候添加上參數呢:
$ docker run entrypointshell I am in run section
$
我們可以發現也沒有將 run 命令後面的參數打印出來。所以一般情況下對於 ENTRYPOINT 來說使用中括號的 exec 形式更好。
總結
一般會用 ENTRYPOINT 的中括號形式作爲 Docker 容器啓動以後的默認執行命令,裏面放的是不變的部分,可變部分比如命令參數可以使用 CMD 的形式提供默認版本,也就是 run 裏面沒有任何參數時使用的默認參數。如果我們想用默認參數,就直接 run,否則想用其他參數,就 run 裏面加上參數。參數即可。
比如我們定義如下的 Dockerfile:
FROM busybox
CMD ["I am in cmd exec form"]
ENTRYPOINT ["echo"]
將上面的 Dockerfile 打包成鏡像 entrypointest,然後直接運行,不帶任何參數:
$ docker build -t entrypointest .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM busybox
---> 020584afccce
Step 2/3 : CMD ["I am in cmd exec form"]
---> Running in 2d7b13b0dfe7
Removing intermediate container 2d7b13b0dfe7
---> 903d739ead9a
Step 3/3 : ENTRYPOINT ["echo"]
---> Running in c61682ea476e
Removing intermediate container c61682ea476e
---> 00b09a578d48
Successfully built 00b09a578d48
Successfully tagged entrypointest:latest
$ docker run entrypointest
I am in cmd exec form
我們可以看到打印的結果是 CMD 裏面指定的內容,也就是默認情況將 CMD 部分作爲 ENTRYPOINT 的參數了。但是如果我們在運行容器的時候如果指定了運行參數呢:
$ docker run entrypointest
I am in run section I am in run section
我們可以看到運行時指定的參數會覆蓋掉 CMD 提供的默認參數,但是默認都是執行的 ENTRYPOINT 裏面的命令。
對於 shell 模式的,任何 run 和 CMD 的參數都無法被傳入到 ENTRYPOINT 裏。官網推薦用上面一種用法。比如我們我們這裏定義一個 Dockerfile 如下:
FROM busybox
CMD ["I am in cmd exec form and entrypoint shell form"]
ENTRYPOINT echo
將上面 Dockerfile 打包成鏡像 entrypointshell,然後直接運行:
$ docker build -t entrypointshell .
Sending build context to Docker daemon 2.048kB
Step 1/3 : FROM busybox
---> 020584afccce
Step 2/3 : CMD ["I am in cmd exec form and entrypoint shell form"]
---> Running in 2aee7326f4cd
Removing intermediate container 2aee7326f4cd
---> e89cadeeecd3
Step 3/3 : ENTRYPOINT echo
---> Running in f359c8bb5025
Removing intermediate container f359c8bb5025
---> f9d4e1d1b0a0
Successfully built f9d4e1d1b0a0
Successfully tagged entrypointshell:latest
$ docker run entrypointshell
我們可以發現 CMD 的參數並沒有被打印出來,如果在運行的時候添加上參數呢:
$ docker run entrypointshell I am in run section
$
我們可以發現也沒有將 run 命令後面的參數打印出來。所以一般情況下對於 ENTRYPOINT 來說使用中括號的 exec 形式更好。
總結
一般會用 ENTRYPOINT 的中括號形式作爲 Docker 容器啓動以後的默認執行命令,裏面放的是不變的部分,可變部分比如命令參數可以使用 CMD 的形式提供默認版本,也就是 run 裏面沒有任何參數時使用的默認參數。如果我們想用默認參數,就直接 run,否則想用其他參數,就 run 裏面加上參數。