Kubernetes api-server源码阅读2(Debug Kubernetes篇)

云原生学习路线导航页(持续更新中)

本文是 Kubernetes api-server源码阅读 系列第二篇,主要讲述如何实现 kubernetes api-server 的 debug

  • 参考b站视频地址:Kubernetes源码开发之旅二

1.本篇章任务

  • Go-Delve:go语言的调试工具
  • Debug模式启动集群:为了能调试,需要使用Debug的方式启动集群
  • 命令行调试:在没有IDE的情况下,迅速调试kubernetes,就可以使用命令行的调试功能
  • VS Code中调试:为了方便我们学习,使用VS Code实现远程调试线上的kubernetes
  • Goland中调试:现在有很多人喜欢使用Goland(包括我),所以这里也给出了Goland的远程调试方法
  • Postman请求Api-Server:像kubectl这种客户端,很多都有cache的机制,通过informer的cache机制,会把apiserver的很多api对象都在本地缓存下来,所以我们执行kubectl命令来调试的话,可能一条命令没有触发到apiserver上,所以我们直接使用postman发送http请求,这样apiserver就一定会被触发执行

2.Go-Delve

2.1.go-delve简介

2.1.1.go-delve是什么

  • go-delve 是一个开源项目,为go语言提供debug能力,简单易用,github地址:https://github.com/go-delve/delve
  • go-delve 属于golang语言基础设施的一部分,像vscode的go语言插件、goland调试功能等,底层都是使用了delve。
  • go的plugin好像也用到了go-delve(go的plugin是什么,可以参考我的另一篇博客:知识点积累 的1.7)

2.1.2.go-delve的能力

  • 支持本地调试:本地写了一个go项目,可以使用delve的dlv命令,直接在命令行进行调试。(本地vscode调试也属于这种)
  • 也支持远程调试:可以使用远程的IDE,调试另一台机器上的go程序
    在这里插入图片描述

2.1.3.go-delve的调试方法

  • delve是一个单独的进程,可以直接使用delve去启动一个go程序,这样delve就是套在go程序外边的壳,可以直接进行调试
  • 也可以单独先去启动go程序,然后delve根据go程序的进程号,接管go程序,然后实现调试

2.2.安装go-delve

  • 官方文档里已经给了安装方法:https://github.com/go-delve/delve/tree/master/Documentation/installation
  • 不过我们使用的go版本是1.18.2,不可以直接安装最新版的delve,会提示golang版本太低,至少需要go1.19
  • 经过我的测试,大家可以使用delve1.9.1版本:
    $ git clone https://github.com/go-delve/delve
    $ cd delve
    $ go install github.com/go-delve/delve/cmd/dlv@v1.9.1
    
  • 安装完,测试是否已经安装成功了
    root@graham-virtual-machine:~/Download/delve# dlv --help
    Delve is a source level debugger for Go programs.
    
    Delve enables you to interact with your program by controlling the execution of the process,
    evaluating variables, and providing information of thread / goroutine state, CPU register state and more.
    
    The goal of this tool is to provide a simple yet powerful interface for debugging Go programs.
    
    Pass flags to the program you are debugging using `--`, for example:
    
    `dlv exec ./hello -- server --config conf/config.toml`
    
    Usage:
      dlv [command]
    
    Available Commands:
      attach      Attach to running process and begin debugging.
      completion  Generate the autocompletion script for the specified shell
      connect     Connect to a headless debug server with a terminal client.
      core        Examine a core dump.
      dap         Starts a headless TCP server communicating via Debug Adaptor Protocol (DAP).
      debug       Compile and begin debugging main package in current directory, or the package specified.
      exec        Execute a precompiled binary, and begin a debug session.
      help        Help about any command
      test        Compile test binary and begin debugging program.
      trace       Compile and begin tracing program.
      version     Prints version.
    
    Additional help topics:
      dlv backend    Help about the --backend flag.
      dlv log        Help about logging flags.
      dlv redirect   Help about file redirection.
    
    Use "dlv [command] --help" for more information about a command.
    
    

2.3.go-delve使用方法

2.3.1.官方文档+命令整体预览

  • https://github.com/go-delve/delve/tree/master/Documentation/usage
    在这里插入图片描述

2.3.2.启动delve接管目标进程 的 命令

2.3.2.1.本地调试
  • delve debug [package]
    • delve debug,会先将package里的程序 build 成可执行文件,然后启动起来,并使用delve接管,接下来就可以在delve的命令行使用过命令调试了
  • delve test [package]
    • 将package里的单元测试函数启动起来,并使用delve接管,接下来就可以在delve的命令行使用过命令调试了
  • delve exec <exec>
    • 如果事先已经编译好了可执行文件,可以直接使用 delve exec 启动,就不用像 delve debug 再build了
  • delve attach <pid>
    • 接管已经启动的go进程,指定进程id
2.3.2.2.远程调试
  • dlv --headless <command> <target> <args>
    • 将目标程序以一个serve的方式启动起来,等待远程连接
    • 我们接下来主要使用这种方式
  • dlv dap
    • dap:Debug Adaptor Protocol
    • 也是支持远程连接的方式,不过我们这里没有使用

2.3.3.调试过程中用到的命令

  • 这里大约列出来90%可用命令
    在这里插入图片描述

2.3.4.通过一个demo演示delve使用方法

2.3.4.1.项目github地址
  • 这是根据 视频中老师讲的项目,编写的demo,大家可以克隆下来直接用
  • https://github.com/graham924/delve-study
    git clone https://github.com/graham924/delve-study.git
    
  • demo内容简介:
    • 使用cobra写了两个命令:rootCmd、createCmd
    • 其中createCmd是rootCmd的子命令
    • cd到项目目录下,运行项目
      • 直接 go run main.go 执行的是rootCmd,会打印"hello world"
      • 执行子命令,并指定参数–name,如 go run main.go create --name grahamzhu,则会打印:
        create command is called
        name:  grahamzhu
        
