k8s源码解析 - 如何使用yaml创建k8s的资源

2023-10-27

如何初始化k8s中的client

1、kubernetes.Clientset 参考链接

  • 集群内访问创建k8s-client - 直接获取集群内的config, 通过config创建clientSet。
// creates the in-cluster config
	config, err := rest.InClusterConfig()
	if err != nil {
		panic(err.Error())
	}
	// creates the clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
// InClusterConfig returns a config object which uses the service account
// kubernetes gives to pods. It's intended for clients that expect to be
// running inside a pod running on kubernetes. It will return ErrNotInCluster
// if called from a process not running in a kubernetes environment.
func InClusterConfig() (*Config, error) {
	const (
		tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
		rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
	)
	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
	if len(host) == 0 || len(port) == 0 {
		return nil, ErrNotInCluster
	}

	token, err := ioutil.ReadFile(tokenFile)
	if err != nil {
		return nil, err
	}

	tlsClientConfig := TLSClientConfig{}

	if _, err := certutil.NewPool(rootCAFile); err != nil {
		klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
	} else {
		tlsClientConfig.CAFile = rootCAFile
	}

	return &Config{
		// TODO: switch to using cluster DNS.
		Host:            "https://" + net.JoinHostPort(host, port),
		TLSClientConfig: tlsClientConfig,
		BearerToken:     string(token),
		BearerTokenFile: tokenFile,
	}, nil
}
  • 集群外访问创建k8s-client,由于client是在集群外,与集群内的client相比要多考虑如何授权集群外的k8s-api访问的逻辑。我们可以使用包含集群上下文信息的kubeconfig文件来初始化客户端。 kubectl命令也使用kubeconfig文件对集群进行身份验证。
	var kubeconfig *string
	if home := homeDir(); home != "" {
		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
	} else {
		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	}
	flag.Parse()

	// use the current context in kubeconfig
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	// create the clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
  • 使用kubeconfig文件来组织关于集群,用户,名称空间和身份验证机制的信息。 kubectl命令行工具使用kubeconfig文件来查找选择群集并与群集的API服务器进行通信所需的信息。默认情况下 kubectl使用的配置文件名称是在$HOME/.kube目录下 config文件,可以通过设置环境变量KUBECONFIG或者–kubeconfig指定其他的配置文件

2、RESTClient

k8s-clientSet是各个k8s资源的客户端集合,先看下k8s-clientSet的结构体,结构体中每个成员实质上为k8s每个资源对应的restful-client。

// Clientset contains the clients for groups. Each group has exactly one
// version included in a Clientset.
type Clientset struct {
	*discovery.DiscoveryClient
	admissionregistrationV1      *admissionregistrationv1.AdmissionregistrationV1Client
	admissionregistrationV1beta1 *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
	appsV1                       *appsv1.AppsV1Client
	appsV1beta1                  *appsv1beta1.AppsV1beta1Client
	appsV1beta2                  *appsv1beta2.AppsV1beta2Client
	auditregistrationV1alpha1    *auditregistrationv1alpha1.AuditregistrationV1alpha1Client
	authenticationV1             *authenticationv1.AuthenticationV1Client
	authenticationV1beta1        *authenticationv1beta1.AuthenticationV1beta1Client
	authorizationV1              *authorizationv1.AuthorizationV1Client
	authorizationV1beta1         *authorizationv1beta1.AuthorizationV1beta1Client
	autoscalingV1                *autoscalingv1.AutoscalingV1Client
	autoscalingV2beta1           *autoscalingv2beta1.AutoscalingV2beta1Client
	autoscalingV2beta2           *autoscalingv2beta2.AutoscalingV2beta2Client
	batchV1                      *batchv1.BatchV1Client
	batchV1beta1                 *batchv1beta1.BatchV1beta1Client
	batchV2alpha1                *batchv2alpha1.BatchV2alpha1Client
	certificatesV1beta1          *certificatesv1beta1.CertificatesV1beta1Client
	coordinationV1beta1          *coordinationv1beta1.CoordinationV1beta1Client
	coordinationV1               *coordinationv1.CoordinationV1Client
	coreV1                       *corev1.CoreV1Client
	discoveryV1alpha1            *discoveryv1alpha1.DiscoveryV1alpha1Client
	discoveryV1beta1             *discoveryv1beta1.DiscoveryV1beta1Client
	eventsV1beta1                *eventsv1beta1.EventsV1beta1Client
	extensionsV1beta1            *extensionsv1beta1.ExtensionsV1beta1Client
	flowcontrolV1alpha1          *flowcontrolv1alpha1.FlowcontrolV1alpha1Client
	networkingV1                 *networkingv1.NetworkingV1Client
	networkingV1beta1            *networkingv1beta1.NetworkingV1beta1Client
	nodeV1alpha1                 *nodev1alpha1.NodeV1alpha1Client
	nodeV1beta1                  *nodev1beta1.NodeV1beta1Client
	policyV1beta1                *policyv1beta1.PolicyV1beta1Client
	rbacV1                       *rbacv1.RbacV1Client
	rbacV1beta1                  *rbacv1beta1.RbacV1beta1Client
	rbacV1alpha1                 *rbacv1alpha1.RbacV1alpha1Client
	schedulingV1alpha1           *schedulingv1alpha1.SchedulingV1alpha1Client
	schedulingV1beta1            *schedulingv1beta1.SchedulingV1beta1Client
	schedulingV1                 *schedulingv1.SchedulingV1Client
	settingsV1alpha1             *settingsv1alpha1.SettingsV1alpha1Client
	storageV1beta1               *storagev1beta1.StorageV1beta1Client
	storageV1                    *storagev1.StorageV1Client
	storageV1alpha1              *storagev1alpha1.StorageV1alpha1Client
}

