From 28149b86a60f650964df96d8de01c3c34a3e5dac Mon Sep 17 00:00:00 2001 From: cdryzun Date: Tue, 5 Sep 2023 15:44:53 +0800 Subject: [PATCH] update --- .dockerignore | 4 + .gitignore | 26 ++ api/v1alpha1/appservice_types.go | 10 +- api/v1alpha1/zz_generated.deepcopy.go | 4 +- ...p.treesir.pub.treesir.pub_appservices.yaml | 316 ++++++++++++++++++ config/rbac/role.yaml | 33 ++ controllers/appservice_controller.go | 107 +++--- 7 files changed, 433 insertions(+), 67 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 config/crd/bases/app.treesir.pub.treesir.pub_appservices.yaml create mode 100644 config/rbac/role.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0f04682 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ +testbin/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e917e5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin +testbin/* +Dockerfile.cross + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Kubernetes Generated files - skip generated files, except for vendored files + +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ diff --git a/api/v1alpha1/appservice_types.go b/api/v1alpha1/appservice_types.go index 793c8d9..8d9ba40 100644 --- a/api/v1alpha1/appservice_types.go +++ b/api/v1alpha1/appservice_types.go @@ -31,11 +31,11 @@ type AppServiceSpec struct { // Important: Run "make" to regenerate code after modifying this file // Foo is an example field of AppService. Edit appservice_types.go to remove/update - Size *int32 `json:"size"` - Image string `json:"image"` - Resource *Resources `json:"resource,omitempty"` - Envs []corev1.EnvVar `json:"envs,omitempty"` - Ports []corev1.ServicePort `json:"ports,omitempty"` + Size *int32 `json:"size"` + Image string `json:"image"` + Resources *Resources `json:"resource,omitempty"` + Envs []corev1.EnvVar `json:"envs,omitempty"` + Ports []corev1.ServicePort `json:"ports,omitempty"` } type Resources struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 891cb3b..440ec96 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -93,8 +93,8 @@ func (in *AppServiceSpec) DeepCopyInto(out *AppServiceSpec) { *out = new(int32) **out = **in } - if in.Resource != nil { - in, out := &in.Resource, &out.Resource + if in.Resources != nil { + in, out := &in.Resources, &out.Resources *out = new(Resources) (*in).DeepCopyInto(*out) } diff --git a/config/crd/bases/app.treesir.pub.treesir.pub_appservices.yaml b/config/crd/bases/app.treesir.pub.treesir.pub_appservices.yaml new file mode 100644 index 0000000..9f31590 --- /dev/null +++ b/config/crd/bases/app.treesir.pub.treesir.pub_appservices.yaml @@ -0,0 +1,316 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: appservices.app.treesir.pub.treesir.pub +spec: + group: app.treesir.pub.treesir.pub + names: + kind: AppService + listKind: AppServiceList + plural: appservices + singular: appservice + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: AppService is the Schema for the appservices 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: AppServiceSpec defines the desired state of AppService + properties: + envs: + items: + description: EnvVar represents an environment variable present in + a Container. + properties: + name: + description: Name of the environment variable. Must be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded using + the previously defined environment variables in the container + and any service environment variables. If a variable cannot + be resolved, the reference in the input string will be unchanged. + Double $$ are reduced to a single $, which allows for escaping + the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will produce the + string literal "$(VAR_NAME)". Escaped references will never + be expanded, regardless of whether the variable exists or + not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's value. Cannot + be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its key + must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: 'Selects a field of the pod: supports metadata.name, + metadata.namespace, `metadata.labels['''']`, `metadata.annotations['''']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, + status.podIP, status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath is + written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in the specified + API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: 'Selects a resource of the container: only + resources limits and requests (limits.cpu, limits.memory, + limits.ephemeral-storage, requests.cpu, requests.memory + and requests.ephemeral-storage) are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of the exposed + resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in the pod's namespace + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the Secret or its key must + be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + type: string + ports: + items: + description: ServicePort contains information on service's port. + properties: + appProtocol: + description: The application protocol for this port. This field + follows standard Kubernetes label syntax. Un-prefixed names + are reserved for IANA standard service names (as per RFC-6335 + and https://www.iana.org/assignments/service-names). Non-standard + protocols should use prefixed names such as mycompany.com/my-custom-protocol. + type: string + name: + description: The name of this port within the service. This + must be a DNS_LABEL. All ports within a ServiceSpec must have + unique names. When considering the endpoints for a Service, + this must match the 'name' field in the EndpointPort. Optional + if only one ServicePort is defined on this service. + type: string + nodePort: + description: 'The port on each node on which this service is + exposed when type is NodePort or LoadBalancer. Usually assigned + by the system. If a value is specified, in-range, and not + in use it will be used, otherwise the operation will fail. If + not specified, a port will be allocated if this Service requires + one. If this field is specified when creating a Service which + does not need it, creation will fail. This field will be wiped + when updating a Service to no longer need it (e.g. changing + type from NodePort to ClusterIP). More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' + format: int32 + type: integer + port: + description: The port that will be exposed by this service. + format: int32 + type: integer + protocol: + default: TCP + description: The IP protocol for this port. Supports "TCP", + "UDP", and "SCTP". Default is TCP. + type: string + targetPort: + anyOf: + - type: integer + - type: string + description: 'Number or name of the port to access on the pods + targeted by the service. Number must be in the range 1 to + 65535. Name must be an IANA_SVC_NAME. If this is a string, + it will be looked up as a named port in the target Pod''s + container ports. If this is not specified, the value of the + ''port'' field is used (an identity map). This field is ignored + for services with clusterIP=None, and should be omitted or + set equal to the ''port'' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service' + x-kubernetes-int-or-string: true + required: + - port + type: object + type: array + resource: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: ResourceList is a set of (resource name, quantity) + pairs. + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: ResourceList is a set of (resource name, quantity) + pairs. + type: object + type: object + size: + description: Foo is an example field of AppService. Edit appservice_types.go + to remove/update + format: int32 + type: integer + required: + - image + - size + type: object + status: + description: AppServiceStatus defines the observed state of AppService + properties: + availableReplicas: + description: Total number of available pods (ready for at least minReadySeconds) + targeted by this deployment. + format: int32 + type: integer + collisionCount: + description: Count of hash collisions for the Deployment. The Deployment + controller uses this field as a collision avoidance mechanism when + it needs to create the name for the newest ReplicaSet. + format: int32 + type: integer + conditions: + description: Represents the latest available observations of a deployment's + current state. + items: + description: DeploymentCondition describes the state of a deployment + at a certain point. + properties: + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + lastUpdateTime: + description: The last time this condition was updated. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of deployment condition. + type: string + required: + - status + - type + type: object + type: array + observedGeneration: + description: The generation observed by the deployment controller. + format: int64 + type: integer + readyReplicas: + description: readyReplicas is the number of pods targeted by this + Deployment with a Ready Condition. + format: int32 + type: integer + replicas: + description: Total number of non-terminated pods targeted by this + deployment (their labels match the selector). + format: int32 + type: integer + unavailableReplicas: + description: Total number of unavailable pods targeted by this deployment. + This is the total number of pods that are still required for the + deployment to have 100% available capacity. They may either be pods + that are running but not yet available or pods that still have not + been created. + format: int32 + type: integer + updatedReplicas: + description: Total number of non-terminated pods targeted by this + deployment that have the desired template spec. + format: int32 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 0000000..6954d02 --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - app.treesir.pub.treesir.pub + resources: + - appservices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - app.treesir.pub.treesir.pub + resources: + - appservices/finalizers + verbs: + - update +- apiGroups: + - app.treesir.pub.treesir.pub + resources: + - appservices/status + verbs: + - get + - patch + - update diff --git a/controllers/appservice_controller.go b/controllers/appservice_controller.go index dfff4ee..77600d4 100644 --- a/controllers/appservice_controller.go +++ b/controllers/appservice_controller.go @@ -19,7 +19,7 @@ package controllers import ( "context" "encoding/json" - appv1alpha1 "git.treesir.pub/cdryzun/operator-demo/api/v1alpha1" + "fmt" "reflect" appsv1 "k8s.io/api/apps/v1" @@ -27,11 +27,12 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" + + apptreesirpubv1alpha1 "git.treesir.pub/cdryzun/operator-demo/api/v1alpha1" ) // AppServiceReconciler reconciles a AppService object @@ -58,7 +59,7 @@ func (r *AppServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) reqLogger.Info("Reconciling AppService") // Fetch the AppService instance - instance := &appv1alpha1.AppService{} + instance := &apptreesirpubv1alpha1.AppService{} err := r.Get(context.TODO(), req.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { @@ -81,6 +82,7 @@ func (r *AppServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) // 如果不需要更新,则正常返回 deploy := &appsv1.Deployment{} + fmt.Println(deploy) if err := r.Get(context.TODO(), req.NamespacedName, deploy); err != nil && errors.IsNotFound(err) { // 创建关联资源 // 1. 创建 Deploy @@ -107,7 +109,7 @@ func (r *AppServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } - oldspec := appv1alpha1.AppServiceSpec{} + oldspec := apptreesirpubv1alpha1.AppServiceSpec{} if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldspec); err != nil { return ctrl.Result{}, err } @@ -147,86 +149,71 @@ func (r *AppServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func NewDeploy(app *appv1alpha1.AppService) *appsv1.Deployment { - labels := map[string]string{"app": app.Name} - selector := &metav1.LabelSelector{MatchLabels: labels} +func NewDeploy(instance *apptreesirpubv1alpha1.AppService) *appsv1.Deployment { + labels := map[string]string{ + "app": instance.Name, + } return &appsv1.Deployment{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "apps/v1", - Kind: "Deployment", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: app.Name, - Namespace: app.Namespace, - - OwnerReferences: []metav1.OwnerReference{ - *metav1.NewControllerRef(app, schema.GroupVersionKind{ - Group: appsv1.SchemeGroupVersion.Group, - Version: appsv1.SchemeGroupVersion.Version, - Kind: "AppService", - }), - }, + ObjectMeta: ctrl.ObjectMeta{ + Name: instance.Name, + Namespace: instance.Namespace, + Labels: labels, }, Spec: appsv1.DeploymentSpec{ - Replicas: app.Spec.Size, + Replicas: instance.Spec.Size, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ + ObjectMeta: ctrl.ObjectMeta{ Labels: labels, }, Spec: corev1.PodSpec{ - Containers: newContainers(app), + Containers: newContainers(instance), }, }, - Selector: selector, }, } } -func newContainers(app *appv1alpha1.AppService) []corev1.Container { +func NewService(instance *apptreesirpubv1alpha1.AppService) *corev1.Service { + labels := map[string]string{ + "app": instance.Name, + } + return &corev1.Service{ + ObjectMeta: ctrl.ObjectMeta{ + Name: instance.Name, + Namespace: instance.Namespace, + Labels: labels, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: instance.Spec.Ports, + Selector: map[string]string{ + "app": instance.Name, + }, + }, + } +} + +func newContainers(instance *apptreesirpubv1alpha1.AppService) []corev1.Container { var containerPorts []corev1.ContainerPort - for _, svcPort := range app.Spec.Ports { + for _, svcPort := range instance.Spec.Ports { cport := corev1.ContainerPort{} cport.ContainerPort = svcPort.TargetPort.IntVal containerPorts = append(containerPorts, cport) } return []corev1.Container{ { - Name: app.Name, - Image: app.Spec.Image, + Name: instance.Name, + Image: instance.Spec.Image, Resources: corev1.ResourceRequirements{ - Requests: app.Spec.Resources.Requests, - Limits: app.Spec.Resources.Limits, + Requests: instance.Spec.Resources.Requests, + Limits: instance.Spec.Resources.Limits, }, Ports: containerPorts, ImagePullPolicy: corev1.PullIfNotPresent, - Env: app.Spec.Envs, - }, - } -} - -func NewService(app *appv1alpha1.AppService) *corev1.Service { - return &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: "v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: app.Name, - Namespace: app.Namespace, - OwnerReferences: []metav1.OwnerReference{ - *metav1.NewControllerRef(app, schema.GroupVersionKind{ - Group: appsv1.SchemeGroupVersion.Group, - Version: appsv1.SchemeGroupVersion.Version, - Kind: "AppService", - }), - }, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeNodePort, - Ports: app.Spec.Ports, - Selector: map[string]string{ - "app": app.Name, - }, + Env: instance.Spec.Envs, }, } }