在 Docker 中使用私有 gitlab 模块构建 Go 应用程序

Posted

技术标签:

【中文标题】在 Docker 中使用私有 gitlab 模块构建 Go 应用程序【英文标题】:Building Go apps with private gitlab modules in Docker 【发布时间】:2021-04-25 17:13:36 【问题描述】:

我正在尝试在 docker 文件上构建我的 go 应用程序。在我的 go.mod 中有一个需要身份验证/ssh 的私有包。这个问题类似于Building Go apps with private modules in Docker,但在我的情况下,我必须从gitlab 而不是github 拉包。这是我的 dockerfile:

# builder image
FROM golang:1.14.11-alpine AS builder

# specific directory for build process
WORKDIR /usr/src/build

# copying the source code 
# to the current working directory
COPY . .
RUN apk add --no-cache openssh-client
RUN apk add --no-cache git

# create ssh directory
RUN mkdir ~/.ssh
RUN touch ~/.ssh/known_hosts
RUN ssh-keyscan -t rsa gitlab.com >> ~/.ssh/known_hosts

# allow private repo pull
RUN git config --global url."https://my-personal-access-token:token@gitlab.com/".insteadOf "https://gitlab.com/"

ADD . /go/src/gitlab.com/my-repo/backends/backend-structs
CMD cd /go/src/gitlab.com/my-repo/backends/backend-structs; go get /go/src/gitlab.com/my-repo/backends/backend-structs && go build -o /go/bin/backend-structs


# executing build process
RUN GOOS=linux go build -ldflags="-s -w" -o app

# runtime image
FROM golang:1.14.11-alpine AS runtime

# create and use non-root user
# to increase container security 
# ref https://pythonspeed.com/articles/root-capabilities-docker-security/
RUN adduser myuser --disabled-password

USER myuser

WORKDIR /home/myuser

# copy the executable binary file from builder directory
# to the current working directory
COPY --from=builder /usr/src/build/app .

# exposing port
EXPOSE 8080

# run the application
CMD ["./app"]

我尝试按照本教程 https://divan.dev/posts/go_get_private/ 将 github.com 更改为 gitlab.com 仍然失败。

这里是错误详情:

#17 5.830       remote: HTTP Basic: Access denied
#17 5.830       fatal: Authentication failed for 'https://gitlab.com/my-repo/backends.git/'
------
executor failed running [/bin/sh -c GOOS=linux go build -ldflags="-s -w" -o app]: exit code: 1

这里的任何人都知道如何使用 golang 私有包创建 dockerfile(repo 托管在 gitlab.com)?

【问题讨论】:

你试过启用 gitlab Go 代理吗? docs.gitlab.com/ee/user/packages/go_proxy 我已经阅读了它说的文档:``` GitLab 的 Go 代理正在开发中,由于大型存储库的潜在性能问题,尚未准备好用于生产。 ``` 【参考方案1】:

根据我的经验,不要使用 git configs 来解决这个问题。仅使用~/.netrc。这是专门为此制作的指南:https://gist.github.com/MicahParks/1ba2b19c39d1e5fccc3e892837b10e21

我也将其内容粘贴到下面。

问题

go 命令行工具需要能够从您的私有 GitLab 获取依赖项,但需要进行身份验证。

这假设您的私人 GitLab 托管在 privategitlab.company.com

环境变量

推荐使用以下环境变量:

export GO111MODULE=on
export GOPRIVATE=privategitlab.company.com

以上几行可能最适合您的 shell 启动,例如 ~/.bashrc

说明

GO111MODULE=on 告诉 Golang 命令行工具你正在使用模块。我没有用不使用的项目对此进行测试 私有 GitLab 上的 Golang 模块。

GOPRIVATE=privategitlab.company.com 告诉 Golang 命令行工具不要将公共互联网资源用于主机名 列出(如公共模块代理)。

从您的私人 GitLab 获取个人访问令牌

为了将来证明这些说明,请关注this guide from the GitLab docs。 我知道 Golang 命令行工具需要 read_api 范围才能工作,我可能怀疑 read_repository 为 好吧,但还没有证实这一点。

设置~/.netrc

为了让 Golang 命令行工具向 GitLab 进行身份验证,最好使用 ~/.netrc 文件。

如果文件不存在,要创建文件,请运行以下命令:

touch ~/.netrc
chmod 600 ~/.netrc

现在编辑文件的内容以匹配以下内容:

machine privategitlab.company.com login USERNAME_HERE password TOKEN_HERE

USERNAME_HERE 替换为您的 GitLab 用户名,TOKEN_HERE 替换为在 上一节。

常见错误

不要设置全局 git 配置,如下所示:

git config --global url."git@privategitlab.company.com:".insteadOf "https://privategitlab.company.com"

我相信在撰写本文时,Golang 命令行工具不完全支持 SSH git,这可能会导致 与~/.netrc 冲突。

奖励:SSH 配置文件

为了经常使用git 工具,而不是Golang 命令行工具,设置~/.ssh/config 文件很方便。 为此,请运行以下命令:

mkdir ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/config
chmod 600 ~/.ssh/config

