k8s Architecture

Earlier I have built a local k3s cluster of two machines and deployed Crawlab (a distributed crawler management platform developed using Golang) to achieve a local distributed cluster service environment. After the service is up, we need Kubernetes to control the container nodes dynamically, monitor the container running status, and achieve even the expansion. Fortunately, Kubernetes directly provides python and golang clients, which can easily implement Kubernetes API operations. So let’s get familiar with it together next, based on golng client Kubernetes control.

Getting Started

Accessing the API and viewing the list

After understanding the basic architecture of Kubernetes and how it provides APIs, we need to know exactly what APIs Kubernetes provides.

For debugging purposes, we first need to run the kubectl proxy command locally. kube-apiserver will then listen on port 8001 locally, which provides an HTTP proxy for the Kubernetes API services.

At this point we can access.

1
$ curl https://127.0.0.1:6443/api/v1

View the corresponding API’s provided.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
  "kind": "APIResourceList",
  "groupVersion": "v1",
  "resources": [
    {
      "name": "bindings",
      "singularName": "",
      "namespaced": true,
      "kind": "Binding",
      "verbs": [
        "create"
      ]
    },
    {
      "name": "componentstatuses",
      "singularName": "",
      "namespaced": false,
      "kind": "ComponentStatus",
      "verbs": [
        "get",
        "list"
      ],
      "shortNames": [
        "cs"
      ]
    },
    ...
  ]
}

Visit the api/v1/pods path and get all Pods.

1
$ curl https://127.0.0.1:6443/api/v1/pods

response:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "614376"
  },
  "items": [
    {
      "metadata": {
        "name": "awesome-project-76788db95b-7ztwr",
        "generateName": "awesome-project-76788db95b-",
        "namespace": "default",
        "selfLink": "/api/v1/namespaces/default/pods/awesome-project-76788db95b-7ztwr",
        "uid": "4fdb6661-edbd-4fc6-bf71-1d2dadb3ffc1",
        "resourceVersion": "608545",
        "creationTimestamp": "2020-05-03T02:29:32Z",
        "labels": {
          "app": "awesome-project",
          "pod-template-hash": "76788db95b"
        },
        ...
        ]
      },
    ]

A more extensive list and description of the API can be found in the official documentation.

Kubernetes officially provides a Client SDK for the Go language, also known as client-go

SDK out-of-cluster access

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import (
    "context"
    "flag"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
    "os"
)

func main() {
    // 配置 k8s 集群外 kubeconfig 配置文件
    var kubeconfig *string
    kubeconfig = flag.String("kubeconfig", "/etc/rancher/k3s/k3s.yaml", "absolute path to the kubeconfig file")
    flag.Parse()

    //在 kubeconfig 中使用当前上下文环境,config 获取支持 url 和 path 方式
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        panic(err.Error())
    }

    // 根据指定的 config 创建一个新的 clientset
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }

    // 通过实现 clientset 的 CoreV1Interface 接口列表中的 PodsGetter 接口方法 Pods(namespace string)返回 PodInterface
    // PodInterface 接口拥有操作 Pod 资源的方法,例如 Create、Update、Get、List 等方法
    // 注意:Pods() 方法中 namespace 不指定则获取 Cluster 所有 Pod 列表
    pods, err := clientset.CoreV1().Pods("").List(context.TODO(),metav1.ListOptions{})
    if err != nil {
        panic(err.Error())
    }
    fmt.Printf("There are %d pods in the k8s cluster\n", len(pods.Items))
    fmt.Println(clientset.CoreV1().Namespaces().List(context.TODO(),metav1.ListOptions{}))

    // 获取指定 namespace 中的 Pod 列表信息
    namespace := "default"
    pods, err = clientset.CoreV1().Pods(namespace).List(context.TODO(),metav1.ListOptions{})

    if err != nil {
        panic(err)
    }
    fmt.Printf("\nThere are %d pods in namespaces %s\n", len(pods.Items), namespace)

}

kubeconfig defaults to /etc/kubernetes/admin.conf, and since I installed K3s the path here is /etc/rancher/k3s/k3s.yaml.

Run the program.

1
2
3
There are 15 pods in the k8s cluster

