Docker In-Depth
David Hsu
Docker History
PaaS to Docker
TimeLine
2015 OCI 出現,Docker 捐出 libcontainer,改名 RunC
OCI 的出現是⼀一些⼤大廠想要避免 Container 技術如同 Android ⼀一樣碎片化
(表⾯面上是這樣講,實際上是要限制 Docker 獨⼤大)。
CNCF 出現
CNCF 背後可以理理解為 Google 要以 Kubernetes 打 Docker 的基⾦金金會,所以該基⾦金金會的專案很多
都會跟 Kubernetes 密切整合,或者是建置在 Kubernetes 之上。比如說 CNI、CoreDNS 與 gRPC 等。
CNCF 確保兩兩件事
k8s 必須能在容器編排取得競爭優勢。
CNCF 社群 將以 k8s 為 CNCF 的核⼼心專案,擴展⾄至更更多使⽤用場景。
Docker vs k8s == 獨裁 vs ⺠民主化
k8s 開放很多 plugin api 讓使⽤用者得以介入 k8s
2017 Docker 投降
k8s and CNCF全⾯面獲勝
runC
⽽而 Docker 在 2015 年年的⾼高速迭代與版本更更新更更令許多使⽤用者苦不堪⾔言,
因此,幾個容器界的⼤大佬就出來來成立中立的基⾦金金會,以切割與 docker 公司的權威。
這個項⽬目就是著名的 RunC,是由 docker 公司領頭將項⽬目 Libcontainer 提供給⼀一個中立的基
⾦金金會,然後⼤大家以 RunC 為基礎,建立⼀一套屬於容器和鏡像的標準和規範。
⽽而這個規範標準就是 Open Containers Initiative(OCI),
規範標準的提出使得容器鏡像與容器 runtime 得以與 Docker 項⽬目分離,
也造就了了現在可以不依賴 Docker 項⽬目也能做出容器平台的成果。
Docker != VM ?
docker is only a process. 依賴rootfs去
提供docker所需的⽬目錄結構,才會讓⼈人
以為他是VM的錯覺 但本質上container
依賴host上的kernel
Docker 为什什么资源的隔离和限制在云时代更更加重要?在默认情
况下,⼀一个操作系统⾥里里所有运⾏行行的进程共享CPU和内存
资源,如果程序设计不当,最极端的情况,某进程出现
死循环可能会耗尽CPU资源,或者由于内存泄漏消耗掉
⼤大部分系统资源,这在企业级产品场景下是不可接受
的,所以进程的资源隔离技术是非常必要的。
Linux OS 本⾝身就⽀支援虛擬化技術,稱為 Linux Container,
也就是常聽到的 LXC 技術。
LXC 有三個特⾊色技術
namespace, cGroup, unionFS
也就是構成現在 Docker 的重要底層技術
Namespace
Example:
Container 裡主要 process PID=1,
但回到 Host 上,會有另外的 PID。
————
Container 裡所使⽤用的是 root 權限,
但對於 Host 來來說,
只不過是⼀一個普通user。
如果CGroup设计出来的⽬目的是为了了隔
离上⾯面描述的物理理资源,那么
namespace则⽤用来隔离PID(进程
ID),IPC,Network等系统资源。
Linux Namespace
Namespace 也有不能隔離的資源: 時間
cGroup
cGroups 全名 control group,⽤用來來限定⼀一個 process 的物理理資源使⽤用,
並由 Linux kernel 原⽣生⽀支援,
可以限制與隔離 Linux process groups 所使⽤用的 “物理理” 資源。
Ex. CPU / MEM / Disk IO / Networking IO
Linux Namespace 帮助进程隔离出⾃自⼰己的单独空
间,⽽而 Cgroups 则可以限制每个空间的⼤大⼩小。
Cgroups 提供了了对⼀一组进程及将来⼦子进程的资源
限制、控制和统计的能⼒力力。
unionFS
Linux ⽂文件結構由
bootfs and rootfs 構成。
bootfs 主要負責引導 kernel 啟動系
統後就會 unmount.
⽽而 rootfs (root file system) 包含典
型⽬目錄結構
 /dev, /proc, /bin, /etc, /lib, /usr,
