docker容器入門最佳教程


為什么要寫這個

簡單回答是:容器技術非常熱門,但門檻高。

容器技術是繼大數據和雲計算之后又一炙手可熱的技術,而且未來相當一段時間內都會非常流行。

對 IT 行業來說,這是一項非常有價值的技術。而對 IT 從業者來說,掌握容器技術是市場的需要,也是提升自我價值的重要途徑。

拿我自己的工作經歷來說,畢業后的頭幾年是做 J2EE 應用開發。后來到一家大型IT公司,公司的產品從中間件到操作系統、從服務器到存儲、從虛擬化到雲計算都有涉及。

我所在的部門是專門做 IT 基礎設施實施服務的,最開始是做傳統的 IT 項目,包括服務器配置,雙機 HA 等。隨着虛擬化技術成熟,工作上也開始涉及各種虛擬化技術的規划和實施,包括 VMWare、KVM、PowerVM等。后來雲計算興起,在公司業務和個人興趣的驅動下,開始學習和實踐 OpenStack,在這個過程中寫了《每天5分鍾玩轉OpenStack》教程並得到大家的認可。

現在以 Docker 為代表的容器技術來了,而且關注度越來越高,這一點可以從 Google Trend 中 Docker 的搜索上升趨勢(藍色曲線)中清楚看到。

每一輪新技術的興起,無論對公司還是個人既是機會也是挑戰。

我個人的看法是:如果某項新技術未來將成為主流,就應該及早盡快掌握。
因為:

  1. 新技術意味着新的市場和新的需求 
    初期掌握這種技術的人不會很多,而市場需求會越來越大,因而會形成供不應求的賣方市場,物以稀為貴,這對技術人員將是一個難得的價值提升機會。
  2. 學習新技術需要時間和精力,早起步早成材

機會講過了,咱們再來看看挑戰。

新技術往往意味着技術上的突破和創新,會有不少新的概念和方法。 
而且從大數據,雲計算和容器技術來看,這些新技術都是平台級別,覆蓋的技術范圍非常廣,包括了計算、網絡、存儲、高可用、監控、安全等多個方面,要掌握這些新技術對 IT 老兵尚有不小難度,更別說新人了。

由於對技術一直保持着很高的熱誠和執着,在掌握了 OpenStack 相關 IaaS 技術后,我便開始調研 PaaS 技術棧。正好這時 Docker 也越來越流行,自然而然便開始了容器相關技術的學習研究和實踐。

學習容器技術的過程可以說是驚喜不斷,經常驚嘆於容器理念的先進和容器生態環境的完整和強大。很多傳統軟件開發和運維中的難題在容器世界里都能輕松解決,也漸漸理解了容器為何如此受到青睞。

不誇張地說,容器為我打開了一扇通往另一個軟件世界的大門,讓我沉浸其中,激動不已。高興之余,我也迫不及待地想把我所看到所學到和所想到的有關容器的知識介紹給更多的人,讓更多的IT工程師能夠從容器技術中受益。

我希望這個教程也能為大家打開這扇門,降低學習的曲線,系統地學習和掌握容器技術。

寫給誰看?

這套教程的目標讀者包括:

軟件開發人員

相信微服務架構(Microservice Architectur)會逐漸成為開發應用系統的主流。而容器則是這種架構的基石。市場將需要更多能夠開發出基於容器的應用程序的軟件開發人員。

IT 實施和運維工程師

容器為應用提供了更好的打包和部署方式。越來越多的應用將以容器的方式在開發、測試和生產環境中運行。掌握容器相關技術將成為實施和運維工程師的核心競爭力。

我自己

我堅信最好的學習方法是分享。編寫這個教程同時也是對自己學習和實踐容器技術的總結。對於知識,只有把它寫出來並能夠讓其他人理解,才能說明真正掌握了這項知識。

包含哪些內容?

本系列教程包括以下三大塊內容:

下面分別介紹各部分包含的內容。

啟程

 

“啟程”會介紹容器的生態系統,讓大家先從整體上了解容器都包含哪些技術,各種技術之間的相互關系是什么,然后再來看我們的教程都會涉及生態中的哪些部分。

為了讓大家盡快對容器有個感性認識,我們會搭建實驗環境並運行第一個容器,為之后的學習熱身。

容器技術

這是教程的主要內容,包含“容器核心知識”和“容器進階知識”兩部分。

核心知識主要回答有關容器 what, why 和 how 三方面的問題。 其中以 how 為重,將展開討論架構、鏡像、容器、網絡和存儲。

進階知識包括將容器真正用於生產所必需的技術,包括多主機管理、跨主機網絡、監控、數據管理、日志管理和安全管理。

 

容器平台技術

如下圖所示,“容器平台技術”包括容器編排引擎、容器管理平台和基於容器的 PaaS。容器平台技術在生態環境中占據着舉足輕重的位置,對於容器是否能夠落地,是否能應用於生產至關重要。

 

怎樣的編寫方式?

我會繼續采用《每天5分鍾玩轉OpenStack》的方式,通過大量的實驗由淺入深地探討和實踐容器技術,力求達到如下目標:

  1. 快速上手:以最直接、最有效的方式讓大家把容器用起來。
  2. 循序漸進:由易到難,從淺入深,詳細分析容器的各種功能和配置使用方法。
  3. 理解架構:從設計原理和架構分析入手,深入探討容器的架構和運行機理。
  4. 注重實踐:以大量實際操作案例為基礎,讓大家能夠掌握真正的實施技能。