2.3.4.2.使用delve演示本地命令行调试
  • cd delve-study 后,执行命令:

    dlv debug delve-study create --name=grahamzhu
    
    • 发现报错 Error: unknown flag: --name
    • 这是因为,使用dlv的情况下,指定命令行参数,不能这么写。需要用一个 -- 分隔开
  • 这么写就对了

    dlv debug delve-study -- create --name=grahamzhu
    
  • 执行后进入dlv的命令模式

    root@graham-virtual-machine:~/zgy/go_project/delve-study# dlv debug delve-study -- create --name=grahamzhu
    Type 'help' for list of commands.
    (dlv)
    
  • 我们在 cmd/create.go 中,在第19行打个断点

    (dlv) break cmd/create.go:19
    Breakpoint 1 set at 0x5f1238 for delve-study/cmd.glob..func1() ./cmd/create.go:19
    

    其实就是这里:在这里插入图片描述

  • 执行 continue ,程序会再下一个断点位置停下来,就是我们刚才打的断点

    (dlv) continue
    > delve-study/cmd.glob..func1() ./cmd/create.go:19 (hits goroutine(1):1 total:1) (PC: 0x5f1238)
        14: var createCmd = &cobra.Command{
        15:         Use:   "create",
        16:         Short: "子命令",
        17:         Long:  "做一个子命令 - create",
        18:         Run: func(cmd *cobra.Command, args []string) {
    =>  19:                 fmt.Println("create command is called")
        20:                 name, _ := cmd.Flags().GetString("name")
        21:                 create(name)
        22:         },
        23: }
        24:
    
  • 执行两次 next,让程序 将要 去执行 create 方法

    (dlv) next
    create command is called
    > delve-study/cmd.glob..func1() ./cmd/create.go:20 (PC: 0x5f128a)
        15:         Use:   "create",
        16:         Short: "子命令",
        17:         Long:  "做一个子命令 - create",
        18:         Run: func(cmd *cobra.Command, args []string) {
        19:                 fmt.Println("create command is called")
    =>  20:                 name, _ := cmd.Flags().GetString("name")
        21:                 create(name)
        22:         },
        23: }
        24:
        25: func create(name string) {
    (dlv) next
    > delve-study/cmd.glob..func1() ./cmd/create.go:21 (PC: 0x5f12cc)
        16:         Short: "子命令",
        17:         Long:  "做一个子命令 - create",
        18:         Run: func(cmd *cobra.Command, args []string) {
        19:                 fmt.Println("create command is called")
        20:                 name, _ := cmd.Flags().GetString("name")
    =>  21:                 create(name)
        22:         },
        23: }
        24:
        25: func create(name string) {
        26:         fmt.Println("name: ", name)
    
  • 执行 step 进入create方法

    (dlv) step
    > delve-study/cmd.create() ./cmd/create.go:25 (PC: 0x5f132a)
        20:                 name, _ := cmd.Flags().GetString("name")
        21:                 create(name)
        22:         },
        23: }
        24:
    =>  25: func create(name string) {
        26:         fmt.Println("name: ", name)
        27: }
    
  • 执行 stepout,会执行完当前的create方法,跳到create函数调用方的下一行代码

    • 可以看到,create函数执行完毕,打印出来了 name: grahamzhu
    • 并且跳到了create函数调用方的下一行代码
    (dlv) stepout
    name:  grahamzhu
    > delve-study/cmd.glob..func1() ./cmd/create.go:22 (PC: 0x5f12db)
    Values returned:
    
        17:         Long:  "做一个子命令 - create",
        18:         Run: func(cmd *cobra.Command, args []string) {
        19:                 fmt.Println("create command is called")
        20:                 name, _ := cmd.Flags().GetString("name")
        21:                 create(name)
    =>  22:         },
        23: }
        24:
        25: func create(name string) {
        26:         fmt.Println("name: ", name)
        27: }
    
  • 执行 continue,让程序执行到下一个断点。不过我们下面没有断点了,所以程序直接执行结束了

    (dlv) continue
    Process 351390 has exited with status 0
    
2.3.4.3.使用delve演示远程命令行调试
  • 使用 dlv --headless debug 进行远程调试
    root@graham-virtual-machine:~/zgy/go_project/delve-study# dlv --headless debug delve-study -- create --name=grahamzhu
    API server listening at: 127.0.0.1:38437
    
    
  • 可以看到,在端口 38437 启动了一个serve,这就是delve启动的一个进程,终端没有结束,正在等待我们远程连接
  • 然后我们开启另一个终端,模拟是远程,使用 dlv connect 127.0.0.1:38437 远程连接调试。可以看到,已经进入了dlv的命令行界面
    root@graham-virtual-machine:~# dlv connect 127.0.0.1:38437
    Type 'help' for list of commands.
    (dlv)
    
  • 接下来就和上面本地调试一样了,使用break打断点,continue、next、step等调试
  • 如果你本机上也安装了delve,也可以在本机上连接,这样也是远程。

3.Debug模式启动集群

3.1.Debug模式启动集群分3步

  • 修改 kubernetes/hack/lib/golang.sh 的编译参数,使得每次编译都不会优化掉debug的东西
  • 重新启动本地集群
  • 使用delve重新启动API Server

3.2.修改编译参数,使得每次编译都不会优化掉debug的东西

  • 修改 kubernetes/hack/lib/golang.sh 的编译参数,使得每次编译都不会优化掉debug的东西。改的内容实际上就是两点:

    • 禁止-w -s,保留文件名,行号

    • 加上-gcflags= “all=-N-I”,禁止优化和内联

  • 编辑 kubernetes/hack/lib/golang.sh,修改方式如下:

    • 找到下面这段,可以看到,两个 if语句,第一个是debug下会干什么、第二个是非debug下会干什么

      gogcflags="all=-trimpath=${trimroot} ${GOGCFLAGS:-}"
        if [[ "${DBG:-}" == 1 ]]; then
            # Debugging - disable optimizations and inlining.
            gogcflags="${gogcflags} -N -l"
        fi
      
        goldflags="all=$(kube::version::ldflags) ${GOLDFLAGS:-}"
        if [[ "${DBG:-}" != 1 ]]; then
            # Not debugging - disable symbols and DWARF.
            goldflags="${goldflags} -s -w"
        fi
      
    • 我们改成下面这样就好了

      • 把debug执行的语句,从if中取出,这样不管怎么样,都会执行
      • 非debug执行的代码注掉,这样就不会执行到了
      gogcflags="all=-trimpath=${trimroot} ${GOGCFLAGS:-}"
        # if [[ "${DBG:-}" == 1 ]]; then
        #     # Debugging - disable optimizations and inlining.
        #     gogcflags="${gogcflags} -N -l"
        # fi
      gogcflags="${gogcflags} -N -l"
        goldflags="all=$(kube::version::ldflags) ${GOLDFLAGS:-}"
        # if [[ "${DBG:-}" != 1 ]]; then
        #     # Not debugging - disable symbols and DWARF.
        #     goldflags="${goldflags} -s -w"
        # fi
      
    • 这样以后再执行 make all,或者执行hack/local-up-cluster.sh,编译出来的东西,就都可以debug了

3.3.重新启动本地集群

  • 修改完 hack/lib.golang.sh 后,因为修改了源码,所以需要执行make clean,清除已编译的旧的可执行程序。

  • 然后通过 hack/local-up-cluster.sh 脚本启动本地集群

    cd ~/go/src/k8s.io/kubernetes
    make clean
    hack/local-up-cluster.sh
    

3.4.使用delve重新启动API Server

  • 本地集群启动之后,我们先看一下,目前机器中启动了kubernetes的哪些组件

    root@graham-virtual-machine:~/zgy/go_project/delve-study# ps -a | grep kube
     343645 pts/0    00:11:32 kube-apiserver
     343946 pts/0    00:03:53 kube-controller
     343948 pts/0    00:00:31 kube-scheduler
     344111 pts/0    00:03:58 kubelet
     344604 pts/0    00:00:04 kube-proxy
    
  • 然后我们 以API Server举例,讲解一下:如何使用delve重新启动kubernetes的一个组件,进而可以进行远程调试