and /tmp
Union File System 就是將不同⽬目錄 mount 在⼀一個資料夾的⼀一個技術。
unionFS
Docker 將 rootfs 做為 read-only layer 放
在最底層 layer,
並在上⾯面掛載⼀一層 writable layer 提供
Container 內寫入。
所以結合在⼀一起的結果,會讓⼈人誤以為可
以直接寫入 rootfs.
但其實兩兩者是分開的,也就是爲什什麼重啟
Container 後,所做的更更變會遺失,是因
為 writable layer 遺失了了。
Review
namespace -> 提供與 Host 隔離的環境
cGroup -> 提供 container 內 CPU / MEM / IO 使⽤用限制,以免 overload 影響 Host
unionFS -> 提供資料夾的分離,讓 container 內也能有 rootfs
Docker FAQ
EXPOSE / docker run -p
Docker中有兩兩個概念念,EXPOSE and PUBLISH。
EXPOSE 是 Image / Container 聲明要暴暴露並可以提供其他容器使⽤用的 port。
但這個聲明,只要沒有設定 —icc=false,實際上沒有意義,因為並不強制。
也就是說沒有聲明 EXPOSE 的 port 其他容器也能訪問。
反之,使⽤用 —icc=false 的話,就只有 EXPOSE 的 port 可以被其他 Container 訪問。
PUBLISH 會直接映射 Host 的 Port ,將 Container 的 Port 對外公開,也就是說其他主機也可
以透過訪問 Host IP 來來訪問 Container。
docker-compose / docker run 裡⾯面的 ports / -p 實際上是指 PUBLISH
EXPOSE 的端⼝口可以不 PUBLISH,这样只有容器间可以访问,
宿主之外⽆无法访问。⽽而 PUBLISH 的端⼝口,可以不事
先 EXPOSE,换句句话说 PUBLISH 等于同时隐式定义了了该端⼝口
要 EXPOSE。

補充:docker run -p 可以指定範圍

ex. -p 1000-2000:1000-2000

開 terminal demo expose and publish
Container 互連?
不使⽤用 —link ?
因為使⽤用 link 的⽅方式下是通過修改 /etc/hosts 來來達成的,
所有網路路沒有適當隔離,安全度不佳。也容易易在頻繁重啟 container 的狀狀況下造成對檔案有
race condiction 的問題,以及 container 內 cache /etc/hosts 的問題。
所以⽬目前普遍使⽤用 docker network 為最好的⽅方式
修改 /etc/hosts ⽂文件有很多弊病。比如,⾼高频繁的容器启停环境时,容易易产⽣生竞争冒
险,导致 /etc/hosts⽂文件损坏,出现访问故障;或者有些应⽤用发现是来⾃自于 /etc/
hosts ⽂文件后,就假定其为静态⽂文件,⽽而缓存结果不再查询,从⽽而导致容器启停 IP 变更更
后,使⽤用旧的条⽬目⽽而⽆无法连接到正确的容器等等
docker-compose network
Docker 裡使⽤用 top / free 看到的
是主機還是容器的資源使⽤用?
A : 主機上
因為 top / free / df…etc 會去抓取 /etc 內的檔案來來顯⽰示資源使⽤用情況,
⽽而 container 內的 /etc 其實並不是真的獨立的 /etc
Volume / docker run -v
docker run -v 是掛載⽂文件 / 檔案 ,建議寫為絕對路路徑,雖然在 docker-compose 裡會⾃自動補
全相對路路徑為絕對路路徑,但使⽤用 docker run 指令時不會。
⽽而 docker volume 是數據卷
數據卷⼀一般設計是為了了裡⾯面內容還能夠被再次使⽤用,
不過使⽤用不當將會造成 Host 上存有⼤大量量 volumes ⽽而造成 full disk。
數據卷 ( Data Volumes )
分為 匿名卷 以及 命名卷。
在 Dockerfile 裡宣告 Volume /data
匿名卷: docker run -itd nginx:latest
=> /data 會被掛在為匿名卷,當 container 死亡後數據會消失
命名卷: docker run -v mydata:/data -itd nginx:latest
=> /data 被掛載為 mydata 命名卷,container 死亡後數據會被保留留在本地
(⼀一般來來說會是 /var/lib/docker/volumes)
所以 docker run -v / Volumes 到
底哪裡不⼀一樣?
掛載⽬目錄是由⽤用⼾戶⾃自⾏行行維護權限 ( Permission ) 像是 uid / gid / SELinux 等問
題,容易易遇到 container 內與 Host 上 Permission Denied 問題 。
⽽而 Docker Volumes 由 docker engine 維護以上問題,docker 會在建立 volume
時加入 rules 以調整權限問題。
在外⾯面Host修改了了容器內的檔案,
在容器內卻遺失了了? 使⽤用dw常⾒見見問題

