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