跳到主要内容

minio节点迁移

· 阅读需 2 分钟
老司机
Maintainer of this proj

minio节点迁移

1. 备份

dir=/data
mc mirror minio/ $dir/backup/minio_backup.$time --overwrite

2. 重建 pv

mkdir -p $dir/backup/minio_${minio_pod}_$time
cd $dir/backup/minio_${minio_pod}_$time
kubectl get pod ${minio_pod} -n ${minio_namespace} -o yaml > minio-pod-${minio_pod}.yaml
minio_pvc=$(kubectl describe pod ${minio_pod} -n ${minio_namespace} | grep ClaimName | awk '{print $NF}')
echo ${minio_pvc}
kubectl get pvc ${minio_pvc} -n ${minio_namespace} -o yaml > minio-pvc-${minio_pvc}.yaml
minio_pv=$(kubectl describe pvc ${minio_pvc} -n ${minio_namespace} | grep Volume: | awk '{print $NF}')
echo ${minio_pv}
local_dir=$(kubectl describe pv ${minio_pv} -n ${minio_namespace} | grep Path | awk '{print $NF}')
echo ${local_dir}

# 备份pv
kubectl get pv ${minio_pv} -o yaml > minio-pv-${minio_pv}.yaml
# 编辑
cp minio-pv-${minio_pv}.yaml minio-pv-${minio_pv}-new.yaml
vi minio-pv-${minio_pv}-new.yaml
spec:
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- ${new_ip}
# 删除pv,由于pv是绑定状态,需要手动Ctrl+C终止
kubectl delete pv ${minio_pv}

# 修改pv,删除' - kubernetes.io/pv-protection'
kubectl edit pv ${minio_pv}

# 检查pv是否成功删除
kubectl get pv ${minio_pv}

# 迁移后节点创建本地目录
ansible ${new_ip} -m file -a "path=${local_dir} state=directory"

# 重建pv
kubectl apply -f minio-pv-${minio_pv}-new.yaml

# 检查pv,要求状态为Bound
kubectl get pv ${minio_pv}
status=$(kubectl get pv ${minio_pv} | grep -v NAME | awk '{print $5}')
$status == "Bound"则符合预期

3. 重启 pod

kubectl delete pod ${minio_pod} -n ${minio_namespace}
# 检查pod状态
kubectl get pod ${minio_pod} -n ${minio_namespace} -owide
node=$(kubectl get pod ${minio_pod} -n ${minio_namespace} -owide | grep -v NAME | awk '{print $7}')
$node == ${new_ip}则符合预期

4. 检查

mc admin info minio

k8s pod亲和性与反亲和性

· 阅读需 2 分钟
老司机
Maintainer of this proj

k8s 基础知识整理。

1. 亲和性

在Kubernetes中,PodAffinity(Pod亲和性)用于指定Pod希望调度到与满足特定标签选择器的Pod相同的节点或拓扑域上,从而实现Pod的聚合部署,比如为了提高数据局部性或减少网络延迟。

podAffinity主要有以下几种可用的规则:

  • requiredDuringSchedulingIgnoredDuringExecution

    • 强制规则,调度时必须满足,否则Pod无法调度成功。
    • 调度器会确保新Pod必须调度到至少有一个匹配标签的Pod所在的节点或拓扑域中。
    • 适用于必须和特定Pod放在一起的场景。
  • preferredDuringSchedulingIgnoredDuringExecution

    • 优先规则,调度时尽量满足,但不强制。
    • 调度器会尽量将Pod调度到有匹配Pod的节点或拓扑域,但如果无法满足,也会调度到其他节点。
    • 用于提高Pod的聚合度,但允许一定的灵活性。
  • TopologyKey

    • 用于指定亲和性生效的拓扑域,比如kubernetes.io/hostname(节点级别)、failure-domain.beta.kubernetes.io/zone(可用区级别)等。
    • 亲和性规则基于该拓扑域进行匹配。

示例:

podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: "kubernetes.io/hostname"

上面规则表示新Pod必须调度到与已有标签app=myapp的Pod相同节点。

2. 反亲和性

Pod的podAntiAffinity(反亲和性)允许你指定Pod不应该调度到某些满足特定标签选择器的Pod所在的节点上,从而实现Pod的分散部署,避免单点故障。 在 preferredDuringSchedulingIgnoredDuringExecution 部分,设置了一个权重值(weight),值越高表示越倾向于满足这个条件。

规则和podAffinity类似,示例:

spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: "kubernetes.io/hostname"

上面规则表示新Pod不能调度到已经有标签app=myapp的Pod所在的节点。

k8s探针参数说明

· 阅读需 3 分钟
老司机
Maintainer of this proj

Kubernetes 中的探针(Probes)总结

Kubernetes 中的探针用于检测容器的健康状态和生命周期,确保容器能够正常运行并及时发现异常。主要有以下三种探针:

1. Liveness Probe(存活探针)

  • 作用:检测容器是否存活。如果探针失败,Kubernetes 会认为容器已经挂掉,并重启该容器。
  • 应用场景:容器出现死锁或无法响应时,通过重启恢复正常状态。

2. Readiness Probe(就绪探针)

  • 作用:检测容器是否准备好接收流量。如果探针失败,容器会从服务的负载均衡池中移除,不会接收请求。
  • 应用场景:容器启动完成但还未准备好处理请求时,避免流量被发送到未准备好的容器。

3. Startup Probe(启动探针)

  • 作用:检测容器的启动过程是否成功。用于启动时间较长的容器,避免因启动超时而被误判为失败。
  • 应用场景:初始化复杂、启动时间长的应用,确保启动完成后再进行存活和就绪检测。

以上探针都支持多种检测方式:

  • HTTP GET 请求
  • TCP Socket 连接
  • 执行命令(exec)

通过合理配置探针参数(如检测方式、间隔时间、超时时间、失败次数等),可以有效保障容器的稳定运行和服务的高可用性。

4. 参数列表

探针类型参数名参数类型参数支持的选项单位用途说明
LivenessProbehttpGet检测方式path, port, host, scheme, httpHeaders通过HTTP GET请求检测容器存活状态
LivenessProbetcpSocket检测方式port, host通过TCP Socket检测端口连通性
LivenessProbeexec检测方式command通过执行命令检测容器状态
LivenessProbeinitialDelaySeconds间隔时间int容器启动后等待多少秒开始第一次检测
LivenessProbeperiodSeconds间隔时间int检测间隔秒数
LivenessProbetimeoutSeconds超时时间int单次检测超时时间
LivenessProbesuccessThreshold次数int连续成功次数,默认1
LivenessProbefailureThreshold次数int连续失败次数,超过则判定失败
ReadinessProbehttpGet检测方式path, port, host, scheme, httpHeaders通过HTTP GET请求检测容器是否就绪
ReadinessProbetcpSocket检测方式port, host通过TCP Socket检测端口连通性
ReadinessProbeexec检测方式command通过执行命令检测容器是否就绪
ReadinessProbeinitialDelaySeconds间隔时间int容器启动后等待多少秒开始第一次检测
ReadinessProbeperiodSeconds间隔时间int检测间隔秒数
ReadinessProbetimeoutSeconds超时时间int单次检测超时时间
ReadinessProbesuccessThreshold次数int连续成功次数,默认1
ReadinessProbefailureThreshold次数int连续失败次数,超过则判定失败
StartupProbehttpGet检测方式path, port, host, scheme, httpHeaders通过HTTP GET请求检测容器启动状态
StartupProbetcpSocket检测方式port, host通过TCP Socket检测端口连通性
StartupProbeexec检测方式command通过执行命令检测容器启动状态
StartupProbeinitialDelaySeconds间隔时间int容器启动后等待多少秒开始第一次检测
StartupProbeperiodSeconds间隔时间int检测间隔秒数
StartupProbetimeoutSeconds超时时间int单次检测超时时间
StartupProbesuccessThreshold次数int连续成功次数,默认1
StartupProbefailureThreshold次数int连续失败次数,超过则判定失败

k8s基础知识总结

· 阅读需 29 分钟
老司机
Maintainer of this proj

k8s 基础知识整理。

Kubernetes 模块架构