3.4.1.杀掉 kube-apiserver 进程

  • 之所以杀掉 kube-apiserver 进程,是因为现在启动的kube-apiserver没有使用delve启动,无法进行远程调试

  • 我们先查看一下当前 kube-apiserver 的一些信息。可以看到:进程号pid是 476450

    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# ps -ef | grep kube-apiserver
    root      476450  448854 10 12月22 pts/0  00:00:49 /root/go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver --authorization-mode=Node,RBAC  --cloud-provider= --cloud-config=   --v=3 --vmodule= --audit-policy-file=/tmp/kube-audit-policy-file --audit-log-path=/tmp/kube-apiserver-audit.log --authorization-webhook-config-file= --authentication-token-webhook-config-file= --cert-dir=/var/run/kubernetes --egress-selector-config-file=/tmp/kube_egress_selector_configuration.yaml --client-ca-file=/var/run/kubernetes/client-ca.crt --kubelet-client-certificate=/var/run/kubernetes/client-kube-apiserver.crt --kubelet-client-key=/var/run/kubernetes/client-kube-apiserver.key --service-account-key-file=/tmp/kube-serviceaccount.key --service-account-lookup=true --service-account-issuer=https://kubernetes.default.svc --service-account-jwks-uri=https://kubernetes.default.svc/openid/v1/jwks --service-account-signing-key-file=/tmp/kube-serviceaccount.key --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,Priority,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,NodeRestriction --disable-admission-plugins= --admission-control-config-file= --bind-address=0.0.0.0 --secure-port=6443 --tls-cert-file=/var/run/kubernetes/serving-kube-apiserver.crt --tls-private-key-file=/var/run/kubernetes/serving-kube-apiserver.key --storage-backend=etcd3 --storage-media-type=application/vnd.kubernetes.protobuf --etcd-servers=http://127.0.0.1:2379 --service-cluster-ip-range=10.0.0.0/24 --feature-gates=AllAlpha=false --external-hostname=localhost --requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file=/var/run/kubernetes/request-header-ca.crt --requestheader-allowed-names=system:auth-proxy --proxy-client-cert-file=/var/run/kubernetes/client-auth-proxy.crt --proxy-client-key-file=/var/run/kubernetes/client-auth-proxy.key --cors-allowed-origins=/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$
    root      483326  468865  0 00:00 pts/1    00:00:00 grep --color=auto kube-apiserver
    
  • 其中,这部分信息,就是当前kube-apiserver进程启动的命令。记录一下,我们等会在用delve启动apiserver的时候,也需要用(你需要记录你自己的,不能直接用我的)

    • 这部分内容,前面是/root/go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver apiserver可执行文件的路径,我们等会也使用这个可执行文件
    • -- 后面 就是 启动的命令行参数,我们等会也要用这个命令行参数,和原来启动的apiserver保持一致
    /root/go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver --authorization-mode=Node,RBAC  --cloud-provider= --cloud-config=   --v=3 --vmodule= --audit-policy-file=/tmp/kube-audit-policy-file --audit-log-path=/tmp/kube-apiserver-audit.log --authorization-webhook-config-file= --authentication-token-webhook-config-file= --cert-dir=/var/run/kubernetes --egress-selector-config-file=/tmp/kube_egress_selector_configuration.yaml --client-ca-file=/var/run/kubernetes/client-ca.crt --kubelet-client-certificate=/var/run/kubernetes/client-kube-apiserver.crt --kubelet-client-key=/var/run/kubernetes/client-kube-apiserver.key --service-account-key-file=/tmp/kube-serviceaccount.key --service-account-lookup=true --service-account-issuer=https://kubernetes.default.svc --service-account-jwks-uri=https://kubernetes.default.svc/openid/v1/jwks --service-account-signing-key-file=/tmp/kube-serviceaccount.key --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,Priority,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,NodeRestriction --disable-admission-plugins= --admission-control-config-file= --bind-address=0.0.0.0 --secure-port=6443 --tls-cert-file=/var/run/kubernetes/serving-kube-apiserver.crt --tls-private-key-file=/var/run/kubernetes/serving-kube-apiserver.key --storage-backend=etcd3 --storage-media-type=application/vnd.kubernetes.protobuf --etcd-servers=http://127.0.0.1:2379 --service-cluster-ip-range=10.0.0.0/24 --feature-gates=AllAlpha=false --external-hostname=localhost --requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file=/var/run/kubernetes/request-header-ca.crt --requestheader-allowed-names=system:auth-proxy --proxy-client-cert-file=/var/run/kubernetes/client-auth-proxy.crt --proxy-client-key-file=/var/run/kubernetes/client-auth-proxy.key --cors-allowed-origins=/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$
    
  • 杀掉kube-apiserver。你需要把进程号改成你自己的

    kill -9 476450
    
  • 然后再查看下当前有哪些kubernetes的进程。可以看到,kube-apiserver已经没有了

    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# ps -a | grep kube
     476748 pts/0    00:00:27 kube-controller
     476754 pts/0    00:00:04 kube-scheduler
     476913 pts/0    00:00:28 kubelet
     477468 pts/0    00:00:00 kube-proxy
    
  • 此时,在集群启动终端上,也会提示,API server被意外终止了

    Alternatively, you can write to the default kubeconfig:
    
      export KUBERNETES_PROVIDER=local
    
      cluster/kubectl.sh config set-cluster local --server=https://localhost:6443 --certificate-authority=/var/run/kubernetes/server-ca.crt
      cluster/kubectl.sh config set-credentials myself --client-key=/var/run/kubernetes/client-admin.key --client-certificate=/var/run/kubernetes/client-admin.crt
      cluster/kubectl.sh config set-context local --cluster=local --user=myself
      cluster/kubectl.sh config use-context local
      cluster/kubectl.sh
    hack/local-up-cluster.sh:行 1223: 476450 已杀死               ${CONTROLPLANE_SUDO} "${GO_OUT}/kube-apiserver" "${authorizer_arg}" "${priv_arg}" ${runtime_config} ${cloud_config_arg} "${advertise_address}" "${node_port_range}" --v="${LOG_LEVEL}" --vmodule="${LOG_SPEC}" --audit-policy-file="${AUDIT_POLICY_FILE}" --audit-log-path="${LOG_DIR}/kube-apiserver-audit.log" --authorization-webhook-config-file="${AUTHORIZATION_WEBHOOK_CONFIG_FILE}" --authentication-token-webhook-config-file="${AUTHENTICATION_WEBHOOK_CONFIG_FILE}" --cert-dir="${CERT_DIR}" --egress-selector-config-file="${EGRESS_SELECTOR_CONFIG_FILE:-}" --client-ca-file="${CERT_DIR}/client-ca.crt" --kubelet-client-certificate="${CERT_DIR}/client-kube-apiserver.crt" --kubelet-client-key="${CERT_DIR}/client-kube-apiserver.key" --service-account-key-file="${SERVICE_ACCOUNT_KEY}" --service-account-lookup="${SERVICE_ACCOUNT_LOOKUP}" --service-account-issuer="https://kubernetes.default.svc" --service-account-jwks-uri="https://kubernetes.default.svc/openid/v1/jwks" --service-account-signing-key-file="${SERVICE_ACCOUNT_KEY}" --enable-admission-plugins="${ENABLE_ADMISSION_PLUGINS}" --disable-admission-plugins="${DISABLE_ADMISSION_PLUGINS}" --admission-control-config-file="${ADMISSION_CONTROL_CONFIG_FILE}" --bind-address="${API_BIND_ADDR}" --secure-port="${API_SECURE_PORT}" --tls-cert-file="${CERT_DIR}/serving-kube-apiserver.crt" --tls-private-key-file="${CERT_DIR}/serving-kube-apiserver.key" --storage-backend="${STORAGE_BACKEND}" --storage-media-type="${STORAGE_MEDIA_TYPE}" --etcd-servers="http://${ETCD_HOST}:${ETCD_PORT}" --service-cluster-ip-range="${SERVICE_CLUSTER_IP_RANGE}" --feature-gates="${FEATURE_GATES}" --external-hostname="${EXTERNAL_HOSTNAME}" --requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file="${CERT_DIR}/request-header-ca.crt" --requestheader-allowed-names=system:auth-proxy --proxy-client-cert-file="${CERT_DIR}/client-auth-proxy.crt" --proxy-client-key-file="${CERT_DIR}/client-auth-proxy.key" --cors-allowed-origins="${API_CORS_ALLOWED_ORIGINS}" > "${APISERVER_LOG}" 2>&1
    W1223 00:07:55]: API server terminated unexpectedly, see /tmp/kube-apiserver.log
    
    

