Kubernetes-基于client-go开发指南

1、安装和配置开发环境

在本文中,介绍的是基于client-go对Kubernetes进行开发。操作系统为window 7,Kubernetes的版本为v1.10。为搭建此开发环境,需要安装和部署kubectl、go和client-go。

1.1 安装kubectl

1)下载kubectl

此处是在windows下安装,因此下载kubectl.exe,下载地址:https://storage.googleapis.com/kubernetes-release/release/v1.9.0/bin/windows/amd64/kubectl.exe

并将kubectl.exe所在的地址添加至Windows的环境变量的Path中。

其他操作系统下安装kubectl,请参考:https://kubernetes.io/docs/tasks/tools/install-kubectl/#tabset-2

2)配置kubeconfig文件

RKE会在配置文件所在的目录下部署一个本地文件,该文件中包含kube配置信息以连接到新生成的群集。

默认情况下,kube配置文件被称为.kube_config_cluster.yml。将这个文件复制到你的本地~/.kube/config,就可以在本地使用kubectl了。

需要注意的是,部署的本地kube配置名称是和集群配置文件相关的。例如,如果您使用名为mycluster.yml的配置文件,则本地kube配置将被命名为.kube_config_mycluster.yml。

3)验证

执行kubectl命令,获取nodes的信息:

$ kubectl get nodes

1.2 安装go

下载go1.10.3.windows-amd64.msi,双击此文件进行安装。默认情况下,安装的根目录为“c:\Go”,安装完成后系统会自动将“c:\Go\bin”目录添加到环境变量的“PATH”。在使用之前需要重新启动命令行工具。在命令工具中执行如下命令,可以查看go安装成功后的信息:

$ go env

1.4 安装go-client

1.4.1 go-client简介

client-go是与Kubernetes进行通信的一种客户端,在client-go中有的三类 client。

Clientset:Clientset 是调用Kubernetes最常用的 client,通过它能够找到 kubernetes所有原生资源对应的 client。 获取方式一般是,指定 group 然后指定特定的 version,然后根据 resource 名字来获取到对应的 client。

DynamicClient:Dynamic client 是一种动态的 client,它能处理 kubernetes 所有的资源。不同于 clientset,dynamic client 返回的对象是一个 map[string]interface{},如果一个 controller 中需要控制所有的 API,可以使用dynamic client,目前它在了 garbage collector 和 namespace controller中被使用。

RESTClient:RESTClient 是 clientset 和 dynamic client 的基础,前面这两个 client 本质上都是 RESTClient,它提供了一些 RESTful 的函数如 Get(),Put(),Post(),Delete()。

如何选择 Client 的类型呢?

  • 如果 Controller 只是需要控制 Kubernetes 原生的资源,如 Pods,Nodes,Deployments等,那么 Clientset 就够用了。
  • 如果需要使用第三方的资源来拓展 Kubernetes 的 API,那么就需要使用 Dynamic Client 或 RESTClient。
  • 需要注意的是,Dynamic Client 目前只支持 JSON 的序列化和反序列化。

当前,client-go和Kubernetes之间的版本对应关系如下:

Kubernetes 1.5 Kubernetes 1.6 Kubernetes 1.7 Kubernetes 1.8 Kubernetes 1.9 Kubernetes 1.10 Kubernetes 1.11
client-go 1.5
client-go 2.0 + – + – + – + – + – + –
client-go 3.0 + – + – + – + – + –
client-go 4.0 + – + – + – + – + – + –
client-go 5.0 + – + – + – + – + – + –
client-go 6.0 + – + – + – + – + – + –
client-go 7.0 + – + – + – + – + – + –
client-go 8.0 + – + – + – + – + – + –
client-go HEAD + – + – + – + – + – + – + –

说明:

  • ✓:client-go和Kubernetes版本中的功能/ API对象完全相同。
  • +:client-go具有可能不存在于Kubernetes集群中的功能或API对象,这可能是因为client-go具有其他新的API,或者服务器已删除的旧API。但是,它们共有的一切(即大多数API)都可以使用。请注意,alpha API可能会在单个版本中消失或发生重大变化。
  • -:Kubernetes集群具有client-go库无法使用的功能,这可能是由于服务器具有其他新API,或者客户端已删除旧API。但是,它们共有的所有内容(即大多数API)都可以使用。

1.4.2 安装部署

1.4.2.1 安装client-go

在命令行中执行如下的命令,进行client-go的安装:

$ go get k8s.io/client-go/...

1.4.2.2 Glide介绍

在本文中go的依赖关系通过Glide进行管理,通过Glide将管理/vendor目录。Glide支持语义化版本;支持Git、Svn配置管理工具等;支持Go工具链;支持vendor目录;支持从Godep、GB、GPM、Gom倒入;支持私有的Repos和Forks。

