我使用 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 GO111MODULE=on RUN go env -w GOPROXY=https://goproxy.cn,direct RUN go get github.com/cosmtrek/air
如上所示,安装了一个名为 air 的工具,熟悉 golang 的朋友都知道,它是用来实现热重载的,本来一切都正常,结果突然报错:「Setctty set but Ctty not valid in child」:
在 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,发现此问题是新版 1.15 才出现的,并且已经修复了,可惜 air 没有升级 pty 版本,于是遇到 1.15 后,问题就出来了。
恰好前几天 Golang 放出来 1.15 的正式版,因为我在 Dockerfile 里使用 golang:alpine 作为标签,并没有明确版本,相当于是 latest,也就是最新版 1.15,所以触发了问题。知道了问题的缘由后,在 air 修复此问题前改用 golang:1.14.7-alpine3.12 作为标签就行了。
此问题给我提了个醒:在 Dockerfile 里要明确版本,不要使用 latest,切记!