我使用 VSCode Remote-Containters 作为 golang 开发环境,因为生产环境使用的镜像主要是 alpine,所以开发环境自然而然使用了 golang:alpine,对应 Dockerfile 的内容如下:
FROM golang:alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.cloud.tencent.com/g' /etc/apk/repositories
RUN apk add alpine-sdk
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go get github.com/cosmtrek/air
如上所示,出于众所周知的原因,我设置了 GOPROXY,并且安装了一个名为 air 的工具,熟悉 golang 的朋友都知道,它是用来实现热重载的,本来一切都正常,结果突然报错:「Setctty set but Ctty not valid in child」:
air
在 air 的 issue 里没找到对应的报告,不过在 golang 的 issue 里倒是发现了一些线索:
If tty is going to be open in the child process, then it must have a file descriptor in the child process. When using os/exec.Cmd as the creack/pty code does, tty must be in Stdin or Stdout or Stderr or ExtraFiles. If it isn’t in any of those, then it will be closed when the child process runs. If it is in one of those, that gives you the file descriptor number that you should use in Ctty. Right now the creack code is setting Ctty to the parent’s file descriptor number. That is working (assuming that it is working) by accident. The correct fix is to ensure that tty is passed to the child somewhere, and set Ctty to the appropriate descriptor number. If it is possible for all of Stdin, Stdout, Stderr to be set, the simplest approach would be to always add tty to ExtraFiles, and set Ctty to 3. That will work for old versions of Go and also for the upcoming 1.15 release.
我只想让 air 正常工作,并不想深究工作原理,好在里面提到了 creack/pty,而 air 正好依赖它,于是顺藤摸瓜找到了对应的 issue,发现此问题是新版 golang 1.15 才出现的,并且已经修复了,可惜 air 没有升级 pty 版本,于是遇到新版 golang 后,问题就出来了。
恰好前几天 Golang 放出来 1.15 的正式版,因为我在 Dockerfile 里使用 golang:alpine 作为标签,并没有明确版本,相当于是 latest,也就是最新版 1.15,所以触发了问题。知道了问题的缘由后,解决方法就简单了,两种方法:
最好的方式莫过于继续使用新版 golang 1.15,同时给 go get 加上 -u 选项:
FROM golang:1.15-alpine3.12
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.cloud.tencent.com/g' /etc/apk/repositories
RUN apk add alpine-sdk
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go get -u github.com/cosmtrek/air
不过当我们这么干的时候,发现我安装的 air 依然有问题,为了验证问题,我在一个干净的容器里手动安装,结果搞出一个匪夷所思的 v1.21.2 的版本来:
shell> go get -u github.com/cosmtrek/air
go: github.com/cosmtrek/air upgrade => v1.21.2
我在官网上根本查不到这个版本,直觉告诉我可能和 GOPROXY 有关,于是禁用后再执行,发现一切都正常了,好在 goproxy.cn 支持查询各个版本的下载量,于是我就查询了一下 v1.21.2 这个匪夷所思的版本,结果发现从 2020-08-07 开始一直有数据:
goproxy
如此看来,问题的来龙去脉大概是这样的:2020-08-07 之前的某天,官方在升级打包的时候搞错了标签(v1.21.2),尽管很快删掉了,但是却被 goproxy.cn 给缓存了下来,之后发布的版本(v.1.12.X)虽然名义上是新版本,但是由于数字上都小于问题版本,结果导致是用 goproxy.cn 的用户在 go get 安装的时候加 -u 选项也得不到新版本。
让各个代理都删除错误版本显然并不现实,毕竟除了 goproxy.cn 还有 goproxy.io 等很多代理都可能有问题,其实只要重新发布一个保证大于 v1.21.2 的新版本(比如 v1.21.3)就可以了,在此之前,我们可以通过「go get -u github.com/cosmtrek/air@v1.12.4」这样的方式来固定主版本并升级依赖版本的权宜之计来缓解问题。