Website-operator

背景

在工作中经常会有使用k8s部署站点应用,一般会创建两个编排文件:deployment.yamlsvc.yaml,但是有operator概念之后,其实我们可以自定义一个operator来帮忙创建deploymentsvc
此篇文章的目的就是通过编写一个自定义的website-operator来实现此功能。只需要提供一个简单的yaml文件,就可以实现需求。
样例YAML

apiVersion: website.xianyuluo.com/v1
kind: Website
metadata:
  name: nginx-app
  namespace: website-operator-system
spec:
  size: 3
  image: xianyuluo/nginx:1.12.2.website-operator
  port:
    - port: 80
      targetPort: 80

website-operator可以根据上面的yaml文件自动部署DeploymentSVC

实现

使用CoreOS公司开源的operator-sdk框架实现。框架的内容可以参考官网,框架其实比较简单,核心的东西就是kubernetesgolang客户端,这里就不在赘述了。

需要自己动手的代码

website_types.go

自定义operator编排文件格式实例状态

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

	Size      *int32                      `json:"size"`
	Image     string                      `json:"image"`
	Resources corev1.ResourceRequirements `json:"resources,omitempty"`
	Envs      []corev1.EnvVar             `json:"envs,omitempty"`
	Port      []corev1.ServicePort        `json:"port,omitempty"`
}

...

// WebsiteStatus defines the observed state of Website
type WebsiteStatus struct {
	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
	// Important: Run "make" to regenerate code after modifying this file
	v1beta1.DeploymentStatus `json:",inline"`
}

website_controller.go

所有逻辑都在由框架自动生成的Reconcile方法中,其他的自己任意补充。

// 1、返回container
func newContainers(app *websitev1.Website) []corev1.Container {
	return []corev1.Container{
		{
			Name:            app.Name,
			Image:           app.Spec.Image,
			Resources:       app.Spec.Resources,
			ImagePullPolicy: corev1.PullIfNotPresent,
		},
	}
}

// 2、创建Deployment
func NewDeploy(app *websitev1.Website) *appsv1beta1.Deployment {
	labels := map[string]string{"app": app.Name}
	selector := &metav1.LabelSelector{MatchLabels: labels}
	return &appsv1beta1.Deployment{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "apps/v1",
			Kind:       "Deployment",
		},

		ObjectMeta: metav1.ObjectMeta{
			Name:      app.Name,
			Namespace: app.Namespace,

			OwnerReferences: []metav1.OwnerReference{
				*metav1.NewControllerRef(app, schema.GroupVersionKind{
					Group:   metav1.SchemeGroupVersion.Group,
					Version: metav1.SchemeGroupVersion.Version,
					Kind:    "Website",
				}),
			},
		},

		Spec: appsv1beta1.DeploymentSpec{
			Replicas: app.Spec.Size,
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: labels,
				},
				Spec: corev1.PodSpec{
					Containers: newContainers(app),
				},
			},
			Selector: selector,
		},
	}
}

// 3、创建Service
func NewService(app *websitev1.Website) *corev1.Service {
	return &corev1.Service{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Service",
			APIVersion: "v1",
		},

		ObjectMeta: metav1.ObjectMeta{
			Name:      app.Name,
			Namespace: app.Namespace,
			OwnerReferences: []metav1.OwnerReference{
				*metav1.NewControllerRef(app, schema.GroupVersionKind{
					Group:   metav1.SchemeGroupVersion.Group,
					Version: metav1.SchemeGroupVersion.Version,
					Kind:    "Website",
				}),
			},
		},

		Spec: corev1.ServiceSpec{
			Type:  corev1.ServiceTypeLoadBalancer,
			Ports: app.Spec.Port,
			Selector: map[string]string{
				"app": app.Name,
			},
		},
	}
}

//+kubebuilder:rbac:groups=website.xianyuluo.com,resources=websites,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=website.xianyuluo.com,resources=websites/status,verbs=get;update;patch

