目录

摘要:

k8s的api定义:

分析发现:

client-go调用k8s接口删除测试:

测试流程:

一. 先启动sts的两个pod, 使处于running状态

二. 使其中一个pod所在的node处于not ready状态, 此时该pod处于Terming, 且无法自动重新调度到其他node上.

三. 执行client-go代码设置GracePeriodSeconds为0, 执行删除pod. 则pod被重新调度, 恢复running


摘要:

记录调用k8s的api删除pod的协议, 目的在于为了更精确的控制删除pod.

k8s的api定义:

"io.k8s.apimachinery.pkg.apis.meta.v1.DeleteOptions": {
        "description": "DeleteOptions may be provided when deleting an API object.",
        "properties": {
          "apiVersion": {
            "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
            "type": "string"
          },
          "dryRun": {
            "description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
            "items": {
              "default": "",
              "type": "string"
            },
            "type": "array"
          },
          "gracePeriodSeconds": {
            "description": "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.",
            "format": "int64",
            "type": "integer"
          },
          "kind": {
            "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
            "type": "string"
          },
          "orphanDependents": {
            "description": "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.",
            "type": "boolean"
          },
          "preconditions": {
            "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Preconditions",
            "description": "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned."
          },
          "propagationPolicy": {
            "description": "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.",
            "type": "string"
          }
        },
        "type": "object",
        "x-kubernetes-group-version-kind": [
          {
            "group": "",
            "kind": "DeleteOptions",
            "version": "v1"
          },
        ]
      },

以上协议没有强制删除的参数. 继续追踪

从k8s的kubectl的cmd命令着手


func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
	cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	o.WarnClusterScope = enforceNamespace && !o.DeleteAllNamespaces

	if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
		if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
			// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
			o.IgnoreNotFound = true
		}
	}
	if o.DeleteNow {
		if o.GracePeriod != -1 {
			return fmt.Errorf("--now and --grace-period cannot be specified together")
		}
		o.GracePeriod = 1
	}
	if o.GracePeriod == 0 && !o.ForceDeletion {
		// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
		// into --grace-period=1. Users may provide --force to bypass this conversion.
		o.GracePeriod = 1
	}
	if o.ForceDeletion && o.GracePeriod < 0 {
		o.GracePeriod = 0
	}

	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
	if err != nil {
		return err
	}
	dynamicClient, err := f.DynamicClient()
	if err != nil {
		return err
	}
	o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, f.OpenAPIGetter())

	if len(o.Raw) == 0 {
		r := f.NewBuilder().
			Unstructured().
			ContinueOnError().
			NamespaceParam(cmdNamespace).DefaultNamespace().
			FilenameParam(enforceNamespace, &o.FilenameOptions).
			LabelSelectorParam(o.LabelSelector).
			FieldSelectorParam(o.FieldSelector).
			SelectAllParam(o.DeleteAll).
			AllNamespaces(o.DeleteAllNamespaces).
			ResourceTypeOrNameArgs(false, args...).RequireObject(false).
			Flatten().
			Do()
		err = r.Err()
		if err != nil {
			return err
		}
		o.Result = r

		o.Mapper, err = f.ToRESTMapper()
		if err != nil {
			return err
		}

		o.DynamicClient, err = f.DynamicClient()
		if err != nil {
			return err
		}
	}

	return nil
}
type DeleteOptions struct {
	resource.FilenameOptions

	LabelSelector       string
	FieldSelector       string
	DeleteAll           bool
	DeleteAllNamespaces bool
	CascadingStrategy   metav1.DeletionPropagation
	IgnoreNotFound      bool
	DeleteNow           bool
	ForceDeletion       bool
	WaitForDeletion     bool
	Quiet               bool
	WarnClusterScope    bool
	Raw                 string

	GracePeriod int
	Timeout     time.Duration

	DryRunStrategy cmdutil.DryRunStrategy
	DryRunVerifier *resource.DryRunVerifier

	Output string

	DynamicClient dynamic.Interface
	Mapper        meta.RESTMapper
	Result        *resource.Result

	genericclioptions.IOStreams
}


func (f *DeleteFlags) AddFlags(cmd *cobra.Command) {
	f.FileNameFlags.AddFlags(cmd.Flags())
	if f.LabelSelector != nil {
		cmd.Flags().StringVarP(f.LabelSelector, "selector", "l", *f.LabelSelector, "Selector (label query) to filter on.")
	}
	if f.FieldSelector != nil {
		cmd.Flags().StringVarP(f.FieldSelector, "field-selector", "", *f.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
	}
	if f.All != nil {
		cmd.Flags().BoolVar(f.All, "all", *f.All, "Delete all resources, in the namespace of the specified resource types.")
	}
	if f.AllNamespaces != nil {
		cmd.Flags().BoolVarP(f.AllNamespaces, "all-namespaces", "A", *f.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
	}
	if f.Force != nil {
		cmd.Flags().BoolVar(f.Force, "force", *f.Force, "If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
	}
	if f.CascadingStrategy != nil {
		cmd.Flags().StringVar(
			f.CascadingStrategy,
			"cascade",
			*f.CascadingStrategy,
			`Must be "background", "orphan", or "foreground". Selects the deletion cascading strategy for the dependents (e.g. Pods created by a ReplicationController). Defaults to background.`)
		cmd.Flags().Lookup("cascade").NoOptDefVal = "background"
	}
	if f.Now != nil {
		cmd.Flags().BoolVar(f.Now, "now", *f.Now, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).")
	}
	if f.GracePeriod != nil {
		cmd.Flags().IntVar(f.GracePeriod, "grace-period", *f.GracePeriod, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion).")
	}
	if f.Timeout != nil {
		cmd.Flags().DurationVar(f.Timeout, "timeout", *f.Timeout, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
	}
	if f.IgnoreNotFound != nil {
		cmd.Flags().BoolVar(f.IgnoreNotFound, "ignore-not-found", *f.IgnoreNotFound, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.")
	}
	if f.Wait != nil {
		cmd.Flags().BoolVar(f.Wait, "wait", *f.Wait, "If true, wait for resources to be gone before returning. This waits for finalizers.")
	}
	if f.Output != nil {
		cmd.Flags().StringVarP(f.Output, "output", "o", *f.Output, "Output mode. Use \"-o name\" for shorter output (resource/name).")
	}
	if f.Raw != nil {
		cmd.Flags().StringVar(f.Raw, "raw", *f.Raw, "Raw URI to DELETE to the server.  Uses the transport specified by the kubeconfig file.")
	}
}

// DeleteFlags composes common printer flag structs
// used for commands requiring deletion logic.
type DeleteFlags struct {
	FileNameFlags *genericclioptions.FileNameFlags
	LabelSelector *string
	FieldSelector *string

	All               *bool
	AllNamespaces     *bool
	CascadingStrategy *string
	Force             *bool
	GracePeriod       *int
	IgnoreNotFound    *bool
	Now               *bool
	Timeout           *time.Duration
	Wait              *bool
	Output            *string
	Raw               *string
}

分析发现:

所谓的--force强制删除, 是将 GracePeriod 设置为0.

那么在协议中, 将GracePeriodSeconds 设置为0, 将达到同样的效果.

client-go调用k8s接口删除测试:

https://github.com/kubernetes/client-go

编译脚本:

#!/bin/bash

rm ./app -rf

go env -w GOPROXY=https://goproxy.cn,direct
go mod download
go mod vendor

go build -o app .

测试删除pod的代码:

/*
Copyright 2016 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Note: the example only works with the code within the same release/branch.
package main

import (
	"context"
	"flag"
	"fmt"
	"path/filepath"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	//
	// Uncomment to load all auth plugins
	// _ "k8s.io/client-go/plugin/pkg/client/auth"
	//
	// Or uncomment to load specific auth plugins
	// _ "k8s.io/client-go/plugin/pkg/client/auth/azure"
	// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
	// _ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
	// _ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
)

func main() {
	var kubeconfig *string
	if home := homedir.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())
	}

	pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

	// for _, pod := range pods.Items {

	// 	// dbClusterStr, _ := json.Marshal(pod.Spec)
	// 	// fmt.Println(pod.Spec.Hostname, pod.Spec.Subdomain)
	// 	// fmt.Println("")
	// }

	// Examples for error handling:
	// - Use helper functions like e.g. errors.IsNotFound()
	// - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
	namespace := "default"

	{
		pod := "mypod-0"

		fPod, err := clientset.CoreV1().Pods(namespace).Get(context.TODO(), pod, metav1.GetOptions{})
		if errors.IsNotFound(err) {
			fmt.Printf("Pod %s in namespace %s not found\n", pod, namespace)
		} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
			fmt.Printf("Error getting pod %s in namespace %s: %v\n",
				pod, namespace, statusError.ErrStatus.Message)
		} else if err != nil {
			panic(err.Error())
		} else {
			fmt.Printf("Found pod %s in namespace %s\n", pod, namespace)

			fmt.Println(fPod.Spec.NodeName)
			// fmt.Println(fPod.Spec.ServiceAccountName)
			fmt.Println(fPod.Spec.Hostname)
			fmt.Println(fPod.Status.HostIP)
			fmt.Println(fPod.Status.PodIP)
			// fmt.Println(fPod.Status.NominatedNodeName)
			// fmt.Println(fPod.Status.Message)
			// fmt.Println(fPod.Status.Reason)
			fmt.Println(fPod.Status.Phase)
		}
	}

	{
		pod := "mypod-1"

		ops := metav1.DeleteOptions{}
		var gs int64 = 0
		ops.GracePeriodSeconds = &gs
		// ops.PropagationPolicy = clientset.CoreV1().DeletePropagationForeground

		err := clientset.CoreV1().Pods(namespace).Delete(context.TODO(), pod, ops)
		if nil != err {
			fmt.Println("Delete pod fail", err)
			return
		} else {
			fmt.Println("Delete pod ok")
		}
	}

}

测试流程:

一. 先启动sts的两个pod, 使处于running状态

NAME       STATUS   ROLES    AGE   VERSION
node-201   Ready    master   21h   v1.18.20
node-202   Ready    <none>   21h   v1.18.20
node-203   Ready    <none>   21h   v1.18.20


mypod-0   1/1     Running   0          90s   10.168.2.34   node-203   <none>           <none>
mypod-1   1/1     Running   0          89s   10.168.1.31   node-202   <none>           <none>

二. 使其中一个pod所在的node处于not ready状态, 此时该pod处于Terming, 且无法自动重新调度到其他node上.

NAME       STATUS     ROLES    AGE   VERSION
node-201   Ready      master   21h   v1.18.20
node-202   NotReady   <none>   21h   v1.18.20
node-203   Ready      <none>   21h   v1.18.20


mypod-0   1/1     Running       0          7m3s   10.168.2.34   node-203   <none>           <none>
mypod-1   1/1     Terminating   0          7m2s   10.168.1.31   node-202   <none>           <none>

三. 执行client-go代码设置GracePeriodSeconds为0, 执行删除pod. 则pod被重新调度, 恢复running

[root@node-201 out-of-cluster-client-configuration]# ./app 
There are 15 pods in the cluster
Found pod mypod-0 in namespace default
node-203
mypod-0
192.168.58.203
10.168.2.34
Running
Delete pod ok

NAME       STATUS     ROLES    AGE   VERSION
node-201   Ready      master   21h   v1.18.20
node-202   NotReady   <none>   21h   v1.18.20
node-203   Ready      <none>   21h   v1.18.20


mypod-0   1/1     Running   0          8m7s   10.168.2.34   node-203   <none>           <none>
mypod-1   1/1     Running   0          3s     10.168.2.36   node-203   <none>           <none>

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