根据我的经验,大多数人(使用Helm或手动yaml)将应用程序部署到Kubernetes上,然后认为他们就可以一直稳定运行。

然而并非如此,实际使用过程还是遇到了一些“陷阱”,我希望在此处列出这些“陷阱”,以帮助您了解在Kubernetes上启动应用程序之前需要注意的一些问题。

Kubernetes调度简介

调度器通过 kubernetes 的 watch 机制来发现集群中新创建且尚未被调度到 Node 上的 Pod。调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行。kube-scheduler作为集群的默认调度器,对每一个新创建的 Pod 或者是未被调度的 Pod,kube-scheduler会选择一个最优的 Node 去运行这个 Pod。然而,Pod 内的每一个容器对资源都有不同的需求,而且 Pod 本身也有不同的资源需求。因此,Pod 在被调度到 Node 上之前,根据这些特定的资源调度需求,需要对集群中的 Node 进行一次过滤。

在一个集群中,满足一个 Pod 调度请求的所有 Node 称之为 可调度节点。如果没有任何一个 Node 能满足 Pod 的资源请求,那么这个 Pod 将一直停留在未调度状态直到调度器能够找到合适的 Node。

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

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的资源进行限制,以避免发生类似的问题。

如示例文件所示,需要加上resources;

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

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

PS:@@@画图我真滴尽力了@@@

Liveness and Readiness Probes

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

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

因为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

此配置的示意图

通过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上部署应用时我们常忽略的几件事的更多相关文章

  1. 基于Kubernetes在AWS上部署Kafka时遇到的一些问题

    作者:Jack47 转载请保留作者和原文出处 欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. 交代一下背景:我们的后台系统是一套使用Kafka消息队列的数据处理管线 ...

  2. 在 Kubernetes 上部署 OpenStack 是什么体验

    红蓝出 CP,OpenStack 和 Kubernetes 在一起会怎样? 背景 从去年开始就想深入地学习 Kubernetes,首先想到是在 OpenStack 上能比较轻松地玩转,所以去 尝试了 ...

  3. 关于在eclipse上部署Tomcat时出现8080等端口被占用问题的解决方法

    问题描述: 在eclipse中部署Tomcat时,出现如下错误. 解决方法如下: 方法一: 1.开始->cmd->输入命令netstat -ano出现下图所示(注意下边显示有些错位,最后一 ...

  4. 在Kubernetes上部署k6的详细步骤

    k6介绍 k6是一款使用go语言编写的开源测试工具,支持用户编写测试脚本,解决了JMeter不易代码化的缺点.它的主要特点有 提供了友好的 CLI 工具 使用 JavaScript 代码编写测试用例 ...

  5. 升级Kubernetes 1.18前,你不得不知的9件事

    本文来自Rancher Labs 昨天Kubernetes最新版本v1.18已经发布,其包含了38项功能增强,其中15项为稳定版功能.11项beta版功能以及12项alpha版功能.在本文中,我们将探 ...

  6. Kubernetes 上部署应用-- 以Wordpress 为例

    用一个 Wordpress 示例来尽可能将前面的知识点串联起来,我们需要达到的目的是让 Wordpress 应用具有高可用.滚动更新的过程中不能中断服务.数据要持久化不能丢失.当应用负载太高的时候能够 ...

  7. 在kubernetes上部署zookeeper,kafka集群

    本文采用网上镜像:mirrorgooglecontainers/kubernetes-zookeeper:1.0-3.4.10 准备共享存储:nfs,glusterfs,seaweed或其他,并在no ...

  8. 在ubuntu18.04上部署项目时遇到的问题总结

    因为在实验室中,有几台空闲的机子,我便选了一台准备做一个本地的服务器,因为买的阿里云学生机和之前用于FQ的机子感觉都不太顺手,阿里的学生机配置稍低,FQ用的服务器延迟太高.开始在centos和ubun ...

  9. 在ubuntu上部署hadoop时出现的问题

    1. 配置ssh登录 不须要改动/etc/ssh/sshd_config 2. 新建hadoop用户时,home以下没有hadoop文件夹 用以下命令创建 useradd -m hadoop 3. n ...

随机推荐

  1. pandas电子表格的读取(pandas中的read_excel)

    上面那篇文章中,初步介绍了一个文本文件的读取:接下来介绍另外一种常见的本地数据格式,那就是Excel电子表格,如果读者在学习或者工作中需要使用Python分析某个Excel表格数据,改如何完成第一个的 ...

  2. 温故知新——Spring AOP

    Spring AOP 面向切面编程,相信大家都不陌生,它和Spring IOC是Spring赖以成名的两个最基础的功能.在咱们平时的工作中,使用IOC的场景比较多,像咱们平时使用的@Controlle ...

  3. C++ int与char[]的相互转换

    C++ int与char[]的相互转换 一.itoa函数与atio函数①把int类型数字转成char类型,可以使用itoa函数. itoa函数原型: char*itoa(int value,char* ...

  4. redis的集群搭建(很详细很详细)

    说在前面的话 之前有一节说了redis单机版的搭建和使用jedis管理redis单机版和集群版, 本节主要讲一下redis的集群搭建. 跳转到jedis管理redis的使用 认识redis集群 首先我 ...

  5. Application.LoadLevel

    Unity在场景切换之间清理下内存 http://www.cnblogs.com/dongz888/p/4920714.html

  6. python小白入门基础(三:整型)

    # Number(int float str complex) #int 整型(正整数 0 负整数)intvar_1 = 100print(intvar_1)invar_2 = 0 print(inv ...

  7. MySQL 外部联结 内连接、左右外连接辨析

    内连接 在进行跨表内连接查询数据时,查询结果只返回符合查询条件的数据:跨表内连接查询的结果和使用where的多表查询结果相同,其实就是普通的查询,没啥好说的 -- 语法: SELECT 别名1.字段名 ...

  8. JS 进制转换的理解

    该事情的由来是来自于一个面试题,题目是这样的,[1,2,3].map(parseInt)的结果是什么? 作为菜鸟的我们一定是觉得分别把1,2,3分别交给parseInt,无非就是1,2,3嘛.其实结果 ...

  9. 15_Python的模块module

    1.模块的概述 1.模块是Python程序架构的一个核心概念,每一个以.py结尾的Python源代码文件都是一个模块 2.模块名和标识符的命名规则一样,由数字字母下划线组成且不能以数字开头,也不要和系 ...

  10. OneDrive Weblist

    OneIndex-Serverless 教程:https://zhuanlan.zhihu.com/p/74538287 https://github.com/LiuChangFreeman/OneI ...