在內容的發布上還是通過微信公眾號(cloudman6)每周 1、3、5 定期分享。歡迎大家通過公眾號提出問題和建議,進行技術交流。

為什么叫《每天5分鍾玩轉Docker容器技術》?

為了降低學習的難度並且考慮到移動端碎片化閱讀的特點,每次推送的內容大家只需要花5分鍾就能看完(注意這里說的是看完,有時候要完全理解可能需要更多時間哈),每篇內容包含1-3個知識點,這就是我把教程命名為《每天5分鍾玩轉Docker容器技術》的原因。雖然是碎片化推送,但整個教程是系統、連貫和完整的,只是化整為零了。

好了,今天這5分鍾算是開了個頭,下次我們正式開始玩轉容器技術。

容器生態系統

對於像容器這類平台級別的技術,通常涉及的知識范圍會很廣,相關的軟件,解決方案也會很多,初學者往往容易迷失。

那怎么辦呢?

我們可以從生活經驗中尋找答案。 
當我們去陌生城市旅游想了解一下這個城市一般我們會怎么做?

我想大部分人應該會打開手機看一下這個城市的地圖:

  1. 城市大概的位置和地理形狀是什么?
  2. 都由哪幾個區或縣組成?
  3. 主要的交通干道是哪幾條?

同樣的道理,學習容器技術我們可以先從天上鳥瞰一下:

  1. 容器生態系統包含哪些不同層次的技術?
  2. 不同技術之間是什么關系?
  3. 哪些是核心技術哪些是輔助技術?

首先得對容器技術有個整體認識,之后我們的學習才能夠有的放矢,才能夠分清輕重緩急,做到心中有數,這樣就不容易迷失了。

接下來我會根據自己的經驗幫大家規划一條學習路線,一起探索容器生態系統。

學習新技術得到及時反饋是非常重要的,所以我們馬上會搭建實驗環境,並運行第一個容器,感受什么是容器。

千里之行始於足下,讓我們從了解生態系統開始吧。

鳥瞰容器生態系統

容器生態系統

一談到容器,大家都會想到 Docker。

Docker 現在幾乎是容器的代名詞。確實,是 Docker 將容器技術發揚光大。同時,大家也需要知道圍繞 Docker 還有一個生態系統。Docker 是這個生態系統的基石,但完善的生態系統才是保障 Docker 以及容器技術能夠真正健康發展的決定因素。

大致來看,容器生態系統包含核心技術、平台技術和支持技術。

下面分別介紹。

容器核心技術

容器核心技術是指能夠讓 container 在 host 上運行起來的那些技術。

 

這些技術包括容器規范、容器 runtime、容器管理工具、容器定義工具、Registry 以及 容器 OS,下面分別介紹。

容器規范

容器不光是 Docker,還有其他容器,比如 CoreOS 的 rkt。為了保證容器生態的健康發展,保證不同容器之間能夠兼容,包含 Docker、CoreOS、Google在內的若干公司共同成立了一個叫 Open Container Initiative(OCI) 的組織,其目是制定開放的容器規范。

目前 OCI 發布了兩個規范:runtime spec 和 image format spec。 
有了這兩個規范,不同組織和廠商開發的容器能夠在不同的 runtime 上運行。這樣就保證了容器的可移植性和互操作性。

容器 runtime

runtime 是容器真正運行的地方。runtime 需要跟操作系統 kernel 緊密協作,為容器提供運行環境。

如果大家用過 Java,可以這樣來理解 runtime 與容器的關系:

Java 程序就好比是容器,JVM 則好比是 runtime。JVM 為 Java 程序提供運行環境。同樣的道理,容器只有在 runtime 中才能運行。

lxc、runc 和 rkt 是目前主流的三種容器 runtime。

lxc 是 Linux 上老牌的容器 runtime。Docker 最初也是用 lxc 作為 runtime。

runc 是 Docker 自己開發的容器 runtime,符合 oci 規范,也是現在 Docker 的默認 runtime。

rkt 是 CoreOS 開發的容器 runtime,符合 oci 規范,因而能夠運行 Docker 的容器。

容器管理工具

光有 runtime 還不夠,用戶得有工具來管理容器啊。容器管理工具對內與 runtime 交互,對外為用戶提供 interface,比如 CLI。這就好比除了 JVM,還得提供 java 命令讓用戶能夠啟停應用不是。

lxd 是 lxc 對應的管理工具。

runc 的管理工具是 docker engine。docker engine 包含后台 deamon 和 cli 兩個部分。我們通常提到 Docker,一般就是指的 docker engine。

rkt 的管理工具是 rkt cli。

容器定義工具

容器定義工具允許用戶定義容器的內容和屬性,這樣容器就能夠被保存,共享和重建。

docker image 是 docker 容器的模板,runtime 依據 docker image 創建容器。

dockerfile 是包含若干命令的文本文件,可以通過這些命令創建出 docker image。

ACI (App Container Image) 與 docker image 類似,只不過它是由 CoreOS 開發的 rkt 容器的 image 格式。

Registry

容器是通過 image 創建的,需要有一個倉庫來統一存放 image,這個倉庫就叫做 Registry。

 

企業可以用 Docker Registry 構建私有的 Registry。

