写点什么

【行云流水线实践】基于“OneBuild”方法对镜像进行快速装箱 | 京东云技术团队

  • 2023-11-02
    北京
  • 本文字数:4360 字

    阅读完需:约 14 分钟

【行云流水线实践】基于“OneBuild”方法对镜像进行快速装箱 | 京东云技术团队

在云原生领域,无论使用哪种编排调度平台,Kubernetes,DockerSwarm,OpenShift 等,业务都需要基于镜像进行交付,我们在内部实践“Source-to-image”和链式构建,总而总结出“OneBuild”模式。


其核心思想是:一处构建,多处使用。

问题

一般,我们会使用类似 Jenkins CI 系统来构建镜像,以满足持续集成,持续开发,持续交付等场景。事实上,如果我们在某一方面能够提升效率或者解决镜像交付实践。


长期来看,将能够带来不少的成本收益,并且对于平台来讲,这种收益是一种可度量收益。假设我们在当前交付(git)分支中,需要 fix 或者 feature 已经 release 分支的,如何进行?如果在已经交付给用户的镜像中存在漏洞,需要批量交付,如何进行?为了解决这些问题,我们的团队必须重新构建镜像,并且找出基本镜像,构建过程有那些依赖关系。然后基于这些成熟的流程和规范进行快速交付。

解决方案

Docker build 是大家比较常用的镜像构建方法,并且在构建中只需要声明自己的 Dockerfile 即可,就可以实现快速构建。但是这并不满足大型企业实践以及快速交付。


所以需要一套规范且能够直接生产的流程,帮助在云原生下进行快速交付。下面我们讲结合行云平台进行“OneBuild”方法的实践。


悬衡而知乎,没规而知圆。因此,我们在团队的流水线建立和改造的过程中,尤其注重标准化。


包括 dockerfile 的命名和设计,构建代码的设计。由此新项目加入时,我们只需复制,然后做小工作量的改造即可。


行云 Build

行云是 JDT 生产效率的标准化产品,是一个比较成熟的产品。用于支撑内部研发,测试,交付的平台。


Build 是行云中一个子系统,用于研发过程中的持续集成,持续测试,持续构建等任务。


团队日常开发语言主要是以 golang 为主,并且在上线或交付制品中,也以 Docker 镜像为主。并且由于大多数时间,我们必须在真实的 K8S 环境中运行。


所以稳定的构建平台,高效,快速的构建,对我们的日常开发和交付都是至关重要,在构建中往往需要构建多版本镜像。所以围绕行云流水线,主要就是发掘功能,适配改造。

Dockerfile 标准化

接下来,我们设计的流程,将会使用上一级构建的产品,对下级镜像进行快速装箱。


Dockerfile 命名



Dockerfile # 标准版
Dockerfile.kylinv10 # kylinv10 base 版本
Dockerfile.oel22 # openeuler base 版本
复制代码


下面我们继续看 dockerfile 中的细节。


首先是 Dockerfile



ARG ARCH
ARG BUILD_IMAGE
ARG BASE_IMAGE
FROM ${BUILD_IMAGE} as builder

ARG ARCH
ENV GOPATH=/go

COPY go.mod go.mod
COPY go.sum go.sum
COPY main.go main.go
COPY api/ api/
COPY controllers/ controllers/
COPY pkg/ pkg/
COPY vendor/ vendor/

# Build
RUN CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=${ARCH} go build --mod=vendor -a -o manager main.go

ARG ARCH
ARG BASE_IMAGE
FROM ${BASE_IMAGE}

ENV PIP3_SOURCE=https://pypi.tuna.tsinghua.edu.cn/simple
ENV DEFAULT_FORKS=50
ENV DEFAULT_TIMEOUT=600
ENV DEFAULT_GATHER_TIMEOUT=600
ENV TZ=Asia/Shanghai
ENV PYTHONWARNINGS=ignore::UserWarning

WORKDIR /
COPY --from=builder /manager .

