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/并添加可执行权限
一些执行原理:
- 服务端为buildkitd,负责和runc或containerd后端连接干活,目前只支持这两个后端
- 客户端为buildctl,负责解析镜像构建文件Dockerfile,并向服务端发出构建指令,所以客户端可以和服务端不在一台机器上,也不需要root权限之类
- 服务端默认使用runc后端,但是建议使用containerd后端,这样构建出的镜像就会存在containerd的buildkit名字空间下
- ctr是containerd自带的命令行工具,除了构建镜像不能干,其他docker-cli的活它都可以干
- 因为buildkitd的后端选择了containerd,本地镜像会去到containerd的本地存储
- 默认本地镜像都存在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'
}
}
备注:第一稿可能部分写的有问题,后续会改进优化。