Docker Hub(https://hub.docker.com) 是 Docker 為公眾提供的托管 Registry,上面有很多現成的 image,為 Docker 用戶提供了極大的便利。

)是另一個公共托管 Registry,提供與 Docker Hub 類似的服務。

容器 OS

由於有容器 runtime,幾乎所有的 Linux、MAC OS 和 Windows 都可以運行容器。但這不並沒有妨礙容器 OS 的問世。

容器 OS 是專門運行容器的操作系統。與常規 OS 相比,容器 OS 通常體積更小,啟動更快。因為是為容器定制的 OS,通常它們運行容器的效率會更高。

目前已經存在不少容器 OS,CoreOS、atomic 和 ubuntu core 是其中的傑出代表。

容器平台技術

容器核心技術使得容器能夠在單個 host 上運行。而容器平台技術能夠讓容器作為集群在分布式環境中運行。

容器平台技術包括容器編排引擎、容器管理平台和基於容器的 PaaS。

容器編排引擎

基於容器的應用一般會采用微服務架構。在這種架構下,應用被划分為不同的組件,並以服務的形式運行在各自的容器中,通過 API 對外提供服務。為了保證應用的高可用,每個組件都可能會運行多個相同的容器。這些容器會組成集群,集群中的容器會根據業務需要被動態地創建、遷移和銷毀。

大家可以看到,這樣一個基於微服務架構的應用系統實際上是一個動態的可伸縮的系統。這對我們的部署環境提出了新的要求,我們需要有一種高效的方法來管理容器集群。而這,就是容器編排引擎要干的工作。

所謂編排(orchestration),通常包括容器管理、調度、集群定義和服務發現等。通過容器編排引擎,容器被有機的組合成微服務應用,實現業務需求。

docker swarm 是 Docker 開發的容器編排引擎。

kubernetes 是 Google 領導開發的開源容器編排引擎,同時支持 Docker 和 CoreOS 容器。

mesos 是一個通用的集群資源調度平台,mesos 與 marathon 一起提供容器編排引擎功能。

以上三者是當前主流的容器編排引擎。

容器管理平台

容器管理平台是架構在容器編排引擎之上的一個更為通用的平台。通常容器管理平台能夠支持多種編排引擎,抽象了編排引擎的底層實現細節,為用戶提供更方便的功能,比如 application catalog 和一鍵應用部署等。

Rancher 和 ContainerShip 是容器管理平台的典型代表。

基於容器的 PaaS

基於容器的 PaaS 為微服務應用開發人員和公司提供了開發、部署和管理應用的平台,使用戶不必關心底層基礎設施而專注於應用的開發。

Deis、Flynn 和 Dokku 都是開源容器 PaaS 的代表。

容器支持技術

下面這些技術被用於支持基於容器的基礎設施。

容器網絡

容器的出現使網絡拓撲變得更加動態和復雜。用戶需要專門的解決方案來管理容器與容器,容器與其他實體之間的連通性和隔離性。

 

docker network 是 Docker 原生的網絡解決方案。除此之外,我們還可以采用第三方開源解決方案,例如 flannel、weave 和 calico。不同方案的設計和實現方式不同,各有優勢和特點,需要根據實際需要來選型。

服務發現

動態變化是微服務應用的一大特點。當負載增加時,集群會自動創建新的容器;負載減小,多余的容器會被銷毀。容器也會根據 host 的資源使用情況在不同 host 中遷移,容器的 IP 和端口也會隨之發生變化。

在這種動態的環境下,必須要有一種機制讓 client 能夠知道如何訪問容器提供的服務。這就是服務發現技術要完成的工作。

服務發現會保存容器集群中所有微服務最新的信息,比如 IP 和端口,並對外提供 API,提供服務查詢功能。

etcd、consul 和 zookeeper 是服務發現的典型解決方案。

監控

監控對於基礎架構非常重要,而容器的動態特征對監控提出更多挑戰。針對容器環境,已經涌現出很多監控工具和方案。

docker ps/top/stats 是 Docker 原生的命令行監控工具。除了命令行,Docker 也提供了 stats API,用戶可以通過 HTTP 請求獲取容器的狀態信息。

sysdig、cAdvisor/Heapster 和 Weave Scope 是其他開源的容器監控方案。

數據管理

容器經常會在不同的 host 之間遷移,如何保證持久化數據也能夠動態遷移,是 Rex-Ray 這類數據管理工具提供的能力。

日志管理

日志為問題排查和事件管理提供了重要依據。

docker logs 是 Docker 原生的日志工具。而 logspout 對日志提供了路由功能,它可以收集不同容器的日志並轉發給其他工具進行后處理。

安全性

對於年輕的容器,安全性一直是業界爭論的焦點。

OpenSCAP 能夠對容器鏡像進行掃描,發現潛在的漏洞。

本教程覆蓋的知識范圍

前面我們已經鳥瞰了整個容器生態系統,對容器所涉及的技術體系有了全面的認識。那我們的系列教程會討論其中的哪些內容呢?

會覆蓋容器生態系統 91.6% 的技術!

運行第一個容器

為了讓大家對容器有個感性認識,我們將盡快讓一個容器運行起來。首先我們需要搭建實驗環境。

環境選擇

