容器技術在近年來的發展速度令人驚嘆,作為這場技術革命的核心推手,Docker已經從一個鮮為人知的工具迅速成為企業IT戰略的核心元件。在我多年參與大型系統架構設計和最佳化的經驗中,很少看到一項技術能像Docker這樣,在短幾年內從默無聞變成幾乎所有大型科技公司的標準配備。

這篇技術旨在帶你深入理解容器技術的本質、Docker的運作機制,以及如何在實際軟體生命週期中充分發揮容器化的優勢。無論你是軟體開發者、系統管理員,還是希望匯入DevOps文化的技術經理,這裡的內容都將幫助你掌握容器技術的精髓。

容器技術的本質與優勢

容器與虛擬機器的根本差異

容器和虛擬機器(VM)在技術上有本質的不同。在我為金融科技客戶設計高用性系統時,這個差異尤為明顯。虛擬機器透過在硬體層之上實作一個完整的作業系統,達到資源隔離的目的。每個VM需要執行自己的作業系統核心(kernel),這意味著較大的資源消耗和啟動時間。

相比之下,容器分享主機的作業系統核心,僅包含應用程式及其依賴項:

主機硬體
  └── 作業系統
      ├── 容器A(應用程式+依賴項)
      ├── 容器B(應用程式+依賴項)
      └── 容器C(應用程式+依賴項)

而虛擬機器架構則是:

主機硬體
  └── Hypervisor
      ├── VM A(作業系統+應用程式+依賴項)
      ├── VM B(作業系統+應用程式+依賴項)
      └── VM C(作業系統+應用程式+依賴項)

這種架構差異帶來了容器的三大優勢:

  1. 輕量級:容器不需要額外的作業系統開銷,一台標準伺服器可以執行數百個容器,而同樣設定可能只能執行十幾個VM
  2. 啟動速度快:容器可以在幾秒鐘內啟動,而VM通常需要幾分鐘
  3. 一致的環境:容器確保開發、測試和生產環境的一致性,解決了"在我的機器上能執行"的典型問題

Docker與容器化的歷史演進

容器技術的概念並非Docker首創。Linux中的cgroups(控制群組)和namespaces(名稱空間)技術早在2008年就已存在。然而,是Docker在2013年將這些底層技術封裝成易於使用的工具,徹底改變了開發者與容器的互動方式。

Docker的成功在於它解決了三個關鍵問題:

  • 簡化了容器的建立和使用流程
  • 提供了映像檔(Image)的概念,使應用封裝和分發變得簡單
  • 建立了映像檔倉函式庫egistry)生態系統,促進了容器分享

Docker的發展歷程也反映了容器技術的演進:從最初的單一二進位檔案,到後來的模組化設計,再到現在的容器協調平台整合。這種演進讓Docker不僅是一個容器執行時,更是一個完整的容器管理生態系統。

Docker的基礎架構與元件

Docker採用了客戶端-伺服器架構,主要由以下幾個部分組成:

  • Docker客戶端(Client):使用者透過命令列介面與Docker互動的工具
  • Docker伺服器(Daemon):管理Docker物件(映像檔、容器、網路等)的後台服務
  • Docker映像檔(Image):容器的唯讀範本,包含執行應用所需的一切
  • Docker容器(Container):映像檔的執行例項
  • Docker登入檔(Registry):儲存和分發Docker映像檔的服務

技術上,Docker建立在以下Linux核心功能之上:

  • Namespaces:提供容器的隔離工作區,包括PID、網路、掛載等
  • Control Groups(cgroups):限制應用程式的資源使用,如CPU、記憶體等
  • Union File System:將檔案系統層疊起來,實作映像檔的分層結構

Docker安裝與基本操作

在不同平台上安裝Docker

Docker支援多種平台,包括各種Linux發行版、macOS和Windows。以下是在主要平台上安裝Docker的方法:

Linux(Ubuntu)上安裝

# 更新套件索引
sudo apt-get update

# 安裝必要套件
sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common

# 新增Docker官方GPG金鑰
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# 設定Docker倉函式庫udo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

# 更新套件索引
sudo apt-get update

# 安裝Docker CE
sudo apt-get install -y docker-ce

macOS和Windows上安裝

對於macOS和Windows使用者,最簡單的方法是下載並安裝Docker Desktop。這個應用程式包含了在這些平台上執行Docker所需的所有元件,包括一個輕量級的Linux虛擬機器。

安裝後,可以透過簡單的命令驗證Docker是否正確安裝:

docker version
docker run hello-world

如果看到版本資訊和"Hello from Docker!“訊息,說明Docker已成功安裝並可以執行容器。

Docker的基礎命令

在容器世界中,掌握基本命令是必須的。以下是一些最常用的Docker命令:

執行容器

# 基本格式
docker run [選項] 映像檔 [命令] [引數]

# 例子:以互動模式執行Ubuntu容器
docker run -it ubuntu bash

# 在後台執行容器
docker run -d nginx

# 對映連線埠
docker run -d -p 8080:80 nginx

檢視和管理容器

# 列出執行中的容器
docker ps

# 列出所有容器(包括已停止的)
docker ps -a

# 停止容器
docker stop 容器ID或名稱

# 啟動已停止的容器
docker start 容器ID或名稱

# 刪除容器
docker rm 容器ID或名稱

處理映像檔

# 列出本地映像檔
docker images

# 提取映像檔
docker pull nginx:latest

# 刪除映像檔
docker rmi nginx:latest

# 為映像檔新增標籤
docker tag nginx:latest myregistry/nginx:v1

從Dockerfile建立映像檔

Dockerfile是建立Docker映像檔的指令碼,它包含一系列指令,告訴Docker如何建立映像檔。以下是一個簡單的Dockerfile範例:

# 使用官方的Python執行時作為基礎映像檔
FROM python:3.9-slim

# 設定工作目錄
WORKDIR /app

# 複製依賴檔案並安裝依賴
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 複製應用程式碼
COPY . .

# 指定容器啟動時執行的命令
CMD ["python", "app.py"]

使用以下命令從Dockerfile建立映像檔:

docker build -t myapp:latest .

