Kubernetes 学习记录

一、kubernetes 入门学习

1.1 部署第一个 Deployment

  • 创建yaml文件 nginx-deploy.yaml
apiVersion: apps/v1	#与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本
kind: Deployment	#该配置的类型,我们使用的是 Deployment
metadata:	        #译名为元数据,即 Deployment 的一些基本属性和信息
  name: nginx-deployment	#Deployment 的名称
  labels:	    #标签,方便后面通过一些方式来选取得标记,key=value 得形式
    app: nginx	#为该Deployment设置key为app,value为nginx的标签
spec:	        #这是关于该Deployment的描述,可以理解为你期待该Deployment在k8s中如何使用
  replicas: 1	#使用该Deployment创建一个应用程序实例
  selector:	    #标签选择器,与上面的标签共同作用,用来选取标签为什么得选择器
    matchLabels: #选择包含标签app:nginx的资源
      app: nginx
  template:	    #这是选择或创建的Pod的模板
    metadata:	#Pod的元数据
      labels:	#Pod的标签,上面的selector即选择包含标签app:nginx的Pod
        app: nginx
    spec:	    #期望Pod实现的功能(即在pod中部署)
      containers:	#生成container,与docker中的container是同一种
      - name: nginx	#container的名称
        image: nginx:1.7.9	#使用镜像nginx:1.7.9创建container,该container默认80端口可访问

  • 执行yaml 文件
kubectl apply -f nginx-deploy.yaml
  • 查询结果
# -n 是 namespace ,命名空间
kubectl get deploy -n default

1.2 查看资源的命令

  • 查看deployment
kubectl get deploy -A
  • 显示资源详细信息
