Docker 基礎(chǔ)實戰(zhàn)
可以使用 docker help 或者 man docker-run 來獲取完整的 Docker 命令列表,本文只介紹一些常用的命令與參數(shù)。
環(huán)境搭建
考慮到安裝流程過于繁瑣,在 CentOS 中,可以使用官方提供的腳本來快速安裝 Docker:
可以從 https://get.docker.com/ 查看支持的操作系統(tǒng)。
# 獲取安裝腳本
curl -fsSL get.docker.com -o get-docker.sh
# 執(zhí)行安裝,并使用aliyun的源加速
sudo sh get-docker.sh --mirror Aliyun
當(dāng)安裝完畢后,設(shè)置開機自啟動 Docker:
# 設(shè)置開機自啟
sudo systemctl enable docker
# 啟動docker
sudo systemctl start docker
由于 Docker 命令會使用 Unix socket 與 Docker 引擎通訊。而只有 root 用戶和 docker 組的用戶才可以訪問 Docker 引擎的 Unix socket,因此我們需要將當(dāng)前用戶加入 docker 用戶組中:
# 建立docker用戶組
sudo groupadd docker
# 將當(dāng)前用戶加入docker組:
sudo usermod -aG docker $USER
重啟終端后,隨便輸入一個命令測試是否設(shè)置成功,例如:
docker run --rm hello-world
為了提供鏡像的拉取速度,這里還需要配置國內(nèi)的鏡像,這里給幾個常用的鏡像源:
- 阿里云:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors,登錄上面這個頁面后在鏡像工具->鏡像加速器頁面查看個人專屬鏡像源。
- 網(wǎng)易云:https://hub-mirror.c.163.com
- 百度云:https://mirror.baidubce.com
接著執(zhí)行下面的命令,修改 daemon 配置文件,將鏡像源添加進(jìn)去:
# 創(chuàng)建配置文件夾
sudo mkdir -p /etc/docker
# 寫入鏡像信息
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://92ycev6l.mirror.aliyuncs.com",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com"
]
}
EOF
# daemon進(jìn)程加載配置文件
sudo systemctl daemon-reload
# 重啟docker服務(wù)
sudo systemctl restart docker
重啟服務(wù),即可完成配置:
sudo systemctl daemon-reload
sudo systemctl restart docker
容器
啟動容器
啟動容器有兩種方式,一種是基于鏡像新建一個容器并啟動,另外一個是將在終止?fàn)顟B(tài)(exited)的容器重新啟動。
新建并啟動容器
這里我們以 docker 自帶的鏡像 ubuntu 來進(jìn)行演示,運行 docker run
命令創(chuàng)建容器,例如:
docker run --name ubuntu -it ubuntu /bin/bash
這里解釋一下這個命令用到的參數(shù)和選項,首先 –name
用于指定容器的名字(如果不指定則會隨機生成一個容器 id),我們在后續(xù)的操作中可以直接使用這個名字來操作容器。接著 -t
參數(shù)用于為該容器分配一個偽終端(pseudo-tty),這樣該容器才能具備一個交互式的 shell。-i
參數(shù)保證容器的標(biāo)準(zhǔn)輸入(stdin)保持打開,這樣我們就可以通過終端在該容器中執(zhí)行命令。接著的 ubuntu 其實就是指定創(chuàng)建容器所需要的鏡像,而最后的 /bin/bash/
則是在容器中啟動了一個 bash shell。
當(dāng)我們運行這個命令之后,docker 它會做一些什么樣的操作呢?
- docker 會檢查本地是否存在對應(yīng)的鏡像,如果不存在則會去 docker hub registry 中查找,并保存到本地。
- docker 利用該鏡像創(chuàng)建并啟動一個容器。
- docker 開始構(gòu)建容器環(huán)境,包括以下內(nèi)容:
- 分配一個文件系統(tǒng),并在只讀的鏡像層外面掛載一層可讀寫層。
- 從宿主主機配置的網(wǎng)橋接口中橋接一個虛擬接口到容器中
- 從地址池為容器配置一個 ip 地址。
- docker 開始執(zhí)行用戶在 bash shell 中輸入的命令。
- 當(dāng)用戶退出 bash 后終止容器。
終止容器
在容器啟動后,我們可以隨時使用 docker stop
命令來終止容器,例如:
docker stop [id/name]
也可以直接在容器中使用 exit
命令,或者直接關(guān)閉 bash 來終止容器。
啟動已終止容器
當(dāng)容器停止后,我們可以使用 docker start
命令來將其啟動,例如:
docker start [id/name]
我們還也可以使用 docker restart
命令來重啟一個容器。
在某些場景中,我們也可以使用 docker create 和 docker start 來更細(xì)粒度的控制容器。
創(chuàng)建守護(hù)態(tài)容器
有的時候我們想要創(chuàng)建一些長期運行的容器,例如一些服務(wù)、應(yīng)用程序等,這時就可以在創(chuàng)建容器時加上 -d
選項,將容器設(shè)置為守護(hù)態(tài)容器(daemonized container),例如:
docker run --name daemon_mysql -d mysql /bin/bash
此時 docker 會將容器放到后臺運行(即 daemon 進(jìn)程),并且不會將主機的控制臺附著到新的 shell 會話上,其只會返回一個容器 id。
需要注意的是, 容器是否會長久運行,是和
docker run
指定的命令有關(guān),和-d
參數(shù)無關(guān)。 即使加上了-d
,還必須確保容器存在一個前臺進(jìn)程,否則依舊無法后臺運行。
此時我們沒有辦法通過終端來看到容器的輸出,需要通過容器日志來獲取到其標(biāo)準(zhǔn)輸出(stdout)。
容器日志
查看日志
我們可以使用 docker logs
命令來查看容器的日志(即標(biāo)準(zhǔn)輸出),例如:
docker logs [id/name]
通常我們還會配合 -t
與 -f
選項一起使用。-t
的作用是為每條日志加上時間戳,而 -f
則是實時返回容器日志。
日志驅(qū)動
在 docker 1.6 之后,我們可以控制 docker 容器所用的日志驅(qū)動,可以在創(chuàng)建容器時通過 --log-driver
選項來實現(xiàn),例如:
docker run --log-driver="syslog" --name daemon_dwayne -d
ubuntu /bin/sh -c "while true; do echo hello world; sleep 1;
done"
常用的選項有以下幾種:
-
json-file:默認(rèn)選項,提供了
docker logs
命令。 -
syslog:禁用
docker logs
,并且將所有容器的日志輸出都重定向到 Syslog。 -
none:會禁用所有容器中的日志,從而導(dǎo)致
docker logs
也被禁用。
進(jìn)入容器
某些時候我們可能想要進(jìn)入容器的內(nèi)部執(zhí)行一些操作(守護(hù)態(tài)容器、重啟后的交互式容器),這時候就可以使用 docker attach
或 docker exec
。
docker attach
我們可以使用 docker attach
命令來附著到一個容器的 shell 上,例如:
docker attach [id/name]
此時我們就可以在容器的 bash 上執(zhí)行我們想要進(jìn)行的操作。
需要注意的是 docker attach 在退出 bash 時,會導(dǎo)致該容器的停止。
docker exec
docker exec
命令可以在容器內(nèi)部額外啟動新進(jìn)程(包括 shell),用法如下:
docker exec -it ubuntu /bin/bash
這里的選項的含義與 docker run
相同,這里就不過多介紹了。
docker exec 退出 bash 時并不會導(dǎo)致容器的停止,所以我們通常用它來進(jìn)入容器。
容器信息
docker ps
docker ps
可以用來查看當(dāng)前存在的容器列表,以及它們的一些基本信息:
docker ps
默認(rèn)查找出運行中的容器,如果想查看全部容器需要加上 -a 參數(shù)
此時就會顯示出這些容器的 id、鏡像、啟動時使用的命令、創(chuàng)建時間、狀態(tài)、端口、容器名稱,例如:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c80a71e0927 keking/kkfileview "java -Dfile.encodin…" 4 months ago Up 3 months 0.0.0.0:8012->8012/tcp, :::8012->8012/tcp agitated_lalande
d86da50629af mysql:5.7 "docker-entrypoint.s…" 4 months ago Up 3 months 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql
docker inspect
如果你覺得 docker ps
所展示的信息還不夠詳細(xì),可以使用 docker inspect
來獲得更多的容器信息。
docker inspect [id/name]
docker inspect
命令會對容器進(jìn)行詳細(xì)的檢查,然后返回其配置信息,包括名稱、命令、網(wǎng)絡(luò)配置以及很多有用的數(shù)據(jù)。例如:
[
{
"Id": "3c80a71e09279d83a92e3c4674b6d8f6175fee57402546496058af6f0ec69aa6",
"Created": "2022-03-17T14:26:27.842511963Z",
"Path": "java",
"Args": [
"-Dfile.encoding=UTF-8",
"-Dspring.config.location=/opt/kkFileView-4.1.0-SNAPSHOT/config/application.properties",
"-jar",
"/opt/kkFileView-4.1.0-SNAPSHOT/bin/kkFileView-4.1.0-SNAPSHOT.jar",
"--name",
"kkfileview"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 6440,
"ExitCode": 0,
"Error": "",
"StartedAt": "2022-04-12T03:21:40.84672668Z",
"FinishedAt": "2022-04-12T11:20:06.846679652+08:00"
},
"Image": "sha256:17aa16bf244f7d5c4886b7322820a83fd401fda5e5fc153bd9a30d2c56075ac5",
"ResolvConfPath": "/var/lib/docker/containers/3c80a71e09279d83a92e3c4674b6d8f6175fee57402546496058af6f0ec69aa6/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/3c80a71e09279d83a92e3c4674b6d8f6175fee57402546496058af6f0ec69aa6/hostname",
"HostsPath": "/var/lib/docker/containers/3c80a71e09279d83a92e3c4674b6d8f6175fee57402546496058af6f0ec69aa6/hosts",
"LogPath": "/var/lib/docker/containers/3c80a71e09279d83a92e3c4674b6d8f6175fee57402546496058af6f0ec69aa6/3c80a71e09279d83a92e3c4674b6d8f6175fee57402546496058af6f0ec69aa6-json.log",
"Name": "/agitated_lalande",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "default",
"PortBindings": {
"8012/tcp": [
{
"HostIp": "",
"HostPort": "8012"
}
]
},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "host",
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/e5617afc1d414b95b27bd0993ef4b4f56bb7c4d863fbdfe0fd9637cf45a446e4-init/diff:/var/lib/docker/overlay2/422ff17c3c84c125a5b5f5187670f5c2d0d3b8fb8b9a3323cd8c35c5dc06c5d5/diff:/var/lib/docker/overlay2/496143afbc6136bd48f26c6e247f5ecd38d138f8ef0068ccb810a7c12817c931/diff:/var/lib/docker/overlay2/afd959b170baface760ce9c0521dff4d5a9326fb518189bf958a4e51ecc5d51a/diff:/var/lib/docker/overlay2/7e5d0edc4323531ed7a79f10ba3aea8b9c8788db150b2359dbf478b536271edb/diff",
"MergedDir": "/var/lib/docker/overlay2/e5617afc1d414b95b27bd0993ef4b4f56bb7c4d863fbdfe0fd9637cf45a446e4/merged",
"UpperDir": "/var/lib/docker/overlay2/e5617afc1d414b95b27bd0993ef4b4f56bb7c4d863fbdfe0fd9637cf45a446e4/diff",
"WorkDir": "/var/lib/docker/overlay2/e5617afc1d414b95b27bd0993ef4b4f56bb7c4d863fbdfe0fd9637cf45a446e4/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "3c80a71e0927",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"8012/tcp": {}
},
"Tty": true,
"OpenStdin": true,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/jdk1.8.0_251/bin",
"JAVA_HOME=/usr/local/jdk1.8.0_251",
"CLASSPATH=/usr/local/jdk1.8.0_251/lib/dt.jar:/usr/local/jdk1.8.0_251/lib/tools.jar",
"LANG=zh_CN.UTF-8",
"LC_ALL=zh_CN.UTF-8",
"KKFILEVIEW_BIN_FOLDER=/opt/kkFileView-4.1.0-SNAPSHOT/bin"
],
"Cmd": [
"--name",
"kkfileview"
],
"Image": "keking/kkfileview",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": [
"java",
"-Dfile.encoding=UTF-8",
"-Dspring.config.location=/opt/kkFileView-4.1.0-SNAPSHOT/config/application.properties",
"-jar",
"/opt/kkFileView-4.1.0-SNAPSHOT/bin/kkFileView-4.1.0-SNAPSHOT.jar"
],
"OnBuild": null,
"Labels": {}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "c79b97931e4c90e792a69908532bec655707d8b2177902b3123f48843ad97b64",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"8012/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8012"
},
{
"HostIp": "::",
"HostPort": "8012"
}
]
},
"SandboxKey": "/var/run/docker/netns/c79b97931e4c",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "ea365ddad7c6fd126eab3885a92331ed432bd1e9495f06e65267016c470e07a4",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:03",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "d85e9121a8111af4ff74c675c38f082932c30ba411f8c3fbc20357437970693d",
"EndpointID": "ea365ddad7c6fd126eab3885a92331ed432bd1e9495f06e65267016c470e07a4",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:03",
"DriverOpts": null
}
}
}
}
]
docker top
docker top
命令用來查看容器內(nèi)部運行的進(jìn)程,例如:
docker top [id/name]
此時我們就能夠看到容器內(nèi)的所有進(jìn)程、運行進(jìn)程的用戶及進(jìn)程 ID,例如:
UID PID PPID C STIME TTY TIME CMD
root 6440 6421 0 Apr12 ? 01:45:19 java -Dfile.encoding=UTF-8 -Dspring.config.location=/opt/kkFileView-4.1.0-SNAPSHOT/config/application.properties -jar /opt/kkFileView-4.1.0-SNAPSHOT/bin/kkFileView-4.1.0-SNAPSHOT.jar --name kkfileview
root 6520 6440 0 Apr12 ? 00:00:00 /opt/libreoffice7.1/program/soffice.bin -accept=socket,host=127.0.0.1,port=2001;urp; -env:UserInstallation=file:///tmp/.jodconverter_socket_host-127.0.0.1_port-2001 -headless -nocrashreport -nodefault -nofirststartwizard -nolockcheck -nologo -norestore
root 6541 6440 0 Apr12 ? 00:00:00 /opt/libreoffice7.1/program/soffice.bin -accept=socket,host=127.0.0.1,port=2002;urp; -env:UserInstallation=file:///tmp/.jodconverter_socket_host-127.0.0.1_port-2002 -headless -nocrashreport -nodefault -nofirststartwizard -nolockcheck -nologo -norestore
docker stats
docker stats
命令用來顯示一個或多個容器的統(tǒng)計信息,使用方法如下:
docker stats [id1/name1] [id2/name2] [...]
我們能看到這些容器的 CPU、內(nèi)存、網(wǎng)絡(luò) I/O 及存儲 I/O 的性能和指標(biāo),可以用于快速的監(jiān)控各個容器的負(fù)載情況。
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
3c80a71e0927 agitated_lalande 0.07% 420.6MiB / 3.649GiB 11.26% 2.27MB / 50.9MB 510MB / 401kB 44
d86da50629af mysql 0.03% 307.3MiB / 3.649GiB 8.22% 172MB / 518MB 117MB / 604MB 36
刪除容器
當(dāng)我們已經(jīng)不再想要使用這些容器時,可以使用 docker rm
來刪除容器:
docker rm [id/name]
如果想要刪除運行中的容器,可以加上
-f
選項強制刪除。
如果我們想要批量刪除所有終止?fàn)顟B(tài)下的容器,可以使用下面這個命令:
docker container prune
而如果想一次性刪除全部的容器,該怎么做呢?這時候可以先使用 docker ps
獲取到所有容器 id,再傳給 docker rm
進(jìn)行批量刪除:
docker rm `docker ps -a -q`
容器快照
某些時候,如果我們想要備份當(dāng)前的容器,又或者是希望將其同步到多臺機器上,此時就可以通過導(dǎo)出、導(dǎo)入容器的快照來實現(xiàn)。
快照與鏡像文件有什么區(qū)別呢?快照文件將丟棄所有的歷史記錄和元數(shù)據(jù)信息(即僅保存容器當(dāng)時的快照狀態(tài)),而鏡像存儲文件將保存完整記錄,體積也更大。
導(dǎo)出快照
我們可以使用 docker export
命令來導(dǎo)出本地的容器,例如:
docker export [id/name] > [文件名.tar]
此時我們就可以獲得導(dǎo)出的快照文件。
導(dǎo)入快照
對于導(dǎo)出的快照,我們可以使用 docker import
將其導(dǎo)入為鏡像,例如:
docker import [快照路徑] [鏡像名]
倉庫
Docker Hub
目前 Docker 官方維護(hù)了一個公共倉庫 Docker Hub,其中已經(jīng)包括了數(shù)量超過 2,650,000 的鏡像。大部分需求都可以通過在 Docker Hub 中直接下載鏡像來實現(xiàn)。
為了區(qū)分同一個倉庫中的不同鏡像,Docker 提供了一種稱為標(biāo)簽(tag)的功能。每個鏡像在列出來時都帶有一個標(biāo)簽,如12.04 、12.10 、quantal 或者 precise 等。每個標(biāo)簽對組成特定鏡像的一些鏡像層進(jìn)行標(biāo)記。這種機制使得在同一個倉庫中可以存儲多個鏡像。
Docker Hub 中有兩種類型的倉庫:
- 用戶倉庫(user repository):用戶倉庫的鏡像都是由 Docker 用戶創(chuàng)建的,因此用戶倉庫的命名由用戶名和倉庫名兩部分組成,例如 jamtur01/puppet。
- 頂層倉庫(top-level repository):頂層倉庫由 Docker 公司和由選定的能提供優(yōu)質(zhì)基礎(chǔ)鏡像的廠商管理,因此頂層倉庫只包含倉庫名部分。
我們可以在 https://hub.docker.com 注冊一個 docker 賬號,接著在命令行使用 docker login
登錄賬號,就可以訪問遠(yuǎn)程倉庫中的鏡像了。當(dāng)想要退出賬號時,可以使用 docker logout
命令。
本地倉庫
如果我們需要構(gòu)建和存儲包含不想被公開的信息或數(shù)據(jù)的鏡像,這時候我們有以下兩種選擇:
- 使用 Docker Hub 上的私有倉庫。
- 自己搭建私有的鏡像倉庫。
這里就介紹一下如何使用 docker-registry 來構(gòu)建一個構(gòu)建私有的鏡像倉庫。
構(gòu)建 registry 容器
我們可以直接使用官方提供的 registry 鏡像來啟動私有倉庫:
docker run -d \
-p 5000:5000 \
-v /opt/data/registry:/var/lib/registry \
--restart=always
--name registry
registry
這里的 -v
參數(shù)用于指定鏡像文件的存儲路徑,–restart
則是為容器設(shè)置自動重啟。之后我們就可以直接向 127.0.0.1:5000 推送我們的鏡像。
配置非 https 倉庫地址
如果我們是在內(nèi)網(wǎng)環(huán)境進(jìn)行開發(fā)協(xié)作,我們就需要用一個內(nèi)網(wǎng)地址作為私有倉庫的地址,并且取消 docker 對非 https 的限制(docker 默認(rèn)拒絕以非 HTTPS 的方式推送鏡像)。我們需要在 /etc/docker/daemon.json 文件中寫入以下內(nèi)容:
{
"insecure-registries": [
"192.168.199.100:5000"
]
}
鏡像
文件系統(tǒng)層
在講解鏡像操作之前,我們先來思考一個問題,為什么我們能夠基于一個鏡像來構(gòu)建另一個鏡像?它究竟是由什么組成的?
Docker 的鏡像其實就是一個層層疊加的文件系統(tǒng)組成的,如下圖所示:
在最底下是一個引導(dǎo)文件系統(tǒng)(bootfs),其包含了 bootloader 和 linux 內(nèi)核兩部分,用戶無法對這一層進(jìn)行修改。當(dāng)容器啟動后,該層就會被卸載,以留出更多的內(nèi)存供 initrd 磁盤鏡像使用。
在引導(dǎo)文件系統(tǒng)之上的是 root 文件系統(tǒng) rootfs,它可以是一種或者多種操作系統(tǒng)(例如 CentOS 或者 Ubuntu)。
那 Docker 是如何將這些文件系統(tǒng)聯(lián)合在一起的呢?
Docker 利用了一種叫做聯(lián)合加載(union mount)的技術(shù),在 root 文件系統(tǒng)層上加載更多的只讀文件系統(tǒng)。聯(lián)合加載指的是一次同時加載多個文件系統(tǒng),但是在外面看起來只能看到一個文件系統(tǒng)。聯(lián)合加載會將各層文件系統(tǒng)疊加到一起,這樣最終的文件系統(tǒng)會包含所有底層的文件和目錄。
接著就到了一層一層堆疊的鏡像。一個鏡像可以放到另一個鏡像的頂部,位于下面的鏡像稱為父鏡像(parent image),可以依次類推,直到鏡像棧的最底部,最底部的鏡像稱為基礎(chǔ)鏡像(base image)。
最后,當(dāng)從一個鏡像啟動容器時,Docker 會在該鏡像的最頂層加載一個讀寫文件系統(tǒng)。我們想在 Docker 中運行的程序就是在這個讀寫層中執(zhí)行的。
當(dāng) Docker 第一次啟動一個容器時,初始的讀寫層是空的。當(dāng)文件系統(tǒng)發(fā)生變化時,這些變化都會應(yīng)用到這一層上。比如,如果想修改一個文件,這個文件首先會從該讀寫層下面的只讀層復(fù)制到該讀寫層。該文件的只讀版本依然存在,但是已經(jīng)被讀寫層中的該文件副本所隱藏,這也就是我們通常提到的寫時拷貝機制。
列出鏡像
我們可以使用 docker images
命令來列出本地已經(jīng)下載的鏡像:
docker images
列表中包含了倉庫名、標(biāo)簽、鏡像 ID、創(chuàng)建時間以及所占用的空間,例如:
REPOSITORY TAG IMAGE ID CREATED SIZE
codercom/code-server latest dc6f07d1c0f8 6 months ago 1.63GB
mysql latest 3218b38490ce 7 months ago 516MB
keking/kkfileview latest 17aa16bf244f 7 months ago 1.58GB
hello-world latest feb5d9fea6a5 10 months ago 13.3kB
查找鏡像
我們可以通過 docker search
命令來查找所有 Docker Hub 上公共的可用鏡像:
docker search [鏡像名]
例如我們查找 redis 鏡像,此時就能夠獲取到相關(guān)鏡像的倉庫名、鏡像描述、關(guān)注數(shù)、是否官方、是否自動構(gòu)建等信息,如下:
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
redis Redis is an open source key-value store that… 11160 [OK]
bitnami/redis Bitnami Redis Docker Image 227 [OK]
bitnami/redis-sentinel Bitnami Docker Image for Redis Sentinel 39 [OK]
bitnami/redis-cluster 34
rapidfort/redis-cluster RapidFort optimized, hardened image for Redi… 15
rapidfort/redis RapidFort optimized, hardened image for Redi… 15
circleci/redis CircleCI images for Redis 14 [OK]
ubuntu/redis Redis, an open source key-value store. Long-… 10
bitnami/redis-exporter 9
google/guestbook-python-redis A simple guestbook example written in Python… 5
clearlinux/redis Redis key-value data structure server with t… 4
ibmcom/redis-ha 1
ibmcom/ibm-cloud-databases-redis-catalog Catalog image for the IBM Operator for Redis 1
bitnami/redis-sentinel-exporter 1
ibmcom/ibm-cloud-databases-redis-operator Container image for the IBM Operator for Red… 1
ibmcom/ibm-cloud-databases-redis-operator-bundle Bundle image for the IBM Operator for Redis 1
rancher/redislabs-ssl 0
rapidfort/redis6-ib RapidFort optimized, hardened image for Redi… 0
drud/redis redis 0 [OK]
blackflysolutions/redis Redis container for Drupal and CiviCRM 0
ibmcom/redisearch-ppc64le 0
greenbone/redis-server A redis service container image for the Gree… 0
vmware/redis-photon 0
cimg/redis 0
ibmcom/redis-ppc64le 0
拉取鏡像
當(dāng)我們用 docker run
命令拉取鏡像時,如果本地沒有,則會自動從 Docker Hub 下載。除了這種方式,我們也可以自己主動使用 docker pull
拉取鏡像:
docker pull [Docker Registry 地址[:端口號]/]倉庫名[:標(biāo)簽]
在拉取完成后,docker 還會給出該鏡像完整的 sha256 的摘要,以確保下載一致性。
構(gòu)建鏡像
docker commit
為了方便演示,這里我們就創(chuàng)建一個 CentoS 8 的 nginx 鏡像為例子:
# 首先,創(chuàng)建并運行一個centos8鏡像
docker run -it centos /bin/bash
# 接著,配置yum倉庫源(centos官方已結(jié)束對該版本的支持,因此需要重新配置yum倉庫源)
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
# 最后,使用yum安裝nginx
yum install -y nginx
# 驗證是否安裝成功
nginx -v
# 退出容器
exit
這里,我們基于一個 CentOS 8 的鏡像創(chuàng)建了一個容器,并在該容器中安裝了一個 nginx,為了能夠?qū)⑦@個容器的當(dāng)前狀態(tài)保存下來,以后不必每次都執(zhí)行這樣的操作,我們需要將其制作成為一個鏡像。
制作容器最簡單的方法是使用 docker commit
命令,例如:
docker commit -m "centos-nginx" 43c02f31293c orekilee/nginx:webserver
在這條命令中,我們首先使用 -m
指定了鏡像的提交信息,接著指定了容器的 id,最后指定了鏡像的用戶名以及倉庫名,并為該鏡像打上了一個 webserver 的 tag。這樣我們就完成了一個簡單的鏡像的制作,之后我們可以使用 orekilee/nginx:webserver 直接訪問到該鏡像。
從上面 docker commit
的流程我們可以看出一個問題,這個鏡像的制作過程是完全黑箱的,除了制作的人知道它執(zhí)行過什么命令、如何進(jìn)行構(gòu)建,其他人根本無從得知,這就帶來了以下幾個問題:
- 可能存在大量無關(guān)的內(nèi)容,導(dǎo)致鏡像極為臃腫,并且每一次在該鏡像上構(gòu)建新的鏡像時,這些內(nèi)容都會保留下來,越來越臃腫。
- 鏡像整個制作過程完全黑箱,存在安全隱患。
- 無法知道構(gòu)建流程和執(zhí)行命令,后續(xù)維護(hù)成本較高。
基于以上問題,我們通常會使用 Dockerfile 文件和 docker build
命令來構(gòu)建鏡像。
Dockerfile
Dockerfile 是一個文本文件,其內(nèi)包含了一條條基于DSL(Domain Specific Language)語法的的指令(Instruction),每一條指令構(gòu)建一層,因此每一條指令的內(nèi)容,就是描述該層應(yīng)當(dāng)如何構(gòu)建。通過這種形式,就使得構(gòu)建的鏡像更具備可重復(fù)性、透明性以及冪等性。
構(gòu)建指令
Dockerfile 由一系列指令和參數(shù)組成,且每條指令都必須為大寫,命令會按照在 Dockerfile 中的順序執(zhí)行。常見的命令有下面這些:
-
FROM:用于指定基礎(chǔ)鏡像,必須為 Dockerfile 的第一條指令,如果不想指定基礎(chǔ)鏡像,可以用 scratch 代替,它代表不存在鏡像。
-
用法:
FROM [鏡像名/id]
-
-
RUN:用于執(zhí)行命令行命令,支持 shell 和 exec 兩種格式??紤]到每執(zhí)行一次命令都對應(yīng)著一次
docker commit
并建立新層,所以我們通常會用&&
來連接一些相關(guān)操作,盡量減少RUN
的數(shù)量。-
shell 格式:就像直接在命令行中輸入命令一樣,例如:
RUN <命令>
-
exec 格式:用于調(diào)用可執(zhí)行程序,用法如下:
RUN ["可執(zhí)行文件", "參數(shù)1", "參數(shù)..."]
-
-
COPY:用于將構(gòu)建上下文目錄中源路徑的文件/目錄,復(fù)制到新的一層的鏡像內(nèi)的目標(biāo)路徑位置(復(fù)制后保留源文件的各種元數(shù)據(jù),如讀、寫、執(zhí)行權(quán)限、文件變更時間等)。
-
用法:
# 格式一 COPY [--chown=<user>:<group>] <源路徑>... <目標(biāo)路徑> # 格式二 COPY [--chown=<user>:<group>] ["<源路徑1>",... "<目標(biāo)路徑>"]
-
-
ADD:用法與
COPY
一樣,但是某些方面得到了加強,例如以下幾個場景:- 源文件為 URL 時,會自動下載該文件放到目標(biāo)路徑中。
- 源路徑為壓縮文件時,會自動解壓縮到目標(biāo)路徑中。
考慮到
ADD
會使鏡像構(gòu)建緩存失效,并且這些功能還需要配合RUN
來修改權(quán)限、清理文件,其實大多數(shù)情況下還是會通過COPY
+RUN
來實現(xiàn)。 -
CMD:用于指定默認(rèn)的容器主進(jìn)程的啟動命令,格式與
RUN
類似。 -
ENTRYPOINT:與
CMD
一樣都是在指定容器啟動程序及參數(shù),但是其不是直接的運行其命令,而是將CMD
的內(nèi)容作為參數(shù)傳給ENTRYPOINT
。其格式與RUN
類似。-
其執(zhí)行時如下:
<ENTRYPOINT> "<CMD>"
-
-
VOLUME:用于指定某些目錄掛載為匿名卷。
-
用法:
VOLUME ["<路徑1>", "<路徑2>"...]
-
-
ENV:用于設(shè)置環(huán)境變量。
-
用法:
# 格式一 ENV <key> <value> # 格式二 ENV <key1>=<value1> <key2>=<value2>...
-
-
ARG:用于構(gòu)建參數(shù),效果與
ENV
相同都能夠構(gòu)建環(huán)境變量,但是在容器運行后這些環(huán)境變量就會被刪除(docker history
仍然能看見)。-
用法:
ARG <參數(shù)名>[=<默認(rèn)值>]
-
-
EXPOSE:聲明容器運行時提供服務(wù)的端口。需要注意的是,這只是一個聲明,在容器運行時并不會因為這個聲明應(yīng)用就會開啟這個端口的服務(wù)。
-
用法:
EXPOSE <端口1> [<端口2>...]
-
-
WORKDIR:用于指定工作目錄。
-
用法:
WORKDIR <工作目錄路徑>
-
-
USER:用于指定當(dāng)前的用戶。
-
用法:
USER <用戶名>[:<用戶組>]
-
構(gòu)建流程
Dockerfile 中每一個指令都會創(chuàng)建一個新的鏡像層并對鏡像進(jìn)行提交,其構(gòu)建流程如下:
- Docker 借助基礎(chǔ)鏡像構(gòu)建出一個容器。
- 執(zhí)行一條指令,對容器做出修改。
- 執(zhí)行類似
docker commit
的操作,提交一個新的鏡像層。 - Docker 借助剛剛提交的鏡像層運行一個新容器。
- 繼續(xù)執(zhí)行 Dockerfile 的下一條指令,繼續(xù)重復(fù)步驟 2 開始的流程,直到所有指令全部執(zhí)行完畢。
從這里也可以看到,docker 天生就具備了版本的機制,即使因為某條命令失敗而導(dǎo)致構(gòu)建結(jié)束。我們也能夠獲取到失敗前的鏡像,并基于這個鏡像進(jìn)行調(diào)試排錯,而當(dāng)下一次開始的時候,還能夠從該鏡像處構(gòu)建(構(gòu)建緩存機制,docker 會將之前的鏡像層看作緩存)。
實例
講完了基本命令和構(gòu)建流程,現(xiàn)在我們就以之前的 CentOS 8 下的 nginx 鏡像作為例子,來編寫一個 Dockerfile。
首先我們需要創(chuàng)建一個目錄,并在該目錄下創(chuàng)建一個 Dockerfile 文件:
mkdir demo
cd demo
touch Dockerfile
這個目錄就是構(gòu)建環(huán)境(build environment),Docker 則稱此環(huán)境為上下文(context)或者構(gòu)建上下文(build context)。Docker 會在構(gòu)建鏡像時將構(gòu)建上下文和該上下文中的文件和目錄上傳到 Docker 守護(hù)進(jìn)程。這樣 Docker 守護(hù)進(jìn)程就能直接訪問用戶想在鏡像中存儲的任何代碼、文件或者其他數(shù)據(jù)。
接著開始 Dockerfile 的編寫:
FROM centos
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* \
&& sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* \
&& yum install -y nginx \
&& yum install -y net-tools
&& yum install vim -y
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
這樣一個簡單的 Dockerfile 就編寫完成了。
docker build
當(dāng) DockerFile 編寫完畢后,我們就可以使用 docker build
命令來構(gòu)建鏡像了:
docker build [選項] <上下文路徑/URL/->
# 例如
docker build -t="orekilee/demo:nginx" .
此時就會開始構(gòu)建鏡像,當(dāng)構(gòu)建完畢后,docker 會返回該鏡像的 id。
推送鏡像
當(dāng)鏡像構(gòu)建完畢后,我們可以將鏡像推送到 Docker Hub 上。
首先,我們需要使用 docker tag
命令標(biāo)記一個本地鏡像,將其歸入到某一個倉庫中:
docker tag [鏡像名/鏡像id][:標(biāo)簽名] [倉庫地址][用戶名]鏡像名[:標(biāo)簽名]
接著,使用 docker push
命令推送鏡像:
docker push [用戶名/鏡像名][:標(biāo)簽名]
需要注意的是,如果不加上倉庫名,則 docker 會認(rèn)為當(dāng)前需要 push 到 root 倉庫中,此時會因為不具備權(quán)限而被拒絕推送。
刪除鏡像
我們可以根據(jù)鏡像名、鏡像 id、鏡像摘要等信息,用 docker rmi
命令來刪除鏡像:文章來源:http://www.zghlxwxcb.cn/news/detail-590490.html
docker rmi [name/id/digests]
我們同樣也能夠通過組合多個命令來達(dá)到批量刪除的目的,例如刪除全部鏡像:文章來源地址http://www.zghlxwxcb.cn/news/detail-590490.html
docker rmi `docker images -a -q`
到了這里,關(guān)于Docker 基礎(chǔ)實戰(zhàn):環(huán)境搭建、容器、倉庫、鏡像的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!