1 回答

TA貢獻(xiàn)1874條經(jīng)驗(yàn) 獲得超12個(gè)贊
請(qǐng)記住以下幾點(diǎn):
每個(gè)控制器只負(fù)責(zé)一個(gè)資源。
協(xié)調(diào)請(qǐng)求包含協(xié)調(diào) Kubernetes 對(duì)象所需的信息。這包括唯一標(biāo)識(shí)對(duì)象的信息 - 它的名稱和命名空間。它不包含有關(guān)任何特定事件或?qū)ο髢?nèi)容本身的信息。
您可以在沒有資源定義的情況下創(chuàng)建第二個(gè)控制器。在您的主文件中,兩個(gè)控制器都將被注冊(cè)。
如果 CRD 根本不相關(guān),或者如果外部資源引用內(nèi)部資源,這可能很有用,因此您可以在外部協(xié)調(diào)器中更改內(nèi)部資源。
kubebuilder create api --group other --version v2 --kind External \
--resource=false --controller=true
這為您提供了一個(gè)控制器,其SetupWithManager方法如下所示。
func (r *ExternalReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
// Uncomment the following line adding a pointer to an instance of the controlled resource as an argument
// For().
Complete(r)
}
請(qǐng)注意 For 方法是如何被注釋掉的,因?yàn)槟枰獙?dǎo)入資源以從其他地方觀看并引用它。
import (
...
otherv2 "other.io/external/api/v2"
)
...
func (r *ExternalReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&otherv2.External{}).
Complete(r)
}
如果您無(wú)法導(dǎo)入外部資源,您可以回過(guò)頭來(lái)自己模擬它,但這可能不是一種非常干凈的方法。您真的應(yīng)該嘗試從其他控制器項(xiàng)目中導(dǎo)入它。
kubebuilder edit --multigroup=true
kubebuilder create api --group=other --version v2 --kind External \
--resource --controller
另一種方式是當(dāng)資源相互關(guān)聯(lián)時(shí),內(nèi)部資源在其規(guī)范中引用了外部資源,并且知道如何在其協(xié)調(diào)時(shí)獲取其規(guī)范中的外部資源。可以在這里找到一個(gè)例子https://book.kubebuilder.io/reference/watching-resources/externally-managed.html
type InternalSpec struct {
// Name of an external resource
ExternalResource string `json:"externalResource,omitempty"`
}
這意味著在每個(gè)協(xié)調(diào)循環(huán)中,控制器將查找外部資源并使用它來(lái)管理內(nèi)部資源。
func (r *InternalReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
internal := examplev1.Internal{}
if err := r.Get(context.TODO(), types.NamespacedName{
Name: req.Name,
Namespace: req.Namespace,
}, &internal); err != nil {
return ctrl.Result{}, err
}
external := otherv2.External{}
if err := r.Get(context.TODO(), types.NamespacedName{
// note how the name is taken from the internal spec
Name: internal.Spec.ExternalResource,
Namespace: req.Namespace,
}, &internal); err != nil {
return ctrl.Result{}, err
}
// do something with internal and external here
return ctrl.Result{}, nil
}
這樣做的問(wèn)題是,當(dāng)內(nèi)部資源沒有變化時(shí),即使外部資源發(fā)生變化,也不會(huì)觸發(fā)協(xié)調(diào)事件。為了解決這個(gè)問(wèn)題,我們可以通過(guò)觀察外部資源來(lái)觸發(fā)協(xié)調(diào)。注意Watches方法:
func (r *InternalReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&examplev1.Main{}).
Watches(
&source.Kind{Type: &otherv2.ExternalResource{}},
handler.EnqueueRequestsFromMapFunc(r.triggerReconcileBecauseExternalHasChanged),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
).
Complete(r)
}
為了知道我們應(yīng)該為哪個(gè)內(nèi)部對(duì)象觸發(fā)事件,我們使用映射函數(shù)來(lái)查找所有引用外部資源的內(nèi)部對(duì)象。
func (r *InternalReconciler) triggerReconcileBecauseExternalHasChanged(o client.Object) []reconcile.Request {
usedByInternals := &examplev1.InternalList{}
listOps := &client.ListOptions{
FieldSelector: fields.OneTermEqualSelector(".spec.ExternalResource", o.GetName()),
Namespace: o.GetNamespace(),
}
err := r.List(context.TODO(), usedByInternals, listOps)
if err != nil {
return []reconcile.Request{}
}
requests := make([]reconcile.Request, len(usedByInternals.Items))
for i, item := range usedByInternals.Items {
requests[i] = reconcile.Request{
NamespacedName: types.NamespacedName{
Name: item.GetName(),
Namespace: item.GetNamespace(),
},
}
}
return requests
}
由于您更新了問(wèn)題,我建議您執(zhí)行以下操作。
我正在創(chuàng)建一個(gè)新項(xiàng)目和 2 個(gè)控制器。注意第二個(gè)控制器命令沒有資源與控制器一起創(chuàng)建。這是因?yàn)榭刂破鲗⒈O(jiān)視外部資源。
mkdir demo && cd demo
go mod init example.io/demo
kubebuilder init --domain example.io --repo example.io/demo --plugins=go/v4-alpha
kubebuilder create api --group=demo --version v1 --kind Internal --controller --resource
kubebuilder create api --group=other --version v2 --kind External --controller --resource=false
$ tree controllers
controllers
├── external_controller.go
├── internal_controller.go
└── suite_test.go
現(xiàn)在我們需要一些共享邏輯,例如將其添加到控制器包中。我們將從兩個(gè)調(diào)解器中調(diào)用它。
// the interface may need tweaking
// depending on what you want to do with
// the reconiler
type reconciler interface {
client.Reader
client.Writer
client.StatusClient
}
func sharedLogic(r reconciler, kobj *demov1.Internal) (ctrl.Result, error) {
// do your shared logic here operating on the internal object struct
// this works out because the external controller will call this passing the
// internal object
return ctrl.Result{}, nil
}
這是內(nèi)部協(xié)調(diào)器的示例。
func (r *InternalReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
obj := demov1.Internal{}
if err := r.Get(ctx, req.NamespacedName, &obj); err != nil {
return ctrl.Result{}, err
}
return sharedLogic(r, &obj)
}
在外部協(xié)調(diào)器中,我們也這樣做。
func (r *ExternalReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = log.FromContext(ctx)
// note, we can use the internal object here as long as the external object
// does contain the same fields we want. That means when unmarshalling the extra
// fields are dropped. If this cannot be done, you could first unmarshal into the external
// resource and then assign the fields you need to the internal one, before passing it down
obj := demov1.Internal{}
if err := r.Get(ctx, req.NamespacedName, &obj); err != nil {
return ctrl.Result{}, err
}
return sharedLogic(r, &obj)
}
func (r *ExternalReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
// note the external resource is imported from another project
// you may be able to watch this without import by creating a minimal
// type with the right GKV
For(otherv2.External{}).
Complete(r)
}
- 1 回答
- 0 關(guān)注
- 166 瀏覽
添加回答
舉報(bào)