docker概念
镜像与容器
docker镜像是一个统一文件系统,由一系列只读层堆叠而成。容器则是在只读层堆顶加上一个读写层。而当一个容器被分配给隔离的进程空间后即可转为运行态。
thin pool的概念
传统的存储空间分配方式成为fat provisioning,要求多少空间就分配多少空间,即使只使用了其中很小一部分。而thin provisioning则是在需要的时候才实际分配空间。thin pool建立在thin provisioning的思想上,它分配的是虚拟的chunk,而不是物理的block。
当docker使用Device Mapper作为存储驱动,可以配置成direct-lvm模式,这种情况下存储使用一个块设备作为thin pool,thin pool由存储实际数据的data space和存储指针的metadata space组成。
OCI
Open Container Initiative,开放容器倡议(标准)。主要有两个标准文档:容器运行时标准 (runtime spec)和 容器镜像标准(image spec)。这两个协议通过 OCI runtime filesytem bundle 的标准格式连接在一起,OCI 镜像可以通过工具转换成 bundle,然后 OCI 容器引擎能够识别这个 bundle 来运行容器。
OCI bundle
所谓bundle是指一个根文件系统和一个config.json文件,runc可以用这两个输入建立一个容器。
OCI state
OCI定义了容器的5种状态,creating, created, running, stopped, paused.
—————————————————————————————————————————————————————————————————————
| | ---start--> | | ---error,exit,crash,kill--> | |
|created| |running| ---pause---> |paused| | |
| | | | <--resume--- | | |stopped|
| | ------------------create failed-----------------> | |
—————————————————————————————————————————————————————————————————————
repository和registry
repository存储一种特定的镜像,其中可以包含多个版本(tag),例如docker.io/busybox是一个repository。
registry则是存有大量repository的服务器,例如docker.io是官方的registry。
docker组件间的交互
dockerd通过监听unix、tcp socket接收请求,对外的API接口是REST方式,可以用curl直接发送GET或者POST请求来模拟通过docker调用指令的效果,例如:
curl '127.0.0.1:2375/v1.37/images/create?fromImage=hello-world&tag=latest' -X POST
与docker pull等效。
dockerd与containerd之间通过grpc协议通信,containerd与containerd-shim之间使用ttrpc协议通信(早期版本似乎用过其他通信方式)。
docker配置
daemon启动参数配置文件
可以在/etc/sysconfig/docker中的OPTIONS中添加参数(这是默认位置,具体以docker service的状态信息中的environment file为准),或者在/etc/docker/daemon.json中添加。
仓库配置文件
/etc/containers/registries.conf,虽然是新版本的推荐配置方法,但单独在这里添加似乎不能正常使用。
其他方法:在/etc/docker/daemon.json中添加如 "insecure-registries": ["***"],此配置不影响首选仓库,不指定仓库的情况下依然搜索官方仓库docker.io,需要另外配置registry-mirror来修改默认的镜像仓库。
存储配置文件
/etc/sysconfig/docker-storage,但同样可以在daemon.json中设置。
网络代理配置文件
/etc/systemd/system/docker.service.d/* 初始状态下需要手动创建该目录,配置文件名无限制,例如proxy.conf。
# 内容示例
[Service]
Environment="HTTPS_PROXY=https://***"
Environment="HTTP_PROXY=http://****"
Environment="NO_PROXY=localhost,huawei.com,athuawei.com,huaweidevice.com,hw3static.com,euleros-obs"
docker daemon会根据其启动环境中的HTTP_PROXY,HTTPS_PROXY,NO_PROXY变量来决定代理行为,注意daemon的启动环境中的变量和linux系统环境变量是两回事,需要单独设置。
docker服务的启动参数
systemctl show docker可以查看docker服务的各种信息,其中EnvironmentFile项目设置的文件包含有环境变量的设置,例如/etc/sysconfig/docker-storage中的DOCKER_STORAGE_OPTIONS,然后在ExecStart项目的启动命令中引用这些变量。
修改服务环境变量后需要systemctl daemon-reload之后再重启服务。
配置DeviceMapper的direct lvm模式
https://docs.docker.com/storage/storagedriver/device-mapper-driver/
注意配成direct lvm后如果要删除逻辑卷,需要先取消逻辑卷的激活状态:lvchange -an docker/thinpool
删除逻辑卷后如果要再重建逻辑卷使用docker,需要删掉docker原来的/var/lib/docker/devicemapper目录,因为其中存有原来的文件系统数据,与新建的逻辑卷不匹配,文件系统校验会失败。
cgroup目录
在/var/run/docker/libcontainerd/xxxxx/config.json(可以用管道转接python -m json.tool再转接less格式化显示内容)中找到容器的cgroup目录,docker run可以用--cgroup-parent参数设置目录路径。
docker服务无法启动时查看容器配置
可以查看/var/lib/docker/container/[container_id]/config.v2.json
容器扩容
docker info中有Base Device Size项显示容器的默认容量,可以通过在docker-storage或daemon.json中添加storage-opt dm.basesize=**G调整,注意daemon.json中的格式要求,以及daemon.json和/etc/sysconfig/docker之间可能存在的配置冲突。修改后需要重启docker服务,并且原有的images不会发生变化,如需调整只能删掉后重下载镜像。另外缩小容量需要删除/var/lib/docker目录,否则重启服务时会报错。
docker存储位置
docker info中的Docker Root Dir项目是docker存储的根目录。创建的容器存储在containers/下,下载的镜像信息记录在image/目录下,但实际的数据存在于${Storage_Driver}目录下。例如使用devicemapper存储驱动,在容器运行着的情况下在devicemapper/mnt/[device_name_ID]下面可以直接看到容器中的统一文件系统。其中的device_name_ID可以在docker inspect中的DeviceName项查到。
日志
docker服务日志
通过以下rsyslog配置修改docker服务日志输出目的地
/etc/rsyslog.d/docker.conf
$FileCreateMode 0644
template(name="DockerLogFileName" type="list") {
constant(value="/var/log/docker/")
property(name="syslogtag" securepath="replace" \
regex.expression="docker/\\(.*\\)\\[" regex.submatch="1")
constant(value="/docker.log")
}
if $programname == 'dockerd' then \
/var/log/docker/combined.log
if $programname == 'dockerd' then \
if $syslogtag contains 'docker/' then \
?DockerLogFileName
else
/var/log/docker/no_tag/docker.log
$FileCreateMode 0600
容器业务日志
docker run启动容器时可以用--log-driver指定日志记录方式。
docker默认的logging driver是json-file,在该模式下日志会被记录在/var/lib/docker/<containerID>/<containerID>-json.log中。
如果指定journald作为logging driver,日志会被记录到linux系统日志中,可以用journalctl -u docker来筛选查看。
如果指定syslog作为logging driver,日志会根据/etc/rsyslog.conf的配置写入指定的文件中。
shim日志
默认情况下不打开,需要docker daemon设置debug级别的日志等级才会输出到messages中。
常用操作
导入导出
docker save 将image保存成tar包,并且保留镜像的分层信息;docker export 将容器保存成tar包,不保留分层信息。
docker load 可以将save产生的包导入成镜像,还原出分层信息;docker import 导入的tar包甚至不是一个镜像,仅仅是一个文件系统。
容器内外的文件传输
docker cp host_file container_ID:container_file 将外部的文件复制到容器中,两个参数反过来也可以将文件从容器中取出。相当于cp -r,可以复制整个文件夹。目标路径是已存在的文件夹时,会将源拷贝到该文件夹下,如果要让源文件夹和目标文件夹合并,需要把源文件夹写成source_dir/.的形式。
关于exec的umask
docker exec执行指令的时候无视容器内的umask设置,固定使用自己的umask,官方版本是022。
exec的等效操作
利用nsenter进入后台容器,先用docker inspect --format "{{.State.Pid}}" container_ID查找容器的pid,然后nsenter --target $PID --mount --uts --ipc --net --pid -- env --ignore-environment -- /bin/bash即可进入容器环境。
容器权限
docker run的--cap-add和--cap-drop用以赋予、取消容器权限,权限类型参考ttp://man7.org/linux/man-pages/man7/capabilities.7.html
shm共享内存
/dev/shm是从内存中映射出来的虚拟文件系统,对应一块内存空间。默认情况下各个容器的/dev/shm是独立的,但可以在docker run参数中设置--ipc指定共享另一个容器的shm(ipc命名空间隔离进程间通信,而共享内存正是进程间通信手段)。
此设置也可在inspect信息中查询IpcMode来确认。
查找被占用的挂载路径
find /proc/*/mountinfo | xargs grep [****]
从mnt路径反向查容器
docker ps -a -q | xargs docker inspect -f "{{.Id}} {{.GraphDriver.Data.DeviceName}}" | grep *****
镜像相关
镜像的各种ID
- 镜像id码
manifest.json的config字段是镜像id,这是对镜像配置文件进行sha256计算得到的。镜像配置文件是tar包解压后manifest.json以外的另一个json文件,文件名就是这个id本身。镜像载入后配置文件存放在环境的/var/lib/docker/image/[graph_driver]/imagedb/content/sha256下。 - 镜像tar包分层路径码
镜像tar包解压出来的manifest.json中记录了各层的路径码,也是解压出来各个存有layer.tar的目录的路径名。这是对分层的config.json数据结构做256校验得到的。
image/v1/imagev1.go: CreateID()创建此路径码
路径码按照从上到下依次为最底层到最新层。 - 镜像分层DiffID
在代码中被称为DiffID的编号是对各分层文件layer.tar进行256校验得到的,分层的diffid可以在镜像配置文件中查看,也可以docker inspect镜像查看各分层的sha256码。
diffid按照从上到下依次为最底层到最新层。 - 镜像分层ChainID
chainID:docker内容寻址机制采用的索引ID,其值根据当前层diffID和祖先层的chainID算得:
若该镜像层是最底层,那么其chainID 和 diffID 相同
否则,chainID=sha256(父层chainID+" "+本层diffID)
layer/layer.go: CreateChainID()创建此ID,注意代码中ChainID是包含sha256:前缀的字符串,计算新层ID时是把前缀一起带进去计算256码的。
镜像存在于本地的情况下/var/lib/docker/image/overlay2/layerdb/sha256下面各个分层的路径名就是该层的chainID。 - 镜像分层parent
父镜像层的chainID
查询第三方仓库中的镜像
docker search默认只访问官方仓库docker.io,要搜索第三方仓库需要加仓库地址,如docker search rnd-dockerhub.huawei.com/busybox
secure registry的认证
registry本质上是server端的一个容器,拉起该容器时需要制定使用的证书(crt)和私钥(key),其中crt是根据key生成的,包含registry信息和公钥。这两个文件的规范获取方式是由CA签发,下游环境应该是自签。
用户端pull/push镜像使用的证书需要放在/etc/docker/certs.d下面,以registry域名和端口作为目录名,例如/etc/docker/certs.d/hub.myreg.com:2121/。目录下应该有key,cert,crt三个文件。其中crt文件就是registry端使用的那个证书,需要手动复制到用户端,用户端使用该文件验证registry的身份;key是client端的私钥,cert是根据私钥生成的证书,证书中包含client的个体信息和公钥,此证书会提供给registry端用于验证用户身份。
参考文档:https://docs.docker.com/registry/insecure/,https://docs.docker.com/engine/security/certificates/。
另外docker login进行的认证是有别于上述TLS安全上的另一种认证,用于限制特定的用户访问仓库。
运行时目录相关
检查exec运行的任务
可以通过/var/run/docker/containerd/[container_id]下的stdin,stdout等文件来判断有没有exec任务
runc相关
用runc查看容器状态
比docker ps更准确
runc --root /var/run/docker/runtime-runc/moby list
其中root参数可以ps查看容器shim进程的-runtime-root参数
健康检查
健康检查可以在dockerfile中用HEALTHCHECK配置,这样做出来的镜像自带健康检查,也可以在docker run的时候用--health-cmd参数添加。
dockerfile中相关指令:
HEALTHCHECK [option] CMD *****
option可用选项:
--interval=DURATION (default: 30s)
--timeout=DURATION (default: 30s)
--start-period=DURATION (default: 0s)
--retries=N (default: 3)
run指令中的相关参数:
--health-cmd Command to run to check health
--health-interval Time between running the check
--health-retries Consecutive failures needed to report unhealthy
--health-timeout Maximum time to allow one check to run
--health-start-period Start period for the container to initialize before starting health-retries countdown
--no-healthcheck Disable any container-specified HEALTHCHECK
注意检查CMD只应该返回0或者1,返回值规定:
0: success - the container is healthy and ready for use
1: unhealthy - the container is not working correctly
2: reserved - do not use this exit code
容器网络
ethtool -S veth*** 可以查看veth网卡另一端的设备号,然后可以在ip link list中查看是什么设备。
ip_forward功能
容器网络需要系统开启ip_forward功能
sysctl net.ipv4.ip_forward 检查系统ip_forward是否开启
如果没有开启,修改/etc/sysctl.conf配置文件中的net.ipv4.ip_forward参数,然后sysctl -p使之生效;向/etc/sysconfig/network添加FORWARD_IPV4=YES,然后重启network服务。
ipv6支持
docker默认关闭ipv6功能,需要在daemon配置中添加"ipv6": true,如果添加后报"could not find an available, non-overlapping IPv6 address",需要进一步添加"fixed-cidr-v6": "****"。
docker代码
client与daemon的交互
docker daemon端接收到client端发送的API请求后,处理入口代码在api/server/router/下,和容器相关的请求在container下,和镜像相关的请求在image下。
daemon与containerd的交互
以grpc方式交互,api定义在api/grpc/types/api.proto中,该文件生成了api.pb.go,是go语言层面上的代码定义。
docker-toolkit
app image
docker_load使用-i参数指定的APP_IMAGE实际上就是原本的镜像名,即docker build制作镜像时用-t参数指定的命名(和tag),如果不知道原本的镜像名,可以查看manifest内容,第一层元素就是镜像名。
hash
docker_save会计算各个tar包的hash值保存在manifest中,docker_load会校验tar包hash值是否和manifest的记录匹配,不匹配则会报诸如Invalid base image tarball的信息。
其他
删除docker逻辑卷
dmsetup remove docker-thinpool
dmsetup remove docker-thinpool_tdata
dmsetup remove docker-thinpool_tmeta
lvremove /dev/docker/thinpool
如果要重建thinpool,重建后可能需要激活 lvchange -ay /dev/[vg_name]/[lv_name]
核对IO高的可能性
/var/log/sa下面有记录io情况的日志,但时间很稀疏。
用curl直接发http请求
例如curl -XGET --unix-socket /var/run/docker.sock http://localhost/images/json,相当于docker images请求
curl -XGET --unix-socket /var/run/docker.sock http://localhost/containers/json?all=true,相当于docker ps -a请求
docker的http api可以查阅https://docs.docker.com/engine/api/v1.40/