以CoreV1资源为例,获取k8s clientSet 结构体中对应资源版本的restclient:

restClient:= clientSet.CoreV1().RESTClient()

//1、CoreV1Client is used to interact with features provided by the  group. 
// CoreV1Client结构体是clientSet的成员字段的类型之一。

type CoreV1Client struct {
	restClient rest.Interface
}

// 2、CoreV1 retrieves the CoreV1Client
func (c *Clientset) CoreV1() corev1.CoreV1Interface {
	return c.coreV1  
//coreV1即CoreV1Client结构体,coreV1为clientSet的成员字段之一,
//coreV1为 *corev1.CoreV1Client
}

type CoreV1Interface interface {
	RESTClient() rest.Interface
	....
}

// 3、CoreV1Client结构体为CoreV1Interface实现了RESTClient()方法。 
//returns a RESTClient that is used to communicate
// with API server by this client implementation.
func (c *CoreV1Client) RESTClient() rest.Interface {
	if c == nil {
		return nil
	}
	return c.restClient
}


// 4、 rest.Interface: Interface captures the set of operations for generically interacting with Kubernetes REST apis.
type Interface interface {
	GetRateLimiter() flowcontrol.RateLimiter
	Verb(verb string) *Request
	Post() *Request
	Put() *Request
	Patch(pt types.PatchType) *Request
	Get() *Request
	Delete() *Request
	APIVersion() schema.GroupVersion
}

//5、Most consumers should use client.New() to get a Kubernetes API client. 
// rest.RESTClient结构体实现了rest.Interface接口。
type RESTClient struct {
	// base is the root URL for all invocations of the client
	base *url.URL
	// versionedAPIPath is a path segment connecting the base URL to the resource root
	versionedAPIPath string

	// content describes how a RESTClient encodes and decodes responses.
	content ClientContentConfig

	// creates BackoffManager that is passed to requests.
	createBackoffMgr func() BackoffManager

	// rateLimiter is shared among all requests created by this client unless specifically
	// overridden.
	rateLimiter flowcontrol.RateLimiter

	// Set specific behavior of the client.  If not set http.DefaultClient will be used.
	Client *http.Client
}

// 6、CoreV1中如何创建RESTClient结构体NewForConfig creates a new CoreV1Client for the given config.
func NewForConfig(c *rest.Config) (*CoreV1Client, error) {
	config := *c
	if err := setConfigDefaults(&config); err != nil {
		return nil, err
	}
	client, err := rest.RESTClientFor(&config)
	if err != nil {
		return nil, err
	}
	return &CoreV1Client{client}, nil
}
// 

