Docker 使用简介

2021-09-20 container docker

从 Docker 1.11 开始,Docker 容器运行已经不是简单的通过 Docker Daemon 启动,而是集成了 containerd、runC 等多个组件,在 Docker 服务启动之后,可以看到系统上启动了 dockerd、containerd 等进程。

这里简单介绍一些与 Docker 相关的基本概念。

简介

在安装了 Docker 之后,比较重要的组件主要有如下几种:

  • docker 一个客户端工具,用来把用户的请求发送给 dockerd;
  • dockerd 一般也会被称为 docker engine,在后台运行;
  • docker-containerd 管理 Docker 的核心组件;
  • docker-containerd-shim 容器的运行时载体;
  • runc 真正运行的容器。

各个模块的组件如下。

docker runc

安装

这里包含了 CentOS、Debain 原生的安装,以及直接通过二进制包安装。

CentOS

可以通过安装 YUM 安装。

----- 如有需要,删除老版本
# yum remove docker docker-client docker-client-latest docker-common docker-latest \
	docker-latest-logrotate docker-logrotate docker-engine

----- 配置阿里云的源
# yum install -y yum-utils device-mapper-persistent-data lvm2
# yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

----- 安装包
# yum install docker-ce docker-ce-cli containerd.io

在 CentOS 8 中,如果使用 yum-config-manager 需要安装 dnf-utils 包。

Debain

通过 apt 进行安装,使用的是网易的镜像源。

----- 添加源
# curl -fsSL http://mirrors.163.com/docker-ce/linux/debian/gpg | sudo apt-key add -
# add-apt-repository "deb http://mirrors.163.com/linux/debian $(lsb_release -cs) stable"

----- 更新源数据并安装
# apt update
# apt install docker-ce

二进制

或者下载 静态二进制文件,直接复制到 /usr/bin 目录下,并将 docker.service docker.socket containerd.service 几个服务添加到 /usr/lib/systemd/system/ 目录下。

正常来说,可以将二进制文件放到 /opt/docker 目录下,但是在执行 run 命令的时候会报 shim 命令在 PATH 中不存在,应该是查找路径没有配置好,暂时没有仔细研究怎么配置。

镜像地址

默认 Docker 会使用 index.docker.io 作为镜像源,国内使用会比较慢,可以使用一些国内镜像替换,直接修改 /etc/docker/daemon.json 配置文件。

{
    "registry-mirrors": [
        "https://registry.hub.docker.com",
        "http://hub-mirror.c.163.com",
        "https://docker.mirrors.ustc.edu.cn",
        "https://registry.docker-cn.com",
        "https://dockerpull.com",                // 2024.10 新增
        "https://dockerproxy.cn"                 // 2024.10 新增
    ]
}

修改后重启,然后通过 docker info 查看 Registry Mirrors 配置项即可。

配置使用

安装完后可以通过 systemctl start docker 启动,通过 docker run hello-world 进行测试。

仓库地址

可以通过 docker info 中的 Registry 字段查看当前使用的仓库地址,如果没有则使用的是默认 https://hub.docker.com/ 地址,在国内的仓库 registry.docker-cn.com 中包含了流行的公有镜像,私有镜像仍需要从 Docker Hub 镜像库中拉取。

----- 通过命令行拉取
$ docker pull registry.docker-cn.com/library/ubuntu:16.04

也可以在启动的时候通过 --registry-mirror 参数指定,然后直接使用 library/ubuntu 获取。还可以在配置文件 /etc/docker/daemon.json 文件并添加上 registry-mirrors 配置。

{
    "registry-mirrors": ["https://registry.docker-cn.com"]
}

修改存储目录

容器的镜像等数据会保存在 Docker 的根目录下,可以通过 docker info | grep 'Docker Root Dir' 查看当前的配置,默认保存在 /var/lib/docker 目录下,可以通过如下方式修改。

# vim /etc/docker/daemon.json
{
	"data-root": "/opt/docker"
}

然后重启 docker 服务即可,另外,在 19 版本之前可以使用 graph 参数,不过其之后建议使用 data-root 参数。

启动参数

可以在启动时通过参数定制,例如.

  • --storage-driver=overlay2 使用存储驱动;
  • --registry-mirror=https://foobar.com 镜像地址;
  • --insecure-registry=http://foobar.com 指定非安全的镜像仓库地址。

常用命令

在通过 run 命令启动时,常用的参数如下。

