分类: DevOps实践

FinOps和稳定性保障的关系是否水火不容?

最近,在工作中听到一些同事反馈的一个困惑:FinOps和稳定性保障的关系是否水火不容?

先说说这个问题产生的背景,最近公司在举办一次业务活动时,流量出奇不意地创下了历史新高。很不幸,在活动开始前的系统扩容因种种原因没有及时完成,为避免系统被流量打垮而启用了限流,导致用户活动体验不太好。因此在后续活动中,几乎对所有组件都进行了扩容。而这种不考虑成本的做法,是对这大半年“降低公有云成本”的做法是一个大转向。

回到问题,首先我觉得在“解题思维”上不能掉进“陷阱”。这问题的提问方式是封闭式,采用了“是否”。但这个问题是复杂的,并不是简单地非黑即白、二选一。所以,若真要有一个回答。答案必定是:不是!

为什么说FinOps和稳定性保障的关系不会是水火不容。

原因一:FinOps和稳定性保障的终极目标是一致的!

FinOps的目标是为让组织的业务价值最大化。稳定性保障的目标是通过确保业务系统可靠可用而支持业务运营,进而实现组织业务价值。

用大白话来说,FinOps就是让公有云资源利用率最大化,节省公有云成本进而让公司能赚钱。稳定性保障是确保系统可用进而让业务不中断而让公司赚钱。它俩只是实现同一目标的不同手段而已,那俩者之间就绝不可能会是水火不容。

原因二:FinOps和稳定性保障不会相互冲突矛盾或只能二选一,而是可以1+1>2!

先举例说明下FinOps和稳定性保障最让人误解为相互冲突矛盾的原因。如:FinOps日常工作中做得最多的是查看成本报表、通过资源使用监控工具等,驱动负责人对资源做降配、回收或整合。而稳定性保障在无法准确预估业务流量情况下,通常会预留较多的冗余资源,就是扩容、扩容、扩容。因此很容易造成了两者是冲突茅盾的错觉。

那为什么说两者并不冲突而是可以1+1>2 ?

在一家公司的工作实践中,会因应业务情况,如调整了活动促销模式、外部环境变化等,会让每次业务活动的流量不会是恒定不变的,也就意味对系统容量也就是稳定性保障的要求也不会是恒定不变的。应对这种不确定性,也正是稳定性保障的非常重要能力。而这种能力的基础其实是建立在公有云的在线弹性伸缩和即开即用特性之上,当业务流量评估和在线弹性伸缩这两个稳定性保障能力的准确性和时效性均达到较高水平后,自然而然就实现了1+1>2.

原因三:从实际工作中,若时间维度去看和从利益相关人的要求维度去看,短期内是会有策略的主次先后之分,但长期看必然是两者结合才是效益最大化

当业务带来的流量会有不确定性时,也意味着系统不可能100%不出问题。一但出现问题,业务方就会施加压力和提高要求。此时,扩容应该是绝大部分情况下最有效的手段了。这时,策略就是稳定性优先,要确保系统可靠可用。反之,就是FinOps优先,减少成本浪费。所以在一个较短时间周期内,会有策略的优先主次之分。但拉长时间周期来看,必然是两者结合方是效益最大化。

最后,从团队能力建设管理维度,应该关注产生这些疑惑的同事,洞察令他们产生困惑的背后原因。“解决不了问题,但解决提出问题的人的背后困惑”,可能才是正确的做法。或许是领导要求的来回摇摆、或许是他们执行时需要大量手工操作,所以引起了员工们的不爽。但需要让团队认知到,高水平的稳定性保障能力必然能自如应对这种不确定性。这并不是管理策略的来回摇摆,而是能力建设还要再上一个水平,方才能彻底摆脱这种困惑。


事件复盘原则

  • 复盘不为追责,只为总结经验、持续改进。
  • 让失败是“安全”的,我们坚信每个人在当时限定环境下已根据他的所知、所具备的技能和可用的资源尽了最大努力。
  • 复盘过程基于事实和数据,敢说真话、不惧冲突、对事不对人。
  • 运用系统性思维分析根因,简单的问题现象下隐藏着复杂、多维的改进机会。

变更管理原则

1.所有的变更都应被记录,以便于跟踪和回溯。
2.我们知道自己不知道,因此会主动协调相关干系人全面评估变更影响和变更实施风险。
3.变更影响将在实施前且尽早地通知受影响用户。
4.对“生产保持敬畏心”和完整清晰的变更执行清单,是最有效的风险管控措施。
5.我们追求技术上的精进,致力于把所有的变更实现自动化、智能化。
6.变更实施完成后的技术验证和业务验证,是变更过程中不可省略的环节。
7.事前对变更技术方案进行测试验证,是变更成功实施的最有力保证。