這個命令告訴Docker使用當前目錄(.)中的Dockerfile建立一個標籤為myapp:latest的映像檔。

使用登入檔管理映像檔

Docker映像檔通常儲存在登入檔(Registry)中,最常用的公共登入檔是Docker Hub。

使用Docker Hub

# 登入Docker Hub
docker login

# 推播映像檔到Docker Hub
docker push username/repository:tag

# 從Docker Hub提取映像檔
docker pull username/repository:tag

私有登入檔

對於企業環境,通常需要設定私有登入檔:

# 執行本地登入檔容器
docker run -d -p 5000:5000 --name registry registry:2

# 標記映像檔以推播到私有登入檔
docker tag myapp:latest localhost:5000/myapp:latest

# 推播到私有登入檔
docker push localhost:5000/myapp:latest

Docker核心概念深度解析

映像檔層與快取機制

Docker映像檔採用分層結構,這是它高效運作的關鍵。每個Dockerfile指令都會建立一個新層,具體來說:

映像檔層1:基礎映像檔(FROM指令)
映像檔層2:安裝套件(RUN apt-get指令)
映像檔層3:複製應用程式碼(COPY指令)
映像檔層4:設定啟動命令(CMD指令)

這種分層機制帶來兩個主要優勢:

  1. 空間效率:相同的層在不同映像檔間分享,節省儲存空間
  2. 建置速度:如果Dockerfile沒有變化,Docker可以重用之前建置的層(快取)

在我為一家電商重構微服務架構時,合理利用層快取將CI/CD管道的映像檔建置時間從15分鐘縮短到不到2分鐘。

容器的網路與通訊

Docker提供多種網路模式,滿足不同的應用場景:

橋接網路(Bridge Network)

這是Docker的預設網路模式。容器在內部網路中執行,並透過NAT連線到外部網路:

# 建立自定義橋接網路
docker network create mynetwork

# 將容器連線到該網路
docker run --network=mynetwork nginx

主機網路(Host Network)

容器直接使用主機的網路名稱空間,分享主機的網路介面:

docker run --network=host nginx

容器間通訊

容器可以透過多種方式相互通訊:

  1. 容器連線(Links):雖然已被網路功能取代,但仍被廣泛使用
  2. 網路:容器可以加入同一網路並透過容器名稱相互存取
  3. 暴露連線埠:容器可以暴露連線埠供其他容器或主機存取

資料卷與持久化儲存

容器本身是臨時的,當容器被刪除時,其中的資料也會消失。Docker提供了資料卷(Volumes)來解決資料持久化問題:

# 建立資料卷
docker volume create mydata

# 使用資料卷執行容器
docker run -v mydata:/data nginx

# 使用主機目錄作為資料卷
docker run -v /host/path:/container/path nginx

資料卷的使用場景包括:

  1. 資料持久化:儲存容器生成的資料
  2. 資料分享:在容器之間或容器與主機之間分享資料
  3. 資料容器:專門用於管理資料卷的容器

在開發環境中使用Docker

建立開發環境

Docker徹底改變了開發環境的建置方式。在我帶領的一個金融科技團隊中,新成員從加入到擁有完整開發環境的時間從原本的3天縮短到了不到1小時,全靠Docker化的開發環境。

以Python應用為例,一個開發環境的Dockerfile可能如下:

FROM python:3.9

WORKDIR /app

# 安裝開發工具
RUN pip install pytest pytest-cov flake8

# 安裝應用依賴
COPY requirements.txt .
RUN pip install -r requirements.txt

# 設定開發環境專用的啟動命令
CMD ["python", "-m", "flask", "run", "--host=0.0.0.0", "--port=5000", "--reload"]

使用Docker Compose管理多容器應用

大多數應用都需要多個服務協同工作,如Web應用可能需要資料函式庫取等。Docker Compose是管理多容器應用的理想工具:

# docker-compose.yml
version: '3'

services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/app
    depends_on:
      - db
      - redis

  db:
    image: postgres:13
    environment:
      

容器技術:徹底改變軟體生態的力量

容器技術正在從根本上改變我們開發、分發和執行軟體的方式。身為一位經常在大型分散式系統中實作容器解決方案的技術顧問,我親眼見證了這項技術如何解決了許多傳統佈署模式的痛點。容器技術讓開發者能夠在本地建構軟體,並確保無論在任何環境—無論是IT部門的機架、使用者的筆電或雲端叢集—都能保持一致的執行效果。

系統工程師現在可以專注在網路規劃、資源管理和系統穩定性上,而不必浪費大量時間在環境設定和處理系統相依性問題。從小型新創到大型企業,容器的使用正以驚人的速度增長。作為開發者或系統工程師,未來幾年內必然會經常接觸到容器技術的應用。

容器的本質與獨特價值

容器本質上是應用程式與其相依性的一種封裝形式。乍看之下,它們似乎只是虛擬機器(VM)的輕量版—就像VM一樣,容器也包含一個隔離的作業系統環境,可用於執行應用程式。

然而,容器擁有幾項關鍵優勢,使其能夠滿足傳統VM難以或無法實作的使用場景:

資源分享與效能優勢

容器直接與主機作業系統分享資源,使其效率提高一個數量級。在處理過金融科技公司的交易系統時,我發現容器可在幾分之一秒內啟動和停止,而VM則需要數十秒甚至數分鐘。容器中執行的應用程式與直接在主機上執行的應用程式相比,幾乎沒有任何效能損失。

可攜性與環境一致性

容器的可攜性有潛力消除一整類別因環境細微變化而導致的錯誤。在協助某跨國電商遷移系統時,容器技術幾乎終結了開發者常說的「但在我的機器上可以正常運作!」這種情況。

容器的封裝特性確保了無論在開發、測試還是生產環境中,應用程式都能在相同的環境中執行,極大降低了環境差異導致的問題。

輕量化與高密度佈署

容器的輕量特性讓開發者能夠同時執行數十個容器,使模擬生產環境中的分散式系統成為可能。在一個雲端基礎設施專案中,我們在同一台主機上佈署的容器數量是使用VM時的10倍以上,大幅提升了資源利用率。

簡化應用分發與安裝