-i, --interactive=true|false   保持STDIN打开,也就是交互方式
-t, --tty=true|false           是否分配一个tty终端
-d, --detach=true|false        在后台运行
-p, --publish=[]               端口映射,可以查看帮助
-v, --volume=[]                文件映射,可以查看帮助
--name                         指定运行时的容器名称

详细可通过 man docker 查看帮助,利用 man docker-run 查看子命令帮助文档。

----- 查找镜像
# docker search centos

----- 下载镜像,可以通过 :NAME.TAG 指定版本号以及Tag信息
# docker pull docker.io/hello-world
# docker pull docker.io/centos

----- 查看镜像,及其详细信息,也可以查看DIGEST
# docker images --digests
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
docker.io/centos    latest              67fa590cfc1c        4 weeks ago         202 MB
# docker inspect 67fa590cfc1c

----- 镜像的构建信息,包括了含有几层,可以通过 --no-trunc 查看完整命令
# docker history busybox

----- 删除镜像,可以通过REPO或者ID指定
# docker image rm 67fa590cfc1c

----- 重命名镜像,通过ID指定,同样可以删除Tag
# docker tag 67fa590cfc1c centos
# docker rmi centos:7.7.1908

----- 运行容器,指定名称为REPOSITORY:TAG
# docker run hello-world
# docker run -it docker.io/centos /bin/bash

----- 后台运行,并attach到后台容器
# docker run -itd --name centos centos /bin/bash
# docker exec -it centos /bin/bash

----- 查看正在运行的容器,可以通过-a参数查看所有
# docker ps

------ 可以从容器和本地之间相互复制文件
# docker cp YourContainerID:/opt/file.txt /opt/file.txt
# docker cp /opt/file.txt YourContainerID:/opt/file.txt

----- 链接到运行的容器,需要指定CONTAINER ID
# docker start 5328dfa90416
# docker stop 5328dfa90416
# docker attach 5328dfa90416
# docker container rm 5328dfa90416

----- 如果要推送镜像是需要登录的,默认会明文保存在~/.docker/config.json文件中,也可以logout
# docker login registry.example.com

----- 重命名镜像,通过ID指定,也可以增加自己的仓库地址,然后直接推送的自己的仓库中
# docker tag 67fa590cfc1c centos:7.9.2009
# docker tag 67fa590cfc1c registry.example.com/centos:latest
# docker push registry.example.com/centos:latest

----- 将正在运行的镜像保存
# docker commit -a "AuthorName" -m "SomeCommitMessage" RunningContainerID Name:Tag

----- 将已有的镜像保存,然后本地加载镜像
$ docker save -o busybox.tar busybox:latest
$ docker load -i busybox.tar

----- 清理已经停止的容器
# docker ps -a | grep "Exited" | awk '{print $1 }' | xargs docker rm

注意,在拉取镜像时还可以指定指纹信息,例如如下命令。

# docker pull kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cb
# docker images --digests

其中指纹信息可以保存在 /var/lib/docker/image/overlay2/repositories.json 文件中。

端口映射

容器在启动的时候如果不指定端口映射参数,在容器外部是无法通过网络来访问容器内的应用的,可以在 Dockerfile 中通过 EXPOSE 80 类似的指令将其导出,或者启动时通过参数指定。

参数格式如下。

ip:hostport:containerport   # 全部指定
ip::containerport           # 主机Port随机
hostport:containerport      # 未指定ip、指定宿主机port、指定容器port

如下是常见的示例。

----- 容器所有端口都随机映射到宿主机上
# docker run -P -it ubuntu /bin/bash

----- 将容器的80端口随机映射到宿主机的一个端口上
# docker run -p 80 -it ubuntu /bin/bash

----- 容器的80端口映射到8000端口上
# docker run -p 8000:80 -it ubuntu /bin/bash

----- 容器的192.168.0.100和80端口随机映射到宿主机的一个端口上
# docker run -p 192.168.0.100::80 -it ubuntu /bin/bash

----- 容器的192.168.0.100和80端口映射到宿主机的8000端口上
# docker run -p 192.168.0.100:8000:80 -it ubuntu /bin/bash

然后可以通过 docker port CONTAINER-ID 命令查看。

资源清理

随着 Docker 的运行,会占用越来越多的资源,比较常见的是磁盘资源,对于一些无用的镜像、容器、网络、数据卷实际可以直接删除,可以通过如下命令查看当前使用的资源。

----- 所有的容器信息,不带-a参数之列举出运行的容器
# docker container ls -a

----- 镜像信息,通过-a会列举出中间的镜像层
# docker image ls -a

