1、容器運行時之docker

容器運行時介紹: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 上,可以設置公有及私有。   https://cloud.docker.com  
命令行登錄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。

CMD 與 ENTRYPOINT 指令
儘管 ENTRYPOINT 和 CMD 都是在容器裏執行一條命令, 但是他們有一些微妙的區別,在絕大多數情況下, 你只要在這2者之間選擇一個調用就可以,但是我們還是非常有必要來認真瞭解下二者的區別。
CMD 指令
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 指令
根據官方定義來說 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 裏面加上參數。
這些官方倉庫的 Dockerfile 都是參考典範: https://github.com/docker-library/docs
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章