This commit is contained in:
cdryzun 2023-09-05 15:44:53 +08:00
parent d644296a54
commit 28149b86a6
7 changed files with 433 additions and 67 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
# Ignore build and test binaries.
bin/
testbin/

26
.gitignore vendored Normal file
View File

@ -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
*~

View File

@ -33,7 +33,7 @@ type AppServiceSpec struct {
// 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"`
Resources *Resources `json:"resource,omitempty"`
Envs []corev1.EnvVar `json:"envs,omitempty"`
Ports []corev1.ServicePort `json:"ports,omitempty"`
}

View File

@ -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)
}

View File

@ -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[''<KEY>'']`, `metadata.annotations[''<KEY>'']`,
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: {}

33
config/rbac/role.yaml Normal file
View File

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

View File

@ -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,
},
}
}