Kubernetes对象深入学习之四:对象属性编码实战

news2024/11/27 17:53:03

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本文是《Kubernetes对象深入学习》系列的第四篇,前面咱们读源码和文档,从理论上学习了kubernetes的对象相关的知识,是时候自己动手来实战操作了

  • 本篇的主要内容就是新建一个golang工程,里面运行一个基于client-go的Controller,监听两种资源类型的变化事件

  • 面对两种不同类型的资源对象,咱们开发一个通用的方法,使用该方法可以获取各种类型的对象的属性,以此来验证前文学习的知识点

  • 整个项目的功能如下图所示,其实挺简单的:用kubectl对资源做修改(修改label),api-server会向所有监听者发送变更事件,object-tutorials是个go开发的应用程序,里面使用client-go库,监听kubernetes上的pod和service所有变更事件,在收到事件后,object-tutorials中会对变更对象做一些读对象属性相关的操作
    在这里插入图片描述

在k8s部署service和deployment

  • 为了实战需要,首先请在kubernetes环境将service和deployment部署好,这里给出我的部署脚本作为参考
  • 所有要部署的内容我都集中在这个名为nginx-deployment-service.yaml脚本中了
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: client-go-tutorials
  name: nginx-deployment
  labels:
    app: nginx-app
    type: front-end
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-app
      type: front-end
  template:
    metadata:
      labels:
        app: nginx-app
        type: front-end
        # 这是第一个业务自定义label,指定了mysql的语言类型是c语言
        language: c
        # 这是第二个业务自定义label,指定了这个pod属于哪一类服务,nginx属于web类
        business-service-type: web
    spec:
      containers:
        - name: nginx-container
          image: nginx:latest
          resources:
            limits:
              cpu: "0.5"
              memory: 128Mi
            requests:
              cpu: "0.1"
              memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
  namespace: client-go-tutorials
  name: nginx-service
spec:
  type: NodePort
  selector:
    app: nginx-app
    type: front-end
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30011
  • 先执行以下命令创建namespace
kubectl create namespace client-go-tutorials
  • 再执行以下命令即可完成资源的创建
kubectl apply -f nginx-deployment-service.yaml
  • 来查看一下资源情况,如下图,service和pod都创建好了,准备工作完成,可以开始编码了
    在这里插入图片描述

源码下载

  • 如果您不想编写代码,也可以从GitHub上直接下载,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):
名称链接备注
项目主页https://github.com/zq2599/blog_demos该项目在GitHub上的主页
git仓库地址(https)https://github.com/zq2599/blog_demos.git该项目源码的仓库地址,https协议
git仓库地址(ssh)git@github.com:zq2599/blog_demos.git该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在object-tutorials文件夹下,如下图黄框所示:
    在这里插入图片描述

编码:准备工程

  • 执行命令名为go mod init object-tutorials,新建module
  • 确保您的goproxy是正常的
  • 执行命令go get k8s.io/client-go@v0.22.8,下载client-go的指定版本
  • 现在工程已经准备好了,接着就是具体的编码

编码:梳理

  • 咱们按照开发顺序开始写代码,如果您看过欣宸的《client-go实战》系列,此刻对使用client-go开发简易版controller应该很熟悉了,这里在简单提一下controller的基本套路
  1. 在整个controller中,核心是队列,或者说队列是唯一的一条线,其他知识点都是珍珠,被队列穿起来
  2. 当队列创建成功后,咱们接下来要做的就是往队列中生产数据,然后取出数据来消费
  3. 还要多考虑一些,例如多个协程并行消费,以及消费过程中发生异常时的处理逻辑
  4. 然后就是本篇的核心了:无视资源类型的不同,可以用同一段代买来处理各种资源对象的属性
  • 这些编码要实现的功能,如下图所示,队列为线,其他知识点为珍珠
    在这里插入图片描述

编码:数据结构

  • 新建controller.go文件,先定义数据结构
