k8s相关的知识

使用k8s运行一个应用的过程

  1. 创建集群

    一切都是在集群(机器)上运行的

  2. 创建deployment

    • Deployment是管理 Pod 创建和扩展的推荐方法

    • 检查Pod的健康状况,并且在 Pod 中的容器终止的情况下重新启动新的容器

  3. 创建service

    • 创建service之前,Pod 只能通过 Kubernetes 集群中的内部 IP 地址访问。

    • 要使得容器可以从 Kubernetes 虚拟网络的外部访问,必须将 Pod(deployment) 暴露为 Kubernetes Service

    • kubectl expose deployment hello-node --type=LoadBalancer --port=8080
      这里的 --type=LoadBalancer 参数表明你希望将你的 Service 暴露到集群外部。
      应用程序代码仅监听 TCP 8080 端口。

更新应用的过程

  1. 应用新的 YAML 文件

    kubectl apply -f https://k8s.io/examples/application/deployment-scale.yaml
    
  2. 验证

    kubectl get pods -l app=nginx
    

包括更新镜像地址,更新副本个数等等

有状态负载(statefulset)和无状态负载(deployment)的区别

  • 【面向服务不同】deployment面向无状态服务,statefulset管理有状态的服务,比如MySQL、MongoDB集群。
  • 【实例数量】无状态服务可以有一个或多个实例,因此支持服务容量调节模式;有状态服务只能有一个实例,不允许创建多个实例,因此也不支持服务容量调节模式。
  • 【存储卷】无状态服务可以有存储卷,也可以没有,即使有也无法备份存储卷里面的数据;有状态服务必须要有存储卷,并且在创建服务时,必须指定给该存储卷分配的磁盘空间大小。
  • 【数据存储】无状态服务运行过程中的所有数据(除日志和监控数据)都存在容器实例里的文件系统中,如果实例停止或者删除,则这些数据都将丢失,无法找回;而对于有状态服务,凡是已经挂载了存储卷的目录下的文件内容都可以随时进行备份,备份的数据可以下载,也可以用于恢复新的服务。但对于没有挂载卷的目录下的数据,仍然是无法备份和保存的,如果实例停止或者删除,这些非挂载卷里的文件内容同样会丢失。

无状态服务

多个实例对于同一个请求响应的结果是完全一致的,服务运行的实例不会在本地存储需要持久化的数据。这类服务的实例因为一些原因停止或者重新创建(如扩容时)时,这些停止的实例里的所有信息(除日志和监控数据外)都将丢失(重启容器即会丢失)。

有状态服务

该服务的实例可以将一部分数据随时进行备份,并且在创建一个新的有状态服务时,可以通过备份恢复这些数据,以达到数据持久化的目的。

为什么需要资源预留

资源预留是给非 Pod 类进程预留的。

按照是否为 Pod,可以把计算节点的进程分为两类:

  • Pod 类进程:容器内部的进程,这些容器由 K8S 创建
  • 非 Pod 类进程:系统进程,如内核,systemd 等;K8S 管理进程,如 Docker, Kubelet 等

如果没有资源预留,K8S 认为宿主机上所有的资源(RAM, CPU)都是可以分配给 Pod 类进程。但是由于非 Pod 类进程也需要占用一定的资源,当 Pod 创建很多时,就有可能出现资源不足的情况。

当 Pod 里面内存不足时,会触发 Cgroup 把 Pod 里面的进程杀死;

当系统内存不足时,就有可能触发系统 OOM,这时候根据 oom score 来确定优先杀死哪个进程,很大概率上OOM的优先级如下best effort pod > 其它进程 > guarantee pod > kubelet/docker等 > sshd 等,从优先级看来,如果节点没有 best effort 类型的 pod,那么其它进程就有可能被 OOM,包括系统进程。

所以,预留一定的资源给系统和 K8S 管理服务,非常有必要。

标签和注解分别有什么用

标签

在微服务架构中,部署的微服务数量容器达到几十个,这些组件可能是副本(部署同一组件的多个副本)和多个不同的发布版本(stable、beta、canary等)同时运行。

标签可以组织pod和其他K8S对象,这是一种能够基于任意标准将上述pod组织成更小群体的方式。

标签是一种简单却功能强大的K8S特性,可以附加到K8S资源的任意键值对。在资源内标签的key需唯一,一个资源可拥有多个标签。

K8S中的任意API对象都是通过Label进行标识、实质是一系列key/value键值对,其中key与value由用户自己指定。

注解

注解是键值对,类似于标签,主要作用是保存标识信息

注解和标签的区别

  • 标签可以通过标签选择器进行过滤和分组,注解不可以用于对对象分组。
  • 与标签相比而言,注解包含的数据更多一些。

存活检查,就绪检查,启动检查

