docker学习纪要
1、容器 ≠ docker ? 版本规划如何?
容器不光是 Docker,还有其他容器,比如 CoreOS 的 rkt。为了保证容器生态的健康发展,保证不同容器之间能够兼容,包含 Docker、CoreOS、Google在内的若干公司共同成立了一个叫 Open Container Initiative(OCI) 的组织,其目是制定开放的容器规范。
版本:年.月.补丁版本号
docker version
#Version: 19.03.3
#即为19年3月的版本,补丁版本为3
2、Docker可以在任何场景运行?
Docker镜像是一个不包含Linux内核而且又精简的Linux操作系统(内核是共享宿主机的)。docker的运行要依赖当前操作系统的kernel,镜像只是提供了rootfs。故不同架构的镜像(比如X86下生成的基础镜像在ARM中)是不通用的。
·bootfs (boot file system) 主要包含 bootloader 和 kernel, bootloader主要是引导加载kernel, 当boot成功后 kernel 被加载到内存中后 bootfs就被umount了.
·rootfs (root file system) 包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。
是否有专门跑容器的OS? 有的!CoreOS、atomic 和 ubuntu core等
3、Docker和虚拟机区别
docker:是宿主机上的线程,使用宿主机的kernel也就是bootfs驱动,自己提供上层的用户空间(userland)=rootfs及文件。所有修改在容器层,不会修改镜像层更不会修改到宿主机,所以他更轻(rootfs),可共享。
虚拟机:是从宿主机上划分相应的硬件资源,运行创建出的完整OS,包含独立的kernel层及userland层,自己更完整(rootfs/bootfs/硬件)、独立,但更臃肿。
4、镜像和容器什么关系?镜像是分层的?镜像可以被多个容器共享?
镜像=只读的,容器=镜像+一个读写层
典型的Linux在启动后,首先将 rootfs 置为 readonly, 进行一系列检查, 然后将其切换为 “readwrite” 供用户使用。
在docker中,起初也是将 rootfs 以readonly方式加载并检查,然而接下来将一个 readwrite 文件系统挂载在 readonly 的rootfs之上,并且允许再次将下层的 file system设定为readonly 并且向上叠加, 这样一组readonly和一个writeable的结构构成一个container的运行目录。
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
添加文件:在容器中创建文件时,新文件被添加到容器层中。
读取文件:在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。
修改文件:在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
删除文件:在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
4.1 从dockerbuild看,首先原目录build,根据dockerfile逐层创建,且历史创建过镜像的相同操作中多级使用了缓存
在修改/data/ncc/nchome_uap-fs/nchome_uap-fs/temp/stage/ROOT/下增加了随便一个文件之后,没有使用cache
4.2从docker history看,修改文件前
修改文件后
结合实例来看,镜像的文件在宿主机上是如何保存的?运作机制是?
1、查看镜像的详细信息,这里定义了一系列镜像运行时的参数,其中这里重点关注GraphDriver项
docker image inspect reg.yyuap.io:81/c87e2267-1001-4c70-bb2a-ab41f3b81aa3/uap-init:20200612203405
其中:GraphDriver是什么?----镜像管理驱动,主要用于管理和维护镜像,包括把镜像从仓库下载下来,到运行时把镜像挂载起来可以被容器访问等,都是graph driver做的。涉及的命令有Docker push import export load save build。
维基词条:联合文件系统(union filesystem),在操作系统中,联合挂载(union mounting)是一种将多个目录结合成一个目录的方式,这个目录看起来就像包含了他们结合的内容一样。
实现通常辅有“写时复制(CoW)”的实现技术,这样任何对于底层文件系统分层的更改都会被“向上拷贝”到文件系统的一个临时、工作、或高层的分层里面。这个可写的层然后可以被看做是一个“改动(diff)”,能将之应用到下层只读的层,而这些层很可能作为底层被很多容器的进程中共享。这是一个很重要的点。
一个Docker中使用分层文件系统的好处就是,1000个运行着bash的ubuntu:latest容器的副本,会共享一个底层的镜像,而并不会产生1000个文件系统的副本。
并且同样重要的是,对于aufs和overlay的实现,用来读取或执行共享库的共享内存也在所有运行的容器之间共享,大大的减少了通用库如'libc'的内存占用。这是一个分层策略的巨大优势,同时也是Docker的graphdriver是引擎中相当重要的一部分的原因之一。
我们的GraphDriver=overlay2,还有如下vfs、aufs、overlay、overlay2、btrfs、zfs、devicemapper和windows。
"LowerDir": "/data/docker/overlay2/0692d0cadd2c300813a3343a3f6077b6e0c35603409499c6c961b79d794609ab/diff:/data/docker/overlay2/0b03c61e8dc6c2c8136fe986c6b83acc70a929a5dbffaf53f0caee7a7a9e993b/diff:/data/docker/overlay2/0395e2f86eb0e796574e5ba12d72912803e6d20fc09e1b6173e62a1a03f65305/diff:/data/docker/overlay2/315b934acc4a0538d2fd00ab2a2709aa2df209292584e050ce66f6c1f899b92b/diff:/data/docker/overlay2/3b920b7c5277f464c39d6725c6f7728c715d82e7c6b472b226298aaf588c64dc/diff:/data/docker/overlay2/68b33d4136d503e111d2dd86f114c6c4cd4a2135ef1eed9858eb2508670b9e25/diff",
"MergedDir": "/data/docker/overlay2/68b7990f2f31cd3f97a891be6a499cef9aed5e56b42ee73af47e4e357bead299/merged",
"UpperDir": "/data/docker/overlay2/68b7990f2f31cd3f97a891be6a499cef9aed5e56b42ee73af47e4e357bead299/diff",
"WorkDir": "/data/docker/overlay2/68b7990f2f31cd3f97a891be6a499cef9aed5e56b42ee73af47e4e357bead299/work"
LowerDir:展示了镜像的层叠关系,从左至右、层叠顺序为从上到下
/data/docker/overlay2/0692d0cadd2c300813a3343a3f6077b6e0c35603409499c6c961b79d794609ab/diff下只有/usr/local/bin/patch.sh 即补丁平台脚本
/data/docker/overlay2/0b03c61e8dc6c2c8136fe986c6b83acc70a929a5dbffaf53f0caee7a7a9e993b/diff下只有/usr/local/bin/offline.sh 即优雅下线脚本
/data/docker/overlay2/0395e2f86eb0e796574e5ba12d72912803e6d20fc09e1b6173e62a1a03f65305/diff下只有/usr/local/bin/entrypoint.sh即初始化脚本
/data/docker/overlay2/315b934acc4a0538d2fd00ab2a2709aa2df209292584e050ce66f6c1f899b92b/diff下有bin etc root lib lib64 usr 等,即用户文件系统rootfs(这里是我们的基础镜像)
/data/docker/overlay2/3b920b7c5277f464c39d6725c6f7728c715d82e7c6b472b226298aaf588c64dc/diff下有/usr/local/tomcat/目录
/data/docker/overlay2/68b33d4136d503e111d2dd86f114c6c4cd4a2135ef1eed9858eb2508670b9e25/diff下是基础镜像的基础镜像alpine:latest层
每一层“变化”的文件,都在diff目录下,每一层还有一个link文件,是该层的短名称。所有的短名称都会在/data/docker/overlay2/l/下有相应的软连接,可以快速找到该层的diff目录。同时,这个文件存储的是指向本layer的一个符号链接文件。overlayfs的原理就是将多个layer挂载到一个目录下,而挂载的多个layer是通过mount options指定的,这个选项的最大长度有限制,通常为4096,如果我们直接使用上面的sha256目录,mount的layer个数就太少了,因此实际mount时使用的是这个符号链接,就能够多支持一些layer了
而相应的,一个所谓的“镜像”就是这样从底层到上层的N个层组合出来的。而相同的层,在不同的镜像之间是共享的。
2、从docker history 看分层
对应dockerfile
FROM tomcat:9.0.31-jdk8u202-alpine
COPY ROOT /usr/local/tomcat/webapps/ROOT
EXPOSE 8888
可以看到以下3层,是通过本dockerfile创建的,但是后面的呢? 是基础镜像。
再看下基础镜像的dockerfile,就知道后面的层是如何来的了
5、docker镜像在本机是如何保存的?
/data/docker/image/overlay2/
├—— distribution
├—— imagedb
├—— layerdb
└—— repositories.json
repositories.json文件存储了当前所有的镜像和其镜像仓库的信息。
imagedb/content/sha256/<IMAGE-ID>目录下是每个镜像的详细信息,文件名即为镜像ID.这里面除了上面提到RootFS信息外,还有镜像的构建历史命令信息。
然后是layerdb/sha256/<ChainID>:这里面的每个目录即为镜像每layer的信息,文件名是layer的ChainID.
通过repositories.json找到镜像的sha256,通过相应的算法即可找到该镜像第一层的位置。
6、镜像制作
6.1通过dockerfile
以merge为例,目前的形态是
cd /data/ncc/nchome_merge/
docker build -f Dockerfile . -t reg.yyuap.io/c87e2267-1001-4c70-bb2a-ab41f3b81aa3/merge:tag
制作过程:
Sending build context to Docker daemon ---会将Dockerfile目录当前目录的文件夹都发送给Dockerdaemon,文件会临时拷贝到/data/docker/tmp/docker-builderXXXXXX/下
#可以通过在dockerfile同级目录配置.dockerignore进行忽略:http://nccloud.yytimes.com/q_322.html
#这里既有的模式可以优化这块,将dockerfile放到ROOT目录再build,实现本环节数据操作量减半
2、以当前运行的镜像为模板,生成新镜像
背景:当前镜像打了很多补丁,补丁和原始镜像是分离的,如何把带着补丁的镜像生成新镜像?
核心命令:
docker commit -a="nxz" -m="niuxiaozheng" 7b2d91a27fa6 reg.yyuap.io/c87e2267-1001-4c70-bb2a-ab41f3b81aa3/fi-gl:v2020
参考:http://nccloud.yytimes.com/q_209.html
PS.不推荐通过docker commit提交,由于通过本指令提交的镜像,其修改无法通过分层文件体现,一方面涉及镜像的修改不可知;一方面会导致存储方面的浪费,再者由于存储分层机制,在既有层删除、增加的内容,均会保留,导致镜像体积膨胀;
6.2 "docker build -f Dockerfile . -t reg.yyuap.io/c87e2267-1001-4c70-bb2a-ab41f3b81aa3/merge:test"其中-t前的“.”是做什么用的?
答:镜像构建过程中的上下文环境目录。在数据第一步打包传送给dockerd时取的数据目录,以及之后它基于docker在做处理COPY等命令时取的相对路径。
#同理,这块延伸上述5.1的build优化思路,可以不改变dockerfile位置,但是修改dockerfile内容及docker build命令,指定上下文路径,参考如下
#dockerfile第2行改为COPY ROOT /usr/local/tomcat/webapps/ROOT
docker build -f Dockerfile nchome_merge/temp/stage -t reg.yyuap.io/c87e2267-1001-4c70-bb2a-ab41f3b81aa3/merge:test
6.3如何理解docker的tag
reg.yyuap.io/c87e2267-1001-4c70-bb2a-ab41f3b81aa3/scm-scm:20200612203405
仓库名/项目名/镜像名:TAGS
7、容器的启动过程
业务过程:
拉取镜像,若本地已经存在该镜像,则不用到Docker仓库去拉取
使用镜像创建新的容器
分配union文件系统并且挂载一个可读写的层,任何修改容器的操作都会被记录在这个读写层上,你可以保存这些修改成新的镜像,也可以选择不保存,那么下次运行改镜像的时候所有修改操作都会被消除
分配网络/桥接接口,创建一个允许容器与本地主机通信的网络接口
设置ip地址,从池中寻找一个可用的ip地址附加到容器上,换句话说,localhost并不能访问到容器
运行你指定的程序
捕获并且提供应用输出:连接并且记录标准输出、输入和错误让你可以看到你的程序是如何运行的。
技术过程:
在Libcontainer中,p.cmd.Start创建子进程,就进入了pipe wait等待父写入pipe,p.cmd.Start创建了新的Namespace,这时子进程就已经在新的Namespace里了。
daemon线程在执行p.manager.Apply,创建新的Cgroup,并把子进程放到新的Cgroup中。
daemon线程做一些网络配置,会把容器的配置信息通过管道发给子进程。同时让子进程继续往下执行。
daemon线程则进入pipe wait阶段,容器剩下的初始化由子进程完成了。
rootfs的切换在setupRootfs函数中。(首先子进程会根据config,把host上的相关目录mount到容器的rootfs中,或挂载到一些虚拟文件系统上,这些挂载信息可能是-v指定的volume、容器的Cgroup信息、proc文件系统等)。
完成文件系统操作,就执行syscall.PivotRoot把容器的根文件系统切换rootfs
再做一些hostname及安全配置,就可以调用syscall.Exec执行容器中的init进程了
容器完成创建和运行操作,同时通知了父进程,此时,daemon线程会回到Docker的函数中,执行等待容器进程结束的操作,整个过程完成
没有找到相关结果
5 个回复
nccloud
基于运维场景的常用操作
1、开发要看容器里的日志和代码?
http://nccloud.yytimes.com/q_323.html
2、基于已有的镜像创建新镜像?
http://nccloud.yytimes.com/q_209.html
3、docker命令(http://nccloud.yytimes.com/q_321.html),及运维场景常用
3.1当前做盘服务器的docker安装流程,位于(1init_forbuild.sh)
3.2查找当前主机上的镜像
3.3查找当前主机上运行的镜像
3.4登录容器
3.5拷贝文件从容器或到容器
3.6清理删除指定镜像
3.7清理docker镜像,释放空间
3.8运行一个镜像
3.9保存一个docker镜像
3.10加载一个镜像到本地
4、基于实际项目已有的及可能的问题点
4.1资源池主机上的镜像迭代、过期资源释放以及镜像仓库的清理
4.2大型机多容器场景下的日志的IO开销,及日志归档存储
4.3底座的升级备份以及资源池主机docker的升级
4.4海量容器下数据库的连接数开销及限制
nccloud
Dockerfile是什么——是构建
docker
镜像的配置文件Dockerfile
分为四个部分:基础镜像(父镜像)信息指令
FROM
维护者信息指令
MAINTAINER
镜像操作指令
RUN
、EVN
、ADD
和WORKDIR
等容器启动指令
CMD
、ENTRYPOINT
和USER
等nccloud
一个原生的 Linux 容器格式,Docker 中称为 libcontainer。
Linux 内核的命名空间(namespace),用于隔离文件系统、进程和网络。
文件系统隔离:每个容器都有自己的 root 文件系统。
进程隔离:每个容器都运行在自己的进程环境中。
网络隔离:容器间的虚拟网络接口和IP地址都是分开的。
资源隔离和分组:使用 cgroups(即control group,Linux 的内核特性之一)将 CPU 和内存之类的资源独立分配给每个 Docker 容器。
写时复制(Copy on write):文件系统都是通过写时复制创建的,这就意味着文件系统是分层的、快速的,而且占用磁盘空间更小。
日志:容器产生的STDOUT、STDERR和STDIN这些IO流都会被收集并计入日志。用来进行日志分割和故障排错。
交互式shell:用户可以创建一个伪 tty 终端,将其连接到STDIN,为容器提供一个交互式shell。
资源隔离
文件系统隔离
每个进程容器运行在完全独立的根文件系统里。
容器中的文件系统都是挂载到了宿主机真实系统中的一个目录下面的。对于不同的容器,挂载点是不一样的,而容器不能穿越根目录上一级去访问, 所以这里对每一个容器都做到了文件系统隔离。
外挂数据卷:
可以把war包等文件的放置路径映射到主机上的某个路径,达到业务逻辑和数据持久化分割开(类似MVC思想)。
资源隔离
Cgroups:Cgroups是Linux内核功能,它让两件事情变成可能:限制Linux进程组的资源占用(内存、CPU);为进程组制作 PID、UTS、IPC、网络、用户及装载命名空间。
docker使用cgroup为每个进程容器分配不同的系统资源,将不同进程的资源使用隔离开。
命名空间隔离
Docker充分利用了一项称为namespaces的技术来提供隔离的工作空间,我们称之为container(容器)。当你运行一个容器的时候,Docker为该容器创建了一个命名空间。这样提供了一个隔离层,每一个应用在它们自己的命名空间中运行而且不会访问到命名空间之外。
一些Docker使用到的命名空间有:
pid命名空间: 进程隔离(PID: Process ID)
net命名空间: 管理网络接口(NET: Networking)
ipc命名空间: 管理进程间通信资源 (IPC: InterProcess Communication)
mnt命名空间: 管理挂载点 (MNT: Mount)
uts命名空间: 隔离内核和版本标识 (UTS: Unix Timesharing System)
namespace让进程隔离更灵活:
linux实现进程的方法为fork,实现的方式分为两个步骤:
在内存中复制一个父进程,得到“子进程”,此时子进程就是父进程上下文的简单克隆,内容完全一致
设置子进程的 pid,parent_pid,以及其他和父进程不一致的内容
从进程被制造的步骤可以看出,进程大部分资源和父进程共享,如果需要制造一个看起来像虚拟机的进程,我们需要比普通的进程多做几步:
可以自定义rootfs,比如我们把整个ubuntu发行版的可执行文件以及其他文件系统都放在目录/home/admin/ubuntu/ 下,当我们重定义rootfs = /home/admin/ubuntu 后,则该文件地址被印射为 "/"
把自身pid 印射为0,并看不到其他任何的pid,这样自身的pid成为系统内唯一存在pid,看起来就像新启动了系统
用户名隔离,可以把用户名设置为“root”
hostname隔离,可以另取一个hostname,成为新启动进程的hostname
IPC隔离,隔离掉进程之间的互相通信
网络隔离,隔离掉进程和主机之间的网络
这些隔离资源需要的方法代码在linux系统内核中已经有提供支持。
所以虽然docker帮助我们准备好了rootfs地址,镜像里面的文件,以及各种资源隔离的配置,但是在启动一个容器的时候,它只是调用系统中早已内置的可以隔离资源的方法,而kernel支持这些方法,也是在创建进程的方法上做了一层资源隔离的扩展而已。这就解释了docker两个特性:
启动速度快,因为本质来说容器和进程差别没有想象中的大,共享了很多代码,流程也差的不多
linux内核版本有最低的要求,因为linux是在某个版本后开始支持隔离特性
网络隔离
每个进程容器运行在自己的网络命名空间里,拥有自己的虚拟接口和IP地址
在安装好docker后,会默认初始化一个docker0的网桥。在host机器上,会为每一个容器生成一个默认的网卡,这个网卡的一端连接在容器的eth0,一端连接到docker0。这样就实现了每个容器有一个单独的IP。
如果需要外部能够访问容器,需要做端口映射规则,和配置虚拟机一样的道理, 只不过这里的80端口并没有占用了本地端口,而是在容器内部做了监听,外部是通过docker0桥接过去的,每个容器间也做到了端口和网络隔离。
nccloud
1. Docker System命令
在《谁用光了磁盘?Docker System命令详解》中,我们详细介绍了Docker System命令,它可以用于管理磁盘空间。
docker system df命令,类似于Linux上的df命令,用于查看Docker的磁盘使用情况:
可知,Docker镜像占用了7.2GB磁盘,Docker容器占用了104.8MB磁盘,Docker数据卷占用了1.4GB磁盘。
docker system prune命令可以用于清理磁盘,删除关闭的容器、无用的数据卷和网络,以及dangling镜像(即无tag的镜像)。docker system prune -a命令清理得更加彻底,可以将没有容器使用Docker镜像都删掉。注意,这两个命令会把你暂时关闭的容器,以及暂时没有用到的Docker镜像都删掉了……所以使用之前一定要想清楚吶。
执行docker system prune -a命令之后,Docker占用的磁盘空间减少了很多:
2. 手动清理Docker镜像/容器/数据卷
对于旧版的Docker(版本1.13之前),是没有Docker System命令的,因此需要进行手动清理。这里给出几个常用的命令:
删除所有关闭的容器:
删除所有dangling镜像(即无tag的镜像):
删除所有dangling数据卷(即无用的Volume):
3. 限制容器的日志大小
有一次,当我使用1与2提到的方法清理磁盘之后,发现并没有什么作用,于是,我进行了一系列分析。
在Ubuntu上,Docker的所有相关文件,包括镜像、容器等都保存在/var/lib/docker/目录中:
Docker竟然使用了将近100GB磁盘,这也是够了。使用du命令继续查看,可以定位到真正占用这么多磁盘的目录:
由docker ps可知,Nginx容器的ID恰好为a376aa694b22,与上面的目录/var/lib/docker/containers/a376aa694b22的前缀一致:
因此,Nginx容器竟然占用了92GB的磁盘。进一步分析可知,真正占用磁盘空间的是Nginx的日志文件。那么这就不难理解了。我们Fundebug每天的数据请求为百万级别,那么日志数据自然非常大。
使用truncate命令,可以将Nginx容器的日志文件“清零”:
当然,这个命令只是临时有作用,日志文件迟早又会涨回来。要从根本上解决问题,需要限制Nginx容器的日志文件大小。这个可以通过配置日志的max-size来实现,下面是Nginx容器的docker-compose配置文件:
重启Nginx容器之后,其日志文件的大小就被限制在5GB,再也不用担心了~
4. 重启Docker
还有一次,当我清理了镜像、容器以及数据卷之后,发现磁盘空间并没有减少。根据Docker disk usage提到过的建议,我重启了Docker,发现磁盘使用率从83%降到了19%。根据高手指点,这应该是与内核3.13相关的Bug,导致Docker无法清理一些无用目录:
我查看了一下内核版本,发现真的是3.13:
如果你的内核版本也是3.13,而且清理磁盘没能成功,不妨重启一下Docker。当然,这个晚上操作比较靠谱。
nccloud
从 Docker 1.10 开始,
COPY
、ADD
和RUN
语句会向镜像中添加新层。前面的示例创建了两个层而不是一个。