hello云胜

技术与生活

0%

bridge

原理

在veth peer那一节我们说过,veth peer可以用于将两个ns进行网络互联。但是只限于两个,如果需要将多个ns的网络打通,那veth peer就不行了。

在这种场景下可以使用linux bridge技术。

linux bridge就是linux系统中的网桥,任何网络设备,不管是真实的物理设备,还是veth等虚拟设备,都可以连接到linux 网桥上。

实践

操作linux bridge可以使用ip link命令,也可以使用brctl。我这里使用brctl。

如果没有,需要先安装

1
$ yum install bridge-utils

创建一个bridge

1
2
3
4
[root@t-middle01 ~]# brctl addbr mybr
[root@t-middle01 ~]# brctl show
bridge name bridge id STP enabled interfaces
mybr 8000.000000000000 no

通过addbr创建一个名为mybr的网桥。通过show命令可以看一下。现在网桥还没有连接任何网卡。

创建一对veth

我们创建一对veth,然后挂到网桥上

1
2
3
[root@t-middle01 ~]# ip link add veth0 type veth peer name veth1
[root@t-middle01 ~]# ifconfig veth0 1.2.3.100/24 up
[root@t-middle01 ~]# ifconfig veth1 1.2.3.200/24 up

image-20231009093950110

Docker的4种网络通信模式

我们在使用docker run命令创建Docker容器时,可以使用–network选项指定容器的网络模式。

Docker有以下4种网络模式:·

  • bridge
  • host
  • container
  • none

在安装完Docker之后,Docker Daemon会在宿主机上自动创建三个网络,分别是bridge网络、host网络和none网络

可以使用docker network ls命令查看

1
2
3
4
5
]# docker network ls
NETWORK ID NAME DRIVER SCOPE
2595499e1c4c bridge bridge local
3440b4c385f2 host host local
6398e6ab94d1 none null local

为什么要建这三个网络?

bridge模式

我们在服务器上安装docker后,docker会自动创建一个名为docker0的linux bridge。

1
2
3
# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02421e0d1279 no

因为还没有启动任何容器,所以interfaces没有任何网卡。

1
2
3
4
5
# docker run --name busybox -itd busybox
e10df4d10de378d6b2db16d9ea5a3f9fb62294d2b9ba89df08f549c37b2769a6
# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02421e0d1279 no veth066ee4f

启动一个容器再看,就绑定了一个新的veth

stop掉,这个veth就会消失。

bridge模式是Docker的默认网络模式。

在bridge模式下,docker每启动一个容器,就会为容器创建一个新的network namespace,设置ip。并将该容器veth pair的一端连接到docker0网桥上。

docker0的默认IP地址均为172.17.0.1。接到docker0上的Docker容器的IP地址范围是172.17.0.0/24

可以查看刚启动的容器的路由来验证

image-20231010112543422

同一台主机上的所有容器都接入docker0网关,并且也是同一个网段,所有同一主机上的各个容器通过广播通信

看一下主机上的路由

