前言
随着 Kubernetes 的广泛应用,它成为了现代化应用程序交付的事实标准。在 Kubernetes 中,我们可以方便地管理许多容器化工作负载,如 Pod、Service、Deployment 等。但在实际的项目中,我们还经常会遇到需要扩展 Kubernetes API 的需求。例如,我们的项目可能需要另外一种资源类型,如数据库,为此我们需要自定义一个资源类型以管理该数据库。
本文主要介绍如何在 Kubernetes 中创建自定义资源控制器。
Kubernetes 扩展 API
在 Kubernetes 中,我们可以使用自定义资源定义(Custom Resource Definition,CRD)为 Kubernetes API 添加新的资源类型。你可以注册自己的资源类型,用于存储和管理通过 Kubernetes 运行的应用程序的特定配置。注册自定义资源类型后,我们可以使用 Kubernetes API 对其进行 CRUD 操作。
扩展 Kubernetes API 的需求案例
假设我们现在需要实现一个简单的书籍管理系统,其中包括书籍信息和作者信息。我们的系统需要支持以下操作:
- 添加新书籍;
- 修改现有书籍;
- 删除书籍;
- 查看所有书籍列表。
针对这个需求,我们需要自定义一个 Book 资源类型,包括以下属性:
- id:每本书的唯一标识符;
- title:书籍标题;
- author:作者名字;
- publisher:出版社。
我们的 Book 资源控制器将管理所有 Book 资源的生命周期。
创建自定义资源控制器
在 Kubernetes 中,自定义资源定义通过 API Server 中的 API 扩展机制实现。因此,为了创建一个自定义资源控制器,我们首先需要定义一种用于描述新资源类型的对象,该对象是 Kubernetes API 中现有对象的一种扩展。
CRD 由三部分组成:
- 自定义资源的名称和版本;
- 该资源的 API 结构,用于描述资源类型和标准 Kubernetes API 功能的元数据;
- 自定义控制器,用于实现资源逻辑,更新我们定义的自定义资源。
以下是一个简单的 Book 资源的定义:
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: books.example.com spec: group: example.com versions: - name: v1 served: true storage: true scope: Namespaced names: plural: books singular: book kind: Book shortNames: - b subresources: status: {}
上述配置描述了一个名为 Book 的自定义资源类型,其中:
- group:所属组;
- versions:API 版本;
- scope:命名空间;
- names:自定义资源名称;
- subresources:定义自定义资源支持的子资源。
接下来,我们需要创建一个 Kubernetes 控制器,用于实现上述自定义资源类型的逻辑操作。
以下是一个简单的 Book 资源控制器示例,它实现了 Book 的增删改查操作:
package main import ( "context" "fmt" "time" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" ctrl "sigs.k8s.io/controller-runtime" bookv1 "github.com/example.com/books/api/v1" ) const ( timeout = time.Second * 15 ) func main() { cfg := config.GetConfigOrDie() k8sclient, err := client.New(cfg, client.Options{Scheme: scheme}) if err != nil { panic(err) } if err := (&BookReconciler{ Client: k8sclient, }).SetupWithManager(ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme, })); err != nil { panic(err) } if err := ctrl.NewWebhookManagedBy(mgr).For(&bookv1.Book{}).Complete(); err != nil { panic(err) } } type BookReconciler struct { client.Client } func (r *BookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) book := &bookv1.Book{} if err := r.Get(ctx, req.NamespacedName, book); err != nil { if errors.IsNotFound(err) { logger.Info("resource not found", "name", req.NamespacedName) return ctrl.Result{}, nil } logger.Error(err, "failed to reconcile book") return ctrl.Result{}, err } logger.Info("reconciling book", "name", book.Name) if book.DeletionTimestamp != nil { logger.Info("deleting book", "name", book.Name) if err := r.deleteResource(ctx, book); err != nil { logger.Error(err, "failed to delete book") return ctrl.Result{}, err } return ctrl.Result{}, nil } if book.Status.Status != bookv1.BookStatusSucceeded { logger.Info("creating book", "name", book.Name) if err := r.createResource(ctx, book); err != nil { logger.Error(err, "failed to create book") return ctrl.Result{}, err } return ctrl.Result{}, nil } logger.Info("book status already succeeded", "name", book.Name) return ctrl.Result{}, nil } func (r *BookReconciler) createResource(ctx context.Context, book *bookv1.Book) error { // Create new book // ... book.Status.Status = bookv1.BookStatusSucceeded if err := r.Status().Update(ctx, book); err != nil { return err } return nil } func (r *BookReconciler) updateResource(ctx context.Context, book *bookv1.Book) error { // Update existing book // ... return nil } func (r *BookReconciler) deleteResource(ctx context.Context, book *bookv1.Book) error { // Delete book // ... return nil } func init() { SchemeBuilder.Register(&bookv1.Book{}, &bookv1.BookList{}) } var scheme = runtime.NewScheme() func addKnownTypes(scheme *runtime.Scheme) error { gv := schema.GroupVersion{Group: bookv1.GroupVersion.Group, Version: bookv1.GroupVersion.Version} scheme.AddKnownTypes(gv, &bookv1.Book{}, &bookv1.BookList{}, ) metav1.AddToGroupVersion(scheme, gv) return nil }
上述代码定义了 Book 控制器,其包括以下逻辑:
- 根据 Book 自定义资源来触发控制器;
- 如果 Book 资源不存在,则创建新资源;
- 如果 Book 资源已被删除,则删除 Book 资源;
- 否则更新 Book 资源。
部署自定义资源控制器
部署自定义资源控制器的过程基本上与部署 Kubernetes 中的任何其他控制器相同。
- 克隆完整的示例代码:
git clone https://github.com/example.com/books cd books
- 预添加 CRD:
kubectl apply -f deploy/crd.yaml
- 部署控制器:
kubectl apply -f deploy/operator.yaml
总结
在 Kubernetes 中,创建自定义资源控制器可以帮助我们扩展 Kubernetes API 以管理我们的应用程序的特定配置。本文介绍了如何创建一个简单的自定义资源控制器,并提供了示例代码以供参考。无论您是需要将现有应用程序移植到 Kubernetes 上,还是要为 Kubernetes 开发新应用程序,创建自定义资源控制器都是非常重要和强大的最佳实践。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65af83a5add4f0e0ff8f5845