# 查看nginx deployment的详情
kubectl describe deploy nginx -n test
# 查看pod nginx 的详情
kubectl deploy  pod -n test nginx-ds-585449566-hc5jv
  • 查看pod日志(类似与docker logs -f
kubectl logs -f -n test nginx-ds-585449566-hc5jv
  • 进入pod中的容器内
kubectl exec -n test -it nginx-ds-585449566-hc5jv /bin/bash

# 该命令将会在未来被替代
kubectl exec -n test nginx-ds-585449566-hc5jv -- "curl localhost"

1.3 发布应用程序

1.3.1 暴漏服务的三种模式

  • ClusterIP(默认)
    在集群中的内部IP上公布服务,这种Service只在集群内布可以访问
  • NodePort
    使用NAT在集群中每个worker节点同一个端口上公布服务。这种方式下,可以通过集群中任意节点+端口号的方式访问服务,此时ClusterIP的访问方式仍然可用。
  • LoadBalancer
    在云环境中,创建一个集群外部的负载均衡器,使用该负载均衡器的IP作为服务的访问地址,此时ClusterIPNodePort的访问方式仍然可用。

1.3.2 创建一个nginx deploymentService

apiVersion: v1
kind: Service
metadata:
  name: nginx-service	#Service 的名称
  labels:     	#Service 自己的标签
    app: nginx	#为该 Service 设置 key 为 app,value 为 nginx 的标签
spec:	    #这是关于该 Service 的定义,描述了 Service 如何选择 Pod,如何被访问
  selector:	    #标签选择器
    app: nginx	#选择包含标签 app:nginx 的 Pod
  ports:
  - name: nginx-port	#端口的名字
    protocol: TCP	    #协议类型 TCP/UDP
    port: 80	        #集群内的其他容器组可通过 80 端口访问 Service
    nodePort: 32600   #通过任意节点的 32600 端口访问 Service
    targetPort: 80	#将请求转发到匹配 Pod 的 80 端口
  type: NodePort	#Serive的类型,ClusterIP/NodePort/LoaderBalancer

  • 执行yaml文件
kubectl apply -f nginx-svc.yaml -n test

1.4 设置命名空间偏好

kubectl config set-context --current --namespace=test

二 、kubernetes 的进阶学习

2.1 管理 kubernetes 对象

2.1.1 指令性的命令行

当使用指令性的命令行(imperative commands)时,用户通过向kubectl 命令提供参数的方式,直接操作集群中的 Kubernetes 对象。此时,用户无需编写或修改 .yaml 文件。

kubectl create deploy nginx --image nginx:latest -n test

2.1.2 指令性的对象配置

使用指令性的对象配置(imperative object configuration)时,需要向 kubectl 命令指定具体的操作(create,replace,apply,delete等),可选参数以及至少一个配置文件的名字。配置文件中必须包括一个完整的对象的定义,可以是yaml格式,也可以是json 格式。

# 创建对象
kubectl create -f nginx.yaml
# 删除对象
kubectl delete -f nginx.yaml -f redis.yaml
# 替换对象
kubectl replace -f nginx.yaml

2.1.3 声明式的对象配置

当使用声明式的对象配置时,用户操作本地存储的Kubernetes对象配置文件,然而,在将文件传递给kubectl命令时,并不指定具体的操作,由 kubectl自动检查每一个对象的状态并自行决定是创建、更新、还是删除该对象。使用这种方法时,可以直接针对一个或多个文件目录进行操作(对不同的对象可能需要执行不同的操作)

# 查看具体的变更
kubectl diff -f configs/
kubectl apply -f configs

# 递归处理目录中的内容
kubectl diff -R -f configs/
kubectl apply -R -f configs/

2.2 标签和选择器

2.2.1 标签(Label

label是附加在Kubernetes对象上的一组名值对,其意图是按照对用户有意义的方式来标识Kubernetes对象,同时,又不对Kubernetes的核心逻辑产生影响。标签可以用来组织和选择一组Kubernetes对象。您可以在创建Kubernetes对象时为其添加标签,也可以在创建以后再为其添加标签。每个Kubernetes对象可以有多个标签,同一个对象的标签的Key必须唯一,例如:

metadata:
  labels:
    key1: value1
    key2: value2

2.2.2 标签选择器

nameUID 不同,标签不一定是唯一的。通常来讲,会有多个Kubernetes对象包含相同的标签。通过使用标签选择器(label selector),用户/客户端可以选择一组对象。标签选择器(label selector)是 Kubernetes 中最主要的分类和筛选手段。

Kubernetes api server支持两种形式的标签选择器,equality-based基于等式的 和set-based 基于集合的。标签选择器可以包含多个条件,并使用逗号分隔,此时只有满足所有条件的 Kubernetes 对象才会被选中。

  • 基于等式的选择方式
envionment = prod
tier != frontend

也可以使用逗号分隔的两个等式 environment=production,tier!=frontend,此时将选中所有 environmentprodtier 不为front 的对象。

Pod 的节点选择器 为例,下面的Pod 可以被调度到包含标签 accelerator=nvidia-tesla-p100 的节点上:

apiVersion: v1
kind: Pod
metadata:
  name: cuda-test
spec:
  containers:
    - name: cuda-test
      image: "k8s.gcr.io/cuda-vector-add:v0.1"
      resources:
        limits:
          nvidia.com/gpu: 1
  nodeSelector:
    accelerator: nvidia-tesla-p100

  • 基于集合的选择方式
    Set-based 标签选择器可以根据标签名的一组值进行筛选。支持的操作符有三种:innotinexists。例如:
# 选择所有的包含 `environment` 标签且值为 `production` 或 `qa` 的对象
environment in (production, qa)
# 选择所有的 `tier` 标签不为 `frontend` 和 `backend`的对象,或不含 `tier` 标签的对象
tier notin (frontend, backend)
# 选择所有包含 `partition` 标签的对象
partition
# 选择所有不包含 `partition` 标签的对象
!partition

基于集合的选择方式是一个更宽泛的基于等式的选择方式
例如,environment=production 等价于 environment in (production)
environment!=production 等价于 environment notin (production)

基于集合的选择方式可以和基于等式的选择方式可以混合使用,例如: partition in (customerA, customerB),environment!=qa

2.3 如何使用标签和选择器

2.3.1 查询条件

  • 基于等式的选择方式
kubectl get pods -l environment=prod,tier=front
  • 使用基于集合的选择方式
kubectl get pods -l ’environment in (prod),tier in (front)

2.3.2 Service

Service 中 通过spec.selector 字段来选择一组Pod,并将服务请求转发到Pod上

selector:
  component: redis

2.3.3 有些对象支持基于集合的选择方式

JobDeploymentReplicaSetDaemonSet 同时支持基于等式的选择方式和基于集合的选择方式。例如

selector:
  matchLabels:
    component: redis
  matchExpressions:
    - {key: tier, operator: In, values: [cache]}
    - {key: environment, operator: NotIn, values: [dev]}

matchLabels 是一个 {key,value} 组成的 mapmap 中的一个{key,value} 条目相当于 matchExpressions 中的一个元素,其 keymapkeyoperatorInvalues 数组则只包含 value 一个元素。matchExpression 等价于基于集合的选择方式,支持的 operatorInNotInExists 和 DoesNotExist。当 operatorInNotIn 时,values 数组不能为空。所有的选择条件都以 AND 的形式合并计算,即所有的条件都满足才可以算是匹配。

2.4 注解annotation

metadata:
  annotations:
    key1: value1
    key2: value2

2.5 字段选择器

所有的对象都支持两个字段是metadata.namemetadata.namespace,在字段选择器中使用不支持的字段,将报错,可以指定多个字段选择器,用 隔开

kubectl get pods --field-selector status.phase=Running -n test

2.6 容器生命周期事件处理postStartpreStop 处理程序

  • 创建 lifecycle-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: nginx
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
      preStop:
        exec:
          command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]

2.7 容器组 - 生命周期

2.7.1 Pod phase

pod phase代表其所处生命周期的阶段。

phase描述
PendingKubernetes 已经创建并确认该Pod。此时可能有两种情况:
- Pod还未完成调度(例如没有合适的节点
- 正在从 docker registry下载镜像
RunningPod 已经被绑定到一个节点,并且该 Pod所有的容器都已经成功创建。其中至少有一个容器正在运行,或者正在启动/重启
SuccededPod 中的所有容器都已经成功终止,并且不会再被重启
FailedPod 中的所有容器都已经终止,至少一个容器终止于失败状态:容器的进程退出码不是 0,或者被系统 kill
Unknow因为某些未知原因,不能确定 Pod 的状态,通常的原因是 masterPod 所在节点之间的通信故障

2.7.2 何时使用健康检查/就绪检查

  • 如果容器中的进程在碰到问题时可以自己 crash,您并不需要执行健康检查;kubelet 可以自动的根据Podrestart policy(重启策略)执行对应的动作

  • 如果您希望在容器的进程无响应后,将容器 kill 掉并重启,则指定一个健康检查 liveness probe,并同时指定 restart policy(重启策略)为 Always 或者 OnFailure

  • 如果您想在探测 Pod 确实就绪之后才向其分发服务请求,请指定一个就绪检查 readiness probe。此时,就绪检查的内容可能和健康检查相同。就绪检查适合如下几类容器:

    • 初始化时需要加载大量的数据、配置文件
    • 启动时需要执行迁移任务
    • 其他

2.7.3 重启策略

定义 Pod或工作负载时,可以指定 restartPolicy,可选的值有:

  • Always (默认值)
  • OnFailure
  • Never

2.8 容器组 - 配置初始化容器

创建一个Pod,该Pod包含一个应用程序容器(工作容器)和一个初始化容器(Init Container)。初始化容器执行结束之后,应用程序容器(工作容器)才开始启动。

apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  containers:
    - name: nginx
      image: nginx:latest
      ports:
        - containerPort: 80
      volumeMounts:
        - name: workdir
          mountPath: /usr/share/nginx/html
  initContainers:
    - name: install
      image: nginx
      command:
        - "/bin/bash"
        - "-c"
      args:
        [
            "echo '<!DOCTYPE html>
            <html lang='en'>
            <head>
            <meta charset='UTF-8'>
            <title>Title</title>
            </head>
            <body>
            <h1>Hello World1</h1>
            </body>
            </html>' > /work-dir/index1.html;
            echo '<!DOCTYPE html>
             <html lang='en'>
             <head>
             <meta charset='UTF-8'>
             <title>Title</title>
             </head>
             <body>
             <h1>Hello World2</h1>
             </body>
             </html>' > /work-dir/index2.html"
        ]
      volumeMounts:
        - name: workdir
          mountPath: "/work-dir"
  dnsPolicy: Default
  volumes:
    - name: workdir
      emptyDir: {}

Pod 中初始化容器和应用程序共享了同一个数据卷,舒适化容器将该共享数据集挂在到/work-dir 路径,应用程序容器将共享数据卷挂在到/usr/share/nginx/html 路径。初始化容器执行command命令后就退出容器。
执行该命令时,初始化容器将结果写入了应用程序nginx服务器对应的html根路径下的index.html

2.9 容器组 - PodDisruptionBudget(PDB)

应用程序管理员可以为每一个应用程序创建 PodDisruptionBudget 对象(PDB)。PDB限制了多副本应用程序在自愿的毁坏情况发生时,最多有多少个副本可以同时停止。例如,一个 web 前端的程序需要确保可用的副本数不低于总副本数的一定比例。属于一种保护机制。

PodDisruptionBudget 包含三个字段:

  • 标签选择器 .spec.selector 用于指定 PDB 适用的Pod。此字段为必填
  • .spec.minAvailable:当完成驱逐时,最少仍然要保留多少个Pod可用。该字段可以是一个整数,也可以是一个百分比
  • .spec.maxUnavailable: 当完成驱逐时,最多可以有多少个 Pod 被终止。该字段可以是一个整数,也可以是一个百分比

在一个 PodDisruptionBudget 中,只能指定maxUnavailableminAvailable 中的一个。 maxUnavailable 只能应用到那些有控制器的 Pod上。

PodDisruptionBudget 并不能真正确保指定数量(或百分比)的Pod始终保持可用。
例如,当 Pod 数量已经为 PDB 中指定的最小数时,某一个节点可能意外宕机,
导致 Pod 数量低于 PDB 中指定的数量。 PodDisruptionBudget 只能保护应用避免受到 自愿毁坏 的影响,
而不是所有原因的毁坏。

使用 minAvailable

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: zookeeper

使用 maxUnavailable

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: zookeeper

查看pdb

kubectl get pdb

2.10 控制器 - ReplicaSet (RS)

kubernetesReplicaSet 用来维护一个数量稳定的 Pod 副本集合,可以保证某种定义一样的 Pod 始终有指定数量的副本数在运行。

2.10.1 ReplicaSet 的工作方式

  • selector: 用于指定哪些Pod 属于该 ReplicaSet的管辖范围
  • replicas: 副本数,用于指定该 ReplicaSet 应该维持多少个 Pod副本
  • templatePod模板,在 ReplicaSet 使用 Pod 模板的定义创建新的 Pod

ReplicaSet 控制器将通过创建或删除 Pod,以使得当前 Pod数量达到 replicas 指定的期望值,通过selector字段的定义,识别哪些 Pod应该由其管理。如果 Pod没有 ownerReference 字段,或者 ownerReference 字段指向的对象不是一个控制器,且该 Pod匹配了 ReplicaSetselector,则该 PodownerReference 将被修改为 该 ReplicaSet的引用(包括不是由该RS创建的Pod)。

2.10.2 何时使用 ReplicaSet

ReplicaSet 用来维护一个数量稳定的 Pod 副本集合。Deployment 是一个更高级别的概念,可以管理 ReplicaSet,并提供声明式的更新,以及其他的许多有用的特性。因此,推荐用户总是使用Deployment,而不是直接使用 ReplicaSet,除非您需要一些自定义的更新应用程序的方式,或者您完全不更新应用。
示例:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: nginx
        image: nginx

2.10.3 ReplicaSet 的定义

与其他Kubernetes对象一样,ReplicaSet需要的字段有:

apiVersion:apps/v1
kind:始终为 ReplicaSet
metadata
specReplicaSet的详细定义

PodTemplate
  • .spec.template 字段是一个 Pod Template,为必填字段,且其中必须定义

  • .spec.template.metadata.labels 字段。在前面的ReplicaSet例子中,定义了 label 为 tier: frontend。请小心该字段不要与其他控制器的 selector重合,以免这些控制器尝试接管该 Pod

  • .spec.template.spec.restartPolicy 的默认值为Always

Pod Selector
  • .spec.selector 字段为一个 标签选择器,用于识别可以接管哪些 Pod。在前面的例子中,标签选择器为
Replicas
  • spec.replicas 字段用于指定同时运行的 Pod 的副本数。ReplicaSet 将创建或者删除由其管理的 Pod,以便使副本数与该字段指定的值匹配。

2.10.4 使用ReplicaSet

删除 ReplicaSetPod
kubectl delete -f  nginx-rs.yaml
只删除 ReplicaSet
kubectl delete nginx-rs --casade=false
将Pod 从 ReplicaSet 中隔离

修改Pod 的标签,可以使Pod脱离ReplicaSet的管理

2.11 控制器 - Deployment (deploy)

2.11.1 创建 Deployment

下面的 yaml 文件定义了一个 Deployment,该 Deployment 将创建一个有 3 个 nginx Pod 副本的 ReplicaSet(副本集):

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.7.9
        ports:
        - containerPort: 80

在这个例子中:

  • 将创建一个名为 nginx-deploymentDeployment(部署),名称由 .metadata.name 字段指定
  • Deployment 将创建 3 个Pod副本,副本数量由 .spec.replicas字段指定
  • .spec.selector字段指定了 Deployment 如何找到由它管理的 Pod。此案例中,我们使用了 Pod template 中定义的一个标签(app: nginx)。对于极少数的情况,这个字段也可以定义更加复杂的规则
  • .template 字段包含了如下字段:
    • .template.metadata.labels 字段,指定了 Pod的标签(app: nginx
    • .template.spec.containers[].image字段,表明该 Pod 运行一个容器 nginx:1.7.9
    • .template.spec.containers[].name 字段,表明该容器的名字是 nginx
  1. 执行命令以创建Deployment
kubectl apply -f nginx-ds.yaml

可以为该命令增加 --record选项,此时 kubectl会将kubectl apply -f nginx-ds.yaml --record 写入 Deployment 的 annotation(注解) kubernetes.io/change-cause中。这样,您在将来就可以回顾某一个 Deployment 版本变化的原因

  1. 查看 Deployment 的发布状态(rollout status),执行命令 kubectl rollout status deploy nginx-ds

  2. 查看 Pod 的标签,执行命令kubectl get pods --show-labels

2.11.2 更新 Deployment

  1. 执行以下命令,将容器镜像从 nginx:1.7.9 更新到 nginx:1.9.1
kubectl set image deploy nginx-ds nginx=nginx:1.9.1 --record

或者也可以edit 该 Deployment,并将 .spec.template.spec.containers[0].imagenginx:1.7.9修改为 nginx:1.9.1

kubectl edit deployments.apps nginx-ds
  1. 查看发布更新(rollout)的状态:
kubectl rollout status deployment nginx-ds

2.11.3 回滚 Deployment

检查Deployment 的更新历史
  1. 执行命令 kubectl rollout history deployment nginx-ds 检查 Deployment 的历史版本,输出结果如下所示:
deployment.apps/nginx-ds 
REVISION  CHANGE-CAUSE
2         image update to 1.9.1
3         kubectl apply --filename=nginx-ds.yaml --record=true

CHANGE-CAUSE 是该 revision(版本)创建时从 Deployment 的 annotation kubernetes.io/change-cause 拷贝而来。
可以通过如下方式制定 CHANGE-CAUSE 信息:

  • 为 Deployment 增加注解,kubectl annotate deploy nginx-ds kubernetes.io/change-cause="image update to 1.9.1"
  • 执行 kubectl apply 命令时,增加 --record 选项
  • 手动编辑 Deployment 的 .metadata.annotation 信息
  1. 执行命令 kubectl rollout history deploy nginx-ds --revision=2 查看revision(版本)的详细信息。输出结果如下:
deployment.apps/nginx-ds with revision #2
Pod Template:
  Labels:	app=nginx
	pod-template-hash=694854bbcb
  Annotations:	kubernetes.io/change-cause: image update to 1.9.1
  Containers:
   nginx:
    Image:	nginx:1.9.1
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>
回滚到前一个 revision (版本)
  1. 执行命令 kubectl rollout undo deployment nginx-ds 将当前版本回滚到前一个版本

2.11.4 伸缩 - Deployment

执行伸缩
  • 执行命令 kubectl scale deploy nginx --replicas=3 实现手动伸缩

  • 执行命令kubectl autoscale deployment nginx --max=5 --min=3 --cpu-percent=80 cpu在百分之下最大5个 最小三个副本自动伸缩

  1. 查看horizontalpodautoscalers.autoscaling (hpa)
kubectl get hpa

2.11.5 暂停和继续 - Deployment

  • 执行命令kubectl rollout pause deployment nginx暂停 Deployment
  • 执行命令 kubectl rollout resume deployment nginx 继续改Deployment

2.11.6 清理策略

通过 Deployment 中 .spec.revisionHistoryLimit 字段,可指定为该 Deployment 保留多少个旧的 ReplicaSet。超出该数字的将被在后台进行垃圾回收。该字段的默认值是 10。如果该字段被设为0,Kubernetes 将清理掉该 Deployment 的所有历史版本(revision),因此,您将无法对该 Deployment 执行回滚操作 kubectl rollout undo

2.11.7 部署策略

通过 Deployment 中 .spec.strategy字段,可以指定使用 滚动更新 RollingUpdate 的部署策略还是使用 重新创建 Recreate的部署策略

字段名称可选值字段描述
类型滚动更新,重新创建如果选择重新创建,Deployment将先删除原有副本集中的所有 Pod,然后再创建新的副本集和新的Pod。如此,更新过程中将出现一段应用程序不可用的情况;
最大超出副本数数字或百分比滚动更新过程中,可以超出期望副本数的最大值。该取值可以是一个绝对值(例如:5),也可以是一个相对于期望副本数的百分比(例如:10%);如果填写百分比,则以期望副本数乘以该百分比后向上取整的方式计算对应的绝对值;当最大超出副本数 maxUnavailable 为 0 时,此数值不能为 0;默认值为 25%。例如:假设此值被设定为 30%,当滚动更新开始时,新的副本集(ReplicaSet)可以立刻扩容,但是旧 Pod 和新 Pod 的总数不超过 Deployment 期待副本数(spec.repilcas)的 130%。一旦旧 Pod 被终止后,新的副本集可以进一步扩容,但是整个滚动更新过程中,新旧 Pod 的总数不超过 Deployment期待副本数(spec.repilcas)的 130%。
最大不可用副本数数字或百分比滚动更新过程中,不可用副本数的最大值。该取值可以是一个绝对值(例如:5),也可以是一个相对于期望副本数的百分比(例如:10%);如果填写百分比,则以期望副本数乘以该百分比后向下取整的方式计算对应的绝对值;当最大超出副本数 maxSurge 为 0 时,此数值不能为 0;默认值为 25%;例如:假设此值被设定为 30%,当滚动更新开始时,旧的副本集(ReplicaSet)可以缩容到期望副本数的 70%;在新副本集扩容的过程中,一旦新的 Pod已就绪,旧的副本集可以进一步缩容,整个滚动更新过程中,确保新旧就绪副本数之和不低于期望副本数的 70%。

2.11.8 金丝雀发布(灰度发布)

如果您想使用 Deployment 将最新的应用程序版本发布给一部分用户(或服务器),您可以为每个版本创建一个 Deployment,此时,应用程序的新旧两个版本都可以同时获得生产上的流量。

  • 部署第一个版本
    第一个版本的 Deployment 包含了 3 个Pod副本,Service 通过label selectorapp: nginx 选择对应的 Pod,nginx 的标签为 1.7.9
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.7.9
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    app: nginx
spec:
  selector:
    app: nginx
  ports:
  - name: nginx-port
    protocol: TCP
    port: 80
    nodePort: 32600
    targetPort: 80
  type: NodePort

  • 假如此时想发布新版本nginx 1.8.0,可以创建第二个Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment-canary
  labels:
    app: nginx
    track: canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
      track: canary
  template:
    metadata:
      labels:
        app: nginx
        track: canary
    spec:
      containers:
      - name: nginx
        image: nginx:1.8.0

局限性
按照 Kubernetes 默认支持的这种方式进行金丝雀发布,有一定的局限性:

  • 不能根据用户注册时间、地区等请求中的内容属性进行流量分配

  • 同一个用户如果多次调用该Service,有可能第一次请求到了旧版本的 Pod,第二次请求到了新版本的 Pod

Kubernetes中不能解决上述局限性的原因是:Kubernetes Service 只在 TCP 层面解决负载均衡的问题,并不对请求响应的消息内容做任何解析和识别。如果想要更完善地实现金丝雀发布,可以考虑如下三种选择:

  • 业务代码编码实现
  • Spring Cloud 灰度发布
  • Istio 灰度发布

2.12 控制器 - StatefulSet

2.12.1 StatefulSet使用场景

StatefulSet 顾名思义,用于管理 Stateful(有状态)的应用程序。

StatefulSet 管理 Pod 时,确保其 Pod 有一个按顺序增长的 ID。

Deployment 相似,StatefulSet 基于一个 Pod 模板管理其 Pod。与 Deployment最大的不同在于 StatefulSet 始终将一系列不变的名字分配给其 Pod。这些 Pod 从同一个模板创建,但是并不能相互替换:每个 Pod都对应一个特有的持久化存储标识。

同其他所有控制器一样,StatefulSet 也使用相同的模式运作:用户在 StatefulSet中定义自己期望的结果,StatefulSet 控制器执行需要的操作,以使得该结果被达成。

场景:

  • 稳定、唯一的网络标识(dnsname)
  • 每个Pod始终对应各自的存储路径(PersistantVolumeClaimTemplate
  • 按顺序地增加副本、减少副本,并在减少副本时执行清理
  • 按顺序自动地执行滚动更新

如果一个应用程序不需要稳定的网络标识,或者不需要按顺序部署、删除、增加副本,您应该考虑使用 Deployment这类无状态(stateless)的控制器。

2.12.2 StatefulSet 的基本信息

创建StatefulSet
  • 一个名为 nginx 的 Headless Service,用于控制网络域
  • 一个名为 web 的StatefulSet,副本数为 3
  • volumeClaimTemplates 提供稳定的存储(每一个 Pod ID 对应自己的存储卷,且Pod重建后,仍然能找到对应的存储卷)
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 # has to match .spec.template.metadata.labels
  serviceName: "nginx"
  replicas: 3 # by default is 1
  template:
    metadata:
      labels:
        app: nginx # has to match .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

2.13 控制器 - DaemonSet

2.14 Service

2.14.1 Service 概述

  • 为何需要Service
    KubernetesPod 是随时可以消亡的(节点故障、容器内应用程序错误等原因)。如果使用Deployment运行您的应用程序,Deployment 将会在 Pod 消亡后再创建一个新的Pod以维持所需要的副本数。每一个 Pod有自己的 IP 地址,然而,对于 Deployment而言,对应Pod 集合是动态变化的。

  • Kubernetes Service
    Kubernetes 中 Service 是一个 API 对象,通过 kubectl + YAML 或者 Kuboard,定义一个 Service,可以将符合 Service 指定条件的 Pod 作为可通过网络访问的服务提供给服务调用者。
    Service 是 Kubernetes 中的一种服务发现机制:

    • Pod 有自己的 IP 地址
    • Service 被赋予一个唯一的 dns name
    • Service 通过 label selector 选定一组 Pod
    • Service 实现负载均衡,可将请求均衡分发到选定这一组 Pod 中

2.14.2 Service 的详细描述

创建 Service
  • 每个Pod都监听9376 TCP端口
  • 每个Pod都有标签 app=MyApp
apiVersion: v1 
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

  • Kubernetes 将为该 Service 分配一个 IP 地址(ClusterIP集群内 IP),供 Service Proxy 使用
  • Kubernetes 将不断扫描符合该 selectorPod,并将最新的结果更新到与 Service同名 my-service 的 Endpoint 对象中。
    TIP
  • Pod 的定义中,Port可能被赋予了一个名字,您可以在 ServicetargetPort字段引用这些名字,而不是直接写端口号。这种做法可以使得您在将来修改后端程序监听的端口号,而无需影响到前端程序。
  • Service 的默认传输协议是 TCP,您也可以使用其他 支持的传输协议。
    Kubernetes Service 中,可以定义多个端口,不同的端口可以使用相同或不同的传输协议。
多端口得Service

可以在一个 Service 对象中定义多个端口,但必须为每个端口定义一个名字

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      nodePort: 32080
      targetPort: 9376
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9377
      # nodeport 模式
  type: NodePort

2.14.3 Ingress Nginx 暴露服务

Ingress

Ingress 是Kubernetes 的一种API对象,将集群内部的Service通过HTTP/HTTPS 方式暴露到集群外部,并通过规则定义HTTP/HTTPS 的路由。Ingress 具备如下特性:集群外部可访问URL,负载均衡,SSL Termination,按域路由。

Ingress Controller

Ingress Controller (通常需要负载均衡器配合)负责实现Ingress API 对象所声明的能力。
使用 Ingress Nginx实现 Ingress Controller

  • 安装 Helm 3 (Kubernetes包管理工具)
wget https://get.helm.sh/helm-v3.4.1-linux-amd64.tar.gz
tar zxvf helm-v3.4.1-linux-amd64.tar.gz
mv linux-amd64/helm /usr/local/bin/helm
  • 使用 Helm 安装 Ingress Nginx
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm pull ingress-nginx/ingress-nginx
tar -zxvf ingress-nginx-3.12.0.tgz
# 编辑valuse.yaml 文件
vim values.yaml
## nginx configuration
## Ref: https://github.com/kubernetes/ingress-nginx/blob/master/controllers/nginx/configuration.md
##
controller:
  image:
    repository: registry.cn-hangzhou.aliyuncs.com/k8s_gcr_io_allen/ingress-nginx-controller
    tag: "v0.41.2"
    pullPolicy: IfNotPresent
    # www-data -> uid 101
    runAsUser: 101
    allowPrivilegeEscalation: true

  # Configures the ports the nginx-controller listens on
  containerPort:
    http: 80
    https: 443

  # Will add custom configuration options to Nginx https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/
  config: {}

  ## Annotations to be added to the controller config configuration configmap
  ##
  configAnnotations: {}

  # Will add custom headers before sending traffic to backends according to https://github.com/kubernetes/ingress-nginx/tree/master/docs/examples/customization/custom-headers
  proxySetHeaders: {}

  # Will add custom headers before sending response traffic to the client according to: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#add-headers
  addHeaders: {}

  # Optionally customize the pod dnsConfig.
  dnsConfig: {}

  # Optionally change this to ClusterFirstWithHostNet in case you have 'hostNetwork: true'.
  # By default, while using host network, name resolution uses the host's DNS. If you wish nginx-controller
  # to keep resolving names inside the k8s network, use ClusterFirstWithHostNet.
  dnsPolicy: ClusterFirst

  # Bare-metal considerations via the host network https://kubernetes.github.io/ingress-nginx/deploy/baremetal/#via-the-host-network
  # Ingress status was blank because there is no Service exposing the NGINX Ingress controller in a configuration using the host network, the default --publish-service flag used in standard cloud setups does not apply
  reportNodeInternalIp: false

  # Required for use with CNI based kubernetes installations (such as ones set up by kubeadm),
  # since CNI and hostport don't mix yet. Can be deprecated once https://github.com/kubernetes/kubernetes/issues/23920
  # is merged
  hostNetwork: false

  ## Use host ports 80 and 443
  ## Disabled by default
  ##
  hostPort:
    enabled: false
    ports:
      http: 80
      https: 443

  ## Election ID to use for status update
  ##
  electionID: ingress-controller-leader

  ## Name of the ingress class to route through this controller
  ##
  ingressClass: nginx

  # labels to add to the pod container metadata
  podLabels: {}
  #  key: value

  ## Security Context policies for controller pods
  ##
  podSecurityContext: {}

  ## See https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ for
  ## notes on enabling and using sysctls
  ###
  sysctls: {}
  # sysctls:
  #   "net.core.somaxconn": "8192"

  ## Allows customization of the source of the IP address or FQDN to report
  ## in the ingress status field. By default, it reads the information provided
  ## by the service. If disable, the status field reports the IP address of the
  ## node or nodes where an ingress controller pod is running.
  publishService:
    enabled: true
    ## Allows overriding of the publish service to bind to
    ## Must be <namespace>/<service_name>
    ##
    pathOverride: ""

  ## Limit the scope of the controller
  ##
  scope:
    enabled: false
    namespace: ""   # defaults to .Release.Namespace

  ## Allows customization of the configmap / nginx-configmap namespace
  ##
  configMapNamespace: ""   # defaults to .Release.Namespace

  ## Allows customization of the tcp-services-configmap
  ##
  tcp:
    configMapNamespace: ""   # defaults to .Release.Namespace
    ## Annotations to be added to the tcp config configmap
    annotations: {}

  ## Allows customization of the udp-services-configmap
  ##
  udp:
    configMapNamespace: ""   # defaults to .Release.Namespace
    ## Annotations to be added to the udp config configmap
    annotations: {}

  ## Additional command line arguments to pass to nginx-ingress-controller
  ## E.g. to specify the default SSL certificate you can use
  ## extraArgs:
#    default-ssl-certificate: "ailuoli-ingress-nginx-secret"
  extraArgs: {}

  ## Additional environment variables to set
  extraEnvs: []
  # extraEnvs:
  #   - name: FOO
  #     valueFrom:
  #       secretKeyRef:
  #         key: FOO
  #         name: secret-resource

  ## DaemonSet or Deployment
  ##
  kind: DaemonSet

  ## Annotations to be added to the controller Deployment or DaemonSet
  ##
  annotations: {}
  #  keel.sh/pollSchedule: "@every 60m"

  ## Labels to be added to the controller Deployment or DaemonSet
  ##
  labels: {}
  #  keel.sh/policy: patch
  #  keel.sh/trigger: poll


  # The update strategy to apply to the Deployment or DaemonSet
  ##
  updateStrategy: {}
  #  rollingUpdate:
  #    maxUnavailable: 1
  #  type: RollingUpdate

  # minReadySeconds to avoid killing pods before we are ready
  ##
  minReadySeconds: 0


  ## Node tolerations for server scheduling to nodes with taints
  ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
  ##
  tolerations: []
  #  - key: "key"
  #    operator: "Equal|Exists"
  #    value: "value"
  #    effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)"

  ## Affinity and anti-affinity
  ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity
  ##
  affinity: {}
    # # An example of preferred pod anti-affinity, weight is in the range 1-100
    # podAntiAffinity:
    #   preferredDuringSchedulingIgnoredDuringExecution:
    #   - weight: 100
    #     podAffinityTerm:
    #       labelSelector:
    #         matchExpressions:
    #         - key: app.kubernetes.io/name
    #           operator: In
    #           values:
    #           - ingress-nginx
    #         - key: app.kubernetes.io/instance
    #           operator: In
    #           values:
    #           - ingress-nginx
    #         - key: app.kubernetes.io/component
    #           operator: In
    #           values:
    #           - controller
    #       topologyKey: kubernetes.io/hostname

    # # An example of required pod anti-affinity
    # podAntiAffinity:
    #   requiredDuringSchedulingIgnoredDuringExecution:
    #   - labelSelector:
    #       matchExpressions:
    #       - key: app.kubernetes.io/name
    #         operator: In
    #         values:
    #         - ingress-nginx
    #       - key: app.kubernetes.io/instance
    #         operator: In
    #         values:
    #         - ingress-nginx
    #       - key: app.kubernetes.io/component
    #         operator: In
    #         values:
    #         - controller
    #     topologyKey: "kubernetes.io/hostname"

  ## Topology spread constraints rely on node labels to identify the topology domain(s) that each Node is in.
  ## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
  ##
  topologySpreadConstraints: []
    # - maxSkew: 1
    #   topologyKey: failure-domain.beta.kubernetes.io/zone
    #   whenUnsatisfiable: DoNotSchedule
    #   labelSelector:
    #     matchLabels:
    #       app.kubernetes.io/instance: ingress-nginx-internal

  ## terminationGracePeriodSeconds
  ## wait up to five minutes for the drain of connections
  ##
  terminationGracePeriodSeconds: 300

  ## Node labels for controller pod assignment
  ## Ref: https://kubernetes.io/docs/user-guide/node-selection/
  ##
  nodeSelector:
    kubernetes.io/os: linux
    ingressApp: ingress
  ## Liveness and readiness probe values
  ## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes
  ##
  livenessProbe:
    failureThreshold: 5
    initialDelaySeconds: 10
    periodSeconds: 10
    successThreshold: 1
    timeoutSeconds: 1
    port: 10254
  readinessProbe:
    failureThreshold: 3
    initialDelaySeconds: 10
    periodSeconds: 10
    successThreshold: 1
    timeoutSeconds: 1
    port: 10254

  # Path of the health check endpoint. All requests received on the port defined by
  # the healthz-port parameter are forwarded internally to this path.
  healthCheckPath: "/healthz"

  ## Annotations to be added to controller pods
  ##
  podAnnotations: {}

  replicaCount: 1

  minAvailable: 1

  # Define requests resources to avoid probe issues due to CPU utilization in busy nodes
  # ref: https://github.com/kubernetes/ingress-nginx/issues/4735#issuecomment-551204903
  # Ideally, there should be no limits.
  # https://engineering.indeedblog.com/blog/2019/12/cpu-throttling-regression-fix/
  resources:
  #  limits:
  #    cpu: 100m
  #    memory: 90Mi
    requests:
      cpu: 100m
      memory: 90Mi

  # Mutually exclusive with keda autoscaling
  autoscaling:
    enabled: false
    minReplicas: 1
    maxReplicas: 11
    targetCPUUtilizationPercentage: 50
    targetMemoryUtilizationPercentage: 50

  autoscalingTemplate: []
  # Custom or additional autoscaling metrics
  # ref: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#support-for-custom-metrics
  # - type: Pods
  #   pods:
  #     metric:
  #       name: nginx_ingress_controller_nginx_process_requests_total
  #     target:
  #       type: AverageValue
  #       averageValue: 10000m

  # Mutually exclusive with hpa autoscaling
  keda:
    apiVersion: "keda.sh/v1alpha1"
  # apiVersion changes with keda 1.x vs 2.x
  # 2.x = keda.sh/v1alpha1
  # 1.x = keda.k8s.io/v1alpha1
    enabled: false
    minReplicas: 1
    maxReplicas: 11
    pollingInterval: 30
    cooldownPeriod: 300
    restoreToOriginalReplicaCount: false
    triggers: []
 #     - type: prometheus
 #       metadata:
 #         serverAddress: http://<prometheus-host>:9090
 #         metricName: http_requests_total
 #         threshold: '100'
 #         query: sum(rate(http_requests_total{deployment="my-deployment"}[2m]))

    behavior: {}
 #     scaleDown:
 #       stabilizationWindowSeconds: 300
 #       policies:
 #       - type: Pods
 #         value: 1
 #         periodSeconds: 180
 #     scaleUp:
 #       stabilizationWindowSeconds: 300
 #       policies:
 #       - type: Pods
 #         value: 2
 #         periodSeconds: 60

  ## Enable mimalloc as a drop-in replacement for malloc.
  ## ref: https://github.com/microsoft/mimalloc
  ##
  enableMimalloc: true

  ## Override NGINX template
  customTemplate:
    configMapName: ""
    configMapKey: ""

  service:
    enabled: true

    annotations: {}
    labels: {}
    # clusterIP: ""

    ## List of IP addresses at which the controller services are available
    ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips
    ##
    externalIPs: []

    # loadBalancerIP: ""
    loadBalancerSourceRanges: []

    enableHttp: true
    enableHttps: true

    ## Set external traffic policy to: "Local" to preserve source IP on
    ## providers supporting it
    ## Ref: https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer
    # externalTrafficPolicy: ""

    # Must be either "None" or "ClientIP" if set. Kubernetes will default to "None".
    # Ref: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies
    # sessionAffinity: ""

    # specifies the health check node port (numeric port number) for the service. If healthCheckNodePort isn’t specified,
    # the service controller allocates a port from your cluster’s NodePort range.
    # Ref: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip
    # healthCheckNodePort: 0

    ports:
      http: 80
      https: 443

    targetPorts:
      http: 80
      https: 443

    type: NodePort

    # type: NodePort
    # nodePorts:
    #   http: 32080
    #   https: 32443
    #   tcp:
    #     8080: 32808
    nodePorts:
      http: "80"
      https: "443"
      tcp:
        8080: 8080
      udp: {}

    ## Enables an additional internal load balancer (besides the external one).
    ## Annotations are mandatory for the load balancer to come up. Varies with the cloud service.
    internal:
      enabled: false
      annotations: {}

      ## Restrict access For LoadBalancer service. Defaults to 0.0.0.0/0.
      loadBalancerSourceRanges: []

      ## Set external traffic policy to: "Local" to preserve source IP on
      ## providers supporting it
      ## Ref: https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer
      # externalTrafficPolicy: ""

  extraContainers: []
  ## Additional containers to be added to the controller pod.
  ## See https://github.com/lemonldap-ng-controller/lemonldap-ng-controller as example.
  #  - name: my-sidecar
  #    image: nginx:latest
  #  - name: lemonldap-ng-controller
  #    image: lemonldapng/lemonldap-ng-controller:0.2.0
  #    args:
  #      - /lemonldap-ng-controller
  #      - --alsologtostderr
  #      - --configmap=$(POD_NAMESPACE)/lemonldap-ng-configuration
  #    env:
  #      - name: POD_NAME
  #        valueFrom:
  #          fieldRef:
  #            fieldPath: metadata.name
  #      - name: POD_NAMESPACE
  #        valueFrom:
  #          fieldRef:
  #            fieldPath: metadata.namespace
  #    volumeMounts:
  #    - name: copy-portal-skins
  #      mountPath: /srv/var/lib/lemonldap-ng/portal/skins

  extraVolumeMounts: []
  ## Additional volumeMounts to the controller main container.
  #  - name: copy-portal-skins
  #   mountPath: /var/lib/lemonldap-ng/portal/skins

  extraVolumes: []
  ## Additional volumes to the controller pod.
  #  - name: copy-portal-skins
  #    emptyDir: {}

  extraInitContainers: []
  ## Containers, which are run before the app containers are started.
  # - name: init-myservice
  #   image: busybox
  #   command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']

  admissionWebhooks:
    annotations: {}
    enabled: false
    failurePolicy: Fail
    # timeoutSeconds: 10
    port: 8443
    certificate: "/usr/local/certificates/cert"
    key: "/usr/local/certificates/key"
    namespaceSelector: {}
    objectSelector: {}

    service:
      annotations: {}
      # clusterIP: ""
      externalIPs: []
      # loadBalancerIP: ""
      loadBalancerSourceRanges: []
      servicePort: 443
      type: ClusterIP

    patch:
      enabled: true
      image:
        repository: docker.io/jettech/kube-webhook-certgen
        tag: v1.5.0
        pullPolicy: IfNotPresent
      ## Provide a priority class name to the webhook patching job
      ##
      priorityClassName: ""
      podAnnotations: {}
      nodeSelector: {}
      tolerations: []
      runAsUser: 2000

  metrics:
    port: 10254
    # if this port is changed, change healthz-port: in extraArgs: accordingly
    enabled: false

    service:
      annotations: {}
      # prometheus.io/scrape: "true"
      # prometheus.io/port: "10254"

      # clusterIP: ""

      ## List of IP addresses at which the stats-exporter service is available
      ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips
      ##
      externalIPs: []

      # loadBalancerIP: ""
      loadBalancerSourceRanges: []
      servicePort: 9913
      type: ClusterIP
      # externalTrafficPolicy: ""
      # nodePort: ""

    serviceMonitor:
      enabled: false
      additionalLabels: {}
      namespace: ""
      namespaceSelector: {}
      # Default: scrape .Release.Namespace only
      # To scrape all, use the following:
      # namespaceSelector:
      #   any: true
      scrapeInterval: 30s
      # honorLabels: true
      targetLabels: []
      metricRelabelings: []

    prometheusRule:
      enabled: false
      additionalLabels: {}
      # namespace: ""
      rules: []
        # # These are just examples rules, please adapt them to your needs
        # - alert: NGINXConfigFailed
        #   expr: count(nginx_ingress_controller_config_last_reload_successful == 0) > 0
        #   for: 1s
        #   labels:
        #     severity: critical
        #   annotations:
        #     description: bad ingress config - nginx config test failed
        #     summary: uninstall the latest ingress changes to allow config reloads to resume
        # - alert: NGINXCertificateExpiry
        #   expr: (avg(nginx_ingress_controller_ssl_expire_time_seconds) by (host) - time()) < 604800
        #   for: 1s
        #   labels:
        #     severity: critical
        #   annotations:
        #     description: ssl certificate(s) will expire in less then a week
        #     summary: renew expiring certificates to avoid downtime
        # - alert: NGINXTooMany500s
        #   expr: 100 * ( sum( nginx_ingress_controller_requests{status=~"5.+"} ) / sum(nginx_ingress_controller_requests) ) > 5
        #   for: 1m
        #   labels:
        #     severity: warning
        #   annotations:
        #     description: Too many 5XXs
        #     summary: More than 5% of all requests returned 5XX, this requires your attention
        # - alert: NGINXTooMany400s
        #   expr: 100 * ( sum( nginx_ingress_controller_requests{status=~"4.+"} ) / sum(nginx_ingress_controller_requests) ) > 5
        #   for: 1m
        #   labels:
        #     severity: warning
        #   annotations:
        #     description: Too many 4XXs
        #     summary: More than 5% of all requests returned 4XX, this requires your attention

  ## Improve connection draining when ingress controller pod is deleted using a lifecycle hook:
  ## With this new hook, we increased the default terminationGracePeriodSeconds from 30 seconds
  ## to 300, allowing the draining of connections up to five minutes.
  ## If the active connections end before that, the pod will terminate gracefully at that time.
  ## To effectively take advantage of this feature, the Configmap feature
  ## worker-shutdown-timeout new value is 240s instead of 10s.
  ##
  lifecycle:
    preStop:
      exec:
        command:
          - /wait-shutdown

  priorityClassName: ""

## Rollback limit
##
revisionHistoryLimit: 10

# Maxmind license key to download GeoLite2 Databases
# https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-geolite2-databases
maxmindLicenseKey: ""

## Default 404 backend
##
defaultBackend:
  ##
  enabled: false

  image:
    repository: k8s.gcr.io/defaultbackend-amd64
    tag: "1.5"
    pullPolicy: IfNotPresent
    # nobody user -> uid 65534
    runAsUser: 65534
    runAsNonRoot: true
    readOnlyRootFilesystem: true
    allowPrivilegeEscalation: false

  extraArgs: {}

  serviceAccount:
    create: true
    name:
  ## Additional environment variables to set for defaultBackend pods
  extraEnvs: []

  port: 8080

  ## Readiness and liveness probes for default backend
  ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/
  ##
  livenessProbe:
    failureThreshold: 3
    initialDelaySeconds: 30
    periodSeconds: 10
    successThreshold: 1
    timeoutSeconds: 5
  readinessProbe:
    failureThreshold: 6
    initialDelaySeconds: 0
    periodSeconds: 5
    successThreshold: 1
    timeoutSeconds: 5

  ## Node tolerations for server scheduling to nodes with taints
  ## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/
  ##
  tolerations: []
  #  - key: "key"
  #    operator: "Equal|Exists"
  #    value: "value"
  #    effect: "NoSchedule|PreferNoSchedule|NoExecute(1.6 only)"

  affinity: {}

  ## Security Context policies for controller pods
  ## See https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ for
  ## notes on enabling and using sysctls
  ##
  podSecurityContext: {}

  # labels to add to the pod container metadata
  podLabels: {}
  #  key: value

  ## Node labels for default backend pod assignment
  ## Ref: https://kubernetes.io/docs/user-guide/node-selection/
  ##
  nodeSelector: {}

  ## Annotations to be added to default backend pods
  ##
  podAnnotations: {}

  replicaCount: 1

  minAvailable: 1

  resources: {}
  # limits:
  #   cpu: 10m
  #   memory: 20Mi
  # requests:
  #   cpu: 10m
  #   memory: 20Mi

  autoscaling:
    enabled: false
    minReplicas: 1
    maxReplicas: 2
    targetCPUUtilizationPercentage: 50
    targetMemoryUtilizationPercentage: 50

  service:
    annotations: {}

    # clusterIP: ""

    ## List of IP addresses at which the default backend service is available
    ## Ref: https://kubernetes.io/docs/user-guide/services/#external-ips
    ##
    externalIPs: []

    # loadBalancerIP: ""
    loadBalancerSourceRanges: []
    servicePort: 80
    type: ClusterIP

  priorityClassName: ""

## Enable RBAC as per https://github.com/kubernetes/ingress/tree/master/examples/rbac/nginx and https://github.com/kubernetes/ingress/issues/266
rbac:
  create: true
  scope: false

# If true, create & use Pod Security Policy resources
# https://kubernetes.io/docs/concepts/policy/pod-security-policy/
podSecurityPolicy:
  enabled: false

serviceAccount:
  create: true
  name:

## Optional array of imagePullSecrets containing private registry credentials
## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
imagePullSecrets: []
# - name: secretName

# TCP service key:value pairs
# Ref: https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx/examples/tcp
##
tcp: {}
#  8080: "default/example-tcp-svc:9000"

# UDP service key:value pairs
# Ref: https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx/examples/udp
##
udp: {}
#  53: "kube-system/kube-dns:53"

这里选择是要NodePort得方式部署这个ingress nginx,默认方式是使用 LoadBlancer 得,所以需要修改一些配置

  1. controller.image.repository : registry.cn-hangzhou.aliyuncs.com/k8s_gcr_io_allen/ingress-nginx-controller image 得仓库要是可用得,默认的是谷歌得镜像,无法访问。
  2. controller.kind:DaemonSet 这个apiVersion建议使用 DaemonSet
  3. controller.nodeSelector.ingressApp: ingress 这里新增个label 选择部署得节点
  4. controller.service.type:NodePort 这里得类型改为NodePort,并且将端口号暴漏出去,注意这里需要把集群得端口范围改为1-65535
    nodePorts:
      http: "80"
      https: "443"
      tcp:
        8080: 8080
      udp: {}
  • 测试部署一个应用并暴漏出去,并且配置https,云服务器要配置80 和 443 端口得开放
    先创建个secret 用于配置 https。
kubectl create secret tls ailuoli-nginx-secret --cert=ailuoli.cn.crt --key=ailuoli.cn.key -n test

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    app: nginx
spec:
  selector:
    app: nginx
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
    - name: https
      protocol: TCP
      port: 443
      targetPort: 443

---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: my-ingress-for-nginx  # Ingress 的名字,仅用于标识
  annotations:
    # 路径匹配规则,匹配后路径rewrite 为 / ,例如访问 https://ailuoli.cn/nginx,实际得路径匹配到了/nginx规则,路由到真正得服务则是https://ailuoli.cn/
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: nginx
    # 设置http请求强制路由到https
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  rules:                      # Ingress 中定义 L7 路由规则
  - host: ailuoli.cn   # 根据 virtual hostname 进行路由(请使用您自己的域名)
    http:
      paths:                  # 按路径进行路由
      - path: /nginx
        backend:
          serviceName: nginx-service  # 指定后端的 Service 为之前创建的 nginx-service
          servicePort: 80
  tls:
    - hosts:
        - ailuoli.cn
      secretName: ailuoli-ingress-nginx-secret


执行创建 Deployment,Service,Ingress

kubectl apply -f nginx-ingress.yaml

关于 Annotations 注解得作用

2.15 数据卷 Volume

kubernetes volume主要解决得问题

  • 数据持久性:通常情况下,容器运行起来之后,写入到其文件系统的文件暂时性的。当容器崩溃后,kubelet 将会重启该容器,此时原容器运行后写入的文件将丢失,因为容器将重新从镜像创建。
  • 数据共享:同一个 Pod(容器组)中运行的容器之间,经常会存在共享文件/文件夹的需求

2.15.1 数据卷的类型

  • emptyDir
    emptyDir 类型的数据卷在容器组被创建时分配给该容器组,并且直到容器组被移除,该数据卷才被释放。该数据卷初始分配时,始终是个空目录。同一容器组中的不通容器都可以对该目录执行读写操作,并且共享其中的数据,当容器组被删除时,emptyDir数据卷中的数据将被永久删除。
  • nfs
    nfs 类型的数据卷可以加载NFS 到容器组/容器。容器组被移除时,将仅仅umount(写在)NFS数据卷,NFS中的数据仍将被保留。
  • cephfs
    cephfs 数据卷可以挂载一个外部CephFS卷到容器组中。
  • hostPath
    hostPath 类型的数据卷将Pod所在的节点文件系统上某一个文件或文件夹挂载进容器组。
    除了为 hostPath 指定 path 字段以外,您还可以为其指定 type字段,可选的type字段描述如下:
Type 字段取值描述
空字符串(default)用于向后兼容,此时,kubernetes 在挂载 hostPath 数据卷前不会执行任何检查
DirectoryOrCreate如果指定的 hostPath 路径不存在,kubernetes 将在节点的该路径上创建一个空文件夹,权限设置为 0755,与 kubelet 进程具备相同的 group 和 ownership
Directory指定 hostPath 路径必须存在,且是一个文件夹
FileOrCreate如果指定的 hostPath 路径不存在,kubernetes 将在节点的该路径上创建一个空的文件,权限设置为 0644,与 kubelet 进程具备相同的 group 和 ownership
File指定 hostPath 路径必须存在,且是一个文件
Socket指定 hostPath 路径必须存在,且是一个 Unix Socket
CharDevice指定 hostPath 路径必须存在,且是一个 character device
BlockDevice指定 hostPath 路径必须存在,且是一个 block device
  • configMap
    ConfigMap 提供了一种向容器组注入配置信息的途径。ConfigMap中的数据可以被Pod中的容器作为一个数据卷挂载。
    在数据卷中引用ConfigMap时
    • 可以引用整个ConfigMap到数据卷,此时 ConfigMap 中的每一个 key 对应一个文件名,value 对应该文件的内容
    • 只引用 ConfigMap 中的某一个名值对,此时可以将 key 映射成一个新的文件名
  • secret
    secret 数据卷可以用来注入敏感信息(例如密码)到容器组。您可以将敏感信息存入 kubernetes secret 对象,并通过 Volume(数据卷)以文件的形式挂载到容器组(或容器)。secret 数据卷使用 tmpfs(基于 RAM 的文件系统)挂载。
  • persistentVolumeClaim
    persistentVolumeClaim数据卷用来挂载 PersistentVolume 存储卷。PersistentVolume 存储卷为用户提供了一种在无需关心具体所在云环境的情况下”声明“ 所需持久化存储的方式。

2.15.2 数据卷挂载

数据卷内子路径

我们需要在同一个 Pod 的不同容器间共享数据卷。使用 volumeMounts.subPath 属性,可以使容器在挂载数据卷时指向数据卷内部的一个子路径,而不是直接指向数据卷的根路径。
下面的例子中,一个 LAMP(Linux Apache Mysql PHP)应用的 Pod 使用了一个共享数据卷,HTML 内容映射到数据卷的 html 目录,数据库的内容映射到了 mysql目录

apiVersion: v1
kind: Pod
metadata:
  name: my-lamp-site
spec:
    containers:
    - name: mysql
      image: mysql
      env:
      - name: MYSQL_ROOT_PASSWORD
        value: "rootpasswd"
      volumeMounts:
      - mountPath: /var/lib/mysql
        name: site-data
        subPath: mysql
        readOnly: false
    - name: php
      image: php:7.0-apache
      volumeMounts:
      - mountPath: /var/www/html
        name: site-data
        subPath: html
        readOnly: false
    volumes:
    - name: site-data
      persistentVolumeClaim:
        claimName: my-lamp-site-data

通过环境变量指定数据卷内子路径

使用 volumeMounts.subPathExpr 字段,可以通过容器的环境变量指定容器内路径。使用此特性时,必须启用 VolumeSubpathEnvExpansion
如下面的例子,该 Pod 使用 subPathExprhostPath 数据卷 /var/log/pods 中创建了一个目录 pod1(该参数来自于Pod的名字)。此时,宿主机目录 /var/log/pods/pod1 挂载到了容器的 /logs 路径:

apiVersion: v1
kind: Pod
metadata:
  name: pod1
spec:
  containers:
  - name: container1
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.name
    image: busybox
    command: [ "sh", "-c", "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ]
    volumeMounts:
    - name: workdir1
      mountPath: /logs
      subPathExpr: $(POD_NAME)
      readOnly: false
  restartPolicy: Never
  volumes:
  - name: workdir1
    hostPath:
      path: /var/log/pods

挂载传播

数据卷的挂载传播(Mount Propagation)由 Pod 的 spec.containers[*].volumeMounts.mountPropagation 字段控制。可选的取值有:

  • None: 默认值。在数据卷被挂载到容器之后,此数据卷不会再接受任何后续宿主机或其他容器挂载到该数据卷对应目录下的子目录的挂载。同样的,在容器中向该数据卷对应目录挂载新目录时,宿主机也不能看到。对应 Linux 的 private mount propagation 选项 。
  • HostToContainer: 在数据卷被挂载到容器之后,宿主机向该数据卷对应目录添加挂载时,对容器是可见的。对应 Linux 的 rslave mount propagation 选项。
  • Bidirectional: 在数据卷被挂载到容器之后,宿主机向该数据卷对应目录添加挂载时,对容器是可见的;同时,从容器中向该数据卷创建挂载,同样也对宿主机可见。对应 Linux 的 rshared mount propagation 选项

2.15.3 PersistentVolume

与管理计算资源相比,管理存储资源是一个完全不同的问题。为了更好的管理存储,Kubernetes 引入了 PersistentVolumePersistentVolumeClaim 两个概念,将存储管理抽象成如何提供存储以及如何使用存储两个关注点。

  • PersistentVolume(PV 存储卷)是集群中的一块存储空间,由集群管理员管理、或者由 Storage Class(存储类)自动管理。PV(存储卷)和 node(节点)一样,是集群中的资源(kubernetes 集群由存储资源和计算资源组成)。PersistentVolumeClaim(存储卷声明)是一种类型的 Volume(数据卷),PersistentVolumeClaim(存储卷声明)引用的 PersistentVolume(存储卷)有自己的生命周期,该生命周期独立于任何使用它的容器组。PersistentVolume(存储卷)描述了如何提供存储的细节信息(NFS、cephfs等存储的具体参数)。
  • PersistentVolumeClaim(PVC 存储卷声明)代表用户使用存储的请求。Pod 容器组消耗 node 计算资源,PVC 存储卷声明消耗 PersistentVolume 存储资源。Pod 容器组可以请求特定数量的计算资源(CPU / 内存);PersistentVolumeClaim 可以请求特定大小/特定访问模式(只能被单节点读写/可被多节点只读/可被多节点读写)的存储资源。
存储卷和存储卷声明的关系
  • PersistentVolume 是集群中的存储资源,通常由集群管理员创建和管理
  • StorageClass 用于对PersistentVolume进行分类,如果正确配置,StorageClass 也可以根据 PersistentVolumeClaim的请求动态创建 Persistent Volume
  • PersistentVolumeClaim 是使用该资源的请求,通常由应用程序提出请求,并指定对应的 StorageClass 和需求的空间大小
  • PersistentVolumeClaim 可以做为数据卷的一种,被挂载到容器组/容器中使用
为PVC 提供 PV的两种方式
  • 静态提供static
    集群管理员实现创建好一系列 PersistentVolume,它们包含了可供集群中应用程序使用的关于实际存储的具体信息。
  • 动态提供Dynamic
    在配置有合适的 StorageClass(存储类)且 PersistentVolumeClaim 关联了该 StorageClass的情况下,kubernetes 集群可以为应用程序动态创建 PersistentVolume
绑定Binding

如果一个 PersistentVolume 是动态提供给一个新的 PersistentVolumeClaim,Kubernetes master 会始终将其绑定到该PersistentVolumeClaim。除此之外,应用程序将被绑定一个不小于(可能大于)其 PersistentVolumeClaim中请求的存储空间大小的 PersistentVolume。一旦绑定,PersistentVolumeClaim将拒绝其他PersistentVolume的绑定关系。PVC 与 PV 之间的绑定关系是一对一的映射。

使用 Using

对于 Pod 容器组来说,PersistentVolumeClaim 存储卷声明是一种类型的 Volume 数据卷。Kubernetes 集群将 PersistentVolumeClaim 所绑定的 PersistentVolume 挂载到容器组供其使用。

Storage Object in Use Protection
  • 使用中保护(Storage Object in Use Protection)的目的是确保正在被容器组使用的 PersistentVolumeClaim 以及其绑定的 PersistentVolume 不能被系统删除,以避免可能的数据丢失。
  • 如果用户删除一个正在使用中的 PersistentVolumeClaim,则该 PVC 不会立即被移除掉,而是推迟到该 PVC 不在被任何容器组使用时才移除;同样的如果管理员删除了一个已经绑定到 PVC 的 PersistentVolume,则该 PV 也不会立刻被移除掉,而是推迟到其绑定的 PVC 被删除后才移除掉。
回收 Reclaiming

当用户不在需要其数据卷时,可以删除掉其 PersistentVolumeClaim,此时其对应的 PersistentVolume将被集群回收并再利用。Kubernetes 集群根据 PersistentVolume 中的 reclaim policy(回收策略)决定在其被回收时做对应的处理。当前支持的回收策略有:Retained(保留)、Recycled(重复利用)、Deleted(删除)

  • 保留 Retain
    保留策略需要集群管理员手工回收该资源。当绑定的 PersistentVolumeClaim 被删除后,PersistentVolume 仍然存在,并被认为是”已释放“。但是此时该存储卷仍然不能被其他 PersistentVolumeClaim 绑定,因为前一个绑定的 PersistentVolumeClaim 对应容器组的数据还在其中。集群管理员可以通过如下步骤回收该 PersistentVolume:

    • 删除该 PersistentVolume。PV 删除后,其数据仍然存在于对应的外部存储介质中(nfs、cefpfs、glusterfs 等)
    • 手工删除对应存储介质上的数据
    • 手工删除对应的存储介质,您也可以创建一个新的 PersistentVolume 并再次使用该存储介质
  • 删除 Delete
    删除策略将从 kubernete 集群移除 PersistentVolume 以及其关联的外部存储介质(云环境中的 AWA EBS、GCE PD、Azure Disk 或 Cinder volume)。

  • 再利用 Recycle
    再利用策略将在 PersistentVolume回收时,执行一个基本的清除操作(rm -rf /thevolume/*),并使其可以再次被新的 PersistentVolumeClaim 绑定。
    集群管理员也可以自定义一个 recycler pod template,用于执行清除操作

2.15.4 使用 nfs 做存储 创建 StorageClass

搭建 nfs server
yum install -y nfs-utils
mkdir /data/nfsdata/
chmod 777 /data/nfsdata/

编辑 /etc/exports 文件,内容如下

/data/nfsdata/ *(rw,sync,insecure,no_subtree_check,no_root_squash)

其中 *可以指定那些 ip 可以共享该文件夹

systemctl enable rpcbind
systemctl enable nfs-server

systemctl start rpcbind
systemctl start nfs-server

添加公网 ip 作为网卡

cat > /etc/sysconfig/network-scripts/ifcfg-eth0:1 <<EOF
BOOTPROTO=static
DEVICE=eth0:1
IPADDR=120.53.234.127
PREFIX=32
TYPE=Ethernet
USERCTL=no
ONBOOT=yes
EOF
客户端测试 nfs server
yum install -y nfs-utils
# 检查服务端是否设有共享目录
showmount -e  ailuoli.cn

执行命令挂载nfs服务器上的共享目录到本机路径

mkdir /data/share
mount -t nfs ailuoli.cn:/data/nfsdata /data/share
#写一个测试文件
echo "hello nfs server" > /data/share/test.txt

验证nfs server 有没有

cat /data/nfsdata/test.txt

取消挂载

umount /data/share
创建 Storage Class 来自动创建 PVC

使用 helm部署 nfs-client-provisioner

helm repo add stable https://charts.helm.sh/stable
helm pull stable/nfs-client-provisioner
tar -zxvf nfs-client-provisioner-1.2.11.tgz

修改 values.yaml

nfs:
  server: ailuoli.cn
  path: /data/nfsdata
... 删除策略
  reclaimPolicy: Retain

执行创建 nfs-client

helm install nfs-client .
# 查看创建好的 Storage Class
kubectl get sc

测试下是否可以正常创建 PVC,创建 pvc-test.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc1
spec:
  storageClassName: nfs-client
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

执行文件

kubectl apply -f pvc-test.yaml

查看 pvc

kubectl get pvc

结果如下,status 为 Bound 就为正常

NAME           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
data-mysql-0   Bound    pvc-be7fdb16-e579-4857-b2ef-e5015b56e6f8   8Gi        RWO            nfs-client     9h
k8s 1.20 以上版本遇到 PVC Pending 问题

编辑该文件

vim /etc/kubernetes/manifests/kube-apiserver.yaml

添加此配置

- --feature-gates=RemoveSelfLink=false

2.16 ConfigMap

可以使用 kubectl create configmap 命令基于目录,文件,字面值来创建

kubectl create configmap <map-name> <data-source>
基于目录创建configMap
kubectl create configmap test-config --from-file=./dev/

查看configmaps

kubectl get configmaps test-config -o yaml
apiVersion: v1
data:
  ailuoli-dev.properties: from=git-dev-1.0-t
  service1-dev.properties: profile=dev-1.0
kind: ConfigMap
metadata:
  creationTimestamp: "2020-12-16T03:17:38Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:ailuoli-dev.properties: {}
        f:service1-dev.properties: {}
    manager: kubectl-create
    operation: Update
    time: "2020-12-16T03:17:38Z"
  name: test-config
  namespace: test
  resourceVersion: "2387780"
  selfLink: /api/v1/namespaces/test/configmaps/test-config
  uid: 87f7843a-8230-4bb8-ad22-4085c0b108a9

基于文件创建configMap
kubectl create configmap test-config2 --from-file=./ailuoli-dev.properties

可以使用多个 --from-file 来创建 configMap

kubectl create configmap game-config-2 --from-file=configure-pod-container/configmap/game.properties --from-file=configure-pod-container/configmap/ui.properties

使用 --from-env-file 来创建 configMap

  • Env 文件中的每一行必须为 VAR=VAL 格式。
  • 以#开头的行(即注释)将被忽略。
  • 空行将被忽略。
  • 引号不会被特殊处理(即它们将成为 ConfigMap 值的一部分)。
kubectl create configmap test-env-config --from-env-file=./ailuoli-test.properties
apiVersion: v1
data:
  from: git-test-1.0
kind: ConfigMap
metadata:
  creationTimestamp: "2020-12-16T03:33:00Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:from: {}
    manager: kubectl-create
    operation: Update
    time: "2020-12-16T03:33:00Z"
  name: test-env-config
  namespace: test
  resourceVersion: "2389938"
  selfLink: /api/v1/namespaces/test/configmaps/test-env-config
  uid: 5bfba06b-7073-487f-80e3-a1e6384eeaeb

同样 --from-env-file 也可以使用多个

kubectl create configmap config-multi-env-files \
        --from-env-file=configure-pod-container/configmap/game-env-file.properties \
        --from-env-file=configure-pod-container/configmap/ui-env-file.properties

定义从文件创建 ConfigMap时要使用的键

kubectl create configmap test-config3 --from-file=testailuoli=./ailuoli-dev.properties
apiVersion: v1
data:
  testailuoli: from=Ailuoli-Ailuoli-Ailuoli
kind: ConfigMap
metadata:
  creationTimestamp: "2020-12-16T03:43:05Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:testailuoli: {}
    manager: kubectl-create
    operation: Update
    time: "2020-12-16T03:43:05Z"
  name: test-config3
  namespace: test
  resourceVersion: "2391353"
  selfLink: /api/v1/namespaces/test/configmaps/test-config3
  uid: 9dafdda6-3085-451c-9e41-b02ffe81d367

根据字面值创建 ConfigMap

--from-literal 根据命令行定义文字值:

kubectl create configmap test-config4 --from-literal=hello1=world1 --from-literal=hello2=world2
apiVersion: v1
data:
  hello1: world1
  hello2: world2
kind: ConfigMap
metadata:
  creationTimestamp: "2020-12-16T06:28:51Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:hello1: {}
        f:hello2: {}
    manager: kubectl-create
    operation: Update
    time: "2020-12-16T06:28:51Z"
  name: test-config4
  namespace: test
  resourceVersion: "2414579"
  selfLink: /api/v1/namespaces/test/configmaps/test-config4
  uid: d05fa08b-44ca-4bdf-aaf4-38b47477190a

基于生成器创建 ConfigMap

自1.14 开始,kubectl 开始支持 kustomization.yaml。

cat <<EOF >./kustomization.yaml
configMapGenerator:
- name: test-config5
  files:
  - configmap/config-repos/ailuoli-dev.properties
# - test=configmap/config-repos/ailuoli-dev.properties
# 自定义key
EOF
kubectl apply -k .
apiVersion: v1
data:
  ailuoli-dev.properties: from=Ailuoli-Ailuoli-Ailuoli
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"ailuoli-dev.properties":"from=Ailuoli-Ailuoli-Ailuoli"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test-config5-245dh9m2fd","namespace":"test"}}
  creationTimestamp: "2020-12-16T09:40:42Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:ailuoli-dev.properties: {}
      f:metadata:
        f:annotations:
          .: {}
          f:kubectl.kubernetes.io/last-applied-configuration: {}
    manager: kubectl-client-side-apply
    operation: Update
    time: "2020-12-16T09:40:42Z"
  name: test-config5-245dh9m2fd
  namespace: test
  resourceVersion: "2441461"
  selfLink: /api/v1/namespaces/test/configmaps/test-config5-245dh9m2fd
  uid: 0f97cda8-0c6c-47db-9599-1287e3dea642
使用 ConfigMap 定义容器环境变量
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        env:
          - name: test-config4
            valueFrom:
              configMapKeyRef:
                key: hello1
                name: test-config4
ConfigMap 数据添加到一个 Volume
apiVersion: v1
kind: Pod
metadata:
  name: dapi-test-pod
spec:
  containers:
    - name: test-container
      image: k8s.gcr.io/busybox
      command: [ "/bin/sh", "-c", "ls /etc/config/" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        # Provide the name of the ConfigMap containing the files you want
        # to add to the container
        name: special-config
        items:
 #使用 path 字段为特定的 ConfigMap 项目指定预期的文件路径。 在这里,SPECIAL_LEVEL 将挂载在 config-volume 数据卷中 /etc/config/keys 目录下。
       - key: SPECIAL_LEVEL
         path: keys       
  restartPolicy: Never

2.17 管理容器资源

apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

2.18 污点和容忍

Pod 中存在属性 Node selector / Node affinity 用于将Pod指定到合适的节点。相对的,节点中存在 污点属性 taints,使得节点可以排斥某些 Pod

污点和容忍(taints and )成对工作,以确保Pod不会被调度到不合适的节点上。

  • 可以为节点增加污点(taints, 一个节点可有 多个污点)
  • 可以为Pod增加容忍 (toleration, 一个节点可以有多个容忍)
  • 如果节点上存在污点,则该节点不会接受任何不能容忍该污点得Pod

2.18.1 向节点添加污点

  • 执行 kubectl taint 命令,可以向节点添加污点
kubectl taint nodes node1 key=value:NoSchedule

该命令向节点 node1 添加了一个污点。污点是一个键值对,污点得键为key,值为value ,污点效果为 NoSchedule。此污点意味着kubernetes 不会向该节点调度任何Pod,除非该 Pod 有一个匹配得容忍(toleration

  • 执行以下命令去除污点
kubectl taint nodes node1 key:NoSchedule-

2.18.2 向Pod添加容忍

PodSpec 中有一个tolerations 字段,可用于向Pod添加容忍。

  • 容忍1:
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"

  • 容忍2:
tolerations:
- key: "key"
  operator: "Exists"
  effect: "NoSchedule"
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"

2.18.3 污点和容忍匹配

当满足如下条件时,k8s 认为容忍和污点匹配:

  • 键(key) 相同
  • 效果(effect)相同
  • 污点 operator 为:
    • Exists (此时污点中不该指定 value)
    • 或者 Equal (此时容忍的 value 应与污点的 value 相同)
      如果不指定 operator ,则默认为 Equal

特殊情况:

  • 容忍中未定义 key 但是定义了 operatorExists,Kubernetes 认为此容忍匹配所有的污点.
tolerations:
- operator: "Exists"
  • 容忍中未定义 effect但是定义了key,Kubernetes 认为此容忍匹配所有effect.
tolerations:
- key: "key"
  operator: "Exists

effect 效果有

  • NoSchedule
  • PreferNoScheduleNoSchedule 更宽容一些,Kubernetes将尽量避免将没有匹配容忍的Pod调度该节点上,但是并不是不可以
  • NoExecute 不能在节点上运行(如果已经运行,将被驱逐 )
    示例:
    给一个Node 添加三个污点
kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule

同时有一个Pod带有两个容忍:

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"

在这个案例中,Pod 上有两个容忍,匹配了节点的前两个污点,只有节点的第三个污点对该 Pod 来说不可忽略,该污点的效果为 NoSchedule

  • Kubernetes 不会将此 Pod 调度到该节点上
  • 如果 Kubernetes 先将 Pod 调度到了该节点,后向该节点添加了第三个污点,则 Pod 将继续在该节点上运行而不会被驱逐(节点上带有 NoExecute 效果的污点已被 Pod 上的第二个容忍匹配,因此被忽略)

可以设置 Pod在多久后被驱逐tolerationSeconds

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600

kubernetes 的节点控制器在碰到某些特定的条件时,将自动为节点添加污点,默认启用。

  • node.kubernetes.io/not-ready: 节点未就绪。对应着 NodeCondition Ready 为 False 的情况
  • node.kubernetes.io/unreachable: 节点不可触达。对应着 NodeCondition Ready 为 Unknown 的情况
  • node.kubernetes.io/out-of-disk:节点磁盘空间已满
  • node.kubernetes.io/memory-pressure:节点内存吃紧
  • node.kubernetes.io/disk-pressure:节点磁盘吃紧
  • node.kubernetes.io/network-unavailable:节点网络不可用
  • node.kubernetes.io/unschedulable:节点不可调度
  • node.cloudprovider.kubernetes.io/uninitialized:如果 kubelet 是由 “外部” 云服务商启动的,该污点用来标识某个节点当前为不可用的状态。在“云控制器”(cloud-controller-manager)初始化这个节点以后,kubelet将此污点移除
    示例:
    某一个包含了大量本地状态的应用,在网络断开时,可能仍然想要在节点上停留比较长的时间,以等待网络能够恢复,而避免从节点上驱逐。此时,该 Pod 的容忍可能如下所示:
tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000

如果 Pod 没有 node.kubernetes.io/not-ready 容忍, Kubernetes 将自动为 Pod 添加一个 tolerationSeconds=300node.kubernetes.io/not-ready 容忍。同样的,如果 Pod 没有 node.kubernetes.io/unreachable 容忍,Kubernetes 将自动为 Pod 添加一个 tolerationSeconds=300node.kubernetes.io/unreachable 容忍
这类自动添加的容忍确保了 Pod 在节点发生 not-ready 和 unreachable 问题时,仍然在节点上保留 5 分钟。

DaemonSet Pod 相对特殊一些,他们在创建时就添加了不带 tolerationSecondsNoExecute 效果的容忍,适用的污点有:

  • node.kubernetes.io/unreachable
  • node.kubernetes.io/not-ready

这将确保 DaemonSet Pod 始终不会被驱逐

2.19 Secret

2.19.1 手动创建 Secret

和创建其他类型 API对象一样,可以现在yaml 文件定义好Secret,然后通过 kubectl apply -f 命令创建。此时可以通过两种方式在yaml中定义Secret

  • data: 使用 字段 时,取值的欸容必须是 base64 编码
  • stringDate: 使用stringData时,更为方便,可以直接将取值以明文方式写在yaml文件中
  1. yaml中定义data
echo -n 'admin' | base64
echo -n '123456' | base64
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: MTIzNDU2
  1. 在yaml中定义stringData
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
stringData:
  username: admin
  password: a1234567890

但是这种写法在yaml文件annotation中仍然可以看见铭文

apiVersion: v1
data:
  password: YTEyMzQ1Njc4OTA=
  username: YWRtaW4=
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"secret-test","namespace":"test"},"stringData":{"password":"a1234567890","username":"admin"},"type":"Opaque"}
  creationTimestamp: "2020-12-24T06:45:47Z"
  managedFields:
  - apiVersion: v1
    fieldsType: FieldsV1
    fieldsV1:
      f:data:
        .: {}
        f:password: {}
        f:username: {}
      f:metadata:
        f:annotations:
          .: {}
          f:kubectl.kubernetes.io/last-applied-configuration: {}
      f:type: {}
    manager: kubectl-client-side-apply
    operation: Update
    time: "2020-12-24T06:45:47Z"
  name: secret-test
  namespace: test
  resourceVersion: "4249564"
  selfLink: /api/v1/namespaces/test/secrets/secret-test
  uid: 6228d000-976f-49ca-8122-4940425b1c35
type: Opaque

  1. 同时定义了 data 和 stringData
    如果同事定义了data 和 stringData,对于对象中key重复的字段,最终采纳 stringData 中的value
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
stringData:
  username: administrator

2.19.2 解码和编辑 Secret

  1. 解码
echo 'YTEyMzQ1Njc4OTA=' | base64 --decode
  1. 编辑
kubectl edit secrets mysecret

2.20 Security Context

2.20.1 为 Pod 设置 Security Context

在 Pod 的定义中增加 securityContext 字段,即可为 Pod 执行 Security 相关的设定。securityContext 字段是一个 PodSecurityContext 对象。通过该字段指定的内容将对该 Pod 中所有的容器生效。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    securityContext:
      allowPrivilegeEscalation: false
  • spec.securityContext.runAsUser 字段指定了该 Pod 中所有容器的进程都以UserID 1000 的身份运行,spec.securityContext.runAsGroup 字段指定了该 Pod 中所有容器的进程都以GroupID 3000 的身份运行
    • 如果该字段被省略,容器进程的GroupID为 root(0)
    • 容器中创建的文件,其所有者为 userID 1000,groupID 3000
  • spec.securityContext.fsGroup 字段指定了该 Pod 的 fsGroup 为 2000
    数据卷 (本例中,对应挂载点 /data/demo 的数据卷为 sec-ctx-demo) 的所有者以及在该数据卷下创建的任何文件,其 GroupID 为 2000

2.20.2 为容器设置Linux Capabilities

使用 Linux Capabilities可以为容器内的进程授予某些特定的权限(而不是 root 用户的所有权限)。在容器定义的 securityContext中添加 capabilities 字段,可以向容器添加或删除Linux Capability

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-4
spec:
  containers:
  - name: sec-ctx-demo-4
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      capabilities:
        add: ["NET_ADMIN", "SYS_TIME"]

三、Kubernetes 高级学习

3.1 安全

3.1.1 用户认证

所有Kubernetes 集群都有两类用户,Kubernetes管理的Service Account 和 普通用户

与普通用户相对,Service Account是通过 Kubernetes API 管理的用户。Service Account 是名称空间级别的对象,可能由 ApiServer 自动创建,或者通过调用 API 接口创建。Service Account 都绑定了一组 SecretSecret 可以被挂载到 Pod 中,以便 Pod 中的进程可以获得调用 Kubernetes API 的权限。

集群内外的任何一个进程,在调用 API Server 的接口时,都必须认证其身份,或者被当做一个匿名用户。可能的场景有:

  • 集群中某一个 Pod 调用 API Server 的接口查询集群的信息
  • 用户通过 kubectl 执行指令,kubectl 调用 API Server 的接口完成用户的指令
  • 用户通过 Kuboard 界面管理集群,Kuboard 调用 API Server 的接口实现界面上的功能
认证策略

Kubernetes 的认证策略(Authentication Strategies)是:通过 authentication plugin 认证发起 API 请求的用户身份,认证方式有 client certificates、bearer tokens、authenticating proxy、HTTP basic auth。当 API Server 接收到 HTTP 请求时,authentication plugin 会尝试将如下属性附加到请求:

  • Username:唯一标识用户的一个字符串。例如 kube-admin 或者 jane@example.com
  • UID:唯一标识用户的一个字符串,相较于 username,提供更强的一致性和唯一性。(某些 Identity Provider 可能允许用户更改 username)
  • Groups:一组字符串,标识用户所属的用户组
  • 额外字段:Key,Value 都是 String 的 Map,包含一些 对 authorizer 可能有用的信息\

上述所有的字段对认证系统来说都是透明的,且只对 authorizer 有意义(authentication plugin 将认证结果中的某些字段赋值到上述字段中,认证系统只是按照自己的方式正常工作,并不知道上述这些字段的存在)。这使得 Kubernetes 可以同时支持多种认证方式,在实际工作中,您通常应该至少配置两种认证方式:

  • Service account tokens 用于认证 Service Account,
  • 至少另外一种认证方式用于认证普通用户。

3.1.2 管理 ServiceAccount

User accounts 和 service accounts

Kubernetes 明确地区分了 user account 和 service account 的概念,原因如下:

  • User account 的使用者是用户(人),service account 的使用者是运行在 Pod 中的进程。
  • User account 应该是全局的,用户名在集群范围内(跨名称空间)必须唯一。Service account 的名称在名称空间内唯一即可
  • 通常,集群的 user account 可能是从企业的数据库同步过来,在那里,创建新的 user account 需要特殊的权限,并且受复杂的业务流程管控。Service account 的创建则更加轻量级,允许集群的用户为特定的任务创建 service account,(最小权限的原则)
  • 对用户(人)和 service account 的审计过程可能会不一样
  • 一个复杂系统中,可能为不同的组件配置不同的 service account。由于 service account 可以临时创建,并且在名称空间内唯一,这种配置信息是可以移植的
Service account admission controller

Admission Controller (opens new window)是 apiserver 的一部分,它在 Pod 创建或者更新时,对 Pod 执行一些修改。此控制器激活时(默认处于激活状态),当 Pod 被创建或修改时,该控制器将执行如下动作:

  • 如果 Pod 未设置 ServiceAccount,将 ServiceAccount 设置为 default
  • 确保 Pod 引用的 ServiceAccount 存在,否则拒绝创建或者修改 Pod
  • 如果 Pod 不包含任何ImagePullSecrets,则 ServiceAccount中的 ImagePullSecrets 将被添加到 Pod 上
  • 为 Pod 添加一个 volume(其中包含了访问 APIServer 的 token)
  • 为 Pod 中的每一个容器添加一个 volumeSource,并挂载到路径/var/run/secrets/kubernetes.io/serviceaccount
Token Controller

TokenController 作为 controller-manager 的一部分运行。以异步的方式执行如下动作:

  • 监听 ServiceAccount的创建,并创建一个对应的 Secret 以允许访问 APIServer
  • 监听 ServiceAccount 的删除,并删除所有对应的 ServiceAccountToken Secrets
  • 监听 Secret 的添加,确保其引用的 ServiceAccount 以存在,并在需要时向 Secret 添加 Token
  • 监听 Secret 的删除,并在需要的情况下将对应 ServiceAccount 中对 Secret 的引用也删除掉

。。。。 未完待续

四、Kubernetes 实战

4.1 使用 port-forward 访问集群中的应用程序

使用kubectl port-forward 访问kubernetes集群中的Redis Server ,Debug 时很有效

  1. 创建 Redis 服务
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-master
  labels:
    app: redis
spec:
  selector:
    matchLabels:
      app: redis
      role: master
      tier: backend
  replicas: 1
  template:
    metadata:
      labels:
        app: redis
        role: master
        tier: backend
    spec:
      containers:
      - name: master
        image: redis
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
  name: redis-master
  labels:
    app: redis
    role: master
    tier: backend
spec:
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    app: redis
    role: master
    tier: backend

这时redis 的pod就创建好了,Service使用的ClusterIP

  1. 转发本地端口到Pod的端口
# 这几个命令执行任意一个即可
kubectl port-forward redis-master-765d459796-258hz 7000:6379
kubectl port-forward pods/redis-master-765d459796-258hz 7000:6379
kubectl port-forward deployment/redis-master 7000:6379
kubectl port-forward rs/redis-master 7000:6379
kubectl port-forward svc/redis-master 7000:6379
Logo

开源、云原生的融合云平台

更多推荐