3.4.2.使用delve命令,重新启动 kube-apiserver

  • 因为已经有了kube-apiserver的可执行文件,所以可以直接使用 dlv --headless exec 启动delve的debug server。

    • 我们需要指定 apiserver 的 可执行文件路径

    • 另外,我们这次就不让delve给我们自动选择端口号了,我们直接指定一个确定的端口号:–listen=:12345,不写ip默认使用localhost

    • 还有,调试apiserver,必须使用delve API版本2,否则会出错。即:–api-version=2

    • 另外,为了方便查看调试过程中的错误,我们将日志打印出来,即:–log --log-output=debugger,gdbwire,lldbout,debuglineerr,rpc,dap,fncall,minidump --log-dest=/root/delve-log/log

    • 最后,添加 -- 分隔符,后面就直接把 原API server 的 命令行参数 拷贝过来。不过需要注意,最后一个参数 --cors-allowed-origins,它的value有特殊符号,需要用引号包裹上:"/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$"

      dlv --headless exec /root/go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver --listen=:12345 --api-version=2 --log --log-output=debugger,gdbwire,lldbout,debuglineerr,rpc,dap,fncall,minidump --log-dest=/root/delve-log/log -- --authorization-mode=Node,RBAC  --cloud-provider= --cloud-config=   --v=3 --vmodule= --audit-policy-file=/tmp/kube-audit-policy-file --audit-log-path=/tmp/kube-apiserver-audit.log --authorization-webhook-config-file= --authentication-token-webhook-config-file= --cert-dir=/var/run/kubernetes --egress-selector-config-file=/tmp/kube_egress_selector_configuration.yaml --client-ca-file=/var/run/kubernetes/client-ca.crt --kubelet-client-certificate=/var/run/kubernetes/client-kube-apiserver.crt --kubelet-client-key=/var/run/kubernetes/client-kube-apiserver.key --service-account-key-file=/tmp/kube-serviceaccount.key --service-account-lookup=true --service-account-issuer=https://kubernetes.default.svc --service-account-jwks-uri=https://kubernetes.default.svc/openid/v1/jwks --service-account-signing-key-file=/tmp/kube-serviceaccount.key --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,Priority,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,NodeRestriction --disable-admission-plugins= --admission-control-config-file= --bind-address=0.0.0.0 --secure-port=6443 --tls-cert-file=/var/run/kubernetes/serving-kube-apiserver.crt --tls-private-key-file=/var/run/kubernetes/serving-kube-apiserver.key --storage-backend=etcd3 --storage-media-type=application/vnd.kubernetes.protobuf --etcd-servers=http://127.0.0.1:2379 --service-cluster-ip-range=10.0.0.0/24 --feature-gates=AllAlpha=false --external-hostname=localhost --requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file=/var/run/kubernetes/request-header-ca.crt --requestheader-allowed-names=system:auth-proxy --proxy-client-cert-file=/var/run/kubernetes/client-auth-proxy.crt --proxy-client-key-file=/var/run/kubernetes/client-auth-proxy.key --cors-allowed-origins="/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$"
      
  • 命令执行之后,程序会停在光标位置,但是没有结束。这就是在等待远程连接

4.使用delve远程连接Debug Server

4.1.演示 命令行 远程连接kube-apiserver

  • 打开我们ubuntu的另一个终端,或者在你的物理机上打开一个终端(物理机上需要安装delve)

  • 使用 dlv connect localhost:12345 远程连接到kube-apiserver上

    root@graham-virtual-machine:~# dlv connect 127.0.0.1:12345
    Type 'help' for list of commands.
    (dlv)
    
  • 使用 break 在cmd/kube-apiserver/apiserver.go文件的33行打个断点,也就是main函数的第一行代码

    (dlv) break cmd/kube-apiserver/apiserver.go:33
    Breakpoint 1 set at 0x5233854 for main.main() cmd/kube-apiserver/apiserver.go:33
    
  • 使用 continue 执行到断点位置

    (dlv) continue
    > main.main() cmd/kube-apiserver/apiserver.go:33 (hits goroutine(1):1 total:1) (PC: 0x5233854)
    
  • 使用 next 执行到下一行

    (dlv) next
    > main.main() cmd/kube-apiserver/apiserver.go:34 (PC: 0x5233860)
    
  • 使用 args 查看当前函数的参数。输出空,因为当前是main函数没有参数

    (dlv) args
    (no args)
    
  • 使用 locals 查看当前的局部变量

    (dlv) locals
    command = ("*k8s.io/kubernetes/vendor/github.com/spf13/cobra.Command")(0xc000890c80)
    
  • 使用 vars 查看当前包级别的变量。可以看到很多,因为apiserver导入了很多包,包再导入包,有很多变量

    (dlv) vars
    .....输出特别多
    
  • 使用 step 进入当前行的调用函数

    (dlv) step
    > k8s.io/kubernetes/vendor/k8s.io/component-base/cli.Run() vendor/k8s.io/component-base/cli/run.go:45 (PC: 0xcf648f)
    
  • 使用 continue 直接让程序运行起来

    (dlv) continue
    
  • 在 delve 的 debug server 端,可以看到打出来的日志,api-server已经运行起来了

4.2.演示 VS Code 远程连接 kube-apiserver

