引子

从 EP 07 中,我们可以看到已经有了成功的 riscv 移植工作,见此仓库. 发现其构建 riscv 采用了以下命令

# Build
rm -rf bin dist build
ARCH=riscv64 SKIP_IMAGE=true SKIP_VALIDATE=true SKIP_AIRGAP=true make

# Split
cd dist/artifacts
gzip < k3s-riscv64 | split -b 20M - k3s-riscv64.gz.

在此 pull request 中他也提到了这一路上的适配工作过程:开始他是使用qemu模拟,之后在硬件上测试并适配我们之前提到过的各种镜像.

所以这篇文章中我们要做的是,弄明白它的构建过程,包括其 Makefile 与 Dockerfile.dapper

Makefile

源码

分段解释此 Makefile

TARGETS := $(shell ls scripts | grep -v \\.sh) 
GO_FILES ?= $$(find . -name '*.go' | grep -v generated)
  • 通过 shell 命令列出 scripts 目录下所有不以 .sh 结尾的文件
  • 查找项目中所有 .go 文件,排除 generated目录
.dapper:
	@echo Downloading dapper
	@curl -sL https://releases.rancher.com/dapper/v0.6.0/dapper-$$(uname -s)-$$(uname -m) > .dapper.tmp
	@@chmod +x .dapper.tmp
	@./.dapper.tmp -v
	@mv .dapper.tmp .dapper
  • 下载 dapper 工具,根据系统类型下载对应版本
  • 添加执行权限并验证版本
  • 重命名为 .dapper
$(TARGETS): .dapper
	DAPPER_DEBUG=1 DAPPER_DOCKER_BUILD_ARGS="--network host" ./.dapper $@
  • 为上面不以 .sh 结尾的文件创建目标
  • 每个目标依赖 .dapper
  • 执行时使用 dapper 运行脚本
.PHONY: deps
deps:
	go mod tidy
  • 清理整理 go 依赖
release:
	./scripts/release.sh
  • 执行发布脚本
.DEFAULT_GOAL := ci
.PHONY: $(TARGETS)
build/data:
	mkdir -p $@
  • 设置默认目标为 ci
  • 声明伪目标
  • 创建 build/data 目录
.PHONY: binary-size-check
binary-size-check:
	scripts/binary_size_check.sh

.PHONY: image-scan
image-scan:
	scripts/image_scan.sh $(IMAGE)

format:
	gofmt -s -l -w $(GO_FILES)
	goimports -w $(GO_FILES)
  • 检查二进制文件大小
  • 扫描指定的镜像
  • 格式化 Go 代码
.PHONY: local
local:
	DOCKER_BUILDKIT=1 docker build \
		--build-arg="REPO TAG GITHUB_TOKEN GOLANG GOCOVER DEBUG" \
		-t k3s-local -f Dockerfile.local --output=. .
  • 使用Docker BuildKit 构建本地开发镜像

可以发现,我们可以在 make 后跟local,format,build/data,release这些参数,执行不同的运行逻辑.

执行过程

当我们执行 make 时,默认执行 make ci,而 ci 是 Target 中的目标, 正是 script/ 下的一个文件,内容如下

#!/bin/bash
set -e

SCRIPT_DIR=$(dirname $0)
pushd $SCRIPT_DIR

./download
./validate
./build
./package

popd

$SCRIPT_DIR/binary_size_check.sh

又因为 Target 依赖于 .dapper,故如果 .dapper不存在,它会执行 .dapper 的逻辑(下载dapper)

最终回到 DAPPER_DEBUG=1 DAPPER_DOCKER_BUILD_ARGS="--network host" ./.dapper ci, 关于dapper我们下面来讲.

Dockerfile.dapper

什么是 dapper

Dapper is a tool to wrap any existing build tool in an consistent environment. This allows people to build your software from source or modify it without worrying about setting up a build environment. The approach is very simple and taken from a common pattern that has adopted by many open source projects. Create a file called Dockerfile.dapper in the root of your repository. Dapper will build that Dockerfile and then execute a container based off of the resulting image. Dapper will also copy in source files and copy out resulting artifacts or will use bind mounting if you choose.

.dapper 是一个可执行的构建工具,它会运行一个构建容器,然后在容器里执行 scripts/ci; 而看到上面 ci 文件中的内容会发现,其执行了 download、validate、build、package 这几个脚本.

这不就和之前我们在 issue 中看到的情况有点像, 他直接自己本地执行的 download,build,package-cli

dapper 源码

分段解释一下 Dockerfile.dapper 的源码

ARG GOLANG=golang:1.23.3-alpine3.20
FROM ${GOLANG}
  • 基于此镜像作为基础镜像
