cgroup的原生接口通过cgroupfs提供,类似于procfs和sysfs,是一种虚拟文件系统,用户可以通过文件操作实现cgroup的组织管理。
cgroup可以限制、记录、隔离进程组所使用的物理资源。
子进程创建之初,与其父进程处于同一个cgroup的控制组里。
cgroup实现本质上是给系统进程挂上hooks,当task运行过程中涉及到某类资源的使用时就会触发hook上附带的子系统进行检测。
主要作用包括:
- 资源限制:可以对进程组使用的资源总额进行限制(例如内存上限,一旦超过配额就触发OOM异常)
- 优先级分配:通过分配的CPU时间片数量及硬盘IO带宽大小,相当于控制进程运行的优先级
- 资源统计:统计系统的资源使用量,如CPU使用时长、内存用量等,非常适用于计费和监控
- 进程控制:对进程组执行挂起、恢复等操作
类型 | 说明 |
---|---|
cpuset | 为cgroup中的task分配独立的cpu(针对多处理器系统)和内存 |
cpu | 控制task对cpu的使用 |
cpuacct | 自动生成cgroup中task对cpu资源使用情况的报告 |
memory | 设定cgroup中task对内存使用量的限定,并且自动生成这些task对内存资源使用情况的报告 |
blkio | 为块设备设定输入/输出限制 |
devices | 开启或关闭cgroup中task对设备的访问 |
freezer | 挂起或恢复cgroup中的task |
net_cls | docker没有直接使用,其通过使用等级识别符(classid)标记网络数据包,从而允许Linux流量控制(TC)程序识别从具体cgroup中生成的数据包 |
perf_event | 对cgroup中的task进行统一的性能测试 |
hugetlb | TODO |
配置 | 说明 |
---|---|
cpu.cfs_burst_us | CFS调度器,允许在一个period内,cpu资源用量超过quota限制,预支的部分在后面的period里扣减出去。 |
cpu.cfs_period_us | cfs周期,单位微秒,默认值100000 |
cpu.cfs_quota_us | 用以配置在当前cfs周期下能够获取的调度配额,单位微秒,如果给95%个核则配置95000,如果给5个核则配置500000,默认值-1表示不受限 |
cpu.shares | 各cgroup共享cpu的权重值,默认1024,闲时cpu用量能超过根据权重计算的共享比例,忙时根据共享比例分配cpu资源 |
cpu.stat | nr_periods, 表示过去了多少个cpu.cfs_period_us里面配置的时间周期 nr_throttled, 在上面的这些周期中,有多少次是受到了限制(即cgroup中的进程在指定的时间周期中用光了它的配额) throttled_time, cgroup中的进程被限制使用CPU持续了多长时间(纳秒) |
cpu.idle | |
cpuacct.usage | 所有cpu核的累加使用时间(nanoseconds) |
cpuacct.usage_percpu | 针对多核,输出的是每个CPU的使用时间(nanoseconds) |
cpuacct.stat | 输出系统(system/kernel mode)耗时和用户(user mode)耗时,单位为USER_HZ。 |
cpu.shares
用于设置下限,在cpu繁忙时生效。cpu.cfs_period_us
和cpu.cfs_quota_us
设置硬上限。
参见:
建一个测试Pod,其resources
配置如下:
resources:
requests:
cpu: 0
limits:
cpu: 1
创建Pod后可确认:
- 调度效果:对request没有要求,不会占节点的allocated request数。
- QoS类型:Burstable
进一步查看cpu cgroup参数:
# cat cpu.cfs_period_us
100000
# cat cpu.cfs_quota_us
100000
# cat cpu.shares
2
- 可看到 cpu.cfs_quota_us / cpu.cfs_period_us 为1,这个是上限。
- cpu.shares为2,而一个核的权重为1024,因此2/1024近乎为0,可看到下限配置很低,对应
request 0
。
作为对比,更新测试Pod的resources
配置如下:
resources:
requests:
cpu: 0.5
limits:
cpu: 1.5
这时cpu cgroup参数如下:
# cat cpu.cfs_period_us
100000
# cat cpu.cfs_quota_us
150000
# cat cpu.shares
512
- 可看到 cpu.cfs_quota_us / cpu.cfs_period_us 为1.5,这个是上限。
- cpu.shares / 1024 为0.5,对应
request 0.5
。
period为100000、quota为50000和period为10000、quota为5000,容器的cpu limit均为0.5核,有什么区别?
- 每个period内,最多执行quota时间。如果在quota时间内未执行完,将被throttle(统计到stat里),并只能等待下一个period继续执行。
- period越大,整体吞吐能力越好、削峰效果越好,但会导致实时性变差,反之亦然。
辅以cfs_burst_us,能既获取良好的吞吐能力,又兼顾实时性,具体的:
- CFS调度器,允许在一个period内,cpu资源用量超过quota限制,预支的部分在后面的period里扣减出去。
# CPU周期次数
old=0
new=0
while true; do
new=$(cat cpu.stat | grep nr_periods | awk '{print $2}')
delta=$((new-old))
echo "$(date) $delta"
old=$new
sleep 1s
done
# CPU限速次数
old=0
new=0
while true; do
new=$(cat cpu.stat | grep nr_throttled | awk '{print $2}')
delta=$((new-old))
echo "$(date) $delta"
old=$new
sleep 1s
done
# 单个容器徒手实现 crictl stats --seconds 10 得效果
cid=xxx
while true; do
start=$(crictl stats -o json $cid | jq -r '.stats[0].cpu | .timestamp + " " + .usageCoreNanoSeconds.value')
sleep 10s
finished=$(crictl stats -o json $cid | jq -r '.stats[0].cpu | .timestamp + " " + .usageCoreNanoSeconds.value')
ts_start=$(echo $start | cut -d' ' -f1)
usage_start=$(echo $start | cut -d' ' -f2)
ts_finished=$(echo $finished | cut -d' ' -f1)
usage_finished=$(echo $finished | cut -d' ' -f2)
ts_delta=$((ts_finished - ts_start))
usage_delta=$((usage_finished - usage_start))
usage=$(echo $(awk -v usage_delta="$usage_delta" -v ts_delta="$ts_delta" 'BEGIN {print usage_delta * 100 / ts_delta}') | cut -d. -f1)
echo "$(date) $usage%"
done
遍历所有kubernetes pod的cpu亲和性:
for f in $(find /sys/fs/cgroup/cpuset -name "cpuset.cpus"); do printf "%-220s %s\n" $f $(cat $f); done
TODO: cgroup v1的oom,文件缓存file_dirty 和 file_writeback 的内存用量,这部分也记到容器内存,可能导致oom。 参见链接cgroup-v2 。
其它相关说明:
- 系统参数
vm.dirty*
,参见更加积极的脏页缓存刷新 。针对大内存节点,调优 vm.dirty 参数,更加积极的脏数据刷新,避免脏页积累导致的容器内 file_dirty 和 file_writeback 过大、容器OOM。 - 读写文件时Direct I/O参数,即
O_DIRECT
标识,避免文件系统缓存,不过相应的带来IO性能降低。 - cgroupv2会限制内存group中pagecache内存用量,因此能避免上述oom。
echo "b 7:0 rwm" > /sys/fs/cgroup/devices/kubepods.slice/devices.allow
可用于控制容器的进程数:
pids.current pids.events pids.max
检查pids数TOP20:
for p in $(find /sys/fs/cgroup/pids/ -name "pids.current"); do echo "$(cat $p) $p"; done | sort -rn | head -n20
以cpuset子系统为例:
mount -t cgroup -o cpuset cpuset /sys/fs/cgroup/cpuset
mkdir /tmp/hehe
# 看能否挂载成功
mount -t cgroup2 none /tmp/hehe
# 另一种方法,看能否搜索到 cgroup2
grep cgroup /proc/filesystems
主机上修改grub配置,重启主机生效:
sudo grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0"
systemd-cgls # 查看systemd cgroup的配置层级关系
systemd-cgtop # 基于cgroup,直接查看cpu和内存的使用情况
mount -t cgroup
lssubsys -m
ls -l /sys/fs/cgroup/
lscgroup
man cgconfig.conf
cgcreate
cgdelete
# 全量统计信息
cat /proc/cgroups
进一步阅读:
host上,查看进程的status文件,可看到其在容器内的pid:
# cat /proc/<pid>/status | grep NSpid
NSpid: 12345 2
其中第1列是进程在host上的pid,第2列是容器内的pid。
进一步阅读:
# 找到一个process的pid命名空间(inode),适用于容器内或者host上执行
ls -Li /proc/<pid>/ns/pid
# 也可以列出全部的pid命名空间
lsns -t pid
# host上遍历寻找所有该pid命名空间下的进程,其中xxxxxxxxxx是pidns的inode
ps -eo pidns,pid,lwp,cmd | awk '$1==xxxxxxxxxx'
进一步阅读:
nsenter -t $(pidof xxx) -m ls
nsenter -t $(pidof xxx) -m vi /path/to/file
# 查看ns的inode信息
ls -Li /proc/1/ns/net
# TODO: https://unix.stackexchange.com/questions/113530/how-to-find-out-namespace-of-a-particular-process
# 查看pid所述的容器/pod
nsenter -t ${pid} -u hostname
# 查看pid所在容器的内存用量
nsenter -t ${pid} -m cat /sys/fs/cgroup/memory/memory.usage_in_bytes
# 查看pid所在容器的cpu使用率(近10秒)
function cpu-usage {
local pid=$1
local start=$(nsenter -t ${pid} -m cat /sys/fs/cgroup/cpu/cpuacct.usage 2>/dev/null)
sleep 10s
local end=$(nsenter -t ${pid} -m cat /sys/fs/cgroup/cpu/cpuacct.usage 2>/dev/null)
if [ "${start}" != "" ] && [ "${end}" != "" ]; then
# echo "(${end} - ${start}) / 100000000" | bc
local cpuacct=$[${end} - ${start}]
echo $[${cpuacct}/100000000]%
fi
}
lsns
工具来自包util-linux
,其常见使用如下:
# 查看网络命名空间列表
lsns -t net
# 查看pid命名空间列表
lsns -t pid
nsenter --net=/proc/19714/ns/net ip addr
nsenter -t 19714 -u hostname
nsenter -t 19714 -m -u -i -n -p bash
nsenter -t 19714 -m -p bash
nsenter -t 12472 -m -p umount /var/lib/origin/openshift.local.volumes/pods/<uid>/volumes/ctriple.cn~drbd/r0002
nsenter -t 19714 -m -p ps -ef
nsenter -t ${pid} -m cat /sys/devices/virtual/net/eth0/iflink 2>/dev/null
nsenter -t 7429 -n cat /proc/net/route
nsenter -t 12345 -n tcpdump -i eth0 -nnl # 关联容器的网络命名空间,直接在宿主机上抓容器里eth0接口的报文
nsenter -t 14756 -n ip link set eth0 address ee:ee:ee:ee:ee:ee # 修改容器 MAC 地址
使用不同的命名空间运行程序,详见man 1 unshare
run program with some namespaces unshared from parent
标准链接image-spec
包括如下信息:
- layer, that will be unpacked to make up the final runnable filesystem.
- image config, includes information such as application arguments, environments, etc.
- image index, a higher-level manifest which points to a list of manifests and descriptors.
最终能够unpacked into an OCI Runtime Bundle.
https://github.com/opencontainers/image-spec/blob/main/media-types.md
$ shasum -a 256 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51 ./blobs/sha256/afff3924849e458c5ef237db5f89539274d5e609db5db935ed3959c90f1f2d51
比如制作一个CentOS操作系统的基础镜像,使用CentOS的yum源即可:
mkdir -p /tmp/test/baseimage
# 往/tmp/test/baseimage这个目录安装bash和yum,过程中会自动解决依赖
yum -c /etc/yum.conf --installroot=/tmp/test/baseimage --releasever=/ install bash yum
# 进入目录可以看到rootfs
[root@xxx baseimage]# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
# 此时,可以手动修改rootfs中的文件,例如修改etc/yum.repos.d目录下*.repo,定制仓库路径
# 生成并上传基础镜像
tar --numeric-owner -c -C "/tmp/test/baseimage" . | docker import - docker.io/ytinirt/baseimage:v1
docker push docker.io/ytinirt/baseimage:v1
TODO
在无法合并打包时,可采用移除基础镜像层的方式实现应用镜像的缩容。
大致原理为,确保目的地容器存储中已存在基础镜像,可将应用镜像中包含于基础镜像的layer删除并重新打包应用镜像,实现应用镜像缩容的目的。 传输到目的地,加载镜像时,虽然应用镜像tar包中没有基础镜像layer,但目的地容器存储中已存在对应的基础layer,因此应用镜像也能加载成功。
参考资料:
- https://docs.docker.com/buildx/working-with-buildx/
- https://medium.com/@artur.klauser/building-multi-architecture-docker-images-with-buildx-27d80f7e2408
- https://github.com/docker/buildx
- docker/buildx#80
环境要求:
- 内核版本:4.8及以上(如果用CentOS,建议直接装CentOS 8)
- Docker版本: 19.03及以上(要使用buildx,19.x版本可能需要开启docker Experimental mode。而20.10.8已默认开启buildx命令。建议使用最新版本的Docker)
环境准备和Demo
# 通过容器方式,准备多架构编译环境(注意,节点重启后需要重新run一次容器)
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# 创建并使用builder
docker buildx create --use --name mybuilder --driver-opt network=host
# 此处使用主机网络"network=host",能用到宿主机/etc/hosts,是为了解决私有仓库域名解析的问题
# 检查builder,并触发其准备就绪,实际上就是启一个buidler容器
docker buildx inspect --bootstrap
# 拷贝为私有仓库签发证书的CA的证书到builder容器,并重启builder容器,解决私有仓库证书问题
BUILDER_ID=$(docker ps|grep 'moby/buildkit' | awk '{print $1}')
docker cp </path/to/ca.crt> ${BUILDER_ID}:/etc/ssl/certs/ca-certificates.crt
docker restart ${BUILDER_ID}
# 查看builder,已支持多种架构
docker buildx ls
# 类似如下输出,可看到支持多种架构
# NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
# mybuilder * docker-container
# mybuilder0 unix:///var/run/docker.sock running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
# 准备镜像的Dockerfile和依赖资源文件,例如
cat << EOF > Dockerfile
FROM alpine:latest
CMD echo "Running on $(uname -m)"
EOF
# 登录镜像仓库
# 构建多架构镜像,并自动以manifest list方式push到镜像仓库
docker buildx build -t "ytinirt/buildx-test:latest" --platform linux/amd64,linux/arm64 --push .
# 查看镜像
docker manifest inspect ytinirt/buildx-test:latest
# 可选:删除builder,什么都没发生过
docker buildx rm mybuilder
参见storage-driver-options。即使采用overlay2存储驱动,也可以借助xfs的pquota特性,为容器rw层做限制。
overlay2.size
Sets the default max size of the container. It is supported only when the backing fs is xfs and mounted with pquota mount option. Under these conditions the user can pass any size less than the backing fs size.
更进一步,通过xfs
文件系统的pquota
属性,可以实现文件夹级别的存储配额限制。
# 进入overlay的数据目录
cd /var/lib/containers/storage/overlay
# 统计容器可读可写层新增文件大小统计排序
for d in $(find . -name "diff" -type d -maxdepth 2 2>/dev/null); do du -sh $d 2>/dev/null; done | grep -v ^0 | grep -v K | sort -n
for cid in $(crictl ps -a -q ); do echo $cid; crictl inspect $cid | grep </var/lib/containers/storage/overlay文件夹下的目录>; done
for cid in $(podman ps -aq); do echo $cid; podman inspect $cid | grep </var/lib/containers/storage/overlay文件夹下的目录>; done
for iid in $(crictl img | sed 1d | awk '{print $3}'); do echo $iid; crictl inspecti $iid | grep </var/lib/containers/storage/overlay文件夹下的目录>; done
宿主机上/proc/{pid}/cwd
是pid所在进程当前的工作路径,如果pid是容器中业务进程在宿主机上的进程号,那么cwd文件夹中能直接看到容器中“当前工作目录”。
因此,宿主机上直接修改cwd文件夹中的内容,也能在容器中生效。
参考文档:
- Overview Of Linux Kernel Security Features
- Configure a Security Context for a Pod or Container
- Pod Security Policies
通过user ID (UID)和group ID (GID),实行访问控制。
为Pod/容器的安全上下文securityContext设置uid和gid:
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
securityContext:
runAsUser: 2000
allowPrivilegeEscalation: false
其中fsGroup施加到volume上,修改volume下文件/文件夹的GID。
定义文档参见capability.h
查看当前进程的capabilities
# cat /proc/$$/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
为Pod设置capabilities
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
containers:
- name: sec-ctx
image: gcr.io/google-samples/node-hello:1.0
securityContext:
capabilities:
add: ["SYS_TIME", "SYS_ADMIN"]
- 增加
SYS_ADMIN
,容器内能够mount
操作。 - 增加
SYS_TIME
,容器内能够设置系统时间。
注意,在add和drop时,去掉了前缀CAP_
。
进一步阅读 。
参考资料seccomp
SECure COMPuting mode (简称seccomp)是Linux内核一种特性(Linux kernel feature)。能够过滤系统调用(Filter a process’s system calls)。 相较linux capabilities,权限控制粒度更细。 利用seccomp特性,Docker能够限制容器中能够访问的系统调用(system call),防止容器中的操作危害整个节点。
通过如下操作,确认Linux和Docker支持seccomp:
[root@zy-super-load docker]# docker info
...
Security Options:
seccomp
WARNING: You're not using the default seccomp profile
Profile: /etc/docker/seccomp.json
selinux
Kernel Version: 3.10.0-862.14.4.el7.x86_64
...
[root@zy-super-load docker]# grep 'CONFIG_SECCOMP=' /boot/config-$(uname -r)
CONFIG_SECCOMP=y
从上述docker info中看到,docker的seccomp配置文件路径为/etc/docker/seccomp.json
。
该配置文件采用白名单模式,即容器内可访问seccomp.json列出的系统调用,除此之外的系统调用无法访问,默认(SCMP_ACT_ERRNO)返回Permission Denied。
以设置系统时间为例:
[root@zy-super-load ~]# strace date -s "15:22:00" 2>&1| grep -i time
...
clock_settime(CLOCK_REALTIME, {1575530520, 0}) = 0
...
其用到了系统调用clock_settime
。
为Pod设置seccomp profile
apiVersion: v1
kind: ReplicationController
...
spec:
replicas: 1
selector:
app: seccomp-demo
template:
metadata:
annotations:
seccomp.security.alpha.kubernetes.io/pod: "localhost/test-profile.json"
labels:
app: seccomp-demo
spec:
containers:
- command:
- /bin/bash
...
当指定为localhost
时,默认从/var/lib/kubelet/seccomp/
中搜索profile文件,详见kubelet
的--seccomp-profile-root
参数。
当test-profile.json
中禁止系统调用clock_settime
后,在pod中使用date设置系统时间失败。
详见issue
https://kubernetes.io/docs/tutorials/security/apparmor/
AppArmor通过调整配置文件(Profile)进行策略配置,以允许特定程序或容器所需的访问, 如 Linux 权能字、网络访问、文件权限等。 每个Profile都可以在 强制(enforcing) 模式(阻止访问不允许的资源)或 投诉(complain) 模式(仅报告冲突)下运行。
AppArmor的Profile施加到Pod的每个容器上,具体的,通过Pod的注解指定容器及其使用的Profile,注解示例如下:
container.apparmor.security.beta.kubernetes.io/<container_name>: <profile_ref>
- 检查是否开启AppArmor内核模块
# 输出为Y cat /sys/module/apparmor/parameters/enabled # 或者 cat /boot/config-$(uname -r) | grep CONFIG_SECURITY_APPARMOR
- 容器运行时支持AppArmor,主流的容器运行时,例如containerd和cri-o,均支持AppArmor
- AppArmor的Profile文件已加载,如果Profile文件未加载,kubelet将拒绝创建使用该Profile的Pod
# 查看已加载的Profile文件 cat /sys/kernel/security/apparmor/profiles | sort
参考资料HowTos/SELinux
SELinux是对文件(file)和资源(例如process、device等)的访问权限控制,是对传统的discretionary access control (DAC) 的补充。 SELinux参照最小权限模型(the model of least-privilege)设计,与之匹配的是严格策略(the strict policy),除非显式配置指定否则默认情况下所有访问均被拒绝(denied)。 但strict policy过于严格、不便使用,为此CentOS定义并默认采用基于目标的策略(the targeted policy),只针对选取的系统进程进行限制,这些进程(例如 httpd, named, dhcpd, mysqld)涉及敏感信息和操作。其它系统进程和用户进程则处于未限制域(unconfined domain)中,不由SELinux控制和保护。
targeted policy有四种形式的访问控制:
类型 | 描述 |
---|---|
Type Enforcement (TE) | Type Enforcement is the primary mechanism of access control used in the targeted policy |
Role-Based Access Control (RBAC) | Based around SELinux users (not necessarily the same as the Linux user), but not used in the default configuration of the targeted policy |
Multi-Level Security (MLS) | Not commonly used and often hidden in the default targeted policy |
Multi-Category Security(MCS) | An extension of Multi-Level Security, used in the targeted policy to implement compartmentalization of virtual machines and containers through sVirt |
所有进程和文件都含有SELinux安全啥下文(SELinux security context)信息
[root@op-master containers]# pwd
/var/lib/docker/containers
[root@op-master containers]# docker ps | grep nginx
...
6b312ef59368 nginx:1.14-alpine "nginx -g 'daemon ..." 4 days ago Up 4 days 80/tcp, 0.0.0.0:8888->8888/tcp apiserver-proxy
[root@op-master containers]# cd 6b312ef59368/
[root@op-master 6b312ef59368]# ls -Z config.v2.json
-rw-r--r--. root root system_u:object_r:container_var_lib_t:s0 config.v2.json
[root@op-master 6b312ef59368]#
其中,system_u:object_r:container_var_lib_t:s0
就是在标准的DAC上增加的SELinux安全上下文信息。格式为user:role:type:mls
,因此类型为container_var_lib_t
。
[root@op-master ~]# ps -efZ | grep 6b312ef593
system_u:system_r:container_runtime_t:s0 root 22190 18571 0 Apr12 ? 00:00:38 /usr/bin/docker-containerd-shim-current 6b312ef59368 /var/run/docker/libcontainerd/6b312ef59368 /usr/libexec/docker/docker-runc-current
可看到该容器的shim进程SELinux安全上下文,标识该进程类型为container_runtime_t
,与上述config.v2.json文件的类型container_var_lib_t
类似、均属于container_t域下,因此shim进程可以访问该文件。
TODO:
Apr 01 09:43:22 master0 setroubleshoot[1417162]: SELinux is preventing /usr/sbin/xtables-nft-multi from ioctl access on the directory /sys/fs/cgroup. For complete SELinux messages run: sealert -l e1a4eb18-019a-4552-bd0c-4706ada83ab9
Apr 01 09:43:22 master0 setroubleshoot[1417162]: SELinux is preventing /usr/sbin/xtables-nft-multi from ioctl access on the directory /sys/fs/cgroup.
***** Plugin catchall (100. confidence) suggests **************************
If you believe that xtables-nft-multi should be allowed ioctl access on the cgroup directory by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'iptables' --raw | audit2allow -M my-iptables
# semodule -X 300 -i my-iptables.pp
Apr 01 09:43:22 master0 setroubleshoot[1417162]: AnalyzeThread.run(): Set alarm timeout to 10
# 设置SELinux模式
setenforce 0
# 查询当前SELinux模式
getenforce
# 查看SELinux状态
sestatus
# 设置具体elements的SELinux策略
semanage
# 查看文件的SELinux标签
ls -Z
# 查看进程的SELinux标签
ps -efZ
# 设置文件的SELinux标签
chcon
chcon -v --type=httpd_sys_content_t /html
chcon -Rv --type=httpd_sys_content_t /html
chcon -R --type container_file_t /var/lib/hostdir
restorecon -R /html
# 查看审计日志
ausearch -m avc --start recent
ausearch -ui 0
setsebool -P virt_use_nfs 1
...
securityContext:
seLinuxOptions:
level: "s0:c123,c456"
...
其中seLinuxOptions施加到volume上。一般情况下,只需设置level,其为Pod及其volumes设置Multi-Category Security (MCS) label。 注意,一旦为Pod设置了MCS label,其它所有相同label的pod均可访问该Pod的volume。
若遇到selinux拦截操作,例如:
SELinux is preventing /usr/sbin/lldpad from sendto access on the unix_dgram_socket ...
可以使用如下命令放开拦截:
ausearch -m avc --start recent
# 根据审计日志,查找被拦截的操作,并生成允许的规则
ausearch -c 'lldpad' --raw | audit2allow -M my-lldpad
# 设置selinux,放开拦截
semodule -X 300 -i my-lldpad.pp
# 查看容器列表
runc --root=/run/containerd/runc/k8s.io list
# 查看容器进程信息
# 其中<cid>可以通过 ctr -n k8s.io c ls | grep <image-name> 获取
runc --root /run/containerd/runc/k8s.io ps <cid>
# 进入容器执行命令
runc --root /run/containerd/runc/k8s.io exec -t <cid> bash
# 使用resume命令,解除paused状态
runc --root=/run/containerd/runc/k8s.io resume <cid>
# 查看容器状态
runc --root=/run/containerd/runc/k8s.io state <容器ID 64位长号>
# 更新容器资源配置
runc update --cpu-share 100 <cid>
# 命令示例
/usr/bin/runc
--systemd-cgroup
--root=/run/runc
create
--bundle /run/containers/storage/overlay-containers/<cid>/userdata
--pid-file /run/containers/storage/overlay-containers/<cid>/userdata/pidfile
<cid>
C语言实现的容器运行时。
资料:
配置一个hook:
# cat /etc/containers/oci/hooks.d/hook.json
{
"version": "1.0.0",
"hook": {
"path": "/root/runtime-hook.sh",
"args": ["runtime-hook.sh"]
},
"when": {
"annotations": {
"^ANNON\\.HEHE$": ".*"
}
},
"stages": ["prestart"]
}
hook执行操作:
# cat /root/runtime-hook.sh
#!/bin/bash
echo "$@" >> /root/runtime-hook.log
env >> /root/runtime-hook.log
echo >> /root/runtime-hook.log
# 在线收集containerd的dump信息,堆栈文件保存在/tmp目录中
kill -s SIGUSR1 $(pidof containerd)
# 批量导出容器
ctr -n k8s.io i export image.tar coredns:v1.7.0 kube-proxy:v1.18.8
# 使用containerd客户端
docker-ctr-current --address unix:///var/run/docker/libcontainerd/docker-containerd.sock
# 日志查看
# 方式1: 目录 /var/run/containerd/io.containerd.grpc.v1.cri/containers 下能够看到容器stdout和stderr的pipe文件。
# 直接cat pipe文件,就能看到标准和错误输出。注意,这里只能看到实时输出。
cat /var/run/containerd/io.containerd.grpc.v1.cri/containers/<容器id>/io/2615573161/<容器id>-stdout
# 方式2: 目录 /var/log/pods 下能够看到kubelet保存的容器日志输出,kubelet也是使用上了上述1把容器的stdout和stderr输出到/var/log下,
# 实现查看历史日志得能力,提升易用性。
cat /var/log/pods/kube-system_apiserver-proxy-xxx/nginx/0.log
# 查看容器指标信息,例如cpu、内存开销
ctr -n k8s.io t metric <cid>
# 挂载镜像
ctr -n k8s.io i mount centos:8 /mnt
# 解除挂载
ctr -n k8s.io i unmount /mnt
可直接在ARM架构的环境编译aarch64,如下示例包含containerd与runc
docker run -it --privileged --network host\
-v /var/lib/containerd \
-v ${PWD}/runc:/go/src/github.com/opencontainers/runc \
-v ${PWD}/containerd:/go/src/github.com/containerd/containerd \
-e GOPATH=/go \
-w /go/src/github.com/containerd/containerd containerd/build-aarch64:1.1.0 sh
# 进入容器里操作
# 编译 runc
cd /go/src/github.com/opencontainers/runc
make
# 编译 containerd
cd /go/src/github.com/containerd/containerd
make
function pid2pod {
local pid=$1
if [ -f /proc/${pid}/cgroup ]; then
local cid=$(cat /proc/${pid}/cgroup | grep ":memory:" | awk -F '/' '{print $NF}' | awk -F ':' '{print $NF}' | sed 's/^cri-containerd-//g' | sed 's/.scope$//g' | grep -v "^crio-")
if [ "${cid}" = "" ]; then
# Try cri-o
cid=$(cat /proc/${pid}/cgroup | grep -m1 "/crio-" | awk -F '/' '{print $NF}' | sed 's/^crio-//g' | sed 's/^conmon-//g' | sed 's/.scope$//g')
if [ "${cid}" != "" ]; then
result=$(sudo crictl inspect ${cid} 2>/dev/null | jq -r '.status.labels["io.kubernetes.pod.namespace"]+" "+.status.labels["io.kubernetes.pod.name"]' 2>/dev/null)
if [ "${result}" != "" ]; then
echo "${result}"
else
sudo crictl inspectp ${cid} 2>/dev/null | jq -r '.status.labels["io.kubernetes.pod.namespace"]+" "+.status.labels["io.kubernetes.pod.name"]' 2>/dev/null
fi
fi
else
result=$(ctr -n k8s.io c info ${cid} 2>/dev/null | jq -r '.Labels["io.kubernetes.pod.namespace"]+" "+.Labels["io.kubernetes.pod.name"]' 2>/dev/null)
if [ "${result}" != "" ]; then
echo "${result}"
else
ctr c ls 2>/dev/null | grep ${cid} 2>/dev/null | awk '{print $2}' 2>/dev/null
fi
fi
fi
}
# 查看当前生效的配置
crio-status config | grep -i pid
for config in $(ls /var/lib/containers/storage/overlay-containers/*/userdata/config.json)
do
diff=$(cat $config 2>/dev/null | jq .root.path -r|sed 's/merged$/diff/g')
du -s $diff
done | awk '{s+=$1} END {print s}'
# /etc/crio/crio.conf
[crio.runtime]
seccomp_profile = "/etc/crio/seccomp.json"
通过配置空的seccomp.json
文件,放开所有限制:
# cat /etc/crio/seccomp.json
{}
/run/containers/storage/overlay-containers/<pod-sandbox>/userdata/
,放置这个pod的hostname
和resolv.conf
等。/run/containers/storage/overlay-containers/<container>/userdata/
,放置容器的配置文件、挂载点等。
参见 non-root-containers-and-devices 。
# 修改crio配置,开启 device_ownership_from_security_context
cat << EEOOFF > /etc/crio/crio.conf.d/10-device-ownership
[crio.runtime]
device_ownership_from_security_context = true
EEOOFF
# 重启crio使配置生效
systemctl restart crio
# 检查配置生效
crio-status c | grep device_ownership_from_security_context
overlay元数据中id数:
sudo cat /var/lib/containers/storage/overlay-layers/layers.json /var/lib/containers/storage/overlay-layers/volatile-layers.json | jq . | grep -c "id\""
和/var/lib/containers/storage/overlay
目录下文件夹数(除l
文件夹外)是否接近:
sudo ls /var/lib/containers/storage/overlay | wc -l
调整日志级别:
# 修改日志级别log_level为info、debug或trace
/etc/crio/crio.conf.d/00-default
# 重载配置
systemctl reload crio
获取pprof数据:
# 通过环境变量,指定开启pprof
Environment="ENABLE_PROFILE_UNIX_SOCKET=true"
# 获取pprof数据,例如goroutine
curl --unix-socket /var/run/crio/crio.sock http://localhost/debug/pprof/goroutine?debug=1
# 例如内存信息
curl --unix-socket /var/run/crio/crio.sock http://localhost/debug/pprof/heap
# 当crio不响应时获取goroutine调用栈,调用栈信息保存在 /tmp/crio-goroutine-stacks-* 文件
systemctl kill -s USR1 crio.service
通过unix socket直接调用API:
# 查询容器详情
curl --unix-socket /var/run/crio/crio.sock http://localhost/containers/{CONTAINER_ID}
创建容器核心逻辑在createSandboxContainer()
。
cri server -> conmon -> runc -> user container process
以/runtime.v1alpha2.ImageService/ListImages
为例,storage/storage_transport.go
中会从容器存储中,解析对应的镜像信息并返回。
核心逻辑在ParseStoreReference()
的parsed reference into
。
- 配置文件在
/usr/share/containers/
和/etc/containers/
。 - 默认seccomp策略文件路径
/usr/share/containers/seccomp.json
。
podman ps --all --external
podman ps --all --storage
podman images
看到的镜像ID(IMAGE ID
)即本地缓存镜像的id,具体对应于/var/lib/containers/storage/overlay-images
目录下一个个文件夹/var/lib/containers/storage/overlay-images/*/manifest
中有容器镜像的layer
信息及每一层的大小- ???
- Linux默认在
${XDG_RUNTIME_DIR}/containers/auth.json
,即例如/run/user/0/containers/auth.json
- Windows和macOS默认在
$HOME/.config/containers/auth.json
- 若缺失上述文件,则继续检查
$HOME/.docker/config.json
,即兼容使用docker login
认证信息
# 新建一个manifest list
podman manifest create localhost/flannel:v0.23.0
# 向manifest list中添加镜像
podman manifest add localhost/flannel:v0.23.0 foo.bar/dev/flannel:v0.23.0-amd64 foo.bar/dev/flannel:v0.23.0-arm64
# 【可选】查看manifest list中镜像列表,检查各镜像携带的arch、variant、os等信息
podman manifest inspect localhost/flannel:v0.23.0
# 【可选】如果镜像没有arch信息,需要通过annotate为镜像添加arch等信息
podman manifest annotate --arch "amd64" localhost/flannel:v0.23.0 foo.bar/dev/flannel:v0.23.0-amd64
podman manifest annotate --arch "arm64" localhost/flannel:v0.23.0 foo.bar/dev/flannel:v0.23.0-arm64
# 上传manifest list至镜像仓库
podman manifest push localhost/flannel:v0.23.0 foo.bar/dev/flannel:v0.23.0
# 查看当前挂载的容器镜像
podman image mount
# 挂载容器镜像
podman image mount quay.io/openshift-scale/etcd-perf:latest
# 卸载容器镜像
podman image unmount quay.io/openshift-scale/etcd-perf:latest
# 查看镜像详情
cat /var/lib/containers/storage/overlay-images | jq
# 调整日志级别
podman pull --authfile /path/to/config.json <image> --log-level debug
# 启容器但不分配网络
podman run -it --rm --net=none centos:latest bash
crictl 访问cri server,同kubelet的行为一致,因此常用于站在kubelet角度去debug容器运行时。
crictl 拉起容器比podman等CLI工具麻烦,需要编辑json或yaml格式的配置文件,再拉起容器。而且,其行为同kubelet一致,因此拉起容器前还需要创建pod sandbox容器。
sandbox配置文件sandbox.json
如下:
{
"metadata": {
"name": "sandbox",
"namespace": "default",
"attempt": 1,
"uid": "xxx"
},
"hostname": "POD",
"log_directory": "/tmp",
"linux": {
"security_context": {
"privileged": true,
"namespace_options": {
"network": 2
}
}
}
}
然后执行如下命令:
crictl runp sandbox.json
业务容器配置文件container.json
如下:
{
"metadata":{
"name":"container",
"attempt": 1
},
"image": {
"image": "centos:latest"
},
"args": [
"sleep", "inf"
],
"mounts": [
{"container_path":"/dev", "host_path":"/dev"},
{"container_path":"/var/log", "host_path":"/var/log"}
],
"log_path": "tmp.log",
"linux": {
"security_context": {
"privileged": true
}
}
}
然后执行如下命令:
crictl create <sandbox-id> container.json sandbox.json
参见vendor/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go
中PodSandboxConfig
和ContainerConfig
结构体定义。
注意和OCI的区别opencontainers/runtime-spec 。
crictl stats -a -o json | jq '.stats[] | .writableLayer.usedBytes.value + " " + .attributes.labels["io.kubernetes.pod.namespace"] + " " + .attributes.labels["io.kubernetes.pod.name"] + " " + .attributes.id' -r | sort -rn | head -n 10
crictl stats -a -o json | jq '.stats[] | .writableLayer.inodesUsed.value + " " + .attributes.labels["io.kubernetes.pod.namespace"] + " " + .attributes.labels["io.kubernetes.pod.name"]' -r | sort -rn | head -n 10
为什么swap不适用于容器平台?我的理解:
- 有swap在,接近limit时容器内的进程会使用swap“腾出”部分内存,容器limit的限制就得不到遵守,这块同cgroups相关
- 容器环境下,虽然主机上内存资源充足,但是swap还是会使用,这与swap的设计初衷背道而驰的。
- 使用swap会严重影响io性能。
总结,swap是在容器崛起前的产物,当前出现的各类swap问题,归根到底需要swap(内存管理)和cgroup“协商”处理。
查询占用swap分区Top20的Pods
#!/bin/bash
for pid in $(top -b -n1 -o SWAP | head -n27 | sed '1,7d' | awk '{print $1}')
do
p=${pid}
while true
do
if [ ${p} = 1 -o ${p} = 0 ]; then
break
fi
pp=$(ps -o ppid= ${p} | grep -Eo '[0-9]+')
if [ ${pp} = 1 -o ${pp} = 0 ]; then
break
fi
search=$(ps -ef | grep "\<${pp}\>" | grep 'docker-containerd-shim')
if [ "${search}" = "" ]; then
p=${pp}
continue
fi
cid=$(echo ${search} | sed 's/.*docker-containerd-shim//g' | awk '{print $1}')
cname=$(docker ps --no-trunc | grep ${cid} | awk '{print $NF}')
if [ "${cname}" = "" ]; then
break
fi
OLD_IFS="$IFS"
IFS="_"
infos=(${cname})
IFS="${OLD_IFS}"
echo "Pid:$(printf "%6d" ${pid}) $(grep VmSwap /proc/${pid}/status) Pod: ${infos[2]}"
break
done
done
docker engine-api: func (cli *Client) ContainerStats
-> dockerd src/github.com/docker/docker/daemon/stats.go:135 daemon.containerd.Stats(c.ID)
-> containerd runtime/container.go func (c *container) Stats() (*Stat, error)
-> runtime (docker-runc events --stats container-id) runc/libcontainer/cgroups/fs/memory.go func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error
-> cgroups (memory)
docker-runc events --stats 9c8ad7d4885e2601a76bc3e1a4883a48a1c83e50ab4b7205176055a6fd6ec548 | jq .data.memory
docker-runc events --stats 9c8ad7d4885e2601a76bc3e1a4883a48a1c83e50ab4b7205176055a6fd6ec548 | jq .data.memory.usage.usage
的值直接取自:
cat /sys/fs/cgroup/memory/kubepods/burstable/podaebd4ae8-8e1b-11e8-b174-3ca82ae95d28/9c8ad7d4885e2601a76bc3e1a4883a48a1c83e50ab4b7205176055a6fd6ec548/memory.usage_in_bytes
# 检查dockerd是否响应服务请求
curl --unix-socket /var/run/docker.sock http://v1.26/containers/json?all=1
# 线程调用栈输出至/var/run/docker文件夹
kill -SIGUSR1 <docker-daemon-pid>
# containerd调用栈输出至messages,也会输出文件至/tmp目录
kill -SIGUSR1 <containerd-pid>
# 获取containerd-shim堆栈,堆栈输出至 shim.stdout.log
# 注意,需要开启containerd-shim -debug
cat /var/lib/containerd/io.containerd.runtime.v1.linux/moby/<container-id>/shim.stdout.log
kill -SIGUSR1 <containerd-shim-pid>
docker system prune # 存储清理,可以加上参数 -a
docker system df # 查看容器、镜像的存储用量
docker 重启是增加 live-restore 选项,可以降低重启docker的开销,重启docker daemon的时候容器不重启 除非bip这些变了。
docker push xxxx # 将镜像push到私有registry,注意,nodeB希望从nodeA的registry获取镜像时,nodeA上必须先push到registry才行
docker pull xxxx # 从registry上下载镜像至本地
docker run -it --name test --net container:1a9bfd40505e --entrypoint=/usr/bin/sh openstack-glance:RC2 # 共享容器网络,glance中携带tcpdump命令,可网络抓包
docker run -it --name test --net=host openstack-keystone:D1101 bash
docker rm -f $(docker ps | grep haproxy | awk '{print $1}')
docker run -it --net=host centos:base bash # 共享HOST网络
docker stats --no-stream # 查看容器状态、资源使用情况
docker run -d -p 881 -v /root/sample/website:/var/www/html/website:rw --privileged=true test-img:1.0 nginx # 映射时需要加--privileged=true防止没有权限
docker attach xxxx # 绑定到容器的stdio
docker exec d8c875f38278 bash -c "echo '1.2.3.4 hehe' >> /etc/hosts" # 进入容器执行命令
docker inspect -f "{{json .Mounts}}" b2aed79fec98
docker inspect ${container} --format '{{.State.Pid}}' # 获取容器的entrypoint进程pid
docker stats --format "{{.Name}} {{.MemPerc}}"
docker images --format "{{.Repository}}:{{.Tag}}"
docker info -f '{{json .}}' | jq # 格式化输出
docker load --input images.tar.gz
docker save myimage:latest | gzip > myimage_latest.tar.gz
curl -v -X POST http://<ip>:2375/v1.26/images/load -T xxx.tar # 调用docker接口load容器镜像
docker export $(docker create busybox:1.0.0) > busybox.tar
mkdir rootfs
tar -C rootfs -xf busybox.tar
# 常规操作
docker build -t centos:base -f Dockerfile .
# 为容器镜像增加label的简便操作
echo "FROM centos:7" | docker build --label foo="bar" --label key="value" -t "centos:7-labeled" -
操作如下:
yum -y install yum-utils
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 查看可安装docker版本
yum list docker-ce --showduplicates | sort -
yum install -y docker-ce-19.03.13-3.el7
systemctl enable docker.service
systemctl restart docker
也支持在既有Containerd
的节点上,安装Docker。
K8s集群网络插件打通容器网络,大多未使用docker0
,另一方面docker0
默认占用172.17.0.1/16
网段,IP地址存在冲突可能,为此考虑关闭docker0
。
注意,要让网络配置修改生效,必须先把容器全部停掉,具体操作如下:
systemctl stop kubelet
让kubelet停掉,不然它又会拉起容器docker stop $(docker ps -q)
停止所有docker容器- 修改
/etc/docker/daemon.json
,在其中增加"bridge": "none"
将docker0网桥干掉 systemctl restart docker
重启docker服务systemctl start kubelet
启动kubelet服务
在/etc/docker/daemon.json
中增加default-ulimits
,修改容器ulimit默认配置
# cat /etc/docker/daemon.json
{
"default-ulimits": {
"core": {
"Name": "core",
"Hard": 0,
"Soft": 0
}
}
}
此后容器内不再输出coredump
文件,进入容器后确认:
bash-4.4# cat /proc/$$/limits
Limit Soft Limit Hard Limit Units
...
Max core file size 0 0 bytes
...
节点上安装docker,并使用docker-storage-setup初始化docker存储。
docker-storage-setup仅依赖配置文件/etc/sysconfig/docker-storage-setup
,会根据配置文件中的VG自动部署docker storage,包括:
- 创建lv
- 创建docker用的dm thin-pool
- 为docker的thin-pool配置自动扩展(auto extend)
- 为docker生成相应的存储配置(/etc/sysconfig/docker-storage)
docker-storage-setup实则软链接到/usr/bin/container-storage-setup
。
container-storage-setup
由RedHat开发,其目的为"This script sets up the storage for container runtimes"。
container-storage-setup
内容可直接阅读脚本。
其配置文件路径为/usr/share/container-storage-setup
,有效内容如下:
[root@zy-op-m224 ~]# cat /usr/share/container-storage-setup/container-storage-setup | grep -v "^$\|^#"
STORAGE_DRIVER=devicemapper
DATA_SIZE=40%FREE
MIN_DATA_SIZE=2G
CHUNK_SIZE=512K
GROWPART=false
AUTO_EXTEND_POOL=yes
POOL_AUTOEXTEND_THRESHOLD=60
POOL_AUTOEXTEND_PERCENT=20
DEVICE_WAIT_TIMEOUT=60
WIPE_SIGNATURES=false
CONTAINER_ROOT_LV_SIZE=40%FREE
Dockerfile同Makefile类似,借助基础镜像和Dockerfile,能方便的制作出干净、内容可知的容器镜像,同docker cp + commit
或docker export
临时方法相比,采用Dockerfile更适合制作正式的、用于发布交付的镜像。
镜像过大导致:
- 离线安装包过大;
- 过大的安装包和镜像,传输、复制时间过长,系统部署时间显著增加;
- 过大的镜像,还会消耗过多的容器存储资源。
针对上述问题,以HAProxy的alpine版镜像为例,根据其官方Dockerfile,介绍如何使用“alpine基础镜像+Dockerfile”方式,制作干净、小巧且够用的Docker镜像,简单归纳如下:
# 【可选】
# 设置环境变量,主要包括软件的版本信息和源码文件MD5校验数据
ENV VERSION 1.6
ENV MD5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# 【可选】
# 安装alpine官方镜像没有,但后期需要使用的工具,以socat为例
RUN apk add --no-cache socat
# 【可选】
# 安装构建、编译工具,注意,在最后需要删除这些工具
RUN apk add --no-cache --virtual .build-deps gcc make binutils
# 【可选】
# 下载源码、编译、安装,并清除源码和中间文件
RUN wget -O source-file.tar.gz "http://www.hehe.org/path/to/source-file-${VERSION}.tar.gz"
RUN echo "$MD5 *source-file.tar.gz" | md5sum -c
RUN xxx #解压源文件、编译、安装、并删除源文件和中间文件
# 【可选】
# 删除.build-deps组中所有package
RUN apk del .build-deps
# 设置Docker的ENTRYPOINT和CMD
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["haproxy", "-f", "/usr/local/etc/haproxy/haproxy.cfg"]
当docker rm -f
无法删除容器时,可以找到容器的docker-container-shim
进程,删除该进程可终结容器,但需关注容器对应的/dev/dm-xx设备。
容器的运行时bundle信息在/var/run/docker/libcontainerd/xxxxxcidxxxxx/config.json
中,使用如下命令
cat config.json | jq .root.path -r
/var/lib/docker/devicemapper/mnt/9a7cc2bf60a1b4b9cfc96212b24528c03f7d74b1eabaf640341348e82e61fd15/rootfs
其中9a7cc2xxx
就是devicemapper
设备的id,可通过dmsetup info
查找到具体的dm-xx
信息
# 在/etc/docker/daemon.json中配置
{
"registry-mirrors": ["https://registry.docker-cn.com","https://3laho3y3.mirror.aliyuncs.com"]
}
# 然后重启dockerd
docker服务设置环境变量以使用代理(也可以直接修改docker.service)
mkdir /etc/systemd/system/docker.service.d
cat <<EOF >/etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:30000/"
Environment="HTTPS_PROXY=http://127.0.0.1:30000/"
Environment="NO_PROXY=*.foo.bar,10.0.0.0/8,192.168.*.*"
EOF
systemctl daemon-reload
# 检查环境变量已配置
systemctl show --property Environment docker
# 重启docker使配置生效
systemctl restart docker
注意,在终端中设置代理时,采用小写,例如:
export https_proxy=http://192.168.58.1:8080/
export http_proxy=http://192.168.58.1:8080/
# 白名单方式,指定不代理的地址或域名
export no_proxy=*.local,10.0.0.0/8,192.168.*.*
umount /mnt 2> /dev/null
for dm in $(ls /dev/mapper/docker-253* | grep -v pool)
do
mount ${dm} /mnt
usage=$(stat -f -c '100-%a*%S/1024*100/10471424' /mnt | bc)
umount /mnt
dmid=$(echo ${dm} | sed 's/.*-//g')
containerid=$(grep -rn ${dmid} /var/run/docker/libcontainerd/*/config.json | sed 's/\/config.json:1.*//g' | sed 's/.*libcontainerd\///g')
containername=$(docker ps --no-trunc | grep ${containerid} | awk '{print $NF}')
echo "${dm} $(printf "%3d%%" ${usage}) ${containername}" | grep -v "k8s_POD_"
done
未经验证:
systemctl stop docker
killall dockerd
systemctl start docker
常用命令
skopeo inspect docker://foo.bar/image:tag
skopeo list-tags docker://foo.bar/image
# 同步镜像的所有tag,当前还不支持多架构
skopeo sync --src docker --dest dir foo.bar/image /mnt/usb --tls-verify=false --preserve-digests
# 复制镜像
skopeo copy --dest-tls-verify=false docker://docker.io/image:v1 docker://my.registry.hehe/image:v1
# 支持所有架构
skopeo copy docker://foo.bar/image:tag dir:/mnt/usb --tls-verify=false --multi-arch=all --preserve-digests
skopeo login registry-1.docker.io -u <username> -p <password>
skopeo login image.foo.bar -u <username> -p <password> --tls-verify=false
i=centos:latest
skopeo copy --dest-tls-verify=false docker://docker.io/$i docker://image.foo.bar/dev/$i
# Windows上构建skopeo可执行文件
GOOS=windows GOARCH=amd64 go build -tags "containers_image_openpgp" -o bin/skopeo ./cmd/skopeo
# 增加 --override-os 搬运指定系统platform的镜像,例如 linux
# 增加 --insecure-policy 跳过容器安全策略检查 /etc/containers/policy.json
skopeo copy --dest-tls-verify=false docker://docker.io/$i docker://image.foo.bar/dev/$i --insecure-policy --override-os linux