可以demo ( ls -i 秀出file index number
)
因為當你使⽤用掛載⽬目錄⽅方式的時候 (docker run -v ) 如果只掛載單⼀一檔案,使⽤用 vim 或其他 IDE
時會出現問題。
容器內跑 cronjob
根據 Docker Best Practice , ⼀一個 container 只應該做⼀一件事,
⽽而 Cronjob 算是另⼀一個服務了了,應該另起 container 來來做 cronjob 才是正確做法。
Database 不適合放在 Docker 上
運⾏行行嗎?
使⽤用 Docker Volume 的狀狀態下,是可以的。
但如果沒有使⽤用 Docker Volume ,資料將會遺失,
所以建議先了了解 Docker Volume 再來來考慮是否在 Docker 上運⾏行行 Database
Docker 禁術
docker commit
docker save
docker load
docker export
docker import
docker commit / import / export 俗稱⿊黑箱 Docker 。
除了了本⼈人之外沒有⼈人知道裡⾯面發⽣生過什什麼,
可能創造者本⼈人過⼀一段時間也忘了了 (?)
並不是正常使⽤用 docker 會⽤用的⽅方式。
這樣的⽅方式實際上是將 container 保存為 tar 檔,然後再變成 Image。
除了了正常操作會有⼀一層⼀一層的 layer 會被壓縮為⼀一層以外,原有層裡⾯面的配置也可能遺失。
如果遺失的是 CMD 就會造成無法正常啟動。
所以不建議使⽤用 commit / import / export.
如何寫好 Dockerfile
Dockerfile 的每⼀一⾏行行指令都是⼀一層 layer , layer 越多當然 Image 也會越⼤大。
應該要盡量量減少 layer 的產⽣生。
把 Dockerfile 當 Shell Script 來來寫
Node.js official Dockerfile
利利⽤用 Docker 分層概念念,
使建構過程更更為順利利與快速
=> docker pull 的時候是分層下載的
已經存在的層,就不會再次 pull
如果只有改 code 沒有更更改 package.json
就不⽤用重跑 npm install,節省了了很多時間。
縮⼩小 docker image : multiple stage
ENTRYPOINT / CMD
docker run -it nginx:latest [ ENTRYPOINT ] [ CMD ]
ENTRYPOINT 不指定的話預設是 sh -c
Docker logs
docker logs 有很多種 logging driver
Ex. CloudwatchLogs 使⽤用的是 awslogs driver
所以你在 ECS 上使⽤用 cloudwatchlogs 的 container 使⽤用 docker logs
=> 是絕對看不到 log 的
Docker ⼀一跑就停⽌止
要觀察停⽌止的 Exit Code
通常為以下情況:
Exited (127) : 很有可能是 Out of Memory
Exited (0) : Docker 主進程退出了了 ( PID=1 的 Process 做完事情或失敗了了 )
其他情況請看 log
Windows Docker
Windows Docker 其實是在 windows 上跑 hyper-v 再運⾏行行 Linux kernel
( Docker for Windows )
Docker on Windows 是 windows 原⽣生⽀支援,但不能與 Linux 平台互通。

