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 ...
随机推荐
- 2流高手速成记(之七):基于Dubbo&Nacos的微服务简要实现
本节内容会用到之前给大家讲过的这两篇: 2流高手速成记(之六):从SpringBoot到SpringCloudAlibaba 2流高手速成记(之三):SpringBoot整合mybatis/mybat ...
- 「浙江理工大学ACM入队200题系列」问题 A: 零基础学C/C++34—— 3个数比较大小(冒泡排序与选择排序算法)
本题是浙江理工大学ACM入队200题第四套中的A题,同时给出了冒泡排序和选择排序算法 我们先来看一下这题的题面. 由于是比较靠前的题目,这里插一句.各位新ACMer朋友们,请一定要养成仔细耐心看题的习 ...
- springboot前端向后端请求返回html语句
后端接口代码 @PostMapping("/service/confirmPay") @ResponseBody public GlobalResponse confirmPay( ...
- DevOps 必备的 Kubernetes 安全清单
Kubernetes 是当今许多公司采用的容器编排平台,它的实施需要对其生态系统有一定的了解,以便部署一个准备好用于生产的集群.然而从原则上来说,Kubernetes 并不是一个安全的平台,因为它缺乏 ...
- gRPC(Java) keepAlive机制研究
基于java gRPC 1.24.2 分析 结论 gRPC keepAlive是grpc框架在应用层面连接保活的一种措施.即当grpc连接上没有业务数据时,是否发送pingpong,以保持连接活跃性, ...
- kubernetes_CoreDNS全解析
一.前言 kubernetes CoreDNS 是 kube-system 命令空间里面的一个Pod,用于域名解析. kubernetes自带三个命名空间(用kubeadm安装的Kubernetes集 ...
- 快捷打开cmd管理员模式
win+s-搜索cmd 直接回车:普通用户模式的cmd CTRL+SHIFT+回车:管理员模式的cmd
- 使用Typora
Markdown学习 标题:#+空格+名称 二级标题 二级标题:##+空格+名称 三级标题 几级标题以此类推,最多支持到六级标题 字体 Hello,world! 变粗体:一句话的前后加上两个** 变斜 ...
- 网络编程 - OSI七层协议详解
目录 网络编程基础 软件开发架构 网络编程简介 OSI七层协议简介 OSI协议之物理连接层 OSI协议之数据链路层 网络相关专业名词 OSI之网络层 OSI协议之传输层 传输层之TCP协议/UDP协议 ...
- AIBOX视频边缘计算终端,助力识别人员违规行为!
目前,制造业工厂工作区布局分散,生产安全质量控制难度较大.人员擅自离岗.玩手机.区域入侵.吸烟.未穿反光衣.异物占位等违法行为不能及时控制,安全风险十分巨大.如果手动检查或通过人眼检查监控录像,不仅产 ...