酒话:Copilot 和运维代码

2021-12-20 15:51:27 浏览数 (1)

这个话题是前些天和一位同学在饭桌上喝着啤酒闲聊产生的内容,叙事极其杂乱,错过本文不会有任何损失。

背景

前些天跟同事讲,Learnk8s 的 A visual guide on troubleshooting Kubernetes deployments 是个很明显可以转换成工具运用到实际运维工作当中的东西,一套组合拳下来,基本问题搞清楚,是个挺方便的事儿,要不我周末把它搓出来给你看看。听者无意说者有心,我试了试,还真是个挺无聊的事情,设计各种场景,根据条件,捕获各种 K8s 输出,最终汇总成报表。不小心发散了一下,前一阵得到试用资格的 Github copilot 好像就很适合这种工作,周末趁娃补觉,用 Copilot PyCharm 就真的实现了个差不多。

Copilot

首先介绍下 Copilot,这东西是使用 AI 技术凭借 Github 上的海量代码,用编辑器/ide 插件的形式提供辅助编程能力的一个产品,和 Tab Nine 这样的“传统”插件最大区别是,可以用“面向注释开发”的方式来使用 Copilot,例如输入 # Import Kubernetes 的注释,他就会推荐出 from kubernetes import client, config 这样的语句,按 Tab 接受之后,因为还没有在 venv 中安装相应的库,PyCharm 会做出提示,安装包即可。

整段脚本大概一百行,其中有一半是注释除了前面简单的 Import 之外,比较复杂的遍历、判断,还有使用 API 时候最头秃的对象结构的拆解,都是 Copilot 完成的,例如:

代码语言:javascript复制
# Import kubernetesfrom kubernetes import client, config# Connect to kubernetesconfig.load_kube_config()# List all podsv1 = client.CoreV1Api()
print("Listing pods with their IPs:")
ret = v1.list_pod_for_all_namespaces(watch=False)for i in ret.items:
    print("%st%st%s" % (i.status.pod_ip, i.metadata.namespace, i.metadata.name))

这其中只有三行注释是手工输入的,对列表的遍历和 print 语句都是自动生成的,非常类似我们在试探阶段编写代码的方法。Copilot 的自动推荐方面还会根据本地代码有所变更,例如:

代码语言:javascript复制
# define a list to store event messagespending_event_list = []
...# save namespace, pod name and message in to an objectpending_event_list.append({'namespace': event.involved_object.namespace, 'pod': event.involved_object.name,        'message': event.message})
...# if length of event_list is greater than 0, then print items into a tableif len(pending_event_list) > 0:
    print('Pending Pods:n')
    print('{:<20}{:<30}{:<20}'.format('Namespace', 'Pod', 'Message'))
    print('{:<20}{:<30}{:<20}'.format('---------', '----', '-------'))    for item in pending_event_list:
        print('{:<20}{:<30}{:<20}'.format(item['namespace'], item['pod'], item['message']))
    print('n')

在定义了这个列表之后,仅凭一行注释,她就机智地把这三个字段形成的对象直接塞进了前面定义的列表之中(中间的 Pending Pods:n 是我的手工输入),后面更是直接输出了表头和间隔线。更有意思的是,Pod 名称一栏的宽度原本是设置为 20 的,我手工改成了 30,后续输出第二个表格时,他就自动将 Pod 字段的宽度修改成了 30。

情况当然不总是一帆风顺的,例如我在输入 # get all events for pod 时,它就推荐了一行错误代码,调用了不存在的成员函数,使用 PyCharm 的自动完成纠正一下也就可以了;在判断容器重启次数时,他也会直接给出 pod.status.container_statuses[0].restart_count > 5 这样的粗暴判断。

总之两个小时下来,我主要的工作就在于几个点:

  1. 设计故障场景
  2. 琢磨英文注释
  3. 纠正错误代码
  4. 运行、调试、更正再运行

用我弱弱的英文输出能力,不停调试注释,让 Copilot 输出合适的代码,并进行微调,最终完成功能。

运维怎么开始写代码