4.2.1.开放12345端口,否则VSCode会连接失败

  • 因为要远程连接,而且我们固定使用12345端口,所以我们事先把ubuntu的12345端口开启

    • 查看一下当前开放了哪些端口,发现12345没有开启。所以我们本机上的VSCode肯定是连不上这里delve启动的debug server的

      root@graham-virtual-machine:~# ufw status
      状态: 激活
      
      至                          动作          来自
      -                          --          --
      22                         ALLOW       Anywhere
      22 (v6)                    ALLOW       Anywhere (v6)
      
    • 然后开放12345端口

      root@graham-virtual-machine:~# ufw allow 12345
      规则已添加
      规则已添加 (v6)
      
    • 再看一下开放端口,可以看到,12345已经开启了

      root@graham-virtual-machine:~# ufw status
      状态: 激活
      
      至                          动作          来自
      -                          --          --
      22                         ALLOW       Anywhere
      12345                      ALLOW       Anywhere
      22 (v6)                    ALLOW       Anywhere (v6)
      12345 (v6)                 ALLOW       Anywhere (v6)
      

4.2.2.使用delve启动api-server等待连接

  • 端口开放后,我们还是执行和 3.5 中一样的命令,用 delve 把api-server启动起来,等待远程连接
dlv --headless exec /root/go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver --listen=:12345 --api-version=2 --log --log-output=debugger,gdbwire,lldbout,debuglineerr,rpc,dap,fncall,minidump --log-dest=/root/delve-log/log -- --authorization-mode=Node,RBAC  --cloud-provider= --cloud-config=   --v=3 --vmodule= --audit-policy-file=/tmp/kube-audit-policy-file --audit-log-path=/tmp/kube-apiserver-audit.log --authorization-webhook-config-file= --authentication-token-webhook-config-file= --cert-dir=/var/run/kubernetes --egress-selector-config-file=/tmp/kube_egress_selector_configuration.yaml --client-ca-file=/var/run/kubernetes/client-ca.crt --kubelet-client-certificate=/var/run/kubernetes/client-kube-apiserver.crt --kubelet-client-key=/var/run/kubernetes/client-kube-apiserver.key --service-account-key-file=/tmp/kube-serviceaccount.key --service-account-lookup=true --service-account-issuer=https://kubernetes.default.svc --service-account-jwks-uri=https://kubernetes.default.svc/openid/v1/jwks --service-account-signing-key-file=/tmp/kube-serviceaccount.key --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,Priority,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,NodeRestriction --disable-admission-plugins= --admission-control-config-file= --bind-address=0.0.0.0 --secure-port=6443 --tls-cert-file=/var/run/kubernetes/serving-kube-apiserver.crt --tls-private-key-file=/var/run/kubernetes/serving-kube-apiserver.key --storage-backend=etcd3 --storage-media-type=application/vnd.kubernetes.protobuf --etcd-servers=http://127.0.0.1:2379 --service-cluster-ip-range=10.0.0.0/24 --feature-gates=AllAlpha=false --external-hostname=localhost --requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file=/var/run/kubernetes/request-header-ca.crt --requestheader-allowed-names=system:auth-proxy --proxy-client-cert-file=/var/run/kubernetes/client-auth-proxy.crt --proxy-client-key-file=/var/run/kubernetes/client-auth-proxy.key --cors-allowed-origins="/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$"

4.2.3.使用VSCode连接delve的debug server

  • 然后来到物理机的VS Code上,使用VSCode打开kubernetes的源代码,然后在kubernetes项目的 .vscode目录 下,创建一个launch.json

    • launch.json 是什么,可以看我的另一篇博客 知识点积累 的 9.1.1

    • 注意:request必须是attach、mode必须是remote

      {
          "version": "0.2.0",
          "configurations": {
              "name": "Connect to server",
              "type": "go",
              "request": "attach",
              "mode": "remote",
              "port": 12345,
              "host": "192.168.245.146"
          }
      }
      

      在这里插入图片描述

  • 并在 VS Code 上,给程序打一个断点。我们还把断点打在 apiserver.go的main函数第一句,也就是33行

    在这里插入图片描述

  • 然后,点开VSCode的左侧Debug页面,点击Debug按钮,稍等一下后,发现程序已经运行,并且停在了我们的断点处,之后就可以使用VSCode进行调试了

    在这里插入图片描述

  • 我们在VSCode把程序放行后,可以在Debug Server终端看到打印信息,ApiServer已经启动起来了
    在这里插入图片描述

  • 如果你发现终端报错,启动失败了,报错:Error: “kube-apiserver” does not take any arguments

    • 大概率是有可能是 dlv --headless exec 命令的 -- 后面的apiserver参数有问题。
    • 因为我们执行的这条命令特别长,屏幕一般又比较小,命令在终端显示的时候,肯定会发生换行
    • 有些ssh终端工具,会在命令的换行部分,处理的有问题。比如我使用的MobaXterm工具,就多次遇到这个问题。
    • 解决方法:先把命令复制到一个文本编辑器里,确保这条命令能显示成一行,再复制到终端去执行,就可以启动成功了
    • kubernetes issues中也有人遇到过这个问题:https://github.com/kubernetes/kubernetes/issues/94758

4.3.演示 Goland 远程连接 kube-apiserver

4.3.1.和VS Code一样,先开放12345端口,并使用delve启动Api-server

  • 开放12345端口

    ufw allow 12345
    
  • 端口开放后,我们还是执行和 4.2.2 中一样的命令,用 delve 把api-server启动起来,等待远程连接

    dlv --headless exec /root/go/src/k8s.io/kubernetes/_output/local/bin/linux/amd64/kube-apiserver --listen=:12345 --api-version=2 --log --log-output=debugger,gdbwire,lldbout,debuglineerr,rpc,dap,fncall,minidump --log-dest=/root/delve-log/log -- --authorization-mode=Node,RBAC  --cloud-provider= --cloud-config=   --v=3 --vmodule= --audit-policy-file=/tmp/kube-audit-policy-file --audit-log-path=/tmp/kube-apiserver-audit.log --authorization-webhook-config-file= --authentication-token-webhook-config-file= --cert-dir=/var/run/kubernetes --egress-selector-config-file=/tmp/kube_egress_selector_configuration.yaml --client-ca-file=/var/run/kubernetes/client-ca.crt --kubelet-client-certificate=/var/run/kubernetes/client-kube-apiserver.crt --kubelet-client-key=/var/run/kubernetes/client-kube-apiserver.key --service-account-key-file=/tmp/kube-serviceaccount.key --service-account-lookup=true --service-account-issuer=https://kubernetes.default.svc --service-account-jwks-uri=https://kubernetes.default.svc/openid/v1/jwks --service-account-signing-key-file=/tmp/kube-serviceaccount.key --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,Priority,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,NodeRestriction --disable-admission-plugins= --admission-control-config-file= --bind-address=0.0.0.0 --secure-port=6443 --tls-cert-file=/var/run/kubernetes/serving-kube-apiserver.crt --tls-private-key-file=/var/run/kubernetes/serving-kube-apiserver.key --storage-backend=etcd3 --storage-media-type=application/vnd.kubernetes.protobuf --etcd-servers=http://127.0.0.1:2379 --service-cluster-ip-range=10.0.0.0/24 --feature-gates=AllAlpha=false --external-hostname=localhost --requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-client-ca-file=/var/run/kubernetes/request-header-ca.crt --requestheader-allowed-names=system:auth-proxy --proxy-client-cert-file=/var/run/kubernetes/client-auth-proxy.crt --proxy-client-key-file=/var/run/kubernetes/client-auth-proxy.key --cors-allowed-origins="/127.0.0.1(:[0-9]+)?$,/localhost(:[0-9]+)?$"
    

