八、Kubernetes 基础资源类型

1、Kubernetes 命名空间

  • 命名空间 Namespace 将对象逻辑上分配到不同 Namespace,可以是不同的项目、用户等区分管理,并设定控制策略,从而实现多租户。
  • 命名空间也称为虚拟集群。通过将 Kubernetes 集群内部的资源对象“分配”到不同的 Namespace 中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。
  • Kubernetes 集群在启动后,会创建一个名为 “default” 的 Namespace,通过 kubectl 可以查看到:
[root@k8s-master ~]# kubectl get namespaces
  • 如果不特别指明 Namespace,则用户创建的 Pod、RC、RS、Service 都奖被系统创建到这个默认的名为 default 的 Namespace 中。
  • Namespace 的定义示例:
[root@k8s-master ~]# vim pod-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: pod-test
  • 定义一个 Pod,并指定它属于哪个 Namespace
[root@k8s-master ~]# vim pod-test.yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: pod-test
spec:
  containers:
  - image: busybox
    command:
      - sleep
      - "3600"
    name: busybox
  • 使用 kubectl get 命令查看 Pod 状态信息时,需要加上 --namespace 参数,指定查看哪个 namespace 下的资源对象,不加这个参数则默认查看 default 下的资源对象。
[root@k8s-master ~]# kubectl get pods --namespace pod-test
  • 给每个租户创建一个 Namespace 来实现多租户的资源隔离时,还能结合 Kubernetes 的资源配额管理,限定不同租户能占用的资源,例如CPU使用量、内存使用量等。
# 进入指定名称空间中的 pod
[root@k8s-master ~]# kubectl exec -it -n pod-test busybox -- /bin/sh

2、Kubernetes 注解

  • Annotation 与 Label 类似,也使用 key/value 键值对的形式进行定义。不同的是 Label 具有严格的命名规则,它定义的是 Kubernetes 对象的元数据(Metadata),并且用于 Label Selector。而 Annotation 则是用户任意定义的“附加”信息,以便于外部工具进行查找。通常 Kubernetes 的模块会通过 Annotation 的方式标记资源对象的一些特殊信息。
  • 使用 Annotation 来记录的信息如下:
  • build 信息、release 信息、Docker 镜像信息等,例如时间戳、release id 号、PR 号、镜像 bash 值、docker registry 地址等
  • 日志库、监控库、分析库等资源库的地址信息。
  • 程序调试工具信息,例如工具名称、版本号等。
  • 团队的联系信息,例如电话号码、负责人名称、网址等。

3、Kubernetes 标签

  • Label 相当于我们熟悉的“标签”,给某个资源对象定义一个Label,就相当于给它打了一个标签,随后可以通过 Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,Kubernetes 通过这种方式实现了类似 SQL 的简单又通用的对象查询机制。
  • Label Selector 相当于SQL语句中的 where 查询条件,例如,name=redis-slave 这个 Label Selector 作用于Pod时,相当于 select * from pod where pod’s name = ‘redis-slave’ 这样的语句。
  • 标签用于区分对象(比如 Pod、Service),键/值对存在;每个对象可以有多个标签,通过标签关联对象。

一个 pod 打多个标签

一个标签可以打给多 pod

  • Label Selector的表达式有两种:基于等式的(Equality-based)和基于集合的(Set-based)。下面是基于等式的匹配例子。
    name=redis-slave:匹配所有标签为 name=redis-slave 的资源对象。
    env != production:匹配所有标签env不等于 production 的资源对象。
    下面是基于集合的匹配例子
  • name in (redis-master, redis-slave):匹配所有标签为 name=redis-master 或者 name=redis-slave 的资源对象。
  • name not in (php-frontend):匹配所有标签 name 不等于php-frontend 的资源对象。
  • 还可以通过多个 Label Selector 表达式的组合实现复杂的条件选择,多个表达式之间用“,”进行分隔即可,几个条件之间是 “AND” 的关系,即同时满足多个条件,例如:
name=redis-slave, env!=production
name not in (php-frontend), env!=production
  • 以 Pod 为例,Label 定义在 metadata 中:
apiVersion: v1
kind: Pod
metadata:
  name: myweb
  labels:
    app: myweb
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
  • RC 和 Service 在 spec 中定义 Selector 与 Pod 进行关联:
apiVersion: v1
kind: ReplicationController
metadata:
  name: myweb
spec:
  replicas: 3
  selector:
    app: myweb
  template:
    metadata:
      labels:
        app: myweb
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
      - name: nginx1
        image: nginx
        ports:
        - containerPort: 80
  • Deployment、ReplicaSet、DaemonSet 和 Job 则可以在 Selector 中使用基于集合的筛选条件:
selector:
  matchLabels:
    app: myweb
    tier: frontend
  matchExpressions:
    - {key: tier, operator: In, values: [frontend]}  
    - {key: environment, operator: NotIn, values: [dev]}   
# 所有标签之间都是and关系
# 写几个匹配条件就需要满足几个
 
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: label-kube
  labels:
    dep: web
spec:  
  replicas: 2
  selector:
    matchLabels:
      app: myweb
      aaa: bbb
    matchExpressions:
      - {key: tier, operator: In, values: [frontend]}  
      - {key: environment, operator: NotIn, values: [dev]} 
  template:
    metadata:
      labels:
        app: myweb
    spec: 
      containers:
        - name: front-end
          image: nginx
          ports:
            - containerPort: 80
  • matchLabels 用于定义一组 Label,与直接写在 Selector 中作用相同;
  • matchExpressions 用于定义一组基于集合的筛选条件,可用的条件运算符包括:In、NotIn、Exists 和 NotExist。
  • 如果同时设置了 matchLabels 和 matchExpressions,则两组条件为“AND”关系,即所有条件需要同时满足才能完成 Selector 的筛选。
  • Kube-controller 进程通过资源对象 RC 上定义的 Label Selector 来筛选要监控的 Pod 副本的数量,从而实现 Pod 副本的数量始终符合预期设定的全自动控制流程。
  • Kube-proxy 进程通过 Service 的 Label Selector 来选择对应的 Pod,自动建立起每个 Service 到对应 Pod 的请求转发路由表,从而实现 Service 的智能负载均衡机制。
  • 通过对某些 Node 定义特定的 Label,并且在 Pod 定义文件中使用 NodeSelector 这种标签调度策略,kube-scheduler 进程可以实现Pod“定向调度”的特性。
  • 下面举个复杂点的例子,假设我们为 Pod 定义了3个Label:release、env和role,不同的Pod定义了不同的Label。如下图所示,如果我们设置了“role=frontend”的Label Selector,则会选取到Node 1和Node 2上的Pod。
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jkg8mhUN-1610418329789)(assets/7288a5dd3d0683d40fa07959b40e8be5.png)]
  • 如果我们设置 “release=beta” 的 Label Selector,则会选取到 Node 2 和 Node 3 上的 Pod,
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0GlXQaF-1610418329793)(assets/4c2f9ad2c06d184fac94eacebeab1d3a.png)]
  • 使用 Label 可以给对象创建多组标签,Label 和 Label Selector 共同构成了 Kubernetes 系统中最核心的应用模型,使得被管理对象能够被精细地分组管理,同时实现了整个集群的高可用性。

4、Kubernetes 复制控制器 RC

1、RC 介绍
  • RC 是Replication Controller (复制控制器), 的作用是声明 Pod 的副本数量在任意时刻都符合某个预期值,所以 RC 的定义包括如下几个部分。
  • Pod 期待的副本数量(replicas)。
  • 用于筛选目标 Pod 的 Label Selector。
  • 当 Pod 的副本数量小于预期数量时,用于创建新 Pod 的 Pod 模板(template)。
  • RC 特性机制
  • 通过定义 RC 实现 Pod 的创建过程及副本数量的自动控制。
  • RC 里包括完整的 Pod 定义模板。
  • RC 通过 Label Selector 机制实现对Pod副本的自动控制。
  • 通过改变 RC 里的 Pod 副本数量,可以实现 Pod 的扩容或缩容功能。
  • 通过改变 RC 里 Pod 模板中的镜像版本,可以实现 Pod 的滚动升级功能。
  • 下面是一个完整的 RC 定义的例子,即确保拥有tier=frontend标签的这个Pod(运行Tomcat容器)在整个 Kubernetes 集群中始终有三个副本:
apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    tier: frontend
  template:
    metadata:
      labels:
        app: app-demo
        tier: frontend
    spec:
      containers:
      - name: app-demo
        image: ikubernetes/myapp:v1
        imagePullPolicy: IfNotPresent
  • 当定义的RC 并提交到 Kubernetes 集群中后,Master 节点上的 Controller Manager 组件就得到通知,定期巡检系统中当前存活的目标 Pod,并确保目标Pod实例的数量刚好等于此 RC 的期望值。
  • 如果有过多的 Pod 副本在运行,系统就会停掉多余的 Pod;如果运行的 Pod 副本少于期望值,即如果某个 Pod 挂掉,系统就会自动创建新的 Pod 以保证数量等于期望值。
  • 通过 RC,Kubernetes 实现了用户应用集群的高可用性,并且大大减少了运维人员在传统 IT 环境中需要完成的许多手工运维工作(如主机监控脚本、应用监控脚本、故障恢复脚本等)。
  • Kubernetes 如何通过 RC 来实现 Pod 副本数量自动控制的机制,假如我们有 3个 Node节点,在 RC 里定义了 redis-slave 这个 Pod 需要保持两个副本,系统将会在其中的两个 Node上创建副本,如下图所示。
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-heYCU785-1610418329796)(assets/bfe96e26db5bf475b47c44b33a526289.png)]
  • 假如 k8s-node02 上的 Pod2 意外终止,根据 RC 定义的 replicas 数量 2,Kubernetes 将会自动创建并启动一个新的 Pod,以保证整个集群中始终有两个 redis-slave Pod 在运行。
  • 系统可能选择 k8s-node01 或者 Node3 来创建一个新的Pod,如下图。
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QT98H9kN-1610418329798)(assets/012a9d02fa7e46ff51f182bc742cd522.png)]
  • 通过修改 RC 的副本数量,可以实现 Pod 的动态缩放(Scaling)功能。
[root@k8s-master ~]# kubectl scale rc redis-slave --replicas=3
  • 此时 Kubernetes 会在 3 个 Node 中选取一个 Node 创建并运行一个新的 Pod3,使 redis-slave Pod 副本数量始终保持3个。
2、RC 滚动升级示例
  • 假设现在运行的redis-master的pod是1.0版本,现在需要升级到2.0版本。
[root@k8s-master ~]# vim redis-master-controller-v1.yaml
apiVersion: v1
kind: ReplicationController
metadata:
 name: nginx
 labels:
  name: web
  version: v1
spec:
  replicas: 3
  selector:
    name: nginx-web
    version: v1
  template:
    metadata:
      labels:
        name: nginx-web
        version: v1
    spec:
      containers:
      - name: nginx
        image: nginx:1.14
        ports:
        - containerPort: 80
  • 升级:
[root@k8s-master ~]# kubectl set image rc nginx nginx=nginx:1.14 --record  # 只能更新rc缓存信息

5、Kubernetes 复制集控制器 RS

  • RS 是新一代 ReplicationController。确保任何给定时间指定的 Pod 副本数量,并提供声明式更新等功能。
  • RS 不参与人为管理,由于Replication Controller 与 Kubernetes 代码中的模块 Replication Controller 同名,同时这个词也无法准确表达它的意思,所以从 Kubernetes v1.2 开始,RC升级成了另外一个新的对象—— Replica Set,官方解释为“下一代的 RC”。
  • RS 与 RC当前存在的唯一区别是:Replica Set 支持基于集合的 Label selector(Set-based selector),而 RC 只支持基于等式的 Label selector(equality-based selector),所以 Replica Set 的功能更强大。
  • Replica Set 很少单独使用,它主要被 Deployment 这个更高层的资源对象所使用,从而形成一整套 Pod 创建、删除、更新的编排机制。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
    matchExpressions:
      - {key: tier, operator: In, values: [frontend]}
  template:
    metadata:
      labels:
        app: guestbook
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        env:
        - name: GET_HOSTS_FROM
          value: dns
        ports:
        - containerPort: 80
  • RS描述文件中,selector除了可以使用matchLabels,还支持集合式的操作:
matchExpressions:
      - {key: tier, operator: In, values: [frontend]}
1、ReplicaSet 的删除
  • 使用kubectl delete命令会删除此RS以及它管理的Pod。在Kubernetes删除RS前,会将RS的replica调整为0,等待所有的Pod被删除后,在执行RS对象的删除。
  • 如果希望仅仅删除RS对象(保留Pod),请使用kubectl delete命令时添加 --cascade=false选项。
2、ReplicaSet的伸缩
  • 通过修改.spec.replicas的值可以实时修改RS运行的Pod数量。

6、Kubernetes 部署控制器

1、Deployment 介绍
  • Deployment 是一个更高层次的 API 对象,它管理 ReplicaSets 和 Pod,并提供声明式更新等功能。
  • 官方建议使用 Deployment 管理 ReplicaSets,而不是直接使用 ReplicaSets,这就意味着可能永远不需要直接操作 ReplicaSet 对象。
  • Deployment 相对于 RC 的最大区别是可以随时知道当前 Pod “部署”的进度。一个 Pod 的创建、调度、绑定节点及在目标 Node 上启动对应的容器这一完整过程需要一定的时间,所以我们期待系统启动 N个 Pod 副本的目标状态,实际上是一个连续变化的“部署过程”导致的最终状态。
  • Deployment 的典型使用场景有以下几个:
  • 创建 Deployment 对象来生成对应的 Replica Set 并完成 Pod 副本的创建过程。
  • 检查 Deployment 的状态来看部署动作是否完成( Pod 副本的数量是否达到预期的值)。
  • 更新 Deployment 以创建新的 Pod(比如镜像升级)。
  • 如果当前 Deployment 不稳定,则回滚到一个早先的 Deployment 版本。
  • 暂停 Deployment 以便于一次性修改多个 Pod Template Spe c的配置项,之后再恢复Deployment,进行新的发布。
  • 扩展 Deployment 以应对高负载。
  • 查看 Deployment 的状态,以此作为发布是否成功的指标。
  • 清理不再需要的旧版本 ReplicaSet。
  • 只需要在 Deployment 中描述想要的目标状态是什么,Deployment controller 就会帮您将 Pod 和ReplicaSet 的实际状态改变到您的目标状态。也可以定义一个全新的 Deployment 来创建 ReplicaSet 或者删除已有的 Deployment 并创建一个新的来替换。
2、Deployment 配置说明
  • Deployment 的定义与 Replica Set 的定义类似,只是 API 声明与 Kind 类型不同。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  
apiVersion: v1
kind: ReplicaSet
metadata:
  name: nginx-repset
  • 下面是 Deployment 定义的一个完整例子:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
  namespace: scm
  labels:
    app: nginx-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx-demo
  minReadySeconds: 60     	# 这里需要估一个比较合理的值,从容器启动到应用正常提供服务
  strategy:
    rollingUpdate:      	# 由于replicas为3,则整个升级,pod个数在2-4个之间
      maxSurge: 1       	# 更新时允许最大激增的容器数,默认 replicas 的 1/4 向上取整
      maxUnavailable: 1 	# 更新时允许最大 unavailable(失效) 容器数,默认 replicas 的 1/4 向下取整
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      terminationGracePeriodSeconds: 60  # k8s 将会给应用发送 SIGTERM(终止进程 软件终止信号)信号,可以用来正确、优雅地关闭应用,默认为30秒
      containers:
      - name: nginx-demo
        image: nginx
        imagePullPolicy: IfNotPresent
        livenessProbe: 				# kubernetes认为该pod是存活的,不存活则需要重启
          httpGet:
            path: /
            port: 80
            scheme: HTTP
          initialDelaySeconds: 60   # 容器启动60秒后开始第一次检测
          timeoutSeconds: 5    		# http检测请求的超时时间
          successThreshold: 1  		# 检测到有1次成功则认为服务是`就绪`
          failureThreshold: 5  		# 检测到有5次失败则认为服务是`未就绪`
        readinessProbe:        		# kubernetes认为该pod是启动成功的
          httpGet:
            path: /
            port: 80
            scheme: HTTP       		# 链接格式样式
          initialDelaySeconds: 30   # 等于应用程序的最小启动时间
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        resources:
          requests:
            cpu: 50m
            memory: 200Mi
          limits:
            cpu: 500m
            memory: 500Mi
        env:
          - name: PROFILE
            value: "test"
        ports:
          - name: http
            containerPort: 80
1、maxSurge 与 maxUnavailable
  • maxSurge: 1 表示滚动升级时会先启动1个pod
  • maxUnavailable: 1 表示滚动升级时允许的最大不可用的pod个数
  • 由于replicas为3,则整个升级,pod个数在2-4个之间
2、termination Grace Period Seconds
  • k8s将会给应用发送 SIGTERM 信号,可以用来正确、优雅地关闭应用,默认为30秒。
  • 如果需要更优雅地关闭,则可以使用k8s提供的pre-stop lifecycle hook 的配置声明,将会在发送SIGTERM之前执行。
3、livenessProbe 与 readinessProbe
  • livenessProbe是kubernetes认为该pod是存活的,不存在则需要kill掉,然后再新启动一个,以达到replicas指定的个数。
  • readinessProbe是kubernetes认为该pod是启动成功的,这里根据每个应用的特性,自己去判断,可以执行command,也可以进行httpGet。比如对于使用java web服务的应用来说,并不是简单地说tomcat启动成功就可以对外提供服务的,还需要等待spring容器初始化,数据库连接连接上等等。对于spring boot应用,默认的actuator带有/health接口,可以用来进行启动成功的判断。
  • 其中readinessProbe.initialDelaySeconds可以设置为系统完全启动起来所需的最少时间,livenessProbe.initialDelaySeconds可以设置为系统完全启动起来所需的最大时间+若干秒。
  • 这几个参数配置好了之后,基本就可以实现近乎无缝地平滑升级了。对于使用服务发现的应用来说,readinessProbe可以去执行命令,去查看是否在服务发现里头应该注册成功了,才算成功。
4、deployment 实例
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      tier: frontend
    matchExpressions:
      - {key: tier, operator: In, values: [frontend,dev,test]}
  template:
    metadata:
      labels:
        app: app-demo
        tier: frontend
    spec:
      containers:
      - name: tomcat-demo
        image: tomcat
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        resources:            
          requests:              
            cpu: 0.1           
            memory: 32Mi         
          limits:               
            cpu: 0.5  
            memory: 32Mi
  • 可以通过命令 kubectl get deployment 来查看 Deployment 的信息,其中的几个参数解释如下:
  • DESIRED::Pod副本数量的期望值,即 Deployment 里定义的 Replica。
  • CURRENT:当前 Replica 的值,如果小于 DESIRED 的期望值,会创建新的 Pod,直到达成 DESIRED 为止。
  • UP-TO-DATE:最新版本的 Pod 的副本数量,用于指示在滚动升级的过程中,有多少个 Pod 副本已经成功升级。
  • AVAILABLE:当前集群中可用的Pod副本数量,即集群中当前存活的Pod数量c。
  • Pod 的管理对象,除了 RC、ReplicaSet、Deployment,还有 DaemonSet、StatefulSet、Job 等,分别用于不同的应用场景。
3、RC 与 deployment 的区别
1、replication controller
  • Replication Controller为Kubernetes的一个核心内容,应用托管到Kubernetes之后,需要保证应用能够持续的运行,Replication Controller就是这个保证的key,主要的功能如下:
  • 确保pod数量:它会确保Kubernetes中有指定数量的Pod在运行。如果少于指定数量的pod,Replication Controller会创建新的,反之则会删除掉多余的以保证Pod数量不变。
  • 确保pod健康:当pod不健康,运行出错或者无法提供服务时,Replication Controller也会杀死不健康的pod,重新创建新的。
  • 弹性伸缩 :在业务高峰或者低峰期的时候,可以通过Replication Controller动态的调整pod的数量来提高资源的利用率。同时,配置相应的监控功能(Hroizontal Pod Autoscaler),会定时自动从监控平台获取Replication Controller关联pod的整体资源使用情况,做到自动伸缩。
  • 滚动升级:滚动升级为一种平滑的升级方式,通过逐步替换的策略,保证整体系统的稳定,在初始化升级的时候就可以及时发现和解决问题,避免问题不断扩大。
2、Deployment
  • Deployment同样为Kubernetes的一个核心内容,主要职责同样是为了保证pod的数量和健康,90%的功能与Replication Controller完全一样,可以看做新一代的Replication Controller。但是,它又具备了Replication Controller之外的新特性:
  • Replication Controller全部功能:Deployment继承了上面描述的Replication Controller全部功能。
  • 事件和状态查看:可以查看Deployment的升级详细进度和状态。
  • 回滚:当升级pod镜像或者相关参数的时候发现问题,可以使用回滚操作回滚到上一个稳定的版本或者指定的版本。
  • 版本记录: 每一次对Deployment的操作,都能保存下来,给予后续可能的回滚使用。
  • 暂停和启动:对于每一次升级,都能够随时暂停和启动。
  • 多种升级方案:
  • Recreate:删除所有已存在的pod,重新创建新的;
  • RollingUpdate:滚动升级,逐步替换的策略,同时滚动升级时,支持更多的附加参数,例如设置最大不可用pod数量,最小升级间隔时间等等。
4、Deployment 的常用命令
1、查看 Deployment
[root@k8s-master ~]# kubectl get deployment --namespace=scm 
[root@k8s-master ~]# kubectl get deployment --namespace=scm -o wide
[root@k8s-master ~]# kubectl get deployment --namespace=scm -o yaml
2、Deployment 升级
  • 创建deployment
[root@k8s-master ~]# kubectl create deployment  nginx-deployment --image=nginx:1.14
  • 修改镜像版本是新滚动升级
[root@k8s-master ~]# kubectl set image deployment nginx-deployment nginx=nginx:1.13 --record
  • 或者
[root@k8s-master ~]# kubectl edit deployment nginx-deployment --record
  • 编辑.spec.template.spec.containers[0].image的值
3、Deployment 暂停升级
[root@k8s-master ~]# kubectl rollout pause deployment nginx-deployment
4、Deployment 恢复升级
[root@k8s-master ~]# kubectl rollout resume deployment nginx-deployment
5、Deployment 回滚
[root@k8s-master ~]# kubectl rollout undo deployment nginx-deployment
6、查看 Deployment 升级历史版本
[root@k8s-master ~]# kubectl rollout history deployments --namespace=scm
7、回滚到 Deployment 指定历史版本
[root@k8s-master ~]# kubectl rollout undo deployment nginx-deployment --to-revision=2
8、查看 Deployment 升级过程
[root@k8s-master ~]# kubectl describe deployment nginx-deployment  --namespace=scm
9、查看Deployment 部署状态
[root@k8s-master ~]# kubectl rollout status deployment nginx-deployment  --namespace=scm
5、Deployment 滚动升级
1、创建 Deployment
[root@k8s-master ~]# vim deployment_nginx.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.12.2
        ports:
        - containerPort: 80
2、创建 deployment 资源
[root@k8s-master ~]# kubectl create -f deployment_nginx.yml
[root@k8s-master ~]# kubectl get deployment
[root@k8s-master ~]# kubectl get rs
[root@k8s-master ~]# kubectl get pods
3、查看 deployment 信息
  • 可以看到这个deloyment下的详情,nginx是1.12.2
[root@k8s-master ~]# kubectl get deployment -o wide
4、deployment 的升级
  • 针对目前的nginx1.12升级成1.13的命令,老的下面自动移除了,全部都在新的下面。
[root@k8s-master ~]# kubectl set image deployment nginx-deployment nginx=nginx:1.13 --record
[root@k8s-master ~]# kubectl get deployment
[root@k8s-master ~]# kubectl get deployment -o wide
[root@k8s-master ~]# kubectl get pods
5、deployment 查看历史版本
[root@k8s-master ~]# kubectl rollout history deployment nginx-deployment
6、deployment 回滚到之前的版本
  • 查看详情又变成了nginx 1.12.2
