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 shellconnect     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 commandtest        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"figoldflags="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 kube343645 pts/0    00:11:32 kube-apiserver343946 pts/0    00:03:53 kube-controller343948 pts/0    00:00:31 kube-scheduler344111 pts/0    00:03:58 kubelet344604 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 kube476748 pts/0    00:00:27 kube-controller476754 pts/0    00:00:04 kube-scheduler476913 pts/0    00:00:28 kubelet477468 pts/0    00:00:00 kube-proxy
    
  • 此时,在集群启动终端上,也会提示,API server被意外终止了

    Alternatively, you can write to the default kubeconfig:export KUBERNETES_PROVIDER=localcluster/kubectl.sh config set-cluster local --server=https://localhost:6443 --certificate-authority=/var/run/kubernetes/server-ca.crtcluster/kubectl.sh config set-credentials myself --client-key=/var/run/kubernetes/client-admin.key --client-certificate=/var/run/kubernetes/client-admin.crtcluster/kubectl.sh config set-context local --cluster=local --user=myselfcluster/kubectl.sh config use-context localcluster/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-secretannotations: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 createdroot@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      28sroot@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: forpostmankubernetes.io/service-account.uid: 44d471a4-2724-410e-a0e0-838568fd8f9bType:  kubernetes.io/service-account-tokenData
    ====
    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 createdroot@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.crtroot@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:00C:\Users\tmp> ls ca.*目录: C:\Users\Gesang\AppData\Local\PostmanMode                 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
    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

webrtc 视频 demo

webrtc 视频 demo webrtc网上封装的很多&#xff0c;demo很多都是一个页面里实现的&#xff0c;今天实现了个完整的 &#xff0c; A 发视频给 BA webrtc.html作为offer <!DOCTYPE html> <html id"home" lang"en"><head><meta http-e…

[转]阿里开源低代码引擎LowCodeEngine

一、什么是低代码引擎 低代码引擎是具备强大扩展能力的低代码研发框架&#xff0c;使用者只需要基于低代码引擎便可以快速定制符合自己业务需求的低代码平台。同时&#xff0c;低代码引擎还在标准低代码设计器的基础上提供了简单易用的定制扩展能力&#xff0c;能够满足业务独特…

Beyond Istio OSS——Istio服务网格的现状与未来

作者&#xff1a;宋净超&#xff08;Jimmy Song&#xff09;&#xff0c;原文地址&#xff1a;https://jimmysong.io/blog/beyond-istio-oss/本文根据笔者在 GIAC 深圳 2022 年大会上的的演讲《Beyond Istio OSS —— Istio 的现状及未来》[1] 整理而成&#xff0c;演讲幻灯片见…

js多维数组扁平化

数组扁平化&#xff0c;就是将多维数组碾平为一维数组&#xff0c;方便使用。 一&#xff1a;例如&#xff0c;一个二维数组 var arr [a, [b, 2], [c, 3, x]]&#xff0c;将其扁平化&#xff1a; 1. 通过 apply 借用数组的 concat 方法&#xff1a; [].concat.apply([], arr)…

第16讲 用户程序的结构与执行

转载于:https://www.cnblogs.com/atuo/p/5609843.html

两种方法清除Excel保护密码

一、利用VBA脚本直接清除 打Excel&#xff0c;打开脚本编辑器&#xff08;AltF11&#xff09;或者如图&#xff0c;右键sheet名称 输入代码并运行&#xff0c;即可清除密码保护&#xff1a; Sub DeletePW()ActiveSheet.Protect DrawingObjects:True, Contents:True, AllowFil…

jsonp-反向代理-CORS解决JS跨域问题的个人总结

jsonp-反向代理-CORS解决JS跨域问题的个人总结 网上说了很多很多&#xff0c;但是看完之后还是很混乱&#xff0c;所以我自己重新总结一下。解决 js 跨域问题一共有8种方法&#xff0c; jsonp&#xff08;只支持 get&#xff09;反向代理CORSdocument.domain iframe 跨域windo…

EasyNetQ-用于使用 RabbitMQ 的 .NET API开源的工具库

Part1介绍EasyNetQ 的目标是提供一个库&#xff0c;用于在 .NET 中使用 RabbitMQ 尽可能简单。为了做到这一点&#xff0c;它通过强制执行一些简单的约定来以灵活性换取简单性。这些包括&#xff1a;消息应该由 .NET 类型表示。消息应按其 .NET 类型进行路由。这意味着消息是由…

C# 实例解释面向对象编程中的依赖反转原则