容器需要管理工具、runtime 和操作系統,我們的選擇如下:

  1. 管理工具 - Docker Engine 
    因為 Docker 最流行使用最廣泛。
  2. runtime - runc 
    Docker 的默認 runtime
  3. 操作系統 - Ubuntu 
    雖然存在諸如 CoreOS 的容器 OS,因考慮到我們目前處於初學階段,選擇大家熟悉的操作系統更為合適。等具備了扎實的容器基礎知識后再使用容器 OS 會更有利。

安裝 Docker

本節我們將在 ubuntu 16.04 虛擬機中安裝 Docker。因為安裝過程需要訪問 internet, 所以虛擬機必須能夠上網。

Docker 支持幾乎所有的 Linux 發行版,也支持 Mac 和 Windows。各操作系統的安裝方法可以訪問:

Docker 分為開源免費的 CE(Community Edition)版本和收費的 EE(Enterprise Edition)版本。下面我們將按照文檔,通過以下步驟在 Ubuntu 16.04 上安裝 Docker CE 版本。

配置 Docker 的 apt 源

1.安裝包,允許 apt 命令 HTTPS 訪問 Docker 源。

2.添加 Docker 官方的 GPG key

3.將 Docker 的源添加到 /etc/apt/sources.list

安裝 Docker

運行第一個容器

環境就緒,馬上運行第一個容器,執行命令:

其過程可以簡單的描述為:

  1. 從 Docker Hub 下載 httpd 鏡像。鏡像中已經安裝好了 Apache HTTP Server。
  2. 啟動 httpd 容器,並將容器的 80 端口映射到 host 的 80 端口。

下面我們可以通過瀏覽器驗證容器是否正常工作。在瀏覽器中輸入 http://[your ubuntu host IP]

可以訪問容器的 http 服務了,第一個容器運行成功!我們輕輕松松就擁有了一個 WEB 服務器。隨着學習的深入,會看到容器技術帶給我們更多的價值。

小結

我們已經完成了教程的第一部分。

我們認識了容器生態系統,后面會陸續學習生態系統中的大部分技術。我們在 Ubuntu 16.04 上配置好了實驗環境,並成功運行了第一個容器 httpd。

容器大門已經打開,讓我們去探秘吧。

 

 

What - 什么是容器?

容器是一種輕量級、可移植、自包含的軟件打包技術,使應用程序可以在幾乎任何地方以相同的方式運行。開發人員在自己筆記本上創建並測試好的容器,無需任何修改就能夠在生產系統的虛擬機、物理服務器或公有雲主機上運行。

容器與虛擬機

談到容器,就不得不將它與虛擬機進行對比,因為兩者都是為應用提供封裝和隔離。

容器由兩部分組成:

  1. 應用程序本身
  2. 依賴:比如應用程序需要的庫或其他軟件

容器在 Host 操作系統的用戶空間中運行,與操作系統的其他進程隔離。這一點顯著區別於的虛擬機。

傳統的虛擬化技術,比如 VMWare, KVM, Xen,目標是創建完整的虛擬機。為了運行應用,除了部署應用本身及其依賴(通常幾十 MB),還得安裝整個操作系統(幾十 GB)。

下圖展示了二者的區別。

如圖所示,由於所有的容器共享同一個 Host OS,這使得容器在體積上要比虛擬機小很多。另外,啟動容器不需要啟動整個操作系統,所以容器部署和啟動速度更快,開銷更小,也更容易遷移。

Why - 為什么需要容器?

為什么需要容器?容器到底解決的是什么問題? 
簡要的答案是:容器使軟件具備了超強的可移植能力。

容器解決的問題

我們來看看今天的軟件開發面臨着怎樣的挑戰?

如今的系統在架構上較十年前已經變得非常復雜了。以前幾乎所有的應用都采用三層架構(Presentation/Application/Data),系統部署到有限的幾台物理服務器上(Web Server/Application Server/Database Server)。

而今天,開發人員通常使用多種服務(比如 MQ,Cache,DB)構建和組裝應用,而且應用很可能會部署到不同的環境,比如虛擬服務器,私有雲和公有雲。

一方面應用包含多種服務,這些服務有自己所依賴的庫和軟件包;另一方面存在多種部署環境,服務在運行時可能需要動態遷移到不同的環境中。這就產生了一個問題:

如何讓每種服務能夠在所有的部署環境中順利運行?

於是我們得到了下面這個矩陣:

各種服務和環境通過排列組合產生了一個大矩陣。開發人員在編寫代碼時需要考慮不同的運行環境,運維人員則需要為不同的服務和平台配置環境。對他們雙方來說,這都是一項困難而艱巨的任務。

如何解決這個問題呢?

聰明的技術人員從傳統的運輸行業找到了答案。

幾十年前,運輸業面臨着類似的問題。

每一次運輸,貨主與承運方都會擔心因貨物類型的不同而導致損失,比如幾個鐵桶錯誤地壓在了一堆香蕉上。另一方面,運輸過程中需要使用不同的交通工具也讓整個過程痛苦不堪:貨物先裝上車運到碼頭,卸貨,然后裝上船,到岸后又卸下船,再裝上火車,到達目的地,最后卸貨。一半以上的時間花費在裝、卸貨上,而且搬上搬下還容易損壞貨物。

這同樣也是一個 NxM 的矩陣。

幸運的是,集裝箱的發明解決這個難題。