+-----------------------------------------------------+
| Kubernetes Cluster |
| |
| +------------------+ +--------------------+ |
| | Master Node | | Worker Node(s) | |
| | | | | |
| | +------------+ | | +---------------+ | |
| | | API Server |<--------->| Kubelet | | |
| | +------------+ | | +---------------+ | |
| | | | | |
| | +------------+ | | +---------------+ | |
| | | Controller | | | | Container | | |
| | | Manager | | | | Runtime | | |
| | +------------+ | | +---------------+ | |
| | | | | |
| | +------------+ | +--------------------+ |
| | | Scheduler | | |
| | +------------+ | |
| | | |
| | +------------+ | |
| | | etcd | | |
| | +------------+ | |
| +------------------+ |
| |
| +------------------+ |
| | Add-ons | |
| | - DNS | |
| | - Dashboard | |
| | - Network Plugin| |
| +------------------+ |
+-----------------------------------------------------+

Master Node:负责集群的管理和控制,包含 API Server、Controller Manager、Scheduler 和 etcd(分布式键值存储)。 Worker Node:负责运行容器化应用,包含 Kubelet(节点代理)、容器运行时(如 containerd、Docker)等。 Add-ons:集群附加组件,如 DNS 服务、Dashboard、网络插件等。

Kubernetes 组件

  • API Server:负责提供 Kubernetes API 服务,包括创建、更新、删除、查询等操作。
  • Controller Manager:负责管理控制器,包括节点控制器、副本控制器、服务控制器等。
  • Scheduler:负责调度 Pod 到合适的节点上。
  • Kubelet:负责管理节点上的容器化应用,包括启动、停止、重启等操作。
  • Container Runtime:负责管理容器,包括启动、停止、重启等操作。
  • etcd:分布式键值存储,用于存储 Kubernetes 集群的状态信息。
  • Add-ons:集群附加组件,如 DNS 服务、Dashboard、网络插件等。
  • Kube-proxy:负责实现 Kubernetes 服务发现和负载均衡,将服务请求路由到正确的 Pod。
  • CNI(Container Network Interface):负责为容器提供网络功能,如创建网络命名空间、配置 IP 地址、路由表等。
  • Kube-dns:负责为 Kubernetes 集群中的服务提供 DNS 解析服务。
  • Ingress Controller:负责处理外部流量进入集群的流量路由和负载均衡。
  • Dashboard:提供 Web 界面,用于可视化管理和监控 Kubernetes 集群。
  • Logging and Monitoring:负责收集和分析容器的日志,提供监控和报警功能。
  • Helm:用于管理 Kubernetes 应用的包管理器,类似于 Linux 系统中的 apt 或 yum。
  • Prometheus:用于监控和报警,提供指标收集和查询功能。
  • Grafana:用于可视化监控数据,支持多种数据源,如 Prometheus、InfluxDB 等。

k8s 常见资源类型

  • Pod Pod也叫容器组,是Kubernetes中最小的可部署单元,它是一个或多个相关容器的组合。Pod中的容器共享网络和存储资源,并在同一主机上运行。Pod由Kubernetes调度器分配给节点,并使用Linux命名空间和cgroups来隔离容器。

  • Deployment Deployment用于管理无状态Pod的创建和更新。它通过控制器模式(ReplicaSet)来确保指定数量的Pod副本在集群中运行。当需要进行扩展、回滚或更新时,Deployment会创建新的Pod,并逐步替换旧的Pod。

  • Service Service提供了一种稳定的网络访问方式,用于暴露Pod或一组Pod的网络服务。Service通过标签选择器(Label Selector)将请求路由到后端Pod。它使用Kubernetes的服务发现机制,通过集群内部的DNS或负载均衡器将请求转发到正确的Pod。

  • Ingress Ingress是一种规则集合,用于将外部请求路由到集群内部的Service。它充当了集群外部和集群内部之间的入口点。Ingress控制器根据Ingress规则将请求转发到相应的Service,并处理负载均衡、SSL终止和路径匹配等功能。

  • ConfigMap ConfigMap用于存储应用程序的配置数据,如环境变量、配置文件等。它将配置数据存储为键值对,并将其注入到Pod的容器中。容器可以通过环境变量或挂载文件的方式访问ConfigMap中的配置数据。

  • Secret Secret用于存储敏感数据,如密码、令牌等。读取Secret时默认以Base64编码的形式从 etcd 中取出,并可以被Pod中的容器使用。Secret可以用于安全地传递敏感信息,如数据库密码或API密钥,需要注意的是 k8s 并不默认提供对 secrets 的加密,需要自己实现密钥管理服务(Key Management Service,KMS)。

pod 创建过程

创建 pod 的流程大致如下:

  1. 当执行 kubectl create deploy 时,k8s 执行的流程如下:
  2. kubectl 执行客户端校验,资源类型、镜像名称等
  3. Kubectl 读取.kube/config获取认证信息,kube-apiserver 地址
  4. kubectl 生成请求内容并往 apiserver 发送创建请求
  5. kube-apiserver 通过认证和鉴权后将信息存储至 etcd
  6. kube-controller-manager监听到 deploy create events,检查相关资源是否已经存在
  7. Deployment controller 创建 ReplicaSets,replicaset controller 创建 pods,存入 etcd,pod 进入 pending 状态
  8. kube-scheduler监听到 pod 未分配节点,开始选择合适的节点,发送 POST 请求给 apiserver
  9. kube-apiserver 将 pod 状态标记为 scheduled
  10. kubelet 轮询 kube-apiserver 获取 pod 信息,开始创建 cgroup,创建数据目录,调用 CRI (container runtime interface)创建 container
  11. 创建 pause 容器,kubelet 通过 CNI(container network interface)创建网络
  12. kubelet 从镜像仓库拉取pod 的镜像,通过 CRI 插件创建 container 以上仅是对关键步骤的简要描述,实际情况要复杂得多。

docker、containerd、runc 是什么关系? containerd、runc 由 docker 拆分出来,runc 是一个标准的OCI runtime实现,containerd 支持多种OCI runtime 实现,对应 runc 的支持提供了 containerd-shim-runc-v2,一个容器的创建具体过程如下:

k8s v1.19版本,kubelet 使用 docker-shim与 dockerd 通信,k8s自 v1.20版本开始建议使用 containerd-shim,1.24 版本彻底废弃 docker-shim。

Canal

Canal 是一个 CNI 网络插件,它很好地结合了 Flannel 和 Calico 的优点。它让你轻松地将 Calico 和 Flannel 网络部署为统一的网络解决方案,将 Calico 的网络策略执行与 Calico(未封装)和 Flannel(封装)丰富的网络连接选项结合起来。 Canal 是 Rancher 默认的 CNI 网络插件,并采用了 Flannel 和 VXLAN 封装。 CNI(容器网络接口)是一个云原生计算基金会项目,它包含了一些规范和库,用于编写在 Linux 容器中配置网络接口的一系列插件。CNI 只关注容器的网络连接,并在容器被删除时移除所分配的资源。 Kubernetes 使用 CNI 作为网络提供商和 Kubernetes Pod 网络之间的接口。 Canal 架构 Flannel 是为 Kubernetes 配置 L3 网络结构的简单方法。Flannel 在每台主机上运行一个名为 flanneld 的二进制 Agent,该 Agent 负责从更大的预配置地址空间中为每台主机分配子网租约。Flannel 通过 Kubernetes API 或直接使用 etcd 来存储网络配置、分配的子网、以及其他辅助数据(例如主机的公共 IP)。数据包使用某种后端机制来转发,默认封装为 VXLAN。

+-------------------------------------------------------------+
| Master Node |
| |
| +-----------+ +-----------+ +----------------+ |
| | Kubelet | --> | Scheduler | | Controller | |
| +-----------+ +-----------+ | Manager | |
| \ | +----------------+ |
| \ | | |
| \ | | |
| \ | v |
| \ +-----------------> API Server <-----+ <-- kubectl
| \ / ^ ^ |
| \ / | | |
| \ / | | |
| \ / | | |
| \ / | | |
| v v | | |
| etcd <---------+ | | |
| ^ | | |
| | | | |
| Proxy <--------------------+ | |
| | | |
+-------------------------------------------------------------+
+-------------------------------------------------------------+
| Worker Nodes |
| |
| +-----------+ +-----------+ +----------------+ |
| | Kubelet | --> | Proxy | | Flannel | |
| +-----------+ +-----------+ +----------------+ |
| | | | |
| v | | |
| Docker <----------------------+ |
| | 10.0.0.0/8 (Flannel 网络) |
| v |
| PODs --------------------------------------------> PODs |
+-------------------------------------------------------------+

