Docker 入门教程 - 2021 最新版(上)
?
容器化的概念很早就有了。2013 年?Docker 引擎的出現使應用程序容器化變得更加容易。
根據?Stack Overflow 開發者調查-2020,Docker是開發者?#1 最想要的平臺、#2 最喜歡的平臺,以及#3 最流行的平臺。
盡管 Docker 功能強大,但上手確并不容易。因此,本書將介紹從基礎知識到更高層次容器化的的所有內容。讀完整本書之后,你應該能夠:
-  容器化(幾乎)任何應用程序 
-  將自定義 Docker 鏡像上傳到在線倉庫 
-  使用 Docker Compose 處理多個容器 
前提
-  熟悉 Linux 終端操作 
-  熟悉 JavaScript(稍后的的演示項目用到了 JavaScript) 
目錄
-  容器化和 Docker 簡介 
-  怎樣安裝 Docker -  怎樣在 macOS 里安裝 Docker 
-  怎樣在 Windows 上安裝 Docker 
-  怎樣在 Linux 上安裝 Docker 
 
-  
-  初識 Docker - Docker 基本知識介紹 -  什么是容器? 
-  什么是 Docker 鏡像? 
-  什么是倉庫? 
-  Docker 架構概述 
-  全景圖 
 
-  
-  Docker 容器操作基礎知識 -  怎樣運行容器 
-  怎樣公開端口 
-  如何使用分離模式 
-  怎樣列表展示容器 
-  怎樣命名或者重命名一個容器 
-  怎樣停止或者殺死運行中的容器 
-  怎樣重新啟動容器 
-  怎樣創建而不運行容器 
-  怎樣移除掛起的容器 
-  怎樣以交互式模式運行容器 
-  怎樣在容器里執行命令 
-  如何處理可執行鏡像 
 
-  
-  Docker 鏡像操作基礎知識 -  如何創建 Docker 鏡像 
-  如何標記 Docker 鏡像 
-  如何刪除、列表展示鏡像 
-  理解 Docker 鏡像的分層 
-  怎樣從源碼構建 NGINX 
-  怎樣優化 Docker 鏡像 
-  擁抱 Alpine Linux 
-  怎樣創建可執行 Docker 鏡像 
-  怎樣在線共享 Docker 鏡像 
 
-  
-  怎樣容器化 JavaScript 應用 -  如何編寫開發 Dockerfile 
-  如何在 Docker 中使用綁定掛載 
-  如何在 Docker 中使用匿名卷 
-  如何在 Docker 中執行多階段構建 
-  如何忽略不必要的文件 
 
-  
-  Docker 中的網絡操作基礎知識 -  Docker 網絡基礎 
-  如何在 Docker 中創建用戶定義的橋接網絡 
-  如何在 Docker 中將容器連接到網絡 
-  如何在 Docker 中從網絡分離容器 
-  如何刪除 Docker 中的網絡 
 
-  
-  如何容器化多容器 JavaScript 應用程序 -  如何運行數據庫服務 
-  如何在 Docker 中使用命名卷 
-  如何從 Docker 中的容器訪問日志 
-  如何在 Docker 中創建網絡并連接數據庫服務 
-  如何編寫 Dockerfile 
-  如何在正在運行的容器中執行命令 
-  如何在 Docker 中編寫管理腳本 
 
-  
-  如何使用 Docker-Compose 組合項目 -  Docker Compose 基礎 
-  如何在 Docker Compose 中啟動服務 
-  如何在 Docker Compose 中列表展示服務 
-  如何在 Docker Compose 正在運行的服務中執行命令 
-  如何訪問 Docker Compose 中正在運行的服務日志 
-  如何在 Docker Compose 中停止服務 
-  如何在 Docker Compose 中編寫全棧應用程序 
 