任何貨物,無論鋼琴還是保時捷,都被放到各自的集裝箱中。集裝箱在整個運輸過程中都是密封的,只有到達最終目的地才被打開。標准集裝箱可以被高效地裝卸、重疊和長途運輸。現代化的起重機可以自動在卡車、輪船和火車之間移動集裝箱。集裝箱被譽為運輸業與世界貿易最重要的發明。

Docker 將集裝箱思想運用到軟件打包上,為代碼提供了一個基於容器的標准化運輸系統。Docker 可以將任何應用及其依賴打包成一個輕量級、可移植、自包含的容器。容器可以運行在幾乎所有的操作系統上。

其實,“集裝箱” 和 “容器” 對應的英文單詞都是 “Container”。 
“容器” 是國內約定俗成的叫法,可能是因為容器比集裝箱更抽象,更適合軟件領域的原故吧。

我個人認為:在老外的思維中,“Container” 只用到了集裝箱這一個意思,Docker 的 Logo 不就是一堆集裝箱嗎?

 

Docker 的特性

我們可以看看集裝箱思想是如何與 Docker 各種特性相對應的。

容器的優勢

對於開發人員 - Build Once, Run Anywhere

容器意味着環境隔離和可重復性。開發人員只需為應用創建一次運行環境,然后打包成容器便可在其他機器上運行。另外,容器環境與所在的 Host 環境是隔離的,就像虛擬機一樣,但更快更簡單。

對於運維人員 - Configure Once, Run Anything

只需要配置好標准的 runtime 環境,服務器就可以運行任何容器。這使得運維人員的工作變得更高效,一致和可重復。容器消除了開發、測試、生產環境的不一致性。

How - 容器是如何工作的?

接下來學習容器核心知識的最主要部分。

我們首先會介紹 Docker 的架構,然后分章節詳細討論 Docker 的鏡像、容器、網絡和存儲。

Docker 架構詳解

Docker 的核心組件包括:

  1. Docker 客戶端 - Client
  2. Docker 服務器 - Docker daemon
  3. Docker 鏡像 - Image
  4. Registry
  5. Docker 容器 - Container

Docker 架構如下圖所示:

Docker 采用的是 Client/Server 架構。客戶端向服務器發送請求,服務器負責構建、運行和分發容器。客戶端和服務器可以運行在同一個 Host 上,客戶端也可以通過 socket 或 REST API 與遠程的服務器通信。

Docker 客戶端

最常用的 Docker 客戶端是 docker 命令。通過 docker 我們可以方便地在 Host 上構建和運行容器。

docker 支持很多操作(子命令),后面會逐步用到。

除了 docker 命令行工具,用戶也可以通過 REST API 與服務器通信。

Docker 服務器

Docker daemon 是服務器組件,以 Linux 后台服務的方式運行。

Docker daemon 運行在 Docker host 上,負責創建、運行、監控容器,構建、存儲鏡像。

默認配置下,Docker daemon 只能響應來自本地 Host 的客戶端請求。如果要允許遠程客戶端請求,需要在配置文件中打開 TCP 監聽,步驟如下:

1.編輯配置文件 /etc/systemd/system/multi-user.target.wants/docker.service,在環境變量 ExecStart 后面添加 -H tcp://0.0.0.0,允許來自任意 IP 的客戶端連接。

如果使用的是其他操作系統,配置文件的位置可能會不一樣。

2.重啟 Docker daemon。

3.服務器 IP 為 192.168.56.102,客戶端在命令行里加上 -H 參數,即可與遠程服務器通信。

info 子命令用於查看 Docker 服務器的信息。

Docker 鏡像

可將 Docker 鏡像看成只讀模板,通過它可以創建 Docker 容器。

例如某個鏡像可能包含一個 Ubuntu 操作系統、一個 Apache HTTP Server 以及用戶開發的 Web 應用。

鏡像有多種生成方法:

  1. 可以從無到有開始創建鏡像
  2. 也可以下載並使用別人創建好的現成的鏡像
  3. 還可以在現有鏡像上創建新的鏡像

我們可以將鏡像的內容和創建步驟描述在一個文本文件中,這個文件被稱作 Dockerfile,通過執行 docker build <docker-file> 命令可以構建出 Docker 鏡像,后面我們會討論。

Docker 容器

Docker 容器就是 Docker 鏡像的運行實例。

用戶可以通過 CLI(docker)或是 API 啟動、停止、移動或刪除容器。可以這么認為,對於應用軟件,鏡像是軟件生命周期的構建和打包階段,而容器則是啟動和運行階段。

Registry

Registry 是存放 Docker 鏡像的倉庫,Registry 分私有和公有兩種。

Docker Hub() 是默認的 Registry,由 Docker 公司維護,上面有數以萬計的鏡像,用戶可以自由下載和使用。

出於對速度或安全的考慮,用戶也可以創建自己的私有 Registry。后面我們會學習如何搭建私有 Registry。

docker pull 命令可以從 Registry 下載鏡像。 docker run 命令則是先下載鏡像(如果本地沒有),然后再啟動容器。

Docker 組件如何協作?

一個完整的例子

還記得我們運行的第一個容器嗎?現在通過它來體會一下 Docker 各個組件是如何協作的。

容器啟動過程如下:

  1. Docker 客戶端執行 docker run 命令。
  2. Docker daemon 發現本地沒有 httpd 鏡像。
  3. daemon 從 Docker Hub 下載鏡像。
  4. 下載完成,鏡像 httpd 被保存到本地。
  5. Docker daemon 啟動容器。

