本文内容主要来源于极客时间《Kubernetes 入门实战课》,夹杂了自己在学习过程中的思考和相关记录
1.容器资源配额Resource:回想容器的萨达隔离技术:namespace创建独立的进程空间,chroot实现独立的文件系统,cgroup实现CPU、内存等资源的管控,但是如何管控呢?只要在Pod容器的描述部分添加一个新字段resources即可
apiVersion: v1
kind: Pod
metadata:
name: ngx-pod-resources
spec:
containers:
- image: nginx:alpine
name: ngx
resources:
requests:
cpu: 10m
memory: 100Mi
limits:
cpu: 20m
memory: 200Mi
几个注意点:
requests
:代表申请,这个是告诉k8s在创建Pod时必须要满足的资源limits
:是容器使用资源的上限,不能超过设定值,否则可能会被系统强制停止运行内存的写法和磁盘容量一样,使用 Ki
、Mi
、Gi
CPU的最小使用单位是0.001,对应的单位是m;如果不带任何单位,就代表几个CPU,可以是小数。 如果不写 resources
字段,代表“既没有上限、也没有下限”,k8s不管CPU和内存是否足够,可以把Pod调度到任意的节点上,后续Pod运营时有额可以无限制的使用CPU如果Pod申请的资源系统没法满足,该Pod会创建成功但是实际无法被调度运行。
2.容器状态探针Probe:有时候Pod看起来正常,但是不一定能提供服务。如何更深入的探测Pod的状态呢, 就需要探针,k8s定义了三种探针,Kubernetes在启动容器后就会不断地调用探针来检查容器的状态:
Startup探针:启动探针,用来检查是否启动成功,适合那些有大量初始化工作要做、启动很慢的应用。如果Startup探针检测失败,就会认为服务没有正常启动,就会尝试反复重启,此时Liveness探针和Readiniss探针不会被启动。 Liveness探针:存活探针,用来检查应用是否正常运行、是否还活着,是否存在死锁、死循环等。如果Liveness探针失败,k8s认为容器发生了异常,会重启容器。 Readiness探针:就绪探针,用来检查应用是否可以接收流量、是否能对外提供服务。如果Readiness探针失败,k8s认为该服务内部错误、无法正常提供服务,会把容器从Service对象的负载均衡集合中排除,不给他分配流量。
3.探针的使用:
三种探针的配置方式都是一样的, 几个关键字段如下:
periodSeconds
:探针探测的时间间隔,默认10s探测一次timeoutSeconds
:探针探测的超时时间,如果超时就认为探测失败,默认1ssuccessThreshold
:连续探测多少次才认为是成功,对于startupProbe和livenessProbe来说只能是1failureThreshold
:连续探测失败几次才认为是真正发生了异常,默认3次
而k8s支持3种探测方式,Shell、TCP Socket、HTTP GET, 需要在探针明确配置:
exec
:执行一个linux命令,如cp、cat等tcpSocket
:使用TCP协议连接指定端口httpGet
:连接端口并发送HTTP Get请求
4.探针实战:
使用一个nginx的Pod来演示探针的使用。
apiVersion: v1
kind: ConfigMap
metadata:
name: ngx-conf
data:
default.conf: |
server {
listen 80;
location = /ready {
return 200 'I am ready';
}
}
---
apiVersion: v1
kind: Pod
metadata:
name: ngx-pod-probe
spec:
volumes:
- name: ngx-conf-vol
configMap:
name: ngx-conf
containers:
- image: nginx:alpine
name: ngx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: ngx-conf-vol
startupProbe:
periodSeconds: 1
exec:
command: ["cat", "/var/run/nginx.pid"]
livenessProbe:
periodSeconds: 10
tcpSocket:
port: 80
readinessProbe:
periodSeconds: 5
httpGet:
path: /ready
port: 80
定义了一个cm对象,启动80端口,并定义了 /ready
路径返回状态码StartupProbe使用shell方式检测ngx存在磁盘上的进程号文件,如果存在就是启动成功 LivenessProbe检查端口 ReadinessProbe使用HTTP GET方式访问 /ready
创建Pod并检查状态:
5.名字空间:
名字空间并不是一个实体对象, 只是一个逻辑上的概念。它把集群切分成一个个彼此独立的区域,然后我们把对象放到这些区域里,就实现了类似容器技术里 namespace 的隔离效果,应用只能在自己的名字空间里分配资源和运行,不会干扰到其他名字空间里的应用。这是k8s面临大规模集群、海量节点时的一种现实考虑,减少资源争抢和冲突的概率。 多团队、多项目共用Kubernates时最好为每一类用户创建出只属于它自己的“工作空间”。如果把 Kubernetes 比做一个大牧场的话,API 对象就是里面的鸡鸭牛羊,而名字空间就是圈养它们的围栏,有了各自合适的活动区域,就能更有效、更安全地利用 Kubernetes。 创建和查看名字空间
kubectl create ns test-ns
kubectl get ns
要想把一个对象放入特定的名字空间,需要在 metadata
里添加一个namespace
字段
apiVersion: v1
kind: Pod
metadata:
name: ngx
namespace: test-ns
创建对象之后,相关的一些查询资源、查询状态的kubectl指令要指明名字空间
kubectl get pod -n test-ns
非常重要:名字空间中的对象都是从属于名字空间的, 一旦名字空间被删除,里面的对象都会被删除
6.从名字空间的维度限制资源配额:
可以从名字空间的维度,限定资源配额。 即这个空间所有对象使用资源的上限,这里不仅包含CPU和内存,还可以现实各种对象的数量 名字空间的资源配额有个专门的对象,叫做ResourceQuota,简称quota
7.资源配额的YAML描述:
如下创建一个namespace并创建一个资源配额
apiVersion: v1
kind: Namespace
metadata:
name: dev-ns
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev-qt
namespace: dev-ns
spec:
hard:
requests.cpu: 10
requests.memory: 10Gi
limits.cpu: 10
limits.memory: 20Gi
requests.storage: 100Gi
persistentvolumeclaims: 100
pods: 100
configmaps: 100
secrets: 100
services: 10
count/jobs.batch: 1
count/cronjobs.batch: 1
count/deployments.apps: 1
其中:
quota对象使用非常灵活,可以限制整个名字空间的配额(硬性全局限制,使用 spec.hard
字段),也可以只限制某些资源(scopeSelector)。CPU和内存的配额,使用limit、request, 跟容器资源限制一样的 可以限制PVC的存储总量和个数 核心资源的个数,直接可以根据名字限制 其他API对象个数,使用 count/name.group
方式
8.如何使用资源配额:
创建&查询&查询剩余
kubectl apply -f quota-ns.yml
kubectl get quota -n dev-ns
kubectl describe quota -n dev-ns
确认效果:上述的quota对象只允许一个Job对象,我们看看效果:
kubectl create job echo1 -n dev-ns --image=busybox -- echo hello
kubectl create job echo2 -n dev-ns --image=busybox -- echo hello
9.默认资源配额:
为什么需要默认资源配额:当我们给名字空间加上资源配额之后,它会有一个约束,要求所有的Pod都必须用resources声明资源需求,否则就无法创建, 原因是:如果Pod未声明资源配额,代表可以无限制的使用CPU和内存,这个和qouta对象的约束冲突了,因此只能拒绝这样的对象创建:
如果大大小小的每个Pod都去声明资源配额,显然很繁琐。因此需要一个默认值,如果Pod没显式指定、那就用这个默认资源配额,所以LimitRange对象应运而生(简称limit):
LimitRange对象的Yaml定义参考如下,其中:
spec.limits
描述资源限制;type
指明要限制的对象,可以是Container、Pod、PVC;default
对应容器中的resources.limites
,是资源的上限,只使用于Container类型;defaultRequest
对应容器中的resources.requests
,只使用于Container类型;max
、min
指明对象能使用的资源的最大最小值
apiVersion: v1
kind: LimitRange
metadata:
name: dev-limits
namespace: dev-ns
spec:
limits:
- type: Container
defaultRequest:
cpu: 200m
memory: 50Mi
default:
cpu: 500m
memory: 100Mi
- type: Pod
max:
cpu: 800m
memory: 200Mi
创建limit对象并查看
查看效果:默认给未显式指定资源约束的对象加上了资源约束
10.集群的可观测性之Metrics Server:
如何像linux的top命令一样,查询每个Pod的资源占用? 我们需要安装一个Metrics Serve插件,它是一个专门从所有节点kubelet获取指标(metrics)、并把这些信息交给apiserver存储的工具, 示意图如下:
安装方法参考:https://github.com/chronolaw/k8s_study/tree/master/metrics 安装完成之后可以在“kube-system”命名空间看到对应的Pod
安装完成并等待运行一段时间后,就可以使用top命令查看node和pod的资源占用了
kubectl top node
kubectl top pod -n kube-system
11.水平自动伸缩功能HPA:
前面讲过,可以使用 kubectl scale
手动的增减Deployment部署的Pod数量即说平方向的扩容缩容,如何基于实际的资源占用,完成自动的水平方向伸缩呢。API对象HorizontalPodAutoscaler简称HPA应运而生HPA的YAML定义非常简单,通过maxReplicas、minReplicas设置Pod数量的上下限,targetCPUUtilizationPercentage指明要保持的CPU使用率目标(对于则扩容、少于则缩容),scaleTargetRef类似于selector、指明要控制的对象
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: ngx-hpa
spec:
maxReplicas: 10
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ngx-hpa-dep
targetCPUUtilizationPercentage: 5
12.HPA实战:
定义如下的Deployment、svc、hpa对象
apiVersion: apps/v1
kind: Deployment
metadata:
name: ngx-hpa-dep
spec:
replicas: 1
selector:
matchLabels:
app: ngx-hpa-dep
template:
metadata:
labels:
app: ngx-hpa-dep
spec:
containers:
- image: nginx:alpine
name: nginx
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 10Mi
limits:
cpu: 100m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: ngx-hpa-svc
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: ngx-hpa-dep
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: ngx-hpa
spec:
maxReplicas: 10
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ngx-hpa-dep
targetCPUUtilizationPercentage: 5
创建完毕之后不久,副本数提升至2个
运行一个httpd的测试Pod,使用ab测试工具加压
kubectl run test -it --image=httpd:alpine -- sh
ab -c 10 -t 60 -n 1000000 'http://ngx-hpa-svc/'
此时通过kubectl get hpa
产看HPA的状态,可以明显看到扩容缩容的过程
13.更全面的监控系统Prometheus:
Prometheus是云原生监控领域的“事实标准”
Prometheus核心是Server,一个时序数据库TSDB存储监控数据、Retrieval组件使用拉取Pull的方式从各个组件收集数据、通过HTTP Server把这些数据吐给外界
除了Server以外还有3个重要的组件: Push Gateway用以适配特殊的监控目标,把Pull转化为Push;Alermanager是告警中心,可以设定规则、发现问题就通过邮件等方式告警;Grafana是图形化界面
安装方法参考https://github.com/chronolaw/k8s_study/blob/master/prom/setup.sh
查看对外服务端口
如上所示Prometheus自带的Web界面端口是32316,直接登录,Web界面上有一个查询框,可以使用PromQL来查询指标,生成可视化图表。这个可视化的效果有点弱,但是数据非常灵活,可以用来调试
我们看看Grafana是图形化界面。默认用户名和密码都是admin。它的内部已经预置了很多强大的仪表盘,可以在左侧菜单栏的“Dashboards - Browse”里任意挑选一个(确实强大)
14.再看容器和Pod的网络模型:
容器的网络模型:Docker有三种网络模式,null、host、bridge。最常用的是bridge。 Docker会创建一个名称为docker0的网桥,每个容器都会创建一个虚拟网卡对(veth对),插在网桥上,这样容器之间就可以互联互通
k8s网络模型, 简而言之就是IP-per-Pod:集群中每个Pod都会有一个唯一的IP地址,Pod内的所有容器共享这个IP,集群中所有的Pod都属于同一网段,Pod直接可以基于IP访问任意的另外一个Pod
15.CNI:
CNI是对于k8s网络模型的标准,CNI(Container Networking Interface。
根据实现技术大致分为3种:
Overlay: 即覆盖,是在真实底层网络之上构建了一个逻辑网络,把原始的Pod网络数据封包、再通过下层网络发送出去、到了目的地再解包。它对底层网络要求低、可适应性强,缺点是有额外的传输成本、性能比较低 Route: 利用系统内置的路由功能来实现Pod跨主机通讯,性能高,但是依赖底层网络 Underlay: 直接利用底层网络来通讯,所有Pod和宿主机都在一个网络里, 性能最好,但是不够灵活、对底层网络和硬件的依赖性最高
目前业界比较流行的三款插件:
Flannel:最早是一种Overlay 模式的网络插件,使用UDP和VXLAN,后来又支持了Route模式。Flannel 简单易用,但是性能不太好 Calico:是一种Route模式的插件,使用 BGP 协议(Border Gateway Protocol)来维护路由信息,性能要比 Flannel 好,而且支持多种网络策略,具备数据加密、安全隔离、流量整形等功能。 Cilium:同时支持 Overlay 模式和 Route 模式,它的特点是深度使用了 Linux eBPF 技术,在内核层次操作网络数据,所以性能很高,可以灵活实现各种功能。