容器技術對最終使用者和應用開發者也有顯著好處。使用者可以下載並執行複雜的應用程式,無需花費數小時處理設定和安裝問題,也不必擔心對系統的變更。相對地,應用程式開發者也不再需要擔心使用者環境差異和相依性問題。

更重要的是,VM與容器的根本目標不同—VM的目的是完全模擬一個異質環境,而容器的目的是使應用程式可攜帶與自給自足。

容器與虛擬機器的本質區別

雖然容器和VM表面上看起來相似,但存在一些重要的差異。以下透過架構比較來解釋這些差異。

虛擬機器的架構

傳統虛擬機器架構中,三個應用程式分別執行在宿主機上的三個獨立VM中。虛擬化層(Hypervisor)負責建立和執行VM,控制對底層作業系統和硬體的存取,並在必要時解釋系統呼叫。每個VM都需要一份完整的作業系統副本、執行的應用程式以及所有支援函式庫 這種架構提供了強大的隔離性,但付出了顯著的資源開銷和效能損失。

容器的架構

相比之下,容器架構中,同樣的三個應用程式以容器形式執行在同一系統上,但其運作方式截然不同:

  • 宿主機的核心(kernel)直接與執行中的容器分享,這意味著容器必須使用與宿主機相同的核心
  • 若應用程式Y和Z使用相同的函式庫們可以分享這些資料而非各自保留冗餘副本
  • 容器引擎負責啟動和停止容器,類別似於VM中的虛擬化層
  • 容器內執行的程式相當於宿主機上的原生程式,不會產生虛擬化層帶來的額外開銷

在實際佈署雲端應用時,我經常觀察到容器啟動時間比VM快10-20倍,資源使用效率提高3-5倍,這些優勢在大規模佈署環境中尤為明顯。

安全隔離與混合使用

VM和容器都可用於隔離同一主機上的應用程式。VM透過虛擬化層提供了額外的隔離度,是一種經過驗證的成熟技術。相比之下,容器技術相對較新,許多組織在容器隔離功能獲得充分驗證前仍對其安全性持謹慎態度。

根據這個原因,我們經常看到混合系統—容器執行在VM內部,以同時利用這兩種技術的優勢。在金融和醫療行業的客戶中,這種混合架構特別常見,可以在保證安全性的同時提高資源利用率。

容器技術的實際應用場景

在實際工作中,容器技術已經在多個領域展現出強大價值:

  1. 持續整合與佈署(CI/CD) - 容器使開發、測試和生產環境保持一致,大幅降低「在我機器上可以執行」的問題

  2. 微服務架構 - 容器天然適合微服務佈署,每個服務可獨立封裝、擴充套件和管理

  3. 遺留系統現代化 - 在不修改舊系統的情況下,將其容器化以便更靈活地佈署和管理

  4. 多雲端策略 - 容器抽象化了底層基礎設施,使應用可以更容易地在不同雲平台間遷移

  5. 開發環境標準化 - 確保開發團隊使用完全相同的環境,消除「在我機器上正常」的問題

容器技術的挑戰與限制

儘管容器技術提供了眾多優勢,但在實際應用中仍面臨一些挑戰:

  • 核心分享限制 - 容器必須使用與宿主機相同的核心,這限制了某些跨平台應用場景
  • 安全考量 - 容器隔離不如VM嚴格,需要額外的安全措施確保安全性
  • 持久化儲存 - 容器本身是臨時性的,需要特別設計才能有效管理持久資料
  • 監控與管理 - 在大規模容器佈署環境中,監控和管理變得更加複雜

在處理某金融機構的大規模容器佈署時,我發現容器協調和持久化儲存是最具挑戰性的問題。透過適當的架構設計和工具選擇,這些問題都能得到有效解決。

容器技術的未來發展

隨著容器技術的持續演進,我們能看到幾個明顯的發展趨勢:

  1. 更強的安全性 - 容器執行時安全性不斷增強,逐漸縮小與VM的安全差距
  2. 無伺服器容器 - 雲端供應商提供的容器即服務(CaaS)和無伺服器容器使佈署更加簡單
  3. 邊緣計算整合 - 容器在邊緣計算中扮演越來越重要的角色,實作分散式應用佈署
  4. AI/ML工作負載 - 容器越來越多地用於封裝和佈署AI和機器學習工作負載

以我的觀察,容器技術正從單純的應用封裝工具,演變成為現代分散式系統的核心構建模組,並將持續推動雲原生應用架構的發展。

容器技術已經不僅是一種技術選擇,而是現代軟體開發和佈署的基礎正規化。無論是開發者還是維運工程師,深入理解容器技術都將成為必備的核心技能。透過正確應用容器技術,我們能夠構建更靈活、更高效與更易於維護的系統。

容器技術的演進與崛起

容器技術的概念遠比我們想像的古老。回顧容器化技術的發展歷程,能幫助我們更好地理解為何 Docker 能夠在短時間內revolutionize整個軟體產業。在我參與大型金融機構的系統現代化專案時,常需要向團隊解釋容器化的重要性,這段歷史總能有效說服持觀望態度的決策者。

從 chroot 到完整容器解決方案

UNIX 系統幾十年前就已經擁有 chroot 命令,提供了一種簡單的檔案系統隔離形式。這是容器概念的雛形,但功能相當有限。2001 年前後,容器技術開始出現重大進展:

  • FreeBSD jail:自 1998 年起,FreeBSD 提供了 jail 工具,將 chroot 沙箱隔離擴充套件到處理程式層面
  • Solaris Zones:約於 2001 年推出,提供了相對完整的容器化技術,但侷限於 Solaris 作業系統
  • Virtuozzo/OpenVZ:同樣在 2001 年,Parallels Inc.(當時名為 SWsoft)發布了商業版 Virtuozzo 容器技術,並在 2005 年將核心技術開放原始碼為 OpenVZ

值得注意的是,OpenVZ 雖然技術成熟,但由於需要執行修補過的核心,從未真正實作大規模採用。這讓我想起早期在評估企業容器方案時遇到的困境,修改核心的要求常成為安全團隊的絕對禁忌。

Google 與 Linux 容器的發展