docker images 可以查看到 httpd 已經下載到本地。

docker ps 或者 docker container ls 顯示容器正在運行。

小結

Docker 借鑒了集裝箱的概念。標准集裝箱將貨物運往世界各地,Docker 將這個模型運用到自己的設計哲學中,唯一不同的是:集裝箱運輸貨物,而 Docker 運輸軟件。

每個容器都有一個軟件鏡像,相當於集裝箱中的貨物。容器可以被創建、啟動、關閉和銷毀。和集裝箱一樣,Docker 在執行這些操作時,並不關心容器里到底裝的什么,它不管里面是 Web Server,還是 Database。

用戶不需要關心容器最終會在哪里運行,因為哪里都可以運行。

開發人員可以在筆記本上構建鏡像並上傳到 Registry,然后 QA 人員將鏡像下載到物理或虛擬機做測試,最終容器會部署到生產環境。

使用 Docker 以及容器技術,我們可以快速構建一個應用服務器、一個消息中間件、一個數據庫、一個持續集成環境。因為 Docker Hub 上有我們能想到的幾乎所有的鏡像。

不知大家是否意識到,潘多拉盒子已經被打開。容器不但降低了我們學習新技術的門檻,更提高了效率。

如果你是一個運維人員,想研究負載均衡軟件 HAProxy,只需要執行 docker run haproxy,無需繁瑣的手工安裝和配置既可以直接進入實戰。

如果你是一個開發人員,想學習怎么用 django 開發 Python Web 應用,執行 docker run django,在容器里隨便折騰吧,不用擔心會搞亂 Host 的環境。

不誇張的說:容器大大提升了 IT 人員的幸福指數。

最小的鏡像

鏡像是 Docker 容器的基石,容器是鏡像的運行實例,有了鏡像才能啟動容器。

本章內容安排如下:

  1. 首先通過研究幾個典型的鏡像,分析鏡像的內部結構。
  2. 然后學習如何構建自己的鏡像。
  3. 最后介紹怎樣管理和分發鏡像。

鏡像的內部結構

為什么我們要討論鏡像的內部結構?

如果只是使用鏡像,當然不需要了解,直接通過 docker 命令下載和運行就可以了。

但如果我們想創建自己的鏡像,或者想理解 Docker 為什么是輕量級的,就非常有必要學習這部分知識了。

我們從一個最小的鏡像開始吧。

hello-world - 最小的鏡像

hello-world 是 Docker 官方提供的一個鏡像,通常用來驗證 Docker 是否安裝成功。

我們先通過 docker pull 從 Docker Hub 下載它。

用 docker images 命令查看鏡像的信息。

hello-world 鏡像竟然還不到 2KB!

通過 docker run 運行。

其實我們更關心 hello-world 鏡像包含哪些內容。

Dockerfile 是鏡像的描述文件,定義了如何構建 Docker 鏡像。Dockerfile 的語法簡潔且可讀性強,后面我們會專門討論如何編寫 Dockerfile。

hello-world 的 Dockerfile 內容如下:

只有短短三條指令。

  1. FROM scratch 
    此鏡像是從白手起家,從 0 開始構建。
  2. COPY hello / 
    將文件“hello”復制到鏡像的根目錄。
  3. CMD ["/hello"] 
    容器啟動時,執行 /hello

鏡像 hello-world 中就只有一個可執行文件 “hello”,其功能就是打印出 “Hello from Docker ......” 等信息。

/hello 就是文件系統的全部內容,連最基本的 /bin,/usr, /lib, /dev 都沒有。

hello-world 雖然是一個完整的鏡像,但它並沒有什么實際用途。通常來說,我們希望鏡像能提供一個基本的操作系統環境,用戶可以根據需要安裝和配置軟件。

這樣的鏡像我們稱作 base 鏡像。

 

base 鏡像

上一節我們介紹了最小的 Docker 鏡像,本節討論 base 鏡像。

base 鏡像有兩層含義:

  1. 不依賴其他鏡像,從 scratch 構建。
  2. 其他鏡像可以之為基礎進行擴展。

所以,能稱作 base 鏡像的通常都是各種 Linux 發行版的 Docker 鏡像,比如 Ubuntu, Debian, CentOS 等。

我們以 CentOS 為例考察 base 鏡像包含哪些內容。
下載鏡像:

docker pull centos

查看鏡像信息:

鏡像大小不到 200MB。

等一下!
一個 CentOS 才 200MB ?
平時我們安裝一個 CentOS 至少都有幾個 GB,怎么可能才 200MB !

相信這是幾乎所有 Docker 初學者都會有的疑問,包括我自己。下面我們來解釋這個問題。

Linux 操作系統由內核空間和用戶空間組成。如下圖所示:

rootfs

內核空間是 kernel,Linux 剛啟動時會加載 bootfs 文件系統,之后 bootfs 會被卸載掉。

用戶空間的文件系統是 rootfs,包含我們熟悉的 /dev, /proc, /bin 等目錄。

對於 base 鏡像來說,底層直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。

而對於一個精簡的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序庫就可以了。相比其他 Linux 發行版,CentOS 的 rootfs 已經算臃腫的了,alpine 還不到 10MB。

我們平時安裝的 CentOS 除了 rootfs 還會選裝很多軟件、服務、圖形桌面等,需要好幾個 GB 就不足為奇了。

base 鏡像提供的是最小安裝的 Linux 發行版。

下面是 CentOS 鏡像的 Dockerfile 的內容:

第二行 ADD 指令添加到鏡像的 tar 包就是 CentOS 7 的 rootfs。在制作鏡像時,這個 tar 包會自動解壓到 / 目錄下,生成 /dev, /proc, /bin 等目錄。

注:可在 Docker Hub 的鏡像描述頁面中查看 Dockerfile 。

支持運行多種 Linux OS

不同 Linux 發行版的區別主要就是 rootfs。

比如 Ubuntu 14.04 使用 upstart 管理服務,apt 管理軟件包;而 CentOS 7 使用 systemd 和 yum。這些都是用戶空間上的區別,Linux kernel 差別不大。

所以 Docker 可以同時支持多種 Linux 鏡像,模擬出多種操作系統環境。

上圖 Debian 和 BusyBox(一種嵌入式 Linux)上層提供各自的 rootfs,底層共用 Docker Host 的 kernel。

這里需要說明的是:

1. base 鏡像只是在用戶空間與發行版一致,kernel 版本與發行版是不同的。
例如 CentOS 7 使用 3.x.x 的 kernel,如果 Docker Host 是 Ubuntu 16.04(比如我們的實驗環境),那么在 CentOS 容器中使用的實際是是 Host 4.x.x 的 kernel。

① Host kernel 為 4.4.0-31

② 啟動並進入 CentOS 容器

③ 驗證容器是 CentOS 7

④ 容器的 kernel 版本與 Host 一致

2. 容器只能使用 Host 的 kernel,並且不能修改。
所有容器都共用 host 的 kernel,在容器中沒辦法對 kernel 升級。如果容器對 kernel 版本有要求(比如應用只能在某個 kernel 版本下運行),則不建議用容器,這種場景虛擬機可能更合適。

鏡像的分層結構

Docker 支持通過擴展現有鏡像,創建新的鏡像。

實際上,Docker Hub 中 99% 的鏡像都是通過在 base 鏡像中安裝和配置需要的軟件構建出來的。比如我們現在構建一個新的鏡像,Dockerfile 如下:

① 新鏡像不再是從 scratch 開始,而是直接在 Debian base 鏡像上構建。

② 安裝 emacs 編輯器。

③ 安裝 apache2。

④ 容器啟動時運行 bash。

構建過程如下圖所示:

可以看到,新鏡像是從 base 鏡像一層一層疊加生成的。每安裝一個軟件,就在現有鏡像的基礎上增加一層。

問什么 Docker 鏡像要采用這種分層結構呢?

最大的一個好處就是 - 共享資源。

比如:有多個鏡像都從相同的 base 鏡像構建而來,那么 Docker Host 只需在磁盤上保存一份 base 鏡像;同時內存中也只需加載一份 base 鏡像,就可以為所有容器服務了。而且鏡像的每一層都可以被共享,我們將在后面更深入地討論這個特性。

這時可能就有人會問了:如果多個容器共享一份基礎鏡像,當某個容器修改了基礎鏡像的內容,比如 /etc 下的文件,這時其他容器的 /etc 是否也會被修改?

答案是不會!

修改會被限制在單個容器內。

這就是我們接下來要學習的容器 Copy-on-Write 特性。

可寫的容器層

當容器啟動時,一個新的可寫層被加載到鏡像的頂部。
這一層通常被稱作“容器層”,“容器層”之下的都叫“鏡像層”。

所有對容器的改動 - 無論添加、刪除、還是修改文件都只會發生在容器層中。

只有容器層是可寫的,容器層下面的所有鏡像層都是只讀的。

下面我們深入討論容器層的細節。

鏡像層數量可能會很多,所有鏡像層會聯合在一起組成一個統一的文件系統。如果不同層中有一個相同路徑的文件,比如 /a,上層的 /a 會覆蓋下層的 /a,也就是說用戶只能訪問到上層中的文件 /a。在容器層中,用戶看到的是一個疊加之后的文件系統。

  1. 添加文件 在容器中創建文件時,新文件被添加到容器層中。
  2. 讀取文件 在容器中讀取某個文件時,Docker 會從上往下依次在各鏡像層中查找此文件。一旦找到,立即將其復制到容器層,然后打開並讀入內存。
  3. 修改文件 在容器中修改已存在的文件時,Docker 會從上往下依次在各鏡像層中查找此文件。一旦找到,立即將其復制到容器層,然后修改之。
  4. 刪除文件 在容器中刪除文件時,Docker 也是從上往下依次在鏡像層中查找此文件。找到后,會在容器層中記錄下此刪除操作。

只有當需要修改時才復制一份數據,這種特性被稱作 Copy-on-Write。可見,容器層保存的是鏡像變化的部分,不會對鏡像本身進行任何修改。

這樣就解釋了我們前面提出的問題:容器層記錄對鏡像的修改,所有鏡像層都是只讀的,不會被容器修改,所以鏡像可以被多個容器共享。

構建鏡像

對於 Docker 用戶來說,最好的情況是不需要自己創建鏡像。幾乎所有常用的數據庫、中間件、應用軟件等都有現成的 Docker 官方鏡像或其他人和組織創建的鏡像,我們只需要稍作配置就可以直接使用。

