上一节(Operator3-设计一个operator)做完发现一个问题 我创建了jan 应用jan-sample,子资源包括deployment,service.ingress,pod(其中pod是deployment管理的)
手动删除Pod.由于Deployment rc控制器。Pod资源可以自动重建。但是我删除deployment能不能自动重建呢?正常的deployment service ingress子资源的生命周期,我应该是靠jan应用去维系的,试一试:
[zhangpeng@zhangpeng jan]$ kubectl delete deployment jan-sample deployment.apps "jan-sample" deleted [zhangpeng@zhangpeng jan]$ kubectl get deployment No resources found in default namespace.
到这里才发现没有考虑周全…删除deployment资源并不能重建,正常创建应用应该要考虑一下jan资源下面资源的重建.搜了一下别人写的operator貌似的可以加一下Owns,尝试一下!
func (r *JanReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&janv1.Jan{}).
Owns(&appsv1.Deployment{}).
Complete(r)
}
Deployment delete尝试
make run develop-operator项目,并尝试delete deployment jan-sample查看是否重建:
[zhangpeng@zhangpeng develop-operator]$ kubectl get Jan [zhangpeng@zhangpeng develop-operator]$ kubectl get all
[zhangpeng@zhangpeng develop-operator]$ kubectl delete deployment jan-sample [zhangpeng@zhangpeng develop-operator]$ kubectl get deployment
恩发现deployment应用可以自动重建了!
but其他资源是否可以呢?是不是也偷懒一下添加Owns?
func (r *JanReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&janv1.Jan{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&v1.Ingress{}).
Complete(r)
}
尝试了一下不生效的,但是这种方式思路是对的至于为什么不生效呢?
deploy声明了&appv1.Deployment,但是service,ingress是没有创建变量声明的!
继续改造Jan operator使其支持service ingress子资源的误删除创建:
把这边拆分一下?
jan_controller.go
package jan
import (
"context"
"encoding/json"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"reflect"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
janv1 "develop-operator/apis/jan/v1"
)
// JanReconciler reconciles a Jan object
type JanReconciler struct {
client.Client
Scheme *runtime.Scheme
}
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Jan object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.2/pkg/reconcile
func (r *JanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
defer utilruntime.HandleCrash()
_ = log.FromContext(ctx)
instance := &janv1.Jan{}
err := r.Client.Get(context.TODO(), req.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return reconcile.Result{}, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, err
}
if instance.DeletionTimestamp != nil {
return reconcile.Result{}, err
}
// 如果不存在,则创建关联资源
// 如果存在,判断是否需要更新
// 如果需要更新,则直接更新
// 如果不需要更新,则正常返回
deploy := &appsv1.Deployment{}
if err := r.Client.Get(context.TODO(), req.NamespacedName, deploy); err != nil && errors.IsNotFound(err) {
// 创建关联资源
// 1. 创建 Deploy
deploy := NewJan(instance)
if err := r.Client.Create(context.TODO(), deploy); err != nil {
return reconcile.Result{}, err
}
// 4. 关联 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{}, nil
}
return reconcile.Result{}, nil
}
Service := &corev1.Service{}
if err := r.Client.Get(context.TODO(), req.NamespacedName, Service); err != nil && errors.IsNotFound(err) {
// 2. 创建 Service
service := NewService(instance)
if err := r.Client.Create(context.TODO(), service); err != nil {
return reconcile.Result{}, err
}
// 4. 关联 Annotations
data, _ := json.Marshal(service.Spec)
if service.Annotations != nil {
service.Annotations["spec"] = string(data)
} else {
service.Annotations = map[string]string{"spec": string(data)}
}
if err := r.Client.Update(context.TODO(), service); err != nil {
return reconcile.Result{}, nil
}
return reconcile.Result{}, nil
}
Ingress := &v1.Ingress{}
if err := r.Client.Get(context.TODO(), req.NamespacedName, Ingress); err != nil && errors.IsNotFound(err) {
// 2. 创建 Service
ingress := NewIngress(instance)
if err := r.Client.Create(context.TODO(), ingress); err != nil {
return reconcile.Result{}, err
}
// 4. 关联 Annotations
data, _ := json.Marshal(ingress.Spec)
if ingress.Annotations != nil {
ingress.Annotations["spec"] = string(data)
} else {
ingress.Annotations = map[string]string{"spec": string(data)}
}
if err := r.Client.Update(context.TODO(), ingress); err != nil {
return reconcile.Result{}, nil
}
return reconcile.Result{}, nil
}
oldspec := janv1.JanSpec{}
if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldspec); err != nil {
return reconcile.Result{}, err
}
if !reflect.DeepEqual(instance.Spec, oldspec) {
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{}, nil
}
// 更新关联资源
newDeploy := NewJan(instance)
oldDeploy := &appsv1.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
}
newStatus := janv1.JanStatus{
Replicas: *instance.Spec.Replicas,
ReadyReplicas: instance.Status.Replicas,
}
if newStatus.Replicas == newStatus.ReadyReplicas {
newStatus.Phase = janv1.Running
} else {
newStatus.Phase = janv1.NotReady
}
if !reflect.DeepEqual(instance.Status, newStatus) {
instance.Status = newStatus
log.FromContext(ctx).Info("update game status", "name", instance.Name)
err = r.Client.Status().Update(ctx, instance)
if err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *JanReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&janv1.Jan{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&v1.Ingress{}).
Complete(r)
}
make run尝试一下:
注意:make run之前默认删除jan应用!
[zhangpeng@zhangpeng develop-operator]$ kubectl delete svc jan-sample [zhangpeng@zhangpeng develop-operator]$ kubectl get svc
en service的自动恢复生效了
然后试一试ingress
[zhangpeng@zhangpeng develop-operator]$ kubectl get ingress [zhangpeng@zhangpeng develop-operator]$ kubectl delete ingress jan-sample [zhangpeng@zhangpeng develop-operator]$ kubectl get ingress
继续发现问题:
en,我修改一下jan_v1_jan.yaml中host ww1.zhangpeng.com修改为ww11.zhangpeng.com,but ingress的相关信息没有及时更新啊?
继续模仿一下上面的service oldservice newservice新增 newIngress oldIngress :
重新make run
ingress相关信息得到了修改
jan_controller.go
package jan
import (
"context"
"encoding/json"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"reflect"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
janv1 "develop-operator/apis/jan/v1"
)
// JanReconciler reconciles a Jan object
type JanReconciler struct {
client.Client
Scheme *runtime.Scheme
}
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Jan object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.2/pkg/reconcile
func (r *JanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
defer utilruntime.HandleCrash()
_ = log.FromContext(ctx)
instance := &janv1.Jan{}
err := r.Client.Get(context.TODO(), req.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return reconcile.Result{}, nil
}
// Error reading the object - requeue the request.
return reconcile.Result{}, err
}
if instance.DeletionTimestamp != nil {
return reconcile.Result{}, err
}
// 如果不存在,则创建关联资源
// 如果存在,判断是否需要更新
// 如果需要更新,则直接更新
// 如果不需要更新,则正常返回
deploy := &appsv1.Deployment{}
if err := r.Client.Get(context.TODO(), req.NamespacedName, deploy); err != nil && errors.IsNotFound(err) {
// 创建关联资源
// 1. 创建 Deploy
deploy := NewJan(instance)
if err := r.Client.Create(context.TODO(), deploy); err != nil {
return reconcile.Result{}, err
}
// 4. 关联 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{}, nil
}
return reconcile.Result{}, nil
}
Service := &corev1.Service{}
if err := r.Client.Get(context.TODO(), req.NamespacedName, Service); err != nil && errors.IsNotFound(err) {
// 2. 创建 Service
service := NewService(instance)
if err := r.Client.Create(context.TODO(), service); err != nil {
return reconcile.Result{}, err
}
// 4. 关联 Annotations
data, _ := json.Marshal(service.Spec)
if service.Annotations != nil {
service.Annotations["spec"] = string(data)
} else {
service.Annotations = map[string]string{"spec": string(data)}
}
if err := r.Client.Update(context.TODO(), service); err != nil {
return reconcile.Result{}, nil
}
return reconcile.Result{}, nil
}
Ingress := &v1.Ingress{}
if err := r.Client.Get(context.TODO(), req.NamespacedName, Ingress); err != nil && errors.IsNotFound(err) {
// 2. 创建 Ingress
ingress := NewIngress(instance)
if err := r.Client.Create(context.TODO(), ingress); err != nil {
return reconcile.Result{}, err
}
// 4. 关联 Annotations
data, _ := json.Marshal(ingress.Spec)
if ingress.Annotations != nil {
ingress.Annotations["spec"] = string(data)
} else {
ingress.Annotations = map[string]string{"spec": string(data)}
}
if err := r.Client.Update(context.TODO(), ingress); err != nil {
return reconcile.Result{}, nil
}
return reconcile.Result{}, nil
}
oldspec := janv1.JanSpec{}
if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldspec); err != nil {
return reconcile.Result{}, err
}
if !reflect.DeepEqual(instance.Spec, oldspec) {
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{}, nil
}
// 更新关联资源
newDeploy := NewJan(instance)
oldDeploy := &appsv1.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
}
newIngress := NewIngress(instance)
oldIngress := &v1.Ingress{}
if err := r.Client.Get(context.TODO(), req.NamespacedName, oldIngress); err != nil {
return reconcile.Result{}, err
}
oldIngress.Spec = newIngress.Spec
if err := r.Client.Update(context.TODO(), oldIngress); err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
newStatus := janv1.JanStatus{
Replicas: *instance.Spec.Replicas,
ReadyReplicas: instance.Status.Replicas,
}
if newStatus.Replicas == newStatus.ReadyReplicas {
newStatus.Phase = janv1.Running
} else {
newStatus.Phase = janv1.NotReady
}
if !reflect.DeepEqual(instance.Status, newStatus) {
instance.Status = newStatus
log.FromContext(ctx).Info("update game status", "name", instance.Name)
err = r.Client.Status().Update(ctx, instance)
if err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *JanReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&janv1.Jan{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&v1.Ingress{}).
Complete(r)
}
总结
- owns的一般使用
- 将 deployment service ingress或者其他资源作为operator应用的子资源,进行生命周期管理
- 下一步想处理一下 make run 控制台的输出,输出一些有用的信息