容器技術的現代發展離不開 Google 的貢獻。Google 開始為 Linux 核心開發 CGroups(控制群組),並逐步將其基礎架構遷移到容器上。2008 年,Linux Containers(LXC)專案啟動,將 CGroups、核心名稱空間和 chroot 技術(以及其他技術)整合在一起,提供了一個相對完整的容器化解決方案。

然而,這些技術工具仍然複雜與不易使用,直到 2013 年,Docker 出現並為容器化難題帶來了最後幾塊拼圖,容器技術才真正進入主流。我記得當時第一次嘗試 Docker 的驚豔感受,它徹底改變了我對應用佈署的認知。

Docker:容器技術的革命者

Docker 採用了現有的 Linux 容器技術,透過行動式映像檔和友善的使用者介面進行包裝和擴充套件,建立了一個完整的容器建立和分發解決方案。Docker 平台有兩個不同的元件:

  • Docker Engine:負責建立和執行容器的核心元件
  • Docker Hub:用於分發容器的雲端服務

Docker Engine:容器執行的簡化介面

Docker Engine 提供了一個快速與便捷的介面來執行容器。在此之前,使用 LXC 等技術執行容器需要相當專業的知識和手動工作。我還記得在 Docker 出現前,設定一個 LXC 環境需要一整天的時間,而 Docker 將這個過程縮短到幾分鐘。

Docker Hub:容器生態系統的催化劑

Docker Hub 提供了大量可供下載的公共容器映像檔,使用者能夠快速上手,避免重複他人已經完成的工作。Docker 開發的其他工具包括:

  • Swarm:叢集管理工具
  • Kitematic:操作容器的圖形化介面
  • Machine:用於設定 Docker 主機的命令列工具

透過開放原始碼 Docker Engine,Docker 能夠在其周圍培養一個龐大的社群,並利用公眾幫助修復錯誤和增強功能。Docker 的快速崛起使其實際上成為了事實標準,這導致業界壓力推動開發容器執行時和格式的獨立正式標準。

開放容器計劃(OCI)

2015 年,在 Docker、Microsoft、CoreOS 等重要組織的贊助下,成立了開放容器計劃(Open Container Initiative),其使命是開發容器標準。Docker 的容器格式和執行時構成了這項努力的基礎。

容器化:開發者驅動的革命

容器技術的普及很大程度上是由開發者推動的。Docker 容器的快速啟動時間對追求快速迭代開發週期的開發者至關重要,他們渴望能夠迅速看到程式碼變更的結果。容器的可移植性和隔離性保證使開發者之間以及與營運團隊的協作更加順暢。

開發者確信他們的程式碼將在各種環境中正常工作,而營運團隊可以專注於託管和協調容器,而不必擔心容器內執行的程式碼。我在帶領團隊轉型至 DevOps 流程時,容器技術是最關鍵的推動力,它真正實作了"構建一次,到處執行"的承諾。

Docker 帶來的變化正在顯著改變我們開發軟體的方式。如果沒有 Docker,容器很可能會在 IT 領域的陰影中停留很長一段時間。

航運隱喻:理解 Docker 的哲學

Docker 的理念常透過航運容器的隱喻來解釋,這也是 Docker 名稱的由來。這個故事通常是這樣的:

當貨物運輸時,它們必須透過各種不同的運輸方式,可能包括卡車、叉車、起重機、火車和輪船。這些運輸方式必須能夠處理各種不同大小和不同需求的貨物(例如,咖啡袋、危險化學品桶、電子產品箱、豪華車隊和冷藏羊肉架)。從歷史上看,這是一個繁瑣與昂貴的過程,需要大量人力,如碼頭作業員,在每個中轉點手動裝卸物品。

運輸業因標準化集裝箱的引入而徹底革新。這些集裝箱具有標準尺寸,設計用於在不同運輸模式之間轉換,同時減少人工操作。所有運輸機械都設計為處理這些容器,從叉車和起重機到卡車、火車和輪船。標準化的好處也延伸到其他支援系統,如容器的標籤和密封。這意味著運輸業可以讓貨物生產者負責容器的內容,而運輸業則專注於容器本身的移動和儲存。

Docker 的目標是將容器標準化的好處帶入 IT 領域。近年來,軟體系統在多樣性方面激增。已經過去了單機執行 LAMP(Linux、Apache、MySQL 和 PHP)堆積積疊的日子。現代系統可能包括 JavaScript 框架、NoSQL 資料函式庫息佇列、REST API 以及用各種程式語言編寫的後端。

這個堆積積疊必須部分或完全執行在各種硬體上——從開發者的筆電到內部測試叢集,再到生產雲端提供商。每個環境都不同,執行不同的作業系統,使用不同版本的函式庫不同的硬體上。簡而言之,我們面臨著與運輸業類別似的問題——我們必須不斷投入大量人力來在不同環境之間移動程式碼。

就像標準化集裝箱簡化了貨物運輸一樣,Docker 容器簡化了軟體應用的運輸。開發者可以專注於構建應用程式並透過測試和生產進行交付,而不必擔心環境和依賴關係的差異。營運團隊可以專注於執行容器的核心問題,如分配資源、啟動和停止容器以及在伺服器之間遷移容器。

Docker 的發展歷程

瞭解 Docker 的發展歷史,有助於理解它為何能夠如此迅速地改變技術格局:

從 dotCloud 到 Docker Inc.

2008 年,Solomon Hykes 創立了 dotCloud,旨在建立一個語言無關的平台即服務(PaaS)產品。語言無關是 dotCloud 的獨特賣點——當時的 PaaS 都與特定語言集繫結(如 Heroku 支援 Ruby,Google App Engine 支援 Java 和 Python)。2010 年,dotCloud 參加了 Y Combinator 加速計劃,接觸到新的合作夥伴並開始吸引嚴肅的投資。

重大轉折點出現在 2013 年 3 月,dotCloud 將 Docker(其核心構建模組)開放原始碼。當一些公司可能害怕他們在贈送他們的"魔豆"時,dotCloud 認識到 Docker 將從成為社群驅動的專案中獲益匪淺。

