当我们通过kubectl来查看、修改Kubernetes资源时,有没有想过后面的接口到底是怎样的?有没有办法探查这些交互数据呢?
Kuberenetes客户端和服务端交互的接口,是基于http协议的。所以只需要能够捕捉并解析https流量,我们就能看到kubernetes的API流量。
但是由于kubenetes使用了客户端私钥来实现对客户端的认证,所以抓包配置要复杂一点。具体是如下的结构:
如果想了解更多Kubernetes证书的知识,可以看下这篇Kubernetes证书解析的文章
从kubeconfig中提取出客户端证书和私钥 kubeconfig中包含了客户端的证书和私钥,我们首先要把它们提取出来:
1 2 3 4 5 6 7 8 9 10 11 12 grep client-certificate-data ~/.kube/config | \ awk '{ print $2 }' | \ base64 --decode > client-cert.pem grep client-key-data ~/.kube/config | \ awk '{ print $2 }' | \ base64 --decode > client-key.pem grep certificate-authority-data ~/.kube/config | \ awk '{ print $2 }' | \ base64 --decode > cluster-ca-cert.pem
参考自Reddit
配置Charles代理软件 从第一张图可以看出,代理软件的作用有两个:一是接收https流量并转发,二是转发到kubernetes apiserver的时候,使用指定的客户端私钥。
首先配置Charles,让他拦截所有的https流量:
然后配置客户端私钥,即对于发送到apiserver的请求,统一使用指定的客户端私钥进行认证:
配置kubectl 需要抓包kubectl的流量,需要两个条件:1. kubectl使用Charles作为代理,2. kubectl需要信任Charles的证书。
1 2 3 4 5 6 $ export https_proxy=http://127.0.0.1:8888/ $ kubectl --insecure-skip-tls-verify get pod NAME READY STATUS RESTARTS AGE sc-b-7f5dfb694b-xtfrz 2/2 Running 0 2d20h
我们就可以看到get pod
的网络请求了:
可以看到,get pod的endpoint是GET /api/v1/namespaces/<namespace>/pods
。
让我们再尝试下创建pod的请求:
1 2 3 4 5 6 7 8 9 10 11 12 $ cat <<EOF >pod.yaml apiVersion: v1 kind: Pod metadata: name: nginx-robberphex spec: containers: - name: nginx image: nginx:1.14.2 EOF $ kubectl --insecure-skip-tls-verify apply -f pod.yaml pod/nginx-robberphex created
也同样可以抓到包:
创建pod的endpoint是POST /api/v1/namespaces/<namespace>/pods
配置kubenetes client 我们先从写一个用kubernetes go client来获取pod的例子(注意,代码中已经信任所有的证书,所以可以抓到包):
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 package mainimport ( "context" "flag" "fmt" "path/filepath" 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" ) func main () { ctx := context.Background() 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) } config.TLSClientConfig.CAData = nil config.TLSClientConfig.Insecure = true clientset, err := kubernetes.NewForConfig(config) if err != nil { panic (err) } podClient := clientset.CoreV1().Pods(apiv1.NamespaceDefault) podList, err := podClient.List(ctx, metav1.ListOptions{}) if err != nil { panic (err) } for _, pod := range podList.Items { fmt.Printf("podName: %s\n" , pod.Name) } fmt.Println("done!" ) }
然后编译执行:
1 2 3 4 5 6 $ go build -o kube-client $ export https_proxy=http://127.0.0.1:8888/ $ ./kube-client podName: nginx-robberphex podName: sc-b-7f5dfb694b-xtfrz done !
这时也可以抓到同样的结果:
基于此,我们就可以分析一个Kubernetes到底干了什么,也是我们分析Kubernetes实现的入口。