نكتب عامل تشغيل لـ Kubernetes في Golang

ملحوظة. ترجمة.: المشغلون عبارة عن برامج مساعدة لـ Kubernetes مصممة لأتمتة تنفيذ الإجراءات الروتينية على كائنات المجموعة أثناء أحداث معينة. لقد كتبنا بالفعل عن المشغلين في هذا المقالحيث تحدثوا عن الأفكار والمبادئ الأساسية لعملهم. ولكن إذا كانت هذه المادة بالأحرى وجهة نظر من جانب تشغيل المكونات الجاهزة لـ Kubernetes ، فإن ترجمة المقالة الجديدة المقترحة الآن هي بالفعل رؤية مطور / مهندس DevOps ، محير من تنفيذ مشغل جديد .

نكتب عامل تشغيل لـ Kubernetes في Golang

قررت كتابة هذا المنشور بمثال واقعي بعد أن خضعت محاولاتي للعثور على وثائق حول إنشاء مشغل لـ Kubernetes ، من خلال دراسة الكود.

المثال الذي سيتم وصفه هو هذا: في مجموعة Kubernetes الخاصة بنا ، كل منها Namespace يمثل بيئة وضع الحماية للفريق ، وأردنا تقييد الوصول إليهم بحيث يمكن للفرق اللعب فقط في صناديق الحماية الخاصة بهم.

يمكنك تحقيق ما تريد من خلال تخصيص مستخدم لمجموعة لديها 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 يمكنك القيام بذلك يدويًا ، ولكن بمجرد حصولك على أكثر من مائة مساحة اسم ، تصبح مهمة شاقة. هذا هو المكان الذي يكون فيه مشغلو 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لإيقاف جميع goroutines بأمان قبل إنهاء التطبيق.
  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
}

(تحكم. 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))
  }
}

(تحكم. 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
}

(تحكم. goفي الخام)

نحن هنا نتحدث WaitGroupأن نبدأ goroutine ثم ندعو namespaceInformerالتي تم تعريفها مسبقًا. عندما تأتي إشارة التوقف ، فإنها ستنهي الوظيفة ، وتبلغ WaitGroup، والتي لم تعد قيد التنفيذ ، وسيتم إنهاء هذه الوظيفة.

يمكن العثور على معلومات حول إنشاء هذا البيان وتشغيله على مجموعة Kubernetes على مستودعات على جيثب.

على هذا البيان الذي يخلق RoleBinding متى Namespace في مجموعة Kubernetes ، جاهزة.

المصدر: www.habr.com

إضافة تعليق