Containerd NRI 插件
Github:https://github.com/containerd/nri.git
Slide:https://static.sched.com/hosted_files/kccncna2022/cc/KubeCon-NA-2022-NRI-presentation.pdf
基本介绍
NRI(Node Resource Interface),即节点资源接口,对标 CNI(容器网络接口),是管理容器相关资源的接口框架,独立于具体的容器运行时。NRI 支持将特定逻辑插入兼容 OCI 的运行时,例如,在容器生命周期时间点执行 OCI 规定范围之外的操作,分配和管理容器的设备和其它资源。目前为止,NRI 已经演进到 2.0 版本,该版本在 1.0 版本上进行了重构,增强了接口的能力。
工作原理
“your cluster, your plugin, your rules”,NRI 提供不同生命周期事件的接口,用户在不修改容器运行时源代码的情况下添加自定义逻辑。
以下是 NRI 的工作流程:
NRI 和 CRI 一起工作,在 CRI runtime 源代码中增加了 NRI adaptation 的逻辑。NRI adaptation 的功能包括插件发现、启动和配置,将 NRI 插件与运行时 Pod 和容器的生命周期事件关联,可以理解为 NRI 插件的 client,将 Container 和 Pod 的信息(OCI Spec 的子集)传递给 NRI 插件,同时,接收 NRI 插件返回的执行结果,在 2.0 版本,NRI adaptaion 支持根据 NRI 插件的返回信息更新容器的信息(OCI Spec)。
1.0 版本
NRI 可以追溯到 2020 年 7 月 20 日发布在 containerd 社区的提案:Add Node Resource Interface design doc[1],大意是,现有的容器网络接口(CNI)在处理不同容器网络栈实现的时候做得很优雅,与传统 Hook 方式介入容器生命周期的方式不同,CNI 提供了安全的 API 注入 Container 生命周期。因此,该提案希望基于类似的思想提出一个用于管理节点资源的接口,处理逻辑位于 Create Container 和 Start Container 之间。
题外话:根据容器网络接口的命名,按理说,应该用容器资源接口(Container Resource Interface),简写 CRI,可能由于 CRI 已经被容器运行时接口(Container Runtime Interface)用了,所以才叫 NRI。(我猜的)
1.0[2] 版本 NRI 功能非常有限,仅用于管理节点的资源。实现方式类似于 OCI Hook,为每个 NRI 事件运行单独的插件实例,容器运行时通过标准输入和标准输出以 JSON 格式数据与插件交互。
Demo 体验
以 containerd 1.6.8 版本为例,体验 1.0 版本的 NRI。
git clone https://github.com/containerd/containerd.git
cd containerd
git checkout v1.6.8
make && sudo make install
CONTAINERD_DIR=$(cat /lib/systemd/system/containerd.service | grep "ExecStart=" | awk -F= '{gsub("/containerd","",$2); print $2}')
sudo cp bin/containerd* ${CONTAINERD_DIR}
NRI 仓库 1.0 版本分支中没有示例插件,README.md 的示例代码无法成功编译,因此可以使用 v2.0 中的示例代码:https://github.com/containerd/nri/tree/v0.2.0
git clone https://github.com/containerd/nri.git
cd nri
git checkout v0.2.0
cd examples/clearcfs
sed -i '/result := r.NewResult(c.Type())/a \\tlogrus.Infof("Invoke clearcfs ok!!")' main.go
sed -i '/result := r.NewResult(c.Type())/a \\tr.Spec.Annotations["qos.class"]="ls"' main.go
sed -i 's/Debugf/Infof/g' main.go
go build
1.0 版本启用 NRI[3],只需要在 containerd 配置文件中设置 NRI 插件二进制文件所在目录和各插件的配置文件即可,默认目录:
const (
// DefaultBinaryPath for nri plugins
DefaultBinaryPath = "/opt/nri/bin"
// DefaultConfPath for the global nri configuration
DefaultConfPath = "/etc/nri/conf.json"
// Version of NRI
Version = "0.1"
)
因此,只需要将编译好的 NRI 插件二进制文件拷贝到/opt/nri/bin
目录,同时在/etc/nri/conf.json
添加插件的配置文件:
sudo mkdir /opt/nri/bin
sudo mkdir -p /etc/nri
sudo cp clearcfs /opt/nri/bin
sudo tee /etc/nri/conf.json <<- EOF
{
"version": "0.1",
"plugins": [
{
"type": "clearcfs"
}
]
}
EOF
通过 crictl 启动容器:
tee container-config.yaml <<- EOF
metadata:
name: busybox
image:
image: busybox
command:
- busybox
- sh
- -c
- echo busybox $(sleep inf)
log_path: busybox.0.log
linux: {}
EOF
tee pod-config.yaml <<- EOF
metadata:
name: nginx-sandbox
namespace: default
attempt: 1
uid: hdishd83djaidwnduwk28bcsb
log_directory: /tmp
linux: {}
EOF
sudo systemctl start containerd
crictl pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6
ctr -n k8s.io i tag registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6 registry.k8s.io/pause:3.6
sudo crictl run container-config.yaml pod-config.yaml
结果验证:
sudo journalctl -xe -u containerd | grep -e "Invoke clearcfs ok" -e "clearing cfs"
查看 cpu quota 的值:
源码分析
在 NRI 插件中,实现了 Invoke 方法:
func (c *clearCFS) Invoke(ctx context.Context, r *types.Request) (*types.Result, error) {
result := r.NewResult(c.Type())
r.Spec.Annotations["qos.class"] = "ls"
logrus.Infof("Invoke clearcfs ok!!")
if r.State != types.Create {
return result, nil
}
switch r.Spec.Annotations["qos.class"] {
case"ls":
logrus.Infof("clearing cfs for %s", r.ID)
control, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(r.Spec.CgroupsPath))
if err != nil {
returnnil, err
}
quota := int64(-1)
return result, control.Update(&specs.LinuxResources{
CPU: &specs.LinuxCPU{
Quota: "a,
},
})
}
return result, nil
}
通过 Request 携带的 Spec 信息得到容器的 CgroupsPath,读取容器的 Cgroups 文件,然后通过control.Update
方法修改 LinuxCPU.Quota 的值为-1。
1.0 版本中,NRI adaptation 能够传递给 NRI 插件的信息包括:
NRI 插件的配置信息
容器当前生命周期的状态(create,delete,update,pause,resume)
容器 ID
SandboxID
容器进程 Pid
Labels
精简版的容器运行时信息,主要内容包括
容器使用的资源
Namespaces
CgroupsPath
Annotations
type Spec struct {
// Resources struct from the OCI specification
//
// Can be WindowsResources or LinuxResources
Resources json.RawMessage `json:"resources"`
// Namespaces for the container
Namespaces map[string]string`json:"namespaces,omitempty"`
// CgroupsPath for the container
CgroupsPath string`json:"cgroupsPath,omitempty"`
// Annotations passed down to the OCI runtime specification
Annotations map[string]string`json:"annotations,omitempty"`
}
2.0 版本
2.0 版本 NRI 只需要运行一个插件实例用于处理所有 NRI 事件和请求,容器运行时通过 unix-domain socket 与插件通信,使用基于 protobuf 的协议数据,和 1.0 版本相比拥有更高的性能,能够实现有状态的 NRI 插件。
Demo 体验
最新发布的 Containerd 版本集成了 NRI 2.0, NRI 仓库的示例程序也更完善。
# 回到 containerd 本地代码仓库
cd containerd
git checkout main
make && sudo make install
CONTAINERD_DIR=$(cat /lib/systemd/system/containerd.service | grep "ExecStart=" | awk -F= '{gsub("/containerd","",$2); print $2}')
sudo cp bin/containerd* ${CONTAINERD_DIR}
2.0 版本的配置文件和 1.0 版本有些不同:
sudo tee -a /etc/containerd/config.toml <<- EOF
[plugins."io.containerd.nri.v1.nri"]
config_file = "/etc/nri/nri.conf"
disable = false
plugin_path = "/opt/nri/plugins"
socket_path = "/var/run/nri.sock"
EOF
sudo tee /etc/nri/nri.conf <<- EOF
disableConnections: false
EOF
NRI 插件二进制的默认目录更改为/opt/nri/plugins
。
2.0 版本的示例程序源代码位于 nri/plugins[4] 目录下(以 logger 为例):
cd nri
cd plugins/logger
go build -o 01-logger
sudo mkdir /opt/nri/plugins
sudo cp 01-logger /opt/nri/plugins
插件的配置文件路径为/etc/nri/conf.d
,文件名可以是id-basename.conf
和basename.conf
:
此外,NRI 并没有规定插件配置文件的格式,用户可以通过Configure
接口自定义实现。在 logger 示例,可以看到,解析的配置文件为 yaml 格式:
func (p *plugin) Configure(config, runtime, version string) (stub.EventMask, error) {
log.Infof("got configuration data: %q from runtime %s %s", config, runtime, version)
if config == "" {
return p.mask, nil
}
oldCfg := cfg
err := yaml.Unmarshal([]byte(config), &cfg)
if err != nil {
return0, fmt.Errorf("failed to parse provided configuration: %w", err)
}
p.mask, err = api.ParseEventMask(cfg.Events...)
if err != nil {
return0, fmt.Errorf("failed to parse events in configuration: %w", err)
}
if cfg.LogFile != oldCfg.LogFile {
f, err := os.OpenFile(cfg.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Errorf("failed to open log file %q: %v", cfg.LogFile, err)
return0, fmt.Errorf("failed to open log file %q: %w", cfg.LogFile, err)
}
log.SetOutput(f)
}
return p.mask, nil
}
Logger 的配置项包括:
type config struct {
LogFile string`json:"logFile"`
Events []string`json:"events"`
AddAnnotation string`json:"addAnnotation"`
SetAnnotation string`json:"setAnnotation"`
AddEnv string`json:"addEnv"`
SetEnv string`json:"setEnv"`
}
为 logger 插件配置 log 的存储路径:
sudo mkdir /etc/nri/conf.d
sudo mkdir /var/run/containerd/nri
sudo tee /etc/nri/conf.d/01-logger.conf <<- EOF
logFile: /var/run/containerd/nri/logger.log
EOF
重启 containerd,运行容器:
sudo systemctl restart containerd
crictl pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.8
ctr -n k8s.io i tag registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.8 registry.k8s.io/pause:3.8
sudo crictl run container-config.yaml pod-config.yaml
除了在 containerd 启动的同时加载 NRI 插件,也支持手动运行插件(需要修改/etc/nri/nri.conf
的disableConnections
为 true):
/opt/nri/plugins/nri-logger -idx 01 -logFile /var/run/containerd/nri/logger.log
查看 log 文件:
cat /var/run/containerd/nri/logger.log
可以看到,logger 打印了不同接口函数从 NRI adaptation 得到的 Pod 和 Container 相关信息:
源码分析
对于 logger 插件,只需要实现 NRI 接口函数,例如,CreateContainer
:
func (p *plugin) CreateContainer(pod *api.PodSandbox, container *api.Container) (*api.ContainerAdjustment, []*api.ContainerUpdate, error) {
dump("CreateContainer", "pod", pod, "container", container)
adjust := &api.ContainerAdjustment{}
if cfg.AddAnnotation != "" {
adjust.AddAnnotation(cfg.AddAnnotation, fmt.Sprintf("logger-pid-%d", os.Getpid()))
}
if cfg.SetAnnotation != "" {
adjust.RemoveAnnotation(cfg.SetAnnotation)
adjust.AddAnnotation(cfg.SetAnnotation, fmt.Sprintf("logger-pid-%d", os.Getpid()))
}
if cfg.AddEnv != "" {
adjust.AddEnv(cfg.AddEnv, fmt.Sprintf("logger-pid-%d", os.Getpid()))
}
if cfg.SetEnv != "" {
adjust.RemoveEnv(cfg.SetEnv)
adjust.AddEnv(cfg.SetEnv, fmt.Sprintf("logger-pid-%d", os.Getpid()))
}
return adjust, nil, nil
}
2.0 版本的 NRI 插件可以通过CreateContainer
接口修改容器的 OCI Spec 内容,能被修改的范围定义在api.ContainerAdjustment
:
type ContainerAdjustment struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Annotations map[string]string`protobuf:"bytes,2,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Mounts []*Mount `protobuf:"bytes,3,rep,name=mounts,proto3" json:"mounts,omitempty"`
Env []*KeyValue `protobuf:"bytes,4,rep,name=env,proto3" json:"env,omitempty"`
Hooks *Hooks `protobuf:"bytes,5,opt,name=hooks,proto3" json:"hooks,omitempty"`
Linux *LinuxContainerAdjustment `protobuf:"bytes,6,opt,name=linux,proto3" json:"linux,omitempty"`
}
容器的 Annotations
容器的 Mounts
容器的环境变量
容器的 OCI Hooks
容器使用的资源,定义在 api.LinuxContainerAdjustment
Devices
容器使用的 Linux 资源
Cgroups 路径
type LinuxContainerAdjustment struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Devices []*LinuxDevice `protobuf:"bytes,1,rep,name=devices,proto3" json:"devices,omitempty"`
Resources *LinuxResources `protobuf:"bytes,2,opt,name=resources,proto3" json:"resources,omitempty"`
CgroupsPath string`protobuf:"bytes,3,opt,name=cgroups_path,json=cgroupsPath,proto3" json:"cgroups_path,omitempty"`
}
接口列表
除了Configure
和CreateContainer
,NRI 插件能实现的接口函数还包括:
func (p *plugin) Synchronize(pods []*api.PodSandbox, containers []*api.Container) ([]*api.ContainerUpdate, error) {
dump("Synchronize", "pods", pods, "containers", containers)
returnnil, nil
}
func (p *plugin) Shutdown() {
dump("Shutdown")
}
func (p *plugin) RunPodSandbox(pod *api.PodSandbox) error {
dump("RunPodSandbox", "pod", pod)
returnnil
}
func (p *plugin) StopPodSandbox(pod *api.PodSandbox) error {
dump("StopPodSandbox", "pod", pod)
returnnil
}
func (p *plugin) RemovePodSandbox(pod *api.PodSandbox) error {
dump("RemovePodSandbox", "pod", pod)
returnnil
}
func (p *plugin) PostCreateContainer(pod *api.PodSandbox, container *api.Container) error {
dump("PostCreateContainer", "pod", pod, "container", container)
returnnil
}
func (p *plugin) StartContainer(pod *api.PodSandbox, container *api.Container) error {
dump("StartContainer", "pod", pod, "container", container)
returnnil
}
func (p *plugin) PostStartContainer(pod *api.PodSandbox, container *api.Container) error {
dump("PostStartContainer", "pod", pod, "container", container)
returnnil
}
func (p *plugin) UpdateContainer(pod *api.PodSandbox, container *api.Container) ([]*api.ContainerUpdate, error) {
dump("UpdateContainer", "pod", pod, "container", container)
returnnil, nil
}
func (p *plugin) PostUpdateContainer(pod *api.PodSandbox, container *api.Container) error {
dump("PostUpdateContainer", "pod", pod, "container", container)
returnnil
}
func (p *plugin) StopContainer(pod *api.PodSandbox, container *api.Container) ([]*api.ContainerUpdate, error) {
dump("StopContainer", "pod", pod, "container", container)
returnnil, nil
}
func (p *plugin) RemoveContainer(pod *api.PodSandbox, container *api.Container) error {
dump("RemoveContainer", "pod", pod, "container", container)
returnnil
}
func (p *plugin) onClose() {
os.Exit(0)
}
可以看到,NRI 插件可以在 Pod 和 Container 的生命周期加入自定义逻辑。
Pod 生命周期
创建 Pod
停止 Pod
删除 Pod
NRI 插件可使用的信息
ID
name
UID
namespace
labels
annotations
cgroup parent directory
runtime handler name
type PodSandbox struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string`protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Name string`protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Uid string`protobuf:"bytes,3,opt,name=uid,proto3" json:"uid,omitempty"`
Namespace string`protobuf:"bytes,4,opt,name=namespace,proto3" json:"namespace,omitempty"`
Labels map[string]string`protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Annotations map[string]string`protobuf:"bytes,6,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
RuntimeHandler string`protobuf:"bytes,7,opt,name=runtime_handler,json=runtimeHandler,proto3" json:"runtime_handler,omitempty"`
Linux *LinuxPodSandbox `protobuf:"bytes,8,opt,name=linux,proto3" json:"linux,omitempty"`
Pid uint32`protobuf:"varint,9,opt,name=pid,proto3" json:"pid,omitempty"`// for NRI v1 emulation
}
Container 生命周期
创建容器 (*)
创建容器完成
启动容器
启动容器完成
更新容器 (*)
更新容器完成
停止容器 (*)
删除容器
NRI 插件可使用的信息
ID
pod ID
name
state
labels
annotations
command line arguments
environment variables
mounts
OCI hooks
linux
memory
CPU
Block I/O class
RDT class
limit
reservation
swap limit
kernel limit
kernel TCP limit
swappiness
OOM disabled flag
hierarchical accounting flag
hugepage limits
shares
quota
period
realtime runtime
realtime period
cpuset CPUs
cpuset memory
namespace IDs
devices
resources
type Container struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id string`protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
PodSandboxId string`protobuf:"bytes,2,opt,name=pod_sandbox_id,json=podSandboxId,proto3" json:"pod_sandbox_id,omitempty"`
Name string`protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
State ContainerState `protobuf:"varint,4,opt,name=state,proto3,enum=nri.pkg.api.v1alpha1.ContainerState" json:"state,omitempty"`
Labels map[string]string`protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Annotations map[string]string`protobuf:"bytes,6,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Args []string`protobuf:"bytes,7,rep,name=args,proto3" json:"args,omitempty"`
Env []string`protobuf:"bytes,8,rep,name=env,proto3" json:"env,omitempty"`
Mounts []*Mount `protobuf:"bytes,9,rep,name=mounts,proto3" json:"mounts,omitempty"`
Hooks *Hooks `protobuf:"bytes,10,opt,name=hooks,proto3" json:"hooks,omitempty"`
Linux *LinuxContainer `protobuf:"bytes,11,opt,name=linux,proto3" json:"linux,omitempty"`
Pid uint32`protobuf:"varint,12,opt,name=pid,proto3" json:"pid,omitempty"`// for NRI v1 emulation
}
创建容器时可修改的信息
annotations
mounts
environment variables
OCI hooks
linux
memory
CPU
Block I/O class
RDT class
limit
reservation
swap limit
kernel limit
kernel TCP limit
swappiness
OOM disabled flag
hierarchical accounting flag
hugepage limits
shares
quota
period
realtime runtime
realtime period
cpuset CPUs
cpuset memory
devices
resources
type ContainerAdjustment struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Annotations map[string]string`protobuf:"bytes,2,rep,name=annotations,proto3" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Mounts []*Mount `protobuf:"bytes,3,rep,name=mounts,proto3" json:"mounts,omitempty"`
Env []*KeyValue `protobuf:"bytes,4,rep,name=env,proto3" json:"env,omitempty"`
Hooks *Hooks `protobuf:"bytes,5,opt,name=hooks,proto3" json:"hooks,omitempty"`
Linux *LinuxContainerAdjustment `protobuf:"bytes,6,opt,name=linux,proto3" json:"linux,omitempty"`
}
更新容器时可修改的信息
容器被创建成功后,插件可以在以下时间请求更新容器的信息:
响应其他容器创建的请求时
响应任意更新容器的请求时
相应任意停止容器的请求时
单独发起更新请求
更新容器信息时,可以修改的信息包括:
resources
shares
quota
period
realtime runtime
realtime period
cpuset CPUs
cpuset memory
limit
reservation
swap limit
kernel limit
kernel TCP limit
swappiness
OOM disabled flag
hierarchical accounting flag
hugepage limits
memory
CPU
Block I/O class
RDT class
type ContainerUpdate struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
ContainerId string`protobuf:"bytes,1,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"`
Linux *LinuxContainerUpdate `protobuf:"bytes,2,opt,name=linux,proto3" json:"linux,omitempty"`
IgnoreFailure bool`protobuf:"varint,3,opt,name=ignore_failure,json=ignoreFailure,proto3" json:"ignore_failure,omitempty"`
}
参考资料
[1] Add Node Resource Interface design doc: https://github.com/containerd/containerd/pull/4411
[2] 1.0: https://github.com/containerd/nri/blob/main/README-v0.1.0.md
[3] 1.0 版本启用 NRI: https://github.com/containerd/containerd/blob/v1.6.8/vendor/github.com/containerd/nri/README.md
[4] nri/plugins: https://github.com/containerd/nri/tree/main/plugins
Containerd NRI 插件的更多相关文章
- 揭秘!containerd 镜像文件丢失问题,竟是镜像生成惹得祸
导语 作者李志宇,腾讯云后台开发工程师,日常负责集群节点和运行时相关的工作,熟悉 containerd.docker.runc 等运行时组件.近期在为某位客户提供技术支持过程中,遇到了 contain ...
- Containerd 简介
我们可以把 docker 抽象为下图所示的结构(此图来自互联网): 从图中可以看出,docker 对容器的管理和操作基本都是通过 containerd 完成的. 那么,containerd 是什么呢? ...
- containerd与kubernetes集成
kubernetes集群三步安装 概念介绍 cri (Container runtime interface) cri is a containerd plugin implementation of ...
- containerd 与安全沙箱的 Kubernetes 初体验
作者 | 易立 阿里云资深技术专家 containerd 是一个开源的行业标准容器运行时,关注于简单.稳定和可移植,同时支持 Linux 和 Windows. 2016 年 12 月 14 日,Do ...
- 使用Docker Maven 插件进行镜像的创建以及上传至私服
1.在进行服务容器化部署的时候,需要将服务以及其运行的环境整个打包做成一个镜像,打包的过程有两种办法,第一种是首选通过maven打成jar包,然后再编写dockerfile,执行docker buil ...
- 3.kubernetes的CNI网络插件-Flannel
目录 1.1.K8S的CNI网络插件-Flannel 1.1.1.集群规划 1.1.2.下载软件.解压.软链接 1.1.3.最终目录结构 1.1.4.拷贝证书 1.1.5.创建配置 1.1.6.创建启 ...
- 【Pod Terminating原因追踪系列之一】containerd中被漏掉的runc错误信息
前一段时间发现有一些containerd集群出现了Pod卡在Terminating的问题,经过一系列的排查发现是containerd对底层异常处理的问题.最后虽然通过一个短小的PR修复了这个bug,但 ...
- Kubernetes 教程:在 Containerd 容器中使用 GPU
原文链接:https://fuckcloudnative.io/posts/add-nvidia-gpu-support-to-k8s-with-containerd/ 前两天闹得沸沸扬扬的事件不知道 ...
- K8s 终将废弃 docker,TKE 早已支持 containerd
近日 K8s 官方称最早将在 1.23版本弃用 docker 作为容器运行时,并在博客中强调可以使用如 containerd 等 CRI 运行时来代替 docker.本文会做详细解读,并介绍 dock ...
- Containerd 的前世今生和保姆级入门教程
原文链接:https://fuckcloudnative.io/posts/getting-started-with-containerd/ 1. Containerd 的前世今生 很久以前,Dock ...
随机推荐
- Python基础部分:2、 对计算机的认识和python解释器
目录 一.计算机五大组成部分 1.控制器 2.运算器 3.储存器 4.输入设备 5.输出设备 二.计算机三大核心硬件 1.cpu 2.内存 3.硬盘 三.操作系统 四.编程与编程语言 1.编程语言 2 ...
- 前端html和css总结
1.html知识总结 1.1 表格的的相关属性 属性 表示 border-collapse 设置表格的边框是否被合并为一个单一的边框 cellpadding 单元格边距 cellspacing 单元格 ...
- 论文笔记 - MetaICL: Learning to Learn In Context
Motivation Facebook 的 MetaICL,牛逼就对了: 对 LM 针对 ICL 进行微调(而不是特定的任务): 去除了自然语言的 Template,使用更直接的方式,排除了 Temp ...
- .NET应用开发之SQLServer常见问题分析
日常我们开发.NET应用时会使用SQLServer数据库,对于SQLServer数据库的日常开发有一些技能和工具,准备给大家分享一下. 一.场景1:SQLServer死锁分析 执行以下SQL,启用S ...
- 领域驱动设计(DDD)在美团点评业务系统的实践
前言 至少 30 年以前,一些软件设计人员就已经意识到领域建模和设计的重要性,并形成一种思潮,Eric Evans 将其定义为领域驱动设计(Domain-Driven Design,简称 DDD).在 ...
- .net随笔——Web开发config替换到正式config appSettings
前言(废话) 查了一些资料,总体来说呢,就是坑,而且顺带吐槽下百度,一个内容被copy那么多遍还排在最前面.同一个内容我点了那么多次,淦. 正题: 实现目的:开发的时候使用system.debug.c ...
- Day20.1:关于this、super的解析
this.super详解 当我们在外部程序调用一个类的方法,如果这个类的方法与其父类的方法重载,我们需要用this.super进行区分 this在Java中是一个复杂的关键字,this的使用形式体现了 ...
- Redis Lettuce长时间超时问题
1. 背景 新上线了一个服务,在压测的时候大量返回错误,查看报错是io.lettuce.core.RedisCommandTimeoutException: Command timed out aft ...
- 自定义RBAC(5)
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 把实体类及Service类都准备好了之后,就可以开始继续写业务代码了.Spring Security的强大之一就在于它的拦截器.那么这里也可以参 ...
- Codeforces Round #838 (Div. 2) D. GCD Queries
题意 有个长度为n的排列p,[0,1,2,...n-1],你可以进行至多2*n次询问,每次询问两个i,j,返回gcd(pi,pj),让你在规定时间内猜出0在哪两个位置之一 思路 这是一道交互题,询问的 ...