There are 5 pods in namespaces crawlab

SDK intra-cluster access

In addition to the above methods, it is possible to run the client inside a k8s cluster to manipulate the resource type. Since you are running within a k8s cluster, you need to put the written code inside the image and then run the image container as a Pod within the k8s cluster.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# cat main2.go
package main

import (
    "fmt"
    "time"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/rest"
)

func main() {
    // 通过集群内部配置创建 k8s 配置信息,通过 KUBERNETES_SERVICE_HOST 和 KUBERNETES_SERVICE_PORT 环境变量方式获取
    // 若集群使用 TLS 认证方式,则默认读取集群内部 tokenFile 和 CAFile
    // tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
    // rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
    config, err := rest.InClusterConfig()
    if err != nil {
        panic(err.Error())
    }

    // 根据指定的 config 创建一个新的 clientset
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }
    for {
        // 通过实现 clientset 的 CoreV1Interface 接口列表中的 PodsGetter 接口方法 Pods(namespace string)返回 PodInterface
        // PodInterface 接口拥有操作 Pod 资源的方法,例如 Create、Update、Get、List 等方法
        // 注意:Pods() 方法中 namespace 不指定则获取 Cluster 所有 Pod 列表
        pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
        if err != nil {
            panic(err.Error())
        }
        fmt.Printf("There are %d pods in the k8s cluster\n", len(pods.Items))

        // 获取指定 namespace 中的 Pod 列表信息
        namespce := "default"
        pods, err = clientset.CoreV1().Pods(namespce).List(metav1.ListOptions{})
        if err != nil {
            panic(err)
        }
        fmt.Printf("\nThere are %d pods in namespaces %s\n", len(pods.Items), namespce)
        for _, pod := range pods.Items {
            fmt.Printf("Name: %s, Status: %s, CreateTime: %s\n", pod.ObjectMeta.Name, pod.Status.Phase, pod.ObjectMeta.CreationTimestamp)
        }

        // 获取所有的 Namespaces 列表信息
        ns, err := clientset.CoreV1().Namespaces().List(metav1.ListOptions{})
        if err != nil {
            panic(err)
        }
        nss := ns.Items
        fmt.Printf("\nThere are %d namespaces in cluster\n", len(nss))
        for _, ns := range nss {
            fmt.Printf("Name: %s, Status: %s, CreateTime: %s\n", ns.ObjectMeta.Name, ns.Status.Phase, ns.CreationTimestamp)
        }

        time.Sleep(10 * time.Second)
    }
}

This example demonstrates how to manipulate Pod and Namespaces resource types within a k8s cluster, including getting the number of Pod lists in the cluster, getting information about the Pod list in a given Namespace, and getting information about the list of all Namespaces in the cluster. Here, this method obtains the k8s cluster configuration in a different way than the above method. It obtains the k8s configuration information created inside the cluster through the KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT environment variables to establish a connection with k8s and then operate its resource types. If k8s has TLS authentication enabled, it reads the tokenFile and CAFile from the specified location inside the cluster by default.

Compile it and see if it passes.

1
2
3
# go build main2.go
# ls
main2  main2.go

Next, create a Dockerfile file in the same level directory as follows.

1
2
3
FROM debian
COPY ./main2 /opt
ENTRYPOINT /opt/main2

Building a docker image.

1
2
3
4
# ls
Dockerfile  main2

# docker build -t client-go/in-cluster:1.0 .

Since RBAC authentication is enabled by default on local k8s, you need to create a clusterrolebinding to give default account view permissions.

1
2
$ kubectl create clusterrolebinding default-view --clusterrole=view --serviceaccount=default:default
clusterrolebinding.rbac.authorization.k8s.io "default-view" created

Finally, just run the image in the Pod, either by yaml or by running the kubectl run command to create it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# kubectl run --rm -i client-go-in-cluster-demo --image=client-go/in-cluster:1.0 --image-pull-policy=Never

There are 3 pods in namespaces default
Name: client-go-in-cluster-demo-58d9b5bd79-7w5ds, Status: Running, CreateTime: 2019-02-13 14:25:38 +0000 UTC
Name: podinfo-7b8c9bc5c9-64g8k, Status: Running, CreateTime: 2019-01-10 14:40:18 +0000 UTC
Name: podinfo-7b8c9bc5c9-bx7ml, Status: Running, CreateTime: 2019-01-10 14:40:18 +0000 UTC