Docker 早期版本只不過是 LXC 的包裝器,搭配聯合檔案系統,但其採用率和開發速度驚人地快。在短六個月內,它在 GitHub 上獲得了超過 6,700 顆星和 175 名非員工貢獻者。這促使 dotCloud 更名為 Docker, Inc. 並重新聚焦其商業模式。

Docker 1.0 與生態系統擴充套件

2014 年 6 月,Docker 1.0 發布,僅在 0.1 版本發布後 15 個月。Docker 1.0 代表了穩定性和可靠性的重大飛躍——它現在被宣稱為"生產就緒”,儘管它已經在包括 Spotify 和百度在內的幾家公司中投入生產使用。同時,隨著 Docker Hub(一個公共容器倉函式庫推出,Docker 開始向完整平台而非僅是容器引擎邁進。

其他公司很快看到了 Docker 的潛力。紅帽在 2013 年 9 月成為主要合作夥伴,並開始使用 Docker 來驅動其 OpenShift 雲端產品。Google、Amazon 和 DigitalOcean 迅速在其雲端上提供 Docker 支援,一些初創公司如 StackDock 開始專攻 Docker 託管。2014 年 10 月,Microsoft 宣佈未來版本的 Windows Server 將支援 Docker,這代表著傳統上與企業軟體巨頭相關的公司的重大立場轉變。

擴充套件與標準化

2014 年 12 月的 DockerConEU 見證了 Docker Swarm(叢集管理器)和 Docker Machine(用於設定 Docker 主機的命令列工具)的發布。這清楚地表明瞭 Docker 有意提供一個完整與整合的容器執行解決方案,而不僅是提供 Docker 引擎。

同年 12 月,CoreOS 宣佈開發 rkt,其自己的容器執行時,以及 appc 容器規範的開發。2015 年 6 月,在舊金山的 DockerCon 期間,Docker 的 Solomon Hykes 和 CoreOS 的 Alex Polvi 宣佈成立開放容器計劃(後改名為開放容器計劃),以開發容器格式和執行時的通用標準。

2015 年 6 月,FreeBSD 專案宣佈 Docker 現在 FreeBSD 上受支援,使用 ZFS 和 Linux 相容層。2015 年 8 月,Docker 和 Microsoft 發布了 Windows Server 上的 Docker Engine “技術預覽”。

隨著 Docker 1.8 的發布,Docker 引入了內容信任功能,驗證 Docker 映像檔的完整性和發布者。內容信任是根據從 Docker 登入檔檢索的映像檔建立可信工作流程的關鍵元件。

外掛與底層基礎設施

作為一家公司,Docker Inc. 一直很快認識到它的成功很大程度上歸功於生態系統。當 Docker Inc. 專注於生產穩定、生產就緒版本的容器引擎時,其他公司如 CoreOS、WeaveWorks 和 ClusterHQ 正在研究相關領域,如協調和網路容器。

然而,很快就明顯看出 Docker Inc. 計劃提供一個完整的開箱即用平台,包括網路、儲存和協調功能。為了鼓勵持續的生態系統增長

Docker容器技術:現代應用程式的基礎設施

容器技術已成為現代應用程式開發與佈署的關鍵技術。作為一位在雲端基礎設施領域工作多年的技術工作者,我觀察到容器技術徹底改變了軟體開發與佈署的方式。在這篇文章中,我將分享容器技術的核心概念以及如何在不同環境中正確安裝與設定Docker。

runC專案與OCI標準

Docker將容器執行時的核心功能貢獻給了runC專案,這個專案由開放容器倡議組織(OCI)監督,可作為其他容器平台的基礎。這是容器標準化的重要一步,讓不同的容器實作可以分享核心執行時元件,促進了容器生態系統的開放發展。

64位元Linux環境需求

在撰寫本文時,Docker的唯一穩定與適合生產環境的平台是64位元Linux。這意味著:

  • 你的電腦需要執行64位元的Linux發行版
  • 所有的容器也都會是64位元Linux環境
  • Windows或Mac OS使用者需要透過虛擬機器來執行Docker

由於Docker本身並不提供虛擬化功能,容器必須與主機核心比對—Windows Server容器只能在Windows Server主機上執行,64位元Linux容器只能在64位元Linux主機上執行。雖然對其他平台的支援(包括BSD、Solaris和Windows Server)正處於不同的開發階段,但目前生產環境仍以Linux為主。

微服務與單體架構的對比

微服務架構的崛起

容器技術普及的最大使用案例和最強勁的驅動力是微服務架構。微服務是一種軟體系統的開發和組合方式,它將系統拆分為小型、獨立的元件,這些元件透過網路互相互動。這與傳統的單體(Monolithic)開發方式形成鮮明對比,傳統方式通常是一個大型程式,典型地用C++或Java編寫。

擴充套件策略差異

在擴充套件單體應用時,通常唯一的選擇是垂直擴充套件(scale up),即透過使用更大的機器、更多的RAM和CPU能力來處理額外的需求。相對地,微服務設計為水平擴充套件(scale out),透過設定多台機器來分散負載。

在微服務架構中,可以只擴充套件系統中特定服務所需的資源,針對瓶頸進行最佳化。而在單體架構中,要麼全部擴充套件,要麼不擴充套件,這常導致資源浪費。

複雜性的雙面性

微服務在複雜性方面是把雙刃劍:

  • 每個別微服務應該易於理解和修改
  • 然而,在由數十或數百個此類別服務組成的系統中,由於元件之間的相互作用,整體複雜性會增加

容器的輕量性和快速佈署特性使其特別適合執行微服務架構。與虛擬機器相比,容器體積小、佈署快,允許微服務架構使用最少的資源並快速回應需求變化。

如果你想深入瞭解微服務,可參考Sam Newman的《Building Microservices》(O’Reilly)和Martin Fowler的《Microservice Resource Guide》。

Docker安裝

Linux環境下的Docker安裝

在Linux上安裝Docker的最佳方式是使用Docker提供的安裝指令碼。雖然大多數主要的Linux發行版都有自己的套件,但這些套件往往落後於Docker的釋出版本,考慮到Docker開發的快速步伐,這是個嚴重問題。

Docker的系統要求