使用現成鏡像的好處除了省去自己做鏡像的工作量外,更重要的是可以利用前人的經驗。特別是使用那些官方鏡像,因為 Docker 的工程師知道如何更好的在容器中運行軟件。

當然,某些情況下我們也不得不自己構建鏡像,比如:

  1. 找不到現成的鏡像,比如自己開發的應用程序。
  2. 需要在鏡像中加入特定的功能,比如官方鏡像幾乎都不提供 ssh。

所以本節我們將介紹構建鏡像的方法。同時分析構建的過程也能夠加深我們對前面鏡像分層結構的理解。

Docker 提供了兩種構建鏡像的方法:

  1. docker commit 命令
  2. Dockerfile 構建文件

docker commit

docker commit 命令是創建新鏡像最直觀的方法,其過程包含三個步驟:

  1. 運行容器
  2. 修改容器
  3. 將容器保存為新的鏡像

舉個例子:在 ubuntu base 鏡像中安裝 vi 並保存為新鏡像。

  1. 第一步, 運行容器

-it 參數的作用是以交互模式進入容器,並打開終端。412b30588f4a 是容器的內部 ID。

  1. 安裝 vi

確認 vi 沒有安裝。

安裝 vi。

  1. 保存為新鏡像
    在新窗口中查看當前運行的容器。

silly_goldberg 是 Docker 為我們的容器隨機分配的名字。

執行 docker commit 命令將容器保存為鏡像。

新鏡像命名為 ubuntu-with-vi

查看新鏡像的屬性。

從 size 上看到鏡像因為安裝了軟件而變大了。

從新鏡像啟動容器,驗證 vi 已經可以使用。

以上演示了如何用 docker commit 創建新鏡像。然而,Docker 並不建議用戶通過這種方式構建鏡像。原因如下:

  1. 這是一種手工創建鏡像的方式,容易出錯,效率低且可重復性弱。比如要在 debian base 鏡像中也加入 vi,還得重復前面的所有步驟。
  2. 更重要的:使用者並不知道鏡像是如何創建出來的,里面是否有惡意程序。也就是說無法對鏡像進行審計,存在安全隱患。

既然 docker commit 不是推薦的方法,我們干嘛還要花時間學習呢?

原因是:即便是用 Dockerfile(推薦方法)構建鏡像,底層也 docker commit 一層一層構建新鏡像的。學習 docker commit 能夠幫助我們更加深入地理解構建過程和鏡像的分層結構。

Dockerfile 構建鏡像

Dockerfile 是一個文本文件,記錄了鏡像構建的所有步驟。

第一個 Dockerfile

用 Dockerfile 創建上節的 ubuntu-with-vi,其內容則為:

 

 

下面我們運行 docker build 命令構建鏡像並詳細分析每個細節。

① 當前目錄為 /root。

② Dockerfile 准備就緒。

③ 運行 docker build 命令,-t 將新鏡像命名為 ubuntu-with-vi-dockerfile,命令末尾的 . 指明 build context 為當前目錄。Docker 默認會從 build context 中查找 Dockerfile 文件,我們也可以通過 -f 參數指定 Dockerfile 的位置。

④ 從這步開始就是鏡像真正的構建過程。 首先 Docker 將 build context 中的所有文件發送給 Docker daemon。build context 為鏡像構建提供所需要的文件或目錄。
Dockerfile 中的 ADD、COPY 等命令可以將 build context 中的文件添加到鏡像。此例中,build context 為當前目錄 /root,該目錄下的所有文件和子目錄都會被發送給 Docker daemon。

所以,使用 build context 就得小心了,不要將多余文件放到 build context,特別不要把 //usr 作為 build context,否則構建過程會相當緩慢甚至失敗。

⑤ Step 1:執行 FROM,將 ubuntu 作為 base 鏡像。
ubuntu 鏡像 ID 為 f753707788c5。

⑥ Step 2:執行 RUN,安裝 vim,具體步驟為 ⑦、⑧、⑨。

⑦ 啟動 ID 為 9f4d4166f7e3 的臨時容器,在容器中通過 apt-get 安裝 vim。

⑧ 安裝成功后,將容器保存為鏡像,其 ID 為 35ca89798937。

這一步底層使用的是類似 docker commit 的命令。

⑨ 刪除臨時容器 9f4d4166f7e3。

⑩ 鏡像構建成功。

通過 docker images 查看鏡像信息。

鏡像 ID 為 35ca89798937,與構建時的輸出一致。

在上面的構建過程中,我們要特別注意指令 RUN 的執行過程 ⑦、⑧、⑨。Docker 會在啟動的臨時容器中執行操作,並通過 commit 保存為新的鏡像。

查看鏡像分層結構

ubuntu-with-vi-dockerfile 是通過在 base 鏡像的頂部添加一個新的鏡像層而得到的。

這個新鏡像層的內容由 RUN apt-get update && apt-get install -y vim 生成。這一點我們可以通過 docker history 命令驗證。

docker history 會顯示鏡像的構建歷史,也就是 Dockerfile 的執行過程。

ubuntu-with-vi-dockerfile 與 ubuntu 鏡像相比,確實只是多了頂部的一層 35ca89798937,由 apt-get 命令創建,大小為 97.07MB。docker history 也向我們展示了鏡像的分層結構,每一層由上至下排列。

注: 表示無法獲取 IMAGE ID,通常從 Docker Hub 下載的鏡像會有這個問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM