نوشتن یک اپراتور برای Kubernetes در Golang

توجه داشته باشید. ترجمه: اپراتورها نرم‌افزار کمکی برای Kubernetes هستند که برای خودکارسازی اجرای اقدامات معمول روی اشیاء خوشه‌ای هنگام وقوع رویدادهای خاص طراحی شده‌اند. قبلاً در مورد اپراتورها نوشته ایم این مقاله، جایی که در مورد ایده ها و اصول اساسی کار خود صحبت کردند. اما اگر این مطالب بیشتر از جنبه عملکرد اجزای آماده برای Kubernetes بود، ترجمه مقاله جدیدی که اکنون پیشنهاد می‌شود در حال حاضر چشم‌انداز یک مهندس توسعه‌دهنده/DevOps است که از اجرای یک اپراتور جدید گیج شده است.

نوشتن یک اپراتور برای Kubernetes در Golang

تصمیم گرفتم این پست را با یک مثال واقعی بنویسم پس از تلاش‌هایم برای یافتن مستندات مربوط به ایجاد یک اپراتور برای Kubernetes، که از طریق مطالعه کد انجام شد.

مثالی که توضیح داده خواهد شد این است: در خوشه Kubernetes ما، هر کدام Namespace محیط sandbox یک تیم را نشان می‌دهد و ما می‌خواستیم دسترسی به آن‌ها را محدود کنیم تا تیم‌ها فقط بتوانند در sandboxهای خودشان بازی کنند.

شما می توانید با اختصاص دادن گروهی که به کاربر دارد، به آنچه می خواهید برسید RoleBinding به خاص Namespace и ClusterRole با حقوق ویرایش نمایش YAML به شکل زیر خواهد بود:

---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: kubernetes-team-1
  namespace: team-1
subjects:
- kind: Group
  name: kubernetes-team-1
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: edit
apiGroup: rbac.authorization.k8s.io

(rolebinding.yamlبه خام)

یکی بساز RoleBinding شما می توانید آن را به صورت دستی انجام دهید، اما پس از عبور از علامت صد فضای نام، به یک کار خسته کننده تبدیل می شود. اینجاست که اپراتورهای Kubernetes به کار می‌آیند—آنها به شما اجازه می‌دهند تا بر اساس تغییرات در منابع، ایجاد منابع Kubernetes را خودکار کنید. در مورد ما می خواهیم ایجاد کنیم RoleBinding در حین ایجاد Namespace.

اول از همه، اجازه دهید تابع را تعریف کنیم mainکه تنظیمات لازم برای اجرای دستور را انجام می دهد و سپس دستور عمل را فراخوانی می کند:

(توجه داشته باشید. ترجمه: در اینجا و زیر نظرات در کد به روسی ترجمه شده است. علاوه بر این، تورفتگی به جای برگه‌های [توصیه‌شده در Go] فقط به منظور خوانایی بهتر در طرح‌بندی Habr به فاصله‌ها اصلاح شده است. پس از هر فهرست، پیوندهایی به نسخه اصلی در GitHub وجود دارد، جایی که نظرات و برگه‌های انگلیسی زبان ذخیره می‌شوند.)

func main() {
  // Устанавливаем вывод логов в консольный STDOUT
  log.SetOutput(os.Stdout)

  sigs := make(chan os.Signal, 1) // Создаем канал для получения сигналов ОС
  stop := make(chan struct{})     // Создаем канал для получения стоп-сигнала

  // Регистрируем получение SIGTERM в канале sigs
  signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 

  // Goroutines могут сами добавлять себя в WaitGroup,
 // чтобы завершения их выполнения дожидались
  wg := &sync.WaitGroup{} 

  runOutsideCluster := flag.Bool("run-outside-cluster", false, "Set this flag when running outside of the cluster.")
  flag.Parse()
  // Создаем clientset для взаимодействия с кластером Kubernetes
  clientset, err := newClientSet(*runOutsideCluster)

  if err != nil {
    panic(err.Error())
  }

  controller.NewNamespaceController(clientset).Run(stop, wg)

  <-sigs // Ждем сигналов (до получения сигнала более ничего не происходит)
  log.Printf("Shutting down...")

  close(stop) // Говорим goroutines остановиться
  wg.Wait()   // Ожидаем, что все остановлено
}

(main.goبه خام)

ما موارد زیر را انجام می دهیم:

  1. ما یک کنترل کننده برای سیگنال های خاص سیستم عامل پیکربندی می کنیم تا باعث خاتمه دلپذیر اپراتور شود.
  2. ما استفاده می کنیم WaitGroupتا قبل از خاتمه برنامه، همه برنامه‌ها را با ظرافت متوقف کنید.
  3. ما با ایجاد دسترسی به خوشه را فراهم می کنیم clientset.
  4. راه اندازی NamespaceController، که تمام منطق ما در آن قرار خواهد گرفت.