client-go中client如何创建的资源?- restful

k8s的client-go提供了对资源的增删改查的接口, 我们可以调用相关资源结构体的api,进行增删改查的操作。如下以deployment示例:

1、用户获取k8s client的 deployment的调用接口

func k8sDeployment() appv1.DeploymentInterface {
   return k8sClientset().AppsV1().Deployments(k8sNamespace)
}

2、相应的资源包已经实现了获取k8s相关资源的调用接口

// deployments implements DeploymentInterface
type deployments struct {
	client rest.Interface
	ns     string
}

// newDeployments returns a Deployments
func newDeployments(c *AppsV1Client, namespace string) *deployments {
	return &deployments{
		client: c.RESTClient(),
		ns:     namespace,
	}
}
//AppsV1包实现了获取deployment 资源的接口
func (c *AppsV1Client) Deployments(namespace string) DeploymentInterface {
	return newDeployments(c, namespace)
}

3、client-go 中 deployments结构体已经实现的调用方法如下

// DeploymentsGetter has a method to return a DeploymentInterface.
// A group's client should implement this interface.
type DeploymentsGetter interface {
	Deployments(namespace string) DeploymentInterface
}
// DeploymentInterface has methods to work with Deployment resources.
type DeploymentInterface interface {
	Create(*v1.Deployment) (*v1.Deployment, error)
	Update(*v1.Deployment) (*v1.Deployment, error)
	UpdateStatus(*v1.Deployment) (*v1.Deployment, error)
	Delete(name string, options *metav1.DeleteOptions) error
	DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
	Get(name string, options metav1.GetOptions) (*v1.Deployment, error)
	List(opts metav1.ListOptions) (*v1.DeploymentList, error)
	Watch(opts metav1.ListOptions) (watch.Interface, error)
	Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Deployment, err error)
	GetScale(deploymentName string, options metav1.GetOptions) (*autoscalingv1.Scale, error)
	UpdateScale(deploymentName string, scale *autoscalingv1.Scale) (*autoscalingv1.Scale, error)

	DeploymentExpansion
}

4、client-go接口的底层实现是给k8s集群的apiserver发送http-restful请求。post方法的body的入参是资源obj。

}
// Create takes the representation of a deployment and creates it. 
// Returns the server's representation of the deployment, and an error, if there is any.
func (c *deployments) Create(deployment *v1.Deployment) (result *v1.Deployment, err error) {
	result = &v1.Deployment{}
	err = c.client.Post().
		Namespace(c.ns).          //资源创建的ns
		Resource("deployments").  //资源创建的kind
		Body(deployment).         //资源创建的spec
		Do().
		Into(result)
	return
}

5、对资源的不同请求restful的method也不相同 , 常见的请求如下, 参考链接

(1)create a Deployment - HTTP Request: 用户要确定创建资源的ns和kind,在url最后两个字段体现。
POST /apis/apps/v1/namespaces/{namespace}/deployments

(2)partially update the specified Deployment - HTTP Request (部分更新)
PATCH /apis/apps/v1/namespaces/{namespace}/deployments/{name}.

(3)replace the specified Deployment - HTTP Request)(完整替换当前资源,yaml的规格要求完备)
PUT /apis/apps/v1/namespaces/{namespace}/deployments/{name}

(4)delete a Deployment - HTTP Request (用户要给出资源ns\kind\name)
DELETE /apis/apps/v1/namespaces/{namespace}/deployments/{name}

(5)read the specified Deployment - HTTP Request (用户要给出资源ns\kind\name)
GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}

(6)list or watch objects of kind Deployment -HTTP Request (用户要给出资源ns\kind)
GET /apis/apps/v1/namespaces/{namespace}/deployments


k8s client-go中用到的interface和struct

1、“k8s.io/apimachinery/pkg/runtime/schema” 包中k8s schema.GroupVersionKind结构体存储group、version、kind字段的值。

// GroupVersionKind unambiguously identifies a kind.  It doesn't anonymously include GroupVersion
// to avoid automatic coercion.  It doesn't use a GroupVersion to avoid custom marshalling
type GroupVersionKind struct {
	Group   string
	Version string
	Kind    string
}