请注意,以上文件和目录的权限是 SSH 在其默认配置下工作的必要条件 大多数 Linux 系统。

然后,编辑~/.ssh/config 文件以匹配以下内容:

Host privategitlab.company.com
  Hostname privategitlab.company.com
  User USERNAME_HERE
  IdentityFile ~/.ssh/id_rsa

请注意上述文件中的空格很重要,如果不正确将导致文件无效。

USERNAME_HERE 是您的 GitLab 用户名,~/.ssh/id_rsa 是文件系统中 SSH private 密钥的路径。 您已经将其 public 密钥上传到 GitLab。这里是some instructions。

【讨论】:

【参考方案2】:

我在私有 github.com 存储库中遇到了这个问题。我使用ssh-agent 作为解决方案,但是,我遇到了困难,因为其他答案中有各种各样的假设。就我而言,我遇到了密钥文件名问题,另一个问题是 AppArmor。

我将在下面详细讨论我的设置和这些问题。

实验性 Docker 功能

首先,在Dockerfile 中,您希望通过在第一行添加类似这样的注释来允许更新的选项:

# syntax=docker/dockerfile:experimental

这将允许我们使用--mount=type=ssh 选项。

注意:如果注释不在第一行,它将被忽略,--mount=... 等扩展名将失败。

基本Dockerfile 设置

和你一样,我们使用 Go alpine。我们还确保收到openssh-client

FROM golang:alpine AS build-env
RUN apk --no-cache add build-base git mercurial gcc curl openssh-client

细节,我们添加我们的代码(整个文件夹):

ADD . .

创建known_hosts & .gitconfig 文件

现在我们遇到了一个问题,因为默认情况下,密钥不会被 SSH 识别。为避免此问题,我们希望将github.com(在您的情况下为gitlab.com)密钥添加到我们已知的主机。我们还想确保 github.com 用于 SSH 而不是 HTTPS。

RUN mkdir -p -m 0700 ~/.ssh && \
    ssh-keyscan github.com >> ~/.ssh/known_hosts && \
    echo -e "[url \"git@github.com:<company-name>\"]\n\tinsteadOf = https://github.com/<company-name>" >> ~/.gitconfig

注意: 此时,docker 中的 ~ 始终为 /root

有些人附加一个卷来让 Docker 访问他们的known_hosts 文件。我认为这并不安全,如果您运行 CircleCI 等在线工具,它可能会崩溃。

构建 Go 应用程序

这为我们提供了最终构建应用程序所需的所有元素:

ENV GO111MODULE=on
ENV GOPRIVATE=github.com/<company-name>
RUN --mount=type=ssh cd cmd/app/ && go build -o app

我们的Dockerfile 有几行,这里没有介绍,用于定义附加文件和最终的ENTRYPOINT

加载密钥ssh-agent

我们几乎准备好运行docker build ...。但是我们仍然需要在ssh-agent 中定义密钥。这很简单:

ssh-add id_rsa

非常重要:键的名称必须ssh 期望的默认值之一。 id_rsa 就是其中之一。如果您的键名不是默认值之一,将不会被拾取

以下是在我的一项测试中检查的名称。当您运行 ssh -A -v ... 命令时,您会看到这些(见下文)。

#18 0.828 debug1: identity file /root/.ssh/id_rsa type -1
#18 0.828 debug1: identity file /root/.ssh/id_rsa-cert type -1
#18 0.828 debug1: identity file /root/.ssh/id_dsa type -1
#18 0.828 debug1: identity file /root/.ssh/id_dsa-cert type -1
#18 0.829 debug1: identity file /root/.ssh/id_ecdsa type -1
#18 0.829 debug1: identity file /root/.ssh/id_ecdsa-cert type -1
#18 0.829 debug1: identity file /root/.ssh/id_ecdsa_sk type -1
#18 0.829 debug1: identity file /root/.ssh/id_ecdsa_sk-cert type -1
#18 0.829 debug1: identity file /root/.ssh/id_ed25519 type -1
#18 0.829 debug1: identity file /root/.ssh/id_ed25519-cert type -1
#18 0.829 debug1: identity file /root/.ssh/id_ed25519_sk type -1
#18 0.829 debug1: identity file /root/.ssh/id_ed25519_sk-cert type -1
#18 0.829 debug1: identity file /root/.ssh/id_xmss type -1
#18 0.829 debug1: identity file /root/.ssh/id_xmss-cert type -1

您可以使用以下命令检查密钥是否已加载:

ssh-add -l

每个键的名称应出现在行尾。它必须是上面提到的默认值之一(您也可以在 docker .ssh/config 文件中摆弄 Host 条目)。

构建 Docker 镜像

为了构建镜像,我们现在像这样运行docker

DOCKER_BUILDKIT=1 docker build --progress=plain .

(当然,您可以使用其他选项,例如 --build-arg GO_VERSION=... 来强制使用 golang 版本)

--progress=plain 让您能够更好地了解正在发生的事情。不知何故,DOCKER_BUILDKIT=1 阻止 Docker 保存中间图像和容器,因此如果没有该选项,您将无法进行太多调试。