4.3.2.使用 Goland 连接 delve的debug server

  • goland打开kubernetes项目,记得将分支切到1.24.0版本
  • 然后按照下面的步骤操作即可
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

5.使用Postman请求API Server

5.1.为什么要用Postman请求API server

  • 由于kubectl是存在informer机制的,会把从apiserver获取到了资源数据缓存下来,所以我们很多时候使用kubectl请求api-server,实际上都是走的缓存,并没有触发到kube-apiserver
  • 所以,我们使用postman这种无缓存机制的工具,直接发送https请求到apiserver,这样每个请求都能触发到apiserver

5.2.Postman请求API Server需要准备什么?

5.2.1.搞清楚我们需要做的事情

  • 由于 ApiServer 对外提供的访问方式,只有HTTPS协议的安全端口443。所以我们需要完成
  • 要保证ApiServer对到达的请求:
    • 验证 登陆信息 通过
    • 验证 鉴权 通过
  • 所以,最繁琐的配置就是,如何配置 登陆和鉴权

5.2.2.配置 登陆和鉴权 需要做什么

  • 由于在kubernetes中,所有的组件,包括正在运行的Pod,与API Server交互的方式都是使用Service Account
  • 所以,我们要想让 kube-apiserver 能认识外部的客户端,也需要为我们的客户端创建一个ServiceAccount,创建好ServiceAccount后,我们需要为这个Service Account做两件事
    • 为这个Service Account绑定一个Secret,提取这个Secret的证书,并从Secret中获取Token,都交给Postman,每次请求的时候都携带过来
    • 为这个Service Account授予权限,把它的权限扩大到能够访问API对象。这里使用的是ClusterRole、RoleBinding的方式

5.2.3.配置 登陆和鉴权 操作 主要分6步

在这里插入图片描述

5.3.在集群中 配置 登陆和鉴权

5.3.1.创建一个ServiceAccount

  • 先查看一下当前集群中有哪些资源。可以看到,环境很干净,只有一个默认的sa

    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cd ~/go/src/k8s.io.kubernetes
    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh get sa
    NAME      SECRETS   AGE
    default   0         162m
    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh get secret
    No resources found in default namespace.
    
  • 然后我们创建一个ServiceAccount,名称为 forpostman

    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh create sa forpostman
    serviceaccount/forpostman created
    
  • 然后我们describe一个这个sa。可以看到,这个sa的Tokens为空,没有绑定任何的Secret。

    • 因为kubernetes1.24及以后,就不再自动为sa创建并绑定secret了,需要我们手动创建并绑定
    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh describe sa forpostman
    Name:                forpostman
    Namespace:           default
    Labels:              &lt;none&gt;
    Annotations:         &lt;none&gt;
    Image pull secrets:  &lt;none&gt;
    Mountable secrets:   &lt;none&gt;
    Tokens:              &lt;none&gt;
    Events:              &lt;none&gt;创建
    

5.3.2.创建一个Secret,并绑定到ServiceAccount上去

  • 我们给出了一个Secret的yaml文件。创建一个名称为 postman-sa-secret 的secret,并使用annotations 的方式,将之绑定到 一个指定的service-account上,即forpostman这个sa

    apiVersion: v1
    kind: Secret
    metadata: 
      name: postman-sa-secret
      annotations:
        kubernetes.io/service-account.name: forpostman
    type: kubernetes.io/service-account-token
    
  • 我们create一下这个secret,然后get查看一下,已经有这个secret了,输出yaml能看到自动生成的 证书和 token

    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh create -f ~/zgy/go_yaml/postman-sa-secret.yaml
    secret/postman-sa-secret created
    
    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh get secret
    NAME                TYPE                                  DATA   AGE
    postman-sa-secret   kubernetes.io/service-account-token   3      28s
    
    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh describe secret postman-sa-secret
    Name:         postman-sa-secret
    Namespace:    default
    Labels:       <none>
    Annotations:  kubernetes.io/service-account.name: forpostman
                  kubernetes.io/service-account.uid: 44d471a4-2724-410e-a0e0-838568fd8f9b
    
    Type:  kubernetes.io/service-account-token
    
    Data
    ====
    ca.crt:     1310 bytes
    namespace:  7 bytes
    token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IjdEeERLbXpSVHlFd2FOMUpUVTNaTXl3TkNBeE5nQ28xbXpIUkxESGVQa1UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InBvc3RtYW4tc2Etc2VjcmV0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImZvcnBvc3RtYW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI0NGQ0NzFhNC0yNzI0LTQxMGUtYTBlMC04Mzg1NjhmZDhmOWIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpmb3Jwb3N0bWFuIn0.m9Jo_ycqz3j6J0TkTHpiQSGbE1Z2QjVTxcYP2dge7YxJn5Adl3r_K9Lt-1-WFs2d3CsBIEWFJX8z1IQNiogyy41nr2DWyepll5vlafDgVh9eTlrz7ktVX6hRshVBQOz4v1qrcPFnbFxdtqXWr_W0Y_7viEuQNX4Yv9P4PqWGUawlQuUQoI0hKzC8pXYMQr_VSneQ3Uh_lqotOLrkf4H4L4b13eTg7La0C4lDWdsssPJQhv_VcW-m8H_jso6tfTQFQ5YQK-_r6gmbZayX_Xi4KnYHa2g13pyJy_xeJ57UlZYs2Wr7057FLChNXoui8-pHF4jby3d0-kusZkMJWAXiuw
    
  • 再describe一下forpostman这个sa,可以看到已经有一个token绑定上来了,就是postman-sa-secret

    • 这个其实就是 登陆 的配置,postman发送请求的时候,需要在http header里携带这个 secret 的 token,才能登陆成功
    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh describe sa forpostman
    Name:                forpostman
    Namespace:           default
    Labels:              <none>
    Annotations:         <none>
    Image pull secrets:  <none>
    Mountable secrets:   <none>
    Tokens:              postman-sa-secret
    Events:              <none>
    

5.3.3.为ServiceAccount授权

  • 虽然secret已经有token了,根据token能找到 对应namespace下的ServiceAccount,即default:forpostman,

  • 但是forpostman还没有任何权限,我们需要给 forpostman 授权。

    • 我们先看一下系统中有哪些权限,我们直接选一个权限大的,设置给这个secret就可以了,不用自己再创建了。从下面来看,集群角色还真不少,我们选一个权限大的:cluster-admin

      root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh get clusterrole
      NAME                                                                   CREATED AT
      admin                                                                  2023-12-23T08:11:11Z
      cluster-admin                                                          2023-12-23T08:11:11Z
      edit                                                                   2023-12-23T08:11:11Z
      system:aggregate-to-admin                                              2023-12-23T08:11:11Z
      system:aggregate-to-edit                                               2023-12-23T08:11:11Z
      system:aggregate-to-view                                               2023-12-23T08:11:11Z
      system:auth-delegator                                                  2023-12-23T08:11:11Z
      ........
      
    • 创建一个rolebinding,名称为forpostmanadmin,其中的集群角色是cluster-admin,绑定的对象是ns=default下的serviceAccount:forpostman

      root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh create rolebinding forpostmanadmin --clusterrole cluster-admin --serviceaccount default:forpostman
      rolebinding.rbac.authorization.k8s.io/forpostmanadmin created
      
      root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh get rolebinding
      NAME              ROLE                        AGE
      forpostmanadmin   ClusterRole/cluster-admin   38s
      
  • 至此,我们完成了 集群中 登录鉴权的配置

    • Secret 中 的token有了,实现了登陆的目标
    • ServiceAccount 也具有了 cluster-admin 的权限

