Docker运行Go测试导出覆盖率文件 Mac正常Linux权限异常问题
一、macOS环境下文件属主非root的原因
该差异来自Docker在不同操作系统的挂载实现逻辑区别:
- macOS上的Docker Desktop基于内置Linux虚拟机运行,默认开启了挂载卷用户ID自动映射:容器内root用户(UID 0)写入绑定挂载目录的内容,会被自动映射为宿主机当前执行docker命令用户的UID/GID,因此最终生成的coverage.txt属主为本地普通用户,不存在权限冲突。
- 原生Linux环境的Docker默认关闭用户命名空间自动映射,容器内进程以什么UID/GID运行,写入挂载卷的文件就会对应什么属主。当前镜像默认以root用户启动go test进程,因此生成的文件属主为root:root,流水线的vsts用户无对应操作权限。
之前加--user参数指定UID/GID运行失败的核心原因:Go默认将构建缓存存放在$HOME/.cache/go-build路径下,指定非root用户运行时,该用户默认HOME目录为/,没有在根目录创建.cache文件夹的权限,因此触发权限报错。
二、Linux环境对齐macOS表现的配置方案
以下方案均不需要事后手动修改文件权限,可覆盖所有生成文件的属主匹配需求,二选一即可:
方案1:运行时指定用户+重定向Go缓存路径(改动最小,无需修改Dockerfile)
只需要在运行容器时,给Go指定所有用户可写的缓存路径,同时传入宿主机当前用户的UID/GID即可解决问题,不需要调整原有镜像构建逻辑。
修改Makefile中的运行命令:
docker compose run \ -e GOCACHE=/tmp/go-build \ -e GOMODCACHE=/tmp/go-mod \ --user $(shell id -u):$(shell id -g) \ unittest
也可以将固定的环境变量配置写入docker-compose.yml,减少命令行参数:
version: "3.5" services: unittest: container_name: unittest build: context: . target: codebase command: [go, test, "-failfast", "-coverprofile=/usr/mount/coverage.txt", "./..."] working_dir: /usr/src environment: - GOCACHE=/tmp/go-build - GOMODCACHE=/tmp/go-mod volumes: - ./:/usr/mount
该配置下:
- 容器内测试进程以宿主机执行用户完全一致的UID/GID运行,写入挂载目录的所有文件属主自动匹配宿主机用户
- Go的构建缓存、模块缓存都指向
/tmp目录,所有用户对该目录都有写入权限,不会再触发缓存目录创建的权限报错
方案2:构建阶段创建匹配UID的运行用户(适合固定运行环境场景)
如果不想每次运行时传入--user参数,可以在镜像构建阶段动态创建和宿主机运行用户UID/GID匹配的非root用户,给该用户分配工作目录、Go路径的对应权限,容器默认以该用户启动。
修改Dockerfile的codebase构建阶段:
FROM golang:1.17.9 AS codebase WORKDIR /usr/src # 定义构建参数,接收外部传入的运行用户UID/GID ARG RUN_UID=1000 ARG RUN_GID=1000 # 创建对应用户组、用户,自动创建用户HOME目录 RUN groupadd -g ${RUN_GID} runuser && \ useradd -u ${RUN_UID} -g ${RUN_GID} -m runuser && \ chown -R runuser:runuser /usr/src /go COPY go.mod go.sum ./ RUN go mod download && go mod verify COPY . . # 切换到非root用户作为默认运行用户 USER runuser FROM codebase AS builder RUN go build -o ./myapp . FROM alpine AS myapp COPY --link --from=builder ./myapp ./ ENTRYPOINT [ "./myapp" ]
调整docker-compose.yml的构建配置,传入当前用户的UID/GID作为构建参数:
version: "3.5" services: unittest: container_name: unittest build: context: . target: codebase args: RUN_UID: ${CURRENT_UID} RUN_GID: ${CURRENT_GID} command: [go, test, "-failfast", "-coverprofile=/usr/mount/coverage.txt", "./..."] working_dir: /usr/src volumes: - ./:/usr/mount
最后在Makefile中构建前导出当前用户的UID/GID即可:
export CURRENT_UID=$(shell id -u) export CURRENT_GID=$(shell id -g) docker compose build unittest docker compose run unittest ls -la coverage.txt whoami
该方案下容器全程以匹配宿主机UID的非root用户运行,所有写入挂载卷的文件属主天然和宿主机用户一致,不需要额外传参,也不会出现缓存目录权限问题。
内容的提问来源于stack exchange,提问作者Kanak Singhal
相关产品推荐
相关产品推荐