说明:

  • Master Node 负责集群管理,包括 Kubelet、Scheduler、Controller Manager、API Server、etcd 和 Proxy。
  • Worker Nodes 运行应用容器,包含 Kubelet、Proxy、Docker 和 POD。
  • Flannel 负责跨节点网络通信,使用 10.0.0.0/8 网段。
  • kubectl 通过 API Server 与集群交互。
  • 流量(traffic)在 Proxy 之间传递。

pod网络通信

pod 是如何与外部通信的?

CNI 插件为每个节点分配一个/24网段,例如10.42.0.0/24 10.42.1.0/24,每个 pod 分配一个该节点持有网段的 ip 地址;每个 pod 内有一个 eth0 虚拟网卡,同时宿主机中有与其配对的另外一个虚拟网卡,二者同属一个 network namespace,这种成对出现的网卡叫veth pair,两者可以相互通信,就好比有一根网线一头连接你的电脑网卡,另一头连接交换机。以下面 pod 举例说明:

[k8s@k8s-192-168-4-88 ~]$ kubectl -n k8s get pod -owide | grep 192.168.4.88
k8s-xxx-648c86cd45-qbhj9 1/1 Running 0 43d 10.42.4.15 192.168.4.88
[k8s@k8s-192-168-4-88 ~]$

节点上查看 pod 对应的 container 如下,与这个 pod 有关的 container 有两个,一个是业务进程本身,一个是/pause进程,查看 pod 内的网卡如下:

[k8s@k8s-192-168-4-88 ~]$ kubectl -n k8s exec k8s-xxx-648c86cd45-qbhj9 -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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
3: eth0@if52: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP
link/ether be:67:6a:d4:65:98 brd ff:ff:ff:ff:ff:ff
inet 10.42.4.15/32 brd 10.42.4.15 scope global eth0
valid_lft forever preferred_lft forever

宿主机上的peer veth 如下:

[k8s@k8s-192-168-4-88 ~]$ ip l | grep ^52:
52: cali18e95c70453@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP mode DEFAULT group default

可以看到 pod 中的网卡名,他有固定的命名规则,带了对端网卡的 index 编号 52,在宿主机上查看 index 编号为 52 的网卡名为cali18e95c70453, pod 的网络包收发都会经过宿主机上的cali18e95c70453网卡,可以抓包验证一下:

[k8s@k8s-192-168-4-88 ~]$ kubectl -n k8s exec -it k8s-xxx-648c86cd45-qbhj9 -- curl xxx.com
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>TLB</center>
</body>
</html>
[k8s@k8s-192-168-4-88 ~]$
[k8s@k8s-192-168-4-88 ~]$ sudo tcpdump -nnpA -i cali18e95c70453 tcp dst port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on cali18e95c70453, link-type EN10MB (Ethernet), capture size 262144 bytes
15:59:43.723438 IP 10.42.4.15.49848 > 122.14.236.25.80: Flags [S], seq 2904204625, win 28200, options [mss 1410,sackOK,TS val 3
E...<.M@.@..
*..z.......P...Q.......n(t.........
..U........
15:59:43.750376 IP 10.42.4.15.49848 > 122.14.236.25.80: Flags [.], ack 3650196656, win 221, options [nop,nop,TS val 3808777527
E..4.N@.@...
*..z.......P...R.......t.......
..U7{...
15:59:43.750434 IP 10.42.4.15.49848 > 122.14.236.25.80: Flags [P.], seq 0:77, ack 1, win 221, options [nop,nop,TS val 380877752
E....O@.@...
*..z.......P...R.......t.......
..U7{...GET / HTTP/1.1
Host: xxx.com
User-Agent: curl/8.5.0
Accept: */*

pod 与 pod 之间是如何通信的呢?首先宿主机的路由表如下:

default via 192.168.4.1 dev eth0
10.42.0.0/24 via 10.42.0.0 dev flannel.1 onlink
10.42.1.0/24 via 10.42.1.0 dev flannel.1 onlink
10.42.2.0/24 via 10.42.2.0 dev flannel.1 onlink
10.42.3.0/24 via 10.42.3.0 dev flannel.1 onlink
10.42.5.0/24 via 10.42.5.0 dev flannel.1 onlink
10.42.4.15 dev cali18e95c70453 scope link
169.254.0.0/16 dev eth0 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.4.0/24 dev eth0 proto kernel scope link src 192.168.4.88

可以看到和其他宿主机的 pod ip 段是通过flannel.1网卡发送,再看一下邻居表:

10.42.2.0 dev flannel.1 lladdr 62:6d:e7:6f:f3:e9 PERMANENT
10.42.1.0 dev flannel.1 lladdr 9a:b6:27:23:b2:21 PERMANENT
10.42.0.0 dev flannel.1 lladdr a6:eb:6d:4e:48:3a PERMANENT
10.42.3.0 dev flannel.1 lladdr 16:8e:4f:5c:1e:63 PERMANENT
10.42.5.0 dev flannel.1 lladdr 0a:14:55:b6:2e:97 PERMANENT

由此可知当发送往其他节点pod的数据包到达flannel.1网卡时,每个网段都对应有一条目标 mac 地址,而这些 mac 地址就是每个宿主机节点上 flannel.1网卡的 mac。 另外需要注意的是上面 pod 的 container 中有一个启动了/pause进程的 container,又叫 infra container 基础容器,他的镜像由 kubelet 指定:

kubelet --infra-container-image=k8s.gcr.io/pause:3.2

Infra container的作用是实现 pod 内多个容器之间的存储、网络等资源的共享,单独一个 pause 容器的好处是他里面没有任何业务逻辑不容易挂掉,使得 namespace 共享更加稳定。

kube-proxy

简介 来自 k8s 官网对 kube-proxy 的定义:Kubernetes 网络代理在每个节点上运行。网络代理反映了每个节点上 Kubernetes API 中定义的服务,并且可以执行简单的 TCP、UDP 和 SCTP 流转发,或者在一组后端进行 循环 TCP、UDP 和 SCTP 转发。 当前可通过 Docker-links-compatible 环境变量找到服务集群 IP 和端口, 这些环境变量指定了服务代理打开的端口。 有一个可选的插件,可以为这些集群 IP 提供集群 DNS。 用户必须使用 apiserver API 创建服务才能配置代理。 简言之, kube-proxy 的作用是承载 k8s svc的流量转发,将请求往 svc ip:port的流量转发到 podip:port上,同时还提供负载均衡与健康检查机制。

部署架构:

linux 系统中的kube-proxy 支持多种代理模式,userspace、iptables 和 ipvs,userspace 模式属于早期实现的方案,因为性能原因已经逐渐弃用,iptables 模式通过 iptables 实现数据包转发,但不具备负载均衡能力且规模上去之后 iptables 规则太多也会引发性能问题,目前 k8s 中使用的是 ipvs 模式,可以通过启动参数看到:

[k8s@k8s-192-168-4-86 ~]$ ps aux|grep kube-proxy
k8s 80898 0.0 0.0 112816 988 pts/1 S+ 23:17 0:00 grep --color=auto kube-proxy
root 196678 0.2 0.0 750776 31484 ? Ssl Jun18 191:02 kube-proxy --feature-gates=TTLAfterFinished=true --proxy-mode=ipvs --cluster-cidr=10.42.0.0/16 --xy.yaml --healthz-bind-address=127.0.0.1 --v=2

ipvs 是 linux kernel 中的模块,具备负载均衡能力的同时还能获得不错的转发性能。ipvs 模块来自于 lvs 项目,lvs 项目诞生于 1998 年,ipvs 模块最新版本为 v1.2 发布于 2004 年,距今已经稳定运行20 年。值得一提的是 lvs 项目的创立者章文嵩博士,他的贡献使得 ipvs 成为了负载均衡领域的重要技术,对互联网基础设施的发展产生了积极深远的影响。

数据流向:

+---------------------------------+
| Node |
| +----------+ +------------+ |
| | Client | | kube-proxy |---+
| +----------+ +------------+ |
| \ / |
| \ / |
| +----------------+ |
| | clusterIP | |
| | (Virtual Server)| |
| +----------------+ |
+------------------------------+ |
- |
+-----------------------+-----------------------+
| - | |
+----------------+ +----------------+ +-----------------+
| Backend Pod 1 | | Backend Pod 2 | | Backend Pod 3 |
| (Real Server) | | (Real Server) | | (Real Server) |
+----------------+ +----------------+ +-----------------+

svc实现机制

在 k8s 中,有以下几种类型的 Service:

  1. ClusterIP:ClusterIP 是默认的 Service 类型,它为 Service 分配一个 Cluster IP 地址,并在集群内部提供负载均衡。当请求发送到 ClusterIP 地址时,k8s 的内部负载均衡机制将流量转发到后端 Pod。
  2. NodePort:NodePort 类型的 Service 具有 ClusterIP 的所有功能,并且还会在每个节点上打开一个静态端口,将流量转发到 Service。当请求发送到任何节点的 NodePort 端口时,k8s 会将流量转发到相应的 Service。
  3. LoadBalancer:LoadBalancer 类型的 Service 通过云服务提供商的负载均衡器(如 AWS ELB、GCP GCLB)来公开 Service。负载均衡器会将流量转发到后端 Pod。
  4. ExternalName:ExternalName 类型的 Service 允许将 Service 映射到集群外部的任意 DNS 名称。它不提供负载均衡功能,只是通过 CNAME 记录将 Service 名称解析为外部 DNS 名称。
  5. Headless Service:无头服务,是 k8s 中的一种特殊类型的 Service,它与其他类型的 Service(如 ClusterIP、NodePort、LoadBalancer)有所不同。Headless Service 不会为 Service 分配 Cluster IP,而是通过 DNS 记录直接暴露后端 Pod 的网络地址。 ClusterIP svc的请求转发过程: 有 svc 如下:
[k8s@k8s-192-168-4-86 ~]$ kubectl -n k8s get svc backend-service -owide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
backend-service ClusterIP 10.43.199.15 <none> 18888/TCP 44d app=backend-service

该 svc 的 clusterip 为 10.43.199.15,外部端口和内部端口一样为 18888,对应的后端 pod 的 app label 是 backend-service。10.43.199.15是一个虚拟机 IP(VIP),kube-proxy 会把这个 ip 绑定在每个节点上,

[k8s@k8s-192-168-4-86 ~]$ run 'ip -o a |grep 10.43.199.15'
192.168.4.87 43: kube-ipvs0 inet 10.43.199.15/32 scope global kube-ipvs0\ valid_lft forever preferred_lft forever
192.168.4.91 33: kube-ipvs0 inet 10.43.199.15/32 scope global kube-ipvs0\ valid_lft forever preferred_lft forever
192.168.4.90 15: kube-ipvs0 inet 10.43.199.15/32 scope global kube-ipvs0\ valid_lft forever preferred_lft forever
192.168.4.88 23: kube-ipvs0 inet 10.43.199.15/32 scope global kube-ipvs0\ valid_lft forever preferred_lft forever
192.168.4.89 33: kube-ipvs0 inet 10.43.199.15/32 scope global kube-ipvs0\ valid_lft forever preferred_lft forever
192.168.4.86 15: kube-ipvs0 inet 10.43.199.15/32 scope global kube-ipvs0\ valid_lft forever preferred_lft forever

接着 kube-proxy 会通过 apiserver 找到 label app=backend-service的后端服务 endpoints,是 pod 的内网 ip+端口,

[k8s@k8s-192-168-4-86 ~]$ kubectl -n k8s get pod -o wide -l app=backend-service
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
backend-service-555d6684b5-9kwc7 1/1 Running 0 31d 10.42.3.171 192.168.4.90 <none> <none>
backend-service-555d6684b5-s7n9n 1/1 Running 0 31d 10.42.0.134 192.168.4.91 <none> <none>
```text
[k8s@k8s-192-168-4-86 ~]$ kubectl -n k8s get ep backend-service
NAME ENDPOINTS AGE
backend-service 10.42.0.134:18888,10.42.3.171:18888 44d

接着,kube-proxy 通过创建 ipvs 规则,将发往 VIP的流量转发到后端 realserver,

[k8s@k8s-192-168-4-86 ~]$ sudo ipvsadm -Ln|grep -A2 10.43.199.15
TCP 10.43.199.15:18888 rr
-> 10.42.0.134:18888 Masq 1 0 0
-> 10.42.3.171:18888 Masq 1 0 0

自此,整个链路就打通了。

ingress

ingress 正如其字面意思,是 k8s 对外的流量入口,除了支持 http 流量外也支持 tcp、udp 流量转发。 ingress-nginx 是其中一种controller实现,除了 nginx 的实现以外还有其他很多 ingress controller实现例如haproxy、apisix、kong等,k8s 中使用的是nginx-ingress-controller。

数据流向:

+------------------------+          +-----------------------+          +-------------------------+
| Kubernetes Cluster SVC |<---------| Ingress Controller |<---------| Load Balancer |
+------------------------+ +-----------------------+ +-------------------------+
^ ^ | ^
| | | leader |
| | | |
| expose business service | | | access FQDN
| | | |
+-----------------------+ +------------------------+ +-------------------------+
| business logic POD | | Kubernetes NodePort SVC| | Client |
+-----------------------+ +------------------------+ +-------------------------+
^
|
| access business svc
|
|
+------------------------------------+
| control plane (green dashed lines) |
+------------------------------------+
| data plane (red solid lines) |
+------------------------------------+

Additional notes:

  • The Ingress Controller gets endpoints & generates config file from Kubernetes Cluster SVC.
  • The Ingress Controller exposes ingress service to Kubernetes NodePort SVC.
  • The Load Balancer accesses the Kubernetes NodePort SVC.
  • The Client accesses the Load Balancer via FQDN.
  • The Load Balancer accesses the business service via Kubernetes NodePort SVC.
  • The business logic POD is exposed by the Kubernetes Cluster SVC.

数据平面: 客户端发送 http 请求至 LB,LB 转发流量至ingress-nginx NodePort svc,由nginx-ingress-controller pod 中的 nginx 进程接收这些流量,nginx 进程充当反向代理的角色,默认情况下nginx 是直接转发至各业务 svc 对应的 endpoints,而非svc 本身,这样做的好处是流量绕过 kube-proxy 流程,缩短链路。这一行为可通过nginx.ingress.kubernetes.io/service-upstream注解控制。 控制平面: nginx-ingress-controller pod 中除了 nginx进程以外还有负责控制面的 nginx-ingress-controller 进程,

 [k8s@k8s-192-168-4-86 ~]$ kubectl -n ingress-nginx exec -it nginx-ingress-controller-68c566495-hj457 -- ps aux
PID USER TIME COMMAND
1 www-data 0:00 /usr/bin/dumb-init -- /nginx-ingress-controller --default-
7 www-data 4h40 /nginx-ingress-controller --default-backend-service=ingres
50 www-data 0:13 nginx: master process /usr/local/nginx/sbin/nginx -c /etc/ng
38549 www-data 0:41 nginx: worker process
38550 www-data 0:39 nginx: worker process
38583 www-data 0:38 nginx: worker process

当 pod 拉起时通过注册并 lock ingress-controller-leader-nginx configmap 选主,只有一个 Pod 能够成功创建 ConfigMap,成为主节点。其他 Pod 会检测到 ConfigMap 已经存在,并成为从节点。

[k8s@k8s-192-168-4-86 ~]$ kubectl -n ingress-nginx get cm ingress-controller-leader-nginx -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
annotations:
control-plane.alpha.kubernetes.io/leader: '{"holderIdentity":"nginx-ingress-controller-7p6wd","leaseDurationSeconds":30,"acquireTime":"2024-07-31T12:31:56Z","renewTime":"2024-07-31T13:44:30Z","leaderTransitions":4}'
creationTimestamp: "2024-06-18T01:23:07Z"
managedFields:
- apiVersion: v1

主节点负责监听 k8s API Server 中的 Ingress 资源,并根据配置规则进行流量转发。从节点会复制主节点的配置,并等待主节点不可用时接管流量转发任务。如果主节点发生故障重启等,超过 30 秒从节点就会检测到主节点不可用,并开始竞选新的主节点。

使用示例

以下 demo 演示如何创建 ingress以及对应的 svc、endpoint 资源,并对外提供访问:

---
apiVersion: v1
kind: Service
metadata:
name: laosiji
namespace: default
spec:
type: ClusterIP
ports:
- port: 6666

---
apiVersion: v1
kind: Endpoints
metadata:
name: laosiji
namespace: default
subsets:
- addresses:
- ip: 192.168.4.86
ports:
- port: 6666

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: laosiji-ingress
namespace: default
spec:
rules:
- host: siji.example.com
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: laosiji
port:
number: 6666

Coredns

coredns 是k8s 生态中原生服务发现的实现方案,插件化设计,一个功能对应一个插件,高度灵活可扩展,支持自定义插件开发。coredns 可以为 svc、sts、pod 等提供域名解析。

解析流程:

+-------------------------------------------------------------+     +----------------------------------------+
| Kubernetes node A | | Kubernetes node B |
| | | |
| +----------------------+ | | +----------------------+ |
| | Pod foo | | | | node's | |
| | | | | | resolv.conf | |
| | +----------------+ | | | +----------------------+ |
| | | Application | | | | |
| | +----------------+ | | | |
| | | | | | |
| | Initiates DNS lookup | | | |
| | v | | | |
| | +----------------+ | | | |
| | | DNS resolver | | | | |
| | +----------------+ | | | |
| | | | | | |
| | Uses the DNS server | | | |
| | specified in | | | |
| | v | | | |
| | +----------------+ | Points to | | +----------------+ |
| | | resolv.conf | | --------------------------------->|---->| | CoreDNS | |
| | +----------------+ | | | +----------------+ |
| | | | | | | | |
| | If not in cache, | | | | Forwards | |
| | retrieves DNS servers| | | v v |
| | from | | | +----------------+ +----------------+|
| | v | | | | node's resolv.conf| |K8s API | |
| | +----------------+ | | | +----------------+ |(Svcs/Endpoints) |
| | | nodelocaldns | | | | +----------------+ |
| | +----------------+ | | | |
| +----------------------+ | +----------------------------------------+
+-------------------------------------------------------------+

记录规则

svc记录规则:

<svc-name>.<namespace-name>.svc.<cluster-domain>

statefulset记录规则:

<pod-name>.<svc-name>.<namespace-name>.svc.<cluster-domain>

配置示例:

.:53 {                            # 监听地址和端口
errors # 启用错误日志
health { # 健康检查配置,提供Liveness探针地址http://:8080/health
lameduck 5s # 在关闭前的等待时间
}
ready # 启用就绪检查
hosts /etc/add_hosts { # 使用指定的hosts文件进行解析
#1.2.3.1 kubenode1 # hosts文件中的解析规则,将kubenode1解析为1.2.3.1
fallthrough # 如果没有匹配到hosts文件中的解析规则,继续向下解析
}
kubernetes cluster.local in-addr.arpa ip6.arpa { # Kubernetes相关配置
pods insecure # 允许通过域名解析Pods的IP地址
fallthrough in-addr.arpa ip6.arpa # 如果没有匹配到Kubernetes相关的解析规则,继续向下解析
}
prometheus :9153 # 启用Prometheus指标收集,监听地址和端口
bufsize 4096 # limits a requester’s UDP payload size to within a maximum value,must be within 512 - 4096
forward . "/etc/resolv.conf" # 使用指定的resolv.conf文件进行转发解析
cache 30 # DNS缓存时间
loop # 启用循环查询
reload # 监听配置文件的变化并自动重新加载
loadbalance # 启用负载均衡
}

pod 中的 dns 配置受dnsPolicy策略和 hostNetwork 策略控制,当hostNetwork=true 时,使用宿主机 dns,其中dnsPolicy有以下几种:


| 策略名称 | 解析行为 |
|-------------------------|-------------------------------------------------------------------------|
| Default | Pod 使用宿主机的 DNS 配置 |
| ClusterFirst | 默认设置,Pod 使用 coredns 作为 nameserver,其 nameserver 来自 kubelet 的--cluster-dns=10.43.0.10参数 |
| ClusterFirstWithHostNet | 即使是hostNetwork=true,也会使用 coredns |
| None | 使用 Pod 的 dnsConfig 配置项 |


解析示例:
```text
root@backend-service-555d6684b5-9kwc7:~# cat /etc/resolv.conf
nameserver 10.43.0.10
search k8s.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
root@backend-service-555d6684b5-9kwc7:~# nslookup clickhouse-service.clickhouse
Server: 10.43.0.10
Address: 10.43.0.10#53
Name: clickhouse-service.clickhouse.svc.cluster.local
Address: 192.168.4.91
Name: clickhouse-service.clickhouse.svc.cluster.local
Address: 192.168.4.86
Name: clickhouse-service.clickhouse.svc.cluster.local
Address: 192.168.4.87
Name: clickhouse-service.clickhouse.svc.cluster.local
Address: 192.168.4.90
Name: clickhouse-service.clickhouse.svc.cluster.local
Address: 192.168.4.89
Name: clickhouse-service.clickhouse.svc.cluster.local
Address: 192.168.4.88
root@backend-service-555d6684b5-9kwc7:~# nslookup clickhouse-1-1-0.clickhouse-service.clickhouse
Server: 10.43.0.10
Address: 10.43.0.10#53
Name: clickhouse-1-1-0.clickhouse-service.clickhouse.svc.cluster.local
Address: 192.168.4.86

k8s 监控

metrics-server 用来采集 k8s 各种监控指标,提供给kubectl top、hpa(Horizontal Pod Autoscaler)、vpa(Vertical Pod Autoscaler)、k8s-dashboard 等使用。 数据链路:

+-----------+      +----------+      +---------------+      +---------+      +--------+
| kubectl | | apiserver| | metrics-server| | kubelet | | cgroup |
+-----------+ +----------+ +---------------+ +---------+ +--------+
\ ^ ^ ^ ^
\ | | | |
+-----------+ | | | |
| dashboard |-----------+ | | |
+-----------+ | | |
\ | | |
\ | | |
+-----------+ | | |
| scheduler |-------------------------------+ | |
+-----------+ | |
| |
| |
+----------------+

metrics-sever 通过采集 kubelet 提供的 metrics 接口(https://nodeip:10250 的 /metrics /metrics/cadvisor, /metrics/probes, /metrics/resource)的数据,采集指标示例:

[k8s@k8s-192-168-4-86 ~]$ curl -s -k -H "Authorization: Bearer $(kubectl -n kube-system get secrets namespace-controller-token-qwvll -ojson|jq '.data.token' -r|base64 -d)" https://192.168.4.86:10250/metrics/resource|head -10
## HELP container_cpu_usage_seconds_total [ALPHA] Cumulative cpu time consumed by the container in core-seconds
## TYPE container_cpu_usage_seconds_total counter
container_cpu_usage_seconds_total{container="apiserver",namespace="hive2ch",pod="hive2ch-apiserver-6fdf5df7f5-lgvgp"} 2270.429353976 1722443959650
container_cpu_usage_seconds_total{container="backend",namespace="sub",pod="subscription-backend-7cd788b876-6z74p"} 7573.822327467 1722443959713
container_cpu_usage_seconds_total{container="calico-node",namespace="kube-system",pod="canal-xrmh4"} 108549.571417276 1722443963395
container_cpu_usage_seconds_total{container="clickd",namespace="clickhouse",pod="clickd-deployment-549944fcf9-2fzzf"} 65865.995640873 1722443965340
container_cpu_usage_seconds_total{container="clickhouse-server",namespace="clickhouse",pod="clickhouse-1-1-0"} 473112.908558262 1722443956984
container_cpu_usage_seconds_total{container="consul-client",namespace="consul",pod="consul-client-3-0"} 103994.031234639 1722443955027
container_cpu_usage_seconds_total{container="controller",namespace="ingress-nginx",pod="nginx-ingress-controller-z46n4"} 103172.807361637 1722443959662
container_cpu_usage_seconds_total{container="coredns",namespace="kube-system",pod="coredns-68c65cb45-9hxdn"} 43348.132995208 1722443948530

再通过 apiserver 的 Metrics API 对内提供服务。其设计的主要目的是用于 autoscaling,不建议用作指标监控。

prometheus

prometheus 是第二个 CNCF 托管的项目(第一个是 k8s),为 k8s 监控量身打造,提供了包含指标采集、存储、查询以及告警等一套完整的能力。

整体架构:

+------------------+          +---------------------------+          +--------------+          +----------------+
| Short-lived jobs | | Service discovery | | Alertmanager | | pagerduty |
+------------------+ | +-----------+ +---------+ | +--------------+ +----------------+
| | | kubernetes | | file_sd| | | notify | Email |
| push metrics at exit| +-----------+ +---------+ | |------------------------| etc |
v +----------------------------+ | +--------+
+--------------+ | discover targets push alerts
| Pushgateway |----------------------|------------------------------------>|
+--------------+ v |
| +-------------------------------+ |
| pull metrics | Prometheus server | |
v | +-----------+ +-----+ +-----+ | |
+----------------+ | | Retrieval | | TSDB| | HTTP| | |
| Jobs/ exporters| | +-----------+ +-----+ +-----+ | |
+----------------+ | | |
+-------------------------------+ |
| |
v v
+------------+ +------------------+
| Node | | Prometheus UI |
| +--------+ | +------------------+
| |HDD/SSD | | |
| +-------+| | |
+------------+ +------------------+
| Grafana |
+------------------+
|
+------------------+
| API clients |
+------------------+

prometheus server自带时序数据库, 通过内置的采集器从 k8s 以及周边组件暴露的 metrics 接口拉取指标数据,同时也诞生了针对各种组件的exporter ,这些 exporter 通过各种协议采集对应组件的指标,再通过 metrics 接口提供给 prometheus 采集。prometheus 的另一个重要能力是创建了 promQL 查询语法,类似 influxQL,可以通过各种内置函数对指标进行计算、聚合,同时还能通过 promql 配置告警规则。 prometheus 的 tsdb 不依赖分布式存储,好处是简单好维护单节点可运行,不好的是本地存储挂了数据就丢了,为此 prometheus 提供了 remote_write/remote_read 的标准接口,可以把数据对接到外部分布式存储中。 promql 查询示例:

## 计算增长率
rate(demo_api_request_duration_seconds_count[5m])
## 根据 label 筛选
node_cpu_seconds_total{cpu!="0",mode=~"user|system"}
## 根据数值筛选
node_filesystem_avail_bytes > 10*1024*1024
## 90 分位
histogram_quantile(0.9, rate(demo_api_request_duration_seconds_bucket[5m]))
## 平均值
avg_over_time(go_goroutines[5m])

更多详细用法可参考官方手册:https://prometheus.io/docs/prometheus/latest/querying/basics/ 自动写 ql 的 gpt 工具:https://www.yeschat.ai/gpts-9t563RLGy4U-PromQL-Advisor

grafana

grafana 是一个精于展示时序数据的可视化工具,具有灵活的图表展示,丰富的数据源支持等特性。早期的 grafana 是一个 kibana 的分支,其元数据存储依赖 es,后来重构成一个纯前端的项目,图标展示能力更强性能更好,再后来有了 golang 写的后端,可以把元数据存到 sqlite、mysql、pgsql 等db 中,同时支持插件化,例如新增一个数据源类型只需要写一个插件。同时构建了在线仪表盘分享社区,可以方便的导入别人设计好的仪表盘。随着云原生及 k8s 的发展,grafana 顺势成为 k8s 监控可视化的最佳搭档。不仅如此,grafana 背后公司继续朝可观测性领域发力,在 metrics、logs、trace 方向都有对应的产品推出,例如我们用到的 loki 也是出自 GrafanaLabs 。

Loki

Loki 是一个开源的日志收集系统,其功能是收集、存储和查询日志。 Loki 压缩日志并将日志存储在块中,并将它们存储在文件系统或 AWS S3 等后端存储中。 块(chunk)是一个压缩文件,其中包含基于日志卷的日志条目,因此当块大小达到其大小限制时,它会将日志存储在另一个块中。每当存储一个块时,它都会为每个块创建一个索引(index)。索引不包含日志的内容,它只包含时间戳、块的标签和块的位置。 loki 特性: 横向扩容、多租户、日志采集展示、日志告警

+-------------------------+-------------------------------+-------------+
| Timestamp | Prometheus-style Labels | Content |
| with nanosecond precision| key-value pairs | logline |
| indexed | indexed | unindexed |
+-------------------------+-------------------------------+-------------+
| 2019-12-11T10:01:02.123456789Z | {app="nginx", cluster="us-west1"} | GET /about |

loki 数据流:

+-----------------+       +-----------------+
| NODE 1 | | NODE 2 |
| +-------------+ | | +-------------+ |
| | Application | | | | Application | |
| +-------------+ | | +-------------+ |
| | | | | |
| Write Logs | | Write Logs |
| v | | v |
| +-------------+ | | +-------------+ |
| | Promtail | | | | Promtail | |
| +-------------+ | | +-------------+ |
+--------|--------+ +--------|--------+
| |
+-----------+-------------+
|
+-------------------------+
| Loki |
| +---------+ +---------+|
| |Distributor| ||
| +---------+ +---------+|
| | | |
| +---------+ +---------+ +--------------+
| | Ingester| | Querier | |Query Frontend|
| +---------+ +---------+ +--------------+
| | ^ ^
| v | |
| +---------+ | |
| | Ruler |< -+ |
| +---------+ |
| |
| +------------------+ +------------------+
| | Index | | Chunks |
| | /var/lib/loki/ | | /var/lib/loki/ |
| | /index | | /chunks |
| +------------------+ +------------------+
+-------------------------------------------+
| |
v v
+-------------------+ +------------------+
| Email Notification| | Visualize Logs |
+-------------------+ +------------------+
|
+--------+
| Grafana|
+--------+

模块介绍

promtail:负责采集pod日志的 agent,以 daemonset 部署在每个节点,k8s 中宿主机上也部署有独立的 promtail 组件,用来采集宿主机部署的组件日志; distributor:以deploy方式部署,是一个无状态组件,负责处理、过滤由 promtail 发送过来的日志流数据,并分发到 ingester; ingester:用statefulset部署,接收到来自distributor 的日志后负责日志数据的存储和索引,也能定期将日志发往对象存储如 s3 兼容的持久性存储系统; querier:用statefulset部署,负责执行 logQL 查询,从 ingester 中查询日志数据,对查询结果做聚合、过滤等; query-frontend:以deploy方式部署,无状态服务,负责接收来自客户端(命令行 cli、grafana 等)的查询请求,将大查询拆成多个小查询;

promtail.yaml配置示例:

  - job_name: mysql-server-slow-query-job
static_configs:
- targets:
- localhost
labels:
__path__: /data00/mysql/log3406/slow_query.log
component: "MySQL"
service: "MysqlServer"
type: "SlowQuery"
hostName: k8s-192-168-4-86
hostIp: 192.168.4.86
logLevel: INFO
pipeline_stages:
- match:
selector: '{service="MysqlServer"}'
stages:
- multiline:
firstline: '^# Time:'
max_lines: 128

logQL 查询

查询分两类,metrics 查询和日志查询 日志查询:

logcli labels  #查所有 label
logcli labels service # 查指定 label
## 查指定label 的日志
logcli query '{service="k8s/backend-service"}'
## limit控制查询行数
logcli query '{service="k8s/backend-service"}' -q -o raw --limit 10
## 支持正则匹配关键字
logcli query '{service="k8s/backend-service"} |~ ".*WARNING.*|.*ERROR.*"' -q -o raw
## host label 筛选节点
logcli query '{service="k8s/backend-service",host="172.16.0.126"}' -q -o raw
## 指定查询时间范围
logcli query '{service="k8s/backend-service"}' -q --from="2022-12-24T00:00:00Z" --to="2022-12-24T02:00:00Z"
## -f 类似 tail -f 实时滚动最新日志
logcli query '{service="k8s/backend-service"} ' -q -o raw -f

日志 Metrics 查询: loki 支持各种聚合函数和运算对日志进行聚合,从日志中得到指标数据,基于日志的告警等,例如:

## 10分钟内ERROR日志过多
count_over_time({host=~"xxxx"}|~"error"[10m]) > 100

## 日志错误率超过 1%
sum(rate({service="xxx"} |= "error" [1m])) by (job) / sum(rate({service="xxx"}[1m])) by (service) > 0.01

metrics 查询函数可参考: https://grafana.com/docs/loki/latest/query/metric_queries/

k8s常用命令

## json格式输出,jq 命令解析字段
kubectl get svc prom-prometheus-server -n monitoring -o json | jq -r '.spec.clusterIP')
## 通过-o 指定输出字段
kubectl get nodes --no-headers -o custom-columns=NAME:.metadata.name
## 按节点统计运行中 pod 个数
kubectl get pod -A -o wide --no-headers |awk '{print $4,$8}' |grep -v Completed |awk '{print $2}' |sort |uniq -c
## 直接请求 apisever 接口
kubectl get --raw /api/v1/nodes/"$nodename"/proxy/stats/summary
## 根据 pod label 查询日志
kubectl -n $ns logs -l $labels
## 查找所有包含某个关键字的 deploy
kubectl get deploy -A --no-headers |awk '{print $1,$2}'|while read -r ns dp;do if kubectl -n $ns describe deploy $dp|grep -Eq 'hadoop-conf|hive-conf|spark-conf'; then echo $ns $dp;fi;done
## 查看一个 pod 最后一次挂掉的日志
kubectl -n monitoring logs k8s-fronted-monitor-f76fc4c88-n29xs -p
## 容器内无相关命令时,从容器网络内往外发送网络请求
docker inspect 51472f1ec767 | grep Pid # 找到 pid
sudo nsenter -t 208696 -n curl xxx.com # 使用 nsenter 命令进入容器网络并执行本地命令
## 查看 events 事件消息,例如 pod 一直 pending,pod 拉起失败等场景
kubectl -n k8s get events
## 查看 kubelet 日志
docker logs kubelet -f --tai 100
## 使用指定镜像运行临时容器,可进入容器查看文件内容等
kubectl run -it --rm --attach --image=registry.xxx.com:5000/xxx:vxxx debugpod -- bash
## 查找pid所属的 container
/tmp/hlkit1000/bin/pid-container.sh 3626508

拓展阅读

  1. https://kubernetes.io/zh-cn/docs/reference/kubectl/
  2. https://grafana.com/docs/loki/latest/query/log_queries/
  3. https://docs.nginx.com/nginx-ingress-controller/overview/design/

常见内核参数调优总结

· 阅读需 7 分钟
老司机
Maintainer of this proj

总结一下常见的内核参数调优

1. 常见参数列表

参数所属子系统取值范围调优示例功能说明优化场景及影响
fs.file-max文件系统取决于系统内存,通常为数十万至数百万1024000系统级最大文件描述符数。适用于高并发服务器,防止文件句柄耗尽导致服务不可用。
fs.inotify.max_user_watches文件系统通常为8192 ~ 数百万8192000单用户最大inotify监视数。适合文件监控密集的应用,如IDE、文件同步工具,防止监视资源不足。
fs.inotify.max_user_instances文件系统通常为128 ~ 数百万8192000单用户最大inotify实例数。配合max_user_watches使用,防止应用启动失败。
net.core.somaxconn网络128 ~ 6553565535每个socket监听等待应用程序accept队列最大长度。适用于高并发网络服务,提升连接排队能力,减少拒绝服务。
net.core.netdev_max_backlog网络128 ~ 6553510000进入网卡还没被内核协议栈处理的报文最大长度。适用于高并发网络服务,防止接收队列溢出。
net.ipv4.tcp_max_syn_backlog网络128 ~ n40960内核保持的未被 ACK 的 SYN 包最大队列长度。适用于高并发网络服务,防止半连接队列溢出。
net.ipv4.tcp_max_tw_buckets网络1024 ~ 65535180000TIME-WAIT套接字最大数量。适用于高并发网络服务,防止TIME-WAIT套接字耗尽。
net.ipv4.tcp_tw_reuse网络0 或 11允许重用TIME-WAIT状态的套接字。适合高连接频率客户端如 nginx 反向代理等,减少TIME-WAIT端口耗尽。
net.ipv4.tcp_fin_timeout网络1 ~ 60秒15TCP连接FIN-WAIT-2状态超时时间。减少关闭连接等待时间,释放资源,适合连接频繁的服务器。
net.ipv4.ip_local_port_range网络1024 ~ 6553515000 60999本地端口分配范围。扩大范围可支持更多短连接,适合高并发客户端或代理服务器。
net.ipv4.tcp_keepalive_time网络1 ~ 7200秒30最大闲置时间从最后一个 data packet之后多长时间开始发送探测包,单位秒,用于检测死连接,适合长连接服务,防止资源浪费。容器内此参数必须比宿主机小,否则可能出现 write:broken pipe报错
net.ipv4.tcp_keepalive_intvl网络1 ~ 300秒30探测间隔时间在此期间连接上传输了任何内容都不影响探测的发送,单位是秒
net.ipv4.tcp_keepalive_probes网络1 ~ 103最大失败次数超过此值后将通知应用层连接失效
net.ipv4.ip_forward网络0 或 11是否启用IP转发。路由器或网关必开,普通主机一般关闭。
net.ipv4.tcp_timestamps网络0 或 11启用TCP时间戳。提高网络性能和防止序列号回绕,但可能泄露时间信息。
net.ipv4.tcp_tw_recycle网络0 或 10快速回收TIME-WAIT套接字。一般禁用,因导致NAT环境连接问题。
net.ipv4.tcp_syncookies网络0 或 11启用SYN Cookies。提高TCP连接建立速度,适合高并发服务器。
net.ipv4.tcp_mem网络3个整数,以空格分隔3043587 4058117 6087174TCP内存分配参数。调整参数可优化TCP连接建立速度,适合高并发服务器。
net.ipv4.tcp_rmem网络3个整数,以空格分隔4096 87380 33554432TCP接收缓冲区参数。调整参数可优化TCP连接建立速度,适合高并发服务器。
net.ipv4.tcp_wmem网络3个整数,以空格分隔4096 65536 33554432TCP发送缓冲区参数。调整参数可优化TCP连接建立速度,适合高并发服务器。
net.core.rmem_max网络1024 ~ 1677721616777216接收缓冲区最大长度。适用于高并发网络服务,防止接收缓冲区溢出。
net.core.wmem_max网络1024 ~ 1677721616777216发送缓冲区最大长度。适用于高并发网络服务,防止发送缓冲区溢出。
net.core.rmem_default网络1024 ~ 167772161048576接收缓冲区默认长度。适用于高并发网络服务,防止接收缓冲区溢出。
net.core.wmem_default网络1024 ~ 16777216524288发送缓冲区4096 65536 33554432 默认长度。适用于高并发网络服务,防止发送缓冲区溢出。
net.ipv4.tcp_congestion_control网络算法名称,如cubic、bbr等cubicTCP拥塞控制算法。适用于高并发网络服务,优化网络性能。BBR 算法,适用于 4.9 以上的所有内核,建议使用 4.19 以上内核
vm.swappiness虚拟内存管理0 ~ 1000swap使用倾向,0尽量不使用swap,100积极使用。数据库等内存敏感应用常设为0以减少IO延迟。
vm.overcommit_memory虚拟内存管理0、1、21内存分配策略:0启发式,1总是允许,2严格限制。1适合内存充足环境,2适合内存紧张防止OOM。
vm.dirty_background_ratio虚拟内存管理0 ~ 1005脏页比例达到该值时触发后台写回。适用于数据库等需要频繁写入的应用,防止写回过多导致性能下降。
vm.max_map_count虚拟内存管理通常为65530 ~ 数百万,具体上限依赖内存1024000设置单个进程最大内存映射区域数量。适用于数据库(如Elasticsearch)等需要大量内存映射的应用,防止映射不足导致失败。
kernel.pid_max内核调度32768 ~ 41943034194303最大进程ID。适合容器或高并发进程场景,避免PID耗尽。
kernel.randomize_va_space内存管理/安全0、1、22地址空间布局随机化等级,0关闭,2完全启用。增强安全性,防止攻击。

半/全连接 半全连接队列是 server 端影响连接建立最常见两个限制,概念如下:

  1. 半连接队列:存放处于 TCP_NEW_SYN_RECV 状态的 sock,此时三次握手尚未完成,内核需要一个队列暂存。通常来说,这个参数和 /proc/sys/net/ipv4/tcp_max_syn_backlog 有关,修改后可立即生效;
  2. 全连接队列:存放完成三次握手,但进程尚未 accept 的队列的 sock。这个参数通常由两个部分确定,分别是 listen 函数的第二个参数和 /proc/sys/net/core/somaxconn 共同决定,小的为准,修改后需重启进程才能生效。

两者的关系:半连接队列受全连接队列影响的,道理也很简单,全连接都已经满了,也没必要再处理半连接了。在排除全连接队列影响下,两者的关系不同内核也一直在调整,不考虑 syncookie 情况下当前的 4.14/4.19 syn-sock 入队的条件如下:

  • 全连接队列未满;
  • tcp_max_syn_backlog - 半连接队列深度 > tcp_max_syn_backlog*1/4;
  • tcp_max_syn_backlog - 半连接队列深度 < 1/4 且client地址不久前成功连接过。

网络收包流程图

如何通过loop模拟一个lvm逻辑卷

· 阅读需 1 分钟
老司机
Maintainer of this proj

某些时候我们需要一个lvm逻辑卷,但是又没有多余的磁盘设备可用,这时可以通过模拟的方式创建一个设备出来,步骤如下:

# 首先创建一个虚拟block文件
dd if=/dev/zero of=pv1 bs=1M count=5000
# 然后通过losetup将其挂载到loop0设备上
sudo losetup /dev/loop0 pv1
# 查看状态
sudo losetup
# 这时可以对loop0设备进行操作,当做一个block设备使用
sudo pvcreate /dev/loop0
sudo vgcreate vg0 /dev/loop0
sudo lvcreate -n lv0 -l 100%FREE vg0
# 创建好lv之后就可以继续格式化文件系统
sudo mkfs.ext4 /dev/vg0/lv0
sudo mkdir /data01
sudo mount /dev/vg0/lv0 /data01

这样我们就完成了通过dd出一个空文件,然后通过loop设备模拟了一个lvm逻辑卷出来。 使用完之后清理:

sudo umount /data01
sudo lvremove /dev/vg0/lv0
sudo vgremove vg0
sudo pvremove /dev/loop0
sudo losetup -d /dev/loop0
rm pv1

普通用户执行systemctl启停服务禁用密码认证

· 阅读需 1 分钟
老司机
Maintainer of this proj

在CentOS系统中默认情况下,使用普通用户管理系统服务启停会要求认证,输出类似如下:

systemctl restart xxx
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password:


这里的认证是systemd引入polkit认证,可以通过polkit配置文件来改变默认行为,例如和配置某个普通用户免密执行sudo一样,这里也可以配置polkit认证免密:

配置文件路径:/etc/polkit-1/localauthority/50-local.d/xxx.pkla

例如 vi /etc/polkit-1/localauthority/50-local.d/xxx.pkla


[disable auth for admin]
Identity=unix-user:yourusername
Action=*
ResultActive=yes
ResultAny=yes
ResultInactive=yes

保存配置立即生效,再执行systemctl restart就不会提示认证了。

influxQL常用语句整理

· 阅读需 1 分钟
老司机
Maintainer of this proj

db

show databases
use db1
show retention policies
create database db2 with duration 30d replication 2

retention

alter retention policy default on db1 duration 168h default
show retention policies

measurements

use db1
show measurements
show measurements with measurement =~ /cpu/

tag

show tag keys from cpu
show tag values from cpu with key=cputype

field

show field keys from cpu

mac上使用docker交叉静态编译jq和fio

· 阅读需 2 分钟
老司机
Maintainer of this proj

思路

为了得到静态编译的jq和fio程序二进制,同时又需要x86_64和aarch64的版本,可以利用docker的buildx实现交叉编译

步骤

编译fio

mkdir fio && cd fio
vi Dockerfile

Dockerfile 如下

FROM ubuntu as build
WORKDIR /opt
ARG VER=fio-3.33
RUN if [ -e /etc/apt/sources.list ];then sed -ri 's/[a-zA-Z0-9.]+(debian.org|ubuntu.com)/mirrors.volces.com/g' /etc/apt/sources.list; fi && \
export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y git gcc make cmake libaio1 libaio-dev zlib1g zlib1g-dev
RUN git clone https://github.com/axboe/fio.git && \
cd fio && \
git checkout ${VER}
RUN cd fio && \
./configure --build-static
RUN cd fio && make && make install && \
strip `which fio` && cp `which fio` /fio-$(dpkg --print-architecture)

FROM scratch AS bin
COPY --from=build /fio-* /

执行编译

docker buildx build . --platform linux/amd64 --target bin --output .
docker buildx build . --platform linux/arm64 --target bin --output .

编译成功会在当前目录得到可执行程序fio-amd64和fio-arm64两个文件.

jq编译步骤类似,dockerfile如下:

FROM ubuntu as build
WORKDIR /opt
ARG VER=jq-1.6
RUN if [ -e /etc/apt/sources.list ];then sed -ri 's/[a-zA-Z0-9.]+(debian.org|ubuntu.com)/mirrors.volces.com/g' /etc/apt/sources.list; fi && \
export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y build-essential libtool git gcc make cmake autotools-dev autoconf
RUN git clone https://github.com/stedolan/jq.git && \
cd jq && \
git checkout ${VER} && git submodule update --init
RUN cd jq && \
autoreconf -fi && \
./configure --disable-maintainer-mode --disable-valgrind --with-oniguruma=builtin --enable-all-static --prefix=/usr/local
RUN cd jq && LDFLAGS=-all-static make -j4 && make install && \
strip /usr/local/bin/jq && cp /usr/local/bin/jq /jq-$(dpkg --print-architecture)

FROM scratch AS bin
COPY --from=build /jq-* /

pre-commit basic usage

· 阅读需 2 分钟
老司机
Maintainer of this proj

install

pip install pre-commit

init

git clone https://xxx/xxx.git
cd xxx
pre-commit install

pre-commit sample-config >.pre-commit-config.yaml

test

pre-commit run --all-files
pre-commit run --files xxx.py

sample conf

# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files

- repo: https://github.com/talos-systems/conform
rev: v0.1.0-alpha.27
hooks:
- id: conform
stages:
- commit-msg


- repo: https://github.com/5xops/mirrors-shellcheck
rev: v1.0
hooks:
- id: shellcheck
args:
- --exclude=SC2148,SC2155,SC2009,SC2029,SC1091

- repo: https://github.com/psf/black
rev: 23.11.0
hooks:
- id: black
args: [--line-length=1000]


# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v0.770
# hooks:
# - id: mypy
# language: python_venv
# exclude: ^(docs/|example-plugin/|tests/fixtures)

- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
exclude: $(.tox/|.git/|__pycache__/|build/|dist/|.cache|.eggs/)
args:
- --ignore=E501,W503,E722,W605,E203

- repo: https://github.com/PyCQA/pylint
rev: v3.0.1
hooks:
- id: pylint
language: python
args:
- --disable=R0801,C0114,C0115,C0116,C0209,C0415,E0401,W1401,R0912,R0913,R0914,R0915,W0212,C0103
- --max-line-length=120