在面向对象编程中&#xff0c;SOLID 是五个设计原则的首字母缩写&#xff0c;旨在使软件设计更易于理解、灵活和可维护。这些原则是由美国软件工程师和讲师罗伯特C马丁(Robert Cecil Martin)提出的许多原则的子集&#xff0c;在他2000年的论文《设计原则与设计模式》中首次提出…

Linux学习笔记之一————什么是Linux及其应用领域

1.1认识Linux 1&#xff09;什么是操作系统 2&#xff09;现实生活中的操作系统 win7 Mac Android iOS 3&#xff09; 操作系统的发展史 &#xff08;1&#xff09;Unix 1965年之前的时候&#xff0c;电脑并不像现在一样普遍&#xff0c;它可不是一般人能碰的起的&#xff0c;…

Lucene详解

一.lucene原理 Lucene 是apache软件基金会一个开放源代码的全文检索引擎工具包&#xff0c;是一个全文检索引擎的架构&#xff0c;提供了完整的查询引擎和索引引擎&#xff0c;部分文本分析引擎。它不是一个完整的搜索应用程序&#xff0c;而是为你的应用程序提供索引和搜索功能…

.NET 6.0中使用Identity框架实现JWT身份认证与授权

原文作者&#xff1a;Sarathlal Saseendran原文链接&#xff1a;https://www.c-sharpcorner.com/article/jwt-authentication-and-authorization-in-net-6-0-with-identity-framework/翻译&#xff1a;沙漠尽头的狼&#xff08;谷歌翻译加持&#xff09;介绍微软于 2021 年 11 …

adb devices 里面有很多 emulator-XXXX的解决方法

2019独角兽企业重金招聘Python工程师标准>>> adb kill-server 转载于:https://my.oschina.net/sfshine/blog/700354

MQ(Message Queue)简介

一、何为MQ&#xff1f; MQ全称为Message Queue, 消息队列&#xff08;MQ&#xff09;是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息&#xff08;针对应用程序的数据&#xff09;来通信&#xff0c;而无需专用连接来链接它们。消息传递指的是程序之间通…

Blazor University (39)JavaScript 互操作 —— 更新 document title

原文链接&#xff1a;https://blazor-university.com/javascript-interop/calling-javascript-from-dotnet/updating-the-document-title/更新 document title源代码[1]在创建 Blazor 布局[2]部分中&#xff0c;我们看到了 Blazor 应用程序如何存在于 HTML&#xff08;或 cshtm…

IIS 日志文件位置

IIS 6 Log files location IIS 6中日志文件的位置%windir%\System32\LogFilesIIS 7 Log files location IIS的日志文件的位置%SystemDrive%\inetpub\logs\LogFiles用户每打开一次网页&#xff0c;iis 都会记录用户IP、访问的网页地址、访问时间、访问状态等信息&#xff0c;这些…

APP测试流程和测试点

1 APP测试基本流程 1.1流程图 1.2测试周期 测试周期可按项目的开发周期来确定测试时间&#xff0c;一般测试时间为两三周&#xff08;即15个工作日&#xff09;&#xff0c;根据项目情况以及版本质量可适当缩短或延长测试时间。正式测试前先向主管确认项目排期。 1.3测试资源 测…

39所强基计划试点高校已全部公布招生简章

截至目前(4月8日下午) 39所强基计划试点高校 已全部公布招生简章 各高校招生要求是什么&#xff1f; 招生专业有哪些&#xff1f; 什么时候报名&#xff1f; 一起来看 北京大学 招生对象及报名条件 各省&#xff08;区、市&#xff09;符合2022年全国普通高等学校招生统…

【ArcGIS错误异常100问】之001:License服务无法启动权威解决办法

测试环境&#xff1a; 操作系统&#xff1a;Windows10ArcGIS版本&#xff1a;10.X结果&#xff1a;通过测试 文章目录1. 错误提示2. 问题分析3. 解决办法3.1 关闭Windows Defender3.2 关闭系统防火墙3.3 删除迈克菲&#xff08;McAfee&#xff09;杀毒软件3.4 在系统服务中启动…

ASP.NET Core 技术内幕与项目实战读后感

前几天拿到了杨中科老师的新书《ASP.NET Core 技术内幕与项目实战》&#xff0c;迫不及待的“两”口气读完了。用一句话来总结&#xff0c;这是一本写给.NET开发者的非常实用的接地气的好书&#xff0c;感觉有必要自发为这本书宣传一波。杨老师在 .NET 开发者社区中的知名度非常…