hello云胜

技术与生活

0%

读书:深入刨析Kubernetes(二)

声明式API

kubectl apply命令

可以简单地理解为,kubectl replace 的执行过程,是使用新的 YAML 文件中的 API对象,替换原有的 API 对象;而 kubectl apply,则是执行了一个对原有 API 对象的 PATCH 操作

例子,Istio 项目,实际上就是一个基于 Kubernetes 项目的微服务治理框架

image-20210817141529538

Istio 项目架构的核心所在。Istio 最根本的组件,是运行在每一个应用 Pod 里的 Envoy 容器

这个 Envoy 项目是 Lyft 公司推出的一个高性能 C++ 网络代理,也是 Lyft 公司对 Istio 项目的唯一贡献

而 Istio 项目,则把这个代理服务以 sidecar 容器的方式,运行在了每一个被治理的应用 Pod中。我们知道,Pod 里的所有容器都共享同一个 Network Namespace。所以,Envoy 容器就能够通过配置 Pod 里的 iptables 规则,把整个 Pod 的进出流量接管下来。

这时候,Istio 的控制层(Control Plane)里的 Pilot 组件,就能够通过调用每个 Envoy 容器的API,对这个 Envoy 代理进行配置,从而实现微服务治理。

这个Envoy 是怎么被嵌入的?

在 Kubernetes 项目中,当一个 Pod 或者任何一个 API 对象被提交给 APIServer 之后,总有一些“初始化”性质的工作需要在它们被 Kubernetes 项目正式处理之前进行。

而这个“初始化”操作的实现,借助的是一个叫作 Admission 的功能。它其实是 Kubernetes项目里一组被称为 Admission Controller 的代码,可以选择性地被编译进 APIServer 中,在API 对象创建之后会被立刻调用到。

但这就意味着,如果你现在想要添加一些自己的规则到 Admission Controller,就会比较困难。因为,这要求重新编译并重启 APIServer。显然,这种使用方法对 Istio 来说,影响太大了

所以,Kubernetes 项目为我们额外提供了一种“热插拔”式的 Admission 机制,它就是Dynamic Admission Control,也叫作:Initializer

Istio 要做的,就是编写一个用来为 Pod“自动注入”Envoy 容器的 Initializer

首先,Istio 会将这个 Envoy 容器本身的定义,以 ConfigMap 的方式保存在 Kubernetes 当中。

编写一个自定义控制器

声明式API详解

在 Kubernetes 项目中,一个 API 对象在 Etcd 里的完整资源路径,是由:Group(API 组)、Version(API 版本)和 Resource(API 资源类型)三个部分组成的。

image-20210817143550433

image-20210817150554001

CRD

Custom Resource Definition。自定义 API 资源。允许用户在Kubernetes 中添加一个跟 Pod、Node 类似的、新的 API 资源类型

需要写go代码

还需要编写自定义控制器

RBAC

一般我们直接使用k8s的内置用户,ServiceAccount

Operator

Operator 的工作原理,实际上是利用了 Kubernetes 的自定义 API 资源(CRD),来描述我们想要部署的“有状态应用”;然后在自定义控制器里,根据自定义 API 对象的变化,来完成具体的部署和运维工作。

约等于用代码来生成每个pod的启动命令,并把他们启动起来。

PV && PVC && StorageClass

PV 描述的,是持久化存储数据卷。这个 API 对象主要定义的是一个持久化存储在宿主机上的目录,比如一个 NFS 的挂载目录。通常情况下,PV 对象是由运维人员事先创建在 Kubernetes 集群里待用的。

而PVC 描述的,则是 Pod 所希望使用的持久化存储的属性。比如,Volume 存储的大小、可读写权限等等。PVC 对象通常由开发人员创建;或者以 PVC 模板的方式成为 StatefulSet 的一部分,然后由StatefulSet 控制器负责创建带编号的 PVC。

用户创建的 PVC 要真正被容器使用起来,就必须先和某个符合条件的 PV 进行绑定。而绑定要满足两个条件:

  1. PV 和 PVC 的 spec 字段。比如,PV 的存储(storage)大小,就必须满足 PVC 的要求
  2. PV 和 PVC 的 storageClassName 字段必须一样

Pod 需要做的,就是在 volumes 字段里声明自己要使用的 PVC 名字

不难看出,PVC 和 PV 的设计,其实跟“面向对象”的思想完全一致。

PVC 可以理解为持久化存储的“接口”,它提供了对某种持久化存储的描述,但不提供具体的实现;而这个持久化存储的实现部分则由 PV 负责完成。

这样做的好处是,作为应用开发者,我们只需要跟 PVC 这个“接口”打交道,而不必关心具体的实现是 NFS 还是 Ceph。

有一个问题,如果创建pod时,没有合适的pv给pvc去绑定,怎么办?

Kubernetes 中,实际上存在着一个专门处理持久化存储的控制器,PersistentVolumeController。

PersistentVolumeController 会不断地查看当前每一个 PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC进行绑定。这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它就能够很快进入绑定状态,从而结束“单身”之旅。

进一步,pv由运维人员提前创建,在大规模的生产环境里,这其实是一个非常麻烦的工作。在实际操作中,这几乎没办法靠人工做到。所以,Kubernetes 为我们提供了一套可以自动创建 PV 的机制,即:Dynamic Provisioning。

Dynamic Provisioning 机制工作的核心,在于一个名叫 StorageClass 的 API 对象。

而 StorageClass 对象的作用,其实就是创建 PV 的模板。

具体地说,StorageClass 对象会定义如下两个部分内容:

第一,PV 的属性。比如,存储类型、Volume 的大小等等。

第二,创建这种 PV 需要用到的存储插件。比如,Ceph 等等

有了这样两个信息之后,Kubernetes 就能够根据用户提交的 PVC,找到一个对应的StorageClass 了。然后,Kubernetes 就会调用该 StorageClass 声明的存储插件,创建出需要的 PV。

这时候,作为应用开发者,我们只需要在 PVC 里指定要使用的 StorageClass 名字即可。

image-20210819142850853

有了 Dynamic Provisioning 机制,运维人员只需要在 Kubernetes 集群里创建出数量有限的StorageClass 对象就可以了。这就好比,运维人员在 Kubernetes 集群里创建出了各种各样的PV 模板。这时候,当开发人员提交了包含 StorageClass 字段的 PVC 之后,Kubernetes 就会根据这个 StorageClass 创建出对应的 PV。

Kubernetes 只会将 StorageClass 相同的 PVC 和 PV 绑定起来。实际上,如果你的集群已经开启了名叫 DefaultStorageClass 的 Admission Plugin,它就会为PVC 和 PV 自动添加一个默认的 StorageClass;否则,PVC 的 storageClassName 的值就是“”,这也意味着它只能够跟 storageClassName 也是“”的 PV 进行绑定。

网络基础

docker0

在 Linux 中,能够起到虚拟交换机作用的网络设备,是网桥(Bridge)。它是一个工作在数据 链路层(Data Link)的设备,主要功能是根据 MAC 地址学习来将数据包转发到网桥的不同端 口(Port)上。

而为了实现上述目的,Docker 项目会默认在宿主机上创建一个名叫 docker0 的网桥,凡是连 接在 docker0 网桥上的容器,就可以通过它来进行通信。

那么如何把这些容器“连接”到 docker0 网桥上呢?

需要使用一种名叫Veth Pair的虚拟设备了。

Veth Pair 设备的特点是:它被创建出来后,总是以两张虚拟网卡(Veth Peer)的形式成对出 现的。并且,从其中一个“网卡”发出的数据包,可以直接出现在与它对应的另一张“网 卡”上,哪怕这两个“网卡”在不同的 Network Namespace 里。 这就使得 Veth Pair 常常被用作连接不同 Network Namespace 的“网线”。

image-20210823102732579

跨主机容器通信

在 Docker 的默认配置下,一台宿主机上的 docker0 网桥,和其他宿主机上的 docker0 网桥, 没有任何关联,它们互相之间也没办法连通。所以,连接在这些网桥上的容器,自然也没办法进 行通信了。

我们通过软件的方式,创建一个整个集群“公用”的网桥,然后把集群里的所有容器都连接 到这个网桥上

image-20210823103054993

构建这种容器网络的核心在于:我们需要在已有的宿主机网络上,再通过软件构建一 个覆盖在已有宿主机网络之上的、可以把所有容器连通在一起的虚拟网络。所以,这种技术就被 称为:Overlay Network(覆盖网络)。

Flannel

Flannel 项目是 CoreOS 公司主推的容器网络方案。事实上,Flannel 项目本身只是一个框架,真 正为我们提供容器网络功能的,是 Flannel 的后端实现。目前,Flannel 支持三种后端实现,分别 是: 1. VXLAN; 2. host-gw; 3. UDP。

UDP模式

UDP 模式,是最直接、也是 最容易理解的容器跨主网络实现。却也是性能最差的一种方式。已废弃。

在 Linux 中,TUN 设备是一种工作在三层(Network Layer)的虚拟网络设备。TUN 设备的功能 非常简单,即:在操作系统内核和用户应用程序之间传递 IP 包。

Flannel 项目里一个非常重要的概念:子网(Subnet)。事实上,在由 Flannel 管理的容器网络里,一台宿主机上的所有容器,都属于该宿主机被分配的一 个“子网”。而这些子网与宿主机的对应关系,正是保存在 Etcd 当中。

flanneld 在收到 container-1 发给 container-2 的 IP 包之后,就会把这个 IP 包直接封装 在一个 UDP 包里,然后发送给 Node 2。

image-20210823105043545

Flannel UDP 模式提供的其实是一个三层的 Overlay 网络,即:它首先对发出端的 IP 包进行 UDP 封装,然后在接收端进行解封装拿到原始的 IP 包,进而把这个 IP 包转发给目标容 器。

性能问题

仅在发出 IP 包的过程中,就需要经过三次用户态与内核态之间的数据拷贝

image-20210823105233969

第一次:用户态的容器进程发出的 IP 包经过 docker0 网桥进入内核态;

第二次:IP 包根据路由表进入 TUN(flannel0)设备,从而回到用户态的 flanneld 进程;

第三次:flanneld 进行 UDP 封包之后重新进入内核态,将 UDP 包通过宿主机的 eth0 发出去。

此外,我们还可以看到,Flannel 进行 UDP 封装(Encapsulation)和解封装(Decapsulation) 的过程,也都是在用户态完成的。在 Linux 操作系统中,上述这些上下文切换和用户态操作的代价 其实是比较高的,这也正是造成 Flannel UDP 模式性能不好的主要原因。

VXLAN模式

主流的容器网络方案。

VXLAN,即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络 虚似化技术。所以说,VXLAN 可以完全在内核态实现上述封装和解封装的工作,从而通过与前面 相似的“隧道”机制,构建出覆盖网络(Overlay Network)。

VXLAN 的覆盖网络的设计思想是:在现有的三层网络之上,“覆盖”一层虚拟的、由内核 VXLAN 模块负责维护的二层网络,使得连接在这个 VXLAN 二层网络上的“主机”(虚拟机或者容器都可 以)之间,可以像在同一个局域网(LAN)里那样自由通信。当然,实际上,这些“主机”可能分 布在不同的宿主机上,甚至是分布在不同的物理机房里。

为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧 道”的两端。这个设备就叫作 VTEP,即:VXLAN Tunnel End Point(虚拟隧道端点)。

image-20210823105654600

每台主机上都有一个VTEP。这些 VTEP 设备之间,就需要想办法组成一个虚拟的二层网络,即:通过二层数据帧进行通信。

“源 VTEP 设备”收到“原始 IP 包”后,就要想办法把“原始 IP 包”加上 一个目的 MAC 地址,封装成一个二层数据帧,然后发送给“目的 VTEP 设备”

K8S的CNI

Kubernetes 是通过一个叫作 CNI 的接口,维护了一个单独的网桥来代替 docker0。这个网桥 的名字就叫作:CNI 网桥,它在宿主机上的设备名称默认是:cni0

以 Flannel 的 VXLAN 模式为例,在 Kubernetes 环境里,它的工作方式没有任何不同。只不过,docker0 网桥被替换成了 CNI 网桥而已。

image-20210823111221761

需要注意的是,CNI 网桥只是接管所有 CNI 插件负责的、即 Kubernetes 创建的容器 (Pod)。而此时,如果你用 docker run 单独启动一个容器,那么 Docker 项目还是会把这个 容器连接到 docker0 网桥上。所以这个容器的 IP 地址,一定是属于 docker0 网桥的 172.17.0.0/16 网段。

纯三层方案

其中的典型例子,是Flannel 的 host-gw 模式和 Calico 项目

Flannel 的 host-gw 模式

host-gw 示意图

image-20210823141353700

可以看到,host-gw 模式的工作原理,其实就是将每个 Flannel 子网(Flannel Subnet,比 如:1x.xx.1.0/24)的“下一跳”,设置成了该子网对应的宿主机的 IP 地址。

host-gw 模式能够正常工作的核心,就在于 IP 包在封 装成帧发送出去的时候,会使用路由表里的“下一跳”来设置目的 MAC 地址。这样,它就会经 过二层网络到达目的宿主机。 所以说,Flannel host-gw 模式必须要求集群宿主机之间是二层连通的。

宿主机之间二层不连通的情况也是广泛存在的。比如,宿主机分布在了不同的子 网(VLAN)里。

Calico

Calico 项目提供的网络解决方案,与 Flannel 的 host-gw 模式,几乎是完全一样的。

1
< 目的容器 IP 地址段 > via < 网关的 IP 地址 > dev eth0

网关的 IP 地址,正是目的容器所在宿主机的 IP 地址。这个三层网络方案得以正常工作的核心,是为每个容器的 IP 地址,找到它所对 应的、“下一跳”的网关。

不同于 Flannel 通过 Etcd 和宿主机上的 flanneld 来维护路由信息的做法,Calico 项目 使用了一个“重型武器”来自动地在整个集群中分发路由信息。这个“重型武器”,就是 BGP。

BGP 的全称是 Border Gateway Protocol,即:边界网关协议。它是一个 Linux 内核原生就支 持的、专门用在大规模数据中心里维护不同的“自治系统”之间路由信息的、无中心的路由协 议。

image-20210823144829233

边界网关。它跟普通 路由器的不同之处在于,它的路由表里拥有其他自治系统里的主机路由信息。

对于一个超大超复杂的网络系统,手动去维护这个路由信息是不现实的。这种情况下,BGP 大显身手的时刻就到了

在使用了 BGP 之后,你可以认为,在每个边界网关上都会运行着一个小程序,它们会将各自的 路由表信息,通过 TCP 传输给其他的边界网关。而其他边界网关上的这个小程序,则会对收到 的这些数据进行分析,然后将需要的信息添加到自己的路由表里。

所以说,所谓 BGP,就是在大规模网络中实现节点路由信息共享的一种协议。

image-20210823145450671

Calico 项目与 Flannel 的 host-gw 模式的另一个不同之处, 就是它不会在宿主机上创建任何网桥设备

Calico 项目实际上将集群里的所有节点,都当作是边界路由器来处理,它们一起组 成了一个全连通的网络,互相之间通过 BGP 协议交换路由规则。这些节点,我们称为 BGP Peer。

但是如果二层不通,需要开启IPIP模式

IPIP模式

image-20210823150010535

在 Calico 的 IPIP 模式下,Felix 进程在 Node 1 上添加的路由规则,会稍微不同,如下所示:

1
10.233.2.0/24 via 192.168.2.2 tunl0

可以看到,尽管这条规则的下一跳地址仍然是 Node 2 的 IP 地址,但这一次,要负责将 IP 包 发出去的设备,变成了 tunl0。注意,是 T-U-N-L-0,而不是 Flannel UDP 模式使用的 T-U-N-0(tun0),这两种设备的功能是完全不一样的。

Calico 使用的这个 tunl0 设备,是一个 IP 隧道(IP tunnel)设备

IP 包进入 IP 隧道设备之后,就会被 Linux 内核的 IPIP 驱动接管。IPIP 驱动 会将这个 IP 包直接封装在一个宿主机网络的 IP 包

这样就通过三层路由,发到node2。然后node2进行解包。

编译步骤

ks-apiserver开放

1
2
kubectl -n kubesphere-system patch svc ks-apiserver -p '{"spec":{"type":"NodePort","ports":[{"port":80,"protocal":"TCP","targetPort":9090,"nodePort":30881}]}}'

修改配置文件

server/config.yaml

1
2
3
4
5
6
# backend service gateway server
apiServer:
clientID: kubesphere
clientSecret: kubesphere
url: http://1x.xxx.151.211:30881
wsUrl: ws://1x.xxx.151.211:30881

启动测试

1
2
3
4
npm install -g yarn@1.22.4
yarn
yarn dev:client
yarn dev:server

遇到的问题

yarn 网络失败

修改了npm源

1
2
3
4
5
6
7
8
9
10
npm config set registry https://registry.npm.taobao.org

npm config set disturl https://npm.taobao.org/dist

npm config rm proxy

npm config rm https-proxy

yarn config set sass-binary-site http://npm.taobao.org/mirrors/node-sass

执行了这些步骤仍然失败
删掉了yarn.lock
yarn 成功

或者把yarn.lock里的域名全部换成registry.npmmirror.com

安装cypress失败

1
npm install cypress@3.8.3 --save-dev

使用npm 直接install成功

备用

生成token

1
2
3
4
5
6
7
curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
'http://1x.xxx.151.208:30881/oauth/token' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=admin' \
--data-urlencode 'password=P@88w0rd' \
--data-urlencode 'client_id=kubesphere' \
--data-urlencode 'client_secret=kubesphere'

测试

1
2
3
curl -X GET -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwidG9rZW5fdHlwZSI6ImFjY2Vzc190b2tlbiIsImV4cCI6MTY2NTU5ODE0MywiaWF0IjoxNjY1NTU0OTQzLCJpc3MiOiJrdWJlc3BoZXJlIiwibmJmIjoxNjY1NTU0OTQzfQ.NK8iD4l9WLz4zbO4XeFexzxxaylXiQgizQlIIvKpB9s" \
-H 'Content-Type: application/json' \
'http://1x.xxx.151.208:30881/kapis/resources.kubesphere.io/v1alpha3/nodes'

编译kubesphere apiserver

遇到的问题:

  1. 找不到依赖问题

kubesphere使用vendor管理依赖。

需要执行

1
2
3
go mod tidy
go mod download
go mod vendor

将依赖拷贝到vendor目录下,否则会找不到

  1. 无执行权限问题

    1
    chomd +x hack/*.sh
  2. Dockerfile配置goproxy

    1
    ENV GOPROXY https://goproxy.cn
  3. build镜像时需要传参

    以确定下载的helm的安装包

    TARGETARCH : amd64

    TARGETOS : linux

  4. 加速Docker build

    有一些去远程下载的文件,换成本地

    helm是从远程下载的,提前下好,放在本地

    1
    COPY helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz /tmp
  5. 提供的获取token的接口,必须是post

    并且Content-Type是application/x-www-form-urlencoded

    ![image-20230802134138690](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230802134138690.png)

编译打包apiserver

1
make ks-apiserver

编译成功,会在bin/cmd目录下生成可执行文件

![image-20230731172458071](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230731172458071.png)

获取kubesphere.yaml

在之前已部署好的ks环境执行

1
kubectl -n kubesphere-system get cm kubesphere-config -o yaml > kubesphere.yaml

然后改一改

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
kubernetes:
kubeconfig: "/root/.kube/config"
master: https://10.x.x.x:6443
qps: 1e+06
burst: 1000000

authentication:
authenticateRateLimiterMaxTries: 10
authenticateRateLimiterDuration: 10m0s
loginHistoryRetentionPeriod: 168h
maximumClockSkew: 10s
multipleLogin: True
kubectlImage: kubesphere/kubectl:v1.18.0
jwtSecret: "mzmyz0FufNThfklUF3vAJVPaSJZDJyaL"

redis:
host: redis.kubesphere-system.svc
port: 6379
password: ""
db: 0
network:
enableNetworkPolicy: true
ippoolType: none
monitoring:
endpoint: http://prometheus-operated.kubesphere-monitoring-system.svc:9090
logging:
host: http://elasticsearch-logging-data.kubesphere-logging-system.svc:9200
indexPrefix: ks-logstash-log
events:
host: http://elasticsearch-logging-data.kubesphere-logging-system.svc:9200
indexPrefix: ks-logstash-events
auditing:
enable: true
host: http://elasticsearch-logging-data.kubesphere-logging-system.svc:9200
indexPrefix: ks-logstash-auditing

alerting:
prometheusEndpoint: http://prometheus-operated.kubesphere-monitoring-system.svc:9090
thanosRulerEndpoint: http://thanos-ruler-operated.kubesphere-monitoring-system.svc:10902
thanosRuleResourceLabels: thanosruler=thanos-ruler,role=thanos-alerting-rules

kubesphere会读取这个配置文件,先从启动的当前目录下找,找不到去/etc/kubesphere/下找

所以,我们现在把kubesphere.yaml放在代码目录的/bin/cmd下

启动

1
./ks-apiserver

![image-20230801093045431](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230801093045431.png)

启动成功,在9090端口启动监听。

测试

我们之前写的测试接口

![image-20230801093135620](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230801093135620.png)

构建镜像

在代码的根目录执行

1
docker build -f build/ks-apiserver/Dockerfile -t harbor-test.xxx.net/kubesphere/ks-apiserver:1.3.0 --build-arg TARGETOS=linux --build-arg TARGETARCH=amd64 .

然后再push到我们自己的harbor上

部署到集群中

1
kubectl -n kubesphere-system edit deploy ks-apiserver

原来的镜像是

1
image: kubesphere/ks-apiserver:v3.1.1

替换为我们自己的

验证

![image-20230801100844578](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230801100844578.png)

从ks-console的端口访问我们自定义的接口,成功

修改ks-installer

1
kubectl -n kubesphere-system edit cc ks-installer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spec:
authentication:
jwtSecret: ''
authenticateRateLimiterMaxTries: 10
authenticateRateLimiterDuration: 10m0s
oauthOptions:
accessTokenMaxAge: 1h
accessTokenInactivityTimeout: 30m
identityProviders:
- name: xxx
type: xxxIDaaSProvider
mappingMethod: auto
provider:
clientID: 'f7d8ba1339b079883cb8e8496690b6c6'
clientSecret: '0ae8e4f66358cd8894b1f6c697726968'
redirectURL: http://1x.xxx.151.208:30880/oauth/redirect/xxx
endpoint:
tokenURL: http://1x.xxx.7.21:30484/auth/getToken
authURL: https://iama.xxx.net/
userInfoURL: http://1x.xx.132.10:8080/auth/getUserInfo

要说的是这个redirectURL。就写这个地址,是ks-console的接口地址。最后的xxx是provider的name

查看日志

1
kubectl logs -n kubesphere-system $(kubectl get pod -n kubesphere-system -l 'app in (ks-install, ks-installer)' -o jsonpath='{.items[0].metadata.name}') -f

![image-20230801171026395](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230801171026395.png)

  1. 调用authURL 获取一个code
  2. 调用tokenURL 获取token。 这里需要上一步返回的code作为入参
  3. 拿着这个token 再去调userInfoURL 获取用户信息
  4. redirectURL 是完成后跳转的地址,写 http://1x.xxx.151.208:30880/oauth/redirect/xxx

![image-20230802153715226](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230802153715226.png)

“users.iam.kubesphere.io is forbidden: User "system:pre-registration" cannot create resource "users" in API group "iam.kubesphere.io" at the cluster scope

![image-20230802170859639](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230802170859639.png)

globalroles.iam.kubesphere.io pre-registration 是有create user的权限的

![image-20230802171020181](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230802171020181.png)

看globalrolebindings.iam.kubesphere.io 是把pre-registration 这个role和名为pre-registration的group绑定了

![image-20230802171404975](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230802171404975.png)

查看过clusterroles.rbac.authorization.k8s.io 并没有pre-registration的相关信息

既然报错说system:pre-registration这个用户没有权限,那就给他加上

1
kubectl edit globalrolebindings.iam.kubesphere.io  pre-registration
1
2
3
- apiGroup: iam.kubesphere.io/v1alpha2
kind: User
name: system:pre-registration

![image-20230802173405927](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230802173405927.png)

成功

![image-20230802173527464](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230802173527464.png)

第二天,不知道为啥又坏了,报错

![image-20230803103644475](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230803103644475.png)

这个报错只能看出是代码出错了,没有其他有用的信息

只能看源码

pkg/kapis/oauth/register.go 是rest请求的路由注册代码

通过反向追踪代码,找到这个源码入口

pkg/kapis/oauth/handler.go:342

![image-20230803103749558](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230803103749558.png)

通过log

![image-20230803151653144](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230803151653144.png)

发现

1
requestInfo, _ := request.RequestInfoFrom(req.Request.Context())

这个requestInfo很奇怪,怎么去调用github了

怀疑是今天网不通了,昨天通。所以昨天好的,今天忽然不好了。

![image-20230803173144211](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230803173144211.png)

1
2
3
4
message: 'workspaces.tenant.kubesphere.io is forbidden: User "system:pre-registration" cannot list resource "workspaces" in API group "tenant.kubesphere.io" at the cluster scope',
reason: 'Forbidden',
details: { group: 'tenant.kubesphere.io', kind: 'workspaces' },

3.1版本的console解析的result

![image-20230804101053395](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230804101053395.png)

和3.3版本的apiserver返回的结构对的上

![image-20230804101208792](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230804101208792.png)

![image-20230804101236192](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230804101236192-16911151567331.png)

apiserver返回的是 access_token bearer refresh_token

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTExMTc5NTIsImlhdCI6MTY5MTExNDM1MiwiaXNzIjoia3ViZXNwaGVyZSIsInN1YiI6InN5c3RlbTpwcmUtcmVnaXN0cmF0aW9uIiwidG9rZW5fdHlwZSI6ImFjY2Vzc190b2tlbiIsInVzZXJuYW1lIjoic3lzdGVtOnByZS1yZWdpc3RyYXRpb24iLCJleHRyYSI6eyJlbWFpbCI6WyJ5YW5neXVuc2hlbmcuaXRAaGFpZXIuY29tIl0sImlkcCI6WyJoYWllciJdLCJ1aWQiOlsiMDE0NzEwNzYiXSwidXNlcm5hbWUiOlsi5p2o5LqR6IOcIl19fQ.Q64FGW2986-jmbLvy-IX8hF6p12LEZnX1dFzUtxeTlY Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTExMTk3NTIsImlhdCI6MTY5MTExNDM1MiwiaXNzIjoia3ViZXNwaGVyZSIsInN1YiI6InN5c3RlbTpwcmUtcmVnaXN0cmF0aW9uIiwidG9rZW5fdHlwZSI6InJlZnJlc2hfdG9rZW4iLCJ1c2VybmFtZSI6InN5c3RlbTpwcmUtcmVnaXN0cmF0aW9uIiwiZXh0cmEiOnsiZW1haWwiOlsieWFuZ3l1bnNoZW5nLml0QGhhaWVyLmNvbSJdLCJpZHAiOlsiaGFpZXIiXSwidWlkIjpbIjAxNDcxMDc2Il0sInVzZXJuYW1lIjpbIuadqOS6keiDnCJdfX0.YqlZpYeL0wGvZDg32tpW5RmJq44LXWmwckMxSEoVONg  3600

看起来也没有问题

进到ks-console的容器里

![image-20230804112024461](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230804112024461.png)

直接访问了一下api-server的callback接口

返回没有问题

我们使用的是3.1版本的ks-console

![image-20230804134808804](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230804134808804.png)

最后定位出来原因了

username不能是中文

3.3版本的console做了优化

![image-20230804134959102](D:\github\docs\云原生\kubesphere\编译kubesphere apiserver.assets\image-20230804134959102.png)

所以3.3版本可能能使用中文名来登录,不确定。没试。

dns解析流程

我们可以通过抓包来理顺dns解析的流程

关于怎么找到pod的pid,请看【k8s运维】pod name与pid互查 (qq.com)

1
2
3
4
# 进入 dns container 的 network namespace
nsenter -n -t pid
# 对 53 端口进行抓包
tcpdump -i eth0 -N udp dst port 53

nsenter -n 是指定进入network命名空间,-t指定pid

53端口为 DNS服务器的服务端口。

另起一个pod进行nslookup

1
nslookup baidu.com dns_container的ip

image-20230919141805456

因为我的集群有多个coredns容器。nslookup的时候指定一下dns_container的ip。这样可以抓全。

image-20230919140745062

可以看到进行了4次dns查询

1
2
3
4
1、 baidu.com.default.svc.cluster.local.
2、 baidu.com.svc.cluster.local.
3、 baidu.com.cluster.local.
4、 baidu.com.

/etc/resolv.conf

为什么会查询这些?原因就是容器的/etc/resolv.conf的配置

1
2
3
4
/# cat /etc/resolv.conf
nameserver 1x.xx.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

查看下我们dns svc,确实是这个ip

image-20230919155658988

FQDN

要完全理解dns解析过程,首先要指导一个名词FQDN。Fully qualified domain name,全限定域名。(以下简称全域名)

全域名要求以.结束。所以你看到上面的dns查询最后都带着.

baidu.com这不是一个fqdn,baidu.com.这才是一个fqdn。

那是不是fqdn有什么关系呢?

规则是,如果是一个fqdn,那么就转发给DNS服务器进行解析。也就是 nameserver 1x.xx.0.10

如果不是一个fqdn,那么这个域名就要和search中的项组合成一个全域名去搜索。

在这里就是

1
2
3
1、 baidu.com.default.svc.cluster.local.
2、 baidu.com.svc.cluster.local.
3、 baidu.com.cluster.local.

这三个。

options ndots:5

这又是什么东西呢?

这个意思是说域名里的点号大于等于这个数字的话就是个全域名fqdn

比如 a.b.c.d.baidu.com 就算不是以点号结尾的,它也是个全域名。会直接交给DNS服务器进行解析。

总之

不管是内部服务名还是外部域名,最终都是交给DNS服务器进行解析。只不过对于非全域名来说要先经过和search域的组装,然后交给DNS服务器进行解析。我们可以利用这一点,对外部域名要用.结束,避免多次无效的dns解析。

另外一点,对于同一namespace下的svc调用,使用直接的svc名字调用,要比svc名.ns名的效率更高。因为svc名.ns名要经过第二次组装才能查询到,而直接的svc名字是第一次查询解析到了。

k8s的dns策略

k8s提供了4种DNS策略,如下:

  • Default: Pod 从运行所在的节点继承名称解析配置。其实就是使用宿主机的 /etc/resolv.conf 来进行解析
  • ClusterFirst: Pod 内的 DNS 优先会使用 k8s 集群内的 DNS 服务,也就是会使用 kubedns 或者 coredns 进行域名解析。如果解析不成功,才会使用宿主机的 DNS 配置进行解析
  • ClusterFirstWithHostNet:对于以 hostNetwork 方式运行的 Pod,这个 POD 中的所有容器都会使用宿主机的 /etc/resolv.conf 配置进行 DNS 查询,但是如果在 Pod 中仍然还想继续使用 k8s 集群 的 DNS 服务时,就需要将 dnsPolicy 设置为应显式设置其 DNS 策略 ClusterFirstWithHostNet
  • None: 不会使用集群和宿主机的 DNS 策略。需要和 dnsConfig 一起配合使用

查看

1
kubectl -n kube-system get deploy coredns -oyaml

image-20230919155434633

目前使用的是Default策略

关于core-dns和kube-dns

CoreDNS 和 Kube-DNS 都是 Kubernetes 集群的 DNS 服务提供者,作用上是完全相同的。

那选谁呢?

主要看性能差异,根据资料来看CoreDNS 更优。

从 Kubernets 1.11 开始,CoreDNS 达到了 GA,kubeadm 使用它来默认安装 Kubernetes。

从Kubernetes 1.13开始,CoreDNS就成了Kubernetes的默认DNS服务器

但是

1
kubectl -n kube-system get svc

看到的却是kube-dns

这是因为容器镜像使用 coredns 而服务名是 kube-dns,这是为了保证应用从 Kube-DNS 迁移到 coredns 时的后向兼容性。

所以我们集群里用的是coreDns。

看deploy的名字就是coredns。

服务器需要能够访问公网!!!

1、关闭防火墙

1
systemctl stop firewalld.service

2、关闭selinux

1
setenforce 0

3、关闭swap

1
2
3
4
5
6
7
8
9
10
11
12
13
swapoff -a  //临时关闭

vi /etc/fstab
#
# /etc/fstab
# Created by anaconda on Wed Jan 3 16:46:39 2024
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/centos-root / xfs defaults 0 0
UUID=cd2a9e51-6f72-410b-997c-9d660e67c409 /boot xfs defaults 0 0
#/dev/mapper/centos-swap swap swap defaults 0 0 //注释这行,永久关闭swap

4、添加主机名与ip对应关系

1
2
3
4
5
6
vi /etc/hosts

127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
1x.xx.243.208 testljcspt-paas04
1x.xx.243.205 apiserver.cluster.local //添加master解析

5、添加/etc/sysctl.d/k8s.conf,将桥接的流量传递到iptables的链

1
2
3
4
vi /etc/sysctl.d/k8s.conf

net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1

6、添加yum阿里云docker-ce仓库

1
2
3
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

yum list docker-ce --showduplicates | sort -r //查看docker-ce所有版本

7、添加yum阿里云kubernetes仓库

1
2
3
4
5
6
7
8
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

8、安装docker

1
yum install docker-ce-19.03.0-3.el7 docker-ce-cli-19.03.0-3.el7 -y

安装可能出现报错缺少container-selinux,解决:

1
2
3
4
5
yum install policycoreutils-python

wget http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.119.1-1.c57a6f9.el7.noarch.rpm

rpm -ivh container-selinux-2.119.1-1.c57a6f9.el7.noarch.rpm

9、安装socat

1
yum install -y socat

10、安装kubectl、kubelet、kubeadm

1
yum install -y kubelet-1.18.8-0 kubectl-1.18.8-0 kubeadm-1.18.8-0

11、复制master上的cni文件到新节点

1
2
3
4
5
6
7
8
9
10
11
//在新节点新增文件夹

mkdir -p /etc/cni/net.d

//在master上copy cni文件
scp 10-calico.conflist 1x.xx.243.208:/etc/cni/net.d/
scp calico-kubeconfig 1x.xx.243.208:/etc/cni/net.d/

//拷贝master上的文件
scp /opt/cni/bin/calico 1x.xx.243.208:/opt/cni/bin/
scp /opt/cni/bin/calico-ipam 1x.xx.243.208:/opt/cni/bin/

12、新节点加入集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//在master节点和新节点都修改cgroup驱动为cgroup,k8s默认systemd
vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

//添加
Environment="KUBELET_CGROUP_ARGS=--cgroup-driver=cgroup"

//执行
systemctl daemon-reload

//在master执行
kubeadm token create --print-join-command
//执行完成终端打印
W0123 14:21:19.978841 10721 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
kubeadm join apiserver.cluster.local:6443 --token t4d0l0.fxaxj7w33fy3pvbg --discovery-token-ca-cert-hash sha256:0f6506f557a6f97719bfad6c1f5e2d1617f78e177d4368effd3ac8dd5b6e429d

//在新节点执行
kubeadm join apiserver.cluster.local:6443 --token t4d0l0.fxaxj7w33fy3pvbg --discovery-token-ca-cert-hash sha256:0f6506f557a6f97719bfad6c1f5e2d1617f78e177d4368effd3ac8dd5b6e429d

在公司运维多个k8s集群的过程中,经常需要kubectl到不同的集群。如果每次都不断的ssh到各个集群,这种感觉就很难受了。我们需要配置本地kubectl,能方便的在多个k8s集群间切换。

kubeconfig文件的结构

首先看一下kubeconfig文件的结构

部署好一个k8s集群后,我们在服务器上连接本地的k8s,默认用的是下面的kubeconfig文件

1
cat ~/.kube/config

和/etc/kubernetes/admin.conf一样的

kubeconfig文件主要分成三部分

context部分

1
2
3
4
5
6
contexts:
- context:
cluster: kubernetes
namespace: default
user: kubernetes-admin
name: kubernetes-admin@kubernetes

cluster部分

1
2
3
4
5
6
clusters:
- cluster:
certificate-authority-data: {BASE64格式的数据xxxxxx}
server: https://apiserver.cluster.local:6443
name: kubernetes

user部分

1
2
3
4
5
users:
- name: kubernetes-admin
user:
client-certificate-data: {BASE64格式的数据xxxxxx}
client-key-data: {BASE64格式的数据xxxxxx}

其他

1
2
3
4
apiVersion: v1
kind: Config
current-context: kubernetes-admin@kubernetes // 这个字段有用,标识当前用的哪个context
preferences: {}

这样分解之后,就比较清晰了。cluster部分是要连接的集群的信息。user部分负责用户鉴权信息。

context部分组装cluser+user。供kubectl使用。

cluster部分

certificate-authority-data是什么?

这个认证数据的作用是用来验证k8s集群的。如果kubectl 开启了 –insecure-skip-tls-verify=true 配置,那么这个certificate-authority-data是错的也无所谓。

怎么验证k8s集群的呢?

首先要清楚kubectl和k8s交互的流程。

当kubectl发送请求给k8s的api-server时,api-server会返回master的服务端证书给kubectl。(这个证书就是/etc/kubernetes/pki/apiserver.crt )

kubectl 就会校验 apiserver.crt 的正确性,这个校验就使用certificate-authority-data

然后,以后kubectl发送消息就会用 apiserver.crt进行加密

apiserver那边会对消息进行解密,使用/etc/kubernetes/pki/apiserver.key

在这份场景下,/etc/kubernetes/pki/apiserver.crt 就是公钥,/etc/kubernetes/pki/apiserver.key就是私钥了

user部分

cluster部分说到了kubectl发送的消息使用apiserver的公钥进行加密。

那么apiserver回给kubectl的消息呢?

当然也是需要加密的

那么用的公钥是啥?

就是这个user部分定义的client-certificate-data

嗯,你也就猜到了,client-key-data就是kubectl这边用来解密的私钥

如何查看证书

1
openssl x509 -in apiserver.crt -noout -text

image-20231129155909853

这里可以看到证书的过期时间,我这搞成100年

也可以把user的client-certificate-data先用base64解码之后保存成crt文件,解析看看

image-20231129160900888

里面会有用户的信息。CN表示user,O是group

可见,当前客户端证书的用户是kubernetes-admin,组是system:masters

system:masters组是k8s内置的用户组。通过查看clusterrolebinding看到system:masters组是绑定了cluster-admin角色,cluster-admin角色是由所有资源的所有权限。

1
2
kubectl get clusterrolebinding cluster-admin -o yaml
kubectl get clusterrole cluster-admin -o yaml

添加其他集群的context

1
2
cd ~/.kube
cp config config.bak

添加cluster信息

先把base64格式的证书数据保存成证书。

1
echo "base64数据" | base64 -d>sit.ca

然后执行添加

1
kubectl config --kubeconfig=config set-cluster sit --server=https://1x.xxx.x.x:6443 --certificate-authority=sit.ca

看一下

image-20231129165124721

已经加上了

添加user信息

同样先保存证书

1
2
echo "base64数据" | base64 -d>sit-admin.crt
echo "base64数据" | base64 -d>sit-admin.key

添加

1
kubectl config --kubeconfig=config set-credentials sit-admin --client-certificate=sit-admin.crt --client-key=sit-admin.key

image-20231129165804899

看一下已经加上了

添加context信息

最后一步,添加context信息

1
kubectl config --kubeconfig=config set-context sit --cluster=sit --namespace=default --user=sit-admin

查看一下,可以使用config view命令

1
kubectl config view

image-20231129170134422

好了,现在就有了2个集群的连接信息

如果加错了要删除

删除cluster

1
kubectl --kubeconfig=config config unset clusters.sit

删除user

1
kubectl --kubeconfig=config config unset users.sit-admin

删除context

1
kubectl --kubeconfig=config config unset contexts.sit

测试一下

默认context连接的集群

image-20231129170357523

指定一下context

1
kubectl --context sit get nodes

image-20231129170424493

就可以连接sit集群了。

还是不太方便,每次都要–context。可以把sit设置为默认连接

1
kubectl config use-context sit

image-20231129170703399

现在,current-context就是sit环境了

好了。以后就方便了~

master的重装

下掉master1

删除master1节点

3台master下掉一个,剩下2个master运行基本也没问题。坚持个一两天问题不大。

1
2
kubectl drain rrs-t-paas-master0 --delete-local-data --force --ignore-daemonsets
kubectl delete node rrs-t-paas-master0

清理etcd集群

进去etcd容器

1
kubectl -n kube-system exec -it etcd-paas-m-k8s-master-2 -- /bin/sh

查看member list

1
etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key member list

看到现在还是3个member,把下掉的那个删除

1
etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key member remove 8aab0313a9647d4d

image-20240110135504525

然后master-1做了一些硬盘修复

之后再加入集群

master1再重新加入集群

重置下master1

1
kubeadm reset

配置一个对域名apiserver.cluster.local的解析

修改 /etc/hosts

1
你的活着的master的ip apiserver.cluster.local

在kubeadm join的时候会用到

在master2上生成join命令

1
2
3
4
5
6
[root@paas-m-k8s-master-2 ~]# kubeadm init phase upload-certs --upload-certs
I0110 10:10:11.254956 12245 version.go:252] remote version is much newer: v1.29.0; falling back to: stable-1.18
W0110 10:10:13.812440 12245 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
[upload-certs] Storing the certificates in Secret "kubeadm-certs" in the "kube-system" Namespace
[upload-certs] Using certificate key:
23d8e27402b4f982d9ec894c37b1a3271c9f27bef2e653ca471426cc57025324
1
2
3
[root@paas-m-k8s-master-2 ~]# kubeadm token create --print-join-command
W0110 10:11:40.990463 14694 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io]
kubeadm join apiserver.cluster.local:6443 --token yubedv.0rg185no5jgqwn07 --discovery-token-ca-cert-hash sha256:be87c7200420224f1f8d439a5f058de7be88282eec1fc833b346b38c62ddf482

master1加入集群

到要被加入的机器上执行

加master节点才需要–control-plane –certificate-key

1
2
3
4
kubeadm join apiserver.cluster.local:6443 \
--token yubedv.0rg185no5jgqwn07 \
--discovery-token-ca-cert-hash sha256:be87c7200420224f1f8d439a5f058de7be88282eec1fc833b346b38c62ddf482 \
--control-plane --certificate-key 23d8e27402b4f982d9ec894c37b1a3271c9f27bef2e653ca471426cc57025324

成功

image-20240110135409427

遇到过的问题总结

  1. 域名解析不到apiserver.cluster.local
1
2
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -oyaml'
error execution phase preflight: unable to fetch the kubeadm-config ConfigMap: failed to get config map: Get https://apiserver.cluster.local:6443/api/v1/namespaces/kube-system/configmaps/kubeadm-config?timeout=10s: dial tcp: lookup apiserver.cluster.local on 1x.xx.xx.xx:53: no such host

是域名解析的问题,找不到apiserver.cluster.local

解决:

直接在/ets/hosts里配上

1
你的活着的master的ip apiserver.cluster.local
  1. kubelet的端口占用
1
Port 10250 is in use

kubelet还活着。kubeadm join时会启动kubelet

使用kubeadm reset 重置配置

  1. etcd目录不为空
1
[ERROR DirAvailable--var-lib-etcd]: /var/lib/etcd is not empty

删除即可。

1
rm -rf /var/lib/etcd

也可以kubeadm reset 重置

  1. etcd健康检查失败

原因是之前的etcd记录还存在,查看

1
etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key member list

删除

1
etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key member remove 7eab7c23b19f6778

K8s安装etcdctl客户端命令行工具

安装

下载工具包

1
wget https://github.com/etcd-io/etcd/releases/download/v3.4.14/etcd-v3.4.14-linux-amd64.tar.gz

解压并加入环境变量

1
2
tar -zxf etcd-v3.4.14-linux-amd64.tar.gz
mv etcd-v3.4.14-linux-amd64/etcdctl /usr/local/bin

验证etcdctl是否能用,出现以下结果代表已经成功了

1
2
3
[root@k8s-master1 ~]#etcdctl version
etcdctl version: 3.4.14
API version: 3.4
1
export ETCDCTL_API=3

查看etcd高可用集群健康状态

我们是通过sealos安装的k8s,sealos部署的etcd,需要去/etc/kubernetes/manifest/etcd.yml查找相关配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- --advertise-client-urls=https://1x.xxx.5.159:2379
- --cert-file=/etc/kubernetes/pki/etcd/server.crt
- --client-cert-auth=true
- --data-dir=/var/lib/etcd
- --initial-advertise-peer-urls=https://1x.xxx.5.159:2380
- --initial-cluster=k8s-master=https://1x.xxx.5.159:2380
- --key-file=/etc/kubernetes/pki/etcd/server.key
- --listen-client-urls=https://127.0.0.1:2379,https://1x.xxx.5.159:2379
- --listen-metrics-urls=http://127.0.0.1:2381
- --listen-peer-urls=https://1x.xxx.5.159:2380
- --name=k8s-master
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --peer-client-cert-auth=true
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --snapshot-count=10000
- --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt

需要的是

advertise-client-urls=https://1x.xxx.5.159:2379

cert-file=/etc/kubernetes/pki/etcd/server.crt

key-file=/etc/kubernetes/pki/etcd/server.key

trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt

1
2
3
4
5
6
[root@k8s-master1 ~]#etcdctl --endpoints=1x.xxx.5.159:2379  --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key --write-out=table endpoint health
+-------------------+--------+-------------+-------+
| ENDPOINT | HEALTH | TOOK | ERROR |
+-------------------+--------+-------------+-------+
| 1x.xxx.5.159:2379 | true | 6.827474ms | |
+-------------------+--------+-------------+-------+

多个endpoints用逗号隔开

查看etcd高可用集群列表

1
2
3
4
5
6
7
8
9
[root@k8s-master1 ~]#etcdctl --endpoints=1x.xxx.5.148:2379,1x.xxx.5.149:2379,1x.xxx.5.150:2379  --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key --write-out=table member list

+------------------+---------+-----------------+---------------------------+---------------------------+------------+
| ID | STATUS | NAME | PEER ADDRS | CLIENT ADDRS | IS LEARNER |
+------------------+---------+-----------------+---------------------------+---------------------------+------------+
| 4b3ac20594cafd80 | started | k8s-master-0002 | https://1x.xxx.5.149:2380 | https://1x.xxx.5.149:2379 | false |
| 5fbabc3223caea9c | started | k8s-master-0001 | https://1x.xxx.5.148:2380 | https://1x.xxx.5.148:2379 | false |
| 93ed7d06d0464add | started | k8s-master-0003 | https://1x.xxx.5.150:2380 | https://1x.xxx.5.150:2379 | false |

查看etcd高可用集群leader

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@k8s-master1 ~]#etcdctl --endpoints=1x.xxx.5.148:2379,1x.xxx.5.149:2379,1x.xxx.5.150:2379  --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key --write-out=table endpoint status



+-------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+-------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| 1x.xxx.5.148:2379 | 5fbabc3223caea9c | 3.4.3 | 32 MB | true | false | 4 | 1077958 | 1077958 | |
| 1x.xxx.5.149:2379 | 4b3ac20594cafd80 | 3.4.3 | 32 MB | false | false | 4 | 1077958 | 1077958 | |
| 1x.xxx.5.150:2379 | 93ed7d06d0464add | 3.4.3 | 32 MB | false | false | 4 | 1077958 | 1077958 | |
+-------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+


其他命令

etcdctl -h 查看

  • 获取 ETCD 所有的 key:

    1
    #etcdctl --endpoints=1x.xxx.5.159:2379  --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key  get / --prefix --keys-only

K8S网络学习

基础知识

K8S的网络中主要存在4种类型的通信:

①同一Pod内的容器间通信:它们之间通过localhost(lo)进行通信。

②各个Pod彼此间的通信:通过Pod IP地址进行通信。

③Pod和Service间的通信:两者并不属于同一网络,实现方式是通过IPVS或iptables规则转发。

④集群外部流量和Service之间的通信:实现方式:Ingress、NodePort、Loadbalance

K8S为Pod和Service资源对象分别使用了各自的专有网络,Pod网络由K8S的网络插件配置实现,而Service网络则由K8S集群进行指定

K8S使用的网络插件需要为每个Pod配置至少一个特定的地址,即Pod IP。Pod IP地址实际存在于某个网卡(可以是虚拟机设备)上。

而Service的地址却是一个虚拟IP地址,没有任何网络接口配置在此地址上,它由Kube-proxy借助iptables规则或ipvs规则重定向到本地端口,再将其调度到后端的Pod对象。Service的IP地址是集群提供服务的接口,也称为Cluster IP。

Pod网络和IP由K8S的网络插件负责配置和管理,具体使用的网络地址可以在管理配置网络插件时进行指定,如1x.xx.0.0/16网络(自己指定)。而Cluster网络和IP是由K8S集群负责配置和管理,如1x.xx.0.0/12网络(默认)。

K8S网络的实现不是集群内部自己实现,而是依赖于第三方网络插件—-CNI(Container Network Interface)

flannel、calico、canel等是目前比较流行的第三方网络插件。

这三种的网络插件需要实现Pod网络方案的方式通常有以下几种:

虚拟网桥、多路复用(MacVLAN)、硬件交换(SR-IOV)

CNI插件进行编排网络,以实现Pod和集群网络管理功能的自动化。每次Pod被初始化或删除,kubelet都会调用默认的CNI插件去创建一个虚拟设备接口附加到相关的底层网络,为Pod去配置IP地址、路由信息并映射到Pod对象的网络名称空间。

CNI的主要核心是:在创建容器时,先创建好网络名称空间(netns),然后调用CNI插件为这个netns配置网络,最后在启动容器内的进程。

service如何找到pod

label selector

calico网络插件

基于BGP的三层网络插件,也支持网络策略进而实现网络的访问控制;它在每台主机上都运行一个虚拟路由,利用Linux内核转发网络数据包,并借助iptables实现防火墙功能。实际上Calico最后的实现就是将每台主机都变成了一台路由器,将各个网络进行连接起来,实现跨主机通信的功能。

用到的命令

1
2
3
yum install traceroute.x86_64 -y
yum install net-tools -y
yum install tcpdump -y

部署几个busybox用于测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app: busybox
name: busybox
namespace: default
spec:
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
- name: busybox
image: busybox:1.28.4
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent

1master4node的环境,cni为calico网络。

IPIP的工作模式

主机ip信息

master 1x.xxx.5.1
node1 1x.xxx.5.2
node2 1x.xxx.5.3
node3 1x.xxx.5.4
node4 1x.xxx.5.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@wlk8s01 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 1x.xxx.5.254 0.0.0.0 UG 0 0 0 eth0
1x.xxx.5.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
100.116.168.192 1x.xxx.5.3 255.255.255.192 UG 0 0 0 tunl0
100.121.66.192 1x.xxx.5.2 255.255.255.192 UG 0 0 0 tunl0
100.121.236.192 1x.xxx.5.4 255.255.255.192 UG 0 0 0 tunl0
100.125.97.128 0.0.0.0 255.255.255.192 U 0 0 0 *
100.125.97.132 0.0.0.0 255.255.255.255 UH 0 0 0 cali8bb97db0aa4
100.125.97.135 0.0.0.0 255.255.255.255 UH 0 0 0 calide035c655d8
100.125.97.136 0.0.0.0 255.255.255.255 UH 0 0 0 caliac4b211f131
100.125.97.137 0.0.0.0 255.255.255.255 UH 0 0 0 cali51caa2d8c29
100.125.97.138 0.0.0.0 255.255.255.255 UH 0 0 0 cali5e5ab1418d1
100.125.97.139 0.0.0.0 255.255.255.255 UH 0 0 0 cali98e6dbf06d5
100.125.176.128 1x.xxx.5.5 255.255.255.192 UG 0 0 0 tunl0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0

创建daemonSet的busybox

1
2
3
4
5
6
[root@wlk8s01 ~]# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-99qmn 1/1 Running 0 95s 100.121.236.245 wlk8s04.cloud.test.cthd <none> <none>
busybox-n4lkm 1/1 Running 0 95s 100.116.168.225 wlk8s03.cloud.test.cthd <none> <none>
busybox-smq7r 1/1 Running 0 95s 100.121.66.229 wlk8s02.cloud.test.cthd <none> <none>
busybox-v76gs 1/1 Running 0 96s 100.125.176.183 wlk8s05.cloud.test.cthd <none> <none>

进入pod,互相ping

可以ping通

ping包的路程

pod100.121.236.245上的路由信息

1
2
3
4
5
/ # route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0
169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0

路由表中Flags标志的含义:

U up表示当前为启动状态

H host表示该路由为一个主机,多为达到数据包的路由

G Gateway 表示该路由是一个网关,如果没有说明目的地是直连的

D Dynamicaly 表示该路由是重定向报文修改

M 表示该路由已被重定向报文修改

第一条路由的意思是去往任何网段的数据包都发往网关169.254.1.1,然后从eth0网卡发送出去。

如果ping 100.116.168.225

那么,会匹配到第一条

回头看master上的路由信息,去100.116.168.225的数据包会匹配到这个tunl0的路由。

image-20220818151212161

这条路由的意思是,去网段100.116.168.192/26的数据包都发到网关1x.xxx.5.3。也就是pod2所在的主机node2

也就是说calico会给每个node分配一个网段,在路由表中记录。之后就可以根据目的ip将数据包发到指定的node节点。

每加一个node,就会对应创建一个隧道tunl0

再去看node2的路由信息。发现node节点会给自己的每一个pod创建一条路由信息。这样达到node的数据就可以准确的发给pod。

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
[root@wlk8s03 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 1x.xxx.5.254 0.0.0.0 UG 0 0 0 eth0
10.103.97.2 1x.xxx.5.3 255.255.255.255 UGH 50 0 0 eth0
1x.xxx.5.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
100.116.168.192 0.0.0.0 255.255.255.192 U 0 0 0 *
100.116.168.193 0.0.0.0 255.255.255.255 UH 0 0 0 cali5e5de735a63
100.116.168.194 0.0.0.0 255.255.255.255 UH 0 0 0 cali4f9ccee34d6
100.116.168.196 0.0.0.0 255.255.255.255 UH 0 0 0 calif7c7bbacc1d
100.116.168.199 0.0.0.0 255.255.255.255 UH 0 0 0 cali822a69c5fd1
100.116.168.201 0.0.0.0 255.255.255.255 UH 0 0 0 cali34f0fd4339f
100.116.168.202 0.0.0.0 255.255.255.255 UH 0 0 0 cali9cb916c7b8e
100.116.168.205 0.0.0.0 255.255.255.255 UH 0 0 0 calid555706b3ed
100.116.168.206 0.0.0.0 255.255.255.255 UH 0 0 0 calibaa8ac4626e
100.116.168.207 0.0.0.0 255.255.255.255 UH 0 0 0 cali1912584f857
100.116.168.208 0.0.0.0 255.255.255.255 UH 0 0 0 cali21d055e76c9
100.116.168.209 0.0.0.0 255.255.255.255 UH 0 0 0 cali0e7af07e6c4
100.116.168.210 0.0.0.0 255.255.255.255 UH 0 0 0 calie9df38822f3
100.116.168.211 0.0.0.0 255.255.255.255 UH 0 0 0 califdbc8377bc0
100.116.168.212 0.0.0.0 255.255.255.255 UH 0 0 0 cali36f34f157ff
100.116.168.213 0.0.0.0 255.255.255.255 UH 0 0 0 cali537fa7ec461
100.116.168.214 0.0.0.0 255.255.255.255 UH 0 0 0 cali45a1dc5faa7
100.116.168.215 0.0.0.0 255.255.255.255 UH 0 0 0 cali7f1fc57aff6
100.116.168.217 0.0.0.0 255.255.255.255 UH 0 0 0 cali31446935ad8
100.116.168.220 0.0.0.0 255.255.255.255 UH 0 0 0 cali629b8c67f54
100.116.168.223 0.0.0.0 255.255.255.255 UH 0 0 0 cali7c917963b12
100.116.168.225 0.0.0.0 255.255.255.255 UH 0 0 0 cali93f1e5221ad
100.121.66.192 1x.xxx.5.2 255.255.255.192 UG 0 0 0 tunl0
100.121.236.192 1x.xxx.5.4 255.255.255.192 UG 0 0 0 tunl0
100.125.97.128 1x.xxx.5.1 255.255.255.192 UG 0 0 0 tunl0
100.125.176.128 1x.xxx.5.5 255.255.255.192 UG 0 0 0 tunl0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0

当node节点网卡收到数据包之后,发现发往的目的ip为100.116.168.225,那么就匹配到这个calico的虚拟设备,并且该设备是本机的直连设备UH

image-20220818152334465

这个虚拟设备就是veth pair的一端。

也就是说在创建这个pod2的时候,calico会给pod2创建一个veth pair设备。

这一对设备,一端是pod2的网卡eth0,另一端就是cali93f1e5221ad。

去pod2中查看

1
2
3
4
5
/ # route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0
169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0

这个pod2中的网卡eth0和node上的cali93f1e5221ad,就是一堆veth

凡是发给cali93f1e5221ad的数据,实际上就是发给了pod2的eth0,这样数据最终就到了pod2

顾名思义,IPIP网络就是将IP网络封装在IP网络里。IPIP网络的特点是所有pod的数据流量都从隧道tunl0发送,并且在tunl0这增加了一层传输层的封包。

抓包

在master上抓,pod1 ping pod2的数据包

1
tcpdump -i eth0 -vvv -w ipip.pcap

BGP

BGP网络相比较IPIP网络,最大的不同之处就是没有了隧道设备 tunl0。 前面介绍过IPIP网络pod之间的流量发送tunl0,然后tunl0发送对端设备。BGP网络中,pod之间的流量直接从网卡发送目的地,减少了tunl0这个环节。

master节点上路由信息。从路由信息来看,没有tunl0设备。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@wlk8s01 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 1x.xxx.5.254 0.0.0.0 UG 0 0 0 eth0
1x.xxx.5.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
1x.xxx.36.128 1x.xxx.5.9 255.255.255.192 UG 0 0 0 eth0
1x.xxx.37.192 0.0.0.0 255.255.255.192 U 0 0 0 *
1x.xxx.37.195 0.0.0.0 255.255.255.255 UH 0 0 0 cali426c1bfdfc6
1x.xxx.37.198 0.0.0.0 255.255.255.255 UH 0 0 0 calide035c655d8
1x.xxx.37.200 0.0.0.0 255.255.255.255 UH 0 0 0 cali74f1cb95b24
1x.xxx.37.201 0.0.0.0 255.255.255.255 UH 0 0 0 cali2d690b6853f
1x.xxx.37.202 0.0.0.0 255.255.255.255 UH 0 0 0 caliac4b211f131
1x.xxx.37.203 0.0.0.0 255.255.255.255 UH 0 0 0 cali6765f213860
1x.xxx.37.204 0.0.0.0 255.255.255.255 UH 0 0 0 calid7e1d68c7d4
1x.xxx.39.0 1x.xxx.5.8 255.255.255.192 UG 0 0 0 eth0
1x.xxx.39.64 1x.xxx.5.10 255.255.255.192 UG 0 0 0 eth0
1x.xxx.39.128 1x.xxx.5.7 255.255.255.192 UG 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0

pod的ip

1
2
3
4
5
6
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
busybox-h2t6q 1/1 Running 0 28m 1x.xxx.39.46 wlk8s03.dubble.test.cthd <none> <none>
busybox-mknk8 1/1 Running 0 28m 1x.xxx.39.108 wlk8s05.dubble.test.cthd <none> <none>
busybox-q6t6x 1/1 Running 0 28m 1x.xxx.39.176 wlk8s02.dubble.test.cthd <none> <none>
busybox-xd57j 1/1 Running 0 28m 1x.xxx.36.168 wlk8s04.dubble.test.cthd <none> <none>

ping包之旅

1x.xxx.39.46的pod称为pod1

1
2
3
4
5
/ # route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0
169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0

根据pod1中的路由信息,ping包通过eth0网卡发送到master节点上

ping 1x.xxx.39.108 在master上匹配到

image-20220818171221978

而5.10就是pod2所在的node5

去看node5上的路由

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
[root@wlk8s05 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 1x.xxx.5.254 0.0.0.0 UG 0 0 0 eth0
10.103.97.2 1x.xxx.5.10 255.255.255.255 UGH 50 0 0 eth0
1x.xxx.5.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
1x.xxx.36.128 1x.xxx.5.9 255.255.255.192 UG 0 0 0 eth0
1x.xxx.37.192 1x.xxx.5.6 255.255.255.192 UG 0 0 0 eth0
1x.xxx.39.0 1x.xxx.5.8 255.255.255.192 UG 0 0 0 eth0
1x.xxx.39.64 0.0.0.0 255.255.255.255 UH 0 0 0 cali7d7ffe7c7d6
1x.xxx.39.64 0.0.0.0 255.255.255.192 U 0 0 0 *
1x.xxx.39.65 0.0.0.0 255.255.255.255 UH 0 0 0 cali9371da1f513
1x.xxx.39.67 0.0.0.0 255.255.255.255 UH 0 0 0 calic8ba59a1725
1x.xxx.39.69 0.0.0.0 255.255.255.255 UH 0 0 0 calie04054d5f38
1x.xxx.39.73 0.0.0.0 255.255.255.255 UH 0 0 0 cali1a54c8bb3d5
1x.xxx.39.74 0.0.0.0 255.255.255.255 UH 0 0 0 cali34f0fd4339f
1x.xxx.39.75 0.0.0.0 255.255.255.255 UH 0 0 0 caliaf36e566c9a
1x.xxx.39.77 0.0.0.0 255.255.255.255 UH 0 0 0 cali9617694e503
1x.xxx.39.78 0.0.0.0 255.255.255.255 UH 0 0 0 cali5a8fbdfbd03
1x.xxx.39.79 0.0.0.0 255.255.255.255 UH 0 0 0 cali994c1aefd56
1x.xxx.39.80 0.0.0.0 255.255.255.255 UH 0 0 0 cali496888aef9b
1x.xxx.39.81 0.0.0.0 255.255.255.255 UH 0 0 0 cali737d7f1222f
1x.xxx.39.82 0.0.0.0 255.255.255.255 UH 0 0 0 cali5089469215d
1x.xxx.39.83 0.0.0.0 255.255.255.255 UH 0 0 0 cali3e2d27cb4e3
1x.xxx.39.84 0.0.0.0 255.255.255.255 UH 0 0 0 calieea52a0a5fc
1x.xxx.39.85 0.0.0.0 255.255.255.255 UH 0 0 0 calie06bd32f5fd
1x.xxx.39.86 0.0.0.0 255.255.255.255 UH 0 0 0 cali2997c644807
1x.xxx.39.87 0.0.0.0 255.255.255.255 UH 0 0 0 calid29def8bdcd
1x.xxx.39.89 0.0.0.0 255.255.255.255 UH 0 0 0 cali601bd94c93b
1x.xxx.39.90 0.0.0.0 255.255.255.255 UH 0 0 0 cali19213c693a4
1x.xxx.39.92 0.0.0.0 255.255.255.255 UH 0 0 0 cali629b8c67f54
1x.xxx.39.102 0.0.0.0 255.255.255.255 UH 0 0 0 califca502b91ed
1x.xxx.39.108 0.0.0.0 255.255.255.255 UH 0 0 0 cali9c223080f7f
1x.xxx.39.128 1x.xxx.5.7 255.255.255.192 UG 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0

根据路由信息,配到

image-20220818171524462

和ipip一样,也是veth pair 的一端

Velero迁移

简介:

Velero 是一种云原生的 Kubernetes 容灾解决方案,支持标准的K8S集群,既可以是私有云平台也可以是公有云。除了灾备之外它还能做资源移转,支持把容器应用从一个集群迁移到另一个集群。它是一个开源的安全备份和恢复工具,执行灾难恢复,迁移 Kubernetes 集群资源和持久的卷。
该方案功能强大,但是操作稍微复杂些,需要安装相应的客户端和服务端。

Velero工作流程:

  1. 流程图

    img

    image.png

  2. 备份过程

    本地 Velero 客户端发送备份指令。

    Kubernetes 集群内就会创建一个 Backup 对象。

    BackupController 监测 Backup 对象并开始备份过程。

    BackupController 会向 API Server 查询相关数据。

    BackupController 将查询到的数据备份到远端的对象存储。

    从以上过程中能看出,使用velero备份k8s集群,需要一个store来存储备份数据,这就需要用到velero-plugin,

    velero-plug中有aws以及阿里云的插件,但是没有华为云直接使用的,经过与华为云沟通,说obs也是s3协议的,可以直接用aws插件。

安装

1. 客户端安装

我们现在用的是1.7.0

1
2
3
4
5
6
https://github.com/vmware-tanzu/velero/releases/download/v1.6.3/velero-v1.6.3-linux-amd64.tar.gz

wget https://github.com/vmware-tanzu/velero/releases/download/v1.7.0/velero-v1.7.0-linux-amd64.tar.gz

内网
http://transfer.paas.xxx.net/1ncSK6Z/velero-v1.7.0-linux-amd64.tar.gz

官方:Velero Docs - Basic Install

1
2
3
4
tar -zxvf velero-v1.7.0-linux-amd64.tar.gz
cd velero-v1.7.0-linux-amd64
chmod +x velero
mv velero /usr/bin

2. 服务端安装

直接安装

创建华为云credentials文件
1
2
3
4
5
# vim /root/velero/velero-credentials

[default]
aws_access_key_id = UQSIGADUGNF9MHUGYWVP
aws_secret_access_key = 26kysnDpVX4b02bhxbpOpgof1B31MtGY6YFE9c6n
1
2
3
4
5
cat>/root/velero/velero-credentials<<EOF
[default]
aws_access_key_id = UQSIGADUGNF9MHUGYWVP
aws_secret_access_key = 26kysnDpVX4b02bhxbpOpgof1B31MtGY6YFE9c6n
EOF
安装velero服务

1.6.3版本

1
2
3
4
5
6
7
8
9
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.2.1 \
--bucket obs-zjjpt \
--prefix middleware \
--secret-file /root/velero/velero-credentials \
--use-restic \
--use-volume-snapshots=false \
--backup-location-config region=cn-east-213,s3ForcePathStyle="true",s3Url=http://1x.xxx.0.3 --image=velero/velero:v1.6.3

1.7.0版本

1
2
3
4
5
6
7
8
9
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.3.0 \
--bucket obs-zjjpt \
--prefix prod-huawei \
--secret-file /root/velero/velero-credentials \
--use-restic \
--use-volume-snapshots=false \
--backup-location-config region=cn-east-213,s3ForcePathStyle="true",s3Url=http://1x.xxx.0.3 --image=velero/velero:v1.7.0
  • –use-volume-snapshots=true: 是否使用kubernetes的卷快照功能
  • –use-restic: 使用开源免费备份工具 restic 备份和还原持久卷数据

使用私服镜像,【使用这个】

obs-zjjpt这个bucket是华为云分给我们的,不能换

1
2
3
4
5
6
7
8
9
10
11
12
13
velero install \
--provider aws \
--plugins harbor-test.xxx.net/velero/velero-plugin-for-aws:v1.3.0 \
--bucket obs-zjjpt \
--prefix dubbo-test \
--secret-file /root/velero/velero-credentials \
--use-restic \
--restic-pod-mem-limit=2048Mi \
--restic-pod-mem-request=2048Mi \
--velero-pod-mem-limit=2048Mi \
--velero-pod-mem-request=2048Mi \
--use-volume-snapshots=false \
--backup-location-config region=cn-east-213,s3ForcePathStyle="true",s3Url=http://1x.xxx.0.3 --image=harbor-test.xxx.net/velero/velero:v1.7.0

在kubesphere平台上安装

【有报错,可能和华为obs的配置有关系,没有解决,不用这种方式】

我目前使用的是kubesphere平台。直接使用helm安装也很简单。

添加velero仓库

https://vmware-tanzu.github.io/helm-charts/

20210913135757381

创建velero应用

使用应用模板部署velero

image-20210913164721573

参数配置

values.yaml

1
2
3
4
5
6
7
initContainers:
- name: velero-plugin-for-aws
image: velero/velero-plugin-for-aws:v1.3.0 //国外的镜像可能下载不下来
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /target
name: plugins
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
configuration:
# 华为云沟通,说obs也是s3协议的,可以直接用aws插件。
# 阿里云的话写:alibabacloud
provider: aws
backupStorageLocation:
name:
provider:
# bucket名字,必填
bucket: obs-zjjpt
caCert:
# 我们创建的目录名
prefix: pcloud-test
default:
config:
region: cn-east-213
1
2
3
4
5
6
7
8
9
credentials:
useSecret: true
name:
existingSecret:
secretContents:
cloud: |
# 如果是阿里云写 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET
aws_access_key_id=UQSIGADUGNF9MHUGYWVP
aws_secret_access_key=26kysnDpVX4b02bhxbpOpgof1B31MtGY6YFE9c6n

备份

默认保存30天

一次性备份

1
velero backup create test-backup --include-namespaces test

备份名:nginx-backup

指定备份的namespace是test

1
2
# 备份全部
velero backup create backup-all

定时任务

1
2
# 创建一个定时任务
velero create schedule backup-all --schedule="0 17 * * *"

定时任务就是全量备份的

写17是因为时区问题。17 + 8 = 在1点备份

备份持久化卷

部署一个wordpress

1
2
3
4
5
6
7
# helm install my-wordpress bitnami/wordpress -n wordpress
...
3. Login with the following credentials below to see your blog:

echo Username: user
echo Password: $(kubectl get secret --namespace wordpress my-wordpress -o jsonpath="{.data.wordpress-password}" | base64 --decode)

备份

1
velero backup create wordpress-backup --include-namespaces wordpress --default-volumes-to-restic -n velero

恢复后数据还在

image-20211216162811468

查看

查看备份

1
velero get backups

查看定时任务

1
velero get schedules

查看恢复

1
velero get restore

查看备份地址

1
velero get backup-locations

恢复

从backup恢复

最基本的操作

1
velero restore create RESTORE_NAME --from-backup BACKUP_NAME 

从schedule恢复

1
velero restore create restore-sch --from-schedule backup-all --preserve-nodeports  --include-namespaces testvelero -n velero2

参数

​ –preserve-nodeports optionalBool[=true] 固定端口

​ –include-namespaces stringArray 选择性的从全部backup里恢复哪些ns

也可以恢复到其他ns,使用–namespace-mappings进行映射

1
2
3
velero restore create RESTORE_NAME \
--from-backup BACKUP_NAME \
--namespace-mappings old-ns-1:new-ns-1,old-ns-2:new-ns-2

要在另一个集群B恢复集群A的backup,那就再建一个集群A相同的velero,只是指定一个新的namespace。(默认的ns是velero)

1
2
3
4
5
6
7
8
9
10
velero install \
--namespace velero2 \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.3.0 \
--bucket obs-zjjpt \
--prefix dubbo-test \
--secret-file /root/velero/velero-credentials \
--use-restic \
--use-volume-snapshots=false \
--backup-location-config region=cn-east-213,s3ForcePathStyle="true",s3Url=http://1x.xxx.0.3 --image=velero/velero:v1.7.0

以后再restor的时候,加上-n velero2

1
velero restore create RESTORE_NAME --from-backup BACKUP_NAME -n velero2

考虑过是不是增加一个backup-location就可以。

1
2
3
4
5
6
7
8
9
10
# 新增一个backup-location
velero create backup-location dubbo-test --provider aws --bucket obs-zjjpt --prefix dubbo-test --config region=cn-east-213,s3ForcePathStyle="true",s3Url=http://1x.xxx.0.3 --default
# 将其设置为default
# velero backup-location set dubbo-test --default
# 查看一下
# velero get backup-location
NAME PROVIDER BUCKET/PREFIX PHASE LAST VALIDATED ACCESS MODE DEFAULT
default aws obs-zjjpt/middleware Available 2021-12-15 15:47:24 +0800 CST ReadWrite
dubbo-test aws obs-zjjpt/dubbo-test Unknown Unknown ReadWrite true

尝试了下,不行。问题是找不到密钥。没看找出 传递密码进去的写法。

官方说以后或再resotre命令中增加指定backup-location的参数。

报错

这个报错是用helm安装报错

kubectl -n velero logs deployment/velero

1
2
time="2021-12-14T06:32:45Z" level=error msg="No backup storage locations found, at least one is required" controller=backup-storage-location error="no backup storage locations found" error.file="/go/src/github.com/vmware-tanzu/velero/internal/storage/storagelocation.go:93" error.function=github.com/vmware-tanzu/velero/internal/storage.ListBackupStorageLocations logSource="pkg/controller/backup_storage_location_controller.go:62"
time="2021-12-14T06:32:49Z" level=error msg="Error getting backup store for this location" backupLocation=default controller=backup-sync error="rpc error: code = Unknown desc = NoCredentialProviders: no valid providers in chain. Deprecated.\n\tFor verbose messaging see aws.Config.CredentialsChainVerboseErrors" error.file="/go/src/velero-plugin-for-aws/velero-plugin-for-aws/volume_snapshotter.go:59" error.function=main.getSession logSource="pkg/controller/backup_sync_controller.go:175"

换用Credential文件的形式安装

2, 存储的文件夹

time=”2021-12-14T09:04:30Z” level=info msg=”Backup storage location is invalid, marking as unavailable” backup-storage-location=default controller=backup-storage-location logSource=”pkg/controller/backup_storage_location_controller.go:117”
time=”2021-12-14T09:04:30Z” level=error msg=”Current backup storage locations available/unavailable/unknown: 0/1/0, Backup storage location "default" is unavailable: Backup store contains invalid top-level directories: [middleware pcloud-test])” controller=backup-storage-location logSource=”pkg/controller/backup_storage_location_controller.go:164”

image-20211214171046011

说我提前建好的文件夹不合法

1
velero backup create test-backup --include-namespaces test

image-20211214171249223

执行备份是可以成功的,会自动生成一个叫backups的文件夹

然后我用1.7.0的版本测试,发现prefix参数是可以的

image-20211214174817515

会在middleware下产生备份的文件夹

dmz-sit

time=”2021-12-17T06:52:06Z” level=error msg=”Error listing backups in backup store” backupLocation=default controller=backup-sync error=”rpc error: code = Unknown desc = RequestError: send request failed\ncaused by: Get http://1x.xxx.0.3/obs-zjjpt?delimiter=%2F&list-type=2&prefix=dmz-sit%2Fbackups%2F: dial tcp 1x.xxx.0.3:80: i/o timeout” error.file=”/go/src/velero-plugin-for-aws/velero-plugin-for-aws/object_store.go:361” error.function=”main.(*ObjectStore).ListCommonPrefixes” logSource=”pkg/controller/backup_sync_controller.go:182”

备份一直卡住InProgress

describe的信息

1
2
3
4
5
6
7
State:          Running
Started: Thu, 18 Aug 2022 09:00:29 +0800
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Wed, 17 Aug 2022 09:01:01 +0800
Finished: Thu, 18 Aug 2022 09:00:28 +0800