// 自定义controller数据结构,嵌入了真实的控制器
type Controller struct {
	// 本地缓存,关注的对象都会同步到这里
	indexer cache.Indexer
	// 消息队列,用来触发对真实对象的处理事件
	queue workqueue.RateLimitingInterface
	// 实际运行运行的控制器
	informer cache.Controller
}

编码:创建队列,生产数据

  • 首先要创建队列,然后对k8s的api-server建立list&watch(list取得全量数据,watch监听数据变化的通知),再让整个监听和响应的逻辑运行起来
  • 上述功能由以下两个方法组成,CreateAndStartController负责创建实例,并且队列的生产逻辑也在此方法中,Run方法负责让队列的生产和消费运转起来

// Run 开始常规的控制器模式(持续响应资源变化事件)
func (c *Controller) Run(threadiness int, stopCh chan struct{}) {
	defer runtime.HandleCrash()

	// Let the workers stop when we are done
	defer c.queue.ShutDown()
	klog.Info("Starting Pod controller")

	go c.informer.Run(stopCh)

	// Wait for all involved caches to be synced, before processing items from the queue is started
	// 刚开始启动,从api-server一次性全量同步所有数据
	if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
		runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
		return
	}

	// 支持多个线程并行从队列中取得数据进行处理
	for i := 0; i < threadiness; i++ {
		go wait.Until(c.runWorker, time.Second, stopCh)
	}

	<-stopCh
	klog.Info("Stopping Pod controller")
}

// CreateAndStartController 为了便于外部使用,这里将controller的创建和启动封装在一起
func CreateAndStartController(c cache.Getter, objType objectruntime.Object, resource string, namespace string, stopCh chan struct{}) {
	// ListWatcher用于获取数据并监听资源的事件
	podListWatcher := cache.NewListWatchFromClient(c, resource, NAMESPACE, fields.Everything())

	// 限速队列,里面存的是有事件发生的对象的身份信息,而非对象本身
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	// 创建本地缓存并对指定类型的资源开始监听
	// 注意,如果业务上有必要,其实可以将新增、修改、删除等事件放入不同队列,然后分别做针对性处理,
	// 但是,controller对应的模式,主要是让status与spec达成一致,也就是说增删改等事件,对应的都是查到实际情况,令其与期望情况保持一致,
	// 因此,多数情况下增删改用一个队列即可,里面放入变化的对象的身份,至于处理方式只有一种:查到实际情况,令其与期望情况保持一致
	indexer, informer := cache.NewIndexerInformer(podListWatcher, objType, 0, cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				// 再次注意:这里放入队列的并非对象,而是对象的身份,作用是仅仅告知消费方,该对象有变化,
				// 至于有什么变化,需要消费方自行判断,然后再做针对性处理
				queue.Add(key)
			}
		},
		UpdateFunc: func(old interface{}, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
	}, cache.Indexers{})

	controller := &Controller{
		informer: informer,
		indexer:  indexer,
		queue:    queue,
	}

	go controller.Run(1, stopCh)
}

编码:消费队列的数据

  • 这里先写好业务代码,就是知道某个对象发生变化时,具体的业务逻辑是什么,如下所示
// syncToStdout 这是业务逻辑代码,被调用意味着key对应的对象有变化(新增或者修改)
func (c *Controller) syncToStdout(key string) error {
	// 从本地缓存中取出完整的对象
	obj, exists, err := c.indexer.GetByKey(key)
	if err != nil {
		klog.Errorf("Fetching object with key %s from store failed with %v", key, err)
		return err
	}

	// 如果不存在,就表示这是个删除事件
	if !exists {
		fmt.Printf("Pod %s does not exist anymore\n", key)
	} else {
		// 这里无视了obj具体是什么类型的对象(deployment、pod这些都有可能),
		// 用meta.Accessor转换出metav1.Object对象后就能获取该对象的所有meta信息
		objMeta, err := meta.Accessor(obj)

		if err != nil {
			klog.Errorf("get meta accessor error, [%s], failed with %v", key, err)
			return err
		}

		// 取得资源的所有属性
		labels := objMeta.GetLabels()

		if labels == nil {
			klog.Infof("name [%s], namespace [%s], label is empty", objMeta.GetName(), objMeta.GetNamespace())
			return nil
		}

		// 遍历每个属性,打印出来
		for key, value := range labels {
			klog.Infof("name [%s], namespace [%s], key [%s], value [%s]",
				objMeta.GetName(),
				objMeta.GetNamespace(),
				key,
				value)
		}
	}
	return nil
}
  • 接下里要写的是从消费队列中的数据,即:取数据,然后调用上面的syncToStdout,这里由两个方法组成,runWorker负责建立一个无限循环,不断调用processNextItem方法
// processNextItem 不间断从队列中取得数据并处理
func (c *Controller) processNextItem() bool {
	// 注意,队列里面不是对象,而是key,这是个阻塞队列,会一直等待
	key, quit := c.queue.Get()
	if quit {
		return false
	}
	// Tell the queue that we are done with processing this key. This unblocks the key for other workers
	// This allows safe parallel processing because two pods with the same key are never processed in
	// parallel.
	defer c.queue.Done(key)

	// 注意,这里的syncToStdout应该是业务代码,处理对象变化的事件
	err := c.syncToStdout(key.(string))

	// 如果前面的业务逻辑遇到了错误,就在此处理
	c.handleErr(err, key)

	// 外面的调用逻辑是:返回true就继续调用processNextItem方法
	return true
}

// runWorker 这是个无限循环,不断地从队列取出数据处理
func (c *Controller) runWorker() {
	for c.processNextItem() {
	}
}

编码:异常处理

  • 还有个handleErr方法,在业务消费队列数据失败时的处理逻辑,可见这里的做法是将key重新放入队列,让业务逻辑再消费一次,这就是失败重试逻辑,这样的重复次数被限定在5次以内,超过了就不再放入队列中了
// handleErr 如果前面的业务逻辑执行出现错误,就在此集中处理错误,本例中主要是重试次数的控制
func (c *Controller) handleErr(err error, key interface{}) {
	if err == nil {
		// Forget about the #AddRateLimited history of the key on every successful synchronization.
		// This ensures that future processing of updates for this key is not delayed because of
		// an outdated error history.
		c.queue.Forget(key)
		return
	}

	// 如果重试次数未超过5次,就继续重试
	if c.queue.NumRequeues(key) < 5 {
		klog.Infof("Error syncing pod %v: %v", key, err)

		// Re-enqueue the key rate limited. Based on the rate limiter on the
		// queue and the re-enqueue history, the key will be processed later again.
		c.queue.AddRateLimited(key)
		return
	}

	// 代码走到这里,意味着有错误并且重试超过了5次,应该立即丢弃
	c.queue.Forget(key)
	// 这种连续五次重试还未成功的错误,交给全局处理逻辑
	runtime.HandleError(err)
	klog.Infof("Dropping pod %q out of the queue: %v", key, err)
}

编码,主程序

  • 最后是main.go文件中的main方法,作用是加载kubernetes的配置文件,以及决定要监听哪些资源的变化,这里通过调用两次CreateAndStartController方法,对pod和service的变化建立了监听
package main

import (
	"flag"
	"path/filepath"

	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"k8s.io/klog/v2"
)

const (
	NAMESPACE = "client-go-tutorials"
)