[root@k8s-master ~]# kubectl rollout undo deployment nginx-deployment
7、deployment 端口暴露
[root@k8s-master ~]# kubectl get node
[root@k8s-master ~]# kubectl get node -o wide
[root@k8s-master ~]# kubectl expose deployment nginx-deployment --port=80  #指定要暴露的端口
#查看node节点暴露的端口
[root@k8s-master ~]# kubectl get svc
  • rolling update,可以使得服务近乎无缝地平滑升级,即在不停止对外服务的前提下完成应用的更新。
8、Deployment 滚动升级案例
  • Httpd 服务滚动升级,第一次部署时使用 httpd:2.2.31,然后更新到 httpd:2.2.32。
1、创建 Deployment 配置
[root@k8s-master ~]# vim httpd.yml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: httpd
spec:
  replicas: 3
  selector:
    matchLabels: 
      run: httpd
  template:
    metadata:
      labels:
        run: httpd
    spec:
      containers:
        - name: httpd
          image: httpd:2.2.31
          ports:
            - containerPort: 80
2、创建 Deployment 资源
[root@k8s-master ~]# kubectl apply -f httpd.yml
3、查看 Deployment
[root@k8s-master ~]# kubectl get deployments httpd -o wide

NAME    READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
httpd   3/3     1            3           24m   httpd        httpd:2.2.31   run=httpd
4、开始 Deployment 滚动升级
  • 把配置文件中的 httpd:2.2.31 改为 httpd:2.2.32,再次启动:ka
[root@k8s-master ~]# kubectl set image deployment httpd httpd=httpd:2.2.32 --record
[root@k8s-master ~]# kubectl apply -f httpd.yml
5、查看 Deployment 升级结果
[root@k8s-master ~]# kubectl get deployments httpd -o wide

NAME    READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
httpd   3/3     1            3           26m   httpd        httpd:2.2.32   run=httpd
7、查看 Deployment 滚动升级详细事件
[root@k8s-master ~]# kubectl describe deployments httpd
...
Events:
  Type    Reason             Age                From                   Message
  ----    ------             ----               ----                   -------
...
  Normal  ScalingReplicaSet  3m33s              deployment-controller  Scaled up replica set httpd-94c4dcb56 to 1
  Normal  ScalingReplicaSet  2m48s              deployment-controller  Scaled down replica set httpd-8c6c4bd9b to 2
  Normal  ScalingReplicaSet  2m48s              deployment-controller  Scaled up replica set httpd-94c4dcb56 to 2
  Normal  ScalingReplicaSet  2m43s              deployment-controller  Scaled down replica set httpd-8c6c4bd9b to 1
  Normal  ScalingReplicaSet  2m43s              deployment-controller  Scaled up replica set httpd-94c4dcb56 to 3
  Normal  ScalingReplicaSet  2m38s              deployment-controller  Scaled down replica set httpd-8c6c4bd9b to 0
  • 事件信息就描述了滚动升级的过程:
  • 启动一个新版 pod
  • 把旧版 pod 数量降为 2
  • 再启动一个新版,数量变为 2
  • 把旧版 pod 数量降为 1
  • 再启动一个新版,数量变为 3
  • 把旧版 pod 数量降为 0
  • 这就是滚动的意思,始终保持副本数量为3,控制新旧 pod 的交替,实现了无缝升级。
8、Deployment 回滚
  • kubectl apply 每次更新应用时,kubernetes 都会记录下当前的配置,保存为一个 revision,这样就可以回滚到某个特定的版本。
  • 创建3个配置文件,内容中唯一不同的就是镜像的版本号。
  • 创建 v1
[root@k8s-master ~]# vim httpd.v1.yml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: httpd
spec:
  revisionHistoryLimit: 10 # 指定保留最近的几个revision
  replicas: 3
  selector:
    matchLabels:
      run: httpd
  template:
    metadata:
      labels:
        run: httpd
    spec:
      containers:
        - name: httpd
          image: httpd:2.2.34
          ports:
            - containerPort: 80
  • 复制 v1-v2 修改 image 版本
[root@k8s-master ~]# vim  httpd.v2.yml
...
          image: httpd:2.4.10
...
  • 复制 v1-v3修改 image 版本
...
          image: httpd:2.4.11
...
  • 部署v1-v3
[root@k8s-master ~]# kubectl apply -f /vagrant/httpd.v1.yml --record
[root@k8s-master ~]# kubectl apply -f /vagrant/httpd.v2.yml --record
[root@k8s-master ~]# kubectl apply -f /vagrant/httpd.v3.yml --record
  • –record 的作用是将当前命令记录到 revision 中,可以知道每个 revision 对应的是哪个配置文件。
  • 查看 deployment
[root@k8s-master ~]# kubectl get deployments -o wide
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS            IMAGES       
httpd                 0/3     1            0           4m4s    httpd                 httpd:2.4.12
  • 查看 revision 历史记录
[root@k8s-master ~]# kubectl rollout history deployment httpd

deployment.extensions/httpd
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=httpd.v1.yml --record=true
2         kubectl apply --filename=httpd.v2.yml --record=true
3         kubectl apply --filename=httpd.v3.yml --record=true
  • CHANGE-CAUSE 就是 --record 的结果。
  • 回滚到 revision 1
[root@k8s-master ~]# kubectl rollout undo deployment httpd --to-revision=1
  • 查看 deployment 版本已经回退成功
[root@k8s-master ~]# kubectl get deployments -o wide
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS            IMAGES       
httpd                 0/3     1            0           4m4s    httpd                 httpd:2.2.34
  • 查看 revision 历史记录
[root@k8s-master ~]# kubectl rollout history deployment httpd

deployment.extensions/httpd
REVISION  CHANGE-CAUSE
2         kubectl apply --filename=httpd.v2.yml --record=true
3         kubectl apply --filename=httpd.v3.yml --record=true
4         kubectl apply --filename=httpd.v1.yml --record=true
  • revision 记录也发生了变化。

7、Kubernetes 横向自动扩展

  • Horizontal Pod Autoscaler(HPA)POD 横向自动扩展
  • HPA 与 RC、Deployment 一样,也属于 Kubernetes 资源对象。
  • 通过追踪分析 RC 或 RS 控制的所有目标 Pod 的负载变化情况,来确定是否需要针对性地调整目标 Pod 的副本数。
  • HPA 有以下两种方式作为 Pod 负载的度量指标:
  • CPU Utilization Percentage (CPU利用率百分比)
  • 应用程序自定义的度量指标,比如服务在每秒内的相应的请求数( TPS 或 QPS )。
  • CPU Utilization Percentage 是一个算术平均值,即目标 Pod 所有副本自带的 CPU 利用率的平均值。
  • Pod 自身的 CPU 利用率是该 Pod 当前 CPU 的使用量除以它的Pod Request 的值,比如我们定义一个 Pod 的 Pod Request 为 0.4,而当前 Pod 的 CPU 使用量为 0.2,则它的 CPU 使用率为 50%,这样就可以算出来一个 RC 或 RS 控制的所有 Pod 副本的 CPU 利用率的算术平均值。
  • 如果某一时刻CPU Utilization Percentage的值超过 80%,则意味着当前的 Pod 副本数很可能不足以支撑接下来更多的请求,需要进行动态扩容,而当请求高峰时段过去后,Pod 的 CPU 利用率又会降下来,此时对应的Pod副本数应该自动减少到一个合理的水平。
  • 支持对象:DeploymentConfig、ReplicationController、Deployment、Replica Set
  • RS可以通过HPA来根据一些运行时指标实现自动伸缩,下面是一个简单的例子:(需要安装 metrics-server
# 下载 metrics-server 修改配置
  - name: metrics-server
    image: mirrorgooglecontainers/metrics-server-amd64:v0.3.6
    args:
      - --cert-dir=/tmp
      - --secure-port=4443
      - --kubelet-insecure-tls
      - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
1、创建 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
spec:
  replicas: 1
  selector:
    matchLabels:
      app: php-apache
  template:
    metadata:
      name: php-apache
      labels:
        app: php-apache
    spec:
      containers:
      - name: php-apache
        image: siriuszg/hpa-example
        resources:
          requests:
            cpu: 20m
        ports:
        - containerPort: 80
2、创建 services
apiVersion: v1
kind: Service
metadata:
  name: php-apache
spec:
  ports:
  - port: 80
  selector:
    app: php-apache
3、创建HPA
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: php-apache
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50
4、使用kubectl create创建资源
[root@k8s-master ~]# kubectl apply -f dep.yaml
[root@k8s-master ~]# kubectl apply -f svc.yaml
[root@k8s-master ~]# kubectl apply -f hpa.yaml
5、进行压力测试
  • 创建压力测试 Pod
apiVersion: v1
kind: Pod
metadata:
  name: load-generator
spec:
  containers:
  - name: busybox
    image: busybox
    command: ["sleep", "3600"]
  • 进入测试 Pod 中的容器中进行压力测试
while true; do wget -q -O- http://php-apache > /dev/null; done
  • 用命令的方式实现
# 增加负载
[root@k8s-master ~]# kubectl run -i --tty load-generator --image=busybox:latest /bin/sh 
# 进入容器后执行一下命令
while true; do wget -q -O- http://php-apache.default.svc.cluster.local; done
6、验证效果
  • 压测几分钟后查看hpa状态,如果cpu压力超过了设置的阈值,可以停止压测。
# 开始查看状态
[root@k8s-master ~]# kubectl get hpa
NAME         REFERENCE               TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache   5%/50%    1         10        10         6m5s
# 过1分钟左右再次检查HPA状态和部署状态
[root@k8s-master ~]# kubectl get hpa
NAME         REFERENCE               TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
php-apache   Deployment/php-apache   460%/50%   1         10        10         4m25s
# 开始 pod 数量状态
[root@k8s-master ~]# kubectl get pods
NAME                          READY   STATUS              RESTARTS   AGE
load-generator                1/1     Running             0          41m
php-apache-7ddb67b575-c8vcd   1/1     Running             0          2m40s
# 1 分钟后pod数量状态
[root@k8s-master ~]# kubectl get pods
NAME                          READY   STATUS    RESTARTS   AGE
load-generator                1/1     Running   0          43m
php-apache-7ddb67b575-b2qkz   1/1     Running   0          2m53s
php-apache-7ddb67b575-c8vcd   1/1     Running   0          5m23s
php-apache-7ddb67b575-cpjjq   1/1     Running   0          2m37s
php-apache-7ddb67b575-p7rw9   1/1     Running   0          2m22s
php-apache-7ddb67b575-pbrzf   1/1     Running   0          2m53s
php-apache-7ddb67b575-pvmg9   1/1     Running   0          2m22s
php-apache-7ddb67b575-sw82k   1/1     Running   0          2m37s
php-apache-7ddb67b575-tk6tn   1/1     Running   0          2m53s
php-apache-7ddb67b575-xxgxw   1/1     Running   0          2m37s
php-apache-7ddb67b575-z2mdm   1/1     Running   0          2m37s

[root@k8s-master ~]# kubectl get deployment php-apache
# 停压,等1分钟查看状态
  • 查看hpa状态已经超过了阈值,此时查看pod,pod的数量已经增加了。
  • 当停止压力测试之后,过一段时间,Pod数量又会恢复到1。

8、Kubernetes 存储卷

1、Volum 介绍
  • Volume 是 Pod 中能够被多个容器访问的共享目录。Volume 定义在 Pod 上,被一个 Pod 里的多个容器挂载到具体的文件目录下,当容器终止或者重启时,Volume (volume 类型,临时性和持久性)中的数据也不会丢失。
  • Kubernetes 支持多种类型的 Volume,例如 本地卷、NFS、GlusterFS、Ceph 等分布式文件系统。
2、Volum 使用
1、临时卷 emptyDir
  • emptyDir 是在 Pod 分配到 Node 时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,它是 Kubernetes 自动分配的一个目录,当 Pod 从 Node 上移除时,emptyDir 中的数据也会被永久删除。
  • emptyDir 的用途
  • 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保存。
  • 长时间任务的中间过程 CheckPoint (检查点)的临时保存目录。
  • 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。
  • emptyDir 的定义
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        app: app-demo
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        volumeMounts:
          - mountPath: /mydata-data
            name: datavol
      volumes:
      - name: datavol
        emptyDir: {}
  • 练习示例
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      tier: frontend
    matchExpressions:
      - {key: tier, operator: In, values: [frontend]}
  template:
    metadata:
      labels:
        app: app-demo
        tier: frontend
    spec:
      containers:
      - name: tomcat-demo
        image: tomcat
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 0.1
          limits:
            cpu: 0.2
        volumeMounts:
          - mountPath: /mydata-data
            name: datavol
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 0.1
          limits:
            cpu: 0.2
        volumeMounts:
          - mountPath: /mydata-data
            name: datavol
      volumes:
      - name: datavol
        emptyDir: {}
 
 # 使用内存作为临时卷
       volumes:
      - name: datavol
        emptyDir:
          medium: Memory
  • 需要使用内存作为 emptyDir的可用存储资源也是可以的,只需要在创建 emptyDir卷时增加一个 emptyDir.medium 字段的定义,并赋值为 “Memory” 即可。
  • 使用 tmpfs 文件系统作为 emptyDir 的存储后端时,如果遇到 node 节点重启,则 emptyDi r中的数据也会全部丢失。同时,你编写的任何文件也都将计入 Container 的内存使用限制,即 emptydir 方式保存的数据不能持久化。
2、本地卷 hostPath
  • 使用 hostPath 挂载宿主机上的文件或目录,主要用于以下几个方面:
  • 容器应用程序生成的日志文件需要永久保存时,可以使用宿主机的文件系统存储。
  • 需要访问宿主机上 Docker 引擎内部数据时,可以定义 hostPath 的宿主机目录为 docker 的数据存储目录,使容器内部应用可以直接访问 docker 的数据文件。
  • 使用 hostPath 时,需要注意以下两点
  • 在不同的 Node 上的 Pod 挂载的是本地宿主机的目录,如果要想让不同的 Node 挂载相同的目录,则可以使用网络存储或分布式文件存储。
  • 如果使用了资源配额管理,则 Kubernetes 无法将其在宿主机上使用的资源纳入管理。
  • hostPath 的定义
volumes:
- name: "persistent-storage"
  hostPath:
    path: "/data"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      tier: frontend
    matchExpressions:
      - {key: tier, operator: In, values: [frontend]}
  template:
    metadata:
      labels:
        app: app-demo
        tier: frontend
    spec:
      containers:
      - name: tomcat-demo
        image: tomcat
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 0.1
          limits:
            cpu: 0.2
        volumeMounts:
          - mountPath: /mydata-data
            name: datavol
      volumes:
      - name: datavol
        hostPath:
          path: "/data"
  • 本地卷不提供pod的亲和性,即host path映射的目录在k8s-node01,而pod可能被调度到k8s-node02,导致原来的在k8s-node01的数据不存在,pod一直无法启动起来。
  • 能够提供pv/pvc/storage class的方式使用。
  • 数据能持久化。
3、谷歌磁盘 gcePersistentDisk
  • 使用这种类型的 Volume 表示使用谷歌公有云提供的永久磁盘(Persistent Disk,PD)存放数据,使用 gcePersistentDisk 有以下一些限制条件:
  • Node 需要是谷歌 GCE 云主机。
  • 这些云主机需要与 PD 存在于相同的 GCE 项目和 Zone 中。
  • 通过 gcloud 命令创建一个PD:
gcloud compute disks create --size=500GB --zone=us-centrall-a my-data-disk
  • 定义gcePersistentDisk类型的Volume
volumes:
- name: test-volume
  gcPersistentDisk:
    pdName: my-data-disk
    fsType: ext4
4、亚马逊卷 awsElasticBlockStore
  • 与 GCE 类似,该类型的 Volume 使用亚马逊公有云提供的 EBS Volume 存储数据,需要先创建一个 EBS Volume 才能使用 awsElasticBlockStore。
  • 使用 awsElasticBlockStore的一些限制条件
  • Node 需要是 AWS EC2 实例。
  • 这些 AWS EC2 实例需要与 EBS volume 存在于相同的 region 和 availability-zone 中。
  • EBS 只支持单个 EC2 实例 mount 一个 volume。
  • 通过 aws ec2 create-volume 命令创建一个 EBS volume
aws ec2 create-volume --availability-zone eu-west-la --size 10 --volume-type gp2
  • 定义awsElasticBlockStore类型的Volume的示例
volumes:
- name: test-volume
  awsElasticBlockStore:
    volumeID: aws://<availability-zone>/<volume-id>
    fsType: ext4
5、共享卷 NFS
  • 使用 NFS 网络文g件系统提供的共享目录存储数据时,需要在系统中部署一个 NFS Server
1、NFS(存储端)
[root@nfs ~]# vim /etc/hosts 
192.168.122.59  nfs
192.168.122.85  client

[root@nfs ~]# yum -y install nfs-utils
[root@nfs ~]# mkdir /data                                 # 存储目录

[root@nfs ~]# vim /etc/exports
/data        192.168.122.0/24(rw,sync,no_root_squash)     # 不压制root(当client端使用root挂载时,也有root权限)
[root@nfs ~]# systemctl start nfs-server
[root@nfs ~]# systemctl enable nfs-server
[root@nfs ~]# exportfs -v
/data     192.168.122.0/24(rw,wdelay,no_root_squash,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
2、NFS(客户端)
[root@client ~]# vim /etc/hosts
192.168.122.59  nfs
192.168.122.85  client

[root@client ~]# yum -y install nfs-utils

# 1. 验证存储端共享 
[root@client ~]# showmount -e nas
Export list for nas:
/data 192.168.122.0/24

# 2、挂载方式
# 手动挂载 
[root@client ~]# mount -t nfs nas:/data /data

# 开机自动挂载
[root@client ~]# vim /etc/fstab
nas:/data      /data           nfs     defaults        0 0
[root@client ~]# mount -a

# 查看挂载
[root@client ~]# df
nas:/data     7923136 692416   6821568  10% /data

# 卸载
[root@client ~]# umount /data
3、定义 NFS 类型的 Volume
volumes:
- name: nfs-volume
  nfs:
    server: nfs-server.localhost
    path: "/"
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      tier: frontend
    matchExpressions:
      - {key: tier, operator: In, values: [frontend]}
  template:
    metadata:
      labels:
        app: app-demo
        tier: frontend
    spec:
      containers:
      - name: tomcat-demo
        image: tomcat
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 0.1
          limits:
            cpu: 0.2
        volumeMounts:
          - mountPath: /mydata-data   	# 容器内部的挂载目录
            name: datavol
            subPath: newdir    			# 挂载后创建一个子目录
      volumes:
      - name: datavol
        nfs:
          server: 192.168.152.193  		# nfs 服务器地址(或域名)
          path: "/data"    				# nfs服务器的数据目录
apiVersion: v1
kind: Pod
metadata:
 name: volume-pod
spec:
  containers:
  - name: nginx1
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - name: app-logs
      mountPath: /usr/share/nginx/html/
  - name: busybox
    image: busybox
    command:
    - "/bin/sh"
    - "-c"
    - "sleep 3600"
    volumeMounts:
    - name: app-logs
      mountPath: /opt
  volumes:
  - name: datavol
    nfs:
      server: 192.168.152.193  		# nfs 服务器地址(或域名)
      path: "/data"    				# nfs服务器的数据目录
      
[root@k8s-master ~]# kubectl exec -it volume-pod --container busybox -- /bin/sh
# 创建 html 文件
6、其他类型的Volume
  • iscsi:使用 iSCSI 存储设备上的目录挂载到 Pod 中。
  • flocker:使用 Flocker 来管理存储卷。
  • glusterfs:使用 GlusterFS 网络文件系统的目录挂载到 Pod 中。
  • rbd:使用 Ceph 块设备共享存储(Rados Block Device)挂载到Pod中。
  • gitRepo:通过挂载一个空目录,并从 GIT 库 clone 一个 git repository 以供 Pod 使用。
  • secret:一个 secret volume 用于为 Pod 提供加密的信息,可以将定义在 Kubernetes 中的 secret 直接挂载为文件让 Pod 访问。Secret volume 是通过 tmfs(内存文件系统)实现的,所以这种类型的 volume 不会持久化。

9、Kubernetes 持久卷

1、持久卷 Persistent Volume 介绍
  • 管理存储是管理计算的一个明显问题。该PersistentVolume子系统为用户和管理员提供了一个API,用于抽象如何根据消费方式提供存储的详细信息。为此,引入了两个新的API资源:PersistentVolume 和 PersistentVolumeClaim
  • Persistent Volume(简称PV)和与之相关联的 Persistent Volume Claim(简称PVC)实现pod 挂载网络存储的功能,PVC 和 PV 是一一对应的。
  • PersistentVolume(PV 持久卷)是集群中由管理员配置的一段网络存储。 它是集群中的资源,就像节点是集群资源一样。
  • PV是容量插件,如Volumes,但其生命周期独立于使用PV的任何单个pod。 此API对象捕获存储实现的详细信息,包括NFS,iSCSI或特定于云提供程序的存储系统。
  • PersistentVolumeClaim(PVC 持久卷请求)是由用户进行存储的请求。 它类似于pod。 Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以一次读/写或多次只读)。
  • PersistentVolumeClaims 允许用户使用抽象存储资源,但是PersistentVolumes对于不同的问题,用户通常需要具有不同属性(例如性能)。群集管理员需要能够提供各种PersistentVolumes的不同配置方式,而不仅仅是大小和访问模式,对于用户还要实现透明化,不让用户了解这些卷的实现方式。需要满足这些需求需要新的资源类型承担,StorageClass 资源就提供了动态获取存储卷的方法。
  • StorageClass 为管理员提供了一种描述动态提供的存储的“类”的方法。 不同的类可能映射到服务质量级别,或备份策略,或者由群集管理员确定的任意策略。
  • Kubernetes 中 StorageClass 是一中如何动态获取持久卷的方法,包含获取持久卷的配置,策略。 这个概念可以理解为存储系统中获取存储的“配置文件”。
  • Kubernetes 中 Volume 是定义在 Pod 上的,属于“计算资源”的一部分,“网络存储”是相对独立于“计算资源”而存在的一种实体资源。在使用主机的情况下,通常会先创建一个网络存储,然后从中划出一个“网盘”并挂载到主机上
  • PV 与 Volume 的区别
  • PV 只能是网络存储,不属于任何 Node,但可以在每个 Node 上访问。
  • PV 并不是定义在 Pod 上的,而是独立于 Pod 之外定义。
2、持久卷 PV 生命周期
  • PV是群集中的资源。PVC是对这些资源的请求,并且还充当对资源的检查。PV和PVC之间的相互作用遵循以下生命周期:
  • Provisioning ——-> Binding ——–>Using——>Releasing——>Recycling
  • 供应准备 Provisioning 通过集群外的存储系统或者云平台来提供存储持久化支持。
  • 静态提供Static:集群管理员创建多个PV。 它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费
  • 动态提供Dynamic:当管理员创建的静态PV都不匹配用户的PersistentVolumeClaim时,集群可能会尝试为PVC动态配置卷。 此配置基于 StorageClasses:PVC必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。 要求该类的声明有效地为自己禁用动态配置。
  • 绑定Binding,用户创建pvc并指定需要的资源和访问模式。在找到可用pv之前,pvc会保持未绑定状态。
  • 使用Using,用户可在pod中像volume一样使用pvc。
  • 释放Releasing,用户删除pvc来回收存储资源,pv将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc使用。
  • 回收Recycling,pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。
  • 保留策略:允许人工处理保留的数据。
  • 删除策略:将删除pv和外部关联的存储资源,需要插件支持。
  • 回收策略:将执行清除操作,之后可以被新的pvc使用,需要插件支持。
  • 注:目前只有 NFS 和 HostPath 类型卷支持回收策略,AWS EBS,GCE PD,Azure Disk和Cinder支持删除(Delete)策略。
3、持久卷 PV 类型
  • GCEPersistentDisk
  • AWSElasticBlockStore
  • AzureFile
  • AzureDisk
  • FC (Fibre Channel)
  • Flexvolume
  • Flocker
  • NFS
  • iSCSI
  • RBD (Ceph Block Device)
  • CephFS
  • Cinder (OpenStack block storage)
  • Glusterfs
  • VsphereVolume
  • Quobyte Volumes
  • HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)
  • Portworx Volumes
  • ScaleIO Volumes
  • StorageOS
4、持久卷 PV 的定义
  • NFS 类型 PV 的 yaml 定义内容,声明需要 5G 的存储空间
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /somepath
    server: 172.17.0.2
  • PV 的 accessModes 属性有以下类型:
  • ReadWriteOnce:读写权限、并且只能被单个 Node 挂载。
  • ReadOnlyMany:只读权限、允许被多个 Node 挂载。
  • ReadWriteMany:读写权限、允许被多个 Node 挂载。
5、持久卷请求 PVC 定义
  • 如果 Pod 想申请使用 PV 资源,则首先需要定义一个 PersistentVolumeClaim(PVC)对象:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
6、Pod 使用 PVC 关联 PV
  • 然后在 Pod 的 volume 定义中引用上述 PVC 即可
volumes:
  - name: mypd
    persistentVolumeClaim:
      claimName: myclaim
  • PV 是有状态的对象,它有以下几种状态:
  • Available:空闲状态。
  • Bound:已经绑定到某个 PVC 上。
  • Released:对应的 PVC 已经删除,但资源还没有被集群收回。
  • Failed:PV 自动回收失败。
7、持久卷 PV 使用实例
1、准备nfs服务
1、nfs 服务器上建立存储卷对应的目录
[root@nfs ~]# cd /data/volumes/
[root@nfs volumes]# mkdir v{1,2,3,4,5}
[root@nfs volumes]# ls
index.html  v1  v2  v3  v4  v5
[root@nfs volumes]# echo "<h1>NFS stor 01</h1>" > v1/index.html
[root@nfs volumes]# echo "<h1>NFS stor 02</h1>" > v2/index.html
[root@nfs volumes]# echo "<h1>NFS stor 03</h1>" > v3/index.html
[root@nfs volumes]# echo "<h1>NFS stor 04</h1>" > v4/index.html
[root@nfs volumes]# echo "<h1>NFS stor 05</h1>" > v5/index.html
2、修改nfs的配置
[root@nfs volumes]# vim /etc/exports
/data/volumes/v1        192.168.130.0/24(rw,no_root_squash)
/data/volumes/v2        192.168.130.0/24(rw,no_root_squash)
/data/volumes/v3        192.168.130.0/24(rw,no_root_squash)
/data/volumes/v4        192.168.130.0/24(rw,no_root_squash)
/data/volumes/v5        192.168.130.0/24(rw,no_root_squash)
3、查看nfs的配置
[root@nfs volumes]# exportfs -arv
exporting 192.168.130.0/24:/data/volumes/v5
exporting 192.168.130.0/24:/data/volumes/v4
exporting 192.168.130.0/24:/data/volumes/v3
exporting 192.168.130.0/24:/data/volumes/v2
exporting 192.168.130.0/24:/data/volumes/v1
4、配置生效
[root@nfs volumes]# showmount -e
Export list for nfs:
/data/volumes/v5 192.168.130.0/24
/data/volumes/v4 192.168.130.0/24
/data/volumes/v3 192.168.130.0/24
/data/volumes/v2 192.168.130.0/24
/data/volumes/v1 192.168.130.0/24
2、创建PV
1、编写yaml文件并创建pv
  • 创建5个pv,存储大小各不相同,是否可读也不相同
[root@k8s-master volumes]# vim pv-damo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
  labels:
    name: pv001
spec:
  nfs:
    path: /data/volumes/v1
    server: nfs
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
  labels:
    name: pv002
spec:
  nfs:
    path: /data/volumes/v2
    server: nfs
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
  labels:
    name: pv003
spec:
  nfs:
    path: /data/volumes/v3
    server: nfs
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 20Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv004
  labels:
    name: pv004
spec:
  nfs:
    path: /data/volumes/v4
    server: nfs
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 10Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv005
  labels:
    name: pv005
spec:
  nfs:
    path: /data/volumes/v5
    server: nfs
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 15Gi
[root@k8s-master volumes]# kubectl apply -f pv-damo.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
persistentvolume/pv004 created
persistentvolume/pv005 created
2、查询验证存储卷
[root@k8s-master ~]# kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
pv001     5Gi        RWO,RWX        Retain           Available                                      9s
pv002     5Gi        RWO            Retain           Available                                      9s
pv003     5Gi        RWO,RWX        Retain           Available                                      9s
pv004     10Gi       RWO,RWX        Retain           Available                                      9s
pv005     15Gi       RWO,RWX        Retain           Available                                      9s
3、创建PVC并绑定PV
1、编写yaml文件创建pvc
  • 创建一个pvc,需要6G存储;所以不会匹配pv001、pv002、pv003
[root@k8s-master volumes]# vim vol-pvc-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 6Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: vol-pvc
  namespace: default
spec:
  volumes:
  - name: html
    persistentVolumeClaim:
      claimName: mypvc
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
[root@k8s-master volumes]# kubectl apply -f vol-pvc-demo.yaml
persistentvolumeclaim/mypvc created
pod/vol-pvc created
2、查询验证
  • pvc已经绑定到pv004上
[root@k8s-master ~]# kubectl get pvc
NAME      STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc     Bound     pv004     10Gi       RWO,RWX                       24s
[root@k8s-master ~]# kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM           STORAGECLASS   REASON    AGE
pv001     5Gi        RWO,RWX        Retain           Available                                            1m
pv002     5Gi        RWO            Retain           Available                                            1m
pv003     5Gi        RWO,RWX        Retain           Available                                            1m
pv004     10Gi       RWO,RWX        Retain           Bound       default/mypvc                            1m
pv005     15Gi       RWO,RWX        Retain           Available                                            1m
3、查询业务验证
[root@k8s-master ~]# kubectl get pods -o wide
NAME      READY     STATUS    RESTARTS   AGE       IP             NODE
vol-pvc   1/1       Running   0          59s       10.244.2.117   k8s-node02
[root@k8s-master ~]# curl 10.244.2.117
<h1>NFS stor 04</h1>
4、设置 pv 策略 (回收pvc)
[root@k8s-master ~]# kubectl patch pv <your-pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Recycle"}}'
# 保留(Retain),回收(Recycle)和删除(Delete)。
[root@k8s-master ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv001   2Gi        RWO,RWX        Retain           Available                                   35m
pv002   5Gi        RWO            Retain           Available                                   35m
pv003   6Gi        RWO,RWX        Recycle          Available                                   35m
pv004   8Gi        RWO,RWX        Retain           Available                                   35m
pv005   10Gi       RWO,RWX        Retain           Available                                   35m

10、Kubernetes 配置管理控制器

1、ConfigMap 介绍
  • kubernetes 配置管理控制器ConfigMap,是应用部署的一个最佳实践,就是将应用所需的配置信息与程序进行分离。
  • kubernetes 提供了一种集群配置管理方案ConfigMap,将一些环境变量或者配置文件定义为 ConfigMap,放在 kubernetes 中,可以让其他 pod 调用。
2、ConfigMap 用法
  • 生成为容器内的环境变量
  • 设置容器启动命令的启动参数(需设置为环境变量)
  • 以 volume 的形式挂载为容器内部的文件或目录
3、ConfigMap 局限
  • ConfigMap 必须在pod之前创建
  • Pod 只能使用同一个命名空间内的 ConfigMap
  • 使用 envFrom 时,将会自动忽略无效的键
  • 如果是volume的形式挂载到容器内部,只能挂载到某个目录下,该目录下原有的文件会被覆盖掉
  • 静态pod不能用configmap(静态 pod 不受API server 管理)
4、ConfigMap 创建
  • 使用 kubectl create configmap 从文件、目录或者 key-value 字符串创建等创建 ConfigMap。也可以通过 kubectl create -f file 创建。
1、从 key-value 字符串创建
[root@k8s-master ~]# kubectl create configmap special-config --from-literal=special.how=very
configmap "special-config" created
[root@k8s-master ~]# kubectl get configmap special-config -o go-template='{{.data}}'
map[special.how:very]
2、从 env 文件创建
[root@k8s-master ~]# echo -e "a=b\nc=d" | tee config.env
a=b
c=d
[root@k8s-master ~]# kubectl create configmap special-config --from-env-file=config.env
configmap "special-config" created
[root@k8s-master ~]# kubectl get configmap special-config -o go-template='{{.data}}'
map[a:b c:d]
3、从目录创建
[root@k8s-master ~]# mkdir config
[root@k8s-master ~]# echo a>config/a
[root@k8s-master ~]# echo b>config/b
[root@k8s-master ~]# kubectl create configmap special-config --from-file=config/
configmap "special-config" created
[root@k8s-master ~]# kubectl get configmap special-config -o go-template='{{.data}}'
map[a:a
  b:b
]
4、从文件 Yaml/Json 文件创建
apiVersion: v1
kind: ConfigMap
metadata:
  name: special-config
  namespace: default
data:
  special.how: very
  special.type: charm

[root@k8s-master ~]# kubectl create -f config.yaml
configmap "special-config" created
5、ConfigMap 使用示例
1、环境变量的方式
1、定义一个ConfigMap
  • 配置文件 cm-appvars.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: cm-appvars
data:
  apploglevel: info
  appdatadir: /var/data
2、创建 ConfigMap
# 创建configmap
[root@k8s-master ~]# kubectl create -f cm-appvars.yaml

# 查看configmap
[root@k8s-master ~]# kubectl describe configmap
[root@k8s-master ~]# kubectl describe configmap cm-appvars
3、使用 ConfigMap
apiVersion: v1
kind: Pod
metadata:
 name: cm-test-pod
spec:
  containers:
  - name: cm-test
    image: busybox
    command: ["/bin/sh","-c","sleep 3600"]
    env:
    - name: APPLOGLEVEL
      valueFrom:
        configMapKeyRef:
          name: cm-appvars   # 要和之前创建的ConfigMap的name对应
          key: apploglevel
    - name: APPDATADIR
      valueFrom:
        configMapKeyRef:
          name: cm-appvars   # 要和之前创建的ConfigMap的name对应
          key: appdatadir
[root@k8s-master ~]# kubectl apply -f configmap-pod.yaml 
pod/cm-test-pod created
[root@k8s-master ~]# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
cm-test-pod   1/1     Running   0          11s

[root@k8s-master ~]# kubectl  exec -it cm-test-pod /bin/sh
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # echo $APPLOGLEVEL
info
/ # echo $APPDATADIR
/var/date
  • 除了可以定义简单的 k-v 键值对,还可以将整个配置文件定义成 ConfigMap
  • 比如 server.xml logging.properties(使用 volumeMount 的形式,挂载到容器内部)
2、用作命令行参数
  • 创建 ConfigMap
[root@k8s-master ~]# kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm
[root@k8s-master ~]# kubectl create configmap env-config --from-literal=log_level=INFO
  • 将 ConfigMap 用作命令行参数时,需要先把 ConfigMap 的数据保存在环境变量中,然后通过 $(VAR_NAME) 的方式引用环境变
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
  - name: test-container
   image: busybox
   command: ["/bin/sh", "-c", "echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ]
   env:
     - name: SPECIAL_LEVEL_KEY
       valueFrom:
         configMapKeyRef:
           name: special-config
           key: special.how
    - name: SPECIAL_TYPE_KEY
      valueFrom:
        configMapKeyRef:
          name: special-config
          key: special.type
  restartPolicy: Never
  • Pod 结束后会输出 very charm
3、使用volume 将 ConfigMap 作为文件或目录直接挂载
  • 将创建的 ConfigMap 直接挂载至 Pod 的 / etc/config 目录下,其中每一个 key-value 键值对都会生成一个文件,key 为文件名,value 为内容
apiVersion: v1
kind: Pod
metadata:
  name: vol-test-pod
spec:
  containers:
    - name: test-container
      image: busybox
      command: ["/bin/sh", "-c", "cat /etc/config/special.how"]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
 volumes:
   - name: config-volume
     configMap:
       name: special-config
restartPolicy: Never
  • Pod 结束后会输出 very
  • 将创建的 ConfigMap 中 special.how 这个 key 挂载到 / etc/config 目录下的一个相对路径 / keys/special.level。如果存在同名文件,直接覆盖。其他的 key 不挂载
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: ["/bin/sh","-c","cat /etc/config/keys/special.level"]
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: special-config
        items:
        - key: special.how
          path: keys/special.level
restartPolicy: Never
  • Pod 结束后会输出 very
  • ConfigMap 支持同一个目录下挂载多个 key 和多个目录。例如下面将 special.how 和special.type 通过挂载到 / etc/config 下。并且还将 special.how 同时挂载到 / etc/config2下。
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: gcr.io/google_containers/busybox
      command: ["/bin/sh","-c","sleep 36000"]
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config
        - name: config-volume2
          mountPath: /etc/config2
     volumes:
       - name: config-volume
         configMap:
           name: special-config
           items:
           - key: special.how
             path: keys/special.level
           - key: special.type
             path: keys/special.type
      - name: config-volume2
        configMap:
          name: special-config
          items:
          - key: special.how
            path: keys/special.level
  restartPolicy: Never
  
# ls /etc/config/keys/
special.level special.type
# ls /etc/config2/keys/
special.level
# cat /etc/config/keys/special.level
very
# cat /etc/config/keys/special.type
charm

使用 subpath 将 ConfigMap 作为单独的文件挂载到目录

  • 在一般情况下 configmap 挂载文件时,会先覆盖掉挂载目录,然后再将 congfigmap 中的内容作为文件挂载进行。如果想不对原来的文件夹下的文件造成覆盖,只是将
    configmap 中的每个 key,按照文件的方式挂载到目录下,可以使用 subpath 参数
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: nginx
      command: ["/bin/sh","-c","sleep 36000"]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/nginx/special.how
        subPath: special.how
  volumes:
    - name: config-volume
      configMap:
      name: special-config
      items:
      - key: special.how
        path: special.how
  restartPolicy: Never
  
root@dapi-test-pod:/# ls /etc/nginx/
conf.d fastcgi_params koi-utf koi-win mime.types modules ng
inx.conf scgi_params special.how uwsgi_params win-utf
root@dapi-test-pod:/# cat /etc/nginx/special.how
very
root@dapi-test-pod:/#
1、定义一个 ConfigMap 配置文件 cm-index.yaml
[root@k8s-master ~]# vim  cm-index.yaml
apiVersion: v1
kind: ConfigMap
metadata:
 name: index-configmap
data:
  key-index.html: |
    hello world!
2、创建 ConfigMap:
[root@k8s-master ~]# kubectl create -f  cm-index.yaml
3 使用 ConfigMap (使用volumeMount的形式)
[root@k8s-master ~]# vim nginx-configmap.yaml
apiVersion: v1
kind: Pod
metadata:
 name: cm-test-app
spec:
  containers:
  - name: cm-test-app
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - name: index            # 应用下面定义的 volumes名
      mountPath: /usr/share/nginx/html
  volumes:
  - name: index              # volumes名
    configMap:
      name: index-configmap  # 这个名字是第二步创建的 configMap
      items:
      - key: key-index.html
        path: index.html     # 最终实际配置文件名称 my.cnf
  • **注意:**如果是 volume 的形式挂载到容器内部,只能挂载到某个目录下,该目录下原有的文件会被覆盖掉

11、Kubernetes 守护程序集

1、DaemonSet 介绍
  • Kubernetes 守护程序集 DaemonSet,DaemonSet 保证在每个 Node 上都运行一个容器副本,常用来部署一些集群的日志、监控或者其他系统管理应用
  • DaemonSet 的一些典型用法
  • 日志收集,比如 fluentd,logstash 等
  • 系统监控,比如 Prometheus Node Exporter,collectd,New Relic agent,Gangliagmond 等
  • 系统程序,比如 kube-proxy, kube-dns, glusterd, ceph 等
2、DaemonSet 使用实例
1、配置使得在每个节点上都有一个fluentd 容器
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-cloud-logging
  namespace: kube-system
  labels:
    k8s-app: fluentd-cloud-logging
spec:
  selector:
    matchLabels:
      k8s-app: fluentd-cloud-logging
  template:
    metadata:
      namespace: kube-system
      labels:
        k8s-app: fluentd-cloud-logging
    spec:
      containers:
      - name: fluentd-cloud-logging
        image: fluent/fluentd 
        resources:
          limits:
            cpu: 100m
            memory: 200Mi
        env:
        - name: FLUENTD_ARGS
          value: -q
        volumeMounts:
        - name: varlog
          mountPath: /var/log
          readOnly: false
        - name: containers
          mountPath: /var/lib/docker/containers
      volumes:
      - name: containers
        hostPath:
          path: /var/lib/docker/containers
      - name: varlog
        hostPath:
          path: /var/log

# 查看
[root@k8s-master ~]# kubectl get daemonsets.apps fluentd-cloud-logging --namespace kube-system
[root@k8s-master ~]# kubectl get pods --namespace kube-system
3、DaemonSet 滚动更新
  • v1.6 + 支持 DaemonSet 的滚动更新,可以通过 .spec.updateStrategy.type 设置更新策略。目前支持两种策略
  • OnDelete:默认策略,更新模板后,只有手动删除了旧的 Pod 后才会创建新的Pod
  • RollingUpdate:更新 DaemonSet 模版后,自动删除旧的 Pod 并创建新的 Pod
  • 在使用 RollingUpdate 策略时,还可以设置
  • .spec.updateStrategy.rollingUpdate.maxUnavailable , 默认 1
  • spec.minReadySeconds ,默认 0
4、DaemonSet 回滚
  • v1.7 + 还支持回滚
# 查询历史版本
[root@k8s-master ~]# kubectl rollout history daemonset <daemonset-name>
# 查询某个历史版本的详细信息
[root@k8s-master ~]# kubectl rollout history daemonset <daemonset-name> --revision=1
# 回滚
[root@k8s-master ~]# kubectl rollout undo daemonset <daemonset-name> --to-revision=<revision>
# 查询回滚状态
[root@k8s-master ~]# kubectl rollout status ds/<daemonset-name>
5、DaemonSet 指定 Node 节点
  • DaemonSet 会忽略 Node 的 unschedulable 状态,有两种方式来指定 Pod 只运行在指定的 Node 节点上:
  • nodeSelector:只调度到匹配指定 label 的 Node 上
  • nodeAffinity:功能更丰富的 Node 选择器,比如支持集合操作
  • podAffinity:调度到满足条件的 Pod 所在的 Node 上
1、nodeSelector 示例
  • 首先给 Node 打上标签
[root@k8s-master ~]# kubectl label nodes node-01 disktype=ssd
  • 然后在 daemonset 中指定 nodeSelector 为 disktype=ssd :
spec:
  nodeSelector:
    disktype: ssd
2、nodeAffinity 示例
  • nodeAffinity 目前支持两种:requiredDuringSchedulingIgnoredDuringExecution 和preferredDuringSchedulingIgnoredDuringExecution,分别代表必须满足条件和优选条件。比如下面的例子代表调度到包含标签 kubernetes.io/e2e-az-name 并且值为e2e-az1 或 e2e-az2 的 Node 上,并且优选还带有标签 another-node-labelkey=another-node-label-value 的 Node。
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
              - e2e-az1
              - e2e-az2
     preferredDuringSchedulingIgnoredDuringExecution:
     - weight: 1
       preference:
         matchExpressions:
         - key: another-node-label-key
           operator: In
           values:
           - another-node-label-value
  containers:
  - name: with-node-affinity
    image: gcr.io/google_containers/pause:2.0
3、podAffinity 示例
  • podAffinity 基于 Pod 的标签来选择 Node,仅调度到满足条件 Pod 所在的 Node 上,支持 podAffinity 和 podAntiAffinity。这个功能比较绕,以下面的例子为例:如果一个 “Node 所在 Zone 中包含至少一个带有 security=S1 标签且运行中的Pod”,那么可以调度到该 Node不调度到 “包含至少一个带有 security=S2 标签且运行中 Pod” 的 Node 上
apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
          topologyKey: failure-domain.beta.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: gcr.io/google_containers/pause:2.0
6、静态 Pod
  • 除了 DaemonSet,还可以使用静态 Pod 来在每台机器上运行指定的 Pod,这需要kubelet 在启动的时候指定 manifest 目录:
[root@k8s-master ~]# kubelet --pod-manifest-path=/etc/kubernetes/manifests
  • 然后将所需要的 Pod 定义文件放到指定的 manifest 目录中。
  • 注意:静态 Pod 不能通过 API Server 来删除,但可以通过删除 manifest 文件来自动删除对应的 Pod。

12、Kubernetes 批处理调度

1、Job 介绍
  • Kubernetes job资源对象用于定义并启动一个批处理任务。
  • Job 负责批量处理短暂的一次性任务 (short lived one-off tasks),即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束
  • API 版本对照表

Kubernetes 版本

Batch API 版本

默认开启

v1.5+

batch/v1


2、Job 类型
  • 非并行 Job:通常创建一个 Pod 直至其成功结束
  • 固定结束次数的 Job:设置 .spec.completions ,创建多个 Pod,直到.spec.completions 个 Pod 成功结束
  • 带有工作队列的并行 Job:设置 .spec.Parallelism 但不设置.spec.completions ,当所有 Pod 结束并且至少一个成功时,Job 就认为是成功根据 .spec.completions 和 .spec.Parallelism 的设置,
  • Job 划分为以下几种 pattern:

Job 类型

使用示例

行为

completions

Parallelism

一次性Job

数据库迁移

创建一个 Pod 直至其成功结束

1

1

固定结束次数的Job

处理工作队列的Pod

依次创建一个 Pod 运行直至 completions 个成功结束

2+

1

固定结束次数的并行 Job

多个 Pod同时处理工作队列

依次创建多个 Pod 运行直至 completions 个成功结束

2+

2+

并行 Job

多个 Pod同时处理工作队列

创建一个或多个 Pod直至有一个成功结束

1

2+

3、Job 特性
  • 运行完成后退出,但是不会被删除,便于用户查看日志信息,了解任务完成的情况
  • 删除job时,产生的pod也会被一起删除
  • job中可以运行多个pod(任务执行多次),且可以并行运行缩短任务完成的时间
  • 限制job中的pod的完成时间,即设置超时时间
  • 可以设置类似定时计划任务的job,定期执行
4、Job Controller
  • Job Controller 负责根据 Job Spec 创建 Pod,并持续监控 Pod 的状态,直至其成功结束。如果失败,则根据 restartPolicy(只支持 OnFailure 和 Never,不支持 Always)决定是否创建新的 Pod 再次重试任务。
5、Job 配置特点
  • spec.template格式同Pod
  • RestartPolicy 仅支持 Never或OnFailure
  • 单个Pod 时,默认 Pod 成功运行后 Job 即结束
  • .spec.completions 标志 Job 结束需要成功运行的Pod个数,默认为1
  • .spec.parallelism 标志并行运行的Pod的个数,默认为1
  • spec.activeDeadlineSeconds 标志失败 Pod 的重试最大时间,超过这个时间不会继续重试
6、Job 配置示例
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    metadata:
      name: pi
    spec:
      containers:
      - name: pi
        image: perl
        imagePullPolicy: IfNotPresent
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
      
[root@k8s-master ~]# kubectl create -f ./job.yaml
job "pi" created
[root@k8s-master ~]# kubectl describe job pi
# 使用'job-name=pi'标签查询属于该 Job 的 Pod
# 注意不要忘记'--show-all'选项显示已经成功(或失败)的 Pod
[root@k8s-master ~]# kubectl get pod --show-all -l job-name=pi
# 使用 jsonpath 获取 pod ID 并查看 Pod 的日志
pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath={.items..metadata.name})
[root@k8s-master ~]# kubectl logs $pods
3.141592653589793238462643383279502...

===================================================================================
apiVersion: batch/v1
kind: Job
metadata:
  name: test-job
spec:
  completions: 6      			# 需要运行的pod数量
  parallelism: 2      			# 允许并发运行的pod数量
  activeDeadlineSeconds: 360  	# pod运行的超时时间 
  template:
    metadata:
      labels:
        app: test-job
    spec:
      containers:
      - name: test-job
        image: luksa/batch-job
        imagePullPolicy: IfNotPresent
      restartPolicy: OnFailure
  • 固定结束次数的 Job 示例
apiVersion: batch/v1
kind: Job
metadata:
  name: busybox
spec:
  completions: 3
  template:
    metadata:
      name: busybox
    spec:
      containers:
      - name: busybox
        image: busybox
        command: ["echo", "hello"]
      restartPolicy: Never

13、Kubernetes 定时计划调度

1、CronJob 介绍
  • CronJob 用于以时间为基准周期性地执行任务,这些自动化任务和运行在Linux或UNIX系统上的CronJob一样。CronJob对于创建定期和重复任务非常有用,例如执行备份任务、周期性调度程序接口、发送电子邮件等。
  • CronJob 运行流程为 创建job ——> 然后job会创建pod ——> 然后执行调度 ——> 然后销毁pod 理解为 在高频率的定时任务就是不断的创建pod然后删除pod
  • 创建CronJob有两种方式,一种是直接使用kubectl创建,一种是使用yaml文件创建。
1、使用kubectl创建CronJob
[root@k8s-master ~]# kubectl run hello --schedule="*/1 * * * *" --restart=OnFailure --image=busybox -- /bin/sh -c "date; echo Hello from the Kubernetes cluster"
2、yaml 文件的方式创建
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: test-cronjob
spec:
  schedule: "*/1 * * * *"      # 参考定时计划任务(分时日月周)
  startingDeadlineSeconds: 15  # pod必须在规定时间后的15秒内开始执行,若超过该时间未执行,则任务将不运行,且标记失败    
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: test-cronjob
        spec:
          containers:
          - name: test-job
            image: busybox
			args:
			- "/bin/sh"
			- "-c"
			- "date; echo Hello from the Kubernetes cluster"
          restartPolicy: OnFailure
  • 查看创建的CronJob:
[root@k8s-master ~]# kubectl get cronjob
  • 等待1分钟可以查看执行的任务(Jobs):
[root@k8s-master ~]# kuberctl get jobs
  • CronJob每次调用任务的时候会创建一个Pod执行命令,执行完任务后,Pod状态就会变成Completed,如下所示:
[root@k8s-master ~]# kuberctl get pods
  • 可以通过logs查看Pod的执行日志:
[root@k8s-master ~]# kubectl logs -f hello-1558779360-jcp4r
Sat May 25 10:16:23 UTC 2019
Hello from the Kubernetes cluster
  • 如果要删除CronJob,直接使用delete即可:
[root@k8s-master ~]# kubectl delete cronjob hello
3、可用参数的配置
  • 定义一个CronJob的yaml文件
apiversion: batch/v1beta1 
kind: CronJob
metadata:
  labels:
    run: hello
    name: hello
  namespace: default
spec:
  concurrencyPolicy: Allow       # 并发策略
  failedJobsHistorylimit: 1      # 要保留的失败的完成作业数(默认为1)
  successfulJobsHistoryLimit: 3  # 要保留的成功完成的作业数(默认为3)
  schedule: "*/1 * * * *"        # 作业时间表。在此示例中,作业将每分钟运行一次
  startingDeadlineSeconds: 60    # job存活时间 默认不设置为永久
  jobTemplate:				     # 作业模板。这类似于工作示例
    metadata: 
      creationTimestamp: null 
    spec:
      template:
        metadata: 
          creationTimestamp: null
          labels:
            run: hello
        spec:
          containers:
            args:
            - "/bin/sh"
            - "с"
            - "date; echo Hello from the Kubernetes cluster" 
          image: busybox
          imagePullPolicy: Always
            name: hello
            resourcea: {}
            terminationMessagePath: /dev/termination-log 
            terminationMessagePolicy: File
          dnsPolicy: ClusterFirst
          restartPolicy: OnFailure
          reschedulerName: default-scheduler 
          securitvContext: {}
          terminationGracePeriodSeconds: 30
  • spec.concurrencyPolicy(并发策略):
  • Allow(默认):允许并发运行 ,会把停止后的job 和现在新创建的一起运行
  • JobForbid:禁止并发运行,如果一个新的Job创建时,正在运行一个旧Job,那么CronJob controller则不会创建这个新Job
  • Replace:如果一个新的Job创建时,正在运行一个旧Job,那么CronJob controller会使用这个新Job替代正在运行的旧Job
  • .spec.successfulJobsHistoryLimit 和 .spec.failedJobsHistoryLimit 这两个字段是可选的。它们指定了可以保留完成和失败 Job 数量的限制。

14、Kubernetes 无状态服务

  • Service 一个应用服务抽象,定义了 Pod 逻辑集合和访问这个 Pod 集合的策略。反向代理
  • Service 代理 Pod 集合对外表现是为一个访问入口,分配一个集群 IP 地址及端口,来自这个 IP 的请求将被负载均衡 (kube-proxy)转发到后端 Pod 中的容器。
  • Service 通过 LableSelector 选择一组 Pod 提供服务。(以标签的形式标识服务)
1、概述
  • Service 其实就是我们经常提起的微服务架构中的一个“微服务”,Pod、RC /RS等资源对象其实都是为它作“嫁衣”的。Pod、RC 或 RS 与 Service 的逻辑关系如下图所示。
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DhUKDk3E-1610418329802)(assets/37378759db298352a6ea78025f75cc33.png)]
  • Kubernetes 集群中的 Service 定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由 Pod 副本组成的集群实例,Service 与其后端 Pod 副本集群之间则是通过 Label Selector 来实现“无缝对接”的。
  • Kubernetes 集群里使用了 Service(服务),提供了一个虚拟的 IP 地址(Cluster IP)和端口号,Kubernetes 集群里的任何服务都可以通过 Cluster IP+端口的方式来访问此服务,至于访问请求最后会被转发到哪个 Pod,则由运行在每个 Node 上的 kube-proxy 负责。
  • kube-proxy 进程是一个智能的软件负载均衡器,它负责把对 Service 的请求转发到后端的某个 Pod 实例上,并在内部实现服务的负载均衡与会话保持机制。
  • Pod 都会被分配一个单独的 IP 地址,而且每个 Pod 都提供了一个独立的 Endpoint(Pod IP+ContainerPort)以被客户端访问,多个 Pod 副本组成了一个集群来提供服务
  • RC/RS 的作用是保证 Service 的服务能力服务质量始终处于预期的标准。通过分析、识别并构建系统中的所有服务为微服务,最终系统由多个提供不同业务能力而又彼此独立的微服务单元所组成,服务之间通过TCP/IP 进行通信,从而形成了强大而又灵活的弹性集群,拥有了强大的分布式能力、弹性扩展能力、容错能力。系统架构也变得简单和直观许多。
2、Service 配置示例
  • 创建一个deployment
[root@k8s-master ~]# cat deployment-hello.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
spec:
  replicas: 4
  selector:
    matchLabels:
      run: hello
  template:
    metadata:
      labels:
        run: hello
    spec:
      containers:
       - name: hello
         image: nginx
         imagePullPolicy: IfNotPresent
         ports:
         - name: http
           containerPort: 80
  • kubectl 创建 pod
[root@k8s-master ~]# kubectl create -f deployment-hello.yaml
deployment.extensions/hello created

[root@k8s-master ~]# kubectl get rs # 因为deployment是三层架构,看rs是否成功,我们看自动创建4个rs.名称后面的字符串是 模板的哈希值。是不会发生变化的,最后pod的是随机值
NAME DESIRED CURRENT READY AGE
hello-68df45bc79 4 4 0 8m29s
  • 定义Service
apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    run: hello
  • 上述内容定义了一个名为 “tomcat-service” 的 Service,它的服务端口为 8080,拥有 “tier=frontend” 这个 Label 的所有 Pod 实例。
  • 很多服务都存在多个端口的问题,通常一个端口提供业务服务,另外一个端口提供管理服务,比如 Mycat、Codis 等常见中间件。
  • Kubernetes Service 支持多个 Endpoint,要求每个 Endpoint 定义一个名字来区分,下面是 tomcat 多端口的 Service 定义样例。
apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  ports:
  - port: 8080
    name: service-port
  - port: 8005
    name: shutdown-port
  selector:
    tie: frontend
    
[root@k8s-master ~]# cat service-hello.yaml 
apiVersion: v1
kind: Service
metadata:
  name: service-hello
  labels:
  name: service-hello
spec: 
  type: NodePort      # 这里代表是NodePort类型的,另外还有ingress,LoadBalancer
  ports:
  - port: 80          # 这里的端口和clusterIP(kubectl describe service service-hello中的IP的port)对应,即在集群中所有机器上curl 10.98.166.242:80可访问发布的应用服务。
    targetPort: 8080  # 端口一定要和container暴露出来的端口对应,tomcat暴露出来的端口是8080,所以这里也应是8080
    protocol: TCP
    nodePort: 31111   # 所有的节点都会开放此端口30000--32767,此端口供外部调用。
  selector:
    run: hello        # 这里选择器一定要选择容器的标签,
  • 多端口为什么需要给每个端口命名呢?这就涉及 Kubernetes 的服务发现机制了。

2、Kubernetes 的服务发现机制

  • 每个 Kubernetes 中的 Service 都有一个唯一的 Cluster IP 及唯一的名字,而名字是自定义的,可以用服务名访问服务
  • 最早时 Kubernetes 采用了 Linux 环境变量的方式来实现,即每个 Service 生成一些对应的 Linux 环境变量(ENV),并在每个Pod的容器启动时,自动注入这些环境变量,以实现通过 Service 的名字来建立连接的目的。
  • 考虑到通过环境变量获取 Service 的 IP 与端口的方式仍然不方便、不直观,后来 Kubernetes 通过 Add-On 增值包的方式引入了 DNS 系统,把服务名作为 DNS 域名,这样程序就可以直接使用服务名来建立连接了。

3、外部系统访问 Service 的问题

  • Kubernetes集群里有三种IP地址,分别如下:
  • Node IP:Node 节点的IP地址,即物理网卡的IP地址。
  • Pod IP:Pod 的IP地址,即 docker 容器的IP地址,此为虚拟IP地址。
  • Cluster IP:Service 的IP地址,此为虚拟IP地址。
  • 外部访问 Kubernetes 集群里的某个节点或者服务时,必须要通过 Node IP 进行通信。
  • Pod IP 是 Docker Engine 根据 flannel.1 网桥的 IP 地址段进行分配的一个虚拟二层网络IP地址,
  • Pod 与 Pod 之间的访问就是通过这个虚拟二层网络进行通信的,而真实的TCP/IP 流量则是通过 Node IP 所在的物理网卡流出的。
  • Service 的 Cluster IP 具有以下特点:
  • Cluster IP 仅仅作用于 Service 这个对象,并由 Kubernetes 管理和分配 IP 地址。
  • Cluster IP 是一个虚拟地址,无法被 ping。
  • Cluster IP 只能结合 Service Port 组成一个具体的通信端口,供 Kubernetes 集群内部访问,单独的 Cluster IP 不具备 TCP/IP 通信的基础,并且外部如果要访问这个通信端口,需要做一些额外的工作。
  • Node IP、Pod IP 和 Cluster IP 之间的通信,采用的是 Kubernetes 自己设计的一种特殊的路由规则,与IP 路由有很大的区别。
  • 如果想让外部访问应用,最常用的作法是使用 NodePort 方式。
apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  type: NodePort
  ports:
  - port: 8080
    nodePort: 31002
  selector:
    tier: frontend
  • NodePort 的实现方式是在 Kubernetes 集群里的每个 Node 上为需要外部访问的 Service 开启一个对应的 TCP 监听端口,外部系统只要用任意一个 Node 的 P 地址+具体的 NodePort 端口号即可访问此服务。
  • NodePort 还没有完全解决外部访问Service的所有问题,比如负载均衡问题,常用的做法是在 Kubernetes 集群之外部署一个负载均衡器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IDRLT24d-1610418329803)(assets/cceea13a51ed6faab6709667f36d1a27.png)]

  • Load balancer 组件独立于 Kubernetes 集群之外,可以是一个硬件负载均衡器,也可以是软件方式实现,例如 HAProxy 或者 Nginx。这种方式,无疑是增加了运维的工作量及出错的概率。
  • Kubernetes 提供了自动化的解决方案,如果使用谷歌的 GCE 公有云,只需要将 type: NodePort 改成 type: LoadBalancer,此时 Kubernetes 会自动创建一个对应的 Load balancer 实例并返回它的 IP 地址供外部客户端使用。其他公有云提供商只要实现了支持此特性的驱动,则也可以达到上述目的。

15、Kubernetes 有状态服务

1、 StatefulSet 介绍
  • Pod 的管理对象 RC、Deployment、DaemonSet 和 Job 都是面向无状态的服务,但实际中有很多服务是有状态的,比如 Mysql集群、MongoDB集群、ZooKeeper集群等,可以使用 StatefulSet 来管理有状态的服务。
  • StatefulSet 是Kubernetes中的一种控制器,他解决的什么问题呢?
  • Deployment是对应用做了一个简化设置,Deployment认为一个应用的所有的pod都是一样的,他们之间没有顺序,也无所谓在那台宿主机上。需要扩容的时候就可以通过pod模板加入一个,需要缩容的时候就可以任意杀掉一个。
  • 实际的场景中,并不是所有的应用都能做到没有顺序等这种状态,尤其是分布式应用,他们各个实例之间往往会有对应的关系,例如:主从、主备。还有数据存储类应用,它的多个实例,往往会在本地磁盘存一份数据,而这些实例一旦被杀掉,即使从建起来,实例与数据之间关系也会丢失,而这些实例有不对等的关系,实例与外部存储有依赖的关系的应用,被称作“有状态应用”。
  • StatefulSet与Deployment相比,相同于他们管理相同容器规范的Pod,不同的时候,StatefulSet为pod创建一个持久的标识符,他可以在任何编排的时候得到相同的标识符。
  • StatefulSet 里的每个 Pod 都有稳定、唯一的网络标识,可以用来发现集群内的其他成员。假设 StatefulSet 的名字叫 kafka,那么第1个 Pod 叫 kafka-0,第2个叫kafka-1,以此类推。
  • StatefulSet 控制的 Pod 副本的启停顺序是受控的,操作第 n 个 Pod 时,前 n-1 个 Pod 已经是运行且准备好的状态。
  • StatefulSet 里的 Pod 采用稳定的持久化存储卷,通过 PV/PVC 来实现,删除 Pod 时默认不会删除与 StatefulSet 相关的存储卷(为了保证数据的安全)。
2、StatefulSet 的应用特点
  • StatefulSet的核心功能就是,通过某种方式记录应用状态,在Pod被重建的时候,通过这种方式还可以恢复原来的状态。
  • StatefulSet由以下几个部分组成:
  • Headless Service 用于定义网络标识(DNS)
  • volumeClaimTemplates 用于创建PV
  • StatefulSet 用于定义具体应用
  • 稳定且有唯一的网络标识符 当节点挂掉,既pod重新调度后其PodName和HostName不变,基于Headless Service来实现
  • 稳定且持久的存储 当节点挂掉,既pod重新调度能访问到相同的持久化存储,基于PVC实现
  • 有序、平滑的扩展、部署 即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现。
  • 有序、平滑的收缩、删除 既Pod是有顺序的,在收缩或者删除的时候要依据定义的顺序依次进行(既从N-1到0,既倒序)。
  • **拓扑状态。**是应用多个实例之间的不完全对等的关系,这些应用实例是必须按照定义的顺序启动的。例如主应用A先于从应用B启动,如果把A和B删掉,应用还要按照先启动主应用A再启动从应用B,且创建的应用必须和原来的应用的网络标识一样(既PodName和HostName)。这样他们就可以按照原来的顺序创建了。
  • 存储状态。应用实例分别绑定了不同的数据存储,Pod A第一次读到的数据要和10分钟后读到的数据,是同一份。哪怕这期间Pod A被重建。这种典型的例子就是数据库应用的多个存储实例。
3、Headless Service(无头服务)
  • kubernetes中的service是定义pod暴露外部访问的一种机制,例如:3个pod,可以定义一个service通过标签选择器选到这三个pod,然后这个service就可以访问这个pod。
  • Headless service是Service通过DNS访问的其中一种方式,StatefulSet 要与 Headless Service 配合使用,即在每个 StatefulSet 的定义中要声明它属于哪个 Headless Service。
  • Headless Service 与普通 Service 的区别在于,它没有 Cluster IP,如果解析 Headless Service 的 DNS 域名,则返回的是该 Service 对应的全部 Pod 的 Endpoint 列表。
  • StatefulSet 在 Headless Service 的基础上又为 StatefulSet 控制的每个 Pod 实例创建了一个 DNS 域名,域名的格式为:
pod-name.svc-name.namespace.svc.cluster.local
  • 比如一个 3 节点的 kafka 的 StatefulSet 集群,对应的 Headless Service 的名字为 kafka,StatefulSet 的名字为 kafka,则 StatefulSet 里面的 3 个 Pod 的 DNS 名称分别为kafka-0.kafka、kafka-1.kafka、kafka-2.kafka,这些 DNS 名称可以直接在集群的配置文件中固定下来。
  • 通过headless service 可以轻松找到statefulSet 的所有节点,只要访问"kafka-0.kafka.namespace.svc.cluster.local",就会访问到kafka下的kafka-0。
  • Service DNS的方式下有两种处理方法:
  • 标准服务 Normal Service 这里访问"mypod.stsname.namespace.svc.cluster.local"的时候会得到mypod的service的IP,既VIP。
  • 无头服务 Headless Service 这里访问"mypod.stsname.namespace.svc.cluster.local"的时候会得到mypod的IP,这里可以看到区别是,Headless Service 不需要分配一个VIP,而是通过DNS访问的方式可以解析出带代理的 Pod 的 IP
  • Headleaa Service的定义方式:
apiVersion: v1
kind: Service
metadata:
  name: myapp-headless
  namespace: default
spec:
  selector:
    app: myapp
    release: dev
  clusterIP: "None"
  ports:
  - port: 80
    targetPort: 80
  • Headless Service 是一个标准的Service的YAML文件,只不过clusterIP定义为None,既,这个service没有VIP作为"头"。
  • Headless Service 以DNS会记录的方式记录所有暴露它代理的Pod。而它代理的Pod依然会采用标签选择器的机制选择。既:所有携带了 app=myapp 标签的pod。都会被service代理起来。
4、 StatefulSet 编排
  • kubectl explain sts.spec 主要字段解释:
  • replicas 副本数
  • selector 那个pod是由自己管理的
  • serviceName 必须关联到一个无头服务商
  • template 定义 pod 模板(其中定义关联那个存储卷)
  • volumeClaimTemplates 生成PVC
  • StatefulSet应用特点:
  • 唯一的网络标识 (ip 地址是变化的)
  • 域名访问(.<无头服务名>.svc.cluster.local) 如:web-0.nginx.default.svc.cluster.local
  • 独立的持久存储
  • 有序的部署和删除
5、基于NFS的PV动态供给(StorageClass)
1、简介
  • PersistentVolume(PV)是指由集群管理员配置提供的某存储系统上的段存储空间,它是对底层共享存储的抽象,将共享存储作为种可由用户申请使的资源,实现了“存储消费”机制。
  • PV 通过存储插件机制,PV支持使用多种网络存储系统或云端存储等多种后端存储系统,例如,NFS、RBD和Cinder等。
  • PV 是集群级别的资源,不属于任何名称空间,用户对PV资源的使需要通过PersistentVolumeClaim(PVC)提出的使申请(或称为声明)来完成绑定,是PV资源的消费者,它向PV申请特定大小的空间及访问模式(如rw或ro),从创建出PVC存储卷,后再由Pod资源通过PersistentVolumeClaim存储卷关联使,
  • PVC 使得用户可以以抽象的方式访问存储资源,但很多时候会涉及PV的不少属性,例如,由于不同场景时设置的性能参数等,因此集群管理员不得不通过多种方式提供多种不同的PV以满不同用户不同的使用需求,两者衔接上的偏差必然会导致用户的需求无法全部及时有效地得到满足。
  • Kubernetes从1.4版起引入了一个新的资源对象StorageClass,可用于将存储资源定义为具有显著特性的类(Class)而不是具体的PV,例如 “fast” “slow” 或 “glod” “silver” “bronze”等。
  • StorageClass 使用户通过PVC直接向意向的类别发出申请,匹配由管理员事先创建的PV,或者由其按需为用户动态创建PV,这样做甚至免去了需要先创建PV的过程。
  • PV对存储系统的支持可通过其插件来实现,目前,Kubernetes支持如下类型的插件。官方地址:https://kubernetes.io/docs/concepts/storage/storage-classes/
  • 官方插件是不支持NFS动态供给的,可以用第三方的插件来实现。
2、搭建 NFS 服务器
  • 作为测试,临时在master节点上部署NFS服务器。
#master节点安装nfs
[root@k8s-master ~]# yum -y install nfs-utils

#创建nfs目录
[root@k8s-master ~]# mkdir -p /data/volumes/

#编辑export文件,这个文件就是nfs默认的配置文件
[root@k8s-master ~]# vim /etc/exports
/data/volumes/  192.168.152.0/24(rw,no_root_squash,sync)

#配置生效
[root@k8s-master ~]# exportfs -r
#查看生效
[root@k8s-master ~]# exportfs

#启动rpcbind、nfs服务
[root@k8s-master ~]# systemctl restart rpcbind && systemctl enable rpcbind
[root@k8s-master ~]# systemctl restart nfs && systemctl enable nfs

#查看 RPC 服务的注册状况
[root@k8s-master ~]# rpcinfo -p localhost

#showmount测试
[root@k8s-master ~]# showmount -e 192.168.152.192
  • 所有node节点安装客户端,开机启动
[root@k8s-node01 ~]# yum -y install nfs-utils
[root@k8s-node01 ~]# systemctl start nfs && systemctl enable nfs
  • 作为准备工作,已经在 k8s-master节点上搭建了一个 NFS 服务器,目录为 /data/volumes/
3、安装 NFS 插件
  • GitHub地址:https://github.com/kubernetes-incubator/external-storage/tree/master/nfs/deploy/kubernetes

1、创建 nfs 的 deployment,

  • 修改相应的nfs服务器ip及挂载路径即可
[root@k8s-master storage-class]# cat deployment-nfs.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-client-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: k8s-master
            - name: NFS_PATH
              value: /data/volumes/v1
      volumes:
        - name: nfs-client-root
          nfs:
            server: k8s-master
            path: /data/volumes/v1
  • 部署deployment-nfs.yaml
[root@k8s-master storage-class]# kubectl apply -f deployment-nfs.yaml
  • 查看创建的POD
[root@k8s-master storage-class]# kubectl get pod -o wide
NAME                                      READY   STATUS             RESTARTS   AGE   IP             NODE        NOMINATED NODE   READINESS GATES
nfs-client-provisioner-75bf876d88-578lg   1/1     Running            0          51m   10.244.2.131   k8s-node2   <none>           <none>

2、创建StorageClass

[root@k8s-master storage-class]# cat storageclass-nfs.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: fuseim.pri/ifs
parameters:
  archiveOnDelete: "false"
  • 部署storageclass-nfs.yaml
[root@k8s-master storage-class]# kubectl apply -f storageclass-nfs.yaml
  • 查看创建的StorageClass
[root@k8s-master storage-class]# kubectl get sc
NAME                  PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage   fuseim.pri/ifs   Delete          Immediate           false                  15m

3、配置授权

  • 如果集群启用了RBAC,则必须执行如下命令授权provisioner
[root@k8s-master storage-class]# cat rbac.yaml
kind: ServiceAccount
apiVersion: v1
metadata:
  name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
  • 部署 rbac.yaml
[root@k8s-master storage-class]# kubectl create -f rbac.yaml

4、创建测试 PVC

[root@k8s-master storage-class]# kubectl create -f test-claim.yaml
  • 指定了其对应的storage-class的名字为managed-nfs-storage
[root@k8s-master storage-class]# cat test-claim.yaml 
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
  • 查看创建的PVC状态为Bound
[root@k8s-master storage-class]# kubectl get pvc
NAME         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
test-claim   Bound    pvc-a17d9fd5-237a-11e9-a2b5-000c291c25f3   1Mi        RWX            managed-nfs-storage   34m
  • 查看自动创建的PV
[root@k8s-master storage-class]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS          REASON   AGE
pvc-a17d9fd5-237a-11e9-a2b5-000c291c25f3   1Mi        RWX            Delete           Bound    default/test-claim   managed-nfs-storage            34m
  • 进入到NFS的export目录查看对应volume name的目录已经创建出来。
  • 其中volume的名字是namespace,PVC name以及uuid的组合:
[root@k8s-master storage-class]# ls /data/volumes/v1
total 0
drwxrwxrwx 2 root root 21 Jan 29 12:03 default-test-claim-pvc-a17d9fd5-237a-11e9-a2b5-000c291c25f3

5、创建测试Pod

  • 指定pod使用刚创建的PVC:test-claim,
  • 完成之后,如果attach到pod中执行一些文件的读写操作,就可以确定pod的/mnt已经使用了NFS的存储服务了。
[root@k8s-master storage-class]# vim test-pod.yaml 
kind: Pod
apiVersion: v1
metadata:
  name: test-pod
spec:
  containers:
  - name: test-pod
    image: busybox:1.24
    command:
      - "/bin/sh"
    args:
      - "-c"
      - "touch /mnt/SUCCESS && exit 0 || exit 1"
    volumeMounts:
      - name: nfs-pvc
        mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: test-claim
  • 执行yaml文件
[root@k8s-master storage-class]# kubectl create -f test-pod.yaml
  • 查看创建的测试POD
[root@k8s-master storage-class]# kubectl get pod -o wide
NAME                                      READY   STATUS             RESTARTS   AGE   IP             NODE        NOMINATED NODE   READINESS GATES
nfs-client-provisioner-75bf876d88-578lg   1/1     Running            0          51m   10.244.2.131   k8s-node2   <none>           <none>
test-pod                                  0/1     Completed          0          41m   10.244.1.
  • 在NFS服务器上的共享目录下的卷子目录中检查创建的NFS PV卷下是否有"SUCCESS" 文件。
[root@k8s-master storage-class]# cd /data/volumes/v1
[root@k8s-master v1]# ll
total 0
drwxrwxrwx 2 root root 21 Jan 29 12:03 default-test-claim-pvc-a17d9fd5-237a-11e9-a2b5-000c291c25f3

[root@k8s-master v1]# cd default-test-claim-pvc-a17d9fd5-237a-11e9-a2b5-000c291c25f3/
[root@k8s-master default-test-claim-pvc-a17d9fd5-237a-11e9-a2b5-000c291c25f3]# ll
total 0
-rw-r--r-- 1 root root 0 Jan 29 12:03 SUCCESS

6、清理测试环境

  • 删除测试POD
[root@k8s-master storage-class]# kubectl delete -f test-pod.yaml
  • 删除测试PVC
[root@k8s-master storage-class]# kubectl delete -f test-claim.yaml
  • 在NFS服务器上的共享目录下查看NFS的PV卷已经被删除。

7、创建一个nginx动态获取PV

[root@k8s-master storage-class]# cat nginx-demo.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "managed-nfs-storage"
      resources:
        requests:
          storage: 1Gi
  • 启动后看到以下信息:
[root@k8s-master storage-class]# kubectl get pods,pv,pvc
NAME                                          READY   STATUS      RESTARTS   AGE
pod/nfs-client-provisioner-5778d56949-ltjbt   1/1     Running     0          42m
pod/test-pod                                  0/1     Completed   0          36m
pod/web-0                                     1/1     Running     0          2m23s
pod/web-1                                     1/1     Running     0          2m6s
pod/web-2                                     1/1     Running     0          104s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS          REASON   AGE
persistentvolume/pvc-1d54bb5b-9c12-41d5-9295-3d827a20bfa2   1Gi        RWO            Delete           Bound    default/www-web-2     managed-nfs-storage            104s
persistentvolume/pvc-8cc0ed15-1458-4384-8792-5d4fd65dca66   1Gi        RWO            Delete           Bound    default/www-web-0     managed-nfs-storage            39m
persistentvolume/pvc-c924a2aa-f844-4d52-96c9-32769eb3f96f   1Mi        RWX            Delete           Bound    default/test-claim    managed-nfs-storage            38m
persistentvolume/pvc-e30333d7-4aed-4700-b381-91a5555ed59f   1Gi        RWO            Delete           Bound    default/www-web-1     managed-nfs-storage            2m6s

NAME                                STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
persistentvolumeclaim/test-claim    Bound    pvc-c924a2aa-f844-4d52-96c9-32769eb3f96f   1Mi        RWX            managed-nfs-storage   38m
persistentvolumeclaim/www-web-0     Bound    pvc-8cc0ed15-1458-4384-8792-5d4fd65dca66   1Gi        RWO            managed-nfs-storage   109m
persistentvolumeclaim/www-web-1     Bound    pvc-e30333d7-4aed-4700-b381-91a5555ed59f   1Gi        RWO            managed-nfs-storage   2m6s
persistentvolumeclaim/www-web-2     Bound    pvc-1d54bb5b-9c12-41d5-9295-3d827a20bfa2   1Gi        RWO            managed-nfs-storage   104s
  • nfs服务器上也会看到自动生成3个挂载目录,当pod删除了数据还会存在。
[root@k8s-master storage-class]# ll /data/volumes/v1/
total 4
drwxrwxrwx 2 root root  6 Aug 16 18:21 default-nginx-web-0-pvc-ea22de1c-fa33-4f82-802c-f04fe3630007
drwxrwxrwx 2 root root 21 Aug 16 18:25 default-test-claim-pvc-c924a2aa-f844-4d52-96c9-32769eb3f96f
drwxrwxrwx 2 root root  6 Aug 16 18:21 default-www-web-0-pvc-8cc0ed15-1458-4384-8792-5d4fd65dca66
drwxrwxrwx 2 root root  6 Aug 16 18:59 default-www-web-1-pvc-e30333d7-4aed-4700-b381-91a5555ed59f
drwxrwxrwx 2 root root  6 Aug 16 18:59 default-www-web-2-pvc-1d54bb5b-9c12-41d5-9295-3d827a20bfa2

16、Kubernetes 保密控制器

1、Secret 介绍
  • Secret 解决了密码、token、密钥,证书等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。Secret 可以以 Volume 或者环境变量的方式使用。
  • Secret 保存 kubernetes 中小片敏感数据资源,可以更方便的控制和如何使用涉密数据,减少暴露的风险。例如密码,token,或者秘钥。
  • Secret 有三种类型:
  • Opaque:base64 编码格式的 Secret,用来存储密码、密钥等;但数据也通过 base64 --decode 解码得到原始数据,所以加密性很弱。
  • kubernetes.io/dockerconfigjson :用来存储私有 docker registry 的认证信息。
  • kubernetes.io/service-account-token : 由ServiceAccount创建的API证书附加的秘钥,用于被 serviceaccount 引用。
  • 內建的 Secrets
  • serviceaccount 用来使得 Pod 能够访问 Kubernetes API户可以创建自己的secret,系统也会有自己的secret。
  • Kubernetes 自动生成的用来访问 apiserver 的 Secret,所有Pod会默认使用这个Secret与apiserver通信
  • 自定义 Secret
  • 使用kubectl create secret命令
  • 使用yaml文件创建Secret
  • Pod 需要先引用才能使用某个secret
  • Pod 有2种方式来使用 secret:
  • 作为volume的一个域被一个或多个容器挂载
  • 在拉取镜像的时候被kubelet引用。
2、Opaque Secret
  • opaque:英[əʊˈpeɪk] 美[oʊˈpeɪk] 模糊,不透明的
1、命令创建 Secret
1、创建 Secret
  • Opaque 类型的数据是一个 map 类型,要求 value 是 base64 编码格式:
  • Pod要访问数据库,需要用户名密码,分别存放在2个文件中:username.txt,password.txt
[root@k8s-master ~]# echo -n 'admin' > ./username.txt
[root@k8s-master ~]# echo -n '1f2d1e2e67df' > ./password.txt
 
# kubectl create secret指令将用户名密码写到secret中,并在apiserver创建Secret
[root@k8s-master ~]# kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
secret "db-user-pass" created
2、查看创建结果
[root@k8s-master ~]# kubectl get secrets
NAME                  TYPE                                  DATA      AGE
db-user-pass          Opaque                                2         51s
 
[root@k8s-master ~]# kubectl describe secrets/db-user-pass
Name:            db-user-pass
Namespace:       default
Labels:          <none>
Annotations:     <none>

Type:            Opaque

Data
====
password.txt:    12 bytes
username.txt:    5 bytes
  • get或describe指令都不会展示secret的实际内容,这是出于对数据的保护的考虑,如果想查看实际内容使用命令:
[root@k8s-master ~]# kubectl get secret db-user-pass -o json
2、yaml 方式创建 Secret
1、base64 编码加密内容
[root@k8s-master ~]# echo -n 'admin' | base64
YWRtaW4=
[root@k8s-master ~]# echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
2、yaml文件内容
[root@k8s-master ~]# vim secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm
3、创建 Secret
[root@k8s-master ~]# kubectl create -f ./secret.yaml
secret "mysecret" created
4、解析 Secret 中内容
[root@k8s-master ~]# kubectl get secret mysecret -o yaml
apiVersion: v1
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm
kind: Secret
metadata:
  creationTimestamp: 2016-01-22T18:41:56Z
  name: mysecret
  namespace: default
  resourceVersion: "164619"
  selfLink: /api/v1/namespaces/default/secrets/mysecret
  uid: cfee02d6-c137-11e5-8d73-42010af00002
type: Opaque

# base64解码:
[root@k8s-master ~]# echo 'MWYyZDFlMmU2N2Rm' | base64 --decode
1f2d1e2e67df
3、Opaque Secret 的使用
  • 创建好 secret 之后,有两种方式来使用它:
  • 以 Volume 方式
  • 以环境变量方式
1、Secret 挂载到 Volume 中
  • 多个Pod可以引用同一个Secret
  • 修改Pod的定义,在spec.volumes[]加一个volume,给这个volume起个名字,spec.volumes[].secret.secretName记录的是要引用的Secret名字
    在每个需要使用Secret的容器中添加一项spec.containers[].volumeMounts[],指定spec.containers[].volumeMounts[].readOnly = true,spec.containers[].volumeMounts[].mountPath要指向一个未被使用的系统路径。
    修改镜像或者命令行使系统可以找到上一步指定的路径。此时Secret中data字段的每一个key都是指定路径下面的一个文件名
[root@k8s-master ~]# vim pod_use_secret.yaml
apiVersion: v1 
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
  • 每一个被引用的Secret都要在spec.volumes中定义
  • 如果Pod中的多个容器都要引用这个Secret那么每一个容器定义中都要指定自己的volumeMounts,但是Pod定义中声明一次spec.volumes就好。
2、从volume中读取secret的值
  • 以文件的形式挂载到容器中的secret,他们的值已经是经过base64解码的了,可以直接读出来使用。
root@mypod:/data#  ls /etc/foo/
username
password
root@mypod:/data#  cat /etc/foo/username
admin
root@mypod:/data#  cat /etc/foo/password
1f2d1e2e67df
  • 被挂载的secret内容自动更新
  • 如果修改一个Secret的内容,那么挂载了该Secret的容器中也将会取到更新后的值,但是这个时间间隔是由kubelet的同步时间决定的。最长的时间将是一个同步周期加上缓存生命周期(period+ttl)
  • 特例:以subPath(https://kubernetes.io/docs/concepts/storage/volumes/#using-subpath)形式挂载到容器中的secret将不会自动更新
3、映射secret key到指定的路径
  • 可以控制secret key被映射到容器内的路径,利用spec.volumes[].secret.items来修改被映射的具体路径
[root@k8s-master ~]# vim pod2_use_secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
      - key: password
        path: my-group/my-password
  • username被映射到了文件/etc/foo/my-group/my-username而不是/etc/foo/username
  • 而password没有被使用,这种方式每个key的调用需要单独用key像username一样调用
[root@k8s-master ~]# kubectl exec -it mypod -- /bin/bash
root@mypod:/data# ls /etc/foo/
my-group
root@mypod:/etc/foo# cd my-group
root@mypod:/etc/foo/my-group# ls
my-username
root@mypod:/etc/foo/my-group# cat my-usernam
admin
3、Secret 文件权限
  • 可以指定secret文件的权限,类似linux系统文件权限,如果不指定默认权限是0644,等同于linux文件的-rw-r–r--权限
  • 可以为整个 Secret 卷指定默认模式;如果需要,可以为每个密钥设定重载值
  • 指定整个 Secret 卷权限
[root@k8s-master ~]# vim pod3_use_secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      defaultMode: 0400
  • 将secret挂载到容器的/etc/foo路径,每一个key衍生出的文件,权限位都将是0400
  • 十进制数256表示0400,511表示 0777
  • JSON 规范不支持八进制符号,因此使用 256 值作为 0400 权限。 如果使用 YAML 而不是 JSON,则可以使用八进制符号以更自然的方式指定权限
  • 单独指定某个key的权限
[root@k8s-master ~]# vim pod4_use_secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
  volumes:
  - name: foo
    secret:
      secretName: mysecret
      items:
      - key: username
        path: my-group/my-username
        mode: 0600
      - key: password
        path: my-group/my-password
        mode: 0600
5、以环境变量的形式使用 Secret
  • 创建一个Secret,多个Pod可以引用同一个Secret
  • 修改pod的定义,定义环境变量并使用env[].valueFrom.secretKeyRef指定secret和相应的key
  • 修改镜像或命令行,让它们可以读到环境变量
[root@k8s-master ~]# vim pod5_use_secret.yaml
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never
  • 容器中读取环境变量,已经是base64解码后的值
[root@k8s-master secrets]# kubectl exec -it secret-env-pod -- /bin/bash
root@secret-env-pod:/data# echo $SECRET_USERNAME
admin
root@secret-env-pod:/data# echo $SECRET_PASSWORD
1f2d1e2e67df
3、仓库认证 Secret
  • kubernetes.io/dockerconfigjson
  • 可以直接用 kubectl 命令来创建用于 docker registry 认证的 secret
[root@k8s-master ~]# kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL 
secret "myregistrykey" created.
  • 查看 secret 的内容:
[root@k8s-master ~]# kubectl get secret myregistrykey -o yaml
apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyJET0NLRVJfUkVHSVNUUllfU0VSVkVSIjp7InVzZXJuYW1lIjoiRE9DS0VSX1VTRVIiLCJwYXNzd29yZCI6IkRPQ0tFUl9QQVNTV09SRCIsImVtYWlsIjoiRE9DS0VSX0VNQUlMIiwiYXV0aCI6IlJFOURTMFZTWDFWVFJWSTZSRTlEUzBWU1gxQkJVMU5YVDFKRSJ9fX0=
kind: Secret
metadata:
  creationTimestamp: "2020-08-13T23:22:33Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:.dockerconfigjson: {}
      f:type: {}
    manager: kubectl
    operation: Update
    time: "2020-08-13T23:22:33Z"
  name: myregistrykey
  namespace: default
  resourceVersion: "717559"
  selfLink: /api/v1/namespaces/default/secrets/myregistrykey
  uid: 08d04be4-57c2-40a8-ba04-e1ad7490bb64
type: kubernetes.io/dockerconfigjson
  • 通过 base64 对 secret 中的内容解码:
[root@k8s-master ~]# echo "eyJjY3IuY2NzLnRlbmNlbnR5dW4uY29tL3RlbmNlbnR5dW4iOnsidXNlcm5hbWUiOiIzMzIxMzM3OTk0IiwicGFzc3dvcmQiOiIxMjM0NTYuY29tIiwiZW1haWwiOiIzMzIxMzM3OTk0QHFxLmNvbSIsImF1dGgiOiJNek15TVRNek56azVORG94TWpNME5UWXVZMjl0XXXX" | base64 --decode
{"ccr.ccs.tencentyun.com/XXXXXXX":{"username":"3321337XXX","password":"123456.com","email":"3321337XXX@qq.com","auth":"MzMyMTMzNzk5NDoxMjM0NTYuY29t"}}
  • 也可以直接读取 ~/.dockercfg 的内容来创建:
[root@k8s-master ~]# kubectl create secret docker-registry myregistrykey --from-file="~/.dockercfg"
  • 在创建 Pod 的时候,通过 imagePullSecrets 来引用刚创建的 myregistrykey :
apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
  - name: foo
    image: janedoe/awesomeapp:v1
  imagePullSecrets:
  - name: myregistrykey
4、Serviceaccount 默认 Secret
  • kubernetes.io/service-account-token
  • kubernetes.io/service-account-token : 用于被 serviceaccount 引用。
  • serviceaccout 创建时 Kubernetes 会默认创建对应的 secret。Pod 如果使用了serviceaccount,对应的 secret 会自动挂载到 Pod 的 /run/secrets/kubernetes.io/serviceaccount 目录中。
[root@k8s-master ~]# kubectl create deployment nginx --image=nginx
deployment.apps/nginx created

[root@k8s-master ~]# kubectl get deployments.apps nginx
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   1/1     1            1           28s

[root@k8s-master ~]# kubectl get pods
NAME                         READY   STATUS    RESTARTS   AGE
nginx-f89759699-c4rfp        1/1     Running   0          11s

[root@k8s-master secrets]# kubectl exec nginx-f89759699-c4rfp -- ls /run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token
5、Secret 限制
  • 需要被挂载到Pod中的secret需要提前创建,否则会导致Pod创建失败
  • secret是有命名空间属性的,只有在相同namespace的Pod才能引用它
  • 单个Secret容量限制的1Mb,这么做是为了防止创建超大的Secret导致apiserver或kubelet的内存耗尽。但是创建过多的小容量secret同样也会耗尽内存,这个问题在将来可能会有方案解决
  • kubelet只支持由API server创建出来的Pod中引用secret,使用特殊方式创建出来的Pod是不支持引用secret的,比如通过kubelet的–manifest-url参数创建的pod,或者–config参数创建的,或者REST API创建的。
  • 通过secretKeyRef引用一个不存在你secret key会导致pod创建失败
1、Pod中的ssh keys
  • 创建一个包含ssh keys的secret
[root@k8s-master ~]# kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
  • 创建一个Pod,其中的容器可以用volume的形式使用ssh keys
kind: Pod
apiVersion: v1
metadata:
  name: secret-test-pod
  labels:
    name: secret-test
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: ssh-key-secret
  containers:
  - name: ssh-test-container
    image: mySshImage
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"
2、Pod 中区分生产和测试证书
  • 创建2种不同的证书,分别用在生产和测试环境
[root@k8s-master ~]# kubectl create secret generic prod-db-secret --from-literal=username=produser --from-literal=password=Y4nys7f11
secret "prod-db-secret" created
[root@k8s-master ~]# kubectl create secret generic test-db-secret --from-literal=username=testuser --from-literal=password=iluvtests
secret "test-db-secret" created
  • 再创建2个不同的Pod
apiVersion: v1
kind: List
items:
- kind: Pod
  apiVersion: v1
  metadata:
    name: prod-db-client-pod
    labels:
      name: prod-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: prod-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
- kind: Pod
  apiVersion: v1
  metadata:
    name: test-db-client-pod
    labels:
      name: test-db-client
  spec:
    volumes:
    - name: secret-volume
      secret:
        secretName: test-db-secret
    containers:
    - name: db-client-container
      image: myClientImage
      volumeMounts:
      - name: secret-volume
        readOnly: true
        mountPath: "/etc/secret-volume"
  • 两个容器中都会有下列的文件
/etc/secret-volume/username
/etc/secret-volume/password
  • 以“.”开头的key可以产生隐藏文件
kind: Secret
apiVersion: v1
metadata:
  name: dotfile-secret
data:
  .secret-file: dmFsdWUtMg0KDQo=
---
kind: Pod
apiVersion: v1
metadata:
  name: secret-dotfiles-pod
spec:
  volumes:
  - name: secret-volume
    secret:
      secretName: dotfile-secret
  containers:
  - name: dotfile-test-container
    image: k8s.gcr.io/busybox
    command:
    - ls
    - "-l"
    - "/etc/secret-volume"
    volumeMounts:
    - name: secret-volume
      readOnly: true
      mountPath: "/etc/secret-volume"
  • 会在挂载目录下产生一个隐藏文件,/etc/secret-volume/.secret-file
6、Secret 实验
  • Secret:作用是帮你把 Pod 想要访问的加密数据,存放到 Etcd 中。然后,就可以通过在 Pod 的容器里挂载 Volume 的方式,访问到这些 Secret 里保存的信息了。
  • Secret 典型的使用场景:
1、存放数据库的 Credential 信息
[root@k8s-master ~]# cat test-projected-volume.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume 
spec:
  containers:
  - name: test-secret-volume
    image: busybox
    args:
    - sleep
    - "86400"
    volumeMounts:
    - name: mysql-cred
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: mysql-cred
    projected:
      sources:
      - secret:
          name: user
      - secret:
          name: pass
  • 定义了一个容器,它声明挂载的 Volume是 projected 类型 , 并不是常见的 emptyDir 或者 hostPath 类型,
  • 而这个 Volume 的数据来源(sources),则是名为 user 和 pass 的 Secret 对象,分别对应的是数据库的用户名和密码。
  • 这里用到的数据库的用户名、密码,正是以 Secret 对象的方式交给 Kubernetes 保存的。
  • 方法1. 使用 kubectl create secret 指令创建Secret对象
[root@k8s-master ~]# cat ./username.txt
admin
[root@k8s-master ~]# cat ./password.txt
c1oudc0w!

[root@k8s-master ~]# kubectl create secret generic user --from-file=./username.txt
[root@k8s-master ~]# kubectl create secret generic pass --from-file=./password.txt
# username.txt 和 password.txt 文件里,存放的就是用户名和密码;而 user 和 pass,则是为 Secret 对象指定的名字。

# 查看Secret 对象:
[root@k8s-master ~]# kubectl get secrets
NAME           TYPE                                DATA      AGE
user          Opaque                                1         51s
pass          Opaque                                1         51s
  • 方法2. 通过编写 YAML 文件的方式来创建这个 Secret 对象
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  user: YWRtaW4=
  pass: MWYyZDFlMmU2N2Rm
  • Secret 对象要求这些数据必须是经过 Base64 转码的,以免出现明文密码的安全隐患
  • 转码操作
[root@k8s-master ~]# echo -n 'admin' | base64
YWRtaW4=
[root@k8s-master ~]# echo -n '1f2d1e2e67df' | base64
MWYyZDFlMmU2N2Rm
  • 注意:像这样创建的 Secret 对象,它里面的内容仅仅是经过了转码,并没有被加密。生产环境中,需要在 Kubernetes 中开启 Secret 的加密插件,增强数据的安全性。
  • 用yaml方式创建的secret调用方法如下:
[root@k8s-master ~]# cat test-projected-volume.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: test-projected-volume1 
spec:
  containers:
  - name: test-secret-volume1
    image: busybox
    args:
    - sleep
    - "86400"
    volumeMounts:
    - name: mysql-cred
      mountPath: "/projected-volume"
      readOnly: true
  volumes:
  - name: mysql-cred
    secret:
      secretName: mysecret
  • 创建这个 Pod
[root@k8s-master ~]# kubectl create -f test-projected-volume.yaml
  • 验证这些 Secret 对象是不是已经在容器里了
[root@k8s-master ~]# kubectl exec -it test-projected-volume -- /bin/sh
/ # ls /projected-volume/
user
pass
/ # cat /projected-volume/user
root
/ # cat /projected-volume/pass
1f2d1e2e67df
  • 结果中看到,保存在 Etcd 里的用户名和密码信息,已经以文件的形式出现在了容器的 Volume 目录里。
  • 而这个文件的名字,就是 kubectl create secret 指定的 Key,或者说是 Secret 对象的 data 字段指定的 Key。
  • 注意:报错:上面这条命令会报错如下
[root@k8s-master ~]# kubectl exec -it test-projected-volume /bin/sh 
error: unable to upgrade connection: Forbidden (user=system:anonymous, verb=create, resource=nodes, subresource=proxy)
  • 解决:绑定一个cluster-admin的权限
[root@k8s-master ~]# kubectl create clusterrolebinding system:anonymous   --clusterrole=cluster-admin   --user=system:anonymous
clusterrolebinding.rbac.authorization.k8s.io/system:anonymous created
2、同步更新
  • 通过挂载方式进入到容器里的 Secret,一旦其对应的 Etcd 里的数据被更新,这些 Volume 里的文件内容,同样也会被更新,kubelet 组件在定时维护这些 Volume。
1、生成新的密码数据
[root@k8s-master ~]# echo -n '111111' | base64
MTExMTEx
2、修改数据
[root@k8s-master ~]# cat mysecret.yaml 
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  user: YWRtaW4=
  pass: MTExMTEx
3、更新数据:
[root@k8s-master ~]# kubectl apply -f mysecret.yaml 
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
secret/mysecret configured
4、查看对应pod里的数据是否更新
[root@k8s-master ~]# kubectl exec -it test-projected-volume1 /bin/sh
/ # cat projected-volume/pass 
111111/ #
  • 注:这个更新可能会有一定的延时。所以在编写应用程序时,在发起数据库连接的代码处写好重试和超时的逻辑,绝对是个好习惯。
5、查看secret具体的值
[root@k8s-master ~]# kubectl get secret mysecret -o json
{
    "apiVersion": "v1",
    "data": {
        "pass": "MTExMTEx",
        "user": "YWRtaW4="
    },
    "kind": "Secret",
    "metadata": {
        "annotations": {
            "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"pass\":\"MTExMTEx\",\"user\":\"YWRtaW4=\"},\"kind\":\"Secret\",\"metadata\":{\"annotations\":{},\"name\":\"mysecret\",\"namespace\":\"default\"},\"type\":\"Opaque\"}\n"
        },
        "creationTimestamp": "2019-01-21T07:31:05Z",
        "name": "mysecret",
        "namespace": "default",
        "resourceVersion": "125857",
        "selfLink": "/api/v1/namespaces/default/secrets/mysecret",
        "uid": "82e20780-1d4e-11e9-baa8-000c29f01606"
    },
    "type": "Opaque"
}

17、Kubernetes 访问控制

1、Kubernetes 访问控制介绍
  • Kubernetes的授权是基于插件形式的,常用的授权插件有以下几种:
  • Node:node节点授权
  • ABAC:基于属性的访问控制,K8s集群中的访问策略只能跟用户直接关联
  • RBAC:基于角色的访问控制,访问策略可以跟某个角色关联,具体的用户在跟一个或多个角色相关联
  • Webhook:自定义http回调方法
  • K8s 在 1.3 版本中发布了alpha版的基于角色的访问控制 RBAC (Role-based Access Control)的授权模式。相对于基于属性的访问控制 ABAC(Attribute-based Access Control),RBAC主要是引入了角色(Role)和角色绑定(RoleBinding)的抽象概念。
  • RBAC使用rbac.authorization.k8s.io API Group 来实现授权决策,允许管理员通过 Kubernetes API 动态配置策略,要启用RBAC,需要在 apiserver 中添加参数–authorization-mode=RBAC,如果使用的kubeadm安装的集群,1.6 版本以上的都默认开启了RBAC,可以通过查看 Master 节点上 apiserver 的静态Pod定义文件:
[root@k8s-master rbac]# cat /etc/kubernetes/manifests/kube-apiserver.yaml
...
    - --authorization-mode=Node,RBAC
...
  • 如果是二进制的方式搭建的集群,添加这个参数过后,记得要重启 apiserver 服务。
2、RBAC API 对象
  • Kubernetes有一个很基本的特性就是它的所有资源对象都是模型化的 API 对象,允许执行 CRUD(Create、Read、Update、Delete)操作(也就是我们常说的增、删、改、查操作),比如下面的这下资源:
  • Pods
  • ConfigMaps
  • Deployments
  • Nodes
  • Secrets
  • Namespaces
  • 可对以上资源对象的存在的操作有:
  • create
  • get
  • delete
  • list
  • update
  • edit
  • watch
  • exec
  • 在更上层,这些资源和 API Group 进行关联,比如Pods属于 Core API Group,而Deployements属于 apps API Group,要在Kubernetes中进行RBAC的管理,除了上面的这些资源和操作以外,还需要另外的一些对象:
  • Rule:规则,规则是一组属于不同 API Group 资源上的一组操作的集合
  • Role 和 ClusterRole:角色和集群角色,这两个对象都包含上面的 Rules 元素,二者的区别在于,在 Role 中,定义的规则只适用于单个命名空间,也就是和 namespace 关联的,而 ClusterRole 是集群范围内的,因此定义的规则不受命名空间的约束。另外 Role 和 ClusterRole 在Kubernetes中都被定义为集群内部的 API 资源,和我们前面学习过的 Pod、ConfigMap 这些类似,都是集群的资源对象,所以同样的可以使用前面的kubectl相关的命令来进行操作
  • Subject:主体,对应在集群中尝试操作的对象,集群中定义了3种类型的主题资源:
  • User Account:用户,这是有外部独立服务进行管理的,管理员进行私钥的分配,用户可以使用 KeyStone或者 Goolge 帐号,甚至一个用户名和密码的文件列表也可以。对于用户的管理集群内部没有一个关联的资源对象,所以用户不能通过集群内部的 API 来进行管理
  • Group:组,这是用来关联多个账户的,集群中有一些默认创建的组,比如cluster-admin
  • Service Account:服务帐号,通过Kubernetes API 来管理的一些用户帐号,和 namespace 进行关联的,适用于集群内部运行的应用程序,需要通过 API 来完成权限认证,所以在集群内部进行权限操作,都需要使用到 ServiceAccount,这也是这节课的重点
  • RoleBinding 和 ClusterRoleBinding:角色绑定和集群角色绑定,简单来说就是把声明的 Subject 和我们的 Role 进行绑定的过程(给某个用户绑定上操作的权限),二者的区别也是作用范围的区别:RoleBinding 只会影响到当前 namespace 下面的资源操作权限,而 ClusterRoleBinding 会影响到所有的 namespace。
  • 基于角色的访问控制:先让一个用户(Users)扮演一个角色(Role),让角色(Role)拥有权限,从而让用户拥有这样的权限,然后在授权机制当中,只需要将权限授予某个角色,此时用户将获取对应角色的权限,从而实现角色的访问控制;
  • Role和ClusterRole
  • Role是一系列的权限的集合,例如一个Role可以包含读取 Pod 的权限和列出 Pod 的权限, ClusterRole 跟 Role 类似,但是可以在集群中全局使用。
  • Role只能授予单个namespace 中资源的访问权限。
  • ClusterRole授权 >= Role授予(与Role类似),但ClusterRole属于集群级别对象:
  • 集群范围(cluster-scoped)的资源访问控制(如:节点访问权限)
  • 非资源类型(如“/ healthz”)
  • 所有namespaces 中的namespaced 资源(如 pod)
  • RoleBinding和ClusterRoleBinding
  • RoleBinding是将Role中定义的权限授予给用户或用户组。它包含一个subjects列表(users,groups ,service accounts),并引用该Role,Role有了权限,用户也就有了权限。RoleBinding在某个namespace 内授权,ClusterRoleBinding适用在集群范围内使用。
  • RoleBinding可以引用相同namespace下定义的Role。
  • Role和ClusterRole、RoleBinding和ClusterRoleBinding关系如下图:
  • 使用RoleBinding去绑定ClusterRole:
  • 如果有10个名称空间,每个名称空间都需要一个管理员,而这些管理员的权限都是一致的。那么此时需要去定义这样的管理员,使用RoleBinding就需要创建10个Role,这样显得很麻烦。为此当使用RoleBinding去绑定一个ClusterRole时,该User仅仅拥有对当前名称空间的集群操作权限,而不是拥有所有名称空间的权限,所以此时只需要创建一个ClusterRole代替掉10个Role就解决了以上的需求。
3、RBAC 访问控制的优势。
  • 对集群中的资源和非资源权限均有完整的覆盖。
  • 整个RBAC完全由 几个API对象完成,同其他API对象一样 ,可以用kubectl或API进行操作。
  • 可以在运行时进行调整,无须重新启动 API Server。
  • 要使用 RBAC 授权模式 ,则 需要在 API Server 的启动 参数中 加上–authorization-mode=RBAC。
4、角色( Role)
  • 一个角色就是一组权限的集合,这里的权限都是许可形式的,不存在拒绝的规则。
  • 在一个命名空间中,可以用角色来定义一个角色,如果是集群级别的,就需要使用ClusterRole了。
  • 角色只能对命名空间内的资源进行授权,下面例子中定义的角色具备读取Pod的权限:
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]    # ""空字符串,表示核心API群
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
  • rules 中的参数说明
  • apiGroups: 支持的API组列表,例如 “apiVersion: batch/v1”、“apiVersion: extensions:v1beta1”、“apiVersion:apps/v1beta1” 等。
  • resources: 支持的资源对象列表,例如 pods、 deployments、 jobs等。
  • verbs: 对资源对象 的操作方法列表 , 例如 get、 watch、 list、 delete、 replace、 patch 等
5、集群角色(ClusterRole)
  • 集群角色除了具有和角色一致的命名空间内资源的管理能力,因其集群级别的生效范围,还可以用于以下特殊元素的授权管理上:
  • 集群范围的资源,如 Node。
  • 非资源型的路径,如 “/healthz”。
  • 包含全部命名空间的资源,例如 pods(用于kubectl get pods --all-namespaces这样的操作授权)
  • 下面的集群角色可以让用户有权访问任意一个或所有命名空间的 secrets(视其绑定方式而定):
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: ClusterRole01
  # ClusterRole不受限于命名空间,所以省略了namespace name的定义
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]
6、角色绑定(RoleBinding)和 集群角色绑定(ClusterRoleBinding)
  • 角色绑定或集群角色绑定用来把一个角色绑定到一个目标上,绑定目标可以是User(用户)、Group(组)或者Service Account。使用RoleBinding可以为某个命名空间授权,使用 ClusterRoleBinding可以为集群范围内授权。
  • RoleBinding 可以引用 Role 进行授权。下例中的 RoleBinding 将在 default 命名空间中把 pod-reader 角色授予用户 jane,这一操作让 jane 可以读取 default命名空间中的 Pod:
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
    name: jane
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
1、RoleBinding 也可以引用 ClusterRole进行授权
  • RoleBinding 可以引用 ClusterRole,对属于同一命名空间内 ClusterRole 定义的资源主体进行授权。一种很常用的做法就是,集群管理员为集群范围预定义好一组角色(ClusterRole),然后在多个命名空间中重复使用这些ClusterRole。这样可以大幅提高授权管理工作效率,也使得各个命名空间下的基础性授权规则与使用体验保持一致。
  • 例如下面,虽然 secret-reader 是一个集群角色,但是因为使用了RoleBinding,所以 dave 只能读取 development 命名空间中的 secret。
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: read-secrets
  namespace: development   # 集群角色中,只有在development命名空间中的权限才能赋予dave
subjects:
- kind: User
  name: dave
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
  • ClusterRoleBinding,集群角色绑定中的角色只能是集群角色。用于进行集群级别或者对所有命名空间都生效的授权。
  • 下面的例子中允许 manager 组的用户读取任意 namespace 中的 secret:
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name: manager
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: ClusterRole01
  apiGroup: rbac.authorization.k8s.io
  • 下图展示了上述对 Pod 的 get/watch/list 操作进行授权的 Role 和 RoleBinding 逻辑关系。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RN2wFKXo-1610418329805)(assets/dr5oXFvj9ieAZJYkAADJ69-Gdcw245.jpg)]

2、对资源的引用方式
  • 多数资源可以用其名称的字符串来表达,也就是Endpoint中的URL相对路径,例如pods。然而,某些k8s API包含下级资源,例如Pod的日志(logs)。Pod日志的Endpoint是GET/api/v1/namespaces/{namespace}/pods/{name}/log。
  • 在这个例子中,Pod 是一个命名空间内的资源,log 就是一个下级资源。要在 RBAC 角色中体现,则需要用斜线“/”来分隔资源和下级资源。
    若想授权让某个主体同时能够读取 Pod 和 Pod log,则可以配置 resources 为一个数组:
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  namespace: default
  name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list"]
  • 资源还可以通过名字(ResourceName)进行引用(这里指的是资源实例的名子)。在指定ResourceName后,使用get、delete、update和patch动词的请求,就会被限制在这个资源实例的范围内。
  • 例如下面的声明让一个主体只能对一个configmap进行get和update操作:
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  namespace: default
  name: configmap-updater
rules:
- apiGroups: [""]
  resources: ["configmap"]
  resourceNames: ["my-configmap"]
  verbs: ["update", "get"]
  • 可想而知,resourceName这种用法对list、watch、create或deletecollection操作是无效的。这是因为必须要通过URL进行鉴权,而资源名称在list、watch、create或deletecollection请求中只是请求Body数据的一部分。
7、创建一个只能访问某个 namespace 的用户
  • 创建一个 User Account,只能访问 kube-system 这个命名空间:
  • username: qfedu
  • group: qfeducloud
1、创建用户凭证
  • Kubernetes 没有 User Account 的 API 对象,利用管理员分配给你的一个私钥就可以创建了,可以参考官方文档中的方法,使用OpenSSL证书来创建一个 User,也可以使用更简单的cfssl工具来创建:
  • 给用户 qfedu 创建一个私钥,命名成:qfedu.key:
[root@k8s-master rbac]# openssl genrsa -out qfedu.key 2048
  • 使用创建的私钥创建一个证书签名请求文件:qfedu.csr,要注意需要确保在-subj参数中指定用户名和组(CN表示用户名,O表示组):
[root@k8s-master rbac]# openssl req -new -key qfedu.key -out qfedu.csr -subj "/CN=qfedu/O=qfeducloud"
  • 找到Kubernetes集群的CA,使用的是kubeadm安装的集群,CA相关证书位于/etc/kubernetes/pki/目录下面,如果是二进制方式搭建的,应该在最开始搭建集群的时候就已经指定好了CA的目录,利用该目录下面的ca.crt和ca.key两个文件来批准上面的证书请求
  • 生成最终的证书文件,设置证书的有效期为500天:
[root@k8s-master rbac]# openssl x509 -req -in qfedu.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out qfedu.crt -days 500
  • 查看当前文件夹下生成的证书文件
[root@k8s-master rbac]# ls
qfedu.csr qfedu.key qfedu.crt
  • 使用创建的证书文件和私钥文件在集群中创建新的凭证和上下文(Context):
[root@k8s-master rbac]# kubectl config set-credentials qfedu --client-certificate=qfedu.crt  --client-key=qfedu.key
  • 看到一个用户qfedu创建了,然后为这个用户设置新的 Context:
[root@k8s-master rbac]# kubectl config set-context qfedu-context --cluster=kubernetes --namespace=kube-system --user=qfedu
  • 用户qfedu创建成功,使用当前配置文件来操作kubectl命令的时候,应该会出现错误,因为还没有为该用户定义任何操作的权限:
[root@k8s-master rbac]# kubectl get pods --context=qfedu-context
Error from server (Forbidden): pods is forbidden: User "qfedu" cannot list pods in the namespace "default"
2、创建角色
  • 用户创建完成后,需要给该用户添加操作权限,定义一个YAML文件用来创建一个允许用户操作 Deployment、Pod、ReplicaSets 的角色。
[root@k8s-master rbac]# vim qfedu-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: qfedu-role
  namespace: kube-system
rules:
- apiGroups: ["", "extensions", "apps"]
  resources: ["deployments", "replicasets", "pods"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]  # 也可以使用['*']
  • Pod 属于 core 这个 API Group,在YAML中用空字符就可以,
  • Deployment ,ReplicaSets属于 apps 这个 API Group
  • rules 下面的 apiGroups 就综合了这几个资源的 API Group:["", “extensions”, “apps”]
  • verbs 就是对这些资源对象执行的操作,这里需要所有的操作方法,所以也可以使用[’*’]来代替。
  • 然后创建这个Role:
[root@k8s-master rbac]# kubectl apply -f qfedu-role.yaml
  • 注意这里没有使用上面的qfedu-context这个上下文了,因为木有权限啦
3、创建角色权限绑定
  • Role 创建完成了,但是这个 Role 和用户 qfedu 还没有任何关系,这里就需要创建一个RoleBinding对象,在 kube-system 这个命名空间下面将上面的 qfedu-role 角色和用户 qfedu 进行绑定:()
[root@k8s-master rbac]# vim qfedu-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: qfedu-rolebinding
  namespace: kube-system
subjects:
- kind: User
  name: qfedu
  apiGroup: ""
roleRef:
  kind: Role
  name: qfedu-role
  apiGroup: ""
  • 上面的YAML文件中subjects关键字,这里就是上面提到的用来尝试操作集群的对象,这里对应上面的 User 帐号 qfedu,使用kubectl创建上面的资源对象:
[root@k8s-master rbac]# kubectl create -f qfedu-rolebinding.yaml
4、测试
  • 现在应该可以使用上面的qfedu-context上下文来操作集群了:
[root@k8s-master rbac]# kubectl get pods --context=qfedu-context
NAME                                       READY   STATUS    RESTARTS   AGE
calico-kube-controllers-578894d4cd-96kn4   1/1     Running   0          11h
calico-node-8ffrw                          1/1     Running   0          11h
calico-node-bt4lv                          1/1     Running   0          11h
calico-node-vwqpx                          1/1     Running   0          11h
canal-2hp4t                                2/2     Running   0          11h
canal-79vfq                                2/2     Running   0          11h
canal-dstjf                                2/2     Running   0          11h
coredns-66bff467f8-6nj98                   1/1     Running   0          13d
coredns-66bff467f8-gfncf                   1/1     Running   0          13d
etcd-k8s-master                            1/1     Running   0          13d
kube-apiserver-k8s-master                  1/1     Running   0          13d
kube-controller-manager-k8s-master         1/1     Running   0          12d
kube-proxy-bt5wj                           1/1     Running   0          13d
kube-proxy-f5cr2                           1/1     Running   1          13d
kube-proxy-wn67g                           1/1     Running   0          13d
kube-scheduler-k8s-master                  1/1     Running   0          12d
metrics-server-96d889b76-zkwx6             1/1     Running   0          12d
  • 使用kubectl并没有指定 namespace ,因为已经为该用户分配了权限了,如果在后面加上一个-n default试看看呢?
[root@k8s-master rbac]# kubectl --context=qfedu-context get pods --namespace=default
Error from server (Forbidden): pods is forbidden: User "qfedu" cannot list pods in the namespace "default"
  • 是符合预期的吧?因为该用户并没有 default 这个命名空间的操作权限
8、创建一个只能访问某个 namespace 的 ServiceAccount
  • 上面创建了一个只能访问某个命名空间下面的普通用户,前面也提到过 subjects 下面还有类型的主题资源:ServiceAccount,创建一个集群内部的用户只能操作 kube-system 这个命名空间下面的 pods 和 deployments,首先来创建一个 ServiceAccount 对象
[root@k8s-master rbac]# kubectl create serviceaccount qfedu-sa -n kube-system
  • 也可以定义成YAML文件的形式来创建。
  • 新建一个 Role 对象
[root@k8s-master rbac]# vim qfedu-sa-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: qfedu-sa-role
  namespace: kube-system
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  • 这里定义的角色没有创建、删除、更新 Pod 的权限,待会可以重点测试一下,创建该 Role 对象:
[root@k8s-master rbac]# kubectl create -f qfedu-sa-role.yaml
  • 创建一个 RoleBinding 对象,将上面的 qfedu-sa 和角色 qfedu-sa-role 进行绑定
[root@k8s-master rbac]# vim qfedu-sa-rolebinding.yaml
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: qfedu-sa-rolebinding
  namespace: kube-system
subjects:
- kind: ServiceAccount
  name: qfedu-sa
  namespace: kube-system
roleRef:
  kind: Role
  name: qfedu-sa-role
  apiGroup: rbac.authorization.k8s.io
  • 添加这个资源对象:
[root@k8s-master rbac]# kubectl create -f qfedu-sa-rolebinding.yaml
  • 怎么去验证这个 ServiceAccount 呢?Secret 中提到过一个 ServiceAccount 会生成一个 Secret 对象和它进行映射,这个 Secret 里面包含一个 token,可以利用这个 token 去登录 Dashboard,然后就可以在 Dashboard 中来验证我们的功能是否符合预期了:
[root@k8s-master rbac]# kubectl get secret -n kube-system |grep qfedu-sa
qfedu-sa-token-kts4n                             kubernetes.io/service-account-token   3      5m32s
[root@k8s-master rbac]# kubectl describe secrets qfedu-sa-token-kts4n -n kube-system
Name:         qfedu-sa-token-kts4n
Namespace:    kube-system
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: qfedu-sa
              kubernetes.io/service-account.uid: 41a0ba23-445b-4140-94cc-587d175f2945

Type:  kubernetes.io/service-account-token

Data
====
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IlZZaGtacVFOLUtrQ3JxSUpQZDBWdG5zNkNJT3M3U25ocmhFVW1XTGZVc1UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJxZmVkdS1zYS10b2tlbi1rdHM0biIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJxZmVkdS1zYSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQxYTBiYTIzLTQ0NWItNDE0MC05NGNjLTU4N2QxNzVmMjk0NSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTpxZmVkdS1zYSJ9.wjCCrzr9kxna-C6QruMWHzyf-cOVhqnKccztOzWJIGzV92md7bI7PKAxseu5QKMzNh-0SrJD4Z_ejX7AVQRKmyEhWJb2MPoVoDM-XedQY-0krc6jaK66MNhOtUptsjHA-lUIf64LcIdb41voczqVN3S-F8PwYyQlO21l7G4XvUej5Y6tBJSO003tUL_Qr-BWowH3Mhle7a5qWDeV5J9k2Spr-0qk4wUhMykQ9sGZID3AAw8Aa8M3nxJS26Lj_lKxWX6v5s055-bW5NQjrp2l3Cnaaafa6UNHd26kPDvVMcfLEP_N30jxLbm9Ok6Znaam9iVrSghZ1sIOQrQrytlm1A
ca.crt:     1025 bytes
namespace:  11 bytes
# 会生成一串很长的base64后的字符串
  • 使用 token 去 Dashboard 页面进行登录:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZzLgtR77-1610418329806)(assets/360截图17290429104140144.png)]

  • 可以看到上面的提示信息,这是因为登录进来后默认跳转到 default 命名空间,切换到 kube-system 命名空间下面就可以了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y8UXrxvM-1610418329807)(assets/360截图17620509297085.png)]

  • 可以看到可以访问pod列表了,但是也会有一些其他额外的提示:events is forbidden: User “system:serviceaccount:kube-system:qfedu-sa” cannot list events in the namespace “kube-system”,这是因为当前登录用只被授权了访问 pod 和 deployment 的权限,同样的,访问下deployment看看可以了吗?
  • 同样的可以根据自己的需求来对访问用户的权限进行限制,可以自己通过 Role 定义更加细粒度的权限,也可以使用系统内置的一些权限……
9、创建一个可以访问所有 namespace 的 ServiceAccount
  • 创建的qfedu-sa这个 ServiceAccount 和一个 Role 角色进行绑定的,如果现在创建一个新的 ServiceAccount,需要他操作的权限作用于所有的 namespace,这个时候就需要使用到 ClusterRole 和 ClusterRoleBinding 这两种资源对象了。同样,首先新建一个 ServiceAcount 对象
[root@k8s-master rbac]# vim qfedu-sa2.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: qfedu-sa2
  namespace: kube-system
  • 创建:
[root@k8s-master rbac]# kubectl create -f qfedu-sa2.yaml
  • 然后创建一个 ClusterRoleBinding 对象
[root@k8s-master rbac]# vim qfedu-clusterolebinding.yaml
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: qfedu-sa2-clusterrolebinding
subjects:
- kind: ServiceAccount
  name: qfedu-sa2
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
  • 没有为这个资源对象声明 namespace,因为这是一个 ClusterRoleBinding 资源对象,是作用于整个集群的,
  • 也没有单独新建一个 ClusterRole 对象,而是使用的 cluster-admin 这个对象,这是Kubernetes集群内置的 ClusterRole 对象,
  • 可以使用kubectl get clusterrole 和kubectl get clusterrolebinding查看系统内置的一些集群角色和集群角色绑定,
  • 这里使用的 cluster-admin 这个集群角色是拥有最高权限的集群角色,所以一般需要谨慎使用该集群角色。
  • 创建上面集群角色绑定资源对象,创建完成后同样使用 ServiceAccount 对应的 token 去登录 Dashboard 验证下:
[root@k8s-master rbac]# kubectl create -f qfedu-clusterolebinding.yaml
[root@k8s-master rbac]# kubectl get secret -n kube-system |grep qfedu-sa2
qfedu-sa2-token-kts5n                             kubernetes.io/service-account-token   3      5m32s
[root@k8s-master rbac]# kubectl describe secrets qfedu-sa2-token-kts5n -n kube-system
Name:         qfedu-sa2-token-kts4n
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: qfedu-sa2
              kubernetes.io/service-account.uid: 41a0ba23-445b-4140-94cc-587d175f2946

Type:  kubernetes.io/service-account-token

Data
====
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IlZZaGtacVFOLUtrQ3JxSUpQZDBWdG5zNkNJT3M3U25ocmhFVW1XTGZVc1UifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJxZmVkdS1zYS10b2tlbi1rdHM0biIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJxZmVkdS1zYSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQxYTBiYTIzLTQ0NWItNDE0MC05NGNjLTU4N2QxNzVmMjk0NSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTpxZmVkdS1zYSJ9.wjCCrzr9kxna-C6QruMWHzyf-cOVhqnKccztOzWJIGzV92md7bI7PKAxseu5QKMzNh-0SrJD4Z_ejX7AVQRKmyEhWJb2MPoVoDM-XedQY-0krc6jaK66MNhOtUptsjHA-lUIf64LcIdb41voczqVN3S-F8PwYyQlO21l7G4XvUej5Y6tBJSO003tUL_Qr-BWowH3Mhle7a5qWDeV5J9k2Spr-0qk4wUhMykQ9sGZID3AAw8Aa8M3nxJS26Lj_lKxWX6v6s055-bW5NQjrp2l3Cnaaafa6UNHd26kPDvVMcfLEP_N30jxLbm9Ok6Znaam9iVrSghZ1sIOQrQrytlm1A
ca.crt:     1025 bytes
namespace:  11 bytes
# 会生成一串很长的base64后的字符串
  • 刚开始接触到 RBAC 认证的时候,可能不太熟悉,特别是不知道应该怎么去编写rules规则,大家可以利用 kubectl 的 get、describe、 -o yaml 这些操作去分析系统自带的 clusterrole、clusterrolebinding 这些资源对象的编写方法,所以kubectl最基本的用户一定要掌握好。
  • RBAC只是Kubernetes中安全认证的一种方式,当然也是现在最重要的一种方式。

18、Kubernetes 服务接入控制器

1、ingress 架构图简介
  • Kubernetes 暴露服务的方式目前只有三种:LoadBlancer Service、NodePort Service、Ingress
  • service 的表现形式为IP:PORT,即工作在第四层传输层(TCP/IP层),对于不同的URL地址经常对应用不同的后端服务或者虚拟服务器,这些应用层的转发机制仅通过kubernetes的service机制是无法实现的,这种情况我么可以使用ingress策略定义和一个具体的ingress Controller,两者结合实现一个完整的Ingress 负载均衡,这个负载均衡是基于nginx七层反向代理来实现的,ingress工作原理如下图:
  • 外部客户端通过访问负载均衡器,然后调度到service上,然后在调度到IngressController,IngressController通过Ingress规则(域名或虚拟主机)访问到后端pod,而在Ingress规则当中对应的主机是又service分组来设定的,可以看到,这幅图有2种service,最上面的service是用来对外提供服务的,而下面2个service仅仅是用来分pod组的
  • 通俗的讲:
  • Service 是后端真实服务的抽象,一个 Service 可以代表多个相同的后端服务
  • Ingress 是反向代理规则,用来规定 HTTP/S 请求应该被转发到哪个 Service 上,比如根据请求中不同的 Host 和 url 路径让请求落到不同的 Service 上
  • Ingress Controller 就是一个反向代理程序,它负责解析 Ingress 的反向代理规则,如果 Ingress 有增删改的变动,所有的 Ingress Controller 都会及时更新自己相应的转发规则,当 Ingress Controller 收到请求后就会根据这些规则将请求转发到对应的 Service。
  • Kubernetes 并没有自带 Ingress Controller,它只是一种标准,具体实现有多种,需要自己单独安装,常用的是 Nginx Ingress Controller 和 Traefik Ingress Controller。
  • Ingress 是一种转发规则的抽象,Ingress Controller 的实现需要根据这些 Ingress 规则来将请求转发到对应的 Service,下图方便大家理解:
  • 从图中可以看出,Ingress Controller 收到请求,匹配 Ingress 转发规则,匹配到了就转发到后端 Service,而 Service 代表的后端 Pod 有多个,选出一个转发到那个 Pod,最终由那个 Pod 处理请求。
  • 有同学可能会问,既然 Ingress Controller 要接受外面的请求,而 Ingress Controller 是部署在集群中的,怎么让 Ingress Controller 本身能够被外面访问到呢,有几种方式:
  • Ingress Controller 用 Deployment 方式部署,给它添加一个 Service,类型为 LoadBalancer,这样会自动生成一个 IP 地址,通过这个 IP 就能访问到了,并且一般这个 IP 是高可用的(前提是集群支持 LoadBalancer,通常云服务提供商才支持,自建集群一般没有)
  • 使用集群内部的某个或某些节点作为边缘节点,给 node 添加 label 来标识,Ingress Controller 用 DaemonSet 方式部署,使用 nodeSelector 绑定到边缘节点,保证每个边缘节点启动一个 Ingress Controller 实例,用 hostPort 直接在这些边缘节点宿主机暴露端口,然后我们可以访问边缘节点中 Ingress Controller 暴露的端口,这样外部就可以访问到 Ingress Controller 了
  • Ingress Controller 用 Deployment 方式部署,给它添加一个 Service,类型为 NodePort,部署完成后查看会给出一个端口,通过 kubectl get svc 我们可以查看到这个端口,这个端口在集群的每个节点都可以访问,通过访问集群节点的这个端口就可以访问 Ingress Controller 了。但是集群节点这么多,而且端口又不是 80和443,太不爽了,一般我们会在前面自己搭个负载均衡器,比如用 Nginx,将请求转发到集群各个节点的那个端口上,这样我们访问 Nginx 就相当于访问到 Ingress Controller 了
2、Ingress 的负载均衡机制
  • k8s提供了两种内建的云端负载均衡机制用于发布公共应用,一种是工作于传输层的service资源,它实现的是TCP负载均衡器,另一种是Ingress资源,它实现的是HTTP(S)负载均衡器。
1、TCP负载均衡器
  • 无论是iptables还是ipvs模型的service资源都配置于Linux内核中的netfilter之上进行四层调度,是一种类型更为通用的调度器,支持调度HTTP、MYSQL等应用层服务。不过,也正是由于工作于传输层从而使得它无法做到https及ssl会话支持等,也不支持基于url的请求调度机制,而且,k8s也不支持为此类负载均衡器配置任何类型的健康检查机制。
2、HTTP(S)负载均衡器
  • HTTP(S)负载均衡器是应用层负载均衡机制的一种,支持根据环境做出更好的调度决策。与传输层调度器相比,它提供了诸如可自定义url映射和tls卸载等功能,并支持多种类型的后端服务器监控状态检测机制。
3、Ingress 和 Ingress Controller
  • service资源和pod资源的IP地址仅能用于集群网络内部的通信,所有的网络流量都无法穿透边界路由器以实现集群内外通信。尽管可以为service使用NodePort或LoadBalancer类型通过节点引入外部流量,但它依然是4层流量转发,可用的负载均衡器也为传输层负载均衡机制。
  • Ingress是k8s的标准资源类型之一,它其实就是一组基于DNS名称或URL路径把请求转发至指定的service资源的规则,用于将集群外部的请求流量转发至集群内部完成服务发布。
  • Ingress资源自身并不能进行流量穿透,它仅是规则的集合,这些规则要想真正发挥作用还需要其他功能的辅助,如监听某套接字,然后根据这些规则的匹配机制路由请求流量。这种能够为Ingress资源监听套接字并转发流量的组件称为Ingress控制器。
  • Ingress控制器可以由任何具有反向代理功能的服务程序实现,如Nginx、Envoy和Traefik等。
  • Ingress控制器自身也是运行于集群中的pod资源对象,它与被代理的运行为pod资源的应用运行于同一网络中。使用Ingress资源进行流量分发时,Ingress控制器可基于某Ingress资源定义的规则将客户端的请求流量直接转发至与service对应的后端pod资源之上,这种转发机制会绕过service资源,从而省去了由kube-proxy实现的端口代理开销。
  • Ingress规则需要由一个service资源对象辅助识别相关的所有pod对象,但Ingress-nginx控制器可经由api.ilinux.io规则的定义直接将请求流量调度至后端pod上,而无须经由service对象api的再次转发。
3、如何创建 Ingress 资源
  • Ingress资源时基于HTTP虚拟主机或URL的转发规则,需要强调的是,这是一条转发规则。资源清单中annotation用于识别其所属的Ingress控制器的类型,这一点在集群中部署有多个Ingress控制器时尤为重要。
  • Ingress 中的spec字段是Ingress资源的核心组成部分,主要包含以下3个字段:
  • rules:用于定义当前Ingress资源的转发规则列表;由rules定义规则,或没有匹配到规则时,所有的流量会转发到由backend定义的默认后端。
  • backend:默认的后端,用于服务那些没有匹配到任何规则的请求;定义Ingress资源时,必须要定义backend或rules两者之一,该字段用于让负载均衡器指定一个全局默认的后端。
  • tls:TLS配置,目前仅支持通过默认端口443提供服务,如果要配置指定的列表成员指向不同的主机,则需要通过SNI TLS扩展机制来支持该功能。
1、rules
  • rules 对象由一系列的配置的 Ingress 资源的 host 规则组成,这些 host 规则用于将一个主机上的某个URL映射到相关后端 Service 对象,其定义格式如下:
spec:
  rules:
  - hosts: <string>
    http:
      paths:
      - path:
        backend:
          serviceName: <string>
          servicePort: <string>
  • 需要注意的是,.spec.rules.host 属性值,目前暂不支持使用IP地址定义,也不支持IP:Port的格式,若该字段留空,代表着通配所有主机名。
2、backend
  • backend 对象的定义由2个必要的字段组成:serviceName和servicePort,分别用于指定流量转发的后端目标 Service 资源名称和端口。
3、tls
  • tls 对象由 2 个内嵌的字段组成,仅在定义TLS主机的转发规则上使用。
  • hosts: 包含于使用 的 TLS 证书 之内 的 主机 名称 字符串 列表, 因此, 此处 使用 的 主机 名 必须 匹配 tlsSecret 中的 名称。
  • secretName: 用于 引用 SSL 会话 的 secret 对象 名称, 在 基于 SNI 实现 多 主机 路 由 的 场景 中, 此 字段 为 可选。
4、Ingress 资源类型
  • 基于http暴露的每个service资源均可发布于一个独立的FQDN主机名之上,如www.qf.com;也可发布于某主机的URL路径之上,从而将它们整合到同一个web站点,如www.qf.com/nginx。至于是否需要发布为https类型的应用则取决于用户的业务需求。
  • Ingress的资源类型有以下4种:
  • 1、单Service资源型Ingress
  • 2、基于URL路径进行流量转发
  • 3、基于主机名称的虚拟主机
  • 4、TLS类型的Ingress资源
1、单 Service 资源型 Ingress
  • 暴露单个服务的方法有多种,如NodePort、LoadBanlancer等等,当然也可以使用Ingress来进行暴露单个服务,只需要为Ingress指定default backend即可,如下示例:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
spec:
  backend:
    serviceName: my-svc
    servicePort: 80
  • Ingress控制器会为其分配一个IP地址接入请求流量,并将其转发至后端my-svc。
2、基于 url 路径进行流量分发
  • 垂直拆分或微服务架构中,每个小的应用都有其专用的 service 资源暴露服务,但在对外开放的站点上,它们可能是财经、新闻、电商等一类的独立应用,可通过主域名的 url 路径分别接入,用于发布集群内名称为 API 和 WAP 的 service 资源。于是,可对应地创建一个如下的 Ingress 资源:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-url-demo
  annotations:
     nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: myapp.syztoo.com
    http:
      paths:
      - path: /v1
        backend:
          serviceName: myappv1-svc
          servicePort: 80
      - path: /v2
        backend:
          serviceName: myappv2-svc
          servicePort: 80
 
---
apiVersion: v1
kind: Service
metadata:
  name: myappv1-svc
  namespace: default
spec:
  selector:
    app: myappv1
    release: canary
  type: ClusterIP
  ports: 
  - port: 80
    targetPort: 80
 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myappv1-depoly
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myappv1
      release: canary
  template:
    metadata:
      labels: 
        app: myappv1
        release: canary
    spec:
      containers:
      - name: myappv1
        image: ikubernetes/myapp:v1
        ports:
        - name: http
          containerPort: 80
 
---
apiVersion: v1
kind: Service
metadata:
  name: myappv2-svc
  namespace: default
spec:
  selector:
    app: myappv2
    release: canary
  type: ClusterIP
  ports: 
  - port: 80
    targetPort: 80
 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myappv2-deploy
  namespace: default
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myappv2
      release: canary
  template:
    metadata:
      labels: 
        app: myappv2
        release: canary
    spec:
      containers:
      - name: myappv2
        image: ikubernetes/myapp:v2
        ports:
        - name: http
          containerPort: 80
---    
# 注意前提:必须部署完 Ingress Controller
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 30080  # 添加端口
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
3、基于主机名称的虚拟主机
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test
spec:
  rules:
  - host: api.qf.com
    http: 
      paths:
      - backend:
          serviceName: api
          servicePort: 80
  - host: wap.qf.com
    http: 
      paths:
      - backend:
          serviceName: wap
          servicePort: 80
4、TLS类型的Ingress资源
  • 这种类型用于以https发布service资源,基于一个含有私钥和证书的secret对象即可配置TLS协议的Ingress资源,目前来说,Ingress资源仅支持单TLS端口,并且还能卸载TLS会话,在Ingress资源中引用此secret即可让Ingress控制器加载并配置为https服务。
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: no-rules-map
spec: 
  tls:
  - secretName: ikubernetesSecret
  backend:
    serviceName: homesite
    servicePort: 80
5、部署 Ingress 控制器(Nginx)
1、下载 mandatory.yaml 文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sslI3ntc-1610418329808)(assets/360截图17860602115120138.png)]

[root@master ~]# mkdir ingress-nginx
[root@master ~]# cd ingress-nginx/
[root@master ingress-nginx]# wget https://github.com/kubernetes/ingress-nginx/blob/master/deploy/static/mandatory.yaml
[root@master ingress-nginx]# wget  https://github.com/kubernetes/ingress-nginx/blob/master/deploy/static/provider/baremetal/service-nodeport.yaml #对外提供服务,如果不需要可以不下载
2、修改镜像
  • 因为mandatory文件中默认用的是谷歌地址,镜像下载会失败,所以替换为阿里镜像源,速度也会更快!
[root@master ingress-nginx]# sed -i 's#quay.io/kubernetes-ingress-controller/nginx-ingress-controller#registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller#g' mandatory.yaml
# 在Service中定义标注prometheus.io/scrape: ‘true’,表明该Service需要被promethues发现并采集数据
3、自定义 service 暴露的 nodeport
  • 如果想手动修改访问的端口可以添加 service-nodeport 文件中 nodePort,如果采取随机分配这一步可以忽略
[root@master ingress-nginx]# cat service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
      nodePort: 30080  # 添加端口
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
      nodePort: 30443  # 添加端口
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
# 上面的文件中,使用了宿主机的固定端口(30080,30443),需保证宿主机的这两个端口未占用。
4、部署 Ingress controller
  • 执行service-nodeport.yaml和mandatory.yaml两个文件,这里service-nodeport使用nodePort方式,
[root@master ingress-nginx]# kubectl apply -f mandatory.yaml 
[root@master ingress-nginx]# kubectl apply -f service-nodeport.yaml
5、查看 ingress-nginx 的 pod 状态
[root@master ingress-nginx]# kubectl get pods -A
NAMESPACE       NAME                                       READY   STATUS    RESTARTS   AGE
default         nginx-7bb7cd8db5-98wvj                     1/1     Running   0          62m
ingress-nginx   nginx-ingress-controller-d786fc9d4-w5nrc   1/1     Running   0          58m
kube-system     coredns-bccdc95cf-8sqzn                    1/1     Running   2          4d2h
kube-system     coredns-bccdc95cf-vt8nz                    1/1     Running   2          4d2h
kube-system     etcd-master                                1/1     Running   1          4d2h
kube-system     kube-apiserver-master                      1/1     Running   1          4d2h
kube-system     kube-controller-manager-master             1/1     Running   2          4d2h
kube-system     kube-flannel-ds-amd64-c97wh                1/1     Running   1          4d1h
kube-system     kube-flannel-ds-amd64-gl6wg                1/1     Running   2          4d1h
kube-system     kube-flannel-ds-amd64-npsqf                1/1     Running   1          4d1h
kube-system     kube-proxy-gwmx8                           1/1     Running   2          4d2h
kube-system     kube-proxy-phqk2                           1/1     Running   1          4d1h
kube-system     kube-proxy-qtt4b                           1/1     Running   1          4d1h
kube-system     kube-scheduler-master                      1/1     Running   2          4d2h
6、查看 svc 状态
[root@master ingress-nginx]# kubectl get svc -n ingress-nginx
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx          NodePort    10.105.53.207   <none>        80:30080/TCP,443:30443/TCP   58m
7、mandatory 部署介绍
1.namespace.yaml 
创建一个独立的命名空间 ingress-nginx

2.configmap.yaml 
ConfigMap 是存储通用的配置变量的,类似于配置文件,使用户可以将分布式系统中用于不同模块的环境变量统一到一个对象中管理;而它与配置文件的区别在于它是存在集群的“环境”中的,并且支持K8S集群中所有通用的操作调用方式。
从数据角度来看,ConfigMap的类型只是键值组,用于存储被Pod或者其他资源对象(如RC)访问的信息。这与secret的设计理念有异曲同工之妙,主要区别在于ConfigMap通常不用于存储敏感信息,而只存储简单的文本信息。
ConfigMap可以保存环境变量的属性,也可以保存配置文件。
创建pod时,对configmap进行绑定,pod内的应用可以直接引用ConfigMap的配置。相当于configmap为应用/运行环境封装配置。
pod使用ConfigMap,通常用于:设置环境变量的值、设置命令行参数、创建配置文件。

3.default-backend.yaml 
如果外界访问的域名不存在的话,则默认转发到default-http-backend这个Service,其会直接返回404:

4.rbac.yaml 
负责Ingress的RBAC授权的控制,其创建了Ingress用到的ServiceAccount、ClusterRole、Role、RoleBinding、ClusterRoleBinding

5.with-rbac.yaml 
是Ingress的核心,用于创建ingress-controller。ingress-controller的作用是将新加入的Ingress进行转化为Nginx的配置
8、 访问浏览器验证

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CNz9uO30-1610418329809)(assets/360截图17571112739489.png)]

  • 上面提示的404是因为后端服务还没有配置,这是OK的
9、创建后端服务
  • 这里我们已 nginx 为服务为例,创建一个 nginx 和跟 nginx 对应的 service,
  • **注意:**metadata.name 要和后面创建的 ingress 中的 serviceName 一致,切记!
[root@master ingress-nginx]# cat mynginx.yaml 
apiVersion: v1
kind: Service
metadata:
  name: service-nginx
  namespace: default
spec:
  selector:
    app: mynginx
  ports:
  - name: http
    port: 80
    targetPort: 80

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mynginx
  namespace: default
spec:
  replicas: 5
  selector:
    matchLabels:
      app: mynginx
  template:
    metadata:
      labels:
        app: mynginx
    spec:
      containers:
      - name: mycontainer
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - name: nginx 
          containerPort: 80
  • 有了前端了,也有后端了,那么接下来就该创建 ingress 规则了
10、配置 ingress 规则
[root@master ingress-nginx]# cat ingress-nginx.yaml 
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-mynginx
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: mynginx.qf.com
    http:
      paths:
      - path:
        backend:
          serviceName: service-nginx
          servicePort: 80
11、访问测试
  • 在打开浏览器的主机添加一条hosts记录(192.168.254.13 mynginx.qf.com)然后打开浏览器验证

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pT6kCmuf-1610418329810)(assets/360截图17950506102101155.png)]

12、查看 ingress-controlle 中 nginx 配置文件是否生效
  • 可以去查看 nginx 的配置文件,去查看我们所创建的规则有没有注入到 ingress 中
#查看ingress-controller中的规则
[root@master ingress-nginx]# kubectl get pods -n ingress-nginx                                                      
NAME                                       READY   STATUS    RESTARTS   AGE
nginx-ingress-controller-d786fc9d4-4vb5z   1/1     Running   0          140m
[root@master ingress-nginx]# kubectl exec -it nginx-ingress-controller-d786fc9d4-4vb5z -n ingress-nginx -- /bin/bash
www-data@nginx-ingress-controller-d786fc9d4-4vb5z:/etc/nginx$ cat nginx.conf
  • nginx.conf 中以成功添加 mynginx.qf.com 结果如下:
## start server mynginx.qf.com
	server {
		server_name mynginx.qf.com ;
		
		listen 80  ;
		listen 443  ssl http2 ;
		
		set $proxy_upstream_name "-";
		
		ssl_certificate_by_lua_block {
			certificate.call()
		}
		
		location / {
			
			set $namespace      "default";
			set $ingress_name   "ingress-mynginx";
			set $service_name   "service-nginx";
			set $service_port   "80";
			set $location_path  "/";
			
			rewrite_by_lua_block {
				lua_ingress.rewrite({
					force_ssl_redirect = false,
					ssl_redirect = true,
					force_no_ssl_redirect = false,
					use_port_in_redirects = false,
				})
				balancer.rewrite()
				plugins.run()
			}
			
			header_filter_by_lua_block {
				
				plugins.run()
			}
			body_filter_by_lua_block {
				
			}
			
			log_by_lua_block {
				
				balancer.log()
				
				monitor.call()
				
				plugins.run()
			}
			
			port_in_redirect off;
			
			set $balancer_ewma_score -1;
			set $proxy_upstream_name "default-service-nginx-80";
			set $proxy_host          $proxy_upstream_name;
			set $pass_access_scheme  $scheme;
			set $pass_server_port    $server_port;
			set $best_http_host      $http_host;
			set $pass_port           $pass_server_port;
			
			set $proxy_alternative_upstream_name "";
			
			client_max_body_size                    1m;
			
			proxy_set_header Host                   $best_http_host;
			
			# Pass the extracted client certificate to the backend
			
			# Allow websocket connections
			proxy_set_header                        Upgrade           $http_upgrade;
			
			proxy_set_header                        Connection        $connection_upgrade;
			
			proxy_set_header X-Request-ID           $req_id;
			proxy_set_header X-Real-IP              $remote_addr;
			
			proxy_set_header X-Forwarded-For        $remote_addr;
			
			proxy_set_header X-Forwarded-Host       $best_http_host;
			proxy_set_header X-Forwarded-Port       $pass_port;
			proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
			
			proxy_set_header X-Scheme               $pass_access_scheme;
			
			# Pass the original X-Forwarded-For
			proxy_set_header X-Original-Forwarded-For $http_x_forwarded_for;
			
			# mitigate HTTPoxy Vulnerability
			# https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
			proxy_set_header Proxy                  "";
			
			# Custom headers to proxied server
			
			proxy_connect_timeout                   5s;
			proxy_send_timeout                      60s;
			proxy_read_timeout                      60s;
			
			proxy_buffering                         off;
			proxy_buffer_size                       4k;
			proxy_buffers                           4 4k;
			
			proxy_max_temp_file_size                1024m;
			
			proxy_request_buffering                 on;
			proxy_http_version                      1.1;
			
			proxy_cookie_domain                     off;
			proxy_cookie_path                       off;
			
			# In case of errors try the next upstream server before returning an error
			proxy_next_upstream                     error timeout;
			proxy_next_upstream_timeout             0;
			proxy_next_upstream_tries               3;
			
			proxy_pass http://upstream_balancer;
			
			proxy_redirect                          off;
			
		}
		
	}
	## end server mynginx.fengzi.com
  • 确定 nginx 配置文件中已经有了我们所定义的反代规则 ok,成功!!!
13、用 ingress 实现 tomcat 服务代理
1、部署 tomcat 服务
  • 下面代码是给 tomcat 服务添加 5 个 pod 和 1 个 service 分组
[root@master ingress-nginx]# cat tomcat.yaml 
apiVersion: v1
kind: Service
metadata:
  name: tomcat
  namespace: default
spec:
  selector:
    app: tomcat
  ports:
  - name: http
    port: 8080
    targetPort: 8080
  - name: ajp
    port: 8009
    targetPort: 8009
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat
  namespace: default
spec:
  replicas: 5
  selector:
    matchLabels:
      app: tomcat
  template:
    metadata:
      labels:
        app: tomcat
    spec:
      containers:
      - name: tomcat
        image: tomcat
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 8080
        - name: ajp
          containerPort: 8009
2、添加 tomcat 的 ingress 配置
[root@master ingress-nginx]# cat ingress-tomcat.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-mytomcat
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: mytomcat.qf.com
    http:
      paths:
      - path:
        backend:
          serviceName: tomcat
          servicePort: 8080
14、访问测试
  • 在浏览器宿主机上添加 hosts 记录(192.168.254.13 mytomcat.qf.com ),然后打开浏览器验证

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qmabbspx-1610418329811)(assets/360截图17860531433933.png)]

  • 这样我们就可以实现利用nginx的反向代理,对于web服务针对主机名的不同显示不通的网站
6、基于ssl 协议的访问
1、创建私有证书及secret
[root@master ingress-nginx]# openssl genrsa -out tls.key 2048

#这里CN=后面要写域名
[root@master ingress-nginx]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=mytomcat.qf.com

#创建secret
[root@master ingress-nginx]# kubectl create secret tls mytomcat-ingress-secret --cert=tls.crt --key=tls.key
[root@master ingress-nginx]# kubectl describe secret mytomcat-ingress-secret
Name:         mytomcat-ingress-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  kubernetes.io/tls

Data
====
tls.crt:  1302 bytes
tls.key:  1675 bytes
2、添加证书到 tomcat 服务
  • 将证书添加到 tomcat 中,执行 ingress-tomcat-tls.yaml 文件,ingress-tomcat-tls.yaml 文件内容如下
[root@master ingress-nginx]# cat ingress-tomcat-tls.yaml 
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-mytomcat-tls
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  tls:
  - hosts:
    - mytomcat.qf.com    #这里写域名
    secretName: mytomcat-ingress-secret   #这里写secret证书名称
  rules:
  - host: mytomcat.qf.com
    http:
      paths:
      - path:
        backend:
          serviceName: tomcat
          servicePort: 8080
3、执行 ingress-tomcat-tls.yaml 文件
[root@k8s-master ingress-nginx]# kubectl apply -f ingress-tomcat-tls.yaml
4、访问验证

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NbxYwam9-1610418329813)(assets/360截图18270612196024.png)]

九、Kubernetes 网络策略

1、NetworkPolicy 简介
  • network policy顾名思义就是对pod进行网络策略控制。 k8s本身并不支持,因为k8s有许多种网络的实现方式,企业内部可以使用简单的flannel、weave、kube-router等,适合公有云的方案则有calico等。不同的网络实现原理(vethpair、bridge、macvlan等)并不能统一地支持network policy。
2、NetworkPolicy 策略模型

1、使用 network policy资源可以配置pod的网络,networkPolicy 是 namespace scoped的,他只能影响某个 namespace 下的 pod 的网络出入站规则。

  • metadata 描述信息
  • podSelector pod 选择器,选定的pod所有的出入站流量要遵循本networkpolicy的约束
  • policyTypes 策略类型。包括了Ingress和Egress,默认情况下一个policyTypes的值一定会包含Ingress,当有egress规则时,policyTypes的值中会包含Egress
  • ingress 入站
  • egress 出站

2、CNI插件需要启用,Calico安装为CNI插件。必须通过传递–network-plugin=cni参数将kubelet配置为使用CNI网络。(在 kubeadm 上,这是默认设置。)

3、支持 kube-proxy 的模式

  • iptables
  • ipvs需要1.9以上的
3、安装 Canal 用于策略
[root@k8s-master network-policy]# wget https://docs.projectcalico.org/v3.14/getting-started/kubernetes/installation/hosted/canal/canal.yaml
  • 默认pod CIDR 10.244.0.0/16 如果POD_CIDR 想要修改,就要修改canal.yaml
[root@k8s-master network-policy]# POD_CIDR="<your-pod-cidr>" \
[root@k8s-master network-policy]# sed -i -e "s?10.244.0.0/16?$POD_CIDR?g" canal.yaml
  • 镜像文件
[root@k8s-master network-policy]# grep image canal.yaml 
          image: calico/cni:v3.14.2
          image: calico/pod2daemon-flexvol:v3.14.2
          image: calico/node:v3.14.2
          image: quay.io/coreos/flannel:v0.11.0
          image: calico/kube-controllers:v3.14.2
  • 所有节点加载镜像
[root@k8s-master net]# docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
calico/node                          v3.14.2             780a7bc34ed2        3 weeks ago         262MB
calico/pod2daemon-flexvol            v3.14.2             9dfa8f25b51c        3 weeks ago         22.8MB
calico/cni                           v3.14.2             e6189009f081        3 weeks ago         119MB
calico/node                          v3.15.1             1470783b1474        5 weeks ago         262MB
calico/pod2daemon-flexvol            v3.15.1             a696ebcb2ac7        5 weeks ago         112MB
calico/cni                           v3.15.1             2858353c1d25        5 weeks ago         217MB
calico/kube-controllers              v3.15.1             8ed9dbffe350        5 weeks ago         53.1MB
quay.io/coreos/flannel               v0.11.0             ff281650a721        18 months ago       52.6MB
  • 安装Canal
[root@k8s-master network-policy]# kubectl apply -f canal.yaml
configmap/canal-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/kubecontrollersconfigurations.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org configured
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org configured
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers unchanged
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers unchanged
clusterrole.rbac.authorization.k8s.io/calico-node configured
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/canal-flannel created
clusterrolebinding.rbac.authorization.k8s.io/canal-calico created
daemonset.apps/canal created
serviceaccount/canal created
deployment.apps/calico-kube-controllers configured
serviceaccount/calico-kube-controllers unchanged
  • 等待安装完成(需要等两分钟)
[root@k8s-master network-policy]# kubectl get pods -n kube-system
NAME                                       READY   STATUS    RESTARTS   AGE
calico-kube-controllers-578894d4cd-96kn4   1/1     Running   0          12m
calico-node-8ffrw                          1/1     Running   0          12m
calico-node-bt4lv                          1/1     Running   0          12m
calico-node-vwqpx                          1/1     Running   0          12m
canal-2hp4t                                2/2     Running   0          20m
canal-79vfq                                2/2     Running   0          20m
canal-dstjf                                2/2     Running   0          20m
coredns-66bff467f8-6nj98                   1/1     Running   0          13d
coredns-66bff467f8-gfncf                   1/1     Running   0          13d
etcd-k8s-master                            1/1     Running   0          13d
kube-apiserver-k8s-master                  1/1     Running   0          13d
kube-controller-manager-k8s-master         1/1     Running   0          12d
kube-proxy-bt5wj                           1/1     Running   0          13d
kube-proxy-f5cr2                           1/1     Running   1          13d
kube-proxy-wn67g                           1/1     Running   0          13d
kube-scheduler-k8s-master                  1/1     Running   0          12d
metrics-server-96d889b76-zkwx6             1/1     Running   0          12d
4、kubernetes中的网络控制策略
  • NetworkPolicy是kubernetes对pod的隔离手段,可以看到,NetworkPolicy实际上只是宿主机上的一系列iptables规则。
  • Egress 表示出站流量,就是pod作为客户端访问外部服务,pod地址作为源地址。策略可以定义目标地址或者目的端口
  • Ingress 表示入站流量,pod地址和服务作为服务端,提供外部访问。pod地址作为目标地址。策略可以定义源地址和自己端口
  • podSelector 规则生效在那个pod上,可以配置单个pod或者一组pod。可以定义单方向。空 podSelector选择命名空间中的Pod。
[root@k8s-master network-policy]# kubectl explain  networkpolicy.spec
  • egress 出站流量规则 可以根据ports和to去定义规则。ports下可以指定目标端口和协议。to(目标地址):目标地址分为ip地址段、pod、namespace
  • ingress 入站流量规则 可以根据ports和from。ports下可以指定目标端口和协议。from(来自那个地址可以进来):地址分为ip地址段、pod、namespace
  • podSelector 定义NetworkPolicy的限制范围。直白的说就是规则应用到那个pod上。
    podSelector: {},留空就是定义对当前namespace下的所有pod生效。没有定义白名单的话 默认就是Deny ALL (拒绝所有)
  • policyTypes 指定那个规则 那个规则生效,不指定就是默认规则。
1、在dev的namespace下定义一个入站流量拒绝的规则:
# 创建 dev namespace
[root@k8s-master network-policy]# vim dev-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
   name: dev
   labels:
     name: development
[root@k8s-master network-policy]# kubectl apply -f dev-namespace.yaml

[root@k8s-master network-policy]# vim networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-policy
spec:
  podSelector: {}
  policyTypes:
  - Ingress

[root@k8s-master network-policy]# kubectl apply -f network-policy.yaml  -n dev
2、在dev和prod的namespace下个各自创建一个pod
# 创建 prod namespace
[root@k8s-master network-policy]# vim prod-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
   name: prod
   labels:
     name: prod
[root@k8s-master network-policy]# kubectl apply -f prod-namespace.yaml
# 创建pod
[root@k8s-master network-policy]# vim pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-1
  labels:
    name: myapp
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1

[root@k8s-master network-policy]# kubectl apply -f policy-pod.yaml  -n dev

[root@k8s-master network-policy]# kubectl apply -f policy-pod.yaml  -n prod

# 测试一下
[root@k8s-master network-policy]# kubectl get pod -o wide -n prod 
NAME      READY     STATUS    RESTARTS   AGE       IP           NODE
pod-1     1/1       Running   0          3h        10.244.2.3   k8s-node02
[root@k8s-master network-policy]# kubectl get pod -o wide -n dev 
NAME      READY     STATUS    RESTARTS   AGE       IP           NODE
pod-1     1/1       Running   0          3h        10.244.2.2   k8s-node02

[root@k8s-master network-policy]# curl 10.244.2.3
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
[root@k8s-master network-policy]# 10.244.2.2 不通
3、表示所有的都被运行的规则
[root@k8s-master network-policy]# vim network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: all-policy
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

[root@k8s-master network-policy]# kubectl apply -f  network-policy.yaml  -n dev 

# 测试
[root@k8s-master network-policy]# curl 10.244.2.2                              
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
4、放行特定的入站访问流量
[root@k8s-master network-policy]# vim alloy-pod.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-myapp-policy
spec:
  podSelector: 
    matchLabels:
      app: myapp   # 选择app=myapp 的标签放行
  ingress:
  - from:
    - ipBlock:     # 地址段
        cidr: 10.24.0.0/16  # 允许这个地址段访问
        except:    # 排除一下地址不可以访问
        - 10.244.0.1/32
    ports:
    - port: 80     # 只运行访问80端口
      protocol: TCP
  policyTypes:
  - Ingress
  
[root@k8s-master network-policy]# kubectl apply -f alloy-pod.yaml -n dev
  • 测试一下(删除之前所有策略)
[root@k8s-master network-policy]# curl 10.244.2.2  # 访问失败
5、创建dev,test名称空间和pod
[root@k8s-master network-policy]# vim demo-dev.yaml
apiVersion: v1
kind: Pod
metadata:
  name: demo-dev
  namespace: dev
  labels:
    app: myapp
    type: pod
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    ports:
    - name: http
      containerPort: 80


[root@k8s-master network-policy]# vim demo-test.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: demo-test
  namespace: test
  labels:
    app: myapp
    type: pod
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    ports:
    - name: http
      containerPort: 80

[root@k8s-master network-policy]# kubectl apply -f demo-test.yaml 
pod/demo-test created
[root@k8s-master network-policy]# kubectl apply -f demo-dev.yaml
pod/demo-dev created
[root@k8s-master network-policy]# kubectl get pod -n dev -o wide
NAME       READY   STATUS    RESTARTS   AGE     IP           NODE     NOMINATED NODE   READINESS GATES
demo-dev   1/1     Running   0          2m51s   10.244.2.2   node02   <none>           <none>
[root@k8s-master network-policy]# kubectl get pod -n test -o wide
NAME        READY   STATUS    RESTARTS   AGE    IP           NODE     NOMINATED NODE   READINESS GATES
demo-test   1/1     Running   0          3m6s   10.244.1.2   node01   <none>           <none>
1、禁止入站
  • 编辑yaml文件,dev名称空间禁止入站
[root@k8s-master network-policy]# vim deny-all-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress

[root@k8s-master network-policy]# kubectl apply -f deny-all-ingress.yaml -n dev
networkpolicy.networking.k8s.io/deny-all-ingress created
[root@k8s-master network-policy]# kubectl get networkpolicies -n dev
NAME               POD-SELECTOR   AGE
deny-all-ingress   <none>         5m25s
  • 测试,发现流量不能入站
[root@k8s-master network-policy]# kubectl get pod -n dev -o wide
NAME       READY   STATUS    RESTARTS   AGE     IP           NODE     NOMINATED NODE   READINESS GATES
demo-dev   1/1     Running   0          2m51s   10.244.2.2   node02   <none>           <none>
[root@k8s-master network-policy]# kubectl get pod -n test -o wide
NAME        READY   STATUS    RESTARTS   AGE    IP           NODE     NOMINATED NODE   READINESS GATES
demo-test   1/1     Running   0          3m6s   10.244.1.2   node01   <none>           <none>
[root@k8s-master network-policy]# curl 10.244.2.2
^C
[root@k8s-master network-policy]# curl 10.244.1.2
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
2、允许入站
  • dev允许入站,编写yaml文件
[root@k8s-master network-policy]# vim allow-all-ingress.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

[root@k8s-master network-policy]# kubectl apply -f allow-all-ingress.yaml -n dev
networkpolicy.networking.k8s.io/allow-all-ingress created
[root@k8s-master network-policy]# kubectl get networkpolicies -n dev
NAME                POD-SELECTOR   AGE
allow-all-ingress   <none>         4s

[root@k8s-master network-policy]# kubectl get pod -n dev -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
demo-dev   1/1     Running   0          17m   10.244.2.2   node02   <none>           <none>
[root@k8s-master network-policy]# kubectl get pod -n test -o wide
NAME        READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
demo-test   1/1     Running   0          17m   10.244.1.2   node01   <none>           <none>
[root@k8s-master network-policy]# curl 10.244.2.2
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
[root@k8s-master network-policy]# curl 10.244.1.2
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>
3、禁止单ip入站
  • 禁止demo-test pod流量入站
[root@k8s-master network-policy]# vim allow-myapp.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-myapp
spec:
  podSelector: 
    matchLabels:
      app: myapp
      type: pod
  ingress:
  - from:
    - ipBlock:
        cidr: 10.244.0.0/16
        except:
        - 10.244.1.2/32
    ports:
    - protocol: TCP
      port: 80
  policyTypes:
  - Ingress

[root@k8s-master network-policy]# kubectl apply -f allow-myapp.yaml -n dev
networkpolicy.networking.k8s.io/allow-all-myapp created
[root@k8s-master network-policy]# kubectl get networkpolicies -n dev
NAME                POD-SELECTOR         AGE
allow-all-myapp     app=myapp,type=pod   6s
  • 测试
[root@k8s-master network-policy]# kubectl get pod -n dev -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
demo-dev   1/1     Running   0          30m   10.244.2.2   node02   <none>           <none>
[root@k8s-master network-policy]# kubectl get pod -n test -o wide
NAME        READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
demo-test   1/1     Running   0          31m   10.244.1.2   node01   <none>           <none>

[root@k8s-master network-policy]# curl 10.244.2.2
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

[root@k8s-master network-policy]# kubectl exec -it -n test demo-test  -- /bin/sh
/ # ping 10.244.2.2
PING 10.244.2.2 (10.244.2.2): 56 data bytes
4、禁止出站
  • 禁止test流量出站
[root@k8s-master network-policy]# vim deny-all-egress.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-egress
spec:
  podSelector: {}
  policyTypes:
  - Egress

[root@k8s-master network-policy]# kubectl apply -f deny-all-egress.yaml -n test
networkpolicy.networking.k8s.io/deny-all-egress created
[root@k8s-master network-policy]# kubectl get networkpolicies -n test 
NAME              POD-SELECTOR   AGE
deny-all-egress   <none>         18s
  • 测试
[root@k8s-master network-policy]# kubectl get pod -n test -o wide
NAME        READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
demo-test   1/1     Running   0          41m   10.244.1.2   node01   <none>           <none>
[root@master01 ~]# kubectl get pod -n dev -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
demo-dev   1/1     Running   0          40m   10.244.2.2   node02   <none>           <none>
[root@k8s-master network-policy]# curl 10.244.2.2 
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

[root@k8s-master network-policy]# kubectl exec -it -n test demo-test -- /bin/sh
/ # ping 10.244.2.2
PING 10.244.2.2 (10.244.2.2): 56 data bytes
5、允许出站
  • 运行test流量出站
[root@k8s-master network-policy]# vim allow-all-egress.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-egress
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Egress

[root@k8s-master network-policy]# kubectl apply -f allow-all-egress.yaml -n test
networkpolicy.networking.k8s.io/allow-all-egress created
[root@k8s-master network-policy]# kubectl get networkpolicies -n test
NAME               POD-SELECTOR   AGE
allow-all-egress   <none>         11s
  • 测试
[root@k8s-master network-policy]# kubectl get pod -n dev -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
demo-dev   1/1     Running   0          47m   10.244.2.2   node02   <none>           <none>
[root@k8s-master network-policy]# kubectl get pod -n test -o wide
NAME        READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
demo-test   1/1     Running   0          47m   10.244.1.2   node01   <none>           <none>
[root@k8s-master network-policy]# kubectl exec -it -n test demo-test -- /bin/sh
/ # ping 10.244.2.2
PING 10.244.2.2 (10.244.2.2): 56 data bytes
64 bytes from 10.244.2.2: seq=0 ttl=62 time=0.837 ms
64 bytes from 10.244.2.2: seq=1 ttl=62 time=1.043 ms
64 bytes from 10.244.2.2: seq=2 ttl=62 time=0.290 ms
6、策略模型实例
  • 如果只允许default这个namespace下label包含access=true的pod访问nginx pod(label:run=nginx),可以对nginx pod设置入站规则:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: access-nginx
  namespace: default
spec:
  podSelector:
    matchLabels:
      run: nginx
  ingress:
  - from:
    - podSelector:
        matchLabels:
          access: "true"

另外一些默认的规则:
1、同namespace的pod,入站规则为全部禁止

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector: {}
  policyTypes:
  - Ingress

2、同namespace的pod,入站规则为全部开放:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
spec:
  podSelector: {}
  ingress:
  - {}

3、同namespace的pod,出站规则为全部禁止

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector: {}
  policyTypes:
  - Egress

4、同namespace的pod,出站规则为全部开放

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
spec:
  podSelector: {}
  egress:
  - {}
  policyTypes:
  - Egress

5、通过创建一个可以选择所有 Pod 但不允许任何流量的 NetworkPolicy,你可以为一个 Namespace 创建一个 “默认的” 隔离策略,如下所示:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector:

6、这确保了即使是没有被任何 NetworkPolicy 选中的 Pod,将仍然是被隔离的。

可选地,在 Namespace 中,如果你想允许所有的流量进入到所有的 Pod(即使已经添加了某些策略,使一些 Pod 被处理为 “隔离的”),你可以通过创建一个策略来显式地指定允许所有流量:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
spec:
  podSelector:
  ingress:
  - {}

十、Kubernetes 使用时需要的注意点

1、滚动升级更新太慢

默认情况下,滚动升级是逐个更新的,当有几十上百个POD需要更新时,再加上就绪检测,整个过程将会更慢。


解决方法:

rollingUpdate:
    maxSurge: 20% #每个滚动更新的实例数量
    maxUnavailable: 10% #允许更新过程中有多少实例不可用

2、就绪检测无损更新

通常,服务重启的时候会有一小段时间是无法正常提供服务的。 为了避免这个过程中有请求的流量进来,我们可以使用就绪检测来检测服务是否就绪可正常接收并处理请求。

......
        readinessProbe:
          httpGet:
            host: api.xxx.com
            path: /
            port: 80
          initialDelaySeconds: 3 # 容器启动3秒后开始第一次检测
          periodSeconds: 60 # 每隔60s检测一次
          timeoutSeconds: 3 # http检测请求的超时时间
          successThreshold: 1 # 检测到有1次成功则认为服务是`就绪`
          failureThreshold: 1 # 检测到有1次失败则认为服务是`未就绪`
......

3、就绪检测全面瘫痪

就绪检测是把双利剑,用不好,反而容易出大问题,比如服务全面瘫痪。 我们可以看到上面就绪检测的配置,漏洞百出。


比如:

  • 超时

高并发情况下,请求处理不过来,个别服务很容易导致检测请求的超时(504),立马被认为未就绪,于是流量被转移到其它服务,进而让本来就高负荷的其它服务出现同样情况,恶性循环,很快,所有服务都被认为是未就绪,结果产生全面瘫痪现象。


解决方法:

设置更长的超时时间,以及更高的失败次数。

  • 重新部署

这种情况可能是误操作,也可能是其它异常导致服务挂了。总之,你需要在用户还在不断尝试请求你服务的时候重启。你会惊讶的发现,一直无法正常启动为就绪状态,所有服务都是未就绪。同样的原因,服务启动过程不是一次全部起来,而是逐批启动,这样每批服务启动后都无法hold住流量,于是还是恶性循环,全面瘫痪


解决方法:

先去掉就绪检测再重新部署。


5、自动扩展 瞬时高峰

自动扩展POD虽然好用,但如果扩展的指标(CPU、内存等)设置的过高,如:50%以上,那么,当突然有翻倍的流量过来时,根本来不及扩展POD,服务直接就超时或挂掉。


解决方法:

尽可能的把指标设置在一个较小的值,对以往流量做参考评估,确保了当有2倍、3倍甚至5倍的流量突袭时不至于hold不住。


6、自动伸缩提前扩容

通常,节点的自动伸缩依赖于POD的自动扩展时资源是否充足。然而在面对定时突然流量高峰的业务时,这种伸缩显然来不及,甚至常常出现高峰10分钟后才扩容的机器,流量已经回到低谷,完全启不到作用。并且,流量到底是因为业务属性很快回落,还是因为扩容不及时导致的流失?


解决方法:

根据自身业务,参考以住流量数量及推广时间,找到规律,提前或定时触发自动扩容。


7、集群移除节点

如何安全地移除节点?这个节点上面部署了你的业务,甚至包括kube-system的东西。