Docker In-Depth

  • 1.
  • 2.
  • 3.
    TimeLine 2015 OCI 出現,Docker捐出 libcontainer,改名 RunC OCI 的出現是⼀一些⼤大廠想要避免 Container 技術如同 Android ⼀一樣碎片化 (表⾯面上是這樣講,實際上是要限制 Docker 獨⼤大)。 CNCF 出現 CNCF 背後可以理理解為 Google 要以 Kubernetes 打 Docker 的基⾦金金會,所以該基⾦金金會的專案很多 都會跟 Kubernetes 密切整合,或者是建置在 Kubernetes 之上。比如說 CNI、CoreDNS 與 gRPC 等。 CNCF 確保兩兩件事 k8s 必須能在容器編排取得競爭優勢。 CNCF 社群 將以 k8s 為 CNCF 的核⼼心專案,擴展⾄至更更多使⽤用場景。 Docker vs k8s == 獨裁 vs ⺠民主化 k8s 開放很多 plugin api 讓使⽤用者得以介入 k8s 2017 Docker 投降 k8s and CNCF全⾯面獲勝
  • 4.
    runC ⽽而 Docker 在2015 年年的⾼高速迭代與版本更更新更更令許多使⽤用者苦不堪⾔言, 因此,幾個容器界的⼤大佬就出來來成立中立的基⾦金金會,以切割與 docker 公司的權威。 這個項⽬目就是著名的 RunC,是由 docker 公司領頭將項⽬目 Libcontainer 提供給⼀一個中立的基 ⾦金金會,然後⼤大家以 RunC 為基礎,建立⼀一套屬於容器和鏡像的標準和規範。 ⽽而這個規範標準就是 Open Containers Initiative(OCI), 規範標準的提出使得容器鏡像與容器 runtime 得以與 Docker 項⽬目分離, 也造就了了現在可以不依賴 Docker 項⽬目也能做出容器平台的成果。
  • 5.
    Docker != VM? docker is only a process. 依賴rootfs去 提供docker所需的⽬目錄結構,才會讓⼈人 以為他是VM的錯覺 但本質上container 依賴host上的kernel
  • 6.
  • 7.
    Namespace Example: Container 裡主要 processPID=1, 但回到 Host 上,會有另外的 PID。 ———— Container 裡所使⽤用的是 root 權限, 但對於 Host 來來說, 只不過是⼀一個普通user。 如果CGroup设计出来的⽬目的是为了了隔 离上⾯面描述的物理理资源,那么 namespace则⽤用来隔离PID(进程 ID),IPC,Network等系统资源。
  • 8.
  • 9.
    cGroup cGroups 全名 controlgroup,⽤用來來限定⼀一個 process 的物理理資源使⽤用, 並由 Linux kernel 原⽣生⽀支援, 可以限制與隔離 Linux process groups 所使⽤用的 “物理理” 資源。 Ex. CPU / MEM / Disk IO / Networking IO Linux Namespace 帮助进程隔离出⾃自⼰己的单独空 间,⽽而 Cgroups 则可以限制每个空间的⼤大⼩小。 Cgroups 提供了了对⼀一组进程及将来⼦子进程的资源 限制、控制和统计的能⼒力力。
  • 10.
    unionFS Linux ⽂文件結構由 bootfs androotfs 構成。 bootfs 主要負責引導 kernel 啟動系 統後就會 unmount. ⽽而 rootfs (root file system) 包含典 型⽬目錄結構  /dev, /proc, /bin, /etc, /lib, /usr, and /tmp Union File System 就是將不同⽬目錄 mount 在⼀一個資料夾的⼀一個技術。
  • 11.
    unionFS Docker 將 rootfs做為 read-only layer 放 在最底層 layer, 並在上⾯面掛載⼀一層 writable layer 提供 Container 內寫入。 所以結合在⼀一起的結果,會讓⼈人誤以為可 以直接寫入 rootfs. 但其實兩兩者是分開的,也就是爲什什麼重啟 Container 後,所做的更更變會遺失,是因 為 writable layer 遺失了了。
  • 12.
    Review namespace -> 提供與Host 隔離的環境 cGroup -> 提供 container 內 CPU / MEM / IO 使⽤用限制,以免 overload 影響 Host unionFS -> 提供資料夾的分離,讓 container 內也能有 rootfs
  • 13.
  • 14.
    EXPOSE / dockerrun -p Docker中有兩兩個概念念,EXPOSE and PUBLISH。 EXPOSE 是 Image / Container 聲明要暴暴露並可以提供其他容器使⽤用的 port。 但這個聲明,只要沒有設定 —icc=false,實際上沒有意義,因為並不強制。 也就是說沒有聲明 EXPOSE 的 port 其他容器也能訪問。 反之,使⽤用 —icc=false 的話,就只有 EXPOSE 的 port 可以被其他 Container 訪問。 PUBLISH 會直接映射 Host 的 Port ,將 Container 的 Port 對外公開,也就是說其他主機也可 以透過訪問 Host IP 來來訪問 Container。 docker-compose / docker run 裡⾯面的 ports / -p 實際上是指 PUBLISH EXPOSE 的端⼝口可以不 PUBLISH,这样只有容器间可以访问, 宿主之外⽆无法访问。⽽而 PUBLISH 的端⼝口,可以不事 先 EXPOSE,换句句话说 PUBLISH 等于同时隐式定义了了该端⼝口 要 EXPOSE。 補充:docker run -p 可以指定範圍 ex. -p 1000-2000:1000-2000 開 terminal demo expose and publish
  • 15.
    Container 互連? 不使⽤用 —link? 因為使⽤用 link 的⽅方式下是通過修改 /etc/hosts 來來達成的, 所有網路路沒有適當隔離,安全度不佳。也容易易在頻繁重啟 container 的狀狀況下造成對檔案有 race condiction 的問題,以及 container 內 cache /etc/hosts 的問題。 所以⽬目前普遍使⽤用 docker network 為最好的⽅方式 修改 /etc/hosts ⽂文件有很多弊病。比如,⾼高频繁的容器启停环境时,容易易产⽣生竞争冒 险,导致 /etc/hosts⽂文件损坏,出现访问故障;或者有些应⽤用发现是来⾃自于 /etc/ hosts ⽂文件后,就假定其为静态⽂文件,⽽而缓存结果不再查询,从⽽而导致容器启停 IP 变更更 后,使⽤用旧的条⽬目⽽而⽆无法连接到正确的容器等等
  • 16.
  • 17.
    Docker 裡使⽤用 top/ free 看到的 是主機還是容器的資源使⽤用? A : 主機上 因為 top / free / df…etc 會去抓取 /etc 內的檔案來來顯⽰示資源使⽤用情況, ⽽而 container 內的 /etc 其實並不是真的獨立的 /etc
  • 18.
    Volume / dockerrun -v docker run -v 是掛載⽂文件 / 檔案 ,建議寫為絕對路路徑,雖然在 docker-compose 裡會⾃自動補 全相對路路徑為絕對路路徑,但使⽤用 docker run 指令時不會。 ⽽而 docker volume 是數據卷 數據卷⼀一般設計是為了了裡⾯面內容還能夠被再次使⽤用, 不過使⽤用不當將會造成 Host 上存有⼤大量量 volumes ⽽而造成 full disk。
  • 19.
    數據卷 ( DataVolumes ) 分為 匿名卷 以及 命名卷。 在 Dockerfile 裡宣告 Volume /data 匿名卷: docker run -itd nginx:latest => /data 會被掛在為匿名卷,當 container 死亡後數據會消失 命名卷: docker run -v mydata:/data -itd nginx:latest => /data 被掛載為 mydata 命名卷,container 死亡後數據會被保留留在本地 (⼀一般來來說會是 /var/lib/docker/volumes)
  • 20.
    所以 docker run-v / Volumes 到 底哪裡不⼀一樣? 掛載⽬目錄是由⽤用⼾戶⾃自⾏行行維護權限 ( Permission ) 像是 uid / gid / SELinux 等問 題,容易易遇到 container 內與 Host 上 Permission Denied 問題 。 ⽽而 Docker Volumes 由 docker engine 維護以上問題,docker 會在建立 volume 時加入 rules 以調整權限問題。
  • 21.
    在外⾯面Host修改了了容器內的檔案, 在容器內卻遺失了了? 使⽤用dw常⾒見見問題 可以demo (ls -i 秀出file index number ) 因為當你使⽤用掛載⽬目錄⽅方式的時候 (docker run -v ) 如果只掛載單⼀一檔案,使⽤用 vim 或其他 IDE 時會出現問題。
  • 22.
    容器內跑 cronjob 根據 DockerBest Practice , ⼀一個 container 只應該做⼀一件事, ⽽而 Cronjob 算是另⼀一個服務了了,應該另起 container 來來做 cronjob 才是正確做法。
  • 23.
    Database 不適合放在 Docker上 運⾏行行嗎? 使⽤用 Docker Volume 的狀狀態下,是可以的。 但如果沒有使⽤用 Docker Volume ,資料將會遺失, 所以建議先了了解 Docker Volume 再來來考慮是否在 Docker 上運⾏行行 Database
  • 24.
    Docker 禁術 docker commit dockersave docker load docker export docker import docker commit / import / export 俗稱⿊黑箱 Docker 。 除了了本⼈人之外沒有⼈人知道裡⾯面發⽣生過什什麼, 可能創造者本⼈人過⼀一段時間也忘了了 (?) 並不是正常使⽤用 docker 會⽤用的⽅方式。 這樣的⽅方式實際上是將 container 保存為 tar 檔,然後再變成 Image。 除了了正常操作會有⼀一層⼀一層的 layer 會被壓縮為⼀一層以外,原有層裡⾯面的配置也可能遺失。 如果遺失的是 CMD 就會造成無法正常啟動。 所以不建議使⽤用 commit / import / export.
  • 25.
    如何寫好 Dockerfile Dockerfile 的每⼀一⾏行行指令都是⼀一層layer , layer 越多當然 Image 也會越⼤大。 應該要盡量量減少 layer 的產⽣生。 把 Dockerfile 當 Shell Script 來來寫 Node.js official Dockerfile 利利⽤用 Docker 分層概念念, 使建構過程更更為順利利與快速 => docker pull 的時候是分層下載的 已經存在的層,就不會再次 pull 如果只有改 code 沒有更更改 package.json 就不⽤用重跑 npm install,節省了了很多時間。 縮⼩小 docker image : multiple stage
  • 26.
    ENTRYPOINT / CMD dockerrun -it nginx:latest [ ENTRYPOINT ] [ CMD ] ENTRYPOINT 不指定的話預設是 sh -c
  • 27.
    Docker logs docker logs有很多種 logging driver Ex. CloudwatchLogs 使⽤用的是 awslogs driver 所以你在 ECS 上使⽤用 cloudwatchlogs 的 container 使⽤用 docker logs => 是絕對看不到 log 的
  • 28.
    Docker ⼀一跑就停⽌止 要觀察停⽌止的 ExitCode 通常為以下情況: Exited (127) : 很有可能是 Out of Memory Exited (0) : Docker 主進程退出了了 ( PID=1 的 Process 做完事情或失敗了了 ) 其他情況請看 log
  • 29.
    Windows Docker Windows Docker其實是在 windows 上跑 hyper-v 再運⾏行行 Linux kernel ( Docker for Windows ) Docker on Windows 是 windows 原⽣生⽀支援,但不能與 Linux 平台互通。