Logstash报maximum shards open无法写入ES处理

一早回来查看kibana,发现今天的index没有创建。开始排查:

  • 查看ES集群状态:正常
  • 查看Logstash状态:运行状态正常。再查看日志发现有报错 this action would add [6] total shards, but this cluster currently has [3000]/[3000] maximum shards open

原因:
ES版本为7.10,集群默认配置的每节点分片数为1000,而集群为3节点,所以总分片数限制在了3000

处理
调整集群配置

#Dev Tools
PUT /_cluster/settings
{
  "persistent": {     #永久配置
    "cluster": {
      "max_shards_per_node":10000
    }
  }
}
#shell 命令
 curl -XPUT "http://localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d'{  "persistent": {    "cluster": {      "max_shards_per_node":10000    }  }}'


Crontab定时执行Curator失败原因分析

早上回来,一看日志中心的ES集群又挂了,有2台节点离线,集群状态为red。

1、处置过程

进入各节点,查看elasticsearch进程状态,发现有两台节点的进程已挂掉。
重启es进程

[ ~]$  cd /espath
[ ~]$ ./elasticsearch -d

使用curl命令,访问可用的es节点,查看集群状态。

可以看到集群节点数已恢复,未分配的分片也在慢慢重分配中,这个时候只能等了

[ ~]$ curl -XGET "http://10.86.18.xxx:9200/_cluster/health?pretty"
{
  "cluster_name" : "elk6.4",
  "status" : "red",
  "timed_out" : false,
  "number_of_nodes" : 5,
  "number_of_data_nodes" : 5,
  "active_primary_shards" : 21928,
  "active_shards" : 21967,
  "relocating_shards" : 0,
  "initializing_shards" : 8,
  "unassigned_shards" : 10362,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 96,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 1986814,
  "active_shards_percent_as_number" : 67.93147168877755
}

2、原因分析

curl查看各节点存储空间状态,发现各节点磁盘空间是足够的,但各节点分片数已飚到6500,大大超出了可负载能力,进而导致在集群自动定时批量创建index时进程OOM了

[ ~]$ curl -XGET "http://10.86.18.xxx:9200/_cat/allocation?v"
shards disk.indices disk.used disk.avail disk.total disk.percent host         ip           node
  6489        1.1tb     1.2tb     13.3tb     14.6tb            8 10.86.x 10.86.x 10.86.x
  6489        1.2tb     1.6tb      5.7tb      7.3tb           21 10.86.x 10.86.x 10.86.x
  1573      268.9gb     1.3tb        6tb      7.3tb           17 10.86.x 10.86.x 110.86.x
  6490        1.2tb     1.4tb      5.8tb      7.3tb           20 10.86.x 10.86.x 110.86.x
  1491      272.3gb     1.5tb      5.7tb      7.3tb           21 10.86.x 10.86.x 110.86.x
  9805                                                                                     UNASSIGNED

集群的index是通过crontab定时执行curator来删除的,查看curator日志发现在9月1日后就没再成功执行过

进一步查看crontab日志,发现根因是执行curator的appxxx用户密码过期导致

Sep  2 22:00:01 appgsvr03 crond[166761]: (appxxx) PAM ERROR (Authentication token is no longer valid; new one required)
Sep  2 22:00:01 appgsvr03 crond[166761]: (appxxx) FAILED to authorize user with PAM (Authentication token is no longer valid; new one required)

3、解决方案

因之前没注意,是在appxxx用户下直接使用crontab -e配置的。现将crontab改回使用root用户执行

sudo vi /etc/crontab

# For details see man 4 crontabs
# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
00 22 * * * root curator --config /curator/curator.yml /curator/action.yml

kubernetes中镜像拉取策略imagepullpolicy分析

一、背景

与同事在通过容器云平台(公司采购的某国内商业产品)部署某系统平台时,同事对文件做了修改并重新构建镜像但没有更改Tag。部署应用时,发现没有起效,一开始以为是容器云平台的Bug,后来感觉有可能是Kubernetes的问题,因此动手进行了验证。

二、原因分析

初步粗略判断,出问题的地方可能有:

  • 1、镜像没上传成功,或者镜像仓库有问题
  • 2、容器云平台的问题
  • 3、kubernetes自身的镜像更新判断机制

通过查看、对比镜像 Image ID,确认镜像上传和镜像仓库均没问题。

与供应商沟通,确认若容器云平台前端界面进行修改,则相当于更新yaml文件给Kubernetes执行,中间并没有再加工处理。而同事除了更新镜像内容,还修改过启动命令,因此容器云平台也没有问题。且通过在Kubernetes集群导出应用的yaml文件,确认使用的镜像拉取策略为imagePullPolicy: IfNotPresent,即本地若有镜像则不会重新拉取。

