การเขียนโอเปอเรเตอร์สำหรับ 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.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
}

(คอนโทรลเลอร์.ไปใน ดิบ)

ที่นี่เรากำหนดค่า 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))
  }
}

(คอนโทรลเลอร์.ไปใน ดิบ)

เราได้รับเนมสเปซเป็น 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
}

(คอนโทรลเลอร์.ไปใน ดิบ)

ที่นี่เรากำลังพูดถึง WaitGroupที่เราเปิด goroutine แล้วจึงเรียก namespaceInformerซึ่งได้รับการกำหนดไว้ก่อนหน้านี้ เมื่อสัญญาณหยุดมาถึงจะสิ้นสุดฟังก์ชันแจ้ง WaitGroupซึ่งไม่ได้ดำเนินการอีกต่อไป และฟังก์ชันนี้จะออกจากการทำงาน

ข้อมูลเกี่ยวกับการสร้างและเรียกใช้คำสั่งนี้บนคลัสเตอร์ Kubernetes มีอยู่ใน ที่เก็บบน GitHub.

เพียงเท่านี้สำหรับผู้ปฏิบัติงานที่สร้าง RoleBinding เมื่อไร Namespace ในคลัสเตอร์ Kubernetes พร้อมแล้ว

ที่มา: will.com

เพิ่มความคิดเห็น