Docker知识总结
容器技术
在 Linux 内核中,提供了 cgroups 功能,来达成资源的限制。它同时也提供了 namespaces 隔离的功能,使应用程序看到的操作系统环境被区隔成独立区间,包括进程树,网络,用户id,以及挂载的文件系统。
Cgroups
CGroup 是 Control Groups 的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如 cpu memory i/o 等等) 的机制,简单来将就是把进程放到一个组里面统一加以控制。CGroup 2007 年进入 Linux 2.6.24 内核,CGroups 不是全新创造的,它将进程管理从 cpuset 中剥离出来,作者是 Google 的 Paul Menage。
CGroups 也是 LXC 为实现虚拟化所使用的资源管理手段,Cgroup 有个重要概念叫子系统,子系统也就是资源控制器,每个子系统就是一个资源分配器,比如 CPU 子系统是控制CPU时间分配的,首先挂载子系统,然后才有 Control Group 的。比如,先挂载 memory 子系统,然后在 memory 子系统中创建一个 Cgroup 子节点,在这个节点中,将需要控制的进程 ID 写入,并且将控制的属性写入,这样就完成了内存的限制。
- 子系统
| 子系统 | 说明 |
|---|---|
blkio |
为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等) |
cpu |
使用调度程序提供对 CPU 的 cgroup 任务访问 |
cpuacct |
自动生成 cgroup 中任务所使用的 CPU 报告 |
cpuset |
为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点 |
freezer |
可允许或者拒绝 cgroup 中的任务访问设备 |
freezer |
设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告 |
memory |
挂起或者恢复 cgroup 中的任务 |
net_cls |
使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包 |
Namespaces
Linux Namespaces 机制提供一种资源隔离方案。PID, IPC, Network 等系统资源不再是全局性的 (在Linux2.6内核以前是全局的),而是属于特定的Namespace。每个 Namespace 里面的资源对其他 Namespace 都是透明的。namespace 是容器中使用到的重要技术之一,是对系统资源的操作上的隔离,使不同的容器间互不干扰。
Linux Namespaces 机制本身就是为了实现“基于容器的虚拟化”开发的。它提供了一套轻量级、高效率的系统资源隔离方案,远比传统的虚拟化技术开销小,不过它也不是完美的,它为内核的开发带来了更多的复杂性,它在隔离性和容错性上跟传统的虚拟化技术比也还有差距。
- 名称空间
| 名称空间 | 系统调用 | 说明 | 内核版本 |
|---|---|---|---|
| UTS | CLONE_NEWUTS | 主机名和域名 | 2.6.19 |
| IPC | CLONE_NEWIPC | 信号量、消息队列、共享内存等进程间通信 | 2.6.19 |
| PID | CLONE_NEWPID | 进程编号 | 2.6.24 |
| Network | CLONE_NEWNET | 网络设备、网络栈、端口 | 2.6.29 |
| Mount | CLONE_NEWNS | 挂载点(文件系统) | 2.4.19 |
| User | CLONE_NEWUSER | 用户和组 | 3.8 |
LXC
LXC(Linux Container)它为Linux内核中容器功能(cgroups与namespaces等组件)提供一个用户空间接口,这个容器可以提供轻量级的虚拟化,以便隔离进程和资源,而且不需要提供指令解释机制以及全虚拟化的其他复杂性(虚拟化层)。容器有效地将由单个操作系统管理的资源划分到孤立的组中,以更好地在孤立的组之间平衡有冲突的资源使用需求。本身极为轻量化,提升了创建虚拟机的速度,LXC 的定位是替代传统的虚拟机,侧重于提供一个个操作系统,如 Ubuntu、Debian等。
LXC 建立在 CGroup 基础上,我们可以粗略的认为 LXC = Cgroup+ namespace + Chroot + veth +用户态控制脚本。LXC 利用内核的新特性 (CGroup) 来提供用户空间的对象,用来保证资源的隔离和对于应用或者系统的资源控制。
Docker简介
Docker 是 dotCloud 也就是现在的 Docker 公司在2013年3月发布的,一开始是基于 LXC 项目来创建单个应用程序容器,Docker 最新版现在已经开发了 libcontainer 容器管理包,直接使用内核的 namespaces 和cgroup,在Docker中 (LXC、libcontainer、runC) 负责资源管理,AUFS负责镜像管理,后被 Overlayfs 代替。
最早的时候 Docker 就是一个开源项目,主要由 Docker 公司维护,2017年年初,Docker 公司将原先的 Docker 项目改名为 Moby,Moby 后由社区维护的的开源项目,Docker 官方则又将 Docker 分为两个版本:Docker CE 一个基于 Moby 版本的分支,由 Docker 公司维护,Docker EE 则闭源,为商业提供支持。
LXC 是面向操作系统级别的轻量级虚拟化,而 Docker 是面向应用的,官方提倡一个容器即是一个应用,以应用为中心,Docker 软件用来管理 LXC 的环境。
runC
OCI组织由Linux基金会和Docker成立,旨在围绕容器格式和运行时制定一个开放的工业化标准,OCI 这个组织提出了 OCF(开放容器格式标准),runC 是Docker按照开放容器格式标准(OCF, Open Container Format)制定的一种具体实现,runC 的前身实际上是 Docker 的 libcontainer 项目演化而来。
UnionFS
UnionFS是一种为Linux,FreeBSD和NetBSD操作系统设计的把其他文件系统联合到一个联合挂载点的文件系统服务。它使用branch把不同文件系统的文件和目录“透明地”覆盖,形成一个单一一致的文件系统。这些 branches 或者是 read-only 或者是 read-write 的,所以当对这个虚拟后的联合文件系统进行写操作的时候,系统是真正写到了一个新的文件中。看起来这个虚拟后的联合文件系统是可以对任何文件进行操作的,但是其实它并没有改变原来的文件,这是因为 unionfs 用到了一个重要的资管管理技术叫写时复制。
写时复制(copy-on-write,下文简称CoW),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。它的思想是,如果一个资源是重复的,但没有任何修改,这时候并不需要立即创建一个新的资源;这个资源可以被新旧实例共享。创建新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方式,可以显著地减少未修改资源复制带来的消耗,但是也会在进行资源修改的时候增减小部分的开销。
- AUFS
AUFS 是一种 UnionFS,是 Junjiro Okajima(岡島順治郎)在2006年开发的,AUFS 完全重写了早期的 UnionFS 1.x,其主要目的是为了可靠性和性能,并且引入了一些新的功能,比如可写分支的负载均衡。AUFS在使用上全兼容 UnionFS,而且比之前的 UnionFS 在稳定性和性能上都要好很多,后来的 UnionFS 2.x 开始抄AUFS中的功能。但是它一直没有整合进入 Linux 内核,就是因为 Linus 不让,基本上是因为代码量比较多,而且写得烂,岡島不断地改进代码质量,不断地提交,不断地被Linus 拒掉,在新版的 Docker 中 AUFS 已经被 Overlayfs 代替。
- Overlayfs
Overlayfs是一种 UnionFS,Linux内核3.18后整合了Overlayfs,和 AUFS 的多层不同的是 Overlay 只有两层:一个 upper 文件系统和一个 lower 文件系统,分别代表Docker的镜像层和容器层。当需要修改一个文件时,使用 CoW 将文件从只读的 lower 复制到可写的 upper 进行修改,结果也保存在 upper 层。在 Docker 中,底下的只读层就是 image,可写层就是 Container,当写入一个新文件时,为在容器的快照里为其分配一个新的数据块,文件写在这个空间里,这个叫用时分配。而当要修改已有文件时,使用 CoW 复制分配一个新的原始数据和快照,在这个新分配的空间变更数据,变结束再更新相关的数据结构指向新子文件系统和快照,原来的原始数据和快照没有指针指向,被覆盖,在新版的 Docker 中 AUFS 已经被 Overlayfs 代替。
Registry
Registry 用于保存 Docker 镜像,包括镜像的层次结构和数据,Registry 由 Registry 和 index 组成。
Registry :一个 Docker 镜像的所有版本组成的仓库叫一个 Registry,每个仓库可以有多个标签,每个标签对应一个镜像。
一个 Registry 中可以存在多个 Registry ,划分为顶层仓库和用户仓库,用户仓库的格式为:用户名/仓库名
Index:维护用户账户,镜像的校验及公共命名空间信息,它相当于为 Registry 提供了一个完成用户认证等功能的检索接口
- 用户可自建 Registry ,也可以使用官方的 Docker HUB,Registry 有以下分类
Sponsor Registry :第三方的 Registry ,供客户和 Docker 社区使用
Mirror Registry :第三方的 Registry ,只让客户使用,如:Docker中国、阿里云
Vendor Registry :由发布 Docker 镜像的供应商提供的 Registry 如:红帽的 Registry
Private Registry :私有实体提供的 Registry ,如:自建 Registry 私有的
- 仓库和镜像的组织形式
Docker 仓库名就是一个镜像主名字,一个镜像使用标签来标记来标记不同的版本,所以仓库名 + 标签名才能唯一标识一个镜像,如果在拉取镜像的时候只给了仓库的名字,则默认拉取这个仓库的最新版本镜像,例如:
nginx # 仓库的名字
- nginx:1.15 nginx:latest # 标签可以有多个
- nginx:1.14 nginx:stble # 不同标签标记不同版本
- nginx:1.10 # 仓库 + 标签能够唯一标识镜像
- 查看所有可用标签
https://hub.docker.com/r/library/nginx/tags/
- Docker HUB
一种是Docker官方提供的 Registry,称为Docker Hub,它和Github一样,我们可以注册账号,并存储自己的镜像,也可以使用别人的镜像,由于 Docker HUB 仓库在国内受到 GFW 的限制,速度极慢,所以互联网上有一些非官方的加速器。
- 国内加速器
https://cr.console.aliyun.com/cn-hangzhou/mirrors # 阿里云镜像加速
http://www.docker-cn.com/registry-mirror # Docker 中国官方镜像加速
https://docker.mirrors.ustc.edu.cn/ # 中科大镜像加速器
Docker镜像
Base镜像
BASE 镜像简单来说就是不依赖其他任何镜像,完全从 0 开始建起,其他镜像都是建立在他的之上,可以比喻为大楼的地基,BASE 镜像有两层含义:
- 不依赖其他镜像,从 scratch (官方制作的特殊空镜像)镜像构建
- 任何镜像都可以作为基础镜像被再次增加新的内容
所以,能称作 BASE 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。
镜像组成
- Linux系统文件系统组成
1. bootfs (引导文件系统),包含: bootloader 和 kernel,bootloader 作用是加载 kernel 到内存运行,完成后 bootfs 就从内存卸载。
2. rootfs (根文件系统),即:bin,etc,lib,mnt,root,sbin,sys,usr,dev,home,media,proc,run,srv,tmp,var,这些目录组成。
- Docker镜像的文件组成
1. bootf (引导文件系统),Docker 镜像使用系统 kernel ,无论什么发行版的 Docker 镜像都直接使用(且只能使用)宿主机的 kernel 且不能修改。
2. rootfs (根文件系统),Docker 不同发行版镜像都只需提供 rootfs ,镜像是只读的无法写入,Docker 镜像是建立在 UnionFS 这种文件系统之上。
分层结构
- 镜像的分层结构
Docker 支持通过扩展现有镜像,创建新的镜像,实际上,Docker Hub 中 99% 的镜像都是通过在 base 镜像中安装和配置需要的软件构建出来的,比如:Nginx 的 base 层是 Alpine Linux 再加上 Nginx 组成,多个镜像共享一个 base 层,这很大程度节约磁盘空间。
由于 Docker 镜像是只读的,Docker 容器在启动后会按顺序加载每一层,最后加载一个可读写的层,所以容器启动后的一切操作都相当于在新层进行读写,所以基于同一个 base 镜像的镜像是不会互相干扰的。
加载过程
- Docker镜像的加载过程
1. Linux 启动时,会首先将 rootfs 置为 readonly, 进行一系列检查, 然后将其切换为 readwrite 供用户使用,用户直接对 rootfs 读写。
2. 在 Docker 中,起初也是将 rootfs 以 readonly 方式加载并检查,之后并不会把 rootfs 的 read-only 改为 read-write,而是用 UnionFS 的 union mount 机制将一个或 read-only 的 rootfs 加载到之前的 read-only 的 rootfs 层之上,在加载了这么多层的 rootfs 之后,仍然让它看起来只像是一个文件系统,在 Docker 的体系里把 union mount 的这些 read-only 的 rootfs 叫做Docker的镜像,但是此时的每一层 rootfs 都是 read-only 的,我们此时还不能对其进行操作。当我们创建一个容器,也就是将 Docker 镜像进行实例化,系统会在一层或是多层 read-only 的 rootfs 之上分配一层空的read-write 的 rootfs。
写时复制
- 联合文件系统与写时复制
镜像层数可能会很多,所有镜像层会联合在一起组成一个统一的文件系统,如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
1. 添加文件:在容器中创建文件时,新文件被添加到容器层中。
2. 读取文件:在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。
3. 修改文件:在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
4. 删除文件:在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
入门使用
工作模式
- Docker Server
Docker daemon 的主要组成部分,默认只监听在本机的 Unix Sock 文件,还可以使用 IPV4 Sock,接受客户端的连接。
- Docker Client
Docker的客户端,默认连接本机
安装部署
- 检查内核版本,Docker 要求 CentOS 系统的内核版本高于 3.10,并且 extras 的 YUM 仓库必须启用。
[root@localhost ~]# uname -r
3.10.0-862.9.1.el7.x86_64
- 设置 Docker 源
curl -o /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
- 安装 Docker 并启动
yum install docker-ce
- 启动 Docker 并查看 Docker 版本
systemctl enable docker
systemctl start docker
docker version
- vim /etc/docker/daemon.json,配置配置加速器 Azure
"http://f1361db2.m.daocloud.io"
"https://dockerhub.azk8s.cn"
"https://reg-mirror.qiniu.com"
cat >/etc/docker/daemon.json<<EOF
{
"registry-mirrors": ["https://dockerhub.azk8s.cn"]
}
EOF
- Docker 日志文件,默认会打印在系统日志
tailf /var/log/messages | grep docker
- 查看服务器实时事件
docker events
镜像管理
- 搜索镜像,和搜索结果的字段含义
docker search nginx
NAME |
DESCRIPTION |
STARS |
OFFICIAL |
AUTOMATED |
|---|---|---|---|---|
| 镜像名称 | 镜像描述 | 星级 | 是否为官方 | 是否自动创建 |
- 拉取镜像指令
docker image pull nginx # 直接下载最新版本
docker image pull nginx:stable-alpine # 指定标签下载
docker pull quay.io/coreos/flannel:v0.10.0-amd64 # 指定第三方仓库和标签下载
- 查看本地镜像
docker image ls # 查看本地镜像
docker image ls --no-trunc # 查看本地镜像完整信息
- 删除一个镜像
docker image rm nginx
- 为镜像打新的标签
docker image tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
docker image tag e1ddd7948a1c jinheng/httpd:latest # 为 e1ddd7948a1c 镜像打一个新的标签
- 查看镜像的详细信息,JSON 数组
docker image inspect busybox:latest
- 查看镜像的历史版本信息
docker image history busybox:latest
- 删除 <none> 的镜像
docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }') # 停止处于退出状态的容器
docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }') # 删除容器
docker rmi $(docker images | grep "none" | awk '{print $3}') # 删除镜像
启动一个容器
- 创建并启动一个容器,如果镜像在本地不存在,则自动联网下载
docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
OPTIONS # 容器启动选项
-t, --tty # 打开一个终端,提供交互式访问
-i, --interactive # 保持交互式访问
-d, --detach # 在后台运行容器
--name string # 为容器指定一个名称
--rm # 容器停止时自动删除容器
IMAGE # 容器使用的镜像,本地没有的镜像会自动联网下载
COMMAND # 启动后运行的命令,缺省为容器内部默认命令
ARG # 启动后运行命令的参数
- 启动一个容器,运行容器内默认的程序,-it 打开默认的交互式终端,–rm 退出容器后自动删除容器
docker container run --name b1 -it --rm busybox # 没有指定运行程序,-it 则会默认打开交互式终端程序 /bin/sh
docker container run --name b1 -it --rm busybox top # 运行自定义程序,当程序退出,容器就会结束
- 启动一个容器,会执行容器默认的命令,-d 在后台执行(与 -it 不能同时使用)
docker container run --name b2 --rm -d nginx # 默认程序为 nginx 会运行为守护进程,它会一直在后台运行
docker container run --name b2 --rm -d busybox sleep 10 # 当运行的 sleep 进程结束,容器就会退出
- 创建一个容器,但不启动
docker container create --name b2 nginx # 创建一个容器,不运行
- 启动停止删除容器
docker container start # 启动一个容器
docker container stop # 停止一个容器
docker container kill # 强制停止一个容器
docker container pause # 暂停一个容器
docker container unpause # 取消暂停一个容器
docker container rm # 删除一个容器
连入运行中的容器
- 在容器中运行额外的命令,-it 终端交互,在容器中运行额外的指令,退出不会停止容器
docker container run --name b2 --rm -d nginx # 启动一个容器,运行默认的进程
docker container exec -it b2 /bin/sh # 连接到已经启动的容器,额外运行一个交互式终端
- 附加到当前容器的
standard input, output也就是附加到主进程,注意从容器退出容器将终止(使用 –sig-proxy=false 不将退出信号代理给容器进程,退出也不会结束容器)
docker container run --name b2 --rm -d nginx # 启动一个容器,运行默认的进程
docker container attach b2 # 附加到已经启动的容器主进程,注意从这个进程中退出(键盘中断),容器就会退出
docker container attach --sig-proxy=false b2 # 附加到已经启动的容器主进程,但是收到的信号不会代理给容器内部的进程,而是被 attach 本身接收,退出不会结束容器
- 启动处于停止状态的容器,-a attach 附加到容器的主进程,-i 打开交互式终端,注意从容器退出容器将终止
docker container create --name b2 --rm nginx # 创建一个容器但不启动
docker container start -i -a b2 # 启动容器,打开一个交互式终端并将终端附加到容器的标准输入、标准输出
容器状态查看
- 列出正在运行的容器,和列出结果的字段含义
docker container ls # 列出正在运行的容器
docker container ls -a # 列出包括停止的所有容器
| CONTAINER ID | IMAGE | COMMAND | CREATED | STATUS | PORTS | NAMES |
|---|---|---|---|---|---|---|
| 容器ID | 镜像 | 运行的程序 | 创建时间 | 当前状态 | 映射端口 | 容器名字 |
- 查看一个容器的详细信息,使用 go 模板语言可以提取单项信息
docker container inspect web1
- 查看容器运行的进程号
docker inspect --format "{{.State.Pid}}" <CONTAINER ID or NAME>
- 查看容器的IP地址信息
docker inspect --format '{{.NetworkSettings.IPAddress}}' <CONTAINER ID or NAME>
- 查看容器内部运行的进程
docker top <CONTAINER ID or NAME>
- 查看一个容器的日志
docker container logs web1
推送镜像到仓库
- 登陆到 Registry
docker login [OPTIONS] [SERVER]
Options:
-u, --username string # Username
-p, --password string # Password
--password-stdin # 从标准输入获取密码
docker login -u Rongyun -p 123456 # 默认登陆 Docker HUB
docker login --username=user registry.cn-hangzhou.aliyuncs.com # 登陆到阿里云
- 为本地镜像打标签
docker image tag 11426a19f1a2 momokanshijie/httpd # 准备推送到 Docker HUB
docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/Rongyun/test:[tag] # 准备推送到阿里云
- 推送镜像,注意镜像的名称一定要和 Registry 相同
docker image push [OPTIONS] NAME[:TAG]
docker image push momokanshijie/httpd # 推送到 Docker HUB
docker push registry.cn-hangzhou.aliyuncs.com/Rongyun/test:[tag] # 推送到阿里云
- 拉取镜像
docker image pull registry.cn-hangzhou.aliyuncs.com/Rongyun/test:[tag] # 从阿里云拉取
基于容器制作镜像
- 首先运行一个镜像,在容器内部做出需要的修改,最后使用 commit 基于容器来制作一个新的镜像,例如:
docker run --name centos -it centos /bin/bash
- 提交对容器的修改到新的镜像,-c 来修改镜像的 Docker 指令(列表格式)。
docker container commit -a "Rongyun" centos Rongyun_centos
docker container commit -a "Rongyun" -c "CMD ['/bin/httpd','-f','-h','/data/html']" -p centos Rongyun_centos
- 基于已有的容器制作镜像
docker container commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
OPTIONS: # 提交选项
-a, --author string # 作者信息
-c, --change list # 修改 Docker 镜像的一些指令定义。就是 inspect 信息
-m, --message string # 提交信息
-p, --pause # 提交时暂停容器运行
CONTAINER: # 容器名称
REPOSITORY[:TAG] # 仓库名称 + 标签
镜像导入导出
- 导出镜像到文件
docker image save [OPTIONS] IMAGE [IMAGE...]
OPTIONS:
-o, --output string # 保存到指定的文件名中
docker image save -o /tmp/centos6.9.tar.gz centos # 导出 centos 镜像到 /tmp/centos6.9.tar.gz 文件中
- 从文件导入镜像
docker image load [OPTIONS]
Options:
-i, --input string # 从归档文件中读取
-q, --quiet # 安静模式
docker image load -i /tmp/centos6.9.tar.gz # 导入 /tmp/centos6.9.tar.gz
命令大全
- daemon.json,可用全部参数
{
"authorization-plugins": [],
"data-root": "",
"dns": [],
"dns-opts": [],
"dns-search": [],
"exec-opts": [],
"exec-root": "",
"experimental": false,
"storage-driver": "",
"storage-opts": [],
"labels": [],
"live-restore": true,
"log-driver": "",
"log-opts": {},
"mtu": 0,
"pidfile": "",
"cluster-store": "",
"cluster-store-opts": {},
"cluster-advertise": "",
"max-concurrent-downloads": 3,
"max-concurrent-uploads": 5,
"default-shm-size": "64M",
"shutdown-timeout": 15,
"debug": true,
"hosts": [],
"log-level": "",
"tls": true,
"tlsverify": true,
"tlscacert": "",
"tlscert": "",
"tlskey": "",
"swarm-default-advertise-addr": "",
"api-cors-header": "",
"selinux-enabled": false,
"userns-remap": "",
"group": "",
"cgroup-parent": "",
"default-ulimits": {},
"init": false,
"init-path": "/usr/libexec/docker-init",
"ipv6": false,
"iptables": false,
"ip-forward": false,
"ip-masq": false,
"userland-proxy": false,
"userland-proxy-path": "/usr/libexec/docker-proxy",
"ip": "0.0.0.0",
"bridge": "",
"bip": "",
"fixed-cidr": "",
"fixed-cidr-v6": "",
"default-gateway": "",
"default-gateway-v6": "",
"icc": false,
"raw-logs": false,
"allow-nondistributable-artifacts": [],
"registry-mirrors": [],
"seccomp-profile": "",
"insecure-registries": [],
"no-new-privileges": false,
"default-runtime": "runc",
"oom-score-adjust": -500,
"node-generic-resources": ["NVIDIA-GPU=UUID1", "NVIDIA-GPU=UUID2"],
"runtimes": {
"cc-runtime": {
"path": "/usr/bin/cc-runtime"
},
"custom": {
"path": "/usr/local/bin/my-runc-replacement",
"runtimeArgs": [
"--debug"
]
}
}
}
- docker container run,可用全部选项
Usage: docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container
Options:
--add-host list 添加自定义主机到IP映射 (host:ip)
-a, --attach list 附加到STDIN,STDOUT或STDERR
--blkio-weight uint16 阻止IO(相对权重),介于10和1000之间,或0阻止(默认为0)
--blkio-weight-device list 阻止IO权重(相对设备权重)(默认 [])
--cap-add list 添加Linux功能
--cap-drop list 删除Linux功能
--cgroup-parent string 容器的可选父cgroup
--cidfile string 将容器ID写入文件
--cpu-period int 限制CPU CFS(完全公平调度程序)期间
--cpu-quota int 限制CPU CFS(完全公平调度程序)配额
--cpu-rt-period int 限制CPU实时周期(以微秒为单位)
--cpu-rt-runtime int 以微秒为单位限制CPU实时运行时间
-c, --cpu-shares int CPU份额(相对权重)
--cpus decimal CPU数量
--cpuset-cpus string 允许执行的CPU(0-3,0,1)
--cpuset-mems string 允许执行的MEM(0-3,0,1)
-d, --detach 在后台运行容器并打印容器ID
--detach-keys string 覆盖用于分离容器的键序列
--device list 将主机设备添加到容器中
--device-cgroup-rule list 将规则添加到cgroup允许的设备列表中
--device-read-bps list 限制设备的读取速率(每秒字节数)(默认为[])
--device-read-iops list 限制设备的读取速率(每秒IO)(默认为[])
--device-write-bps list 限制写入速率(每秒字节数)到设备(默认[])
--device-write-iops list 限制写入速率(每秒IO)到设备(默认[])
--disable-content-trust 跳过图像验证(默认为true)
--dns list 设置自定义DNS服务器
--dns-option list 设置DNS选项
--dns-search list 设置自定义DNS搜索域
--entrypoint string 覆盖图像的默认 ENTRYPOINT 例如:docker run -it --entrypoint /bin/sh busybox -c 'echo ok'
-e, --env list 设置环境变量
--env-file list 读入环境变量文件
--expose list 暴露端口或一系列端口
--group-add list 添加其他组以加入
--health-cmd string 运行以检查运行状况的命令
--health-interval duration 运行检查之间的时间(ms|s|m|h) (默认 0s)
--health-retries int 报告不健康需要连续失败
--health-start-period duration 在开始运行状况重试倒计时之前初始化容器的开始时间段(ms|s|m|h) (默认 0s)
--health-timeout duration 允许一次检查运行的最长时间(ms|s|m|h) (默认 0s)
--help 打印用法
-h, --hostname string 容器主机名
--init 在容器内运行init,转发信号并重新获取进程
-i, --interactive 即使没有连接,也要保持STDIN打开
--ip string IPv4地址(例如,172.30.100.104)
--ip6 string IPv6地址(例如,2001:db8::33)
--ipc string 使用IPC模式
--isolation string 容器隔离技术
-l, --label list 在容器上设置元数据
--label-file list 读入行分隔的标签文件
--link list 添加到另一个容器的链接
--link-local-ip list 容器IPv4 / IPv6链路本地地址
--log-driver string 记录容器的驱动程序
--log-opt list 日志驱动程序选项
--mac-address string 容器MAC地址(例如,92:d0:c6:0a:29:33)
-m, --memory bytes 内存限制
--memory-reservation bytes 内存软限制
--memory-swap bytes 交换限制等于内存加交换:'-1' 以启用无限制交换
--memory-swappiness int 调整容器内存swappiness(0到100)(默认-1)
--oom-kill-disable 禁用 OOM 杀手
--oom-score-adj int 调整主机的 OOM 首选项(-1000到1000)
--kernel-memory bytes 内核内存限制
--mount mount 将文件系统挂载附加到容器
--name string 为容器指定名称
--network string 将容器连接到网络(默认为"default")
--network-alias list 为容器添加网络范围的别名
--no-healthcheck 禁用任何容器指定的 HEALTHCHECK
--pid string 要使用的PID命名空间
--pids-limit int 调整容器pids限制(设置-1为无限制)
--privileged 为此容器授予扩展权限
-p, --publish list 将容器的端口发布到主机
-P, --publish-all 将所有公开的端口发布到随机端口
--read-only 将容器的根文件系统挂载为只读
--restart string 重新启动容器退出时应用的策略(默认为"no")
--rm 退出时自动删除容器
--runtime string 用于此容器的运行时
--security-opt list 安全选项
--shm-size bytes / dev / shm的大小
--sig-proxy 代理接收到进程的信号(默认为true)
--stop-signal string 用于停止容器的信号(默认为"SIGTERM")
--stop-timeout int 停止容器的超时(以秒为单位)
--storage-opt list 容器的存储驱动程序选项
--sysctl map Sysctl选项(默认map [])
--tmpfs list 挂载tmpfs目录
-t, --tty 分配伪TTY
--ulimit ulimit Ulimit选项(默认[])
-u, --user string 用户名或UID(格式: <name|uid>[:<group|gid>])
--userns string 要使用的用户名称空间
--uts string 要使用的UTS名称空间
-v, --volume list 绑定一个卷
--volume-driver string 容器的可选卷驱动程序
--volumes-from list 从指定容器装载卷
-w, --workdir string 容器内的工作目录
网络连接
名称空间
在 Linux 中提供了对网络名称空间进行操作的命令,可以将创建的虚拟网卡移动到一个指定的命名空间。
- ip 网络命名空间相关命令
ip netns list # 列出网络名称空间
ip netns add NAME # 添加网络名称空间
ip netns set NAME NETNSID # 设置网络名称空间的 SID
ip [-all] netns delete [NAME] # 删除网络名称空间
ip netns identify [PID] #
ip netns pids NAME #
ip [-all] netns exec [NAME] cmd ... # 在网络名称空间中执行命令
ip netns monitor #
ip netns list-id #
ip link set # 移动设备到指定的网络名称空间
- 创建名称空间
ip netns add r1
ip netns add r2
- 查看指定网络名称空间内网卡信息
ip netns exec r1 ip a # 只有一个 lo 接口
- 创建一对网卡
ip link add name veth1.1 type veth peer name veth1.2 # 使用 ip a 可以看到这两个网卡
- 将虚拟网卡移动到指定的名称空间
ip link set dev veth1.2 netns r1 # 将一个网卡移动到 r1 名称空间
- 现在查看
ip netns exec r1 ip a
- 在指定网络名称空间执行命令,修改网卡的名称
ip netns exec r1 ip link set dev veth1.2 name eth0
- 将刚创建的两个虚拟网卡中的 veth1.1 激活,并且将另外一般也激活,这样两块网卡就能通信了
ip addr add 10.0.0.1/24 dev veth1.1 # 为在宿主机一端的网卡设置 IP 地址
ip link set dev veth1.1 up # 启动宿主机一端的网卡
ip netns exec r1 ip addr add 10.0.0.2/24 dev eth0 # 为 r1 名称空间的网卡设置IP地址
ip netns exec r1 ip link set dev eth0 up # 启动 r1 名称空间的网卡
ping 10.0.0.2 # 现在宿主机与 r1 可以通信
- 将 veth1 网卡移动到 r2 的名称空间,现在两个名称空间的网卡就可以通信了
ip link set dev veth1.1 netns r2 # 移动 veth1.1 到 r2 名称空间
ip netns exec r2 ip link set dev veth1.1 name eth0 # 配置 r2 名称空间的网卡名字
ip netns exec r2 ip addr add 10.0.0.3/24 dev eth0 # 配置 r2 名称空间的网卡 ip
ip netns exec r2 ip link set dev eth0 up # 启动 r2 名称空间的网卡
ip netns exec r2 ping 10.0.0.2 # 在 r2 中与 r1 通信
Docker 网络模式
- Docker 安装后默认会创建三种模式的网络,可以通过
docker network ls查看
NETWORK ID NAME DRIVER SCOPE
bdad94c4064f bridge bridge local # 一个 NAT 网络桥,容器默认加入的网络,Docker 默认网络
77c50bc6c253 host host local # 和宿主机共享网络名称空间,直接操作宿主机网络
1e42d83b5291 none null local # 除了 lo 接口,没有任何网络
- 默认网络
在宿主机上可以看到的 docker0,它是一个网桥,同时是一个 NAT 桥,它就是 Docker 默认的网络,它为接入的容器自动分配一个地址,每当启动一个容器,Docker 会虚拟出来一对网卡接口(像网线一样有两头),一个接口在容器内,另一个接口接入 宿主机的 docker0 交换机上。
- 在宿主机上查看网卡信息,
ip addr,可以看到 docker0 和容器的网卡
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:50:78:2f:4d brd ff:ff:ff:ff:ff:ff
8: veth974bf58@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 82:94:17:89:09:dc brd ff:ff:ff:ff:ff:ff link-netnsid 0
10: veth146e765@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether de:3d:0c:76:49:43 brd ff:ff:ff:ff:ff:ff link-netnsid 1
12: vethcf3ebe2@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 9e:2b:60:34:3f:84 brd ff:ff:ff:ff:ff:ff link-netnsid 2
- 可以使用
brctl或者docker network inspect bridge来查看网络拓扑,需要安装yum install bridge-utils,执行命令:brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024250782f4d no veth146e765
veth974bf58
vethcf3ebe2
- Docker0 网桥如何通信
Docker0 桥接到宿主机的物理网卡上,物理网卡的报文也会收到一份,同时 Docker0 工作在 NAT 模式下,所有容器的虚拟网卡都连接到 Docker0 这个网桥的 NAT 网络下,所以从容器发出的报文会经过 Docker0 的 SNAT 到主机外部,从主机外部发来的报文会经过 DNAT 到达容器。
自定义docker0
- 自定义 docker0 桥的网络信息,
vim /etc/docker/daemon.json。
{
"bip": "192.168.1.0/24",
"fixed-cidr": "10.20.0.0/16",
"fixed-cidr-v6": "2001:db8::/64",
"mtu": 1500,
"default-gateway": "10.20.1.1",
"default-gateway-v6": "2001:db8:abcd::89",
"dns": ["10.20.1.2","10.20.1.3"]
}
核心选项为:bip,即 bridge ip 之意,用于指定 docker0 桥自身 IP 地址,其他选项可通过此地址计算得出
容器相关命令
- 设置容器的主机名称
docker container run -it --rm --name my_busybox --hostname my_busybox busybox
- 设置容器的 DNS 服务器
docker container run -it --rm --name my_busybox --dns 8.8.8.8 busybox
- 添加域名解析
docker container run -it --rm --add-host www.Rongyun.com:2.2.2.2 busybox
指定网络模式
- 创建容器时指定网络,
--network string。
docker container run [OPTIONS] --network string IMAGE [COMMAND] [ARG...]
- Docker 网络模式
| 网络 | 说明 |
|---|---|
bridge |
每个容器有自己的名称空间,都加入 docker0 网络,可以互相通信、访问外部、宿主机可以直接访问,但从互联网不能访问容器 |
joined |
联盟网络,两个容器使用相同的网络名称空间,它们使用相同的 IP 地址,结果就是两个容器可以直接通过 127.0.0.1 通信 |
host |
开放式容器,容器使用宿主机的网络名称空间,意味着容器与宿主机的网络不隔离,同样容器可以直接对宿主机的网络进行修改 |
null |
没有网络,意味着除了有 lo 接口之外,没有任何网络 |
- 命令帮助
Usage: docker network COMMAND
Commands:
ls # 列出网络
connect # 将容器连接到网络
disconnect # 断开容器与网络的连接
create # 创建一个网络
inspect # 显示一个或多个网络的详细信息
prune # 删除所有未使用的网络
rm # 删除一个或多个网络
开放式容器
- 启动容器时候指定网路类型为 HOST
docker container run -it --rm --network host --name b2 busybox
- 在宿主机查看 80端口,
netstat -tnl |grep 80,已经处于监听
tcp6 0 0 :::80 :::* LISTEN
联盟式网络
联盟式网络就是两个容器使用相同的网络命名空间,它们彼此可以通过 127.0.0.1 进行通信
- 启动第一个容器
docker container run -it --rm --name b1 busybox
- 启动第二个容器
docker container run -it --rm --network container:b1 --name b2 busybox
- 对比网卡信息,发现两个容器处于同一个网络命名空间了,使用相同 IP 地址
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
- 基于联盟式网络的LNMT架构,Nginx 通过 127.0.0.1 就能访问到 Tomcat 同时 Tomcat 也可以直接访问 My-sql
Nginx + Tomcat + Mysql
| | |
\ | /
\ | /
联盟式网络
|
物理网卡
容器间互联
容器的连接(linking)系统是除了端口映射外,另一种跟容器中应用交互的方式。该系统会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息,这种方式可以后可能被废弃。
- 运行两个容器
docker run -d --rm --name node1 nginx
docker run -d --rm --name node2 nginx
- 运行第三个容器,链接到 node1、node2 节点实现负载均衡
docker run --name lb -it --rm -p 80:80 -v /root/nginx.conf:/etc/nginx/nginx.conf --link node1 --link node2 nginx
自定义网络
- 查看 Docker 支持的网络,
docker info
Network:
bridge # 桥
host # 主机
macvlan # 有 vlan 技术的网络
null # 无网络
overlay # 隧道叠加网络
- 创建网络
docker network create -d bridge --subnet "172.26.0.0/16" --gateway "172.26.0.1" mybr0
- 容器指定加入自定义的网络
docker container run -it --name b1 --rm --network mybr0 busybox
端口映射
- 随机映射端口,容器的指定端口映射到主机的指定 IP 地址+随机端口,-p 选项可使用多次
docker container run --name myweb -it --rm -p 80 nginx:stable-alpine
docker container run --name myweb -it --rm -p 172.16.100.253::80 nginx:stable-alpine
- 指定映射端口,容器的指定端口映射到主机的指定 IP 地址 + 端口,-p 选项可使用多次
docker container run --name myweb -it --rm -p 80:80 nginx:stable-alpine
docker container run --name myweb -it --rm -p 172.16.100.253:8080:80 nginx:stable-alpine
- 随机映射容器所有端口,将容器计划暴漏的所有端口全部映射至主机的随机端口
docker container run --name myweb -it --rm -P nginx:stable-alpine
- 查看指定容器 IP 端口映射
docker container port myweb # 查看随机映射到的端口
iptables -t nat -vnL # 查看随机映射到的端口
存储卷
在 Docker 中运行的程序如果需要存储数据,那么默认会保存在容器层状文件系统的最顶层,也就是读写层,这其中还用到了写时复制的过程,所以存储性能一般都比较低,但是最重要的一点是,如果程序需要持久存储数据,那么这个容器就不能删除,因为如果删除容器那么其数据将全部丢失,所以存在以下几个问题:
- 存储于联合文件系统中,不易于宿主机访问
- 容器间数据共享不方便
- 删除容器就会丢失数据
Docker 为容器持久存储数据提供了一个解决方案:卷(volume),卷是容器上的一个或多个目录,此类目录可绕过联合文件系统,与宿主机上某目录进行绑定关联。
卷的类型
Docker有两种类型的卷,每种类型都在容器中存在一个挂载点,但其在宿主机上的位置有所不同。
- 绑定挂载卷,用户指定宿主机关联的目录。
- Docker自行管理的卷,一般位于:
/var/lib/docker/volumes/。
Docker管理卷
- 创建容器时指定Docker管理卷
docker container run -it --rm --name b1 -v /opt/docker busybox
- 查看卷的信息
docker container inspect -f {{.Mounts}} b1
绑定挂载卷
- 创建容器时候指定绑定挂载卷
docker container run -it --rm --name b2 -v /opt/docker:/opt/docker busybox
- 查看卷的信息
docker container inspect -f {{.Mounts}} b2
容器共享卷
- 复制指定容器的卷信息,使用其他容器的卷,而这个容器可以没有启动
docker container run -it --name b4 --volumes-from b3 busybox
- 启动第一个容器
docker container run -it --rm --name b3 -v /opt/docker:/opt/docker busybox
- 启动第二个容器
docker container run -it --name b4 --volumes-from b3 busybox
Dockerfile
Dockerfile 是由一系列命令和参数构成的脚本,一个 Dockerfile 里面包含了构建整个 image 的完整命令。Docker通过 docker build 依次执行 Dockerfile 中的一系列命令来构建 image,各命令独立运行,比如,即便使用 RUN cd /home/hello 切换工作目录,也不影响后面的命令。
注意事项
- 需要注意
1. Dockerfile 每一条指令都会生成一个新层,所以应该指令应该精心设计从而减少层数
2. Dockerfile 的命令不区分大小写,但一般使用大写
3. 使用 # 表示注释,使用 \ 表示转义和续行
4. JSON 数组中应该使用双引号而不是单引号
5. 通常会为新的 Dockerfile 新建一个空目录,目录中只存放与镜像相关的文件
.dockerignore,忽略配置文件
构建过程的第一件事是将整个上下文(递归地)将文件发送到守护进程,所以可以配置 .dockerignore 文件来排除文件和目录。
# comment
*/temp* # 排除路径中包含 /temp 的所有文件和目录,例如:/temporary.txt 和 /temp/test
*/*/temp* # 排除任何目录下路径中包含 /tmp 的所有文件和目录,例如:/subdir/temporary.txt
temp? # 排除根目录中的文件和目录,例如:/tempa与/tempb 被排除在外
- 使用.dockerignore文件
在 Dockerfile 目录下新建一个 .dockerignore 件来指定要忽略的文件和目录。.dockerignore 文件的排除模式语法和 Git 的 .gitignore 文件相似。
- 多阶段构建
Docker 17.05 版本以后,官方就提供了一个新的特性:Multi-stage builds(多阶段构建),你可以使用多阶段构建 来减少所构建镜像的大小,使用多阶段构建,你可以在一个 Dockerfile 中使用多个 FROM 语句。每个 FROM 指令都可以使用不同的基础镜像,并表示开始一个新的构建阶段。
FROM golang AS build-env # 阶段 1
ADD . /go/src/app
WORKDIR /go/src/app
RUN go get -u -v github.com/kardianos/govendor
RUN govendor sync
RUN GOOS=linux GOARCH=386 go build -v -o /go/src/app/app-server
FROM alpine
RUN apk add -U tzdata
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY --from=build-env /go/src/app/app-server /usr/local/bin/app-server # 拷贝阶段 1 构建的文件
EXPOSE 8080
CMD [ "app-server" ]
- 避免不必要的包
为了降低复杂性、减少依赖、减小文件大小和构建时间,应该避免安装额外的或者不必要的软件包。例如,不要在数据库镜像中包含一个文本编辑器。
- 一个容器只做一件事情
应该保证在一个容器中只运行一个进程。将多个应用解耦到不同容器中,保证了容器的横向扩展和复用。例如一个 web 应用程序可能包含三个独立的容器:web应用、数据库、缓存,每个容器都是独立的镜像,分开运行。但这并不是说一个容器就只跑一个进程,因为有的程序可能会自行产生其他进程,比如 Celery 就可以有很多个工作进程。虽然“每个容器跑一个进程”是一条很好的法则,但这并不是一条硬性的规定。我们主要是希望一个容器只关注一件事情,尽量保持干净和模块化。
- 最小化镜像层数
在 Docker 17.05 甚至更早 1.10之 前,尽量减少镜像层数是非常重要的,不过现在的版本已经有了一定的改善了:
在 1.10 以后,只有 RUN、COPY 和 ADD 指令会创建层,其他指令会创建临时的中间镜像,但是不会直接增加构建的镜像大小了。 上节课我们也讲解到了 17.05 版本以后增加了多阶段构建的支持,允许我们把需要的数据直接复制到最终的镜像中,这就允许我们在中间阶段包含一些工具或者调试信息了,而且不会增加最终的镜像大小。
当然减少RUN、COPY、ADD的指令仍然是很有必要的,但是我们也需要在 Dockerfile 可读性(也包括长期的可维护性)和减少层数之间做一个平衡。
- 写的很好的一些官方对象,可以作为参考
https://github.com/docker-library/docs
构建镜像
- 构建镜像并设置镜像标签
docker build -t Rongyun:latest ./
- 启动容器时候通过设置环境变量改变容器运行特性
docker container run --env WEBSERVER_PACKAGE="nginx-1.15.1"
FROM
指定基于的基础镜像,Dockerfile 一般总是以 FROM 起始,ARG 指令是 Dockerfile 中唯一可以在 FROM 之前的指令,关于基础镜像,推荐使用:alpine,它是一个完整的发行版,但只有 5M
- 语法
FROM <image> [AS <name>] # 引用仓库的 latest 版本
FROM <image>[:<tag>] [AS <name>] # 使用仓库 + 标签
FROM <image>[@<digest>] [AS <name>] # 使用仓库 + hash 码来引用
- 示例
FROM debian:stretch-slim
MAINTAINER
指令设置生成的镜像中的作者信息,在新版本中弃用,已经被替换为:LABEL 标签。
- MAINTAINER 语法
MAINTAINER <name>
- LABEL 指令可以设置您需要的任何元数据,并且可以使用
docker inspec查看。同样设置 MAINTAINER 信息就可以使用键值方式设置
LABEL maintainer="jinhengyang@foxmail.com>"
COPY
用于从主机复制文件至创建的新镜像文件
- 语法
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] # 文件名中有空格等特殊字符时候使用这种 JSON 数组方式
1. <src> 必须在 Dockerfile 所在的当前目录
2. <dest> 必须使用容器的绝对路径
3. <dest> 文件名不以 / 结尾则视为常规文件,使用 / 结尾表示一个目录
4. <src> 如果是目录,则只递归复制目录内容,不复制目录本身
5. <src> 支持通配符,如果 <src> 为多个那么 <dest> 只能为目录
6. <dest> 如果不存在,则递归创建其父目录
7. <user> <group> 可以使用 UID / GID 或用户名和组名,缺省为 root
- 示例
COPY hom* /mydir/
COPY --chown=docker files* /somedir/
COPY --chown=10:11 files* /somedir/
ADD
类似COPY指令,ADD支持使用TAR文件和URL路径,会自动展开本地TAR ,从 URL下载文件,而网络上的TAR文件不自动展开。
- 语法
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"] # 文件名中有空格等特殊字符时候使用这种 JSON 数组方式
1. 包含 COPY 指令的全部使用规则
2. <src> 可以是文件、tar归档文件、目录、URL
3. <src> 是 identity,gzip,bzip2,xz 的本地 tar 存档文件,则将其自动展开为目录,但 URL 不做展开
4. ADD 指令是根据文件内容来判断是否为归档文件的,与扩展名无关
5. 如果 <src> 是 URL,<dest> 是文件则下载文件名为文件名,如果是目录,则使用 URL 文件名下载保存到目录
- 例如
ADD https://nginx.org/download/nginx-1.14.0.tar.gz /usr/local/src/
WORKDIR
用于为 Dockerfile 中任何 RUN、CMD、ENTRYPOINT、COPY、ADD 指令设定工作目录,如果 WORKDIR 不存在,则会自动创建;
WORKDIR指令可以出现多次,每次影响后面的指令,如果使用相对路径,则表示是相对于前一条 WORKDIR 指令的路径;
WORKDIR 可以解析 ENV 指令设置的环境变量;
- 示例,下面使用相对路径,那么最终的结果为
/a/b/c
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd
VOLUME
为容器指定创建一个卷的挂载点,这个卷是由 Docker 管理的卷,也可以是 JSON 格式的数组。
- 语法和示例
VOLUME /data
VOLUME ["/data"]
1. 如果挂载点路径下有文件存在,则 Docker 在卷挂载完成后将此前的目录中的文件复制到新挂载的卷中
EXPOSE
通知容器在运行时候会监听的端口和协议,如果没有指定协议,默认为TCP,EXPOSE 指令不会主动在容器启动后不会映射宿主机端口,它只作为一个待映射的名单,当使用 -P 参数运行容器的时候,会按照 EXPOSE 指定的端口与宿主机随机映射。
- 语法和规则
EXPOSE <port> [<port>/<protocol>...]
1. 无论 EXPOSE 设置如何,都可以在容器运行时使用 -p 选项覆盖
- 示例
EXPOSE 80/tcp
EXPOSE 80/udp
ENV
为容器运行时设置环境变量,
- 语法和规则
ENV <key> <value>
ENV <key>=<value> ...
1. 第一种形式 <key> 之后的所有内容均被视作 <value>
1. 第二种形式可以设置多个变量
2. 在后续引用时候可以使用变量扩展的形式
3. 支持 ENV 的指令有:ADD,COPY,ENV,EXPOSE,FROM,LABEL,STOPSIGNAL,USER,VOLUME,WORKDIR,ONBUILD
- 示例
ENV MYNAME Rongyun
ENV MYNAME="Rongyun" \
MYDOG=Rex\ The\ Dog \
MYCAT=fluffy
- 引用
$MYNAME # Rongyun
$MYDOG # Rex The Dog
$MYCAT # fluffy
- 变量扩展形式引用
${AGE:-29} # 表示 AGE 不存在则值替换为 29,如果 AGE 存在 则为 $AGE
${MYCAT:+mimi} # 表示 MYCAT 存在则值替换为 mimi ,如果 MYCAT 存在则替换为空
RUN
设置容器编为镜像的段运行的命令,运行命令的结果将在生成的镜像中。
- 语法1
RUN <command>
1. 这种方式命令在 shell 中运行,默认情况下以 /bin/sh -c 方式执行,其执行结果会提交为镜像的一个新层,所以应该使用 && 连接多个命令。
- 语法1 示例
RUN set -x \
&& apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates wget \
&& apt-get purge -y --auto-remove ca-certificates wget
- 语法2
RUN ["executable", "param1", "param2"]
1. 这种方式命令直接使用内核 exec 函数,由于没有 shell 环境,所以 $ 符号、通配符等 shell 特性不能被解析;
2. 需要使用 shell 特性可以使用 RUN ["/bin/bash", "-c", "echo hello"] ,这还可以改变默认的 /bin/sh 来执行指令;
- 语法2 示例
RUN ["/bin/bash", "-c", "echo hello"]
RUN ["/bin/bash", "-c", "set -o pipefail && wget -O - https://some.site | wc -l > /number"]
CMD
为容器的运行阶段执行的默认命令,它可以包含执行文件,也可以省略执行文件而当作为 ENTRYPOINT 设置的命令提供默认参数(可被用户覆盖)。
- 前提知识
1. 在 Linux 中 PID 为 1 的进程称之为 INIT 进程,所有的进程都是它的子进程,同时在 Linux 中如果父进程结束,那么在退出之前会结束所有子进程;
2. 在 Docker 容器内一般只运行一个主进程,它的 PID 为 1,它可以接收 Docker 发来的 SIGTERM 信号来停止容器,其他进程都是它的子进程;
3. 同时如果 PID 为 1 的进程退出,那么它将杀死所有的子进程,然后容器退出;
4. 如果想让容器中的程序为主进程,那么就必须让它的 PID 为 ,才能决定容器的生命周期,才能接收 SIGTERM 信号主动停止容器;
5. shell 有个参数 -c ,它可以实现 fork + exec,意思是首先 fock 子进程,使用子进程替换当前进程,这为使用 shell 启动主进程提供了一种方法。
- 语法1
CMD ["executable","param1","param2"] # 首选 JSON 数组形式
CMD ["param1","param2"] # 作为 ENTRYPOINT 的默认参数
CMD command param1 param2 # shell 方式
1. Dockerfile 只能有一条 CMD 指令,存在多条,则最后一条生效
2. CMD 的主要目的是为 ENTRYPOINT 提供默认的参数,必须使用 JSON 数组格式
3. 注意 JSON 数组格式会使用内核 exec 函数,不识别 shell 的特性
4. 如果使用 shell 形式,那么默认会使用 /bin/sh -c 执行指令
5. 注意它与 RUN 指令的区别,RUN 运行在构建镜像期间,而 CMD 是容器运行期
- JSON 数组形式,由内核 exec 调用启动
CMD ["/bin/sh","-c","/bin/httpd -f -h $WEB_DOC_ROOT"] # 如果参数有值那么应该同参数写在一个引号内,如运行 httpd 是 shell 的参数
"Cmd": [
"/bin/sh", # 内核启动 /bin/sh
"-c", # 同 exec 一样
"/bin/httpd -f -h $WEB_DOC_ROOT" # /bin/sh 启动 httpd 并识别 $ 变量
],
- shell 方式示例,由 shell 启动
CMD /bin/httpd -f -h $WEB_DOC_ROOT
"Cmd": [
"/bin/sh",
"-c",
"/bin/httpd -f -h $WEB_DOC_ROOT"
],
PID USER TIME COMMAND
1 root 0:00 /bin/httpd -f -h /data/web/html/ # 这里可以看出来 Docker 默认帮我们执行了 /bin/sh exec 模式
8 root 0:00 /bin/sh # exec 的结果就是使用子进程替换当前进程,进而 httpd 的 PID 为 1
15 root 0:00 ps
- 为 ENTRYPOINT 指令提供默认参数
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld"]
ENTRYPOINT
设置容器启动时运行的程序,它不会被用户启动容器时指定运行的指令所覆盖,并且用户指定的指令会作为参数放到 ENTRYPOINT 指令的后面。
- 语法
ENTRYPOINT ["executable", "param1", "param2"] # exec 方式,首选
ENTRYPOINT command param1 param2 # shell 方式
1. 用户在启动容器传递的指令都会作为参数传递给 ENTRYPOINT,如:docker run -it yanghttpd ls /etc/ 被 ENTRYPOINT 接收
- exec 方式 示例
CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/sh","/bin/entrypoint.sh"]
CMD 指令将被传递给 ENTRYPOINT 当做容器启动时候的默认参数,同时用户可以在启动容器时指定运行指令,会覆盖 CMD 的默认参数,传递给 ENTRYPOINT
- shell 方式 示例
ENTRYPOINT /bin/httpd -f -h $WEB_DOC_ROOT
设置容器启动的程序,这种形式会使用 /bin/sh -c 来执行命令
- 明确覆盖镜像 ENTRYPOINT 所执行的命令来启动容器
docker container run -it --entrypoint "/bin/sh" busybox -c 'echo ok' // 覆盖 entrypoint 为 /bin/sh 并在后面传递参数,这中方式有违直觉
docker container run -it --entrypoint "" busybox /bin/sh -c 'echo ok' // 推荐使用这样的方法,覆盖 entrypoint 为空,然后在 cmd 中执行
- 一个 NGINX 的示例性项目,要求:启动容器时传递环境变量,从而改变 nginx 的运行
FROM nginx:stable-alpine
LABEL maintainer="yang <jinhengyang@foxmail.com>"
ENV NGX_DOC_ROOT="/data/web/html/"
ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/
EXPOSE 80/tcp
HEALTHCHECK --start-period=3s --interval=1m --timeout=3s \ # 定义容器的健康状态检测,容器启动等待3秒开始1分钟检测一次健康,3s的超时
CMD wget -O -q - http://${IP:-0.0.0.0}:${PORT:-80}/
CMD ["/usr/sbin/nginx","-g","daemon off;"] # 为 ENTRYPOINT 执行的指令传递参数
ENTRYPOINT ["/bin/sh","/bin/entrypoint.sh"] # 默认启动
#!/bin/sh # entrypoint.sh 根据环境变量生成配置文件
cat > /etc/nginx/conf.d/www.conf <<EOF
server {
server_name $HOSTNAME; # 这些变量从容器环境变量中取得
listen ${IP:-0.0.0.0}:${PORT:-80}; # 这些变量从容器环境变量中取得
root ${NGX_DOC_ROOT:-/usr/share/nginx/html};
}
EOF
exec "$@" # 以 fock + exec 方式 执行 CMD 传递的参数,确保 PID 为 1
<h1>hello word</h1> # index.html
docker container run --env PORT=80 # 启动容器时候传递参数,改变容器的环境变量,从而改变容器特性
USER
设置运行指令的用户,影响在该指令后的指令,RUN、CMD、ENTRYPOINT
- 语法
USER <user>[:<group>] or
USER <UID>[:<GID>]
- 示例
USER patrick
HEALTHCHECK
容器健康检查,该指令告诉 Docker 如何判断容器它是否仍在工作,例如:判断一个程序在运行但是无法提供服务的容器。
- 语法
HEALTHCHECK [OPTIONS] CMD command # 通过在容器内运行命令来检查容器运行状况
HEALTHCHECK NONE # 禁用从基础映像继承的任何运行状况检查
OPTIONS:
--interval=DURATION # 检查间隔时间,默认 30s
--timeout=DURATION # 超时时间,默认 30s,超时将视作失败
--start-period=DURATION # 为主进程初始化预留的等待时间,默认 0s
--retries=N # 几次判定为死亡,默认为 3 次
CMD:
CMD # 可以是 CMD 指令,它的返回值 0 表示成功,1 表示失败
1. Dockerfile中只能有一条指令。如果列出多个,则只有最后一个HEALTHCHECK生效。
- 示例
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost/ || exit 1
SHELL
定于运行指令默认的 SHELL 程序,默认值为 /bin/sh,一般不做更改。
- 语法和规则
SHELL ["executable", "parameters"]
该SHELL指令可以多次出现。每条SHELL指令都会覆盖所有先前的SHELL指令,并影响所有后续指令。
STOPSIGNAL
STOPSIGNAL 能够更换 SIGTERM 信号,例如更换为 -9 信号,那么只有 -9 信号才能停止主进程。
- 语法和示例用法
STOPSIGNAL 9
ARG
接收构建期间向 Dockerfile 传递的变量,ARG 是 Dockerfile 中唯一可以在 FROM 之前的指令。
- 示例
ARG author="yang"
LABEL maintainer=${author}
ARG VERSION=latest
FROM busybox:$VERSION
ARG VERSION
RUN echo $VERSION > image_version
- 构建时向 Dockerfile 传递变量
docker image build --build-arg <varname>=<value>
ONBUILD
当镜像用作另一个 Dockerfie 作为基础镜像时,该 ONBUILD 指令向镜像被触发并执行后面的指令。
- 语法
ONBUILD [INSTRUCTION]
- 示例
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
自建仓库
Docker-Registry
官方提供了 docker-distribution ,用来自建 Docker-Registry,部署的 Registry 有两个方式,一种是直接运行在宿主机的 Regsiry,一种是运行为容器。
安装部署
- 安装
docker-distribution。
yum install docker-registry
- vim /etc/docker-distribution/registry/config.yml,配置文件位置
version: 0.1
log:
fields:
service: registry
storage:
cache:
layerinfo: inmemory
filesystem:
rootdirectory: /var/lib/registry # 仓库存储位置
http:
addr: :5000 # 监听的端口
- 启动
systemctl restart docker-distribution
推送镜像
- 设置 Docker 允许 HTTP 协议进行仓库通信,无论上传和下载都需要修改,vim /etc/docker/daemon.json。
{
"insecure-registries": ["template:5000"]
}
- 为镜像打标
docker image tag busybox:latest template:5000/busybox:latest
- 推送到仓库
docker image push template:5000/busybox
- 在另一个客户端拉取镜像
docker pull template:5000/busybox
Harbor
Harbor 是一个用于存储和分发 Docker 镜像的企业级 Registry 服务器,通过添加一些企业必需的功能特性,例如安全、标识和管理等,扩展了开源 Docker Distribution。作为一个企业级私有 Registry 服务器,Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。Harbor 支持安装在多个 Registry 节点的镜像资源复制,镜像全部保存在私有 Registry 中, 确保数据和知识产权在公司内部网络中管控。另外,Harbor 也提供了高级的安全特性,诸如用户管理,访问控制和活动审计等。
基于角色的访问控制- 用户与 Docker 镜像仓库通过“项目”进行组织管理,一个用户可以对多个镜像仓库在同一命名空间(project)里有不同的权限。镜像复制- 镜像可以在多个 Registry 实例中复制(同步)。尤其适合于负载均衡,高可用,混合云和多云的场景。图形化用户界面- 用户可以通过浏览器来浏览,检索当前 Docker 镜像仓库,管理项目和命名空间。AD/LDAP支持 - Harbor 可以集成企业内部已有的 AD/LDAP,用于鉴权认证管理。审计管理- 所有针对镜像仓库的操作都可以被记录追溯,用于审计管理。国际化- 已拥有英文、中文、德文、日文和俄文的本地化版本。更多的语言将会添加进来。RESTful API- RESTful API 提供给管理员对于 Harbor 更多的操控, 使得与其它管理软件集成变得更容易。部署简单- 提供在线和离线两种安装工具, 也可以安装到 vSphere 平台(OVA方式)虚拟设备。
安装配置
- 安装指南
https://github.com/goharbor/harbor/blob/master/docs/installation_guide.md
- 下载地址,选择 offline
https://github.com/goharbor/harbor/releases
https://storage.googleapis.com/harbor-releases/release-1.8.0/harbor-offline-installer-v1.8.0.tgz
- 安装依赖软件
yum install docker-compose
- 解压文件
tar xf harbor-offline-installer-v1.5.2.tar -C /usr/local/ && cd /usr/local/harbor/
- 编辑配置文件,vim /usr/local/harbor/harbor.cfg
hostname = template # 修改为外部可以访问的域名
harbor_admin_password = Harbor12345 # 管理员密码
db_password = root123 # My-sql 密码
- 开始安装,安装完成后会自动启动
bash install.sh
启动停止
- 启动
cd /usr/local/harbor/
docker-compose up -d
- 停止
cd /usr/local/harbor/
docker-compose stop
创建项目
- 打开网页,创建一个用户,再创建一个仓库
http://172.16.100.253/
- 使用刚创建的普通用户登陆,创建一个项目
http://172.16.100.253/
推送镜像
- 设置 Docker 允许 HTTP 协议进行仓库通信,无论上传和下载都需要修改,vim /etc/docker/daemon.json。
{
"registry-mirrors": ["https://registry.docker-cn.com"],
"insecure-registries": ["template:80"]
}
- 为镜像打标
docker image tag busybox:latest template:80/devel/busybox:latest
- 登陆私有仓库
docker login -u Rongyun -p Jinheng890821 template:80
- 推送镜像
docker image push template:80 # 推送全部镜像
docker image push template:80/devel/busybox:latest # 推送一个镜像
资源限制
默认情况下,容器没有资源限制,可以使用主机内核调度程序允许的尽可能多的给定资源。Docker提供了控制容器可以使用的内存,CPU或块IO的方法。
内存资源
Usage: docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
Options:
-m, --memory bytes 容器可以使用的最大内存量。如果设置此选项,则允许的最小值为4m(4兆字节)
--memory-reservation bytes 允许此容器交换到磁盘的内存量,需要首先设置 --memory 选项
--memory-swap bytes 交换限制等于内存加交换:'-1' 以启用无限制交换
--memory-swappiness int 调整容器内存swappiness(0到100)(默认-1)
--oom-kill-disable 禁用 OOM 杀手
--oom-score-adj int 调整主机的 OOM 首选项(-1000到1000)
- –memory-swap 与 –memory 的关系
--memory-swap |
--memory |
功能 |
|---|---|---|
| 正数S | 正数M | 容器内存可用空间为S,其中 RAM 为 M,SWAP 为 S-M,若S=M,则表示容器没有内存 |
| 0 或 unset | 正数M | 若主机(Docker Host)启用了SWAP,则容器的可用的 SWAP为 2*M |
| -1 | 正数M | 若主机(Docker Host)启用了SWAP,则容器可使用最大至主机上所有SWAP空间资源 |
在容器内使用 free 命令看到的 swap 空间并不具有其展示出的空间指示意义。
- OOM
在 Linux 主机上,如果内核检测到没有足够的内存来执行重要的系统功能,它会抛出 OOME 或 Out of Memory 异常,并开始终止进程以释放内存。任何进程都会被杀死,包括 Docker 和其他重要的应用程序。如果错误的进程被杀死,这可以有效地降低整个系统。
Docker 尝试通过调整 Docker 守护程序上的 OOM 优先级来降低这些风险,以便它比系统上的其他进程更不可能被杀死。容器上的 OOM 优先级未调整。这使得单个容器被杀死的可能性比 Docker 守护程序或其他系统进程被杀死的可能性更大。
您不应试图通过在守护程序或容器上手动设置 --oom-score-adj 到极端负数,或通过在容器上设置 --oom-kill-disable 来绕过这些安全措施。
- 限制容器内存大小
docker run --name stress -it --rm --memory 256m lorel/docker-stress-ng:latest --vm 2
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
d5c609605bc4 stress 191.52% 255.9MiB / 256MiB 99.95% 578B / 0B 438MB / 1.7GB 5
- 关于 cgroups 的 oom 杀手
当物理内存达到上限后,系统的默认行为是 kill 掉 cgroup 中继续申请内存的进程,那么怎么控制这样的行为呢?答案是配置 memory.oom_control
/sys/fs/cgroup/memory/memory.oom_control
cgroup.event_control # 用于eventfd的接口
memory.usage_in_bytes # 显示当前已用的内存
memory.limit_in_bytes # 设置/显示当前限制的内存额度
memory.failcnt # 显示内存使用量达到限制值的次数
memory.max_usage_in_bytes # 历史内存最大使用量
memory.soft_limit_in_bytes # 设置/显示当前限制的内存软额度
memory.stat # 显示当前cgroup的内存使用情况
memory.use_hierarchy # 设置/显示是否将子cgroup的内存使用情况统计到当前cgroup里面
memory.force_empty # 触发系统立即尽可能的回收当前cgroup中可以回收的内存
memory.pressure_level # 设置内存压力的通知事件,配合cgroup.event_control一起使用
memory.swappiness # 设置和显示当前的swappiness
memory.move_charge_at_immigrate # 设置当进程移动到其他cgroup中时,它所占用的内存是否也随着移动过去
memory.oom_control # 设置/显示oom controls相关的配置
memory.numa_stat # 显示numa相关的内存
- 动态修改内存限制
echo "268435456" > /sys/fs/cgroup/memory/docker/9fa20d824b18.../memory.limit_in_bytes
echo 2000 > /sys/fs/cgroup/cpu,cpuacct/docker/容器 ID/cpu.shares
CPU限制
默认情况下每个容器对主机CPU周期的访问权限是不受限制的,您可以设置各种约束来限制给定容器访问主机的CPU周期。大多数用户空间进程默认被 CFS 调度器调度。
Usage: docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]
Options:
-c, --cpu-shares int CPU份额(相对比例),如:三个容器,为1:2:5,那么可以是7:1:1,如果全部繁忙,那就按比例分配
--cpus decimal 限制CPU数量,不限制CPU编号,例如,如果主机有两个CPU,设置--cpus="1.5",则容器最多保证一个半CPU
--cpuset-cpus string 限制运行在哪些CPU编号,(0-3,0,1)
--cpu-period int 限制CPU CFS(完全公平调度程序)期间
--cpu-quota int 限制CPU CFS(完全公平调度程序)配额
--cpu-rt-period int 限制CPU实时周期(以微秒为单位)
--cpu-rt-runtime int 以微秒为单位限制CPU实时运行时间
--cpuset-mems string 允许执行的MEM(0-3,0,1)
- 下面的指令会保证容器每秒最多占CPU的50%
docker run --name stress -it --rm --cpus=".5" lorel/docker-stress-ng:latest --cpu 8
- 分配两个CPU,如果有八颗CPU,则按比例分两个,就是占用 200%
docker run --name stress -it --rm --cpus 2 lorel/docker-stress-ng:latest --cpu 8
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
98852c2796cc stress 200.00% 15.82MiB / 1.935GiB 0.80% 648B / 0B 0B / 0B 9
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
98852c2796cc stress 200.00% 15.82MiB / 1.935GiB 0.80% 648B / 0B 0B / 0B 9
- 按份额划分
docker run --name stress1 -it --rm --cpu-shares 512 lorel/docker-stress-ng:latest --cpu 8
docker run --name stress2 -it --rm --cpu-shares 512 lorel/docker-stress-ng:latest --cpu 8
docker run --name stress3 -it --rm --cpu-shares 1024 lorel/docker-stress-ng:latest --cpu 8
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
614274d03cc4 stress1 200.11% 22.65MiB / 1.935GiB 1.14% 648B / 0B 0B / 0B 9
1c737b7a56b9 stress2 201.12% 15.81MiB / 1.935GiB 0.80% 648B / 0B 0B / 0B 9
d8b52ba4567a stress 426.07% 15.82MiB / 1.935GiB 0.80% 648B / 0B 0B / 0B 9
资源监视
- 显示容器的运行进程
docker container top stress
UID PID PPID C STIME TTY TIME CMD
root 19220 19202 0 21:50 pts/0 00:00:00 /usr/bin/stress-ng --cpu 8
root 19265 19220 99 21:50 pts/0 00:00:08 /usr/bin/stress-ng --cpu 8
root 19266 19220 99 21:50 pts/0 00:00:08 /usr/bin/stress-ng --cpu 8
root 19267 19220 99 21:50 pts/0 00:00:08 /usr/bin/stress-ng --cpu 8
root 19268 19220 99 21:50 pts/0 00:00:08 /usr/bin/stress-ng --cpu 8
root 19269 19220 99 21:50 pts/0 00:00:08 /usr/bin/stress-ng --cpu 8
root 19270 19220 99 21:50 pts/0 00:00:08 /usr/bin/stress-ng --cpu 8
root 19271 19220 99 21:50 pts/0 00:00:08 /usr/bin/stress-ng --cpu 8
root 19272 19220 99 21:50 pts/0 00:00:08 /usr/bin/stress-ng --cpu 8
- 显示容器资源使用情况统计信息的实时流
docker container stats [OPTIONS] [CONTAINER...]
docker container stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
e9cfd00dd136 harbor-jobservice 0.08% 8.227MiB / 1.935GiB 0.42% 270kB / 2.43MB 26.9MB / 0B 19
0b8367c53e65 nginx 0.00% 5.293MiB / 1.935GiB 0.27% 61.1kB / 22.6kB 21.1MB / 0B 9
e71810022898 harbor-ui 0.00% 7.777MiB / 1.935GiB 0.39% 123kB / 118kB 29.7MB / 0B 16
8fb94f39f45c registry 0.02% 6.254MiB / 1.935GiB 0.32% 6.18kB / 2.53kB 28.9MB / 0B 14
016f887c4e22 harbor-adminserver 0.00% 5.207MiB / 1.935GiB 0.26% 182kB / 128kB 25.9MB / 0B 14
b7fecac55ebe harbor-db 0.03% 106.6MiB / 1.935GiB 5.38% 49.6kB / 169kB 55.7MB / 6.36MB 36
3b196739335d redis 0.12% 6.762MiB / 1.935GiB 0.34% 2.47MB / 277kB 18.6MB / 62kB 5
f5667ad4c962 harbor-log 0.00% 1.953MiB / 1.935GiB 0.10% 83.1kB / 30.9kB 1