Golang'da Kubernetes için bir operatör yazma

Not. tercüme: Operatörler, belirli olaylar meydana geldiğinde küme nesneleri üzerinde rutin eylemlerin yürütülmesini otomatikleştirmek için tasarlanmış Kubernetes için yardımcı yazılımlardır. Operatörler hakkında zaten yazmıştık. Bu makalede, çalışmalarının temel fikirleri ve ilkeleri hakkında konuştular. Ancak bu materyal daha çok Kubernetes için hazır bileşenlerin çalıştırılması açısından bir bakış açısıysa, o zaman yeni makalenin şimdi önerilen çevirisi, yeni bir operatörün uygulanmasıyla kafası karışan bir geliştirici/DevOps mühendisinin vizyonudur.

Golang'da Kubernetes için bir operatör yazma

Kubernetes için bir operatör oluşturmaya yönelik, kodun incelenmesiyle ilgili dokümantasyon bulma girişimlerimin ardından, bu yazıyı gerçek hayattan bir örnekle yazmaya karar verdim.

Açıklanacak örnek şudur: Kubernetes kümemizde her biri Namespace bir takımın sanal alan ortamını temsil ediyor ve takımların yalnızca kendi sanal alanlarda oynayabilmesi için bunlara erişimi sınırlamak istedik.

Bir kullanıcıya aşağıdaki özelliklere sahip bir grup atayarak istediğinizi elde edebilirsiniz: RoleBinding spesifik olarak Namespace и ClusterRole düzenleme haklarına sahip. YAML gösterimi şu şekilde görünecektir:

---
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.yamlIçinde çiğ)

Bir tane yarat RoleBinding Bunu manuel olarak yapabilirsiniz, ancak yüz ad alanı sınırını geçtikten sonra sıkıcı bir iş haline gelir. Kubernetes operatörlerinin kullanışlı olduğu nokta burasıdır; kaynaklarda yapılan değişikliklere göre Kubernetes kaynaklarının oluşturulmasını otomatikleştirmenize olanak tanır. Bizim durumumuzda yaratmak istiyoruz RoleBinding oluştururken Namespace.

Öncelikle fonksiyonu tanımlayalım mainbu, ifadeyi çalıştırmak için gerekli kurulumu yapar ve ardından ifade eylemini çağırır:

(Not. tercüme: burada ve altında koddaki yorumlar Rusçaya çevrilmiştir. Ayrıca, yalnızca Habr düzeninde daha iyi okunabilirlik sağlamak amacıyla girintiler [Go'da önerilir] sekmeleri yerine boşluklara göre düzeltildi. Her listelemeden sonra, İngilizce yorumların ve sekmelerin saklandığı GitHub'da orijinaline bağlantılar bulunur.)

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()   // Ожидаем, что все остановлено
}

(ana.goIçinde çiğ)

Aşağıdakileri yapıyoruz:

  1. Operatörün sorunsuz bir şekilde sonlandırılmasını sağlamak amacıyla belirli işletim sistemi sinyalleri için bir işleyici yapılandırıyoruz.
  2. Kullanırız WaitGroupUygulamayı sonlandırmadan önce tüm goroutinleri zarif bir şekilde durdurmak için.
  3. Oluşturarak Cluster'a erişim sağlıyoruz. clientset.
  4. koşmak NamespaceController, tüm mantığımızın yer alacağı yer.

Artık mantık için bir temele ihtiyacımız var ve bizim durumumuzda bahsedilen de bu. 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
}

(kontrolör.goIçinde çiğ)

Burada yapılandırıyoruz SharedIndexInformer, etkili bir şekilde (önbellek kullanarak) ad alanlarındaki değişiklikleri bekleyecektir (makaledeki muhbirler hakkında daha fazla bilgi edinin “Kubernetes zamanlayıcı aslında nasıl çalışıyor?»- yaklaşık. tercüme). Bundan sonra bağlanıyoruz EventHandler bilgilendiriciye, böylece bir ad alanı eklerken (Namespace) işlevi çağrılır createRoleBinding.

Bir sonraki adım bu fonksiyonu tanımlamaktır 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))
  }
}

(kontrolör.goIçinde çiğ)

Ad alanını şu şekilde alıyoruz: obj ve onu bir nesneye dönüştürün Namespace. Sonra tanımlarız RoleBinding, sağlanan nesneyi kullanarak, başlangıçta belirtilen YAML dosyasına göre Namespace ve yaratmak RoleBinding. Son olarak, oluşturma işleminin başarılı olup olmadığını günlüğe kaydederiz.

Tanımlanacak son fonksiyon 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
}

(kontrolör.goIçinde çiğ)

İşte konuşuyoruz WaitGroupgoroutine'i başlatıyoruz ve sonra çağırıyoruz namespaceInformerDaha önce tanımlanmış olan . Durdurma sinyali geldiğinde fonksiyonu sonlandıracaktır, bilgilendirin WaitGroupartık yürütülmeyecek ve bu işlevden çıkılacaktır.

Bu bildirimi bir Kubernetes kümesinde oluşturmaya ve çalıştırmaya ilişkin bilgileri şu adreste bulabilirsiniz: GitHub'daki depolar.

Oluşturan operatör için bu kadar RoleBinding görünüşte Namespace Kubernetes kümesinde hazır.

Kaynak: habr.com

Yorum ekle