hello云胜

技术与生活

0%

为啥service的clusterip要用虚ip

基于网桥的容器网络

Kubernetes 集群网络有很多种实现,有很大一部分都用到了 Linux 网桥:

img

  • 每个 Pod 的网卡都是 veth 设备,veth pair 的另一端连上宿主机上的网桥。
  • 由于网桥是虚拟的二层设备,同节点的 Pod 之间通信直接走二层转发,跨节点通信才会经过宿主机 eth0。

Service 同节点通信问题

不管是 iptables 还是 ipvs 转发模式,Kubernetes 中访问 Service 都会进行 DNAT,将原本访问 ClusterIP:Port 的数据包 DNAT 成 Service 的某个 Endpoint (PodIP:Port),然后内核将连接信息插入 conntrack 表以记录连接,目的端回包的时候内核从 conntrack 表匹配连接并反向 NAT,这样原路返回形成一个完整的连接链路:

img

但是 Linux 网桥是一个虚拟的二层转发设备,而 iptables conntrack 是在三层上,所以如果直接访问同一网桥内的地址,就会直接走二层转发,不经过 conntrack:

  1. Pod 访问 Service,目的 IP 是 Cluster IP,不是网桥内的地址,走三层转发,会被 DNAT 成 PodIP:Port。
  2. 如果 DNAT 后是转发到了同节点上的 Pod,目的 Pod 回包时发现目的 IP 在同一网桥上,就直接走二层转发了,没有调用 conntrack,导致回包时没有原路返回 (见下图)。

img

由于没有原路返回,客户端与服务端的通信就不在一个 “频道” 上,不认为处在同一个连接,也就无法正常通信。

常见的问题现象就是偶现 DNS 解析失败,当 coredns 所在节点上的 pod 解析 dns 时,dns 请求落到当前节点的 coredns pod 上时,就可能发生这个问题。

开启 bridge-nf-call-iptables

如果 Kubernetes 环境的网络链路中走了 bridge 就可能遇到上述 Service 同节点通信问题,而 Kubernetes 很多网络实现都用到了 bridge。

bridge-nf-call-iptables 这个内核参数 (置为 1),表示 bridge 设备在二层转发时也去调用 iptables 配置的三层规则 (包含 conntrack),所以开启这个参数就能够解决上述 Service 同节点通信问题,这也是为什么在 Kubernetes 环境中,大多都要求开启 bridge-nf-call-iptables 的原因。

在运维过程中经常需要根据pid查是哪个pod,或者需要查这个pod的进程id。

比如我们收到监控报警,某台 Kubernetes Node 节点负载高。通过 top 或者 pidstat 命令获取 Pid

查询某个pod的进程PID

1
kubectl describe pod xxxpod

![image-20230919135218465](D:\github\docs\云原生\k8s\pod name与pid互查.assets\image-20230919135218465.png)

找到其运行的node节点和docker容器id。

快捷操作:

1
kubectl get pod busybox -o=jsonpath='{.status.containerStatuses[0].containerID}'

ssh到该node节点执行

1
docker inspect 容器id --format '{{.State.Pid}}'

如果是containerd

1
crictl inspect 容器id | grep -i pid

根据PID查询pod name

根据所有的进程都会在/proc/有自己的文件夹。

比如PID是22586

![image-20230919162950598](D:\github\docs\云原生\k8s\pod name与pid互查.assets\image-20230919162950598.png)

cat /proc/22586/cgroup 显示的最后一列就是容器的id

把他截取出来

1
cat /proc/22586/cgroup | head -1 | awk -F '/' '{print $5}'

然后就可以docker inspect了

1
docker inspect d569f04a

![image-20230919164622876](D:\github\docs\云原生\k8s\pod name与pid互查.assets\image-20230919164622876.png)

在Config.Labels下可以看到pod的name和namespace

总结成一个脚本getPodName.sh

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env bash

get_pod_name() {
CID=`cat /proc/${pid}/cgroup | head -1 | awk -F '/' '{print $5}'`
CID=$(echo ${CID:0:8})
podname=$(docker inspect $CID | jq '.[0].Config.Labels."io.kubernetes.pod.name"')
ns=$(docker inspect $CID | jq '.[0].Config.Labels."io.kubernetes.pod.namespace"')
echo $ns:$podname
}