那这么个破事怎么就联系到运维代码上了呢?其实像这种无聊尝试意义是不大的,但是运维代码的开发特点非常适合使用这个东西进行辅助

  1. 需求描述非常技术化,容易转换为 Copilot 的注释输入
  2. 具体相关内容在网络上会有非常多的代码碎片,适合被 Copilot 抓取提炼和使用
  3. 甚至连注释都会有很多自动完成的机会
  4. 场景相对固定,代码量一般不大,方便进行调试

很多运维同学都在面临类似的焦虑——觉得自己手上的工作价值不大,编程基础不太好,又希望能像《Google SRE 解密》中提到的,从手工、脚本、软件直到 Borg 的过程中飞速进化。但是机会不总是存在的,将日常经验有效地变成代码从而得以积累和传播,对组织和个人都是非常具备实际意义的。在个人长期的运维相关工作过程中的经验来看,不管是哪个阶段,不管这个工作应该算是运维、DevOps、SRE 还是别的什么,现场工作一方面是工具的工作场景,另一方面也是工具的需求源头,同时也是工具实现方法的重要参考。

那么如何在现场开始写代码呢?我总结了一个 3X2 原则:如果在你的手工操作中,遇到如下场景:连续三个命令的序列,重复执行了第二次,那么就建议你将它写成一个 Shell 脚本——通常来说,会执行第二次,就会有第三次第四次,随着不断地使用,身兼作者和用户,会有各种非常具体的需求会加入到这个简陋的脚本之中,诱导你在其中逐步加入原本不太熟悉的判断、循环、管道、启动参数等的处理能力——毕竟每次只加一点点,例如我自己的经验:

  • 起初是为了节约公有云测试开支,根据 gcloud 命令行文档,写了在 GKE 上一次性创建和删除 Kubernetes 集群的两个脚本
  • 后来发现,删除集群时,相应的挂载卷也需要进行遍历删除
  • 为了测试一些兼容性问题,需要创建集群时选择不同的 Kubernetes 版本
  • 我拿到了一个每月 300 美金的 Azure 测试账号,我需要将脚本改造成可以选择 Azure 还是 Google Cloud 的版本 这一组脚本已经伴随我走了四五年,经过多年的“随手”改造的积累,已经成为几百行的小怪兽,能够在 GCP、AWS、Azure 创建各种不同规格、不同版本的 Kubernetes 集群,完成工作后能不留后患(账单)的进行彻底清理,帮助我完成了很多的测试,输出了很多的云原生相关的记录内容。

在团队中应该把小工具们组织起来,这里非常要提醒大家的一点就是,在文档中保存的代码段,相当于让代码脱离了生存环境,是很容易死掉的,所以具有相同或相似工作内容的组织内,用版本管理的方式将这些小工具们进行归拢。同行之间互相 Review,取长补短、协同进步会让代码段逐步变得成熟和广普。一致的工具集,紧贴现场需求,有助于形成“一个萝卜一个坑”的一致的问题应对方式,代码本身就成为团队的知识积累和沉淀,新兵加入后,可能就不那么需要从 Linux/Docker/Kubernetes 开始了——对于初出茅庐的新手来说,哪怕是一键提取日志,也是快速进入战场的有效途径。

小工具们成长壮大之后,就进入了抽象和复用的阶段了,这一方面,Kubernetes 生态中充满了绝佳范例:重度依赖调度能力、避免直接和节点/服务器之类的具体环境发生联系、专心解决自己的问题、对外提供稳定的声明式接口(声明式接口同时还意味着可重入、最终一致性等等)。例如我屡屡提到超过 300 行算我输的 Shell Operator,用极其简陋的方式,为运维人提供了用少量代码完成 Validating/Mutating Webhook、Prometheus Server、任务队列等复杂任务的能力。

抽象和复用程度代表了一个小工具在时间和场景上的适应能力,也代表了其需求和功能的稳定性,此时就完全可以考虑将其纳入具体的产品,甚至其自身就具备成为产品的潜在能力了——例如我偶尔一写的“介绍一个小工具”。

最后,运维人们,Happy Coding..

0 人点赞