There are 5 namespaces in cluster
Name: custom-metrics, Status: Active, CreateTime: 2019-01-10 09:01:52 +0000 UTC
Name: default, Status: Active, CreateTime: 2019-01-05 09:18:02 +0000 UTC
Name: kube-public, Status: Active, CreateTime: 2019-01-05 09:18:02 +0000 UTC
Name: kube-system, Status: Active, CreateTime: 2019-01-05 09:18:02 +0000 UTC
Name: monitoring, Status: Active, CreateTime: 2019-01-08 15:00:41 +0000 UTC
There are 16 pods in the k8s cluster

It runs fine, simply verify it!

1
2
3
4
5
# kubectl get pods -n default
NAME                                         READY   STATUS    RESTARTS   AGE
client-go-in-cluster-demo-58d9b5bd79-7w5ds   1/1     Running   0          10m
podinfo-7b8c9bc5c9-64g8k                     1/1     Running   1          33d
podinfo-7b8c9bc5c9-bx7ml                     1/1     Running   1          33d

SDK operations on k8s various resource objects

As demonstrated above, running clients inside and outside the k8s cluster operate on resource types, but only Read related read operations, the next brief demonstration of how to Create, Update, Delete operations. Create the main.go file as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# cat main3.go
package main

import (
    "flag"
    "fmt"
    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"
)

func main() {
    // 配置 k8s 集群外 kubeconfig 配置文件
    var kubeconfig *string
        kubeconfig = flag.String("kubeconfig", "/etc/rancher/k3s/k3s.yaml", "absolute path to the kubeconfig file")
    flag.Parse()

    //在 kubeconfig 中使用当前上下文环境,config 获取支持 url 和 path 方式
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        panic(err)
    }

    // 根据指定的 config 创建一个新的 clientset
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err)
    }

    // 通过实现 clientset 的 CoreV1Interface 接口列表中的 NamespacesGetter 接口方法 Namespaces 返回 NamespaceInterface
    // NamespaceInterface 接口拥有操作 Namespace 资源的方法,例如 Create、Update、Get、List 等方法
    name := "client-go-test"
    namespacesClient := clientset.CoreV1().Namespaces()
    namespace := &apiv1.Namespace{
        ObjectMeta: metav1.ObjectMeta{
            Name: name,
        },
        Status: apiv1.NamespaceStatus{
            Phase: apiv1.NamespaceActive,
        },
    }

    // 创建一个新的 Namespaces
    fmt.Println("Creating Namespaces...")
    result, err := namespacesClient.Create(namespace)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created Namespaces %s on %s\n", result.ObjectMeta.Name, result.ObjectMeta.CreationTimestamp)

    // 获取指定名称的 Namespaces 信息
    fmt.Println("Getting Namespaces...")
    result, err = namespacesClient.Get(name, metav1.GetOptions{})
    if err != nil {
        panic(err)
    }
    fmt.Printf("Name: %s, Status: %s, selfLink: %s, uid: %s\n",
        result.ObjectMeta.Name, result.Status.Phase, result.ObjectMeta.SelfLink, result.ObjectMeta.UID)

    // 删除指定名称的 Namespaces 信息
    fmt.Println("Deleting Namespaces...")
    deletePolicy := metav1.DeletePropagationForeground
    if err := namespacesClient.Delete(name, &metav1.DeleteOptions{
        PropagationPolicy: &deletePolicy,
    }); err != nil {
        panic(err)
    }
    fmt.Printf("Deleted Namespaces %s\n", name)
}

Run the program.

1
2
3
4
5
6
7
# go run main3.go
Creating Namespaces...
Created Namespaces client-go-test on 2019-02-13 21:44:52 +0800 CST
Getting Namespaces...
Name: client-go-test, Status: Active, selfLink: /api/v1/namespaces/client-go-test, uid: 8a2de86e-2f95-11e9-b2e0-a0369f3f0404
Deleting Namespaces...
Deleted Namespaces client-go-test