COPY inventory/ inventory/
COPY roles/ roles/
COPY etcd-restore.yml etcd-restore.yml
COPY facts.yml facts.yml
COPY requirements.txt requirements.txt
COPY inventory.tmpl.ini inventory.tmpl.ini
COPY ansible.cfg ansible.cfg

RUN yum -y install kde-l10n-Chinese && \
yum -y reinstall glibc-common && \
localedef -c -f UTF-8 -i zh_CN zh_CN.UFT-8 && \
echo 'LANG="zh_CN.UTF-8"' > /etc/locale.conf && \
source /etc/locale.conf && \
yum clean all

ENV LANG=zh_CN.UTF-8
ENV LC_ALL=zh_CN.UTF-8

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone && \
yum install python3 python3-devel sshpass openssh-clients -y && \
yum clean all && \
/usr/bin/python3 -m pip --no-cache-dir install pip==21.3.1 -U -i $PIP3_SOURCE && \
/usr/bin/python3 -m pip --no-cache-dir install -r requirements.txt -i $PIP3_SOURCE

USER root

ENTRYPOINT ["/manager"]
复制代码


上述 dockerfile 中,分为两个阶段构建,第一个阶段 builder 构建出需要的二进制。这与正常的 dockerfile 相同。


唯一不同的是,我们讲构建镜像和 base 镜像进行了参数化,这也使得当变更构建镜像和 base 镜像,我们只需要在构建时控制参数即可。


再看 dockerfile.kylinv10



ARG BASE_IMAGE
FROM ${BASE_IMAGE}

ENV PIP3_SOURCE=https://pypi.tuna.tsinghua.edu.cn/simple
ENV DEFAULT_FORKS=50
ENV LANG=en_US.UTF-8
ENV DEFAULT_TIMEOUT=600
ENV DEFAULT_GATHER_TIMEOUT=600
ENV TZ=Asia/Shanghai
ENV PYTHONWARNINGS=ignore::UserWarning

WORKDIR /
COPY --from=jdos-etcd-restore-helper:latest /manager /

COPY inventory/ inventory/
COPY roles/ roles/
COPY etcd-restore.yml etcd-restore.yml
COPY facts.yml facts.yml
COPY requirements.txt requirements.txt
COPY inventory.tmpl.ini inventory.tmpl.ini
COPY ansible.cfg ansible.cfg

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone && \
yum install python3 python3-devel python3-pip sshpass openssh-clients -y && \
/usr/bin/python3 -m pip --no-cache-dir install pip==21.3.1 -U -i $PIP3_SOURCE && \
/usr/bin/python3 -m pip --no-cache-dir install -r requirements.txt -i $PIP3_SOURCE

USER root

ENTRYPOINT ["/manager"]
复制代码


发现了什么?在 dockerfile.kylinv10 中少了 builder 这一步,COPY --from=jdos-etcd-restore-helper:latest 是从一个指定的临时镜像中直接做了拷贝。这就直接复用了第一步 dockerfile 中构建出的产物。效率提升比较明显。


在 dockerfile 设计中,COPY 是可以从一个指定的镜像中,copy 指定的文件的。


再看 Dockerfile.oel22



ARG BASE_IMAGE
FROM ${BASE_IMAGE}

ENV PIP3_SOURCE=https://pypi.tuna.tsinghua.edu.cn/simple
ENV DEFAULT_FORKS=50
ENV LANG=en_US.UTF-8
ENV DEFAULT_TIMEOUT=600
ENV DEFAULT_GATHER_TIMEOUT=600
ENV TZ=Asia/Shanghai
ENV PYTHONWARNINGS=ignore::UserWarning

WORKDIR /
COPY --from=jdos-etcd-restore-helper:latest /manager /

COPY inventory/ inventory/
COPY roles/ roles/
COPY etcd-restore.yml etcd-restore.yml
COPY facts.yml facts.yml
COPY requirements.txt requirements.txt
COPY inventory.tmpl.ini inventory.tmpl.ini
COPY ansible.cfg ansible.cfg

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone && \
yum install python3 python3-devel python3-pip sshpass openssh-clients -y && \
/usr/bin/python3 -m pip --no-cache-dir install pip==21.3.1 -U -i $PIP3_SOURCE && \
/usr/bin/python3 -m pip --no-cache-dir install -r requirements.txt -i $PIP3_SOURCE

USER root

ENTRYPOINT ["/manager"]
复制代码


是不是与 dockerfile.kylinv10 的思路非常相似,事实上,这两个文件已经可以合并了(内部为了向后兼容,没有合并这两个文件)。

脚本标准化

还需要在行云流水线中将 shell 脚本进行固化,与 dockerfile 进行配合。



# 支持shell语言代码的多行输入

cd /

sudo docker login -u $IMAGE_REPO_USER -p $IMAGE_REPO_PASSWD $(echo $IMAGE_REPO | awk -F '/' '{print $1}')

git_commit=${output.Download_Code.GIT_LAST_COMMIT_SHA1}
build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
image_tag="${env.GenerateNewVersion}-${git_commit:0:6}"

echo "start build image - standard"
new_image_repo="${IMAGE_REPO}"
sudo docker build -t ${new_image_repo}:${image_tag} -f Dockerfile --build-arg ARCH="amd64" .
sudo docker login -u $IMAGE_REPO_USER -p $IMAGE_REPO_PASSWD $(echo $IMAGE_REPO | awk -F '/' '{print $1}')
sudo docker push ${new_image_repo}:${image_tag}
echo "end to build image - standard"
echo "amd64ImageName=${new_image_repo}:${image_tag}" > ./amd64_output

# 重新命名一个新镜像,供下级dockerfile进行多阶段构建时直接copy
sudo docker tag ${new_image_repo}:${image_tag} jdos-etcd-restore-helper:latest

# 条件性选择构建基于kylinv10OS的镜像
if [[ -f Dockerfile.kylinv10 ]];then
echo "start build image - security - kylin base"
new_image_repo="${IMAGE_REPO}-kylinv10-amd64"
sudo docker build -t ${new_image_repo}:${image_tag} -f Dockerfile.kylinv10 --build-arg ARCH="amd64" .
sudo docker login -u $IMAGE_REPO_USER -p $IMAGE_REPO_PASSWD $(echo $IMAGE_REPO | awk -F '/' '{print $1}')
sudo docker push ${new_image_repo}:${image_tag}
echo "end to build image - security - kylin base"
echo "amd64KylinImageName=${new_image_repo}:${image_tag}" >> ./amd64_output
fi

# 条件性选择构建基于欧拉OS的镜像
if [[ -f Dockerfile.oel22 ]];then
echo "start build image - security - openeuler22 base"
new_image_repo="${IMAGE_REPO}-openeuler22-amd64"
sudo docker build -t ${new_image_repo}:${image_tag} -f Dockerfile.oel22 --build-arg ARCH="amd64" .
sudo docker login -u $IMAGE_REPO_USER -p $IMAGE_REPO_PASSWD $(echo $IMAGE_REPO | awk -F '/' '{print $1}')
sudo docker push ${new_image_repo}:${image_tag}
echo "end to build image - security - openeuler22 base"
echo "amd64Oel22ImageName=${new_image_repo}:${image_tag}" >> ./amd64_output
fi

# 清理builder镜像,避免产生none垃圾镜像。
sudo docker rmi jdos-etcd-restore-helper:latest --force
复制代码

提升

基于以上,构建时间从 21min 缩短至 7min,构建效率提升 66%👊。我们总结出“OneBuild”方法:即构建一次,多处使用的思路。


标准化的 shell 与 dockerfile 进行配合,能够做到一次构建,多处使用。提升了构建效率。

讨论

上述完整介绍了多个镜像构建的流程和设计规范,也说明“OneBuild”可以进行快速构建的优点。所以 OneBuild 的对于中大型组织或者有快速交付需求的团队来讲,是非常有帮助的。


并且对效率的提升是可以看得见的。


作者:京东科技 王晓飞

来源:京东云开发者社区 转载请注明来源

发布于: 刚刚阅读数: 5
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
【行云流水线实践】基于“OneBuild”方法对镜像进行快速装箱 | 京东云技术团队_云原生_京东科技开发者_InfoQ写作社区