func main() {
	var kubeconfig *string
	var master string

	// 试图取到当前账号的家目录
	if home := homedir.HomeDir(); home != "" {
		// 如果能取到,就把家目录下的.kube/config作为默认配置文件
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
		master = ""
	} else {
		// 如果取不到,就没有默认配置文件,必须通过kubeconfig参数来指定
		flag.StringVar(kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
		flag.StringVar(&master, "master", "", "master url")
		flag.Parse()
	}

	config, err := clientcmd.BuildConfigFromFlags(master, *kubeconfig)
	if err != nil {
		klog.Fatal(err)
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		klog.Fatal(err)
	}

	stop := make(chan struct{})
	defer close(stop)

	CreateAndStartController(clientset.CoreV1().RESTClient(), &v1.Pod{}, "pods", NAMESPACE, stop)
	CreateAndStartController(clientset.CoreV1().RESTClient(), &v1.Service{}, "services", NAMESPACE, stop)

	select {}
}

运行程序,验证效果

  • 现在将程序运行起来,用go build构建应用,或者直接用IDE运行,启动后可以看到输入如下,可见程序符合预期,将所有service和pod的label都在日志中打印出来了,值得注意的是service现在还没有任何label,这在日志中也有提示
Starting: /root/software/gopath/bin/dlv dap --listen=127.0.0.1:33405 --log-dest=3 from /root/github/blog_demos/tutorials/object-tutorials
DAP server listening at: 127.0.0.1:33405
Type 'dlv help' for list of commands.
I0723 08:20:14.695662 2256666 controller.go:131] Starting Pod controller
I0723 08:20:14.696133 2256666 controller.go:131] Starting Pod controller
I0723 08:20:14.796419 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [business-service-type], value [web]
I0723 08:20:14.796620 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [language], value [c]
I0723 08:20:14.796669 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [pod-template-hash], value [78f6b696d9]
I0723 08:20:14.796704 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [type], value [front-end]
I0723 08:20:14.796737 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wpnt7], namespace [client-go-tutorials], key [app], value [nginx-app]
I0723 08:20:14.796792 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [app], value [nginx-app]
I0723 08:20:14.796831 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [business-service-type], value [web]
I0723 08:20:14.796865 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [language], value [c]
I0723 08:20:14.796901 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [pod-template-hash], value [78f6b696d9]
I0723 08:20:14.796960 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-wp4qf], namespace [client-go-tutorials], key [type], value [front-end]
I0723 08:20:14.797007 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [pod-template-hash], value [78f6b696d9]
I0723 08:20:14.797047 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [type], value [front-end]
I0723 08:20:14.797100 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [app], value [nginx-app]
I0723 08:20:14.797139 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [business-service-type], value [web]
I0723 08:20:14.797174 2256666 controller.go:88] name [nginx-deployment-78f6b696d9-j98xj], namespace [client-go-tutorials], key [language], value [c]
I0723 08:20:14.797346 2256666 controller.go:82] name [nginx-service], namespace [client-go-tutorials], label is empty
  • 现在修改资源对象试试,首先修改service,执行以下命令进入vi编辑模式
kubectl edit service nginx-service -n client-go-tutorial
  • 下图红框中是新增的内容
    在这里插入图片描述
  • 保存退出,在程序的控制台可见以下日志输出,证明service的变更事件都被会咱们的object-tutorials程序响应,也能顺利取出service对象的属性并打印到日志中
    在这里插入图片描述
  • 接着修改一个pod的label,新增内容如下图黄色箭头所示
    在这里插入图片描述
  • 保存后,程序这边立即有日志输出,会打印该pod的所有label
    在这里插入图片描述
  • 至此,编码和验证都完成了,结果符合预期,meta.Accessor方法很好用,拿到属性对象后所有资源的属性信息都能轻易获取

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/780141.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【算法基础:搜索与图论】3.6 二分图(染色法判定二分图匈牙利算法)

文章目录 二分图介绍染色法判定二分图例题&#xff1a;860. 染色法判定二分图 匈牙利匹配二分图最大匹配匈牙利匹配算法思想例题&#xff1a;861. 二分图的最大匹配 二分图介绍 https://oi-wiki.org/graph/bi-graph/ 二分图是图论中的一个概念&#xff0c;它的所有节点可以被…

群组变量选择、组惩罚group lasso套索模型预测新生儿出生体重风险因素数据和交叉验证、可视化...

原文链接&#xff1a;http://tecdat.cn/?p25158 本文介绍具有分组惩罚的线性回归、GLM和Cox回归模型的正则化路径。这包括组选择方法&#xff0c;如组lasso套索、组MCP和组SCAD&#xff0c;以及双级选择方法&#xff0c;如组指数lasso、组MCP&#xff08;点击文末“阅读原文”…

htmlCSS-----背景样式

目录 前言&#xff1a; 背景样式 1.背景颜色 background-color 2.背景图片 background-image 背景的权重比较 代码示例&#xff1a; 前言&#xff1a; 很久没写文章了&#xff0c;会不会想我呢&#xff01;今天我们开始学习html和CSS的背景样式以及文字样式&#xff…

井川里予是谁呢?是中国人,还是日本人?

井川里予是抖音上的一个网红&#xff0c;名字叫庞欣然。 井川里予不是日本人&#xff0c;她是地地道道的中国人。 井川里予2001年6月出生于浙江省杭州市&#xff0c;现在在广东湛江发展。她毕业于浙江经济职业技术学院&#xff0c;抖音女网红&#xff0c;粉丝高达一千多万&…

Day 64:集成学习之 AdaBoosting (2. 树桩分类器)

做了一个超类, 用于支持不同的基础分类器. 这里为了减少代码量, 只实现了树桩分类器.树桩分类器每次只将数据分成两堆, 与决策树相比, 简单至极. 当然, 这里处理的是实型数据, 而 ID3 处理的是符号型数据. 抽象分类器代码&#xff1a; package dl;import java.util.Random;im…

图像处理之LoG算子(高斯拉普拉斯)

LoG算子&#xff08;高斯拉普拉斯算子&#xff09; LoG算子是由拉普拉斯算子改进而来。拉普拉斯算子是二阶导数算子&#xff0c;是一个标量&#xff0c;具有线性、位移不变性&#xff0c;其传函在频域空间的原点为0。所有经过拉普拉斯算子滤波的图像具有零平均灰度。但是该算子…

栈OJ(C++)

文章目录 1.最小栈2.栈的压入、弹出序列3.逆波兰表达式&#xff08;后缀表达式&#xff09;求值3.1后缀表达式求值3.2中缀表达式转后缀表达式3.3带有括号的中缀表达式转后缀表达式 1.最小栈 class MinStack { public:MinStack(){}void push(int val){_st.push(val);//empty放在…

MQTT网关 5G物联网网关 PLC控制工业网关

MQTT网关&#xff0c;两个以上的节点之间通信的新型网关&#xff0c;网络节点之间通过互连来实现双向通信。支持PLC协议转MQTT&#xff0c;实现plc数据采集上云&#xff0c;物联网云平台对接&#xff0c;广泛应用于工业自动化plc远程监测控制。 计讯物联5G MQTT物联网网关TG463…

设计模式-单例模式进阶

在前面的文章(设计模式-单例模式)中&#xff0c;我们分别介绍了四种单例设计模式&#xff0c;包括普通恶汉式单例、双重检查锁单例(DCL)、静态内部类单例以及枚举单例。但是&#xff0c;这四种模式还有一些问题我们没有仔细分析&#xff0c;以至于我们无法深入分析他们的优点以…

【面试题】万字总结MYSQL面试题

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 1、三大范式 2、DML 语句和 DDL 语句区别 3、主键和外键的区别 4、drop、delete、truncate 区别 5、基础架构 6、MyISAM 和 InnoDB 有什么区别&#xff1f; 7、推荐自增id作为…

【mac系统】mac系统调整妙控鼠标速度

当下环境&#xff1a; mac系统版本&#xff0c;其他系统应该也可以&#xff0c;大家可以自行试下&#xff1a; 鼠标 mac妙控鼠标&#xff0c;型号A1657 问题描述&#xff1a; 通过mac系统自带的鼠标速度调节按钮&#xff0c;调到最大后还是感觉移动速度哦过慢 问题解决&…

若依微服务整合activiti7.1.0.M6

若依微服务3.6.3版本整合activiti7&#xff08;7.1.0.M6&#xff09; 目前有两种办法集成activiti7 放弃activiti7新版本封装的API&#xff0c;使用老版本的API&#xff0c;这种方式只需要直接集成即可&#xff0c;在7.1.0.M6版本中甚至不需要去除security的依赖。不多介绍&a…

日常问题记录-Android-Bug-OOM

大家好哇&#xff0c;我是梦辛工作室的灵&#xff0c;最近的项目中&#xff0c;我又遇到了一个bug&#xff0c;就是我写了一个类 将app会用到的Bitmap缓存起来进行管理&#xff0c;防止OOM嘛&#xff0c;不过莫名奇妙的事情还是发生了&#xff0c;内存依旧上涨&#xff0c;且没…

数据结构day7(2023.7.23)

一、Xmind整理&#xff1a; 二、课上练习&#xff1a; 练习1&#xff1a;结点之间的关系 练习2&#xff1a;二叉树的特殊形态 练习3&#xff1a;满二叉树的形态 练习4&#xff1a;完全二叉树的形态 满二叉树一定是完全二叉树&#xff0c;完全二叉树不一定是满二叉树 练习5&am…

Windows系统自检中断导致存储文件系统损坏的服务器数据恢复案例

服务器数据恢复环境&#xff1a; 一台挂载在Windows server操作系统服务器上的v7000存储&#xff0c;划分了一个分区&#xff0c;格式化为NTFS文件系统&#xff0c;该分区存放oracle数据库。 服务器故障&#xff1a; 服务器在工作过程中由于未知原因宕机&#xff0c;工作人员重…

机器学习深度学习——线性回归

之前已经介绍过线性回归的基本元素和随机梯度下降法及优化&#xff0c;现在把线性回归讲解完&#xff1a; 线性回归 矢量化加速正态分布与平方损失从线性回归到深度网络神经网络图生物学 矢量化加速 在训练模型时&#xff0c;我们常希望能够同时处理小批量样本&#xff0c;所以…

涵子来信——自己的电脑——谈谈想法

大家好&#xff1a; 上一次谈论了苹果的那些事&#xff0c;今天我们来聊聊电脑。 我的第一台电脑现在成了这样子&#xff1a; 很多人以为是我自己拆了电脑做研究&#xff0c;其实是我的第一台电脑&#xff0c;真的坏了。 2021年&#xff0c;我有了属于我自己的第一台电脑&am…

STM32 HAL库串口重映射printf

添加代码 #include "stdio.h" int fputc(int ch, FILE *f) {HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);return ch; }keil设置 实现效果&#xff1a; 打印变量 printf("Hello, I am %s\r\n", "iii"); // printf输出字符…

Kubernetes pv-pvc-nfs-service综合实验

目录 实验&#xff1a;pv-pvc-nfs-service综合实验 实验环境 实验描述 实验拓扑图&#xff1a; 实验步骤&#xff1a; 1、修改nfs服务器的主机名&#xff1a; 2、搭建nfs服务器&#xff1a;(131条消息) 搭建NFS服务器_搭建nfs存储_Claylpf的博客-CSDN博客 3、测试k8s上…

极速跳板机登陆服务器

目录 一&#xff1a;简单登陆跳板器二&#xff1a;一键申请相关的服务器权限三&#xff1a;简化登陆 一&#xff1a;简单登陆跳板器 登陆公司提供的网址&#xff0c; 下载自己的专属RSA密钥。在密钥文件处&#xff0c; 执行登陆指令&#xff1a; ssh -p 36000 -i id_rsa 用户跳…