当前位置:网站首页>Introduction to operator

Introduction to operator

2022-06-26 21:50:00 0x00000

One 、Operator brief introduction

      stay Kubernetes We often use Deployment、DaemonSet、Service、ConfigMap And so on , These are all resources Kubernetes Built in resources for , Their creation 、 to update 、 Delete, etc Controller Manager Responsible for managing the .

 

Two 、Operator form

      Operator(Controller+CRD),Operator By Kubernetes Custom resources (CRD) And controller (Controller) The meta - native extension service , among CRD Define each Operator Custom resource objects that need to be created and managed , The bottom layer is actually through APIServer Interface in ETCD Register a new resource type in , After registration, you can create objects of this resource type . But just registering resources and creating resource objects doesn't make any sense ,CRD The most important thing is to cooperate with the corresponding Controller To achieve the desired state of the user-defined resource , For example, built-in Deployment Controller Used to control Department Functions of resource objects , Generate a specific number of... According to the configuration Pod Monitor its status , And make corresponding actions according to the event .

 

3、 ... and 、Operator Use

      Users want to build a for their own custom resources Kubernetes Operator, There are many tools to choose from, such as Operator SDK、Kubebuilder, You can even use Operator SDK(HELM、Ansible、Go). These tools create Kubernetes Operator Used to monitor custom resources , And adjust the resource status according to the change of resources .

     Operator As a custom extension resource, you can Deployment Deploy to Kubernetes in , adopt List-Watch Method to monitor the changes of corresponding resources , When a user modifies anything in a custom resource ,Operator Will monitor changes to resources , And perform specific operations according to the changes , These operations usually have an impact on Kubernetes API Some resources in the .

 

Four 、Operator The application case

 Kubebuilder Introduce

    Kubebuilder It's a use. Go Language construction Kubenetes API Controller and CRD Scaffolding tools , By using Kubebuilder, Users can follow a simple programming framework , To write Operator Using examples .

  • kubenetes: v1.23.8
  • go
tar xf go1.18.3.linux-amd64.tar.gz
mv go /usr/local/
vim /etc/profile.d/go183.sh
export GO111MODULE=on
export GOROOT=/usr/local/go 
export GOPATH=/root/gopath
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

source /etc/profile.d/go183.sh
  • kubebuilder: 3.5.0
yum -y install gcc
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/

kubebuilder version

Case study

  Welcome The case mainly realizes the use of Operator and CRD Deploy a complete application environment , You can create resources based on custom types , By creating a Welcome Type of resource , Automatically created in the background Deployment and Service, adopt Web Page access Service Render application deployment , Control management is carried out by means of user-defined controller

   We created western medicine Welcome User defined resources and corresponding Controllers, Finally, we can use the following code YAML File deployment is simple Web application

apiVersion: webapp.demo.welcome.domain/v1
kind: Welcome
metadata:
  name: welcome-sample
spec:
  name: myfriends

Web Application Introduction

  In this case , We use Go Language HTTP Module to create a Web service , After the user visits the page, it will be loaded automatically NAME And PORT Environment variables and render index.html In static file , The code is as follows :

package main

import (
	"fmt"
	"net/http"
	"os"
)

func main() {
	name := os.Getenv("NAME")
	hello := fmt.Sprintf("Hello %s", name)
	http.Handle("/hello/", http.StripPrefix("/hello/", http.FileServer(http.Dir("static"))))
	f, err := os.OpenFile("./static/index.html", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		panic(err)
	}
	defer f.Close()
	if _, err = f.WriteString(hello); err != nil {
		panic(err)
	}
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
}

    among ,NAME Environment variables are passed through us in Welcome As defined in name Field get , In the following controller writing, we will introduce in detail the detailed methods of obtaining fields . We will index.html Put it in Static Under the folder , And package the project file as Docker Mirror image ,Dockerfile as follows :

FROM golang:1.17 as builder

WORKDIR /
COPY . .
COPY static

RUN CGO_ENABLED=0 GOOS=linux go build -v -o main
FROM alpine
RUN apk add --no-cache ca-certificates
COPY --from=builder /main /usr/local/main
COPY --from=builder static /static
CMD ["/usr/local/main"]

Project initialization

Use Kubebuilder Command image project initialization

mkdir demo
cd demo
go mod init welcome_demo.domain
kubebuilder init --domain demo.welcome.domain

