Menulis operator untuk Kubernetes di Golang

Catatan. terjemah: Operator ialah perisian tambahan untuk Kubernetes, direka untuk mengautomasikan pelaksanaan tindakan rutin pada objek kelompok apabila peristiwa tertentu berlaku. Kami telah pun menulis tentang pengendali dalam artikel ini, di mana mereka bercakap tentang idea dan prinsip asas kerja mereka. Tetapi jika bahan itu lebih kepada pandangan dari sisi pengendalian komponen siap sedia untuk Kubernetes, maka terjemahan artikel baharu yang kini dicadangkan sudah menjadi visi seorang pembangun/jurutera DevOps yang bingung dengan pelaksanaan pengendali baharu.

Menulis operator untuk Kubernetes di Golang

Saya memutuskan untuk menulis siaran ini dengan contoh kehidupan sebenar selepas percubaan saya untuk mencari dokumentasi tentang mencipta operator untuk Kubernetes, yang melalui kajian kod.

Contoh yang akan diterangkan adalah ini: dalam kelompok Kubernetes kami, setiap satu Namespace mewakili persekitaran kotak pasir pasukan dan kami mahu mengehadkan akses kepada mereka supaya pasukan hanya boleh bermain dalam kotak pasir mereka sendiri.

Anda boleh mencapai apa yang anda inginkan dengan memberikan pengguna kumpulan yang mempunyai RoleBinding kepada khusus Namespace ΠΈ ClusterRole dengan hak penyuntingan. Perwakilan YAML akan kelihatan seperti ini:

---
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.yamlDalam mentah)

Buat satu RoleBinding Anda boleh melakukannya secara manual, tetapi selepas melintasi tanda seratus ruang nama, ia menjadi tugas yang membosankan. Di sinilah pengendali Kubernetes bergunaβ€”mereka membenarkan anda mengautomasikan penciptaan sumber Kubernetes berdasarkan perubahan kepada sumber. Dalam kes kami, kami ingin mencipta RoleBinding semasa mencipta Namespace.

Pertama sekali, mari kita tentukan fungsi mainyang melakukan persediaan yang diperlukan untuk menjalankan pernyataan dan kemudian memanggil tindakan pernyataan:

(Catatan. terjemah: di sini dan di bawah komen dalam kod diterjemahkan ke dalam bahasa Rusia. Selain itu, lekukan telah diperbetulkan kepada ruang dan bukannya tab [disyorkan dalam Go] semata-mata untuk tujuan kebolehbacaan yang lebih baik dalam reka letak Habr. Selepas setiap penyenaraian terdapat pautan kepada yang asal di GitHub, di mana komen dan tab bahasa Inggeris disimpan.)

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.goDalam mentah)

Kami melakukan perkara berikut:

  1. Kami mengkonfigurasi pengendali untuk isyarat sistem pengendalian tertentu untuk menyebabkan penamatan yang anggun pengendali.
  2. Kami guna WaitGroupuntuk menghentikan semua goroutine dengan anggun sebelum menamatkan permohonan.
  3. Kami menyediakan akses kepada kluster dengan membuat clientset.
  4. Kami melancarkan NamespaceController, di mana semua logik kita akan ditempatkan.

Sekarang kita memerlukan asas untuk logik, dan dalam kes kita inilah yang disebutkan 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
}

(pengawal.pergiDalam mentah)

Di sini kami mengkonfigurasi SharedIndexInformer, yang secara berkesan akan (menggunakan cache) menunggu perubahan dalam ruang nama (baca lebih lanjut mengenai pemberi maklumat dalam artikel "Bagaimanakah penjadual Kubernetes sebenarnya berfungsi?"- lebih kurang terjemahan). Selepas ini kita sambung EventHandler kepada pemberi maklumat, supaya apabila menambah ruang nama (Namespace) fungsi dipanggil createRoleBinding.

Langkah seterusnya ialah menentukan fungsi ini 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))
  }
}

(pengawal.pergiDalam mentah)

Kami mendapat ruang nama sebagai obj dan menukarnya kepada objek Namespace. Kemudian kita tentukan RoleBinding, berdasarkan fail YAML yang disebutkan pada mulanya, menggunakan objek yang disediakan Namespace dan mencipta RoleBinding. Akhirnya, kami log sama ada penciptaan itu berjaya.

Fungsi terakhir yang akan ditakrifkan ialah 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
}

(pengawal.pergiDalam mentah)

Di sini kita bercakap WaitGroupbahawa kita melancarkan goroutine dan kemudian memanggil namespaceInformer, yang telah ditakrifkan sebelum ini. Apabila isyarat berhenti tiba, ia akan menamatkan fungsi, maklumkan WaitGroup, yang tidak lagi dilaksanakan, dan fungsi ini akan keluar.

Maklumat tentang membina dan menjalankan pernyataan ini pada gugusan Kubernetes boleh didapati dalam repositori di GitHub.

Itu sahaja untuk pengendali yang mencipta RoleBinding bila Namespace dalam gugusan Kubernetes, sedia.

Sumber: www.habr.com

Tambah komen