Docker的系統要求很少,但你需要執行相對現代的核心(版本3.10或更高)。你可以透過執行uname -r來檢查這一點。如果你使用RHEL或CentOS,需要7版本或更高。

另外,記住你需要在64位元架構上執行。可以透過執行uname -m來檢查;結果應該是x86_64

安裝指令碼

你可以使用提供在https://get.docker.com/ 的指令碼自動安裝Docker。官方說明會告訴你只需執行:

curl -sSL https://get.docker.com | sh

wget -qO- https://get.docker.com | sh

不過,我建議在執行前先檢查指令碼,確認你對它將對系統做的更改感到滿意:

curl https://get.docker.com > /tmp/install.sh
cat /tmp/install.sh
chmod +x /tmp/install.sh
/tmp/install.sh

這個指令碼會進行幾項檢查,然後使用適合你係統的套件安裝Docker。如果缺少安全性和檔案系統功能的額外依賴,它也會安裝這些。

如果你不想使用安裝程式,或者想使用與安裝程式提供的版本不同的Docker版本,也可以從Docker網站下載二進位檔。這種方法的缺點是不會檢查依賴關係,與需要手動安裝更新。更多資訊和二進位檔連結,請參閱Docker Binary頁面。

SELinux設定建議

如果你執行根據Red Hat的發行版,包括RHEL、CentOS和Fedora,你可能安裝了SELinux安全模組。

在開始使用Docker時,我建議在寬容模式(permissive mode)下執行SELinux,這會記錄而不是強制執行錯誤。如果在強制模式(enforcing mode)下執行SELinux,執行本文中的範例時可能會看到各種神秘的「許可權被拒絕」錯誤。

要檢查SELinux模式,執行sestatus並檢視輸出:

$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: error (Success)
Policy MLS status: enabled
Policy deny_unknown status: allowed
Max kernel policy version: 28

如果你在這裡看到「enforcing」,表示SELinux已啟用並正在強制執行規則。

要將SELinux更改為寬容模式,只需執行:

sudo setenforce 0

關於SELinux以及為什麼在熟悉Docker後應該考慮啟用它的更多資訊,請參閱「SELinux」部分。

無需sudo執行Docker

由於Docker是一個特權二進位檔,預設情況下,我們需要在命令前加上sudo才能執行。這很快就會變得麻煩。我們可以透過將使用者新增到docker群組來解決這個問題。在Ubuntu上,你應該能夠執行以下操作:

sudo usermod -aG docker $USER

這將建立docker群組(如果它尚不存在),並新增當前使用者。然後你需要登出並重新登入。其他Linux發行版應該類別似。

你還需要重啟Docker服務,這取決於發行版。在Ubuntu上,這看起來像:

sudo service docker restart

為簡潔起見,本文省略了所有Docker命令中的sudo。

需要注意的是,將使用者新增到docker群組相當於給予該使用者root許可權。因此,它具有你應該瞭解的安全影響,尤其是在使用分享機器時。更多資訊,請參閱Docker安全頁面。

Mac OS或Windows上的Docker安裝

如果你使用Windows或Mac OS,你將需要某種形式的虛擬化來執行Docker。

你可以下載完整的VM解決方案並按照Linux說明安裝Docker,或安裝Docker Toolbox,其中包括最小的boot2docker VM以及我們將在本章中使用的其他Docker工具,如Compose和Swarm。如果你使用Homebrew在Mac上安裝應用程式,有一個可用的boot2docker配方;但一般來說,我建議使用官方的Toolbox安裝以避免問題。

安裝Toolbox後,你可以透過開啟Docker快速啟動終端來存取Docker。或者,你可以透過輸入以下命令來設定現有終端:

$ docker-machine start default
Starting VM...
Started machines may have new IP addresses. You may need to rerun the
`docker-machine env` command.
$ eval $(docker-machine env default)

這將設定你的環境,提供存取在VM中執行的Docker引擎所需的設定。

使用Docker Toolbox時,請注意以下事項:

  • 在本文的範例中,我假設Docker執行在主機上。如果你使用Docker Toolbox,情況不會如此。特別是,你需要將對localhost的參照更改為VM的IP位址。例如:
curl localhost:5000

將變成類別似:

curl 192.168.59.103:5000

你可以透過執行docker-machine ip default輕鬆發現VM的IP,這允許一些自動化:

curl $(docker-machine ip default):5000
  • 在本地OS和Docker容器之間對映的卷必須在VM內部交叉掛載。Docker Toolbox在一定程度上自動化了這一點,但如果在使用Docker卷時遇到問題,請注意這一點。

  • 如果你有特殊要求,可能需要更改VM內部的設定。boot2docker VM內的/var/lib/boot2docker/profile檔案包含各種設定,包括Docker引擎設定。你還可以透過編輯/var/lib/boot2docker/bootlocal.sh檔案在VM初始化後執行自己的指令碼。完整詳情請參閱boot2docker GitHub儲存函式庫 如果你在按照本文的範例操作時遇到任何問題,嘗試使用docker-machine ssh default直接登入到VM,並從那裡執行命令。

Docker實驗頻道

除了正常的穩定版本外,Docker還維護一個實驗版本,其中包含用於測試目的最新功能。由於這些功能仍在討論和開發中,它們可能會在進入穩定版本之前發生重大變化。實驗版本應僅用於在官方發布前調查新功能,切勿在生產環境中使用。

實驗版本可以在Linux上使用以下指令碼安裝:

curl -sSL https://experimental.docker.com/ | sh

或者從Docker網站下載二進位版本。請注意,該版本每晚更新,並提供雜湊值以驗證下載。

快速檢查

為確保一切安裝正確並正常工作,嘗試執行docker version命令。你應該看到類別似以下內容:

$ docker version
Client:
 Version:      1.8.1
 API version:  1.20
 Go version:   go1.4.2
 Git commit:   d12ea79
 Built:        Thu Aug 13 02:35:49 UTC 2015
 OS/Arch:      linux/amd64

Server:
 Version:      1.8.1
 API version:  1.20
 Go version:   go1.4.2
 Git commit:   d12ea79
 Built:        Thu Aug 13 02:35:49 UTC 2015
 OS/Arch:      linux/amd64