2、"k8s.io/apimachinery/pkg/runtime"包中runtime.Object接口,k8s的资源类型都应该实现了该接口。在Scheme上注册的所有API类型都必须支持object对象接口。 由于预计方案中的对象将被序列化到wire,因此对象必须提供接口允许序列化程序设置对象表示的种类,版本和组。 在不希望序列化对象的情况下,对象可以选择返回无操作ObjectKindAccessor。

// Object interface must be supported by all API types registered with Scheme. Since objects in a scheme are
// expected to be serialized to the wire, the interface an Object must provide to the Scheme allows
// serializers to set the kind, version, and group the object is represented as. An Object may choose
// to return a no-op ObjectKindAccessor in cases where it is not expected to be serialized.
type Object interface {
	GetObjectKind() schema.ObjectKind
	DeepCopyObject() Object
}

3、从Scheme序列化的所有对象都对其类型信息进行编码。 序列化使用此接口,将Scheme中的类型信息设置到对象的序列化版本上。 对于无法序列化或具有独特要求的对象,此接口可能是无操作的。

// All objects that are serialized from a Scheme encode their type information. This interface is used
// by serialization to set type information from the Scheme onto the serialized version of an object.
// For objects that cannot be serialized or have unique requirements, this interface may be a no-op.
type ObjectKind interface {
	// SetGroupVersionKind sets or clears the intended serialized kind of an object. Passing kind nil
	// should clear the current setting.
	SetGroupVersionKind(kind GroupVersionKind)
	// GroupVersionKind returns the stored group, version, and kind of an object, or nil if the object does
	// not expose or provide these fields.
	GroupVersionKind() GroupVersionKind
}

4、runtime.RawExtension结构体,存储资源object的结构体和object的json字节流数据。

type RawExtension struct {
	// Raw is the underlying serialization of this object.
	//
	// TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data.
	Raw []byte `json:"-" protobuf:"bytes,1,opt,name=raw"`
	// Object can hold a representation of this extension - useful for working with versioned
	// structs.
	Object Object `json:"-"`
}

+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 指请在生成DeepCopy时,实现Kubernetes提供的runtime.Object接口

5、“k8s.io/apimachinery/pkg/apis/meta/v1/unstructured”unstructuredJSONScheme 是k8s-client中重要的结构体。unstructuredJSONScheme结构体的decode方法反序列化object的json字节流数据为相应的runtime.Object,并返回相应的GroupVersionKind结构体。

// Codec is a Serializer that deals with the details of versioning objects. It offers the same
// interface as Serializer, so this is a marker to consumers that care about the version of the objects
// they receive.
type Codec Serializer

// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
// type, which can be used for generic access to objects without a predefined scheme.
// TODO: move into serializer/json.
var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{}

type unstructuredJSONScheme struct{}

func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
	var err error
	if obj != nil {
		err = s.decodeInto(data, obj)
	} else {
		obj, err = s.decode(data)
	}

	if err != nil {
		return nil, nil, err
	}

	gvk := obj.GetObjectKind().GroupVersionKind()
	if len(gvk.Kind) == 0 {
		return nil, &gvk, runtime.NewMissingKindErr(string(data))
	}

	return obj, &gvk, nil
}

6、“k8s.io/apimachinery/pkg/util/yaml”, k8syaml文件解析包,一般使用decoder := yaml.NewYAMLOrJSONDecoder(r, 4096),来解析原始yaml字节流请求为json字节流的形式。

// NewYAMLToJSONDecoder decodes YAML documents from the provided
// stream in chunks by converting each document (as defined by
// the YAML spec) into its own chunk, converting it to JSON via
// yaml.YAMLToJSON, and then passing it to json.Decoder.
func NewYAMLToJSONDecoder(r io.Reader) *YAMLToJSONDecoder {
	reader := bufio.NewReader(r)
	return &YAMLToJSONDecoder{
		reader: NewYAMLReader(reader),
	}
}

// Decode reads a YAML document as JSON from the stream or returns
// an error. The decoding rules match json.Unmarshal, not
// yaml.Unmarshal.
func (d *YAMLToJSONDecoder) Decode(into interface{}) error {
	bytes, err := d.reader.Read()
	if err != nil && err != io.EOF {
		return err
	}

	if len(bytes) != 0 {
		err := yaml.Unmarshal(bytes, into)
		if err != nil {
			return YAMLSyntaxError{err}
		}
	}
	return err
}

