Shell 中 grep 的副作用

最近在 Jenkins 中编写项目的 Build 脚本时碰到一个关于 grep 的副作用的问题,让人感觉挺费解,解决时需要用一点小技巧,因此在这里记录一下。

先描述一下所碰到的问题的现象,我需要用 Jenkins Shell script 将为我们的项目部署到 Kubernetes cluster 上,在部署之前会进行一些权限的检查和设置,因此在脚本中直接使用了 kubectl 查询资源并结合 grep 的方式来判断资源是否已经创建出来了,例如:


...
is_service_account_exists=$(kubectl get sa --name myns | grep 'my-service-account')
if [ -z "$is_service_account_exists" ]; then
  echo "The service account does not exist, will create it ..."
fi
...

当 Jenkins Job 开始运行时,虽然 service account 并不存在,但是该 Job 却莫名其妙失败了,并没有看到期待的输出 “The service account does not exist, will create it …”。经过一番检查发现问题出在 grep 命令上。当 grep 正确执行,且从标准输入上匹配到期望的内容时,它会返回错误代码 0,例如:


$ kubectl get sa --namespace kube-system | grep tiller
tiller                               1         392d
$ echo $?
0

否则如果没有匹配到任何内容,该管道命令会返回 1,还是以上面的例子为例,当我故意查找并不存在的内容时,返回结果如下:


$ kubectl get sa --namespace kube-system | grep not-exists
$ echo $?
1

而 Jenkins Shell 脚本在执行时默认使用了 -xe 参数,-x 参数将打印每一行执行的命令, -e 则在运行 script 时检查每一行的输出结果,如果结果是非 0 则立即停止脚本执行。在本例子中,我们只想通过获取 grep 结果的输出来判断是否获得期待的结果,因此我们希望 ‘kubectl get sa –name myns | grep ‘my-service-account’ 无论如何都返回 0。解决办法是将 ‘|| true’ 添加到命令的最后,我们知道 shell 脚本通过布尔操作符连接时,会根据命令的返回结果来决定是否运行布尔操作符连接的其他命令。通过 || 操作符,可以保证 true 始终被执行,从而保证该命令的执行始终能够返回 0。例如:


$ kubectl get sa --namespace kube-system | grep not-exists || true
$ echo $?
0

最终将 Jenkins 脚本修改如下,并成功执行。


...
is_service_account_exists=$(kubectl get sa --name myns | grep 'my-service-account || true')
if [ -z "$is_service_account_exists" ]; then
  echo "The service account does not exist, will create it ..."
fi
...

参考:https://stackoverflow.com/questions/22814559/how-when-does-execute-shell-mark-a-build-as-failure-in-jenkins/22816074

20 次阅读

Go 语言中的超时处理

最近在项目里用 Go 语言对一些微服务进行重构,由于 Go 对我来说仍然算是一种新语言,因此在项目过程中碰到的一些比较不同一些写法,将会以 Tips 的方式记录在这里。今天想记录的是 Go 语言里的超时处理。

超时在一些业务场景里非常普遍,例如:

– 数据库访问操作,进行网络连接时通常都有超时时间。
– 本地客户端用阻塞方式异步访问远程,等待一段时间之后,如果远程没有返回结果,则认为超时。
– 微服务在启动时,需要初始化某些数据,例如等待服务注册表返回的总线密码等等,如果在规定的时间内未获得需要的内容,则认为超时,服务启动失败。

在 Java 等传统语言里,通常会首先记录当前时间,然后使用一个无限循环进行资源等待检查,如果需要等待的资源无法获取到,则将当前线程休眠一段时间,当休眠恢复后检查时间是否超过规定时间,如果超时则抛出异常,否则继续进入循环进行等待。用伪代码表述如下:


long startTime = Time.currentTimeMillis();
long EXPIRE = 1000*30; // 30 seconds to expire

while (true) {
    resource = getResourceFromRemote();
    if (resource == null) {
        Thread.sleep(1000); // Sleep 1 second
    }

    long currentTime = Time.currentTimeMillis();
    if (currentTime - startTime > EXPIRE)
        throw new RuntimeException("Expired to get remote resource");
    }

上述 Java 实现中,需要手工处理超时时间以及线程休眠等等,看上去也是非常的清晰,但是在 Go 语言的版本里,整个过程会显得更加简洁:


for resource := getResourceFromRemote(); resource == nil {
    select {
        case <-time.After(time.Second * 30):
            panic("Unable to receive resource within 30 seconds.")
        default:
            logs.Info("Resource is not received, waiting for 1 seconds...")
            time.Sleep(time.Second) // Wait for 1s until the resource is received.
    }
}

