本地调试kubernetes组件源码方法

安装etcd hack/install-etcd.sh export PATH="/home/blue/codes/kubernetes/third_party/etcd:${PATH}" 修改hack/local-up-cluster.sh,出以调试kube-apiserver为例 diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index e6195d73383..cfbabc65642 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -550,7 +550,7 @@ EOF APISERVER_LOG=${LOG_DIR}/kube-apiserver.log # shellcheck disable=SC2086 - ${CONTROLPLANE_SUDO} "${GO_OUT}/kube-apiserver" "${authorizer_arg}" "${priv_arg}" ${runtime_config} \ + ${CONTROLPLANE_SUDO} /opt/GoLand-2023.2.5/plugins/go-plugin/lib/dlv/linux/dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec -- "${GO_OUT}/kube-apiserver" "${authorizer_arg}" "${priv_arg}" ${runtime_config} \ ${cloud_config_arg} \ "${advertise_address}" \ "${node_port_range}" \ goland中添加golang远程调试配置。 启动本地集群 mkdir -p /var/run/kubernetes/ # 权限改成当前用户 chown -R blue:blue /var/run/kubernetes/ DBG=1 hack/local-up-cluster.sh 在出现这句提示时,点击启动调试按钮 此时等待集群启动完成,如果遇到不能启动,可查看/tmp/kube-apiserver.log找到具体原因。 kubectl --kubeconfig /var/run/kubernetes/admin.kubeconfig…

使用kwok测试kube-scheduler

