【Linux】Docker入门
文章目錄
- Docker 入門篇
- 一、初始容器
- 1.Docker 的安裝
- 1.1 啟動容器
- 1.2 驗證容器是否安裝成功
- 2.Docker 的使用
- 2.1 查看容器
- 2.2 拉取鏡像
- 2.3 查看鏡像
- 2.4 運行容器
- 3.Docker 的架構
- 4.小結
- 二、容器的本質
- 1.容器到底是什么
- 2.為什么要隔離
- 3.與虛擬機的區別是什么
- 4.隔離是怎么實現的
- 5.小結
- 三、容器化的應用:會了這些你就是Docker高手
- 1.什么是容器化的應用
- 2.常用的鏡像操作有哪些
- 2.1 使用名字和 IMAGE ID 來刪除鏡像:
- 3.常用的容器操作有哪些
- 4.小結
- 四、創建容器鏡像:如何編寫正確、高效的Dockerfile
- 1.鏡像的內部機制是什么
- 2.Dockerfile 是什么
- 3.怎樣編寫正確、高效的 Dockerfile
- 4.docker build 是怎么工作的
- 5.小結
- 五、鏡像倉庫: Dokcer Hub
- 1.什么是鏡像倉庫(Registry)
- 2.什么是 Docker Hub
- 3.如何在 Docker Hub 上挑選鏡像
- 4.Docker Hub 上鏡像命名的規則是什么
- 5.該怎么上傳自己的鏡像
- 6.離線環境該怎么辦
- 7.小結
- 六、容器該如何與外界互聯互通
- 1.如何拷貝容器內的數據
- 2.如何共享主機上的文件
- 3.如何實現網絡互通
- 4.如何分配服務端口號
- 5.小結
- 七、玩轉Docker
- 1.容器技術要點回顧
- 2. 搭建私有鏡像倉庫
- 3.搭建 WordPress 網站
- 4.小結
Docker 入門篇
簡介:Docker是一個開源的引擎,可以輕松的為任何應用創建一個輕量級的、可移植的、自給自足的容器。開發者在筆記本上編譯測試通過的容器可以批量地在生產環境中部署,包括VMs(虛擬機)、bare metal、OpenStack 集群和其他的基礎應用平臺。
容器是一種沙盒技術。那什么是沙盒呢?沙盒就像一個裝著小貓的紙箱,把小貓“放”進去的技術。不同的小貓之間,因為有了紙箱的邊界,而不至于互相干擾,紙箱 A 中吃飯的小貓并不會打擾到紙箱 B 中睡覺的小貓;而被裝進紙箱的小貓,也可以方便地搬來搬去,你不用再去找它躲在哪里了!
一、初始容器
注意: 這里使用的系統是ubuntu
1.Docker 的安裝
注意:可以先運行docker version 看命令是否存在,不存在可以執行以下命令進行安裝
#安裝Docker Engine sudo apt install docker.io1.1 啟動容器
root@template:/# systemctl start docker注意: 如果注重安全,可以使用普通用戶進行操作,但必須做以下操作
sudo systemctl start docker #啟動docker服務 sudo usermod -aG docker ${USER} #當前用戶加入docker組第一個 systemctl start docker 是啟動 Docker 的后臺服務,第二個 usermod -aG 是把當前的用戶加入 Docker 的用戶組。這是因為操作 Docker 必須要有 root 權限,而直接使用 root 用戶不夠安全,加入 Docker 用戶組是一個比較好的選擇,這也是 Docker 官方推薦的做法。當然,如果只是為了圖省事,你也可以直接切換到 root 用戶來操作 Docker。上面的三條命令執行完之后,我們還需要退出系統(命令 exit ),再重新登錄一次,這樣才能讓修改用戶組的命令 usermod 生效。
1.2 驗證容器是否安裝成功
驗證 Docker 是否安裝成功了,使用的命令是 docker version 和 docker info。
# docker version 會輸出 Docker 客戶端和服務器各自的版本信息: root@template:/# docker version Client:Version: 20.10.21API version: 1.41Go version: go1.18.1Git commit: 20.10.21-0ubuntu1~20.04.1Built: Thu Jan 26 21:14:47 2023OS/Arch: linux/amd64Context: defaultExperimental: trueServer:Engine:Version: 20.10.21API version: 1.41 (minimum version 1.12)Go version: go1.18.1Git commit: 20.10.21-0ubuntu1~20.04.1Built: Thu Nov 17 20:19:30 2022OS/Arch: linux/amd64Experimental: falsecontainerd:Version: 1.6.12-0ubuntu1~20.04.1GitCommit: runc:Version: 1.1.4-0ubuntu1~20.04.1GitCommit: docker-init:Version: 0.19.0GitCommit:docker info 會顯示當前 Docker 系統相關的信息,例如 CPU、內存、容器數量、鏡像數量、容器運行時、存儲文件系統等等,這里我也摘錄了一部分:
root@template:/# docker info Client:Context: defaultDebug Mode: falseServer:Containers: 11Running: 8Paused: 0Stopped: 3Images: 13Server Version: 20.10.21Storage Driver: overlay2Backing Filesystem: xfsSupports d_type: trueNative Overlay Diff: trueuserxattr: falseLogging Driver: json-fileCgroup Driver: cgroupfsCgroup Version: 1Plugins:Volume: localNetwork: bridge host ipvlan macvlan null overlayLog: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslogSwarm: inactiveRuntimes: io.containerd.runtime.v1.linux runc io.containerd.runc.v2Default Runtime: runcInit Binary: docker-initcontainerd version: runc version: init version: Security Options:apparmorseccompProfile: defaultKernel Version: 5.4.0-144-genericOperating System: Ubuntu 20.04.5 LTSOSType: linuxArchitecture: x86_64CPUs: 2Total Memory: 3.84GiBName: templateID: 4LG5:PQRN:HJXS:4RJK:ROM5:JEL4:NVZI:FYWW:ZKPR:AOKA:4FU4:QIRPDocker Root Dir: /var/lib/dockerDebug Mode: falseRegistry: https://index.docker.io/v1/Labels:Experimental: falseInsecure Registries:127.0.0.0/8Registry Mirrors:https://reg-mirror.qiniu.com/Live Restore Enabled: falseWARNING: No swap limit support2.Docker 的使用
現在,我們已經有了可用的 Docker 運行環境,可以做一些操作了
首先,我們使用命令 docker ps,它會列出當前系統里運行的容器,就像我們在 Linux 系統里使用 ps 命令列出運行的進程一樣。
2.1 查看容器
root@template:/# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES注意: 所有的 Docker 操作都是這種形式:以 docker 開始,然后是一個具體的子命令,之前的 docker version 和 docker info 也遵循了這樣的規則。你還可以用 help 或者 --help 來獲取幫助信息,查看命令清單和更詳細的說明。
我們嘗試另一個非常重要的命令 docker pull ,從外部的鏡像倉庫(Registry)拉取一個 busybox 鏡像(image),你可以把它類比成是 Ubuntu 里的“apt install”下載軟件包:
2.2 拉取鏡像
docker pull busybox #拉取busybox鏡像 root@template:/# docker pull busybox Using default tag: latest latest: Pulling from library/busybox Digest: sha256:c118f538365369207c12e5794c3cbfb7b042d950af590ae6c287ede74f29b7d4 Status: Downloaded newer image for busybox:latest docker.io/library/busybox:latest2.3 查看鏡像
我們再執行命令 docker images ,它會列出當前 Docker 所存儲(本地)的所有鏡像:可以看到,命令會顯示有一個叫 busybox 的鏡像,鏡像的 ID 號是一串 16 進制數字,大小是 1.41MB。
root@template:/# docker images REPOSITORY TAG IMAGE ID CREATED SIZE busybox latest 2bc7edbc3cf2 4 weeks ago 1.4MB2.4 運行容器
現在,我們就要從這個鏡像啟動容器了,命令是 docker run ,執行 echo 輸出字符串
root@template:/# docker run busybox echo hello world hello world root@template:/#然后我們再用 docker ps 命令,加上一個參數 -a ,就可以看到這個已經運行完畢的容器:
root@template:/# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3fe3b3d75f72 busybox "echo hello world" 35 seconds ago Exited (0) 34 seconds ago nifty_brown3.Docker 的架構
這張圖精準地描述了 Docker Engine 的內部角色和工作流程,對學習研究非常有指導意義。
剛才敲的命令行 docker 實際上是一個客戶端 client ,它會與 Docker Engine 里的后臺服務 Docker daemon 通信,而鏡像則存儲在遠端的倉庫 Registry 里,客戶端并不能直接訪問鏡像倉庫。Docker client 可以通過 build、pull、run等命令向 Docker daemon 發送請求,而 Docker daemon 則是容器和鏡像的“大管家”,負責從遠端拉取鏡像、在本地存儲鏡像,還有從鏡像生成容器、管理容器等所有功能。所以,在 Docker Engine 里,真正干活的其實是默默運行在后臺的 Docker daemon,而我們實際操作的命令行工具“docker”只是個“傳聲筒”的角色。
Docker 官方還提供一個“hello-world”示例,可以為你展示 Docker client 到 Docker daemon 再到 Registry 的詳細工作流程,你只需要執行這樣一個命令:
docker run hello-world
4.小結
二、容器的本質
廣義上來說,容器技術是動態的容器、靜態的鏡像和遠端的倉庫這三者的組合。不過,“容器”這個術語作為容器技術里的核心概念,不僅是大多數初次接觸這個領域的人,即使是一些已經有使用經驗的人,想要準確地把握它們的內涵、本質都是比較困難的。
1.容器到底是什么
從字面上來看,容器就是 Container,一般把它形象地比喻成現實世界里的集裝箱,它也正好和 Docker 的現實含義相對應,因為碼頭工人(那只可愛的小鯨魚)就是不停地在搬運集裝箱。
集裝箱的作用是標準化封裝各種貨物,一旦打包完成之后,就可以從一個地方遷移到任意的其他地方。相比散裝形式而言,集裝箱隔離了箱內箱外兩個世界,保持了貨物的原始形態,避免了內外部相互干擾,極大地簡化了商品的存儲、運輸、管理等工作。再回到我們的計算機世界,容器也發揮著同樣的作用,不過它封裝的貨物是運行中的應用程序,也就是進程,同樣它也會把進程與外界隔離開,讓進程與外部系統互不影響。
我們還是來實際操作一下吧,來看看在容器里運行的進程是個什么樣子。
使用 docker pull 命令,拉取一個新的鏡像——操作系統 Alpine:
docker pull alpine使用 docker run 命令運行它的 Shell 程序:
docker run -it alpine sh注意我們在這里多加了一個 -it 參數,這樣我們就會暫時離開當前的 Ubuntu 操作系統,進入容器內部。
執行 cat /etc/os-release ,還有 ps 這兩個命令,最后再使用 exit 退出,看看容器里與容器外有什么不同:
root@template:/# docker run -it alpine sh / # / # cat /etc/os-release NAME="Alpine Linux" ID=alpine VERSION_ID=3.17.2 PRETTY_NAME="Alpine Linux v3.17" HOME_URL="https://alpinelinux.org/" BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues" / # / # ps -ef PID USER TIME COMMAND1 root 0:00 sh7 root 0:00 ps -ef / # exit就像這里所顯示的,在容器里查看系統信息,會發現已經不再是外面的 Ubuntu 系統了,而是變成了 Alpine Linux 3.15,使用 ps 命令也只會看到一個完全“干凈”的運行環境,除了 Shell(即 sh)沒有其他的進程存在。也就是說,在容器內部是一個全新的 Alpine 操作系統,在這里運行的應用程序完全看不到外面的 Ubuntu 系統,兩個系統被互相“隔離”了,就像是一個“世外桃源”。
我們可以在這個“世外桃源”做任意的事情,比如安裝應用、運行 Redis 服務等。但無論我們在容器里做什么,都不會影響外面的 Ubuntu 系統(當然不是絕對的)。
到這里,我們就可以得到一個初步的結論:容器,就是一個特殊的隔離環境,它能夠讓進程只看到這個環境里的有限信息,不能對外界環境施加影響。
那么,很自然地,我們會產生另外一個問題:為什么需要創建這樣的一個隔離環境,直接讓進程在系統里運行不好嗎?
2.為什么要隔離
相信因為這兩年疫情,你對“隔離”這個詞不會感覺到太陌生。為了防止疫情蔓延,我們需要建立方艙、定點醫院,把患病人群控制在特定的區域內,更進一步還會實施封閉小區、關停商場等行動。雖然這些措施帶來了一些不便,但都是為了整個社會更大范圍的正常運轉。
同樣的,在計算機世界里的隔離也是出于同樣的考慮,也就是系統安全。
對于 Linux 操作系統來說,一個不受任何限制的應用程序是十分危險的。這個進程能夠看到系統里所有的文件、所有的進程、所有的網絡流量,訪問內存里的任何數據,那么惡意程序很容易就會把系統搞癱瘓,正常程序也可能會因為無意的 Bug 導致信息泄漏或者其他安全事故。雖然 Linux 提供了用戶權限控制,能夠限制進程只訪問某些資源,但這個機制還是比較薄弱的,和真正的“隔離”需求相差得很遠。
而現在,使用容器技術,我們就可以讓應用程序運行在一個有嚴密防護的“沙盒”(Sandbox)環境之內,就好像是把進程請進了“隔離酒店”,它可以在這個環境里自由活動,但絕不允許“越界”,從而保證了容器外系統的安全。
另外,在計算機里有各種各樣的資源,CPU、內存、硬盤、網卡,雖然目前的高性能服務器都是幾十核 CPU、上百 GB 的內存、數 TB 的硬盤、萬兆網卡,但這些資源終究是有限的,而且考慮到成本,也不允許某個應用程序無限制地占用。
容器技術的另一個本領就是為應用程序加上資源隔離,在系統里切分出一部分資源,讓它只能使用指定的配額,比如只能使用一個 CPU,只能使用 1GB 內存等等,就好像在隔離酒店里保證一日三餐,但想要吃山珍海味那是不行的。這樣就可以避免容器內進程的過度系統消耗,充分利用計算機硬件,讓有限的資源能夠提供穩定可靠的服務。所以,雖然進程被“關”在了容器里,損失了一些自由,但卻保證了整個系統的安全。而且只要進程遵守隔離規定,不做什么出格的事情,也完全是可以正常運行的。
3.與虛擬機的區別是什么
你也許會說,這么看來,容器不過就是常見的“沙盒”技術中的一種,和虛擬機差不了多少,那么它與虛擬機的區別在哪里呢?又有什么樣的優勢呢?在我看來,其實容器和虛擬機面對的都是相同的問題,使用的也都是虛擬化技術,只是所在的層次不同,可以參考 Docker 官網上的兩張圖,把這兩者對比起來會更利于學習理解。
Docker 官網的圖示其實并不太準確,容器并不直接運行在 Docker 上,Docker 只是輔助建立隔離環境,讓容器基于 Linux 操作系統運行
首先,容器和虛擬機的目的都是隔離資源,保證系統安全,然后是盡量提高資源的利用率。
VMware等虛擬化軟件 創建虛擬機的時候,,它們能夠在宿主機系統里完整虛擬化出一套計算機硬件,在里面還能夠安裝任意的操作系統,這內外兩個系統也同樣是完全隔離,互不干擾。
而在數據中心的服務器上,虛擬機軟件(即圖中的 Hypervisor)同樣可以把一臺物理服務器虛擬成多臺邏輯服務器,這些邏輯服務器彼此獨立,可以按需分隔物理服務器的資源,為不同的用戶所使用。
從實現的角度來看,虛擬機虛擬化出來的是硬件,需要在上面再安裝一個操作系統后才能夠運行應用程序,而硬件虛擬化和操作系統都比較“重”,會消耗大量的 CPU、內存、硬盤等系統資源,但這些消耗其實并沒有帶來什么價值,屬于“重復勞動”和“無用功”,不過好處就是隔離程度非常高,每個虛擬機之間可以做到完全無干擾。
我們再來看容器(即圖中的 Docker),它直接利用了下層的計算機硬件和操作系統,因為比虛擬機少了一層,所以自然就會節約 CPU 和內存,顯得非常輕量級,能夠更高效地利用硬件資源。不過,因為多個容器共用操作系統內核,應用程序的隔離程度就沒有虛擬機那么高了。
運行效率,可以說是容器相比于虛擬機最大的優勢,在這個對比圖中就可以看到,同樣的系統資源,虛擬機只能跑 3 個應用,其他的資源都用來支持虛擬機運行了,而容器則能夠把這部分資源釋放出來,同時運行 6 個應用。
當然,這個對比圖只是一個形象的展示,不是嚴謹的數值比較,不過可以用手里現有的 VirtualBox/VMware 虛擬機與 Docker 容器做個簡單對比。
一個普通的 Ubuntu 虛擬機安裝完成之后,體積都是 GB 級別的,再安裝一些應用很容易就會上到 10GB,啟動的時間通常需要幾分鐘,我們的電腦上同時運行十來個虛擬機可能就是極限了。
而一個 Ubuntu 鏡像大小則只有幾十 MB,啟動起來更是非常快,基本上不超過一秒鐘,同時跑上百個容器也毫無問題。不過,虛擬機和容器這兩種技術也不是互相排斥的,它們完全可以結合起來使用,就像我們的課程里一樣,用虛擬機實現與宿主機的強隔離,然后在虛擬機里使用 Docker 容器來快速運行應用程序。
4.隔離是怎么實現的
虛擬機使用的是 Hypervisor(KVM、Xen 等),那么,容器是怎么實現和下層計算機硬件和操作系統交互的呢?為什么它會具有高效輕便的隔離特性呢?
其實奧秘就在于 Linux 操作系統內核之中,為資源隔離提供了三種技術:namespace、cgroup、chroot,雖然這三種技術的初衷并不是為了實現容器,但它們三個結合在一起就會發生奇妙的“化學反應”
namespace 是 2002 年從 Linux 2.4.19 開始出現的,和編程語言里的 namespace 有點類似,它可以創建出獨立的文件系統、主機名、進程號、網絡等資源空間,相當于給進程蓋了一間小板房,這樣就實現了系統全局資源和進程局部資源的隔離。
cgroup 是 2008 年從 Linux 2.6.24 開始出現的,它的全稱是 Linux Control Group,用來實現對進程的 CPU、內存等資源的優先級和配額限制,相當于給進程的小板房加了一個天花板。
chroot 的歷史則要比前面的 namespace、cgroup 要古老得多,早在 1979 年的 UNIX V7 就已經出現了,它可以更改進程的根目錄,也就是限制訪問文件系統,相當于給進程的小板房鋪上了地磚。
你看,綜合運用這三種技術,一個四四方方、具有完善的隔離特性的容器就此出現了,進程就可以搬進這個小房間,過它的“快樂生活”了。我覺得用魯迅先生的一句詩來描述這個情景最為恰當:躲進小樓成一統,管他冬夏與春秋。
5.小結
普通的進程 + namespace(一重枷鎖,能看到什么進程) + cgroup(二重枷鎖,能用多少資源,內存/磁盤。cpu等) + chroot(三重枷鎖,能看到什么文件)= 特殊的進程 = 容器
容器和虛擬機有著本質的區別,虛擬機是虛擬出一套軟硬件系統環境,我們的應用跑在虛擬機中,可以大致看作是跑在一個獨立的服務器中,而容器只是一個進程,他們存在本質上的區別;如果硬要說他們的相同點,那么只是在隔離性這個廣義的角度上,他們所做的事情是類似的。
三、容器化的應用:會了這些你就是Docker高手
容器技術中最核心的概念:容器,知道它就是一個系統中被隔離的特殊環境,進程可以在其中不受干擾地運行。我們也可以把這段描述再簡化一點:容器就是被隔離的進程。
1.什么是容器化的應用
之前運行容器的時候,顯然不是從零開始的,而是要先拉取一個“鏡像”(image),再從這個“鏡像”來啟動容器,那么,這個“鏡像”到底是什么東西呢?它又和“容器”有什么關系呢?
其實在其他場合中也曾經見到過“鏡像”這個詞,比如最常見的光盤鏡像,重裝電腦時使用的硬盤鏡像,還有虛擬機系統鏡像。這些“鏡像”都有一些相同點:只讀,不允許修改,以標準格式存儲了一系列的文件,然后在需要的時候再從中提取出數據運行起來。
容器技術里的鏡像也是同樣的道理。因為容器是由操作系統動態創建的,那么必然就可以用一種辦法把它的初始環境給固化下來,保存成一個靜態的文件,相當于是把容器給“拍扁”了,這樣就可以非常方便地存放、傳輸、版本化管理了。
如果還拿之前的“小板房”來做比喻的話,那么鏡像就可以說是一個“樣板間”,把運行進程所需要的文件系統、依賴庫、環境變量、啟動參數等所有信息打包整合到了一起。之后鏡像文件無論放在哪里,操作系統都能根據這個“樣板間”快速重建容器,應用程序看到的就會是一致的運行環境了。
從功能上來看,鏡像和常見的 tar、rpm、deb 等安裝包一樣,都打包了應用程序,但最大的不同點在于它里面不僅有基本的可執行文件,還有應用運行時的整個系統環境。這就讓鏡像具有了非常好的跨平臺便攜性和兼容性,能夠讓開發者在一個系統上開發(例如 Ubuntu),然后打包成鏡像,再去另一個系統上運行(例如 CentOS),完全不需要考慮環境依賴的問題,是一種更高級的應用打包方式。
docker pull busybox ,就是獲取了一個打包了 busybox 應用的鏡像,里面固化了 busybox 程序和它所需的完整運行環境。
docker run busybox echo hello world ,就是提取鏡像里的各種信息,運用 namespace、cgroup、chroot 技術創建出隔離環境,然后再運行 busybox 的 echo 命令,輸出 hello world 的字符串。
這兩個步驟,由于是基于標準的 Linux 系統調用和只讀的鏡像文件,所以,無論是在哪種操作系統上,或者是使用哪種容器實現技術,都會得到完全一致的結果。
推而廣之,任何應用都能夠用這種形式打包再分發后運行,這也是無數開發者夢寐以求的“一次編寫,到處運行(Build once, Run anywhere)”的至高境界。所以,所謂的“容器化的應用”,或者“應用的容器化”,就是指應用程序不再直接和操作系統打交道,而是封裝成鏡像,再交給容器環境去運行。
現在就知道了,鏡像就是靜態的應用容器,容器就是動態的應用鏡像,兩者互相依存,互相轉化,密不可分。
之前的那張 Docker 官方架構圖可以看到,在 Docker 里的核心處理對象就是鏡像(image)和容器(container):
好,理解了什么是容器化的應用,接下來再來學習怎么操縱容器化的應用。因為鏡像是容器運行的根本,先有鏡像才有容器,所以先來看看關于鏡像的一些常用命令。
2.常用的鏡像操作有哪些
在前面已經了解了兩個基本命令,docker pull 從遠端倉庫拉取鏡像,docker images 列出當前本地已有的鏡像。docker pull 的用法還是比較簡單的,和普通的下載非常像,不過我們需要知道鏡像的命名規則,這樣才能準確地獲取到我們想要的容器鏡像。
鏡像的完整名字由兩個部分組成,名字和標簽,中間用 : 連接起來。
名字表明了應用的身份,比如 busybox、Alpine、Nginx、Redis
等等。標簽(tag)則可以理解成是為了區分不同版本的應用而做的額外標記,任何字符串都可以,比如 3.15 是純數字的版本號、jammy
是項目代號、1.21-alpine
是版本號加操作系統名等等。其中有一個比較特殊的標簽叫“latest”,它是默認的標簽,如果只提供名字沒有附帶標簽,那么就會使用這個默認的“latest”標簽。
那么現在,就可以把名字和標簽組合起來,使用 docker pull 來拉取一些鏡像了:
docker pull alpine:3.15 docker pull ubuntu:jammy docker pull nginx:1.21-alpine docker pull nginx:alpine docker pull redis有了這些鏡像之后,再用 docker images 命令來看看它們的具體信息:
root@template:/# docker images REPOSITORY TAG IMAGE ID CREATED SIZE busybox latest bab98d58e29e 5 days ago 4.86MB redis latest f9c173b0f012 10 days ago 117MB ubuntu jammy 74f2314a03de 11 days ago 77.8MB 127.0.0.1:5000/nginx alpine 2bc7edbc3cf2 4 weeks ago 40.7MB nginx alpine 2bc7edbc3cf2 4 weeks ago 40.7MB alpine 3.15 5ce65d7b0fde 4 weeks ago 5.59MB alpine latest b2aa39c304c2 4 weeks ago 7.05MB nginx 1.21-alpine b1c3acb28882 9 months ago 23.4MB hello-world latest feb5d9fea6a5 17 months ago 13.3kB在這個列表里,你可以看到,REPOSITORY 列就是鏡像的名字,TAG 就是這個鏡像的標簽,那么第三列“IMAGE ID”又是什么意思呢?
它可以說是鏡像唯一的標識,就好像是身份證號一樣。比如這里我們可以用“ubuntu:jammy”來表示 Ubuntu 22.04 鏡像,同樣也可以用它的 ID“d4c2c……”來表示。
另外,截圖里的兩個鏡像“nginx:1.21-alpine”和“nginx:alpine”的 IMAGE ID 是一樣的,都是“a63aa……”。這其實也很好理解,這就像是人的身份證號碼是唯一的,但可以有大名、小名、昵稱、綽號,同一個鏡像也可以打上不同的標簽,這樣應用在不同的場合就更容易理解。
IMAGE ID 還有一個好處,因為它是十六進制形式且唯一,Docker 特意為它提供了“短路”操作,在本地使用鏡像的時候,我們不用像名字那樣要完全寫出來這一長串數字,通常只需要寫出前三位就能夠快速定位,在鏡像數量比較少的時候用兩位甚至一位數字也許就可以了。
來看另一個鏡像操作命令 docker rmi ,它用來刪除不再使用的鏡像,可以節約磁盤空間,注意命令 rmi ,實際上是“remove image”的簡寫。
2.1 使用名字和 IMAGE ID 來刪除鏡像:
docker rmi redis docker rmi d4c這里的第一個 rmi 刪除了 Redis 鏡像,因為沒有顯式寫出標簽,默認使用的就是“latest”。第二個 rmi 沒有給出名字,而是直接使用了 IMAGE ID 的前三位,也就是“d4c”,Docker 就會直接找到這個 ID 前綴的鏡像然后刪除。
Docker 里與鏡像相關的命令還有很多,不過以上的 docker pull、docker images、docker rmi 就是最常用的三個了,更多的在后續。
3.常用的容器操作有哪些
現在已經在本地存放了鏡像,就可以使用 docker run 命令把這些靜態的應用運行起來,變成動態的容器了。
基本的格式是“docker run 設置參數”,再跟上“鏡像名或 ID”,后面可能還會有附加的“運行命令”。
比如這個命令:
docker run -h srv alpine hostname -h srv 就是容器的運行參數(指定容器的主機名),alpine 是鏡像名,它后面的 hostname 表示要在容器里運行的“hostname”這個程序,輸出主機名。docker run 是最復雜的一個容器操作命令,有非常多的額外參數用來調整容器的運行狀態,你可以加上 --help 來看它的幫助信息,今天我只說幾個最常用的參數。-it 表示開啟一個交互式操作的 Shell,這樣可以直接進入容器內部,就好像是登錄虛擬機一樣。(它實際上是“-i”和“-t”兩個參數的組合形式)--name 可以為容器起一個名字,方便我們查看,不過它不是必須的,如果不用這個參數,Docker 會分配一個隨機的名字。下面來練習一下這三個參數,分別運行 Nginx、Redis 和 Ubuntu:
docker run -d nginx:alpine # 后臺運行Nginx docker run -d --name red_srv redis # 后臺運行Redis docker run -it --name ubuntu 2e6 sh # 使用IMAGE ID,登錄Ubuntu18.04因為第三個命令使用的是 -it 而不是 -d ,所以它會進入容器里的 Ubuntu 系統,我們需要另外開一個終端窗口,使用 docker ps 命令來查看容器的運行狀態:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 464e9c2226a4 redis "docker-entrypoint.s…" 42 seconds ago Up 41 seconds 6379/tcp red_srv fd344a63ab4b nginx:alpine "/docker-entrypoint.…" 48 seconds ago Up 46 seconds 80/tcp festive_sutherland可以看到,每一個容器也會有一個“CONTAINER ID”,它的作用和鏡像的“IMAGE ID”是一樣的,唯一標識了容器。
對于正在運行中的容器,我們可以使用docker exec 命令在里面執行另一個程序,效果和 docker run 很類似,但因為容器已經存在,所以不會創建新的容器。它最常見的用法是使用 -it 參數打開一個 Shell,從而進入容器內部,例如:
docker exec -it red_srv sh這樣就“登錄”進了 Redis 容器,可以很方便地查看服務的運行狀態或者日志。
運行中的容器還可以使用 docker stop 命令來強制停止,這里我們仍然可以使用容器名字,不過或許用“CONTAINER ID”的前三位數字會更加方便。
容器被停止后使用 docker ps 命令就看不到了,不過容器并沒有被徹底銷毀,可以使用 docker ps -a 命令查看系統里所有的容器,當然也包括已經停止運行的容器:
root@template:/# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 464e9c2226a4 redis "docker-entrypoint.s…" 3 minutes ago Up 3 minutes 6379/tcp red_srv fd344a63ab4b nginx:alpine "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 80/tcp festive_sutherland 00264fc0da74 redis "docker-entrypoint.s…" 5 minutes ago Exited (0) 3 minutes ago suspicious_banzai 30da924e0b9e busybox "sh" 6 minutes ago Exited (0) 6 minutes ago recursing_williamson 619b33d27020 alpine "sh" 51 minutes ago Exited (0) 20 minutes ago great_ride 7d23588a7734 hello-world "/hello" About an hour ago Exited (0) About an hour ago romantic_haibt 3fe3b3d75f72 busybox "echo hello world" About an hour ago Exited (0) About an hour ago nifty_brown這些停止運行的容器可以用 docker start 再次啟動運行,如果你確定不再需要它們,可以使用 docker rm 命令來徹底刪除。
注意,這個命令與 docker rmi 非常像,區別在于它沒有后面的字母“i”,所以只會刪除容器,不刪除鏡像。
下面就來運行 docker rm 命令,使用“CONTAINER ID”的前兩位數字來刪除這些容器:
docker rm ed d6 45執行刪除命令之后,再用 docker ps -a 查看列表就會發現這些容器已經徹底消失了。
你可能會感覺這樣的容器管理方式很麻煩,啟動后要 ps 看 ID 再刪除,如果稍微不注意,系統就會遺留非常多的“死”容器,占用系統資源,有沒有什么辦法能夠讓 Docker 自動刪除不需要的容器呢?
辦法當然有,就是在執行 docker run 命令的時候加上一個 --rm 參數,這就會告訴 Docker 不保存容器,只要運行完畢就自動清除,省去了我們手工管理容器的麻煩。
我們還是用剛才的 Nginx、Redis 和 Ubuntu 這三個容器來試驗一下,加上 --rm 參數(省略了 name 參數):
docker run -d --rm nginx:alpine docker run -d --rm redis docker run -it --rm 2e6 sh然后用 docker stop 停止容器,再用 docker ps -a ,就會發現不需要我們再手動執行 docker rm ,Docker 已經自動刪除了這三個容器。
4.小結
鏡像是容器的靜態形式,它打包了應用程序的所有運行依賴項,方便保存和傳輸。使用容器技術運行鏡像,就形成了動態的容器,由于鏡像只讀不可修改,所以應用程序的運行環境總是一致的。而容器化的應用就是指以鏡像的形式打包應用程序,然后在容器環境里從鏡像啟動容器。由于 Docker 的命令比較多,而且每個命令還有許多參數,一節課里很難把它們都詳細說清楚,希望你課下參考 Docker 自帶的幫助或者官網文檔
說一說你對容器鏡像的理解,它與 rpm、deb 安裝包有哪些不同和優缺點。
你覺得 docker run 和 docker exec 的區別在哪里,應該怎么使用它們?
1:容器鏡像比起這些安裝包的差別就在于通用,不同linux版本下的安裝包還不同。
2: run是針對容器本身啟動,而exec是進入了容器內部去跑命令,相當于進去操作系統跑應用。
四、創建容器鏡像:如何編寫正確、高效的Dockerfile
容器化的應用,也就是被打包成鏡像的應用程序,然后再用各種 Docker 命令來運行、管理它們。
這些鏡像是怎么創建出來的?我們能不能夠制作屬于自己的鏡像呢?
今天學習鏡像的內部機制,還有高效、正確地編寫 Dockerfile 制作容器鏡像的方法。
1.鏡像的內部機制是什么
現在你應該知道,鏡像就是一個打包文件,里面包含了應用程序還有它運行所依賴的環境,例如文件系統、環境變量、配置參數等等。
環境變量、配置參數這些東西還是比較簡單的,隨便用一個 manifest 清單就可以管理,真正麻煩的是文件系統。為了保證容器運行環境的一致性,鏡像必須把應用程序所在操作系統的根目錄,也就是 rootfs,都包含進來。
雖然這些文件里不包含系統內核(因為容器共享了宿主機的內核),但如果每個鏡像都重復做這樣的打包操作,仍然會導致大量的冗余。可以想象,如果有一千個鏡像,都基于 Ubuntu 系統打包,那么這些鏡像里就會重復一千次 Ubuntu 根目錄,對磁盤存儲、網絡傳輸都是很大的浪費。
很自然的,我們就會想到,應該把重復的部分抽取出來,只存放一份 Ubuntu 根目錄文件,然后讓這一千個鏡像以某種方式共享這部分數據。
這個思路,也正是容器鏡像的一個重大創新點:分層,術語叫“Layer”。
容器鏡像內部并不是一個平坦的結構,而是由許多的鏡像層組成的,每層都是只讀不可修改的一組文件,相同的層可以在鏡像之間共享,然后多個層像搭積木一樣堆疊起來,再使用一種叫“Union FS 聯合文件系統”的技術把它們合并在一起,就形成了容器最終看到的文件系統
拿大家都熟悉的千層糕做一個形象的比喻吧。
千層糕也是由很多層疊加在一起的,從最上面可以看到每層里面鑲嵌的葡萄干、核桃、杏仁、青絲等,每一層糕就相當于一個 Layer,干果就好比是 Layer 里的各個文件。但如果某兩層的同一個位置都有干果,也就是有文件同名,那么我們就只能看到上層的文件,而下層的就被屏蔽了。
你可以用命令 docker inspect 來查看鏡像的分層信息,比如 nginx:alpine 鏡像:
docker inspect nginx:alpine它的分層信息在“RootFS”部分:
通過這張截圖就可以看到,nginx:alpine 鏡像里一共有 7 個 Layer。
相信你現在也就明白,之前在使用 docker pull、docker rmi 等命令操作鏡像的時候,那些“奇怪”的輸出信息是什么了,其實就是鏡像里的各個 Layer。Docker 會檢查是否有重復的層,如果本地已經存在就不會重復下載,如果層被其他鏡像共享就不會刪除,這樣就可以節約磁盤和網絡成本。
2.Dockerfile 是什么
知道了容器鏡像的內部結構和基本原理,我們就可以來學習如何自己動手制作容器鏡像了,也就是自己打包應用。
在之前我們講容器的時候,曾經說過容器就是“小板房”,鏡像就是“樣板間”。那么,要造出這個“樣板間”,就必然要有一個“施工圖紙”,由它來規定如何建造地基、鋪設水電、開窗搭門等動作。這個“施工圖紙”就是“Dockerfile”。
比起容器、鏡像來說,Dockerfile 非常普通,它就是一個純文本,里面記錄了一系列的構建指令,比如選擇基礎鏡像、拷貝文件、運行腳本等等,每個指令都會生成一個 Layer,而 Docker 順序執行這個文件里的所有步驟,最后就會創建出一個新的鏡像出來。
我們來看一個最簡單的 Dockerfile 實例:
# Dockerfile.busybox FROM busybox # 選擇基礎鏡像 CMD echo "hello world" # 啟動容器時默認運行的命令這個文件里只有兩條指令。
第一條指令是 FROM,所有的 Dockerfile 都要從它開始,表示選擇構建使用的基礎鏡像,相當于“打地基”,這里我們使用的是 busybox。
第二條指令是 CMD,它指定 docker run 啟動容器時默認運行的命令,這里我們使用了 echo 命令,輸出“hello world”字符串。現在有了 Dockerfile 這張“施工圖紙”,我們就可以請出“施工隊”了,用 docker build 命令來創建出鏡像:
你需要特別注意命令的格式,用 -f 參數指定 Dockerfile 文件名,后面必須跟一個文件路徑,叫做“構建上下文”(build’s context),這里只是一個簡單的點號,表示當前路徑的意思。
接下來,你就會看到 Docker 會逐行地讀取并執行 Dockerfile 里的指令,依次創建鏡像層,再生成完整的鏡像。
新的鏡像暫時還沒有名字(用 docker images 會看到是 ),但我們可以直接使用“IMAGE ID”來查看或者運行:
docker inspect b61 docker run b613.怎樣編寫正確、高效的 Dockerfile
大概了解了 Dockerfile 之后,我再來講講編寫 Dockerfile 的一些常用指令和最佳實踐,幫你在今后的工作中把它寫好、用好。
首先因為構建鏡像的第一條指令必須是 FROM,所以基礎鏡像的選擇非常關鍵。如果關注的是鏡像的安全和大小,那么一般會選擇 Alpine;如果關注的是應用的運行穩定性,那么可能會選擇 Ubuntu、Debian、CentOS。
FROM alpine:3.15 # 選擇Alpine鏡像 FROM ubuntu:bionic # 選擇Ubuntu鏡像我們在本機上開發測試時會產生一些源碼、配置等文件,需要打包進鏡像里,這時可以使用 COPY 命令,它的用法和 Linux 的 cp 差不多,不過拷貝的源文件必須是“構建上下文”路徑里的,不能隨意指定文件。也就是說,如果要從本機向鏡像拷貝文件,就必須把這些文件放到一個專門的目錄,然后在 docker build 里指定“構建上下文”到這個目錄才行。
這里有兩個 COPY 命令示例,你可以看一下:
COPY ./a.txt /tmp/a.txt # 把構建上下文里的a.txt拷貝到鏡像的/tmp目錄 COPY /etc/hosts /tmp # 錯誤!不能使用構建上下文之外的文件接下來要說的就是 Dockerfile 里最重要的一個指令 RUN ,它可以執行任意的 Shell 命令,比如更新系統、安裝應用、下載文件、創建目錄、編譯程序等等,實現任意的鏡像構建步驟,非常靈活。
RUN 通常會是 Dockerfile 里最復雜的指令,會包含很多的 Shell 命令,但 Dockerfile 里一條指令只能是一行,所以有的 RUN 指令會在每行的末尾使用續行符 \,命令之間也會用 && 來連接,這樣保證在邏輯上是一行,就像下面這樣:
RUN apt-get update \&& apt-get install -y \build-essential \curl \make \unzip \&& cd /tmp \&& curl -fSL xxx.tar.gz -o xxx.tar.gz\&& tar xzf xxx.tar.gz \&& cd xxx \&& ./config \&& make \&& make clean有的時候在 Dockerfile 里寫這種超長的 RUN 指令很不美觀,而且一旦寫錯了,每次調試都要重新構建也很麻煩,所以你可以采用一種變通的技巧:把這些 Shell 命令集中到一個腳本文件里,用 COPY 命令拷貝進去再用 RUN 來執行:
COPY setup.sh /tmp/ # 拷貝腳本到/tmp目錄RUN cd /tmp && chmod +x setup.sh \ # 添加執行權限&& ./setup.sh && rm setup.sh # 運行腳本然后再刪除RUN 指令實際上就是 Shell 編程,如果你對它有所了解,就應該知道它有變量的概念,可以實現參數化運行,這在 Dockerfile 里也可以做到,需要使用兩個指令 ARG 和 ENV。
它們區別在于 ARG 創建的變量只在鏡像構建過程中可見,容器運行時不可見,而 ENV 創建的變量不僅能夠在構建鏡像的過程中使用,在容器運行時也能夠以環境變量的形式被應用程序使用。
下面是一個簡單的例子,使用 ARG 定義了基礎鏡像的名字(可以用在“FROM”指令里),使用 ENV 定義了兩個環境變量:
ARG IMAGE_BASE="node" ARG IMAGE_TAG="alpine"ENV PATH=$PATH:/tmp ENV DEBUG=OFF還有一個重要的指令是 EXPOSE,它用來聲明容器對外服務的端口號,對現在基于 Node.js、Tomcat、Nginx、Go 等開發的微服務系統來說非常有用:
EXPOSE 443 # 默認是tcp協議 EXPOSE 53/udp # 可以指定udp協議講了這些 Dockerfile 指令之后,我還要特別強調一下,因為每個指令都會生成一個鏡像層,所以 Dockerfile 里最好不要濫用指令,盡量精簡合并,否則太多的層會導致鏡像臃腫不堪。
4.docker build 是怎么工作的
- Dockerfile 必須要經過 docker build 才能生效,所以我們再來看看 docker build 的詳細用法。
- 剛才在構建鏡像的時候,你是否對“構建上下文”這個詞感到有些困惑呢?它到底是什么含義呢?
- 我覺得用 Docker 的官方架構圖來理解會比較清楚(注意圖中與“docker build”關聯的虛線)。
- 因為命令行“docker”是一個簡單的客戶端,真正的鏡像構建工作是由服務器端的“Docker daemon”來完成的,所以“docker”客戶端就只能把“構建上下文”目錄打包上傳(顯示信息 Sending build context to Docker daemon ),這樣服務器才能夠獲取本地的這些文件。
明白了這一點,你就會知道,“構建上下文”其實與 Dockerfile 并沒有直接的關系,它其實指定了要打包進鏡像的一些依賴文件。而 COPY 命令也只能使用基于“構建上下文”的相對路徑,因為“Docker daemon”看不到本地環境,只能看到打包上傳的那些文件。
但這個機制也會導致一些麻煩,如果目錄里有的文件(例如 readme/.git/.svn 等)不需要拷貝進鏡像,docker 也會一股腦地打包上傳,效率很低。
為了避免這種問題,你可以在“構建上下文”目錄里再建立一個 .dockerignore 文件,語法與 .gitignore 類似,排除那些不需要的文件。
下面是一個簡單的示例,表示不打包上傳后綴是“swp”“sh”的文件:
# docker ignore *.swp *.sh另外關于 Dockerfile,一般應該在命令行里使用 -f 來顯式指定。但如果省略這個參數,docker build 就會在當前目錄下找名字是 Dockerfile 的文件。所以,如果只有一個構建目標的話,文件直接叫“Dockerfile”是最省事的。
現在我們使用 docker build 應該就沒什么難點了,不過構建出來的鏡像只有“IMAGE ID”沒有名字,不是很方便。
為此你可以加上一個 -t 參數,也就是指定鏡像的標簽(tag),這樣 Docker 就會在構建完成后自動給鏡像添加名字。當然,名字必須要符合上節課里的命名規范,用 : 分隔名字和標簽,如果不提供標簽默認就是“latest”。
5.小結
重點理解容器鏡像是由多個只讀的 Layer 構成的,同一個 Layer 可以被不同的鏡像共享,減少了存儲和傳輸的成本。
如何編寫 Dockerfile 內容稍微多一點,我再簡單做個小結:
比如使用緩存、多階段構建等等,可以再參考 Docker官方文檔
當然還有兩個思考題:
答案:
1.容器最上一層是讀寫層,鏡像所有的層是只讀層。容器啟動后,Docker daemon會在容器的鏡像上添加一個讀寫層。
2.容器分層可以共享資源,節約空間,相同的內容只需要加載一份份到內存。
五、鏡像倉庫: Dokcer Hub
知道了如何創建自己的鏡像。那么鏡像文件應該如何管理呢,具體來說,應該如何存儲、檢索、分發、共享鏡像呢?不解決這些問題,我們的容器化應用還是無法順利地實施。
1.什么是鏡像倉庫(Registry)
還是來看 Docker 的官方架構圖(它真的非常重要):
圖里右邊的區域就是鏡像倉庫,術語叫 Registry,直譯就是“注冊中心”,意思是所有鏡像的 Repository 都在這里登記保管,就像是一個巨大的檔案館。
然后我們再來看左邊的“docker pull”,虛線顯示了它的工作流程,先到“Docker daemon”,再到 Registry,只有當 Registry 里存有鏡像才能真正把它下載到本地。
當然了,拉取鏡像只是鏡像倉庫最基本的一個功能,它還會提供更多的功能,比如上傳、查詢、刪除等等,是一個全面的鏡像管理服務站點。
你也可以把鏡像倉庫類比成手機上的應用商店,里面分門別類存放了許多容器化的應用,需要什么去找一下就行了。有了它,我們使用鏡像才能夠免除后顧之憂。
2.什么是 Docker Hub
不過,你有沒有注意到,在使用 docker pull 獲取鏡像的時候,我們并沒有明確地指定鏡像倉庫。在這種情況下,Docker 就會使用一個默認的鏡像倉庫,也就是大名鼎鼎的“Docker Hub”(https://hub.docker.com/)。
Docker Hub 是 Docker 公司搭建的官方 Registry 服務,創立于 2014 年 6 月,和 Docker 1.0 同時發布。它號稱是世界上最大的鏡像倉庫,和 GitHub 一樣,幾乎成為了容器世界的基礎設施。
Docker Hub 里面不僅有 Docker 自己打包的鏡像,而且還對公眾免費開放,任何人都可以上傳自己的作品。經過這 8 年的發展,Docker Hub 已經不再是一個單純的鏡像倉庫了,更應該說是一個豐富而繁榮的容器社區。
你可以看看下面的這張截圖,里面列出的都是下載量超過 10 億次(1 Billion)的最受歡迎的應用程序,比如 Nginx、MongoDB、Node.js、Redis、OpenJDK 等等。顯然,把這些容器化的應用引入到我們自己的系統里,就像是站在了巨人的肩膀上,一開始就會有一個高水平的起點。
但和 GitHub、App Store 一樣,面向所有人公開的 Docker Hub 也有一個不可避免的缺點,就是“良莠不齊”。
在 Docker Hub 搜索框里輸入關鍵字,比如 Nginx、MySQL,它立即就會給出幾百幾千個搜索結果,有點“亂花迷人眼”的感覺,這么多鏡像,應該如何挑選出最適合自己的呢?下面我就來說說自己在這方面的一些經驗。
3.如何在 Docker Hub 上挑選鏡像
首先,你應該知道,在 Docker Hub 上有官方鏡像、認證鏡像和非官方鏡像的區別。
官方鏡像是指 Docker 公司官方提供的高質量鏡像(https://github.com/docker-library/official-images),都經過了嚴格的漏洞掃描和安全檢測,支持 x86_64、arm64 等多種硬件架構,還具有清晰易讀的文檔,一般來說是我們構建鏡像的首選,也是我們編寫 Dockerfile 的最佳范例。
官方鏡像目前有大約 100 多個,基本上囊括了現在的各種流行技術,下面就是官方的 Nginx 鏡像網頁截圖:
你會看到,官方鏡像會有一個特殊的“Official image”的標記,這就表示這個鏡像經過了 Docker 公司的認證,有專門的團隊負責審核、發布和更新,質量上絕對可以放心。
第二類是認證鏡像,標記是“Verified publisher”,也就是認證發行商,比如 Bitnami、Rancher、Ubuntu 等。它們都是頗具規模的大公司,具有不遜于 Docker 公司的實力,所以就在 Docker Hub 上開了個認證賬號,發布自己打包的鏡像,有點類似我們微博上的“大 V”。
這些鏡像有公司背書,當然也很值得信賴,不過它們難免會帶上一些各自公司的“烙印”,比如 Bitnami 的鏡像就統一以“minideb”為基礎,靈活性上比 Docker 官方鏡像略差,有的時候也許會不符合我們的需求。
除了官方鏡像和認證鏡像,剩下的就都屬于非官方鏡像了,不過這里面也可以分出兩類。
第一類是“半官方”鏡像。因為成為“Verified publisher”是要給 Docker 公司交錢的,而很多公司不想花這筆“冤枉錢”,所以只在 Docker Hub 上開了公司賬號,但并不加入認證。
這里我以 OpenResty 為例,看一下它的 Docker Hub 頁面,可以看到顯示的是 OpenResty 官方發布,但并沒有經過 Docker 正式認證,所以難免就會存在一些風險,有被“冒名頂替”的可能,需要我們在使用的時候留心鑒別一下。不過一般來說,這種“半官方”鏡像也是比較可靠的。
第二類就是純粹的“民間”鏡像了,通常是個人上傳到 Docker Hub 的,因為條件所限,測試不完全甚至沒有測試,質量上難以得到保證,下載的時候需要小心謹慎。
除了查看鏡像是否為官方認證,我們還應該再結合其他的條件來判斷鏡像質量是否足夠好。做法和 GitHub 差不多,就是看它的下載量、星數、還有更新歷史,簡單來說就是“好評”數量。
一般來說下載量是最重要的參考依據,好的鏡像下載量通常都在百萬級別(超過 1M),而有的鏡像雖然也是官方認證,但缺乏維護,更新不及時,用的人很少,星數、下載數都寥寥無幾,那么還是應該選擇下載量最多的鏡像,通俗來說就是“隨大流”。
下面的這張截圖就是 OpenResty 在 Docker Hub 上的搜索結果。可以看到,有兩個認證發行商的鏡像(Bitnami、IBM),但下載量都很少,還有一個“民間”鏡像下載量雖然超過了 1M,但更新時間是 3 年前,所以毫無疑問,我們應該選擇排在第三位,但下載量超過 10M、有 360 多個星的“半官方”鏡像。
看了這么多 Docker Hub 上的鏡像,你一定注意到了,應用都是一樣的名字,比如都是 Nginx、Redis、OpenResty,該怎么區分不同作者打包出的鏡像呢?
如果你熟悉 GitHub,就會發現 Docker Hub 也使用了同樣的規則,就是**“用戶名 / 應用名”**的形式,比如 bitnami/nginx、ubuntu/nginx、rancher/nginx 等等。
所以,我們在使用 docker pull 下載這些非官方鏡像的時候,就必須把用戶名也帶上,否則默認就會使用官方鏡像:
docker pull bitnami/nginx docker pull ubuntu/nginx4.Docker Hub 上鏡像命名的規則是什么
確定了要使用的鏡像還不夠,因為鏡像還會有許多不同的版本,也就是“標簽”(tag)。
直接使用默認的“latest”雖然簡單方便,但在生產環境里是一種非常不負責任的做法,會導致版本不可控。所以我們還需要理解 Docker Hub 上標簽命名的含義,才能夠挑選出最適合我們自己的鏡像版本。
下面我就拿官方的 Redis 鏡像作為例子,解釋一下這些標簽都是什么意思。
通常來說,鏡像標簽的格式是應用的版本號加上操作系統。
版本號你應該比較了解吧,基本上都是主版本號 + 次版本號 + 補丁號的形式,有的還會在正式發布前出 rc 版(候選版本,release candidate)。而操作系統的情況略微復雜一些,因為各個 Linux 發行版的命名方式“花樣”太多了。
Alpine、CentOS 的命名比較簡單明了,就是數字的版本號,像這里的 alpine3.15 ,而 Ubuntu、Debian 則采用了代號的形式。比如 Ubuntu 18.04 是 bionic,Ubuntu 20.04 是 focal,Debian 9 是 stretch,Debian 10 是 buster,Debian 11 是 bullseye。
另外,有的標簽還會加上 **slim、fat,**來進一步表示這個鏡像的內容是經過精簡的,還是包含了較多的輔助工具。通常 slim 鏡像會比較小,運行效率高,而 fat 鏡像會比較大,適合用來開發調試。
下面我就列出幾個標簽的例子來說明一下。
- nginx:1.21.6-alpine,表示版本號是 1.21.6,基礎鏡像是最新的 Alpine。
- redis:7.0-rc-bullseye,表示版本號是 7.0 候選版,基礎鏡像是 Debian 11。
- node:17-buster-slim,表示版本號是 17,基礎鏡像是精簡的 Debian 10。該怎么上傳自己的鏡像
5.該怎么上傳自己的鏡像
現在,我想你應該對如何在 Docker Hub 上選擇鏡像有了比較全面的了解,那么接下來的問題就是,我們自己用 Dockerfile 創建的鏡像該如何上傳到 Docker Hub 上呢?
這件事其實一點也不難,只需要 4 個步驟就能完成。
第一步,你需要在 Docker Hub 上注冊一個用戶,這個就不必再多說了。(https://hub.docker.com/)
第二步,你需要在本機上使用 docker login 命令,用剛才注冊的用戶名和密碼認證身份登錄,像這里就用了我的用戶名“Lizhongyi”:
docker login -u Lizhongyi 然后輸入密碼
第三步很關鍵,需要使用 docker tag 命令,給鏡像改成帶用戶名的完整名字,表示鏡像是屬于這個用戶的。或者簡單一點,直接用 docker build -t 在創建鏡像的時候就起好名字。
這里我就用上次課里的鏡像“ngx-app”作為例子,給它改名成 Lizhongyi /ngx-app:1.0:
docker tag ngx-app Lizhongyi /ngx-app:1.0第四步,用 docker push 把這個鏡像推上去,我們的鏡像發布工作就大功告成了:
docker push chronolaw/ngx-app:1.0你還可以登錄 Docker Hub 網站驗證一下鏡像發布的效果,可以看到它會自動為我們生成一個頁面模板,里面還可以進一步豐富完善,比如添加描述信息、使用說明等等
現在你就可以把這個鏡像的名字(用戶名 / 應用名: 標簽)告訴你的同事,讓他去用 docker pull 下載部署了。
6.離線環境該怎么辦
使用 Docker Hub 來管理鏡像的確是非常方便,不過有一種場景下它卻是無法發揮作用,那就是企業內網的離線環境,連不上外網,自然也就不能使用 docker push、docker pull 來推送拉取鏡像了。
那這種情況有沒有解決辦法呢?
方法當然有,而且有很多。最佳的方法就是在內網環境里仿造 Docker Hub,創建一個自己的私有 Registry 服務,由它來管理我們的鏡像,就像我們自己搭建 GitLab 做版本管理一樣。
自建 Registry 已經有很多成熟的解決方案,比如 Docker Registry,還有 CNCF Harbor,不過使用它們還需要一些目前沒有講到的知識,步驟也有點繁瑣,所以我會在后續的課程里再介紹。
下面我講講存儲、分發鏡像的一種“笨”辦法,雖然比較“原始”,但簡單易行,可以作為臨時的應急手段。
Docker 提供了 save 和 load 這兩個鏡像歸檔命令,可以把鏡像導出成壓縮包,或者從壓縮包導入 Docker,而壓縮包是非常容易保管和傳輸的,可以聯機拷貝,FTP 共享,甚至存在 U 盤上隨身攜帶。
需要注意的是,這兩個命令默認使用標準流作為輸入輸出(為了方便 Linux 管道操作),所以一般會用 -o、-i 參數來使用文件的形式,例如:
docker save ngx-app:latest -o ngx.tar docker load -i ngx.tar7.小結
了解了 Docker Hub 的使用方法,整理一下要點方便加深理解:
六、容器該如何與外界互聯互通
在前面,我們已經學習了容器、鏡像、鏡像倉庫的概念和用法,也知道了應該如何創建鏡像,再以容器的形式啟動應用。
不過,用容器來運行“busybox”“hello world”這樣比較簡單的應用還好,如果是 Nginx、Redis、MySQL 這樣的后臺服務應用,因為它們運行在容器的“沙盒”里,完全與外界隔離,無法對外提供服務,也就失去了價值。這個時候,容器的隔離環境反而成為了一種負面特性。
所以,容器的這個“小板房”不應該是一個完全密閉的鐵屋子,而是應該給它開幾扇門窗,讓應用在“足不出戶”的情況下,也能夠與外界交換數據、互通有無,這樣“有限的隔離”才是我們真正所需要的運行環境。
那么今天,我就以 Docker 為例,來講講有哪些手段能夠在容器與外部系統之間溝通交流。
1.如何拷貝容器內的數據
我們先來看看 Docker 提供的 cp 命令,它可以在宿主機和容器之間拷貝文件,是最基本的一種數據交換功能。
試驗這個命令需要先用 docker run 啟動一個容器,就用 Redis 吧:
docker run -d --rm redis注意這里使用了 -d、--rm 兩個參數,表示運行在后臺,容器結束后自動刪除,然后使用 docker ps 命令可以看到 Redis 容器正在運行,容器 ID 前三位 是“062”。。
docker cp 的用法很簡單,很類似 Linux 的“cp”“scp”,指定源路徑(src path)和目標路徑(dest path)就可以了。如果源路徑是宿主機那么就是把文件拷貝進容器,如果源路徑是容器那么就是把文件拷貝出容器,注意需要用容器名或者容器 ID 來指明是哪個容器的路徑。
假設當前目錄下有一個“a.txt”的文件,現在我們要把它拷貝進 Redis 容器的“/tmp”目錄,如果使用容器 ID,命令就會是這樣:
docker cp a.txt 062:/tmp接下來我們可以使用 docker exec 命令,進入容器看看文件是否已經正確拷貝了:
docker exec -it 062 sh [root@Template ~]# docker exec -it 062 sh # ls /tmp a.txt可以看到,在“/tmp”目錄下,確實已經有了一個“a.txt”。
現在讓我們再來試驗一下從容器拷貝出文件,只需要把 docker cp 后面的兩個路徑調換一下位置:
docker cp 062:/tmp/a.txt ./b.txt這樣,在宿主機的當前目錄里,就會多出一個新的“b.txt”,也就是從容器里拿到的文件。
2.如何共享主機上的文件
docker cp 的用法模仿了操作系統的拷貝命令,偶爾一兩次的文件共享還可以應付,如果容器運行時經常有文件來往互通,這樣反復地拷來拷去就顯得很麻煩,也很容易出錯。
你也許會聯想到虛擬機有一種“共享目錄”的功能。它可以在宿主機上開一個目錄,然后把這個目錄“掛載”進虛擬機,這樣就實現了兩者共享同一個目錄,一邊對目錄里文件的操作另一邊立刻就能看到,沒有了數據拷貝,效率自然也會高很多。
沿用這個思路,容器也提供了這樣的共享宿主機目錄的功能,效果也和虛擬機幾乎一樣,用起來很方便,只需要在 docker run 命令啟動容器的時候使用 -v 參數就行,具體的格式是“宿主機路徑: 容器內路徑”。
我還是以 Redis 為例,啟動容器,使用 -v 參數把本機的“/tmp”目錄掛載到容器里的“/tmp”目錄,也就是說讓容器共享宿主機的“/tmp”目錄:
docker run -d --rm -v /tmp:/tmp redis然后我們再用 docker exec 進入容器,查看一下容器內的“/tmp”目錄,應該就可以看到文件與宿主機是完全一致的。
docker exec -it b5a sh # b5a是容器I你也可以在容器里的“/tmp”目錄下隨便做一些操作,比如刪除文件、建立新目錄等等,再回頭觀察一下宿主機,會發現修改會即時同步,這就表明容器和宿主機確實已經共享了這個目錄。
-v 參數掛載宿主機目錄的這個功能,對于我們日常開發測試工作來說非常有用,我們可以在不變動本機環境的前提下,使用鏡像安裝任意的應用,然后直接以容器來運行我們本地的源碼、腳本,非常方便。
這里我舉一個簡單的例子。比如我本機上只有 Python 2.7,但我想用 Python 3 開發,如果同時安裝 Python 2 和 Python 3 很容易就會把系統搞亂,所以我就可以這么做:
顯然,這種方式比把文件打包到鏡像或者 docker cp 會更加靈活,非常適合有頻繁修改的開發測試工作。
3.如何實現網絡互通
現在我們使用 docker cp 和 docker run -v 可以解決容器與外界的文件互通問題,但對于 Nginx、Redis 這些服務器來說,網絡互通才是更要緊的問題。
網絡互通的關鍵在于“打通”容器內外的網絡,而處理網絡通信無疑是計算機系統里最棘手的工作之一,有許許多多的名詞、協議、工具,在這里我也沒有辦法一下子就把它都完全說清楚,所以只能從“宏觀”層面講個大概,幫助你快速理解。
Docker 提供了三種網絡模式,分別是 null、host 和 bridge。
1.null 是最簡單的模式,也就是沒有網絡,但允許其他的網絡插件來自定義網絡連接,這里就不多做介紹了。
2.host 的意思是直接使用宿主機網絡,相當于去掉了容器的網絡隔離(其他隔離依然保留),所有的容器會共享宿主機的 IP 地址和網卡。這種模式沒有中間層,自然通信效率高,但缺少了隔離,運行太多的容器也容易導致端口沖突。
host 模式需要在 docker run 時使用 --net=host 參數,下面我就用這個參數啟動 Nginx:
docker run -d --rm --net=host nginx:alpine為了驗證效果,我們可以在本機和容器里分別執行 ip addr 命令,查看網卡信息:
ip addr # 本機查看網卡docker exec xxx ip addr # 容器查看網卡
可以看到這兩個 ip addr 命令的輸出信息是完全一樣的,比如都是一個網卡 ens160,IP 地址是“192.168.10.208”,這就證明 Nginx 容器確實與本機共享了網絡棧。
第三種 bridge,也就是橋接模式,它有點類似現實世界里的交換機、路由器,只不過是由軟件虛擬出來的,容器和宿主機再通過虛擬網卡接入這個網橋(圖中的 docker0),那么它們之間也就可以正常的收發網絡數據包了。不過和 host 模式相比,bridge 模式多了虛擬網橋和網卡,通信效率會低一些。
和 host 模式一樣,我們也可以用 --net=bridge 來啟用橋接模式,但其實并沒有這個必要,因為 Docker 默認的網絡模式就是 bridge,所以一般不需要顯式指定。
下面我們啟動兩個容器 Nginx 和 Redis,就像剛才說的,沒有特殊指定就會使用 bridge 模式:
docker run -d --rm nginx:alpine # 默認使用橋接模式 docker run -d --rm redis # 默認使用橋接模式然后我們還是在本機和容器里執行 ip addr 命令(Redis 容器里沒有 ip 命令,所以只能在 Nginx 容器里執行):
對比一下剛才 host 模式的輸出,就可以發現容器里的網卡設置與宿主機完全不同,eth0 是一個虛擬網卡,IP 地址是 B 類私有地址“172.17.0.2”。
我們還可以用 docker inspect 直接查看容器的 ip 地址:
docker inspect xxx |grep IPAddress這顯示出兩個容器的 IP 地址分別是“172.17.0.2”和“172.17.0.3”,而宿主機的 IP 地址則是“172.17.0.1”,所以它們都在“172.17.0.0/16”這個 Docker 的默認網段,彼此之間就能夠使用 IP 地址來實現網絡通信了。
4.如何分配服務端口號
使用 host 模式或者 bridge 模式,我們的容器就有了 IP 地址,建立了與外部世界的網絡連接,接下來要解決的就是網絡服務的端口號問題。
你一定知道,服務器應用都必須要有端口號才能對外提供服務,比如 HTTP 協議用 80、HTTPS 用 443、Redis 是 6379、MySQL 是 3306。在學習編寫 Dockerfile 的時候也看到過,可以用 EXPOSE 指令聲明容器對外的端口號。
一臺主機上的端口號數量是有限的,而且多個服務之間還不能夠沖突,但我們打包鏡像應用的時候通常都使用的是默認端口,容器實際運行起來就很容易因為端口號被占用而無法啟動。
解決這個問題的方法就是加入一個“中間層”,由容器環境例如 Docker 來統一管理分配端口號,在本機端口和容器端口之間做一個“映射”操作,容器內部還是用自己的端口號,但外界看到的卻是另外一個端口號,這樣就很好地避免了沖突。
端口號映射需要使用 bridge 模式,并且在 docker run 啟動容器時使用 -p 參數,形式和共享目錄的 -v 參數很類似,用 : 分隔本機端口和容器端口。比如,如果要啟動兩個 Nginx 容器,分別跑在 80 和 8080 端口上:
docker run -d -p 80:80 --rm nginx:alpine docker run -d -p 8080:80 --rm nginx:alpine這樣就把本機的 80 和 8080 端口分別“映射”到了兩個容器里的 80 端口,不會發生沖突,我們可以用 curl 再驗證一下:
使用 docker ps 命令能夠在“PORTS”欄里更直觀地看到端口的映射情況:
5.小結
今天學習了容器與外部系統之間溝通交流的幾種方法。
你會發現,這些方法幾乎消除了容器化的應用和本地應用因為隔離特性而產生的差異,而因為鏡像獨特的打包機制,容器技術顯然能夠比 apt/yum 更方便地安裝各種應用,絕不會“污染”已有的系統。
我們也可以把 Redis、MySQL、Node.js 都運行起來,讓容器成為我們工作中的得力助手。
思考:
1.docker cp 命令和第 4 講 Dockerfile 里的 COPY 指令有什么區別嗎?
2.你覺得 host 模式和 bridge 模式各有什么優缺點,在什么場景下應用最合適?
答:
1.第四節的copy命令是在容器啟動過程中的COPY命令,該命令應該是在聲明了“namespace”之后,所以這個時候進程看到的世界是一個隔離的環境;而這里的COPY更像是站在“上帝視角(宿主機操作系統層面)”進行拷貝,所以這里不受“namespace”的約束;(copy 拷貝的文件會新增鏡像層,從而是永久性的,而docker cp只會臨時存在)
2.host就是簡單粗暴效率高,適合小規模集群的簡單拓撲結構;bridge適合大規模集群,有了bridge就有更多的可操作空間,比如XLAN和VXLAN這些,它可以提供更多的可定制化服務,比如流量控制、灰度策略這些,從而像flannel和Calico這些組件才有了更多的發揮余地。
七、玩轉Docker
要提醒你的是,Docker 相關的內容很多很廣,在入門篇中,我只從中挑選出了一些最基本最有用的介紹給你。而且在我看來,我們不需要完全了解 Docker 的所有功能,我也不建議你對 Docker 的內部架構細節和具體的命令行參數做過多的了解,太浪費精力,只要會用夠用,需要的時候能夠查找官方手冊就行。
我先把容器技術做一個簡要的總結,然后演示兩個實戰項目:使用 Docker 部署 Registry 和 WordPress。
1.容器技術要點回顧
容器技術是后端應用領域的一項重大創新,它徹底變革了應用的開發、交付與部署方式,是“云原生”的根本
容器基于 Linux 底層的 namespace、cgroup、chroot 等功能,雖然它們很早就出現了,但直到 Docker“橫空出世”,把它們整合在一起,容器才真正走近了大眾的視野,逐漸為廣大開發者所熟知
容器技術中有三個核心概念:容器(Container)、鏡像(Image),以及鏡像倉庫(Registry)
從本質上來說,容器屬于虛擬化技術的一種,和虛擬機(Virtual Machine)很類似,都能夠分拆系統資源,隔離應用進程,但容器更加輕量級,運行效率更高,比虛擬機更適合云計算的需求。
鏡像是容器的靜態形式,它把應用程序連同依賴的操作系統、配置文件、環境變量等等都打包到了一起,因而能夠在任何系統上運行,免除了很多部署運維和平臺遷移的麻煩。
鏡像內部由多個層(Layer)組成,每一層都是一組文件,多個層會使用 Union FS 技術合并成一個文件系統供容器使用。這種細粒度結構的好處是相同的層可以共享、復用,節約磁盤存儲和網絡傳輸的成本,也讓構建鏡像的工作變得更加容易
為了方便管理鏡像,就出現了鏡像倉庫,它集中存放各種容器化的應用,用戶可以任意上傳下載,是分發鏡像的最佳方式
目前最知名的公開鏡像倉庫是 Docker Hub,其他的還有 quay.io、gcr.io,我們可以在這些網站上找到許多高質量鏡像,集成到我們自己的應用系統中。
容器技術有很多具體的實現,Docker 是最初也是最流行的容器技術,它的主要形態是運行在 Linux 上的“Docker Engine”。我們日常使用的 docker 命令其實只是一個前端工具,它必須與后臺服務“Docker daemon”通信才能實現各種功能。
操作容器的常用命令有 docker ps、docker run、docker exec、docker stop 等;操作鏡像的常用命令有 docker images、docker rmi、docker build、docker tag 等;操作鏡像倉庫的常用命令有 docker pull、docker push 等。
好簡單地回顧了容器技術,下面我們就來綜合運用在“入門篇”所學到的各個知識點,開始實戰演練,玩轉 Docker。
2. 搭建私有鏡像倉庫
在第 5 節 Docker Hub 的時候曾經說過,在離線環境里,我們可以自己搭建私有倉庫。但因為鏡像倉庫是網絡服務的形式,當時還沒有學到容器網絡相關的知識,所以只有到了現在,我們具備了比較完整的 Docker 知識體系,才能夠搭建私有倉庫。
私有鏡像倉庫有很多現成的解決方案,今天我只選擇最簡單的 Docker Registry,而功能更完善的 CNCF Harbor 留到后續學習 Kubernetes 時再介紹。
你可以在 Docker Hub 網站上搜索“registry”,找到它的官方頁面 https://registry.hub.docker.com/_/registry/
Docker Registry 的網頁上有很詳細的說明,包括下載命令、用法等,我們可以完全照著它來操作。
首先,你需要使用 docker pull 命令拉取鏡像:
docker pull registry然后,我們需要做一個端口映射,對外暴露端口,這樣 Docker Registry 才能提供服務。它的容器內端口是 5000,簡單起見,我們在外面也使用同樣的 5000 端口,所以運行命令就是 docker run -d -p 5000:5000 registry :
docker run -d -p 5000:5000 registry
啟動 Docker Registry 之后,你可以使用 docker ps 查看它的運行狀態,可以看到它確實把本機的 5000 端口映射到了容器內的 5000 端口。
接下來,我們就要使用 docker tag 命令給鏡像打標簽再上傳了。因為上傳的目標不是默認的 Docker Hub,而是本地的私有倉庫,所以鏡像的名字前面還必須再加上倉庫的地址(域名或者 IP 地址都行),形式上和 HTTP 的 URL 非常像。
比如在這里,我就把“nginx:alpine”改成了“127.0.0.1:5000/nginx:alpine”:
docker tag nginx:alpine 127.0.0.1:5000/nginx:alpine現在,這個鏡像有了一個附加倉庫地址的完整名字,就可以用 docker push 推上去了:
docker push 127.0.0.1:5000/nginx:alpine為了驗證是否已經成功推送,我們可以把剛才打標簽的鏡像刪掉,再重新下載:
docker rmi 127.0.0.1:5000/nginx:alpine docker pull 127.0.0.1:5000/nginx:alpine
這里 docker pull 確實完成了鏡像下載任務,不過因為原來的層原本就已經存在,所以不會有實際的下載動作,只會創建一個新的鏡像標簽。
Docker Registry 雖然沒有圖形界面,但提供了 RESTful API,也可以發送 HTTP 請求來查看倉庫里的鏡像,具體的端點信息可以參考官方文檔(https://docs.docker.com/registry/spec/api/),下面的這兩條 curl 命令就分別獲取了鏡像列表和 Nginx 鏡像的標簽列表:
curl 127.1:5000/v2/_catalog curl 127.1:5000/v2/nginx/tags/list可以看到,因為應用被封裝到了鏡像里,所以我們只用簡單的一兩條命令就完成了私有倉庫的搭建工作,完全不需要復雜的軟件安裝、環境設置、調試測試等繁瑣的操作,這在容器技術出現之前簡直是不可想象的。
3.搭建 WordPress 網站
Docker Registry 應用比較簡單,只用單個容器就運行了一個完整的服務,下面我們再來搭建一個有點復雜的 WordPress 網站。
網站需要用到三個容器:WordPress、MariaDB、Nginx,它們都是非常流行的開源項目,在 Docker Hub 網站上有官方鏡像,網頁上的說明也很詳細,所以具體的搜索過程我就略過了,直接使用 docker pull 拉取它們的鏡像:
docker pull wordpress:5 docker pull mariadb:10 docker pull nginx:alpine我畫了一個簡單的網絡架構圖,你可以直觀感受一下它們之間的關系:
這個系統可以說是比較典型的網站了。MariaDB 作為后面的關系型數據庫,端口號是 3306;WordPress 是中間的應用服務器,使用 MariaDB 來存儲數據,它的端口是 80;Nginx 是前面的反向代理,它對外暴露 80 端口,然后把請求轉發給 WordPress。
我們先來運行 MariaDB。根據說明文檔,需要配置“MARIADB_DATABASE”等幾個環境變量,用 --env 參數來指定啟動時的數據庫、用戶名和密碼,這里我指定數據庫是“db”,用戶名是“wp”,密碼是“123”,管理員密碼(root password)也是“123”。
下面就是啟動 MariaDB 的 docker run 命令:
docker run -d --rm \--env MARIADB_DATABASE=db \--env MARIADB_USER=wp \--env MARIADB_PASSWORD=123 \--env MARIADB_ROOT_PASSWORD=123 \mariadb:10啟動之后,我們還可以使用 docker exec 命令,執行數據庫的客戶端工具“mysql”,驗證數據庫是否正常運行:
docker exec -it 9ac mysql -u wp -p輸入剛才設定的用戶名“wp”和密碼“123”之后,我們就連接上了 MariaDB,可以使用 show databases; 和 show tables; 等命令來查看數據庫里的內容。當然,現在肯定是空的。
因為 Docker 的 bridge 網絡模式的默認網段是“172.17.0.0/16”,宿主機固定是“172.17.0.1”,而且 IP 地址是順序分配的,所以如果之前沒有其他容器在運行的話,MariaDB 容器的 IP 地址應該就是“172.17.0.2”,這可以通過 docker inspect 命令來驗證:
docker inspect 9ac |grep IPAddress現在數據庫服務已經正常,該運行應用服務器 WordPress 了,它也要用 --env 參數來指定一些環境變量才能連接到 MariaDB,注意“WORDPRESS_DB_HOST”必須是 MariaDB 的 IP 地址,否則會無法連接數據庫:
docker run -d --rm \--env WORDPRESS_DB_HOST=172.17.0.2 \--env WORDPRESS_DB_USER=wp \--env WORDPRESS_DB_PASSWORD=123 \--env WORDPRESS_DB_NAME=db \wordpress:5WordPress 容器在啟動的時候并沒有使用 -p 參數映射端口號,所以外界是不能直接訪問的,我們需要在前面配一個 Nginx 反向代理,把請求轉發給 WordPress 的 80 端口。
配置 Nginx 反向代理必須要知道 WordPress 的 IP 地址,同樣可以用 docker inspect 命令查看,如果沒有什么意外的話它應該是“172.17.0.3”,所以我們就能夠寫出如下的配置文件(Nginx 的用法可參考其他資料,這里就不展開講了):
server {listen 80;default_type text/html;location / {proxy_http_version 1.1;proxy_set_header Host $host;proxy_pass http://172.17.0.3;} }有了這個配置文件,最關鍵的一步就來了,我們需要用 -p 參數把本機的端口映射到 Nginx 容器內部的 80 端口,再用 -v 參數把配置文件掛載到 Nginx 的“conf.d”目錄下。這樣,Nginx 就會使用剛才編寫好的配置文件,在 80 端口上監聽 HTTP 請求,再轉發到 WordPress 應用:
docker run -d --rm \-p 80:80 \-v `pwd`/wp.conf:/etc/nginx/conf.d/default.conf \nginx:alpine三個容器都啟動之后,我們再用 docker ps 來看看它們的狀態:
可以看到,WordPress 和 MariaDB 雖然使用了 80 和 3306 端口,但被容器隔離,外界不可見,只有 Nginx 有端口映射,能夠從外界的 80 端口收發數據,網絡狀態和我們的架構圖是一致的。
現在整個系統就已經在容器環境里運行好了,我們來打開瀏覽器,輸入本機的“127.0.0.1”或者是虛擬機的 IP 地址(我這里是“http://192.168.10.208”),就可以看到 WordPress 的界面:
在創建基本的用戶、初始化網站之后,我們可以再登錄 MariaDB,看看是否已經有了一些數據:
可以看到,WordPress 已經在數據庫里新建了很多的表,這就證明我們的容器化的 WordPress 網站搭建成功。
4.小結
好了,今天先簡單地回顧了一下容器技術,這里有一份思維導圖,是對前面所有容器知識要點的總結,你可以對照著用來復習。
我們還使用 Docker 實際搭建了兩個服務:Registry 鏡像倉庫和 WordPress 網站。
通過這兩個項目的實戰演練,你應該能夠感受到容器化對后端開發帶來的巨大改變,它簡化了應用的打包、分發和部署,簡單的幾條命令就可以完成之前需要編寫大量腳本才能完成的任務,對于開發、運維來絕對是一個“福音”。
不過,在感受容器便利的同時,你有沒有注意到它還是存在一些遺憾呢?比如說:
- 我們還是要手動運行一些命令來啟動應用,然后再人工確認運行狀態。
- 運行多個容器組成的應用比較麻煩,需要人工干預(如檢查 IP 地址)才能維護網絡通信。
- 現有的網絡模式功能只適合單機,多臺服務器上運行應用、負載均衡該怎么做?
- 如果要增加應用數量該怎么辦?這時容器技術完全幫不上忙。
其實,如果我們仔細整理這些運行容器的 docker run 命令,寫成腳本,再加上一些 Shell、Python 編程來實現自動化,也許就能夠得到一個勉強可用的解決方案。
這個方案已經超越了容器技術本身,是在更高的層次上規劃容器的運行次序、網絡連接、數據持久化等應用要素,也就是現在我們常說的“容器編排”(Container Orchestration)的雛形,也正是后面要學習的 Kubernetes 的主要出發點。
思考:
1.你覺得容器編排應該解決哪些方面的問題?
答:
容器編排主要應用于大規模集成應用。可以類比分布式系統,但是規模一旦變大到系統層面,就會出現一些問題,比如如何保證數據一致性?如何保證負載均衡?如何盡可能減少網絡故障所帶來的影響?如何能保證數據(容器)的持久化等等。。。這些問題需要運用容器編排來解決
總結
以上是生活随笔為你收集整理的【Linux】Docker入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 远程桌面连接发生身份验证错误(错误代码:
- 下一篇: linux 其他常用命令