参考:https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-startup-probes

三种检查方式

三类检查的检查方式都相同,以下的yaml以存活探测为例,其他检查的yaml文件也都类似,只是字段不同:存活探测是livenessProbe,启动探测是startupProbe,就绪探测是readinessProbe

  • 执行命令

    kubelet 在容器内执行命令 cat /tmp/healthy 来进行探测。

    如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。

    如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。

    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        test: liveness
      name: liveness-exec
    spec:
      containers:
      - name: liveness
        image: k8s.gcr.io/busybox
        args:
        - /bin/sh
        - -c
        - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
        livenessProbe:
          exec:
            command: # 执行命令 cat /tmp/healthy 来进行探测。
            - cat
            - /tmp/healthy
          initialDelaySeconds: 5
          periodSeconds: 5 #  kubelet 应该每 5 秒执行一次存活探测
    
  • 发送网络get请求

    如果服务器上 /healthz 路径下的处理程序返回成功代码,则 kubelet 认为容器是健康存活的。

    如果处理程序返回失败代码,则 kubelet 会杀死这个容器并且重新启动它。

    apiVersion: v1
    kind: Pod
    metadata:
      labels:
        test: liveness
      name: liveness-http
    spec:
      containers:
      - name: liveness
        image: k8s.gcr.io/liveness
        args:
        - /server
        livenessProbe:
          httpGet:
            path: /healthz # 请求的服务
            port: 8080
            httpHeaders:
            - name: Custom-Header
              value: Awesome
          initialDelaySeconds: 3 # kubelet在执行第一次探测前应该等待3秒
          periodSeconds: 3 # kubelet每隔3秒执行一次存活探测
    
  • 测试TCP Socket连接

    kubelet 会尝试在指定端口和容器建立套接字链接。

    如果能建立连接,这个容器就被看作是健康的

    如果不能则这个容器就被看作是有问题的。

    apiVersion: v1
    kind: Pod
    metadata:
      name: goproxy
      labels:
        app: goproxy
    spec:
      containers:
      - name: goproxy
        image: k8s.gcr.io/goproxy:0.1
        ports:
        - containerPort: 8080
        readinessProbe:
          tcpSocket:
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          tcpSocket:
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20
    

对于HTTP 或者 TCP 存活检测可以使用命名的 ContainerPort。

ports:
- name: liveness-port
  containerPort: 8080
  hostPort: 8080

livenessProbe:
  httpGet:
    path: /healthz
    port: liveness-port

启动探测

有时候,会有一些现有的应用程序在启动时需要较多的初始化时间。

为了避免容器在启动成功之前被存活、就绪探测器影响,使它们在启动运行之前就被杀掉,kubelet 使用启动探测器来知道应用程序容器什么时候启动了。

针对HTTP 或者 TCP 检测,可以通过设置 failureThreshold * periodSeconds 参数来保证有足够长的时间应对糟糕情况下的启动时间。

一旦启动探测成功一次,存活探测任务就会接管对容器的探测,对容器死锁可以快速响应。 如果启动探测一直没有成功,容器会在 300 秒后被杀死,并且根据 restartPolicy 来设置 Pod 状态。

ports:
- name: liveness-port
  containerPort: 8080
  hostPort: 8080

livenessProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 1
  periodSeconds: 10
  
# 应用程序将会有最多 5 分钟(30 * 10 = 300s) 的时间来完成它的启动
startupProbe:
  httpGet:
    path: /healthz
    port: liveness-port
  failureThreshold: 30 
  periodSeconds: 10

存活探测

kubelet 使用存活探测器来知道什么时候要重启容器

例如,存活探测器可以捕捉到死锁(应用程序在运行,但是无法继续执行后面的步骤)。

这样的情况下重启容器有助于让应用程序在有问题的情况下更可用。

就绪探测

kubelet 使用就绪探测器可以知道容器什么时候准备好了并可以开始接受请求流量, 当一个 Pod 内的所有容器都准备好了,才能把这个 Pod 看作已经就绪。

有时候,应用程序会暂时性的不能提供通信服务。 例如,应用程序在启动时可能需要加载很大的数据或配置文件,或是启动后要依赖等待外部服务。 在这种情况下,既不想杀死应用程序,也不想给它发送请求。 Kubernetes 提供了就绪探测器来发现并缓解这些情况。 如果容器所在 Pod 上还在报未就绪的信息,就不接受通过 Kubernetes Service 的流量。

就绪和存活探测可以在同一个容器上并行使用。 两者都可以确保流量不会发给还没有准备好的容器,并且容器会在它们失败的时候被重新启动。

就绪探测器的配置和存活探测器的配置相似。 唯一区别就是要使用 readinessProbe 字段,而不是 livenessProbe 字段。

