项目地址:
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