摸索了半天,终于在十一长假之前把线上服务器 docker 部署 golang 服务搞定了。
❓ 为何要用 docker 部署 golang 服务
因为 golang 以部署便捷著称,编译之后只有一个二进制可执行文件,扔到服务器上就能跑。不需要多余的操作。 而不像 .NET 的 AOT,还需要编译环境跟部署环境系统版本号一致。连交叉编译都不支持。
这么看,完全没有必要使用 docker。
但是,由于生产环境之前为了部署多版本的 PHP,把 MySQL 等服务都放到了 Docker 中。 于是要让 golang 服务器能正常访问 MySQL,也得把 golang 服务部署到跟 MySQL 相同的 docker 网络组里。
蛋疼。。。我从心底不想这样搞,因为正常在 Linux 系统里部署,5 分钟就搞定了,这用上 docker,加之不熟悉,搞了一个小时愣是没有搞定。
正确的配置
先上可以运行的配置吧,先不讲心路历程了。也方便以后复制配置。
直接使用 docker-compose.yml, 而不需要使用 Dockerfile
services:
goapp:
#image: alpine:3.20.3
image: m.daocloud.io/docker.io/alpine:3.20.3
ports:
- "8000:8000"
volumes:
- /var/www/goapp:/app
command: /app/goapp
例如,线上把 golang 编译好的文件及配置文件放到 /var/www/goapp 即可。 然后参考上面配置把这个目录映射到 docker 里的 /app 目录。
启动容器,并查看是否运行正常:
docker compose up -d goapp
docker compose ps
golang 编译配置 CGO_ENABLED=0
这里有个天坑,就是如果使用 alpine 容器,就需要在编译 golang 代码时,增加编译配置。
CGO_ENABLED=0 go build
为何使用 docker alpine 镜像,因为 7M 的镜像大小太具诱惑力。
> docker.exe images
REPOSITORY TAG IMAGE ID CREATED SIZE
m.daocloud.io/docker.io/alpine 3.20.3 91ef0af61f39 3 weeks ago 7.8MB
m.daocloud.io/docker.io/php 8.2-fpm 9b2142f48ee3 2 months ago 494MB
m.daocloud.io/docker.io/mysql 8.0 23b013c7c67d 2 months ago 572MB
nginx latest a72860cb95fd 3 months ago 188MB
不使用 CGO_ENABLED=0 会怎样
不这样设置,在运行 docker 容器之后,会看到 golang 服务启动失败,并报错:
> docker.exe compose up --build
[+] Running 1/1
✔ Container goapp Recreated
0.3s Attaching to goapp
goapp | exec /app/goapp: no such file or directory
goapp exited with code 1
这个错误有巨大的迷惑性,我开始以为是 goapp 可执行文件没有找到,或者 .env 配置文件没有找到。 实际上不是,而真相是,alpine 镜像默认没有内置 libc 。。。默认的 golang 编译是启用了 cgo 的。 (注意, 在交叉编译时,这个配置则是默认关闭的)
- 当 CGO_ENABLED=1 进行编译时,会将文件中引用 libc 的库(比如常用的 net 包),以动态链接的方式生成目标文件。
- 当 CGO_ENABLED=0 进行编译时,则会把在目标文件中未定义的符号(外部函数)一起链接到可执行文件中。
参考:
https://www.reddit.com/r/docker/comments/bld85r/help_exec_user_process_caused_no_such_file_or/
IIRC, it's because with cgo enabled the binary is dynamically linked to a version of libc that isn't present in the alpine container. Disabling cgo means you get a fully statically linked binary. If you need cgo you can just install the correct libc version in the alpine container.
alpine 这种小体积的镜像确实节省了服务器的磁盘空间,但是却浪费了老子的大量时间。
但是我唯一不能理解的是,为何设置了 CGO_ENABLED=0 之后,反而编译后的体积变小了。。。
- 19031388: 这是 go build 编译体积
- 18978468: 这是 CGO_ENABLED=0 go build 的编译体积
其他细节
- golang 程序需要从当前可执行文件同级目录下读取 .env
- gorm 数据库连接字符串,需要改成数据库 HOST Name 和端口可配置。而不是默认 127.0.0.1 和 3306
配置 docker 前,搞清楚自己想要什么
最初折腾时,我先用的 Dockerfile 方案,并且把 golang 编译后的文件,及配置文件都 COPY 进了镜像。
但是,后来我想了一下,这样并不是我想要的结果。
我只需要能将编译好的 golang 文件,能 scp 到服务器上,即 docker 的宿主机磁盘上,然后重启 docker 容器即可。 而不是需要每次都重新构建新镜像。这种情况连 Dockerfile 都不需要写。
虽然所有的 golang docker 教程都在 COPY 二进制文件到镜像中,但是大家都没有说明一点, 这是否是你想要的结果。这是目前市面上所有的 docker 教程和书籍的最大问题,就是里面讲的配置,并没有讲是否有其他方案, 以及不同方案的区别到底是什么?
我目前使用 docker 只有一个原因,就是 php 或者 python 在多版本情况下,担心破坏系统环境,所以使用 docker 的部署方式。 并没有把代码打包进镜像的需求。
微信关注我哦 👍
我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式