After initializing the project ,kubebuilder The generated project interface is as follows

[[email protected] demo]# tree .
.
├── config
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── controller_manager_config.yaml
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   └── rbac
│       ├── auth_proxy_client_clusterrole.yaml
│       ├── auth_proxy_role_binding.yaml
│       ├── auth_proxy_role.yaml
│       ├── auth_proxy_service.yaml
│       ├── kustomization.yaml
│       ├── leader_election_role_binding.yaml
│       ├── leader_election_role.yaml
│       ├── role_binding.yaml
│       └── service_account.yaml
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── main.go
├── Makefile
├── PROJECT
└── README.md

6 directories, 25 files

 

establish `Welcome` Kind And its corresponding controller

[[email protected] demo]# kubebuilder create api --group webapp --kind Welcome --version v1
Create Resource [y/n]
y
Create Controller [y/n]
y

    Type twice y, Kubebuilder Create templates for resources and controllers respectively , Here group、version、kind this 3 Attributes are combined to identify a k8s Of CRD, After creation ,Kubebuilder Add the following file :

[[email protected] demo]# tree .
.
├── api
│   └── v1
│       ├── groupversion_info.go
│       ├── welcome_types.go
│       └── zz_generated.deepcopy.go
├── bin
│   └── controller-gen
├── config
│   ├── crd
│   │   ├── kustomization.yaml
│   │   ├── kustomizeconfig.yaml
│   │   └── patches
│   │       ├── cainjection_in_welcomes.yaml
│   │       └── webhook_in_welcomes.yaml
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── controller_manager_config.yaml
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   ├── rbac
│   │   ├── auth_proxy_client_clusterrole.yaml
│   │   ├── auth_proxy_role_binding.yaml
│   │   ├── auth_proxy_role.yaml
│   │   ├── auth_proxy_service.yaml
│   │   ├── kustomization.yaml
│   │   ├── leader_election_role_binding.yaml
│   │   ├── leader_election_role.yaml
│   │   ├── role_binding.yaml
│   │   ├── service_account.yaml
│   │   ├── welcome_editor_role.yaml
│   │   └── welcome_viewer_role.yaml
│   └── samples
│       └── webapp_v1_welcome.yaml
├── controllers
│   ├── suite_test.go
│   └── welcome_controller.go
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── main.go
├── Makefile
├── PROJECT
└── README.md

13 directories, 38 files

The following two steps are required :

  1. modify Resource Type
  2. modify Controller Logic

 

modify Resource Type

  here Resource Type For the resource fields that need to be defined , Used in Yaml Make a statement in the document , In this case, you need to add name Fields are used for “Welcome”Kind Medium Web application , The code is as follows :

vim api/v1/welcome_types.go

// WelcomeSpec defines the desired state of Welcome
type WelcomeSpec struct {
        // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
        // Important: Run "make" to regenerate code after modifying this file

        // Foo is an example field of Welcome. Edit welcome_types.go to remove/update
        // Foo string `json:"foo,omitempty"`
        Name string `json:"name,omitempty"`
}

modify Controller Logic

    stay Controller We need to go through Reconcile Method to complete Deployment and Service Deploy , And finally achieve the desired state .

vim controllers/welcome_controller.go

//+kubebuilder:rbac:groups=webapp.demo.welcome.domain,resources=welcomes,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=webapp.demo.welcome.domain,resources=welcomes/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=webapp.demo.welcome.domain,resources=welcomes/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Welcome object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
func (r *WelcomeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        // _ = log.FromContext(ctx)

        // TODO(user): your logic here
        log := r.Log.WithValues("welcome", req.NamespacedName)
        log.Info("reconcilling welcome")
        return ctrl.Result{}, nil
}
  • The final code is as follows