如果是這樣,你已準備好進入下一章。如果相反,你得到類別似以下內容:

$ docker version
Client:
 Version:      1.8.1
 API version:  1.20
 Go version:   go1.4.2
 Git commit:   d12ea79
 Built:        Thu Aug 13 02:35:49 UTC 2015
 OS/Arch:      linux/amd64

Get http:///var/run/docker.sock/v1.20/version: dial unix /var/run/docker.sock:
no such file or directory.

這表示Docker客戶端已經安裝,但無法連線到Docker守護程式。確保Docker服務正在執行:

sudo service docker start

如果仍然遇到問題,可能需要檢視Docker日誌以取得更詳細的錯誤訊息:

sudo journalctl -u docker

容器技術的實務應用思考

在我多年的技術顧問經驗中,發現容器技術最大的價值不僅在於其技術優勢,更在於它如何改變組織的開發流程。當一個團隊採用容器技術後,通常會看到以下變化:

  1. 開發與維運之間的溝通障礙減少,因為容器提供了統一的環境
  2. 佈署頻率提高,從而加速功能交付與市場反饋迴圈
  3. 資源使用效率提升,降低基礎設施成本

特別是對於正在轉型為微服務架構的團隊,容器不僅是技術選擇,更是整個開發文化的催化劑

容器初體驗:Docker 入門

Docker 容器技術已經徹底改變了軟體開發和佈署的方式。在這篇文章中,我將帶領你瞭解 Docker 的基礎知識,從安裝後的首次使用到建立自己的容器映像檔。我們將探索 Docker 的核心概念,實際操作一些基本命令,並深入瞭解容器和映像檔之間的關係。

執行你的第一個 Docker 容器

確認 Docker 安裝成功的最佳方式就是執行一個簡單的容器。讓我們試看:

$ docker run debian echo "Hello World"

如果這是你第一次執行這個命令,你可能會看到類別似以下的輸出:

Unable to find image 'debian' locally
debian:latest: The image you are pulling has been verified
511136ea3c5a: Pull complete
638fd9704285: Pull complete
61f7f4f722fb: Pull complete
Status: Downloaded newer image for debian:latest
Hello World

這個簡單的命令背後發生了什麼?讓我分析一下:

  1. 我們使用 docker run 命令,這是啟動容器的基本指令
  2. debian 是我們想要使用的映像檔名稱 — 這是一個精簡版的 Debian Linux 發行版
  3. 由於本機上沒有 Debian 映像檔,Docker 自動從 Docker Hub 下載最新版本
  4. 下載完成後,Docker 將映像檔轉換為執行中的容器
  5. 容器執行我們指定的命令 — echo "Hello World" — 並顯示結果
  6. 命令執行完畢後,容器自動停止

如果再次執行相同的命令,Docker 會立即啟動容器而不需要下載,整個過程只需約一秒鐘。這個速度令人驚嘆,特別是與傳統虛擬機器相比,後者可能需要數十秒甚至數分鐘才能啟動。

與容器互動

我們可以請求 Docker 提供一個容器內的 shell 環境:

$ docker run -i -t debian /bin/bash
root@622ac5689680:/# echo "Hello from Container-land!"
Hello from Container-land!
root@622ac5689680:/# exit
exit

這個命令會在容器內給我們一個新的命令提示符,類別似於 SSH 連線到遠端機器。這裡的 -i-t 旗標告訴 Docker 我們需要一個互動式工作階段並附加一個終端。/bin/bash 命令則提供了一個 bash shell。當你結束 shell 時,容器會停止 — 容器只在其主要程式執行期間保持執行。

基本 Docker 命令

讓我們透過啟動一個容器並觀察各種命令的效果來進一步瞭解 Docker。首先,啟動一個新容器,並使用 -h 旗標給它設定一個新的主機名:

$ docker run -h CONTAINER -i -t debian /bin/bash
root@CONTAINER:/#

現在,讓我們故意「破壞」這個容器:

root@CONTAINER:/# mv /bin /basket
root@CONTAINER:/# ls
bash: ls: command not found

我們將 /bin 目錄移動到了其他位置,使容器暫時無法使用。在處理這個容器之前,讓我們看 psinspectdiff 命令能告訴我們什麼。在主機上開啟一個新終端(保持容器工作階段執行),並嘗試執行 docker ps

$ docker ps
CONTAINER ID IMAGE COMMAND ... NAMES
00723499fdbf debian "/bin/bash" ... stupefied_turing

這向我們展示了所有當前執行的容器的一些詳細資訊。注意 Docker 已經給容器分配了一個可讀的名稱,在這個例子中是 “stupefied_turing”。

我們可以使用 docker inspect 命令取得有關特定容器的更多資訊:

$ docker inspect stupefied_turing
[
{
"Id": "00723499fdbfe55c14565dc53d61452519deac72e18a8a6fd7b371ccb75f1d91",
"Created": "2015-09-14T09:47:20.2064793Z",
"Path": "/bin/bash",
"Args": [],
"State": {
"Running": true,
...

這裡有大量有價值的輸出,但不容易解析。我們可以使用 grep--format 引數(接受 Go 範本)來過濾我們感興趣的資訊:

$ docker inspect stupefied_turing | grep IPAddress
"IPAddress": "172.17.0.4",
"SecondaryIPAddresses": null,

$ docker inspect --format {{.NetworkSettings.IPAddress}} stupefied_turing
172.17.0.4

兩種方式都能取得容器的 IP 地址。現在,讓我們看 docker diff 命令:

$ docker diff stupefied_turing
C /.wh..wh.plnk
A /.wh..wh.plnk/101.715484
D /bin
A /basket
A /basket/bash
A /basket/cat
A /basket/chacl
A /basket/chgrp
A /basket/chmod
...

這裡我們看到的是執行中容器中已更改的檔案列表:/bin 被刪除,/basket 及其中的所有內容被新增,以及一些與儲存驅動程式相關的檔案被建立。Docker 使用聯合檔案系統(UFS)來處理容器,它允許多個檔案系統以階層方式掛載並顯示為單個檔案系統。映像檔的檔案系統被掛載為只讀層,而對執行容器的任何更改都會寫入掛載在此之上的讀寫層。因此,Docker 只需檢視最頂層的讀寫層即可找到對執行系統所做的更改。

在結束這個容器之前,我想向你展示 docker logs 命令。如果你使用容器的名稱執行這個命令,你將獲得容器內部發生的所有事情的列表:

$ docker logs stupefied_turing
root@CONTRAINER:/# mv /bin /basket
root@CONTRAINER:/# ls
bash: ls: command not found

現在我們已經完成了對這個「壞掉」的容器的研究,讓我們結束 shell:

root@CONTRAINER:/# exit
exit
$

這也會停止容器,因為 shell 是唯一執行的程式。如果你執行 docker ps,應該看不到任何執行的容器。

然而,這並不能告訴我們全部情況。如果你輸入 docker ps -a,你將獲得所有容器的列表,包括已停止的容器(官方稱為「結束的容器」)。結束的容器可以透過發出 docker start 命令重新啟動(雖然在這個例子中,由於我們破壞了路徑,所以無法啟動它)。要刪除容器,使用 docker rm 命令:

$ docker rm stupefied_turing
stupefied_turing

清理停止的容器

如果你想刪除所有已停止的容器,可以使用以下命令:

$ docker rm -v $(docker ps -aq -f status=exited)

這個命令取得所有已停止容器的 ID 並將其刪除。由於這是一個常見操作,你可能想把它放入 shell 指令碼或別名中。注意,-v 引數將刪除任何沒有被其他容器參照的 Docker 管理卷。

你也可以在 docker run 中加入 --rm 旗標來避免累積已停止的容器,這將在容器結束時刪除容器及其相關檔案系統。

建立自己的容器映像檔

現在讓我們來建立一個實用的容器。我們將製作一個 Dockerized cowsay 應用程式,這是一個能夠產生 ASCII 藝術的小工具。首先啟動一個容器並安裝一些套件:

$ docker run -it --name cowsay --hostname cowsay debian bash
root@cowsay:/# apt-get update
...
Reading package lists... Done
root@cowsay:/# apt-get install -y cowsay fortune
...
root@cowsay:/#

讓我們試看效果:

root@cowsay:/# /usr/games/fortune | /usr/games/cowsay
_____________________________________
/ Writing is easy; all you do is sit \
| staring at the blank sheet of paper |
| until drops of blood form on your |
| forehead. |
| |
||----w |
|| ||

太棒了!現在讓我們保留這個容器。要將其轉換為映像檔,我們可以使用 docker commit 命令,不管容器是執行中還是已停止:

root@cowsay:/# exit
exit
$ docker commit cowsay test/cowsayimage
d1795abbc71e14db39d24628ab335c58b0b45458060d1973af7acf113a0ce61d

回傳值是我們映像檔的唯一 ID。現在我們有了一個安裝了 cowsay 的映像檔,可以執行它:

$ docker run test/cowsayimage /usr/games/cowsay "Moo"
______
< Moo >
------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

使用 Dockerfile 建立映像檔

雖然 docker commit 是建立映像檔的一種方式,但更好的方法是使用 Dockerfile。Dockerfile 是一個包含構建映像檔所需指令的文字檔案。

建立一個名為 Dockerfile 的檔案,內容如下:

FROM debian:wheezy
RUN apt-get update && apt-get install -y cowsay fortune

FROM 指令指定基礎映像檔,必須是第一條非註解指令。RUN 指令指定要在映像檔內執行的 shell 命令。在這種情況下,我們只是像之前一樣安裝 cowsay 和 fortune。

現在,我們可以在相同目錄下執行 docker build 命令來構建映像檔:

$ ls
Dockerfile
$ docker build -t test/cowsay-dockerfile .
Sending build context to Docker daemon 2.048 kB
Step 0 : FROM debian:wheezy
---> f6fab3b798be
Step 1 : RUN apt-get update && apt-get install -y cowsay fortune
---> Running in 29c7bd4b0adc
...
Setting up cowsay (3.03+dfsg1-4) ...
---> dd66dc5a99bd
Removing intermediate container 29c7bd4b0adc
Successfully built dd66dc5a99bd

然後,我們可以像之前一樣執行這個映像檔:

$ docker run test/cowsay-dockerfile /usr/games/cowsay "Moo"

映像檔、容器和聯合檔案系統

為了理解映像檔和容器之間的關係,我們需要解釋一個使 Docker 成為可能的關鍵技術 — 聯合檔案系統(UFS,有時簡稱為 union mount)。聯合檔案系統允許多個檔案系統疊加,對使用者顯示為單個檔案系統。資料夾可能包含來自多個檔案系統的檔案,但如果兩個檔案具有完全相同的路徑,則最後掛載的檔案將隱藏任何先前的檔案。

Docker 支援幾種不同的 UFS 實作,包括 AUFS、Overlay、devicemapper、BTRFS 和 ZFS。使用哪種實作取決於系統,可以透過執行 docker info 檢視,其中在「Storage Driver」下列出。可以更改檔案系統,但只有在你知道自己在做什麼並瞭解優缺點的情況下才建議這樣做。

Docker 映像檔由多個層組成。每一層都是一個只讀檔案系統。對於 Dockerfile 中的每條指令,都會建立一個層,並放在前一層之上。當映像檔透過 docker rundocker create 轉換為容器時,Docker 會在映像檔的頂部新增一個可寫層,允許容器修改檔案系統而不影響底層映像檔。

這種分層方法使 Docker 映像檔非常高效。當你從映像檔建立多個容器時,它們分享相同的底層,只有每個容器的可寫層是唯一的。這大減少了儲存需求,並使容器啟動速度極快。

在這篇文章中,我們探索了 Docker 的基礎知識,從執行第一個簡單容器到建立自己的自定義映像檔。我們瞭解了基本的 Docker 命令,如 runpsinspectdifflogsrm,以及如何使用 docker commit 和 Dockerfile 建立映像檔。我們還簡要討論了映像檔、容器和聯合檔案系統之間的關係,這是理解 Docker 如何工作的關鍵。