pid=$1
get_pod_name

用到了jq工具,需要提前安装

1
yum install -y jq

pod内访问api-server

在默认namespace下有个名为kubernetes的svc

1
2
root@curlpod:/# ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token

我们和api-server交互的过程中,需要带上这个ca.crt。用来验证api-server的安全性。

有问题

secret

创建存储tls的secret

1
2
export out="--dry-run=client -o yaml"
kubectl create secret tls secret的名字 -n 命名空间 --cert=k8s.test.crt(crt文件) --key=k8s.test.key(密钥文件) $out > cert.yml

创建ssl证书

1
2
3
4
openssl req -x509 -days 365 -out k8s.test.crt -keyout k8s.test.key \
-newkey rsa:2048 -nodes -sha256 \
-subj '/CN=k8s.test' -extensions EXT -config <( \
printf "[dn]\nCN=k8s.test\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:k8s.test\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")

它生成的是一个 X509 格式的证书,有效期 365 天,私钥是 RSA2048 位,摘要算法是 SHA256,签发的网站是“k8s.test”。

运行命令行后会生成两个文件,一个是证书“k8s.test.crt”,另一个是私钥“k8s.test.key”

创建普通的secret

1
kubectl create secret generic user --from-literal=name=root $out

这里使用–from-literal,会自动进行base64编码

如果我们自己添加数据,需要进行base64编码

1
2
echo -n "123456" | base64
MTIzNDU2

要注意这条命令里的 echo ,必须要加参数 -n 去掉字符串里隐含的换行符,否则 Base64 编码出来的字符串就是错误的。

报错现象

简单来说就是创建pod进入pending状态,继续看日志发现最终原因是创建pv失败。

我这个集群使用的是nfs作为storageClass

查看nfs-provisioner的日志,发现报错

![image-20231027180250101](D:\github\docs\云原生\k8s\nfs 创建pv失败.assets\image-20231027180250101.png)

信息为

1
unexpected error getting claim reference: selfLink was empty, can’t make reference

查询多方资料

从kubernetes 1.20版本开始 禁用了 selfLink。

enhancements/keps/sig-api-machinery/1164-remove-selflink at master · kubernetes/enhancements (github.com)

我之前安装的一直是1.18版本。这次安装的是k8s的1.25版本。但是还是按照以前的安装步骤操作的,所有出现这个问题。

解决方法

使用新的不基于 SelfLink 功能的 provisioner 镜像,重新创建 provisioner 容器。

若你能科学上网,可使用这个镜像:

1
gcr.io/k8s-staging-sig-storage/nfs-subdir-external-provisioner:v4.0.0

国内可使用这个镜像:

1
registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0

该方法亲测有效

不建议的方案

在网上还看到这种方法,但是实际尝试之后api-server起不来,不建议使用。

这种方案应该是只适用于1.21~1.24的k8s版本

编辑/etc/kubernetes/manifests/kube-apiserver.yaml

1
2
3
4
spec:
containers:
- command:
- kube-apiserver

下添加一行

1
- --feature-gates=RemoveSelfLink=false

然后重启api-server

1
kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml

不建议用这个方法,改了之后api-server起不来了

kubesphere查看打镜像前的原始代码包

需求:

用户镜像启动有问题。问了排查原因,我需要看下打镜像前用户上传的原始代码包。

操作过程如下:

在kubesphere中,用户上传的代码包是放在minio中,然后再进行后续的打镜像操作。

所以问题转变为查找minio的登录信息

img

1
2
3
4
5
6
7
8
9
10
11
# kubectl get pod -n kubesphere-system
NAME READY STATUS RESTARTS AGE
etcd-65796969c7-vb67h 1/1 Running 0 89d
ks-apiserver-67ccdf6dfd-grshf 1/1 Running 0 31d
ks-console-b4df86d6f-qhpfm 1/1 Running 1 89d
ks-controller-manager-8bc6554fb-w6klz 1/1 Running 0 88d
ks-installer-7cb866bd-g8xdp 1/1 Running 0 89d
minio-7bfdb5968b-djwkw 1/1 Running 0 89d
mysql-7f64d9f584-pw6j2 1/1 Running 0 89d
openldap-0 1/1 Running 1 89d
redis-644bc597b9-c22kw 1/1 Running 1 89d

查看minio的登录信息

1
2
3
4
5
6
7
8
9
# kubectl logs minio-7bfdb5968b-djwkw -n kubesphere-system
You are running an older version of MinIO released 2 years ago
Update: https://docs.min.io/docs/deploy-minio-on-kubernetes


Endpoint: http://x.x.x.x:9000 http://127.0.0.1:9000

Browser Access:
http://x.x.x.x:9000 http://127.0.0.1:9000

得到登录地址

但是需要用户名和密码。

1
2
3
4
5
6
7
# kubectl get secret minio -n kubesphere-system -oyaml
apiVersion: v1
data:
accesskey: b3BlbnBpdHJpeG1pbmlvYWNjZXNza2V5
secretkey: b3BlbnBpdHJpeG1pbmlvc2VjcmV0a2V5
kind: Secret
...

得到了accesskey和secretkey。但是现在的key是base64加密的,所以还需要转换一下

1
2
3
4
# echo b3BlbnBpdHJpeG1pbmlvYWNjZXNza2V5 | base64 -d
openpitrixminioaccesskey
# echo b3BlbnBpdHJpeG1pbmlvc2VjcmV0a2V5 | base64 -d
openpitrixminiosecretkey

image-20210628155657066

登录成功,可以去s2i-binaries这个bucket下查找上传的代码包

kubectx 和 kubens工具

运维k8s的小伙伴们,平时敲的最多的命令应该就是kubectl -n了吧。

每次要在某个namespace空间下执行命令,都要加个-n namespce我觉得挺烦的。

有个开源工具可以解决这个烦恼。

kubens

kubens的功能就很简单,目的就是代替kubectl -n。

使用kubens指定了namespace之后,使用kubectl的命令就是在这个namespace下指定的了。

kubens-demo

kubectx

我之前写过一篇文章,将怎么配置kubeconfig文件来管理多个集群

使用kubectl config命令可以切换默认执行的集群。

现在推荐kubectx这个工具,在管理多个集群时可以更方便的切换

使用方法

1
2
kubectx # 查看集群列表
kubectx cluster-name # 切换集群

(这个也需要你先将kubeconfig文件配置好,只是操作起来更方便)

顺便说一个快速合并kubeconfig文件的方法

假设我们有两个 kubeconfig 文件,分别为 kcA 和 kcB。执行以下命令将它们合并到 kc文件中:

1
KUBECONFIG=kcA:kcB kubectl config view --flatten > kc

这里使用了 KUBECONFIG 环境变量来指定要合并的 kc文件,用冒号分隔多个文件路径。kubectl config view --flatten 命令用于将 kubeconfig 文件的内容展平为一个文件。最后,将合并后的内容重定向到 kc 文件中。

安装

1
2
3
git clone https://github.com/ahmetb/kubectx /opt/kubectx
ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
ln -s /opt/kubectx/kubens /usr/local/bin/kubens

另一种方式安装

也可以安装为kubectl命令的增强插件,需要先安装krew

1
2
kubectl krew install ctx
kubectl krew install ns

这种方式是这样使用的:

1
2
kubectl ctx
kubectl ns

kubectl报错

执行kubectl get 命令报错

1
Error from server (Timeout): the server was unable to return a response in the time allotted, but may still be processing the request (get pods)

查看api-server的日志

1
2
crictl ps
crictl logs 81bcff1a1d96b

看到错误信息

1
2
E1025 10:52:27.114364       1 controller.go:214] Unable to remove endpoints from kubernetes service: no master IPs were listed in storage, refusing to erase all endpoints for the kubernetes service

看etcd的日志

1
{"level":"warn","ts":"2023-10-25T03:06:43.360Z","caller":"etcdserver/util.go:166","msg":"apply request took too long","took":"179.923879ms","expected-duration":"100ms","prefix":"read-only range ","request":"key:\"/registry/leases/kubesphere-monitoring-system/7b8d27e6.kubesphere.io\" ","response":"range_response_count:1 size:582"}

kubectl label操作

显示标签

显示pod上所有标签

1
2
3
4
$ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nfs-provisioner-5c684ddcff-d4qmx 1/1 Running 0 56d app=nfs-provisioner,pod-template-hash=5c684ddcff,release=nfs-client

只显示指定标签

1
2
3
4
$ kubectl get pod -L app,release
NAME READY STATUS RESTARTS AGE APP RELEASE
nfs-provisioner-5c684ddcff-d4qmx 1/1 Running 0 56d nfs-provisioner nfs-client

加标签

1
$ kubectl label pod xxx creation_method=manual

改标签

1
$ kubectl label pod xxx env=debug --overwrite

选择

选择所有带有标签creation_method,并且其值为manual的pod

1
$ kubectl get pod -l creation_method=manual

选择所有带有标签creation_method,并且不管值是啥

1
$ kubectl get pod -l creation_method

没有env标签的pod

1
$ kubectl get pod -l '!env'

选择所有带有标签creation_method,并且其值不为manual的pod

1
$ kubectl get pod -l creation_method!=manual

用in做多值选择

1
$ kubectl get pod -l env in (prod,devel)

也有not in

1
$ kubectl get pod -l env not in (prod,devel)

多个条件,用逗号,隔开即可

1
$ kubectl get pod -l creation_method=manual,env=prod

kubebuilder(四)部署

将crd部署到k8s

1
make install

日志:

kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/demoes.tutorial.demo.com created

查看下

1
2
[root@paas-m-k8s-master-1 demo-operator]# kubectl api-resources | grep demo
demoes tutorial.demo.com true Demo

确实有了,不错哦。

编译运行controller

1
make run

保持窗口开启

1
2
3
4
5
6
7
8
9
。。。日志如下
go run ./main.go
I0314 14:37:11.802311 20314 request.go:665] Waited for 1.037997549s due to client-side throttling, not priority and fairness, request: GET:https://apiserver.cluster.local:6443/apis/rbac.istio.io/v1alpha1?timeout=32s
2024-03-14T14:37:13.055+0800 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"}
2024-03-14T14:37:13.055+0800 INFO setup starting manager
2024-03-14T14:37:13.055+0800 INFO starting metrics server {"path": "/metrics"}
2024-03-14T14:37:13.055+0800 INFO controller.demo Starting EventSource {"reconciler group": "tutorial.demo.com", "reconciler kind": "Demo", "source": "kind source: /, Kind="}
2024-03-14T14:37:13.055+0800 INFO controller.demo Starting Controller {"reconciler group": "tutorial.demo.com", "reconciler kind": "Demo"}
2024-03-14T14:37:13.258+0800 INFO controller.demo Starting workers {"reconciler group": "tutorial.demo.com", "reconciler kind": "Demo", "worker count": 1}

创建一个crd实例

在samples目录下有一个默认的资源描述文件tutorial_v1_demo.yaml

我们可以使用进行部署测试

1
2
3
4
5
6
7
8
9
10
11
apiVersion: tutorial.demo.com/v1
kind: Demo
metadata:
namespace: demo
name: demo-sample
spec:
# TODO(user): Add fields here
image: nginx:1.22
svcName: demo-ng
replicas: 3

现在还没有我们自定义的demo crd实例,demo nameSpace下也没有任何pod

1
2
3
4
# kubectl -n demo get demo
No resources found in default namespace.
# kubectl -n demo get pod
No resources found in demo namespace.

我们apply一下这个demo crd

1
# kubectl apply -f config/samples/tutorial_v1_demo.yaml

查看自定义资源

1
2
3
#  kubectl get demo -n demo
NAME AGE
demo-sample 12s

controller的日志

image-20240314160120867

查看资源

1
2
3
4
5
# kubectl -n demo get pod
NAME READY STATUS RESTARTS AGE
demo-ng-6df8f7c68f-4mg9n 1/1 Running 0 4m33s
demo-ng-6df8f7c68f-699bv 1/1 Running 0 4m33s
demo-ng-6df8f7c68f-n6zkc 1/1 Running 0 4m33s

看到3个pod都创建出来了

验证通过patch修改podNum,来增减pod的数量

1
kubectl -n demo patch demo demo-sample --type merge --patch '{"spec": {"replicas": 5}}'

image-20240314160619545

自动加到了5个

减一下

1
kubectl -n demo patch demo demo-sample --type merge --patch '{"spec": {"replicas": 2}}'

image-20240314160740364

controller中也会看到对应的日志

image-20240314162601021