云计算与云原生 — Docker 容器技术完全解析
目錄
文章目錄
- 目錄
- Docker Overview
- Docker 的組件
- Docker 的軟件架構
- Docker Client
- Docker Daemon
- Docker Registry
- Graph
- Driver
- Graphdriver
- Networkdriver
- Execdriver
- Libcontainer
- Docker Container
- Docker 的安裝(CentOS)
- Docker 的 Image 與 Dockerfile
- Image
- Dockerfile
- 構建指令
- FROM(指定 base image)
- MAINTAINER(指定鏡像創建者信息)
- RUN(指定構建鏡像時執行的指令)
- 設置指令
- ENV(設置環境變量)
- WORKDIR(設置目錄的切換)
- CMD(設置容器啟動時執行的操作)
- ENTRYPOINT(設置容器啟動時執行的操作)
- USER(設置啟動容器的用戶)
- EXPOSE(設置容器需要映射到宿主機的端口)
- ADD 和 COPY
- VOLUME(設置卷的掛載點)
- Image build
- Build Context
- Build 鏡像層
- Docker 的基本使用
- 鏡像操作
- 容器操作
- Docker 的容器網絡技術原理
- CNM 標準規范
- CNM 驅動接口
- 網絡驅動
- IPAM 驅動
- Docker 原生網絡驅動
- Bridge 模式(默認)
- 用戶自定義 Bridge 網絡
- Host 模式
- 外部訪問容器(容器的端口映射)
- MACVLAN 模式
- 使用 MACVLAN 進行 VLAN 中繼
- 使用 MACVLAN 實現跨主機容器之前的通信
- Container 模式
- None 模式(隔離)
- Docker 遠程網絡驅動
- Docker 遠程 IPAM 驅動
- Docker 的容器存儲技術原理
- 數據卷管理
- 掛載時創建卷
- 創建卷后掛載
- 數據容器管理
- Docker 的 Register
- Private Registry
- 使用 docker-distribution 搭建 Private Registry
- 軟件包安裝
- Docker Container 安裝(推薦)
- 注冊表操作
Docker Overview
2010 年,在美國舊金山成立了一家名叫 dotCloud 的公司。這家公司主要提供基于 PaaS 的云計算技術服務。具體來說,是和 LXC 有關的容器技術。
LXC(Linux Container)是一種輕量級的虛擬化技術,可以隔離進程和資源,提供了在單一可控主機節點上支持多個相互隔離的 Server Container 同時執行的機制。Container 有效地將由單個操作系統管理的資源劃分到孤立的組中,以更好地在孤立的組之間平衡有沖突的資源使用需求。Container 將應用程序與操作系統解耦,這意味著用戶擁有了一個 Container 就近似的擁有了一個輕量且具有一定隔離性的操作系統運行時(Runtime)。
LXC 類似于 Chroot,提供了一個擁有自己進程和網絡空間的虛擬環境,但又有別于虛擬機,因為 LXC 是一種操作系統層次上的資源的虛擬化,本質是一種操作系統虛擬化技術,基于 Linux Kernel 的 cgroups 和 namespace 實現。
- namespace 用于完成資源的隔離。
- cgroups 用于完成進程對資源使用的限制和管理。
后來,dotCloud 公司將自己的容器技術進行了簡化和標準化,并命名為 Docker。Docker 誕生之初,并沒有引起行業的關注。而 dotCloud 公司在激烈的競爭之下,決定將 Docker 開源。
2013 年 3 月,dotCloud 公司的創始人之一,28 歲的 Solomon?Hykes 正式決定將 Docker 項目開源。開源當月,Docker 0.1 版本發布。此后的每一個月,Docker 都會發布一個版本。到 2014 年 6 月 9 日,Docker 1.0 版本正式發布。此時的 Docker,已經成為行業里人氣最火爆的開源技術。dotCloud 公司也干脆把公司名字也改成了 Docker Inc.。
在容器技術之前,業界的網紅是虛擬機。虛擬機屬于服務器虛擬化技術。而 Docker 這樣輕量級的虛擬化,屬于操作系統虛擬化技術。
Docker Container 的啟動時間很快,幾秒鐘就能完成。而且,它對資源的利用率很高,一臺主機可以同時運行幾千個 Docker Container。此外,它占的空間很小,虛擬機一般要幾 GB 到幾十 GB 的空間,而容器只需要 MB 級甚至 KB 級。
實際上要理解 Docker 并不困難,主要是兩句口號:
Docker 的三大核心概念,分別是:
- 鏡像(Image)
- 容器(Container)
- 倉庫(Repository)
簡而言之,Docker 就是一個 Golang 開發的開源容器引擎技術,最初基于 LXC 來實現。在 LXC 的基礎之上,實現了幾項重大的更改,通過類似 Git 的操作方式來構建、分發 Image,使 Container 的使用更加便捷和靈活。
Docker 的組件
Docker 的組件包括:
-
Docker Client:向 Docker Server 進程發起請求,如:build、pull、run 等操作。Docker Client 既可以在訪問本地守護(local host)進程,也可以訪問遠程(remote host)守護進程。
-
Docker Server:偵聽 REST API 請求并管理 Docker 對象,例如:鏡像,容器,網絡和卷。守護程序還可以與其他守護程序通信以管理 Docker 服務。
-
Docker Registry(注冊表,倉庫注冊服務器):存儲 Docker Image 的中央倉庫。其中 Docker Hub 是任何人都可以使用的 Public Registry,Docker Server 默認配置在 Docker Hub 上查找 Images。個人也可以運行 Private Registry,如果使用 Docker DataCenter,則其中包括 Docker Trusted registry(DTR)。使用 docker pull 或 docker run 指令時,所需的 Image 將從 Docker Server 配置的 Registry 中提取。
Docker 的軟件架構
從上圖可以看出,Docker 主要的模塊有:
- Docker Client
- Docker Daemon
- Docker Registry
- Graph
- Driver
- Libcontainer
- Docker Container
用戶使用 Client 與 Daemon 建立通信,并發送請求給后者 Daemon 作為 Docker 的核心,首先提供 Server 來接受 Client 的請求,而后通過 Engine 執行 Docker 內部的一系列工作,每一項工作都是以一個 Job 的形式的存在。
- 當需要為 Container 提供 Image 時,則從 Registry 中下載鏡像,并通過鏡像管理驅動 Graphdriver 將下載鏡像以 Graph 的形式存儲;
- 當需要為 Container 創建網絡環境時,則通過網絡管理驅動 Networkdriver 創建并配置 Container 網絡環境;
- 當需要為 Container 限制運行資源或執行用戶指令等操作時,則通過 Execdriver 來完成。
而 Libcontainer 則作為一個獨立的 Container 管理模塊,Networkdriver 以及 Execdriver 都是通過 Libcontainer 來完成對 Container 進行的操作。當執行 docker run 的一系列工作后,一個實際的 Container 就處于運行狀態,該 Container 擁有獨立的文件系統,獨立并且安全的運行環境等。
Docker Client
Docker Client 在 Linux 上表現為一個 docker 可執行文件,當 Client 接收到 Daemon 返回的響應并進行簡單處理后,Client 一次完整的生命周期就結束了。Client 可以通過 3 種方式和 Daemon 建立通信:
Docker Daemon
Docker Daemon 在 Linux 上表現為一個常駐在后臺的系統進程,可以通過 systemd 來進行管理,實際上跟 Docker Client 是同一個 docker 可執行文件。
Docker Daemon 可以細分為以下模塊:
- API Server:Daemon 會在后臺啟動一個 Docker Server,是一個 API Server,基于 Golang 的 Gorilla/Mux 包,接受 Client 發送的請求并路由分發到不同的 Handler 進行處理。
值得注意的是:Docker Server 的啟動是靠一個名為 serveapi 的 Job 運行來完成的。所以 Server 的本質是眾多 Job 中的一個。
-
Engine:是 Docker 的運行引擎,它扮演 Docker Container 存儲倉庫的角色,并且通過執行 Job 的方式來操縱管理這些容器。Docker Engine 有兩個不同的版本:Docker Engine Enterprise(企業版)和 Docker Engine Community(社區版)。
-
Job:一個 Job 可以認為是 Engine 內部最基本的工作執行單元。Docker 做的每一項工作,都可以抽象為一個 Job。例如:在容器內部運行一個進程,這是一個 Job;創建一個新的容器,這是一個 Job,從 Internet上 下載一個文檔,這是一個 Job,等等。Job 的設計者,把 Job 設計得與 Unix Processor 相仿。比如說:Job 有一個名稱,有參數,有環境變量,有標準的輸入輸出,有錯誤處理,有返回狀態等。
Docker Registry
Docker Registry 是一個存儲 Images 的倉庫。在 Docker 的運行過程中,Daemon 會與 Registry 通信,并實現搜索鏡像、下載鏡像、上傳鏡像三個功能,這三個功能對應的 Job 分別為 search、pul 與 push。
Docker 可以使用公有的 Docker Registry,即:Docker Hub,可以從中找到來自開源項目、軟件供應商、乃至個人賬戶的 Docker Image。同時,Docker 也允許用戶構建本地私有的 Docker Registry,這樣可以保證容器鏡像的獲取在內網完成。
Graph
Graph 充當已下載鏡像的保管者,以及已下載鏡像之間關系的記錄者。一方面,Graph 存儲著本地具有版本信息的文件系統鏡像,另一方面也通過 GraphDB 記錄著所有文件系統鏡像彼此之間的關系。
其中,GraphDB 是一個構建在 SQLite 之上的小型圖數據庫,實現了節點的命名以及節點之間關聯關系的記錄。它僅僅實現了大多數圖數據庫所擁有的一個小的子集,但是提供了簡單的接口表示節點之間的關系。
同時在 Graph 的本地目錄中,關于每一個的容器鏡像,具體存儲的信息有:該容器鏡像的元數據,容器鏡像的大小信息,以及該容器鏡像所代表的具體 Rootfs。
Driver
Driver 作為驅動模塊,Docker 通過 Driver 來實現對 Container 執行環境的定制。可以分為以下三類驅動:
Graphdriver
Graphdriver 用于完成 Image 的管理,包括存儲與獲取。當用戶需要下載指定的鏡像時,Graphdriver 就將鏡像存儲在本地的指定目錄;當用戶需要使用指定的鏡像來創建容器的 Rootfs 時,Graphdriver 就從本地鏡像存儲目錄中獲取指定的容器鏡像。
在 Graphdriver 初始化之前,有 4 種文件系統或類文件系統在其內部注冊,它們分別是:
而 Graphdriver 在初始化之時,通過獲取系統環境變量 DOCKER_DRIVER 來提取所使用 Driver 的指定類型。而之后所有的 Graph 操作,都使用該 Driver 來執行。
Graphdriver 的架構如下:
Networkdriver
Networkdriver 用于完成 Container 網絡環境的配置,其中包括:
- Docker deamon 啟動時為其創建 Bridge 網橋;
- Container 創建時為其創建專屬虛擬網卡設備,以及為 Container 分配 IP、Port 并與宿主機做端口映射,設置容器防火墻策略等。
Networkdriver 的架構如下:
Execdriver
Execdriver 作為 Container 的執行驅動,負責創建 Container 運行時 namespace,負責容器資源使用的統計與限制,負責容器內部進程的真正運行等。
在 Execdriver 實現的初期使用了 LXC Driver 調用 LXC 的接口,來操縱容器的配置以及生命周期,而現在 Execdriver 默認使用 Native 驅動,不再依賴于 LXC。可以通過啟動 Daemon 時指定 ExecDriverflag 參數來進行選擇,默認為 native。
Execdriver 架構如下:
Libcontainer
Libcontainer 是 Docker 架構中一個使用 Golang 實現的庫,設計初衷是希望該庫可以不依靠任何依賴,直接訪問 Kernel 中與容器相關的 API。
正是由于 Libcontainer 的存在,Docker 最終得以操縱 Container 的 Namespace、Cgroups、Apparmor、網絡設備以及防火墻規則等。這一系列操作的完成都不需要依賴 LXC 或者其他庫。
Libcontainer 架構如下:
Docker 將底層容器運行時剝離出來,實現更好的平臺無關性。LibContainer 是對各種容器的抽象,發展為 RunC,并貢獻給 OCP 組織作為定義容器環境的標準。
Docker Container
Docker Container 變現為一個運行在 Linux 操作系統之上的容器進程,是 Docker 服務交付的最終形式。
- 用戶通過指定 Image,使得 Container 可以自定義 Rootfs 等文件系統。
- 用戶通過指定計算資源的配額,使得 Container 使用指定的計算資源。
- 用戶通過配置網絡及其安全策略,使得 Container 擁有獨立且安全的網絡環境。
- 用戶通過指定運行的命令,使得 Container 執行指定的工作。
Docker 的安裝(CentOS)
- 常規安裝
- 安裝指定的版本
- 啟動服務
Docker 的 Image 與 Dockerfile
Image
Docker Image 就是一個只讀的文件,作為創建 Docker Container 的模板。鏡像是容器的基石,容器基于鏡像啟動,鏡像就像是容器的源代碼,保存了用于容器啟動的各種條件。
Docker 支持通過擴展現有鏡像,繼而創建新的鏡像。實際上,Docker Hub 中 99% 的鏡像都是通過在 base image 中安裝和配置需要的軟件構建出來的。
Docker Image 是一個層疊的只讀文件系統,結構如下:
- bootfs(引導文件系統):與 Linux Kernel 交互的引導系統。
- rootfs(root 文件系統):根文件系統,即 base image,可以是一種或多種操作系統,如:Ubuntu 或 CentOS,rootfs 永遠是只讀狀態。
- unionFS(聯合文件系統):即所有 base image 之上的文件系統。Docker 應用了 union mount(聯合加載技術),一次可以加載多個只讀文件系統到 rootfs 之上,從外面看到的只是一個文件系統。union mount 將各層文件系統疊加到一起,使最終呈現出來的文件系統包含了所有底層文件系統和目錄,這樣的文件系統就是鏡像。
一個鏡像可以放到另一個鏡像的頂部,位于下邊的鏡像叫做父鏡像,依次類推,最底部的鏡像叫做 base image,指的就是 rootfs,即 Ubuntu 或 CentOS 等。
當使用 Image 啟動一個 Container 后,一個新的可寫的文件系統被加載到鏡像的頂部,即:可寫層,通常也稱作 “容器層”,“容器層” 之下的都叫 “鏡像層”。
Container 中運行的程序就是在這個 “容器層” 中執行的。第一次啟動 Container 時,“容器層” 是空的,當文件系統發生變化,都會應用到這一層。如果想修改一個文件,該文件首先會從 “容器層” 下邊的 “鏡像只讀層” 復制到可寫層,該文件的只讀版本依然存在,但是已經被可寫層中的該文件副本所隱藏。這個是 Docker 重要的寫時復制(copy on write)機制。
- Docker Image 的生命周期
Dockerfile
每個 Docker Container 都從一個 Dockerfile 開始。Dockerfile 是一個使用易于理解的語法編寫的文本文件,描述如何生成 Docker Image,指定了容器的操作系統、編程語言、環境變量、文件位置、網絡端口和其他組件等配置信息,當然還指定了容器啟動后要執行的內容。
Docker Image 是 Docker Container 的 “內容載體”,本質是一個可移植文件,包含容器將運行哪些軟件組件以及如何運行的規范,每個 Container 都是一個 Image 的實例。因為 Dockerfile 可能包含關于從在線資源庫獲取某些軟件包的說明,所以需要注意指定正確的版本,否則 Dockerfile 可能會根據調用的時間不同生成不一致的鏡像。但是一旦創建了一個 Image,它就是靜態的。
- 編寫 Dockerfile
Dockerfile 指令大小寫不敏感的,但通常使用大寫,使用 # 作為注釋,每一行只支持一條指令,每條指令可以攜帶多個參數。
Dockerfile 指令根據作用可以分為兩種:
- 構建指令:用于構建 Image,其指定的操作不會在運行 Image 的容器上執行;
- 設置指令:用于設置 Image 的屬性,其指定的操作將在運行 Image 的容器中執行。
構建指令
FROM(指定 base image)
必須在 Dockerfile 頭部指定,后續的指令都依賴 FROM 指定的 base image。
該指令有兩種格式:
# 指定 base image 為該 image 的最后修改的版本。 FROM <image> # 指定 base image 為該 image 的一個 tag 版本。 FROM <image>:<tag>MAINTAINER(指定鏡像創建者信息)
指定 image 的創建者,當使用 docker inspect 查看時,會輸出中有相應的字段記錄該信息。
MAINTAINER <name>RUN(指定構建鏡像時執行的指令)
RUN 可以運行任何被 base image 支持的指令,例如:軟件管理命令。
- base image 為 ubuntu,則使用 apt install。
- base image 為 centos,則使用 yum install。
該指令有兩種格式:
RUN <command> (the command is run in a shell - `/bin/sh -c`) RUN ["executable", "param1", "param2" ... ] (exec form)設置指令
ENV(設置環境變量)
ENV 有 2 種格式:
ENV <key> <value> ENV <key1>=<value1> <key2>=<value2>...設置了環境變量之后,后續的 RUN 指令就都可以使用了,容器啟動后,可以通過 docker inspect 指令查看這個環境變量,也可以通過指令 docker run --env key=value 設置或修改指定的環境變量。
使用環境變量:
ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"WORKDIR(設置目錄的切換)
相當于 cd 命令,可以多次切換,為后續的 RUN、CMD、ENTRYPOINT、COPY、ADD 等命令配置工作目錄。
# 在 /p1/p2 下執行 vim a.txt WORKDIR /p1/p2 RUN vim a.txtCMD(設置容器啟動時執行的操作)
一個 Dockerfile 只能生效一條 CMD 指令,如果有多個,則僅執行最后一條。CMD 指定執行的操作類型可以是二進制程序、自定義腳本,或操作系統指令。
該指令有 3 種格式:
# like an exec, this is the preferred form CMD ["executable","param1","param2"] # as a shell CMD command param1 param2# as default parameters to ENTRYPOINT CMD ["param1", "param2"]ENTRYPOINT(設置容器啟動時執行的操作)
ENTRYPOINT 與 CMD 類似,用于指定一個可執行程序或腳本的路徑,該程序會腳本會以 param1 和 param2 作為參數。
ENTRYPOINT ["executable", "param1", "param2"] (like an exec, the preferred form) ENTRYPOINT command param1 param2 (as a shell)與 CMD 的區別在于 ENTRYPOINT 不僅僅具有 CMD 的功能,還能夠與 CMD 結合使用。
獨自使用:當獨自使用時,如果同一個 Dockerfile 也編寫了 CMD 命令,且 CMD 是一個完整的可執行的命令,那么 CMD 指令和 ENTRYPOINT 指令會互相覆蓋,且只有最后的一條 CMD 或者 ENTRYPOINT 指令生效。
和 CMD 指令配合使用:使用 CMD 來指定 ENTRYPOINT 的默認參數,這時 CMD 指令不是一個完整的可執行命令,僅僅提供了 parameters 部分。例如:
User Case1:使用 CMD 提供經常會被動態修改的參數部分,把穩定的參數部分寫到 ENTRYPOINT,表示啟動不同的 containe 時,可以靈活修改不同的參數部分。
FROM ubuntuCMD ["-c"]ENTRYPOINT ["top", "-b"]# 使用 docker run -it --rm --name test top -H # 效果 top -b -HUser Case2:啟動容器就是啟動主進程,但有些時候,啟動主進程前,需要一些準備工作。比如 MySQL 之類的數據庫,可能需要一些數據庫配置、初始化的工作,這些工作要在最終的 MySQL 服務器運行之前解決。這種情況下,可以寫一個腳本,然后放入 ENTRYPOINT 中去執行,而這個腳本會將接到 CMD 的參數,并在腳本最后執行。比如 Redis 的官方鏡像中就是這么做的:
FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis ... ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD [ "redis-server" ]# docker-entrypoint.sh #!/bin/sh ... # allow the container to be started with `--user` if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; thenfind . \! -user redis -exec chown redis '{}' +exec gosu redis "$0" "$@" fiexec "$@"該腳本的內容就是根據 CMD 的內容來判斷,如果是 redis-server 的話,則切換到 redis 用戶身份啟動服務器,否則依舊使用 root 身份執行。
USER(設置啟動容器的用戶)
默認為 root 用戶。指定 memcached 的運行用戶例如:
ENTRYPOINT ["memcached"] USER daemon # 或 ENTRYPOINT ["memcached", "-u", "daemon"]EXPOSE(設置容器需要映射到宿主機的端口)
該指令會將容器中的端口映射成宿主機中的某個端口,外部可以通過宿主機的 IP:Port 來訪問容器的服務 Socket。
要實現這個效果需要兩個步驟:
這樣 EXPOSE 設置的端口號會被隨機映射成宿主機中的一個端口號了。當然,也可以指定需要映射到宿主機的具體端口號,這時首先就需要確保宿主機上的端口號沒有被占用。
ADD 和 COPY
Dockerfile 中提供了兩個非常相似的命令 COPY 和 ADD:
- ADD:設置從 HostSrc 復制文件到 ContainerDest
- COPY:如果僅僅是把本地的文件拷貝到容器鏡像中,COPY 會更合適。
ADD 和 COPY 擁有相同的特點,只復制目錄中的內容而不包含目錄自身:
- src 的路徑是 docker build 目錄的相對路徑,也可以是一個遠程的文件 URL;dest 是容器的絕對路徑。
- 如果 src 是文件且 dest 中不使用 “/” 斜杠結束,則會將 dest 也視為文件,src 的內容會寫入 dest。
- 如果 src 是文件且 dest 中使用 “/” 斜杠結束,則會將 src 文件拷貝到 dest 目錄下。
- 如果 src 是一個目錄,那么會將該目錄下的所有文件添加到 dest 中,不包括 src 目錄本身。
- 如果 src 是文件且是可識別的壓縮格式,則進行解壓縮(注意壓縮格式)。
注意,所有從 HostOS 拷貝到容器中的文件或目錄的權限為均為 0755,uid 和 gid 為 0。
COPY 區別于 ADD 的一個用法是在 multi-stage(多級別)場景下。在 multi-stage 場景中,可以使用 COPY 把前一階段構建的產物拷貝到另一個鏡像中,比如:其中的 COPY 命令通過指定 --from=0 參數,把前一階段構建的產物拷貝到了當前的鏡像中。
# Dockerfile1 FROM golang:1.7.3 WORKDIR /go/src/github.com/sparkdevo/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .# Dockerfile2 FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=0 /go/src/github.com/sparkdevo/href-counter/app . CMD ["./app"]而 ADD 除了不能用在 multistage 的場景下,ADD 命令可以完成 COPY 命令的所有功能,并且還可以完成兩類超酷的功能:
VOLUME(設置卷的掛載點)
設置卷的掛載點是為了讓容器中的一個目錄具有持久化存儲數據的功能,該目錄可以被容器本身使用,也可以共享給其他容器使用。
容器本身使用的文件系統是 AUFS,這種文件系統不能持久化數據,當容器關閉后,所有的更改都會丟失,即:容器的無狀態性,或者稱之為不變性。
所以,當容器中的應用需要持久化數據時就可以使用 VOLUME 指令來掛載一個宿主機的目錄到容器,使用宿主機的文件系統來進行持久化。
FROM base VOLUME ["{src}", "{dest}"]需要注意的是,與 CLI 方式不同,-v /src:/dest 可以指定 HostOS 和 Container 的絕對路徑,但通過 VOLUME 指令創建的掛載點,卻無法指定 HostOS 對應的目錄,是自動生成的。
"Mounts": [{"Name": "d411f6b8f17f4418629d4e5a1ab69679dee369b39e13bb68bed77aa4a0d12d21","Source": "/var/lib/docker/volumes/d411f6b8f17f4418629d4e5a1ab69679dee369b39e13bb68bed77aa4a0d12d21/_data","Destination": "/data1","Driver": "local","Mode": "","RW": true}],Image build
docker image build -t {repo:tag} {Dockerfile_dir_path} # or docker build --build-arg http_proxy=http://<IP>:<PORT> --build-arg https_proxy=http://<IP>:<PORT> -t {repo:tag} {Dockerfile_dir_path}構建鏡像的過程中,docker daemon 會啟動一個臨時的 container 用于運行 Dockerfile Commands,同時還會構建一個臨時的 image。當我們在 building 的過程中出現問題時,可以臨時進入這個 image 中進行調試。
這樣做的好處有兩個:
例如:
$ docker image build -t vim-ide:gcc-9.1.0 ./gcc-9.1.0/$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 24d4c83c959d 0ee0a12e5deb "/bin/sh -c 'bash ./…" 40 seconds ago Up 39 seconds nifty_mendel$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE <none> <none> 0ee0a12e5deb 30 seconds ago 2.11GB$ docker run --rm -it --entrypoint bash {image_id}Build Context
在通過 docker build Dockerfile 創建鏡像時,具有一個 build context 的概念。本質是 docker build 的 PATH 或 URL 指定的路徑中的文件的集合。
在 docker build 的過程中經常會引用不同的文件,而 Build Context 就是為了指定這些文件的引用路徑,從而支持 COPY 和 ADD 命令。換句話說,COPY 和 ADD 命令不能夠引用 Build Context 之外的文件。
例如:docker build -t testx . 命令中的 . 就表示 Build Context 為當前目錄。當然我們也可以指定一個 Build Context。
Build 鏡像層
善于利用 Docker Build 鏡像層,將有利于加速鏡像構建。
例如:把那些最不容易發生變化的文件的拷貝操作放在較低的鏡像層中,這樣在重新 build 鏡像時就會使用前面 build 產生的緩存(using cache)。
Docker 的基本使用
鏡像操作
# 查看本地鏡像 docker images# 搜索倉庫的鏡像 docker search# 拉取倉庫的鏡像 docker pull docker.io/centos# 利用 DockerFile 創建鏡像 docker build <dockerfile># 刪除已經終止的容器 docker rm # 可以刪除正在運行的容器 docker -f rm # 跟據 ID 將鏡像保存成一個文件。 docker save <image_id> > <image_name>.tar# 同時將多個 image 打包成一個文件。 docker save postgres:9.6 mongo:3.4 -o <image_name>.tar# 將容器提交為鏡像 # OPTIONS: # -a:提交的鏡像作者 # -m:提交時的說明文字 # -p:提交時將容器暫停 # -c:使用 Dockerfile 指令來創建鏡像 docker commit [OPTIONS] CONTAINER_ID [REPOSITORY[:TAG]]# 將容器保存成一個文件。 docker export <container_id> > <image_name>.tar# 從本地將鏡像導入 docker load --input centos.tar # 或 docker load < centos.tar# 修改鏡像標簽 docker tag <OLD REPOSITORY>:<OLD TAG> <NEW REPOSITORY>:<NEW TAG># Push 指定的 image 到指定的 repo。 docker tag 300e315adb2f 172.27.100.93/developer/centos:latest docker push 172.27.100.93/developer/centos:latest容器操作
# 創建一個容器但不啟動它 docker create # 創建并啟動一個容器 docker run # 停止容器運行,發送信號 SIGTERM docker stop # 啟動一個停止狀態的容器 docker start # 重啟一個容器 docker restart# 刪除一個容器 docker rm # 發送信號給容器,默認為信號 SIGKILL docker kill # 進入到一個正在運行的容器 docker attach # 阻塞一個容器,直到容器停止運行 docker wait # 顯示狀態為運行(Up)的容器 docker ps # 顯示所有容器,包括運行中(Up)的和退出的(Exited) docker ps -a # 深入容器內部獲取容器所有信息 docker inspect # 查看容器的日志(stdout/stderr) docker logs # 得到 Docker Server 的實時的事件 docker events # 顯示容器的端口映射 docker port # 顯示容器的進程信息 docker top # 顯示容器文件系統的前后變化 docker diff # 在容器里執行一個命令,可以執行 bash 進入交互模式 docker exec# docker 復制文件 docker cp mycontainer:/opt/testnew/file.txt /opt/test/Docker 的容器網絡技術原理
CNM 標準規范
Docker 的網絡架構是建立在一系列稱為 CNM(Container Networking Model,容器網絡模型)的接口之上的。
CNM 的設計哲學是為了提供跨多種基礎設施的應用可移植性。這一模型在應用可移植性和充分利用基礎設施自有特性、能力之間,取得了一個平衡。
Libnetwork 是 CNM 標準的實現,Libnetwork 提供 Docker 守護程序和網絡驅動程序之間的接口。網絡控制器負責將驅動程序與網絡配對。每個驅動程序負責管理其擁有的網絡,包括提供給該網絡的服務。每個網絡有一個驅動程序,多個驅動程序可以與連接到多個網絡的容器同時使用。
CNM 網絡模型有 3 個組件,它們全部都是操作系統和基礎硬件不可感的,因此應用可以在任何基礎設施棧中擁有一致的表現。
例如:
- 容器的 Sandbox 上至少有兩個 Endpoints。
- gwbridge 是為了訪問外部網絡,br0 是為了容器間互通。
- 為了跨主機通信還有一個全局的 KV 數據庫(這里用的 Consul)。
- 容器間通信是通過 VxLAN 實現的。
CNM 驅動接口
CNM 提供了兩個可插拔的開放接口,供用戶、社區和供應商使用,以更好地利用網絡中的其他功能、可見性或可控性。
網絡驅動
Docker 的網絡驅動提供使網絡運行的實際實現。它們是可插拔的,因此可以使用不同的驅動程序并輕松互換以支持不同的用例。可以在給定的 Docker Engine 或群集上同時使用多個網絡驅動程序,但每個 Docker 網絡僅通過單個網絡驅動程序進行實例化。有兩種類型的 CNM 網絡驅動程序:
- 原生網絡驅動:是 Docker Engine 的原生部分,由 Docker 提供。有多種驅動程序可供選擇,支持不同的功能,如覆蓋網絡或本地網橋。
- 遠程網絡驅動:是社區和其他供應商創建的網絡驅動程序。這些驅動程序可用于和現有軟硬件相集成。用戶還可以在需要用到現有網絡驅動程序不支持的特定功能的情況下創建自己的驅動程序。
IPAM 驅動
-
原生 IPAM(IP 地址管理)驅動程序:可以為 Docker 集群全局簡單為網絡和端點分配默認子網或 IP 地址,并防止重復分配。IP 地址也可以通過網絡、容器和服務創建命令手動分配。
-
遠程 IPAM 驅動程序:使用來自其他供應商和社區的遠程 IPAM 驅動程序的接口。這些驅動程序可以提供與現有供應商或自建 IPAM 工具的集成。
Docker 原生網絡驅動
Docker 原生網絡驅動程序是 Docker Engine 的一部分,不需要任何額外的模塊。它們通過標準 Docker 網絡命令調用和使用。共有以下幾種原生網絡驅動程序。它決定了容器之間、容器與外界之前的通信方式。
- 基礎網絡類型
Docker 網絡驅動程序具有 “范圍(SCOPE)” 的概念。網絡范圍是驅動程序的作用域,可以是本地范圍或 Swarm 集群范圍。
- 本地范圍驅動程序在主機范圍內提供連接和網絡服務(如 DNS 或 IPAM)。
- Swarm 范圍驅動程序提供跨群集的連接和網絡服務。
集群范圍網絡在整個群集中具有相同的網絡 ID,而本地范圍網絡在每個主機上具有唯一的網絡 ID。
- 查看所有容器網絡類型:
Bridge 模式(默認)
當 Docker Daemon 啟動后,會在 HostOS 上創建一個名為 docker0 的 Linux Bridge,它處于 Host Network Namespace。
在此 HostOS 上啟動使用了 Bridge 模式的 Container 時:
Bridge 模式使得只有在同一個 Bridge 網絡中的 Containers 之間可以相互通信,外界想要訪問到這個網絡中的 Containers 也同樣需要接入 Bridge 網絡并通過 iptables 做了 DNAT 規則,實現內外部地址轉換。Container 可以具有任意個 Bridge 模式的網絡接口。
可見,Bridge 模式的 Docker 網絡基于 Linux 的虛擬網絡技術來實現。Docker Container 的網絡接口默認都是虛擬接口,可以充分發揮數據在不同 Container 之間或跨主機的 Container 之間的轉發效率。這是因為 Linux 虛擬網絡技術通過在內核中的數據復制來實現虛擬接口之間的數據轉發,即:發送接口的發送緩存中的數據包將被直接復制到接收接口的接收緩存中,而無需通過外部物理網絡設備進行交換。
$ ip a 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group defaultlink/ether 02:42:46:c3:00:eb brd ff:ff:ff:ff:ff:ffinet 172.17.0.1/16 scope global docker0valid_lft forever preferred_lft foreverinet6 fe80::42:46ff:fec3:eb/64 scope linkvalid_lft forever preferred_lft forever$ docker run -itd --name box1 busybox$ docker exec -it box1 sh/ # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever 6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueuelink/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ffinet 172.17.0.2/16 scope global eth0valid_lft forever preferred_lft foreverinet6 fe80::42:acff:fe11:2/64 scope linkvalid_lft forever preferred_lft forever/ # ip r default via 172.17.0.1 dev eth0 172.17.0.0/16 dev eth0 scope link src 172.17.0.2$ brctl show bridge name bridge id STP enabled interfaces docker0 8000.024246c300eb no vethd4ae072用戶自定義 Bridge 網絡
默認情況下,Docker Daemon 會從以下范圍分配一個子網 172.[17-31].0.0/16 或 192.168.[0-240].0/20 給 Bridge 設備,盡量的與 HostOS Interface 不重疊。
除了默認 Bridge 網絡,用戶也可以創建自己的 Bridge 網絡,相當于在 HostOS 上設置了新的 Linux Bridge 設備。與默認 Bridge 網絡不同,用戶定義的 Bridge 網絡支持手動分配 IP 地址和子網,否則 Docker Daemon 的 IPAM 驅動程序會進行隱式分配。
$ docker network create -d bridge --subnet 10.0.0.0/24 my_bridge$ docker run -itd --name c2 --net my_bridge busybox sh $ docker run -itd --name c3 --net my_bridge --ip 10.0.0.254 busybox sh$ brctl show bridge name bridge id STP enabled interfaces br-b5db4578d8c9 8000.02428d936bb1 no vethc9b3282vethf3ba8b5 docker0 8000.0242504b5200 no vethb64e8b8$ docker network ls NETWORK ID NAME DRIVER SCOPE b5db4578d8c9 my_bridge bridge local e1cac9da3116 bridge bridge localHost 模式
如果啟動 Container 的時候使用 host 模式(Docker Host 網絡驅動),那么這個容器將不會獲得一個獨立的 Linux Kernel Network Namespace,而是和 HostOS 共用一個 Network Namespace。
也就是說 Container 不會虛擬出自己的網卡,配置自己的 IP 等,而是直接使用 HostOS 的 IP 和端口。Host 網絡中的所有 Containers 都能夠在 HostOS Interface 上相互通信。可見,Host 模式對外界是完全開放的,能夠訪問到主機,就能訪問到 Containers。
從網絡角度來看,Containers 相當于直接在 HostOS 上運行的多個進程,它們使用相同的主機接口,所以任意兩個 Containers 不能綁定到同一個 TCP 端口,否則會導致端口爭用。
當然,Container 的其他方面,如:文件系統、進程列表等還是和 HostOS Namespace 隔離的。
在此示例中,HostOS、C1 和 nginx 都共享相同的 eth0 接口。
#Create containers on the host network $ docker run -itd --net host --name C1 alpine sh $ docker run -itd --net host --name nginx#Show host eth0 $ ip add | grep eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000inet 172.31.21.213/20 brd 172.31.31.255 scope global eth0#Show eth0 from C1 $ docker run -it --net host --name C1 alpine ip add | grep eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP qlen 1000inet 172.31.21.213/20 brd 172.31.31.255 scope global eth0需要注意的是,因為 Host 模式不會新建 Network Namespace,所以端口映射、路由規則等一系列的網絡協議棧操作都會被忽略。這意味著像 -p 和 --icc 這樣的常見網絡選型沒有任何意義。
外部訪問容器(容器的端口映射)
上述內容我們知道,當 Containers 處于同一個 Network(e.g. host、bridge、overlay)時,互相之間是可以互通的。但當 External Network 希望訪問 Containers 時,還需要將容器的服務端口映射到主機上的某個端口上。并且,出于基本的安全考慮,我們通常需要配置一些端口安全策略。
# 自定義映射 docker run -d -p 8888:80 nginx:latest # 隨機映射(需要鏡像支持) docker run -P- Ingress:顯式進行端口映射。
- Egress:隱式 SNAT 到臨時端口(通常在 32768 到 60999 的范圍內)。
MACVLAN 模式
MACVLAN 網絡設備是 Linux Kernel 的新特性,在 Linux kernel v3.9-3.19 和 v4.0+ 版本中支持,比較穩定的版本推薦 4.0+。
通過 MACVLAN 可以將一個 Host Physical NIC 虛擬成多個 Virtual Sub-NIC(虛擬網絡子接口),這些 VNIs 可以擁有自己獨立的 MAC/IP 地址,且與主機網絡處在同一個 LAN 里面,共享同一個廣播域。
有時候我們可能會需求對一塊 Physical NIC 綁定多個 IP 以及多個 MAC 地址。綁定多個 IP 很容易,但是這些 IP 會共享 Physical NIC 的 MAC 地址,這樣就無法滿足我們的設計需求,所以就有了支持在同一塊物理網卡上同時虛擬出 IP/MAC 地址的 MACVLAN 技術。
從實現效果上看,MACVLAN 與 Bridge 很類型,但相比,MACVLAN 沒有了 Bridge 帶來的 MAC 地址學習和翻譯的負擔,是一種高效直接的互聯技術。
通過不同的子接口設備,MACVLAN 做到了流量的隔離。MACVLAN 會根據數據幀的 dstMAC 地址來判斷這個二層幀需要交給哪張 VNI,VNI 再把包交給上層的內核協議棧處理。
MACVLAN 的使用場景包括:
- 超低延時應用。
- 設計一個網絡,要求容器在同一子網內,并使用和外部主機網絡相同的 IP 地址。
- 實現跨主機容器之前的通信。
此示例將 MACVLAN 網絡綁定到主機上的 eth0 物理網卡,每個容器在物理網絡 192.168.0.0/24 的子網上都有一個 IP 地址,并指定其 Default GW 是物理網絡中的網關地址。需要注意的是,這個網關地址必須是主機外部的 Provider Underlay L3 網關地址,否者無法在不同的 MACVLAN 網絡之間進行互訪。
注意,雖然 MACVLAN 驅動程序提供了這些獨特的優勢,但它犧牲的是可移植性。MACVLAN 配置和部署與底層網絡密切相關。除了防止重疊地址分配之外,容器尋址必須遵守容器放置的物理位置。因此,必須注意在 MACVLAN 網絡外部維護 IPAM。重復的 IP 地址或不正確的子網可能導致容器連接丟失。
使用 MACVLAN 進行 VLAN 中繼
通常情況下,將 VLAN 802.1q 中繼到 Linux 主機是非常痛苦的,可能需要頻繁的手動的對 HostOS VLAN Sub-Interface 的配置進行更改。使用 MACVLAN 技術則可以自動化的完成 MACVLAN 網絡的子接口和其他配置的創建、銷毀與持久化。
該示例中,使用了 HostOS Interface eth0 的 VLAN Sub-Interface 來進行 MACVLAN 網絡的創建,它實現了將 VLAN 中繼到 HostOS,在 L2 層對 Containers 網絡進行隔離。
Docker Daemon 的 MACVLAN 驅動程序將會動創 VLAN Sub-Interface 并將它們連接到容器接口。繼而實現處于不同 MACVLAN 網絡中的 Containers 具有不同的 VLAN,除非在物理網絡中進行 L3 網關路由流量,否則它們之間無法進行通信。
#Creation of macvlan10 network in VLAN 10 $ docker network create -d macvlan --subnet 192.168.10.0/24 --gateway 192.168.10.1 -o parent=eth0.10 macvlan10#Creation of macvlan20 network in VLAN 20 $ docker network create -d macvlan --subnet 192.168.20.0/24 --gateway 192.168.20.1 -o parent=eth0.20 macvlan20#Creation of containers on separate MACVLAN networks $ docker run -itd --name c1--net macvlan10 --ip 192.168.10.2 busybox sh $ docker run -it --name c2--net macvlan20 --ip 192.168.20.2 busybox sh注意,必須將主機接口和上游交換機設置為 switch port mode trunk,以便在 HostOS Interface 上標記 VLAN。
使用 MACVLAN 實現跨主機容器之前的通信
#Creation of local macvlan network on both hosts host-A $ docker network create -d macvlan --subnet 192.168.0.0/24 --gateway 192.168.0.1 -o parent=eth0 petsMacvlan host-B $ docker network create -d macvlan --subnet 192.168.0.0/24 --gateway 192.168.0.1 -o parent=eth0 petsMacvlan#Creation of db container on host-B host-B $ docker run -d --net petsMacvlan --ip 192.168.0.5 --name db consul#Creation of Web container on host-A host-A $ docker run -it --net petsMacvlan --ip 192.168.0.4 -e 'DB=192.168.0.5:8500' --name Web chrch/docker-pets:1.0Container 模式
Container 模式,又稱為 Docker links,是一種 Docker Container 之間的通信機制。如果一個新容器鏈接到一個已有容器,新容器將會通過環境變量獲得已有容器的鏈接信息。通過提供給信任容器有關已有容器的鏈接信息,實現容器間的通信。
Container 模式和 host 模式很類似,只是 Container 模式創建容器共享的是其他容器的 IP 和 Port 而不是物理機的,此模式容器自身是不會配置網絡和端口,創建此模式的容器進去后會發現里邊的 IP 是你所指定的那個容器 IP 并且 Port 也是共享的。當然,其它還是互相隔離的,如進程等。
docker run -it --network container:<container ID>None 模式(隔離)
使用 None 模式 Container 會擁有自己的 Network Namespace,但是,并不為 Container 進行任何網絡配置。也就是說,這個 Container 不會具有網卡、IP、路由等信息,只有一個 loopback 接口。所以,需要手動的為 Container 添加網卡、配置 IP 等。
因此,使用 None 網絡的容器是完全隔離的,不會去參與網絡通信,這樣就能夠保證容器的安全性。
$ docker run -itd --network none --name box3 busybox$ docker exec -it box3 sh/ # ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft foreverDocker 遠程網絡驅動
以下社區和供應商創建的遠程網絡驅動程序與 CNM 兼容,每個都為容器提供獨特的功能和網絡服務。
- contiv:由 Cisco Systems 領導的開源網絡插件,為多租戶微服務部署提供基礎架構和安全策略。Contiv 還為非容器工作負載和物理網絡(如 ACI)提供兼容集成。Contiv 實現了遠程網絡和 IPAM 驅動。
- weave:作為網絡插件,weave 用于創建跨多個主機或多個云連接 Docker 容器的虛擬網絡。Weave 提供應用程序的自動發現功能,可以在部分連接的網絡上運行,不需要外部群集存儲,并且操作友好。
- calico:云數據中心虛擬網絡的開源解決方案。它面向數據中心,大多數工作負載(虛擬機,容器或裸機服務器)只需要 IP 連接。Calico 使用標準 IP 路由提供此連接。工作負載之間的隔離都是通過托管源和目標工作負載的服務器上的 iptables 實現的,無論是根據租戶所有權還是任何更細粒度的策略。
- kuryr:作為 OpenStack Kuryr 項目的一部分開發的網絡插件。它通過利用 OpenStack 網絡服務 Neutron 實現 Docker 網絡(libnetwork)遠程驅動程序 API。Kuryr 還包括一個 IPAM 驅動程序。
Docker 遠程 IPAM 驅動
社區和供應商創建的 IPAM 驅動程序還可用于提供與現有系統或特殊功能的集成。
- infoblox:一個開源 IPAM 插件,提供與現有 Infoblox 工具的集成。
Docker 的容器存儲技術原理
數據卷管理
核心選項:
- -v 宿主機目錄:指定掛載到容器內的目錄。
映射多個宿主機目錄,只需要多寫幾個 -v 即可。
掛載時創建卷
- 掛載卷:
- 設置共享卷,使用同一個卷啟動一個新的容器:
創建卷后掛載
- 查看卷列表:
- 查看未被容器使用的數據盤
- 創建一個卷:
- 查看卷路徑:
- 使用卷創建容器:
- 刪除卷
數據容器管理
可以創建一個特殊的容器,來充當數據容器,也就是在創建容器時指定這個容器的數據盤,然后讓其他容器可以使用這個數據容器作為他們的數據盤。
- 創建一個數據容器:
- 利用此數據容器容器運行一個容器
Docker 的 Register
- 官方文檔:https://docs.docker.com/registry/
Docker Registry 是一個存儲 Images 的倉庫。在 Docker 的運行過程中,Daemon 會與 Registry 通信,并實現了搜索鏡像、下載鏡像、上傳鏡像三個功能,這三個功能對應的 Job 分別為 search、pul 與 push。
Docker Registry 分為兩大類型:
- Pubilc Registry:如 Docker Hub,可以從中找到來自開源項目、軟件供應商、乃至個人賬戶的 Docker Image。
- Private Registry:這樣可以保證容器鏡像的獲取在內網完成。
NOTE:Registry(注冊表)和 Repository(倉庫)是有區別的,Daemon 可以 Push/Pull Repository,也可以 Push/Pull Image。
- 每個 Registry 上可以存放多個 Repository。
- 每個 Repository 中又包含了多個 Images。
- 每個 Image 有著不同的 Tag(標簽)。
當 Docker Server 的 Local Images 不存在時,才會到 Remote Registry/Repository 下載 Images。
Private Registry
Pubilc Registry 有著明顯的缺陷:
所以,在生產環節中的多數時候還是需要創建自己的 Private Registry。
搭建 Private Registry 有兩種方式:
使用 docker-distribution 搭建 Private Registry
軟件包安裝
Docker Container 安裝(推薦)
使用 Docker Container 來安裝 docker-distribution 并搭建成為 Private Registry 是一種簡易的手段。
# 將宿主機的 /opt/registry 掛載到容器的 /var/lib/registry。 $ docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 --restart=always --name registry registry:2$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE docker.io/registry 2 1fd8e1b0bb7e 2 months ago 26.2 MB$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 59f0159a62c4 registry:2 "/entrypoint.sh /e..." 26 seconds ago Up 25 seconds 0.0.0.0:5000->5000/tcp registry$ netstat -lpntu | grep 5000 tcp6 0 0 :::5000 :::* LISTEN 21070/docker-proxy-- 查看 Docker Container 的進程:
可見,跟我們手動安裝的方式沒有本質的區別。
注冊表操作
- 創建倉庫:
- 修改配置文件,使之支持 HTTP
- 重啟生效
搭建帶 basic 認證的倉庫:
總結
以上是生活随笔為你收集整理的云计算与云原生 — Docker 容器技术完全解析的全部內容,希望文章能夠幫你解決所遇到的問題。