-  
-  結論 
項目代碼
可以在這個倉庫中找到示例項目的代碼,歡迎 ?。
完整代碼在?containerized?分支。
貢獻
這本書是完全開源的,歡迎高質量的貢獻。可以在這個倉庫中找到完整的內容。
我通常先在本書的 GitBook 版本上進行更改和更新,然后在將其發布在 freeCodeCamp 專欄。你可以在這個鏈接中找到本書的最新編輯中版本。別忘了評分支持。
如果你正在尋找本書的完整穩定版本,那么 freeCodeCamp 是最好的選擇。如果你有所收獲,請分享給你的朋友。
不管閱讀本書的哪個版本,都不要忘記留下你的意見。歡迎提出建設性的批評。
容器化和 Docker 簡介
摘自 IBM,
容器化意味著封裝或打包軟件代碼及其所有依賴項,以便它可以在任何基礎架構上統一且一致地運行。
換句話說,容器化可以將軟件及其所有依賴項打包在一個自包含的軟件包中,這樣就可以省略麻煩的配置,直接運行。
舉一個現實生活的場景。假設你已經開發了一個很棒的圖書管理應用程序,該應用程序可以存儲所有圖書的信息,還可以為別人提供圖書借閱服務。
如果列出依賴項,如下所示:
-  Node.js 
-  Express.js 
-  SQLite3 
理論上應該是這樣。但是實際上還要搞定其他一些事情。Node.js 使用了?node-gyp?構建工具來構建原生加載項。根據官方存儲庫中的安裝說明,此構建工具需要 Python 2 或 3 和相應的的 C/C ++ 編譯器工具鏈。
考慮到所有這些因素,最終的依賴關系列表如下:
-  Node.js 
-  Express.js 
-  SQLite3 
-  Python 2 or 3 
-  C/C++ tool-chain 
無論使用什么平臺,安裝 Python 2 或 3 都非常簡單。在 Linux 上,設置 C/C ++ 工具鏈也非常容易,但是在 Windows 和 Mac 上,這是一項繁重的工作。
在 Windows 上,C++ 構建工具包有數 GB 之大,安裝需要花費相當長的時間。在 Mac 上,可以安裝龐大的 Xcode 應用程序,也可以安裝小巧的 Xcode 命令行工具包。
不管安裝了哪一種,它都可能會在 OS 更新時中斷。實際上,該問題非常普遍,甚至連官方倉庫都專門提供了?macOS Catalina 的安裝說明。
這里假設你已經解決了設置依賴項的所有麻煩,并且已經準備好開始。這是否意味著現在開始就一帆風順了?當然不是。
如果你使用 Linux 而同事使用 Windows 該怎么辦?現在,必須考慮如何處理這兩個不同的操作系統不一致的路徑,或諸如 nginx 之類的流行技術在 Windows 上未得到很好的優化的事實,以及諸如 Redis 之類的某些技術甚至都不是針對 Windows 預先構建的。
即使你完成了整個開發,如果負責管理服務器的人員部署流程搞錯了,該怎么辦?
所有這些問題都可以通過以下方式解決:
-  在與最終部署環境匹配的隔離環境(稱為容器)中開發和運行應用程序。 
-  將你的應用程序及其所有依賴項和必要的部署配置放入一個文件(稱為鏡像)中。 
-  并通過具有適當授權的任何人都可以訪問的中央服務器(稱為倉庫)共享該鏡像。 
然后,你的同事就可以從倉庫中下載鏡像,可以在沒有平臺沖突的隔離環境中運行應用,甚至可以直接在服務器上進行部署,因為該鏡像也可以進行生產環境配置。
這就是容器化背后的想法:將應用程序放在一個獨立的程序包中,使其在各種環境中都可移植且可回溯。
現在的問題是:Docker 在這里扮演什么角色?
正如我之前講的,容器化是一種將一切統一放入盒子中來解決軟件開發過程中的問題的思想。
這個想法有很多實現。Docker 就是這樣的實現。這是一個開放源代碼的容器化平臺,可讓你對應用程序進行容器化,使用公共或私有倉庫共享它們,也可以編排它們。
目前,Docker 并不是市場上唯一的容器化工具,卻是最受歡迎的容器化工具。我喜歡的另一個容器化引擎是 Red Hat 開發的 Podman。其他工具,例如 Google 的 Kaniko,CoreOS 的 rkt 都很棒,但和 Docker 還是有差距。
此外,如果你想了解容器的歷史,可以閱讀?A Brief History of Containers: From the 1970s Till Now,它描述了該技術的很多重要節點。
怎樣安裝 Docker
Docker 的安裝因使用的操作系統而異。但這整個過程都非常簡單。
Docker可在 Mac、Windows 和 Linux 這三個主要平臺上完美運行。在這三者中,在 Mac 上的安裝過程是最簡單的,因此我們從這里開始。
怎樣在 macOS 里安裝 Docker
在 Mac 上,要做的就是跳轉到官方的下載頁面,然后單擊_Download for Mac(stable)_按鈕。
你會看到一個常規的?Apple Disk Image?文件,在該文件的內有 Docker 應用程序。所要做的就是將文件拖放到 Applications 目錄中。
只需雙擊應用程序圖標即可啟動 Docker。應用程序啟動后,將看到 Docker 圖標出現在菜單欄上。
現在,打開終端并執行?docker --version?和?docker-compose --version?以驗證是否安裝成功。
怎樣在 Windows 上安裝 Docker
在 Windows 上,步驟幾乎相同,當然還需要執行一些額外的操作。安裝步驟如下:
跳轉到此站點,然后按照說明在 Windows 10 上安裝 WSL2。
然后跳轉到官方下載頁面?并單擊?Download for Windows(stable)?按鈕。
雙擊下載的安裝程序,然后使用默認設置進行安裝。
安裝完成后,從開始菜單或桌面啟動?Docker Desktop。Docker 圖標應顯示在任務欄上。
現在,打開 Ubuntu 或從 Microsoft Store 安裝的任何發行版。執行?docker --version?和?docker-compose --version?命令以確保安裝成功。
也可以從常規命令提示符或 PowerShell 訪問 Docker,只是我更喜歡使用 WSL2。
怎樣在 Linux 上安裝 Docker
在 Linux 上安裝 Docker 的過程有所不同,具體操作取決于你所使用的發行版,它們之間差異可能更大。但老實說,安裝與其他兩個平臺一樣容易(如果不能算更容易的話)。
Windows 或 Mac 上的 Docker Desktop 軟件包是一系列工具的集合,例如Docker Engine、Docker Compose、Docker Dashboard、Kubernetes?和其他一些好東西。
但是,在 Linux 上,沒有得到這樣的捆綁包。可以手動安裝所需的所有必要工具。不同發行版的安裝過程如下:
-  如果你使用的是 Ubuntu,則可以遵循官方文檔中的在 Ubuntu 上安裝 Docker 引擎部分。 
-  對于其他發行版,官方文檔中提供了?不同發行版的安裝指南。 -  在 Debian上安裝 Docker Engine 
-  在 Fedora 上安裝 Docker Engine 
-  在 CentOS 上安裝 Docker Engine 
 