那最后就是Kubernetes自身的镜像更新判断机制了,这个可以通过动手实验来验证下。

三、实践验证

验证思路:构建Nginx镜像,创建Pod并定义imagepullpolicy:ifNotPresent,通过修改index.html内容,来分别实现【重新构建镜像内容但不更改Tag】和【重新构建镜像内容且更改Tag】,进行验证。

  1. 编辑Dockersfile。主要是使用Nginx做为基础镜像,复制一个网页文件进去重新构建验证镜像。
    [root@masterserver imagepullpolicy]# vi Dockerfile
    FROM harbor.xxx.com.cn/library/nginx:1.13.8-alpine
    COPY index.html /usr/share/nginx/html/
  2. Docker Build构建镜像
    [root@masterserver imagepullpolicy]# docker build -t mynginx:1.1 .
    Sending build context to Docker daemon  4.096kB
    Step 1/2 : FROM harbor.xxx.com.cn/library/nginx:1.13.8-alpine
    ---> bb00c21b4edf
    Step 2/2 : COPY index.html /usr/share/nginx/html/
    ---> be86871a6bce
    Successfully built be86871a6bce
    Successfully tagged mynginx:1.1
  3. 使用kubectl run –dry-run且带上imagepullpolicy参数,输出yaml文件。
    [root@masterserver imagepullpolicy]# kubectl run testnginx --image=mynginx:1.1 --dry-run=client --image-pull-policy=IfNotPresent -o yaml > testnginx.yaml
    [root@masterserver imagepullpolicy]# cat testnginx.yaml 
    apiVersion: v1
    kind: Pod
    metadata:
    creationTimestamp: null
    labels:
    run: testnginx
    name: testnginx
    spec:
    containers:
    - image: mynginx:1.1
    imagePullPolicy: IfNotPresent
    name: testnginx
    resources: {}
    dnsPolicy: ClusterFirst
    restartPolicy: Always
    status: {}
  4. 通过kubectl apply -f nginx.yaml,运行pod。
  5. 修改index.html内容,重新构建镜像但不修改tag,再重新kubectl apply。查看pod事件
    [root@masterserver imagepullpolicy]# kubectl describe po mynginx
    Name:         mynginx
    Namespace:    default
    Priority:     0
    Node:         node1/192.168.56.104
    Start Time:   Wed, 05 Aug 2020 11:02:41 +0800
    Labels:       run=mynginx
    Annotations:  Status:  Running
    IP:           10.244.1.12
    IPs:
    IP:  10.244.1.12
    Containers:
    mynginx:
    Container ID:   docker://6aebe0a359144afa5cd9da4e04d8837e62d7c26118b64933d4b71af50cab07f2
    Image:          harbor.xxx.com.cn/test_dmgtest/mynginx:1.1
    Image ID:       docker-pullable://harbor.xxx.com.cn/test_dmgtest/mynginx@sha256:09a2064d81e1ffbb9c126320d5a3c1173585969da9160e26559bbb777ba2ed4d
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 05 Aug 2020 11:04:07 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-768sh (ro)
    Conditions:
    Type              Status
    Initialized       True 
    Ready             True 
    ContainersReady   True 
    PodScheduled      True 
    Volumes:
    default-token-768sh:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-768sh
    Optional:    false
    QoS Class:       BestEffort
    Node-Selectors:  <none>
    Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
    Events:
    Type     Reason     Age                From               Message
    ----     ------     ----               ----               -------
    Normal   Scheduled  <unknown>          default-scheduler  Successfully assigned default/mynginx to node1
    Normal   Pulling    34s (x3 over 78s)  kubelet, node1     Pulling image "harbor.infinitus.com.cn/test_dmgtest/mynginx:1.1"
    Normal   Pulled     <invalid>          kubelet, node1     Container image "harbor.xxx.com.cn/test_dmgtest/mynginx:1.1" already present on machine 
    Normal   Created    <invalid>          kubelet, node1     Created container mynginx
    Normal   Started    <invalid>          kubelet, node1     Started container mynginx

在pod事件中,可以看到 【Container image "harbor.xxx.com.cn/test_dmgtest/mynginx:1.1" already present on machine 】。

而通过Docker images,可以查看image id是两个不同的id。

四、结论

通过实践验证,可以得出结论:当pod定义了imagepullpolicy: IfNotPresent,判断运行pod的node本地有没有镜像。进一步,这个判断机制Kubernetes原生是这样设置的:会根据tag来判断,而不是根据sha id来判断。
所以即使镜像内容变化重新构建镜像但Tag没有更改的话,kubernetes是不会去重新拉取镜像的。