# Set proxy environment variables
ARG http_proxy
ARG https_proxy
ARG no_proxy
ENV http_proxy=${http_proxy} \
    https_proxy=${https_proxy} \
    no_proxy=${no_proxy}
  • 传递代理设置,这里可能会引发网络问题,除非你可以保证代理地址可以从容器内访问
RUN apk -U --no-cache add \
    bash git gcc musl-dev docker vim less file curl wget ca-certificates jq linux-headers \
    zlib-dev tar zip squashfs-tools npm coreutils python3 py3-pip openssl-dev libffi-dev libseccomp \
    libseccomp-dev libseccomp-static make libuv-static sqlite-dev sqlite-static libselinux \
    libselinux-dev zlib-dev zlib-static zstd pigz alpine-sdk binutils-gold btrfs-progs-dev \
    btrfs-progs-static gawk yq pipx \
    && [ "$(go env GOARCH)" = "amd64" ] && apk -U --no-cache add mingw-w64-gcc || true
  • 安装编译和工具链所需的依赖
ENV TRIVY_VERSION="0.56.2"
RUN case "$(go env GOARCH)" in \
    arm64) TRIVY_ARCH="ARM64" ;; \
    amd64) TRIVY_ARCH="64bit" ;; \
    s390x) TRIVY_ARCH="s390x" ;; \
    *) TRIVY_ARCH="" ;; \
    esac
RUN if [ -n "${TRIVY_ARCH}" ]; then \ 
        wget --no-verbose "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-${TRIVY_ARCH}.tar.gz" \
        && tar -zxvf "trivy_${TRIVY_VERSION}_Linux-${TRIVY_ARCH}.tar.gz" \
        && mv trivy /usr/local/bin; \
    fi
  • 安装 Trivy 安全扫描工具
RUN GOPROXY=https://goproxy.cn,direct go install golang.org/x/tools/cmd/goimports@latest
RUN rm -rf /go/src /go/pkg
RUN if [ "$(go env GOARCH)" = "amd64" ]; then \
    curl -sL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.55.2;  \
    fi
ARG SELINUX=true
ENV SELINUX=${SELINUX}
  • 安装 goimports 工具
  • 清理缓存
  • 仅对于 amd64 进行静态分析
  • 控制是否启用 SELinux
ENV DAPPER_RUN_ARGS="--privileged -v k3s-cache:/go/src/github.com/k3s-io/k3s/.cache -v trivy-cache:/root/.cache/trivy" \
    DAPPER_ENV="REPO TAG DRONE_TAG IMAGE_NAME SKIP_VALIDATE SKIP_IMAGE SKIP_AIRGAP AWS_SECRET_ACCESS_KEY AWS_ACCESS_KEY_ID GITHUB_TOKEN GOLANG GOCOVER DEBUG" \
    DAPPER_SOURCE="/go/src/github.com/k3s-io/k3s/" \
    DAPPER_OUTPUT="./bin ./dist ./build/out ./build/static ./pkg/static ./pkg/deploy" \
    DAPPER_DOCKER_SOCKET=true \
    CROSS=true \
    STATIC_BUILD=true
  • 设置 Dapper 运行时配置
ENV HOME=${DAPPER_SOURCE}
WORKDIR ${DAPPER_SOURCE}
ENTRYPOINT ["./scripts/entry.sh"]
CMD ["ci"]
  • 设置工作目录为源码中的路径
  • 执行 ./scripts/entry.sh ci,启动构建流程

entry.sh

#!/bin/bash
set -e

mkdir -p bin dist
if [ -e ./scripts/$1 ]; then
    ./scripts/"$@"
else
    exec "$@"
fi

chown -R $DAPPER_UID:$DAPPER_GID .
  • 如果 scripts/ci 存在,那就执行它(因为 $1 是传给 entry.sh 的第一个参数 ci)
  • $@ 是所有参数

总结

所以当我们在终端按下make 的时候,发生的是:

  1. make ci
  2. ./.dapper ci
  3. dapper 启动一个 Docker 容器,挂载当前代码目录,并在容器中执行构建命令(如 ci)
  4. 容器内执行 ./scripts/entry.sh ci
  5. 容器内执行 download,validate,build,package

在开发板上构建的时候,因为下载 dapper 这个过程可能会遇到网络问题(我没找到怎么解决)

所以我们可以跳过 dapper 这个过程,直接在网络好的本机进行 download,然后将 download 得到的 build 目录传给开发板,在开发板上执行 build、package-cli 命令. 又回到了之前这篇文章中说过的过程了,只是这次我们修改了内部的镜像并使用了私有镜像仓库.

在此之前我们 fork 出一个仓库上传到 gitee 然后在开发板上拉取源码进行构建.git clone https://gitee.com/ltxworld/k3s-riscv64.git

这里是LTX,感谢您阅读这篇博客,人生海海,和自己对话,像只蝴蝶纵横四海。