文章目录
Docker 基础实战
可以使用 docker help 或者 man docker-run 来获取完整的 Docker 命令列表,本文只介绍一些常用的命令与参数。
环境搭建
考虑到安装流程过于繁琐,在 CentOS 中,可以使用官方提供的脚本来快速安装 Docker:
可以从 https://get.docker.com/ 查看支持的操作系统。
# 获取安装脚本curl -fsSL get.docker.com -o get-docker.sh
# 执行安装,并使用aliyun的源加速sudosh get-docker.sh --mirror Aliyun
当安装完毕后,设置开机自启动 Docker:
# 设置开机自启sudo systemctl enabledocker# 启动dockersudo systemctl start docker
由于 Docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket,因此我们需要将当前用户加入 docker 用户组中:
# 建立docker用户组sudogroupadddocker# 将当前用户加入docker组:sudousermod -aG docker$USER
重启终端后,随便输入一个命令测试是否设置成功,例如:
docker run --rm hello-world
为了提供镜像的拉取速度,这里还需要配置国内的镜像,这里给几个常用的镜像源:
- 阿里云:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors,登录上面这个页面后在镜像工具->镜像加速器页面查看个人专属镜像源。
- 网易云:https://hub-mirror.c.163.com
- 百度云:https://mirror.baidubce.com
接着执行下面的命令,修改 daemon 配置文件,将镜像源添加进去:
# 创建配置文件夹sudomkdir -p /etc/docker
# 写入镜像信息sudotee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://92ycev6l.mirror.aliyuncs.com",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com"
]
}
EOF# daemon进程加载配置文件sudo systemctl daemon-reload
# 重启docker服务sudo systemctl restart docker
重启服务,即可完成配置:
sudo systemctl daemon-reload
sudo systemctl restart docker
容器
启动容器
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(exited)的容器重新启动。
新建并启动容器
这里我们以 docker 自带的镜像 ubuntu 来进行演示,运行
docker run
命令创建容器,例如:
docker run --name ubuntu -it ubuntu /bin/bash
这里解释一下这个命令用到的参数和选项,首先
–name
用于指定容器的名字(如果不指定则会随机生成一个容器 id),我们在后续的操作中可以直接使用这个名字来操作容器。接着
-t
参数用于为该容器分配一个伪终端(pseudo-tty),这样该容器才能具备一个交互式的 shell。
-i
参数保证容器的标准输入(stdin)保持打开,这样我们就可以通过终端在该容器中执行命令。接着的 ubuntu 其实就是指定创建容器所需要的镜像,而最后的
/bin/bash/
则是在容器中启动了一个 bash shell。
当我们运行这个命令之后,docker 它会做一些什么样的操作呢?
- docker 会检查本地是否存在对应的镜像,如果不存在则会去 docker hub registry 中查找,并保存到本地。
- docker 利用该镜像创建并启动一个容器。
- docker 开始构建容器环境,包括以下内容: - 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层。- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中- 从地址池为容器配置一个 ip 地址。
- docker 开始执行用户在 bash shell 中输入的命令。
- 当用户退出 bash 后终止容器。
终止容器
在容器启动后,我们可以随时使用
docker stop
命令来终止容器,例如:
docker stop [id/name]
也可以直接在容器中使用
exit
命令,或者直接关闭 bash 来终止容器。
启动已终止容器
当容器停止后,我们可以使用
docker start
命令来将其启动,例如:
docker start [id/name]
我们还也可以使用
docker restart
命令来重启一个容器。
在某些场景中,我们也可以使用 docker create 和 docker start 来更细粒度的控制容器。
创建守护态容器
有的时候我们想要创建一些长期运行的容器,例如一些服务、应用程序等,这时就可以在创建容器时加上
-d
选项,将容器设置为守护态容器(daemonized container),例如:
docker run --name daemon_mysql -d mysql /bin/bash
此时 docker 会将容器放到后台运行(即 daemon 进程),并且不会将主机的控制台附着到新的 shell 会话上,其只会返回一个容器 id。
需要注意的是, 容器是否会长久运行,是和
docker run
指定的命令有关,和
-d
参数无关。 即使加上了
-d
,还必须确保容器存在一个前台进程,否则依旧无法后台运行。
此时我们没有办法通过终端来看到容器的输出,需要通过容器日志来获取到其标准输出(stdout)。
容器日志
查看日志
我们可以使用
docker logs
命令来查看容器的日志(即标准输出),例如:
docker logs [id/name]
通常我们还会配合
-t
与
-f
选项一起使用。
-t
的作用是为每条日志加上时间戳,而
-f
则是实时返回容器日志。
日志驱动
在 docker 1.6 之后,我们可以控制 docker 容器所用的日志驱动,可以在创建容器时通过
--log-driver
选项来实现,例如:
docker run --log-driver="syslog" --name daemon_dwayne -d
ubuntu /bin/sh -c "while true; do echo hello world; sleep 1;
done"
常用的选项有以下几种:
- json-file:默认选项,提供了
docker logs
命令。 - syslog:禁用
docker logs
,并且将所有容器的日志输出都重定向到 Syslog。 - none:会禁用所有容器中的日志,从而导致
docker logs
也被禁用。
进入容器
某些时候我们可能想要进入容器的内部执行一些操作(守护态容器、重启后的交互式容器),这时候就可以使用
docker attach
或
docker exec
。
docker attach
我们可以使用
docker attach
命令来附着到一个容器的 shell 上,例如:
docker attach [id/name]
此时我们就可以在容器的 bash 上执行我们想要进行的操作。
需要注意的是 docker attach 在退出 bash 时,会导致该容器的停止。
docker exec
docker exec
命令可以在容器内部额外启动新进程(包括 shell),用法如下:
dockerexec -it ubuntu /bin/bash
这里的选项的含义与
docker run
相同,这里就不过多介绍了。
docker exec 退出 bash 时并不会导致容器的停止,所以我们通常用它来进入容器。
容器信息
docker ps
docker ps
可以用来查看当前存在的容器列表,以及它们的一些基本信息:
dockerps
默认查找出运行中的容器,如果想查看全部容器需要加上 -a 参数
此时就会显示出这些容器的 id、镜像、启动时使用的命令、创建时间、状态、端口、容器名称,例如:
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
所展示的信息还不够详细,可以使用
docker inspect
来获得更多的容器信息。
docker inspect [id/name]
docker inspect
命令会对容器进行详细的检查,然后返回其配置信息,包括名称、命令、网络配置以及很多有用的数据。例如:
[{"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
命令用来查看容器内部运行的进程,例如:
dockertop[id/name]
此时我们就能够看到容器内的所有进程、运行进程的用户及进程 ID,例如:
UID PID PPID C STIME TTY TIME CMD
root 644064210 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 652064400 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 654164400 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
命令用来显示一个或多个容器的统计信息,使用方法如下:
docker stats [id1/name1][id2/name2][...]
我们能看到这些容器的 CPU、内存、网络 I/O 及存储 I/O 的性能和指标,可以用于快速的监控各个容器的负载情况。
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
删除容器
当我们已经不再想要使用这些容器时,可以使用
docker rm
来删除容器:
dockerrm[id/name]
如果想要删除运行中的容器,可以加上
-f
选项强制删除。
如果我们想要批量删除所有终止状态下的容器,可以使用下面这个命令:
docker container prune
而如果想一次性删除全部的容器,该怎么做呢?这时候可以先使用
docker ps
获取到所有容器 id,再传给
docker rm
进行批量删除:
dockerrm`dockerps -a -q`
容器快照
某些时候,如果我们想要备份当前的容器,又或者是希望将其同步到多台机器上,此时就可以通过导出、导入容器的快照来实现。
快照与镜像文件有什么区别呢?快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也更大。
导出快照
我们可以使用
docker export
命令来导出本地的容器,例如:
dockerexport[id/name]>[文件名.tar]
此时我们就可以获得导出的快照文件。
导入快照
对于导出的快照,我们可以使用
docker import
将其导入为镜像,例如:
dockerimport[快照路径][镜像名]
仓库
Docker Hub
目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了数量超过 2,650,000 的镜像。大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。
为了区分同一个仓库中的不同镜像,Docker 提供了一种称为标签(tag)的功能。每个镜像在列出来时都带有一个标签,如12.04 、12.10 、quantal 或者 precise 等。每个标签对组成特定镜像的一些镜像层进行标记。这种机制使得在同一个仓库中可以存储多个镜像。
Docker Hub 中有两种类型的仓库:
- 用户仓库(user repository):用户仓库的镜像都是由 Docker 用户创建的,因此用户仓库的命名由用户名和仓库名两部分组成,例如 jamtur01/puppet。
- 顶层仓库(top-level repository):顶层仓库由 Docker 公司和由选定的能提供优质基础镜像的厂商管理,因此顶层仓库只包含仓库名部分。
我们可以在 https://hub.docker.com 注册一个 docker 账号,接着在命令行使用
docker login
登录账号,就可以访问远程仓库中的镜像了。当想要退出账号时,可以使用
docker logout
命令。
本地仓库
如果我们需要构建和存储包含不想被公开的信息或数据的镜像,这时候我们有以下两种选择:
- 使用 Docker Hub 上的私有仓库。
- 自己搭建私有的镜像仓库。
这里就介绍一下如何使用 docker-registry 来构建一个构建私有的镜像仓库。
构建 registry 容器
我们可以直接使用官方提供的 registry 镜像来启动私有仓库:
docker run -d \
-p 5000:5000 \
-v /opt/data/registry:/var/lib/registry \
--restart=always
--name registry
registry
这里的
-v
参数用于指定镜像文件的存储路径,
–restart
则是为容器设置自动重启。之后我们就可以直接向 127.0.0.1:5000 推送我们的镜像。
配置非 https 仓库地址
如果我们是在内网环境进行开发协作,我们就需要用一个内网地址作为私有仓库的地址,并且取消 docker 对非 https 的限制(docker 默认拒绝以非 HTTPS 的方式推送镜像)。我们需要在 /etc/docker/daemon.json 文件中写入以下内容:
{"insecure-registries":["192.168.199.100:5000"]}
镜像
文件系统层
在讲解镜像操作之前,我们先来思考一个问题,为什么我们能够基于一个镜像来构建另一个镜像?它究竟是由什么组成的?
Docker 的镜像其实就是一个层层叠加的文件系统组成的,如下图所示:
Docker 文件系统层
在最底下是一个引导文件系统(bootfs),其包含了 bootloader 和 linux 内核两部分,用户无法对这一层进行修改。当容器启动后,该层就会被卸载,以留出更多的内存供 initrd 磁盘镜像使用。
在引导文件系统之上的是 root 文件系统 rootfs,它可以是一种或者多种操作系统(例如 CentOS 或者 Ubuntu)。
那 Docker 是如何将这些文件系统联合在一起的呢?
Docker 利用了一种叫做联合加载(union mount)的技术,在 root 文件系统层上加载更多的只读文件系统。联合加载指的是一次同时加载多个文件系统,但是在外面看起来只能看到一个文件系统。联合加载会将各层文件系统叠加到一起,这样最终的文件系统会包含所有底层的文件和目录。
接着就到了一层一层堆叠的镜像。一个镜像可以放到另一个镜像的顶部,位于下面的镜像称为父镜像(parent image),可以依次类推,直到镜像栈的最底部,最底部的镜像称为基础镜像(base image)。
最后,当从一个镜像启动容器时,Docker 会在该镜像的最顶层加载一个读写文件系统。我们想在 Docker 中运行的程序就是在这个读写层中执行的。
当 Docker 第一次启动一个容器时,初始的读写层是空的。当文件系统发生变化时,这些变化都会应用到这一层上。比如,如果想修改一个文件,这个文件首先会从该读写层下面的只读层复制到该读写层。该文件的只读版本依然存在,但是已经被读写层中的该文件副本所隐藏,这也就是我们通常提到的写时拷贝机制。
列出镜像
我们可以使用
docker images
命令来列出本地已经下载的镜像:
docker images
列表中包含了仓库名、标签、镜像 ID、创建时间以及所占用的空间,例如:
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 镜像,此时就能够获取到相关镜像的仓库名、镜像描述、关注数、是否官方、是否自动构建等信息,如下:
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
redis Redis is an opensource 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 opensource 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
拉取镜像
当我们用
docker run
命令拉取镜像时,如果本地没有,则会自动从 Docker Hub 下载。除了这种方式,我们也可以自己主动使用
docker pull
拉取镜像:
docker pull [Docker Registry 地址[:端口号]/]仓库名[:标签]
在拉取完成后,docker 还会给出该镜像完整的 sha256 的摘要,以确保下载一致性。
构建镜像
docker commit
为了方便演示,这里我们就创建一个 CentoS 8 的 nginx 镜像为例子:
# 首先,创建并运行一个centos8镜像docker run -it centos /bin/bash
# 接着,配置yum仓库源(centos官方已结束对该版本的支持,因此需要重新配置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 的镜像创建了一个容器,并在该容器中安装了一个 nginx,为了能够将这个容器的当前状态保存下来,以后不必每次都执行这样的操作,我们需要将其制作成为一个镜像。
制作容器最简单的方法是使用
docker commit
命令,例如:
docker commit -m "centos-nginx" 43c02f31293c orekilee/nginx:webserver
在这条命令中,我们首先使用
-m
指定了镜像的提交信息,接着指定了容器的 id,最后指定了镜像的用户名以及仓库名,并为该镜像打上了一个 webserver 的 tag。这样我们就完成了一个简单的镜像的制作,之后我们可以使用 orekilee/nginx:webserver 直接访问到该镜像。
从上面
docker commit
的流程我们可以看出一个问题,这个镜像的制作过程是完全黑箱的,除了制作的人知道它执行过什么命令、如何进行构建,其他人根本无从得知,这就带来了以下几个问题:
- 可能存在大量无关的内容,导致镜像极为臃肿,并且每一次在该镜像上构建新的镜像时,这些内容都会保留下来,越来越臃肿。
- 镜像整个制作过程完全黑箱,存在安全隐患。
- 无法知道构建流程和执行命令,后续维护成本较高。
基于以上问题,我们通常会使用 Dockerfile 文件和
docker build
命令来构建镜像。
Dockerfile
Dockerfile 是一个文本文件,其内包含了一条条基于DSL(Domain Specific Language)语法的的**指令(Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。通过这种形式,就使得构建的镜像更具备可重复性、透明性以及幂等性。
构建指令
Dockerfile 由一系列指令和参数组成,且每条指令都必须为大写,命令会按照在 Dockerfile 中的顺序执行。常见的命令有下面这些:
- FROM:用于指定基础镜像,必须为 Dockerfile 的第一条指令,如果不想指定基础镜像,可以用 scratch 代替,它代表不存在镜像。- 用法:
FROM [镜像名/id]
- RUN:用于执行命令行命令,支持 shell 和 exec 两种格式。考虑到每执行一次命令都对应着一次
docker commit
并建立新层,所以我们通常会用&&
来连接一些相关操作,尽量减少RUN
的数量。- shell 格式:就像直接在命令行中输入命令一样,例如:RUN <命令>
- exec 格式:用于调用可执行程序,用法如下:RUN ["可执行文件", "参数1", "参数..."]
- COPY:用于将构建上下文目录中源路径的文件/目录,复制到新的一层的镜像内的目标路径位置(复制后保留源文件的各种元数据,如读、写、执行权限、文件变更时间等)。- 用法:
# 格式一COPY [--chown=<user>:<group>] <源路径>... <目标路径># 格式二COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
- ADD:用法与
COPY
一样,但是某些方面得到了加强,例如以下几个场景:- 源文件为 URL 时,会自动下载该文件放到目标路径中。- 源路径为压缩文件时,会自动解压缩到目标路径中。考虑到ADD
会使镜像构建缓存失效,并且这些功能还需要配合RUN
来修改权限、清理文件,其实大多数情况下还是会通过COPY
+RUN
来实现。 - CMD:用于指定默认的容器主进程的启动命令,格式与
RUN
类似。 - ENTRYPOINT:与
CMD
一样都是在指定容器启动程序及参数,但是其不是直接的运行其命令,而是将CMD
的内容作为参数传给ENTRYPOINT
。其格式与RUN
类似。- 其执行时如下:<ENTRYPOINT> "<CMD>"
- VOLUME:用于指定某些目录挂载为匿名卷。- 用法:
VOLUME ["<路径1>", "<路径2>"...]
- ENV:用于设置环境变量。- 用法:
# 格式一ENV <key> <value># 格式二ENV <key1>=<value1> <key2>=<value2>...
- ARG:用于构建参数,效果与
ENV
相同都能够构建环境变量,但是在容器运行后这些环境变量就会被删除(docker history
仍然能看见)。- 用法:ARG <参数名>[=<默认值>]
- EXPOSE:声明容器运行时提供服务的端口。需要注意的是,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。- 用法:
EXPOSE <端口1> [<端口2>...]
- WORKDIR:用于指定工作目录。- 用法:
WORKDIR <工作目录路径>
- USER:用于指定当前的用户。- 用法:
USER <用户名>[:<用户组>]
构建流程
Dockerfile 中每一个指令都会创建一个新的镜像层并对镜像进行提交,其构建流程如下:
- Docker 借助基础镜像构建出一个容器。
- 执行一条指令,对容器做出修改。
- 执行类似
docker commit
的操作,提交一个新的镜像层。 - Docker 借助刚刚提交的镜像层运行一个新容器。
- 继续执行 Dockerfile 的下一条指令,继续重复步骤 2 开始的流程,直到所有指令全部执行完毕。
从这里也可以看到,docker 天生就具备了版本的机制,即使因为某条命令失败而导致构建结束。我们也能够获取到失败前的镜像,并基于这个镜像进行调试排错,而当下一次开始的时候,还能够从该镜像处构建(构建缓存机制,docker 会将之前的镜像层看作缓存)。
实例
讲完了基本命令和构建流程,现在我们就以之前的 CentOS 8 下的 nginx 镜像作为例子,来编写一个 Dockerfile。
首先我们需要创建一个目录,并在该目录下创建一个 Dockerfile 文件:
mkdir demo
cd demo
touch Dockerfile
这个目录就是构建环境(build environment),Docker 则称此环境为上下文(context)或者构建上下文(build context)。Docker 会在构建镜像时将构建上下文和该上下文中的文件和目录上传到 Docker 守护进程。这样 Docker 守护进程就能直接访问用户想在镜像中存储的任何代码、文件或者其他数据。
接着开始 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
当 DockerFile 编写完毕后,我们就可以使用
docker build
命令来构建镜像了:
docker build [选项]<上下文路径/URL/-># 例如docker build -t="orekilee/demo:nginx".
此时就会开始构建镜像,当构建完毕后,docker 会返回该镜像的 id。
推送镜像
当镜像构建完毕后,我们可以将镜像推送到 Docker Hub 上。
首先,我们需要使用
docker tag
命令标记一个本地镜像,将其归入到某一个仓库中:
docker tag [镜像名/镜像id][:标签名][仓库地址][用户名]镜像名[:标签名]
接着,使用
docker push
命令推送镜像:
docker push [用户名/镜像名][:标签名]
需要注意的是,如果不加上仓库名,则 docker 会认为当前需要 push 到 root 仓库中,此时会因为不具备权限而被拒绝推送。
删除镜像
我们可以根据镜像名、镜像 id、镜像摘要等信息,用
docker rmi
命令来删除镜像:
docker rmi [name/id/digests]
我们同样也能够通过组合多个命令来达到批量删除的目的,例如删除全部镜像:
docker rmi `docker images -a -q`
版权归原作者 凌桓丶 所有, 如有侵权,请联系我们删除。