kwok是Kubernetes WithOut Kubelet的缩写,意思是无需要kubelet的kubernetes,kwok可轻松部署数千节点的集群。通过kwok的控制器,实现让这些节点模拟真实节点的行为,从而做到在普通笔记本上部署大规模集群,帮助我们对一些核心组件进行性能测试。还有一个类似的工具叫做kubemark,虽然这个也不真实运行容器,但是要模拟多个节点需要多个kubemark实例,仍然会需要大量内存。 安装 安装docker。 这里,我们不使用官方的release版本,而是下载kwok代码到本地编译,此处我们需要修改一些参数配置,方便我们的测试。这里使用的是0.5分支,修改下列位置,主要添加了/debug/*接口跳过鉴权和调整client-go客户端qps配置(默认为50): diff --git a/pkg/kwokctl/components/kube_scheduler.go b/pkg/kwokctl/components/kube_scheduler.go index 07316fd..9541186 100644 --- a/pkg/kwokctl/components/kube_scheduler.go +++ b/pkg/kwokctl/components/kube_scheduler.go @@ -115,7 +115,7 @@ func BuildKubeSchedulerComponent(conf BuildKubeSchedulerComponentConfig) (compon if conf.SecurePort { if conf.Version.GE(version.NewVersion(1, 13, 0)) { kubeSchedulerArgs = append(kubeSchedulerArgs, - "--authorization-always-allow-paths=/healthz,/readyz,/livez,/metrics", + "--authorization-always-allow-paths=/healthz,/readyz,/livez,/metrics,/debug/*", ) } diff --git a/pkg/kwokctl/runtime/scheduler.go b/pkg/kwokctl/runtime/scheduler.go index f23701a..09bd9d0 100644 --- a/pkg/kwokctl/runtime/scheduler.go +++ b/pkg/kwokctl/runtime/scheduler.go @@ -30,6 +30,7 @@ func (c *Cluster) CopySchedulerConfig(oldpath, newpath, kubeconfig string) error err = c.AppendToFile(newpath, []byte(fmt.Sprintf(` clientConnection: kubeconfig: %q…

kube-scheduler中的cache介绍

介绍 首先我们来了解下Cache的作用:收集pod信息,并提供节点级的聚合信息。其设计目标是为通用调度器提供高效查询。Cache的操作是以pod为中心的,能够基于pod的事件进行增量更新。但是由于事件是通过网络发送的,无法保证所有事件都能够传递,因为使用了Reflector进行list和watch操作,可能会由于延迟或relist出现事件丢失的情况。具体参考pkg/scheduler/internal/cache/interface.go中的说明。 在Cache中有有一个assume的概念,其含义为假定、假设,代表了调度器认为这个pod是已调度状态,这个是在scheduleCycle找到合适的节点之后设置的,但是此时还没有调用bind操作将其绑定到具体的节点上,在运行完绑定之后会将其从assumedPods中移除。由于bind的是异步执行的,所以调度器在启动这个协程之后会开始一个新的调度周期来调度下一个pod,在计算已用资源时,是会包含在assume集合中的pod的。 Cache中的数据时基于事件触发添加和更新的,因此为了保证在一个调度周期执行过程中,用到的数据不会发生变化,保证数据的一致性,scheduler在调度开始会使用当前Cache生成一个节点信息的快照,后续会用这个快照进行计算。 接口 下面首先看下Cache的接口定义,了解一下有哪些方法: type Cache interface { // 节点数量 // 测试用 NodeCount() int // pod数量 // 测试用 PodCount() (int, error) // 假设pod已调度,并聚合pod的信息到其对应的节点上,加入到assumedPods。 // 调度框架使用 AssumePod(pod *v1.Pod) error // 完成绑定,设置assumedPod过期时间,后面在执行清理时会在assumedPods中删掉。 // 调度框架使用 FinishBinding(pod *v1.Pod) error // 删除assumedPod。 // 调度框架使用 ForgetPod(pod *v1.Pod) error // 添加pod,如果是assumedPod,则会执行确认逻辑,否则则是过期的pod,将其加入到缓存。 // 已调度pod informer使用 AddPod(pod *v1.Pod) error // 更新pod信息 // 已调度pod informer使用 UpdatePod(oldPod, newPod *v1.Pod) error // 从缓存中删除pod // 已调度pod informer使用 RemovePod(pod *v1.Pod) error //…

runc init流程分析

runc init的作用是创建和配置容器并启动容器内进程。在执行runc create或runc exec等命令时,都会通过fork自己(/proc/self/exec)的方式创建出一个容器的parent进程,该parent进一步创建出child进程,而child进程再进一步创建出grandchild进程,在不同进程内会分别做不同的配置工作,这些进程之间通过环境变量和管道进行通信和交互。…

runc命令行工具基本使用

runc是linux上一个用来运行容器的管理工具,遵循OCI规范。 首先,我们通过一些简单的例子来演示一下runc创建和启动容器的例子: mkdir /tmp/mycontainer cd /tmp/mycontainer # 创建容器文件系统 mkdir rootfs # 导出busybox系统目录到rootfs docker export $(docker create busybox) | tar -C rootfs -xvf - 下面来生成一个spec文件:config.json。runc提供一个一个spec命令帮助我们生成一个基础的模板,后续我们可以基于这个模板作出自定义的修改。 ruc spec 启动容器: root@workstation:/tmp/testrunc# runc run mycontainer / # ip a 1: lo: <LOOPBACK,UP,LOWER_UPmtu 65536 qdisc noqueue qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever / # hostname runc /…

cgroup模式检测

cgroup有v1和v2版本,系统上对cgroupfs的挂载支持三种不同的模式: 统一(Unified) — 最简单的方式,只暴露纯粹的cgroup v2接口。在该模式下,/sys/fs/cgroup 是唯一挂载到主机上的文件系统,所有的控制器都通过它暴露。 兼容(Legacy) — 传统的cgroup v1模式,该模式下不同的控制器拥有自己挂载目录:/sys/fs/cgroup/<controller>/。在此基础之上,systemd拥有自己的cgroup层级来实现管理功能:/sys/fs/cgroup/systemd/。 混合(Hybrid) — 这是上面两种模式的结合形式。这种模式和兼容模式非常相似,但是会多挂载一个层级目录/sys/fs/cgroup/unified/,该目录下包含cgoup v2的接口(但是该目录下只是暴露cgroupfs层级树,不包含控制器功能,控制器都挂载到了兼容模式的单独目录下,/sys/fs/cgroup/unified/仅仅是纯粹的cgroupfs的v2的功能能力,无关资源管控)。在此种模式下,既能够兼容cgroup v1的功能,又能使用一些cgroup v2的特性。 对于不同的操作系统发行版,或者相同发行版的不同版本,挂载模式可能都不一样,因此要使用cgroup功能,需要使用者(如runc,新版jvm)能够检测当前的系统上使用的是何种模式,对于检测何种模式可以使用下列简单的方式: 在根命名空间中,可以使用statfs()获取/sys/fs/cgroup/这个文件系统的类型,如果.f_type是 CGROUP2_SUPER_MAGIC,则说明是统一模式。如果.f_type是TMPFS_MAGIC ,则可能是cgroup v1模式或者可能是混合模式,更进一步检查可通过statfs()获取/sys/fs/cgroup/unified/的文件系统类型,如果它的.f_type是CGROUP2_SUPER_MAGIC,则说明使用的是混合模式,否则就是cgroup v1模式。 下面是containerd中的实现: // github.com/containerd/cgroups/utils.go // Mode returns the cgroups mode running on the host func Mode() CGMode { checkMode.Do(func() { var st unix.Statfs_t if err := unix.Statfs(unifiedMountpoint, &st); err != nil { cgMode = Unavailable return } switch st.Type { case unix.CGROUP2_SUPER_MAGIC: cgMode = Unified default: cgMode = Legacy if err := unix.Statfs(filepath.Join(unifiedMountpoint,…

kube-prometheus问题小计

最近再Kubernetes集群上部署kube-prometheus套件遇到了一点小问题,再次记录一下。 按照官方的快速指引部署到集群中后,发现没有部署custom-metrics和external-metrics服务,而正好需要使用这两个功能做自动扩缩容,因此按照该文档进行自定义部署,但是这里遇到了一些小问题。 首先按照文档中的指引,安装jb,jsonnet和gojsontoyaml这几个命令: # 此处静态编译为了拿到任何地方都能直接用 yum install -y glibc-devel go install -a -ldflags='-linkmode external -extldflags -static' github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest go install -a github.com/google/go-jsonnet/cmd/jsonnet@latest go install -a github.com/brancz/gojsontoyaml@latest mkdir bin/ cp ~/go/bin/{gojsontoyaml,jsonnet,jb} bin/ export PATH=`pwd`/bin:$PATH jb init # Creates the initial/empty `jsonnetfile.json` jb install github.com/prometheus-operator/kube-prometheus/jsonnet/kube-prometheus@release-0.9 # jb update # 下载对应版本的build.sh和example.jsonnet文件 $ wget https://raw.githubusercontent.com/prometheus-operator/kube-prometheus/release-0.9/example.jsonnet -O example.jsonnet $ wget https://raw.githubusercontent.com/prometheus-operator/kube-prometheus/release-0.9/build.sh -O build.sh # 编辑example.jsonnet,去掉被注释掉的custom metrics和external metrics部分 sh build.sh example.jsonnet # 上面命令会重新生成manifests目录 kubectl apply -f manifests/setup…

external-dns介绍和基本使用

external-dns用来将集群内的pod,service,ingress等资源注册到外面的dns上,这样集群外的服务可通过统一的dns访问到集群内的服务,external-dns支持将域名注册到不同的域名服务,如aws,linode,或coredns,bind-dns等。external-dns支持众多kubernetes资源到外部域名服务的注册发布,具体支持的资源类型可进入source目录查看,同时也支持众多的外部域名服务支持,具体可进入provider目录查看。 项目主页:https://github.com/kubernetes-sigs/external-dns 本文主要使用bind作为外部dns服务进行演示测试,将集群内的service,ingress,和istio-gateway,istio-virtualservice注册到外面的dns上。 域名划分 首先我们需要对域名做一个划分: svc.mydomain.com kubernetes集群内服务域名,此处我们集群内外使用相同的域 hosts.mydomain.com 主机节点的dns解析域 mydomain.com 基础服务 部署bind bind的主配置文件修改了本地监听的ip地址,注释掉了ipv6的监听,使用tsig-keygen -a hmac-sha256 externaldns命令生成了认证密匙并配置到了配置文件中,加入forwarders转发配置,此处要求配置为客户基础环境中的。 [root@vm10-77-1-87 ~]# cat /etc/named.conf // // named.conf // // Provided by Red Hat bind package to configure the ISC BIND named(8) DNS // server as a caching only nameserver (as a localhost DNS resolver only). // // See /usr/share/doc/bind*/sample/ for example named configuration files. // // See the BIND Administrator's Reference Manual (ARM) for…