readinessProbe:
  exec:
    command:
    - cat
    - /tmp/healthy
  initialDelaySeconds: 5
  periodSeconds: 5        

部署策略

参考: https://kubernetes.io/zh/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/

通常情况下,k8s调度程序会自动合理地将pod分配给节点,比如将Pod分散到节点上,不将Pod放置在可用资源不足的节点上等等。

但某些情况下,我们需要把Pod分配到特定的节点,而不是让k8s来自动调度,比如确保Pod最终落在连接了SSD的机器上。

可以通过标签选择器(nodeSelector)来约束pod分配给某个特定的节点。

通过以下约束过程可以让Pod分配在相应标签附加到的节点上:

  1. 将标签粘贴到节点上

    kubectl label nodes [节点名称] disktype=ssd

  2. 将nodeSelector字段添加到Pod配置中

    apiVersion: v1
    kind: Pod
    metadata:
      name: nginx
      labels:
        env: test
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
      # nodeSelector
      nodeSelector:
        disktype: ssd
    

亲和力和反亲和力

nodeSelector已经提供了一种非常简单的方法来将pod约束到具有特定标签的节点,向 Node 对象添加标签可以将 pod 定位到特定的节点或节点组。 亲和力和反亲和力则扩展了更多的表达类型,其关键的增强功能为:

  1. 除了通过逻辑AND运算创建的完全匹配之外,该语言还提供了更多匹配规则。
  2. 可以制定规则是”soft”/“preference”而不是硬性要求,因此,如果调度程序无法满足该要求,则仍会调度pod。
  3. 可以限制节点(或其他拓扑域)上运行的其他Pod上的标签,而不仅仅是节点本身上的标签,这允许配置哪些Pod可以和不能共置在一个节点上的规则

节点亲和性

目前有两种类型的节点亲和性:

  1. 硬需求requiredDuringSchedulingIgnoredDuringExecution,指定了将 Pod 调度到一个节点上 必须满足的规则,类似nodeSelector
  2. 软需求preferredDuringSchedulingIgnoredDuringExecution,指定调度器将尝试执行但不能保证调度结果和预期一致。

名称的IgnoredDuringExecution部分意味着即使节点的标签在运行时发生变更,从而不再满足 Pod 上的亲和性规则, Pod 也仍然继续在该节点上运行,类似nodeSelector

示例
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
    # Pod 只能放置在具有标签键 kubernetes.io/e2e-az-name 且标签值为 e2e-az1 或 e2e-az2 的节点上
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/e2e-az-name
            operator: In
            values:
            - e2e-az1
            - e2e-az2
      # 在满足这些标准的节点中,具有标签键为 another-node-label-key 且标签值为 another-node-label-value 的节点应该优先使用
      preferredDuringSchedulingIgnoredDuringExecution:
      # weight 字段值的范围是 1-100
      # 对于每个符合所有调度要求的节点,调度器将遍历该字段的元素来计算总和,并且如果节点匹配对应的 MatchExpressions,则添加“权重”到总和,并且将这个评分与该节点的其他优先级函数的评分进行组合。 
      # 总分最高的节点是最优选的。
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label-key
            operator: In
            values:
            - another-node-label-value
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0
注意事项:
  1. 如果同时指定了 nodeSelectornodeAffinity两者必须都要满足, 才能将 Pod 调度到候选节点上。
  2. 如果指定了多个与 nodeAffinity 类型关联的 nodeSelectorTerms,则 如果其中一个 nodeSelectorTerms 满足的话,pod将可以调度到节点上。
  3. 如果指定了多个与 nodeSelectorTerms 关联的 matchExpressions,则 只有当所有 matchExpressions 满足的话,Pod 才会可以调度到节点上。
  4. 如果修改或删除了 pod 所调度到的节点的标签,Pod 不会被删除。 换句话说,亲和性选择只在 Pod 调度期间有效。

容器组亲和性和反亲和性

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
  # 仅当节点和至少一个已运行且有键为“security”且值为“S1”的标签 的 Pod 处于同一区域时,才可以将该 Pod 调度到节点上。
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
     # 当节点和具有键 “security”和值“S2”的标签的 Pod 处于相同的区域,Pod 不能被调度到该节点上
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0

上一篇
python直接赋值,浅拷贝,深拷贝解析 python直接赋值,浅拷贝,深拷贝解析
参考:https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html 直接赋值:其实就是对象的引用(别名)。 浅拷贝(copy
2021-03-03
下一篇
对于endpoint(端点)的理解 对于endpoint(端点)的理解
对于endpoint(端点)的理解 参考文章:https://www.jianshu.com/p/808917d76b51 flask框架的程序理念是把URL地址映射到相应的业务逻辑上。 其中,映射过程并非直接为URL–>viewf
2021-03-02
目录