func (r *WebsiteReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
	_ = context.Background()
	_ = r.Log.WithValues("website", req.NamespacedName)

	// your logic here
	// Fetch the website instance
	instance := &websitev1.Website{}
	fmt.Println("Instance实例内容为:", instance)
	err := r.Client.Get(context.TODO(), req.NamespacedName, instance)
	if err != nil {
		if errors.IsNotFound(err) {
			return reconcile.Result{}, nil
		}
		return reconcile.Result{}, err
	}

	if instance.DeletionTimestamp != nil {
		return reconcile.Result{}, err
	}

	// 一、如果不存在,则创建关联资源
	// 二、如果存在,则判断是否需要更新
	//	1、如果需要更新,则直接更新
	//	2、如果不需要更新,则正常返回
	deploy := &appsv1beta1.Deployment{}
	if err := r.Client.Get(context.TODO(), req.NamespacedName, deploy); err != nil && errors.IsNotFound(err) {
		// 没有找到相关资源,需要创建
		// 1、创建 Deploy
		fmt.Println("创建Deployment")
		deploy := NewDeploy(instance)
		if err := r.Client.Create(context.TODO(), deploy); err != nil {
			return reconcile.Result{}, err
		}

		// 2、创建Service
		fmt.Println("创建Services")
		service := NewService(instance)
		if err := r.Client.Create(context.TODO(), service); err != nil {
			return reconcile.Result{}, err
		}

		// 3、关联 Annotations
		fmt.Println("关联Annotations")
		data, _ := json.Marshal(instance.Spec)
		if instance.Annotations != nil {
			instance.Annotations["spec"] = string(data)
		} else {
			instance.Annotations = map[string]string{"spec": string(data)}
		}

		if err := r.Client.Update(context.TODO(), instance); err != nil {
			return reconcile.Result{}, err
		}
		return reconcile.Result{}, err
	}

	oldspec := websitev1.WebsiteSpec{}
	if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldspec); err != nil {
		return reconcile.Result{}, err
	}

	if !reflect.DeepEqual(instance.Spec, oldspec) {
		// 更新关联资源
		fmt.Println("更新关联资源...")
		newDeploy := NewDeploy(instance)
		oldDeploy := &appsv1beta1.Deployment{}

		if err := r.Client.Get(context.TODO(), req.NamespacedName, oldDeploy); err != nil {
			return reconcile.Result{}, err
		}
		oldDeploy.Spec = newDeploy.Spec
		if err := r.Client.Update(context.TODO(), oldDeploy); err != nil {
			return reconcile.Result{}, err
		}

		newService := NewService(instance)
		oldService := &corev1.Service{}
		if err := r.Client.Get(context.TODO(), req.NamespacedName, oldService); err != nil {
			return reconcile.Result{}, err
		}
		oldService.Spec = newService.Spec
		if err := r.Client.Update(context.TODO(), oldService); err != nil {
			return reconcile.Result{}, err
		}
		return reconcile.Result{}, nil
	}
	return ctrl.Result{}, nil
}

最终效果

Operator部署

部署成功后,会创建出一个deployment和一个serviceservice是用于监控应用监控,可以先不管
在这里插入图片描述

创建website

一、根据最开始的提到的简易yaml来创建website实例
test-website.yaml

apiVersion: website.xianyuluo.com/v1
kind: Website
metadata:
  name: nginx-app
  namespace: website-operator-system
spec:
  size: 1
  image: xianyuluo/nginx:1.12.2.website-operator
  port:
    - port: 80
      targetPort: 80

在这里插入图片描述

二、观察集群中的deploymentservice
deploymentservice已经由website-operator帮我们创建出来了,Nice~(svc默认为LoadBalancer类型)。访问一下看看!
在这里插入图片描述
在这里插入图片描述
站点正常,Good!

参考文档

https://github.com/xianyuLuo/website-operator

https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/

https://www.qikqiak.com/post/k8s-operator-101/

记录的不是很详细,有些知识自己也还在琢磨当中,后面再补充!

ok@XIGUAPIPI:~/OpenFOAM/ok-7/run/pitzDaily$ simpleFoam /*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | Website: https://openfoam.org \\ / A nd | Version: 7 \\/ M anipulation | \*---------------------------------------------------------------------------*/ Build : 7 Exec : simpleFoam Date : Aug 09 2025 Time : 16:23:56 Host : "XIGUAPIPI" PID : 3397 I/O : uncollated Case : /home/ok/OpenFOAM/ok-7/run/pitzDaily nProcs : 1 sigFpe : Enabling floating point exception trapping (FOAM_SIGFPE). fileModificationChecking : Monitoring run-time modified files using timeStampMaster (fileModificationSkew 10) allowSystemOperations : Allowing user-supplied system call operations // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // Create time Create mesh for time = 0 SIMPLE: Convergence criteria found p: tolerance 0.01 U: tolerance 0.001 "(k|epsilon|omega|f|v2)": tolerance 0.001 Reading field p Reading field U Reading/calculating face flux field phi Selecting incompressible transport model Newtonian Selecting turbulence model type RAS Selecting RAS turbulence model kEpsilon RAS { RASModel kEpsilon; turbulence on; printCoeffs on; Cmu 0.09; C1 1.44; C2 1.92; C3 0; sigmak 1; sigmaEps 1.3; } No MRF models present No finite volume options present Starting time loop --> FOAM FATAL IO ERROR: error in IOstream "OSHA1stream.sinkFile_" for operation Ostream& operator<<(Ostream&, const word&) file: OSHA1stream.sinkFile_ at line 0. From function virtual bool Foam::IOstream::check(const char*) const in file db/IOstreams/IOstreams/IOstream.C at line 96. FOAM exiting
08-10
<think>我们正在处理一个OpenFOAM运行时错误,具体错误信息如下: --> FOAM FATAL IO ERROR: error in IOstream "OSHA1stream.sinkFile_" for operation Ostream& operator<<(Ostream&, const word&) file: OSHA1stream.sinkFile_ at line 0. From function virtual bool Foam::IOstream::check(const char*) const in file db/IOstreams/IOstreams/IOstream.C at line 96. FOAM exiting 这个错误通常发生在尝试写入文件时出现问题。错误信息指出在操作符`<<`(用于输出一个word类型)时,在名为"OSHA1stream.sinkFile_"的IOstream中发生了错误。 根据OpenFOAM的源代码,这个错误是在`IOstream.C`文件中的`check`函数中抛出的。错误可能的原因包括: 1. 磁盘空间不足。 2. 文件权限问题,导致无法写入。 3. 文件路径不存在或路径过长。 4. 文件系统错误(如损坏的磁盘)。 5. 在并行计算中,可能某个进程无法写入。 由于错误信息中提到了`OSHA1stream`,这通常与OpenFOAM的SHA1摘要功能有关,可能是在写入某个摘要文件时出错。 **解决步骤:** 1. **检查磁盘空间:** 使用`df -h`(Linux)命令检查磁盘剩余空间。如果磁盘已满,清理空间后重新运行。 2. **检查文件权限:** 确保当前用户对运行目录(通常是case目录)有写入权限。可以使用`ls -ld`查看目录权限。 3. **检查文件路径:** 确保运行目录的路径存在,并且路径长度没有超过系统限制(特别是Windows系统下运行OpenFOAM时需要注意)。 4. **检查文件系统:** 如果怀疑文件系统损坏,可以运行文件系统检查工具(如`fsck`)。 5. **并行运行的情况:** 如果是并行运行(如使用`mpirun`),请确保每个进程都能访问输出目录,并且目录存在。特别是,检查`processorX`(X为数字)目录的权限和存在性。 6. **检查运行环境:** 如果是在集群上运行,可能还需要检查作业调度系统(如Slurm)的输出目录设置。 7. **检查案例设置:** 检查案例中的文件,特别是控制输出频率的文件(如`controlDict`)中设置的输出路径是否合理。 8. **尝试简化案例:** 如果上述步骤不能解决问题,尝试运行一个更简单的案例(如OpenFOAM自带的教程案例)来确认是否是你的案例设置问题。 9. **查看日志:** 查看运行日志(通常是在终端输出或日志文件中)是否有更多线索。 由于错误发生在写入操作时,最可能的原因是磁盘空间或权限问题。请首先检查这两点。 如果问题仍然存在,请提供更多关于你的运行环境(如操作系统,OpenFOAM版本,串行还是并行运行)和案例设置的信息。 [^1]: 引用自错误信息原文。</think>根据您提供的错误信息,FOAM FATAL IO ERROR 通常表示 OpenFOAM 在文件读写操作中遇到关键问题。以下是针对 `OSHA1stream.sinkFile_` 错误的系统化解决方案: ### 错误原因分析 1. **文件系统权限问题** 进程对目标目录缺少写入权限(常见于共享文件系统或 Docker 环境)[^1] 2. **磁盘空间不足** 当可用空间低于 5% 时可能触发此类错误(检查命令:`df -h`) 3. **路径名称问题** - 路径包含特殊字符(如空格、中文) - 路径长度超过系统限制(Linux 默认 255 字符) 4. **并行计算问题** 在 `mpirun` 执行时,某些计算节点无法访问共享存储 5. **文件损坏** 案例文件被异常中断写入导致损坏 ### 解决步骤 1. **权限修复(首选方案)** ```bash # 递归修改案例目录权限 chmod -R 775 case_directory # 修改用户组(若使用共享集群) chgrp -R group_name case_directory ``` 2. **磁盘空间检查** ```bash # 检查磁盘使用率 df -h /mount_point # 清理临时文件 foamCleanTutorials rm -rf processor*/0/uniform ``` 3. **路径规范化** - 将案例移至纯英文短路径(如 `/home/openfoam/run`) - 避免使用特殊字符: ❌ `~/模拟/流场 分析/` → ✅ `/opt/of_cases/pipe_flow` 4. **并行调试** ```bash # 单进程测试 decomposePar -force mpirun -np 1 simpleFoam # 逐步增加进程数 mpirun -np 2 simpleFoam ``` 5. **文件完整性验证** ```bash # 检查关键文件 foamDictionary system/controlDict foamDictionary constant/turbulenceProperties # 重新生成初始化字段 cp -r 0.orig 0 ``` ### 高级排查 若上述步骤无效: 1. 检查系统日志: ```bash journalctl -b | grep -i openfoam ``` 2. 启用调试模式: ```bash export FOAM_SIGFPE=true simpleFoam -debugIO ``` 3. 验证 MPI 配置: ```bash mpirun --version ldd $(which simpleFoam) | grep mpi ``` > **关键建议**:在 Docker 环境中运行时,确保挂载目录添加 `:z` 标签(如 `-v /host/path:/container/path:z`)解决 SELinux 权限问题[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值