探索client-go中的exec和cp的原理

开发kubernetes控制器中可能涉及到进入特定的container中执行命令的操作,或者拷贝文件等操作,此时我们可能会借助client-go提供的这个工具去实现: "k8s.io/client-go/tools/remotecommand" // 构建请求 req := client.RESTClient().Post(). Resource("pods"). Name(podName). Namespace(namespace). SubResource("exec") req.VersionedParams(&v1.PodExecOptions{ Container: containerName, Command: cmd, Stdin: false, Stdout: true, Stderr: true, TTY: true, }, scheme.ParameterCodec) // 创建执行器 exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL()) // 执行命令 exec.Stream(remotecommand.StreamOptions{ Stdin: nil, Stdout: &buf, Stderr: &buf, Tty: true, }) 上面代码执行时会向apiserver发起一个请求,apiserver会通过代理机制将请求转发给相应节点上的kubelet服务,kubelet会通过cri接口调用runtime的接口发起流式接口中的Exec()接口进入到container执行。针对一般化的命令调用,输入参数和输出结果多数以文本为主,并不会占用带宽和apiserver的资源,但是当设计到文件拷贝的时候则会占用较多带宽,因此在实际使用时这种方式拷贝大文件来说可能并不是最优方案。 此处顺便描述一下如kubectl工具对cp命令的原理,其实也是调用的exec接口,然后执行tar命令进行文件的拷贝动作: # 从容器向外拷贝,通过tar命令压缩将输出重定向到标准输出 tar cf - <srcFile# 从外面向容器内拷贝,将标准输入数据通过tar解压写入到container内的文件目录 tar --no-same-permissions --no-same-owner -xmf - # 或 tar -xmf - 考虑到上面所说的带宽问题,在实际使用时也可向kubelet直接发起exec请求,只要通过client-go将pod所在节点实现查询一下即可,当然还会设计一些端口,证书问题,下面是一个直接向kubelet发起请求的示例代码。 package main import…