四时宝库

程序员的知识宝库

jenkins+k8s1.24+contianerd devops实践

K8s1.24版本开始已经不支持docker作为默认的容器运行时,因此原来基于docker的CI/CD流程要调整到基于contianerd的CI/CD流程,两者主要区别在于镜像构建工具的不同。

contianerd vs docker

containerd与docker相比,调用链更少,效率更高,如下图所示

当系统启动containerd服务时,会生成一个接口文件(unix:///var/run/containerd/containerd.sock)供各客户端连接

如使用docker时,docker客户端连接到docker服务器端,docker服务器端连接到containerd服务端,最后调用runc管理容器

containerd服务的其他一些客户端:

Contianerd下的打包工具安装

contianerd将容器镜像管理客户端工具替换成nerdctl,而nerdctl本身是不能直接进行镜像构建的,需要buildkit作为构建工具,基本调用链如下

安装nerdctl

下载地址

https://github.com/containerd/nerdctl/releases/download/v0.20.0/nerdctl-0.20.0-linux-amd64.tar.gz

解压后得到nerdctl可执行文件,直接复制到/usr/bin,并给予执行权限chmod +x /usr/bin/nerdctl

安装buildkt

下载地址 https://github.com/moby/buildkit/releases/download/v0.10.3/buildkit-v0.10.3.linux-amd64.tar.gz

解压后得到一系列build开头的进进制文件,分别将他们移到/usr/bin/并添加可执行权限

一些执行原理:

  1. 服务端为buildkitd,负责和runc或containerd后端连接干活,目前只支持这两个后端
  2. 客户端为buildctl,负责解析镜像构建文件Dockerfile,并向服务端发出构建指令,所以客户端可以和服务端不在一台机器上,也不需要root权限之类
  3. 服务端默认使用runc后端,但是建议使用containerd后端,这样构建出的镜像就会存在containerd的buildkit名字空间下
  4. ctr是containerd自带的命令行工具,除了构建镜像不能干,其他docker-cli的活它都可以干
  5. 因为buildkitd的后端选择了containerd,本地镜像会去到containerd的本地存储
  6. 默认本地镜像都存在buildkit名字空间下,要修改此名字空间, 可以去配置 /etc/buildkit/buildkitd.toml

Jenkins主从架构原理

Docker 容器技术目前是微服务/持续集成/持续交付领域的第一选择。而在 DevOps 中,我们需要将各种后端/前端的测试/构建环境打包成 Docker 镜像,然后在需要的时候,Jenkins 会使用这些镜像启动容器以执行 Jenkins 任务。

为了方便维护,我们的 CI 系统如 Jenkins,也会使用 Docker 方式部署。
Jenkins 任务中有些任务需要将微服务构建成 Docker 镜像,然后推送到 Harbor 私有仓库中。
我们所有的 Jenkins Master 镜像和 Jenkins Slave 镜像本身都不包含任何额外的构建环境,执行任务时都需要启动包含对应环境的镜像来执行任务,一个Master可以关联多个Slave,Slave节点起到了分担工作任务和隔离构建环境的作用。


Jenkins 系统架构它是一个 master、slave 的系统架构。我们说的 Jenkins master 就是 Jenkins 服务器,Jenkins slave 的话其实就是我们说的构建节点,两者相当于是 server 和 agent 的一个概念。

当 job 被分配到slave 上进行运行的时候,此时 master 和 slave 其实是建立的一个双向字节流的链接,其中 master 与 slave 节点之间的连接方式主要有两种:

一种就是说 master 到 slave 节点的网络链路是通的,这种情况下的话,我们是可以使用 SSH 的方式,使用用户名、密码连接和启动 slave 节点;

另外一种情况就是说 master 到 slave 这个网络链路不通,但是 slave 到 master 这个网络链路是通的,这种情况下的话,我们就可以使用 JNLP 的连接方式(默认方式)。

我们的 Jenkins Master、Jenkins Slaves 都是跑在容器里面的,该如何在这些容器里面调用 docker run 命令启动包含 CI 环境的镜像呢?
在这些 CI 镜像里面,我们从源码编译完成后,又如何通过 docker build 将编译结果打包成 Docker 镜像,然后推送到内网仓库呢?

  • Docker解决方案:

Docker out of Docker

Docker 实现,将 Docker 的 UNIX Socket(/var/run/docker.sock)作为 hostPath 挂载到 CI/CD 的业务 Pod 中,之后在容器里通过 UNIX Socket 来调用宿主机上的 Docker 进行构建

真正执行我们的 docker 命令的是 docker engine,而这个 engine 跑在宿主机上。

  • Containerd解决方案

nerdctl in containerd

在容器中使用nerdctl需要将本地相关可执行文件或sock文件映射给容器使用,这里配置在jenkins在K8s环境里面使用nerdctl和buildkit,给出相关配置

       volumeMounts:
        - mountPath: /var/jenkins_home
          name: data
        - mountPath: /etc/localtime
          name: localtime
        - mountPath: /usr/bin/kubectl
          name: kubectl
        - mountPath: /usr/bin/buildctl
          name: buildctl
        - mountPath: /usr/bin/nerdctl
          name: nerdctl
        - mountPath: /usr/bin/containerd
          name: containerd
        - mountPath: /var/run/buildkit/buildkitd.sock
          name: buildkitd-sock
        - mountPath: /run/containerd/containerd.sock
          name: containerd-sock
      volumes:
      - hostPath:
          path: /deploy/sorts/jenkins/data
          type: ""
        name: data
      - hostPath:
          path: /etc/localtime
          type: ""
        name: localtime
      - hostPath:
          path: /usr/bin/kubectl
          type: ""
        name: kubectl
      - hostPath:
          path: /usr/bin/buildctl
          type: ""
        name: buildctl
      - hostPath:
          path: /usr/bin/nerdctl
          type: ""
        name: nerdctl
      - hostPath:
          path: /usr/bin/containerd
          type: ""
        name: containerd
      - hostPath:
          path: /var/run/buildkit/buildkitd.sock
          type: ""
        name: buildkitd-sock
      - hostPath:
          path: /run/containerd/containerd.sock
          type: ""
        name: containerd-sock


要特别注意,官方的jenkins配置k8s 自带jnlp continer,配置界面没有显示,这个用于在各个容器共享文件用的

pipeline示例:

pipeline {
  agent {
    node {
      label 'maven'
    }

  }
  stages {
    stage('Clone repository') {
      steps {
        git(branch: "${GIT_BRANCH}", credentialsId: "${GIT_CREDENTIAL_ID}", url: "${GIT_URL}")
      }
    }
  stage('Run compile') {
      steps {
          container('maven') {
          sh 'ls && pwd'
          sh 'mvn -s configuration/mvn-settings.xml clean install -DskipTests=true'
          sh 'sleep 10'
          }
      }
    }
 stage("build"){
    steps {
          container('maven') {
          sh 'echo "11.11.156.220 registry.example.com" >>/etc/hosts'
          sh 'nerdctl build -f  Dockerfile/Dockerfile  -t $REGISTRY/dj/$APP_NAME:v1 .'
           sh 'echo "xxxxxxxxx" | nerdctl login $REGISTRY -u "admin"  --insecure-registry --password-stdin'
          sh 'nerdctl push $REGISTRY/dj/$APP_NAME:v1 --insecure-registry'
          sh 'sleep 10'
          sh 'kubectl create deployment devopstest --image="$REGISTRY/dj/$APP_NAME:v1" -n jenkins'
          }
      }
    }    
     
  }
    environment {
    GIT_URL = 'http://11.11.xx.xx/cloudtf/cloudtf-basic.git'
    GIT_CREDENTIAL_ID = 'gitlab'
    GIT_BRANCH = 'sasac'
    REGISTRY = 'registry.example.com'
    APP_NAME = 'cloudtf-basic'
    REGISTRY_CREDENTIAL_ID = 'harbor-220'
    PROJECT_NAME = 'dj-test'
  }
}


备注:第一稿可能部分写的有问题,后续会改进优化。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接