Skrive en operatør for Kubernetes i Golang

Merk. overs.: Operatører er hjelpeprogramvare for Kubernetes, designet for å automatisere utførelse av rutinehandlinger på klyngeobjekter når visse hendelser inntreffer. Vi har allerede skrevet om operatører i denne artikkelen, hvor de snakket om de grunnleggende ideene og prinsippene i arbeidet deres. Men hvis dette materialet mer var et syn fra siden av drift av ferdige komponenter for Kubernetes, så er oversettelsen av den nye artikkelen som nå foreslås allerede visjonen til en utvikler/DevOps-ingeniør som er forvirret over implementeringen av en ny operatør.

Skrive en operatør for Kubernetes i Golang

Jeg bestemte meg for å skrive dette innlegget med et virkelighetseksempel etter mine forsøk på å finne dokumentasjon på å lage en operatør for Kubernetes, som gikk gjennom å studere koden.

Eksemplet som vil bli beskrevet er dette: i vår Kubernetes-klynge, hver Namespace representerer et lags sandkassemiljø, og vi ønsket å begrense tilgangen til dem slik at lagene bare kunne spille i sine egne sandkasser.

Du kan oppnå det du ønsker ved å tildele en bruker en gruppe som har RoleBinding til spesifikke Namespace и ClusterRole med redigeringsrettigheter. YAML-representasjonen vil se slik ut:

---
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

(rollebinding.yamlI )

Lag en RoleBinding Du kan gjøre det manuelt, men etter å ha krysset hundre navneområder, blir det en kjedelig oppgave. Det er her Kubernetes-operatører kommer godt med – de lar deg automatisere opprettelsen av Kubernetes-ressurser basert på endringer i ressurser. I vårt tilfelle ønsker vi å skape RoleBinding mens du lager Namespace.

Først av alt, la oss definere funksjonen mainsom gjør det nødvendige oppsettet for å kjøre setningen og deretter kaller setningshandlingen:

(Merk. overs.: her og under er kommentarene i koden oversatt til russisk. I tillegg har innrykk blitt korrigert til mellomrom i stedet for [anbefalt i Go]-faner utelukkende for bedre lesbarhet i Habr-oppsettet. Etter hver oppføring er det lenker til originalen på GitHub, hvor engelskspråklige kommentarer og faner er lagret.)

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.goI )

Vi gjør følgende:

  1. Vi konfigurerer en behandler for spesifikke operativsystemsignaler for å forårsake elegant avslutning av operatøren.
  2. Vi bruker WaitGroupå stoppe alle goroutiner på en elegant måte før du avslutter applikasjonen.
  3. Vi gir tilgang til klyngen ved å opprette clientset.
  4. Lansering NamespaceController, der all vår logikk vil være plassert.

Nå trenger vi et grunnlag for logikk, og i vårt tilfelle er det denne som er nevnt 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
}

(controller.goI )

Her konfigurerer vi SharedIndexInformer, som effektivt (ved hjelp av en hurtigbuffer) vil vente på endringer i navneområder (les mer om informanter i artikkelen "Hvordan fungerer egentlig Kubernetes-planleggeren?"- ca. oversettelse). Etter dette kobler vi til EventHandler til informanten, slik at når du legger til et navneområde (Namespace) funksjon kalles createRoleBinding.

Det neste trinnet er å definere denne funksjonen 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))
  }
}

(controller.goI )

Vi får navneområdet som obj og konvertere den til et objekt Namespace. Så definerer vi RoleBinding, basert på YAML-filen nevnt i begynnelsen, ved å bruke det oppgitte objektet Namespace og skaper RoleBinding. Til slutt logger vi om opprettelsen var vellykket.

Den siste funksjonen som skal defineres er 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
}

(controller.goI )

Her snakker vi WaitGroupat vi starter goroutinen og så ringer namespaceInformer, som er tidligere definert. Når stoppsignalet kommer vil det avslutte funksjonen, informer WaitGroup, som ikke lenger kjøres, og denne funksjonen avsluttes.

Informasjon om å bygge og kjøre denne setningen på en Kubernetes-klynge finner du i repositories på GitHub.

Det er det for operatøren som lager RoleBinding når Namespace i Kubernetes-klyngen, klar.

Kilde: www.habr.com

Legg til en kommentar