一、前言
总所周知,从Kubernetes1.24版本开始已经弃用Docker这个陪伴它风声水起的”初恋女友”, 届时在Kubernetes社区掀起了异常”轩然大波”,影响甚至波及到社区之外的,也导致了Kubernetes不得不写好几篇博客来反复解释这么做的原因,虽然是老生常谈的问题了,如今距离1.24版本正式发布已过去一年之久,本篇文章带大家回顾下为什么Kubernetes要弃用陪伴它多年的Docker
二、Kubernetes发展史
时间回到2014年,Docker正如日中天,可以说放眼容器领域,没有任何对手,可谓是无敌于天下,而这是Kubernetes才刚刚诞生,虽然背后有Google和Borg这两位大佬支持,但自身实力还是比较弱小,需要时间去孵化成长,就在此时,Kubernetes与Dcoker邂逅了,它被Docker强大的魅力所吸引,自然而然的选择了依附于它,毕竟“背靠大树好乘凉”。同时也好趁机”养精蓄锐”逐步发展壮大自己。
时间转瞬即逝来到了2016年,CNCF已经成立了一年,而Kubernetes也已经发布了1.0版本,可以正式投入企业生产环境中。这已经标志Kubernetes已成长起来了,有了底气之后不需要”看脸色吃饭”, 为了进一步壮大自己,于是乎它加入了CNCF这个组织,成为了CNCF一个托管项目,其目的就是借助基金会的力量联合其它厂商一起”绊倒”Docker
那么Kubernetes是如何一步步实现自己的“宏图大业”呢? 在2016年底1.5版。Kubernetes引入了新的接口标准,即CRI(Container Runtime Interface),该接口采用了ProtoBuffer和gPRC,规定kubelet该如何调用容器运行时去管理容器和自己的镜像。但由于这是一套全新的接口,无法和自己的”初恋女友”Docker继续相处。其实到这里,Kubernetes意思很明显,就是不想再绑定Docker上,允许在底层接入其它容器技术(比如rkt、kata等),随时可以将Docker踢开
但是这个Docker已是非常强大了。占领较大的市场份额且市场惯性非常强大,很多企业已对Docker这款开源产品非常依赖了,再者各大云厂商不可能一下子将Docker全部替换掉,因此Kubernetes便提出了折中的方案,在kubelet和Docker中加入了符合CRI接口,我们可以把接口理解为”适配器”,主要适用于Docker的接口转换成符合Kubernetes 自己的CRI标准的接口
如上图所示,这个”适配器”在kubelet和Docker之间,所以就被很形象的称之为”shim”,也就是垫片的意思。有了CRI和shim,虽然Kubernetes还使用Docker作为底层运行时,但也具备了和Docker解耦的条件,从此拉开了”弃用Docker”的帷幕。
另一方面,Docker没有坐以待毙,而是采用了断臂求生的策略,推动自身的重构,把原本单体架构的Docker Engine拆分成多个模块,其中的Docker daemon部分捐献给CNCF,至此这就形成了我们熟知的Containerd概念,Containerd作为CNCF托管项目,自然是要符合CRI标准的,但由于Docker出于自身诸多原因考虑,它只是在Docker Engine里调用了containerd。外部接口仍然保持不变,也就是说还是不能和CRI兼容
此时Kubernetes便给出了两种调用链,如下如所示:
1.kubelet基于CRI接口去调用dockershim适配器,然后dockershim适配器进行转换去调用docker,随后docker在去调用containerd去操作容器
2.kubelet基于CRI接口直接调用containerd去操作容器
综合对比,上述都是用containerd来管理容器,虽然最终效果是完全一样的,但第二种方式省去了dockershim和Docker Engine两个环节,对于整个架构体系来说,更加间接明了,在性能方面也得到的较大提升。同时在2018年Kubernetes 1.10发布时,Containerd也更新至1.1版。正式与Kubernetes集成,官方也针对其两者做了性能压测,如下所示
上述性能对比来自Kubernetes官方(https://kubernetes.io/blog/2018/05/24/kubernetes-containerd-integration-goes-ga/)从上述数据可以看到,Containerd相对于Docker,Pod的启动延迟降低了20%,CPU使用率降低了68,内存使用率降低了12%。 由此,这是一个相对较大的性能优化,这这对于我们企业生产环境来说是非常具有诱惑力的。
有了上述对比,一直青睐于Docker的各位云厂商以及企业开始有了动摇,到了2020年,Kubernetes在 1.20版本正式向Docker宣战,宣告在未来1.24版本彻底弃用Docker,让它做好心里准备。
三、需求背景
时间来到今天,因为项目环境特殊需求,阿里云平台ACK版本最老的集群版本就是1.24,对于我们一直在用Kubernetes v1.24之前算是新的挑战和学习。首先要克服的就是研发提交代码如何自动化部署到k8s集群的问题,这里我将问题归类以下两点:
1、1.24版本开始已弃用Docker,如何集成DevOps CICD流水线技术呢?
2、在1.24版本之前通常基于Dockerfile构建业务镜像,但弃用Docker之后,如何构建业务镜像部署到K8S集群呢?
带着上述的疑问,接下来介绍Kubernetes 构建业务镜像的新宠-“kaniko”
3.1、什么是kaniko?
kaniko文档地址:https://github.com/GoogleContainerTools/kaniko#–insecure
Kaniko是一种在容器或者Kubernetes 集群内从Dockerfile构建容器镜像的工具,不依赖Docker.sock守护进程,而是完全在用户空间中执行Dockerfile中的每个命令,这使得在没有docker.sock的基础环境中构建Dockerfile镜像成为可能,例如标准的Kubernetes1.24集群。
3.2、为何要使用kaniko?
其主要原因由于Kaniko不依赖Docker守护进程,并且完全在用户空间下执行Dockerfile中每个命令,这使得能够轻松并安全的运行在无Docker环境守护进程,在kubernetes 1.24版本之后默认将会采用containerd.io,作为缺省的CRI接口,不在支持dockershim,也就意味着不需要安装docker环境就能构建Dockerfile镜像。
3.3、kaniko工作原理流程是什么?
首先会读取指定Dockerfile文件,将基本的镜像(FROM指令中指定)提取到容器文件系统中,在独立的Dockerfile中分别运行每个指令,并且每次运行后都会对用户空间文件系统做一次快照,并将快照层附加到基础层,完成之后就会自动推送镜像至仓库,全程无需人工干涉
Kaniko仅支持容器镜像运行,接受三个参数:一是Dockerfile构建文件,二是构建上下文目录,三是将业务镜像构建完成之后需要推送的注册表中心,也就是我们所说的镜像仓库。kaniko容器中有一个可执行的二进制文件即/kaniko/executor,它在执行程序镜像中提取基本镜像的文件系统,然后在Dockerfile中执行任何命令,快照用户空间的文件系统,Kaniko在每个命令后都会将一层更改的文件附加到基本镜像,最后,执行程序将新的镜像推送到镜像仓库中,由于Kaniko在执行程序镜像的用户空间中完全执行了这些操作,因此它完全避免了用户计算机上需要特权访问,从而提高了安全性。
参数详解
--context:指定构建上下文的路径。默认情况下,上下文是Dockerfile所在的目录。可简写 -c
--dockerfile:指定要使用的Dockerfile的路径。默认情况下,Kaniko会在上下文中查找名为Dockerfile的文件。可简写 -f
--destination:指定构建完成后的Docker镜像名称,可服务器托管网以包括标签。例如:myregistry/myimage:tag。可简写 -d
--cache:启用或禁用Kaniko的构建缓存功能。默认情况下,缓存是启用的。
--cache-ttl:设置构建缓存的生存时间。例如,--cache-ttl=10h表示缓存在构建完成后的10小时内有效。
--cache-repo:指定用于存储构建缓存的Docker仓库。默认情况下,缓存存储在本地。
--cache-dir:指定用于存储构建缓存的本地目录路径。
--skip-tls-verify:跳过TLS证书验证,用于不安全的Docker仓库。
--build-arg:传递构建参数给Dockerfile中的ARG指令。例如:--build-arg key=value。
--insecure:允许从不受信任的Registry拉取基础镜像。
--insecure-registry:允许连接到不受信任的Registry。
--verbosity:设置构建的详细程度,可以是panic、error、warning、info、debug或trace。
--digest-file:指定一个文件,用于存储构建生成的镜像的摘要(Digest)。
--oci-layout-path:指定OCI(Open Container Initiative)布局文件的路径,用于存储构建过程的元数据。
四、企业实战演练
上述对Kankio进行了细致化概念性的讲解,接下来通过实战带大家更好的去了解Kaiko它独有的魅力,逐一去演示。
Dockerfile准备就绪之后,开始启动容器,并验证是否自动生成镜像并完成推送
4.1、基于Kubernets环境使用Kaniko构建业务镜像
4.1.1. 定义镜像仓库secret
获取镜像仓库认证信息,在这里我使用阿里云中的作为镜像仓库,随便找一台有docker服务的Linux服务器,然后基于docker login做一次镜像仓库认证即可生成认证文件~/.docker/config.json,随后,将config.json拷贝拷贝至容器编排集群中,将其定义为secret资源。
指定harbor镜像仓库用户名以及地址
#docker login -u [镜像账号] [镜像仓库地址]
创建指定harbor镜像config.json文件,定义名为kaniko-secret的secret资源
# kubectl create secret generic kaniko-secret --from-file=/root/.docker/config.json
4.1.2. 定义Dockerfile
既然要通过Kaniko去构建业务镜像,那么必须必不可少的仍然是Dockerfile,在这里准备了一个简单的Docker文件,需要将其定义为Configmap作为容器编排使用。
定义configmap资源
#kubectl create configmap kaniko-dockerfile --from-file=Dockerfile
截止目前,Kaniko所依赖的镜像仓库认证信息以及构建对象都已准备完毕。
4.1.3. 基于Pod实例实现业务镜像构建
如下所示,在这里我给出了pod定义的实例,
apiVersion: v1
kind: Pod
metadata:
name: kaniko
spec:
containers:
- name: kaniko
image: kubebiz/kaniko:executor-v1.9.1
args: ["--dockerfile=/workspace/Dockerfile", #指定Dockerfile文件
"--context=dir://workspace", #指定Dockerfile 上下文件目录
"--destination=registry.cn-beijing.aliyuncs.com/devops-o服务器托管网p/kaniko-demo:1.0"] #指定镜像仓库地址
volumeMounts: #将上述信息都挂载至kaniko容器相应的目录中
- name: kaniko-secret
mountPath: /kaniko/.docker
- name: dockerfile
mountPath: /workspace
volumes:
- name: kaniko-secret
secret:
secretName: kaniko-secret #容器编排所依赖的镜像仓库认证信息
items:
- key: config.json
path: config.json
- name: dockerfile
configMap:
name: kaniko-dockerfile #容器编排所依赖的Dockerfile
通过kubectl apply 创建更新下pod实例资源
#kubectl apply -f kaniko_build.yaml
[root@akc-node01 kaniko]# kubectl apply -f kaniko_build.yaml
pod/kaniko created
[root@akc-node01 kaniko]# kubectl get pod
NAME READY STATUS RESTARTS AGE
kaniko 1/1 Running 0 3s
查看kaniko日志即可看到详细的构建过程,如下图所示
[root@akc-node01 kaniko]# kubectl logs -f kaniko
最后在仓库平台上可以看到,镜像已推送成功。以上就是如何去再容器编排pod实例构建自己的业务镜像,并完成推送的详细演示了。
4.2、基于Jenkins Pipeline 流水线环境集成Kaniko构建业务镜像
那么如何将Kaniko集成至我们的企业生产的DevOps流程中呢?能否直接将上面的pod实例拷贝直接用呢?答案显然是不行的!我们都知道每个项目所构建的业务镜像是不一样,也就是说Dockerfile对象资源是动态的,上述演示案例本质是将Dockerfile资源作为Configmap资源挂载到pod实例中进行构建。再来回顾下传统打包流程是怎样的呢?当业务代码构建完毕之后,通过docker build -t去指定Dockerfile资源。我们的Kaniko能否Dockerfile去构建业务镜像呢?话不多说,来吧展示!
4.2.1、定义镜像仓库认证信息Secret
由于在上述暗示过,这里不再赘述。
4.2.2、Jenkins pipeline使用
基于Kubernetes 动态生成Jenkins Slave pod CI/CD,以下仅是给出简化的Pipeline示例
pipeline {
agent {
kubernetes {
cloud 'kubernetes-ACK'
.............................................
.............................................
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
.............................................
.............................................
.............................................
.............................................
#配置kaniko slave pod镜像
- command:
- /bin/sh
- -c
args:
- cat
tty: true
volumeMounts: #将镜像仓库地址挂载到kaniko中
- name: kaniko-secret #指定secret名称,上述已定义好,这里直接指定即可
mountPath: /kaniko/.docker
env:
- name: "LANGUAGE"
value: "en_US:en"
- name: "LC_ALL"
value: "en_US.UTF-8"
- name: "LANG"
value: "en_US.UTF-8"
image: registry.cn-beijing.aliyuncs.com/devops-tols/kaniko-executor:debug #kaniko二进制容器镜像
imagePullPolicy: Always
name: "kaniko"
tty: true
nodeSelector:
jenkins-build: "true"
volumes:
- name: kaniko-secret
secret:
secretName: kaniko-secret
items:
- key: config.json
path: config.json
- hostPath:
path: "/usr/share/zoneinfo/Asia/Shanghai"
name: "localtime"
- name: "cachedir"
hostPath:
path: "/opt/m2"
'''
}
}
stages {
stage('Pulling Code') {
parallel {
stage('Pulling Code by Jenkins') {
.............................................
代码拉取环节
.............................................
}
}
stage('Building') {
steps {
container(name: 'build') {
sh """
构建部分 .............................................
"""
}
}
}
stage('Docker build for creating image') {
environment {
HARBOR_USER = credentials('HARBOR_ACCOUNT')
}
steps {
container(name: 'kaniko') {
sh """
echo ${HARBOR_USER_USR} ${HARBOR_USER_PSW} ${TAG}
pwd
#由于Dockerfile定义在git上,项目构建完成之后,基于Kaniko二进制文件/kaniko/executor构建业务镜像,其中
-f是dockerfile的简写,用于指定Dockerfile文件路径
-c是context的简写,用于指定构建上下文的路径。默认情况下,上下文是Dockerfile所在的目录
-d是destination的简写,用于指定构建完成后的Docker镜像名称
/kaniko/executor -f Dockerfile -c ./ -d ${HARBOR_ADDRESS}/${REGISTRY_DIR}/${IMAGE_NAME}:${TAG} --force
"""
}
}
}
stage('Deploying to K8s') {
environment {
MY_KUBECONFIG = credentials('k8s-kubeconfig')
}
steps {
container(name: 'kubectl'){
sh """
部署 .....................
}
}
}
}
environment {
定义变量
.............................................
.............................................
TAG = ""
}
parameters {
gitParameter(branch: '', branchFilter: 'origin/(.*)', defaultValue: '', description: 'Branch for build and deploy', name: 'BRANCH', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH')
}
}
五、验证
基于Kaniko集成至Pipeline流水线体系到这里就结束了,如下图所示
关于Kaniko与Jenkins Pipeline流水线的使用就说这么多,与君共勉
END
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
this关键字 this 是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。在java中,这是一个引用当前对象的引用变量。 java this关键字的用法如下: this关键字可用来引用当前类的实例变量。 this关键字可用于调用当前类方法(…