Golang Go语言中使用docker compose做调试的困惑

发布于 1周前 作者 nodeper 来自 Go语言

服务端开发时,每次调试都先编译程序,和其他相关文件一并都覆盖复制到一个本地临时目录,启动 docker compose,容器内建立目录映射到本机临时目录,容器内再分别把文件复制到容器中相应的位置,容器中启动程序。以上步骤都通过本地脚本和 compose yaml 的 command 实现。

想求教一下彦祖们,这种操作姿势是否正确?有没有更好的调试方式?

为什么我总会遇到明明代码没问题,但在容器中会得到莫名其妙的错误,然后让 docker 重启一下就能恢复正常。当不能确定是代码问题还是环境问题时,每次排查都很浪费时间。


Golang Go语言中使用docker compose做调试的困惑

更多关于Golang Go语言中使用docker compose做调试的困惑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

29 回复

是不是包含这个程序依赖了其他容器内的服务?比如 mysql 、redis 之类的,在同时启动的时候,不同容器内的程序启动完成的时候不一致,就会导致失败,如果是这种情况一般在程序里加上重试逻辑可解决

更多关于Golang Go语言中使用docker compose做调试的困惑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


看描述,还看不出你说的问题是哪类问题。程序崩溃?资源、配置读取到的数据不对?还是外面能建立的连接到容器内就不行了?

然后你用容器启动调试的过程感觉过于复杂了。
比如“容器内再分别把文件复制到容器中相应的位置”,这个步骤一般是在做镜像时才会做的,build 镜像时,如果有类似操作,还会做一定程度上的验证确保复制正确。实际启动容器后不会做这步。必要的配置或资源,在启动容器时能准确的挂在进容器的任何位置。
程序一般也是和镜像强绑定的。新编译的程序,都会有新的版本的镜像。通过容器使用的镜像版本就能准确确定程序版本。

然后如果你觉得容器有什么问题,可以 docker exec 进容器里面去找线索验证。看看里面的文件版本、程序版本、配置的细节是否和你期望的一致。如果什么都一致,就可能是你的代码有 BUG 。

docker compose 将一个 service 编排了 n 个容器,然后同时启动。每个容器的程序不依赖其他容器的服务。

我想确认一下我用的这种方式是否有问题。

如果你没有热更新之类的机制的话,要重启很正常吧。

我的建议是你贴出你的 docker-compose 配置文件,以及你使用 dockercompose 的知识,并且把详细的报错信息贴出来。这样大家才能给你对应的意见

问题的关键是,同样的程序代码,出现错误后把 docker 重启一下就好了。我不知道具体原因,但可以确定跟代码本身没有关系。我对 docker 不是很了解,有没有可能是 docker 缓存的问题?因为整个启动过程中有多处需要复制文件。

应该给点错误提示啥的。

你需要搭建 CI/CD 了

compose 里的服务启动顺序是不固定的。也许出错的原因是依赖的服务还没启动好。
没有错误的细节没法深入推测了。

指令用错?
up - down
start - stop

yaml<br>version: "3.9"<br>services:<br> n:<br> build:<br> dockerfile: ./Dockerfile.alpine<br> ports:<br> - "12300-12310:12380"<br> - "45600-45610:12381"<br> volumes:<br> - ./temp/:/temp/<br> scale: 1<br> networks:<br> - default<br> command:<br> - sh<br> - -c<br> - |<br> mkdir /etc/xxxxx<br> cp /temp/main /etc/xxxxx/main<br> cp /temp/一些.pem /etc/ssl/certs/<br> cp -rf /temp/一些目录 /etc/xxxxx/一些目录<br> cp /temp/获取一些值写入 <a target="_blank" href="http://env.sh" rel="nofollow noopener">env.sh</a> /etc/xxxxx/获取一些值写入 <a target="_blank" href="http://env.sh" rel="nofollow noopener">env.sh</a><br> source /etc/xxxxx/获取一些值写入 <a target="_blank" href="http://env.sh" rel="nofollow noopener">env.sh</a><br> cp /temp/nginx.conf /etc/nginx/http.d/default.conf <br> nginx<br> cd /etc/xxxxx<br> ./main<br>networks:<br> default:<br> driver: bridge<br>

启动时scale =10

*用临时文件夹是因为启动时需要准备的文件有点多,路径都不同,为了避免麻烦就临时启动时放在同一个目录。



都是程序内的错误,docker 本身没有报错。
会不会是文件复制时候偶尔会出错?

为啥不把复制操作直接写在 dockerfile 里呢?

应用程序内的错误也很多种,是 io 错误还是指针错误还是运算逻辑错误。
重启大法任何时候都管用,不排除是你的应用程序的问题。
另外错误发生的时机?手动复制文件到 temp 下,然后重启所有服务,百分百出错?

把要运行的文件 volume 进去?那容器起到啥作用??你直接在外面运行不就得了?

十年来第一次见这么长的 command ,真的啰嗦,每一行都在藏隐患

即使写了这么啰嗦,甚至最重要的代码编译过程并没有被容器所管理

初步建议:
1. 把编译过程写进 dockerfile
2. 把文件夹创建、文件复制过程写进 dockerfile
3. 编译过程的镜像和运行阶段的镜像分离,使用多阶段构建
4. 尽量少用 volume
5. nginx 单独容器,除非是做 nginx 功能相关开发
6. dockerfile 或者 composefile 都有 env 相关的功能
7. compose.yml 里给每个服务取有意义的名字

