【行雲流水線實踐】基於“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的對於中大型組織或者有快速交付需求的團隊來講,是非常有幫助的。

並且對效率的提升是可以看得見的。

作者:京東科技 王曉飛

來源:京東雲開發者社區 轉載請註明來源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章