首页 > 其他分享 >自动镜像构建和加载镜像

自动镜像构建和加载镜像

时间:2023-06-02 15:58:29浏览次数:30  
标签:return nil err json 构建 镜像 omitempty string 加载

项目地址:
buildimage: https://github.com/zhangchi6414/buildimage
buildrun: https://github.com/zhangchi6414/buildrun
s2i-operator: https://github.com/kubesphere/s2ioperator
s2irun: https://github.com/kubesphere/s2irun

根据需求,实现一个通过上传的dockerfile构建镜像,解压缩save、export压缩的镜像,以及从外部加载镜像到本地仓库功能的插件。
参照了开源工具s2i-operator实现,增加了解压缩的功能。

buildimage

1.kubebuilder生成代码

https://xuejipeng.github.io/kubebuilder-doc-cn/

kubebuilder init --domain buildimage //创建项目
kubebuilder create api --group buildimage --version v1 --kind Builder  //创建API

生成了基础的代码框架,然后开始自定义字段开发和逻辑开发

2.自定义字段开发

//自定义minio字段
type MinioOption struct {
	Endpoint   string `json:"endpoint,omitempty" `
	DisableSSL bool   `json:"disableSSL,omitempty"`
	//ForcePathStyle  string `json:"forcePathStyle,omitempty" `
	AccessKeyID     string `json:"accessKeyID,omitempty" `
	SecretAccessKey string `json:"secretAccessKey,omitempty" `
	SessionToken    string `json:"sessionToken,omitempty" `
	Bucket          string `json:"bucket,omitempty" `
	CodeName        string `json:"codeName,omitempty"`
	CodePath        string `json:"codePath,omitempty"`
}
//自定义harbor字段
type HarborOption struct {
	Endpoint   string `json:"endpoint,omitempty"`
	DisableSSL bool   `json:"disableSSL,omitempty" `
	Username   string `json:"username,omitempty" `
	Password   string `json:"password,omitempty" `
}
//自定义git字段
type GitOption struct {
	Endpoint   string `json:"endpoint,omitempty"`
	DisableSSL bool   `json:"disableSSL,omitempty" `
	Username   string `json:"username,omitempty" `
	Password   string `json:"password,omitempty" `
}
//自定义构建操作相关字段,对应crd文件中的字段
type Buildconfig struct {
	IsMinio        bool          `json:"IsMinio,omitempty"`
	Minio          *MinioOption  `json:"minio,omitempty"`
	IsSave         bool          `json:"IsSave,omitempty"`
	IsExport       bool          `json:"IsExport,omitempty"`
	HarborUrl      string        `json:"harborUrl,omitempty"`
	Harbor         *HarborOption `json:"harbor,omitempty"`
	NewImageName   string        `json:"newImageName,omitempty"`
	NewTag         string        `json:"newTag,omitempty"`
	IsGit          bool          `json:"isGit,omitempty"`
	Git            *GitOption    `json:"git,omitempty"`
	DockerfileName string        `json:"dockerfileName,omitempty"`
	BackLimit      int32         `json:"backLimit,omitempty"`
	SaveImageName  string        `json:"saveImageName,omitempty"`
}

// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.

// BuilderSpec defines the desired state of Builder
type BuilderSpec struct {
	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
	// Important: Run "make" to regenerate code after modifying this file

	Config *Buildconfig `json:"config,omitempty" `
}

//定义自定义资源状态的相关字段
// BuilderStatus defines the observed state of Builder
type BuilderStatus struct {
	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
	// Important: Run "make" to regenerate code after modifying this file
	//RunCount represent the sum of s2irun of this builder
	RunCount int `json:"runCount"`
	//LastRunState return the state of the newest run of this builder
	LastRunState RunState `json:"lastRunState,omitempty"`
	//LastRunState return the name of the newest run of this builder
	LastRunName *string `json:"lastRunName,omitempty"`
	//LastRunStartTime return the startTime of the newest run of this builder
	LastRunStartTime *metav1.Time `json:"lastRunStartTime,omitempty"`
}

// +genclient

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// Builder is the Schema for the builders API
type Builder struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   BuilderSpec   `json:"spec,omitempty"`
	Status BuilderStatus `json:"status,omitempty"`
}