حال ما به یک مبنای منطق نیاز داریم و در مورد ما این همان چیزی است که ذکر شد NamespaceController:

// NamespaceController следит через Kubernetes API за изменениями
// в пространствах имен и создает RoleBinding для конкретного namespace.
type NamespaceController struct {
  namespaceInformer cache.SharedIndexInformer
  kclient           *kubernetes.Clientset
}

// NewNamespaceController создает новый NewNamespaceController
func NewNamespaceController(kclient *kubernetes.Clientset) *NamespaceController {
  namespaceWatcher := &NamespaceController{}

  // Создаем информер для слежения за Namespaces
  namespaceInformer := cache.NewSharedIndexInformer(
    &cache.ListWatch{
      ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
        return kclient.Core().Namespaces().List(options)
      },
      WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
        return kclient.Core().Namespaces().Watch(options)
      },
    },
    &v1.Namespace{},
    3*time.Minute,
    cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
  )

  namespaceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    AddFunc: namespaceWatcher.createRoleBinding,
  })

  namespaceWatcher.kclient = kclient
  namespaceWatcher.namespaceInformer = namespaceInformer

  return namespaceWatcher
}

(controller.goبه خام)

در اینجا ما پیکربندی می کنیم SharedIndexInformer، که به طور موثر (با استفاده از کش) منتظر تغییرات در فضاهای نام می ماند (اطلاعات بیشتر در مورد خبررسان در مقاله "زمانبندی Kubernetes واقعا چگونه کار می کند؟"- تقریبا ترجمه). بعد از این وصل می شویم EventHandler به اطلاع رسانی، به طوری که هنگام افزودن فضای نام (Namespace) تابع فراخوانی می شود createRoleBinding.

مرحله بعدی تعریف این تابع است createRoleBinding:

func (c *NamespaceController) createRoleBinding(obj interface{}) {
  namespaceObj := obj.(*v1.Namespace)
  namespaceName := namespaceObj.Name

  roleBinding := &v1beta1.RoleBinding{
    TypeMeta: metav1.TypeMeta{
      Kind:       "RoleBinding",
      APIVersion: "rbac.authorization.k8s.io/v1beta1",
    },
    ObjectMeta: metav1.ObjectMeta{
      Name:      fmt.Sprintf("ad-kubernetes-%s", namespaceName),
      Namespace: namespaceName,
    },
    Subjects: []v1beta1.Subject{
      v1beta1.Subject{
        Kind: "Group",
        Name: fmt.Sprintf("ad-kubernetes-%s", namespaceName),
      },
    },
    RoleRef: v1beta1.RoleRef{
      APIGroup: "rbac.authorization.k8s.io",
        Kind:     "ClusterRole",
        Name:     "edit",
    },
  }

  _, err := c.kclient.Rbac().RoleBindings(namespaceName).Create(roleBinding)

  if err != nil {
    log.Println(fmt.Sprintf("Failed to create Role Binding: %s", err.Error()))
  } else {
    log.Println(fmt.Sprintf("Created AD RoleBinding for Namespace: %s", roleBinding.Name))
  }
}

(controller.goبه خام)

ما فضای نام را به عنوان دریافت می کنیم obj و آن را به یک شی تبدیل کنید Namespace. سپس تعریف می کنیم RoleBinding، بر اساس فایل YAML ذکر شده در ابتدا با استفاده از شی ارائه شده Namespace و ایجاد RoleBinding. در نهایت، ما ثبت می کنیم که آیا ایجاد موفقیت آمیز بوده است یا خیر.

آخرین تابعی که باید تعریف شود این است Run:

// Run запускает процесс ожидания изменений в пространствах имён
// и действия в соответствии с этими изменениями.
func (c *NamespaceController) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
  // Когда эта функция завершена, пометим как выполненную
  defer wg.Done()

  // Инкрементируем wait group, т.к. собираемся вызвать goroutine
  wg.Add(1)

  // Вызываем goroutine
  go c.namespaceInformer.Run(stopCh)

  // Ожидаем получения стоп-сигнала
  <-stopCh
}

(controller.goبه خام)

اینجا داریم صحبت می کنیم WaitGroupکه ما گوروتین را راه اندازی می کنیم و سپس تماس می گیریم namespaceInformer، که قبلاً تعریف شده است. هنگامی که سیگنال توقف می رسد، عملکرد را به پایان می رساند، اطلاع دهید WaitGroup، که دیگر اجرا نمی شود و این تابع خارج می شود.

اطلاعات مربوط به ساخت و اجرای این عبارت در یک خوشه Kubernetes را می توان در این قسمت یافت مخازن در GitHub.

این برای اپراتور است که ایجاد می کند RoleBinding چه زمانی Namespace در خوشه Kubernetes، آماده است.

منبع: www.habr.com

اضافه کردن نظر