Operaattorin kirjoittaminen Kubernetesille Golangissa

Huomautus. käännös: Operaattorit ovat Kubernetesin apuohjelmistoja, jotka on suunniteltu automatisoimaan rutiinitoimintojen suorittaminen klusteriobjekteille tiettyjen tapahtumien sattuessa. Olemme jo kirjoittaneet operaattoreista vuonna tässä artikkelissa, jossa he keskustelivat työnsä perusideoista ja periaatteista. Mutta jos tuo materiaali oli enemmänkin näkemys Kubernetesin valmiiden komponenttien käyttämisestä, niin nyt ehdotetun uuden artikkelin käännös on jo uuden operaattorin käyttöönotosta ymmällään kehittäjän/DevOps-insinöörin visio.

Operaattorin kirjoittaminen Kubernetesille Golangissa

Päätin kirjoittaa tämän postauksen tosielämän esimerkillä, kun yritin löytää dokumentaatiota Kubernetes-operaattorin luomisesta, mikä kävi läpi koodin tutkimisen.

Kuvattava esimerkki on tämä: Kubernetes-klusterissamme jokainen Namespace edustaa joukkueen hiekkalaatikkoympäristöä, ja halusimme rajoittaa pääsyä niihin niin, että joukkueet voivat pelata vain omissa hiekkalaatikoissaan.

Voit saavuttaa haluamasi määrittämällä käyttäjälle ryhmän, jolla on RoleBinding tiettyyn Namespace и ClusterRole muokkausoikeuksilla. YAML-esitys näyttää tältä:

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

(roolisidonta.yamlSisään raaka)

Luoda yksi RoleBinding Voit tehdä sen manuaalisesti, mutta sadan nimiavaruuden rajan ylittämisen jälkeen siitä tulee ikävä tehtävä. Tässä Kubernetes-operaattorit ovat hyödyllisiä – niiden avulla voit automatisoida Kubernetes-resurssien luomisen resurssien muutosten perusteella. Meidän tapauksessamme haluamme luoda RoleBinding luodessaan Namespace.

Ensinnäkin määritellään funktio mainjoka suorittaa tarvittavat asetukset käskyn suorittamiseksi ja kutsuu sitten lausekkeen toiminnon:

(Huomautus. käännös: tässä ja alla koodin kommentit on käännetty venäjäksi. Lisäksi sisennys on korjattu välilyönneiksi [suositellaan Go]-välilehtien sijaan pelkästään paremman luettavuuden vuoksi Habr-asettelussa. Jokaisen listauksen jälkeen on linkit alkuperäiseen GitHubissa, johon on tallennettu englanninkieliset kommentit ja välilehdet.)

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.goSisään raaka)

Teemme seuraavaa:

  1. Konfiguroimme käsittelijän tietyille käyttöjärjestelmäsignaaleille, jotta käyttäjä lopettaa sulavasti.
  2. käyttö WaitGrouplopettaa kaikki goutiinit sulavasti ennen sovelluksen lopettamista.
  3. Tarjoamme pääsyn klusteriin luomalla clientset.
  4. Tuoda markkinoille NamespaceController, johon kaikki logiikkamme sijoittuu.

Nyt tarvitsemme pohjan logiikalle, ja meidän tapauksessamme tämä on mainittu 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.goSisään raaka)

Tässä konfiguroidaan SharedIndexInformer, joka tehokkaasti (käyttäen välimuistia) odottaa muutoksia nimitiloihin (lue lisää tiedottajista artikkelista "Miten Kubernetes-ajastin todella toimii?"- noin käännös). Tämän jälkeen yhdistämme EventHandler ilmoittajalle, jotta nimiavaruutta lisättäessä (Namespace) -funktiota kutsutaan createRoleBinding.

Seuraava vaihe on määrittää tämä toiminto 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.goSisään raaka)

Saamme nimiavaruuden muodossa obj ja muuntaa sen objektiksi Namespace. Sitten määrittelemme RoleBinding, perustuu alussa mainittuun YAML-tiedostoon, käyttämällä annettua objektia Namespace ja luominen RoleBinding. Lopuksi kirjaamme, onnistuiko luominen.

Viimeinen määritettävä funktio on 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.goSisään raaka)

Täällä me puhumme WaitGroupettä käynnistämme gorutiinin ja soitamme sitten namespaceInformer, joka on määritelty aiemmin. Kun pysäytyssignaali saapuu, se lopettaa toiminnon, ilmoittaa WaitGroup, jota ei enää suoriteta, ja tämä toiminto poistuu.

Tietoja tämän lausunnon rakentamisesta ja suorittamisesta Kubernetes-klusterissa on osoitteessa arkistot GitHubissa.

Siinä se luojalle RoleBinding kun Namespace Kubernetes-klusterissa, valmiina.

Lähde: will.com

Lisää kommentti