K8S中构建双架构镜像-从零到成功

背景介绍

公司一个客户的项目使用的全信创的环境,服务器采用arm64的机器,而我们的应用全部是amd64的,于是需要对现在公司流水线进行arm64版本的同步镜像生成。本文介绍从最开始到最终生成双架构的全部过程,以及其中使用的相关配置文件。如果大家有需要请仔细阅读。

环境介绍

K8S 版本:

bash 复制代码
Client Version: v1.32.3
Kustomize Version: v5.5.0
Server Version: v1.32.3

CICD平台: KubeSphere 4.1.3 (这个平台无关紧要,只有理解思路就可以了)

镜像仓库 : Harbor 2.13.0 ,使用私有的域名和自签的证书,这个场景应该是大多数自建harbor的情况,我在自签证书上踩了很多坑,如果有条件的使用授信的CA的证书

思路介绍

  • 运用buildkit 工具进行多架构构建
  • 流水线步骤: 克隆代码 -> 编译代码 -> 构建多架构镜像并推送, 在Dockerfile中判定容器的架构并拷贝不同的应用制品,如果是Java和VUEJS不需要做这个判定。

前置条件

  • 在harbor中已经具备同一个镜像tag,不同的架构的镜像。其中包含: binfmt,buildkit,openeuler(应用运行的基础镜像)
  • k8s集群节点 Linux kernel >= 5.10
  • k8s集群允许特权模式
  • k8s集群节点已经安装qemu,如果没有安装,可以通过以下命令或同等效果的命令安装
bash 复制代码
yum install -y qemu-user qemu-user-static

步骤

一 、解决buildkit拉取和上传镜像对证书的不信任

  • 获取自建harbor的ca证书文件
bash 复制代码
echo -n | openssl s_client -showcerts -connect harbor.easzlab.io.local:443 |   sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > harbor-ca.crt
  • 将harbor 的ca文件添加到buildkit的镜像中,对下面的命令如果有疑虑请参考我的另外一个文章【Docker多架构镜像构建踩坑记】,这个命令会同时构建出来多架构的基础镜像并推送到仓库中
bash 复制代码
docker buildx build --platform=linux/amd64,linux/arm64 --network host --add-host harbor.wldc.site:10.159.16.5 --build-arg HTTP_PROXY=socks5://10.32.4.150:10808 --build-arg HTTPS_PROXY=socks5://10.32.4.150:10808 --build-arg NO_PROXY=localhost,127.0.0.1,.wldc.site,.wldc.site --build-arg http_proxy=socks5://10.32.4.150:10808 --build-arg https_proxy=socks5://10.32.4.150:10808 --build-arg no_proxy=localhost,127.0.0.1,.wldc.site,.wldc.site --push -t harbor.easzlab.io.local/base/buildkit:v0.21.1 .

Dockerfile 文件

bash 复制代码
FROM moby/buildkit:v0.21.1
# 安装 ca-certificates
RUN apk add --no-cache ca-certificates git
# 复制证书并更新
COPY harbor-ca.crt /usr/local/share/ca-certificates/harbor.crt
RUN update-ca-certificates
RUN mkdir -p /root/.docker
COPY config.json /root/.docker/config.json #config.json 文件是用于buildkit推送到仓库的认证信息通过docker login 以后就会自动创建~/.docker/config.json ,把这个文件copy到与Dockerfile同一目录

二、初始化K8S worker节点支持多架构

  • 在命令行运行如下yaml
bash 复制代码
kubectl apply -f binfmt-daemonset.yaml #如果所有的pod都Completed以后可以把这个daemonset删除
yaml 复制代码
#binfmt-daemonset.yaml文件内容
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: binfmt
spec:
  selector:
    matchLabels:
      name: binfmt
  template:
    metadata:
      labels:
        name: binfmt
    spec:
      containers:
        - name: install-binfmt
          image: harbor.easzlab.io.local/base/binfmt:latest
          args: ["--install", "all"]
          securityContext:
            privileged: true  # 必须:允许加载 binfmt_misc
      hostPID: true
      restartPolicy: Never #容器运行以后会成为complete状态,如果再次运行会变成failed状态
      nodeSelector:
        kubernetes.io/os: linux
      tolerations:
        - operator: Exists

三、将新创建的buildkit镜像添加到Jenkins的PodTemplate中

  • 在KubeSphere中找到这个配置文件,namespace: kubesphere-devops-system
  • 每一个agent模板中都添加buildkit的容器
yaml 复制代码
                - name: "buildkit"
                  image: "harbor.easzlab.io.local/base/buildkit:v0.21.1"
                  command: "sleep"
                  args: "infinity"
                  ttyEnabled: true
                  privileged: true
                  resourceRequestCpu: "500m"
                  resourceLimitCpu: "4000m"
                  resourceRequestMemory: "500Mi"
                  resourceLimitMemory: "8192Mi" 
  • 创建buildkit的服务端
bash 复制代码
kubectl apply -f buildkit-service.yaml -n kubesphere-devops-worker #jenkins流水线启动的pod都在kubesphere-devops-worker 这个空间中,所有服务也创建在这里

buildkit服务文件内容

yaml 复制代码
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: buildkitd
  labels:
    app: buildkitd