3.逻辑开发

创建自定义资源---读取字段---创建对应的job---通过buid容器来拉取代码构建镜像

读取字段

func (r *BuilderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	//_ = log.FromContext(ctx)
	log.Info("开始执行构建任务:", "Name", req.Name)
	//判断资源是否被创建
	instance := &buildimagev1.Builder{}
    //实例化对象,把创建的builder中的字段和结构体绑定
	err := r.Get(ctx, req.NamespacedName, instance)
	if err != nil {
		if errors.IsNotFound(err) {
			fmt.Println("not found resource~!")
			return ctrl.Result{}, err
		}
		return ctrl.Result{}, err
	}
	crs, err := createRBAC(ctx, r, instance)
	if err != nil {
		return crs, err
	}
	//查看job是否存在,不存在则创建
	Job := &v1.Job{}
	err = r.Get(ctx, types.NamespacedName{Namespace: instance.Namespace, Name: instance.Name}, Job)
	if err != nil {
		if errors.IsNotFound(err) {
			res, err := createJob(ctx, r, instance)
			return res, err
		} else {
			return ctrl.Result{}, err
		}
	}
	//检查job状态,更新build状态

	return ctrl.Result{}, nil
}

创建job

type Jobs struct {
	VolumeMount []v1.VolumeMount
	Volume      []v1.Volume
	Env         []v1.EnvVar
}
//创建job任务
func (j *Jobs) CreateJob(instance *buildimagev1.Builder) (*v12.Job, error) {
	var job = &v12.Job{}
	jobName := instance.Name + fmt.Sprintf("-%s", utils.Randow()+"-job")
	imageName := os.Getenv("BUILDIMAGENAME")
	//TODO 测试
	//if imageName == "" {
	//	return nil, fmt.Errorf("Failed to get s2i-image name, please set the env 'S2IIMAGENAME' ")
	//}
	//TODO 默认镜像需要替换
	if imageName == "" {
		imageName = "Alpine"
	}
	job = &v12.Job{
		ObjectMeta: metav1.ObjectMeta{
			Name:      jobName,
			Namespace: instance.ObjectMeta.Namespace,
		},
		Spec: v12.JobSpec{
			Template: v1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{"job-name": jobName},
				},
				Spec: v1.PodSpec{
					ServiceAccountName: RegularServiceAccount,
					Containers: []v1.Container{
						{
							Name:            "buildimage",
							Image:           imageName,
							Command:         []string{"./builder"},
							ImagePullPolicy: v1.PullIfNotPresent,
							Env:             j.Env,
							VolumeMounts:    j.VolumeMount,
							SecurityContext: &v1.SecurityContext{
								Privileged: truePtr(),
							},
						},
					},
					RestartPolicy: v1.RestartPolicyNever,
					Volumes:       j.Volume,
				},
			},
			BackoffLimit: &instance.Spec.Config.BackLimit,
		},
	}
	return job, nil
}

判断不同不模式挂载不同变量和文件

func slectFunc(instance *buildimagev1.Builder) (*v1.Job, error) {
	//判断获取代码文件的方式
	jobs := &pkg.Jobs{}
	if instance.Spec.Config.IsMinio {
		// TODO 创建从minio获取源码的方式
		jobs = &pkg.Jobs{
			Volume: []corev1.Volume{
				{
					Name: "dockerfile",
					VolumeSource: corev1.VolumeSource{
						ConfigMap: &corev1.ConfigMapVolumeSource{
							LocalObjectReference: corev1.LocalObjectReference{
								Name: instance.Spec.Config.DockerfileName,
							},
							Items: []corev1.KeyToPath{
								{
									Key:  pkg.ConfigDataKey,
									Path: "Dockerfile",
								},
							},
						},
					},
				},
				{
					Name: "docker-sock",
					VolumeSource: corev1.VolumeSource{
						HostPath: &corev1.HostPathVolumeSource{
							Path: "/var/run/docker.sock",
						},
					},
				},
			},
			VolumeMount: []corev1.VolumeMount{
				{
					Name:      "docker-sock",
					MountPath: "/var/run/docker.sock",
				},
				{
					Name:      "dockerfile",
					MountPath: "/config",
				},
			},
			Env: []corev1.EnvVar{
				{
					Name:  "MinioUrl",
					Value: instance.Spec.Config.Minio.Endpoint,
				},
			....
	}
	if instance.Spec.Config.IsSave {
		// TODO 创建通过Save方式上传镜像的方法
			....
	}
	if instance.Spec.Config.IsExport {
        // TODO 创建通过Export方式上传镜像的方法
			....
	}
	if instance.Spec.Config.IsGit {
		// TODO 创建从git获取源码的方式
				...

	}
	job, err := jobs.CreateJob(instance)
	if err != nil {
		return nil, err
	}
	return job, err
}

buildRun

实现代码拉取,镜像导入,镜像推送

代码拉取

func (o *MinioOption) Pull(cli *minio.Client) error {
	fmt.Println(&cli)
	zap.S().Info("Start download code!")
	ctx := context.Background()
	err := cli.FGetObject(ctx, o.Bucket, o.CodePath+o.CodeName, o.ForcePathStyle+o.CodeName, minio.GetObjectOptions{})
	if err != nil {
		zap.S().Error(err)
		return nil
	}
	zap.S().Info("Download the file successful!")
	return nil
}

镜像构建

func (d *stiDocker) BuildImage(cli *client.Client, codeName, name string) error {
	var tags = []string{name}
	fileOptions := types.ImageBuildOptions{
		Tags:           tags,
		Dockerfile:     "docker/Dockerfile",
		SuppressOutput: false,
		Remove:         true,
		ForceRemove:    true,
		PullParent:     true,
	}
	//拷贝Dockerfile
	err := utils.Copy(pkg.DOCKERFILE, pkg.DOCKERFILEPATH+"Dockerfile")
	if err != nil {
		return err
	}
	//拷贝代码文件
	err = utils.Copy(codeName, pkg.DOCKERFILEPATH+codeName)
	if err != nil {
		return err
	}
	var destTar = "docker.tar"
	//把文件打成tar包
	err = utils.Tar(pkg.DOCKERFILEPATH, destTar, false)
	if err != nil {
		zap.S().Error(err)
		return err
	}
	//执行构建
	zap.S().Info("Start build image:", name)
	ctx := context.Background()
	dockerBuildContext, err := os.Open(destTar)
	if err != nil {
		return err
	}
	defer dockerBuildContext.Close()
	buildResponse, err := cli.ImageBuild(ctx, dockerBuildContext, fileOptions)
	if err != nil {
		zap.S().Error(err)
		return err
	}
	_ = logImage(buildResponse.Body)
	zap.S().Info("Start build image:", name, "success!")
	err = d.PushImage(cli, name)
	if err != nil {
		zap.S().Error(err)
		return err
	}
	return nil
}

镜像导入

func (d *stiDocker) LoadImage(cli *client.Client, code, oldName, name string) error {
	//打开镜像文件
	imageFile, err := os.Open(code)
	if err != nil {
		zap.S().Error(err)
	}
	defer imageFile.Close()
	ctx := context.Background()
	zap.S().Info("Start load image")
	load, err := cli.ImageLoad(ctx, imageFile, true)

	defer load.Body.Close()
	//load镜像
	str := logImage(load.Body)
	//_ = logImage(load.Body)
	if err != nil {
		os.Exit(pkg.LOADIMAGEERROR)
		zap.S().Error(err)
	}
	//获取load后的镜像名称
	start := strings.Index(str, ": ") + 2
	end := strings.Index(str[start:], "\\n")
	imageName := str[start : start+end]
	zap.S().Info("Image load success!")
	if name == "" {
		name = imageName
	}
	err = cli.ImageTag(ctx, oldName, name)
	if err != nil {
		return err
	}
	//导入镜像
	err = d.PushImage(cli, name)
	if err != nil {
		return err
	}
	return nil
}

func (d *stiDocker) ImportImage(cli *client.Client, name, imageName string) error {
	//读取镜像文件
	imageFile, err := os.Open(name)
	defer imageFile.Close()
	if err != nil {
		return err
	}
	options := types.ImageImportOptions{}
	source := types.ImageImportSource{
		Source:     imageFile,
		SourceName: "-",
	}
	//import镜像文件
	ctx := context.Background()
	zap.S().Info("Start import image!")
	imageImport, err := cli.ImageImport(ctx, source, imageName, options)
	defer imageImport.Close()
	_ = logImage(imageImport)
	if err != nil {
		os.Exit(pkg.IMPORTIMAGEERROR)
		return err
	}
	//推送镜像
	err = d.PushImage(cli, imageName)

	if err != nil {
		return err
	}
	return nil
}

镜像推送

func (d *stiDocker) PushImage(cli *client.Client, name string) error {
	//harbor认证
	authConfig := types.AuthConfig{
		Username: d.UserName,
		Password: d.Password,
	}
	authStr, err := encodeAuthToBase64(authConfig)
	if err != nil {
		zap.S().Error(err)
		return err
	}
	//读取镜像文件
	zap.S().Info("start push image:", name)
	var pushReader io.ReadCloser
	pushReader, err = cli.ImagePush(context.Background(), name, types.ImagePushOptions{
		All:           false,
		RegistryAuth:  authStr,
		PrivilegeFunc: nil,
	})
	defer pushReader.Close()
	//输出推送进度
	_ = logImage(pushReader)
	if err != nil {
		os.Exit(pkg.PUSHIMAGEERROR)
		zap.S().Error(err)
		return err
	}
	zap.S().Info("push success ! ", name)
	return nil
}

使用方式

Dockerfile

#需要通过ConfigMap挂载Dockerfile
apiVersion: v1
kind: ConfigMap
metadata:
  name: game-demo
data:
  dockerfile: |    #key值固定
    FROM 192.168.2.106:1180/456/alpine:3.6
    COPY docker/address.png /root/
    CMD ["sleep 30000"]

1.自定义builder
apiVersion: buildimage.buildimage/v1  #自定义的crd资源对象
kind: Builder
metadata:
  name: build-test
spec:
  config:
    IsMinio: true   #改模式为自定义Dockerfile,不是自定义Dockerfile这个关键字可不写
    harbor:   #harbor信息
      username: admin
      password: Dyg@12345
    newImageName: 192.168.2.106:1180/4561/fzzn-test1  #新构建镜像的名称
    newTag: v3  #新镜像tag
    dockerfileName: game-demo  #指定构建镜像的Dockerfile 需要在同一namespace
    backLimit: 1   #job出错重启次数限制
    fromImage: 192.168.2.106:1180/4561/fzzn:v1  #Dockerfile中的基础镜像名称

2.上传通过save生成的rar/tar格式镜像文件
apiVersion: buildimage.buildimage/v1
kind: Builder
metadata:
  name: build-test
spec:
  config:
    IsSave: true
    saveImageName:  redis:5.0.7  #打包之前的镜像名称  记不起可以不填
    minio:       #minio的信息
      accessKeyID: admin
      secretAccessKey: abcdefg123456
      endpoint: 192.168.2.108:30900  #注意格式要一致,不能有Http/https
      codePath: /fz-1/ay/  #文件在minio中的路径要一致不能少 /
      bucket: dyg-fzzn     #minio中的bucket
      codeName: redis.rar   #需要从minio中下载的文件
    harbor:
      username: admin
      password: Dyg@12345
    newImageName: 192.168.2.106:1180/456/redis
    newTag: v3
    backLimit: 1
3.上传通过export生成的Img格式镜像文件
apiVersion: buildimage.buildimage/v1
kind: Builder
metadata:
  name: build-test
spec:
  config:
    IsExport: true
    minio:
      accessKeyID: admin
      secretAccessKey: abcdefg123456
      endpoint: 192.168.2.108:30900
      codePath: /fz-1/
      bucket: dyg-fzzn
      codeName: import.img   #文件名称不要错了
    harbor:
      username: admin
      password: Dyg@12345
    newImageName: 192.168.2.106:1180/456/img
    newTag: v3
    backLimit: 1

部署方式

make install 
make deploy
make undeploy

标签:return,nil,err,json,构建,镜像,omitempty,string,加载
From: https://www.cnblogs.com/jingzhe2020/p/17451997.html

相关文章

  • 仙境传说RO :ra脚本加载结构和开发语法讲解​
    仙境传说RO:ra脚本加载结构和开发语法讲解大家好,我是艾西。上一篇文章中我们聊完了怎么在游戏中新建NPC,感觉还是有不少小伙伴没有太看懂原理。今天艾西给大家深度讲解一下脚本加载结构和开发语法环境文档。我们最后都是以ra脚本为主要讲解以及实操,那想完全掌握ra脚本查看相关的文档......
  • npm安装淘宝镜像cnpm报错:npm ERR! Windows_NT 10.0.19045
    1、报错信息如下:npmERR!Windows_NT10.0.19045...2、原因,环境没配好3、解决:配置环境变量系统变量:①新建-变量名:NODE_HOME;变量值:D:\node-v14.18\nodeInstall\node-v14.18.2-win-x64node根目录下,新建两个目录,分别命名为:”node_global”、”node_cache”②path......
  • [UE4]资源异步加载(Assets Asynchronous Loading)与内存释放(Free Memory)
    为什么需要异步加载资源,因为当一次性加载的资源较多或者单个资源较大时,普通的LoadObject()方式会阻塞引擎的主线程。 假设测试工程叫TestTD4,自定义Character叫ATestTD4Character(头文件为TestTD4Character.h)假设在Content/Assets/目录下放了三个动画文件(AnimSequence)。异步加......
  • 玩转服务器之应用篇:从零开始构建小型高可用环境
    高可用环境介绍搭建高可用环境,可以消除单点故障的影响,使系统在出现故障时自动地切换到其它节点,保障系统的平稳运行,提高系统的可靠性和可用性,同时保证数据的安全性,高可用环境已经是现代企业应用的标配。本文介绍如何快速搭建一个小型的高可用环境,涉及的产品包括:2台云主机(CenTOS7.6......
  • 构建之法阅读笔记06
    9.1PM是啥软件团队里除了能写代码、测试代码和画图做设计的成员,还有一类角色,不做上面这些事情但也很重要,我们叫他们项目经理——PM ProductManager:产品经理——正确地做产品ProjectManager:项目经理——正确地做流程ProgramManager:微软的职位名称 微软产品团队三足鼎立......
  • 构建之法阅读笔记3
    下面这些都是按照顺序整理的一些零碎的阅读笔记,可能看起来毕竟杂乱,同时也阅览了网上的一些其他的阅读笔记进行借鉴。读完这本书,感觉并不是只讲软件工程,或者说并不像我想象的那些。但是至少我读到了一些东西,获得了一些知识。一图胜千言文学化编程:写文档,时不时写些代码设计的......
  • 基于nerdctl+buildkitd+containerd实现镜像构建
    1、容器技术简介容器技术除了的docker之外,还有coreOS的rkt、google的gvisor、以及docker开源的containerd、redhat的podman、阿⾥的pouch等,为了保证容器⽣态的标准性和健康可持续发展,包括Linux基⾦会、Docker、微软、红帽、⾕歌和IBM等公司在2015年6⽉共同成⽴了⼀个叫opencont......
  • RabbitMq镜像策略模式
    镜像策略ha-modeha-params说明exactlycount集群中队列副本的数量(主队列加上镜像)。count值为1表示一个副本:只有主节点。如果主节点不可用,则其行为取决于队列是否持久化。count值为2表示两个副本:一个队列主队列和一个队列镜像。换句话说......
  • Label数据窗口, “标签”窗口加载数据并打印
    说明:依据的"数据窗口(dw_6)"数据变化时需要调用该函数重新检索加载打印数据(of_label()),如下  效果:1//自建的函数,用于操作标签"label"数据窗口对象,加载及打印数据(隐藏)2//依据的"数据窗口(dw_6)"数据变化时需要调用该函数重新检索加载打印数据(of_label())3long......
  • VuePress2.0构建项目文档系统
    VuePress2.0构建项目文档系统参考TerraMours官网。https://terramours.site/文件结构参考:1.修改首页README.md修改项目下的README.md,修改内容:---home:trueheroImage:images/hero.pngheroText:TerraMoursactions:-text:快速开始link:/guide/type:pr......