k8s-编写CSI插件(3)

1、概述

在 Kubernetes 中,存储插件的开发主要有以下几种方式:

  1. CSI插件:Container Storage Interface (CSI) 是 Kubernetes 的标准插件接口,是全新的插件方案,插件和驱动调用通过grpc协议,功能丰富,支持存储卷动态提供、快速、动态扩容等等。例如,可以连 in-tree 的插件做平滑迁移,当系统中运行有对应类型的 CSI 驱动时,其实使用方式虽然还是 in-tree 的方式,但 Kubernetes 已在后台默默为你替换成了CSI 插件方案,Volume 的 Attach/Detah、Mount/Unmount 等操作都会调用相应的 CSI 驱动完成。本文以 CSI 插件为例子学习。后续会详细介绍一下 CSI的实现原理和编写自己的你CSI驱动。

  2. FlexVolume插件:FlexVolume 是 Kubernetes 的早期存储插件接口之一,它提供了一个简单的接口,但局限性却很大,用于将存储驱动程序接入到 Kubernetes 中。通过实现 FlexVolume 接口,可以将各种存储系统接入到 Kubernetes 集群中,包括 NFS、GlusterFS、Ceph 等等。

  3. in-tree插件:in-tree 存储插件是 Kubernetes 的早期存储插件接口之一,它将存储驱动程序嵌入到 Kubernetes 主体代码库中。in-tree 插件可以实现对本地存储、NFS、iSCSI 等存储系统的支持。不过,由于 in-tree 插件需要嵌入到 Kubernetes 主体代码库中,因此对于插件开发者而言,维护成本较高,并且需要适应 Kubernetes 主体代码库的版本变化。

说明:CSI 插件是 Kubernetes 中推荐使用的存储插件接口,它提供了一种标准化的接口,更完善、更友好的编程插件方式,能够将各种存储系统集成到 Kubernetes 中。而 FlexVolume 插件和 in-tree 插件则是早期的存储插件接口,由于它们的维护成本较高,因此在新的 Kubernetes 版本中已经不再被推荐使用。

2、CSI 插件实现原理

CSI 的设计思想,把插件的职责从之前讲的 “两阶段处理”,扩展成了 Provision、Attach 和 Mount 三个阶段。趁着可以复习持久化 Volume 的两个阶段 k8s-持久化存储PV与PVC。

CSI 插件设计还将 kubernetes 里面的部分存储管理功能剥离出来,做成独立的外部组件(External Components)分为 Driver Register、External Provisioner 和 External Attacher。

2.1、CSI 处理的三个阶段

(1)Provision 阶段实现是指与外部存储供应商协凋卷 CreateVolume 和 DeleteVolume。假如外部存储供应商为 Google Cloud ,那么此阶段应该完成在 Google Cloud 存储商创建/删除一个指定大小的块存储设备。

(2)Attach 阶段是指将外部存储供应商提供好的卷存储设备挂载到本地或从本地卸载,其实就是实现 ControllerPublishVolume 和 ControllerUnpublishVolume。假如以外部存储供应商为Google Cloud存储为例,在 Provisioning 阶段创建好的卷的块设备,在此阶段应该实现将其挂载到服务器本地或从本地卸载,在必要的情况下还需要进行格式化等操作,但会在 Mount 进行格式化操作。

(3)Mount 阶段实现会当一个目标 Pod 在某个 Node 节点上调度时,kubelet 会根据前两个阶段返回的结果来创建这个 Pod。以外部存储供应商为Google Cloud云存储为例,此阶段将会把已经 Attaching 的本地块设备以目录形式挂载到 Pod 中或者从 Pod 中卸载这个块设备。

更加简单的描述:

  • Provision 等价于 “创建磁盘”;
  • Attach 等价于 “挂载磁盘到虚拟机”;
  • Mount 等价于 “将该磁盘格式化后,挂载在 Volume 的宿主机目录上”;
2.2、External Components

Provisioner、

(1)External Provisioner 组件:负责的是 Provision 阶段,它会去 Watch APIServer 里面的 PVC 对象,当有 PVC 被创建时,它会去调用 CSI Controller 的 CreateVolume 方法创建 PV 出来。

(2)External Attacher 组件:负责的是 Attach 阶段,它监听了 APIServer 里 VolumeAttachment 对象的变化,一旦有变化,它会去调用 CSI Controller 的 ControllerPublish 方法完成它所对应的 Volume 的 Attach 阶段。

(3)Driver Registrar 组件,负责将插件注册到 kubelet 里面。

关于描述,建议 查阅文档

2.3 、CSI gRPC Server

CSI 的三大阶段实际上更细粒度的划分到 CSI Sidecar Containers 中,其实开发 CSI 实际上是面向 CSI Sidecar Containers 编程。针对于 CSI Sidecar Containers 主要实现 CSI 插件以 gRPC 的方式对外提供的三个 gRPC Service,分别叫作:CSI Identity Service、CSI Controller Service 和 CSI Node Service,这三个服务也是需要我们去编写代码来实现的 CSI 插件。

(1)Identity Service

CSI Identity 服务,主要负责对外暴露这个插件本身的信息,所定义的 proto 文件如下:

service Identity {
// return the version and name of the plugin
rpc GetPluginInfo(GetPluginInfoRequest) returns (GetPluginInfoResponse) {}// 报告插件是否具有为控制器接口提供服务的能力
rpc GetPluginCapabilities(GetPluginCapabilitiesRequest) returns (GetPluginCapabilitiesResponse) {}// 由 CO 调用只是为了检查插件是否正在运行
rpc Probe (ProbeRequest) returns (ProbeResponse) {}}

IdentityServer 定义的接口如下:

// IdentityServer is the server API for Identity service.type IdentityServer interface {GetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error)GetPluginCapabilities(context.Context, *GetPluginCapabilitiesRequest) (*GetPluginCapabilitiesResponse, error)Probe(context.Context, *ProbeRequest) (*ProbeResponse, error)}

(2)Controller Service

CSI Controller 服务,定义的是对 CSI Volume(对应 Kubernetes 里的 PV)的管理接口,比如:创建和删除 CSI Volume、对 CSI Volume 进行 Attach/Dettach(在 CSI 里,这个操作被叫作 Publish/Unpublish),以及对 CSI Volume 进行 Snapshot 等。

CSI Controller 服务里定义的这些操作大部分的核心逻辑应该在 ControllerServer 中实现,比如创建/销毁 Volume,创建/销毁 Snapshot 等。在实际开发中,自己编写的 CSI 都会实现 CreateVolume 和 DeleteVolume,至于其他方法根据业务需求以及外部存储供应商实际情况来决定是否进行实现。

接口定义如下所示:

// ControllerServer is the server API for Controller service.type ControllerServer interface {// 
CreateVolume(context.Context, *CreateVolumeRequest) (*CreateVolumeResponse, error)DeleteVolume(context.Context, *DeleteVolumeRequest) (*DeleteVolumeResponse, error)ControllerPublishVolume(context.Context, *ControllerPublishVolumeRequest) (*ControllerPublishVolumeResponse, error)ControllerUnpublishVolume(context.Context, *ControllerUnpublishVolumeRequest) (*ControllerUnpublishVolumeResponse, error)ValidateVolumeCapabilities(context.Context, *ValidateVolumeCapabilitiesRequest) (*ValidateVolumeCapabilitiesResponse, error)ListVolumes(context.Context, *ListVolumesRequest) (*ListVolumesResponse, error)GetCapacity(context.Context, *GetCapacityRequest) (*GetCapacityResponse, error)ControllerGetCapabilities(context.Context, *ControllerGetCapabilitiesRequest) (*ControllerGetCapabilitiesResponse, error)CreateSnapshot(context.Context, *CreateSnapshotRequest) (*CreateSnapshotResponse, error)DeleteSnapshot(context.Context, *DeleteSnapshotRequest) (*DeleteSnapshotResponse, error)ListSnapshots(context.Context, *ListSnapshotsRequest) (*ListSnapshotsResponse, error)ControllerExpandVolume(context.Context, *ControllerExpandVolumeRequest) (*ControllerExpandVolumeResponse, error)ControllerGetVolume(context.Context, *ControllerGetVolumeRequest) (*ControllerGetVolumeResponse, error)}

(3)CSI Node Service

在 Mount 阶段 kubelet 会通过 node-driver-registrar 容器调用这三个方法:NodePublishVolumeNodeUnpublishVolumeNodeGetCapabilities 。

// NodeServer is the server API for Node service.type NodeServer interface {NodeStageVolume(context.Context, *NodeStageVolumeRequest) (*NodeStageVolumeResponse, error)NodeUnstageVolume(context.Context, *NodeUnstageVolumeRequest) (*NodeUnstageVolumeResponse, error)NodePublishVolume(context.Context, *NodePublishVolumeRequest) (*NodePublishVolumeResponse, error)NodeUnpublishVolume(context.Context, *NodeUnpublishVolumeRequest) (*NodeUnpublishVolumeResponse, error)NodeGetVolumeStats(context.Context, *NodeGetVolumeStatsRequest) (*NodeGetVolumeStatsResponse, error)NodeExpandVolume(context.Context, *NodeExpandVolumeRequest) (*NodeExpandVolumeResponse, error)// 返回节点支持的功能
NodeGetCapabilities(context.Context, *NodeGetCapabilitiesRequest) (*NodeGetCapabilitiesResponse, error)NodeGetInfo(context.Context, *NodeGetInfoRequest) (*NodeGetInfoResponse, error)}
  • proto 文件源码位置:https://github.com/container-storage-interface/spec/blob/master/csi.proto
  • go文件源码:https://github.com/container-storage-interface/spec/blob/master/lib/go/csi/csi.pb.go

3、编写一个 CSI 插件

3.1 介绍

了解 CSI 插件机制原理及相关概念后,接下来实战一个自己的 CSI 插件。CSI 插件的代码结构比较清晰的,可以参考 csi-driver-host-path 、csi-digitalocean ,正如 csi-driver-host-path 代码结构如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以看到,IdentityServer 、ControllerServer 、NodeServer 三个服务都定义在了 pkg/hostpath 目录下。

为了能够让 Kubernetes 访问到 CSI 的三个 服务,需要定义一个标准的 gRPC Server,把编写好的 gRPC Server 注册给 CSI,然后它就可以响应来自 External Components 的 CSI 请求了。 csi-driver-host-path 项目是写在了 server.go 文件夹里头的:


func (s *nonBlockingGRPCServer) serve(ep string, ids csi.IdentityServer, cs csi.ControllerServer, ns csi.NodeServer) {listener, cleanup, err := endpoint.Listen(ep)...server := grpc.NewServer(opts...)s.server = servers.cleanup = cleanupif ids != nil {csi.RegisterIdentityServer(server, ids)}if cs != nil {csi.RegisterControllerServer(server, cs)}if ns != nil {csi.RegisterNodeServer(server, ns)}...
server.Serve(listener)}
3.2 CSI 插件,需要实现的三个服务
(1)CSI Identity 服务

CSI Identity 服务,主要负责对外暴露这个插件本身的信息


package driver  import (  `context`  "github.com/container-storage-interface/spec/lib/go/csi""github.com/sirupsen/logrus"
)  // GetPluginInfo 返回插件名字和版本
func (d *Driver) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {  logrus.Infof("GetPluginInfo: called with args: %+v", *req)  return &csi.GetPluginInfoResponse{  // K8s 通过 DriverName 这个值来找到在 StorageClass 里声明要使用的 CSI 插件的Name:          d.config.DriverName,VendorVersion: d.config.VendorVersion,  }, nil  
}  // GetPluginCapabilities 返回插件所支持的功能  
func (d *Driver) GetPluginCapabilities(ctx context.Context, req *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {  logrus.Infof("GetPluginCapabilities: called with args: %+v", *req)  caps := []*csi.PluginCapability{  {  Type: &csi.PluginCapability_Service_{  Service: &csi.PluginCapability_Service{  Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,  },  },  },  {  Type: &csi.PluginCapability_Service_{  Service: &csi.PluginCapability_Service{  Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS,  },  },  },  }  return &csi.GetPluginCapabilitiesResponse{Capabilities: caps}, nil  
}  // K8S 调用它来检查这个 CSI 插件是否正常工作(健康检测 )。
func (d *Driver) Probe(ctx context.Context, req *csi.ProbeRequest) (*csi.ProbeResponse, error) {  logrus.Infof("Probe: called with args %+v", req)  return &csi.ProbeResponse{}, nil  
}
(2)CSI Controller 服务

这个服务主要任务是负责 “Provision ”和“Attach ” 阶段。

Provision 阶段对应的接口,是 CreateVolume 和 DeleteVolume,它们的调用者是 External Provisoner。代码如下所示:

package driver  import (  `context`  `github.com/container-storage-interface/spec/lib/go/csi`     `github.com/sirupsen/logrus`
)  var (  // controllerCaps 代表Controller Plugin支持的功能// 这里只实现Volume的创建/删除,附加/卸载  controllerCaps = []csi.ControllerServiceCapability_RPC_Type{  csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,  csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,  }  
)  // CreateVolume 创建  
func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (resp *csi.CreateVolumeResponse, finalErr error) {  logrus.Infof("CreateVolume: called with args %+v", *req)  // 对于nfs来说,这里就是创建一个存储卷,其它类型块存储如Ceph RDB 也类似vol, err := hp.createVolume(volumeID, req.GetName(), capacity, requestedAccessType, ...)glog.V(4).Infof("created volume %s at path %s", vol.VolID, vol.VolPath)vmReq := &csi.Volume{  VolumeId:      "w-123",  CapacityBytes: 10 * (1 << 30),  VolumeContext: req.GetParameters(),  }  return &csi.CreateVolumeResponse{Volume: vmReq}, nil  
}  // DeleteVolume 删除  
func (d *Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {  logrus.Infof("DeleteVolume: called with args %+v", *req)  return &csi.DeleteVolumeResponse{}, nil  
}  

Attach 阶段”对应的接口是 ControllerPublishVolume 和 ControllerUnpublishVolume,它们的调用者是 External Attacher。

Attach 阶段主要的工作是调用 供应商提供存储 API,将前面创建好的存储卷,挂载到指定的虚拟机上。


// ControllerPublishVolume 附加  
func (d *Driver) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {  logrus.Infof("ControllerPublishVolume: called with args %+v", *req)  pvInfo := map[string]string{"DevicePathKey": "/dev/sdb"}  return &csi.ControllerPublishVolumeResponse{PublishContext: pvInfo}, nil  
}  // ControllerUnpublishVolume 卸载  
func (d *Driver) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {  logrus.Infof("ControllerUnpublishVolume: called with args %+v", *req)  return &csi.ControllerUnpublishVolumeResponse{}, nil  
}
(3)CSI Node 服务

CSI Node 服务所对应的是 “Mount 阶段”,而调用 CSI Node 服务来完成 “Mount 阶段”的是 “kubelet 的 VolumeManagerReconciler 控制循环”。

在 “Mount 阶段” 中,它是手续需要格式化这个设备,然后才能把它挂载到 Volume 对应的宿主机目录上。

在 kubelet 的 VolumeManagerReconciler 控制循环中,这两步操作分别叫作 MountDevice 和 SetUp,SetUp 操作则会调用 CSI Node 服务的 NodePublishVolume 接口,而 MountDevice 操作,就是直接调用了 CSI Node 服务里的 NodeStageVolume 接口。

nodeServer go文件中两个重要接口:NodeStageVolume 和 NodePublishVolume

// 格式化 Volume 在宿主机上对应的存储设备,然后挂载到一个临时目录(Staging 目录)上。
func (hp *hostPath) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {  // Check arguments  if len(req.GetVolumeId()) == 0 {  return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")  }  stagingTargetPath := req.GetStagingTargetPath()  if stagingTargetPath == "" {  return nil, status.Error(codes.InvalidArgument, "Target path missing in request")  }  if req.GetVolumeCapability() == nil {  return nil, status.Error(codes.InvalidArgument, "Volume Capability missing in request")  }  // Lock before acting on global state. A production-quality  // driver might use more fine-grained locking.   hp.mutex.Lock()  defer hp.mutex.Unlock()  vol, err := hp.state.GetVolumeByID(req.VolumeId)  if err != nil {  return nil, err  }  if hp.config.EnableAttach && !vol.Attached {  return nil, status.Errorf(codes.Internal, "ControllerPublishVolume must be called on volume '%s' before staging on node",  vol.VolID)  }  if vol.Staged.Has(stagingTargetPath) {  glog.V(4).Infof("Volume %q is already staged at %q, nothing to do.", req.VolumeId, stagingTargetPath)  return &csi.NodeStageVolumeResponse{}, nil  }  if !vol.Staged.Empty() {  return nil, status.Errorf(codes.FailedPrecondition, "volume %q is already staged at %v", req.VolumeId, vol.Staged)  }  vol.Staged.Add(stagingTargetPath)  if err := hp.state.UpdateVolume(vol); err != nil {  return nil, err  }  return &csi.NodeStageVolumeResponse{}, nil  
}

总结

1、以上主要是 分析 nfs 的 CSI 插件为例,大致了解编写 CSI 插件的流程。CSI 开发其实是针对 Kubernetes CSI Sidecar Containers 的 gRPC 开发,根据 CSI 规范实现驱动程序,开发者可以根据需求,完成三大阶段中对应三大 gRPC Server 相应方法即可。需要实现以下接口:

  • IdentityServer:用于验证驱动程序的身份并返回支持的 CSI 版本信息;
  • ControllerServer:用于创建、删除和扩容存储卷;
  • NodeServer:用于挂载、卸载和格式化存储卷;

2、根据 CSI 规范实现 CSI 插件框架

(1)编写 CSI Controller 插件:实现 CSI 控制器插件用于管理存储卷的创建、删除和扩容等操作。

如常见需要实现的接口:

  • CreateVolume:用于处理创建存储卷的请求;
  • DeleteVolume:用于处理删除存储卷的请求;
  • ControllerPublishVolume:用于处理存储卷挂载请求;
  • ControllerUnpublishVolume:用于处理存储卷卸载请求;
  • ValidateVolumeCapabilities:用于验证存储卷的可用性;
  • ExpandVolume:用于扩容存储卷;

(2) 编写 CSI Node 插件:该插件用于管理存储卷的挂载和卸载,常见需要实现的接口:

  • NodePublishVolume:用于将存储卷挂载到节点上;
  • NodeUnpublishVolume:用于将存储卷卸载从节点上;
  • NodeStageVolume:用于将存储卷暂存到节点上,以便后续挂载;
  • NodeUnstageVolume:用于从节点上取消存储卷的暂存;
  • NodeGetInfo:用于获取节点信息;

Reference:

  • 开发自己的Kubernetes CSI存储插件
  • https://github.com/kubernetes-csi/csi-driver-host-path
  • 如何编写 CSI 插件
  • 浅析 CSI 工作原理

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

R语言机器学习论文(三):特征提取

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据一、数据归一化二、离散型分类变量的编码三、筛选特征四、重要特征五、输出结果六、总结系统信息介绍 在数据分析和机器学习项目中,经常需要对数据进行预…

用 NotePad++ 运行 Java 程序

安装包 网盘链接 下载得到的安装包: 安装步骤 双击安装包开始安装. 安装完成: 配置编码 用 NotePad 写 Java 程序时, 需要设置编码. 在 设置, 首选项, 新建 中进行设置, 可以对每一个新建的文件起作用. 之前写的文件不起作用. 在文件名处右键, 可以快速打开 CMD 窗口, 且路…

Leetcode打卡:骑士在棋盘上的概率

执行结果&#xff1a;通过 题目&#xff1a;骑士在棋盘上的概率 在一个 n x n 的国际象棋棋盘上&#xff0c;一个骑士从单元格 (row, column) 开始&#xff0c;并尝试进行 k 次移动。行和列是 从 0 开始 的&#xff0c;所以左上单元格是 (0,0) &#xff0c;右下单元格是 (n - …

[Java]项目入门

这篇简单介绍一些入门的有关项目和行业的知识&#xff0c;并带着实现一个小项目。便于已经编程入门的各位准备进阶到下一个阶段。 先大致地介绍&#xff0c;一个完整的项目(不看客户端、服务端的分类)基本可以划分为三部分&#xff1a; 1.前端。比如你现在看到的CSDN页面就是一…

全连接层与链式求导法则在神经网络中的应用

目录 ​编辑 引言 全连接层的工作原理 前向传播 反向传播 链式求导法则及其在神经网络中的应用 链式求导法则 应用于全连接层 计算梯度 结论 引言 在深度学习领域&#xff0c;全连接层&#xff08;Fully Connected Layer&#xff0c;FC&#xff09;和链式求导法则是…

泷羽Sec-星河飞雪-bp抓APP包的相关配置方法

免责声明 学习视频来自 B 站up主泷羽sec&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下代码、网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 泷羽sec官网&#xff1a;http…

00. Nginx-知识网络

知识目录 语雀知识网络&#xff0c;点击“”-- 点击“”查看知识网络 01. Nginx-基础知识 02. Nginx-虚拟主机 03. Nginx-Web模块 04. Nginx-访问控制 05. Nginx-代理服务 06. Nginx-负载均衡 07. Nginx-动静分离 08. Nginx-平滑升级 09. Nginx-日志切割 10. Nginx-…

【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之后端环境搭建

【Springboot3vue3】从零到一搭建Springboot3vue3前后端分离项目&#xff0c;整合knef4j和mybaits实现基础用户信息管理 后端环境搭建1.1 环境准备1.2 数据库表准备1.3 SpringBoot3项目创建1.4 MySql环境整合&#xff0c;使用druid连接池1.5 整合mybatis-plus1.5.1 引入mybatie…

【大数据技术基础】 课程 第3章 Hadoop的安装和使用 大数据基础编程、实验和案例教程(第2版)

第3章 Hadoop的安装和使用 3.1 Hadoop简介 Hadoop是Apache软件基金会旗下的一个开源分布式计算平台&#xff0c;为用户提供了系统底层细节透明的分布式基础架构。Hadoop是基于Java语言开发的&#xff0c;具有很好的跨平台特性&#xff0c;并且可以部署在廉价的计算机集群中。H…

【Elasticsearch】ES+MySQL实现迷糊搜索

1. 技术选型 使用 Elasticsearch (ES) 结合 MySQL 进行数据存储和查询&#xff0c;而不是直接从 MySQL 中进行查询&#xff0c;主要是为了弥补传统关系型数据库&#xff08;如 MySQL&#xff09;在处理大规模、高并发和复杂搜索查询时的性能瓶颈。具体来说&#xff0c;ES 与 My…

Tomcat 的使用(图文教学)

Tomcat 的使用&#xff08;图文教学&#xff09; 前言一、什么是Tomcat&#xff1f;二、Tomcat 服务器和 Servlet 版本的对应关系三、Tomcat 的使用 1、安装2、目录介绍3、如何启动4、Tomcat 的停止5、如何修改 Tomcat 的端口号6、如何部暑 web 工程到 Tomcat 中 6.1 方式一6.…

Altium Designer学习笔记 31 PCB布线优化_GND处理

基于Altium Designer 23学习版&#xff0c;四层板智能小车PCB 更多AD学习笔记&#xff1a;Altium Designer学习笔记 1-5 工程创建_元件库创建Altium Designer学习笔记 6-10 异性元件库创建_原理图绘制Altium Designer学习笔记 11-15 原理图的封装 编译 检查 _PCB封装库的创建Al…

前端知识1html

VScode一些快捷键 Ctrl/——注释 !——生成html框架元素 *n——生成n个标签 直接书写html的名字回车生成对应的标签 常见标签 span&#xff1a; <span style"color: red;">hello</span> <span>demo</span> span实现&#xff1a; 标题…

Java项目实战II基于微信小程序的私家车位共享系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着城市化进程的加速&…

在google cloud虚拟机上配置anaconda虚拟环境简单教程

下载anaconda安装包 wget https://repo.anaconda.com/archive/Anaconda3-2022.10-Linux-x86_64.sh 安装 bash Anaconda3-2022.10-Linux-x86_64.sh 进入base环境 eval "$(/home/xmxhuihui/anaconda3/bin/conda shell.bash hook)" source ~/.bashrc 安装虚拟环境…

天天 AI-241207:今日热点- Windsurf:在工程能力上进一步进化的Cursor

2AGI.NET | 探索 AI 无限潜力&#xff0c;2AGI 为您带来最前沿资讯。 Windsurf&#xff1a;在工程能力上进一步进化的Cursor 介绍了一个新的AI代码编辑器Windsurf&#xff0c;它被认为是Cursor的进化版&#xff0c;具有更强的工程能力。文章强调了Windsurf在自动化编码和系统…

数据结构---单链表

目录 一、概念 二、分类 1. 单向或者双向 2. 带头或者不带头 3. 循环或者非循环 三、接口实现 1.定义结构 2、申请节点 3、尾插 4、头插 5、尾删 6、头删 7.查找&#xff0c;也可以充当修改 8、在pos之前插入x 9、在pos之后插入x ​编辑 10、删除pos位置 …

CSU课内课程资料【github仓库】

里面是我当时的PPT&#xff0c;作业答案&#xff0c;实验&#xff0c;还有一些笔记啥的&#xff0c;里面有的是他人的笔记和报告&#xff0c;等之后闲下来的话&#xff0c;我会删掉这部分&#xff0c;起码人家的笔记也是有隐私权的。关于实验&#xff0c;大多也是很普通&#x…

深算院崖山发布核心平替战略 加速金融数智化跃迁

2024年11月14日&#xff0c;由深圳计算科学研究院&#xff08;简称&#xff1a;深算院&#xff09;主办、深圳崖山科技有限公司&#xff08;简称&#xff1a;崖山科技&#xff09;和赛迪网承办的“2024国产数据库创新生态大会”在深圳成功举办。会上&#xff0c;崖山数据库重磅…

【Web】2023安洵杯第六届网络安全挑战赛 WP

目录 Whats my name easy_unserialize signal Swagger docs 赛题链接&#xff1a;GitHub - D0g3-Lab/i-SOON_CTF_2023: 2023 第六届安洵杯 题目环境/源码 Whats my name 第一段正则用于匹配以 include 结尾的字符串&#xff0c;并且在 include 之前&#xff0c;可以有任…