----- 列出数据卷、网络信息、系统信息
# docker volume ls
# docker network ls
# docker info

默认提供了 docker system prune 命令来删除已经停止的容器、dangling 镜像、为被容器引用的 network 以及构建过程中的 cache 。

注意,为了安全,默认不会删除未被任何容器引用的数据卷,如果需要则应该显示指定 --volumes 参数。

# docker system prune --all --force --volumes

dangling images

所谓的 dangling images ,可以被理解为未被任何镜像引用的镜像,当通过 docker image ls 查看时,会在 TAG 中显示 <none>,例如,重新构建镜像之后,之前依赖的镜像就变成了 dangling images 。

另外,还有 REPOSITOYTAG 都是 <none> 的镜像,一般是其它镜像的依赖层。

镜像介绍

Docker 镜像由多个只读文件系统叠加而成,起始于一个基础镜像层,在修改后就会在当前镜像层之上创建新的镜像层,而且是只读的,例如如下。

docker images layer

启动容器后,会在顶层增加一个读写层,容器修改存在文件时,实际是将只读层文件复制到读写层,然后进行修改,原文件则会被隐藏,这也是 Union File System 的作用,在重启之后则原文件保持不变,也就是对应的数据会丢失。

目录结构

这种镜像采用的分层设计,每层被可以称之为 layer,其中 Docker 的镜像默认会保存在 /var/lib/docker 目录下,当前环境可以通过 docker info | grep 'Docker Root Dir' 查看,各个目录的含义介绍如下。

另外 podman 默认会保存在 /var/lib/containers/storage/overlay 目录下,这里仍然以 Docker 为例。

builder/
buildkit/
containers/
image/                   存储镜像管理数据的目录
  |-overlay2/            子目录对应了存储驱动
    |-distribution/      远端拉到本地的镜像相关元数据
    |-imagedb/           镜像数据库
    | |-content/
    |   |-sha256/        镜像的ID信息,images命令的前12字节来自这里
    |-leveldb/           镜像每个Layer的元数据
    | |-sha256/          Layer的ChainID
    |-repositories.json  通过JSON保存的相关元数据
network/
overlay2/
plugins/
runtimes/
swarm/
tmp/
trust/
volumes/

其中 image 由多个 layer 组合而成的,那么有可能多个 image 会指向同一个 layer ,一个 image 指向了那些 layer 可以通过 imagedb 查看。

存储驱动

也就是 GraphDriver ,默认支持多种,包括了 VFS、DeviceMapper、aufs、Overlay 等,开始最常用的是 aufs ,不过随着 Linux 3.18 将 Overlay 纳入内核,默认的驱动一般就是 Overlay 。

当前的驱动可以通过 docker info | grep 'Storage Driver' 查看,最新的一般是 overlay2 ,这里只需要关心 root 目录下的 image 和 overlay2 目录即可。

OverlayFS 是一种堆叠文件系统,不会直接操作磁盘,而是建立在其它的文件系统之上,仅仅将原来底层文件系统中不同的目录进行合并。在 Linux 内核中包含了 overlay 和 overlay2 ,后者是一种改进,相比前者 inode 使用率更加高效。

Overlay2

Overlay 也是一种 Union FS,不过只有两层:A) Upper 文件系统;B) Lower 文件系统;分别代表 Docker 的镜像层和容器层,当需要修改一个文件时,使用 CoW 将文件从只读的 Lower 复制到可写的 Upper 进行修改,结果也保存在 Upper 层。

docker overlayfs

在挂载完成之后,可以通过 mount | grep overlay 查看其挂载的参数,如下是一个简单的示例。

mkdir -p /tmp/test/{lower1,lower2,upper,worker,merged}
echo "from lower 1" > /tmp/test/lower1/data.txt
echo "hello world" > /tmp/test/lower1/hi.txt
echo "from lower 2" > /tmp/test/lower2/data.txt
echo "from upper" > /tmp/test/upper/data.txt
echo "from worker" > /tmp/test/worker/data.txt
mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=worker merged
cat /tmp/test/merged/hi.txt
cat /tmp/test/merged/data.txt

挂载时包含了三个目录:

  • Lower 可以是多个,一般对应了磁盘镜像,通常只读;
  • Upper 通常只有一个,作为容器运行操作目录;
  • Worker 基础目录,挂在后会被清空,使用时对用户不可见。

最后会统一呈现的视图在 merged 目录。

镜像管理