如何解析yaml创建资源

1、把传入的yaml字节流请求进行解析,一个yaml字节流可能包含多个需要创建更新的资源object, 把yaml字节流中每个资源放到相应runtime.RawExtension{}中进行存储。

func (h *handler) decode(r io.Reader) error {
	decoder := yaml.NewYAMLOrJSONDecoder(r, 4096)
	for {
//提供object序列化后的存储
		ext := runtime.RawExtension{}
//每次序列化一个object到ext中,把接收的yaml文件流转为json流到ext中
		if err := decoder.Decode(&ext); err != nil {
			if err == io.EOF {
				return nil
			}
			return fmt.Errorf("error parsing: %v", err)
		}
		ext.Raw = bytes.TrimSpace(ext.Raw)
		if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
			continue
		}
//关键步骤反序列化json数据为相应的runtime.Object,并返回相应的gkv结构
		obj, gkv, err := unstructured.UnstructuredJSONScheme.Decode(ext.Raw, nil, nil)
		if err != nil {
			return err
		}
		ext.Object = obj
		h.groupVersionKinds = append(h.groupVersionKinds, gkv)
		h.exts = append(h.exts, &ext)
	}
}

2、调用rest-client创建解析后的yaml资源

func (h *handler) apply(ctx context.Context) *metav1.Status {
	type applyObject struct {
		obj             runtime.Object
		isCreateRequest bool
		restClient      clientrest.Interface
		namespace       string
		kind            string
		name            string
	}

	applyObjects := make([]*applyObject, len(h.exts))

	for idx, ext := range h.exts {
		obj := ext.Object
		gvk := h.groupVersionKinds[idx]
		restClient := util.RESTClientFor(h.client, gvk.Group, gvk.Version)

		namespace, err := h.metaAccessor.Namespace(obj)
		if err != nil {
			return errorInternal
		}

		name, err := h.metaAccessor.Name(obj)
		if err != nil {
			return errorInternal
		}

		genName, err := h.metaAccessor.GenerateName(obj)
		if err != nil {
			return errorInternal
		}

		if len(name) == 0 && len(genName) == 0 {
			return errorBadName
		}

		if len(name) != 0 {
			result := restClient.Get().
				Context(ctx).
				NamespaceIfScoped(parseNamespaceIfScoped(namespace, gvk.Kind)).
				Resource(util.ResourceFromKind(gvk.Kind)).
				Name(name).
				Do()
			err := result.Error()
			if err != nil && !errors.IsNotFound(err) {
				if statusError, ok := err.(*errors.StatusError); ok {
					status := statusError.Status()
					return &status
				}
				return unknownError(err)
			}
			if err == nil {
				if h.notUpdate {
					return &metav1.Status{
						TypeMeta: metav1.TypeMeta{
							Kind:       "Status",
							APIVersion: "v1",
						},
						Status: metav1.StatusFailure,
						Code:   http.StatusConflict,
						Reason: metav1.StatusReasonAlreadyExists,
						Details: &metav1.StatusDetails{
							Name:  name,
							Group: gvk.Group,
							Kind:  gvk.Kind,
						},
						Message: fmt.Sprintf("%s \"%s\" already exists", gvk.Kind, name),
					}
				}
				returnedObj, err := result.Get()
				if err != nil {
					return errorInternal
				}
				resourceVersion, err := h.metaAccessor.ResourceVersion(obj)
				if err != nil {
					return errorInternal
				}
				if resourceVersion != "" {
					return errorHasResourceVersion
				}
				savedResourceVersion, err := h.metaAccessor.ResourceVersion(returnedObj)
				if err != nil {
					return errorInternal
				}
				if err := h.metaAccessor.SetResourceVersion(obj, savedResourceVersion); err != nil {
					return errorInternal
				}
				applyObjects[idx] = &applyObject{
					obj:             obj,
					isCreateRequest: false,
					restClient:      restClient,
					namespace:       namespace,
					kind:            gvk.Kind,
					name:            name,
				}
				continue
			}
		}
		// create
		applyObjects[idx] = &applyObject{
			obj:             obj,
			isCreateRequest: true,
			restClient:      restClient,
			namespace:       namespace,
			kind:            gvk.Kind,
			name:            name,
		}
	}

	var messages []string
	for _, applyObj := range applyObjects {
		if applyObj.isCreateRequest {
			// create
			result := applyObj.restClient.Post().
				Context(ctx).
				NamespaceIfScoped(parseNamespaceIfScoped(applyObj.namespace, applyObj.kind)).
				Resource(util.ResourceFromKind(applyObj.kind)).
				Body(applyObj.obj).
				Do()
			log.Debugf("Apply cluster bucket create call: %v", applyObj)
			err := result.Error()
			if err != nil {
				if statusError, ok := err.(*errors.StatusError); ok {
					status := statusError.Status()
					return &status
				}
				return unknownError(err)
			}
			if len(applyObj.name) != 0 {
				messages = append(messages, fmt.Sprintf("%s %s created", applyObj.kind, applyObj.name))
			} else {
				messages = append(messages, fmt.Sprintf("%s generated", applyObj.kind))
			}
		} else {
			// update
			result := applyObj.restClient.Put().
				Context(ctx).
				NamespaceIfScoped(parseNamespaceIfScoped(applyObj.namespace, applyObj.kind)).
				Resource(util.ResourceFromKind(applyObj.kind)).
				Name(applyObj.name).
				Body(applyObj.obj).
				Do()
			log.Debugf("Apply cluster bucket update call: %v", applyObj)
			err := result.Error()
			if err != nil {
				if statusError, ok := err.(*errors.StatusError); ok {
					status := statusError.Status()
					return &status
				}
				return unknownError(err)
			}
			messages = append(messages, fmt.Sprintf("%s %s configured", applyObj.kind, applyObj.name))
		}
	}

	return &metav1.Status{
		Status:  metav1.StatusSuccess,
		Code:    http.StatusOK,
		Message: strings.Join(messages, "\n"),
	}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

k8s源码解析 - 如何使用yaml创建k8s的资源 的相关文章

随机推荐

  • android 动态数据抓取,mitmproxy抓取Keep热门动态-安卓APP抓包爬虫案例

    使用mitmproxy来抓取Keep首页热门动态 鳄鱼君也是费了一天的时间去测试 在这里需要提醒大家抓包不要使用Android系统超过7 0的手机 Android系统越高手机越安全 而我们的抓包很显然是不允许的 这就是为什么你在配置了手机证
  • ajax与javascript,jquery, jquery UI

    ajax就是使用javascript语言实现的一种与服务器异步通信的方式 其核心是xmlhttprequest 像谷歌地图这种应用 如果点击某个点 需要重新刷新页面的话 用户体验很不好 这样的场所使用ajax就非常的方便 jQuery是一个
  • css媒体查询改变上边距,html - CSS宽度和边距不会与媒体查询相加

    我正在建立一个博客布局 目标是以窗口为中心 以最大宽度和最小边距为窗口 以内容为中心 我使用max width和媒体查询的组合来设置一定阈值的边距 我试着把max width和fixedmargin left和margin right放在一
  • go get & go mod找不到包问题解决

    问题一 今天在云虚机中遇到了go get执行报错的问题 报错内容如下 试了ping百度正常 说明不是DNS的问题 执行go mod tidy同样报错 最后找到问题 将GOPROXY由https proxy golang org改为了http
  • SpringBoot:自动装配提速设计

    名词约定 配置类 指使用了 Configuration Component ComponentScan Import ImportResource Bean的类 SpringBoot的设计思想就是通过一个配置类导入多个项目范围内适用的配置类
  • 《零基础入门学习Python》第087讲:Pygame:播放声音和音效

    这节课我们来谈谈 Pygame 中的 播放声音和音效 因为几乎没有任何游戏是一声不吭的 多重的感官体验更能刺激玩家的神经 没有声音的游戏就好比 不蘸番茄的薯条 尽管如此 Pygame 对于声音的处理并不是太理想 我说的是如果你想用 Pyga
  • 线性代数学习笔记——行列式的性质及拉普拉斯定理——11. 拉普拉斯定理

    这节如果不看教学视频而只看PPT的话 很难理解 这充分说明了老师的重要性 1 拉普拉斯 Laplace定理 2 基本结论 三角 对角分块矩阵行列式的计算 3 拉普拉斯定理的应用示例 求行列式 4 分块矩阵的逆的求解
  • ansible批量添加用户账户密码

    一 批量添加root用户以及密码 首先批量添加root 账号密码 不用ssh copy id root ip地址 因为如果ip比较多的情况下 ssh脚本添加会特别的麻烦 步骤1 首先要配置ansible清单 远程主机的密码这里为 12345
  • Idea上传已有项目到git

    开发经常遇到的问题是开发初期没有建立GIT仓库 开发一段时间后 需要将已有代码上传到Git 怎么将已有项目与新建的Git仓库相关联呢 借助Idea可以轻松实现 1 首先使用Git命令行 git clone XXXXX git 将项目下载 2
  • JAVA控制台输出格式

    public class PrintFormat public static void main String args Console print format System out printf format args format为指
  • Python键鼠操作自动化库PyAutoGUI简介

    PyAutoGUI是一个Python语言的键鼠自动化库 简单来说和按键精灵的功能一样 但是因为是Python的类库 所以可以使用Python代码配合一些其他类库完成更加强大的功能 下面让我为大家介绍一下吧 安装 从pip安装即可 pip i
  • 【threejs 】添加标签和射线

    three 添加标签 应用 上一篇文章我们说了世界坐标和屏幕坐标的准换那么有什么应用呢 应用 可以实现该效果 鼠标移动该模块的时候展示该模块的标签 或者可以常显 射线就是当鼠标移动到该模块该模块变化颜色 创建一个标签js文件作为封装的方法
  • 深入理解文字高度和行高的设置

    font size设置的是什么 line height设置的是什么 各种行高是怎么计算出来的 你真的知道吗 1 从font size讲起 说文字高度 当你按住鼠标左键选中一段文字的时候 这段文字背后会有一个颜色变化的区域 这个区域可以近似的
  • 零基础开发WIFI设备(esp8266)

    目录 前言 一 本例程实现功能 二 Core提供的TCP功能介绍 三 接线图 四 材料清单 五 完整代码 通过IP地址和服务器建立连接 代码运行结果 前言 shineblink core 开发板 简称Core 的库函数支持WIFI功能 所以
  • 将websocket通信端口代理到TCP通信端口的方法记录

    websocketproxy代理服务基于go语言实现 功能描述 Proxy of gateway Websockt transfer TCP protocol Websocket gt TCP TCP gt Websocket 即 实现将w
  • 禅道项目管理系统RCE漏洞复现+利用

    1 漏洞概述 禅道研发项目管理软件是国产的开源项目管理软件 专注研发项目管理 内置需求管理 任务管理 bug管理 缺陷管理 用例管理 计划发布等功能 实现了软件的完整生命周期管理 2023年1月6日 互联网披露其旧版本中存在权限绕过与命令执
  • 编写一个方法,将字符串中的空格全部替换为“%20”

    请编写一个方法 将字符串中的空格全部替换为 20 假定该字符串有足够的空间存放新增的字符 并且知道字符串的真实长度 小于等于1000 同时保证字符串由大小写的英文字母组成 给定一个string iniString 为原始的串 以及串的长度
  • 杂凑密码——摘要算法简介

    概念 密码杂凑函数 Cryptographic hash function 又称为密码散列函数 加密散列函数 散列函数的一种 杂凑函数是一种单向函数 要由散列函数输出的结果 回推输入的资料是什么 是非常困难的 散列函数的输出结果 被称为讯息
  • Kotlin inline 关键字

    文章目录 前言 一 inline 是什么 二 未使用 2 使用后 总结 前言 最近又到了面试的季节 今天在面试中被问到了kotlin inline 关键字 感觉回答的不是很好 今天研究一下它 一 inline 是什么 inline 是kot
  • k8s源码解析 - 如何使用yaml创建k8s的资源

    如何初始化k8s中的client 1 kubernetes Clientset 参考链接 集群内访问创建k8s client 直接获取集群内的config 通过config创建clientSet creates the in cluster