这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
$ tree client-go-tutorials
client-go-tutorials
├── action
│ ├── action.go
│ ├── conflict.go
│ └── list_pod.go
├── client-go-tutorials
├── go.mod
├── go.sum
└── main.go
const (
// deployment的名称
DP_NAME string = "demo-deployment"
// 用于更新的标签的名字
LABEL_CUSTOMIZE string = "biz-version"
)
func int32Ptr(i int32) *int32 { return &i }
// 创建deployment
func create(clientset *kubernetes.Clientset) error {
deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: DP_NAME,
Labels: map[string]string{LABEL_CUSTOMIZE: "101"},
},
Spec: appsv1.DeploymentSpec{
Replicas: int32Ptr(1),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "demo",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "demo",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "web",
Image: "nginx:1.12",
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: 80,
},
},
},
},
},
},
},
}
// Create Deployment
fmt.Println("Creating deployment...")
result, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
if err != nil {
return err
}
fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())
return nil
}
// 按照名称删除
func delete(clientset *kubernetes.Clientset, name string) error {
deletePolicy := metav1.DeletePropagationBackground
err := clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Delete(context.TODO(), name, metav1.DeleteOptions{PropagationPolicy: &deletePolicy})
if err != nil {
return err
}
return nil
}
// 按照名称查找deployment
func get(clientset *kubernetes.Clientset, name string) (*v1.Deployment, error) {
deployment, err := clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return deployment, nil
}
// 查询指定名称的deployment对象,得到其名为biz-version的label,加一后保存
func updateByGetAndUpdate(clientset *kubernetes.Clientset, name string) error {
deployment, err := clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return err
}
// 取出当前值
currentVal, ok := deployment.Labels[LABEL_CUSTOMIZE]
if !ok {
return errors.New("未取得自定义标签")
}
// 将字符串类型转为int型
val, err := strconv.Atoi(currentVal)
if err != nil {
fmt.Println("取得了无效的标签,重新赋初值")
currentVal = "101"
}
// 将int型的label加一,再转为字符串
deployment.Labels[LABEL_CUSTOMIZE] = strconv.Itoa(val + 1)
_, err = clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Update(context.TODO(), deployment, metav1.UpdateOptions{})
return err
}
type Confilct struct{}
func (conflict Confilct) DoAction(clientset *kubernetes.Clientset) error {
fmt.Println("开始创建deployment")
// 开始创建deployment
err := create(clientset)
if err != nil {
return err
}
// 如果不延时,就会导致下面的更新过早,会报错
<-time.NewTimer(1 * time.Second).C
// 一旦创建成功,就一定到删除再返回
defer delete(clientset, DP_NAME)
testNum := 5
waitGroup := sync.WaitGroup{}
waitGroup.Add(testNum)
fmt.Println("在协程中并发更新自定义标签")
startTime := time.Now().UnixMilli()
for i := 0; i < testNum; i++ {
go func(clientsetA *kubernetes.Clientset, index int) {
// 避免进程卡死
defer waitGroup.Done()
err := updateByGetAndUpdate(clientsetA, DP_NAME)
// var retryParam = wait.Backoff{
// Steps: 5,
// Duration: 10 * time.Millisecond,
// Factor: 1.0,
// Jitter: 0.1,
// }
// err := retry.RetryOnConflict(retryParam, func() error {
// return updateByGetAndUpdate(clientset, DP_NAME)
// })
if err != nil {
fmt.Printf("err: %v\n", err)
}
}(clientset, i)
}
// 等待协程完成全部操作
waitGroup.Wait()
// 再查一下,自定义标签的最终值
deployment, err := get(clientset, DP_NAME)
if err != nil {
fmt.Printf("查询deployment发生异常: %v\n", err)
return err
}
fmt.Printf("自定义标签的最终值为: %v,耗时%v毫秒\n", deployment.Labels[LABEL_CUSTOMIZE], time.Now().UnixMilli()-startTime)
return nil
}
package main
import (
"client-go-tutorials/action"
"flag"
"fmt"
"path/filepath"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
func main() {
var kubeconfig *string
var actionFlag *string
// 试图取到当前账号的家目录
if home := homedir.HomeDir(); home != "" {
// 如果能取到,就把家目录下的.kube/config作为默认配置文件
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
// 如果取不到,就没有默认配置文件,必须通过kubeconfig参数来指定
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
actionFlag = flag.String("action", "list-pod", "指定实际操作功能")
flag.Parse()
fmt.Println("解析命令完毕,开始加载配置文件")
// 加载配置文件
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err.Error())
}
// 用clientset类来执行后续的查询操作
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
fmt.Printf("加载配置文件完毕,即将执行业务 [%v]\n", *actionFlag)
var actionInterface action.Action
// 注意,如果有新的功能类实现,就在这里添加对应的处理
switch *actionFlag {
case "list-pod":
listPod := action.ListPod{}
actionInterface = &listPod
case "conflict":
conflict := action.Confilct{}
actionInterface = &conflict
}
err = actionInterface.DoAction(clientset)
if err != nil {
fmt.Printf("err: %v\n", err)
} else {
fmt.Println("执行完成")
}
}
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"args": ["-action=conflict"]
}
]
}
回顾上面的代码,您会发现是5个协程并行执行先查询再修改提交的逻辑,理论上会出现前面提到的冲突问题,5个协程并发更新,会出现并发冲突,因此最终标签的值是小于101+5=106的,咱们来运行代码试试
至此,咱们通过代码证明了资源版本冲突问题确实存在,接下来就要想办法解决此问题了
In the case of a conflict, the correct client action at this point is to GET the resource again, apply the changes afresh, and try submitting again
func RetryOnConflict(backoff wait.Backoff, fn func() error) error {
return OnError(backoff, errors.IsConflict, fn)
}
var retryParam = wait.Backoff{
Steps: 5,
Duration: 10 * time.Millisecond,
Factor: 1.0,
Jitter: 0.1,
}
名称 | 链接 | 备注 |
---|---|---|
项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | [email protected]:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |