Kubernetes

2024-06-13

K8s

[TOC]

一、K8S

基于Google公司Borg用go语言翻写的

1、k8s特性

Kubernetes是自动化容器编排的开源平台,目标是让部署容器化的应用简单并且高效,提供了应用部署,规划,更新,维护的一种机制,轻量级,消耗资源小,开源,,弹性伸缩,负载均衡:IPVS

Kubernetes一个核心的特点就是可以让容器按照用户的期望状态运行

关闭虚拟内存,防止容器运行在虚拟内存中

2、K8S各组件功能

1606869998366

1
2
3
4
5
6
7
etcd:键值对数据库  储存K8S集群所有重要信息(持久化)保存了整个集群的状态
kube-apiserver:所有服务访问统一入口,并提供认证、授权、访问控制、API注册和发现等机制
kube-controller-manager:是与底层云计算服务商交互的管理控制器,维持副本期望数目
kub-scheduler:负责接受任务,调度资源,选择合适的节点进行分配任务,或者说,按照预定的调度策略将Pod调度到相应的机器上
kubelet:直接跟容器引擎交互实现容器的生命周期管理,同时也负责Volume和网络的管理
kube-proxy:负责为Service提供内部的服务发现和负载均衡,并维护网络规则(写入规则至 IPTABLES、IPVS 实现服务映射访问的)
container-runtime:是负责管理运行容器的软件,比如docker
1
2
3
Node(节点)是k8s集群中相对于Master而言的工作主机。Node可以是一台物理主机,也可以是一台虚拟机(VM)。
在每个Node上运行用于启动和管理Pid的服务Kubelet,并能够被Master管理。
在Node上运行的服务进行包括Kubelet、kube-proxy和docker daemon。
1
Pod:k8s基本管理单元,容器的集合,可以理解为多个linux命名空间的联合,包括PID命名空间同一个Pod中的容器可以互相看到PID、网络命名空间同一个Pod中的容器可以使用同一IP

其他:

1
2
3
4
5
6
coreDNS:可以为集群中的SVC创建一个域名IP的对应关系解析,实现负载均衡中的功能
dashboard:给 K8S 集群提供一个 B/S 结构访问体系,网页UI界面
ingress controller:官方只能实现四层代理,INGRESS 可以实现七层代理
federation:提供一个可以跨集群中心多K8S统一管理功能
Prometheus:提供K8S集群的监控能力
ELK:提供 K8S 集群日志统一分析介入平台

高可用集群副本数量最好是 >=3 的奇数个

3、查看日志命令

有两种日志方案在工作,默认为rsyslogd和systemd journald的两种方式会占用内存,所以改为使用systemd journald

1
2
3
4
5
6
7
8
9
journalctl -u kube-scheduler

journalctl -xefu kubelet

journalctl -u kube-apiserver

journalctl -u kubelet |tail

journalctl -xe

二、Pod

1、概念

Pod:k8s基本管理单元,容器的集合,可以理解为多个linux命名空间的联合,包括PID命名空间同一个Pod中的容器可以互相看到PID、网络命名空间同一个Pod中的容器可以使用同一IP(共用pause的网络栈)

自主式Pod:

由控制器管理的Pod:

2、优点

Pod中的容器可以共用Pod提供的基础设施

Pod的生命周期与管理器的生命周期的分离

调度和管理的易用性,解偶控制器和服务,后段管理器仅仅监控Pod

3、创建和删除Pod的流程

创建:

1
2
3
4
5
6
7
8
9
10
11
1、客户端提交Pod的配置信息(可以是Deployment定义好的信息)到kube-apiserver,kube-apiserve会把Pod信息存储到ETCD当中

2、kube-scheduler 检测到Pod信息会开始调度

3、kube-scheduler 开始调度预选,主要是过滤掉不符合Pod要求的节点

4、kube-scheduler 开始调度调优,主要是会给节点打分以选择更加适合的节点

5、kube-scheduler 选择好节点后会把结果存储到ETCD

6、kubelet 根据调度结果执行Pod创建操作

删除:

1
2
3
4
5
6
7
8
9
10
11
kube-apiserver会接受到用户的删除指令,默认有30秒时间等待优雅退出,超过30秒会被标记为死亡状态

此时Pod的状态是Terminating,Kubelet看到Pod标记为Terminating开始了关闭Pod的工作

1、Pod从service的列表中被删除

2、如果该Pod定义了一个停止前的钩子,其会在pod内部被调用,停止钩子一般定义了如何优雅结束进程

3、进程被发送TERM信号(kill -14)

4、当超过优雅退出时间时,Pod中的所有进程都很被发送SIGKILL信号(kill -9)

4、Pod通讯

1)、Pod中容器互相通讯

①、localhost

1
2
3
4
pod内部的容器是共享网络名称空间的,所以容器直接可以使用localhost访问其他容器;k8s在启动容器的时候会先启动一个Pause容器,这个容器就是实现这个功能的。

pause:
每个Pod里运行着一个特殊的被称之为Pause的容器,其他容器则为业务容器,这些业务容器共享Pause容器的网络栈和Volume存储卷,因此他们之间通信和数据交换更为高效,在设计时我们可以充分利用这一特性将一组密切相关的服务进程放入同一个Pod中。

②、环回网口

③、挂载的数据卷

port、nodeport、targetport、containerport分别是什么

1608264425269

image-20210128213740751

2)、Pod1与Pod2在同一台机器

由Docker0网桥直接转发请求至Pod2,不需要经过Flannel

3)、Pod1与Pod2不在同一台主机

image-20210128212409882

这种情况k8s官方推荐的是使用flannel网络,pod的ip分配由flannel统一分配,通讯过程也是走flannel的网桥方式。

Pod的地址和docker0在同一个网段,但docker网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在Node的IP关联起来,通过这个关联可以让Pod互相访问

Web app2 —–> Backend 通讯

1
数据包源地址写自己的docker0网段的地址,目标写目标的ip地址,因为不在同一网段,所以发到docker0,docker0上有对应的钩子函数把它抓到Flannel0,Flannel由路由表记录(从etcd里获取到的)写入到当前的主机,判断路由到哪一台机器,由Flannel0到Flanneld后对这个数据报文进行封装,由mac到三层,源为本node的IP地址,目为目标node的IP地址,下一层封装时UDP报文(因为flannel使用的是UDP保温去转发这些数据包的,因为更快,毕竟在同一局域网,再下一层,又封装了一层新的三层信息,源为源Pod的docker网段网址,目为目标Pod的docker网段地址,外面封装了一层数据包实体(Payload),然后数据帧被转发到目标node的网卡上,然后到Flanneld上,然后拆封,转发到Flannel0,再转发到docker0,由docker转发到目标Pod

4)、Pod至Service网络

由各节点的iptables维护和转发,还有一种方法是LVS进行转发(转发效率更高,上限更高)

5)、Pod至外网

Pod向外网发送请求,查找路由表,转发数据包到宿主机的网卡,宿主网卡完成路由选择后,iptables执行Masquerade,把源IP更改为宿主网卡的IP,然后向外网服务器发送请求

6)外网访问Pod

kubernetes集群上运行的pod,在集群内访问是很容易的,最简单的,可以通过pod的ip来访问,也可以通过对应的svc来访问,但在集群外,由于kubernetes集群的pod ip地址是内部网络地址,因此从集群外是访问不到的。

为了解决这个问题,kubernetes提供了如下几个方法。

  • hostNetwork
  • hostPort
  • service NodePort

1、hostNetwork: true

hostNetwork为true时,容器将使用宿主机node的网络,因此,只要知道容器在哪个node上运行,从集群外以 node-ip + port 的方式就可以访问容器的服务。

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
hostNetwork: true
containers:
- name: nginx
image: nginx

pod启动后,如下,可以看到pod的ip地址与node节点的地址是一致的

1
2
3
4
5
6
7
[root@localhost ~]# kubectl get pods -o wide nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx 1/1 Running 0 8m2s 192.168.10.10 minikube <none>

[root@localhost ~]# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
minikube Ready master 11d v1.12.1 192.168.10.10 <none> CentOS Linux 7 (Core) 3.10.0-957.el7.x86_64 docker://17.12.1-ce

可以通过curl或者浏览器直接访问node节点的地址,即可以访问到nginx的服务

1
2
3
4
5
6
7
8
[root@localhost ~]# curl http://192.168.10.10
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
......
</html>
[root@localhost ~]#

hostNetwork的优点是直接使用宿主机的网络,只要宿主机能访问,Pod就可以访问;但缺点也是很明显的:

  • 易用性:Pod漂移到其他node上,访问时需要更换ip地址。解决方法是将Pod绑定在某几个node上,并在这几个node上运行keepalived以漂移vip,从而客户端可以使用vip+port的方式来访问。
  • 易用性:Pod间可能出现端口冲突,造成Pod无法调度成功。
  • 安全性:Pod可以直接观察到宿主机的网络。

2、hostPort

hostPort的效果与hostNetwork类似,hostPort是直接将容器的端口与所调度的节点上的端口进行映射,这样用户就可以通过宿主机的IP加上映射到绑定主机的端口来访问Pod了

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80 #pod容器指定的端口
hostPort: 9090 #映射到node主机的端口

pod启动后如下,可以看到,pod的ip地址是内部网络ip,与宿主机node的ip不同;与hostNetwork一样,也可以通过node ip + pod port访问,此处的访问port地址为,pod容器端口映射到node主机的端口地址

1
2
3
4
[root@localhost ~]# kubectl get pods -o wide nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx 1/1 Running 0 57s 172.17.0.2 minikube <none>
[root@localhost ~]#

可以curl访问,也可以浏览器访问

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# curl -I http://192.168.10.10:9090
HTTP/1.1 200 OK
Server: nginx/1.19.2
Date: Tue, 01 Sep 2020 09:56:16 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 11 Aug 2020 14:50:35 GMT
Connection: keep-alive
ETag: "5f32b03b-264"
Accept-Ranges: bytes

[root@localhost ~]#

hostPort的优缺点与hostNetwork类似,因为它们都是使用了宿主机的网络资源。hostPort相对hostNetwork的一个优点是,hostPort不需要提供宿主机的网络信息,但其性能不如hostNetwork,因为需要经过iptables的转发才能到达Pod。

3、NodePort

与hostPort、hostNetwork只是Pod的配置不同,NodePort是一种service,其使用的是宿主机node上的端口号,从集群外以 任意node的ip + nodePort 来访问Pod的服务。

NodePort 在 kubenretes 里是一个广泛应用的服务暴露方式。Kubernetes中的service默认情况下都是使用的ClusterIP这种类型,这样的service会产生一个ClusterIP,这个IP只能在集群内部访问,要想让外部能够直接访问service,需要将service type修改为 nodePort。

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
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
name: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: nginx
spec:
type: NodePort
ports:
- name: nginx
port: 80
nodePort: 30000
selector:
name: nginx

svc 配置中的nodePort,即为访问服务时,宿主机的端口号。可以在配置文件中指定(当然不能与其他nodePort类型的svc冲突),也可以不配置,由k8s来分配。

创建上述Pod和service后,如下,查看pod和svc的相关信息,我们可以通过宿主机的ip地址+noePort来访问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
[root@localhost ~]# kubectl get pods -o wide nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx 1/1 Running 0 5m47s 172.17.0.2 minikube <none>
[root@localhost ~]#
[root@localhost ~]# kubectl get svc -o wide nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
nginx NodePort 10.96.116.150 <none> 80:30000/TCP 5m58s name=nginx
[root@localhost ~]#
[root@localhost ~]# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
minikube Ready master 11d v1.12.1 192.168.10.10 <none> CentOS Linux 7 (Core) 3.10.0-957.el7.x86_64 docker://17.12.1-ce
[root@localhost ~]#
[root@localhost ~]# curl -I http://192.168.10.10:30000
HTTP/1.1 200 OK
Server: nginx/1.19.2
Date: Tue, 01 Sep 2020 10:14:12 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 11 Aug 2020 14:50:35 GMT
Connection: keep-alive
ETag: "5f32b03b-264"
Accept-Ranges: bytes

[root@localhost ~]#

集群外就可以使用kubernetes任意一个节点的IP加上30000端口访问该服务了。kube-proxy会自动将流量以round-robin的方式转发给该service的每一个pod。

这种服务暴露方式,无法让你指定自己想要的应用常用端口,不过可以在集群上再部署一个反向代理作为流量入口

7)kubernetes中的CNI网络插件

kubernetes中的网络插件

Kubernetes中常见的网络插件有哪些?

1.flannel:能提供ip地址,但不支持网络策略

2.calico:既提供ip地址,又支持网络策略

3.canal:flannel和calico结合,通过flannel提供ip地址,calico提供网络策略

什么叫做网络策略?

网络策略:可以达到多租户网络隔离,可以控制入网和出网流量,入网和出网ip访问的一种策略

8)flannel介绍

Flannel是CoreOS团队针对Kubernetes设计的一个网络服务,它的功能是让集群中的不同节点创建的Docker容器都具有全集群唯一的虚拟IP地址,Flannel的设计目的就是为集群中的所有节点重新规划IP地址,从而使得不同节点上的容器能够获得“同属一个内网”且”不重复的”IP地址,并让属于不同节点上的容器能够直接通过内网IP通信,Flannel实质上是一种“覆盖网络(overlaynetwork)”,也就是将TCP数据包装在另一种网络包里面进行路由转发和通信,目前已经支持udp、vxlan、host-gw、aws-vpc、gce路由等数据转发方式。

实现扁平网络空间

(1)、flannel支持的路由转发方式

①、vxlan,包含以下两种模式

1
2
3
vxlan:叠加网络模式,利用内核级别的VXLAN来封装host之间传送的包;

Directrouting:直接路由模式,当主机位于同一子网时,启用直接路由(类似host-gw),通过宿主机的物理网卡通信;

②、host-gw:直接路由模式,要求所有pod在同一个网段中,host-gw性能好,依赖少,并且易于设置

③、UDP: 不可用于生产环境,仅在内核或网络无法使用VXLAN或 host-gw时,用 UDP 进行调试

(2)、flannel网络原理图

image-20210128212409882

原理图解释说明

①、网卡和组件说明

安装 flanneld 的守护进程,这个进程会监听一个端口(后期转发和接收数据包的端口)进程开启后会创建一个名为 flannel0 的网桥,这个网桥的一端连接 docker0 的网桥(强行获取 docker0 的数据报文),docker会分配ip到对应的 Pod上

Flanneld 守护进程:它需要连 etcd,利用 etcd 来管理可分配的IP资源,同时监控 etcd 中每个 Pod 的实际地址,将 docker0 发给它的数据包装起来,利用物理网络的连接将数据包投递到目标 flanneld 上,从而完成 pod 到 pod 之间的通信

②、ping包走向

1
2
3
Ⅰ、数据从源容器中发出后,经由所在主机的docker0虚拟网卡转发到flannel0虚拟网卡,flanneld服务监听在网卡的另外一端。

Ⅱ、源主机的flanneld服务将原本的数据内容封装成一个数据包,然后根据自己的路由表投递给目的节点的flanneld服务,数据到达以后被解包,然后直接进入目的节点的flannel0虚拟网卡,然后被转发到目的主机的docker0虚拟网卡,最后就像本机容器通信一样由docker0路由到达目标容器。

注:

flannel通过Etcd分配了每个节点可用的IP地址段后,可以保证每个结点上容器分配的地址不冲突。

(3)、flannel的原理

flannel旨在解决不同节点上的容器网络互联问题,大致原理是为每个 host 分配一个子网,容器从此子网中分配IP,这些 IP可以在 host间路由,容器间无需 NAT 转发就可以跨主机通信,为了在各个主机间共享信息,flannel 用 etcd(如果是k8s集群会直接调用k8s api)存放网络配置、已分配的子网、主机ip等信息。

通过具体例子解释容器跨节点通信时数据包走向

假设容器1是nginx,容器2是tomcat

img

1
2
3
4
5
①、在发送端的node1节点上,数据请求从nginx容器(10.0.46.2:2379)发出后,首先经由所在主机的docker0虚拟网卡(10.0.46.1)转发到flannel0虚拟网卡(10.0.46.0)。

②、接着flannel服务将原本的数据内容UDP封装后根据自己的路由表投递给目的节点的flanneld服务。在此包中,包含有router-ip(宿主机源ip:192.168.8.227 ;宿主机目的ip:192.168.8.228),还有容器ip(source:10.0.46.2:2379dest:10.0.90.2:8080)等数据信息。

③、然后在接收端node2节点上,数据到达以后被解包,直接进入目的节点的flannel0虚拟网卡中(10.0.90.0),且被转发到目的主机的docker0虚拟网卡(10.0.90.1),最后就像本机容器通信一样由docker0路由到达目标 tomcat 容器(10.0.90.2:8080)。
(4)、flannel部署及参数配置

①、部署

1
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

kubectl get pods -n kube-system -o wide 显示如下:

img

kubectl get configmap -n kube-system 显示如下:

img

这个kube-flannel-cfg用来设置上面看到的flannel是怎么运行的,它可以把相关的变量注入到flannel的pod中。

kubectl get configmap kube-flannel-cfg -n kube-system -o yaml可显示configmap的yaml文件内容,具体内容在备注列出

1
2
3
4
5
6
7
8
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
上面可以看到使用的后端网络模型是vxlan,为pod分配的网段是10.244.0.0/16

②、把flannel后端的通信类型修改成Directrouting

重新修改flannel的yaml文件

cd/root/manifests/

mkdir flannel

cd flannel

修改kube-flannel.yml文件

img

kube-flannel.yaml这个文件里在net-conf.json字段增加了”Directrouting”: true,表示是使用直接路由模式,如果没有这个字段就表示使用的是vxlan这种叠加网络模式通信。

kubectl delete -f kube-flannel.yml

等到上面关于flannel的pod都删除之后再重新执行:

kubectl apply -f kube-flannel.yml

kubectl get pods -n kube-system 显示如下,说明重新生成了flannel这个pod,这时后端通信类型使用的就是directrouting了

img

验证是否是通过Directrouting(直接路由模式)通信

cd /root/manifests

kubectl apply -f deploy-myapp.yaml

cat deploy-myapp.yaml

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: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: canary
template:
metadata:
labels:
app: myapp
release: canary
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- name: http
          containerPort: 80

kubectl get pods -o wide 显示如下,说明pod创建成功:

img

窗口1:

kubectl exec -itmyapp-deploy-69b47bc96d-7s7zs – /bin/sh

窗口2:

kubectl exec -itmyapp-deploy-69b47bc96d-lm4m4 – /bin/sh

ping10.244.0.6

窗口3:抓包

tcpdump -i ens160 -nnhost 172.21.0.100 显示如下:

img

上面可以看到两个pod之间的通信是通过ens160,而不是overlay(覆盖网络),这样就提高了网络传输性能;如果两个节点是跨网段的,那么就会降级到vxlan(叠加网络)模式,否则就可以使用直接路由模式。

③、把flannel的后端通信方式改成host-gw

修改kube-flannel.yml文件的如下部分

1
2
3
4
5
6
7
net-conf.json: |
{
"Network":"10.244.0.0/16",
"Backend":{
"Type":"host-gw"
}
}

注:上面修改flannel的网络模式,一般在刚开始安装flannel的时候修改,不要在线上直接修改

④、flannel中vxlan和host-gw的区别

vxlan模式下的的vxlan是一种覆盖网络;host-gw是直接路由模式,将主机作为网关,依赖于纯三层的ip转发。

不同网络模型的性能分析

1
2
1.vxlan的Directrouting模式和host-gw模式性能一样,都是通过宿主机的物理网络进行通信,效率高于vxlan的vxlan模式,但是不能实现跨网段通信;
2.如果要跨网段通信vxlan的Directrouting模式会自动降级到vxlan的vxlan这种叠加网络模式
Flannel网络优点:

1)集群中的不同Node主机创建的Docker容器都具有全集群唯一的虚拟IP地址。

2)etcd保证了所有node上flanned所看到的配置是一致的,同时每个node上的flanned监听etcd上的数据变化,实时感知集群中node的变化

6.Flannel网络缺点:

不支持pod之间的网络隔离,不支持网路策略

(4)、卸载flannel网络

1
2
3
4
5
6
7
8
9
10
11
12
13
#第一步,在master节点删除flannel
kubectl delete -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

