Docker 基础实战:环境搭建、容器、仓库、镜像

news2024/10/7 10:24:30

文章目录

  • Docker 基础实战
    • 环境搭建
    • 容器
      • 启动容器
        • 新建并启动容器
        • 终止容器
        • 启动已终止容器
        • 创建守护态容器
      • 容器日志
        • 查看日志
        • 日志驱动
      • 进入容器
        • docker attach
        • docker exec
      • 容器信息
        • docker ps
        • docker inspect
        • docker top
        • docker stats
      • 删除容器
      • 容器快照
        • 导出快照
        • 导入快照
    • 仓库
      • Docker Hub
      • 本地仓库
        • 构建 registry 容器
        • 配置非 https 仓库地址
    • 镜像
      • 文件系统层
      • 列出镜像
      • 查找镜像
      • 拉取镜像
      • 构建镜像
        • docker commit
        • Dockerfile
          • 构建指令
          • 构建流程
          • 实例
        • docker build
      • 推送镜像
      • 删除镜像


Docker 基础实战

可以使用 docker help 或者 man docker-run 来获取完整的 Docker 命令列表,本文只介绍一些常用的命令与参数。

环境搭建

考虑到安装流程过于繁琐,在 CentOS 中,可以使用官方提供的脚本来快速安装 Docker:

可以从 https://get.docker.com/ 查看支持的操作系统。

# 获取安装脚本
curl -fsSL get.docker.com -o get-docker.sh

# 执行安装,并使用aliyun的源加速
sudo sh get-docker.sh --mirror Aliyun

当安装完毕后,设置开机自启动 Docker:

# 设置开机自启
sudo systemctl enable docker

# 启动docker
sudo systemctl start docker

由于 Docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket,因此我们需要将当前用户加入 docker 用户组中:

# 建立docker用户组
sudo groupadd docker

# 将当前用户加入docker组:
sudo usermod -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 配置文件,将镜像源添加进去:

# 创建配置文件夹
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进程加载配置文件
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 它会做一些什么样的操作呢?

  1. docker 会检查本地是否存在对应的镜像,如果不存在则会去 docker hub registry 中查找,并保存到本地。
  2. docker 利用该镜像创建并启动一个容器。
  3. docker 开始构建容器环境,包括以下内容:
    • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层。
    • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中
    • 从地址池为容器配置一个 ip 地址。
  4. docker 开始执行用户在 bash shell 中输入的命令。
  5. 当用户退出 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 attachdocker exec

docker attach

我们可以使用 docker attach 命令来附着到一个容器的 shell 上,例如:

docker attach [id/name]

此时我们就可以在容器的 bash 上执行我们想要进行的操作。

需要注意的是 docker attach 在退出 bash 时,会导致该容器的停止。


docker exec

docker exec 命令可以在容器内部额外启动新进程(包括 shell),用法如下:

docker exec -it ubuntu /bin/bash

这里的选项的含义与 docker run 相同,这里就不过多介绍了。

docker exec 退出 bash 时并不会导致容器的停止,所以我们通常用它来进入容器。


容器信息

docker ps

docker ps 可以用来查看当前存在的容器列表,以及它们的一些基本信息:

docker ps

默认查找出运行中的容器,如果想查看全部容器需要加上 -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 命令用来查看容器内部运行的进程,例如:

docker top [id/name]

此时我们就能够看到容器内的所有进程、运行进程的用户及进程 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 命令用来显示一个或多个容器的统计信息,使用方法如下:

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 来删除容器:

docker rm [id/name]

如果想要删除运行中的容器,可以加上 -f 选项强制删除。

如果我们想要批量删除所有终止状态下的容器,可以使用下面这个命令:

docker container prune

而如果想一次性删除全部的容器,该怎么做呢?这时候可以先使用 docker ps 获取到所有容器 id,再传给 docker rm 进行批量删除:

docker rm `docker ps -a -q`


容器快照

某些时候,如果我们想要备份当前的容器,又或者是希望将其同步到多台机器上,此时就可以通过导出、导入容器的快照来实现。

快照与镜像文件有什么区别呢?快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也更大。

导出快照

我们可以使用 docker export 命令来导出本地的容器,例如:

docker export [id/name] > [文件名.tar]

此时我们就可以获得导出的快照文件。


导入快照

对于导出的快照,我们可以使用 docker import 将其导入为镜像,例如:

docker import [快照路径] [镜像名]


仓库

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 命令。


本地仓库

如果我们需要构建和存储包含不想被公开的信息或数据的镜像,这时候我们有以下两种选择:

  1. 使用 Docker Hub 上的私有仓库。
  2. 自己搭建私有的镜像仓库。

这里就介绍一下如何使用 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 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


拉取镜像

当我们用 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 的流程我们可以看出一个问题,这个镜像的制作过程是完全黑箱的,除了制作的人知道它执行过什么命令、如何进行构建,其他人根本无从得知,这就带来了以下几个问题:

  1. 可能存在大量无关的内容,导致镜像极为臃肿,并且每一次在该镜像上构建新的镜像时,这些内容都会保留下来,越来越臃肿。
  2. 镜像整个制作过程完全黑箱,存在安全隐患。
  3. 无法知道构建流程和执行命令,后续维护成本较高。

基于以上问题,我们通常会使用 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 中每一个指令都会创建一个新的镜像层并对镜像进行提交,其构建流程如下:

  1. Docker 借助基础镜像构建出一个容器。
  2. 执行一条指令,对容器做出修改。
  3. 执行类似 docker commit 的操作,提交一个新的镜像层。
  4. Docker 借助刚刚提交的镜像层运行一个新容器。
  5. 继续执行 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`

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/499458.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

软考--快速掌握七层模型与各种协议的划分

目录 协议族 网络层涉及的协议 传输层涉及的协议 应用层涉及的协议 协议族 认识几个协议族&#xff0c;所谓协议族就是说他不是单一的协议。而是很多协议拼在一起的。 TCP/IP协议族是internet的标准协议族&#xff0c;所以使用广&#xff0c;但是tcp/ip协议族传输效率是比较…

面试题:String,StringBuilder和StringBuffer的区别

面试题&#xff1a;String&#xff0c;StringBuilder和StringBuffer的区别 一、String 1、String是一个长度不可变的字符序列&#xff0c;底层是一个被final修饰的char[]数组。 2、任何对String类型进行改变的操作实际上都是重新生产一个新的String对象&#xff0c;然后将指…

micro-app在vue-element-admin中一些使用研究

1、简述 本文承接上一篇micro-app在vue-element-admi中的搭建&#xff0c;对micro-app在vue-element-admin中的一些平时开发中常用的功能做了一些研究。本文代码 2、路由 关于路由&#xff0c;这边从两方面进行研究&#xff0c;一方面是对菜单的配置&#xff0c;另一方面是页…

❤ 个人博客的搭建和设置

❤ 个人博客的搭建和设置 闲暇时候自己试着做了一个自己的个人博客&#xff0c;博客使用的Github进行托管&#xff0c;还在阿里云买了属于自己的域名 具体步骤如下 1、登陆自己的博客账号&#xff0c;新建个人主页的仓库 2、到了我们的名字设置&#xff0c;格外注意&#xf…

Leetcode刷题之两两交换链表中的结点和相交链表

只有把抱怨环境的心情&#xff0c;化为上进的力量&#xff0c;才是成功的保证。 ——罗曼罗兰目录 &#x1f349;一.相交链表 &#x1f490;1.双指针 &#x1f34d;2.计算长度加双指针 &#x1f352;二.两两交换链表中的结点 &#x1f34c;1.迭代 &#x1f349;一…

【Golang】多线程下载器的实现

〇、前言 多线程下载&#xff0c;顾名思义就是对一个文件进行切片访问&#xff0c;等待所有的文件下载完成后在本地进行拼接成一个整体文件的过程。 因此可以利用 golang 的多协程对每个分片同步下载&#xff0c;之后再合并且进行md5校验或者总长度校验。 一、请求资源 下载…

5.7学习周报

文章目录 前言文献阅读摘要简介数据方法论预测结果结论 时间序列预测总结 前言 本周阅读文献《Water quality forecasting based on data decomposition, fuzzy clustering and deep learning neural network》&#xff0c;文献主要结合数据分解、模糊C均值聚类和双向门控循环…

设计模式 - 工厂方法模式

设计模式 - 工厂方法模式 1、关于工厂方法模式2、工厂方法模式小试牛刀2.1、类图2.2、代码清单3、工厂方法模式的扩展3.1、简单工厂模式&#xff08;静态工厂模式&#xff09; 1、关于工厂方法模式 工厂方法模式&#xff0c;就是定义一个用于创建对象的接口&#xff0c;让子类决…

Ubuntu20升级nodejs版本

执行 grunt build的时候提示node版本过低 当前版本为10.19.0&#xff0c;不满足要求 安装 n&#xff0c;用于更新 node 版本的 sudo npm install n -g用n 下载 nodejs 的最新稳定版本 sudo n stable3. 安装完毕&#xff0c;node -r检查当前版本

【算法与数据结构】链表

链表 链表&#xff1a;结构定义 链表是由一串节点串联在一起的&#xff0c;链表的每个节点存储两个信息&#xff1a;数据下一个节点的地址 分清楚两个概念&#xff1a;什么是内存内部&#xff0c;什么是程序内部 内存内部&#xff1a; 信息存储在内存空间里的 程序内部&#…

复习笔记1

考纲(张友生版本软件架构 考试题型&#xff1a; 10*3单选 5*3简答题 5*3设计图&#xff08;含画图&#xff09; 10*2 论述题 10*2综合题 复习以课件为主&#xff0c;书为辅 第一章 (软件危机) &#xff1f; &#xff1f; 构造模型与实现 掌握软件结构体系核心模型 第二章 软件体…

阿里云Alibaba Cloud Linux镜像系统介绍及常见问题解答FAQ

阿里云服务器操作系统Alibaba Cloud Linux镜像怎么样&#xff1f;可以代替CentOS吗&#xff1f;Alibaba Cloud Linux兼容性如何&#xff1f;有人维护吗&#xff1f;漏洞可以修复吗&#xff1f;Alibaba Cloud Linux完全兼容CentOS&#xff0c;并由阿里云官方免费提供长期维护。 …

Java并发,夺命 60 问

基础 1.并行跟并发有什么区别&#xff1f; 从操作系统的角度来看&#xff0c;线程是CPU分配的最小单位。 并行就是同一时刻&#xff0c;两个线程都在执行。这就要求有两个CPU去分别执行两个线程。 并发就是同一时刻&#xff0c;只有一个执行&#xff0c;但是一个时间段内&am…

linux系统中rpm与Yum软件仓库

rpm的作用&#xff1a; 在没有rpm软件管理之前我们在安装、升级、卸载服务程序时要考虑到其他程序、库的依赖关系&#xff0c;所以在进行安装、校验、卸载、升级等操作时的难度就非常之大。 rpm机制则为就是为了解决这些问题而设计的&#xff0c;rpm有点像windows系统中的控制…

xawtv涉及的vivid系统调用分析

xawtv涉及的vivid系统调用分析 文章目录 xawtv涉及的vivid系统调用分析调用过程分析摄像头驱动程序必需的11个ioctl非必须必须 分析数据的获取过程1.请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区2.查询映射缓冲区:3.把缓冲区放入队列:4.启动摄像头5.用selec…

Docker-Flannel网络讲解

目录 什么是Flannel Flannel原理 部署 环境准备 安装etcd 安装fannel 在server2上装flannel 测试 什么是Flannel flannel是 CoreOS 团队针对 Kubernetes 设计的一个覆盖网络&#xff08;Overlay Network&#xff09;工具&#xff0c;其目的在于帮助每一个使用 Kuberent…

文心一言 VS chatgpt (15)-- 算法导论3.2 4~5题

四、函数 ⌈ lg ⁡ n ⌉ ! \lceil \lg n \rceil ! ⌈lgn⌉! 多项式有界吗&#xff1f;函数 ⌈ lg ⁡ lg ⁡ n ⌉ ! \lceil \lg \lg n \rceil ! ⌈lglgn⌉! 多项式有界吗&#xff1f; 文心一言&#xff1a; chatgpt&#xff1a; 对于第一个问题&#xff0c;函数 ⌈ lg ⁡ n…

C++类和对象(6)

类和对象 1.在谈构造函数1.1. 构造函数体赋值1.2. 初始化列表1.3. explicit关键字 2. static成员2.1. 概念2.2. 特性 3.友元函数3.2.友元类 4. 内部类5.匿名对象6.拷贝对象时的一些编译器优化7.再次理解类和对象 1.在谈构造函数 1.1. 构造函数体赋值 在创建对象时&#xff0c…

【SPSS】回归分析详细操作教程(附案例实战)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

数据结构入门-顺序表链表

线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。线性表是一种实际中广泛使用多个数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直线。…