Menulis operator untuk Kubernetes di Golang

Catatan. terjemahan: Operator adalah perangkat lunak tambahan untuk Kubernetes, yang dirancang untuk mengotomatisasi pelaksanaan tindakan rutin pada objek cluster ketika peristiwa tertentu terjadi. Kami telah menulis tentang operator di Artikel ini, di mana mereka berbicara tentang ide-ide dasar dan prinsip-prinsip pekerjaan mereka. Namun jika materi tersebut lebih merupakan pandangan dari sisi pengoperasian komponen siap pakai untuk Kubernetes, maka terjemahan artikel baru yang diusulkan sekarang sudah merupakan visi seorang pengembang/insinyur DevOps yang bingung dengan implementasi operator baru.

Menulis operator untuk Kubernetes di Golang

Saya memutuskan untuk menulis postingan ini dengan contoh kehidupan nyata setelah upaya saya menemukan dokumentasi tentang pembuatan operator untuk Kubernetes, melalui pembelajaran kode.

Contoh yang akan dijelaskan adalah ini: di cluster Kubernetes kita, masing-masing Namespace mewakili lingkungan sandbox tim, dan kami ingin membatasi akses ke sana sehingga tim hanya bisa bermain di sandbox mereka sendiri.

Anda dapat mencapai apa yang Anda inginkan dengan menugaskan pengguna ke grup yang dimilikinya RoleBinding untuk spesifik Namespace ΠΈ ClusterRole dengan hak mengedit. Representasi YAML akan terlihat 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

(pengikatan peran.yamlDi mentah)

Buat satu RoleBinding Anda dapat melakukannya secara manual, tetapi setelah melewati tanda seratus namespace, itu menjadi tugas yang membosankan. Di sinilah operator Kubernetes bergunaβ€”mereka memungkinkan Anda mengotomatiskan pembuatan sumber daya Kubernetes berdasarkan perubahan pada sumber daya. Dalam kasus kami, kami ingin membuat RoleBinding saat membuat Namespace.

Pertama-tama, mari kita definisikan fungsinya mainyang melakukan pengaturan yang diperlukan untuk menjalankan pernyataan dan kemudian memanggil tindakan pernyataan:

(Catatan. terjemahan: di sini dan di bawah komentar dalam kode diterjemahkan ke dalam bahasa Rusia. Selain itu, lekukan telah diperbaiki menjadi spasi, bukan tab [disarankan di Go] semata-mata untuk tujuan keterbacaan yang lebih baik dalam tata letak Habr. Setelah setiap daftar ada tautan ke aslinya di GitHub, tempat komentar dan tab berbahasa Inggris 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()   // ОТидаСм, Ρ‡Ρ‚ΠΎ всС остановлСно
}

(utama.pergiDi mentah)

Kami melakukan hal berikut:

  1. Kami mengonfigurasi pengendali untuk sinyal sistem operasi tertentu untuk menyebabkan penghentian operator secara baik-baik.
  2. Gunakan WaitGroupuntuk menghentikan semua goroutine dengan baik sebelum menghentikan aplikasi.
  3. Kami menyediakan akses ke cluster dengan membuat clientset.
  4. Meluncurkan NamespaceController, di mana semua logika kita akan ditempatkan.

Sekarang kita memerlukan dasar logika, dan dalam kasus 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
}

(pengontrol.pergiDi mentah)

Di sini kita mengkonfigurasi SharedIndexInformer, yang secara efektif (menggunakan cache) menunggu perubahan pada namespace (baca lebih lanjut tentang informan di artikel β€œBagaimana sebenarnya penjadwal Kubernetes bekerja?"- kira-kira. terjemahan). Setelah ini kita terhubung EventHandler ke informan, sehingga ketika menambahkan namespace (Namespace) fungsi dipanggil createRoleBinding.

Langkah selanjutnya adalah mendefinisikan 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))
  }
}

(pengontrol.pergiDi mentah)

Kami mendapatkan namespace sebagai obj dan mengubahnya menjadi sebuah objek Namespace. Lalu kita definisikan RoleBinding, berdasarkan file YAML yang disebutkan di awal, menggunakan objek yang disediakan Namespace dan menciptakan RoleBinding. Terakhir, kami mencatat apakah pembuatannya berhasil.

Fungsi terakhir yang didefinisikan adalah 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
}

(pengontrol.pergiDi mentah)

Di sini kita berbicara WaitGroupbahwa kita meluncurkan goroutine dan kemudian memanggil namespaceInformer, yang telah didefinisikan sebelumnya. Ketika sinyal berhenti tiba, fungsinya akan berakhir, informasikan WaitGroup, yang tidak lagi dijalankan, dan fungsi ini akan keluar.

Informasi tentang membuat dan menjalankan pernyataan ini pada cluster Kubernetes dapat ditemukan di repositori di GitHub.

Itu saja untuk operator yang membuat RoleBinding Kapan Namespace di cluster Kubernetes, siap.

Sumber: www.habr.com

Tambah komentar