/*
Copyright 2022.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
	"context"
	"fmt"
	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"sigs.k8s.io/controller-runtime/pkg/log"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	webappv1 "welcome_demo.domain/api/v1"
)

// WelcomeReconciler reconciles a Welcome object
type WelcomeReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=webapp.demo.welcome.domain,resources=welcomes,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=webapp.demo.welcome.domain,resources=welcomes/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=webapp.demo.welcome.domain,resources=welcomes/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Welcome object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
func (r *WelcomeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	_ = log.FromContext(ctx)

	// TODO(user): your logic here
	welcome := &webappv1.Welcome{}
	if err := r.Client.Get(ctx, req.NamespacedName, welcome); err != nil {
		return ctrl.Result{}, err
	}
	deployment, err := r.createWelcomeDeployment(welcome)
	if err != nil {
		return ctrl.Result{}, err
	}
	fmt.Println("create deployment success!")
	svc, err := r.createService(welcome)
	if err != nil {
		return ctrl.Result{}, err
	}
	fmt.Println("create service success!")
	applyOpts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("welcome_controller")}
	err = r.Patch(ctx, &deployment, client.Apply, applyOpts...)
	if err != nil {
		return ctrl.Result{}, err
	}
	err = r.Patch(ctx, &svc, client.Apply, applyOpts...)
	if err != nil {
		return ctrl.Result{}, err
	}
	return ctrl.Result{}, nil
}

func (r *WelcomeReconciler) createWelcomeDeployment(welcome *webappv1.Welcome) (appsv1.Deployment, error) {
	defOne := int32(1)
	name := welcome.Spec.Name
	if name == "" {
		name = "world"
	}
	depl := appsv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			APIVersion: appsv1.SchemeGroupVersion.String(),
			Kind:       "Deployment",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      welcome.Name,
			Namespace: welcome.Namespace,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: &defOne,
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{"welcome": welcome.Name},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{"welcome": welcome.Name},
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "welcome",
							Env: []corev1.EnvVar{
								{Name: "NAME", Value: name},
							},
							Ports: []corev1.ContainerPort{
								{ContainerPort: 8080,
									Name:     "http",
									Protocol: "TCP",
								},
							},
							Image: "sdfcdwefe/operatordemo:v1",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:    *resource.NewMilliQuantity(100, resource.DecimalSI),
									corev1.ResourceMemory: *resource.NewMilliQuantity(100000, resource.BinarySI),
								},
							},
						},
					},
				},
			},
		},
	}

	return depl, nil
}

func (r *WelcomeReconciler) createService(welcome *webappv1.Welcome) (corev1.Service, error) {
	svc := corev1.Service{
		TypeMeta: metav1.TypeMeta{
			APIVersion: corev1.SchemeGroupVersion.String(),
			Kind:       "Service"},
		ObjectMeta: metav1.ObjectMeta{
			Name:      welcome.Name,
			Namespace: welcome.Namespace,
		},
		Spec: corev1.ServiceSpec{
			Ports: []corev1.ServicePort{
				{Name: "http",
					Port:       8080,
					Protocol:   "TCP",
					TargetPort: intstr.FromString("http")},
			},
			Selector: map[string]string{"welcome": welcome.Name},
			Type:     corev1.ServiceTypeLoadBalancer,
		},
	}
	return svc, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *WelcomeReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		For(&webappv1.Welcome{}).
		Complete(r)
}

 

Welcome Should be deployed

  •   Generate CRD resources
[[email protected] demo]# make manifests
/root/demo/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases

[[email protected] demo]# tree .
.
├── api
│   └── v1
│       ├── groupversion_info.go
│       ├── welcome_types.go
│       └── zz_generated.deepcopy.go
├── bin
│   └── controller-gen
├── config
│   ├── crd
│   │   ├── bases
│   │   │   └── webapp.demo.welcome.domain_welcomes.yaml
│   │   ├── kustomization.yaml
│   │   ├── kustomizeconfig.yaml
│   │   └── patches
│   │       ├── cainjection_in_welcomes.yaml
│   │       └── webhook_in_welcomes.yaml
│   ├── default
│   │   ├── kustomization.yaml
│   │   ├── manager_auth_proxy_patch.yaml
│   │   └── manager_config_patch.yaml
│   ├── manager
│   │   ├── controller_manager_config.yaml
│   │   ├── kustomization.yaml
│   │   └── manager.yaml
│   ├── prometheus
│   │   ├── kustomization.yaml
│   │   └── monitor.yaml
│   ├── rbac
│   │   ├── auth_proxy_client_clusterrole.yaml
│   │   ├── auth_proxy_role_binding.yaml
│   │   ├── auth_proxy_role.yaml
│   │   ├── auth_proxy_service.yaml
│   │   ├── kustomization.yaml
│   │   ├── leader_election_role_binding.yaml
│   │   ├── leader_election_role.yaml
│   │   ├── role_binding.yaml
│   │   ├── role.yaml
│   │   ├── service_account.yaml
│   │   ├── welcome_editor_role.yaml
│   │   └── welcome_viewer_role.yaml
│   └── samples
│       └── webapp_v1_welcome.yaml
├── controllers
│   ├── suite_test.go
│   └── welcome_controller.go
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── main.go
├── Makefile
├── PROJECT
└── README.md

14 directories, 40 files


[[email protected] demo]# cat config/crd/bases/webapp.demo.welcome.domain_welcomes.yaml 
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  annotations:
    controller-gen.kubebuilder.io/version: v0.9.0
  creationTimestamp: null
  name: welcomes.webapp.demo.welcome.domain
spec:
  group: webapp.demo.welcome.domain
  names:
    kind: Welcome
    listKind: WelcomeList
    plural: welcomes
    singular: welcome
  scope: Namespaced
  versions:
  - name: v1
    schema:
      openAPIV3Schema:
        description: Welcome is the Schema for the welcomes API
        properties:
          apiVersion:
            description: 'APIVersion defines the versioned schema of this representation
              of an object. Servers should convert recognized schemas to the latest
              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
            type: string
          kind:
            description: 'Kind is a string value representing the REST resource this
              object represents. Servers may infer this from the endpoint the client
              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
            type: string
          metadata:
            type: object
          spec:
            description: WelcomeSpec defines the desired state of Welcome
            properties:
              name:
                description: Foo is an example field of Welcome. Edit welcome_types.go
                  to remove/update Foo string `json:"foo,omitempty"`
                type: string
            type: object
          status:
            description: WelcomeStatus defines the observed state of Welcome
            type: object
        type: object
    served: true
    storage: true
    subresources:
      status: {}
  •   establish Welcome Type resources
[[email protected] demo]# kubectl create -f config/crd/bases/
customresourcedefinition.apiextensions.k8s.io/welcomes.webapp.demo.welcome.domain created
[[email protected] demo]# kubectl create -f config/samples/webapp_v1_welcome.yaml 
welcome.webapp.demo.welcome.domain/welcome-sample created
  •   Use `kubectl get crd` Command to view custom objects
[[email protected] demo]# kubectl get crd | grep welcome
welcomes.webapp.demo.welcome.domain          2022-06-25T09:10:37Z
  • adopt kubectl get welcome The command can see the created welcome object
[[email protected] demo]# kubectl get welcome
NAME             AGE
welcome-sample   2m20s

  here CRD Will not complete any work , It's just ETCD Created a record in , We need to run Controller To help us complete the work and finally achieve welcome Defined states .

[[email protected] demo]# make run
/root/demo/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
/root/demo/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
api/v1/welcome_types.go
go vet ./...
go run ./main.go
1.6561485987622015e+09	INFO	controller-runtime.metrics	Metrics server is starting to listen	{"addr": ":8080"}
1.6561485987624757e+09	INFO	setup	starting manager
1.6561485987638762e+09	INFO	Starting server	{"path": "/metrics", "kind": "metrics", "addr": "[::]:8080"}
1.656148598763948e+09	INFO	Starting server	{"kind": "health probe", "addr": "[::]:8081"}
1.656148598764167e+09	INFO	Starting EventSource	{"controller": "welcome", "controllerGroup": "webapp.demo.welcome.domain", "controllerKind": "Welcome", "source": "kind source: *v1.Welcome"}
1.6561485987641926e+09	INFO	Starting Controller	{"controller": "welcome", "controllerGroup": "webapp.demo.welcome.domain", "controllerKind": "Welcome"}
1.6561485988653958e+09	INFO	Starting workers	{"controller": "welcome", "controllerGroup": "webapp.demo.welcome.domain", "controllerKind": "Welcome", "worker count": 1}
create deployment success!
create service success!

  Start the controller locally in the above way , Convenient for debugging and verification

  •   View the created deployment、service
[[email protected] demo]# kubectl get deploy
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
welcome-sample   1/1     1            1           34s
[[email protected] demo]# kubectl get svc
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes       ClusterIP      10.96.0.1       <none>        443/TCP          4d2h
welcome-sample   LoadBalancer   10.106.36.129   <pending>     8080:30037/TCP   39s

 

原网站

版权声明
本文为[0x00000]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/177/202206262053216961.html