【行云流水线实践】基于“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命名

shell 复制代码
Dockerfile            # 标准版

Dockerfile.kylinv10   # kylinv10 base 版本

Dockerfile.oel22      # openeuler base 版本

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

首先是 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

dockerfile 复制代码
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

dockerfile 复制代码
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 复制代码
# 支持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的对于中大型组织或者有快速交付需求的团队来讲,是非常有帮助的。

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

作者:京东科技 王晓飞

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

相关推荐
天河归来4 小时前
本地windows环境升级dify到1.11.1版本
java·spring boot·docker
么么...6 小时前
在 Ubuntu 上安装 Docker 并部署 MySQL 容器
linux·运维·经验分享·笔记·mysql·ubuntu·docker
学Linux的语莫6 小时前
kompose、docker转k8s
docker·容器·kubernetes
阿里云云原生7 小时前
探秘 AgentRun丨流量一大就瘫痪?如何解决 AI 模型调用之痛
云原生
AI视觉网奇7 小时前
nvcr.io 登录方法
docker·ue5
是Yu欸7 小时前
从Ascend C算子开发视角看CANN的“软硬协同”
c语言·开发语言·云原生·昇腾·ascend·cann·开放社区
码界奇点8 小时前
基于微服务架构的企业身份与访问管理系统设计与实现
微服务·云原生·架构·车载系统·毕业设计·源代码管理
weixin_445476689 小时前
Docker 在 Ubuntu(国内网络)安装及问题解决总结
网络·ubuntu·docker
一点晖光10 小时前
docker配置npm环境变量出现问题
docker·容器·npm
一分半心动10 小时前
windows docker desktop 安装VibeVoice
运维·docker·容器