#第二步,在node节点清理flannel网络留下的文件
ifconfig cni0 down
ip link delete cni0
ifconfig flannel.1 down
ip link delete flannel.1
rm -rf /var/lib/cni/
rm -f /etc/cni/net.d/*
注:执行完上面的操作,重启kubelet

#第三步,应用calico相关的yaml文件

9)、Calico实现Pod间的网络隔离(比flannel多的功能)

Calico 是一个纯三层的方案,不需要 Overlay,基于 Etcd 维护网络准确性,也基于 Iptables 增加了策略配置

安装Calico,定义网络策略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
- ports:
- protocol: TCP
port: 80

这里的 ingress 规则中我们定义了 fromports,表示允许流入的白名单和端口,上面我们也说了 Kubernetes 会拒绝任何访问被隔离 Pod 的请求,除非这个请求来自于以下“白名单”里的对象,并且访问的是被隔离 Pod 的 8- 端口。而这个允许流入的白名单中指定了三种并列的情况,分别是:ipBlocknamespaceSelectorpodSelector

  • default 命名空间下面带有 role=frontend 标签的 Pod
  • 带有 project=myproject 标签的 Namespace 下的任何 Pod
  • 任何源地址属于 172.17.0.0/16 网段,且不属于 172.17.1.0/24 网段的请求。

egress: 每个 NetworkPolicy 包含一个 egress 规则的白名单列表。每个规则都允许匹配 toport 部分的流量。比如我们这里示例规则的配置:

1
2
3
4
5
6
7
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978

表示 Kubernetes 会拒绝被隔离 Pod 对外发起任何请求,除非请求的目的地址属于 10.0.0.0/24 网段,并且访问的是该网段地址的 5978 端口。

4、Pod生命周期状态

1607674213722

  • 挂起(Pending):Pod 信息已经提交给了集群,但是还没有被调度器调度到合适的节点或者 Pod 里的镜像正在下载,或者是下载镜像慢,调度不成功

  • 运行中(Running):该 Pod 已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态

  • 成功(Succeeded):Pod 中的所有容器都被成功终止,并且不会再重启

  • 失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止

  • 未知(Unknown):因为某些原因apiserver无法取得 Pod 的状态,通常是master与与 Pod 所在主机通信失败导致的

  • Pending状态:Pending 说明 Pod 还没有调度到某个 Node 上面。可以通过

    kubectl describe pod 命令查看到当前 Pod 的事件,进而判断为什么没有调度。可能的原因包括:资源不足,集群内所有的 Node 都不满足该 Pod 请求的 CPU、内存、GPU 等资源

    HostPort 已被占用,通常推荐使用 Service 对外开放服务端口

  • imagePullBackOff:镜像拉取失败这也是我们测试环境常见的,通常是镜像拉取失败。这种情况可以使用 docker pull 来验证镜像是否可以正常拉取。

  • Error:通常处于 Error 状态说明 Pod 启动过程中发生了错误。常见的原因包括:

    依赖的 ConfigMap、Secret 或者 PV 等不存在

    请求的资源超过了管理员设置的限制,比如超过了 LimitRange 等

    违反集群的安全策略,比如违反了 PodSecurityPolicy 等

    容器无权操作集群内的资源,比如开启 RBAC 后,需要为 ServiceAccount 配置角色绑定

  • Evicted状态:出现这种情况,多见于系统内存或硬盘资源不足,可df-h查看docker存储所在目录的资源使用情况,如果百分比大于85%,就要及时清理下资源,尤其是一些大文件、docker镜像。

5、Pod的重启策略

restartpolicy字段设置Pod中所有容器的重启策略 Always(默认),OnFailure,Nerver

不同类型的的控制器可以控制 Pod 的重启策略:

  • Job:适用于一次性任务如批量计算,任务结束后 Pod 会被此类控制器清除。Job 的重启策略只能是"OnFailure"或者"Never"
  • Replication Controller, ReplicaSet, or Deployment,此类控制器希望 Pod 一直运行下去,它们的重启策略只能是"Always"
  • DaemonSet:每个节点上启动一个 Pod,很明显此类控制器的重启策略也应该是"Always"

6、Init容器(初始化容器)

1、解决服务间的依赖问题

2、做初始化配置

7、Pod Hook(钩子函数)

由kubelet发起的,当容器中的进程启动前或者容器中的进程终止之前运行。,包含在容器的生命周期中,了一通是为 Pod 中的所有容器都配置 hook

PostStart:容器创建后立即执行,主要用于资源部署、环境准备等

PreStop: 在容器终止之前立即被调用。 主要用于优雅关闭应用程序、通知其他系统等

Hook 的类型:

exec:执行一段命令

HTTP:发送HTTP请求

8、Pod健康检查(探针)

liveness probe(存活探针) 和 readiness probe(可读性探针)

三种方式:

exec:执行一段命令,返回码为0,诊断成功

http:检测某个http请求,状态码在200~400之间,认为诊断成功

tcpSocket:检查端口,打开就是成功

liveness probe(存活探针):探测容器是否存活。如果存活探测失败,则kubelet会杀死容器,并且容器将受重启策略影响。如果容器不提供存活探针,则默认状态为Success。

readiness probe(可读性探针):指容器是否准备好服务请求。如果探测失败,端点控制器从与pod匹配的所有service的端点中删除该pod的IP地址。

  • initialDelaySeconds:容器启动,探针延后工作,默认是0s
  • periodSeconds 探针探测周期,默认10s
  • timeoutSeconds: 探针工作的超时时间,默认1s
  • successThreshold: 连续几次探测成功,该探针被认为是成功的,默认1次
  • failureThreshold: 连续几次探测失败,该探针被认为最终失败,对于livenes探针最终失败意味着重启,对于readiness探针意味着该pod Unready, 默认3次。

9、Pod漂移

1
众所周知Kubernetes具有强大的副本控制能力,能保证在任意副本(Pod)挂掉时自动从其他机器启动一个新的,还可以动态扩容等,总之一句话,这个Pod可能在任何时刻出现在任何节点上,也可能在任何时刻死在任何节点上;那么自然随着Pod 的创建和销毁,Pod lP肯定会动态变化;那么如何把这个动态的 Pod lP暴露出去?这里借助于Kubernetes的 Service机制,Service可以以标签的形式选定一组带有指定标签的Pod,并监控和自动负载他们的Pod IP,那么我们向外暴露只暴露Service lP就行了;这就是NodePort模式:即在每个节点上开起一个端口,然后转发到内部 Pod iP上

10、pod中的容器可以共享的资源

76a396c1fb96b62174f18f6f6ce8b31

三、资源清单

1、集群资源分类

k8s中所有的内容都抽象为资源,资源实例化之后,叫对象

1)名称空间级别

1
2
3
4
5
6
7
工作负载型资源(workload):Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、CronJob(ReplicationController在v1.11版本被废弃)

服务发现及负载均衡型资源(Service Discovery LoadBalance):Service、Ingress、...

配置与存储型资源:Volume(存储卷)、CSI(容器存储接口,可扩展各种各样的第三方存储卷)

特殊类型的存储卷:ConfigMap(当配置中心来使用的资源类型)、Secret(保存敏感数据)、DownwardAPI(把外部环境中的信息输出给容器)

2)集群级别

1
Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding

3)元数据型:通过指标进行操作

1
Hpa、PodTemplate、LimitRange

2、资源清单

k8s中一般使用yaml格式的文件来创建我么不所预期的pod,这样的yaml文件我们一般称为资源清单

3、常用字段解释说明

参数名 字段类型 说明
apiVersion String 这里指的是k8s API的版本,基本上是v1.可以用kubectl api-version命令查询
kind String 这里指的是yaml文件定义的资源类型和角色,比如:Pod
metadata Object 元数据对象,固定值就写medata
metadata.name String 元数据对象的名字,这里由我们白那些,比如命名Pod的名字
metadata.namespace String 元数据对象的命名空间,由我们自身定义
metadata.labels
metadata.labels.app
metadata.labels.version
spec Object 详细定义对象,固定值就写Spec
spec.containers[] List 这里是Spec对象的容器列表定义,是个列表
spec.containers[].name String 这里定义容器的名字
spec.containers[].image String 这里定义要用到镜像名称
spce.containers[].imagepPullPolicy String 定义镜像拉取策略,由Always、Never、IfNotPresent三个值可选(1)Always:意思是每次都尝试拉取镜像(2)Never:表示仅使用本地镜像(3)IfNotPresent:如果本地有镜像就使用本地镜像,没有就在线拉取镜像,上面三个值都没有设置的话,就默认Always
spec.containers[].command[] List 指定容器启动命令,因为是数组可以指定多个,不指定则使用镜像打包时使用的启动命令
spec.containers[].arg[] List 指定容器启动命令参数,因为是数组可以指定多个
spec.sontainers[].workingDir String 指定容器的工作目录
spec.containers[].volumeMounts[] List 指定容器内部的存储卷配置
spec.containers[].volumeMounts[].name String 指定可以被容器挂载的存储卷的名称
spec.containers[].volumeMounts[].mountPath String 指定可以被容器挂载的存储卷的路径
spec.containers[].volumeMounts[].readOnly String 设置存储卷路径的读写模式,true或者是false,默认为读写模式
spec.containers[].pots[] List 指定容器需要用到的端口列表
spec.containers[].pots[].name String 指定端口名称
spec.containers[].pots[].containerPots String 指定容器需要监听的端口号
spec.containers[].pots[].hostPort String 指定容器需要监听的端口号,默认跟上面的containerPort相同,注意设置了hostPort同一台主机无法启动该容器的相同副本(因为主机的端口号不能相同,这样会冲突)
spec.containers[].ports[].protocol String 指定端口协议,支持TCP和UDP,默认为TCP
spec.containers[].env[] List 指定容器运行前需设置的环境变量列表
spec.containers[].env[].name String 指定环境变量名称
spec.containers[].env[].value String 指定环境变量值
spec.containers[].env[].resources Object 指定资源限制和资源请求的值(这里开始就是设置容器的资源上限)
spec.containers[].env[].resources.limits Object 指定设置容器运行时资源的运行上限
spec.containers[].env[].resources.cpu String 指定CPU的限制,单位为core数,将用于docker run –cpu-shares参数(这里前面文章Pod资源限制有讲过)
spec.containers[].env[].resources.limits.memory String 指定MEM内存的限制,单位为MIB、GiB
spec.containers[].env[].resources.requests Object 指定容器启动和调度时的限制设置
spec.containers[].env[].resources.resquests.cpu String CPU请求,单位为core数,容器启动时初始化可用数量
spec.containers[].env[].resources.request.memory String 内存请求,单位为MIB、GiB,容器启动的初始化可用数量
spec.restartPolicy String 定义Pod的重启策略,可选值为Always、OnFailure,默认值为Always 1.Always:Pod一旦终止运行,则无论容器是如何终止的,kubectl服务都将重启它 2.OnFailure:只有Pod以非零退出码终止时,kubectl才会重启该容器。如果以正常结束(退出码为0),则kubectl将不会重启它 3.Never:Pod终止后,kubectl将退出码报告给Master,不会重启该Pod
sspec.nodeSelector Object 定义Node的Label过滤标签,以key:value格式指定
sspec.imagePullSecrets Object 定义Pull镜像时使用secret名称,以name:secretkey格式指定
sspec.hostNetwork Boolean 定义是否使用主机网络模式,默认值为false。设置ture表示使用宿主机网络,不使用docker网桥,同时设置了true将无法在同一台宿主机上启动第二个副本

4、容器生命周期

image-20210201182125253

Pod 能够具有多个容器,应用运行在容器里,但是它也有可能有一个或者多个先于容器应用启动的 Init 容器

Init 容器与普通容器非常像,除了如以下两点:

  • Init 容器总是运行到成功完成为止
  • 每个 Init 容器都必须在下一个 Init 容器自动启动之前完成

如果 Pod 的 Init 容器启动失败, Kubernetes 会不断的重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的 retartPolicy 为 Never,它不会重新启动

因为 Init 容器具有与应用程序分离的单独镜像,所以他们的启动相关代码具有如下优势

  • 它们可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这些实用工具的

  • 它们可以包含使用工具和定制化代码来安装,但是不能出现在应用程序镜像中。例如,创建镜像没必要FROM另一个镜像,只需要在安装过程中使用类似sed、awk、python或dig这样的工具。

  • 应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。

  • Init容器使用Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此,它们能够具有访问Secret的权限,而应用程序容器则不能。

  • 它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以 Init容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法,直到满足了一组先决条件。

  • 在Pod 启动过程中,Init容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出

  • 如果由于运行时或失败退出,将导致容器启动失败,它会根据 Pod 的restartPolicy 指定的策略进行重试。然而,如果Pod 的 restartPolicy 设置为Always,Init 容器失败时会使用 RestartPolicy 策略

  • 在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready 状态。Init 容器的端口将不会在 Service 中进行聚集。正在初始化中的 Pod 处于 Pending 状态,但应该会将 Initializing 状态设置为true

  • 如果 Pod 重启,所有 Init 容器必须重新执行

  • #对 Init 容器 spec 的修改被限制在容器 image 字段,修改其他字段都不会生效。更改 Init 容器的 image 字段,等价于重启该 Pod

  • Init 容器具有应用容器的所有字段。除了 readinessProbe,因为 Init 容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态。这会在验证过程中强制执行

  • 在 Pod 中的每个 app 和 Init 容器的名称必须唯一; 与任何其它容器共享同一个名称,会在验证时抛出错误

四、控制器

ReplicaSet跟RelicationController没有什么本质的不同,只是名字不同,并且ReplicaSet支持集合式的selector,虽然RS可以独立使用,但一般还是建议使用Deployment来自动管理RS,这样就无需担心跟其他机制不兼容问题(比如RS不支持rolling-update但Deployment支持)

1、ReplicaSet

作用: 维持一组 Pod 副本的运行,保证一定数量的 Pod 在集群中正常运行,ReplicaSet 控制器会持续监听它说控制的这些 Pod 的运行状态,在 Pod 发送故障数量减少或者增加时会触发调谐过程,始终保持副本数量一定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-rs
namespace: default
spec:
replicas: 3 # 期望的 Pod 副本数量,默认值为1
selector: # Label Selector,必须匹配 Pod 模板中的标签
matchLabels:
app: nginx
template: # Pod 模板
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80

RS通过标签找Pod

HPA

基于RS定义,监控RS中Pod的利用率,

模板

1
2
3
CPU > 80  ##时进行扩展  
Max 10 ##扩展最大值为10
Min 2 ##最小值为2

当CPU达到模板中的值时,在RS中新建Pod直到达到max最大值(如果创建第三个Pod时CPU利用率就小于了80,那么也不会继续新建Pod),利用率变低以后Pod就会被回收,删到最小值min为止

2、Deployment(针对无状态服务)

1、定义Deployment来创建Pod和RS

2、滚动升级和回滚应用

3、扩容和缩容

4、暂停和继续Deployment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: apps/v1
kind: Deployment ###控制器类型
metadata:
name: nginx-deploy
namespace: default
spec:
replicas: 3 # 期望的 Pod 副本数量,默认值为1
selector: # Label Selector,必须匹配 Pod 模板中的标签
matchLabels:
app: nginx
template: # Pod 模板
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80

Deployment控制RS,RS控制Pod

1)、扩容

水平增加Pod数量,通过管理副本数

1
kubectl scale deployment nginx-deployment --replicas 10

设置自动扩展

1
kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80

2)、滚动升级(更新镜像)

1
kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1

更新一个删除一个

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: nginx
minReadySeconds: 5
strategy:
type: RollingUpdate # 指定更新策略:RollingUpdate和Recreate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
  • minReadySeconds:表示 Kubernetes 在等待设置的时间后才进行升级,如果没有设置该值,Kubernetes 会假设该容器启动起来后就提供服务了,如果没有设置该值,在某些极端情况下可能会造成服务不正常运行,默认值就是0。
  • type=RollingUpdate:表示设置更新策略为滚动更新,可以设置为RecreateRollingUpdate两个值,Recreate表示全部重新创建,默认值就是RollingUpdate
  • maxSurge:表示升级过程中最多可以比原先设置多出的 Pod 数量,例如:maxSurage=1,replicas=5,就表示Kubernetes 会先启动一个新的 Pod,然后才删掉一个旧的 Pod,整个升级过程中最多会有5+1个 Pod。
  • maxUnavaible:表示升级过程中最多有多少个 Pod 处于无法提供服务的状态,当maxSurge不为0时,该值也不能为0,例如:maxUnavaible=1,则表示 Kubernetes 整个升级过程中最多会有1个 Pod 处于无法服务的状态。

我们可以添加了一个额外的 --record 参数来记录下我们的每次操作所执行的命令,以方便后面查看。

查看滚动更新状态

1
kubectl rollout status deployment/nginx-deployment

暂停滚动更新

1
kubectl rollout pause deployment/nginx-deploy

恢复滚动更新

1
kubectl rollout resume deployment/nginx-deployment

查看历史版本

1
kubectl rollout history deployment nginx-deployment

3)、回滚

1
kubectl rollout undo deployment/nginx-deployment

回滚一个删除一个

回退到指定版本

1
kubectl rollout undo deployment/nginx-deployment --to-revision=1

3、StatefulSet(针对有状态服务)

1)、有状态服务和无状态服务

无状态服务(Stateless Service):该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的

有状态服务(Stateful Service):就和上面的概念是对立的了,该服务运行的实例需要在本地存储持久化数据 :MySQL,mangoDB

2)、功能特性

  • 稳定的、唯一的网络标识符,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
  • 稳定的、持久化的存储,即Pod重新调度后还能访问到相同持久化数据,基于PVC来实现
  • 有序的、优雅的部署和缩放,即Pod时有顺序的在部署或者扩展的时候要有根据定义的顺序依次进行(即从 0 到 N-1 ,在下一个Pod运行之前所有的 Pod 必须都是 Running 和 Ready 状态),基于 initial containers 来实现
  • 有序的、优雅的删除和终止(即 N-1 到 0)
  • 有序的、自动滚动更新

启停顺序:

1606929335349

3)、Headless Service(无头服务)

特点:ClusterIP=None

创建后不会被分配clusterip,以DNS记录的方式暴露所代理的Pod,所代理的Pod绑定如下DNS记录

1
<pod-name>.<svc-name>.<namespace>.svc.cluster.local

4)、创建

1、先创建PV

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
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv001

---

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv002

2、stateful的资源清单

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
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: default
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- name: web
containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi

上面资源清单中和 volumeMounts 进行关联的不是 volumes 而是一个新的属性:volumeClaimTemplates,该属性会自动创建一个 PVC 对象 ,PVC 被创建后会自动去关联当前系统中和他合适的 PV 进行绑定

serviceName 就是管理当前 StatefulSet 的服务名称,该服务必须在 StatefulSet 之前存在,并且负责该集合的网络标识,Pod 会遵循以下格式获取 DNS/主机名:pod-specific-string.serviceName.default.svc.cluster.local,其中 pod-specific-string 由 StatefulSet 控制器管理。

1606921192288

StatefulSet 引入了 PV 和 PVC 对象来持久存储服务产生的状态,这样所有的服务虽然可以被杀掉或者重启,但是其中的数据由于 PV 的原因不会丢失。

Pod 的名称的形式为

1
<statefulset name>-<ordinal index>

StatefulSet 中 Pod 副本的创建会按照序列号升序处理,副本的更新和删除会按照序列号降序处理。

5)、管理策略

对于某些分布式系统来说,StatefulSet 的顺序性保证是不必要和/或者不应该的,这些系统仅仅要求唯一性和身份标志。为了解决这个问题,我们只需要在声明 StatefulSet 的时候重新设置 spec.podManagementPolicy 的策略即可。

默认的管理策略是 OrderedReady,表示让 StatefulSet 控制器遵循上文演示的顺序性保证。除此之外,还可以设置为 Parallel 管理模式,表示让 StatefulSet 控制器并行的终止所有 Pod,在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态。

6)、更新策略

在 StatefulSet 中同样也支持两种升级策略:onDeleteRollingUpdate,同样可以通过设置 .spec.updateStrategy.type 进行指定

  • OnDelete: 该策略表示当更新了 StatefulSet 的模板后,只有手动删除旧的 Pod 才会创建新的 Pod。
  • RollingUpdate:该策略表示当更新 StatefulSet 模板后会自动删除旧的 Pod 并创建新的Pod,如果更新发生了错误,这次“滚动更新”就会停止。不过需要注意 StatefulSet 的 Pod 在部署时是顺序从 0n 的,而在滚动更新时,这些 Pod 则是按逆序的方式即 n0 一次删除并创建。

另外SatefulSet 的滚动升级还支持 Partitions的特性,可以通过 过.spec.updateStrategy.rollingUpdate.partition 进行设置,在设置 partition 后,SatefulSet 的 Pod 中序号大于或等于 partition 的 Pod 会在 StatefulSet 的模板更新后进行滚动升级,而其余的 Pod 保持不变

有状态服务一般拿更高级的 Operator 来部署

7)、使用场景

1606929298297

4、DaemonSet

确保全部(或一些)Node上运行一个(有且只有一个)Pod副本。当有Node加入集群时,会为他们新增一个Pod。当有Node从集群移除时,这些Pod也会被回收。删除DaemonSet将会删除它所创建的所有Pod

典型用法:

  • 集群存储守护程序,如 glusterd、ceph 要部署在每个节点上以提供持久性存储;
  • 节点监控守护进程,如 Prometheus 监控集群,可以在每个节点上运行一个 node-exporter 进程来收集监控节点的信息;
  • 日志收集守护程序,如 fluentd 或 logstash,在每个节点上运行以收集容器的日志
  • 节点网络插件,比如 flannel、calico,在每个节点上运行为 Pod 提供网络服务。

每个节点部署nginx pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-ds
namespace: default
spec:
selector:
matchLabels:
k8s-app: nginx
template:
metadata:
labels:
k8s-app: nginx
spec:
containers:
- image: nginx:1.7.9
name: nginx
ports:
- name: http
containerPort: 80

那么,DaemonSet 控制器是如何保证每个 Node 上有且只有一个被管理的 Pod 呢?

  • 首先控制器从 Etcd 获取到所有的 Node 列表,然后遍历所有的 Node。
  • 根据资源对象定义是否有调度相关的配置,然后分别检查 Node 是否符合要求。
  • 在可运行 Pod 的节点上检查是否已有对应的 Pod,如果没有,则在这个 Node 上创建该 Pod;如果有,并且数量大于 1,那就把多余的 Pod 从这个节点上删除;如果有且只有一个 Pod,那就说明是正常情况。

5、Job及CronJob

五、对外暴露集群服务

image-20210128211500390

有状态服务:删除后重新加入,可能不能正常工作

无状态服务:删除后重新加入工作正常进行,无影响

1、Service

概念:Pod的逻辑分组,提供可以访问Pod的逻辑策略

通过标签找到对应的Pod

局限:只提供4层负载均衡能力,没有7层功能(ingress)

1)、服务类型:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的服务类型。
  • NodePort:通过每个 Node 节点上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 NodeIp:NodePort,可以从集群的外部访问一个 NodePort 服务。
  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务,这个需要结合具体的云厂商进行操作。
  • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。

2)、IPVS代理模式:

1606835979507

kube-proxy会监视Kubernetes Service对象和Endpoints,调用netlink接口以相应地创建ipvs规则并定期与Kubernetes Service对象和Endpoints对象同步ipvs规则,以确保ipvs状态与期望一致。访问服务时,流量将被重定向到其中一个后端Pod

3)、iptables代理模式

1606871650608

这种模式,kube-proxy 会监视 apiserver 对 Service 对象和 Endpoints 对象的添加和移除。对每个 Service,它会添加上 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某一个 Pod 上面。我们还可以使用 Pod readiness 探针 验证后端 Pod 可以正常工作,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端,这样做意味着可以避免将流量通过 kube-proxy 发送到已知失败的 Pod 中,所以对于线上的应用来说一定要做 readiness 探针。

4)、ClusterIP实现:

1606836081494

Headless Server:指定ClusterIP为“none”。不会分配Cluster IP,kube-proxy不会处理它们,平台也不会为它们进行负载均衡和路由

5)、NodePort(重点)

如果设置 type 的值为 “NodePort”,Kubernetes master 将从给定的配置范围内(默认:30000-32767)分配端口,每个 Node 将从该端口(每个 Node 上的同一端口)代理到 Service。该端口将通过 Service 的 spec.ports[*].nodePort 字段被指定,如果不指定的话会自动生成一个端口。

需要注意的是,Service 将能够通过 spec.ports[].nodePortspec.clusterIp:spec.ports[].port 而对外可见。

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
selector:
app: myapp
type: NodePort
ports:
- protocol: TCP
port: 80
targetPort: 80
name: myapp-http

6)、ExternalName

ExternalName 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

1
2
3
4
5
6
7
8
kind: Service
apiVersion: v1
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com

当访问地址 my-service.prod.svc.cluster.local(后面服务发现的时候我们会再深入讲解)时,集群的 DNS 服务将返回一个值为 my.database.example.com 的 CNAME 记录。访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type,完全不需要修改调用的代码,这样就完全解耦了。

除了可以直接通过 externalName 指定外部服务的域名之外,我们还可以通过自定义 Endpoints 来创建 Service,前提是 clusterIP=None,名称要和 Service 保持一致,如下所示:

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
apiVersion: v1
kind: Service
metadata:
name: etcd-k8s
namespace: kube-system
labels:
k8s-app: etcd
spec:
type: ClusterIP
clusterIP: None
ports:
- name: port
port: 2379

---

apiVersion: v1
kind: Endpoints
metadata:
name: etcd-k8s # 名称必须和 Service 一致
namespace: kube-system
labels:
k8s-app: etcd
subsets:
- addresses:
- ip: 10.151.30.57 # Service 将连接重定向到 endpoint
ports:
- name: port
port: 2379 # endpoint 的目标端口

上面这个服务就是将外部的 etcd 服务引入到 Kubernetes 集群中来。

7)、Headless Service(无头服务)

特点:ClusterIP=None

创建后不会被分配clusterip,以DNS记录的方式暴露所代理的Pod,所代理的Pod绑定如下DNS记录

1
<pod-name>.<svc-name>.<namespace>.svc.cluster.local

8)、生成的域名

  • 普通的 Service:会生成 servicename.namespace.svc.cluster.local 的域名,会解析到 Service 对应的 ClusterIP 上,在 Pod 之间的调用可以简写成 servicename.namespace,如果处于同一个命名空间下面,甚至可以只写成 servicename 即可访问
  • Headless Service:无头服务,就是把 clusterIP 设置为 None 的,会被解析为指定 Pod 的 IP 列表,同样还可以通过 podname.servicename.namespace.svc.cluster.local 访问到具体的某一个 Pod。

2、Ingress(ingress-nginx)

Ingress 其实就是从 Kuberenets 集群外部访问集群的一个入口,将外部的请求转发到集群内不同的 Service 上,其实就相当于 nginx、haproxy 等负载均衡代理服务器 。 ngress 实际上就是这样实现的,只是服务发现的功能自己实现了,不需要使用第三方的服务了,然后再加上一个域名规则定义,路由信息的刷新依靠 Ingress Controller 来提供

1606892751950

Ingress Controller 可以理解为一个监听器,通过不断地监听 kube-apiserver,实时的感知后端 Service、Pod 的变化,当得到这些信息变化后,Ingress Controller 再结合 Ingress 的配置,更新反向代理负载均衡器,达到服务发现的作用。其实这点和服务发现工具 consul、 consul-template 非常类似。

1606836650135

下图显示了客户端是如果通过 Ingress Controller 连接到其中一个 Pod 的流程,客户端首先对 ngdemo.qikqiak.com 执行 DNS 解析,得到 Ingress Controller 所在节点的 IP,然后客户端向 Ingress Controller 发送 HTTP 请求,然后根据 Ingress 对象里面的描述匹配域名,找到对应的 Service 对象,并获取关联的 Endpoints 列表,将客户端的请求转发给其中一个 Pod。

图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: my-nginx
annotations:
kubernetes.io/ingress.class: "nginx" #指定让这个 Ingress 通过 nginx-ingress 来处理
spec:
rules:
- host: ngdemo.qikqiak.com # 将域名映射到 my-nginx 服务
http:
paths:
- path: /
backend:
serviceName: my-nginx # 将所有请求发送到 my-nginx 服务的 80 端口
servicePort: 80 # 不过需要注意大部分Ingress controller都不是直接转发到Service
# 而是只是通过Service来获取后端的Endpoints列表,直接转发到Pod,这样可以减少网络跳转,提高性能

1)、URL Rewrite

1606874071461

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: fe
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: http://foo.bar.com:31795/hostname.html
spec:
rules:
- host: foo10.bar.com
http:
paths:
- path
backend:
serviceName: fe
servicePort: 3000

2)、Auth认证

1、生成密码

1
htpasswd -c auth foo

2、创建secret对象

1
kubectl create secret generic basic-auth --from-file=auth

3、创建Ingress对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-with-auth
annotations:
# 认证类型
nginx.ingress.kubernetes.io/auth-type: basic
# 包含 user/password 定义的 secret 对象名
nginx.ingress.kubernetes.io/auth-secret: basic-auth
# 要显示的带有适当上下文的消息,说明需要身份验证的原因
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /
backend:
serviceName: my-nginx
servicePort: 80

3)、HTTPS代理访问

1、创建证书

1
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=foo.bar.com"

2、 通过 Secret 对象来引用证书文件

1
2
# 要注意证书文件名称必须是 tls.crt 和 tls.key 
$ kubectl create secret tls foo-tls --cert=tls.crt --key=tls.key

3、创建Ingress对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-with-auth
annotations:
# 认证类型
nginx.ingress.kubernetes.io/auth-type: basic
# 包含 user/password 定义的 secret 对象名
nginx.ingress.kubernetes.io/auth-secret: basic-auth
# 要显示的带有适当上下文的消息,说明需要身份验证的原因
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /
backend:
serviceName: my-nginx
servicePort: 80
tls:
- hosts:
- foo.bar.com
secretName: foo-tls

六、配置管理

1、ConfigMap(可变配置管理)

概念:向容器中注入配置信息。ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象

注:一般情况ConfigMap存储一些非安全的配置信息,是明文存储的

ConfigMap创建

1)、使用目录创建

1
2
3
4
5
6
7
8
9
10
11
$ ls testcm
redis.conf
mysql.conf

$ cat testcm/redis.conf
host=127.0.0.1
port=6379

$ cat testcm/mysql.conf
host=127.0.0.1
port=3306

然后我们就可以使用 from-file 关键字来创建包含这个目录下面所以配置文件的 ConfigMap

1
kubectl create configmap cm-demo1 --from-file=testcm

查看完整键值

1
kubectl get configmap cm-demo1 -o yaml

2)、使用文件创建

1
$ kubectl create configmap cm-demo2 --from-file=testcm/redis.conf

注意: --from-file 这个参数可以使用多次,比如我们这里使用两次分别指定 redis.conf 和 mysql.conf 文件,就和直接指定整个目录是一样的效果了。

3)、使用字符串创建

通过 --from-literal 参数传递配置信息,同样的,这个参数可以使用多次

1
kubectl create configmap cm-demo3 --from-literal=db.host=localhost --from-literal=db.port=3306

ConfigMap使用

1)、使用ConfigMap代替环境变量

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: Pod
metadata:
name: testcm1-pod
spec:
containers:
- name: testcm1
image: busybox
command: [ "/bin/sh", "-c", "env" ]
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: cm-demo3
key: db.host
- name: DB_PORT
valueFrom:
configMapKeyRef:
name: cm-demo3
key: db.port
envFrom:
- configMapRef:
name: cm-demo1

输出效果:

1
2
3
4
5
6
7
8
9
$ kubectl logs testcm1-pod
......
DB_HOST=localhost
DB_PORT=3306
mysql.conf=host=127.0.0.1
port=3306
redis.conf=host=127.0.0.1
port=6379
......

2)、使用ConfigMap设置命令行参数

使用 ConfigMap来设置命令行参数,ConfigMap 也可以被用来设置容器中的命令或者参数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: testcm2-pod
spec:
containers:
- name: testcm2
image: busybox
command: [ "/bin/sh", "-c", "echo $(DB_HOST) $(DB_PORT)" ]
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: cm-demo3
key: db.host
- name: DB_PORT
valueFrom:
configMapKeyRef:
name: cm-demo3
key: db.port

输出效果:

1
$ kubectl logs testcm2-pod localhost 3306

3)、通过数据卷使用

通过数据卷使用,在数据卷里面使用 ConfigMap,就是将文件填入数据卷,在这个文件中,键就是文件名,键值就是文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: testcm3-pod
spec:
volumes:
- name: config-volume
configMap:
name: cm-demo2
containers:
- name: testcm3
image: busybox
command: [ "/bin/sh", "-c", "cat /etc/config/redis.conf" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config

输出效果:

1
2
3
$ kubectl logs testcm3-pod
host=127.0.0.1
port=6379

也可以在 ConfigMap 值被映射的数据卷里去控制路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Pod
metadata:
name: testcm4-pod
spec:
volumes:
- name: config-volume
configMap:
name: cm-demo1
items:
- key: mysql.conf
path: path/to/msyql.conf
containers:
- name: testcm4
image: busybox
command: [ "/bin/sh","-c","cat /etc/config/path/to/msyql.conf" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config

输出效果:

1
2
3
$ kubectl logs testcm4-pod
host=127.0.0.1
port=3306

ConfigMap的热更新

1、 当 ConfigMap 以数据卷的形式挂载进 Pod 的时,这时更新 ConfigMap(或删掉重建ConfigMap),Pod 内挂载的配置信息会热更新 。 这时可以增加一些监测配置文件变更的脚本,然后重加载对应服务就可以实现应用的热更新。

2、使用ConfigMap挂载的ENV不会同步更新

3、 只有通过 Kubernetes API 创建的 Pod 才能使用 ConfigMap,其他方式创建的(比如静态 Pod)不能使用

4、 ConfigMap 文件大小限制为 1MB(ETCD 的要求)

5、更新ConfigMap目前并不会触发相关Pod 的滚动更新,可以通过修改pod annotations的方式强制触发滚动更新

2、Secret(敏感信息配置管理)

Secret用来保存敏感信息,例如密码、OAuth 令牌和 ssh key 等等,将这些信息放在 Secret 中比放在 Pod 的定义中或者 Docker 镜像中要更加安全和灵活。

Secret 主要使用的有以下三种类型:

  • Opaque:base64 编码格式的 Secret,用来存储密码、密钥等;但数据也可以通过base64 –decode解码得到原始数据,所有加密性很弱。
  • kubernetes.io/dockerconfigjson:用来存储私有docker registry的认证信息。
  • kubernetes.io/service-account-token:用于被 ServiceAccount ServiceAccount 创建时 Kubernetes 会默认创建一个对应的 Secret 对象。Pod 如果使用了 ServiceAccount,对应的 Secret 会自动挂载到 Pod 目录 /run/secrets/kubernetes.io/serviceaccount 中。
  • bootstrap.kubernetes.io/token:用于节点接入集群的校验的 Secret

1)、Opaque Secret

Opaque 类型的数据是一个 map 类型,要求 value 必须是 base64 编码格式,比如我们来创建一个用户名为 admin,密码为 admin321 的 Secret 对象,首先我们需要先把用户名和密码做 base64 编码:

1
2
3
4
$ echo -n "admin" | base64
YWRtaW4=
$ echo -n "admin321" | base64
YWRtaW4zMjE=

编写yaml文件

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: YWRtaW4zMjE=

发布并查看

1
2
3
4
5
6
$ kubectl create -f secret-demo.yaml

$ kubectl get secret
NAME TYPE DATA AGE
default-token-n9w2d kubernetes.io/service-account-token 3 33d
mysecret Opaque 2 40s

default-token-n9w2d 为创建集群时默认创建的 Secret,被 serviceacount/default 引用

两种使用方式
  • 以环境变量的形式
  • 以Volume的形式挂载

环境变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: secret1-pod
spec:
containers:
- name: secret1
image: busybox
command: [ "/bin/sh", "-c", "env" ]
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password

创建后查看日志输出:

1
2
3
4
5
$ kubectl logs secret1-pod
...
USERNAME=admin
PASSWORD=admin321
...

volume挂载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: secret2-pod
spec:
containers:
- name: secret2
image: busybox
command: ["/bin/sh", "-c", "ls /etc/secrets"]
volumeMounts:
- name: secrets
mountPath: /etc/secrets
volumes:
- name: secrets
secret:
secretName: mysecret

2)、service-account

Service Account用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中

有三个文件:

  • ca.crt:用于校验服务端的证书信息
  • namespace:表示当前管理的命名空间
  • token:用于 Pod 身份认证的 Token

3)、dockerconfigjson

(1)创建用户 docker registry 认证的 Secret,直接使用``kubectl create` 命令创建即可

1
2
$ kubectl create secret docker-registry myregistry --docker-server=DOCKER_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
secret "myregistry" created

(2)使用指定文件

1
$ kubectl create secret generic myregistry --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson

3、Secret和ConfidMap的异同点

相同点:

  • key/value的形式
  • 属于某个特定的命名空间
  • 可以导出到环境变量
  • 可以通过目录/文件形式挂载
  • 通过 volume 挂载的配置信息均可热更新

不同点:

  • Secret 可以被 ServerAccount 关联
  • Secret 可以存储 docker register 的鉴权信息,用在 ImagePullSecret 参数中,用于拉取私有仓库的镜像
  • Secret 支持 Base64 加密
  • Secret 分为 kubernetes.io/service-account-tokenkubernetes.io/dockerconfigjsonOpaque 三种类型,而 Configmap 不区分类型

七、存储

1)、背景

首先,当容器崩溃时,kubelet会重启容器,但容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。

其次,在Pod中同时运行多个容器时,这些容器之间通常需要共享文件,K8S中的volume抽象就很好的解决这些问题。

2)、emptyDir

当Pod被分配到节点时,首先创建emptyDir卷,并且只要该Pod在该节点上运行,该卷就会存在。它最初是空的,Pod中的容器可以读取和写入emptyDir卷中的相同文件,尽管改卷可以挂载到容器中的相同或不同路径上。当出于任何原因从节点中删除Pod时,emptyDir中的数据讲被永久删除。

emptyDir的用法:

1、暂存空间,例如用于基于磁盘的合并排序;

2、用作长时间计算崩溃恢复时的检查点;

3、Web服务器容器提供数据时,保存内容管理器容器提取的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}

3)、hostpath

将主机节点的文件系统中的文件或目录挂载到集群中

1606928497934

1606928561247

hostPath Volume 的作用是将 Docker Host 文件系统中已经存在的目录 mount 给 Pod 的容器。大部分应用都不会使用 hostPath Volume,因为这实际上增加了 Pod 与节点的耦合,限制了 Pod 的使用。不过那些需要访问 Kubernetes 或 Docker 内部数据(配置文件和二进制库)的应用则需要使用 hostPath

4)、PV和PVC概念

PV 的全称是:PersistentVolume(持久化卷),是对底层共享存储的一种抽象,PV 由管理员进行创建和配置,它和具体的底层的共享存储技术的实现方式有关,比如 CephGlusterFSNFShostPath 等,都是通过插件机制完成与共享存储的对接。

静态PV

集群管理员创建一些PV。它们带有可供集群用户使用的实际存储细节。它们存在于Kubernetes API中,可用于消费。

动态PV

静态PV都不匹配PVC,集群可能会尝试动态为PVC创建卷。基于StorageClasses。

PVC 的全称是:PersistentVolumeClaim(持久化卷声明),PVC 是用户存储的一种声明,PVC 和 Pod 比较类似,Pod 消耗的是节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 和内存,而 PVC 可以请求特定的存储空间和访问模式。对于真正使用存储的用户不需要关心底层的存储实现细节,只需要直接使用 PVC 即可。

但是通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求,而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了,此外 StorageClass 还可以为我们自动生成 PV,免去了每次手动创建的麻烦。

绑定

master中的控制环路监视新的PVC,寻找匹配的PV(如果可能),并将它们绑定在一起。如果为新的PVC 动态调配PV,则该环路将始终将该PV 绑定到PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦PV 和PVC 绑定后PersistentVolumeClaim(PVC)绑定是排他性的,不管它们是如何绑定的。

PVC 跟PV 绑定是一对一的映射。

5)、PV访问模式

AccessModes(访问模式):用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:

  • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
  • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
  • ReadWriteMany(RWX):读写权限,可以被多个节点挂载

6)、PV回收策略

其中有一项 RECLAIM POLICY 的配置,同样我们可以通过 PV 的 persistentVolumeReclaimPolicy(回收策略)属性来进行配置,目前 PV 支持的策略有三种:

  • Retain(保留):保留数据,需要管理员手工清理数据
  • Recycle(回收):清除 PV 中的数据,效果相当于执行 rm -rf /thevoluem/*
  • Delete(删除):与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务,比如 ASW EBS。

不过需要注意的是,目前只有 NFSHostPath 两种类型支持回收策略,当然一般来说还是设置为 Retain 这种策略保险一点。

7)、PV状态

关于 PV 的状态,实际上描述的是 PV 的生命周期的某个阶段,一个 PV 的生命周期中,可能会处于4种不同的阶段:

  • Available(可用):表示可用状态,还未被任何 PVC 绑定
  • Bound(已绑定):表示 PVC 已经被 PVC 绑定
  • Released(已释放):PVC 被删除,但是资源还未被集群重新声明
  • Failed(失败): 表示该 PV 的自动回收失败

现在我们创建完成了 PV,如果我们需要使用这个 PV 的话,就需要创建一个对应的 PVC 来和他进行绑定了,就类似于我们的服务是通过 Pod 来运行的,而不是 Node,只是 Pod 跑在 Node 上而已。

8)、创建PVC的资源清单

1606929485610

八、kube-scheduler调度器

kube-scheduler 是 kubernetes 的核心组件之一,主要负责整个集群资源的调度功能,根据特定的调度算法和策略,将 Pod 调度到最优的工作节点上面去,从而更加合理、更加充分的利用集群的资源

img

1、调度流程

1606956766317

  • 首先,客户端通过 API Server 的 REST API 或者 kubectl 工具创建 Pod 资源
  • API Server 收到用户请求后,存储相关数据到 etcd 数据库中
  • 调度器监听 API Server 查看到还未被调度(bind)的 Pod 列表,循环遍历地为每个 Pod 尝试分配节点,这个分配过程就是我们上面提到的两个阶段:
    • 预选阶段(Predicates),过滤节点,调度器用一组规则过滤掉不符合要求的 Node 节点,比如 Pod 设置了资源的 request,那么可用资源比 Pod 需要的资源少的主机显然就会被过滤掉
    • 优选阶段(Priorities),为节点的优先级打分,将上一阶段过滤出来的 Node 列表进行打分,调度器会考虑一些整体的优化策略,比如把 Deployment 控制的多个 Pod 副本尽量分布到不同的主机上,使用最低负载的主机等等策略
  • 经过上面的阶段过滤后选择打分最高的 Node 节点和 Pod 进行 binding 操作,然后将结果存储到 etcd 中 最后被选择出来的 Node 节点对应的 kubelet 去执行创建 Pod 的相关操作(当然也是 watch APIServer 发现的)。

(首先过滤掉不满足条件的节点,这个过程称为predicate;然后对通过的节点按照优先级排序,这个是priority;最后从中选择优先级最高的节点。如果中间热河一步骤有错误,就直接返回报错。)

1)、预选过程

Predicates 阶段首先遍历全部节点,过滤掉不满足条件的节点,属于强制性规则,这一阶段输出的所有满足要求的节点将被记录并作为第二阶段的输入,如果所有的节点都不满足条件,那么 Pod 将会一直处于 Pending 状态,直到有节点满足条件,在这期间调度器会不断的重试

预选过程的调度策略算法

  • PodFitsResources:节点上剩余的资源(如 CPU 和内存)是否满足 Pod 请求的资源
  • PodFitsHost:如果 Pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配
  • PodFitsHostPorts:如果 Pod 中定义了 hostPort 属性,那么需要先检查这个指定端口是否已经被节点上其他服务占用了
  • PodMatchNodeSelector:检查 Node 的标签是否能匹配 Pod 的 nodeSelector 的标签值
  • NoDiskConflict:检查 Pod 请求的存储卷在 Node 上是否可用,若不存在冲突则通过检查
  • NoVolumeZoneConflict:检测 Pod 请求的 Volumes 在节点上是否可用,因为某些存储卷存在区域调度约束
  • CheckNodeDiskPressure:检查节点磁盘空间是否符合要求
  • CheckNodeMemoryPressure:检查节点内存是否够用
  • CheckNodeCondition:Node 可以上报其自身的状态,如磁盘、网络不可用,表明 kubelet 未准备壕运行 Pod,如果节点被设置成这种状态,那么 Pod 不会被调度到这个节点上
  • PodToleratesNodeTaints:检查 Pod 属性上的 tolerations 能否容忍节点的 taints 污点
  • CheckVolumeBinding:检查节点上已经绑定的和未绑定的 PVC 能否满足 Pod 的存储卷需求

如果在predicate过程中没有合适的节点,pod会一直在pending状态,不断重试调度,直到有节点满足条件,经过这个步骤,如果有多个节点满足条件,就继续priorities过程,按照优先级大小对节点排序

2)、优选过程

  • LeastRequestedPriority:通过计算 CPU 和内存的使用率来决定权重,使用率越低权重越高,当然正常肯定也是资源是使用率越低权重越高,能给别的 Pod 运行的可能性就越大
  • SelectorSpreadPriority:为了更好的高可用,对同属于一个 Deployment 或者 RC 下面的多个 Pod 副本,尽量调度到多个不同的节点上,当一个 Pod 被调度的时候,会先去查找该 Pod 对应的 controller,然后查看该 controller 下面的已存在的 Pod,运行 Pod 越少的节点权重越高
  • InterPodAffinityPriority:遍历 Pod 的亲和性条目,并将那些能够匹配到给定节点的条目的权重相加,结果值越大的节点得分越高
  • MostRequestedPriority:空闲资源比例越低的 Node 得分越高,这个调度策略将会把你的所有的工作负载(Pod)调度到尽量少的节点上
  • RequestedToCapacityRatioPriority:为 Node 上每个资源占用比例设定得分值,给资源打分函数在打分时使用
  • BalancedResourceAllocation:优选那些使得资源利用率更为均衡的节点。
  • NodePreferAvoidPodsPriority:这个策略将根据 Node 的注解信息中是否含有 scheduler.alpha.kubernetes.io/preferAvoidPods 来计算其优先级,使用这个策略可以将两个不同 Pod 运行在不同的 Node 上
  • NodeAffinityPriority:基于 Pod 属性中 PreferredDuringSchedulingIgnoredDuringExecution 来进行 Node 亲和性调度
  • TaintTolerationPriority:基于 Pod 中对每个 Node 上污点容忍程度进行优先级评估,这个策略能够调整待选 Node 的排名
  • ImageLocalityPriority:Node 上已经拥有 Pod 需要的容器镜像的 Node 会有较高的优先级
  • ServiceSpreadingPriority:这个调度策略的主要目的是确保将归属于同一个 Service 的 Pod 调度到不同的 Node 上,如果 Node 上没有归属于同一个 Service 的 Pod,这个策略更倾向于将 Pod 调度到这类 Node 上。最终的目的:即使在一个 Node 宕机之后 Service 也具有很强容灾能力。
  • CalculateAntiAffinityPriorityMap:这个策略主要是用来实现 Pod 反亲和的
  • EqualPriorityMap:将所有的 Node 设置成相同的权重为 1

每一个优先级函数会返回一个 0-10 的分数,分数越高表示节点越优,同时每一个函数也会对应一个表示权重的值。最终主机的得分用以下公式计算得出:

1
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (weightn * priorityFuncn

2、自定义调度器

通过 spec:schedulername 参数指定调度器的名字,可以为pod选择某个调度器进行调度

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: annotation-second-scheduler
labels:
name: multischeduler-example
spec:
schedulername: my-scheduler
containers:
- name: pod-with-second-annotation-container
image: gcr.io/google_containers/pause:2.0

3、节点调度

1)、nodeSelector

通过节点标签来调度

查看node的标签:

1
$ kubectl get nodes --show-labels

给节点打标签:

1
$ kubectl label nodes ydzs-node2 com=youdianzhishi

将pod调度到选定节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
labels:
app: busybox-pod
name: test-busybox
spec:
containers:
- command:
- sleep
- "3600"
image: busybox
imagePullPolicy: Always
name: test-busybox
nodeSelector:
com: youdianzhishi

nodeSelector属于强制性,如果目标节点没有可用的资源。Pod会一直处于Pending状态

2)、亲和性和反亲和性

实际需求来控制 Pod 的调度,这就需要用到 nodeAffinity(节点亲和性)podAffinity(pod 亲和性) 以及 podAntiAffinity(pod 反亲和性)

亲和性调度分为软策略硬策略两种方式

  • 软策略就是如果现在没有满足调度要求的节点的话,Pod 就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有的话也无所谓
  • 硬策略就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然就不干了

节点亲和性

控制 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
35
36
37
38
39
apiVersion: apps/v1
kind: Deployment
metadata:
name: node-affinity
labels:
app: node-affinity
spec:
replicas: 8
selector:
matchLabels:
app: node-affinity
template:
metadata:
labels:
app: node-affinity
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
name: nginxweb
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- ydzs-node3
preferredDuringSchedulingIgnoredDuringExecution: # 软策略
- weight: 1
preference:
matchExpressions:
- key: com
operator: In
values:
- youdianzhishi

上面这个 Pod 首先是要求不能运行在 ydzs-node3 这个节点上,如果有个节点满足 com=youdianzhishi 的话就优先调度到这个节点上。

现在 Kubernetes 提供的操作符有下面的几种:

  • In:label 的值在某个列表中
  • NotIn:label 的值不在某个列表中
  • Gt:label 的值大于某个值
  • Lt:label 的值小于某个值
  • Exists:某个 label 存在
  • DoesNotExist:某个 label 不存在

Pod亲和性

Pod 亲和性(podAffinity)主要解决 Pod 可以和哪些 Pod 部署在同一个拓扑域中的问题(其中拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的 cluster、zone 等等)

Pod 反亲和性(podAntiAffinity)主要是解决 Pod 不能和哪些 Pod 部署在同一个拓扑域中的问题

它们都是处理的 Pod 与 Pod 之间的关系,比如一个 Pod 在一个节点上了,那么我这个也得在这个节点,或者你这个 Pod 在节点上了,那么我就不想和你待在同一个节点上。

污点

对于 nodeAffinity 无论是硬策略还是软策略方式,都是调度 Pod 到预期节点上,而污点(Taints)恰好与之相反,如果一个节点标记为 污点(Taints) ,除非 Pod 也被标识为可以容忍污点节点,否则该污点(Taints) 节点不会被调度 Pod。

污点的组成:

1
key=value:effect

每个污点有一个key和value作为污点的标签,其中value可以为空,effect描述污点的作用。当前taint effect支持三个选项:

  • NoSchedule:表示k8s将不会把Pod调度到具有该污点的Node上
  • PreferNoSchedule:表示k8s将尽量避免把Pod调度到具有该污点的Node上
  • NoExecute:表示k8s将不会将Pod调度到具有该污点的Node上,同时会将Node上已经存在的Pod驱逐出去

污点的设置、查看和去除

1
2
3
4
5
6
7
8
#设置污点
kubectl taint nodes node1 key1=value:NoSchedule

#节点说明中,查找Taints字段
kubectl describe pod pod-name

#去除污点
kubectl taint nodes node1 key1:NoSchedule-

容忍

在Pod上设置容忍,可以容忍污点的存在,可以被调度到存在污点的Node上

1
2
3
4
tolerations:
- key: "node-role.kubernetes.io/master"
operator: "Exists"
effect: "NoSchedule"
  • 其中的 key、value、effect 与 Node 的 Taint 设置需保持一致

  • 如果 operator 的值是 Exists,则 value 属性可省略

  • 如果 operator 的值是 Equal,则表示其 key 与 value 之间的关系是 equal(等于)

  • 如果不指定 operator 属性,则默认值为 Equal

另外,还有两个特殊值:

  • 空的 key 如果再配合 Exists 就能匹配所有的 key 与 value,也就是是能容忍所有节点的所有 Taints
  • 空的 effect 匹配所有的 effect

九、安全

1、机制说明

API Server是集群内部各个组件通信的中介,也是外部控制的入口。所以Kubernetes的安全机制基本就是围绕保护API Server来设计的

Kubernetes使用了认证(Authentication)、鉴权(Authorization)、准入控制(Admission Control)三步来保证API Server的安全。

1607094107893

2、Authentication(认证)

  • HTTP Token 认证:通过一个Token来识别合法用户
  • HTTP Base认证:通过 用户名+密码 的方式认证
  • 最严格的HTTPS证书认证:基于CA根证书签名的客户端身份认证方式

1)、HTTPS证书认证

1607094395494

2)、需要认证的节点

两种类型

  • Kubenetes组件对API Server的访问:kubectl、Controller Manager、Scheduler、kubelete、kube-proxy
  • Kubernetes管理的Pod对容器的访问:Pod(dashborad也是也Pod形式运行)

安全性说明

  • Controller Manager、Scheduler与API Server在同一台机器,所以直接使用API Server的非安全端口访问,–insecure-bind-address=127.0.0.1
  • kubectl、kubelet、kube-proxy访问API Server就都需要证书进行HTTPS双向认证

证书颁发

  • 手动签发:通过k8s集群的跟ca进行签发HTTPS证书
  • 自动签发:kubelet首次访问API Server时,使用token做认证,通过后,Controller Manager会为kubelet生成一个证书,以后的访问都是用证书做认证了

3)、kubeconfig

kubeconfig文件包含集群参数(CA证书、API Server地址),客户端参数(上面生成的证书和私钥),集群context信息(集群名称、用户名)。Kubenetes组件通过启动时指定不同的kubeconfig文件可以

4)、ServiceAccount

Pod中的容器访问API Server。因为Pod的创建、销毁是动态的,所以要为它手动生成证书就不可行了。Kubenetes使用了Service Account解决Pod访问API Server的认证问题

5)、Secret与SA的关系

Kubernetes设计了一种资源对象叫做Secret,分为两类,一种是用于ServiceAccount的service-account-token,另一种是用于保存用户自定义保密信息的Opaque。ServiceAccount中用到包含三个部分:Token、ca.crt、namespace

  • token是使用API Server私钥签名的JWT。用于访问API Server时,Server端认证
  • ca.crt,根证书。用于Client端验证API Server发送的证书
  • namespace,表示这个service-account-token的作用域名空间

默认情况下,每个namespace都会有一个ServiceAccount,如果Pod在创建时没有指定ServiceAccount,就会使用Pod所属的namespace的ServiceAccount。

1607097229100

3、Authorization(鉴权)

上面认证过程,只是确认通信的双方都确认了对方是可信的,可以相互通信。而鉴权是确定请求方有哪些资源的权限。API Server目前支持以下几种授权策略(通过API Server的启动参数“–anthorization”设置)

AlwaysDeny:表示拒绝所有的请求,一般用于测试

AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略

(这两种现在不使用)

ABAC(Attribute-Based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制

Webbook:通过调用外部REST服务对用户进行授权

RBAC(Role-Based Access Control):基于角色的访问控制,现行默认规则

1)、RBAC授权模式

RABC:基于角色的权限控制

RBAC优势

  • 对集群中的资源和非资源均拥有完整的覆盖
  • 整个RBAC完全由几个API对象完成,同其他API对象一样,可以拥有kubectl或API进行操作
  • 可以在运行时进行调整,无需重启API Server

需要了解的概念

  • Rule:规则,规则是一组属于不同 API Group 资源上的一组操作的集合
  • RoleClusterRole:角色和集群角色,这两个对象都包含上面的 Rules 元素,二者的区别在于,在 Role 中,定义的规则只适用于单个命名空间,也就是和 namespace 关联的,而 ClusterRole 是集群范围内的,因此定义的规则不受命名空间的约束。另外 Role 和 ClusterRole 在Kubernetes 中都被定义为集群内部的 API 资源,和我们前面学习过的 Pod、Deployment 这些对象类似,都是我们集群的资源对象,所以同样的可以使用 YAML 文件来描述,用 kubectl 工具来管理
  • Subject:主题,对应集群中尝试操作的对象,集群中定义了3种类型的主题资源:
    • User Account:用户,这是有外部独立服务进行管理的,管理员进行私钥的分配,用户可以使用 KeyStone 或者 Goolge 帐号,甚至一个用户名和密码的文件列表也可以。对于用户的管理集群内部没有一个关联的资源对象,所以用户不能通过集群内部的 API 来进行管理
    • Group:组,这是用来关联多个账户的,集群中有一些默认创建的组,比如 cluster-admin
    • Service Account:服务帐号,通过 Kubernetes API 来管理的一些用户帐号,和 namespace 进行关联的,适用于集群内部运行的应用程序,需要通过 API 来完成权限认证,所以在集群内部进行权限操作,我们都需要使用到 ServiceAccount,这也是我们这节课的重点
  • RoleBindingClusterRoleBinding:角色绑定和集群角色绑定,简单来说就是把声明的 Subject 和我们的 Role 进行绑定的过程(给某个用户绑定上操作的权限),二者的区别也是作用范围的区别:RoleBinding 只会影响到当前 namespace 下面的资源操作权限,而 ClusterRoleBinding 会影响到所有的 namespace。

2)、Role和ClusterRole

  • Role表示一组规则权限,权限指挥增加(累加权限),不存在一个资源一开始就有很多权限而通过RBAC对其减少的操作。Role可以定义在一个namespace中,如果想要跨namespace则可以创建ClusterRole
  • ClusterRole具有和Role相同的权限角色控制能力,不同的时ClusterRole是集群级别的,ClusterRole可以用于:
  1. 集群级别的资源控制(例如node访问权限)
  2. 非资源型endpoints(例如/healthz访问)
  3. 所有命名空间资源控制(例如pods)

3)、RoleBinding和ClusterRoleBinding

RoleBinding可以将角色中定义的权限授予用户或用户组,RoleBindbing包含一组权限列表(subjects),权限列表中包含有不同形式的代收与权限资源类型(users,groups,or service accounts);RoleBinding同样包含对被Bind的Role引用;RoleBinding适用于某个命名空间授权,而ClusterRoleBinding适用于集群范围内的授权

RoleBinding同样可以引用ClusterRole来对当前namespace内用户、用户组或ServiceAccount进行授权,这种操作允许集群管理员在整个集群内定义一些通用的ClusterRole,然后再不同的namespace中使用RoleBinding来引用

4)、只能访问某个namespace的普通用户

1
2
username: cnych
group: youdianzhishi

1、创建用户凭证

使用OpenSSL证书来创建一个User

给用户cnych创建一个私钥,命名为cnych.key

1
$ openssl genrsa -out cnych.key 2048

使用我们刚刚创建的私钥创建一个证书签名请求文件:cnych.csr,要注意需要确保在-subj参数中指定用户名和组(CN表示用户名,O表示组)

1
$ openssl req -new -key cnych.key -out cnych.csr -subj "/CN=cnych/O=youdianzhishi"

通过CA证书(在 /etc/kubernetes/pki/ 下的ca.crt和ca.key)批准上面的证书请求,生成最终的证书文件。设置有效期500天。

1
2
3
4
$ openssl x509 -req -in cnych.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out cnych.crt -days 500
Signature ok
subject=/CN=cnych/O=youdianzhishi
Getting CA Private Key

现在我们可以使用刚刚创建的证书文件和私钥文件在集群中创建新的凭证和上下文(Context):

1
2
$ kubectl config set-credentials cnych --client-certificate=cnych.crt --client-key=cnych.key
User "cnych" set.

我们可以看到一个用户 cnych 创建了,然后为这个用户设置新的 Context,我们这里指定特定的一个 namespace:

1
2
$ kubectl config set-context cnych-context --cluster=kubernetes --namespace=kube-system --user=cnych
Context "cnych-context" created.

2、创建角色

允许用户操作Deployment,Pod,ReplicaSets

1
2
3
4
5
6
7
8
9
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cnych-role
namespace: kube-system
rules:
- apiGroups: ["", "apps"]
resources: ["deployments", "replicasets", "pods"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # 也可以使用['*']

其中 Pod 属于 core 这个 API Group,在 YAML 中用空字符就可以,而 Deployment 和 ReplicaSet 现在都属于 apps 这个 API Group(如果不知道则可以用 kubectl explain 命令查看),所以 rules 下面的 apiGroups 就综合了这几个资源的 API Group:[“”, “apps”],其中verbs 就是我们上面提到的可以对这些资源对象执行的操作,我们这里需要所有的操作方法,所以我们也可以使用[‘*’]来代替。然后直接创建这个 Role

3、创建角色权限绑定

在 kube-system 这个命名空间下面将上面的 cnych-role 角色和用户 cnych 进行绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cnych-rolebinding
namespace: kube-system
subjects:
- kind: User
name: cnych
apiGroup: ""
roleRef:
kind: Role
name: cnych-role
apiGroup: rbac.authorization.k8s.io # 留空字符串也可以,则使用当前的apiGroup

4、准入控制

准入控制是API Server的插件集合,通过添加不同的插件,实现额外的准入控制规则。甚至于API Server的一些主要的功能都需要通过Admission Controllers实现,比如ServiceAccount。

列举一些插件的功能:

NamespaceLifecycle:防止在不存在的namespace上创建对象,防止删除系统预置namespace,删除namespace时,连带删除它的所有资源对象;

LimitRanger:确保请求的资源不会超过资源所在Namespace的LimitRange的限制;

ServiceAccount:实现自动化添加ServiceAccount;

ResourceQuota:确保请求的资源不会超过资源的ResourceQuota限制

十、Helm

相当于Linux里的yun

1、用途

做为 Kubernetes 的一个包管理工具,Helm具有如下功能:

  • 创建新的 chart
  • chart 打包成 tgz 格式
  • 上传 chart 到 chart 仓库或从仓库中下载 chart
  • Kubernetes集群中安装或卸载 chart
  • 管理用Helm安装的 chart 的发布周期

2、重要概念

Helm 有三个重要概念:

  • chart:包含了创建Kubernetes的一个应用实例的必要信息
  • config:包含了应用发布配置信息
  • release:是一个 chart 及其配置的一个运行实例

3、Helm组件

Helm 有以下两个组成部分:

Helm Client 是用户命令行工具,其主要负责如下:

  • 本地 chart 开发
  • 仓库管理
  • 与 Tiller sever 交互
  • 发送预安装的 chart
  • 查询 release 信息
  • 要求升级或卸载已存在的 release

Tiller Server是一个部署在Kubernetes集群内部的 server,其与 Helm client、Kubernetes API server 进行交互。Tiller server 主要负责如下:

  • 监听来自 Helm client 的请求
  • 通过 chart 及其配置构建一次发布
  • 安装 chart 到Kubernetes集群,并跟踪随后的发布
  • 通过与Kubernetes交互升级或卸载 chart
  • 简单的说,client 管理 charts,而 server 管理发布 release

4、Helm命令

1、安装应用:helm install 可以指定命名空间、yaml文件

2、查看已安装的release:helm ls

3、删除release:helm uninstall release名字

uninstall 命令会从 Kubernetes 中删除 release,也会删除与 release 相关的所有 Kubernetes 资源以及 release 历史记录。也可以在删除的时候使用 --keep-history 参数,则会保留 release 的历史记录,可以获取该 release 的状态就是 UNINSTALLED,而不是找不到 release了

4、helm show values release名 查看chart包所有可配置的参数选项

5、helm install 命令可以从多个源进行安装:

  • chart 仓库(类似于上面我们提到的)
  • 本地 chart 压缩包(helm install foo-0.1.1.tgz)
  • 本地解压缩的 chart 目录(helm install foo path/to/foo)
  • 在线的 URL(helm install fool https://example.com/charts/foo-1.2.3.tgz)

6、升级和回滚

升级:helm upgrade

回滚:helm rollback 根据RESISION

十一、kubeadm部署高可用集群

k8s集群的高可用实际上是api server的高可用

2种方案:

1607501612188

1、堆叠方案: etcd服务和控制平面被部署在同样的节点中,对基础设施的要求较低,对故障的应对能力也较低

1607501638725

2、 外置etcd方案:etcd和控制平面被分离,需要更多的硬件,也有更好的保障能力

具体过程

1、系统设置(/etc/hosts,依赖环境,关闭防火墙,交换分区等)

2、所有节点安装Docker

3、安装必要工具:kubeadm(所有节点),kubelet(所有节点),kubectl(master节点)

4、安装LVS负载均衡器与keeplived(或haproxy)高可用软件

5、第一台kubeadm进行初始化,生成两个token令牌,一个是master的,一个是slave的

token 过期之后,如何加入集群

1
2
3
4
5
6
7
8
9
10
11
12
# 创建token
$ kubeadm token create
ll3wpn.pct6tlq66lis3uhk

# 查看token
$ kubeadm token list
TOKEN TTL EXPIRES USAGES DESCRIPTION EXTRA GROUPS
ll3wpn.pct6tlq66lis3uhk 23h 2020-01-17T14:42:50+08:00 authentication,signing <none> system:bootstrappers:kubeadm:default-node-token

# 获取 CA 证书 sha256 编码 hash 值
$ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
b5e5c1a000284781677336b00e7345838195ca78af21bddd9defad799243752b

十二、更新10年可用证书

kubeadm安装的根证书位于Master节点:/etc/kubernetes/pki/ca.crt

1、查看证书是否过期

1
$ kubeadm alpha certs check-expiration

2、部署go语言环境

3、根据版本去Github kubernetes release下载对应的源码并解压

4、修改 cmd/kubeadm/app/util/pkiutil/pki_helpers.go 文件

修改NotAfter NotAfter: now.Add(duration365d * 10).UTC(),

4、重新编译 make WHAT=cmd/kubeadm GOFLAGS=-v

5、更新kubeadm

1
2
3
cp /usr/bin/kubeadm /usr/bin/kubeadm.old
cp /root/kubeadm-new /usr/bin/kubeadm
chmod a+x /usr/bin/kubeadm

6、更新各节点证书至Master节点

1
2
3
4
cp-r /etc/kubernetes/pki /etc/kubernetes/pki.old
cd /etc/kubernetes/pki
kubeadm alpha certs renew all --config=/root/kubeadm-config.yaml
openssl x509 -in apiserver.crt -text-noout | grep Not

7、HA集群其余master节点证书更新

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
masterNode="192.168.66.20 192.168.66.21"
#for host in ${masterNode}; do
# scp /etc/kubernetes/pki/{ca.crt,ca.key,sa.key,sa.pub,front-proxy-ca.crt,front-proxy-ca.key}"${USER}"@$host:/etc/kubernetes/pki/
# scp /etc/kubernetes/pki/etcd/{ca.crt,ca.key} "root"@$host:/etc/kubernetes/pki/etcd
# scp /etc/kubernetes/admin.conf "root"@$host:/etc/kubernetes/
#done
for host in${CONTROL_PLANE_IPS}; do
scp /etc/kubernetes/pki/{ca.crt,ca.key,sa.key,sa.pub,front-proxy-ca.crt,front-proxy-ca.key}"${USER}"@$host:/root/pki/
scp /etc/kubernetes/pki/etcd/{ca.crt,ca.key} "root"@$host:/root/etcd
scp /etc/kubernetes/admin.conf "root"@$host:/root/kubernetes/
done

故障排查

https://bbs.huaweicloud.com/blogs/192473 node节点网络不通