所有镜像最终以 tar.gz 方式静态存储在服务器端,这种存储适用于对象存储而不是块存储。

常用镜像

Docker 提供了一个 Hello-World 镜像可以用来测试基本环境安装是否成功,另外,还要一些常用的较小镜像,例如 BusyBox(1M+)、Alpine Linux (使用 musl libc 和 busybox 减小体积,只有 5M+ ) 。

Alpine

一个面向安全的轻型 Linux 发行版,采用了 musl libc 和 busybox 以减小系统的体积和运行时资源消耗,但功能上比 busybox 又完善的多,通过 apk 管理包,也可以从 pkgs 上查看。

可以通过 docker run alpine echo '123' 或者 docker run -it alpine sh 简单运行。

私有镜像

默认的私有镜像通过可以通过 docker-registry 包进行安装,详细的安装步骤可以参考 Deploy a registry server 中的详细介绍。

----- 下载仓库镜像
# docker pull registry

----- 运行镜像,并将本机的/opt/docker/registry映射到容器中
# docker run -d -p 5000:5000 --restart=always --name=registry \
    -v /opt/docker/registry:/var/lib/registry registry

----- 验证是否创建成功,正常应该返回{}
# curl http://127.0.0.1:5000/v2/

Docker 在与 Docker Registry 进行交互时默认使用 https,但是当前的镜像仓库仅提供了 http ,需要将本地仓库添加为不安全仓库。

# cat /etc/docker/daemon.json
{
	"registry-mirrors": [
		"https://registry.docker-cn.com"
	],
	"insecure-registries": [
		"127.0.0.1:5000"
	]
}

然后将本地的 busybox 添加到新搭建的仓库。

----- 查看当前的镜像
# docker images

----- 修改某个镜像的tag,例如busybox,并将其添加到本地
# docker tag busybox:latest 127.0.0.1:5000/busybox:latest
# docker push 127.0.0.1:5000/busybox:latest

然后调用 REST API 查看镜像信息。

----- 查询私有仓库镜像列表
# curl http://127.0.0.1:5000/v2/_catalog

----- 查询busybox镜像的标签列表
# curl http://127.0.0.1:5000/v2/busybox/tags/list

----- 从私有仓库拉取镜像
# docker pull 127.0.0.1:5000/busybox:latest

常见问题

容器已经在运行

详细报错为 The container name "XXX" is already in use by container

----- 查看所有的容器信息,并根据ID删除
# docker ps -a
# docker rm 36bceeae4b22

证书验证报错

详细报错为 Error response from daemon: Get "https://registry.exmaple.com/v2/": tls: failed to verify certificate: x509: certificate relies on legacy Common Name field, use SANs instead

Go 1.15 版本开始废弃 X.509 CommonName 推荐使用 SAN 证书,也可以将其添加到非安全的仓库配置中,此时将不再检查证书,常见有如下两种方式。

----- 通过systemd管理docker服务进程,可以通过如下方式增加启动选项
# vim /etc/sysconfig/docker
OPTIONS='--insecure-registry registry.example.com'

----- 或者修改如下的配置项
# vim /etc/docker/daemon.json
{
    "insecure-registries" : ["registry.example.com"]
}

然后通过 systemctl restart docker 命令重启服务。

拉取镜像报错

详细报错为 Error response from daemon: manifest for your/image:latest not found: manifest unknown: manifest unknown

通常是因为没有这个镜像,多数原因是 tag 错误或者是默认没有设置 latest,可以从对应的 repository 中搜索或者,可以通过如下命令查看当前支持的 tag 信息。

$ curl -XGET http://hub-mirror.c.163.com/v2/your/image/tags/list

这里使用的是网易的镜像进行测试。

下载 ARM 镜像

有时需要在 x86 平台上下载 arm 镜像,例如,在 x86 机器上只是做下载,然后在其它平台上运行,可以通过如下方式修改。

依赖 manifest 特性,需要同时配置服务端和客户端。

----- 开启服务端配置,然后重启
# vim /etc/docker/daemon.json
{
	"experimental": true
}
# systemctl restart docker

----- 配置客户端,可以设置环境变量或者修改配置文件
# export DOCKER_CLI_EXPERIMENTAL=enabled
# vim ~/.docker/config.json
{
	"experimental": "enabled"
}

----- 下载时指定平台
# docker pull docker.io/hello-world --platform=linux/arm64

----- 查看某个镜像的当前架构信息
$ docker inspect 638e1285665f | grep -Ew 'Architecture|Os'