为啥要容器内 cp?
多写几行 volumes ,不是更直观?

感谢感谢
初步建议:
1. 把编译过程写进 dockerfile
- 编译过程是指程序的编译吗?我是写入启动脚本.sh 文件中,编译后再 up 。

2. 把文件夹创建、文件复制过程写进 dockerfile
- 文件复制 我是想区别不大,索性都放在 compose 中方便调试。
3. 编译过程的镜像和运行阶段的镜像分离,使用多阶段构建

4. 尽量少用 volume
- 少用 volume 的原因是什么?防止文件 io 冲突吗?之前遇到过,所以就复制进容器内了。

5. nginx 单独容器,除非是做 nginx 功能相关开发
- 项目中有 web 部分,所以 dockerfile 中 add 了安装 nginx ,容器启动时启动 ngixn 。

6. dockerfile 或者 composefile 都有 env 相关的功能
- 对 env 有一些逻辑判断,所以写入 sh 。

7. compose.yml 里给每个服务取有意义的名字
- 好嘞

compose 主要是编排,让多容器可以容易组合,你不应该让他参与过多的事物,如果容器启动前需要处理事务,我觉得还是用启动脚本比较好。另外容器应该是最小化的应用,你这个是把 nginx 也打包到 dockerfile 中去吗?为何不用单独的 nginx 容器呢?

  1. 把编译过程写进 dockerfile
    - 编译过程是指程序的编译吗?我是写入启动脚本.sh 文件中,编译后再 up 。
    ---- 指的就是程序的编译( go build 或者 java 之类的 build )。编译软件的版本对生成结果有影响,本机编译和环境一致的目标相悖,这是第一个隐患。

    2. 把文件夹创建、文件复制过程写进 dockerfile
    - 文件复制 我是想区别不大,索性都放在 compose 中方便调试。
    ---- 不认同这条,可以放弃使用容器了。

    3. 编译过程的镜像和运行阶段的镜像分离,使用多阶段构建
    ---- 补充:多阶段构建和上面第一条有关。

    4. 尽量少用 volume
    - 少用 volume 的原因是什么?防止文件 io 冲突吗?之前遇到过,所以就复制进容器内了。
    ---- 跟 io 没关系。具体去看文档

    5. nginx 单独容器,除非是做 nginx 功能相关开发
    - 项目中有 web 部分,所以 dockerfile 中 add 了安装 nginx ,容器启动时启动 ngixn 。
    ---- 没有因果关系,“所以”没意义。巨大的单个容器可以只用 docker 没必要 compose 。既然用了 compose ,就要考虑多容器的优势,拆分 nginx 、Redis 、MySQL 等基础组件容器,拆开后也很容易支持动态和静态。

    6. dockerfile 或者 composefile 都有 env 相关的功能
    - 对 env 有一些逻辑判断,所以写入 sh 。
    ---- compose 的 env 功能支持简单逻辑。env 只存环境相关变量。少量逻辑放在启动脚本完全没问题。

    7. compose.yml 里给每个服务取有意义的名字
    - 好嘞
    ---- 好嘞

dockerfile + docker-compose.yml + gitlab ci/cd ,最后 docker ps 查看状态,docker log [container id] 查看日志。

不好意思的说,主要是不懂怎样单独用 nginx 容器:)只会用已知的方式实现。

用 compose 是为了模拟网络环境,生产环境是应用程序和 nginx 直接运行在节点上没有用到 docker ,所以选择容器中装 nginx 而没有单独用 nginx 容器,不知道这样做对不对请指教。

compose 编排多容器,nginx 作为一个容器,依赖 web 应用就可以。

所以有没有可能,在你的这种场景下并不适合用 docker-compose 来调试程序,程序调试的时候,直接在本机跑和调试,应该是最方便的。看了你的 docker-compose.yml ,也是感觉不用 docker-compose 应该是一个更好的选择。

这种很基础的应用场景,compose 文档讲的很清楚,花两三个小时通读一遍,很多问题自然就能解决了

在Golang开发中使用Docker Compose进行调试确实能带来很多便利,但也可能遇到一些困惑。以下是一些常见的困惑及解决方案,希望能帮助到你:

  1. 环境变量配置: 在docker-compose.yml中正确配置环境变量是关键。确保你的Go应用所需的所有环境变量都已正确设置,并且与你的开发环境保持一致。

  2. 服务依赖: 如果你的Go应用依赖于其他服务(如数据库、缓存等),确保这些服务在docker-compose.yml中已正确配置,并且启动顺序正确。使用depends_on指令可以确保服务按依赖顺序启动。

  3. 调试信息输出: 确保你的Go应用在Docker容器中运行时能够输出足够的调试信息。这可以通过在代码中添加日志语句,并配置Docker Compose日志输出级别来实现。

  4. 代码热更新: 在调试过程中,你可能需要频繁更新代码。使用Docker Compose的--build选项可以重新构建服务,但更高效的方法是使用像tiltskaffold这样的工具,它们支持更快速的代码热更新。

  5. 网络问题: 如果服务间通信出现问题,检查Docker网络配置。确保服务在同一网络下,并且端口映射正确。

  6. 性能调优: 如果你的Go应用在Docker中运行缓慢,可能是由于资源限制。检查Docker Compose的资源分配设置,并根据需要进行调整。

希望这些建议能帮助你解决在使用Docker Compose调试Go应用时遇到的困惑。如果问题依旧,请提供更详细的错误信息以便进一步分析。

回到顶部