controller控制器
pod和controller的关系
pod是通过controller实现应用的运维比如伸缩,滚动升级等等
node通过labels和master的labels建立联系,然后master进行管理
声明式定义: apply
命令式定义:create
什么是controller
是实际存在的,管理pod(pod是虚拟的),在集群上管理和运行容器的对象
所有控制器种类(内置):
ReplicationController和ReplicaSet
(RC)用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来替代;而如果异常多出来的容器也会自动回收
在新版本的 Kubernetes 中建议使用 ReplicaSet 来取代 ReplicationController 。 ReplicaSet 支持集合式的 selector和标签选择器
Deployment
为 Pod 和 ReplicaSet 提供了一个声明式定义 (declarative) 方法,用来替代以前的ReplicationController 来方便的管理应用。典型的应用场景包括
- 定义 Deployment 来创建 Pod 和 ReplicaSet
- 滚动升级和回滚应用
- 扩容和缩容
- 暂停和继续 Deployment
DaemonSet
确保全部(或者一些)Node 上运行一个 Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod
Job
Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束
CronJob
管理基于时间的 Job,即:**
- 在给定时间点只运行一次
- 周期性地在给定时间点运行
StatefulSet
StatefulSet 作为 Controller 为 Pod 提供唯一的标识。它可以保证部署和 scale 的顺序.StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括
- 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
- 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
- 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
- 有序收缩,有序删除(即从N-1到0
HPA
应用的资源使用率通常都有高峰和低谷的时候,如何削峰填谷,提高集群的整体资源利用率,让 service 中的Pod个数自动调整呢?这就有赖于 HPA (Horizontal Pod Autoscaling)了,顾名思义,使 Pod 水平自动缩放
控制器详细介绍
RC 控制器:
rc.yaml
apiVersion: v1
kind: ReplicationController
metadata:
name: frontend
spec:
replicas: 3
selector:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: php-redis
image: wangyanglinux/myapp:v1
env:
- name: GET_HOSTS_FROM
value: dns
name: zhangsan
value: "123"
ports:
- containerPort: 80
selector下的app应该是metadata下的labels下的app的子集,要求上匹配下
注入环境变量:
env:
- key1: value1
key2: value2
kubectl create -f rc.yaml
删除后依然管控,保持原先的数量
控制器是通过标签来控制资源的,如果修改了标签,控制器将无法控制
kubectl label pod frontend-bqvs9 app=myapp --overwrite
一般标签不能乱改不建议这样操作
kubectl get pods --show-labels
被修改了标签的pod不在控制器管辖范围之内,所以重新建了一个pod
RS控制器:
kubectl explain rs.spec.selector
添加了更多的匹配机制(matchLabels && matchExpressions【更是支持了运算】),可以取代RC
启动一个RS:
matchLabels.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
kubectl create -f matchLabels
使用更灵活的选择器案例:selector.matchExpressions
rs 在标签选择器上,除了可以定义键值对的选择形式,还支持 matchExpressions 字段,可以提供多种选择。
目前支持的操作包括:
- In:label 的值在某个列表中
- NotIn:label 的值不在某个列表中
- Exists:某个 label 存在
- DoesNotExist:某个 label 不存在
例1 Exists判断:
matchExpressionsExists.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-demo
spec:
selector:
matchExpressions:
- key: app
operator: Exists
template:
metadata:
labels:
app: spring-k8s
spec:
containers:
- name: rs-c1
image: wangyanglinux/myapp:v1
ports:
- containerPort: 80
kubectl create -f matchExpressionsExists.yaml
学习一条扩容命令:
kubectl scale rs/foo --replicas=3
例2:in判断:
matchExpressionsIn.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: rs-demo
spec:
selector:
matchExpressions:
- key: app
operator: In
values:
- spring-k8s
- hahahah
template:
metadata:
labels:
app: sg-k8s
spec:
containers:
- name: rs-c1
image: wangyanglinux/myapp:v1
ports:
- containerPort: 80
修改:
Deployment控制器
Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义(declarative)方法,用来替代以前的ReplicationController 来方便的管理应用。典型的应用场景包括
- 定义Deployment来创建Pod和ReplicaSet
- 滚动升级和回滚应用
- 扩容和缩容
- 暂停和继续Deployment
升级回滚
滚动更新过程
弹性伸缩和回滚操作:
弹性伸缩
nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-deploy
name: nginx-deploy
spec:
replicas: 1
selector:
matchLabels:
app: nginx-deploy
template:
metadata:
labels:
app: nginx-deploy
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v1
ports:
- containerPort: 80
通过deployment控制器扩容:
kubectl scale deployments.apps nginx-deploy --replicas=3
通过资源资源利用率弹性扩容(horizontal pod autoscaling):
kubectl autoscale deployment nginx-deploy --min=2 --max=5 --cpu-percent=80
kubectl get horizontalpodautoscalers.autoscaling (kubectl get hpa)
学习helm之后演示
更新镜像:
kubectl set image deployment/nginx-deploy nginx=wangyanglinux/myapp:v2
将容器名containers的名字为"nginx"的镜像修改成wangyanglinux/myapp:v2
更新前为v1版本
更新后为v2版本
回滚:
kubectl rollout undo deployment/nginx-deploy
选择版本进行回滚:
kubectl rollout history deployment nginx-deploy
kubectl rollout undo deployment nginx-deploy --to-revision=2
但是此时也不知道2 3 到底对应的什么版本,这就需要在创建一个版本时需要一个记录
删除所有deployment,重新来
kubectl apply -f nginx-deploy.yaml --record
kubectl set image deployment/nginx-deploy nginx=wangyanglinux/myapp:v2 --record
kubectl rollout history deployment/nginx-deploy
继续升级为v3、v4版本
kubectl set image deployment/nginx-deploy nginx=wangyanglinux/myapp:v3 --record
kubectl set image deployment/nginx-deploy nginx=wangyanglinux/myapp:v4 --record
kubectl rollout history deployment nginx-deploy
可以看到deployment已经创建了4个rs控制器:
指定回滚到某个版本:
使用--record=ture所带来的问题
只要有一次忘记添加此参数,就全都乱了
此时如果更新到v5版本,且忘记添加--record=true参数,则会显示又一次设置为v4版本,会搞乱,所以最佳方案是,在本地目录下创建多个版本的yaml文件并加上名字标记的唯一性,更新版本和回滚版本直接kubectl apply -f 对应版本文件即可
Deployment 更新策略
旧版本:
Deployment 可以保证在升级时只有一定数量的 Pod 是 down 的。默认的,它会确保至少有比期望的Pod数量少一个是up状态(最多一个不可用)
Deployment 同时也可以确保只创建出超过期望数量的一定数量的 Pod。默认的,它会确保最多比期望的Pod数量多一个的 Pod 是 up 的(最多1个 surge )
参数讲解:
maxSurge:指定超出副本数有几个,两种方式:1、指定数量 2、百分比
maxUnavailable : 最多有几个(百分之多少)不可用
Kuberentes 新版本中,将从1-1变成25%-25%
kubectl describe deployments.apps nginx-deploy
kubectl edit deployments.apps nginx-deploy
打补丁:
使用json进行补丁更新:
使用打补丁方式将镜像版本修改为v2:
kubectl create deployment web --image=wangyanglinux/myapp:v1
kubectl get deployments.apps web -o json >web.json
层层粘贴抓取name和image
kubectl patch deployment web -p '{"spec":{"template":{"spec":{"containers":[{"name": "wangyanglinux","image": "wangyanglinux/myapp:v2"}]}}}}'
金丝雀(之后使用service方式更好):
添加一个pod使用新版本内测,如果没问题,再全都切换至新版本
实验(使用nginx-deploy.yaml,副本设置为3):
kubectl patch deployment nginx-deploy -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
kubectl describe deployments.apps nginx-deploy
更新策略将会变成添加一个创建中的pod且不能有pod不可用
金丝雀命令:
kubectl set image deploy nginx-deploy nginx=wangyanglinux/myapp:v2 && kubectl rollout pause deploy nginx-deploy
更新等添加一个pod然后瞬间暂停
多了一个v2版本的pod
测试负载均衡:
kubectl get pods --show-labels
curl 10.97.217.174
等v2版本没问题了,恢复执行更新,所有pod都成为新版本:
kubectl rollout resume deploy nginx-deploy
查看是否全部更新成功:
kubectl rollout status deployments nginx-deploy
Rollover(多个rollout并行)
假如您创建了一个有5个nginx:1.7.9 replica的 Deployment,但是如果还只有3个nginx:1.7.9的 replica 创建出来的时候您就开始更新含有5个nginx:1.9.1 replica 的 Deployment,在这种情况下,Deployment 会立即杀掉已创建的3个nginx:1.7.9的 Pod,并开始创建nginx:1.9.1的 Pod。它不会等到所有的5个 nginx:1.7.9的 Pod 都创建完成后才开始改变航道
清理 Policy
可以通过设置.spec.revisionHistoryLimit
项来指定 deployment 最多保留多少 revision 历史记录。默认的会保留所有的 revision;如果将该项设置为0,Deployment 就不允许回退了,可以有效节约资源
目前有很多版本的rs,比较浪费资源:
生产环境:
nginx-deploy-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-deploy
name: nginx-deploy
spec:
revisionHistoryLimit: 0
replicas: 3
selector:
matchLabels:
app: nginx-deploy
template:
metadata:
labels:
app: nginx-deploy
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v1
nginx-deploy-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-deploy
name: nginx-deploy
spec:
revisionHistoryLimit: 0
replicas: 3
selector:
matchLabels:
app: nginx-deploy
template:
metadata:
labels:
app: nginx-deploy
spec:
containers:
- name: nginx
image: wangyanglinux/myapp:v2
创建并升级版本,假如不对rs进行限制会产生两个rs
kubectl create -f nginx-deploy-v1.yaml
kubectl apply -f nginx-deploy-v2.yaml
kubectl get rs
但是目前只有一个rs
生产环境金丝雀部署:
deployment控制器部署无状态应用
导出yaml文件
kubectl create deployment web --image=nginx --dry-run -o yaml > web.yaml
部署命令+尝试部署+导出命令+导出文件
vim web.yaml
使用yaml文件部署
kubectl apply -f web.yaml
kubectl get pods
对外进行发布,暴露端口号
kubectl expose deployment web --port=80 --type=NodePort --target-port=80(可省略) --name=web1 -o yaml > web-expose.yaml
vim web-expose.yaml
kubectl apply -f web-expose.yaml
kubectl get svc
DaemonSet
DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod
使用 DaemonSet 的一些典型用法:
- 运行集群存储 daemon,例如在每个 Node 上运行
glusterd
、ceph
- 在每个 Node 上运行日志收集 daemon,例如
fluentd
、logstash
- 在每个 Node 上运行监控 daemon,例如 Prometheus Node Exporter、
collectd
、Datadog 代理、New Relic 代理,或 Gangliagmond
练习: vi DaemonSet.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: deamonset-example
labels:
app: daemonset
spec:
selector:
matchLabels:
name: deamonset-example
template:
metadata:
labels:
name: deamonset-example
spec:
containers:
- name: daemonset-example
image: wangyanglinux/myapp:v1
apply一下
node1和node都被部署了pod,但是master没有被部署,因为master自带污点
kubectl describe node k8s-master
如果master有污点,设置容忍的node有可能会接受master,也可以选择不容忍
flannel就是典型的daemonSet
kubectl get daemonsets.apps -n kube-system
部署守护进程DaemonSet日志采集工具
实现所有node在同一个pod中运行,新加入的node也同样在一个pod中
每个node中安装数据采集工具
ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ds-test
labels:
app: filebeat
spec:
selector:
matchLabels:
app: filebeat
template:
metadata:
labels:
app: filebeat
spec:
containers:
- name: logs
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: varlog
mountPath: /tmp/log
volumes:
- name: varlog
hostPath:
path: /var/log
kubectl delete statefulset --all
kubectl apply -f ds.yaml
kubectl get pods
进入容器查看日志
kubectl exec -it ds-test-pkzk7 bash
deployment控制器部署有状态应用(statefulset)
有状态通过唯一的网络标识符和持久存储实现每个pod的唯一性
部署有状态应用
无头service
安装流程:
1:无头IP
2:使用StatefulSet
通过statefulset部署
特点:
sts.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
#通过clusterIP: None创建一个无状态的service
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
#重点:StatefulSet
kind: StatefulSet
metadata:
name: nginx-statefulset
namespace: default
spec:
serviceName: nginx
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
kubectl apply -f sts.yaml
kubectl get pods
三个pod,每个都是唯一的名称
kubectl get svc
查看svc,类型为无头service
区分方式
一次性任务和定时任务
job
概念:
Job 负责【批处理】任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束(比如说数据库备份需要处理多次)
Job可以干什么
Job配置参数详解
apiVersion: batch/v1
kind: Job
metadata:
labels:
job-name: echo
name: echo
namespace: default
spec: # 单个Pod时,默认Pod成功运行后Job即结束
activeDeadlineSeconds: 100 # 标志失败Pod的重试最大时间,超过这个时间不会继续重试
# suspend: true # 1.21+
# ttlSecondsAfterFinished: 100 # 自动清理已结束的 Job(Complete 或 Failed),需要开启特性
backoffLimit: 4 # 如果任务执行失败,失败多少次后不再执行,默认为1
completions: 5 # 有多少个Pod执行成功,认为任务是成功的,默认为1
parallelism: 3 # 并行执行任务的数量,如果parallelism数值大于未完成任务数,只会创建未完成的数量,比如completions是4,并发是3,第一次会创建3个Pod执行任务,第二次只会创建一个Pod执行任务
template: # 格式同Pod
spec:
containers:
- command:
- echo
- Hello, Job
image: registry.cn-beijing.aliyuncs.com/dotbalo/busybox
imagePullPolicy: Always
name: echo
resources: {}
restartPolicy: Never # RestartPolicy仅支持Never或OnFailure
python项目实战(计算圆周率):
main.py
# -*- coding: utf-8 -*-
from __future__ import division
# 导入时间模块
import time
# 计算当前时间
time1=time.time()
# 算法根据马青公式计算圆周率 #
number = 1000
# 多计算10位,防止尾数取舍的影响
number1 = number+10
# 算到小数点后number1位
b = 10**number1
# 求含4/5的首项
x1 = b*4//5
# 求含1/239的首项
x2 = b // -239
# 求第一大项
he = x1+x2
#设置下面循环的终点,即共计算n项
number *= 2
#循环初值=3,末值2n,步长=2
for i in xrange(3,number,2):
# 求每个含1/5的项及符号
x1 //= -25
# 求每个含1/239的项及符号
x2 //= -57121
# 求两项之和
x = (x1+x2) // i
# 求总和
he += x
# 求出π
pai = he*4
#舍掉后十位
pai //= 10**10
# 输出圆周率π的值
paistring=str(pai)
result=paistring[0]+str('.')+paistring[1:len(paistring)]
print result
time2=time.time()
print u'Total time:' + str(time2 - time1) + 's'
python main.py
vi Dockerfile
FROM hub.c.163.com/public/python:2.7
ADD ./main.py /root
CMD /usr/bin/python /root/main.py
docker build -t pi:v1 .
只做好之后保存并传送node下载下镜像
docker save -o pi.tar pi:v1
docker load -i pi.tar
vi job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
metadata:
name: pi
spec:
containers:
- name: pi
image: pi:v1
restartPolicy: Never
kubectl apply -f job.yaml
添加选项:
kubectl delete job --all
kubectl apply -f job.yaml && kubectl get pods -o wide -w
CronJob
特性:
管理基于时间的
- 在给定时间点只运行一次
- 周期性地在给定时间点运行
典型的用法如下所示:
在给定的时间点调度 Job 运行
创建周期性运行的 Job,例如:数据库备份、发送邮件
使用条件:当前使用的 Kubernetes 集群,版本 >= 1.8(对 CronJob)
CronJob模版:
apiVersion: batch/v1beta1 # apiVersion: batch/v1beta1 #1.21+ batch/v1
kind: CronJob
metadata:
labels:
run: hello
name: hello
namespace: default
spec:
schedule: '*/1 * * * *' # schedule:调度周期,和Linux一致,分别是分时日月周
successfulJobsHistoryLimit: 3 # 保留多少已完成的任务,按需配置
failedJobsHistoryLimit: 1 # 保留多少失败的任务。
suspend: false # 如果设置为true,则暂停后续的任务,默认为false
concurrencyPolicy: Allow # 并发调度策略。可选参数如下
# Allow:允许同时运行多个任务。
# Forbid:不允许并发运行,如果之前的任务尚未完成,新的任务不会被创建。
# Replace:如果之前的任务尚未完成,新的任务会替换的之前的任务。
jobTemplate:
metadata:
spec:
activeDeadlineSeconds: 8 # 在 8 秒后终止 Pod,必须在 8 秒内完成运行,这个属于 job 的策略
template:
metadata:
labels:
run: hello
spec:
containers:
- args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
image: registry.cn-beijing.aliyuncs.com/dotbalo/busybox
imagePullPolicy: Always
name: hello
resources: {}
restartPolicy: OnFailure # restartPolicy:重启策略,和Pod一致
securityContext: {}
Job Spec参考Job
CronJob Spec
.spec.schedule:调度,必需字段,指定任务运行周期,格式同 Cron
.spec.jobTemplate:Job 模板,必需字段,指定需要运行的任务,格式同 Job**
.spec.startingDeadlineSeconds :启动 Job 的期限(秒级别),该字段是可选的。如果因为任何原因而错过了被调度的时间,那么错过执行时间的 Job 将被认为是失败的。如果没有指定,则没有期限
.spec.concurrencyPolicy:并发策略,该字段也是可选的。它指定了如何处理被 Cron Job 创建的 Job 的并发执行。只允许指定下面策略中的一种:
Allow
(默认):允许并发运行 Job(可能第二个job被创建时,第一个job还未完成,则并行执行)
Forbid
:禁止并发运行,如果前一个还没有完成,则直接跳过下一个(干掉此时要新建的job)
Replace
:取消当前正在运行的 Job,用一个新的来替换(干掉上一个job)
注意,当前策略只能应用于同一个 Cron Job 创建的 Job。如果存在多个 Cron Job,它们创建的 Job 之间总是允许并发运行。.spec.suspend
:挂起,该字段也是可选的。如果设置为 true
,后续所有执行都会被挂起。它对已经开始执行的 Job 不起作用。默认值为 false
(认为的在运行时运行,不运行时edit下选择false即可).spec.successfulJobsHistoryLimit
和 .spec.failedJobsHistoryLimit
:历史限制,是可选的字段。它们指定了可以保留多少完成和失败的 Job。默认情况下,它们分别设置为 3
和 1
。设置限制的值为 0
,相关类型的 Job 完成后将不会被保留
练习:
cronjob.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
kubectl create -f cronjob.yaml
kubectl get cronjobs,pods
保留三个job,间隔60秒
暂时不用了,先挂起:
kubectl edit cronjobs.batch hello
kubectl get cronjobs,pods
一般挂起适用于集群告警暂停处理问题
CrondJob 本身的一些限制:
创建 Job 操作应该是幂等的
service
service介绍:
在 Kubernetes 集群中,每个 Node 运行一个 kube-proxy
进程。kube-proxy
负责为 Service
实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName
的形式。 在 Kubernetes v1.0 版本,代理完全在 userspace。在 Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。 从 Kubernetes v1.2 起,默认就是 iptables 代理。 在 Kubernetes v1.8.0-beta.0 中,添加了 ipvs 代理
userspace模式
userspace模式所有流量都要经过kube-proxy,类似于ipvs的nat模式
在 userspace 模式下,kube-proxy 通过监听 K8s apiserver 获取关于 Service 和 Endpoint 的变化信息,在内存中维护一份从ClusterIP:Port 到后端 Endpoints 的映射关系,通过反向代理的形式,将收到的数据包转发给后端,并将后端返回的应答报文转发给客户端。该模式下,kube-proxy 会为每个 Service (每种协议,每个 Service IP,每个 Service Port)在宿主机上创建一个 Socket 套接字(监听端口随机)用于接收和转发 client 的请求。默认条件下,kube-proxy 采用 round-robin 算法从后端 Endpoint 列表中选择一个响应请求。
由于其需要来回在用户空间和内核空间交互通信,因此效率很差
Iptables模式
iptables模式减少可kube-proxy压力
在iptables 模式下,kube-proxy 依然需要通过监听K8s apiserver 获取关于 Service 和 Endpoint 的变化信息。不过与 userspace 模式不同的是,kube-proxy 不再为每个 Service 创建反向代理(也就是无需创建 Socket 监听),而是通过安装 iptables 规则,捕获访问 Service ClusterIP:Port 的流量,直接重定向到指定的 Endpoints 后端。默认条件下,kube-proxy 会 随机 从后端 Endpoint 列表中选择一个响应请求。ipatbles 模式与 userspace 模式的不同之处在于,数据包的转发不再通过 kube-proxy 在用户空间通过反向代理来做,而是基于 iptables/netfilter 在内核空间直接转发,避免了数据的来回拷贝,因此在性能上具有很大优势,而且也避免了大量宿主机端口被占用的问题。
但是将数据转发完全交给 iptables 来做也有个缺点,就是一旦选择的后端没有响应,连接就会直接失败了,而不会像 userspace 模式那样,反向代理可以支持自动重新选择后端重试,算是失去了一定的重试灵活性。不过,官方建议使用 Readiness 探针来解决这个问题,一旦检测到后端故障,就自动将其移出 Endpoint 列表,避免请求被代理到存在问题的后端。
并且iptables 因为它纯粹是为防火墙而设计的,并且基于内核规则列表,集群数量越多性能越差。
一个例子是,在5000节点集群中使用 NodePort 服务,如果我们有2000个服务并且每个服务有10个 pod,这将在每个工作节点上至少产生20000个 iptable 记录,这可能使内核非常繁忙。
Ipvs代理模式
ipvs模式引入了LVS,可以承受更大的并发量
IPVS 是一个用于负载均衡的 Linux 内核功能。IPVS 模式下,kube-proxy 使用 IPVS 负载均衡代替了 iptable。这种模式同样有效,IPVS 的设计就是用来为大量服务进行负载均衡的,它有一套优化过的 API,使用优化的查找算法,而不是简单的从列表中查找规则。
这样一来,kube-proxy 在 IPVS 模式下,其连接过程的复杂度为 O(1)。换句话说,多数情况下,他的连接处理效率是和集群规模无关的。
另外作为一个独立的负载均衡器,IPVS 包含了多种不同的负载均衡算法,例如轮询、最短期望延迟、最少连接以及各种哈希方法等。而 iptables 就只有一种随机平等的选择算法。
IPVS代理模式基于netfilter hook函数,该函数类似于iptables模式,但使用hash表作为底层数据结构,在内核空间中工作。这意味着IPVS模式下的kube-proxy使用更低的重定向流量。其同步规则的效率和网络吞吐量也更高。因此当service数量达到一定规模时,hash查表的速度优势就会显现出来,从而提高service的服务性能。IPVS是专门为负载均衡设计的,并且底层使用哈希表这种非常高效的数据结构,几乎可以允许无限扩容。
IPVS 的一个潜在缺点就是,IPVS 处理数据包的路径和通常情况下 iptables 过滤器的路径是不同的。如果计划在有其他程序使用 iptables 的环境中使用 IPVS,需要进行一些研究,看看他们是否能够协调工作。(Calico 已经和 IPVS kube-proxy 兼容)
在 Kubernetes 1.14 版本开始默认使用 ipvs 代理
在 Kubernetes v1.0 版本,Service
是 “4层”(TCP/UDP over IP)概念。 在 Kubernetes v1.1 版本,新增了 Ingress
API(beta 版),用来表示 “7层”(HTTP)服务
Service能够提供负载均衡的能力,但是只提供 4 层负载均衡能力,而没有 7 层功能,但有时我们可能需要更多的匹配规则来转发请求,这点上 4 层负载均衡是不支持的
性能对比
iptables 的连接处理算法复杂度是 O(n),而 IPVS 模式是 O(1)
在微服务环境中,其具体表现如何呢
在多数场景中,有两个关键属性需要关注:
- 响应时间:一个微服务向另一个微服务发起调用时,第一个微服务发送请求,并从第二个微服务中得到响应,中间消耗了多少时间?
- CPU消耗:运行微服务的过程中,总体 CPU 使用情况如何?包括用户和核心空间的 CPU 使用,包含所有用于支持微服务的进程(也包括 kube-proxy)。
为了说明问题,我们运行一个微服务作为客户端,这个微服务以 Pod 的形式运行在一个独立的节点上,每秒钟发出 1000 个请求,请求的目标是一个 Kubernetes 服务,这个服务由 10 个 Pod 作为后端,运行在其它的节点上。接下来我们在客户端节点上进行了测量,包括 iptables 以及 IPVS 模式,运行了数量不等的 Kubernetes 服务,每个服务都有 10 个 Pod,最大有 10,000 个服务(也就是 100,000 个 Pod)。我们用 golang 编写了一个简单的测试工具作为客户端,用标准的 NGINX 作为后端服务
响应时间
响应时间很重要,有助于我们理解连接和请求的差异。典型情况下,多数微服务都会使用持久或者 keepalive 连接,这意味着每个连接都会被多个请求复用,而不是每个请求一次连接。这很重要,因为多数连接的新建过程都需要完成三次 TCP 握手的过程,这需要消耗时间,也需要在 Linux 网络栈中进行更多操作,也就会消耗更多 CPU 和时间。
这张图展示了两个关键点:
- iptables 和 IPVS 的平均响应时间在 1000 个服务(10000 个 Pod)以上时,会开始观察到差异。
- 只有在每次请求都发起新连接的情况下,两种模式的差异才比较明显。
不管是 iptables 还是 IPVS,kube-proxy 的响应时间开销都是和建立连接的数量相关的,而不是数据包或者请求数量,这是因为 Linux 使用了 Conntrack,能够高效地将数据包和现存连接关联起来。如果数据包能够被 Conntrack 成功匹配,那就不需要通过 kube-proxy 的 iptables 或 IPVS 规则来推算去向。Linux conntrack 非常棒!(绝大多数时候)
值得注意的是,例子中的服务端微服务使用 NGINX 提供一个静态小页面。多数微服务要做更多操作,因此会产生更高的响应时间,也就是 kube-proxy 处理过程在总体时间中的占比会减少。
还有个需要解释的古怪问题:既然 IPVS 的连接过程复杂度是 O(1),为什么在 10,000 服务的情况下,非 Keepalive 的响应时间还是提高了?我们需要深入挖掘更多内容才能解释这一问题,但是其中一个因素就是因为上升的 CPU 用量拖慢了整个系统。这就是下一个主题需要探究的内容。
CPU用量
为了描述 CPU 用量,下图关注的是最差情况:不使用持久/keepalive 连接的情况下,kube-proxy 会有最大的处理开销。
上图说明了两件事:
- 在超过 1000 个服务(也就是 10,000 个 Pod)的情况下,CPU 用量差异才开始明显。
- 在一万个服务的情况下(十万个后端 Pod),iptables 模式增长了 0.35 个核心的占用,而 IPVS 模式仅增长了 8%。
有两个主要因素造成 CPU 用量增长:
第一个因素是,缺省情况下 kube-proxy 每 30 秒会用所有服务对内核重新编程。这也解释了为什么 IPVS 模式下,新建连接的 O(1) 复杂度也仍然会产生更多的 CPU 占用。另外,如果是旧版本内核,重新编程 iptables 的 API 会更慢。所以如果你用的内核较旧,iptables 模式可能会占用更多的 CPU。
另一个因素是,kube-proxy 使用 IPVS 或者 iptables 处理新连接的消耗。对 iptables 来说,通常是 O(n) 的复杂度。在存在大量服务的情况下,会出现显著的 CPU 占用升高。例如在 10,000 服务(100,000 个后端 Pod)的情况下,iptables 会为每个请求的每个连接处理大约 20000 条规则。如果使用 NINGX 缺省每连接 100 请求的 keepalive 设置,kube-proxy 的 iptables 规则执行次数会减少为 1%,会把 iptables 的 CPU 消耗降低到和 IPVS 类似的水平。
客户端微服务会简单的丢弃响应内容。真实世界中自然会进行更多处理,也会造成更多的 CPU 消耗,但是不会影响 CPU 消耗随服务数量增长的事实。
结论
二者有着本质的差别:iptables是为防火墙而设计的;IPVS则专门用于高性能负载均衡,并使用更高效的数据结构(Hash表),允许几乎无限的规模扩张。
在超过 1000 服务的规模下,kube-proxy 的 IPVS 模式会有更好的性能表现。虽然可能有多种不同情况,但是通常来说,让微服务使用持久连接、运行现代内核,也能取得较好的效果。如果运行的内核较旧,或者无法使用持久连接,那么 IPVS 模式可能是个更好的选择。
抛开性能问题不谈,IPVS 模式还有个好处就是具有更多的负载均衡算法可供选择。
如果你还不确定 IPVS 是否合适,那就继续使用 iptables 模式好了。这种传统模式有大量的生产案例支撑,他是一个不完美的缺省选项。
Calico和kube-proxy的iptables比较
我们看到,kube-proxy 中的 iptables 用法在大规模集群中可能会产生性能问题。有人问我 Calico 为什么没有类似的问题。答案是 Calico 中 kube-proxy 的用法是不同的。kube-proxy 使用了一个很长的规则链条,链条长度会随着集群规模而增长,Calico 使用的是一个很短的优化过的规则链,经由 ipsets 的加持,也具备了 O(1) 复杂度的查询能力。
下图证明了这一观点,其中展示了每次连接过程中,kube-proxy 和 Calico 中 iptables 规则数量的平均值。这里假设集群中的节点平均有 30 个 Pod,每个 Pod 具有 3 个网络规则。
即使是使用 10,000 个服务和 100,000 个 Pod 的情况下,Calico 每连接执行的 iptables 规则也只是和 kube-proxy 在 20 服务 200 个 Pod 的情况基本一致。
service存在的意义
1、防止pod失联
pod升级回滚操作会导致ip变化,需要在service上注册ip,pod IP即使变化也会在service中记录,通过pod发现防止服务器失联
2、负载均衡作用
pod和service的关系
service类似于lvs进行负载均衡,并拥有自己的VIP,service通过labels控制pod
常用sevice类型
kubectl explain service.spec.type
ClusterIP:集群内部使用
ClusterIp:默认类型,自动分配一个仅 集群 内部可以访问的虚拟 IP
clusterIP是不会变的,由service分配生成,即使有deployment实例化的pod异常退出,svc后端pod IP也会动态监听,修改轮巡IP配置
ClusterIP类似于内部要配置NAT
为了实现图上的功能,主要需要以下几个组件的协同工作:
- apiserver:用户通过 kubectl 命令向 apiserver 发送创建 service 的命令,apiserver 接收到请求后将数据存储到 etcd 中
- kube-proxy:kubernetes 的每个节点中都有一个叫做 kube-porxy 的进程,这个进程负责感知service,pod 的变化,并将变化的信息写入本地的 ipvs 规则中
- ipvs:基于内核的钩子函数机制实现负载
创建一个集群内部IP
kubectl create deployment web --image=nginx --dry-run -o yaml > web.yaml
kubectl apply -f web.yaml
kubectl expose deployment web --port=80 --target-port=80 --dry-run -o yaml > service1.yaml
cat service1.yaml
kubectl apply -f service1.yaml
kubectl get svc
curl 10.100.27.227
验证service在kube-dns中注册了内部域名:
myapp-deploy.yaml文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
template:
metadata:
labels:
app: myapp
release: stabel
env: test
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
kubectl apply -f myapp-deploy.yaml
创建 Service 信息
clusterIp-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
type: ClusterIP
selector:
app: myapp
release: stabel
ports:
- name: http
port: 80
targetPort: 80
kubectl apply -f clusterIp-svc.yaml
kube-dns中查看
dig -t A myapp.default.svc.cluster.local. @10.96.0.10
格式:dig -t -A [svc名字].[命名空间].svc.cluster.local. @[kube-system中kube-dns的域名]
svc在内部kube-dns上注册了内部域名
任意创建一个pod
pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-pod
namespace: default
labels:
app: myapp
spec:
containers:
- name: readiness-httpget-container
image: wangyanglinux/myapp:v1
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index.html
initialDelaySeconds: 1
periodSeconds: 3
进入pod
kubectl exec -it readiness-httpget-pod -- sh
说明每个新建的pod都可以通过coredns获取到服务的域名
基于 service 模拟金丝雀部署:
设计思路:通过相同的标签选择器,将不同版本容器的 pod 部署在相同的 service 后端,新版本先分配小部分流量(创建极小比例 pod),如果新版本没有问题,旧版本使用 edit 方式跟着升级到新版本,最后删除新版本的测试 pod
kubectl delete deployments.apps --all
myapp-deploy-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy-v1
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: stabel
version: v1
template:
metadata:
labels:
app: myapp
release: stabel
version: v1
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
myapp-deploy-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy-v2
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: myapp
release: stabel
version: v2
template:
metadata:
labels:
app: myapp
release: stabel
version: v2
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
kubectl apply -f myapp-deploy-v1.yaml
kubectl get svc
kubectl apply -f myapp-deploy-v2.yaml
svc成功负载v2版本作为金丝雀
如果v2版本没问题,继续升级v1到v2版本
kubectl edit deployments.apps myapp-deploy-v1
如果没必要最后可以删除金丝雀版本测试pod
kubectl delete -f myapp-deploy-v2.yaml
Headless Service(隶属于ClusterIp)
有时不需要或不想要负载均衡,以及单独的 Service IP 。遇到这种情况,可以通过指定 Cluster IP ( spec.clusterIP ) 的值为 “ None ” 来创建 Headless Service 。这类 Service 并不会分配 Cluster IP, kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由,但是会给分配一个内部的dns
headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-headless
namespace: default
spec:
selector:
app: myapp
clusterIP: "None"
ports:
- port: 80
targetPort: 80
dig -t A myapp-headless.default.svc.cluster.local. @10.96.0.10
给每一个pod提供一个固定的网络标识,当一个pod消失会自动添加一个pod,并更新core-dns的clusterIP
NodePort(对外访问应用使用,将调度器地址绑定在物理网卡上)
NodePort:在 ClusterIP 基础上为 Service 在每台机器上绑定一个端口,这样就可以通过 : NodePort 来访问该服务
创建一个暴露外部集群的端口(NodePort)
vim service1.yaml
kubectl apply -f service1.yaml
kubectl get svc
可实现外网访问
练习:
kubectl get pod --show-labels
vim myapp-service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
type: NodePort
selector:
app: myapp
release: stabel
ports:
- name: http
port: 80
targetPort: 80
k8s中升级较稳定,降级建议先删除要修改的svc,然后再创建新的svc,例如clusterIP->NodePort为升级,反之为降级
升级操作:
kubectl edit svc myapp
master和nodeIP都可以用过NodePort访问:
LoadBalacer:对外访问使用,公有云
因为node在内网部署,不使用外网,可以使用nginx进行负载均衡
LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部负载均衡器,并将请求转发到: NodePort
纯物理云搭建高可用方式:
公有云默认没有IPVS内核模块,需要购买负载均衡,公有云会在NodePort创建之时自动创建一个负载均衡对外暴露端口(LAAS)
ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有 kubernetes 1.7 或更高版本的 kube-dns 才支持
当内部有多个pod要访问外部的服务时,可以将外部的服务抽象到内部集群的域名。可以使用变量的方式,pod通过本地svc获取到外部服务,当外部服务IP发生变化时,只需要修改内部svc的IP值。CoreDNS会自动将外部的服务做解析解析到新的IP上,使得服务更加灵活
这种类型的 Service 通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容( 例如:hub.hongfu.com )。ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务
ExternalName-svc.yaml
kind: Service
apiVersion: v1
metadata:
name: my-service-1
namespace: default
spec:
type: ExternalName
externalName: hub.hongfu.com
另外也可以使用DNS但是需要DNS的add-on
ConfigMap
作用:
ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制等对象,给我们提供了向容器中注入配置信息的机制
使用configmap创建环境变量和使用环境变量
创建环境变量:
使用目录注入环境变量:
文件目录结构和内容(/root/k8s/configmap下创建并编辑game.file和ui.properties两个文件)
game.file
version=1.17
name=dave
age=18
ui.properties
level=2
color=yellow
目录注入环境变量
kubectl create configmap game-config --from-file=/root/k8s/configmap
--from-file 指定所在目录下的所有文件都会被用在ConfigMap里面创建一个键值对,键的名字就是文件名,值就是文件内容
熟悉下yaml文件格式:
kubectl create configmap game-config --from-file=/root/k8s/configmap --dry-run=client -o yaml
查看cm中的kv信息方式:
kubectl describe cm game-config
kubectl get cm -o yaml
使用文件创建环境变量:
只要指定为一个文件就可以从单个文件中创建ConfigMap
kubectl create configmap game-config-v2 --from-file=/root/k8s/configmap/game.file
kubectl describe cm game-config-v2
使用字面值命令创建:
利用 —from-literal
参数传递配置信息,该参数可以使用多次
kubectl create configmap literal-config --from-literal=name=dave --from-literal=password=pass
kubectl describe cm literal-config
pod使用环境变量:
使用 ConfigMap 来替代环境变量
运行过程:现将环境变量信息注入在cm中,在pod运行时使用cm中的值
literal-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: literal-config
namespace: default
data:
name: dave
password: pass
env-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: default
data:
log_level: INFO
cm-env-test-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: cm-env-test-pod
spec:
containers:
- name: test-container
image: wangyanglinux/myapp:v1
command: [ "/bin/sh", "-c", "env" ]
env:
- name: USERNAME
valueFrom:
configMapKeyRef:
name: literal-config
key: name
- name: PASSWORD
valueFrom:
configMapKeyRef:
name: literal-config
key: password
envFrom:
- configMapRef:
name: env-config
restartPolicy: Never
kubectl logs cm-env-test-pod
用configmap设置启动命令行参数
cm-command-dapi-test-pod
apiVersion: v1
kind: Pod
metadata:
name: cm-command-dapi-test-pod
spec:
containers:
- name: test-container
image: wangyanglinux/myapp:v1
command: [ "/bin/sh", "-c", "echo $(USERNAME) $(PASSWORD)" ]
env:
- name: USERNAME
valueFrom:
configMapKeyRef:
name: literal-config
key: name
- name: PASSWORD
valueFrom:
configMapKeyRef:
name: literal-config
key: password
restartPolicy: Never
kubectl logs cm-command-dapi-test-pod
通过数据卷插件使用ConfigMap
在数据卷里面使用这个 ConfigMap,有不同的选项。最基本的就是将文件填入数据卷,在这个文件中,【键就是文件名,键值就是文件内容】,环境变量方式是不支持热更新的
ConfigMap 的热更新配置文件
1、文件热更新
热更新是通过环境变量主动进行批量化注入的方式(不是完全共享目录方式),不会对IO造成很大的影响,但更新速度也不会太快
log-config-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: log-config
namespace: default
data:
log_level: INFO
hot-update-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: my-nginx
name: hot-update
spec:
replicas: 1
selector:
matchLabels:
run: my-nginx
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: wangyanglinux/myapp:v1
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: log-config
while 2>1; do kubectl exec hot-update-75b6496495-mmzqf -- cat /etc/config/log_level; echo; sleep 1s; done
新开一个终端:kubectl edit cm log-config
所有INFO都要修改成ERROR:
热更新显示:
2、ConfigMap 更新后滚动更新 Pod
会有很多服务不单单是配置文件更新就可,往往需要重启才能更新
方式一:通过修改docker tag 改名实现滚动更新
方式二:打补丁
更新 ConfigMap 目前并不会触发相关 Pod 的滚动更新,可以通过修改 pod annotations 的方式强制触发重启滚动更新(会更新容器)
kubectl patch deployment hot-update --patch '{"spec": {"template": {"metadata": {"annotations": {"version/config": "20190411" }}}}}'
希望未来可以直接edit就实现热更新
自定义挂载权限及名称
volumes:
- name: redisconf
configMap:
name: redis-conf
- name: cmfromfile
configMap:
name: cmfromfile
items:
- key: redis.conf
path: redis.conf.bak
- key: redis.conf
path: redis.conf.bak2
mode: 0644 # 设置权限优先级高
defaultMode: 0666 # 默认权限优先级
secret
Secret常用类型
*Opaque:通用型Secret,默认类型;
*kubernetes.io/service-account-token:作用于ServiceAccount,包含一个令牌,用于标识API服务账户;
*kubernetes.io/dockerconfigjson:下载私有仓库镜像使用的Secret,和宿主机的/root/.docker/config.json一致,宿主机登录后即可产生该文件;
*kubernetes.io/basic-auth:用于使用基本认证(账号密码)的Secret,可以使用Opaque取代;
*kubernetes.io/tls:用于存储HTTPS域名证书文件的Secret,可以被Ingress使用;
*bootstrap.kubernetes.io/token:一种简单的 bearer token,用于创建新集群或将新节点添加到现有集群,在集群安装时可用于自动颁发集群的证书。
Secret 存在意义
Secret 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。Secret 可以以 Volume 或者环境变量的方式使用
Secret 有三种类型:
Service Account :用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod 的 /run/secrets/kubernetes.io/serviceaccount
目录中
Opaque :base64 编码格式的 Secret,用来存储密码、密钥等
kubernetes.io/dockerconfigjson :用来存储私有 docker 仓库的认证信息
创建secret的方式和类型
Service Account
创建证书资源消耗较大,且pods变化较大,因此使用SA类似于临时员工牌,可以共享使用,Service Account 用来访问 Kubernetes API,由 Kubernetes 自动创建,并且会自动挂载到 Pod的 /run/secrets/kubernetes.io/serviceaccount
目录中
查看:
kubectl get pods -n kube-system
kubectl exec -it kube-flannel-ds-4jdrk -n kube-system -- /bin/sh
cd /run/secrets/kubernetes.io/serviceaccount
ca.crt:pod认证apiserver是否合法
namespace:标识当前的名称空间作用域
token:json web token实现单点认证,api认证当前的的pod使用
在安全认证和准入控制中会细讲
Opaque
base64编码格式的Secret,用来存储密码,密钥等,当Opaque secret挂在到pod之后,会自动解码
base64加密解密过程:
加密:
echo -n "user1" | base64
解密:
echo -n "dXNlcjE=" | base64 -d
使用secret挂载数据
1、以环境变量的形式挂载到pod容器中
创建volumn加密数据
通过base64进行加密
secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
kubectl create -f secret.yaml
kubectl get secret
secret-env.yaml
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: nginx
image: nginx
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
解析:secret.yaml中的name为mysecret,通过mysecret建立联系,将SECRET_USERNAME的key的值对应在secret.yaml创建的mysecret的username,SECRET_PASSWORD的key的值对应在secret.yaml创建的mysecret的password
应用:
kubectl apply -f secret-var.yaml
kubectl get secret
kubectl get pods
进入pod验证
kubectl exec -it mypod bash
echo $SECRET_USERNAME && echo $SECRET_PASSWORD
2、以数据卷方式挂载
secret-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
防止metadata重复,先删除之前的变量挂载规则
kubectl delete -f secret-env.yaml
kubectl apply -f secret-vol.yaml
kubectl exec -it mypod -- bash
cd /etc/foo/
cat password
cat username
kubernetes.io/dockerconfigjson
如果有非常多的node,不可能每个node都要dockerlogin一遍,kubernetes.io/dockerconfigjson批量解决了私有docker仓库认证问题
dockerhub搭建请参考章节“Harbor – 企业级 Docker 私有仓库”:https://www.ljh.cool/4978.html
添加一台虚拟机,搭建dockerhub,hub域名设置为hub.ljh.com,并添加到hosts集群中
node以及master添加私有镜像仓库"insecure-registries": ["hub.ljh.com"],
重启客户端docker
systemctl daemon-reload
systemctl restart docker
公开仓库
master作为推送镜像客户端:
docker login hub.ljh.com
推送镜像进行测试:
启动一个deployment使用公有镜像仓库:
hub.ljh.com/library/wangyanglinux/myapp:v1
因为访问级别是公开,所以必定会成功推送和下载
私有仓库使用:
master推送下镜像:
推送镜像并退出登陆:
docker tag wangyanglinux/myapp:v1 hub.ljh.com/secret/myapp:v1
docker push hub.ljh.com/secret/myapp:v1
docker logout
删除公有镜像deployment
kubectl delete deployments.apps hot-update
更新为私有仓库镜像:
检测:
kubectl get pods
kubectl describe pod hot-update-db8f6949d-2656p
因为是私有镜像仓库,所以解决方式有两种:
所有节点docker login
dockerconfigjson认证
dockerconfigjson实现
使用 Kuberctl 创建 docker 仓库认证的 secret:
kubectl create secret docker-registry myregistrykey --docker-server=hub.ljh.com --docker-username=admin --docker-password=Harbor12345 --docker-email=ljhxxxxxxxx@163.com
在创建 Pod 的时候,通过 `imagePullSecrets` 来引用刚创建的 `myregistrykey`
添加spec.imagePullSecrets:
imagePullSecrets:
- name: myregistrykey
vi hot-update-deploy.yaml
kubectl apply -f hot-update-deploy.yaml
k8s安全机制
概述
三”A“服务
Kubernetes 作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。API Server 是集群内部各个组件通信的中介,也是外部控制的入口。所以 Kubernetes 的安全机制基本就是围绕保护 API Server 来设计的。Kubernetes 使用了认证(Authentication)、鉴权(Authorization)、准入控制(Admission Control)三步来保证API Server的安全
是不是自己人、有没有权力、是不是有自己权限范围内内但不应该出现操作
Authentication(认证)
认证概念:
当请求发起方建立与 API Server 的安全连接后,进入请求的认证阶段(图中步骤 1)。认证的方式主要有:客户端证书、密码、普通 token、bootstrap token 和 JWT 认证 (主要用于 Service Account)。认证模块会检查请求头或者客户端证书的内容,我们可以同时配置一种或几种方式对请求进行认证。多种认证方式会被依次执行,只要一种方式通过,请求便得到合法认证。当所有方式都未通过时,会返回 401 状态码并中断请求。认证解决的问题是校验访问方是否合法并识别其身份。
两种类型认证:
UA和 SA:用户账户UA是人机交互的过程。 是大家非常熟悉的用 kubectl 对 apiserver 的一个请求过程,k8s 无法直接操作这种资源,需要通过外部创建用户进行授权认证;服务账户SA是 Pod 中的业务逻辑与 apiserver 之间的交互。
k8s 常用的三种认证方式:
api-server配置查看下默认支持的认证方式
HTTP Token 认证:通过一个 Token 来识别合法用户
HTTP 为验证使用者身份,需要客户端向服务端提供一个可靠的身份信息,称之为Token,这个Token被放在HTTP Header头里,在Token里有信息来表明客户身份。Token通常是一个有一定长度的难以被篡改的字符串,每一个 Token 对应一个用户名存储在 API Server 能访问的文件中。当客户端发起 API 调用请求时,需要在 HTTP Header 里放入 Token。在K8s中,每个Bearer Token都对应一个用户名,存储在API Server能访问的一个文件中(Static Token file)。客户端发起API调用请求时,需要在HTTPHeader里放入此Token,这样一来,API Server就能识别合法用户和非法用户了。SA认证方式也采用了与HTTP Bearer Token相同的实现方式。每个sa都对应一个Secret,在Secret中有一个加密的Token字段,这个Token字段就是Bearer Token。当API Server设置了启动参数–service-account-lookup=true,API Server就会验证Token是否在etcd中存在,若已从etcd中删除,则将注销容器中Token的有效性
HTTP Base 认证:通过 用户名+密码 的方式认证(1.19 版本已废弃)
用户名+:+密码 用 BASE64 算法进行编码后的字符串放在 HTTP Request 中的 Heather Authorization 域里发送给服务端,服务端收到后进行编码,获取用户名及密码
最严格的 HTTPS 证书认证:基于 CA 根证书签名的客户端身份认证方式(目前使用方式)(一般说是X509 证书认证)
当两个组件进行双向 TLS 认证时,会涉及到下表中的相关文件:
名称 | 作用 | 例子 |
服务端证书 | 包含服务端公钥和服务端身份信息 | 通过根证书手动或者 kubeadm 自动生成的 API Server 服务端证书文件 apiserver.crt |
服务器私钥 | 主要用于 TLS 认证时进行数字签名,证明自己是服务端证书的拥有者 | 通过根证书手动或者 kubeadm 生成的 API Server 服务端私钥文件 apiserver.key |
客户端证书 | 包含客户端公钥和客户端身份信息 | 由同一个 CA 根证书签发的.crt 文件 |
客户端私钥 | 主要用于 TLS 认证时进行数字签名,证明自己是客户端证书的拥有者 | 由同一个 CA 根证书签发的.key 文件 |
服务端 CA 根证书 | 签发服务端证书的 CA 根证书 | 通过 openssl 等工具生成的 ca.crt 文件,并在服务端启动时进行指定 |
客户端 CA 根证书 | 签发客户端证书的 CA 根证书 | 通过 openssl 等工具生成的 ca.crt 文件,并在客户端启动时进行指定 (一般与服务端使用一个) |
k8s 内部系统之间的两种类型的访问
1、Kubenetes 组件对 API Server 的访问:kubectl、Controller Manager、Scheduler、kubelet、kube-proxy
2、Kubernetes 管理的 Pod 对容器的访问:Pod(dashborad 也是以 Pod 形式运行)
kubectl对集群访问:kubeconfig
kubeconfig 文件包含集群参数(CA证书、API Server地址),客户端参数(上面生成的证书和私钥),集群 context 信息(集群名称、用户名)。Kubenetes 组件通过启动时指定不同的 kubeconfig 文件可以切换到不同的集群
需要认证的组件:
k8s集群中两套认证系统(apiserver和etcd):
ServiceAccount认证:
Pod中的容器访问API Server。因为Pod的创建、销毁是动态的,所以要为它手动生成证书就不可行了。Kubenetes使用了Service Account解决Pod 访问API Server的认证问题
Secret 与 SA 的关系
查看SA
Kubernetes 设计了一种资源对象叫做 Secret,分为两类,一种是用于 ServiceAccount 的 service-account-token, 另一种是用于保存用户自定义保密信息的 Opaque。ServiceAccount 中用到包含三个部分:Token、ca.crt、namespace
1、token是使用 API Server 私钥签名的 JWT。用于访问API Server时,Server端认证(<!--Json web token (JWT) , 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准([(RFC 7519] ).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 token 也可直接被用于认证,也可被加密-->)
2、ca.crt,根证书。用于Client端验证API Server发送的证书
3、namespace, 标识这个service-account-token的作用域名空间
一个SA可以对应多个pod
总结:
Authorization(鉴权)
鉴权概念:
K8s API 访问请求必须包括请求者的用户名、请求的操作以及操作对象,该阶段就是对用户的操作合法性进行校验。如果现有策略声明用户具有完成请求操作的权限,则对请求进行授权。K8s 支持 ABAC 模式、RBAC 模式、Webhook 模式等多种授权模块。同样的,当多个授权模块被配置时,请求只要满足其中任意一种授权规则便会被放行,反之,API Server 会返回 403 状态码并终止该请求。鉴权是为了判别用户的操作权限范围。
上面认证过程,只是确认通信的双方都确认了对方是可信的,可以相互通信。而鉴权是确定请求方有哪些资源的权限。API Server 目前支持以下几种授权策略 (通过 API Server 的启动参数 “--authorization-mode” 设置)
AlwaysDeny:表示拒绝所有的请求,一般用于测试
AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略
ABAC(Attribute-Based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制
Webhook:通过调用外部 REST 服务对用户进行授权
RBAC(Role-Based Access Control):基于角色的访问控制,现行默认规则(目前使用)
RBAC 授权模式
RBAC(Role-Based Access Control)基于角色的访问控制,在 Kubernetes 1.5 中引入,现行版本成为默认标准。相对其它访问控制方式,拥有以下优势:
- 对集群中的资源和非资源均拥有完整的覆盖
- 整个 RBAC 完全由几个 API 对象完成,同其它 API 对象一样,可以用 kubectl 或 API 进行操作
- 可以在运行时进行调整,无需重启 API Server
细粒度权限划分
绑定方式:
RBAC 引入了 4 个新的顶级资源对象:Role、ClusterRole、RoleBinding、ClusterRoleBinding,4 种对象类型均可以通过 kubectl 与 API 操作
权限只能降级,不能升级
需要注意的是 Kubenetes 并不会提供用户管理,那么 User、Group、ServiceAccount 指定的用户又是从哪里来的呢? Kubenetes 组件(kubectl、kube-proxy)或是其他自定义的用户在向 CA 申请证书时,需要提供一个证书请求文件
{
"CN": "admin",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "HangZhou",
"L": "XS",
"O": "system:masters",
"OU": "System"
}
]
}
API Server会把客户端证书的CN
字段作为User,把names.O
字段作为Group
kubelet 使用 TLS Bootstraping 认证时,API Server 可以使用 Bootstrap Tokens 或者 Token authentication file 验证 =token,无论哪一种,Kubenetes 都会为 token 绑定一个默认的 User 和 Group
Pod使用 ServiceAccount 认证时,service-account-token 中的 JWT 会保存 User 信息
有了用户信息,再创建一对角色/角色绑定(集群角色/集群角色绑定)资源对象,就可以完成权限绑定了
Role and ClusterRole
在 RBAC API 中,Role 表示一组规则权限,权限只会增加(累加权限),不存在一个资源一开始就有很多权限而通过 RBAC 对其进行减少的操作;Role 可以定义在一个 namespace 中,如果想要跨 namespace 则可以创建 ClusterRole
Role
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
ClusterRole
kubectl get clusterrole | grep -E 'cluster-admin|flannel'
ClusterRole 具有与 Role 相同的权限角色控制能力,不同的是 ClusterRole 是集群级别的,ClusterRole 可以用于:
集群级别的资源控制( 例如 node 访问权限 )
非资源型 endpoints( 例如 /health
访问 )
所有命名空间资源控制(例如 pods )
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
# "namespace" omitted since ClusterRoles are not namespaced
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "watch", "list"]
聚合ClusterRole
如果你创建一个与某个已存在的聚合 ClusterRole 的标签选择算符匹配的 ClusterRole, 这一变化会触发新的规则被添加到聚合 ClusterRole 的操作。 下面的例子中,通过创建一个标签同样为 rbac.example.com/aggregate-to-monitoring: true
的 ClusterRole,新的规则可被添加到 "monitoring" ClusterRole 中
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # 控制面自动填充这里的规则
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: monitoring-endpoints
labels:
rbac.example.com/aggregate-to-monitoring: "true"
# 当你创建 "monitoring-endpoints" ClusterRole 时,
# 下面的规则会被添加到 "monitoring" ClusterRole 中
rules:
- apiGroups: [""]
resources: ["services", "endpoints", "pods"]
verbs: ["get", "list", "watch"]
Resources限制
Kubernetes 集群内一些资源一般以其名称字符串来表示,这些字符串一般会在 API 的 URL 地址中出现;同时某些资源也会包含子资源,例如 logs 资源就属于 pods 的子资源,API 中 URL 样例如下
如果要在 RBAC 授权模型中控制这些子资源的访问权限,可以通过‘/’分隔符来实现,以下是一个定义 pods 资资源 logs 访问权限的 Role 定义样例(常见动作类型:verbs: ["get", "list", "watch", "create", "update", "patch", "delete"])
GET /api/v1/namespaces/{namespace}/pods/{name}/log
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
写的越详细,权限越小
RoleBinding and ClusterRoleBinding
RoleBinding:
RoleBinding 可以将角色中定义的权限授予用户或用户组,RoleBinding 包含一组权限列表(subjects),权限列表中包含有不同形式的待授予权限资源类型(users, groups, or service accounts);RoloBinding 同样包含对被 Bind 的 Role 引用;RoleBinding 适用于某个命名空间内授权,而 ClusterRoleBinding 适用于集群范围内的授权
将 default 命名空间的pod-readerRole 授予 jane 用户,此后 jane 用户在 default 命名空间中将具有pod-reader的权限
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
ClusterRoleBinding
使用 ClusterRoleBinding 可以对整个集群中的所有命名空间资源权限进行授权;以下 ClusterRoleBinding 样例展示了授权 manager 组内所有用户在全部命名空间中对 secrets 进行访问
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
降级:
RoleBinding 同样可以引用 ClusterRole 来对当前 namespace 内用户、用户组或 ServiceAccount 进行授权,这种操作允许集群管理员在整个集群内定义一些通用的 ClusterRole,然后在不同的 namespace 中使用 RoleBinding 来引用
RoleBinding 同样可以引用 ClusterRole 来对当前 namespace 内用户、用户组或 ServiceAccount 进行授权,这种操作允许集群管理员在整个集群内定义一些通用的 ClusterRole,然后在不同的 namespace 中使用 RoleBinding 来引用
以下 RoleBinding 引用了一个 ClusterRole,这个 ClusterRole 具有整个集群内对 secrets 的访问权限;但是其授权用户dave只能访问 development 空间中的 secrets(因为 RoleBinding 定义在 development 命名空间)
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: read-secrets
namespace: dev # This only grants permissions within the "development" namespace.
subjects:
- kind: User
name: dave
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
to Subjects
RoleBinding 和 ClusterRoleBinding 可以将 Role 绑定到 Subjects;Subjects 可以是 groups、users 或者 service accounts
Subjects 中 Users 使用字符串表示,它可以是一个普通的名字字符串,如 “alice”;也可以是 email 格式的邮箱地址,如 “wangyanglinux@163.com”;甚至是一组字符串形式的数字 ID 。但是 Users 的前缀 system: 是系统保留的,集群管理员应该确保普通用户不会使用这个前缀格式
Groups 书写格式与 Users 相同,都为一个字符串,并且没有特定的格式要求;同样 system: 前缀为系统保留
集群默认角色:
默认 ClusterRole | 默认 ClusterRoleBinding | 描述 |
---|---|---|
cluster-admin | system:masters 组 | 允许超级用户在平台上的任何资源上执行所有操作。 当在 ClusterRoleBinding 中使用时,可以授权对集群中以及所有名字空间中的全部资源进行完全控制。 当在 RoleBinding 中使用时,可以授权控制角色绑定所在名字空间中的所有资源,包括名字空间本身。 |
admin | 无 | 允许管理员访问权限,旨在使用 RoleBinding 在名字空间内执行授权。如果在 RoleBinding 中使用,则可授予对名字空间中的大多数资源的读/写权限, 包括创建角色和角色绑定的能力。 此角色不允许对资源配额或者名字空间本身进行写操作。 此角色也不允许对 Kubernetes v1.22+ 创建的 EndpointSlices(或 Endpoints)进行写操作。 更多信息参阅 “EndpointSlices 和 Endpoints 写权限”小节。 |
edit | 无 | 允许对名字空间的大多数对象进行读/写操作。此角色不允许查看或者修改角色或者角色绑定。 不过,此角色可以访问 Secret,以名字空间中任何 ServiceAccount 的身份运行 Pod, 所以可以用来了解名字空间内所有服务账户的 API 访问级别。 此角色也不允许对 Kubernetes v1.22+ 创建的 EndpointSlices(或 Endpoints)进行写操作。 更多信息参阅 “EndpointSlices 和 Endpoints 写操作”小节。 |
view | 无 | 允许对名字空间的大多数对象有只读权限。 它不允许查看角色或角色绑定。此角色不允许查看 Secrets,因为读取 Secret 的内容意味着可以访问名字空间中 ServiceAccount 的凭据信息,进而允许利用名字空间中任何 ServiceAccount 的身份访问 API(这是一种特权提升)。 |
实践一:基于ServiceAcount
题目要求:
- 创建一个名为deployment-clusterrole的clusterrole,该clusterrole只允许创建Deployment、Daemonset、Statefulset的create操作
- 在名字为app-team1的namespace下创建一个名为cicd-token的serviceAccount,并且将上一步创建clusterrole的权限绑定到该serviceAccount
1、创建namespace和serviceAccount
[root@k8s-master01 examples]# kubectl create ns app-team1
namespace/app-team1 created
[root@k8s-master01 examples]# kubectl create sa cicd-token -n app-team1
serviceaccount/cicd-token created
2、创建clusterrole,授权动作和资源
[root@k8s-master01 ~]# kubectl create clusterrole/role NAME --verb=["get", "list", "watch", "create", "update", "patch", "delete"] --resource=deployments,statefulsets,daemonsets
clusterrole.rbac.authorization.k8s.io/deployment-clusterrole created
3、创建 rolebinding,绑定权限到SA
[root@k8s-master01 ~]# kubectl create rolebinding deployment-rolebinding --clusterrole=deployment-clusterrole --serviceaccount=app-team1:cicd-token -n app-team1
或者使用 yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployment-rolebinding
namespace: app-team1
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: deployment-clusterrole
subjects:
- kind: ServiceAccount
name: cicd-token
namespace: app-team1
4、创建deployment,使用 SA
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: app-team1
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: nginx
resources: {}
serviceAccountName: app-team1
restartPolicy: Always
也可以直接使用命令授权
kubectl -n app-team1 set serviceaccount deployments nginx-deployment cicd-token
实践二:基于用户仅能管理 dev 空间
实施思路:
- 用K8S CA签发客户端证书,也就是是用K8s根证书签发一个客户端证书。(客户端证书都是基于K8s根证书来生成的,根证书在搭建K8s集群时候就被创建了,且创建时生成的ca-config配置文件是被删除了,根证书位置在/etc/kubernetes/pki目录下,一个是在K8s层面上的根证书,一个是etcd是用的),然后生成客户端证书请求文件,使用cfssl工具来生成客户端证书
- 生成kubeconfig授权文件(集群信息、客户端信息、上下文信息)
- 创建RBAC权限策略
- 把kubeconfig文件给小弟就可以使用了。
证书生成过程:通过ca.crt和ca.key 以及devuser.json生成devuser-key.pem和devuser.pem -> 通过注入主机IP、devuser-key.pem和devuser.pem
cd /etc/kubernetes/pki/
vi devuser.json
{
"CN": "devuser",
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}
]
}
下载证书生成工具
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
mv cfssl_linux-amd64 /usr/local/bin/cfssl
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
mv cfssl-certinfo_linux-amd64 /usr/local/bin/cfssl-certinfo
chmod +x /usr/local/bin/cfssl*
通过ca生成 devuser 的证书和私钥
cfssl gencert -ca=ca.crt -ca-key=ca.key -profile=kubernetes devuser.json | cfssljson -bare devuser
设置集群参数,生成 devuser.kubeconfig
export KUBE_APISERVER="https://192.168.1.10:6443"
kubectl config set-cluster kubernetes \
--certificate-authority=ca.crt \
--embed-certs=true \
--server=${KUBE_APISERVER} \
--kubeconfig=devuser.kubeconfig
设置客户端认证参数
kubectl config set-credentials devuser \
--client-certificate=devuser.pem \
--client-key=devuser-key.pem \
--embed-certs=true \
--kubeconfig=devuser.kubeconfig
设置上下文参数
kubectl config set-context kubernetes \
--cluster=kubernetes \
--user=devuser \
--namespace=dev \
--kubeconfig=devuser.kubeconfig
添加命名空间:
kubectl create ns dev
创建角色,绑定权限到用户
kubectl config use-context kubernetes --kubeconfig=devuser.kubeconfig
kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=dev
上面那条命令类似于yaml方式创建,可以导出查看下
kubectl create rolebinding devuser-admin-binding --clusterrole=admin --user=devuser --namespace=dev --dry-run -o yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: devuser-admin-binding
namespace: dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: devuser
使用普通用户去登陆devuser
useradd devuser
passwd devuser
mkdir -p /home/devuser/.kube/
cp /etc/kubernetes/pki/devuser.kubeconfig /home/devuser/.kube/config
chown devuser:devuser -R /home/devuser/.kube
登陆devuser用户:
实践三:基于不同用户配置不同权限
如何进行授权?
如何进行用户管理?
在权限上可以选择创建多个ClusterRole分别具有不同权限,这样角色可以复用授权可以绑定到任何名称空间下,对于用户管理来说,在数据库中会有一个单独的user表,同理,需要分配一个单独的命名空间kube-users来作为用户的统一进行管理,所有用户都需要切换名称空间和查看pod权限,使用ClusterRileBinding绑定,然后使用RoleBinding将clusterrole和对应需要授权的user进行降级绑定操作,将具体的角色绑定到具体的名称空间进行授权
RBAC企业实战:不同用户不同权限
需求:
- 1. 用户ljh可以查看default、kube-system下Pod的日志
- 2. 用户howell可以在default下的Pod中执行命令,并且可以删除Pod
授权四个ClusterRole
# 命名空间切换和pod查看权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-readonly
rules:
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- metrics.k8s.io
resources:
- pods
verbs:
- get
- list
- watch
# 删除权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-delete
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- delete
# 执行命令权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-exec
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
# 查看日志权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-log
rules:
- apiGroups:
- ""
resources:
- pods
- pods/log
verbs:
- get
- list
- watch
创建用户管理命名空间:
kubectl create ns kube-users
将角色授予kube-users
名字空间中查看查看所有名称空间和pod权限(将角色授予名字空间中所有服务账户,如果你想要名字空间中所有应用都具有某角色,无论它们使用的什么服务账户, 可以将角色授予该名字空间的服务账户组。):
kubectl create clusterrolebinding namespace-readonly --clusterrole=namespace-readonly --serviceaccount=system:serviceaccounts:kube-users
创建用户:
kubectl create sa ljh howell -n kube-users
绑定权限:
# 用户ljh可以查看default、kube-system下Pod的日志
kubectl create rolebinding ljh-pod-log \
--clusterrole=pod-log --serviceaccount=kube-users:ljh --namespace=kube-system
kubectl create rolebinding ljh-pod-log \
--clusterrole=pod-log --serviceaccount=kube-users:ljh --namespace=default
# 用户howell可以在default下的Pod中执行命令,并且可以删除Pod
kubectl create rolebinding howell-pod-exec \
--clusterrole=pod-exec --serviceaccount=kube-users:howell --namespace=default
kubectl create rolebinding howell-pod-delete \
--clusterrole=pod-delete --serviceaccount=kube-users:howell --namespace=default
准入控制
准入控制概念:
准入控制是请求操作被持久化到 etcd 之前的 “拦截器”。准入控制模块由多个 “准入控制器” 构成,“准入控制器” 就是一段自定义代码,它们能够在请求对 K8s 创建、修改、删除或者连接一个 K8s 对象时生效。官方自带了 30 多个准入控制器可供使用,同时支持用户扩展。准入控制器的作用往往是检查请求的规范性或者赋予一些默认信息。例如,我们在创建一个 pod 时,准入控制器会检查提交的信息是否符合 pod 资源的规范,并对请求中没有明确规定的字段,设置对应的默认值填充到请求中。与前两个阶段不同的是,只要有一个 “准入校验” 逻辑未通过,那么请求就会被拒绝。若请求仅仅是读取一个对象,“准入控制器” 将不会生效。准入控制作用于 K8s 中的对象,通过校验规范和默认值的设置,能够保证系统的安全可靠。
准入控制是API Server的插件集合,通过添加不同的插件,实现额外的准入控制规则。甚至于API Server的一些主要的功能都需要通过 Admission Controllers 实现,比如 ServiceAccount
官方文档上有一份针对不同版本的准入控制器推荐列表,其中最新的 1.14 的推荐列表是:NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota
列举几个插件的功能:NamespaceLifecycle: 防止在不存在的 namespace 上创建对象,防止删除系统预置 namespace,删除 namespace 时,连带删除它的所有资源对象。
补充:持久化阶段。当我们的请求通过了前面三个阶段的校验,它会被转换为一个 K8s 对象相应的变更请求,最终持久化到 etcd 中。
k8s常见的资源管理方式:计算资源管理(Compute Resources)、资源的配置范围管理(LimitRange)和资源的配额管理(Resource Quotas)
- 计算资源管理(Compute Resources): 为Pod中的容器指定使用的计算资源(CPU和内存)。
- 资源的配置范围管理(LimitRange):可以对集群内Request和Limits的配置做一个全局的统一的限制,相当于批量设置了某一个范围内(某个命名空间)的Pod的资源使用限制。
- 资源的配额管理(Resource Quotas):可以为每一个命名空间(namespace)提供一个总体的资源使用限制,通过它可以限制命名空间中某个类型的对象的总数目上限,也可以设置命名空间中Pod可以使用到的计算资源的总上限。资源的配额管理有效解决了多用户或多个团队公用一个k8s集群时资源有效分配的问题。
资源配额ResourceQuota
资源配额是限制某个命名空间对资源使用的一个总量限制,比如内存、CPU、Pod数量等。
资源配额的重要性
非k8s管理员的项目组都会在自己的名称空间下创建多个资源,并且没用的资源没有回收,产生大量废弃资源,给指定的项目组分配指定的资源
ResourceQuota是Kubernetes中的一种资源限制机制,它用于对命名空间内的资源使用进行限制和配额控制。它的出现背景是为了解决以下问题:
- 多租户隔离:在Kubernetes中,多个团队或用户可以在同一个集群中共享资源。为了确保每个命名空间或用户不会无限制地使用资源,需要一种机制来实现资源的隔离和限制。ResourceQuota提供了这样的能力,可以为每个命名空间设置资源使用的上限。
- 防止资源滥用:资源滥用可能导致整个集群的性能下降或崩溃。通过设置ResourceQuota,可以限制每个命名空间或用户可以使用的资源数量,以防止某个应用程序或用户抢占过多的资源,从而保护集群的稳定性和可靠性。
- 预测和控制成本:在云计算环境中,资源的使用与成本直接相关。通过设置ResourceQuota,可以根据团队或用户的需求和预算来限制资源使用,从而更好地预测和控制成本。
用户可以对给定命名空间下的可被请求的 计算资源总量进行限制。其中配额机制所支持的资源类型:
资源名称 | 描述 |
---|---|
limits.cpu | 所有非终止状态的 Pod,其 CPU 限额总量不能超过该值。 |
limits.memory | 所有非终止状态的 Pod,其内存限额总量不能超过该值。 |
requests.cpu | 所有非终止状态的 Pod,其 CPU 需求总量不能超过该值。 |
requests.memory | 所有非终止状态的 Pod,其内存需求总量不能超过该值。 |
hugepages-<size> | 对于所有非终止状态的 Pod,针对指定尺寸的巨页请求总数不能超过此值。 |
cpu | 与 requests.cpu 相同。 |
memory | 与 requests.memory 相同。 |
对有限的一组资源上实施一般性的对象数量配额也是可能的。
资源名称 | 描述 |
---|---|
configmaps | 在该命名空间中允许存在的 ConfigMap 总数上限。 |
persistentvolumeclaims | 在该命名空间中允许存在的 PVC的总数上限。 |
pods | 在该命名空间中允许存在的非终止状态的 Pod 总数上限。Pod 终止状态等价于 Pod 的 .status.phase in (Failed, Succeeded) 为真。 |
replicationcontrollers | 在该命名空间中允许存在的 ReplicationController 总数上限。 |
resourcequotas | 在该命名空间中允许存在的 ResourceQuota 总数上限。 |
services | 在该命名空间中允许存在的 Service 总数上限。 |
services.loadbalancers | 在该命名空间中允许存在的 LoadBalancer 类型的 Service 总数上限。 |
services.nodeports | 在该命名空间中允许存在的 NodePort 类型的 Service 总数上限。 |
secrets | 在该命名空间中允许存在的 Secret 总数上限。 |
ResourceQuota配置样例展示:
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-test
labels:
app: resourcequota
spec:
hard:
pods: 50 # 限制最多启动Pod的个数
requests.cpu: 0.5 # 所有非终止状态的 Pod,其 CPU 限额总量不能超过该值
requests.memory: 512Mi # 所有非终止状态的 Pod,其内存限额总量不能超过该值。
limits.cpu: 5 # 所有非终止状态的 Pod,其 CPU 需求总量不能超过该值。
limits.memory: 16Gi # 所有非终止状态的 Pod,其内存需求总量不能超过该值。
requests.storage: 40Gi # 所有 PVC,存储资源的需求总量不能超过该值。
persistentvolumeclaims: 20 # 在该命名空间中所允许的 PVC 总量。
replicationcontrollers: 20
configmaps: 20
secrets: 20
services: 50 # 限制service数量
services.loadbalancers: "2" # 限制负载均衡数
services.nodeports: "10" # 设置端口数量
ResourceQuota作用于Pod,并且有命名空间限制
K8s资源限制LimitRange
只有ResourceQuota是不够的
默认情况下, Kubernetes 集群上的容器运行使用的计算资源没有限制。 使用 Kubernetes 资源配额, 管理员(也称为 集群操作者)可以在一个指定的命名空间内限制集群资源的使用与创建。 在命名空间中,一个 Pod最多能够使用命名空间的资源配额所定义的 CPU 和内存用量。 作为集群操作者或命名空间级的管理员,你可能也会担心如何确保一个 Pod 不会垄断命名空间内所有可用的资源。
一个 LimitRange(限制范围) 对象提供的限制能够做到:
- 在一个命名空间中实施对每个 Pod 或 Container 最小和最大的资源使用量的限制。
- 在一个命名空间中实施对每个 PersistentVolumeClaim能申请的最小和最大的存储空间大小的限制。
- 在一个命名空间中实施对一种资源的申请值和限制值的比值的控制。
- 设置一个命名空间中对计算资源的默认申请/限制值,并且自动的在运行时注入到多个 Container 中。
LimitRange之所以出现,一般只为应对两种常见场景:
场景一:假如我们通过ResourceQuota只限制了内存和CPU,没有限制Pod数量的情况,在CPU和内存为0时无限制地创建Pod,从而造成无法统计的情况。
场景二:假如一个Namespace分配了16核、64GB的空间,之后创建一个申请了requests.cpu为16、requests.memory为64GB的容器,那么单个Pod就能把整个Namespace的资源全部占用。
假如我们只限制内存和CPU,但是在pod中没有配置resources,则默认CPU和内存使用量还被认为0
LimitRange做了什么
pod中即使没有任何配置,可以为每个pod默认添加资源配置参数
LimitRange配置示例:默认的requests和limits
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-mem-limit-range
spec:
limits:
- default: # 默认limits配置
cpu: 1
memory: 512Mi
defaultRequest: # 默认requests配置
cpu: 0.5
memory: 256Mi
type: Container
限制内存和CPU requests和limits的范围
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-min-max-demo-lr
spec:
limits:
- max: # 内存CPU的最大配置
cpu: "800m"
memory: 1Gi
min: # 内存CPU的最小配置
cpu: "200m"
memory: 500Mi
type: Container
限制存储范围
apiVersion: v1
kind: LimitRange
metadata:
name: storagelimits
spec:
limits:
- type: PersistentVolumeClaim
max: # 最大PVC的空间
storage: 2Gi
min: # 最小PVC的空间
storage: 1Gi
注意:在配置了requests和limits参数时,会以自行配置的为准(如果没有超过LimitRanger的最大、最小限制的话)。如果配置了limits而没有配置requests,那么requests的默认值将被设置成limits配置的参数。
K8s服务质量QoS
虽然我们可以给没有配置requests和limits的Pod添加默认值,也可以限制资源请求的范围,但是在使用Kubernetes部署时,应用的部署和更新都会经过一系列的调度策略将应用部署在最合适的节点上,但是随着时间的推移,当时“最优”的节点可能已经不再是最佳选择,因为在该服务器上别的应用或者其他管理员部署的应用可能忘记了配置资源限制,所以在日积月累的消耗中,宿主机一些不可压缩的资源(比如内存、磁盘)的使用率将达到最高峰。假如内存达到最高峰时会引起OOMKilled故障,此时Kubelet会根据某些策略重启上面的容器用来避免宿主机宕机引来的风险,但是重启容器难免会带来服务中断的现象,如果重启的是比较重要的业务应用,这将是一个非常不好的体验。QoS就应运而生,可以保证在系统资源不够的情况下尽量保证一些比较重要的Pod不被杀死。
Resources配置的重要性
假如node01已经将资源占满,继续创建一个Deployment,如果没有配置Resources则会导致资源继续分配在node01上,导致内存溢出
Resources也并非万能
node01如果只有4c8g,只能满足pod的requests配置,当达到性能上限,就会杀一些pod,那么被杀死的pod顺序是什么呢
- Guaranteed:最高服务质量,当宿主机内存不够时,会先kill掉QoS为BestEffort和Burstable的Pod,如果内存还是不够,才会kill掉QoS为Guaranteed,该级别Pod的资源占用量一般比较明确,即requests的cpu和memory和limits的cpu和memory配置的一致。
- Burstable: 服务质量低于Guaranteed,当宿主机内存不够时,会先kill掉QoS为BestEffort的Pod,如果内存还是不够之后就会kill掉QoS级别为Burstable的Pod,用来保证QoS质量为Guaranteed的Pod,该级别Pod一般知道最小资源使用量,但是当机器资源充足时,还是想尽可能的使用更多的资源,即limits字段的cpu和memory大于requests的cpu和memory的配置。
- BestEffort:尽力而为,当宿主机内存不够时,首先kill的就是该QoS的Pod,用以保证Burstable和Guaranteed级别的Pod正常运行。
示例1:实现QoS为Guaranteed的Pod
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
namespace: qos-example
spec:
containers:
- name: qos-demo-ctr
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "200Mi"
cpu: "700m"
①Pod中的每个容器必须指定requests.memory和limits.memory,并且两者需要相等;
②Pod中的每个容器必须指定requests.cpu和limits.cpu,并且两者需要相等。
示例2:实现QoS为Burstable的Pod
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-2
namespace: qos-example
spec:
containers:
- name: qos-demo-2-ctr
image: nginx
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
①Pod不符合Guaranteed的配置要求;
②Pod中至少有一个容器配置了requests.cpu或requests.memory。
示例3:实现QoS为BestEffort的Pod
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-3
namespace: qos-example
spec:
containers:
- name: qos-demo-3-ctr
image: nginx
①不设置resources参数
发布者:LJH,转发请注明出处:https://www.ljh.cool/8084.html