首先也是进入一个循环,循环条件是可以获取需要的资源,在循环内部使用 select 语句试图从 case 语句指定的通道里读取数据,如果无法读取到数据则进入下一个 case, <- time.After(time.Second * 30) 表示在 30 秒之后从通道内返回数据,在未到达规定的时间执行 ‘default’ 语句并且使用 time.Sleep(time.Second) 休眠 1 秒。

在这个 Go 语言的版本里,虽然看上去代码更加简洁,更多是利用 Go 语言本身的的一些特性,而不是过多依赖于函数库,这体现了 Go 语言设计时将一些互联网时代常用的功能变成语言本身的实用主义特点。

73 次阅读

新项目

八月底一位资深的同事离职,他手上的一个新项目交给我来带,简单的交接后同事就离职了。九月初开始上手这个项目,由于这个项目实际上是后台从老项目迁移,前端重新开发,其中使用到的技术基于微服务架构非常繁杂,简单列举一下:

后端:

  • 开发语言:Golang, Ruby, Shell
  • 开发框架:Sinatra (Ruby), Beego (Go)

前端:

  • 开发语言:Javascript (ES6)
  • 开发框架:React, Redux, Saga
  • 编译工具:Babel
  • 打包工具:Webpack
  • 包管理器:npm

基础架构部分:

  • 服务发现:etcd
  • 缓存:Redis, Memcached
  • 消息中间件:Redis
  • 容器技术:Docker
  • 容器编排:Kubernates
  • 容器安装:Helm
  • 数据库:Mongo

作为架构师我需要熟悉这一系列的技术,重新梳理架构,了解旧代码。吾生也有涯,而代码无涯,之后整个九月都很痛苦,到目前为止也仍然在不停学习中。由于项目时间紧,任务重,从一开始只有两个人,到目前增加了到将近六个人的小团队,仍然都在加班赶工。我自己有很长时间的Java技术背景,做过解决方案架构,又专门去学了项目管理,比较看重用户体验和项目组的氛围,接手这样一个项目之后渐渐会有些感觉不吐不快。政治上的槽点就不宜多说,单说技术和项目管理方面:

  • 这个项目有很多代码是从旧项目迁移过来的,一份代码被若干人改过,风格迥异。
  • 注释偏少(程序员的通病?),搭配一些奇葩的变量命名方式,让人抓狂。
  • 架构的文档少。
  • 接口更是没有文档,有时想调用某个接口,往往需要去直接看代码。
  • 代码日志输出不规范,后端代码调试困难。
  • 一些配置,例如IP地址, 被写死在代码里,直到碰到错误时才发现。

这些都是我们后面需要逐步去改进梳理的,有时在繁忙的工作之余,我会仔细思考一些问题:为什么我们越来越不快乐了,我们曾经是那么喜欢技术本身带来挑战和成就感?把时间耗费在修复前人因为种种原因而犯下的错误是不是对自己生命的浪费?作为对职业的尊重和责任,我常常感到非常无奈,这也许便是成长的烦恼。对于这个项目相关技术现象的几点看法,或许能解释一点点上述困惑,欢迎大家探讨:

  • 前端开发真的是日新月异,像 React 这样的技术虽然克服了传统前端开发的一些性能问题,但是缺少开发工具的支持,开发效率上未见得到实质的提升。现代前端开发我们实际上看到已经形成了一个非常深的技术栈:从开发语言,框架到打包工具等等。这使得前端开发的学习成本越来越高,这大概也是前端开发现在细分成一些专门的职位方向的原因。
  • 过分追求新技术,异构技术对于团队来说不是好事。从技术栈可以看到,我们使用到的语言,框架,基础软件是不少的,这大大地增加了团队,特别是一些新手的学习负担。在我看来,技术够用就好,最关键的还是能够满足需求,只有当一样技术无法满足需求时,再去考虑更新的方案。很多开源框架演进非常快,意味着一定的不稳定性,做技术跟踪也需要大量的时间。
  • 微服务治理是个很大,但是很现实的话题。虽然在为服务架构中,服务注册和服务发现已经有现成的软件去解决,但是仍然不够。服务之间的互相调用仍然是彼此交织在一起,分布在各种代码逻辑里,编织成错综复杂的调用网络,维护起来十分困难。
  • 产业界仍然有很浮躁的气象,虽然重复造轮子的事情少了,但是凡事都想拿来主义,比如原来可以用单一技术将一件事情做到极致,现在却多使用各种开源代码攒成一件成品,就好比苹果电脑和 DIY PC 一样,后者自然是很难做出精品的。

 

771 次阅读