作为开发人员,我们的大量时间都花在了调试上。但有时,由于设置限制和容器开发缺乏调试工具,这是不可能的。

在本地工作时,您所要做的就是单击_Debug_,一切都会像魔术一样工作。

然而,当处理远程环境或在容器内运行时,我们不再有 Debug 选项,我们甚至无法在调试模式下编译二进制文件。

一些 IDE 试图解决这个问题,但大多数时候他们的解决方案还不够好,所以我们只能依赖 ad-hoc 日志记录。

真正的问题是容器不是为主动开发而构建的。它们在重新启动进程、动态重新编译二进制文件以及动态修改运行时设置了障碍。因此,使用容器化环境很麻烦,我们花了很多时间在变通方法上:

1\。我们可以添加日志并重建图像

因为调试需要很长时间才能设置,我们可能会放弃。相反,我们通过在我们正在开发的区域周围添加额外的临时日志来进行迭代。我们不仅获得的信息有限,而且我们的周期时间非常长,因为构建映像和重新部署容器(无论是本地还是远程)可能需要几分钟而不是几秒钟。

有时这是最好的选择......如果我们最终在几次迭代中找到错误 - 否则,我们最终会取得进展,但非常缓慢。问题是你永远不知道它需要多少次迭代,而且通常我们低估了;)。

2\。我们可以对我们目前正在开发的服务进行去容器化

保留环境中的所有其他容器,我们可以使用我们当前正在开发的服务移除停止容器,并在本地运行它。这使我们能够使用出色的内置 IDE 工具进行调试和运行!

不幸的是,这意味着我们需要为我们开发的所有容器设置一个有效的本地设置,如果我们有多个容器 - 保持这些工作可能会很麻烦。我们可能还需要使用远程呈现等工具来处理代理网络流量。我们还需要记住在完成后重新容器化它,否则我们最终会得到一个非常不一致的设置。

3\。我们可以通过在镜像中安装所需的依赖项并重新构建二进制文件来在容器内进行调试

这是在容器内部进行调试的最具可扩展性的方法,因为按照一次性设置成本,允许快速迭代和完整的调试功能。

我们将深入探讨选项 #3 并探索如何在容器内调试 GO 代码。为了完成这项工作,我们需要安装调试基础设施 (delve) 和工具链,以便我们可以在容器中进行编译。

在运行的容器中使用 GO 进行调试

让我们从一个容器化 Go 服务的简单示例开始,并进行必要的更改以使用 Delve 调试服务:

FROM golang:1.18-alpine3.15 as builder

WORKDIR /code/
COPY ./go.mod ./go.sum /code/
RUN --mount=type=cache,target=/go/pkg/mod go mod download

COPY . .
RUN go build -o /code/build/server main.go

FROM alpine:3.15
COPY --from=builder /code/build/server /server

ENTRYPOINT ["/server"]

进入全屏模式 退出全屏模式

第 1 步:更改构建二进制文件的方式

go build
FROM golang:1.18-alpine3.15 as builder

WORKDIR /code/
COPY ./go.mod ./go.sum /code/
RUN --mount=type=cache,target=/go/pkg/mod go mod download

COPY . .
RUN go build -gcflags "all=-N -l" -o /code/build/server main.go

FROM alpine:3.15
COPY --from=builder /code/build/server /server

ENTRYPOINT ["/server"]

进入全屏模式 退出全屏模式

第二步:安装需要的依赖(如Delve)

接下来,让我们安装 Delve -

FROM golang:1.18-alpine3.15 as builder

RUN apk add build-base
RUN go install github.com/go-delve/delve/cmd/dlv@latest

WORKDIR /code/
COPY ./go.mod ./go.sum /code/
RUN --mount=type=cache,target=/go/pkg/mod go mod download

COPY . .
RUN go build -gcflags "all=-N -l" -o /code/build/server main.go

FROM alpine:3.15
COPY --from=builder /go/bin/dlv /
COPY --from=builder /code/build/server /

ENTRYPOINT ["/server"]

进入全屏模式 退出全屏模式

第 3 步:使用 dlv 运行进程 - 这样我们就可以实际调试了!

最后,将入口点更改为我们复制过来的 Delve 可执行文件,并将其配置为充当二进制文件的调试服务器。

FROM golang:1.18-alpine3.15 as builder

RUN apk add build-base
RUN go install github.com/go-delve/delve/cmd/dlv@latest

WORKDIR /code/
COPY ./go.mod ./go.sum /code/
RUN --mount=type=cache,target=/go/pkg/mod go mod download

COPY . .
RUN go build -gcflags "all=-N -l" -o /code/build/server main.go

FROM alpine:3.15
COPY --from=builder /go/bin/dlv /
COPY --from=builder /code/build/server /

EXPOSE 2345
ENTRYPOINT ["/dlv", "--listen=:2345", "--headless=true", "--api-version=2", \
            "--accept-multiclient", "exec", "/server"]

进入全屏模式 退出全屏模式

容器调试,好像是本地的

在研发领域,拥有高效且易于使用的开发环境和工作流程非常重要,这样我们才能无忧无虑地工作。开发周期的长度(从代码更改到反馈)是一个关键组成部分,对于给定的代码更改,调试为我们提供了非常丰富的反馈。

但是,在容器化环境中缺乏对调试工具的支持会带来持续的干扰,并会中断员工的工作流程和思维过程。

这就是为什么在容器化服务中像本地一样进行调试的能力和简单性可以为您的团队带来巨大的好处。

即使开发人员从未打算以这种方式使用容器,但使用 Raftt,调试容器化服务就像在您的机器上本地运行它一样容易。

关于拉夫特

Raftt 创建与您的工具、功能和工作流程同步的现代灵活环境,让您可以自由探索和开发。我们的云平台将您从硬件的限制中解放出来,允许您生成无限数量的环境、协作和共享,并享受稳定性、一致性和性能。