1
2
3
4
5
[root@paas-m-k8s-master-1 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
。。。。
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0

从主机上对容器的访问都经过docker0

host模式

这种模式下,容器不会创建自己的network namespace。而是共享主机的network namespace,容器不会创建虚拟网卡,也不会配置ip。而是共享主机的ip和端口。

–network=host

image-20231010113514930

看到输出的全是主机的网卡信息

host模式的好处是

容器可以直接使用宿主机IP和主机名与外界通信,不需要进行NAT,也不需要通过Linux bridge进行转发或者进行数据包的封装。因此性能最高。

缺点也很明显,网络的隔离性不好,可以和主机争夺网络资源。并且存在端口冲突的问题。

container模式

和host模式类似,container模式就是将自己加到已经存在的某个容器的网络空间里。即共享别的容器的。

前面我们创建的一个busybox容器,其ip是172.17.0.4

image-20231010140654966

我新创建一个busybox2,指定–network=container:busybox。

可以看到其网络配置是容器busybox的。

是不是很熟悉的感觉?对,这就是k8s的pod网络的基本原理。

k8s 的pause容器创建出来之后,其他的容器会加入到pause容器的网络空间里。

none模式

none模式是只创建网路空间,然后创建一个lo回环设备。不再进行其他任何网络配置。

这种情况下,没有办法联网,属于完全封闭的网络。

固定PODIP

相比于在服务器上部署redis cluster,在k8s环境下的部署redis cluster的主要困难点就在于POD的IP重启会改变。

redis组织运行cluster强依赖于ip。所以我们必须解决redis ip变化的问题。

之前的部署文档

k8s中部署redis集群 (qq.com)

曾经写过一个方案,使用fix-ip.sh脚本。在pod重启时,将自己之前生成的nodes.conf文件中myself的ip改成新的。

其他活着的pod能够自动将这个重启的pod更新成新的ip。

不过这种方案有一个致命的问题:只能处理一个pod重启的情况,如果有多个pod或者k8s集群故障导致整个redis集群重启,

那么redis cluster是无法自己恢复的,表现为redis cluster整体故障,并且手工也很难恢复。在生产上不可接受。

那么就换另一个思路,能不能保持pod的ip是固定的,这样就解决了pod重启ip变化的问题。

答案是肯定的,k8s支持对pod保持固定ip。但是需要我们来人工进行干预配置。

我的k8s集群之前搭建时使用的网络插件是calico。所以使用calico的配置方案。

下载calicoctl工具

1
2
3
curl -O -L  https://github.com/projectcalico/calicoctl/releases/download/v3.18.4/calicoctl
chmod +x calicoctl
cp calicoctl /usr/local/bin/

创建地址池

查看一下calico的配置文件

1
cat  /etc/cni/net.d/10-calico.conflist

看一下ipam是不是calico-ipam。(IPAM是k8s cni插件中负责分配ip的一类插件)

不是的话先改成这个。

image-20230711173949619

calico默认会创建一个cidr

1
2
3
[root@paas-m-k8s-master-1 ~]# calicoctl get ippool
NAME CIDR SELECTOR
default-ipv4-ippool 100.64.0.0/10 all()

这是calico在启动pod时默认进行分配的ip地址池。

然后我们可以创建自己的地址池。

因为我们搭建3主3从的集群,需要6个ip。所以可以创建1个掩码为30位的地址池,其可用ip正好是4个

再加一个31位掩码的地址池,其可用ip是2个。

(calico分配ip时,全0和全1的ip是可用的)

1
2
3
4
5
6
7
8
9
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: redis-ippool1
spec:
blockSize: 31
cidr: 11.11.11.0/30
ipipMode: Always
natOutgoing: true

image-20230717165134495

1
2
3
4
5
6
7
8
9
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: redis-ippool1
spec:
blockSize: 31
cidr: 11.11.11.4/31
ipipMode: Always
natOutgoing: true

image-20230717165120545

1
2
3
4
5
6
7
[root@paas-m-k8s-master-1 calico]# calicoctl apply -f ippool1.yaml
[root@paas-m-k8s-master-1 calico]# calicoctl apply -f ippool2.yaml
[root@paas-m-k8s-master-1 calico]# calicoctl get ippool
NAME CIDR SELECTOR
default-ipv4-ippool 100.64.0.0/10 all()
redis-ippool1 11.11.11.0/30 all()
redis-ippool2 11.11.11.4/31 all()

对之前的redis部署资源文件进行改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
apiVersion: apps/v1
kind: StatefulSet
metadata:
namespace: redis-test
name: redis-cluster-static-ip
spec:
serviceName: redis-cluster-static-ip
replicas: 6
selector:
matchLabels:
app: redis-cluster-static-ip
template:
metadata:
labels:
app: redis-cluster-static-ip
# 增加这个注解
annotations:
#可以配置多个,逗号隔开
"cni.projectcalico.org/ipv4pools": "[\"redis-ippool1\",\"redis-ippool2\"]"
spec:
containers:
- name: redis
image: redis:5.0.5-alpine
ports:
- containerPort: 6379
name: client
- containerPort: 16379
name: gossport
# 不再使用fix-ip.sh
command: ["redis-server", "/data/conf/redis.conf"]
### 就绪指针探测
readinessProbe:
exec:
command:
- sh
- -c
- "redis-cli -h $(hostname) ping"
initialDelaySeconds: 15
timeoutSeconds: 5
livenessProbe:
exec:
command:
- sh
- -c
- "redis-cli -h $(hostname) ping"
initialDelaySeconds: 20
periodSeconds: 3

volumeMounts:
- name: config
mountPath: /data/conf
readOnly: false
- name: data
mountPath: /data/data
readOnly: false
volumes:
- name: config
configMap:
name: redis-config
defaultMode: 0755
volumeClaimTemplates:
- metadata:
name: data
annotations:
volume.beta.kubernetes.io/storage-class: "nfs-client"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi

部署

1
2
[root@paas-m-k8s-master-1 calico]# kubectl apply -f redis-sts-ippool.yaml
statefulset.apps/redis-cluster created

image-20230717171615011

组成集群

1
kubectl -n redis-test exec -it redis-cluster-static-ip-0 -- redis-cli --cluster create --cluster-replicas 1 -a 123456 $((kubectl -n redis-test get pods -l app=redis-cluster-static-ip -o jsonpath='{range.items[*]}{.status.podIP}:6379 ') | awk '{print $1" "$2" "$3" "$4" "$5" "$6" "}')

image-20230717171928812

可靠性测试

现在通过ip池限制了6个ip,但是这6个ip在6个pod之间是随机分配的。

如果6个pod全部重启,重新打乱ip的分配。集群是否可用?

当前的cluster nodes

image-20230717172126526

删除sts,模拟pod全部重启

1
2
3
[root@paas-m-k8s-master-1 calico]# kc -n redis-test delete sts redis-cluster-static-ip
[root@paas-m-k8s-master-1 calico]# kc -n redis-test get pod
[root@paas-m-k8s-master-1 calico]# kc apply -f redis-sts-ippool.yaml

image-20230717172818158

pod 3和5的ip换了

image-20230717172951710

测试redis集群是可用的。

总结,通过实验,该方案较以前的方案来说更加可靠。

(208条消息) Kubernetes 固定 Pod IP 地址方法_k8s如何固定pod的ip_-WF的博客-CSDN博客

(208条消息) Kubernetes 部署 Redis-Cluster_k8s部署redis集群ip变化就用不了怎么解决_-WF的博客-CSDN博客

修改label的影响

把index.html改一下

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<title>111111</title>
</head>
<body>
<h1>nghinx-11111111111111</h1>
</body>
</html>

curl一下

image-20231221150308560

只修改pod的label

1
2
3
4
5
[root@paas-m-k8s-master-1 yaml]# kubectl -n nginx get pod -Lapp
NAME READY STATUS RESTARTS AGE APP
nginx-deployment1-547fb748cf-z22p7 1/1 Running 0 5m52s nginx
[root@paas-m-k8s-master-1 yaml]# kubectl -n nginx label pod nginx-deployment1-547fb748cf-z22p7 app=nginx2 --overwrite
pod/nginx-deployment1-547fb748cf-z22p7 labeled

现在的deployment和service会怎么样了

image-20231221150752541

没事,一切照旧

因为又自动拉起了label是app=nginx的pod。

image-20231221150831183

就是deploy找不到pod了,replicas对不上了。就当pod死了,再拉起来一个。

现在svc指向的pod是 10.10.15.211

image-20231221151732869

是新拉起的lable是app=nginx的那个pod

修改svc的select

1
2
selector:
app: nginx

发现endpoint指向了之前的label改成app=nginx2的pod

image-20231221152323509

也就是说我们可以手动修改pod和svc的label对应关系来控制svc的服务指向

关闭kmem

修改虚机启动的引导项 grub 中的cgroup.memory=nokmem,让机器启动时直接禁用 cgroup的 kmem 属性

修改/etc/default/grub 为: 最后面加上cgroup.memory=nokmem

1
GRUB_CMDLINE_LINUX="crashkernel=auto net.ifnames=0 biosdevname=0 intel_pstate=disable cgroup.memory=nokmem"

生成引导文件配置:
虚拟机:
/usr/sbin/grub2-mkconfig -o /boot/grub2/grub.cfg
物理机:
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg

重启机器:
reboot

使用kube-prometheus监控k8s

使用kube-prometheus部署k8s监控(最新版)-腾讯云开发者社区-腾讯云 (tencent.com)

查看官方的版本对应关系

image-20231204151126271

但是我们公司的集群有一些比较老,还是1.18的

image-20231204151239351

找到了一个以前的对应表。

决定安装release-0.6的版本

1
2
git clone https://github.com/prometheus-operator/kube-prometheus.git
git checkout release-0.6

增加存储配置

kube-prometheus\manifests\prometheus-prometheus.yaml

1
2
3
4
5
6
7
8
9
10
...
version: v2.20.0
retention: 3d
storage:
volumeClaimTemplate:
spec:
storageClassName: nfs-client
resources:
requests:
storage: 5Gi

kube-prometheus\manifests\grafana-deployment.yaml

1
2
3
4
5
6
volumes:
# - emptyDir: {}
# name: grafana-storage
- name: grafana-storage
persistentVolumeClaim:
claimName: grafana-data

创建对应的grafana-data pvc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: grafana-data
namespace: monitoring
annotations:
volume.beta.kubernetes.io/storage-class: nfs-client
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: nfs-client

安装

1
2
3
kubectl apply -f manifests/setup
until kubectl get servicemonitors --all-namespaces ; do date; sleep 1; echo ""; done
kubectl apply -f manifests/

修复kube-scheduler和controller-manager监控异常问题

image-20231205154544527

image-20231205154634646

原本三个都是down的状态。我已经修复了一个。

原因是kube-scheduler和controller-manager部署的时候配置的bind-address是127.0.0.1

现在我们用主机的ip去连,自然是不会成功的。

修改一下bind-address为0.0.0.0就好了

image-20231205155018829

我的kube-scheduler和controller-manager都是静态pod

修改后,从/etc/kubernetes/manifests/移出,稍等几秒再移进来就可以了

目标是需要生成一个仅能操作demo这个namespace的kubeconfig文件

rbac资源准备

创建ServiceAccount

1
2
3
4
5
apiVersion: v1
kind: ServiceAccount
metadata:
name: demo-account
namespace: demo

Secret

在 K8s 1.24 版本之后,ServiceAccount 对应的 Secret 就不会自动创建了

1
2
# kubectl -n demo get secret
No resources found in demo namespace.

需要我们自己手动创建一下。之前的k8s自动会创建。

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
name: demo-account-secret
namespace: demo
annotations:
kubernetes.io/service-account.name: "demo-account"
type: kubernetes.io/service-account-token

这个 Secret 创建出来之后,K8s 会自动将 ServiceAccount 对应的 token 写进这个 Secret

image-20240614163227585

可以看到data部分中,ca.crt和token信息都有了。

后面创建kubeconfig文件时会用到这个token。

创建Role

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: demo-role
namespace: demo
rules:
- apiGroups: [""]
resources: ["services", "configmaps", "secrets", "pods"]
verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]

对常用资源的操作权限

RoleBinding

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: demo-rolebinding
namespace: demo
subjects:
- kind: ServiceAccount
name: demo-account
namespace: demo
roleRef:
kind: Role
name: demo-role
apiGroup: rbac.authorization.k8s.io

创建kubeconfig文件

关于kubeconfig文件的介绍之前写过

我们可以先复制一下集群默认的kubeconfig文件

1
cp ~/.kube/config demo.config

改一下其中的users部分

把之前secret中的token用base64解码一下

1
echo "xxx" | base64 -d 

填到下面user部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: v1
kind: Config

clusters:
- cluster:
certificate-authority-data: 都一样,不用改
server: 都一样,不用改
name: demo-cluster改一下,和业务相关命名


users:
- name: demo 你的用户名
user:
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IklaWG5QekZrSzM4cXkyZ2NPRFRxX3hxYUVuZ01vaTZ1NFZOZUxtUVBSeVUifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZW1vIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlbW8tYWNjb3VudC1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVtby1hY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiMWQwYmY1M2EtOTUwNS00N2E3LWE5OTItYjA4YzdiMTc2Yjc2Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlbW86ZGVtby1hY2NvdW50In0.tsH_CoGD_p7Rn1MMwEJrW-PEdUYh7H6KnS6QXxQzl4DMN3wWBA_jeiuir-4I0WhTjS5TQ_0OMtRtsaWcvL1XuvencUtAZFygqpsA20AHLeEsyFAvwmb_2GeInV8a-BFchcQUp8IcHEv2GXEdP5BGlK6zfroEK1cfPbaM-ITV82HFmVjn8eCynq0EON5EZATz4WO7ewnX2mEg7mh_aPWiW7_StjzZrPtvbbkA8ef1fpf67Ou9wym70TkEmovgYcTfBecY-OHrCkSuSStQilSQ5wi0rbsPDH-v8Z2SckXrCqO9kmqDBk-2d_65BK5oM0aTD7VFlxeWvRXXx_NjBCNh6A



