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: &quota,
},
})
}
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.confbasename.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.confdisableConnections为 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"`
}

接口列表

除了ConfigureCreateContainer,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"`
}

更新容器时可修改的信息

容器被创建成功后,插件可以在以下时间请求更新容器的信息:

  1. 响应其他容器创建的请求时

  2. 响应任意更新容器的请求时

  3. 相应任意停止容器的请求时

  4. 单独发起更新请求

更新容器信息时,可以修改的信息包括:

  • 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 插件的更多相关文章

  1. 揭秘!containerd 镜像文件丢失问题,竟是镜像生成惹得祸

    导语 作者李志宇,腾讯云后台开发工程师,日常负责集群节点和运行时相关的工作,熟悉 containerd.docker.runc 等运行时组件.近期在为某位客户提供技术支持过程中,遇到了 contain ...

  2. Containerd 简介

    我们可以把 docker 抽象为下图所示的结构(此图来自互联网): 从图中可以看出,docker 对容器的管理和操作基本都是通过 containerd 完成的. 那么,containerd 是什么呢? ...

  3. containerd与kubernetes集成

    kubernetes集群三步安装 概念介绍 cri (Container runtime interface) cri is a containerd plugin implementation of ...

  4. containerd 与安全沙箱的 Kubernetes 初体验

    作者 | 易立  阿里云资深技术专家 containerd 是一个开源的行业标准容器运行时,关注于简单.稳定和可移植,同时支持 Linux 和 Windows. 2016 年 12 月 14 日,Do ...

  5. 使用Docker Maven 插件进行镜像的创建以及上传至私服

    1.在进行服务容器化部署的时候,需要将服务以及其运行的环境整个打包做成一个镜像,打包的过程有两种办法,第一种是首选通过maven打成jar包,然后再编写dockerfile,执行docker buil ...

  6. 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.创建启 ...

  7. 【Pod Terminating原因追踪系列之一】containerd中被漏掉的runc错误信息

    前一段时间发现有一些containerd集群出现了Pod卡在Terminating的问题,经过一系列的排查发现是containerd对底层异常处理的问题.最后虽然通过一个短小的PR修复了这个bug,但 ...

  8. Kubernetes 教程:在 Containerd 容器中使用 GPU

    原文链接:https://fuckcloudnative.io/posts/add-nvidia-gpu-support-to-k8s-with-containerd/ 前两天闹得沸沸扬扬的事件不知道 ...

  9. K8s 终将废弃 docker,TKE 早已支持 containerd

    近日 K8s 官方称最早将在 1.23版本弃用 docker 作为容器运行时,并在博客中强调可以使用如 containerd 等 CRI 运行时来代替 docker.本文会做详细解读,并介绍 dock ...

  10. Containerd 的前世今生和保姆级入门教程

    原文链接:https://fuckcloudnative.io/posts/getting-started-with-containerd/ 1. Containerd 的前世今生 很久以前,Dock ...

随机推荐

  1. letcode-学习-数组去重

    数组去重 问题描述: 给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度.元素的 相对顺序 应该保持 一致 . 由于在某些语言中不 ...

  2. ElasticSearch这些坑记得避开

    目录 一.管理方式 二.结构维护 三.数据调度 1.同步方案 2.中断和恢复 四.刷新策略 五.深度分页 六.参考源码 Index用不好,麻烦事不会少: 一.管理方式 ElasticSearch作为最 ...

  3. 基于docker和cri-dockerd部署kubernetes v1.25.3

    基于docker和cri-dockerd部署kubernetes v1.25.3 1.环境准备 1-1.主机清单 主机名 IP地址 系统版本 k8s-master01 k8s-master01.wan ...

  4. TreeUtils工具类一行代码实现列表转树 实战Java8 三级菜单 三级分类 附视频

    一.序言 在日常一线开发过程中,总有列表转树的需求,几乎是项目的标配,比方说做多级菜单.多级目录.多级分类等,有没有一种通用且跨项目的解决方式呢?帮助广大技术朋友给业务瘦身,提高开发效率. 本文将基于 ...

  5. Crypto - Caesar I

    原题链接:http://www.wechall.net/challenge/training/crypto/caesar/index.php 告诉我们这是个古凯撒密码,让我们解...我们百度下古凯撒密 ...

  6. 线性时间选择(含平均情况O(n)和最坏情况O(n)算法)

    前言 本篇文章我将介绍 期望为线性时间 的选择算法和 最坏情况为线性时间 的选择算法,即分别为 平均情况下时间复杂度为O(n) 和 最坏情况下时间复杂度为O(n) 的线性时间选择.以下包含了我自己的全 ...

  7. php7怎么安装memcache扩展

    php7安装memcache扩展 1.下载文件,解压缩 memcache windows php7下载地址: https://github.com/nono303/PHP7-memcache-dll ...

  8. 关于python实现与体重秤蓝牙ble通信研究(Linux)

    前言 前几天买一个带蓝牙的体重秤,功能就是可以通过手机app连接,然后每一次称重都会记录下来,然后进行一些计算(体脂等),但是我不想用手机来操作,我习惯用电脑,就想写一个软件来与体重秤通信,记录我的每 ...

  9. ArcObjects SDK开发 001 ArcObjects SDK 简介

    1.什么是ArcObjects SDK 在网上搜索什么是ArcObjects,会搜到如下的定义. 这个定义比较准确,也比较容易理解. 2.什么是ArcEngine 在网上搜索ArcEngine,一般会 ...

  10. 第二十五节:scrapy爬虫识别验证码(四)手绘验证码识别

    一.介绍 今天主要介绍的是微博客户端在登录时出现的四宫格手绘验证码,不多说直接看看验证码长成什么样.        二.思路 1.由于微博上的手绘验证码只有四个宫格,且每个宫格之间都有有向线段连接,所 ...