k8s的Pod控制器详解

1 Pod控制器的介绍

  • 在kubernetes中,按照Pod的创建方式可以将其分为两类:
    • 自主式Pod:kubernetes直接创建出来的Pod,这种Pod删除后就没有了,也不会重建。
    • 控制器创建Pod:通过Pod控制器创建的Pod,这种Pod删除之后还会自动重建,这种用的是最多的。

Pod控制器: Pod控制器是管理Pod的中间层,使用了Pod控制器之后,我们只需要告诉Pod控制器,想要多少个什么样的Pod就可以了,它就会创建出满足条件的Pod并确保每一个Pod处于用户期望的状态,如果Pod在运行中出现故障,控制器会基于指定的策略重启或重建Pod。

在kubernetes中,有很多类型的Pod控制器,每种都有自己的适合的场景,常见的有下面这些:

  • ReplicationController:比较原始的Pod控制器,已经被废弃,由ReplicaSet替代。
  • ReplicaSet:保证指定数量的Pod运行,比如我们指定了3个pod,就一定保证是3个pod在运行,如果运行中有pod挂了,就会创建一个新的pod,并支持Pod数量变更,镜像版本变更。
  • Deployment:通过控制ReplicaSet来控制Pod,他更加高级,拥有ReplicaSet的全部功能,并支持滚动升级、版本回退。这个用的比较多
  • Horizontal Pod Autoscaler:可以根据集群负载自动调整Pod的数量,实现削峰填谷,流量过大就增加pod数量,流量下来就减少pod数量。
  • DaemonSet:在集群中的指定Node上都运行一个副本,一般用于守护进程类的任务,比如收集日志的程序,每个node都应该有且仅有一个。
  • Job:它创建出来的Pod只要完成任务就立即退出,用于执行一次性任务,比如准备一些资源,准备完了,就可以退出了。
  • CronJob:它创建的Pod会周期性的执行,用于执行周期性的任务,比如定期备份。
  • StatefulSet:管理有状态的应用。

    2 ReplicaSet(RS)

    2.1 概述

  • ReplicaSet的主要作用是保证一定数量的Pod能够正常运行,它会持续监听这些Pod的运行状态,一旦Pod发生故障,就会重启或重建。同时它还支持对Pod数量的扩缩容(当pod数量不够用的时候,可以直接编辑rs的文件来增加pod数量)版本镜像的升级(将当前镜像版本为nginx1.17.1,我们只需要编辑下rs把镜像版本升级为1.17.2,他就会把所有的pod的镜像都替换为1.17.2

云原生-第二部分 - 图11

  • ReplicaSet的资源清单文件:
    1. apiVersion: apps/v1 # 版本号
    2. kind: ReplicaSet # 类型
    3. metadata: # 元数据
    4. name: # rs名称
    5. namespace: # 所属命名空间
    6. labels: #标签
    7. controller: rs
    8. spec: # 详情描述
    9. replicas: 3 # 副本数量 ,默认值为1
    10. selector: # 选择器,通过它指定该控制器管理哪些po
    11. matchLabels: # Labels匹配规则
    12. app: nginx-pod
    13. matchExpressions: # Expressions匹配规则
    14. #这个表达式的意思就是 app 要 in values数组的值中,
    15. - {key: app, operator: In, values: [nginx-pod]}
    16. template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    17. #下边这些就是我们的pod的配置
    18. metadata:
    19. labels: #pod的标签
    20. app: nginx-pod
    21. spec:
    22. containers: #容器香瓜配置
    23. - name: nginx #容器名
    24. image: nginx:1.17.1 #镜像版本
    25. ports:
    26. - containerPort: 80
    对matchExpressions的解释: matchExpressions 是一个pod的选择器条件的列表,key就是我们要匹配的 标签的key。 operator使我们要进行的比较动作,值有:In, NotIn, Exists, and DoesNotExist。在In和NotIn的情况下,值的组必须不能为空。values就是我们 value的数组。比如下边这句:
    {key: app, operator: In, values: [nginx-pod]}
    他的意思就是 我们的key的值要为app,操作为in 要匹配的value的值必须位于values的数组中。

还有就是当 matchLabelsmatchExpressions共存是,他们是AND关系,也就是需要同时被满足

  • 在这里,需要新了解的配置项就是spec下面几个选项:

    • replicas:指定副本数量,其实就是当然rs创建出来的Pod的数量,默认为1.
    • selector:选择器,它的作用是建立Pod控制器和Pod之间的关联关系,采用了Label Selector机制(在Pod模块上定义Label,在控制器上定义选择器,就可以表明当前控制器能管理哪些Pod了)。
    • template:模板,就是当前控制器创建Pod所使用的模板,里面其实就是前面学过的Pod的定义。

      2.2 创建ReplicaSet

  • 创建pc-replicaset.yaml文件,内容如下:

    1. apiVersion: apps/v1 # 版本号
    2. kind: ReplicaSet # 类型
    3. metadata: # 元数据
    4. name: pc-replicaset # rs名称
    5. namespace: dev # 命名类型
    6. spec: # 详细描述
    7. replicas: 3 # 副本数量
    8. selector: # 选择器,通过它指定该控制器可以管理哪些Pod
    9. matchLabels: # Labels匹配规则
    10. app: nginx-pod
    11. template: # 模块 当副本数据不足的时候,会根据下面的模板创建Pod副本
    12. metadata:
    13. labels:
    14. app: nginx-pod
    15. spec:
    16. containers:
    17. - name: nginx # 容器名称
    18. image: nginx:1.17.1 # 容器需要的镜像地址
    19. ports:
    20. - containerPort: 80 # 容器所监听的端口
  • 创建rs:

kubectl create -f pc-replicaset.yaml
云原生-第二部分 - 图12

  • 查看rs:

kubectl get rs pc-replicaset -n dev -o wide
云原生-第二部分 - 图13
这里:

  • CURRENT的数量是指当前,也就是说他会从1 2 3 这么增长显示,只要你手速够快,运气够好 ,你就看得见
  • READY:它是准备按号提供服务的服务数量,上图中描述丢了服务两个字

  • 查看当前控制器创建出来的Pod(控制器创建出来的Pod的名称是在控制器名称后面拼接了-xxx随机码(五位数)):

kubectl get pod -n dev
云原生-第二部分 - 图14

2.3 扩缩容

  • 编辑rs的副本数量,修改spec:replicas:6即可。

kubectl edit rs pc-replicaset -n dev 这里 pc-replicaset使我们创建的控制器的名字
云原生-第二部分 - 图15
image.png

  • 使用scale命令实现扩缩容,后面加上—replicas=n直接指定目标数量即可。

kubectl scale rs pc-replicaset --replicas=2 -n dev
云原生-第二部分 - 图17
上述我们可以看见,只有两个pod在正常运行,四个pod正在删除,过了一会,就只剩下两个pod了

2.4 镜像升级

  • 编辑rs的容器镜像,修改spec:containers:image为nginx:1.17.2即可。

kubectl edit rs pc-replicaset -n dev
云原生-第二部分 - 图18
image.png
可以看到 ,版本升级成功了

  • 使用set命令实现镜像升级。

语法
kubectl set image rs rs名称 容器名称=镜像版本 -n 命名空间
kubectl set image rs pc-replicaset nginx=nginx:1.17.1 -n dev
云原生-第二部分 - 图20

2.5 删除ReplicaSet

  • 使用kubectl delete rs 命令会删除ReplicaSet和其管理的Pod。

在kubernetes删除ReplicaSet前,会将ReplicaSet的replicas调整为0,等到所有的Pod被删除后,再执行ReplicaSet对象的删除
kubectl delete rs pc-replicaset -n dev
云原生-第二部分 - 图21

  • 如果希望仅仅删除ReplicaSet对象(保留Pod),只需要在使用kubectl delete rs命令的时候添加—cascade=false选项(不推荐):

kubectl delete rs pc-replicaset -n dev --cascade=false
—cascade的意思就是是否级联删除,级联删除就是删除控制器的同时删除pod,不级联删除就是删除控制器但是不删除pod,--cascade=false的意思就是不级联删除。我们一般都是级联删除。。

  • 使用yaml直接删除(推荐):

kubectl delete -f pc-replicaset.yaml
image.png

3 Deployment(Deploy)

3.1 概述

  • 为了更好的解决服务编排的问题,kubernetes在v1.2版本开始,引入了Deployment控制器。值得一提的是,Deployment控制器并不直接管理Pod,而是通过管理ReplicaSet来间接管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment的功能比ReplicaSet强大。

云原生-第二部分 - 图23

  • Deployment的主要功能如下:
    • 支持ReplicaSet的所有功能。
    • 支持发布的停止、继续。
    • 支持版本滚动更新和版本回退。
  • Deployment的资源清单:

    1. apiVersion: apps/v1 # 版本号
    2. kind: Deployment # 类型
    3. metadata: # 元数据
    4. name: # rs名称
    5. namespace: # 所属命名空间
    6. labels: #标签
    7. controller: deploy
    8. spec: # 详情描述
    9. replicas: 3 # 副本数量
    10. revisionHistoryLimit: 3 # 保留历史版本,默认为10 ,更新一次镜像,就会产生一个版本。保留这个版本是为了版本回退
    11. paused: false # 暂停部署,默认是false 就是deploy创建成功后,是否要立刻创建pod。false就是不暂停,就是立刻开始部署
    12. progressDeadlineSeconds: 600 # 部署超时时间(s),默认是600
    13. strategy: # 镜像更新策略
    14. type: RollingUpdate # 滚动更新策略
    15. rollingUpdate: # 滚动更新
    16. maxSurge: 30% # 最大额外可以存在的副本数,可以为百分比,也可以为整数 maxUnavailable: 30% # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
    17. selector: # 选择器,通过它指定该控制器管理哪些pod
    18. matchLabels: # Labels匹配规则
    19. app: nginx-pod
    20. matchExpressions: # Expressions匹配规则
    21. - {key: app, operator: In, values: [nginx-pod]}
    22. template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
    23. metadata:
    24. labels:
    25. app: nginx-pod
    26. spec:
    27. containers:
    28. - name: nginx
    29. image: nginx:1.17.1
    30. ports:
    31. - containerPort: 80

    3.2 创建Deployment

  • 创建pc-deployment.yaml文件,内容如下:

    1. apiVersion: apps/v1 # 版本号
    2. kind: Deployment # 类型
    3. metadata: # 元数据
    4. name: pc-deployment # deployment的名称
    5. namespace: dev # 命名类型
    6. spec: # 详细描述
    7. replicas: 3 # 副本数量
    8. selector: # 选择器,通过它指定该控制器可以管理哪些Pod
    9. matchLabels: # Labels匹配规则
    10. app: nginx-pod
    11. template: # 模块 当副本数据不足的时候,会根据下面的模板创建Pod副本
    12. metadata:
    13. labels:
    14. app: nginx-pod
    15. spec:
    16. containers:
    17. - name: nginx # 容器名称
    18. image: nginx:1.17.1 # 容器需要的镜像地址
    19. ports:
    20. - containerPort: 80 # 容器所监听的端口
  • 创建Deployment:

kubectl create -f pc-deployment.yaml
云原生-第二部分 - 图24

  • 查看Deployment:

UP-TO-DATE 最新版本的Pod数量
# AVAILABLE 当前可用的Pod数量
# 之所以区分上述两个,是因为我们的镜像可能存在更新。新版本的镜像只有新启动的容器才会使用老的容器还是之间版本的镜像,所以就有上述两个参数的差别
kubectl get deploy pc-deployment -n dev
云原生-第二部分 - 图25

  • 查看ReplicaSet:

kubectl get rs -n dev
云原生-第二部分 - 图26

  • 查看Pod:

kubectl get pod -n dev
云原生-第二部分 - 图27
可以看到 pod的名字就是在rs的名字基础上,又拼上了5个随机符

3.3 扩缩容

  • 使用scale命令实现扩缩容:下列命令将pod的数量改为了5

kubectl scale deploy pc-deployment --replicas=5 -n dev
云原生-第二部分 - 图28

  • 编辑Deployment的副本数量,修改spec:replicas:4即可。

kubectl edit deployment pc-deployment -n dev
云原生-第二部分 - 图29

3.4 镜像更新

3.4.1 概述

  • Deployment支持两种镜像更新的策略:重建更新滚动更新(默认),可以通过strategy选项进行配置。

    重建更新:一次性删除所有老版本的pod,然后创建同样数量的新版本的pod 滚动更新:不一次性删除所有老的pod,而是删除一部分老的pod,创建一部分新的pod。这样滚动进行

  1. strategy: 指定新的Pod替代旧的Pod的策略,支持两个属性
  2. type: 指定策略类型,支持两种策略
  3. Recreate: 在创建出新的Pod之前会先杀掉所有已经存在的Pod
  4. RollingUpdate: 滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本的Pod
  5. #rollingUpdate: 当type为RollingUpdate的时候生效,用于为rollingUpdate设置参数,支持如下两个属性:
  6. maxUnavailable: 用来指定在升级过程中不可用的Pod的最大数量,默认为25%。
  7. maxSurge: 用来指定在升级过程中可以超过期望的Pod的最大数量,默认为25%,这个值的意思就是,我们滚动更新的时候可能存在老的容器正在停止状态,新的容器正在创建状态,此时pod的数量是会超过我们指定的数量,这个maxSurge就是用来限制这个可以超过的数量的最大值。

3.4.2 重建更新

  • 编辑pc-deployment.yaml文件,在spec节点下添加更新策略

    1. apiVersion: apps/v1 # 版本号
    2. kind: Deployment # 类型
    3. metadata: # 元数据
    4. name: pc-deployment # deployment的名称
    5. namespace: dev # 命名类型
    6. spec: # 详细描述
    7. replicas: 3 # 副本数量
    8. strategy: # 镜像更新策略 这里填了我们的更新策略
    9. type: Recreate # Recreate:在创建出新的Pod之前会先杀掉所有已经存在的Pod
    10. selector: # 选择器,通过它指定该控制器可以管理哪些Pod
    11. matchLabels: # Labels匹配规则
    12. app: nginx-pod
    13. template: # 模块 当副本数据不足的时候,会根据下面的模板创建Pod副本
    14. metadata:
    15. labels:
    16. app: nginx-pod
    17. spec:
    18. containers:
    19. - name: nginx # 容器名称
    20. image: nginx:1.17.1 # 容器需要的镜像地址
    21. ports:
    22. - containerPort: 80 # 容器所监听的端口
  • 更新Deployment:

kubectl apply -f pc-deployment.yaml

  • 镜像升级:

kubectl set image deployment pc-deployment nginx=nginx:1.17.2 -n dev
云原生-第二部分 - 图30
当我们执行完上述命令后,可以发现
image.png
上述中框起来的 老的容器占全部从Running状态变成了Terminating状态,标识所有老的容器全部一起正在停止
image.png
也可以看到,是在老容器全部确认杀死后,才开始了新的容器的创建过程

  • 查看升级过程:

kubectl get pod -n dev -w
云原生-第二部分 - 图33

3.4.3 滚动更新

  • 编辑pc-deployment.yaml文件,在spec节点下添加更新策略:

    1. apiVersion: apps/v1 # 版本号
    2. kind: Deployment # 类型
    3. metadata: # 元数据
    4. name: pc-deployment # deployment的名称
    5. namespace: dev # 命名类型
    6. spec: # 详细描述
    7. replicas: 3 # 副本数量
    8. strategy: # 镜像更新策略
    9. type: RollingUpdate # RollingUpdate:滚动更新,就是杀死一部分,就启动一部分,在更新过程中,存在两个版本的Pod
    10. rollingUpdate: #这里我们都是设置的默认值,这段配置加不加都一样,因为默认就是滚动更新
    11. maxUnavailable: 25%
    12. maxSurge: 25%
    13. selector: # 选择器,通过它指定该控制器可以管理哪些Pod
    14. matchLabels: # Labels匹配规则
    15. app: nginx-pod
    16. template: # 模块 当副本数据不足的时候,会根据下面的模板创建Pod副本
    17. metadata:
    18. labels:
    19. app: nginx-pod
    20. spec:
    21. containers:
    22. - name: nginx # 容器名称
    23. image: nginx:1.17.1 # 容器需要的镜像地址
    24. ports:
    25. - containerPort: 80 # 容器所监听的端口
  • 更新Deployment:

kubectl apply -f pc-deployment.yaml

  • 镜像升级:

kubectl set image deployment pc-deployment nginx=nginx:1.17.3 -n dev
云原生-第二部分 - 图34

  • 查看升级过程:

kubectl get pod -n dev -w
云原生-第二部分 - 图35
上图中我们从上往下看,发现它是先启动了一个新的pod,然后才开始停止一个老的pod。可以发现它是启动一个新的,停止一个老的这么进行的。

  • 滚动更新的过程:

云原生-第二部分 - 图36

  • 镜像更新中rs的变化:
  • 查看rs,发现原来的rs依旧存在,只是Pod的数量变为0,而后又产生了一个rs,Pod的数量变为 其实这就是deployment能够进行版本回退的奥妙所在
    kubectl get rs -n dev

云原生-第二部分 - 图37

3.5 版本回退

在开始版本回退之前,我们先看一下deployment的内部是如何进行镜像升级的
首先我们先升级一下镜像的版本;
image.png
然后我们不在聚焦于pod的id,而是pod前面的那一串rs的id
image.png
通过这个id我们可以发现,新启动的pod(镜像升级过得)和老的pod的rs并不是同一个id
也就是说,他们并不属于同一个rs
所以说,升级的过程,其实是新创建了一个rs,在新的rs中进行的,老的rs和其中的pod不再被使用
image.png

通过查看rs我们也可以发现,老的rs中的pod已经没有了。新的rs中的pod变成了我们定义的3个。但是我们也可以看到,虽然老的rs中已经没有pod了,但是他并没有被删除,这就是版本回退的关键点

版本还原的时候,只需要将当前rs中的pod逐一停止,把指定版本的rs中的pod在逐一启动即可。

  • Deployment支持版本升级过程中的暂停、继续功能以及版本回退等诸多功能,下面具体来看:

    1. # 版本升级相关功能
    2. kubetl rollout 参数 deploy xx # 支持下面的选择
    3. # status 显示当前升级的状态,比如升级是否成功
    4. # history 显示升级历史记录
    5. # pause 暂停版本升级过程
    6. # resume 继续已经暂停的版本升级过程 配合pause使用
    7. # restart 重启版本升级过程,配合pause使用
    8. # undo 回滚到上一级版本 (可以使用--to-revision回滚到指定的版本)
  • 查看当前升级版本的状态:

kubectl rollout status deployment pc-deployment -n dev
云原生-第二部分 - 图41

  • 查看升级历史记录:

kubectl rollout history deployment pc-deployment -n dev
云原生-第二部分 - 图42
上述之所以出现none是因为我们在创建deployment的时候 没有加 --record这个参数,加了这个参数就能正确显示了:
image.png
这个参数的使用方式为:可以看到是创建的时候使用的
image.png
当我们使用镜像升级的时候,手速快的情况下,还能看到升级过程的提示:
image.png
在查看历史状态 ,也可以发现多了一个版本:
image.png

  • 版本回退:

可以使用-to-revision=1回退到1版本,如果省略这个选项,就是回退到上个版本,即2版本(注意:这个是相对情况,因为我们当前有三个版本,所以他说是2)
kubectl rollout undo deployment pc-deployment --to-revision=1 -n dev
云原生-第二部分 - 图47
回退成功之后,再次查看历史记录,发现1版本已经没了,变成4版本了:
image.png

deployment之所以能够实现版本的回退,就是通过记录下历史的ReplicaSet来实现的,一旦想回滚到那个版本,只需要将当前版本的Pod数量降为0,然后将回退版本的Pod提升为目标数量即可。

3.6 金丝雀发布

  • Deployment支持更新过程中的控制,如暂停更新操作(pause)或继续更新操作(resume)。
  • 例如有一批新的Pod资源创建完成后立即暂停更新过程,此时,仅存在一部分新版本的应用,主体部分还是旧的版本。然后,再筛选一小部分的用户请求到新版本的Pod应用,继续观察能够稳定的按照期望的方式运行,如果没有问题之后再继续完成余下的Pod资源的滚动更新,否则立即回滚操作。这里注意有新的pod在运行,老的pod也都在运行,没有停止
  • 更新Deployment的版本,并配置暂停Deployment:

kubectl set image deployment pc-deployment nginx=nginx:1.17.4 -n dev && kubectl rollout pause deployment pc-deployment -n dev
image.png

  • 观察更新状态:

kubectl rollout status deployment pc-deployment -n dev
云原生-第二部分 - 图50
更新状态显示,正在登台更新,并提示了总共需要更新三个,单只完成了一个。

  • 监控更新的过程,可以看到已经新增了一个资源,但是并没有按照预期的状态去删除一个旧的资源,因为使用了pause暂停命令:

kubectl get rs -n dev -o wide
云原生-第二部分 - 图51

  • 查看Pod:

kubectl get pod -n dev
云原生-第二部分 - 图52

  • 确保更新后的新版本的Pod没问题之后,继续更新:

kubectl rollout resume deployment pc-deployment -n dev
云原生-第二部分 - 图53

此时查看我们的更新状态显示:
image.png
可以看到它更新的提示,新的在创建。老的再终止

  • 查看最后的更新情况:

kubectl get rs -n dev -o wide
kubectl get pod -n dev
云原生-第二部分 - 图55
金丝雀回退:
当我们发现版本升级有问题,想要去掉当前金丝雀发布的时候,可以使用回退指令,首先我们可以确定,金丝雀发布的方式,也会产生一个历史记录,如下图中的4版本所示:
image.png
image.png
我们只需要回退到3版本就可以了:
kubectl rollout resume deployment pc-deployment -n dev && kubectl rollout undo deployment pc-deployment --to-revision=3 -n dev
注意回退的时候,要先继续运行 暂停的pod,否则会抛出异常:
image.png

3.7 删除Deployment

  • 删除Deployment,其下的ReplicaSet和Pod也会一起被删除:

kubectl delete -f pc-deployment.yaml
云原生-第二部分 - 图59

4 Horizontal Pod Autoscaler(HPA)

4.1 概述

  • 我们已经可以通过手动执行kubectl scale命令实现Pod的扩缩容,但是这显然不符合kubernetes的定位目标–自动化和智能化。kubernetes期望可以通过监测Pod的使用情况,实现Pod数量的自动调整,于是就产生了HPA这种控制器。
  • HPA可以获取每个Pod的利用率(需要我们定义指标值,比如cpu大于30%),然后和HPA中定义的指标进行对比,同时计算出需要伸缩的具体值,最后实现Pod的数量的调整。其实HPA和之前的Deployment一样,也属于一种kubernetes资源对象,它通过追踪分析目标Pod的负载变化情况,来确定是否需要针对性的调整目标Pod的副本数。

他就是把我们手动调整pod的方式变成了自动的调整
云原生-第二部分 - 图60
上图中我们可以发现,他是在Deployment的基础上 又增加了一层,也就是说他更加的高级,是控制Deployment的

4.2 安装metrics-server(v0.3.6)

  • metrics-server可以用来收集集群中的资源使用情况(不仅限于pod)。
  • 获取metrics-server,需要注意使用的版本(网路不行,请点这里📎v0.3.6.tar.gz):

wget https://github.com/kubernetes-sigs/metrics-server/archive/v0.3.6.tar.gz

  • 解压v0.3.6.tar.gz文件:

tar -zxvf v0.3.6.tar.gz

  • 进入metrics-server-0.3.6/deploy/1.8+/目录:

cd metrics-server-0.3.6/deploy/1.8+/

  • 修改metrics-server-deployment.yaml文件:

vim metrics-server-deployment.yaml
在上边的这个文件中,按图中添加下面选项

  1. hostNetwork: true
  2. #这里是调整镜像的下载仓库
  3. image: registry.cn-hangzhou.aliyuncs.com/google_containers/metrics-server-amd64:v0.3.6
  4. args:
  5. - --kubelet-insecure-tls
  6. - --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP

云原生-第二部分 - 图61

  • 安装metrics-server:

kubectl apply -f ./ #这里要和metrics-server-deployment.yaml这个配置文件在同一个目录下
云原生-第二部分 - 图62

  • 查看metrics-server生成的Pod:

kubectl get pod -n kube-system
云原生-第二部分 - 图63

  • 查看资源使用情况(因为数据采集需要时间,所以我们这里需要稍微等待一下):

kubectl top node 这里查看的是node的情况
kubectl top pod -n kube-system 这里查看的是指定的命名空间中 所有pod的资源使用情况
云原生-第二部分 - 图64
上图中cpu一列中 m为 :cpu一核=1000m。一次可以计算出占用的cpu百分比

4.3 安装metrics-server(v0.4.1)

  • 获取metrics-server:

wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.4.1/components.yaml

  • 修改components.yaml(修改之后的components.yaml文件📎components.yaml):

    1. apiVersion: apps/v1
    2. kind: Deployment
    3. metadata:
    4. labels:
    5. k8s-app: metrics-server
    6. name: metrics-server
    7. namespace: kube-system
    8. spec:
    9. selector:
    10. matchLabels:
    11. k8s-app: metrics-server
    12. strategy:
    13. rollingUpdate:
    14. maxUnavailable: 0
    15. template:
    16. metadata:
    17. labels:
    18. k8s-app: metrics-server
    19. spec:
    20. containers:
    21. - args:
    22. - --cert-dir=/tmp
    23. - --secure-port=4443
    24. - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    25. - --kubelet-use-node-status-port
    26. # 修改部分
    27. - --kubelet-insecure-tls
    28. # 修改部分
    29. image: registry.cn-shanghai.aliyuncs.com/xuweiwei-kubernetes/metrics-server:v0.4.1

    云原生-第二部分 - 图65

  • 安装metrics-server:

kubectl apply -f components.yaml

4.4 准备Deployment和Service

  • 创建Deployment:

    • 创建nginx.yaml文件,内容如下:

      1. apiVersion: apps/v1 # 版本号
      2. kind: Deployment # 类型
      3. metadata: # 元数据
      4. name: nginx # deployment的名称
      5. namespace: dev # 命名类型
      6. spec: # 详细描述
      7. selector: # 选择器,通过它指定该控制器可以管理哪些Pod
      8. matchLabels: # Labels匹配规则
      9. app: nginx-pod
      10. template: # 模块 当副本数据不足的时候,会根据下面的模板创建Pod副本
      11. metadata:
      12. labels:
      13. app: nginx-pod
      14. spec:
      15. containers:
      16. - name: nginx # 容器名称
      17. image: nginx:1.17.1 # 容器需要的镜像地址
      18. ports:
      19. - containerPort: 80 # 容器所监听的端口
      20. resources: # 资源限制
      21. requests:
      22. cpu: "100m" # 100m表示100millicpu,即0.1个CPU
    • 创建Deployment:

kubectl create -f nginx.yaml
云原生-第二部分 - 图66

  • 查看Deployment和Pod:

kubectl get pod,deploy -n dev
云原生-第二部分 - 图67

  • 创建Service:
    • 创建Service:方便对pod集群进行测试

kubectl expose deployment nginx --name=nginx --type=NodePort --port=80 --target-port=80 -n dev
云原生-第二部分 - 图68

  • 查看Service:

kubectl get svc -n dev
云原生-第二部分 - 图69

4.5 部署HPA

  • 创建pc-hpa.yaml文件,内容如下:

    1. apiVersion: autoscaling/v1 # 版本号
    2. kind: HorizontalPodAutoscaler # 类型
    3. metadata: # 元数据
    4. name: pc-hpa # deployment的名称
    5. namespace: dev # 命名空间
    6. spec:
    7. minReplicas: 1 # 最小Pod数量
    8. maxReplicas: 10 # 最大Pod数量,自动扩容的最大数量值
    9. targetCPUUtilizationPercentage: 3 # CPU使用率指标,这里3是3%。这里是为了方便压测
    10. scaleTargetRef: # 指定要控制的Nginx的信息。这里是有一个叫nginx的deployment控制器,这个deployment控制器要必须已存在。本次文档中,4.4已经创建好了这个deploy
    11. apiVersion: apps/v1
    12. kind: Deployment
    13. name: nginx
  • 创建hpa:

kubectl create -f pc-hpa.yaml
云原生-第二部分 - 图70

  • 查看hpa:

kubectl get hpa -n dev
云原生-第二部分 - 图71
我们还能会看到如下图所示的样子
image.png
这就标识他还在进行当前cpu使用率的计算,一般是刚创建hpa的时候才会出现这种情况。

4.6 测试

  • 使用压测工具如Jmeter对service的地址http://192.168.18.100:30395进行压测,然后通过控制台查看hpa和pod的变化。
  • hpa的变化:

kubectl get hpa -n dev -w

当压测到一定程度时候,可以看到hpa的cpu使用率发生了变化:
image.png
从pod上来看,也能发现在新创建pod:
image.png
然后我们从deploy中也可以看出来它内部的pod数量变多了:
image.png
我们可以发现,它会随着压力的增大,创建的越来越多。
云原生-第二部分 - 图76

  • Deployment的变化:

kubectl get deployment -n dev -w
云原生-第二部分 - 图77

  • Pod的变化:

kubectl get pod -n dev -w
云原生-第二部分 - 图78
当我们的使用率降到2%的时候:
image.png
pod并不会直接终止,他会等待一段时间,因为他创建和销毁pod都是需要占用资源的,他等一会是防止流量刚降下去,突然又变高了
经过一段时间等待,他就会变化回来:
image.png

5 DaemonSet(DS)

5.1 概述

  • DaemonSet类型的控制器可以保证集群中的每一台(或指定)节点上都运行一个副本,一般适用于日志收集、节点监控等场景。也就是说,如果一个Pod提供的功能是节点级别的(每个节点都需要且只需要一个),那么这类Pod就适合使用DaemonSet类型的控制器创建。

云原生-第二部分 - 图81

  • DaemonSet控制器的特点:
    • 每向集群中添加一个节点的时候,指定的Pod副本也将添加到该节点上。
    • 当节点从集群中移除的时候,Pod也会被垃圾回收。
  • DaemonSet的资源清单: ```yaml apiVersion: apps/v1 # 版本号 kind: DaemonSet # 类型 metadata: # 元数据 name: # 名称 namespace: #命名空间 labels: #标签 controller: daemonset spec: # 详情描述 revisionHistoryLimit: 3 # 保留历史版本 updateStrategy: # 更新策略 type: RollingUpdate # 滚动更新策略 rollingUpdate: # 滚动更新 maxUnavailable: 1 # 最大不可用状态的Pod的最大值,可用为百分比,也可以为整数 selector: # 选择器,通过它指定该控制器管理那些Pod matchLabels: # Labels匹配规则 app: nginx-pod matchExpressions: # Expressions匹配规则
  • key: app operator: In values:
  • nginx-pod template: # 模板,当副本数量不足时,会根据下面的模板创建Pod模板 metadata: labels: app: nginx-pod spec: containers:
  • name: nginx image: nginx:1.17.1 ports:

    1. - containerPort: 80

    ```

    5.2 创建DaemonSet

  • 创建pc-daemonset.yaml文件,内容如下:他会在每个node上边都创建一个nginx。如果我们新加入一个node,他也会在我们新加入的node上创建这个pod

    1. apiVersion: apps/v1 # 版本号
    2. kind: DaemonSet # 类型
    3. metadata: # 元数据
    4. name: pc-damonset # 名称
    5. namespace: dev #命名空间
    6. spec: # 详情描述
    7. selector: # 选择器,通过它指定该控制器管理那些Pod
    8. matchLabels: # Labels匹配规则
    9. app: nginx-pod
    10. template: # 模板,当副本数量不足时,会根据下面的模板创建Pod模板
    11. metadata:
    12. labels:
    13. app: nginx-pod
    14. spec:
    15. containers:
    16. - name: nginx
    17. image: nginx:1.17.1
    18. ports:
    19. - containerPort: 80
  • 创建DaemonSet:

kubectl create -f pc-daemonset.yaml
云原生-第二部分 - 图82

5.3 查看DaemonSet

  • 查看DaemonSet:

kubectl get ds -n dev -o wide
云原生-第二部分 - 图83
通过get pod可以发现,(画红框的)他在每个node上都创建了一个pod
image.png

5.4 删除DaemonSet

  • 删除DaemonSet:

kubectl delete ds pc-damonset -n dev
云原生-第二部分 - 图85

6 Job

6.1 概述

  • Job主要用于负责批量处理(可以指定数量)短暂的一次性任务。
  • Job的特点:
    • 当Job创建的Pod执行成功结束时,Job将记录成功结束的Pod数量。
    • 当成功结束的Pod达到指定的数量时,Job将完成执行。

Job可以保证指定数量的Pod执行完成。
云原生-第二部分 - 图86

  • Job的资源清单:

    1. apiVersion: batch/v1 # 版本号 spec之前是Job的资源描述
    2. kind: Job # 类型
    3. metadata: # 元数据
    4. name: # 名称
    5. namespace: #命名空间
    6. labels: # 标签
    7. controller: job
    8. spec: # 详情描述
    9. completions: 1 # 指定Job需要成功运行Pod的总次数,默认为1
    10. parallelism: 1 # 指定Job在任一时刻应该并发运行Pod的数量,默认为1
    11. activeDeadlineSeconds: 30 # 指定Job可以运行的时间期限,超过时间还没结束,系统将会尝试进行终止
    12. #上述的三个比较常用
    13. backoffLimit: 6 # 指定Job失败后进行重试的次数,默认为6
    14. manualSelector: true # 是否可以使用selector选择器选择Pod,默认为false
    15. selector: # 选择器,通过它指定该控制器管理那些Pod
    16. matchLabels: # Labels匹配规则
    17. app: counter-pod
    18. matchExpressions: # Expressions匹配规则
    19. - key: app
    20. operator: In
    21. values:
    22. - counter-pod
    23. template: # 模板,当副本数量不足时,会根据下面的模板创建Pod模板
    24. metadata:
    25. labels:
    26. app: counter-pod
    27. spec:
    28. restartPolicy: Never # 重启策略只能设置为Never或OnFailure。之所有只有这两个重启策略,是因为我们job类型的pod是有一定特殊性的。
    29. containers:
    30. - name: counter
    31. image: busybox:1.30
    32. command: ["/bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1;do echo $i;sleep 20;done"]

    关于模板中的重启策略的说明:

  • 如果设置为OnFailure,则Job会在Pod出现故障的时候重启容器,而不是创建Pod,failed次数不变。

  • 如果设置为Never,则Job会在Pod出现故障的时候创建新的Pod,并且故障Pod不会消失,也不会重启,failed次数+1。
  • 如果指定为Always的话,就意味着一直重启,意味着Pod任务会重复执行,这和Job的定义冲突,所以不能设置为Always。

    6.2 创建Job

  • 创建pc-job.yaml文件,内容如下:

    1. apiVersion: batch/v1 # 版本号
    2. kind: Job # 类型
    3. metadata: # 元数据
    4. name: pc-job # 名称
    5. namespace: dev #命名空间
    6. spec: # 详情描述
    7. manualSelector: true # 是否可以使用selector选择器选择Pod,默认为false
    8. selector: # 选择器,通过它指定该控制器管理那些Pod
    9. matchLabels: # Labels匹配规则
    10. app: counter-pod
    11. template: # 模板,当副本数量不足时,会根据下面的模板创建Pod模板
    12. metadata:
    13. labels:
    14. app: counter-pod
    15. spec:
    16. restartPolicy: Never # 重启策略只能设置为Never或OnFailure
    17. containers:
    18. - name: counter
    19. image: busybox:1.30
    20. command: [ "/bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1;do echo $i;sleep 3;done" ]
  • 创建Job:

kubectl create -f pc-job.yaml
云原生-第二部分 - 图87

6.3 查看Job

  • 查看Job:

kubectl get job -n dev -w
云原生-第二部分 - 图88
注意上述不是说有三个jod。他加了-w参数,是处于一直打印状态,实际上只有一个jod。

COMPLETIONS的意思就是 0/1中的1表示,一共有多少个任务,0表示成功执行的任务。
也就是说我们当前设置了一个任务,有0个执行成功了。
29s以后,成功的任务数就变成一个了

  • 查看Pod:

kubectl get pod -n dev -w
云原生-第二部分 - 图89
上图中我们可以看到,STATUS中状态变成Completed就是这个任务已经执行完了,此时pod也停止运行了。
我们在来看看job特有的两个参数
image.png
这两个参数对任务执行的影响:
此时我们再次创建这个控制器后,查看job的输出:
kubectl get job -n dev -w
image.png
可以看到job中待执行的任务数变成了六个,然后我们看看一共有几个pod同时运行:
image.png

可以发现正好是3个
然后我们也可以考看到,他也是三个三个的吧任务执行完成:
image.png
image.png

6.4 删除Job

  • 删除Job:

kubectl delete -f pc-job.yaml
云原生-第二部分 - 图95

7 CronJob(CJ)

7.1 概述

  • CronJob控制器以Job控制器为其管控对象,并借助它管理Pod资源对象,Job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类似Linux操作系统的周期性任务作业计划的方式控制器运行时间点重复运行的方式,换言之,CronJob可以在特定的时间点反复去执行Job任务。

云原生-第二部分 - 图96

  • CronJob的资源清单:

    1. apiVersion: batch/v1beta1 # 版本号
    2. kind: CronJob # 类型
    3. metadata: # 元数据
    4. name: # 名称
    5. namespace: #命名空间
    6. labels:
    7. controller: cronjob
    8. spec: # CronJob详情描述
    9. schedule: # cron格式的作业调度运行时间点,用于控制任务任务时间执行
    10. concurrencyPolicy: # 并发执行策略
    11. failedJobsHistoryLimit: # 为失败的任务执行保留的历史记录数,默认为1
    12. successfulJobsHistoryLimit: # 为成功的任务执行保留的历史记录数,默认为3
    13. jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象,下面其实就是job的定义
    14. metadata: {}
    15. spec:
    16. completions: 1 # 指定Job需要成功运行Pod的总次数,默认为1
    17. parallelism: 1 # 指定Job在任一时刻应该并发运行Pod的数量,默认为1
    18. activeDeadlineSeconds: 30 # 指定Job可以运行的时间期限,超过时间还没结束,系统将会尝试进行终止
    19. backoffLimit: 6 # 指定Job失败后进行重试的次数,默认为6
    20. template: # 模板,当副本数量不足时,会根据下面的模板创建Pod模板
    21. spec:
    22. restartPolicy: Never # 重启策略只能设置为Never或OnFailure
    23. containers:
    24. - name: counter
    25. image: busybox:1.30
    26. command: [ "/bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1;do echo $i;sleep 20;done" ]

    schedule:cron表达式,用于指定任务的执行时间。

          • *:这些星号从左到右分别表示分钟 小时 日 月份 星期。
  • 1 表示每个小时的第一分钟运行
  • */1 * * * * 表示每一分钟。 注意和上边的区别。
  • 分钟的值从0到59。
  • 小时的值从0到23。
  • 日的值从1到31。
  • 月的值从1到12。
  • 星期的值从0到6,0表示星期日。
  • 多个时间可以用逗号隔开,范围可以用连字符给出:* 可以作为通配符,/表示每…

concurrencyPolicy:并发执行策略

  • Allow:运行Job并发运行(默认)。
  • Forbid:禁止并发运行,如果上一次运行尚未完成,则跳过下一次运行。
  • Replace:替换,取消当前正在运行的作业并使用新作业替换它。

    7.2 创建CronJob

  • 创建pc-cronjob.yaml文件,内容如下:

    1. apiVersion: batch/v1beta1 # 版本号
    2. kind: CronJob # 类型
    3. metadata: # 元数据
    4. name: pc-cronjob # 名称
    5. namespace: dev #命名空间
    6. spec: # 详情描述
    7. schedule: "*/1 * * * * " # cron格式的作业调度运行时间点,用于控制任务任务时间执行
    8. jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象,下面其实就是job的定义
    9. metadata: {}
    10. spec:
    11. template: # 模板,当副本数量不足时,会根据下面的模板创建Pod模板
    12. spec:
    13. restartPolicy: Never # 重启策略只能设置为Never或OnFailure
    14. containers:
    15. - name: counter
    16. image: busybox:1.30
    17. command: [ "/bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1;do echo $i;sleep 2;done" ]
  • 创建CronJob:

kubectl create -f pc-cronjob.yaml
云原生-第二部分 - 图97

7.3 查看CronJob

  • 查看CronJob:

kubectl get cronjob -n dev -w
云原生-第二部分 - 图98
关于上图中SUSPEND和ACTIVE的解释:

  • SUSPEND:如果其值为True表示此CronJob暂时失效,不变成False之前不再创建新任务。对于已经创建的任务没有影响。
  • ACTIVE:表示当前活动的任务数,0表示当前没有活动任务。1表示有一个活动任务。此值可能大于1,原因如下:
    • 1.任务允许重复启动,如前一次启动后还没有退出,下一次已经启动。
    • 2.允许延后启动,当CronJob Controller发现因为某种原因错误启动,并且任务允许延后启动,则会启动任务。
  • 查看Job:

kubectl get job -n dev -w
云原生-第二部分 - 图99

  • 查看Pod:

kubectl get pod -n dev -w
云原生-第二部分 - 图100

7.4 删除CronJob

  • 删除CronJob:

kubectl delete -f pc-cronjob.yaml
云原生-第二部分 - 图101

8 StatefulSet(有状态)

8.1 概述

  • 无状态应用:
    • 认为Pod都是一样的。
    • 没有顺序要求。
    • 不用考虑在哪个Node节点上运行。
    • 随意进行伸缩和扩展。
  • 有状态应用:
    • 有顺序的要求。
    • 认为每个Pod都是不一样的。
    • 需要考虑在哪个Node节点上运行。
    • 需要按照顺序进行伸缩和扩展。
    • 让每个Pod都是独立的,保持Pod启动顺序和唯一性。
  • StatefulSet是Kubernetes提供的管理有状态应用的负载管理控制器。
  • StatefulSet部署需要HeadLinessService(无头服务)。

为什么需要HeadLinessService(无头服务)?

  • 在用Deployment时,每一个Pod名称是没有顺序的,是随机字符串,因此是Pod名称是无序的,但是在StatefulSet中要求必须是有序 ,每一个Pod不能被随意取代,Pod重建后pod名称还是一样的。
  • 而Pod IP是变化的,所以是以Pod名称来识别。Pod名称是Pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称 。
  • StatefulSet常用来部署RabbitMQ集群、Zookeeper集群、MySQL集群、Eureka集群等。

    8.2 创建StatefulSet

  • 创建pc-stateful.yaml文件,内容如下: ```yaml apiVersion: v1 kind: Service metadata: name: service-headliness namespace: dev spec: selector: app: nginx-pod clusterIP: None # 将clusterIP设置为None,即可创建headliness Service type: ClusterIP ports:

    • port: 80 # Service的端口 targetPort: 80 # Pod的端口

apiVersion: apps/v1 kind: StatefulSet metadata: name: pc-statefulset namespace: dev spec: replicas: 3 serviceName: service-headliness selector: matchLabels: app: nginx-pod template: metadata: labels: app: nginx-pod spec: containers:

  1. - name: nginx
  2. image: nginx:1.17.1
  3. ports:
  4. - containerPort: 80
  1. - 创建StatefulSet
  2. `kubectl create -f pc-stateful.yaml`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610089549869-21f5ab78-5857-4965-9f23-320ee6fd08fc.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=hzLWN&margin=%5Bobject%20Object%5D&originHeight=81&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  3. <a name="vR2iD"></a>
  4. ## 8.3 查看StatefulSet
  5. - 查看StatefulSet
  6. `kubectl get statefulset pc-statefulset -n dev -o wide`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610089596026-4cf0982e-39c7-4642-8954-b36aeb134c99.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=OahXk&margin=%5Bobject%20Object%5D&originHeight=69&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  7. - 查看Pod
  8. `kubectl get pod -n dev -o wide`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610089644029-920a2816-3cf1-4400-9346-4b3f36839271.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=o9WQJ&margin=%5Bobject%20Object%5D&originHeight=96&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  9. <a name="ZMHKB"></a>
  10. ## 8.4 删除StatefulSet
  11. - 删除StatefulSet
  12. `kubectl delete -f pc-stateful.yaml`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610089691535-90d6e0df-d274-44b3-8ba4-99c623f5c280.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=EiL06&margin=%5Bobject%20Object%5D&originHeight=64&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  13. <a name="qY17j"></a>
  14. ## 8.5 Deployment和StatefulSet的区别
  15. - DeploymentStatefulSet的区别:Deployment没有唯一标识而StatefulSet有唯一标识。
  16. - StatefulSet的唯一标识是根据主机名+一定规则生成的。
  17. - StatefulSet的唯一标识是主机名.无头Service名称.命名空间.svc.cluster.local
  18. <a name="ogsbN"></a>
  19. ## 8.6 StatefulSet的金丝雀发布
  20. - StatefulSet支持两种更新策略:OnDeleteRollingUpdate(默认),其中OnDelete表示删除之后才更新,RollingUpdate表示滚动更新。
  21. ```yaml
  22. updateStrategy:
  23. rollingUpdate: # 如果更新的策略是OnDelete,那么rollingUpdate就失效
  24. partition: 2 # 表示从第2个分区开始更新,默认是0
  25. type: RollingUpdate /OnDelete # 滚动更新
  • 示例:pc-statefulset.yaml ```yaml apiVersion: v1 kind: Service metadata: name: service-headliness namespace: dev spec: selector: app: nginx-pod clusterIP: None # 将clusterIP设置为None,即可创建headliness Service type: ClusterIP ports:
    • port: 80 # Service的端口 targetPort: 80 # Pod的端口

apiVersion: apps/v1 kind: StatefulSet metadata: name: pc-statefulset namespace: dev spec: replicas: 3 serviceName: service-headliness selector: matchLabels: app: nginx-pod template: metadata: labels: app: nginx-pod spec: containers:

  1. - name: nginx
  2. image: nginx:1.17.1
  3. ports:
  4. - containerPort: 80

updateStrategy: rollingUpdate: partition: 0 type: RollingUpdate

  1. 主要介绍kubernetes的流量负载组件:ServiceIngress
  2. <a name="jZvpX"></a>
  3. # **K8s的Service详解**
  4. 这一章主要介绍k8s的流量负载组件:serviceIngressService是工作在四层的负载均衡。Ingress是七层负载
  5. <a name="t8sHD"></a>
  6. # 1 Service介绍
  7. - kubernetes中,Pod是应用程序的载体,我们可以通过PodIP来访问应用程序,但是PodIP地址不是固定的,每次销毁重启podip地址都会发生变化,这就意味着不方便直接采用PodIP对服务进行访问。
  8. - 为了解决这个问题,kubernetes提供了Service资源,Service会对提供同一个服务的多个Pod进行聚合,并且提供一个统一的入口地址,这个地址不会发生变化,通过访问Service的入口地址就能访问到后面的Pod服务。
  9. ![](https://cdn.nlark.com/yuque/0/2021/png/513185/1609904160160-74eebf02-ec02-416b-83b7-58a2b5392c3a.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_21%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=nVQsC&margin=%5Bobject%20Object%5D&originHeight=346&originWidth=746&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  10. - Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个Node节点上都运行了一个kube-proxy的服务进程。当创建Service的时候会通过API Serveretcd写入创建的Service的信息,而kube-proxy会基于监听的机制发现这种Service的变化,然后**它会将最新的Service信息转换为对应的访问规则**。
  11. ![](https://cdn.nlark.com/yuque/0/2021/png/513185/1609904171516-d7d58ebc-785b-4e71-a370-6f6f163c713d.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_30%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=Ctolx&margin=%5Bobject%20Object%5D&originHeight=350&originWidth=1048&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
  12. ```yaml
  13. # 10.97.97.97:80 是service提供的访问入口
  14. # 当访问这个入口的时候,可以发现后面有三个pod的服务在等待调用,
  15. # kube-proxy会基于rr(轮询)的策略,将请求分发到其中一个pod上去
  16. # 这个规则会同时在集群内的所有节点上都生成,所以在任何一个节点上访问都可以。
  17. [root@k8s-node1 ~]# ipvsadm -Ln
  18. IP Virtual Server version 1.2.1 (size=4096)
  19. Prot LocalAddress:Port Scheduler Flags
  20. -> RemoteAddress:Port Forward Weight ActiveConn InActConn
  21. TCP 10.97.97.97:80 rr #rr标识轮询,这个ip是service的ip,下边的是那个是service关联的三个pod的ip
  22. -> 10.244.1.39:80 Masq 1 0 0
  23. -> 10.244.1.40:80 Masq 1 0 0
  24. -> 10.244.2.33:80 Masq 1 0 0
  • kube-proxy目前支持三种工作模式:
    • userspace模式(最原始模式-也叫用户空间模式):
      • userspace模式下,kube-proxy会为每一个Service创建一个监听端口,发向Cluster IP(这个就是Service的IP)的请求被iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法(负载均衡算法)选择一个提供服务的Pod并和其建立连接,以便将请求转发到Pod上。
      • 该模式下,kube-proxy充当了一个四层负载均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理的时候会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率非常低下。

云原生-第二部分 - 图102
上图中Client就是我们用户的请求。

  • iptables模式:
    • iptables模式下,kube-proxy为Service后端的每个Pod创建对应的iptables规则,直接将发向Cluster IP的请求重定向到一个Pod的IP上。
    • 该模式下kube-proxy不承担四层负载均衡器的角色,只负责创建iptables规则。该模式的优点在于较userspace模式效率更高,但是不能提供灵活的LB策略(只能轮序或者随机),当后端Pod不可用的时候无法进行重试(他的意思就是当请求发送到pod2上的时候,返回错误,他不会在去尝试把请求发送到pod3上,而是把这个错误返回给用户)。

云原生-第二部分 - 图103
上图中Client就是我们用户的请求。

  • ipvs模式(最流行模式,也是默认模式):
    • ipvs模式和iptables类似,kube-proxy监控Pod的变化并创建相应的ipvs规则,请求会根据这个规则进行相应的转发。ipvs相对iptables转发效率更高,除此之外,ipvs支持更多的LB算法

云原生-第二部分 - 图104
上图中 Client就是我们的请求

可以通过 ipvsadm -Ln来查看是否开启了ipvs:
image.png
这里就表示ipvs没有配置任何规则,也就是ipvs没有生效

  • 开启ipvs(必须安装ipvs内核模块,否则会降级为iptables):

kubectl edit cm kube-proxy -n kube-system
云原生-第二部分 - 图106
下边这里是我们要删除已存在的kube-proxy的pod,让它重新创建,以便把模式变成ipvs
kubectl delete pod -l k8s-app=kube-proxy -n kube-system
云原生-第二部分 - 图107
# 测试ipvs模块是否开启成功
ipvsadm -Ln 看到有如下一批规则产生了,也就代表我们的ipvs生效了。
云原生-第二部分 - 图108
上图中规则的一些说明,带TCP的就是我们的service的ip,后边的rr标识的是轮序策略,然后直到下一个TCP的 ip地址前面带有 ->符号的每一行,都是我们当前service负责的pod节点的ip,轮序也就在这些ip中进行

2 Service类型

  • Service的资源清单:
    1. apiVersion: v1 # 版本
    2. kind: Service # 类型
    3. metadata: # 元数据
    4. name: # 资源名称
    5. namespace: # 命名空间
    6. spec:
    7. selector: # 标签选择器,用于确定当前Service代理那些Pod
    8. app: nginx
    9. type: NodePort # Service的类型,指定Service的访问方式
    10. clusterIP: # 虚拟服务的IP地址,就是service的IP
    11. sessionAffinity: # session亲和性,支持ClientIP、None两个选项,默认值为None,如果配置了ClientIP的就根据ClientIP来吧同一个ip的多次请求路由到同一个pod
    12. ports: # 端口信息
    13. - port: 8080 # Service端口
    14. protocol: TCP # 协议
    15. targetPort : # Pod端口
    16. nodePort: # 主机端口
    1. spec.type的说明:
    2. ClusterIP:默认值,它是kubernetes系统自动分配的虚拟IP,只能在集群内部访问。
    3. NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务。
    4. LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境的支持。
    5. ExternalName:把集群外部的服务引入集群内部,直接使用。这个就是让进去内部的服务能用集群外部的服务,相当于反向访问了。

关于service的标签选择器的一些说明:
标签选择器只是一种表现,是用来给service生成ipvs规则使用的,最终的转发还是根据ipvs中的规则来进行的,也就不再有标签什么事了,标签就在最一开始确定service要管理哪些pod的代理工作。

3 Service使用

3.1 实验环境准备

  • 在使用Service之前,首先利用Deployment创建出3个Pod,注意要为Pod设置app=nginx-pod的标签。
  • 创建deployment.yaml文件,内容如下:

    1. apiVersion: apps/v1
    2. kind: Deployment
    3. metadata:
    4. name: pc-deployment
    5. namespace: dev
    6. spec:
    7. replicas: 3
    8. selector:
    9. matchLabels:
    10. app: nginx-pod
    11. template:
    12. metadata:
    13. labels:
    14. app: nginx-pod
    15. spec:
    16. containers:
    17. - name: nginx
    18. image: nginx:1.17.1
    19. ports:
    20. - containerPort: 80
  • 创建Deployment:

kubectl create -f deployment.yaml
云原生-第二部分 - 图109

  • 查看Pod信息:

kubectl get pod -n dev -o wide --show-labels
云原生-第二部分 - 图110

  • 为了方便后面的测试,修改三台Nginx的index.html:这样更容易观察请求落在了哪个pod上边

kubectl exec -it pc-deployment-7d7dd5499b-59qkm -c nginx -n dev /bin/sh
echo "10.244.1.30" > /usr/share/nginx/html/index.html

kubectl exec -it pc-deployment-7d7dd5499b-fwpgx -c nginx -n dev /bin/sh
echo "10.244.1.31" > /usr/share/nginx/html/index.html

kubectl exec -it pc-deployment-7d7dd5499b-nb6sv -c nginx -n dev /bin/sh
echo "10.244.2.67" > /usr/share/nginx/html/index.html

注意上述中 每个都是两条命令 kubectl开头的和echo开头的两个命令

  • 修改完毕之后,测试访问:

curl 10.244.1.30
curl 10.244.1.31
curl 10.244.2.67
云原生-第二部分 - 图111

3.2 ClusterIP类型的Service

3.2.1 创建Service

  • 创建service-clusterip.yaml文件,内容如下:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: service-clusterip
    5. namespace: dev
    6. spec:
    7. selector:
    8. app: nginx-pod
    9. clusterIP: 10.97.97.97 # service的IP地址,如果不写,默认会生成一个
    10. type: ClusterIP
    11. ports:
    12. - port: 80 # Service的端口
    13. targetPort: 80 # Pod的端口
  • 创建Service:

kubectl create -f service-clusterip.yaml
云原生-第二部分 - 图112

3.2.2 查看Service

  • 查看Service:

kubectl get svc -n dev -o wide
云原生-第二部分 - 图113

3.2.3 查看Service的详细信息

  • 查看Service的详细信息:

kubectl describe svc service-clusterip -n dev
云原生-第二部分 - 图114
其实这个Endpoints就是我们的pod的ip+对外提供服务的端口,下边也有说明

3.2.4 查看ipvs的映射规则

  • 查看ipvs的映射规则:

ipvsadm -Ln
云原生-第二部分 - 图115

3.2.5 访问10.97.97.97:80,观察效果

  • 访问10.97.97.97:80,观察效果,下图中可以看出来,他就是一个轮序的策略:

curl 10.97.97.97:80
云原生-第二部分 - 图116

3.2.6 Endpoint(实际中使用的不多)

  • Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有Pod的访问地址,它是根据service配置文件中的selector描述产生的。
  • 一个service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合。换言之,service和Pod之间的联系是通过Endpoints实现的

云原生-第二部分 - 图117

  • 查看Endpoint:

kubectl get endpoints -n dev -o wide
云原生-第二部分 - 图118

3.2.7 负载分发策略

  • 对Service的访问被分发到了后端的Pod上去,目前kubernetes提供了两种负载分发策略:
    • 如果不定义,默认使用kube-proxy的策略,比如随机、轮询等。
    • 基于客户端地址的会话保持模式(这就是session的亲和性),即来自同一个客户端发起的所有请求都会转发到固定的一个Pod上,这对于传统基于Session的认证项目来说很友好,此模式可以在spec中添加sessionAffinity: ClusterIP选项。
  • 查看ipvs的映射规则,rr表示轮询:

ipvsadm -Ln
云原生-第二部分 - 图119

  • 循环测试访问:

while true;do curl 10.97.97.97:80; sleep 5; done;
云原生-第二部分 - 图120

  • 修改分发策略:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: service-clusterip
    5. namespace: dev
    6. spec:
    7. selector:
    8. app: nginx-pod
    9. clusterIP: 10.97.97.97 # service的IP地址,如果不写,默认会生成一个
    10. type: ClusterIP
    11. sessionAffinity: ClientIP # 修改分发策略为基于客户端地址的会话保持模式
    12. ports:
    13. - port: 80 # Service的端口
    14. targetPort: 80 # Pod的端口
    15. kubectl apply -f service-clusterip.yaml

    云原生-第二部分 - 图121

  • 循环测试访问:

while true;do curl 10.97.97.97:80; sleep 5; done;
云原生-第二部分 - 图122
可以看到他都请求到了同一个pod上边

3.2.8 删除Service

  • 删除Service:

kubectl delete -f service-clusterip.yaml
云原生-第二部分 - 图123
删除完后在查询,发现就没有了:
image.png

3.3 HeadLiness类型的Service

3.3.1 概述

  • 在某些场景中,开发人员可能不想使用Service提供的负载均衡功能,而希望自己来控制负载均衡策略,针对这种情况,kubernetes提供了HeadLinesss Service,这类Service不会分配Cluster IP,如果想要访问Service,只能通过Service的域名进行查询。
  • 因为没有ClusterIP,kube-proxy 并不处理此类服务,因为没有load balancing或 proxy 代理设置,在访问服务的时候回返回后端的全部的Pods IP地址,主要用于开发者自己根据pods进行负载均衡器的开发(设置了selector)。

    3.3.2 创建Service

  • 创建service-headliness.yaml文件,内容如下:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: service-headliness
    5. namespace: dev
    6. spec:
    7. selector:
    8. app: nginx-pod
    9. clusterIP: None # 将clusterIP设置为None,即可创建headliness Service,主要就是设置这里
    10. type: ClusterIP
    11. ports:
    12. - port: 80 # Service的端口
    13. targetPort: 80 # Pod的端口
  • 创建Service:

kubectl create -f service-headliness.yaml
云原生-第二部分 - 图125

3.3.3 查看Service

  • 查看Service:

kubectl get svc service-headliness -n dev -o wide
云原生-第二部分 - 图126

3.3.4 查看Service详情

  • 查看Service详情:

kubectl describe svc service-headliness -n dev
云原生-第二部分 - 图127
虽然service中没有了自己的ip,但是它和pod的关系还是在的。那么没有ip了我们怎么访问这个service呢,这个我们只能通过域名的形式来查询了,如下我们是我们找这个域名的过程。

3.3.5 查看域名解析情况

这里我们进入一个这个service管理的pod,查看这个pod的域名解析服务器地址

  • 查看Pod:

kubectl get pod -n dev
云原生-第二部分 - 图128

  • 进入Pod中,执行cat /etc/resolv.conf命令:

kubectl exec -it pc-deployment-7d7dd5499b-59qkm -n dev /bin/sh
cat /etc/resolv.conf
云原生-第二部分 - 图129
可以看到他的域名解析服务器为10.96.0.10

3.3.6 通过Service的域名进行查询

  • 通过Service的域名进行查询:

Service域名格式:$(service name).$(namespace).svc.cluster.local,其中 cluster.local 为指定的集群的域名,cluster.local是可以修改的。

dig @10.96.0.10 service-headliness.dev.svc.cluster.local

  1. dig是用来解析域名的。 上述中@10.96.0.10的意思是到指定的域名服务器去解析。也就是说他指定到10.96.0.10这个ip来解析service-headliness.dev.svc.cluster.local这个域名

image.png
上图中我们可以看到,通过这个域名解析出来了他绑定的ip地址,这些ip就是我们pod的地址

使用场景:有时候我们创建的服务不想走负载均衡,想直接通过pod-ip链接后端,怎么办呢,使用headless service接可以解决。headless service 是将service的发布文件中的clusterip=none ,不让其获取clusterip , DNS解析的时候直接走pod。

3.4 NodePort类型的Service

3.4.1 概述

  • 在之前的案例中,创建的Service的IP地址只能在集群内部才可以访问,如果希望Service暴露给集群外部使用,那么就需要使用到另外一种类型的Service,称为NodePort类型的Service。NodePort的工作原理就是将Service的端口映射到Node的一个端口上,然后就可以通过NodeIP:NodePort来访问Service了。

云原生-第二部分 - 图131
上图中 NodePort就是我们的node节点上的一个端口,Port就是我们service的端口,NodePort和Port两个端口之间,有一个映射关系,这样当我们访问node节点的端口的时候,就会映射到我们service的Port

3.4.2 创建Service

  • 创建service-nodeport.yaml文件,内容如下:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: service-nodeport
    5. namespace: dev
    6. spec:
    7. selector:
    8. app: nginx-pod
    9. type: NodePort # Service类型为NodePort,这里是重点
    10. ports:
    11. - port: 80 # Service的端口
    12. targetPort: 80 # Pod的端口
    13. nodePort: 30002 # 指定绑定的node的端口(默认取值范围是30000~32767),如果不指定,会默认分配
  • 创建Service:

kubectl create -f service-nodeport.yaml
云原生-第二部分 - 图132

3.4.3 查看Service

  • 查看Service:

kubectl get svc service-nodeport -n dev -o wide
云原生-第二部分 - 图133
这里我们可以看到,CLUSTER-IP依然是存在的,只不过我们不会使用它,而是使用node节点的ip

3.4.4 访问

  • 通过浏览器访问:http://192.168.18.100:30002/即可访问对应的Pod。

    3.5 LoadBalancer类型的Service

  • LoadBalancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境的支持,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。

  • 这里感觉就是在更外一层的负载均衡了,NodePort是在pod层的负载均衡,LoadBalancer需要外部负载均衡的支持,在node节点的级别上做负载均衡

云原生-第二部分 - 图134

3.6 ExternalName类型的Service

3.6.1 概述

  • ExternalName类型的Service用于引入集群外部的服务,它通过externalName属性指定一个服务的地址,然后在集群内部访问此Service就可以访问到外部的服务了。
  • 他和我们的NodePort和nodebalancer正好是相反的,NodePort和nodebalancer是将我们的服务暴露出去,而ExternalName是为了把外部的服务加入集群内部

云原生-第二部分 - 图135

3.6.2 创建Service

  • 创建service-externalname.yaml文件,内容如下:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. name: service-externalname
    5. namespace: dev
    6. spec:
    7. type: ExternalName # Service类型为ExternalName
    8. externalName: www.baidu.com # 改成IP地址也可以,主要是调整这里,标识我们这个服务引入了baidu这个服务
  • 创建Service:

kubectl create -f service-externalname.yaml
云原生-第二部分 - 图136
然后我们可以看到service的详情:
image.png

3.6.3 域名解析

  • 域名解析:

dig @10.96.0.10 service-externalname.dev.svc.cluster.local
解析以后就能看到我们配置的baidu这个外部的服务:
image.png
这个ExternalName我感觉也是需要依赖外部的负载均衡的。就像headless一样

4 Ingress介绍

  • 我们已经知道,Service对集群之外暴露服务的主要方式有两种:NodePort和LoadBalancer,但是这两种方式,都有一定的缺点:
    • NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显,因为一个service就要占用机器上的一个端口,如果service过多,就会导致端口不够用。
    • LoadBalancer的缺点是每个Service都需要一个LB,浪费,麻烦,并且需要kubernetes之外的设备的支持。
  • 基于这种现状,kubernetes提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求,工作机制大致如下图所示:

云原生-第二部分 - 图139
上图中我们可以看到我们在ingress中配置了多个域名,每个域名对应一个service

  • 实际上,Ingress相当于一个七层的负载均衡器,是kubernetes对反向代理的一个抽象,它的工作原理类似于Nginx,可以理解为Ingress里面建立了诸多映射规则,Ingress Controller通过监听这些配置规则并转化为Nginx的反向代理配置,然后对外提供服务。
    • Ingress:kubernetes中的一个对象,作用是定义请求如何转发到Service的规则。
    • Ingress Controller:具体实现反向代理及负载均衡的程序,对Ingress定义的规则进行解析,根据配置的规则来实现请求转发,实现的方式有很多,比如Nginx,Contour,Haproxy等。
  • Ingress(以Nginx)的工作原理如下:
    • 用户编写Ingress规则,说明那个域名对应kubernetes集群中的那个Service。
    • Ingress控制器动态感知Ingress服务规则的变化,然后生成一段对应的Nginx的反向代理配置。
    • Ingress控制器会将生成的Nginx配置写入到一个运行着的Nginx服务中,并动态更新。
    • 到此为止,其实真正在工作的就是一个Nginx了,内部配置了用户定义的请求规则。

云原生-第二部分 - 图140

5 Ingress使用

5.1 环境准备

5.1.1 搭建Ingress环境

  • 创建文件夹,并进入到此文件夹中:

mkdir ingress-controller
cd ingress-controller

mandatory.ymal这个文件巨长

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/baremetal/service-nodeport.yaml

上述mandatory.yaml文件中,我们还要做下镜像的替换:
image.png
把image后边的参数替换为:quay-mirror.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0

  • 创建Ingress-nginx:

kubectl apply -f ./
image.png
可以看到他创建了很多东西。

  • 查看ingress-nginx:他应该有这个一个pod

kubectl get pod -n ingress-nginx
云原生-第二部分 - 图143

  • 查看Service:以及应该有这么一个service

kubectl get svc -n ingress-nginx
云原生-第二部分 - 图144
上图中80对应的node上的端口号是http的,443对应的node端口号是https使用的。

5.1.2 准备Service和Pod

  • 为了后面的实验比较方便,创建如下图所示的模型:

云原生-第二部分 - 图145

  • 创建tomcat-nginx.yaml文件,内容如下: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment namespace: dev spec: replicas: 3 selector: matchLabels:
    1. app: nginx-pod
    template: metadata:
    1. labels:
    2. app: nginx-pod
    spec:
    1. containers:
    2. - name: nginx
    3. image: nginx:1.17.1
    4. ports:
    5. - containerPort: 80

apiVersion: apps/v1 kind: Deployment metadata: name: tomcat-deployment namespace: dev spec: replicas: 3 selector: matchLabels: app: tomcat-pod template: metadata: labels: app: tomcat-pod spec: containers:

  1. - name: tomcat
  2. image: tomcat:8.5-jre10-slim
  3. ports:
  4. - containerPort: 8080

apiVersion: v1 kind: Service metadata: name: nginx-service namespace: dev spec: selector: app: nginx-pod clusterIP: None type: ClusterIP ports:

  • port: 80 targetPort: 80

apiVersion: v1 kind: Service metadata: name: tomcat-service namespace: dev spec: selector: app: tomcat-pod clusterIP: None type: ClusterIP ports:

  • port: 8080 targetPort: 8080 ```
  • 创建Service和Pod:

kubectl create -f tomcat-nginx.yaml
云原生-第二部分 - 图146

  • 查看Service和Pod:

kubectl get svc,pod -n dev
云原生-第二部分 - 图147

5.2 Http代理

  • 创建ingress-http.yaml文件,内容如下:

    1. apiVersion: extensions/v1beta1
    2. kind: Ingress
    3. metadata:
    4. name: ingress-http
    5. namespace: dev
    6. spec:
    7. rules: #这里就是定义我们的规则,他是一个数组
    8. - host: nginx.xudaxian.com #主机域名,也就是可以通过这个域名访问我们的nginx
    9. http:
    10. paths:
    11. - path: / #就是我们的应用根目录,如果是 www.xxx.com/index.html 这里就要写/index.html
    12. backend:
    13. serviceName: nginx-service #指定要访问的service的名称
    14. servicePort: 80 #指定要访问的service的端口
    15. - host: tomcat.xudaxian.com
    16. http:
    17. paths:
    18. - path: /
    19. backend:
    20. serviceName: tomcat-service
    21. servicePort: 8080
  • 创建:

kubectl create -f ingress-http.yaml
云原生-第二部分 - 图148

  • 查看:

kubectl get ingress ingress-http -n dev
云原生-第二部分 - 图149

  • 查看详情:

kubectl describe ingress ingress-http -n dev
云原生-第二部分 - 图150
上图中我们可以看到 Rules:下边的对应关系 如果的是 nginx.xudaxian.com 他就会访问nginx-service:80这个服务对应的pod,并且默认的path为 /
tomcat的那个也是这个意思。

  • 在本机的hosts文件中添加如下的规则(192.168.209.100为Master节点的IP地址),要不然我们的域名解析不出来:

192.168.209.100 nginx.xudaxian.com
192.168.209.100 tomcat.xudaxian.com
云原生-第二部分 - 图151

  • 查看ingress-nginx的端口(本次测试http的端口是30378,https的端口是31125):

kubectl get svc -n ingress-nginx
云原生-第二部分 - 图152

  • 本机通过浏览器输入下面的地址访问:

http://nginx.xudaxian.com:30378
http://tomcat.xudaxian.com:30378
云原生-第二部分 - 图153
云原生-第二部分 - 图154

5.3 Https代理

因为https的安全性,所以我们需要先把整数生成好,然后再在k8s中创建好秘钥

  • 生成证书:

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/C=CN/ST=BJ/L=BJ/O=nginx/CN=xudaxian.com"
云原生-第二部分 - 图155

  • 创建密钥:

kubectl create secret tls tls-secret --key tls.key --cert tls.crt
云原生-第二部分 - 图156

  • 创建ingress-https.yaml文件,内容如下:

    1. apiVersion: extensions/v1beta1
    2. kind: Ingress
    3. metadata:
    4. name: ingress-https
    5. namespace: dev
    6. spec:
    7. tls:
    8. - hosts:
    9. - nginx.xudaxian.com #这两个域名会使用下边这个秘钥
    10. - tomcat.xudaxian.com
    11. secretName: tls-secret # 指定秘钥,https的这个是必须要配置的。这里就是我们上边创建的按个秘钥,注意名字要一致
    12. rules:
    13. - host: nginx.xudaxian.com
    14. http:
    15. paths:
    16. - path: /
    17. backend:
    18. serviceName: nginx-service
    19. servicePort: 80
    20. - host: tomcat.xudaxian.com
    21. http:
    22. paths:
    23. - path: /
    24. backend:
    25. serviceName: tomcat-service
    26. servicePort: 8080
  • 创建:

kubectl create -f ingress-https.yaml
云原生-第二部分 - 图157

  • 查看:

kubectl get ingress ingress-https -n dev
云原生-第二部分 - 图158

  • 查看详情:

kubectl describe ingress ingress-https -n dev
云原生-第二部分 - 图159
上图中可以发现,Rules上多了一个TLS套接字的配置

  • 在本机的hosts文件中添加如下的规则(192.168.209.100为Master节点的IP地址):略。
  • 本机通过浏览器输入下面的地址访问:

https://nginx.xudaxian.com:31125
https://tomcat.xudaxian.com:31125

k8s的数据存储

1 概述

  • 在前面已经提到,容器的生命周期可能很短,会被频繁的创建和销毁。那么容器在销毁的时候,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器中的数据,kubernetes引入了Volume的概念。
  • Volume是Pod中能够被多个容器访问的共享目录,它被定义在Pod上,然后被一个Pod里面的多个容器挂载到具体的文件目录下,kubernetes通过Volume实现同一个Pod中不同容器之间的数据共享以及数据的持久化存储。Volume的生命周期不和Pod中的单个容器的生命周期有关,当容器终止或者重启的时候,Volume中的数据也不会丢失。
  • kubernetes的Volume支持多种类型,比较常见的有下面的几个:
    • 简单存储:EmptyDir、HostPath、NFS。
    • 高级存储:PV、PVC。
    • 配置存储:ConfigMap、Secret。

有的volume是和pod挂钩的,pod销毁了,数据也就没有了,有的volume是和pod无关的,pod销毁了,数据也不会受到影响。

2 基本存储

2.1 EmptyDir

2.1.1 概述

EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一个空目录。

EmptyDir就是对应着主机上的空目录,和他的名字一样

EmptyDir是在Pod被分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为kubernetes会自动分配一个目录,当Pod销毁时,EmptyDir中的数据也会被永久删除。

EmptyDir的用途如下:

  • 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。
  • 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。这句话的意思就是同一个pod中的容器共享目录

接下来,通过一个容器之间的共享案例来使用描述一个EmptyDir。

在一个Pod中准备两个容器nginx和busybox,然后声明一个volume分别挂载到两个容器的目录中,然后nginx容器负责向volume中写日志,busybox中通过命令将日志内容读到控制台。
云原生-第二部分 - 图160

2.1.2 创建Pod

  • 创建volume-emptydir.yaml文件,内容如下:

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: volume-emptydir
    5. namespace: dev
    6. spec:
    7. containers:
    8. - name: nginx
    9. image: nginx:1.17.1
    10. imagePullPolicy: IfNotPresent
    11. ports:
    12. - containerPort: 80
    13. volumeMounts: # 将logs-volume挂载到nginx容器中对应的目录,该目录为/var/log/nginx
    14. - name: logs-volume #要挂载的容器,在下边有配置。
    15. mountPath: /var/log/nginx #这里我们是把volume这个存储卷挂载到了nginx容器的这个目录下,nginx往这个目录写的东西都会写入到volume中
    16. - name: busybox
    17. image: busybox:1.30
    18. imagePullPolicy: IfNotPresent
    19. command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件
    20. volumeMounts: # 将logs-volume挂载到busybox容器中的对应目录,该目录为/logs
    21. - name: logs-volume
    22. mountPath: /logs #这里也是我们是把volume这个存储卷挂载到了busybox容器的这个目录下,busybox对这个目录的读,就会转换成对volume的读
    23. volumes: # 声明volume,name为logs-volume,类型为emptyDir,这里就是我们的重点,配置volume
    24. - name: logs-volume
    25. emptyDir: {} #这个大括号不能省略,因为它是固定写法
  • 创建Pod:

kubectl create -f volume-emptydir.yaml
云原生-第二部分 - 图161

2.1.3 查看Pod

  • 查看Pod:

kubectl get pod volume-emptydir -n dev -o wide
云原生-第二部分 - 图162
可以看到有两个容器在其中运行了。

2.1.4 访问Pod中的Nginx

  • 访问Pod中的Nginx:

curl 10.244.2.2
云原生-第二部分 - 图163

2.1.5 查看指定容器的标准输出

  • 查看指定容器的标准输出:

kubectl logs -f volume-emptydir -n dev -c busybox
云原生-第二部分 - 图164

2.2 HostPath

2.2.1 概述

  • 我们已经知道EmptyDir中的数据不会被持久化,它会随着Pod的结束而销毁,如果想要简单的将数据持久化到主机中,可以选择HostPath。
  • HostPath就是将Node主机中的一个实际目录挂载到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依旧可以保存在Node主机上。
  • hostpath翻译过来就是 主机路径的意思,这也是他的工作原理,简单直接。。。

云原生-第二部分 - 图165
这个图的意思就是,我们容器产生的数据,存到了我们的node节点的主机上。

2.2.2 创建Pod

  • 创建volume-hostpath.yaml文件,内容如下:

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: volume-hostpath
    5. namespace: dev
    6. spec:
    7. containers:
    8. - name: nginx
    9. image: nginx:1.17.1
    10. imagePullPolicy: IfNotPresent
    11. ports:
    12. - containerPort: 80
    13. volumeMounts: # 将logs-volume挂载到nginx容器中对应的目录,该目录为/var/log/nginx
    14. - name: logs-volume
    15. mountPath: /var/log/nginx
    16. - name: busybox
    17. image: busybox:1.30
    18. imagePullPolicy: IfNotPresent
    19. command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件
    20. volumeMounts: # 将logs-volume挂载到busybox容器中的对应目录,该目录为/logs
    21. - name: logs-volume
    22. mountPath: /logs
    23. volumes: # 声明volume,name为logs-volume,类型为hostPath,这里就是我们的重点
    24. - name: logs-volume
    25. hostPath:
    26. path: /root/logs #这里指定了我们要存储到主机的那个路径上。
    27. type: DirectoryOrCreate # 这个DirectorOrCreate的意思就是:目录存在就使用,不存在就先创建再使用

    type的值的说明:

  • DirectoryOrCreate:目录存在就使用,不存在就先创建后使用。

  • Directory:目录必须存在。
  • FileOrCreate:文件存在就使用,不存在就先创建后使用。
  • File:文件必须存在。
  • Socket:unix套接字必须存在。
  • CharDevice:字符设备必须存在。
  • BlockDevice:块设备必须存在。
  • 创建Pod:

kubectl create -f volume-hostpath.yaml
云原生-第二部分 - 图166
查看主机也发现多了这么一个目录:
image.png
即使pod停掉了,这个目录也是存在的。
这里要注意,pod部署在哪个节点,文件就会在哪个节点创建,注意别找错了node

2.2.3 查看Pod

  • 查看Pod:

kubectl get pod volume-hostpath -n dev -o wide
云原生-第二部分 - 图168

2.2.4 访问Pod中的Nginx

  • 访问Pod中的Nginx:

curl 10.244.2.3
云原生-第二部分 - 图169

2.2.5 去node节点找到hostPath映射的目录中的文件

  • 需要到Pod所在的节点(k8s-node2)查看hostPath映射的目录中的文件:

ls /root/logs
云原生-第二部分 - 图170
同样的道理,如果在此目录中创建文件,到容器中也是可以看到的。

2.3 NFS

2.3.1 概述

  • HostPath虽然可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到别的Node节点上,又会出现问题(因为hostPath模式下,我们的会存储在pod所在node本地上),此时需要准备单独的网络存储系统,比较常用的是NFS和CIFS。
  • NFS是一个网络文件存储系统,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样,无论Pod在node节点上怎么转移,只要Node和NFS的对接没有问题,数据就可以成功访问。

云原生-第二部分 - 图171

2.3.2 搭建NFS服务器

  • 首先需要准备NFS服务器,这里为了简单,直接在Master节点做NFS服务器。
  • 在Master节点上安装NFS服务器:

yum install -y nfs-utils rpcbind

  • 准备一个共享目录:

mkdir -pv /root/data/nfs

  • 将共享目录以读写权限暴露给192.168.18.0/24网段中的所有主机:

vim /etc/exports
/root/data/nfs 192.168.18.0/24(rw,no_root_squash)

  • 修改权限:

chmod 777 -R /root/data/nfs

  • 加载配置:

exportfs -r

  • 启动nfs服务:

systemctl start rpcbind
systemctl enable rpcbind
systemctl start nfs
systemctl enable nfs

  • 在Master节点测试是否挂载成功:

showmount -e 192.168.18.100
云原生-第二部分 - 图172

  • 在Node节点上都安装NFS服务器,目的是为了Node节点可以驱动NFS设备。

在Node节点上安装NFS服务,不需要启动,如果启动了node也变成nfs服务器了,我们只需要它里边的工具支持而已。
yum -y install nfs-utils

  • 在Node节点测试是否挂载成功:

showmount -e 192.168.18.100

  • 高可用备份方式,在所有节点执行如下的命令:

mount -t nfs 192.168.18.100:/root/data/nfs /mnt

2.3.3 创建Pod

  • 创建volume-nfs.yaml文件,内容如下:

    apiVersion: v1
    kind: Pod
    metadata:
    name: volume-nfs
    namespace: dev
    spec:
    containers:
      - name: nginx
        image: nginx:1.17.1
        imagePullPolicy: IfNotPresent
        ports:
          - containerPort: 80
        volumeMounts: # 将logs-volume挂载到nginx容器中对应的目录,该目录为/var/log/nginx
          - name: logs-volume
            mountPath: /var/log/nginx
      - name: busybox
        image: busybox:1.30
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh","-c","tail -f /logs/access.log"] # 初始命令,动态读取指定文件
        volumeMounts: # 将logs-volume挂载到busybox容器中的对应目录,该目录为/logs
          - name: logs-volume
            mountPath: /logs
    volumes: # 声明volume
      - name: logs-volume
        nfs:
          server: 192.168.18.100 # NFS服务器地址
          path: /root/data/nfs # 共享文件路径,这个要和我们前面共享出来的nfs路径一致
    
  • 创建Pod:

kubectl create -f volume-nfs.yaml
云原生-第二部分 - 图173

2.3.4 查看Pod

  • 查看Pod:

kubectl get pod volume-nfs -n dev
云原生-第二部分 - 图174

2.3.5 查看nfs服务器上共享目录

  • 查看nfs服务器上共享目录:

ls /root/data/nfs
云原生-第二部分 - 图175

3 高级存储

3.1 PV和PVC概述

  • 前面我们已经学习了使用NFS提供存储,此时就要求用户会搭建NFS系统,并且会在yaml配置nfs。由于kubernetes支持的存储系统有很多,要求客户全部掌握,显然不现实。为了能够屏蔽底层存储实现的细节,方便用户使用,kubernetes引入了PV和PVC两种资源对象。
  • PV(Persistent Volume)是持久化卷的意思,是对底层的共享存储的一种抽象。一般情况下PV由kubernetes管理员进行创建和配置,它和底层具体的共享存储技术有关,并通过插件完成和共享存储的对接。
  • PVC(Persistent Volume Claim)是持久化卷声明的意思,是用户对于存储需求的一种声明。换言之,PVC其实就是用户向kubernetes系统发出的一种资源需求申请。

云原生-第二部分 - 图176
上图中,可以看到 pv和pvc处在不同的“层” pv所在的是k8s的管理员来使用,对NFS,CIFS等进行抽象,掩盖她们的区别,提供统一的对外实现。pv在这些基础上,准备好一些不同的大小的存储空间供pvc使用。。pvc所在的层是我们用户使用k8s的地方,pvc就是我们用户发出的用来申请存储空间的声明。可以说pv和pvc做了一个分层架构,pv重点是如何实现,考虑底层细节,pvc重点是如何使用,不关注如何实现。
上图中 pv和pvc通过映射的方式关联起来

  • 使用了PV和PVC之后,工作可以得到进一步的提升:

    • 存储:存储工程师维护。
    • PV:kubernetes管理员维护。
    • PVC:kubernetes用户维护。

      3.2 PV

      3.2.1 PV的资源清单文件

  • PV是存储资源的抽象,下面是PV的资源清单文件:

    apiVersion: v1
    kind: PersistentVolume
    metadata:
    name: pv2
    #注意这里我们没有指定namespace,因为pv是跨集群资源的,不能放在namespace里边,需要全局可见
    spec:
    nfs: # 存储类型,和底层正则的存储对应
      path:
      server:
    capacity: # 存储能力,就是他能对外提供的资源有多少,是什么,目前只支持存储空间的设置
      storage: 2Gi 
    accessModes: # 访问模式
    storageClassName: # 存储类别,这个用的不多
    persistentVolumeReclaimPolicy: # 回收策略
    

    pv的关键配置参数说明:

  • 存储类型:底层实际存储的类型,kubernetes支持多种存储类型,每种存储类型的配置有所不同。

  • 存储能力(capacity):目前只支持存储空间的设置(storage=1Gi),不过未来可能会加入IOPS、吞吐量等指标的配置。
  • 访问模式(accessModes):
    • 用来描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
      • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载。也就是一个pv只能被一个pvc挂载,不能被多个pvc挂载。如下都是一个意思。
      • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载。
      • ReadWriteMany(RWX):读写权限,可以被多个节点挂载。
    • 需要注意的是,底层不同的存储类型可能支持的访问模式不同。就是可能全支持这三种模式,也可能是部分支持
  • 回收策略( persistentVolumeReclaimPolicy):
    • 当PV不再被pvc使用之后,对其的处理方式,目前支持三种策略:
      • Retain(保留):保留数据,需要管理员手动清理数据。
      • Recycle(回收):清除PV中的数据,效果相当于rm -rf /volume/*。
      • Delete(删除):和PV相连的后端存储完成volume的删除操作,常见于云服务器厂商的存储服务。
    • 需要注意的是,底层不同的存储类型可能支持的回收策略不同。就是可能全支持这三种模式,也可能是部分支持
  • 存储类别(storageClassName):PV可以通过storageClassName参数指定一个存储类别。相当于一个标签,pvc选择pv的时候也可以指定这么一个标签,那么k8s就会让有同样标签的pv和pvc进行匹配
    • 具有特定类型的PV只能和请求了该类别的PVC进行绑定。
    • 未设定类别的PV只能和不请求任何类别的PVC进行绑定。
  • 状态(status):一个PV的生命周期,可能会处于4种不同的阶段。注意这个字段不在配置文件中,而是pv的一个生命周期说明:

    • Available(可用):表示可用状态,还未被任何PVC绑定。
    • Bound(已绑定):表示PV已经被PVC绑定。
    • Released(已释放):表示PVC被删除,但是资源还没有被集群重新释放。未被重新释放应该就是说集群虽然这个pv已经没有被pvc绑定了,但是还需要进行一些预处理之类的工作,才能变成Available
    • Failed(失败):表示该PV的自动回收失败。

      3.2.2 准备工作(准备NFS环境)

      使用NFS作为存储,来演示PV的使用,创建3个PV,对应NFS中3个暴露的路径
  • 创建目录:

mkdir -pv /root/data/{pv1,pv2,pv3}

  • 授权:

chmod 777 -R /root/data

  • 修改/etc/exports文件: 就是我们的暴露丼

vim /etc/exports
/root/data/pv1 192.168.18.0/24(rw,no_root_squash) <br />``/root/data/pv2 192.168.18.0/24(rw,no_root_squash) <br />``/root/data/pv3 192.168.18.0/24(rw,no_root_squash)

  • 重启nfs服务:

systemctl restart nfs

3.2.3 创建PV

  • 创建pv.yaml文件,内容如下:里边一共3个pv ```yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv1 spec: nfs: # 存储类型吗,和底层正则的存储对应 path: /root/data/pv1 server: 192.168.18.100 capacity: # 存储能力,目前只支持存储空间的设置 storage: 1Gi accessModes: # 访问模式
    • ReadWriteMany persistentVolumeReclaimPolicy: Retain # 回收策略

apiVersion: v1 kind: PersistentVolume metadata: name: pv2 spec: nfs: # 存储类型吗,和底层正则的存储对应 path: /root/data/pv2 server: 192.168.18.100 capacity: # 存储能力,目前只支持存储空间的设置 storage: 2Gi accessModes: # 访问模式

- ReadWriteMany

persistentVolumeReclaimPolicy: Retain # 回收策略


apiVersion: v1 kind: PersistentVolume metadata: name: pv3 spec: nfs: # 存储类型吗,和底层正则的存储对应 path: /root/data/pv3 server: 192.168.18.100 capacity: # 存储能力,目前只支持存储空间的设置 storage: 3Gi accessModes: # 访问模式

- ReadWriteMany

persistentVolumeReclaimPolicy: Retain # 回收策略


- 创建PV:

`kubectl create -f pv.yaml`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610412216994-970c3c96-be4d-48f2-a5b1-5ded496e5ac9.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_22%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=IMc0Y&margin=%5Bobject%20Object%5D&originHeight=70&originWidth=763&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="tlxzV"></a>
### _3.2.4 查看PV_

- 查看PV:

`kubectl get pv -o wide`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610067479029-d48b08b7-feaa-4104-8f9c-8e53d9b49195.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=M0voE&margin=%5Bobject%20Object%5D&originHeight=96&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />CLAIM:它是显示这个pv被哪一个pvc绑定,这里没显示是因为还有被绑定
<a name="JlRho"></a>
## 3.3 PVC
<a name="EdKVO"></a>
### _3.3.1 PVC的资源清单文件_

- PVC是资源的申请,用来声明对存储空间、访问模式、存储类别需求信息,下面是PVC的资源清单文件:
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc
  namespace: dev # pvc有namespace,这说明 pvc是属于namespace,不像pv,需要跨集群可见
spec: #这里就是挑选pvc的信息。
  accessModes: # 访客模式
  selector: # 采用标签对PV选择
  storageClassName: # 存储类别
  resources: # 请求空间
    requests:
      storage: 5Gi

PVC的关键配置参数说明:

  • 访客模式(accessModes):用于描述用户应用对存储资源的访问权限。
  • 用于描述用户应用对存储资源的访问权限:

    • 选择条件(selector):通过Label Selector的设置,可使PVC对于系统中已存在的PV进行筛选。
    • 存储类别(storageClassName):PVC在定义时可以设定需要的后端存储的类别,只有设置了该class的pv才能被系统选出。
    • 资源请求(resources):描述对存储资源的请求。

      3.3.2 创建PVC

  • 创建pvc.yaml文件,内容如下: ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc1 namespace: dev spec: accessModes: # 访客模式

    • ReadWriteMany #这里要和pv中的一样,否则他会访问不上 resources: # 请求空间 requests: storage: 1Gi

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc2 namespace: dev spec: accessModes: # 访客模式

- ReadWriteMany

resources: # 请求空间 requests: storage: 1Gi


apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc3 namespace: dev spec: accessModes: # 访客模式

- ReadWriteMany

resources: # 请求空间 requests: storage: 5Gi


- 创建PVC:

`kubectl create -f pvc.yaml`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610067496412-9ab34e13-ff1f-4ad7-bdcb-38a1a8d78b4f.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=L8ncD&margin=%5Bobject%20Object%5D&originHeight=101&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
<a name="gJUkh"></a>
### _3.3.3 查看PVC_

- 查看PVC:

`kubectl get pvc -n dev -o wide`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610067508744-36ebd290-e9db-4184-a932-6648a751f2d9.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=ZvpZA&margin=%5Bobject%20Object%5D&originHeight=95&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />这里我们注意 pvc3的 STATUS 的状态是Pending,这是因为我们给他声明的需要的pv为5G,但是我们上述创建pv只有1,2,3G的,他匹配不上,所以一直等待

- 查看PV:

`kubectl get pv -o wide`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610067522773-fda92017-f7b4-497c-8103-e2766e2fccfb.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=PpIYO&margin=%5Bobject%20Object%5D&originHeight=98&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />可以看到,绑定了pvc的pv的状态变成了Bound,也就是表示他被pvc绑定了。
<a name="ltVjv"></a>
### _3.3.4 创建Pod使用PVC_

- 创建pvc-pod.yaml文件,内容如下:
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod1
  namespace: dev
spec:
  containers:
  - name: busybox
    image: busybox:1.30
    command: ["/bin/sh","-c","while true;do echo pod1 >> /root/out.txt; sleep 10; done;"]
    volumeMounts:
    - name: volume
      mountPath: /root/
  volumes:
    - name: volume
      persistentVolumeClaim: #我们可以看到 pvc就是这个的简称,他就是声明我们当前的这个pod使用哪个pvc,
        claimName: pvc1 #要是用的pvc的名字
        readOnly: false #false就表示这个pod对这个pvc可读可写,否则就只能读

---
apiVersion: v1
kind: Pod
metadata:
  name: pod2
  namespace: dev
spec:
  containers:
    - name: busybox
      image: busybox:1.30
      command: ["/bin/sh","-c","while true;do echo pod1 >> /root/out.txt; sleep 10; done;"]
      volumeMounts:
        - name: volume
          mountPath: /root/
  volumes:
    - name: volume
      persistentVolumeClaim:
        claimName: pvc2
        readOnly: false
  • 创建Pod:

kubectl create -f pvc-pod.yaml
云原生-第二部分 - 图177

3.3.5 创建Pod使用PVC后查看Pod

  • 查看Pod:

kubectl get pod -n dev -o wide
云原生-第二部分 - 图178

3.3.6 创建Pod使用PVC后查看PVC

  • 查看PVC:

kubectl get pvc -n dev -o wide
云原生-第二部分 - 图179

3.3.7 创建Pod使用PVC后查看PV

  • 查看PV:

kubectl get pv -n dev -o wide
云原生-第二部分 - 图180

3.3.8 查看nfs中的文件存储

  • 查看nfs中的文件存储:

ls /root/data/pv1/out.txt
ls /root/data/pv2/out.txt
云原生-第二部分 - 图181

当我们把pvc干掉的时候,查看pv的状态,会显示如下:
image.png
可以发现他变成了release状态

3.4 生命周期

他有助于把pv和pvc之间状态的变化描述清楚
云原生-第二部分 - 图183

  • PVC和PV是一一对应的,PV和PVC之间的相互作用遵循如下的生命周期。
  • 资源供应:管理员手动创建底层存储和PV。
  • 资源绑定:
    • 用户创建PVC,kubernetes负责根据PVC声明去寻找PV,并绑定在用户定义好PVC之后,系统将根据PVC对存储资源的请求在以存在的PV中选择一个满足条件的。
      • 一旦找到,就将该PV和用户定义的PVC进行绑定,用户的应用就可以使用这个PVC了。
      • 如果找不到,PVC就会无限期的处于Pending状态,直到系统管理员创建一个符合其要求的PV。
    • PV一旦绑定到某个PVC上,就会被这个PVC独占,不能再和其他的PVC进行绑定了。这句话存疑,和上边的pv模式说明的有区别。
  • 资源使用:用户可以在Pod中像volume一样使用PVC,Pod使用Volume的定义,将PVC挂载到容器内的某个路径进行使用。
  • 资源释放:
    • 用户删除PVC来释放PV。
    • 当存储资源使用完毕后,用户可以删除PVC,和该PVC绑定的PV将会标记为“已释放”,但是还不能立刻和其他的PVC进行绑定。通过之前PVC写入的数据可能还留在存储设备上,只有在清除之后该PV才能再次使用。
  • 资源回收:

    • kubernetes根据PV设置的回收策略进行资源的回收。
    • 对于PV,管理员可以设定回收策略,用于设置与之绑定的PVC释放资源之后如何处理遗留数据的问题。只有PV的存储空间完成回收,才能供新的PVC绑定和使用。

      3.5 创建PVC后一直绑定不了PV的原因

  • ①PVC的空间申请大小比PV的空间要大。

  • ②PVC的storageClassName和PV的storageClassName不一致。
  • ③PVC的accessModes和PV的accessModes不一致。

    4 配置存储

    4.1 ConfigMap

    4.1.1 概述

  • ConfigMap是一个比较特殊的存储卷,它的主要作用是用来存储配置信息的。

    4.1.2 ConfigMap的资源清单文件

  • ConfigMap的资源清单文件: ```yaml apiVersion: v1 kind: ConfigMap metadata: name: configMap namespace: dev data: # info: #这个就是key,这个info的整个子集就是value, username: admin password: 123456

<a name="fAn1i"></a>
### _4.1.3 创建ConfigMap_

- 创建configmap.yaml文件,内容如下:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: configmap
  namespace: dev
data:
  info:
    username:admin
    password:123456
  • 创建ConfigMap:

kubectl create -f configmap.yaml
云原生-第二部分 - 图184
查看configMap的信息描述:
image.png

4.1.4 创建Pod

  • 创建pod-configmap.yaml文件,内容如下:

    apiVersion: v1
    kind: Pod
    metadata:
    name: pod-configmap
    namespace: dev
    spec:
    containers:
      - name: nginx
        image: nginx:1.17.1
        volumeMounts:
          - mountPath: /configmap/config
            name: config
    volumes:
      - name: config #这里我们就挂载了刚刚的configMap
        configMap:
          name: configmap
    
  • 创建Pod:

kubectl create -f pod-configmap.yaml
云原生-第二部分 - 图186

4.1.5 查看Pod

  • 查看Pod:

kubectl get pod pod-configmap -n dev
云原生-第二部分 - 图187

4.1.6 进入容器

  • 进入容器,查看配置:

kubectl exec -it pod-configmap -n dev /bin/sh
cd /configmap/config
ls
more info

云原生-第二部分 - 图188
image.png
上述中可以看到 每个ConfigMap都映射成了一个文件。
ConfigMap中的key映射为一个文件,value映射为文件中的内容。如果更新了ConfigMap中的内容,容器中的值也会动态更新,就是如果我们修改了上述创建的configmap.yaml中的键值对的值的话(这里将password改成了123456789),通过如下命令:
image.png
如下是修改的部分,把password从123456改成了123456789
image.png
那么我们再次查看pod中的该的值,发现其也发生了变化(需要等待一会它进行同步):
image.png

4.2 Secret

4.2.1 概述

configMap中存储的信息都是明文存储的

  • 在kubernetes中,还存在一种和ConfigMap非常类似的对象,称为Secret对象,它主要用来存储敏感信息,例如密码、密钥、证书等等。

    4.2.2 准备数据

  • 使用base64对数据进行编码:

准备username
echo -n “admin” | base64
云原生-第二部分 - 图193
echo -n “123456” | base64
云原生-第二部分 - 图194

4.2.3 创建Secret

  • 创建secret.yaml文件,内容如下:

    apiVersion: v1
    kind: Secret
    metadata:
    name: secret
    namespace: dev
    type: Opaque
    data:
    username: YWRtaW4=  #这里是我们base64编码之后的字符
    password: MTIzNDU2
    
  • 创建Secret:

kubectl create -f secret.yaml
云原生-第二部分 - 图195

  • 上面的方式是先手动将数据进行编码,其实也可以使用直接编写数据,将数据编码交给kubernetes。 ```yaml

apiVersion: v1 kind: Secret metadata: name: secret namespace: dev type: Opaque stringData: username: admin password: 123456

如果同时使用data和stringData,那么data会被忽略。
<a name="mqjWJ"></a>
### _4.2.4 查看Secret详情_

- 查看Secret详情:

`kubectl describe secret secret -n dev`<br />![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610067708440-8269dd93-3a42-4244-92f7-aff26445cc37.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=jRnEU&margin=%5Bobject%20Object%5D&originHeight=194&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)<br />注意我们上边显示的只有字节,没有显示密文,这也是为了保护敏感数据
<a name="rvkrK"></a>
### 
<a name="Lu66L"></a>
### _4.2.5 创建Pod_

- 创建pod-secret.yaml文件,内容如下:
```yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-secret
  namespace: dev
spec:
  containers:
    - name: nginx
      image: nginx:1.17.1
      volumeMounts:
        - mountPath: /secret/config #要挂在的目录
          name: config
  volumes:  #挂载我们上述的secret
    - name: config
      secret:
        secretName: secret
  • 创建Pod:

kubectl create -f pod-secret.yaml
云原生-第二部分 - 图196

4.2.6 查看Pod

  • 查看Pod:

kubectl get pod pod-secret -n dev
云原生-第二部分 - 图197

4.2.7 进入容器

  • 进入容器,查看secret信息,发现已经自动解码了:

kubectl exec -it pod-secret -n dev /bin/sh
ls /secret/config
more /secret/config/username
more /secret/config/password
云原生-第二部分 - 图198
我们可以发现,在pod的文件中,他已经被自动解密了:
image.png

4.2.8 Secret的用途

  • imagePullSecret:Pod拉取私有镜像仓库的时使用的账户密码,会传递给kubelet,然后kubelet就可以拉取有密码的仓库里面的镜像。
  • 创建一个ImagePullSecret:

kubectl create secret docker-registry docker-harbor-registrykey —docker-server=192.168.18.119:85 \
—docker-username=admin —docker-password=Harbor12345 \
—docker-email=1900919313@qq.com

  • 查看是否创建成功:

kubectl get secret docker-harbor-registrykey

  • 新建redis.yaml文件,内容如下:

apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: 192.168.18.119:85/yuncloud/redis # 这是Harbor的镜像私有仓库地址
imagePullSecrets:
- name: docker-harbor-registrykey

  • 创建Pod:

kubectl apply -f redis.yaml

4.3 ConfigMap高级

4.3.1 概述

  • 在ConfigMap基础中,我们已经可以实现创建ConfigMap了,但是如果实际工作中这样使用,就会显得很繁琐。

注意事项:

  • ConfigMap 在设计上不是用来保存大量数据的。在 ConfigMap 中保存的数据不可超过 1 MiB。
  • 如果需要保存超出此尺寸限制的数据,需要考虑挂载存储卷或者使用独立的数据库或者文件服务。
  • 语法:

kubectl create configmap <map-name> <data-source>

4.3.2 从一个目录中创建ConfigMap

  • 示例:

    mkdir -pv configure-pod-container/configmap/
    wget https://kubernetes.io/examples/configmap/game.properties -O configure-pod-container/configmap/game.properties
    wget https://kubernetes.io/examples/configmap/ui.properties -O configure-pod-container/configmap/ui.properties
    kubectl create configmap cm1 --from-file=configure-pod-container/configmap/
    kubectl get cm cm1 -o yaml
    

    云原生-第二部分 - 图200

    4.3.3 从一个文件中创建ConfigMap

  • 示例:

    mkdir -pv configure-pod-container/configmap/
    wget https://kubernetes.io/examples/configmap/game.properties -O configure-pod-container/configmap/game.properties
    # 默认情况下的key的名称是文件的名称
    kubectl create configmap cm2 --from-file=configure-pod-container/configmap/game.properties
    

    云原生-第二部分 - 图201

    4.3.4 从一个文件中创建ConfigMap,并自定义ConfigMap中key的名称

  • 示例:

    mkdir -pv configure-pod-container/configmap/
    wget https://kubernetes.io/examples/configmap/game.properties -O configure-pod-container/configmap/game.properties
    kubectl create configmap cm3 --from-file=cm3=configure-pod-container/configmap/game.properties
    

    云原生-第二部分 - 图202

    4.3.5 从环境变量文件创建ConfigMap

  • 示例:

vim configure-pod-container/configmap/env-file.properties
# 语法规则:
# env 文件中的每一行必须为 VAR = VAL 格式。
# 以#开头的行(即注释)将被忽略。
# 空行将被忽略。
# 引号没有特殊处理(即它们将成为 ConfigMap 值的一部分)
enemies=aliens
lives=3
allowed=”true”
kubectl create cm cm4 —from-env-file=configure-pod-container/configmap/env-file.properties
云原生-第二部分 - 图203
注意:当—from-env-file从多个数据源创建ConfigMap的时候,仅仅最后一个env文件有效。

4.3.6 在命令行根据键值对创建ConfigMap

  • 示例:

kubectl create configmap cm5 —from-literal=special.how=very —from-literal=special.type=charm
云原生-第二部分 - 图204

4.3.7 使用ConfigMap定义容器环境变量

  • 示例:

    kubectl create configmap cm6 --from-literal=special.how=very --from-literal=special.type=charm
    vim test-pod.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: test-pod
    spec:
    containers:
      - name: test-container
        image: busybox
        command: [ "/bin/sh", "-c", "env" ]
        env:
          # 定义环境变量
          - name: SPECIAL_LEVEL_KEY
            valueFrom:
              configMapKeyRef:
                # ConfigMap的名称
                name: cm6
                # ConfigMap的key
                key: special.how
    restartPolicy: Never
    kubectl apply -f test-pod.yaml
    

    云原生-第二部分 - 图205

    4.3.8 将 ConfigMap 中的所有键值对配置为容器环境变量

  • 示例:

    kubectl create configmap cm7 --from-literal=special.how=very --from-literal=special.type=charm
    vim test-pod.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: test-pod
    spec:
    containers:
      - name: test-container
        image: busybox
        command: [ "/bin/sh", "-c", "env" ]
        envFrom:
        - configMapRef:
            name: cm7
    restartPolicy: Never
    kubectl apply -f test-pod.yaml
    

    云原生-第二部分 - 图206

    4.3.9 使用存储在 ConfigMap 中的数据填充容器

  • 示例:

    kubectl create configmap cm8 --from-literal=special.how=very --from-literal=special.type=charm
    vim test-pod.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: test-pod
    spec:
    containers:
      - name: test-container
        image: busybox
        command: [ "/bin/sh", "-c", "ls /etc/config/" ]
        volumeMounts:
        - name: config-volume
          mountPath: /etc/config
    volumes:
      - name: config-volume
        configMap:
          # configMap的名称
          name: cm8
    restartPolicy: Never
    kubectl apply -f test-pod.yaml
    

    云原生-第二部分 - 图207

    4.4 Secret高级

  • 略(和ConfigMap高级类似)。

    4.5 ConfigMap&&Secret使用SubPath解决目录覆盖问题

  • ConfigMap和Secret在进行目录挂载的时候会覆盖目录,我们可以使用SubPath解决这个问题。

  • 示例:

    # 创建一个Pod
    kubectl run nginx --image=nginx:1.17.1
    # 将nginx.conf导出到本地
    kubectl exec -it nginx -- cat /etc/nginx/nginx.conf > nginx.conf
    # 创建ConfigMap
    kubectl create cm nginx-conf --from-file=nginx.conf
    kubectl delete pod nginx
    vim nginx.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: nginx
    spec:
    containers:
      - name: nginx
        image: nginx:1.17.1
        command: [ "/bin/sh", "-c", "sleep 3600" ]
        volumeMounts:
        - name: nginx-conf
          mountPath: /etc/nginx
    volumes:
      - name: nginx-conf
        configMap:
          # configMap的名称
          name: nginx-conf
    restartPolicy: Never
    kubectl apply -f nginx.yaml
    kubectl exec -it nginx -- ls /etc/nginx
    

    云原生-第二部分 - 图208

    vim nginx.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: nginx
    spec:
    containers:
      - name: nginx
        image: nginx:1.17.1
        command: [ "/bin/sh", "-c", "sleep 3600" ]
        volumeMounts:
        - name: nginx-conf
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf # subPath:要覆盖文件的相对路径
    volumes:
      - name: nginx-conf
        configMap:
          # configMap的名称
          name: nginx-conf
          items:
           - key: nginx.conf # key:ConfigMap中的key的名称
             path: nginx.conf # 此处的path相当于 mv nginx.conf nginx.conf
    restartPolicy: Never
    kubectl apply -f nginx.yaml
    kubectl exec -it nginx -- ls /etc/nginx
    

    云原生-第二部分 - 图209

    4.6 ConfigMap&&Secret的热更新

  • 注意事项:

  • ①如果ConfigMap和Secret是以subPath的形式挂载的,那么Pod是不会感知到ConfigMap和Secret的更新的。
  • ②如果Pod的变量来自ConfigMap和Secret中定义的内容,那么ConfigMap和Secret更新后,也不会更新Pod中的变量。

    k8s的Helm

    Helm v3版本

    1 引入

  • kubernetes上的应用对象,都是由特定的资源描述组成,包括Deployment、Service等,都保存在各自文件中或者集中写在一个配置文件,然后通过kubectl apply -f 部署。如果应用只由一个或几个这样的服务组成,上面的部署方式就足够了。但是对于一个复杂的应用,会有很多类似上面的资源描述文件,例如微服务架构应用,组成应用的服务可能多达几十、上百个,如果有更新或回滚应用的需求,可能要修改和维护所涉及到大量的资源文件,而这种组织和管理应用的方式就显得力不从心了。并且由于缺少对发布过的应用进行版本管理和控制,使得kubernetes上的应用维护和更新面临诸多的挑战,主要面临以下的问题:

    • ①如何将这些服务作为一个整体管理?
    • ②这些资源文件如何高效复用?
    • ③应用级别的版本如何管理?

      2 概述

  • Helm是一个kubernetes的包管理工具,就像Linux下的包管理器,如yum、apt等,可以很方便的将之前打包好的yaml文件部署到kubernetes上。

  • Helm有3个重要概念:

    • helm:一个命令行客户端工具,主要用于kubernetes应用chart的创建、打包、发布和管理。
    • chart:应用描述,一系列用于描述kubernetes资源相关文件的集合。
    • release:基于chart的部署实体,一个chart被Helm运行后将会生成对应的一个release,将在kubernetes中创建出真实运行的资源对象。

      3 Helm v3变化

  • 2019年11月13日,Helm团队发布Helm v3的第一个稳定版本。

  • 该版本主要变化如下:
    • ①最明显的变化是Tiller删除。

云原生-第二部分 - 图210

  • ②release名称可以在不同的命名空间重用。
  • ③支持将chart推动到Docker镜像仓库中。
  • ④使用JSONSchema验证chart values。
  • ⑤其他。

    4 Helm客户端

    4.1 部署Helm客户端

wget https://get.helm.sh/helm-v3.2.1-linux-amd64.tar.gz

  • 解压Helm到/usr/bin目录:

tar -zxvf helm-v3.2.1-linux-amd64.tar.gz
cd linux-amd64/
cp helm /usr/bin/

4.2 配置国内的chart仓库

4.2.1 仓库概述

helm repo add 仓库名 仓库地址。

  • 更新仓库命令:

helm repo update

  • 示例:添加微软仓库,并更新仓库

helm repo add stable http://mirror.azure.cn/kubernetes/charts
helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
helm repo update

4.2.3 删除存储库

  • 删除存储库:

helm repo remove 仓库名

4.2.4 查看配置的存储库

  • 查看配置的存储库:

helm repo list
云原生-第二部分 - 图211

4.3 helm的常用命令

命令 描述
create 创建一个chart并指定名字
dependency 管理chart依赖
get 下载一个release。可用的子命令:all、hooks、manifest、notes、values。
history 获取release历史。
install 安装一个chart。
list 列出release。
package 将chart目录打包到chart存档文件中。
pull 从远程仓库中下载chart并解压到本地。比如:helm install stable/mysql —untar。
repo 添加、列出、移除、更新和索引chart仓库。可用的子命令:add、index、list、remove、update。
rollback 从之前的版本回退。
search 根据关键字搜索chart。可用的子命令:all、chart、readme、values。
show 查看chart的详细信息。可用的子命令:all、chart、readme、values。
status 显示已命名版本的状态。
template 本地呈现模板。
uninstall 卸载一个release。
upgrade 更新一个release。
version 查看Helm客户端版本。

5 Helm基本使用

5.1 使用chart部署一个应用

5.1.1 根据关键字搜索chart

  • 语法:

helm search repo|hub chart名称

  • 示例:查询名为weave的chart

helm search repo weave
云原生-第二部分 - 图212

5.1.2 查看chart信息

  • 语法:

helm show chart 仓库名/chart名称

  • 示例:查看名为stable的仓库中的名为mysql的chart

helm show chart stable/mysql
云原生-第二部分 - 图213

5.1.3 安装chart,形成release

  • 语法:

helm install 安装之后的名称 仓库名/chart的名称(即搜索之后的应用名称)

  • 示例:安装stable/weave-scope,并将其命名为ui

helm install ui stable/weave-scope
云原生-第二部分 - 图214

5.1.4 查看release列表

  • 语法:

helm list

  • 示例:查看当前的release列表

helm list
云原生-第二部分 - 图215

5.1.5 查看已命名release的状态

  • 语法:

helm status 安装之后的名称

  • 示例:查看安装chart的release为ui的状态

helm status ui
云原生-第二部分 - 图216

5.1.6 查看Service,并将其改为NodePort

  • 语法:

kubectl edit svc xxx

  • 示例:查看Service,并将其改为NodePort

kubectl edit svc ui-weave-scope
云原生-第二部分 - 图217

5.2 安装前自定义chart配置选项

5.2.1 概述

  • 自定义选项是因为并不是所有的chart都能按照默认配置运行成功,可能会需要一些环境依赖,例如PV。
  • 所以我们需要自定义chart配置选项,安装过程中有两种方法可以传递配置数据:

    • ①—values(或-f):指定带有覆盖的YAML文件。这里可以多次指定,最右边的文件优先。
    • ②—set:在命令行上指定替代。如果两种都用,那么—set的优先级高。

      5.2.2 —values的使用(不推荐,太麻烦)

      安装可能报错,需要自己手动安装PV。
  • 先将修改的变量写到一个文件中,并修改此文件。

helm show values stable/mysql > config.yaml

  • 查看这个文件:

cat config.yaml
— 修改部分
persistence:
enabled: true
accessMode: ReadWriteOnce
size: 8Gi

mysqlUser: “k8s”
mysqlPassword: “123456”
mysqlDatabase: “k8s”

  • 使用—values来替换默认的配置:

helm install -f config.yaml self-mysql stable/mysql

5.2.3 命令行替代变量(推荐)

  • 可以使用命令行替代变量:

helm install db —set persistence.storageClass=”managed-nfs-storage” stable/mysql

6 构建一个Helm Chart

6.1 开发步骤

  • ①使用helm create创建chart:

helm create chart名称

  • ②进入自定义chart目录的templates目录中,修改其中的deployment.yaml和service.yaml等文件。

cd chart名称/templates
云原生-第二部分 - 图218

  • ③通过刚才创建的chart目录,将其部署:

helm install xxx chart名称

  • ④打包chart,让别人共享:

helm package chart名称

6.2 应用示例

  • 示例:本人是在root目录下操作的
  • 创建chart:

helm create nginx

  • 进入chart目录,修改values.yaml文件,内容如下:

cd nginx
vim values.yaml
replicas: 3
image: nginx
tag: 1.17
serviceport: 80
targetport: 80
label: nginx

  • 进入templates目录:

cd emplates/

  • 删除templates目录中的所有文件和文件夹:

rm -rf *

  • 修改deployment.yaml文件,内容如下:

vim deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: {{ .Values.label }} 
  name: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicas }} 
  selector:
    matchLabels:
      app: {{ .Values.label }} 
  strategy: {}
  template:
    metadata:
      labels:
        app: {{ .Values.label }} 
    spec:
      containers:
      - image: {{ .Values.image }}:{{ .Values.tag }} 
        name: {{ .Values.image }}
  • 修改service.yaml文件,内容如下:

vim service.yaml

apiVersion:   v1
kind: Service
metadata:
  labels:
    app: {{ .Values.label }} 
  name:  {{ .Release.Name }}
spec: 
  ports:
    -  port: {{ .Values.serviceport }} 
       protocol: TCP
       targetPort: {{ .Values.targetport }} 
  selector:
    app: {{ .Values.label }} 
  type: NodePort
  • 切换到chart目录的上一层目录:

cd ~

  • 安装应用:

helm install nginx nginx/

6.3 调试

  • Helm也提供了—dry-run和—debug调试参数,帮助你验证模板的正确性。在执行helm install的时候带上这两个参数就可以把对应的values值和渲染的资源清单打印出来,而不是真正的做部署一个release。
  • 示例:

helm install nginx nginx/ —dry-run —debug

6.4 内置对象

  • 上面我们使用的{{ .Release.Name }}将release的名称插入到模板中,这里的Release就是Helm的内置对象,下面是一些常用的内置对象: | 内置对象 | 描述 | | —- | —- | | Release.Name | release的名称 | | Release.Namespace | release的命名空间 | | Release.Service | release的服务的名称 | | Release.Revision | release的修订版本号,从1开始累加 |

6.5 Values

  • Values对象是为Chart模板提供值,这个对象的值有4个来源:
    • chart包中的values.yaml文件。
    • 父chart包的values.yaml文件。
    • 通过helm install或者helm upgrade的-f或者—values参数传入的自定义的yaml文件。
    • 通过—set参数传入的值。
  • Chart的values.yaml提供的值可以被用户提供的values文件覆盖,而该文件同样可以被—set参数所覆盖,换言之,—set参数的优先级高。

    6.6 升级、回滚和删除

    6.6.1 升级

  • 发布新版本的chart时,或者当我们需要更改发布的配置,可以使用helm upgrade命令:

helm upgrade —set imageTag=1.18 nginx nginx
helm upgrade -f values.yaml nginx nginx

6.6.2 回滚

  • 如果在发布后没有达到预期的效果,则可以使用helm rollback回滚到之前的版本:

helm rollback nginx 1

6.6.3 卸载发行版本

  • 卸载发行版本,可以使用helm uninstall命令:

helm uninstall nginx

6.6.4 查看历史版本配置信息

  • 查看历史版本配置信息:

helm get all —revision 1 nginx

6.7 管道和函数

6.7.1 管道

  • 在上面的案例中,其实是将值传递给模板引擎进行渲染,模板引擎还支持对拿到的数据进行二次处理。
  • 示例:从.Values中读取的值变成字符串,可以使用quote函数实现。

vi templates/deployment.yaml
# 略
app: {{ quote .Values.label.app }}
# 略
helm install —dry-run nginx ../nginx/ app:”nginx”
quote .Values.label.app将后面的值作为参数传递过quote函数。
模板函数调用语法为:functionName arg1 arg2…

6.7.2 default函数

  • default函数运行在模板中指定默认值,以防止该值会忽略掉。如果忘记定义,执行helm install的时候会因为缺少字段而无法创建资源,这时就可以定义一个默认值了。
  • 示例:

vim templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: {{ .Values.label }}
  name: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicas }}
  selector:
    matchLabels:
      app: {{ .Values.label }}
  strategy: {}
  template:
    metadata:
      labels:
        app: {{ .Values.label }}
    spec:
      containers:
      - image: {{ .Values.image| default "nginx" }}:{{ .Values.tag }}
        name: {{ .Values.image }}

6.7.3 其他函数

  • 缩进函数:

{{ .Values.resources | indent 12 }}

  • 大写:

{{ upper .Values.resources }}

  • 首字母大写:

{{ title .Values.resources }}

7 流程控制

7.1 概述

  • 流程控制是为模板提供了一种能力,满足更复杂的数据逻辑处理。
  • Helm模板语言提供以下流程控制语句:

    • if/else条件块。
    • with指定范围。
    • range循环块。

      7.2 if/else

  • if/else块是用于在模板有条件的包含文本块的方法,条件块的基本结构如下:

{{ if 条件表达式}}
# xxx
{{ else if 条件表达式}}
# xxx
{{ else }}
# xxx
{{ end }}

  • 条件判断:就是判断条件是否为真,如果值为以下几种情况则为false,否则为true:
    • 一个布尔类型的false。
    • 一个数字0。
    • 一个空的字符串。
    • 一个空的集合(map、slice、tuple、dict、array)。
  • 示例:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    labels:
      app: {{ .Values.label }}
    name: {{ .Release.Name }}
    spec:
    replicas: {{ .Values.replicas }}
    selector:
      matchLabels:
        app: {{ .Values.label }}
    strategy: {}
    template:
      metadata:
        labels:
          app: {{ .Values.label }}
      spec:
        containers:
          - image: {{ .Values.image| default "nginx" }}:{{ .Values.tag }}
            name: {{ .Values.image }}
            env:
              {{ if eq .Values.devops "k8s" }}
            - name : hello
              value: "123"
              {{ else }}
            - name : hello
              value: "456"
              {{ end }}
    

    还可以使用ne、lt、gt、and、or等运算符。

  • 通过模板引擎渲染一下,会得到如下的结果:

helm install —dry-run nginx nginx
云原生-第二部分 - 图219

  • 可以看到渲染出来会有多余的空行,这是因为当模板引擎运行的时候,会将控制指令删除,所以之前占的位置也就空白了,需要使用{{- if …}}的方式消除此空行

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    labels:
      app: {{ .Values.label }}
    name: {{ .Release.Name }}
    spec:
    replicas: {{ .Values.replicas }}
    selector:
      matchLabels:
        app: {{ .Values.label }}
    strategy: {}
    template:
      metadata:
        labels:
          app: {{ .Values.label }}
      spec:
        containers:
          - image: {{ .Values.image| default "nginx" }}:{{ .Values.tag }}
            name: {{ .Values.image }}
            env:
              {{- if eq .Values.devops "k8s" }}
            - name : hello
              value: "123"
              {{- else }}
            - name : hello
              value: "456"
              {{- end }}
    
  • 通过模板引擎渲染一下,会得到如下的结果:

helm install —dry-run nginx nginx
云原生-第二部分 - 图220

  • 如果使用了{{- if … -}}那么就需要谨慎了,比如:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    labels:
      app: {{ .Values.label }}
    name: {{ .Release.Name }}
    spec:
    replicas: {{ .Values.replicas }}
    selector:
      matchLabels:
        app: {{ .Values.label }}
    strategy: {}
    template:
      metadata:
        labels:
          app: {{ .Values.label }}
      spec:
        containers:
          - image: {{ .Values.image| default "nginx" }}:{{ .Values.tag }}
            name: {{ .Values.image }}
            env:
              {{- if eq .Values.devops "k8s" -}}
            - name : hello
              value: "123"
              {{- else }}
            - name : hello
              value: "456"
              {{- end }}
    
  • 通过模板引擎渲染一下,会得到如下的结果:

helm install —dry-run nginx nginx
云原生-第二部分 - 图221

  • 相当于下面这种格式: ```yaml NAME: nginx LAST DEPLOYED: Mon Jan 11 20:46:10 2021 NAMESPACE: default STATUS: pending-install REVISION: 1 TEST SUITE: None HOOKS: MANIFEST:

Source: nginx/templates/service.yaml

apiVersion: v1 kind: Service metadata: labels: app: nginx name: nginx spec: ports:

-  port: 80 
   protocol: TCP
   targetPort: 80 

selector: app: nginx

type: NodePort

Source: nginx/templates/deployment.yaml

apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx spec: replicas: 3 selector: matchLabels: app: nginx strategy: {} template: metadata: labels: app: nginx spec: containers:

    - image: nginx:1.17
      name: nginx
      env: - name: hello value: 123

- 当然不对,因为{{- if ... -}}删除了双方的换行符。
<a name="JgioS"></a>
## 7.3 range

- 在Helm模板语言中,使用range关键字来进行循环操作。
- 示例:
- 在values.yaml中添加一个变量列表

test:<br />  - 1 <br />  - 2 <br />  - 3

- 循环打印该列表:
```yaml
apiVersion: v1 
kind: ConfigMap 
metadata:
  name:    {{   .Release.Name    }} 
data:
  test:    
    {{- range . Values.test }}
      {{   .    }}
    {{-   end   }}

7.4 with

  • with可以用来控制变量作用域。
  • 在前面我们使用{{ .Release.xxx }}或者{{ .Values.xxx }},其中.就是表示对当前范围的引用,.values就是告诉模板在当前范围中查找Values对象的值。
  • with语句就可以用来控制变量的作用域范围,其语法和一个简单的if语句类似:

{{ with 条件表达式 }}
# xxx
{{ end }}

  • with语句可以允许将当前范围的.设置为特定的对象,比如我们前面一直使用的.Values.label,我们可以使用with来将.范围指向.Values.label。
  • 示例:
  • values.yaml ```yaml

nodeSelector: team: a gpu: yes ● deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: {{- with .Values.nodeSelector }} nodeSelector: team: {{ .team }} gpu: {{ .gpu }} {{- end }}

<a name="es5TS"></a>
## 7.5 命名模板

- 需要复用代码的地方可以使用命名模板。
- 命名模板:使用define定义,template引入,在templates目录中默认下划线开头的文件为公共模板(比如_helpers.tpl)。
- 示例:
- _helpers.tpl:

{{-   define   "demo.fullname"   -}}<br />{{- .Chart.Name -}}-{{ .Release.Name }}<br />{{-   end   -}}

- deployment.yaml
```yaml
apiVersion:   apps/v1 
kind:   Deployment 
metadata:
  name:  {{   template"demo.fullname"   .    }}
# 其他略

template指令是将一个模板包含在另一个模板中的方法。但是,template函数不能用于Go模板管道,为了解决该问题,增加了include功能。

  • _helpers.tpl:

{{- define “demo.labels” -}}
app: {{ template”demo.fullname” . }}
chart: “{{ .Chart.Name }}-{{ .Chart.Version }}”
release: “{{ .Release.Name }}”
{{- end -}}

  • deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include”demo.fullname” . }}
labels:
{{- include “demo.labels” . | nindent 4 }}

上面包含一个名为demo.labels的模板,然后将值 . 传递给模板,最后将该模板的 输出传递给nindent函数。

8 开发自己的chart

  • 创建模板。
  • 修改Chart.yaml,Values.yaml,添加常用的变量。
  • 在templates目录下创建部署镜像所需要的yaml文件,并使用变量引用yaml文件里面经常变动的字段。

    k8s的安全认证

    1 访问控制概述

    1.1 概述

  • kubernetes作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。所谓的安全性其实就是保证对kubernetes的各种客户端进行认证和授权操作。

    1.2 客户端

  • 在kubernetes集群中,客户端通常由两类:

    • ① User Account:一般是独立于kubernetes之外的其他服务管理的用户账号。
    • ② Service Account:kubernetes管理的账号,用于为Pod的服务进程在访问kubernetes时提供身份标识。

云原生-第二部分 - 图222

1.3 认证、授权和准入控制

  • API Server是访问和管理资源对象的唯一入口。任何一个请求访问API Server,都要经过下面的三个流程:
    • ① Authentication(认证):身份鉴别,只有正确的账号才能通过认证。
    • ② Authorization(授权):判断用户是否有权限对访问的资源执行特定的动作。
    • ③ Admission Control(注入控制):用于补充授权机制以实现更加精细的访问控制功能。

云原生-第二部分 - 图223

2 认证管理

2.1 kubernetes的客户端身份认证方式

  • kubernetes集群安全的关键点在于如何识别并认证客户端身份,它提供了3种客户端身份认证方式:
  • ① HTTP Base认证:
    • 通过用户名+密码的方式进行认证。
    • 这种方式是把“用户名:密码”用BASE64算法进行编码后的字符串放在HTTP请求中的Header的Authorization域里面发送给服务端。服务端收到后进行解码,获取用户名和密码,然后进行用户身份认证的过程。
  • ② HTTP Token认证:
    • 通过一个Token来识别合法用户。
    • 这种认证方式是用一个很长的难以被模仿的字符串—Token来表明客户端身份的一种方式。每个Token对应一个用户名,当客户端发起API调用请求的时候,需要在HTTP的Header中放入Token,API Server接受到Token后会和服务器中保存的Token进行比对,然后进行用户身份认证的过程。
  • ③ HTTPS证书认证:(推荐)
    • 基于CA根证书签名的双向数字证书认证方式。
    • 这种认证方式是安全性最高的一种方式,但是同时也是操作起来最麻烦的一种方式。

云原生-第二部分 - 图224

2.2 HTTPS认证过程

  • ① 证书申请和下发:
    • HTTPS通信双方的服务器向CA机构申请证书,CA机构发根证书、服务端证书及私钥给申请者。
  • ② 客户端和服务器的双向认证:
    • 客户端向服务端发起请求,服务端下发自己的证书给客户端。客户端收到证书后,通过私钥解密证书,在证书中获取服务端的私钥。客户端利用服务器端的公钥认证证书中的信息,如果一致,则认可这个服务器。
    • 客户端发送自己的证书给服务器端,服务端接收到证书后,通过私钥解密证书。在证书中获取客户端的公钥,并用该公钥认证证书信息,确认客户端是否合法。
  • ③ 服务器端和客户端进行通信。

    • 服务器端和客户端协商好加密方案后,客户端会产生一个随机的私钥并加密,然后发送到服务器端。
    • 服务器端接收到这个私钥后,双方接下来通信的所有内容都通过该随机私钥加密。

      2.3 总结

  • kubernetes允许同时配置多种认证方式(上述三种都可以配置上),只要其中任意一种方式认证通过即可。

    3 授权管理

    3.1 概述

  • 授权发生在认证成功之后,通过认证就可以知道请求用户是谁,然后kubernetes会根据事先定义的授权策略来决定用户是否有权限访问,这个过程就称为授权。

  • 每个发送到API Server的请求都带上了用户和资源的信息:比如发送请求的用户、请求的路径、请求的动作等,授权就是根据这些信息和授权策略进行比较,如果符合策略,则认为授权通过,否则会返回错误。

    3.2 API Server目前支持的几种授权策略

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

  • AlwaysAllow:允许接收所有的请求,相当于集群不需要授权流程(kubernetes默认的策略)。
  • ABAC:基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制。不常见
  • Webhook:通过调用外部REST服务对用户进行授权。不常见
  • Node:是一种专用模式,用于对kubelet发出的请求进行访问控制。不常见
  • RBAC:基于角色的访问控制(kubeadm安装方式下的默认选项)。

    3.3 RBAC

    3.3.1 概述

  • RBAC(Role Based Access Control):基于角色的访问控制,主要是在描述一件事情:给哪些对象授权了哪些权限。

  • RBAC涉及到了下面几个概念:
    • 对象:User、Groups、ServiceAccount。
    • 角色:代表着一组定义在资源上的可操作的动作(权限)的集合。
    • 绑定:将定义好的角色和用户绑定在一起。

云原生-第二部分 - 图225

  • RBAC还引入了4个顶级资源对象:

    • Role、ClusterRole:角色,用于指定一组权限。这里Cluster的开头的一般是集群级别的角色,而单独的Role开头的则是namespace开头的角色。
    • RoleBinding、ClusterRoleBinding:角色绑定,用于将角色(权限的集合)赋予给对象。

      3.3.2 Role、ClusterRole

  • 一个角色就是一组权限的集合,这里的权限都是许可形式的(白名单)。

  • Role的资源清单文件: ```yaml

    Role只能对命名空间的资源进行授权,需要指定namespace

    apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: authorization-role namespace: dev rules:

    • apiGroups: [“”] # 支持的API组列表,””空字符串,表示核心API群 resources: [“pods”] # 支持的资源对象列表 verbs: [“get”,”watch”,”list”] #允许的对资源对象的操作方法列表

      上述加一起name.apiGroups.resources.verbs:就是authorization-role对 API组列表的pods资源有get,watch,list的访问权限


- ClusterRole的资源清单文件:
```yaml
# ClusterRole可以对集群范围内的资源、跨namespace的范围资源、非资源类型进行授权,所以他不需要指定namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: authorization-clusterrole
rules:
  - apiGroups: [""] # 支持的API组列表,""空字符串,表示核心API群
    resources: ["pods"] # 支持的资源对象列表
    verbs: ["get","watch","list"]

rules中的参数说明:

  • apiGroups:
    • 支持的API组列表。
    • “”,”apps”,”autoscaling”,”batch”。
  • resources:
    • 支持的资源对象列表。
    • “services”,”endpoints”,”pods”,”secrets”,”configmaps”,”crontabs”,”deployments”,”jobs”,”nodes”,”rolebindings”,”clusterroles”,”daemonsets”,”replicasets”,”statefulsets”,”horizontalpodautoscalers”,”replicationcontrollers”,”cronjobs”。
  • verbs:

    • 对资源对象的操作方法列表。就是我们的相关命令
    • “get”, “list”, “watch”, “create”, “update”, “patch”, “delete”, “exec”。

      3.3.3 RoleBinding、ClusterRoleBinding

  • 角色绑定用来把一个角色绑定到一个目标对象上,绑定目标可以是User、Group或者ServiceAccount。

  • RoleBinding的资源清单文件:

    # RoleBinding可以将同一namespace中的subject对象绑定到某个Role下,则此Subject具有该Role定义的权限
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    name: authorization-role-binding
    namespace: dev
    subjects: #这里就是用来定义对象的
    - kind: User
      name: xudaxian
      apiGroup: rbac.authorization.k8s.io  
    roleRef: #对象要绑定的角色。
    apiGroup: rbac.authorization.k8s.io
    kind: Role
    name: authorization-role
    
  • ClusterRoleBinding的资源清单文件:

    # ClusterRoleBinding在整个集群级别和所有namespaces将特定的subject与ClusterRole绑定,授予权限
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
    name: authorization-clusterrole-binding
    subjects:
    - kind: User
      name: xudaxian
      apiGroup: rbac.authorization.k8s.io
    roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole
    name: authorization-clusterrole
    

    3.3.4 RoleBinding引用ClusterRole进行授权

  • RoleBinding可以引用ClusterRole,对属于同一命名空间内ClusterRole定义的资源主体进行授权。

  • 一种很常用的做法是,集群管理员为集群范围预定义好一组角色(ClusterRole),然后在多个命名空间中重复使用这些ClusterRole。这样可以大幅度提高授权管理工作效率,也使得各个命名空间下的基础性授权规则和使用体验保持一致。

    # 虽然authorization-clusterrole是一个集群角色,但是因为使用了RoleBinding
    # 所以xudaxian只能读取dev命名空间中的资源
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
    name: authorization-clusterrole-binding
    subjects:
    - kind: User
      name: xudaxian
      apiGroup: rbac.authorization.k8s.io
    roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole
    name: authorization-clusterrole
    

    3.4 RBAC实战

    3.4.1 需求

  • 创建一个只能管理dev命名空间下Pods资源的账号。

    3.4.2 创建账号

    ```shell ● 创建证书: cd /etc/kubernetes/pki/ (umask 077;openssl genrsa -out devman.key 2048)

● 用API Server的证书去签署证书: ○ 签名申请:申请的用户是devman,组是devgroup openssl req -new -key devman.key -out devman.csr -subj “/CN=devman/O=devgroup”

○ 签署证书: openssl x509 -req -in devman.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out devman.crt -days 3650

● 设置集群、用户、上下文信息: kubectl config set-cluster kubernetes —embed-certs=true —certificate-authority=/etc/kubernetes/pki/ca.crt —server=https://192.168.18.100:6443 kubectl config set-credentials devman —embed-certs=true —client-certificate=/etc/kubernetes/pki/devman.crt —client-key=/etc/kubernetes/pki/devman.key kubectl config set-context devman@kubernetes —cluster=kubernetes —user=devman

● 切换账号到devman: kubectl config use-context devman@kubernetes

● 查看dev下的Pod,发现没有权限: kubectl get pods -n dev

![](https://cdn.nlark.com/yuque/0/2021/png/513185/1610071333624-1e7cf1d6-c816-4d69-8ecb-1ceed8a63807.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_25%2Ctext_6K645aSn5LuZ%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10#crop=0&crop=0&crop=1&crop=1&from=url&id=vEKs9&margin=%5Bobject%20Object%5D&originHeight=94&originWidth=889&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)

- 切换到admin账户:

kubectl config use-context kubernetes-admin@kubernetes
<a name="MdydG"></a>
### _3.4.3 创建Role和RoleBinding,为devman授权_

- 创建dev-role.yaml文件,内容如下:
```yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev-role
  namespace: dev
rules:
  - apiGroups: [""] # 支持的API组列表,""空字符串,表示核心API群
    resources: ["pods"] # 支持的资源对象列表
    verbs: ["get","watch","list"]

---

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: authorization-role-binding 
  namespace: dev
subjects:
  - kind: User
    name: devman
    apiGroup: rbac.authorization.k8s.io  #这里应该是固定写法
roleRef:
  kind: Role 
  name: dev-role
  apiGroup: rbac.authorization.k8s.io
  • 创建Role和RoleBinding:

kubectl create -f dev-role.yaml
云原生-第二部分 - 图226

3.4.4 切换账户,再次验证

  • 切换账户到devman:

kubectl config use-context devman@kubernetes

  • 再次查看:

kubectl get pod -n dev
云原生-第二部分 - 图227

  • 查看deployment:

image.png
可以发现是查不到的

  • 切回admin账户:

kubectl config use-context kubernetes-admin@kubernetes

4 准入控制

4.1 概述

  • 通过了前面的认证和授权之后,还需要经过准入控制通过之后,API Server才会处理这个请求。
  • 准入控制是一个可配置的控制器列表,可以通过在API Server上通过命令行设置选择执行哪些注入控制器。

    --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds
    
  • 只有当所有的注入控制器都检查通过之后,API Server才会执行该请求,否则返回拒绝。

    4.2 当前可配置的Admission Control(准入控制)

  • AlwaysAdmit:允许所有请求。

  • AlwaysDeny:禁止所有请求,一般用于测试。
  • AlwaysPullImages:在启动容器之前总去下载镜像。
  • DenyExecOnPrivileged:它会拦截所有想在Privileged Container上执行命令的请求。
  • ImagePolicyWebhook:这个插件将允许后端的一个Webhook程序来完成admission controller的功能。
  • Service Account:实现ServiceAccount实现了自动化。
  • SecurityContextDeny:这个插件将使用SecurityContext的Pod中的定义全部失效。
  • ResourceQuota:用于资源配额管理目的,观察所有请求,确保在namespace上的配额不会超标。
  • LimitRanger:用于资源限制管理,作用于namespace上,确保对Pod进行资源限制。
  • InitialResources:为未设置资源请求与限制的Pod,根据其镜像的历史资源的使用情况进行设置。
  • NamespaceLifecycle:如果尝试在一个不存在的namespace中创建资源对象,则该创建请求将被拒 绝。当删除一个namespace时,系统将会删除该namespace中所有对象。
  • DefaultStorageClass:为了实现共享存储的动态供应,为未指定StorageClass或PV的PVC尝试匹配默认StorageClass,尽可能减少用户在申请PVC时所需了解的后端存储细节。
  • DefaultTolerationSeconds:这个插件为那些没有设置forgiveness tolerations并具有notready:NoExecute和unreachable:NoExecute两种taints的Pod设置默认的“容忍”时间,为5min。
  • PodSecurityPolicy:这个插件用于在创建或修改Pod时决定是否根据Pod的security context和可用的 PodSecurityPolicy对Pod的安全策略进行控制

    搭建DashBoard

    1 概述

  • 之前在kubernetes中完成的所有操作都是通过命令行工具kubectl完成的。其实,为了提供更丰富的用户体验,kubernetes还开发了一个基于web的用户界面(DashBoard)。用户可以使用DashBoard部署容器化的应用,而且还可以监控应用的状态,执行故障排查以及管理kubernetes中的各种资源。

  • 可以说能用kubectl完成的大部分都能用DashBoard完成。

    2 部署DashBoard

    2.1 下载yaml,并运行DashBoard

  • 下载yaml(网络不行,请点这里📎recommended.yaml):

wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml

  • 修改kubernetes-dashboard的Service类型

    vim recommended.yaml
    kind: Service
    apiVersion: v1
    metadata:
    labels:
      k8s-app: kubernetes-dashboard
    name: kubernetes-dashboard
    namespace: kubernetes-dashboard
    spec:
    type: NodePort # 新增,就是我们上述那个recommanded.yaml中要修改的地方
    ports:
      - port: 443
        targetPort: 8443
        nodePort: 30009 # 新增,就是我们上述那个recommanded.yaml中要修改的地方
    selector:
      k8s-app: kubernetes-dashboard
    

    云原生-第二部分 - 图229

  • 部署DashBoard:

kubectl create -f recommended.yaml
云原生-第二部分 - 图230

  • 查看namespace为kubernetes-dashboard下的资源:

kubectl get pod,svc -n kubernetes-dashboard
云原生-第二部分 - 图231

2.2 创建账户,获取token

  • 创建账户:

kubectl create serviceaccount dashboard-admin -n kubernetes-dashboard
云原生-第二部分 - 图232

  • 授权:

kubectl create clusterrolebinding dashboard-admin-rb --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin
云原生-第二部分 - 图233

  • 获取账号token:

kubectl get secrets -n kubernetes-dashboard | grep dashboard-admin
云原生-第二部分 - 图234
kubectl describe secrets dashboard-admin-token-b992l -n kubernetes-dashboard
云原生-第二部分 - 图235

2.3 通过浏览器访问DashBoard的UI

云原生-第二部分 - 图236

  • 出现下面的页面代表成功部署DashBoard:

云原生-第二部分 - 图237

kubeadm安装高可用k8s集群

电脑内存最好32G以上。

1 高可用集群规划图

云原生-第二部分 - 图238

2 主机规划

角色 IP地址 操作系统 配置 主机名称
Master1 192.168.18.100 CentOS7.x,基础设施服务器 2核CPU,3G内存,50G硬盘 k8s-master01
Master2 192.168.18.101 CentOS7.x,基础设施服务器 2核CPU,3G内存,50G硬盘 k8s-master02
Master3 192.168.18.102 CentOS7.x,基础设施服务器 2核CPU,3G内存,50G硬盘 k8s-master03
Node1 192.168.18.103 CentOS7.x,基础设施服务器 2核CPU,3G内存,50G硬盘 k8s-node01
Node1 192.168.18.104 CentOS7.x,基础设施服务器 2核CPU,3G内存,50G硬盘 k8s-node02

3 环境搭建

3.1 前言

  • 本次搭建的环境需要五台CentOS服务器(三主二从),然后在每台服务器中分别安装Docker、kubeadm和kubectl以及kubelet。

没有特殊说明,就是所有机器都需要执行。

3.2 环境初始化

3.2.1 检查操作系统的版本

  • 检查操作系统的版本(要求操作系统的版本至少在7.5以上):

cat /etc/redhat-release
云原生-第二部分 - 图239

3.2.2 关闭防火墙并禁止防火墙开机启动

  • 关闭防火墙:

systemctl stop firewalld

  • 禁止防火墙开机启动:

systemctl disable firewalld

3.2.3 设置主机名

  • 设置主机名:

hostnamectl set-hostname

  • 设置192.168.18.100的主机名:

hostnamectl set-hostname k8s-master01

  • 设置192.168.18.101的主机名:

hostnamectl set-hostname k8s-master02

  • 设置192.168.18.102的主机名:

hostnamectl set-hostname k8s-master03

  • 设置192.168.18.103的主机名:

hostnamectl set-hostname k8s-node01

  • 设置192.168.18.104的主机名:

hostnamectl set-hostname k8s-node02

3.2.4 主机名解析

  • 为了方便后面集群节点间的直接调用,需要配置一下主机名解析,企业中推荐使用内部的DNS服务器。

cat >> /etc/hosts << EOF
192.168.18.100 k8s-master01
192.168.18.101 k8s-master02
192.168.18.102 k8s-master03
192.168.18.103 k8s-node01
192.168.18.104 k8s-node02
192.168.18.110 k8s-master-lb # VIP(虚拟IP)用于LoadBalance,如果不是高可用集群,该IP可以是k8s-master01的IP
EOF

3.2.5 时间同步

  • kubernetes要求集群中的节点时间必须精确一致,所以在每个节点上添加时间同步:

yum install ntpdate -y
ntpdate time.windows.com

3.2.6 关闭selinux

  • 查看selinux是否开启:

getenforce

  • 永久关闭selinux,需要重启:

sed -i ‘s/enforcing/disabled/‘ /etc/selinux/config

  • 临时关闭selinux,重启之后,无效:

setenforce 0

3.2.7 关闭swap分区

  • 永久关闭swap分区,需要重启:

sed -ri ‘s/.swap./#&/‘ /etc/fstab

  • 临时关闭swap分区,重启之后,无效::

swapoff -a

3.2.8 将桥接的IPv4流量传递到iptables的链

  • 在每个节点上将桥接的IPv4流量传递到iptables的链:

cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
vm.swappiness = 0
EOF
# 加载br_netfilter模块
modprobe br_netfilter
# 查看是否加载
lsmod | grep br_netfilter
# 生效
sysctl —system

3.2.9 开启ipvs

  • 在kubernetes中service有两种代理模型,一种是基于iptables,另一种是基于ipvs的。ipvs的性能要高于iptables的,但是如果要使用它,需要手动载入ipvs模块。
  • 在每个节点安装ipset和ipvsadm:

yum -y install ipset ipvsadm

  • 在所有节点执行如下脚本:

cat > /etc/sysconfig/modules/ipvs.modules <#!/bin/bash
modprobe — ip_vs
modprobe — ip_vs_rr
modprobe — ip_vs_wrr
modprobe — ip_vs_sh
modprobe — nf_conntrack_ipv4
EOF

  • 授权、运行、检查是否加载:

chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4

  • 检查是否加载:

lsmod | grep -e ipvs -e nf_conntrack_ipv4

3.2.10 所有节点配置limit

  • 临时生效:

ulimit -SHn 65536

  • 永久生效:

vim /etc/security/limits.conf
# 末尾追加如下的内容
soft nofile 65536
hard nofile 65536
soft nproc 4096
hard nproc 4096
soft memlock unlimited
soft memlock unlimited

3.2.11 在k8s-master01节点设置免密钥登录到其他节点

  • 在k8s-master01节点生成配置文件和整数,并传输到其他节点上。

遇到输入,直接Enter即可
ssh-keygen -t rsa
for i in k8s-master01 k8s-master02 k8s-master03 k8s-node01 k8s-node02;do ssh-copy-id -i .ssh/id_rsa.pub $i;done

3.2.12 所有节点升级系统并重启

  • 所有节点升级系统并重启,此处没有升级内核:

yum -y —exclude=kernel* update && reboot

3.3 内核配置

3.3.1 查看默认的内核

  • 查看默认的内核:

uname -r
云原生-第二部分 - 图240

3.3.2 升级内核配置

  • CentOS7需要升级内核到4.18+。
  • 在 CentOS 7 上启用 ELRepo 仓库:

rpm —import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm

  • 仓库启用后,你可以使用下面的命令列出可用的内核相关包:

yum —disablerepo=”*” —enablerepo=”elrepo-kernel” list available

  • 安装最新的主线稳定内核:

yum -y —enablerepo=elrepo-kernel install kernel-ml

  • 设置 GRUB 默认的内核版本:

vim /etc/default/grub
# 修改部分, GRUB 初始化页面的第一个内核将作为默认内核
GRUB_DEFAULT=0
# 重新创建内核配置
grub2-mkconfig -o /boot/grub2/grub.cfg

  • 重启机器应用最新内核:

reboot

  • 查看最新内核版本:

uname -sr

3.4 每个节点安装Docker、kubeadm、kubelete和kubectl

3.4.1 安装Docker

  • 安装Docker:

wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
yum -y install docker-ce-20.10.2
systemctl enable docker && systemctl start docker
docker version

  • 设置Docker镜像加速器:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-‘EOF’
{
“exec-opts”: [“native.cgroupdriver=systemd”],
“registry-mirrors”: [“https://b9pmyelo.mirror.aliyuncs.com“],
“live-restore”: true,
“log-driver”:”json-file”,
“log-opts”: {“max-size”:”500m”, “max-file”:”3”}
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

3.4.2 添加阿里云的YUM软件源

  • 由于kubernetes的镜像源在国外,非常慢,这里切换成国内的阿里云镜像源:

cat > /etc/yum.repos.d/kubernetes.repo << EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF

3.4.3 安装kubeadm、kubelet和kubectl

  • 查看kubeadm的版本:

yum list kubeadm.x86_64 —showduplicates | sort -r

  • 由于版本更新频繁,这里指定版本号部署:

yum install -y kubelet-1.20.2 kubeadm-1.20.2 kubectl-1.20.2

  • 为了实现Docker使用的cgroup drvier和kubelet使用的cgroup drver一致,建议修改”/etc/sysconfig/kubelet”文件的内容:

vim /etc/sysconfig/kubelet
# 修改
KUBELET_EXTRA_ARGS=”—cgroup-driver=systemd”
KUBE_PROXY_MODE=”ipvs”

  • 所有的节点设置为开机自启动即可,由于没有生成配置文件,集群初始化后会自动启动:

systemctl enable kubelet

3.5 高可用组件安装

注意:如果不是高可用集群,haproxy和keepalived无需安装。

  • k8s-master01、k8s-master02、k8s-master03节点通过yum安装HAProxy和keepAlived。

yum -y install keepalived haproxy

  • k8s-master01、k8s-master02、k8s-master03节点配置HAProxy:

mkdir -pv /etc/haproxy
vim /etc/haproxy/haproxy.cfg
global
maxconn 2000
ulimit-n 16384
log 127.0.0.1 local0 err
stats timeout 30s

defaults
log global
mode http
option httplog
timeout connect 5000
timeout client 50000
timeout server 50000
timeout http-request 15s
timeout http-keep-alive 15s

frontend monitor-in
bind :33305
mode http
option httplog
monitor-uri /monitor

listen stats
bind
:8006
mode http
stats enable
stats hide-version
stats uri /stats
stats refresh 30s
stats realm Haproxy\ Statistics
stats auth admin:admin

frontend k8s-master
bind 0.0.0.0:16443
bind 127.0.0.1:16443
mode tcp
option tcplog
tcp-request inspect-delay 5s
default_backend k8s-master

backend k8s-master
mode tcp
option tcplog
option tcp-check
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
# 下面的配置根据实际情况修改
server k8s-master01 192.168.18.100:6443 check
server k8s-master02 192.168.18.101:6443 check
server k8s-master03 192.168.18.102:6443 check

  • k8s-master01配置Keepalived:

vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
## 标识本节点的字条串,通常为 hostname
router_id k8s-master01
script_user root
enable_script_security
}
## 检测脚本
## keepalived 会定时执行脚本并对脚本执行的结果进行分析,动态调整 vrrp_instance 的优先级。如果脚本执行结果为 0,并且 weight 配置的值大于 0,则优先级相应的增加。如果脚本执行结果非 0,并且 weight配置的值小于 0,则优先级相应的减少。其他情况,维持原本配置的优先级,即配置文件中 priority 对应的值。
vrrp_script chk_apiserver {
script “/etc/keepalived/check_apiserver.sh”
# 每2秒检查一次
interval 2
# 一旦脚本执行成功,权重减少5
weight -5
fall 3
rise 2
}
## 定义虚拟路由,VI_1 为虚拟路由的标示符,自己定义名称
vrrp_instance VI_1 {
## 主节点为 MASTER,对应的备份节点为 BACKUP
state MASTER
## 绑定虚拟 IP 的网络接口,与本机 IP 地址所在的网络接口相同
interface ens33
# 主机的IP地址
mcast_src_ip 192.168.18.100
# 虚拟路由id
virtual_router_id 100
## 节点优先级,值范围 0-254,MASTER 要比 BACKUP 高
priority 100
## 优先级高的设置 nopreempt 解决异常恢复后再次抢占的问题
nopreempt
## 组播信息发送间隔,所有节点设置必须一样,默认 1s
advert_int 2
## 设置验证信息,所有节点必须一致
authentication {
auth_type PASS
auth_pass K8SHA_KA_AUTH
}
## 虚拟 IP 池, 所有节点设置必须一样
virtual_ipaddress {
## 虚拟 ip,可以定义多个
192.168.18.110
}
track_script {
chk_apiserver
}
}

  • k8s-master02配置Keepalived:

vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
router_id k8s-master02
script_user root
enable_script_security
}
vrrp_script chk_apiserver {
script “/etc/keepalived/check_apiserver.sh”
interval 2
weight -5
fall 3
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface ens33
mcast_src_ip 192.168.18.101
virtual_router_id 101
priority 99
advert_int 2
authentication {
auth_type PASS
auth_pass K8SHA_KA_AUTH
}
virtual_ipaddress {
192.168.18.110
}
track_script {
chk_apiserver
}
}

  • k8s-master02配置Keepalived:

vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
router_id k8s-master03
script_user root
enable_script_security
}

vrrp_script chk_apiserver {
script “/etc/keepalived/check_apiserver.sh”
interval 2
weight -5
fall 3
rise 2
}
vrrp_instance VI_1 {
state BACKUP
interface ens33
mcast_src_ip 192.168.18.102
virtual_router_id 102
priority 98
advert_int 2
authentication {
auth_type PASS
auth_pass K8SHA_KA_AUTH
}
virtual_ipaddress {
192.168.18.110
}
track_script {
chk_apiserver
}
}

  • 在k8s-master01、k8s-master02、k8s-master03上新建监控脚本,并设置权限:

vim /etc/keepalived/check_apiserver.sh
#!/bin/bash

err=0
for k in $(seq 1 5)
do
check_code=$(pgrep kube-apiserver)
if [[ $check_code == “” ]]; then
err=$(expr $err + 1)
sleep 5
continue
else
err=0
break
fi
done

if [[ $err != “0” ]]; then
echo “systemctl stop keepalived”
/usr/bin/systemctl stop keepalived
exit 1
else
exit 0
fi
chmod +x /etc/keepalived/check_apiserver.sh

  • 在k8s-master01、k8s-master02、k8s-master03上启动haproxy和keepalived:

systemctl daemon-reload
systemctl enable —now haproxy
systemctl enable —now keepalived

  • 测试VIP(虚拟IP):

ping 192.168.18.110 -c 4

3.6 部署k8s的Master节点

3.6.1 yaml配置文件的方式部署k8s的Master节点

  • 在k8s-master01创建kubeadm-config.yaml,内容如下:

apiVersion: kubeadm.k8s.io/v1beta2
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.18.100 # 本机IP
bindPort: 6443
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: k8s-master01 # 本主机名
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
—-
apiServer:
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta2
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: “192.168.18.110:16443” # 虚拟IP和haproxy端口
controllerManager: {}
dns:
type: CoreDNS
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.aliyuncs.com/google_containers # 镜像仓库源
kind: ClusterConfiguration
kubernetesVersion: v1.20.2 # k8s版本
networking:
dnsDomain: cluster.local
podSubnet: “10.244.0.0/16”
serviceSubnet: “10.96.0.0/12”
scheduler: {}

—-
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
featureGates:
SupportIPVSProxyMode: true
mode: ipvs

  • 可以使用如下的命令更新kubeadm-config.yaml文件,需要将k8s设置到对应的版本:

kubeadm config migrate —old-config kubeadm-config.yaml —new-config “new.yaml”

  • 将new.yaml文件复制到所有的master节点

scp new.yaml k8s-master02:/root/new.yaml
scp new.yaml k8s-master03:/root/new.yaml

  • 所有的master节点提前下载镜像,可以节省初始化时间:

kubeadm config images pull —config /root/new.yaml

  • k8s-master01节点初始化后,会在/etc/kubernetes目录下生成对应的证书和配置文件,之后其他的Master节点加入到k8s-master01节点即可。

kubeadm init —config /root/new.yaml —upload-certs
云原生-第二部分 - 图241

  • 如果初始化失败,重置后再次初始化,命令如下:

kubeadm reset -f;ipvsadm —clear;rm -rf ~/.kube

  • 初始化成功后,会产生token值,用于其他节点加入时使用,

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run “kubectl apply -f [podnetwork].yaml” with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of the control-plane node running the following command on each as root:

kubeadm join 192.168.18.110:16443 —token abcdef.0123456789abcdef \
—discovery-token-ca-cert-hash sha256:505e373bae6123fc3e27e778c5fedbccbf0f91a51efdcc11b32c4573605b8e71 \
—control-plane —certificate-key 70aef5f76111a5824085c644b3f34cf830efad00c1b16b878701166bf069664e

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
“kubeadm init phase upload-certs —upload-certs” to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.18.110:16443 —token abcdef.0123456789abcdef \
—discovery-token-ca-cert-hash sha256:505e373bae6123fc3e27e778c5fedbccbf0f91a51efdcc11b32c4573605b8e71

  • k8s-master01节点配置环境变量,用于访问kubernetes集群:
    • 如果是root用户:

cat > /root/.bashrc <export KUBECONFIG=/etc/kubernetes/admin.conf
EOF
source ~/.bash_profile

  • 如果是普通用户:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

  • k8s-master01中查看节点的状态:

kubectl get nodes
云原生-第二部分 - 图242

  • 采用初始化安装方式,所有的系统组件均以容器的方式运行并且在kube-system命名空间内,此时可以在k8s-master01节点查看Pod的状态:

kubectl get pod -n kube-system -o wide
云原生-第二部分 - 图243

3.6.2 命令行的方式部署k8s的Master节点

  • 在k8s-master01、k8s-master02以及k8s-master03节点输入如下的命令:

kubeadm config images pull —kubernetes-version=v1.20.2 —image-repository=registry.aliyuncs.com/google_containers

  • 在k8s-master01节点输入如下的命令:

kubeadm init \
—apiserver-advertise-address=192.168.18.100 \
—image-repository registry.aliyuncs.com/google_containers \
—control-plane-endpoint=192.168.18.110:16443 \
—kubernetes-version v1.20.2 \
—service-cidr=10.96.0.0/12 \
—pod-network-cidr=10.244.0.0/16 \
—upload-certs

3.7 高可用Master

  • 将k8s-master02节点加入到集群中:

kubeadm join 192.168.18.110:16443 —token abcdef.0123456789abcdef \
—discovery-token-ca-cert-hash sha256:505e373bae6123fc3e27e778c5fedbccbf0f91a51efdcc11b32c4573605b8e71 \
—control-plane —certificate-key 70aef5f76111a5824085c644b3f34cf830efad00c1b16b878701166bf069664e
# 防止不能在此节点中不能使用kubectl命令
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

  • 将k8s-master03节点加入到集群中:

kubeadm join 192.168.18.110:16443 —token abcdef.0123456789abcdef \
—discovery-token-ca-cert-hash sha256:505e373bae6123fc3e27e778c5fedbccbf0f91a51efdcc11b32c4573605b8e71 \
—control-plane —certificate-key 70aef5f76111a5824085c644b3f34cf830efad00c1b16b878701166bf069664e
# 防止不能在此节点中不能使用kubectl命令
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

  • 如果token过期了,需要生成新的token(在k8s-master01节点):

kubeadm token create —print-join-command

  • Master节点如果要加入到集群中,需要生成—certificate-key(在k8s-master01节点):

kubeadm init phase upload-certs —upload-certs

  • 然后将其他Master节点加入到集群中:

需要做对应的修改
kubeadm join 192.168.18.110:16443 —token abcdef.0123456789abcdef \
—discovery-token-ca-cert-hash sha256:505e373bae6123fc3e27e778c5fedbccbf0f91a51efdcc11b32c4573605b8e71 \
—control-plane —certificate-key 70aef5f76111a5824085c644b3f34cf830efad00c1b16b878701166bf069664e

3.8 Node 节点的配置

  • 将k8s-node1加入到集群中:

kubeadm join 192.168.18.110:16443 —token abcdef.0123456789abcdef \
—discovery-token-ca-cert-hash sha256:505e373bae6123fc3e27e778c5fedbccbf0f91a51efdcc11b32c4573605b8e71

  • 将k8s-node2加入到集群中:

kubeadm join 192.168.18.110:16443 —token abcdef.0123456789abcdef \
—discovery-token-ca-cert-hash sha256:505e373bae6123fc3e27e778c5fedbccbf0f91a51efdcc11b32c4573605b8e71

3.9 部署CNI网络插件

  • 根据提示,在Master节点上使用kubectl工具查看节点状态:

kubectl get node
云原生-第二部分 - 图244

  • kubernetes支持多种网络插件,比如flannel、calico、canal等,任选一种即可,本次选择flannel。
  • 在所有Master节点上获取flannel配置文件(可能会失败,如果失败,请下载到本地,然后安装,如果网速不行,请点这里📎kube-flannel.yml,当然,你也可以安装calico,请点这里📎calico.yaml,推荐安装calico):

wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml

  • 在所有Master节点使用配置文件启动flannel:

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

  • 在所有Master节点查看部署CNI网络插件进度:

kubectl get pods -n kube-system
云原生-第二部分 - 图245

  • 在所有Master节点再次使用kubectl工具查看节点状态:

kubectl get nodes
云原生-第二部分 - 图246

  • 在所有Master节点查看集群健康状况:

kubectl get cs
云原生-第二部分 - 图247

  • 发现集群不健康,那么需要注释掉etc/kubernetes/manifests下的kube-controller-manager.yaml和kube-scheduler.yaml的—port=0:

vim /etc/kubernetes/manifests/kube-controller-manager.yaml
spec:
containers:
- command:
- kube-controller-manager
- —allocate-node-cidrs=true
- —authentication-kubeconfig=/etc/kubernetes/controller-manager.conf
- —authorization-kubeconfig=/etc/kubernetes/controller-manager.conf
- —bind-address=127.0.0.1
- —client-ca-file=/etc/kubernetes/pki/ca.crt
- —cluster-cidr=10.244.0.0/16
- —cluster-name=kubernetes
- —cluster-signing-cert-file=/etc/kubernetes/pki/ca.crt
- —cluster-signing-key-file=/etc/kubernetes/pki/ca.key
- —controllers=*,bootstrapsigner,tokencleaner
- —kubeconfig=/etc/kubernetes/controller-manager.conf
- —leader-elect=true
# 修改部分
# - —port=0
- —requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
- —root-ca-file=/etc/kubernetes/pki/ca.crt
- —service-account-private-key-file=/etc/kubernetes/pki/sa.key
- —service-cluster-ip-range=10.96.0.0/12
- —use-service-account-credentials=true
vim /etc/kubernetes/manifests/kube-scheduler.yaml
spec:
containers:
- command:
- kube-scheduler
- —authentication-kubeconfig=/etc/kubernetes/scheduler.conf
- —authorization-kubeconfig=/etc/kubernetes/scheduler.conf
- —bind-address=127.0.0.1
- —kubeconfig=/etc/kubernetes/scheduler.conf
- —leader-elect=true
# 修改部分
# - —port=0

  • 在所有Master节点再次查看集群健康状况:

kubectl get cs
云原生-第二部分 - 图248

k8s的项目部署

1 容器交付流程

1.1 开发阶段

  • 编写代码。
  • 测试。
  • 编写Dockerfile。

    1.2 持续集成

  • 代码编译、打包。

  • 制作镜像。
  • 将镜像上传到镜像仓库。

    1.3 应用部署

  • 环境准备。

  • 创建Pod、Service、Ingress。

    1.4 运维

  • 监控。

  • 故障排查。
  • 应用升级及优化。
  • ……

    2 k8s中部署Java项目的流程

  • ① 通过Dockerfile制作镜像。

  • ② 将镜像推送到镜像仓库,比如阿里云镜像仓库等。
  • ③ Pod控制器部署镜像。
  • ④ 创建Service或Ingress对外暴露应用。
  • ⑤ 对集群进行监控、升级等。

    3 k8s中部署Java项目

    3.1 前提说明

  • 本人是在Windows进行开发的,部署在Linux(CentOS7)中的k8集群。

    3.2 准备Java项目,并将项目进行打包

    3.2.1 概述

  • 准备一个Java项目,将Java项目进行打包,本次使用SpringBoot项目为例,使用的JDK的版本是11。

    3.2.2 准备工作

  • JDK 11 。

  • Maven 3.6x。

    3.2.3 演示的SpringBoot项目

  • pom.xml

<?xml version=”1.0” encoding=”UTF-8”?>
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0

org.springframework.boot
spring-boot-starter-parent
2.3.7.RELEASE


com.example
springboot2
1.0
springboot2
Demo project for Spring Boot

<properties><br />        <java.version>11</java.version><br />    </properties>

<dependencies><br />        <dependency><br />            <groupId>org.springframework.boot</groupId><br />            <artifactId>spring-boot-starter-web</artifactId><br />        </dependency>

    <dependency><br />            <groupId>org.springframework.boot</groupId><br />            <artifactId>spring-boot-starter-test</artifactId><br />            <scope>test</scope><br />            <exclusions><br />                <exclusion><br />                    <groupId>org.junit.vintage</groupId><br />                    <artifactId>junit-vintage-engine</artifactId><br />                </exclusion><br />            </exclusions><br />        </dependency><br />    </dependencies>

<build><br />        <plugins><br />            <plugin><br />                <groupId>org.springframework.boot</groupId><br />                <artifactId>spring-boot-maven-plugin</artifactId><br />            </plugin><br />        </plugins><br />    </build>

  • HelloController.java

package com.example.springboot2.web;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/*
@author 许大仙
@version 1.0
@since 2021-01-12 09:18
*/
@RestController
public class HelloController {

@RequestMapping(value = "/hello")<br />    public String hello() {<br />        return "hello";<br />    }

}

  • 启动类:

package com.example.springboot2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Springboot2Application {

public static void main(String[] args) {<br />        SpringApplication.run(Springboot2Application.class, args);<br />    }<br />}

3.2.4 使用Maven进行打包

  • 打包命令:

mvn clean install
云原生-第二部分 - 图249

3.2.5 在项目的根目录下新建Dockerfile文件

  • Dockerfile:

FROM openjdk
VOLUME /tmp
COPY ./target/springboot2-1.0.jar springboot2-1.0.jar
RUN bash -c “touch /springboot2-1.0.jar”
# 声明时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo ‘Asia/Shanghai’ >/etc/timezone
#声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务
EXPOSE 8080
ENTRYPOINT [“java”,”-jar”,”/springboot2-1.0.jar”]
云原生-第二部分 - 图250

3.3 制作镜像

  • 将整个项目通过ftp上传到k8s集群所在的服务器中(其实完全可以只上传jar包和Dockerfile文件)。

云原生-第二部分 - 图251

  • 进入springboot2目录:

cd springboot2
云原生-第二部分 - 图252

  • 使用docker build构建镜像:

springboot是镜像的名称
docker build -t springboot2 .
云原生-第二部分 - 图253

3.4 推送镜像

  • 阿里云创建命名空间:

云原生-第二部分 - 图254
云原生-第二部分 - 图255

  • 阿里云创建镜像仓库:

云原生-第二部分 - 图256
云原生-第二部分 - 图257
云原生-第二部分 - 图258

  • 登录阿里云Docker Registry:

sudo docker login —username=阿里云账号 registry.cn-hangzhou.aliyuncs.com
云原生-第二部分 - 图259

  • 查看上传的Docker镜像的id:

docker images
云原生-第二部分 - 图260

  • 给镜像打tag:

sudo docker tag bc56e4a83ff7 registry.cn-hangzhou.aliyuncs.com/k8s-test-123/springboot2:latest
云原生-第二部分 - 图261

  • 推送镜像:

sudo docker push registry.cn-hangzhou.aliyuncs.com/k8s-test-123/springboot2:latest
云原生-第二部分 - 图262

3.5 部署镜像暴露应用

  • 创建deployment.yaml文件,内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: springboot2
name: springboot2
spec:
replicas: 3
selector:
matchLabels:
app: springboot2
template:
metadata:
labels:
app: springboot2
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/k8s-test-123/springboot2:latest
name: springboot2

  • 创建Deployment:

kubectl create -f deployment.yaml
云原生-第二部分 - 图263

  • 查看Deployment和Pod:

kubectl get deploy,pod
云原生-第二部分 - 图264

  • 创建service.yaml文件,内容如下:

apiVersion: v1
kind: Service
metadata:
labels:
app: springboot2
name: svc
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
nodePort: 30091
selector:
app: springboot2
type: NodePort

  • 创建Service:

kubectl create -f service.yaml
云原生-第二部分 - 图265

  • 查看Service:

kubectl get service
云原生-第二部分 - 图266