Glide扫描应用程序或库的源代码以确定所需的依赖。Glide通过读取glide.yaml文件来确定依赖的版本和位置。通过这些信息,Glide会获取所需的依赖。遇到依赖包时,会扫描导入以确定依赖关系的依赖关系(传递依赖关系)。如果依赖项目包含一个glide.yaml文件,确定依赖规则中各依赖的位置和版本。此文件还可以确定导入了Godep,GB,GOM和GPM的配置。当go工作发现和使用Glide时,依赖将会被导出到vendor/目录下。glide.lock文件包含所有的依赖,以及传递。通过glide init命令能够安装新项目,通过glide update能够使用扫描和规则重新生成依赖版本,glide install会安装在glide.lock文件列示的版本。glide.yaml文件目录结构如下所示:

- $GOPATH/src/myProject (Your project)
  |
  |-- glide.yaml
  |
  |-- glide.lock
  |
  |-- main.go (Your main go code can live here)
  |
  |-- mySubpackage (You can create your own subpackages, too)
  |    |
  |    |-- foo.go
  |
  |-- vendor
       |-- github.com
            |
            |-- Masterminds
                  |
                  |-- ... etc.

通过执行如下的命令安装Glide:

$ go get github.com/Masterminds/glide

在安装成功后,可以通过下面的名称确认Glide是否部署成功:

$ glide -v

Glide的核心使用命令如下:

$ glide create                            # 初始化一个新项目,并创建一个glide.yaml文件
$ open glide.yaml                         # 编辑glide.yaml文件
$ glide get github.com/Masterminds/cookoo # 获取包,并将其添加至glide.yaml
$ glide install                           # 安装包和依赖,从glide.lock文件安装特定的版本
$ go build                                # Go tools work normally
$ glide up                                # 下载或更新glide.yaml文件中列出的所有库,并将它们放在vendor目录中。

1.4.2.3 通过Glide管理依赖关系

避免使用Glide的许多子命令,最好直接修改Glide的清单文件(glide.yaml),然后执行glide updeate –strip-vendor进行更新。

首先,在项目的根目录(这里的根目录为bjsasc.com/demo)下创建一个glide.yaml文件:

package: bjsasc.com/demo
import:
- package: k8s.io/client-go
  version: v7.0.0

接着,在下面中添加一个Go文件,此文件导入client-go,否则将不会导入client-go依赖到下面的vendor/目录下。

2、开发示例

在“%GOPATH%\src\bjsasc.com\demo”目录下创建一个main.go文件,main.go(通过此链接获取:https://github.com/kubernetes/client-go/tree/master/examples/create-update-delete-deployment)的代码如下所示,此代码的示例演示如何通过client-go在Kubernetes中创建、更新和删除部署。

/*
Copyright 2017 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 (
        "bufio"
        "flag"
        "fmt"
        "os"
        "path/filepath"
        appsv1 "k8s.io/api/apps/v1"
        apiv1 "k8s.io/api/core/v1"
        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"
        "k8s.io/client-go/util/retry"
        // Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).
        // _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
)
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()
        config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
        if err != nil {
               panic(err)
        }
        clientset, err := kubernetes.NewForConfig(config)
        if err != nil {
               panic(err)
        }
        deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
        //定义部署对象
        deployment := &appsv1.Deployment{
               ObjectMeta: metav1.ObjectMeta{
                       Name: "demo-deployment",
               },
               Spec: appsv1.DeploymentSpec{
                       Replicas: int32Ptr(2),
                       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,
                                                             },
                                                     },
                                              },
                                      },
                               },
                       },
               },
        }
        // 创建部署
        fmt.Println("Creating deployment...")
        result, err := deploymentsClient.Create(deployment)
        if err != nil {
               panic(err)
        }
        fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())
       // 更新部署
        prompt()
        fmt.Println("Updating deployment...")
        //    You have two options to Update() this Deployment:
        //
        //    1. Modify the "deployment" variable and call: Update(deployment).
        //       This works like the "kubectl replace" command and it overwrites/loses changes
        //       made by other clients between you Create() and Update() the object.
        //    2. Modify the "result" returned by Get() and retry Update(result) until
        //       you no longer get a conflict error. This way, you can preserve changes made
        //       by other clients between Create() and Update(). This is implemented below
        //                     using the retry utility package included with client-go. (RECOMMENDED)
        //
        // More Info:
        // https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency
        retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
               // Retrieve the latest version of Deployment before attempting update
               // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver
               result, getErr := deploymentsClient.Get("demo-deployment", metav1.GetOptions{})
               if getErr != nil {
                       panic(fmt.Errorf("Failed to get latest version of Deployment: %v", getErr))
               }
        result.Spec.Replicas = int32Ptr(1)                           
       // 减少副本数量
               result.Spec.Template.Spec.Containers[0].Image = "nginx:1.13" // change nginx version
               _, updateErr := deploymentsClient.Update(result)
               return updateErr
        })
        if retryErr != nil {
               panic(fmt.Errorf("Update failed: %v", retryErr))
        }
        fmt.Println("Updated deployment...")
        // 列示部署
        prompt()
        fmt.Printf("Listing deployments in namespace %q:\n", apiv1.NamespaceDefault)
        list, err := deploymentsClient.List(metav1.ListOptions{})
        if err != nil {
               panic(err)
        }
        for _, d := range list.Items {
               fmt.Printf(" * %s (%d replicas)\n", d.Name, *d.Spec.Replicas)
        }
        // 删除部署
        prompt()
        fmt.Println("Deleting deployment...")
        deletePolicy := metav1.DeletePropagationForeground
        if err := deploymentsClient.Delete("demo-deployment", &metav1.DeleteOptions{
               PropagationPolicy: &deletePolicy,
        }); err != nil {
               panic(err)
        }
        fmt.Println("Deleted deployment.")
}
func prompt() {
        fmt.Printf("-> Press Return key to continue.")
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
               break
        }
        if err := scanner.Err(); err != nil {
               panic(err)
        }
        fmt.Println()
}
func int32Ptr(i int32) *int32 { return &i }

2.1 运行这个例子

在glide.yaml文件所在的目录中运行如下的命令:

glide update --strip-vendor

也可以使用下面的命令:

glide up -v

执行上述命令后,k8s.io/client-go和k8s.io/apimachinery都会被添加至下面的vendor/命令下。

确保具有一个Kubernetes集群,并以及进行了kubectl配置:

$ kubectl get nodes

通过执行下面的命令编译此示例:

$ cd bjsasc.com/demo
$ go build

现在,使用本地kubeconfig文件在工作站上运行此应用程序:

$ demo.exe
# or specify a kubeconfig file with flag
$ demo.exe -kubeconfig=$HOME/.kube/config

运行此命令将在群集上执行以下操作:

  • 创建部署:这将创建副本数为2部署。验证 kubectl get pods
  • 更新部署:这将通过将副本计数设置为1并将容器映像更改为更新在上一步中创建的部署资源nginx:1.13。建议您检查处理冲突的重试循环。使用确认新的副本计数和容器图像 kubectl describe deployment demo
  • 回滚部署:这将将部署回滚到上一版本。在这种情况下,它是在步骤1中创建的修订版。kubectl describe用于验证容器图像现在nginx:1.12。另请注意,部署的副本计数仍为1; 这是因为当且仅当部署的pod模板(.spec.template)发生更改时才会创建部署修订。
  • 列表部署:这将检索default 命名空间中的部署并打印其名称和副本计数。
  • 删除部署:这将删除Deployment对象及其相关的ReplicaSet资源。验证kubectl get deployments

每个步骤都由交互式提示分隔。您必须 Return按键才能继续下一步。您可以使用这些提示作为中断来花时间运行kubectl并检查执行的操作的结果。

您应该看到如下输出:

1)创建一个名称为“demo-deployment”的部署

Creating deployment...
Created deployment "demo-deployment".

2)更新部署的版本

-> Press Return key to continue.
Updating deployment...
Updated deployment...

3)回滚部署

-> Press Return key to continue.
Rolling back deployment...
Rolled back deployment...

4)以列表的形式展示“default”命名空间下的部署

-> Press Return key to continue.
Listing deployments in namespace "default":

5)删除“demo-deployment”部署

-> Press Return key to continue.
Deleting deployment...
Deleted deployment.

2.2 清理

成功运行此程序将清除创建的工件。如果在未完成的情况下终止程序,则可以使用以下方法清理已创建的部署:

$ kubectl delete deploy demo-deployment

2.3 故障排除

如果您收到以下错误,请确保您的群集的Kubernetes版本为v1.6或更高版本kubectl version

panic: the server could not find the requested resource

 

参考资料

1.《client-go》地址:https://github.com/kubernetes/client-go

2.《Installing client-go》地址:https://github.com/kubernetes/client-go/blob/master/INSTALL.md

3.《Create, Update & Delete Deployment》地址:https://github.com/kubernetes/client-go/tree/master/examples/create-update-delete-deployment

4.《Introducing client-go version 6》地址:https://kubernetes.io/blog/2018/01/introducing-client-go-version-6/

5.《Glide: Vendor Package Management for Golang》地址:https://github.com/Masterminds/glide

6.《Getting Started》地址:http://docs.studygolang.com/doc/install

作者简介:
季向远,北京神舟航天软件技术有限公司。本文版权归原作者所有。

K8S中文社区微信公众号