在Kubernetes上部署应用时咱们常忽略的几件事

根据个人经验,大多数人(使用Helm或手动yaml)将应用程序部署到Kubernetes上,而后认为他们就能够一直稳定运行。
然而并不是如此,实际使用过程仍是遇到了一些“陷阱”,我但愿在此处列出这些“陷阱”,以帮助您了解在Kubernetes上启动应用程序以前须要注意的一些问题。html

Kubernetes调度简介

调度器经过 kubernetes 的 watch 机制来发现集群中新建立且还没有被调度到 Node 上的 Pod。调度器会将发现的每个未调度的 Pod 调度到一个合适的 Node 上来运行。kube-scheduler做为集群的默认调度器,对每个新建立的 Pod 或者是未被调度的 Pod,kube-scheduler会选择一个最优的 Node 去运行这个 Pod。然而,Pod 内的每个容器对资源都有不一样的需求,并且 Pod 自己也有不一样的资源需求。所以,Pod 在被调度到 Node 上以前,根据这些特定的资源调度需求,须要对集群中的 Node 进行一次过滤。nginx

在一个集群中,知足一个 Pod 调度请求的全部 Node 称之为 可调度节点。若是没有任何一个 Node 能知足 Pod 的资源请求,那么这个 Pod 将一直停留在未调度状态直到调度器可以找到合适的 Node。redis

在作调度决定时须要考虑的因素包括:单独和总体的资源请求、硬件/软件/策略限制、亲和以及反亲和要求、数据局域性、负载间的干扰等等。关于调度更多信息请官网自行查阅shell

Pod Requests and Limits

来看个简单例子,这里只截取yaml部分信息数据库

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx-demo
    image: nginx
    resources:
      limits:
        memory: "100Mi"
        cpu: 100m
      requests:
        memory: "1000Mi"
        cpu: 100m

默认状况下,我们建立服务部署文件,若是不写resources字段,Kubernetes集群会使用默认策略,不对Pod作任何资源限制,这就意味着Pod能够随意使用Node节点的内存和CPU资源。可是这样就会引起一个问题:资源争抢。
例如:一个Node节点有8G内存,有两个Pod在其上运行。
刚开始运行,两个Pod都只须要2G内存就足够运行,这时候都没有问题,可是若是其中一个Pod由于内存泄漏或者流程忽然增长到致使内存用到了7G,这时候Node的8G内存显然就不够用了。这就会致使服务服务极慢或者不可用。
因此,通常状况下,咱们再部署服务时,须要对pod的资源进行限制,以免发生相似的问题。api

如示例文件所示,须要加上resources;安全

requests: 表示运行服务所须要的最少资源,本例为须要内存100Mi,CPU 100m
limits: 表示服务能使用的最大资源,本例最大资源限制在内存1000Mi,CPU 100m

什么意思呢?一图胜千言吧。bash

在Kubernetes上部署应用时咱们常忽略的几件事

Liveness and Readiness Probes

Kubernetes社区中常常讨论的另外一个热点话题。 掌握Liveness和Readiness探针很是重要,由于它们提供了一种运行容错软件并最大程度减小停机时间的机制。 可是,若是配置不正确,它们可能会对您的应用程序形成严重的性能影响。 如下是这两个探测的概要以及如何推理它们:网络

Liveness Probe:探测容器是否正在运行。 若是活动性探针失败,则kubelet将杀死Container,而且Container将接受其从新启动策略。 若是“容器”不提供活动性探针,则默认状态为“成功”。tcp

由于Liveness探针运行频率比较高,设置尽量简单,好比:将其设置为每秒运行一次,那么每秒将增长1个请求的额外流量,所以须要考虑该请求所需的额外资源。一般,咱们会为Liveness提供一个健康检查接口,该接口返回响应代码200代表您的进程已启动而且能够处理请求。

Readiness Probe:探测容器是否准备好处理请求。 若是准备就绪探针失败,则Endpoint将从与Pod匹配的全部服务的端点中删除Pod的IP地址。

Readiness探针的检查要求比较高,由于它代表整个应用程序正在运行并准备好接收请求。对于某些应用程序,只有从数据库返回记录后,才会接受请求。 经过使用通过深思熟虑的准备状况探针,咱们可以实现更高水平的可用性以及零停机部署。

Liveness and Readiness Probes检测方法一致,有三种

  1. 定义存活命令:
    若是命令执行成功,返回值为零,Kubernetes 则认为本次探测成功;若是命令返回值非零,本次 Liveness 探测失败。
  2. 定义一个存活态 HTTP 请求接口;
    发送一个HTTP请求,返回任何大于或等于 200 而且小于 400 的返回代码表示成功,其它返回代码都标示失败。
  3. 定义 TCP 的存活探测
    向执行端口发送一个tcpSocket请求,若是可以链接表示成功,不然失败。

来看个例子,这里以经常使用的 TCP存活探测为例

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx-demo
    image: nginx
    livenessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 10
    readinessProbe:
      tcpSocket:
        port: 80
      initialDelaySeconds: 10
      periodSeconds: 10
livenessProbe 部分定义如何执行 Liveness 探测:
1. 探测的方法是:经过tcpSocket链接nginx的80端口。若是执行成功,返回值为零,Kubernetes 则认为本次 Liveness 探测成功;若是命令返回值非零,本次 Liveness 探测失败。

