之前的博客中介绍了细力度控制pod的方案, 我们可以通过增加自定义资源的方式来针对性的调整pod数目, 但是这个一个基本的原语. 如果将其作为平台的功能提供给用户呢? 这篇博客将会介绍一下我们如何利用这个特性来精准的控制机器部署的pod数目.

简单回顾

我们的PaaS平台中允许用户提供机器, 由我们接入kubernetes, 而这部分用户自己的机器, 他们希望这些机器的性能利用率能达到很优. 无论怎么依靠程序的分配策略, 总是不如自己手动调整的效果最佳, 因此才会需要平台提供能够确定在每台机器部署确定Pod数目的功能.

上篇博客中我已经给出了可行的方案, 具体的实现是增加新的资源限制

书写控制策略时配合cpu以及mem来使用:

系统设计与实现

用户交互部分

在调研了实现方案之后, 我就实现了一个可以修改自定义资源配置的功能, 但是给用户之后, 反馈是十分难用, 修改起来工作量很大, 几乎完全不可用. 在组内讨论了方案之后, 我们决定, 对于每一个应用, 如果用户想要指定每台机器部署多少个Pod, 那么, 最简化的配置应该是这样的:

1
2
3
4
应用1配置:
机器1: 3,
机器2: 1,
机器3: 4,
  • 用户不应该关心底层的实现细节, 也不用去管什么自定义的资源限制, 只要给定node名称, 就可以实现控制, 这才是用户真正想要的功能.

实现部分

增加了自定义资源之后, 程序部署时需要增加一步声明每台机器的资源数目, 下面的代码就是声明机器的自定义资源.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def k8s_update_capacity(node, key, value=None):
k8s_core_v1 = client.CoreV1Api()
body = {
"metadata": {
'name': node,
},
"status": {
"capacity": {
key: value,
}
}
}
# 调用patch_node_status
succ, err = apply_by_api(k8s_core_v1, 'node_status', config=body, action='patch')
return succ, err

比如我们机器1需要配置3个pod, 那么k8s_update_capacity('机器1', 'NS名称-app名称-版本名称', 3), 在创建deployments时, 将对应的资源请求设置为1, 就可以实现在机器1中部署3个Pod, 当然, 你需要针对指定的所有机器都做一样的操作.

细节部分

需要注意的是, 我们的平台支持两种类型的发布, 一种是滚动更新, 另一种是版本更新, 我简单描述下两者有什么区别:

  • 滚动更新: 没有版本的概念, 更新时, 旧的Pod会被逐步删除, 新的Pod逐步产生
  • 版本更新: 更新时, 会创建一份新版本的Pod, 然后用户可以选择是否丢弃或是上线这个新版本

我觉得可以水一篇它们两个区别的博客

对于我们的实现中, 使用版本更新的应用, 资源标签就应该是NS-app_name-version_id, 对于滚动更新的应用, 资源标签应该是NS-app_name.

为什么滚动更新的应用拥有自己的版本号, 但是不能加在标签中使用呢?

答:

  1. 我们PaaS平台在设计之初, 就是只有版本更新功能. 结合了k8s之后, 增加了滚动更新的功能, 因此, 即使是使用滚动更新的应用, 依然是有版本号的, 只是没给k8s看到.

  2. 这个涉及到我们的资源使用情况, 我们应用占用的资源很多. 例如一台机器中, 运行4个Pod已经是极限了, 我们需要滚动更新策略, 删除几个再新建几个, 始终维持最大的Pod数目是4, 否则可能会造成资源使用过度(报警或是机器挂掉). 如果对于这种应用使用了携带版本号的标签. 也就是说, 同一时刻, 有可能存在一台机器上有 两个标签NS-app_name-旧版本,NS-app_name-新版本. 这样, 很可能会在更新应用时 机器中新旧版本的Pod数目加起来超过4. 因此, 滚动更新的应用, 自定义资源一定不能携带版本号.

这两种更新的方式标签名也就决定了我们在更新自定义资源时, 需要采取不同的策略:

对于版本更新的应用:

部署时: 我们需要给待部署的的机器增加自定义资源配置, 因为携带了版本号, 所以新版本一定能部署到正确的问题; 更新结束后下线旧版本时: 需要删除旧版号本对应的资源设置.

对于滚动更新的应用: 部署时: 我们需要更新所有可能会部署到的机器资源配置, 因为用户可能此次更新修改了部署机器, 例如, 原有的host1部署了3个pod, 部署新的时指定host1不再部署应用, 那么就必须在部署前就将host1上面的自定义资源删除, 否则没有版本号的限制, pod依然有可能部署到host1; 更新结束后不需要进行其他处理

具体效果

别的我也不说了, 同事一句话表明了效果.

目前该功能已经稳定上线运行了2个月, 程序按照预想行为进行.

遇到的问题

扩缩容

使用了自定义资源限制之后, 一个比较麻烦的问题是扩缩容, 原有的扩缩容可以简单的修改replica实现, 但是现在, 需要同步更新一下机器中对应的资源数目.

扩容可以简单的同步更新机器资源配置, 然后修改replica数量来做到, 但是缩容是不行的, 具体的表现为, 如果你调低了某台机器的自定义资源, 然后减少replica数目时, Pod的删除不一定会出现在对应的机器, 很可能误删其他机器的Pod, 只有重新部署可以缩容.

具体的原因需要去看replicaset的代码, 我下一篇博客会介绍下k8s缩容的策略, 顺便也能写写怎么调试control-manager的代码, 不知不觉, 通过这篇博客, 我好像又能水3篇博客出来~

maxunavailable 的设置

主要针对滚动更新的应用, 当我们只有2个Pod, 而maxunavailable为25%时. 此时, 只能容忍1/4容器不可用, 因此, 原有的2个Pod都不会被删除, 而新的Pod也无法被创建, 会出现死锁, 具体的解决方案就是用户配置时确保maxunavailable * replica > 1.

总结

这个细力度控制功能的实现, 给我的最大感受就是, 调研了解决方案之后, 需要针对用户的需求, 提供真正能用的上的功能, 而不是草草实现(既花了时间,用户也用不上).

另外就是针对不同的部署方案, 需要考虑这种基础功能的实现策略, 上面的实现中, 我省略了cronjob以及job的实现, 希望读者需要实现的时候自己考虑下, 应该比较简单.