-  
-  如果你使用的發行版未在文檔中列出,則可以參考從二進制文件安裝 Docker 引擎指南。 
-  無論參考什么程序,都必須完成一些非常重要的?Linux 的安裝后續步驟。 
-  完成 docker 安裝后,必須安裝另一個名為 Docker Compose 的工具。可以參考官方文檔中的?Install Docker Compose?指南。 
安裝完成后,打開終端并執行?docker --version?和?docker-compose --version?以確保安裝成功。
盡管無論使用哪個平臺,Docker 的性能都很好,但與其他平臺相比,我更喜歡 Linux。在整本書中,我將使用?Ubuntu 20.10 或者 Fedora 33。
一開始就需要闡明的另一件事是,在整本書中,我不會使用任何 GUI 工具操作 Docker。
我在各個平臺用過很多不錯的 GUI 工具,但是介紹常見的 docker 命令是本書的主要目標之一。
初識 Docker - 介紹 Docker 基本知識
已經在計算機上啟動并運行了 Docker,現在該運行第一個容器了。打開終端并執行以下命令:
docker?run?hello-world#?Unable?to?find?image?'hello-world:latest'?locally #?latest:?Pulling?from?library/hello-world #?0e03bdcc26d7:?Pull?complete? #?Digest:?sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc #?Status:?Downloaded?newer?image?for?hello-world:latest #? #?Hello?from?Docker! #?This?message?shows?that?your?installation?appears?to?be?working?correctly. #? #?To?generate?this?message,?Docker?took?the?following?steps: #??1.?The?Docker?client?contacted?the?Docker?daemon. #??2.?The?Docker?daemon?pulled?the?"hello-world"?image?from?the?Docker?Hub. #?????(amd64) #??3.?The?Docker?daemon?created?a?new?container?from?that?image?which?runs?the #?????executable?that?produces?the?output?you?are?currently?reading. #??4.?The?Docker?daemon?streamed?that?output?to?the?Docker?client,?which?sent?it #?????to?your?terminal. # #?To?try?something?more?ambitious,?you?can?run?an?Ubuntu?container?with: #??$?docker?run?-it?ubuntu?bash #? #?Share?images,?automate?workflows,?and?more?with?a?free?Docker?ID: #??https://hub.docker.com/ # #?For?more?examples?and?ideas,?visit: #??https://docs.docker.com/get-started/hello-world 鏡像是使用 Docker 進行最小化容器化的一個示例。它有一個從 hello.c 文件編譯的程序,負責打印出終端看到的消息。
現在,在終端中,可以使用?docker ps -a?命令查看當前正在運行或過去運行的所有容器:
docker?ps?-a#?CONTAINER?ID????????IMAGE???????????????COMMAND?????????????CREATED?????????????STATUS?????????????????????PORTS???????????????NAMES #?128ec8ceab71????????hello-world?????????"/hello"????????????14?seconds?ago??????Exited?(0)?13?seconds?ago??????????????????????exciting_chebyshev在輸出中,使用?hello-world?鏡像運行了名為?exciting_chebyshev?的容器,其容器標識為?128ec8ceab71。它已經在?Exited (0) 13 seconds ago,其中?(0)?退出代碼表示在容器運行時未發生任何錯誤。
現在,為了了解背后發生的事情,必須熟悉 Docker 體系結構和三個非常基本的容器化概念,如下所示:
-  容器 
-  鏡像 
-  倉庫 
我已經按字母順序列出了這三個概念,并且將從列表中的第一個開始介紹。
什么是容器?
在容器化世界中,沒有什么比容器的概念更基礎的了。
官方 Docker?resources?網站說 -
容器是應用程序層的抽象,可以將代碼和依賴項打包在一起。容器不虛擬化整個物理機,僅虛擬化主機操作系統。
可以認為容器是下一代虛擬機。
就像虛擬機一樣,容器是與主機系統是彼此之間完全隔離的環境。它也比傳統虛擬機輕量得多,因此可以同時運行大量容器,而不會影響主機系統的性能。
容器和虛擬機實際上是虛擬化物理硬件的不同方法。兩者之間的主要區別是虛擬化方式。
虛擬機通常由稱為虛擬機監控器的程序創建和管理,例如 Oracle VM VirtualBox,VMware Workstation,KVM,Microsoft Hyper-V 等等。該虛擬機監控程序通常位于主機操作系統和虛擬機之間,充當通信介質。
每個虛擬機都有自己的 guest 操作系統,該操作系統與主機操作系統一樣消耗資源。
在虛擬機內部運行的應用程序與 guest 操作系統進行通信,該 guest 操作系統在與虛擬機監控器進行通信,后者隨后又與主機操作系統進行通信,以將必要的資源從物理基礎設施分配給正在運行的應用程序。
虛擬機內部運行的應用程序與物理基礎設施之間存在很長的通信鏈。在虛擬機內部運行的應用程序可能只擁有少量資源,因為 guest 操作系統會占用很大的開銷。
與虛擬機不同,容器以更智能的方式完成虛擬化工作。在容器內部沒有完整的 guest 操作系統,它只是通過容器運行時使用主機操作系統,同時保持隔離 – 就像傳統的虛擬機一樣。
容器運行時(即 Docker)位于容器和主機操作系統之間,而不是虛擬機監控器中。容器與容器運行時進行通信,容器運行時再與主機操作系統進行通信,以從物理基礎設施中獲取必要的資源。
由于消除了整個主機操作系統層,因此與傳統的虛擬機相比,容器的更輕量,資源占用更少。
為了說明這一點,請看下面的代碼片段:
uname?-a #?Linux?alpha-centauri?5.8.0-22-generic?#23-Ubuntu?SMP?Fri?Oct?9?00:34:40?UTC?2020?x86_64?x86_64?x86_64?GNU/Linuxdocker?run?alpine?uname?-a #?Linux?f08dbbe9199b?5.8.0-22-generic?#23-Ubuntu?SMP?Fri?Oct?9?00:34:40?UTC?2020?x86_64?Linux在上面的代碼片段中,在主機操作系統上執行了?uname -a?命令以打印出內核詳細信息。然后在下一行,我在運行 Alpine Linux 的容器內執行了相同的命令。
從輸出中可以看到,該容器確實正在使用主機操作系統中的內核。這證明了容器虛擬化主機操作系統而不是擁有自己的操作系統這一點。
如果你使用的是 Windows 計算機,則會發現所有容器都使用 WSL2 內核。發生這種情況是因為 WSL2 充當了 Windows 上 Docker 的后端。在 macOS 上,默認后端是在 ?HyperKit 虛擬機管理程序上運行的 VM。
什么是 Docker 鏡像?
鏡像是分層的自包含文件,充當創建容器的模板。它們就像容器的凍結只讀副本。鏡像可以通過倉庫進行共享。
過去,不同的容器引擎具有不同的鏡像格式。但是后來,開放式容器計劃(OCI)定義了容器鏡像的標準規范,該規范被主要的容器化引擎所遵循。這意味著使用 Docker 構建的映像可以與 Podman 等其他運行時一起使用,而不會有兼容性問題。
容器只是處于運行狀態的鏡像。當從互聯網上獲取鏡像并使用該鏡像運行容器時,實際上是在先前的只讀層之上創建了另一個臨時可寫層。
在本書的后續部分中,這一概念將變得更加清晰。但就目前而言,請記住,鏡像是分層只讀文件,其中保留著應用程序所需的狀態。
什么是倉庫?
已經了解了這個難題的兩個非常重要的部分,即?Containers?和?Images?。最后一個是?Registry。
鏡像倉庫是一個集中式的位置,可以在其中上傳鏡像,也可以下載其他人創建的鏡像。Docker Hub 是 Docker 的默認公共倉庫。另一個非常流行的鏡像倉庫是 Red Hat 的 Quay。
在本書中,我將使用 Docker Hub 作為首選倉庫。
可以免費在 Docker Hub 上共享任意數量的公共鏡像。供世界各地的人們下載免費使用。可在我的個人資料(fhsinchy)頁面上找到我上傳的鏡像。
除了 Docker Hub 或 Quay,還可以創建自己的鏡像倉庫來托管私有鏡像。計算機中還運行著一個本地倉庫,該倉庫緩存從遠程倉庫提取的鏡像。
Docker 架構概述
既然已經熟悉了有關容器化和 Docker 的大多數基本概念,那么現在是時候了解 Docker 作為軟件的架構了。
該引擎包括三個主要組件:
Docker 守護程序:?守護程序(dockerd)是一個始終在后臺運行并等待來自客戶端的命令的進程。守護程序能夠管理各種 Docker 對象。
Docker 客戶端:?客戶端(docker)是一個命令行界面程序,主要負責傳輸用戶發出的命令。
REST API:?REST API 充當守護程序和客戶端之間的橋梁。使用客戶端發出的任何命令都將通過 API 傳遞,最終到達守護程序。
根據官方文檔,
“ Docker 使用客戶端-服務器體系結構。Docker?client?與 Docker?daemon?對話,daemon 繁重地構建、運行和分發 Docker 容器”。
作為用戶,通常將使用客戶端組件執行命令。然后,客戶端使用 REST API 來訪問長期運行的守護程序并完成工作。
全景圖
好吧,說的夠多了。現在是時候了解剛剛學習的所有這些知識如何和諧地工作了。在深入解釋運行?docker run hello-world?命令時實際發生的情況之前,看一下下面的圖片:
該圖像是在官方文檔中找到的圖像的略微修改版本。執行命令時發生的事件如下:
執行?docker run hello-world?命令,其中?hello-world?是鏡像的名稱。
Docker 客戶端訪問守護程序,告訴它獲取?hello-world?鏡像并從中運行一個容器。
Docker 守護程序在本地倉庫中查找鏡像,并發現它不存在,所以在終端上打印?Unable to find image 'hello-world:latest' locally。
然后,守護程序訪問默認的公共倉庫 Docker Hub,拉取?hello-world?鏡像的最新副本,并在命令行中展示?Unable to find image 'hello-world:latest' locally。
Docker 守護程序根據新拉取的鏡像創建一個新容器。
最后,Docker 守護程序運行使用?hello-world?鏡像創建的容器,該鏡像在終端上輸出文本。
Docker 守護程序的默認行為是在 hub 中查找本地不存在的鏡像。但是,拉取了鏡像之后,它將保留在本地緩存中。因此,如果再次執行該命令,則在輸出中將看不到以下幾行:
Unable?to?find?image?'hello-world:latest'?locally latest:?Pulling?from?library/hello-world 0e03bdcc26d7:?Pull?complete Digest:?sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9 Status:?Downloaded?newer?image?for?hello-world:latest如果公共倉庫中有可用鏡像的更新版本,則守護程序將再次拉取該鏡像。那個?:latest?是一個標記。鏡像通常包含有意義的標記以指示版本或內部版本。稍后,將更詳細地介紹這一點。
Docker 容器操作基礎知識
在前面的部分中,已經了解了 Docker 的構建模塊,還使用?docker run?命令運行了一個容器。
在本節中,將詳細介紹容器的操作。容器操作是每天要執行的最常見的任務之一,因此,正確理解各種命令至關重要。
但是請記住,這并不是可以在 Docker 上執行的所有命令的詳盡列表。我只會介紹最常見的那些。當想知道某一命令的更多用法時,可以訪問 Docker 命令行的官方參考。
怎樣運行容器
之前,已經使用?docker run?來使用?hello-world?鏡像創建和啟動容器。此命令的通用語法如下:
docker?run?<image?name>盡管這是一個完全有效的命令,但是有一種更好的方式可以將命令分配給?docker?守護程序。
在版本?1.13?之前,Docker 僅具有前面提到的命令語法。后來,命令行經過了重構具有了以下語法:
docker?<object>?<command>?<options>使用以下語法:
-  object?表示將要操作的 Docker 對象的類型。這可以是?container、image、network?或者?volume?對象。 
-  command?表示守護程序要執行的任務,即?run?命令。 
-  options?可以是任何可以覆蓋命令默認行為的有效參數,例如端口映射的?--publish?選項。 
現在,遵循此語法,可以將?run?命令編寫如下:
docker?container?run?<image?name>image name?可以是在線倉庫或本地系統中的任何鏡像。例如,可以嘗試使用fhsinchy / hello-dock 鏡像運行容器。該鏡像包含一個簡單的 Vue.js應用程序,該應用程序在容器內部的端口 80 上運行。
請在終端上執行以下命令以使用此鏡像運行容器:
docker?container?run?--publish?8080:80?fhsinchy/hello-dock#?/docker-entrypoint.sh:?/docker-entrypoint.d/?is?not?empty,?will?attempt?to?perform?configuration #?/docker-entrypoint.sh:?Looking?for?shell?scripts?in?/docker-entrypoint.d/ #?/docker-entrypoint.sh:?Launching?/docker-entrypoint.d/10-listen-on-ipv6-by-default.sh #?10-listen-on-ipv6-by-default.sh:?Getting?the?checksum?of?/etc/nginx/conf.d/default.conf #?10-listen-on-ipv6-by-default.sh:?Enabled?listen?on?IPv6?in?/etc/nginx/conf.d/default.conf #?/docker-entrypoint.sh:?Launching?/docker-entrypoint.d/20-envsubst-on-templates.sh #?/docker-entrypoint.sh:?Configuration?complete;?ready?for?start?up該命令不言自明。唯一需要說明的部分是?--publish 8080:80?部分,將在下一個小節中進行說明。
怎樣公開端口
容器是隔離的環境。主機系統對容器內部發生的事情一無所知。因此,從外部無法訪問在容器內部運行的應用程序。
要允許從容器外部進行訪問,必須將容器內的相應端口發布到本地網絡上的端口。--publish?或?-p?選項的通用語法如下:
--publish?<host?port>:<container?port>在上一小節中編寫了?--publish 8080:80?時,這意味著發送到主機系統端口 8080 的任何請求都將轉發到容器內的端口 80。
現在要在瀏覽器上訪問該應用程序,只需訪問 ?http://127.0.0.1:8080。
可以在終端窗口按下?ctrl + c?組合鍵或關閉終端窗口來停止容器。
如何使用分離模式
run?命令的另一個非常流行的選項是?---detach?或?-d?選項。在上面的示例中,為了使容器繼續運行,必須將終端窗口保持打開狀態。關閉終端窗口會停止正在運行的容器。
這是因為,默認情況下,容器在前臺運行,并像從終端調用的任何其他普通程序一樣將其自身附加到終端。
為了覆蓋此行為并保持容器在后臺運行,可以在?run?命令中包含?--detach?選項,如下所示:
docker?container?run?--detach?--publish?8080:80?fhsinchy/hello-dock#?9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc與前面的示例不同,這次不會看到很多文字,而只獲得新創建的容器的 ID。
提供選項的順序并不重要。如果將?--publish?選項放在?--detach?選項之前,效果相同。
使用?run?命令時必須記住的一件事是鏡像名稱必須最后出現。如果在鏡像名稱后放置任何內容,則將其作為參數傳遞給容器入口點(在在容器內執行命令小節做了解釋),可能會導致意外情況。
怎樣列表展示容器
container ls?命令可用于列出當前正在運行的容器。執行以下命令:
docker?container?ls#?CONTAINER?ID????????IMAGE?????????????????COMMAND??????????????????CREATED?????????????STATUS??????????????PORTS??????????????????NAMES #?9f21cb777058????????fhsinchy/hello-dock???"/docker-entrypoint.…"???5?seconds?ago???????Up?5?seconds????????0.0.0.0:8080->80/tcp???gifted_sammet一個名為?gifted_sammet?的容器正在運行。它是在?5 seconds ago?前創建的,狀態為?Up 5 seconds,這表明自創建以來,該容器一直運行良好。
CONTAINER ID?為?9f21cb777058,這是完整容器 ID 的前 12 個字符。完整的容器 ID 是?9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc,該字符長 64 個字符。在上一節中?docker container run??命令行的輸的就是完整的容器 ID 。
列表的?PORTS?列下,本地網絡的端口 8080 指向容器內的端口 80。name?gifted_sammet?是由 Docker 生成的,可能與你的計算機的不同。
container ls?命令僅列出系統上當前正在運行的容器。為了列出過去運行的所有容器,可以使用?--all?或?-a?選項。
docker?container?ls?--all#?CONTAINER?ID????????IMAGE?????????????????COMMAND??????????????????CREATED?????????????STATUS?????????????????????PORTS??????????????????NAMES #?9f21cb777058????????fhsinchy/hello-dock???"/docker-entrypoint.…"???2?minutes?ago???????Up?2?minutes???????????????0.0.0.0:8080->80/tcp???gifted_sammet #?6cf52771dde1????????fhsinchy/hello-dock???"/docker-entrypoint.…"???3?minutes?ago???????Exited?(0)?3?minutes?ago??????????????????????????reverent_torvalds #?128ec8ceab71????????hello-world???????????"/hello"?????????????????4?minutes?ago???????Exited?(0)?4?minutes?ago??????????????????????????exciting_chebyshev如你所見,列表?reverent_torvalds?中的第二個容器是較早創建的,并以狀態代碼 0 退出,這表明在容器運行期間未產生任何錯誤。
怎樣命名或者重命名一個容器
默認情況下,每個容器都有兩個標識符。如下:
-  CONTAINER ID?- 64 個字符的隨機字符串。 
-  NAME?- 兩個隨機詞的組合,下劃線連接。 
基于這兩個隨機標識符來引用容器非常不方便。如果可以使用自定義的名稱來引用容器,那就太好了。
可以使用?--name?選項來命名容器。要使用名為?hello-dock-container?的?fhsinchy/hello-dock?鏡像運行另一個容器,可以執行以下命令:
docker?container?run?--detach?--publish?8888:80?--name?hello-dock-container?fhsinchy/hello-dock#?b1db06e400c4c5e81a93a64d30acc1bf821bed63af36cab5cdb95d25e114f5fb本地網絡上的 8080 端口被?gifted_sammet?容器(在上一小節中創建的容器)占用了。這就是為什么必須使用其他端口號(例如 8888)的原因。要進行驗證,執行?container ls?命令:
docker?container?ls#?CONTAINER?ID????????IMAGE?????????????????COMMAND??????????????????CREATED?????????????STATUS??????????????PORTS??????????????????NAMES #?b1db06e400c4????????fhsinchy/hello-dock???"/docker-entrypoint.…"???28?seconds?ago??????Up?26?seconds???????0.0.0.0:8888->80/tcp???hello-dock-container #?9f21cb777058????????fhsinchy/hello-dock???"/docker-entrypoint.…"???4?minutes?ago???????Up?4?minutes????????0.0.0.0:8080->80/tcp???gifted_sammet一個名為?hello-dock-container?的新容器已經啟動。
甚至可以使用?container rename?命令來重命名舊容器。該命令的語法如下:
docker?container?rename?<container?identifier>?<new?name>要將?gifted_sammet?容器重命名為?hello-dock-container-2,可以執行以下命令:
docker?container?rename?gifted_sammet?hello-dock-container-2該命令不會產生任何輸出,但是可以使用?container ls?命令來驗證是否已進行更改。rename?命令不僅適用于處于運行狀態的容器和還適用于處于停止狀態的容器。
怎樣停止或者殺死運行中的容器
可以通過簡單地關閉終端窗口或單擊?ctrl + c?來停止在前臺運行的容器。但是,不能以相同方式停止在后臺運行的容器。
有兩個命令可以完成此任務。第一個是?container stop?命令。該命令的通用語法如下:
docker?container?stop?<container?identifier>其中?container identifier?可以是容器的 ID 或名稱。
應該還記得上一節中啟動的容器。它仍在后臺運行。使用?docker container ls?獲取該容器的標識符(在本演示中,我將使用?hello-dock-container?容器)。現在執行以下命令來停止容器:
docker?container?stop?hello-dock-container#?hello-dock-container如果使用 name 作為標識符,則 name 將作為輸出返回。stop?命令通過發送信號SIGTERM?來正常關閉容器。如果容器在一定時間內沒有停止運行,則會發出?SIGKILL?信號,該信號會立即關閉容器。
如果要發送?SIGKILL?信號而不是?SIGTERM?信號,則可以改用?container kill?命令。container kill?命令遵循與?stop?命令相同的語法。
docker?container?kill?hello-dock-container-2#?hello-dock-container-2怎樣重新啟動容器
當我說重啟時,我指的如下是兩種情況:
-  重新啟動先前已停止或終止的容器。 
-  重新啟動正在運行的容器。 
正如上一小節中學到的,停止的容器保留在系統中。如果需要,可以重新啟動它們。container start??命令可用于啟動任何已停止或終止的容器。該命令的語法如下:
docker?container?start?<container?identifier>可以通過執行?container ls --all?命令來獲取所有容器的列表,然后尋找狀態為?Exited?的容器。
docker?container?ls?--all#?CONTAINER?ID????????IMAGE?????????????????COMMAND??????????????????CREATED?????????????STATUS????????????????????????PORTS???????????????NAMES #?b1db06e400c4????????fhsinchy/hello-dock???"/docker-entrypoint.…"???3?minutes?ago???????Exited?(0)?47?seconds?ago?????????????????????????hello-dock-container #?9f21cb777058????????fhsinchy/hello-dock???"/docker-entrypoint.…"???7?minutes?ago???????Exited?(137)?17?seconds?ago???????????????????????hello-dock-container-2 #?6cf52771dde1????????fhsinchy/hello-dock???"/docker-entrypoint.…"???7?minutes?ago???????Exited?(0)?7?minutes?ago??????????????????????????reverent_torvalds #?128ec8ceab71????????hello-world???????????"/hello"?????????????????9?minutes?ago???????Exited?(0)?9?minutes?ago??????????????????????????exciting_chebyshev現在要重新啟動?hello-dock-container?容器,可以執行以下命令:
docker?container?start?hello-dock-container#?hello-dock-container現在,可以使用?container ls?命令查看正在運行的容器列表,以確保該容器正在運行。
默認情況下,container start?命令以分離模式啟動容器,并保留之前進行的端口配置。因此,如果現在訪問?http://127.0.0.1:8080,應該能夠像以前一樣訪問?hello-dock?應用程序。
現在,在想重新啟動正在運行的容器,可以使用?container restart?命令。container restart?命令遵循與?container start?命令完全相同的語法。
docker?container?restart?hello-dock-container-2#?hello-dock-container-2這兩個命令之間的主要區別在于,container restart?命令嘗試停止目標容器,然后再次啟動它,而 start 命令只是啟動一個已經停止的容器。
在容器停止的情況下,兩個命令完全相同。但是如果容器正在運行,則必須使用container restart?命令。
怎樣創建而不運行容器
到目前為止,在本節中,已經使用?container run?命令啟動了容器,該命令實際上是兩個單獨命令的組合。這兩個命令如下:
-  container create?命令從給定的鏡像創建一個容器。 
-  container start?命令將啟動一個已經創建的容器。 
現在,要使用這兩個命令執行運行容器部分中顯示的演示,可以執行以下操作 :
docker?container?create?--publish?8080:80?fhsinchy/hello-dock#?2e7ef5098bab92f4536eb9a372d9b99ed852a9a816c341127399f51a6d053856docker?container?ls?--all#?CONTAINER?ID????????IMAGE?????????????????COMMAND??????????????????CREATED?????????????STATUS??????????????PORTS???????????????NAMES #?2e7ef5098bab????????fhsinchy/hello-dock???"/docker-entrypoint.…"???30?seconds?ago??????Created?????????????????????????????????hello-dock通過?container ls --all?命令的輸出可以明顯看出,已經使用?fhsinchy/hello-dock鏡像創建了一個名稱為?hello-dock?的容器。容器的?STATUS?目前處于?Created?狀態,并且鑒于其未運行,因此不使用 ?--all?選項就不會列出該容器。
一旦創建了容器,就可以使用?container start?命令來啟動它。
docker?container?start?hello-dock#?hello-dockdocker?container?ls#?CONTAINER?ID????????IMAGE?????????????????COMMAND??????????????????CREATED??????????????STATUS??????????????PORTS??????????????????NAMES #?2e7ef5098bab????????fhsinchy/hello-dock???"/docker-entrypoint.…"???About?a?minute?ago???Up?29?seconds???????0.0.0.0:8080->80/tcp???hello-dock容器?STATUS?已從?Created?更改為?Up 29 seconds,這表明容器現在處于運行狀態。端口配置也顯示在以前為空的?PORTS?列中。
盡管可以在大多數情況下使用?container run?命令,但本書稍后還會有一些情況要求使用?container create?命令。
怎樣移除掛起的容器
如你所見,已被停止或終止的容器仍保留在系統中。這些掛起的容器可能會占用空間或與較新的容器發生沖突。
可以使用?container rm?命令刪除停止的容器。通用語法如下:
docker?container?rm?<container?identifier>要找出哪些容器沒有運行,使用?container ls --all?命令并查找狀態為?Exited?的容器。
docker?container?ls?--all#?CONTAINER?ID????????IMAGE?????????????????COMMAND??????????????????CREATED?????????????STATUS??????????????????????PORTS??????????????????NAMES #?b1db06e400c4????????fhsinchy/hello-dock???"/docker-entrypoint.…"???6?minutes?ago???????Up?About?a?minute???????????0.0.0.0:8888->80/tcp???hello-dock-container #?9f21cb777058????????fhsinchy/hello-dock???"/docker-entrypoint.…"???10?minutes?ago??????Up?About?a?minute???????????0.0.0.0:8080->80/tcp???hello-dock-container-2 #?6cf52771dde1????????fhsinchy/hello-dock???"/docker-entrypoint.…"???10?minutes?ago??????Exited?(0)?10?minutes?ago??????????????????????????reverent_torvalds #?128ec8ceab71????????hello-world???????????"/hello"?????????????????12?minutes?ago??????Exited?(0)?12?minutes?ago??????????????????????????exciting_chebyshev從輸出中可以看到,ID為?6cf52771dde1?和?128ec8ceab71?的容器未運行。要刪除?6cf52771dde1,可以執行以下命令:
docker?container?rm?6cf52771dde1#?6cf52771dde1可以使用?container ls?命令檢查容器是否被刪除。也可以一次刪除多個容器,方法是將其標識符一個接一個地傳遞,每個標識符之間用空格隔開。
也可以使用?container prune?命令來一次性刪除所有掛起的容器。
可以使用?container ls --all?命令檢查容器列表,以確保已刪除了掛起的容器:
docker?container?ls?--all#?CONTAINER?ID????????IMAGE?????????????????COMMAND??????????????????CREATED?????????????STATUS??????????????PORTS??????????????????NAMES #?b1db06e400c4????????fhsinchy/hello-dock???"/docker-entrypoint.…"???8?minutes?ago???????Up?3?minutes????????0.0.0.0:8888->80/tcp???hello-dock-container #?9f21cb777058????????fhsinchy/hello-dock???"/docker-entrypoint.…"???12?minutes?ago??????Up?3?minutes????????0.0.0.0:8080->80/tcp???hello-dock-container-2如果按照本書的順序進行操作,則應該只在列表中看到?hello-dock-container?和?hello-dock-container-2。建議停止并刪除兩個容器,然后再繼續進行下一部分。
container run?和?container start?命令還有?--rm?選項,它們表示希望容器在停止后立即被移除。執行以下命令,使用?--rm?選項啟動另一個?hello-dock?容器:
docker?container?run?--rm?--detach?--publish?8888:80?--name?hello-dock-volatile?fhsinchy/hello-dock#?0d74e14091dc6262732bee226d95702c21894678efb4043663f7911c53fb79f3可以使用?container ls?命令來驗證容器是否正在運行:
docker?container?ls#?CONTAINER?ID???IMAGE?????????????????COMMAND??????????????????CREATED??????????????STATUS??????????????PORTS??????????????????NAMES #?0d74e14091dc???fhsinchy/hello-dock???"/docker-entrypoint.…"???About?a?minute?ago???Up?About?a?minute???0.0.0.0:8888->80/tcp???hello-dock-volatile現在,如果停止了容器,使用?container ls --all?命令再次檢查:
docker?container?stop?hello-dock-volatile#?hello-dock-volatiledocker?container?ls?--all#?CONTAINER?ID???IMAGE?????COMMAND???CREATED???STATUS????PORTS?????NAMES該容器已被自動刪除。從現在開始,我將對大多數容器使用?--rm?選項。不需要的地方我會明確提到。
怎樣以交互式模式運行容器
到目前為止,只運行了 hello-world 鏡像或 fhsinchy/hello-dock 鏡像。這些鏡像用于執行非交互式的簡單程序。
好吧,鏡像并不是那么簡單。鏡像可以將整個 Linux 發行版封裝在其中。
流行的發行版,例如 Ubuntu,Fedora 和 Debian 都在 hub 有官方的 Docker 鏡像。編程語言,例如 python、php、[go](https:// hub.docker.com/_/golang) 或類似 node 和 deno 都有其官方鏡像。
這些鏡像不但僅運行某些預配置的程序。還將它們配置為默認情況下運行的 shell 程序。在鏡像是操作系統的情況下,它可以是諸如?sh?或?bash?之類的東西,在竟像是編程語言或運行時的情況下,通常是它們的默認語言的 shell。
正如可能從以前的計算機中學到的一樣,shell 是交互式程序。被配置為運行這樣的程序的鏡像是交互式鏡像。這些鏡像需要在 ?container run??命令中傳遞特殊的?-it?選項。
例如,如果通過執行?docker container run ubuntu?使用?ubuntu?鏡像運行一個容器,將不會發生任何事情。但是,如果使用?-it?選項執行相同的命令,會直接進入到 Ubuntu 容器內的 bash 上。
docker?container?run?--rm?-it?ubuntu#?root@dbb1f56b9563:/#?cat?/etc/os-release #?NAME="Ubuntu" #?VERSION="20.04.1?LTS?(Focal?Fossa)" #?ID=ubuntu #?ID_LIKE=debian #?PRETTY_NAME="Ubuntu?20.04.1?LTS" #?VERSION_ID="20.04" #?HOME_URL="https://www.ubuntu.com/" #?SUPPORT_URL="https://help.ubuntu.com/" #?BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" #?PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" #?VERSION_CODENAME=focal #?UBUNTU_CODENAME=focal從 ?cat /etc/os-release??命令的輸出中可以看到,我確實正在與在 Ubuntu 容器中運行的 bash 進行交互。
-it?選項提供了與容器內的程序進行交互的場景。此選項實際上是將兩個單獨的選項混在一起。
-  選項 ?-i?或 ?--interactive?連接到容器的輸入流,以便可以將輸入發送到 bash。 
-  -t?或?--tty??選項可通過分配偽 tty 來格式化展示并提供類似本機終端的體驗。 
當想以交互方式運行容器時,可以使用?-it?選項。以交互式方式運行?node?鏡像,如下:
docker?container?run?-it?node#?Welcome?to?Node.js?v15.0.0. #?Type?".help"?for?more?information. #?>?['farhan',?'hasin',?'chowdhury'].map(name?=>?name.toUpperCase()) #?[?'FARHAN',?'HASIN',?'CHOWDHURY'?]任何有效的 JavaScript 代碼都可以在 node shell 中執行。除了輸入?-it,還可以輸入?--interactive --tty,效果一樣,只不過更冗長。
怎樣在容器里執行命令
在本書中初識 Docker 部分中,已經了解了在 Alpine Linux 容器內執行命令。 它是這樣的:
docker?run?alpine?uname?-a #?Linux?f08dbbe9199b?5.8.0-22-generic?#23-Ubuntu?SMP?Fri?Oct?9?00:34:40?UTC?2020?x86_64?Linux在此命令中,在 Alpine Linux 容器中執行了?uname -a?命令。像這樣的場景(要做的就是在特定的容器內執行特定的命令)非常常見。
假設想使用?base64?程序對字符串進行編碼。幾乎所有基于 Linux 或 Unix 的操作系統都可以使用此功能(但 Windows 則不可用)。
在這種情況下,可以使用 busybox 之類的鏡像快速啟動容器,然后執行命令。
使用?base64?編碼字符串的通用語法如下:
echo?-n?my-secret?|?base64#?bXktc2VjcmV0將命令傳遞到未運行的容器的通用語法如下:
docker?container?run?<image?name>?<command>要使用 busybox 鏡像執行 base64 ?編碼,可以執行以下命令:
docker?container?run?--rm?busybox?echo?-n?my-secret?|?base64#?bXktc2VjcmV0這里發生的是,在?container run?命令中,鏡像名稱后傳遞的任何內容都將傳遞到鏡像的默認入口里。
入口點就像是通往鏡像的網關。除可執行鏡像外的大多數鏡像(在使用可執行鏡像小節中說明)使用 shell 或?sh?作為默認入口點。因此,任何有效的 shell 命令都可以作為參數傳遞給它們。
如何處理可執行鏡像
在上一節中,我簡要提到了可執行鏡像。這些鏡像旨在表現得像可執行程序。
以的 rmbyext 項目為例。這是一個簡單的 Python 腳本,能夠遞歸刪除給定擴展名的文件。 要了解有關該項目的更多信息,可以查看倉庫。
如果同時安裝了 Git 和 Python,則可以通過執行以下命令來安裝此腳本:
pip?install?git+https://github.com/fhsinchy/rmbyext.git#egg=rmbyext假設的系統上已經正確設置了 Python,則該腳本應該可以在終端的任何位置使用。使用此腳本的通用語法如下:
rmbyext?<file?extension>要對其進行測試,請在一個空目錄下打開終端,并在其中創建具有不同擴展名的一些文件。可以使用?touch?命令來做到這一點。現在,計算機上有一個包含以下文件的目錄:
touch?a.pdf?b.pdf?c.txt?d.pdf?e.txtls#?a.pdf??b.pdf??c.txt??d.pdf??e.txt要從該目錄刪除所有?pdf?文件,可以執行以下命令:
rmbyext?pdf#?Removing:?PDF #?b.pdf #?a.pdf #?d.pdf該程序的可執行鏡像能夠將文件擴展名用作參數,并像?rmbyext?程序一樣刪除它們。
fhsinchy/rmbyext 鏡像的行為類似。該鏡像包含?rmbyext?腳本的副本,并配置為在容器內的目錄?/zone上運行該腳本。
現在的問題是容器與本地系統隔離,因此在容器內運行的?rmbyext?程序無法訪問本地文件系統。因此,如果可以通過某種方式將包含 pdf 文件的本地目錄映射到容器內的?/zone目錄,則容器應該可以訪問這些文件。
授予容器直接訪問本地文件系統的一種方法是使用綁定掛載。
綁定掛載可以在本地文件系統目錄(源)與容器內另一個目錄(目標)之間形成雙向數據綁定。這樣,在目標目錄中進行的任何更改都將在源目錄上生效,反之亦然。
讓我們看一下綁定掛載的實際應用。要使用此鏡像而不是程序本身刪除文件,可以執行以下命令:
docker?container?run?--rm?-v?$(pwd):/zone?fhsinchy/rmbyext?pdf#?Removing:?PDF #?b.pdf #?a.pdf #?d.pdf已經在命令中看到了?-v $(pwd):/zone??部分,你可能已經猜到了?-v??或?--volume?選項用于為容器創建綁定掛載。該選項可以使用三個以冒號(:)分隔的字段。該選項的通用語法如下:
--volume?<local?file?system?directory?absolute?path>:<container?file?system?directory?absolute?path>:<read?write?access>第三個字段是可選的,但必須傳遞本地目錄的絕對路徑和容器內目錄的絕對路徑。
在這里,源目錄是?/home/fhsinchy/the-zone。假設終端當前在目錄中,則?$(pwd)?將替換為包含先前提到的?.pdf?和?.txt?文件的?/home/fhsinchy/the-zone。
可以在command substitution here 上了解更多信息。
--volume?或 ?-v??選項對?container run?以及?container create?命令均有效。我們將在接下來的部分中更詳細地探討卷,因此,如果在這里不太了解它們,請不要擔心。
常規鏡像和可執行鏡像之間的區別在于,可執行鏡像的入口點設置為自定義程序而不是?sh,在本例中為?rmbyext?程序。正如在上一小節中所學到的那樣,在?container run命令中在鏡像名稱之后編寫的所有內容都將傳遞到鏡像的入口點。
所以最后,docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf?命令轉換為容器內的 ?rmbyext pdf?。可執行鏡像并不常見,但在某些情況下可能非常有用。
總結
以上是生活随笔為你收集整理的Docker 入门教程 - 2021 最新版(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Nature子刊 | 神经义肢技术:从感
- 下一篇: Windows 2008 R2 标准版
