Skriver en operatör för Kubernetes i Golang

Notera. transl.: Operatörer är hjälpprogram för Kubernetes, utformad för att automatisera utförandet av rutinåtgärder på klusterobjekt när vissa händelser inträffar. Vi har redan skrivit om operatörer i den här artikeln, där de pratade om de grundläggande idéerna och principerna för sitt arbete. Men om det materialet mer var en vy från sidan av driften av färdiga komponenter för Kubernetes, så är översättningen av den nya artikeln som nu föreslagits redan visionen för en utvecklare/DevOps-ingenjör som förbryllas över implementeringen av en ny operatör.

Skriver en operatör för Kubernetes i Golang

Jag bestämde mig för att skriva det här inlägget med ett exempel från verkligheten efter mina försök att hitta dokumentation om att skapa en operatör för Kubernetes, som gick igenom koden.

Exemplet som kommer att beskrivas är detta: i vårt Kubernetes-kluster, var och en Namespace representerar ett lags sandlådemiljö, och vi ville begränsa tillgången till dem så att lag bara kunde spela i sina egna sandlådor.

Du kan uppnå vad du vill genom att tilldela en användare en grupp som har RoleBinding till specifikt Namespace и ClusterRole med redigeringsrättigheter. YAML-representationen kommer att se ut så här:

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

Skapa en RoleBinding Du kan göra det manuellt, men efter att ha passerat hundra namnutrymmen blir det en tråkig uppgift. Det är här Kubernetes-operatörerna kommer till nytta – de låter dig automatisera skapandet av Kubernetes-resurser baserat på ändringar av resurser. I vårt fall vill vi skapa RoleBinding medan du skapar Namespace.

Först av allt, låt oss definiera funktionen mainsom gör den nödvändiga inställningen för att köra satsen och sedan anropar satsåtgärden:

(Notera. transl.: här och nedan är kommentarerna i koden översatta till ryska. Dessutom har indraget korrigerats till mellanslag istället för [rekommenderas i Go]-flikar enbart i syfte att förbättra läsbarheten inom Habr-layouten. Efter varje listning finns det länkar till originalet på GitHub, där engelskspråkiga kommentarer och flikar lagras.)

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

Vi gör följande:

  1. Vi konfigurerar en hanterare för specifika operativsystemsignaler för att orsaka graciös uppsägning av operatören.
  2. Vi använder WaitGroupatt graciöst stoppa alla goroutiner innan du avslutar applikationen.
  3. Vi ger tillgång till klustret genom att skapa clientset.
  4. Vi lanserar NamespaceController, där all vår logik kommer att finnas.

Nu behöver vi en grund för logik, och i vårt fall är det den som nämns 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 obehandlad)

Här konfigurerar vi SharedIndexInformer, som effektivt (med hjälp av en cache) väntar på ändringar i namnutrymmen (läs mer om informatörer i artikeln "Hur fungerar Kubernetes-schemaläggaren egentligen?"- cirka. översättning). Efter detta ansluter vi EventHandler till informatören, så att när du lägger till ett namnområde (Namespace) funktionen anropas createRoleBinding.

Nästa steg är att definiera denna funktion 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 obehandlad)

Vi får namnutrymmet som obj och konvertera det till ett objekt Namespace. Sedan definierar vi RoleBinding, baserat på YAML-filen som nämndes i början, med hjälp av det angivna objektet Namespace och skapande RoleBinding. Slutligen loggar vi om skapandet lyckades.

Den sista funktionen som ska definieras är 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 obehandlad)

Här pratar vi WaitGroupatt vi startar goroutinen och sedan ringer namespaceInformer, som tidigare har definierats. När stoppsignalen kommer kommer den att avsluta funktionen, informera WaitGroup, som inte längre körs, och den här funktionen avslutas.

Information om att bygga och köra detta uttalande på ett Kubernetes-kluster finns i repositories på GitHub.

Det är det för operatören som skapar RoleBinding när Namespace i Kubernetes-klustret, redo.

Källa: will.com

Lägg en kommentar