--ssh default 选项

可能仍需要此命令行选项。我实际上使用它。但是,在最新版本的 docker 中,如果检测到 git@github.com(或类似的?),它会自动打开。我不太确定它是否可以在所有情况下检测到这种情况。如果您遇到问题,请确保在您的 ... docker build ... 命令行中包含该选项。

根据我的经验,无需指定任何细节。只需default 就足够了。

调试 SSH 连接

如果您遇到连接问题(即 SSH 告诉您连接被拒绝),那么您可以在 RUN ... go build ... 之前添加一个 RUN 命令来调试该部分:

RUN ssh -A -v -l git github.com

-A 选项告诉 SSH 使用 ssh-agent 检索私钥。-v 要求 SSH 打印出调试信息。-l 选项定义用户名。对于github.com,您应该使用git 作为用户名。默认情况下,ssh 使用$USER,它在 Docker 中是root。那是行不通的。

如果连接正常,github.com 会告诉您您已获得授权,但没有可连接的 shell,因此您会立即被踢出。如果您没有看到该友好消息,则 SSH 尚未正确设置。事实上,您可以像这样在控制台中测试该连接:

$ ssh -l git github.com
PTY allocation request failed on channel 0
Hi <your-name>! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

问题 1:apparmor

所有这些对我都不起作用。事实是ssh-agent 创建了一个在/run/user/&lt;uid&gt;/keyring/ssh隐藏 的套接字,并且默认情况下不允许docker ... 工具和服务的路径。至少,如果你的内核有像 Ubuntu 服务器那样的 apparmor,它就不会工作。

您可以通过查看您的/var/log/syslog 或类似的文件(可能是/var/log/auth.log)来了解这种情况。会出现这样的 DENIED 错误:

10 月 28 日 10:42:13 ubuntu2004 内核:[78018.511407] 审核:type=1400 审核(1635442933.692:143):apparmor="DENIED" operation="connect" profile="snap.docker.docker" name=" /run/user/1000/keyring/ssh" pid=36260 comm="docker" requested_mask="wr" denied_mask="wr" fsuid=1000 ouid=1000

我们看到了我的密钥环套接字的完整路径、拒绝访问的 apparmor 配置文件的名称以及操作,这里是“连接”。要解决此问题,您首先需要找到配置文件。这是/var/lib/snapd下的:

/var/lib/snapd/apparmor/profiles/snap.docker.docker

如果你不使用 Docker 的 snap 版本,配置文件一般在 /etc/apparmor.d/... 下,但我在较新版本的 docker (2021) 中看不到该文件...

编辑该文件,到最后,在结束 字符之前,输入这一行:

/run/user/1000/keyring/ssh rw,

这意味着docker 将能够读取和写入此特定套接字。

显然,1000 是特定用户。如果不是您,请使用您的用户 ID (id -u) 或运行 docker build ... 的用户标识符。

您也可以允许该计算机上的所有用户,但不建议这样做:

/run/user/[0-9]*/keyring/ssh rw,

(它仍然很安全,因为您只将权限授予docker,但您永远不知道......)

问题 2:密钥文件名

我在这里重复第二个问题,因为那非常重要。来自 Docker 的键查找将搜索名为 id_rsa 的键(以及其他类似的默认键名称,请参见上文)。如果你为你的密钥使用一个特殊的名字,比如github_rsa,那么它就不会被 Docker 拾取。

您可以通过添加以下内容来使用.ssh/config 文件:

Host github.com
  IdentityFile /root/.ssh/github_rsa

在某些时候,我摆弄了这样的东西,但无法让它工作。可能是因为问题 #1(又名 apparmor)。但是,如果您要与许多程序员共享您的Dockerfile,则需要详细记录以这种方式使用特殊名称。大多数程序员不这样做,他们可能需要一段时间才能找出为什么他们不能在他们的系统上创建 Docker 映像。

不要chmod 任何东西!

在许多页面/答案中,您会看到权限通常是使用chmod 命令解决的。例如,由于/run/user/1000 文件夹上的权限为 700 (rwx-----) 或 @987654401 上的权限为 600 (rw------) 而认为 Docker 无法访问其密钥的人@files 可能认为更改这些权限会有所帮助。它不会。 ssh-agent 中的条目足以根据需要共享您的私钥。

关于使用.netrc 的附注

据我所知,当您使用.netrc 时,您会将您的凭据包含在 Docker 映像中。这意味着任何获得您的图像副本的人都拥有您的凭据。可能不是你想要的东西。如果你的图片只在内部使用,可能没问题...

【讨论】:

以上是关于在 Docker 中使用私有 gitlab 模块构建 Go 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

无需 Docker-in-Docker 的私有 Gitlab Runner 代码质量

使用来自 Gitlab Registry 的私有 Docker 镜像作为 CI 的基础镜像

构建Docker私有仓库Gitlab仓库和持续集成环境

从 Gitlab 私有存储库导入节点模块时出错

`docker pull` 从私有 gitlab 注册表返回 `denied: access denied`

如何使用 docker-compose 下载私有图像(本地)