spec:
  replicas: 1
  selector:
    matchLabels:
      app: buildkitd
  template:
    metadata:
      labels:
        app: buildkitd
    spec:
      hostAliases:
        - ip: "10.159.16.5"
          hostnames:
            - "harbor.easzlab.io.local"
      containers:
        - name: buildkitd
          image: harbor.easzlab.io.local/base/buildkit:v0.21.1
          imagePullPolicy: Always
          args:
            - --addr
            - unix:///run/buildkit/buildkitd.sock
            - --addr
            - tcp://0.0.0.0:1234
          ports:
            - containerPort: 1234
          readinessProbe:
            exec:
              command:
                - buildctl
                - debug
                - workers
            initialDelaySeconds: 5
            periodSeconds: 30
          livenessProbe:
            exec:
              command:
                - buildctl
                - debug
                - workers
            initialDelaySeconds: 5
            periodSeconds: 30
          securityContext:
            privileged: true
---

apiVersion: v1
kind: Service
metadata:
  name: buildkitd
  labels:
    app: buildkitd
spec:
  selector:
    app: buildkitd
  ports:
    - name: tcp
      port: 1234
      targetPort: 1234
      protocol: TCP
  • 调整流水线的jenkins文件,把之前docker build 和docker push的步骤替换成为buildctl方式,其中一个golang的应用的jenkins文件如下(如果是java的应用不存在判定架构,直接jar包运行在不同的jvm上就可以):
bash 复制代码
pipeline {
  agent {
    node {
      label 'go'  //使用golang的agent,这个agent里面定义了4个容器,分别是base,buildkit,go,jnlp
    }

  }
  stages {
    stage('clone code') {
      agent none
      steps {
        //使base容器把代码拉取到jenkins的cicd worker pod中
        container('base') {
          git(url: 'https://git.abc.cn/background/openapi/openapi-front-api.git', credentialsId: 'tenxcloud', branch: '$BRANCH_NAME', changelog: true, poll: false)
        }

      }
    }

    stage('代码编译') {
      agent none
      steps {
      // 使用golang的容器把二进制制品制作出来
        container('go') {
          sh '''export GO111MODULE=on
go env -w GOPROXY=http://10.159.1.2:8081/repository/gogroup/,direct
#打包amd64的制品
GOOS=linux 
GOARCH=amd64
go build -mod=vendor -a -v -o app-amd64 main.go
#打包arm64的制品
GOOS=linux 
GOARCH=arm64
go build -mod=vendor -a -v -o app-arm64 main.go'''
        }

      }
    }

    stage('构建镜像并上传') {
      agent none
      steps {
        //使用buildkit 容器构建双架构镜像
        container('buildkit') {
          withCredentials([usernamePassword(credentialsId: 'harbor', passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {
            sh '''
 # 生成Dockerfile文件,这个地方需要注意一下命令里面的单引号,双引号和各种转义符,我踩了比较多的坑
tee Dockerfile <<-'EOF'
FROM harbor.easzlab.io.local/base/openeuler:24.03-lts
ARG TARGETARCH
COPY ./app-amd64 /app/app-amd64
COPY ./app-arm64 /app/app-arm64
RUN if [ "\$TARGETARCH" = "amd64" ]; then \
      mv /app/app-amd64 /app/app; \
      rm -f  /app/app-arm64; \
    elif [ "\$TARGETARCH" = "arm64" ]; then \
      mv /app/app-arm64 /app/app; \
      rm -f  /app/app-amd64; \
    else \
      echo "Unsupported arch: \$TARGETARCH"; \
      exit -1; \
    fi

COPY yml /app/yml
WORKDIR /app
RUN chmod +x /app/app
CMD ["/app/app"]
EOF

BUILD_TIME=`date +%Y%m%d%H%M%S`
buildctl --addr tcp://buildkitd.kubesphere-devops-worker:1234 build \
--frontend=dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--opt platform=linux/amd64,linux/arm64 \
--output type=image,name=$REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$BRANCH_NAME-${BUILD_TIME}-$BUILD_NUMBER,push=true
'''
          }

        }

      }
    }

  }
  environment {
    DOCKER_CREDENTIAL_ID = 'dockerhub'
    KUBECONFIG_CREDENTIAL_ID = 'kubeconfig'
    REGISTRY = 'harbor.easzlab.io.local'
    DOCKERHUB_NAMESPACE = 'release'
    APP_NAME = 'openapi-front-api'
    SONAR_CREDENTIAL_ID = 'sonar-token'
    BUILD_TIME = ''
  }
  parameters {
    string(name: 'BRANCH_NAME', defaultValue: 'master', description: '')
  }
}
  • 运行流水线,检验多架构的镜像是否上次成功
  • 验证多架构镜像,同一个tag,不同得架构都可以拉取成功。

    PS: 如有疑问可以仔细看一下整篇文章。如果还是有问题,私信给我,我看到就会回复。
相关推荐
敲上瘾2 小时前
Docker 容器核心指令与数据库容器化实践
linux·运维·服务器·docker·容器·eureka·dubbo
Linux运维技术栈2 小时前
Kubernetes 全景指南:从核心概念到云原生未来
云原生·容器·kubernetes
deeper_wind3 小时前
K8S-资源对象(小白的“升级打怪”成长之路)
容器·kubernetes
岚天start3 小时前
K8S容器POD内存快照导出分析处理方案
云原生·容器·kubernetes·内存·快照·pod·内存快照
奋斗的老史15 小时前
25年Docker镜像无法下载的四种对策
docker·容器·eureka
chillxiaohan15 小时前
Docker学习记录
学习·docker·容器
柯南二号16 小时前
【后端】Docker 常用命令详解
服务器·nginx·docker·容器
新鲜萝卜皮16 小时前
容器内运行的进程,在宿主机的top命令中可以显示吗?
容器
容器魔方18 小时前
Karmada v1.15 版本发布!多模板工作负载资源感知能力增强
云原生·容器·云计算
容器魔方19 小时前
全栈AI驱动!华为云云容器引擎CCE智能助手焕新升级
云原生·容器·云计算