5.4.为Postman设置证书和token

  • 从我们创建的secret:postman-sa-secret 中,提出证书

    • 将secret的data中,ca.crt 的内容,以 base64 的编码,写到 /tmp/ca.crt,此即为证书
    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# cluster/kubectl.sh get secret postman-sa-secret -o jsonpath="{.data['ca\.crt']}" | base64 -d > /tmp/ca.crt
    
    root@graham-virtual-machine:~/go/src/k8s.io/kubernetes# ls /tmp/ca.*
    /tmp/ca.crt
    
    • 我们将这个证书,复制到postman的主机上

      C:\Users\tmp> scp root@192.168.245.146:/tmp/ca.crt ./ca.crt
      root@192.168.245.146's password:
      ca.crt                                                                                100% 1310   638.4KB/s   00:00
      
      C:\Users\tmp> ls ca.*
      
          目录: C:\Users\Gesang\AppData\Local\Postman
          
      Mode                 LastWriteTime         Length Name
      ----                 -------------         ------ ----
      -a----        2023/12/23     20:21           1310 ca.crt
      
  • postman创建一个请求,填写url:https://192.168.245.146:6443/apis ,请求方式是Get

    • 6443:启动本地集群的话,暴漏的API端口就是 6443

    • /apis:获取apiserver的api object的endpoints

    • 为请求设置token

      在这里插入图片描述

    • 设置证书

      在这里插入图片描述

      在这里插入图片描述

  • 发送请求,已经有响应了,response如下:

    {
        "kind": "APIGroupList",
        "apiVersion": "v1",
        "groups": [
            {
                "name": "apiregistration.k8s.io",
                "versions": [
                    {
                        "groupVersion": "apiregistration.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "apiregistration.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "apps",
                "versions": [
                    {
                        "groupVersion": "apps/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "apps/v1",
                    "version": "v1"
                }
            },
            {
                "name": "events.k8s.io",
                "versions": [
                    {
                        "groupVersion": "events.k8s.io/v1",
                        "version": "v1"
                    },
                    {
                        "groupVersion": "events.k8s.io/v1beta1",
                        "version": "v1beta1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "events.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "authentication.k8s.io",
                "versions": [
                    {
                        "groupVersion": "authentication.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "authentication.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "authorization.k8s.io",
                "versions": [
                    {
                        "groupVersion": "authorization.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "authorization.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "autoscaling",
                "versions": [
                    {
                        "groupVersion": "autoscaling/v2",
                        "version": "v2"
                    },
                    {
                        "groupVersion": "autoscaling/v1",
                        "version": "v1"
                    },
                    {
                        "groupVersion": "autoscaling/v2beta1",
                        "version": "v2beta1"
                    },
                    {
                        "groupVersion": "autoscaling/v2beta2",
                        "version": "v2beta2"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "autoscaling/v2",
                    "version": "v2"
                }
            },
            {
                "name": "batch",
                "versions": [
                    {
                        "groupVersion": "batch/v1",
                        "version": "v1"
                    },
                    {
                        "groupVersion": "batch/v1beta1",
                        "version": "v1beta1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "batch/v1",
                    "version": "v1"
                }
            },
            {
                "name": "certificates.k8s.io",
                "versions": [
                    {
                        "groupVersion": "certificates.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "certificates.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "networking.k8s.io",
                "versions": [
                    {
                        "groupVersion": "networking.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "networking.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "policy",
                "versions": [
                    {
                        "groupVersion": "policy/v1",
                        "version": "v1"
                    },
                    {
                        "groupVersion": "policy/v1beta1",
                        "version": "v1beta1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "policy/v1",
                    "version": "v1"
                }
            },
            {
                "name": "rbac.authorization.k8s.io",
                "versions": [
                    {
                        "groupVersion": "rbac.authorization.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "rbac.authorization.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "storage.k8s.io",
                "versions": [
                    {
                        "groupVersion": "storage.k8s.io/v1",
                        "version": "v1"
                    },
                    {
                        "groupVersion": "storage.k8s.io/v1beta1",
                        "version": "v1beta1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "storage.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "admissionregistration.k8s.io",
                "versions": [
                    {
                        "groupVersion": "admissionregistration.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "admissionregistration.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "apiextensions.k8s.io",
                "versions": [
                    {
                        "groupVersion": "apiextensions.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "apiextensions.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "scheduling.k8s.io",
                "versions": [
                    {
                        "groupVersion": "scheduling.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "scheduling.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "coordination.k8s.io",
                "versions": [
                    {
                        "groupVersion": "coordination.k8s.io/v1",
                        "version": "v1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "coordination.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "node.k8s.io",
                "versions": [
                    {
                        "groupVersion": "node.k8s.io/v1",
                        "version": "v1"
                    },
                    {
                        "groupVersion": "node.k8s.io/v1beta1",
                        "version": "v1beta1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "node.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "discovery.k8s.io",
                "versions": [
                    {
                        "groupVersion": "discovery.k8s.io/v1",
                        "version": "v1"
                    },
                    {
                        "groupVersion": "discovery.k8s.io/v1beta1",
                        "version": "v1beta1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "discovery.k8s.io/v1",
                    "version": "v1"
                }
            },
            {
                "name": "flowcontrol.apiserver.k8s.io",
                "versions": [
                    {
                        "groupVersion": "flowcontrol.apiserver.k8s.io/v1beta2",
                        "version": "v1beta2"
                    },
                    {
                        "groupVersion": "flowcontrol.apiserver.k8s.io/v1beta1",
                        "version": "v1beta1"
                    }
                ],
                "preferredVersion": {
                    "groupVersion": "flowcontrol.apiserver.k8s.io/v1beta2",
                    "version": "v1beta2"
                }
            }
        ]
    }
    
  • 如果postman报错:Could not send request Error: Request timed out,请把服务器的6443端口开启

    ufw allow 6443
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/266773.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

INFINI Gateway 如何防止大跨度查询

背景 业务每天生成一个日期后缀的索引&#xff0c;写入当日数据。 业务查询有时会查询好多天的数据&#xff0c;导致负载告警。 现在想对查询进行限制–只允许查询一天的数据&#xff08;不限定是哪天&#xff09;&#xff0c;如果想查询多天的数据就走申请。 技术分析 在每…

排序算法——桶排序

把数据放进若干个桶&#xff0c;然后在桶里用其他排序&#xff0c;近乎分治思想。从数值的低位到高位依次排序&#xff0c;有几位就排序几次。例如二位数就排两次&#xff0c;三位数就排三次&#xff0c;依次按照个十百...的顺序来排序。 第一次排序&#xff1a;50 12 …

二级指针的作用 -- 将变量从函数中带出

使用一级指针不能将变量带出 void test(int *p) {static int nub 10; /*使用static是保证函数结束, 变量依然存在, 不然即使将它带出来, 函数结束时这片内存已经被释放了就没有意义了*/p &nub; }int main(void) {int *p NULL;test(p);printf("%d",*p);return …

计算机网络-网络层

计算机网络-网络层 以下笔记整理为哔哩哔哩湖科大教书匠的《计算机网络微课堂》的教学视频。 链接&#xff1a;计算机网络微课堂 1. 网络层概述 1.1 网络层的主要任务是实现网络互联&#xff0c;进而实现数据包在各网络之间的传输。 1.2 要实现网络层任务&#xff0c;需要解决…

java中静态修饰符(static)的使用

static-静态 修饰属性 静态属性,也称为静态变量 类变量等 static 数据类型 属性名; 使用 静态内容独立存放在方法区 静态内容在内存中只有一份,被该类所有对象共享 普通属性所有对象在对象内容中都有一份 可以通过类名.静态属性名的方式直接访问静态属性 静态属性封装之…

Milvus数据一致性介绍及选择方法

1、Milvus 时钟机制 Milvus 通过时间戳水印来保障读链路的一致性&#xff0c;如下图所示&#xff0c;在往消息队列插入数据时&#xff0c; Milvus 不光会为这些插入记录打上时间戳&#xff0c;还会不间断地插入同步时间戳&#xff0c;以图中同步时间戳 syncTs1 为例&#xff0…

MySQL 数据库系列课程 04:MySQL Workbench的安装

Workbench 是 MySQL 官方推出的免费的强大的可视化工具&#xff0c;不熟悉命令行工具的人&#xff0c;可以安装这一款软件&#xff0c;通过编写 SQL 进行数据库中数据的增删改查操作&#xff0c;接下来我们详细说明一下 Workbench 的安装。 一、Windows安装Workbench &#x…

postgresql vacuum流程分析

概述 VACUUM是postgresql MVCC机制不可分割的组成部分。 postgresql在管理同一个元组的多个版本时&#xff0c;采取在堆表页面上从老版本到新版本放置元组的方法&#xff0c;每个元组都记录了xmax和xmin用于判断其可见性。这样的好处是&#xff08;1&#xff09;在索引键没有…

HarmonyOS - 基础组件绘制

文章目录 所有组件开发 tipsBlankTextImageTextInputButtonLoadingProgress 本文改编自&#xff1a;<HarmonyOS第一课>从简单的页面开始 https://developer.huawei.com/consumer/cn/training/course/slightMooc/C101667360160710997 所有组件 在 macOS 上&#xff0c;组…

设计模式篇---职责链模式

文章目录 概念结构实例总结 概念 职责链模式&#xff1a;避免将一个请求的发送者与接收者耦合在一起&#xff0c;让多个对象都有机会处理请求。将接收请求的对象连接成一条链&#xff0c;并且沿着这条链传递请求&#xff0c;直到有一个对象能够处理它为止。 比如大学期间&…

DLLNotFoundException:xxx tolua... 错误打印

DLLNotFoundException:xxx tolua... 错误打印 一、DLLNotFoundException介绍二、Plugins文件夹文件目录结构如下&#xff1a; 三、Plugins中的Android文件夹四、Plugins中的IOS文件夹这里不说了没测试过不过原理应该也是选择对应的平台即可五、Plugins中的x86和X86_64文件夹 一…

C# Onnx Yolov8 Detect yolov8n、yolov8s、yolov8m、yolov8l、yolov8x 对比

目录 效果 yolov8n yolov8s yolov8m yolov8l yolov8x 模型信息 项目 代码 下载 C# Onnx Yolov8 Detect yolov8n、yolov8s、yolov8m、yolov8l、yolov8x 对比 效果 yolov8n yolov8s yolov8m yolov8l yolov8x 模型信息 Model Properties ------------------------- d…

持续集成交付CICD:Jira 远程触发 Jenkins 实现更新 GitLab 分支

目录 一、实验 1.环境 2.GitLab 查看项目 3.Jira新建模块 4. Jira 通过Webhook 触发Jenkins流水线 3.Jira 远程触发 Jenkins 实现更新 GitLab 分支 二、问题 1.Jira 配置网络钩子失败 2. Jira 远程触发Jenkins 报错 一、实验 1.环境 &#xff08;1&#xff09;主机 …

Python算法例26 落单的数Ⅳ

1. 问题描述 给定数组&#xff0c;除了一个数出现一次外&#xff0c;所有数都出现两次&#xff0c;并且所有出现两次的数都挨着&#xff0c;找出出现一次的数。 2. 问题示例 给出nums[3&#xff0c;3&#xff0c;2&#xff0c;2&#xff0c;4&#xff0c;5&#xff0c;5]&am…

HTML---网页布局

目录 文章目录 一.常见的网页布局 二.标准文档流 标准文档流常见标签 三.display属性 四.float属性 总结 一.常见网页布局 二.标准文档流 标准文档流常见标签 标准文档流的组成 块级元素<div>、<p>、<h1>-<h6>、<ul>、<ol>等内联元素<…

异常处理和单元测试python

一、实验题目 异常处理和单元测试 二、实验目的 了解异常的基本概念和常用异常类。掌握异常处理的格式、处理方法。掌握断言语句的作用和使用方法。了解单元测试的基本概念和作用。掌握在Python中使用测试模块进行单元测试的方法和步骤。 三、实验内容 编程实现如下功能&a…

智能优化算法应用:基于材料生成算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于材料生成算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于材料生成算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.材料生成算法4.实验参数设定5.算法结果6.…

交通流预测 | Matlab基于KNN-BiLSTM的交通流预测(对比SVR、LSTM、GRU、KNN-LSTM)

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 交通流预测 | Matlab基于KNN-BiLSTM的交通流预测&#xff08;对比SVR、LSTM、GRU、KNN-LSTM&#xff09; 程序设计 完整程序和数据获取方式&#xff1a;私信博主回复Matlab基于KNN-BiLSTM的交通流预测&#xff08;对…

Netty-2-数据编解码

解析编解码支持的原理 以编码为例&#xff0c;要将对象序列化成字节流&#xff0c;你可以使用MessageToByteEncoder或MessageToMessageEncoder类。 这两个类都继承自ChannelOutboundHandlerAdapter适配器类&#xff0c;用于进行数据的转换。 其中&#xff0c;对于MessageToMe…

3dsmax渲染太慢,用云渲染农场多少钱?

对于许多从事计算机图形设计的创作者来说&#xff0c;渲染速度慢是一个常见问题&#xff0c;尤其是对于那些追求极致出图效果的室内设计师和建筑可视化师&#xff0c;他们通常使用3ds Max这样的工具&#xff0c;而高质量的渲染经常意味着长时间的等待。场景复杂、细节丰富&…