2. initialDelaySeconds: 10 指定容器启动 10 以后开始执行 Liveness 探测,通常会根据应用启动的准备时间来设置。好比应用正常启动要花 30 秒,那么 initialDelaySeconds 的值就应该大于 30。

3. periodSeconds: 10 指定每 10 秒执行一次 Liveness 探测。Kubernetes 若是连续执行 3 次 Liveness 探测均失败,则会杀掉并重启容器。

readinessProbe 探测同样,可是 readiness 的 READY 状态会经历了以下变化:
1. 刚被建立时,READY 状态为不可用。
2. 20 秒后(initialDelaySeconds + periodSeconds),第一次进行 Readiness 探测并成功返回,设置 READY 为可用。
3. 若是Kubernetes连续 3 次 Readiness 探测均失败后,READY 被设置为不可用。

为Pod设置默认的网络策略

Kubernetes使用一种“扁平”的网络拓扑,默认状况下,全部Pod均可以直接相互通讯。 可是在某些状况下咱们不但愿这样,甚至是没必要要的。 会存在一些潜在的安全隐患,例如一个易受的应用程序被利用,则能够为者提供彻底访问权限,以将流量发送到网络上的全部pod。 像在许多安全领域中同样,最小访问策略也适用于此,理想状况下,将建立网络策略以明确指定容许哪些容器到容器的链接。

举例,如下是一个简单的策略,该策略将拒绝特定名称空间的全部入口流量

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

此配置的示意图
在Kubernetes上部署应用时咱们常忽略的几件事

经过Hooks和init容器的自定义行为

咱们使用Kubernetes系统的主要目标之一就是尝试为现成的开发人员提供尽量零停机的部署。 因为应用程序关闭自身和清理已利用资源的方式多种多样,所以这很困难。 咱们遇到特别困难的一个应用是Nginx。 咱们注意到,当咱们启动这些Pod的滚动部署时,活动链接在成功终止以前被丢弃。 通过普遍的在线研究,事实证实,Kubernetes并无等待Nginx在终止Pod以前耗尽其链接。 使用中止前挂钩,咱们可以注入此功能,并经过此更改实现了零停机时间。

一般状况下,好比咱们要对Nginx进行滚动升级,可是Kubernetes在中止Pod以前并不会等待Nginx终止链接。这就会致使被停掉的nginx并无正确关闭全部链接,这样是不合理的。因此咱们须要在中止钱使用钩子,以解决这样的问题。

咱们能够在部署文件添加lifecycle

lifecycle:
  preStop:
    exec:
      command: ["/usr/local/bin/nginx-killer.sh"]

nginx-killer.sh

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
    echo "Waiting while shutting down nginx..."
    sleep 10
done

这样,Kubernetes在关闭Pod以前,会执行nginx-killer.sh脚本,以咱们定义的方式关闭nginx

另一种状况就是使用init容器
Init Container就是用来作初始化工做的容器,能够是一个或者多个,若是有多个的话,这些容器会按定义的顺序依次执行,只有全部的Init Container执行完后,主容器才会被启动
例如:

initContainers:
        - name: init
          image: busybox
          command: ["chmod","777","-R","/var/www/html"]
          imagePullPolicy: Always
          volumeMounts:
          - name: volume
            mountPath: /var/www/html
      containers:
      - name: nginx-demo
        image: nginx
        ports:
        - containerPort: 80
          name: port
        volumeMounts:
        - name: volume
          mountPath: /var/www/html

咱们给nginx的/var/www/html挂载了一块数据盘,在主容器运行前,咱们把/var/www/html权限改为777,以便主容器使用时不会存在权限问题。
固然这里只是一个小栗子,Init Container更多强大的功能,好比初始化配置等。。。

Kernel Tuning(内核参数优化)

最后,将更先进的技术留给最后,哈哈
Kubernetes是一个很是灵活的平台,旨在让你以本身认为合适的方式运行服务。一般若是咱们有高性能的服务,对资源要求比较严苛,好比常见的redis,启动之后会有以下提示

WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

这就须要咱们修改系统的内核参数了。好在Kubernetes容许咱们运行一个特权容器,该容器能够修改仅适用于特定运行Pod的内核参数。 如下是咱们用来修改/proc/sys/net/core/somaxconn参数的示例。

initContainers:
   - name: sysctl
      image: alpine:3.10
      securityContext:
          privileged: true
       command: ['sh', '-c', "echo 511 > /proc/sys/net/core/somaxconn"]

总结

尽管Kubernetes提供了一种开箱即用的解决方案,可是也须要你采起一些关键的步骤来确保程序的稳定运行。在程序上线前,务必进行屡次测试,观察关键指标,并实时进行调整。
在咱们将服务部署到Kubernetes集群前,咱们能够问本身几个问题:

  • 咱们的程序须要多少资源,例如内存,CPU等?
  • 服务的平均流量是多少,高峰流量是多少?
  • 咱们但愿服务多长时间进行扩张,须要多长时间新的Pod能够接受流量?
  • 咱们的Pod是正常的中止了吗?怎么作不影响线上服务?
  • 怎么保证咱们的服务出问题不会影响其余服务,不会形成大规模的服务宕机?
  • 咱们的权限是否过大?安全吗?

终于写完了,呜呜呜~真滴好难呀~
在Kubernetes上部署应用时咱们常忽略的几件事