Viết toán tử cho Kubernetes ở Golang

Ghi chú. bản dịch.: Toán tử là phần mềm phụ trợ cho Kubernetes, được thiết kế để tự động hóa việc thực thi các hành động thông thường trên các đối tượng cụm khi một số sự kiện nhất định xảy ra. Chúng tôi đã viết về các toán tử trong bài viết này, nơi họ nói về những ý tưởng và nguyên tắc cơ bản trong công việc của mình. Nhưng nếu tài liệu đó thiên về góc nhìn vận hành các thành phần làm sẵn cho Kubernetes, thì bản dịch của bài viết mới hiện được đề xuất đã là tầm nhìn của một nhà phát triển/kỹ sư DevOps đang bối rối trước việc triển khai một toán tử mới.

Viết toán tử cho Kubernetes ở Golang

Tôi quyết định viết bài đăng này với một ví dụ thực tế sau khi cố gắng tìm tài liệu về cách tạo toán tử cho Kubernetes, trải qua quá trình nghiên cứu mã.

Ví dụ sẽ được mô tả như sau: trong cụm Kubernetes của chúng tôi, mỗi Namespace đại diện cho môi trường hộp cát của một đội và chúng tôi muốn hạn chế quyền truy cập vào chúng để các đội chỉ có thể chơi trong hộp cát của riêng họ.

Bạn có thể đạt được điều mình muốn bằng cách chỉ định cho người dùng một nhóm có RoleBinding cụ thể Namespace и ClusterRole với quyền chỉnh sửa. Biểu diễn YAML sẽ trông như thế này:

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

(vai trò ràng buộc.yamlTrong nguyên)

Tạo một cái RoleBinding Bạn có thể thực hiện thủ công, nhưng sau khi vượt qua hàng trăm không gian tên, nó sẽ trở thành một công việc tẻ nhạt. Đây là lúc các toán tử Kubernetes trở nên hữu ích—chúng cho phép bạn tự động hóa việc tạo tài nguyên Kubernetes dựa trên những thay đổi đối với tài nguyên. Trong trường hợp của chúng tôi, chúng tôi muốn tạo RoleBinding trong khi tạo Namespace.

Trước hết hãy xác định hàm mainthực hiện thiết lập cần thiết để chạy câu lệnh và sau đó gọi hành động câu lệnh:

(Ghi chú. bản dịch.: ở đây và bên dưới các nhận xét trong mã được dịch sang tiếng Nga. Ngoài ra, phần thụt lề đã được sửa thành khoảng trắng thay vì tab [được khuyến nghị trong Go] chỉ nhằm mục đích dễ đọc hơn trong bố cục Habr. Sau mỗi danh sách sẽ có các liên kết đến bản gốc trên GitHub, nơi lưu trữ các tab và nhận xét bằng tiếng Anh.)

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.goTrong nguyên)

Chúng tôi làm như sau:

  1. Chúng tôi định cấu hình trình xử lý cho các tín hiệu hệ điều hành cụ thể để khiến toán tử chấm dứt một cách nhẹ nhàng.
  2. Chúng tôi sử dụng WaitGroupđể dừng tất cả goroutine một cách duyên dáng trước khi chấm dứt ứng dụng.
  3. Chúng tôi cung cấp quyền truy cập vào cụm bằng cách tạo clientset.
  4. Phóng NamespaceController, trong đó tất cả logic của chúng tôi sẽ được đặt.

Bây giờ chúng ta cần một cơ sở cho logic, và trong trường hợp của chúng ta đây là cơ sở được đề cập 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
}

(bộ điều khiển.goTrong nguyên)

Ở đây chúng tôi cấu hình SharedIndexInformer, điều này sẽ (sử dụng bộ đệm) chờ đợi những thay đổi trong không gian tên một cách hiệu quả (đọc thêm về người cung cấp thông tin trong bài viết “Bộ lập lịch Kubernetes thực sự hoạt động như thế nào?"- khoảng dịch). Sau này chúng tôi kết nối EventHandler cho người cung cấp thông tin, để khi thêm một không gian tên (Namespace) hàm được gọi createRoleBinding.

Bước tiếp theo là xác định hàm này 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))
  }
}

(bộ điều khiển.goTrong nguyên)

Chúng tôi nhận được không gian tên như obj và chuyển đổi nó thành một đối tượng Namespace. Sau đó chúng tôi xác định RoleBinding, dựa trên tệp YAML được đề cập ở phần đầu, sử dụng đối tượng được cung cấp Namespace và tạo ra RoleBinding. Cuối cùng, chúng tôi ghi lại xem quá trình tạo có thành công hay không.

Hàm cuối cùng được xác định là 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
}

(bộ điều khiển.goTrong nguyên)

Ở đây chúng ta đang nói chuyện WaitGrouprằng chúng tôi khởi chạy goroutine và sau đó gọi namespaceInformer, đã được xác định trước đó. Khi có tín hiệu dừng đến sẽ kết thúc chức năng, thông báo WaitGroup, không còn được thực thi nữa và hàm này sẽ thoát.

Thông tin về việc xây dựng và chạy câu lệnh này trên cụm Kubernetes có thể được tìm thấy trong kho lưu trữ trên GitHub.

Đó là việc của người vận hành tạo ra RoleBinding khi nào Namespace trong cụm Kubernetes đã sẵn sàng.

Nguồn: www.habr.com

Thêm một lời nhận xét