contexts:
- context:
cluster: demo-cluster 和上面改的名要一致
namespace: demo 你的ns
user: demo 和上面改的名要一致
name: demo-context 改一下,和业务相关命名

合并kubeconfig文件

1
export KUBECONFIG=/root/.kube/config:/root/.kube/demo.config

之后kubectl config view 可以看到合并后的kubeconfig文件

image-20240614173502678

测试一下

1
kubectl --context demo-context get pod

image-20240614173926900

也可以使用之前推荐过的kubectx工具

image-20240614174742062

velero备份失败排查与解决

最近发现有一个集群的velero经常失败。

排查经过

  1. 查看velero备份状态
1
2
3
4
# 所有的备份
kubectl -n velero get backups
# 查看具体某个备份的状态
kubectl -n velero describe backups backup-all-20230409170058

image-20230411103629111

发现其状态是InProgress

如果是正常完成的

screenshot-20230411-103052

状态会是Completed

也可以通过

1
velero get backups

查看

image-20230411111421220

  1. 查看velero备份日志

    1
    2
    # velero backup logs backup-all-20230410170007
    Logs for backup "backup-all-20230410170007" are not available until it's finished processing. Please wait until the backup has a phase of Completed or Failed and try again.

    发现只有完成的或失败的,才能看日志,InProgress状态的不能查看日志

  2. 看一下velero的pod的日志

    1
    kubectl -n velero logs velero-84c6fd74dc-q7gph

    全是info日志,没有看出什么

    然后describe一下pod看看

    1
    kubectl -n velero describe pod velero-84c6fd74dc-q7gph

    image-20230411105633827

因为是OOM问题,把memory限制改大点试试。

修改其deployment

1
kubectl -n velero edit deploy velero

image-20230411111014097

根因

当velero的backup的集群或者namespace里资源比较多的情况下,容易触发OOM。

这些OOM的backup任务就会停留在InProgress状态

OOM之后,velero的pod重启,但是并不会执行之前InProgress状态的任务

所以就一直是InProgress状态

后来查看github发现,这是velero的一个bug,在1.8.0版本修复了。

想要升级velero,结果发现我们的k8s集群版本是1.17,velero1.8要求k8s在1.18以上,无奈作罢。

image-20230411113708458

后续观察

经过几天的观察,发现效果还不错。最近几天都是Completed

image-20230414094559150

velero打印log报错

使用velero logs打印日志

1
velero backup logs s02136

一直卡住,起初以为是因为日志太大,比较慢。

然后看了下pod的log,发现一直在报错

1
kubectl -n velero logs velero-7c69c567cd-k58zl

time=”2023-04-11T06:24:58Z” level=error msg=”Error updating download request” controller=download-request downloadRequest=velero/s02136-6b3dc423-69d5-4959-b7e9-cd74a45e4ac0 error=”downloadrequests.velero.io "s02136-6b3dc423-69d5-4959-b7e9-cd74a45e4ac0" not found” logSource=”pkg/controller/download_request_controller.go:81”

说download的时候有个资源找不到

那先看看这个s02136-6b3dc423-69d5-4959-b7e9-cd74a45e4ac0到底有没有

1
2
3
4
5
# kubectl -n velero get downloadrequest
NAME AGE
s02136-6b3dc423-69d5-4959-b7e9-cd74a45e4ac0 34m
s02136-fb567675-d0d0-4d41-b26b-303a407d3374 37m

发现是有的

经过研究发现,大概率是velero版本和k8s版本的匹配问题。

velero版本是1.7

有问题的这个k8s环境是1.17

我查看了1.18版本的k8s集群,发现velero logs命令没有问题。

volume的节点亲和性冲突问题解决

image-20231106155341771

describe一下pod看看

1
Warning  FailedScheduling  <unknown>  default-scheduler  0/5 nodes are available: 1 node(s) had taints that the pod didn't tolerate, 1 node(s) were unschedulable, 3 node(s) had volume node affinity conflict.

node上有volume的节点亲和性冲突。

那么去看看看这个pv有什么清河县的配置

1
kubectl -n kubesphere-monitoring-system get pod prometheus-k8s-0 -o yaml

看到pvc是这个

1
2
3
4
volumes:
- name: prometheus-k8s-db
persistentVolumeClaim:
claimName: prometheus-k8s-db-prometheus-k8s-0

进而找到pv,查看pv的信息

1
kubectl get pv pvc-15224634-6105-4716-a4b7-5e2fd6689db8 -oyaml

肯定是有nodeAffinity信息的

1
2
3
4
5
6
7
8
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- str-paas-k8s-0-node-2

嗯,设置了只能调度到str-paas-k8s-0-node-2这台机器上,

而之前因为内存过高,将这台机器加了污点,禁止调度