Operator3-设计一个operator
Posted saynaihe
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Operator3-设计一个operator相关的知识,希望对你有一定的参考价值。
背景:
前置知识Operator-1初识Operator,Operator-2从pod开始简单operator。
先拿一个个人的工作环境来设计吧,应用有十多个微服务,恩各种类型job deployment statefulset service ingress pv pvc configmap这些资源准备模仿eck:https://github.com/elastic/cloud-on-k8s来设计!
创建一个自己的operator
goland 创建项目
命名规则
关于应用命名一下吧:
就拿月份来作应用名称吧:
应用1:Jan
应用2:feb
应用3:mar
应用4:apr
应用5:may
也没有想好具体的代表什么,下面了边作边看…
kubebuilder init
[zhangpeng@zhangpeng develop-operator]$ kubebuilder init --plugins go/v3 --domain zhangpeng.com --owner "zhang peng"
开启支持多接口组
模仿目录结构:
设置multigroup=true,忘了这是在哪个地方搜到的了,应该是思否一篇文章
[zhangpeng@zhangpeng develop-operator]$ kubebuilder edit --multigroup=true
[zhangpeng@zhangpeng develop-operator]$ kubebuilder create api --group jan --version v1 --kind Jan
[zhangpeng@zhangpeng develop-operator]$ kubebuilder create api --group feb --version v1 --kind Feb
[zhangpeng@zhangpeng develop-operator]$ kubebuilder create api --group mar --version v1 --kind Mar
[zhangpeng@zhangpeng develop-operator]$ kubebuilder create api --group apr --version v1 --kind Apr
[zhangpeng@zhangpeng develop-operator]$ kubebuilder create api --group may --version v1 --kind May
从Jan开始
jan应用为一个deployment应用,参照https://www.qikqiak.com/post/k8s-operator-101/,创建一个deployment并与Operator-2从pod开始简单operator中对比一下pod 与deployment的区别!
注意:以下代码都是抄写自阳明大佬,些许修改…,比如有个& 还有关于service的修改
定义jan_type
apis/jan/v1/jan_type.go
type JanSpec struct
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Size *int32 `json:"size"`
Image string `json:"image"`
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
Envs []corev1.EnvVar `json:"envs,omitempty"`
Ports []corev1.ServicePort `json:"ports,omitempty"`
// JanStatus defines the observed state of Jan
type JanStatus struct
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
appsv1.DeploymentStatus `json:",inline"`
make install
make install 失败,继续拆解命令:
[zhangpeng@zhangpeng develop-operator]$ ./bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
cat config/crd/bases/jan.zhangpeng.com_jans.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.8.0
creationTimestamp: null
name: jans.jan.zhangpeng.com
spec:
group: jan.zhangpeng.com
names:
kind: Jan
listKind: JanList
plural: jans
singular: jan
scope: Namespaced
versions:
- name: v1
schema:
openAPIV3Schema:
description: Jan is the Schema for the jans 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: JanSpec defines the desired state of Jan
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
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
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
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
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 http://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
resources:
description: ResourceRequirements describes the compute resource requirements.
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: 'Limits describes the maximum amount of compute resources
allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
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: 'Requests describes the minimum amount of compute
resources required. If Requests is omitted for a container,
it defaults to Limits if that is explicitly specified, otherwise
to an implementation-defined value. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/'
type: object
type: object
size:
format: int32
maximum: 5
minimum: 1
type: integer
type: object
status:
description: JanStatus defines the observed state of Jan
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:
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
继续发布到集群:
[zhangpeng@zhangpeng develop-operator]$ kustomize build config/crd | kubectl apply -f -
不知道有没有单独发布的办法,一整都一起发布了…
[zhangpeng@zhangpeng develop-operator]$ kubectl describe crd jans.jan.zhangpeng.com
describe crd的内容与config/crd/bases/jan.zhangpeng.com_jans.yaml 内容是一样的,强调一下…
创建deployment pod service的方法
对应文件都偷懒了,直接放在controllers/jan目录下了:
cat jan_helper.go
package jan
import (
janv1 "develop-operator/apis/jan/v1"
appv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func NewJan(app *janv1.Jan) *appv1.Deployment
labels := map[string]string"app": app.Name
selector := &metav1.LabelSelectorMatchLabels: labels
return &appv1.Deployment
TypeMeta: metav1.TypeMeta
Kind: "apps/v1",
APIVersion: "Deployment",
,
ObjectMeta: metav1.ObjectMeta
Name: app.Name,
Namespace: app.Namespace,
OwnerReferences: []metav1.OwnerReference
*metav1.NewControllerRef(app, schema.GroupVersionKind
Group: janv1.GroupVersion.Group,
Version: janv1.GroupVersion.Version,
Kind: "Jan",
),
,
,
Spec: appv1.DeploymentSpec
Replicas: app.Spec.Size,
Selector: selector,
Template: corev1.PodTemplateSpec
ObjectMeta: metav1.ObjectMetaLabels: labels,
Spec: corev1.PodSpecContainers: newContainers(app),
,
MinReadySeconds: 0,
,
func newContainers(app *janv1.Jan) []corev1.Container
containerPorts := []corev1.ContainerPort
for _, svcPort := range app.Spec.Ports
cport := corev1.ContainerPort
cport.ContainerPort = svcPort.TargetPort.IntVal
containerPorts = append(containerPorts, cport)
return []corev1.Container
Name: app.Name,
Image: app.Spec.Image,
Resources: app.Spec.Resources,
Ports: containerPorts,
ImagePullPolicy: corev1.PullIfNotPresent,
Env: app.Spec.Envs,
,
func NewService(app *janv1.Jan) *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: janv1.GroupVersion.Group,
Version: janv1.GroupVersion.Version,
Kind: "Jan",
),
,
,
Spec: corev1.ServiceSpec
Type: corev1.ServiceTypeNodePort,
Ports: app.Spec.Ports,
Selector: map[string]string
"app": app.Name,
,
,
jan_controller.go Reconcile
基本阳明大佬的博客抄来的,Reconcile调谐函数:
/*
Copyright 2022 zhang peng.
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 jan
import (
"context"
"encoding/json"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
janv1 "develop-operator/apis/jan/v1"
)
// JanReconciler reconciles a Jan object
type JanReconciler struct
client.Client
Scheme *runtime.Scheme
//+kubebuilder:rbac:groups=jan.zhangpeng.com,resources=jans,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=jan.zhangpeng.com,resources=jans/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=jan.zhangpeng.com,resources=jans/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 Jan 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/controller-runtime@v0.11.2/pkg/reconcile
func (r *JanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
_ = log.FromContext(ctx)
instance := &janv1.Jan
err := r.Client.Get(context.TODO(), req.NamespacedName, instance)
if err != nil
if errors.IsNotFound(err)
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return reconcile.Result, nil
// Error reading the object - requeue the request.
return reconcile.Result, err
if instance.DeletionTimestamp != nil
return reconcile.Result, err
// 如果不存在,则创建关联资源
// 如果存在,判断是否需要更新
// 如果需要更新,则直接更新
// 如果不需要更新,则正常返回
deploy := &appsv1.Deployment
if err := r.Client.Get(context.TODO(), req.NamespacedName, deploy); err != nil && errors.IsNotFound(err)
// 创建关联资源
// 1. 创建 Deploy
deploy := NewJan(instance)
if err := r.Client.Create(context.TODO(), deploy); err != nil
return reconcile.Result, err
// 2. 创建 Service
service := NewService(instance)
if err := r.Client.Create(context.TODO(), service); err != nil
return reconcile.Result, err
// 3. 关联 Annotations
data, _ := json.Marshal(instance.Spec)
if instance.Annotations != nil
instance.Annotations["spec"] = string(data)
else
instance.Annotations = map[string]string"spec": string(data)
if err := r.Client.Update(context.TODO(), instance); err != nil
return reconcile.Result, nil
return reconcile.Result, nil
oldspec := janv1.JanSpec
if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldspec); err != nil
return reconcile.Result, err
if !reflect.DeepEqual(instance.Spec, oldspec)
// 更新关联资源
newDeploy := NewJan(instance)
oldDeploy := &appsv1.Deployment
if err := r.Client.Get(context.TODO(), req.NamespacedName, oldDeploy); err != nil
return reconcile.Result, err
oldDeploy.Spec = newDeploy.Spec
if err := r.Client.Update(context.TODO(), oldDeploy); err != nil
return reconcile.Result, err
newService := NewService(instance)
oldService := &corev1.Service
if err := r.Client.Get(context.TODO(), req.NamespacedName, oldService); err != nil
return reconcile.Result, err
oldService.Spec = newService.Spec
if err := r.Client.Update(context.TODO(), oldService); err != nil
return reconcile.Result, err
return reconcile.Result, nil
return reconcile.Result, nil
// SetupWithManager sets up the controller with the Manager.
func (r *JanReconciler) SetupWithManager(mgr ctrl.Manager) error
return ctrl.NewControllerManagedBy(mgr).
For(&janv1.Jan).
Complete(r)
强调一下:
json.Unmarshal
https://www.qikqiak.com/post/k8s-operator-101/#%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84是这样写的,but maker run就报错了,看了一下别人有人写了&就加了一下
make run and test
测试yaml就用config/samples/jan_v1_jan.yaml去测试了:
apiVersion: jan.zhangpeng.com/v1
kind: Jan
metadata:
name: jan-sample
spec:
size: 2
image: nginx:1.7.9
ports:
- port: 80
targetPort: 80
nodePort: 30002
[zhangpeng@zhangpeng develop-operator]$ kubectl apply -f config/samples/jan_v1_jan.yaml
jan.jan.zhangpeng.com/jan-sample created
修改副本数为3:
继续改造
修改Service Type
en 对比前一节的pod operator我想输出更多的内容,get Jan也想输出数量:继续改造(还有Jan服务可以输入Type我不喜欢nodePort的方式)
[zhangpeng@zhangpeng develop-operator]$ make install
[zhangpeng@zhangpeng develop-operator]$ ./bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
[zhangpeng@zhangpeng develop-operator]$ kustomize build config/crd | kubectl apply -f -
kubecelt delete -f config/samples/jan_v1_jan.yaml
重新编辑文件如下:
apiVersion: jan.zhangpeng.com/v1
kind: Jan
metadata:
name: jan-sample
spec:
size: 3
image: nginx:1.7.9
ports:
- port: 80
targetPort: 80
// nodePort: 30002
type: ClusterIP
controllers/jan/jan_helper.go NewService修改Type如下:Type: app.Spec.Type,
make run and kubectl apply
[zhangpeng@zhangpeng develop-operator]$ kubectl delete -f config/samples/jan_v1_jan.yaml
jan.jan.zhangpeng.com "jan-sample" deleted
[zhangpeng@zhangpeng develop-operator]$ kubectl apply -f config/samples/jan_v1_jan.yaml
jan.jan.zhangpeng.com/jan-sample created
[zhangpeng@zhangpeng develop-operator]$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jan-sample ClusterIP 10.99.27.123 <none> 80/TCP 1s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d
[zhangpeng@zhangpeng develop-operator]$ kubectl get jan
NAME AGE
jan-sample 5m21s
继续模仿
eck 有更多的输出阿 咱们的输出现在只有AGE…想输出更多
注意:comon我还是没有用到,创建了就创建了吧,后面看看还是否用的到!
人家eck有个公用的comon?咱也搞一个
kubebuilder create api --group common --version v1 --kind Common
继续狗一下
这里懒得写了 网上搜到一个博客:https://qingwave.github.io/how-to-write-a-k8s-operator就按照他写的改一下了:
apis/jan/v1/jan_type.go
/*
Copyright 2022 zhang peng.
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 v1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// JanSpec defines the desired state of Jan
type JanSpec struct
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
//+kubebuilder:default:=1
//+kubebuilder:validation:Minimum:=1
Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"`
Image string `json:"image"`
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
Envs []corev1.EnvVar `json:"envs,omitempty"`
Ports []corev1.ServicePort `json:"ports,omitempty"`
Type corev1.ServiceType `json:"type,omitempty"`
const (
Running = "Running"
Pending = "Pending"
NotReady = "NotReady"
Failed = "Failed"
)
// JanStatus defines the observed state of Jan
type JanStatus struct
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Phase is the phase of guestbook
Phase string `json:"phase,omitempty"`
// replicas is the number of Pods created by the StatefulSet controller.
Replicas int32 `json:"replicas"`
// readyReplicas is the number of Pods created by the StatefulSet controller that have a Ready Condition.
ReadyReplicas int32 `json:"readyReplicas"`
// LabelSelector is label selectors for query over pods that should match the replica count used by HPA.
LabelSelector string `json:"labelSelector,omitempty"`
//+kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.labelSelector
//+kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",description="The phase of game."
//+kubebuilder:printcolumn:name="DESIRED",type="integer",JSONPath=".spec.replicas",description="The desired number of pods."
//+kubebuilder:printcolumn:name="CURRENT",type="integer",JSONPath=".status.replicas",description="The number of currently all pods."
//+kubebuilder:printcolumn:name="READY",type="integer",JSONPath=".status.readyReplicas",description="The number of pods ready."
//+kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp",description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC."
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// Jan is the Schema for the jans API
type Jan struct
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec JanSpec `json:"spec,omitempty"`
Status JanStatus `json:"status,omitempty"`
//+kubebuilder:object:root=true
// JanList contains a list of Jan
type JanList struct
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Jan `json:"items"`
func init()
SchemeBuilder.Register(&Jan, &JanList)
make install
controllers/jan/jan_helper.go
其实我就修改了一下type,我可不想一直写nodeport…一般都司clasterip
package jan
import (
janv1 "develop-operator/apis/jan/v1"
appv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func NewJan(app *janv1.Jan) *appv1.Deployment
labels := map[string]string"app": app.Name
selector := &metav1.LabelSelectorMatchLabels: labels
return &appv1.Deployment
TypeMeta: metav1.TypeMeta
Kind: "apps/v1",
APIVersion: "Deployment",
,
ObjectMeta: metav1.ObjectMeta
Name: app.Name,
Namespace: app.Namespace,
OwnerReferences: []metav1.OwnerReference
*metav1.NewControllerRef(app, schema.GroupVersionKind
Group: janv1.GroupVersion.Group,
Version: janv1.GroupVersion.Version,
Kind: "Jan",
),
,
,
Spec: appv1.DeploymentSpec
Replicas: app.Spec.Replicas,
Selector: selector,
Template: corev1.PodTemplateSpec
ObjectMeta: metav1.ObjectMetaLabels: labels,
Spec: corev1.PodSpecContainers: newContainers(app),
,
MinReadySeconds: 0,
,
Status: appv1.DeploymentStatus,
func newContainers(app *janv1.Jan) []corev1.Container
containerPorts := []corev1.ContainerPort
for _, svcPort := range app.Spec.Ports
cport := corev1.ContainerPort
cport.ContainerPort = svcPort.TargetPort.IntVal
containerPorts = append(containerPorts, cport)
return []corev1.Container
Name: app.Name,
Image: app.Spec.Image,
Resources: app.Spec.Resources,
Ports: containerPorts,
ImagePullPolicy: corev1.PullIfNotPresent,
Env: app.Spec.Envs,
,
func NewService(app *janv1.Jan) *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: janv1.GroupVersion.Group,
Version: janv1.GroupVersion.Version,
Kind: "Jan",
),
,
,
Spec: corev1.ServiceSpec
Type: app.Spec.Type,
Ports: app.Spec.Ports,
Selector: map[string]string
"app": app.Name,
,
,
jan_controller.go
/*
Copyright 2022 zhang peng.
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 jan
import (
"context"
"encoding/json"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"reflect"
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"
janv1 "develop-operator/apis/jan/v1"
)
// JanReconciler reconciles a Jan object
type JanReconciler struct
client.Client
Scheme *runtime.Scheme
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=mar.zhangpeng.com,resources=jan/finalizers,verbs=update
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=networking,resources=ingresses,verbs=get;list;watch;create;update;patch;delete
// 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 Jan 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/controller-runtime@v0.11.2/pkg/reconcile
func (r *JanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
defer utilruntime.HandleCrash()
_ = log.FromContext(ctx)
instance := &janv1.Jan
err := r.Client.Get(context.TODO(), req.NamespacedName, instance)
if err != nil
if errors.IsNotFound(err)
// Request object not found, could have been deleted after reconcile request.
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
// Return and don't requeue
return reconcile.Result, nil
// Error reading the object - requeue the request.
return reconcile.Result, err
if instance.DeletionTimestamp != nil
return reconcile.Result, err
// 如果不存在,则创建关联资源
// 如果存在,判断是否需要更新
// 如果需要更新,则直接更新
// 如果不需要更新,则正常返回
deploy := &appsv1.Deployment
if err := r.Client.Get(context.TODO(), req.NamespacedName, deploy); err != nil && errors.IsNotFound(err)
// 创建关联资源
// 1. 创建 Deploy
deploy := NewJan(instance)
if err := r.Client.Create(context.TODO(), deploy); err != nil
return reconcile.Result, err
// 2. 创建 Service
service := NewService(instance)
if err := r.Client.Create(context.TODO(), service); err != nil
return reconcile.Result, err
// 3. 关联 Annotations
data, _ := json.Marshal(instance.Spec)
if instance.Annotations != nil
instance.Annotations["spec"] = string(data)
else
instance.Annotations = map[string]string"spec": string(data)
if err := r.Client.Update(context.TODO(), instance); err != nil
return reconcile.Result, nil
return reconcile.Result, nil
oldspec := janv1.JanSpec
if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldspec); err != nil
return reconcile.Result, err
if !reflect.DeepEqual(instance.Spec, oldspec)
// 更新关联资源
newDeploy := NewJan(instance)
oldDeploy := &appsv1.Deployment
if err := r.Client.Get(context.TODO(), req.NamespacedName, oldDeploy); err != nil
return reconcile.Result, err
oldDeploy.Spec = newDeploy.Spec
if err := r.Client.Update(context.TODO(), oldDeploy); err != nil
return reconcile.Result, err
newService := NewService(instance)
oldService := &corev1.Service
if err := r.Client.Get(context.TODO(), req.NamespacedName, oldService); err != nil
return reconcile.Result, err
oldService.Spec = newService.Spec
if err := r.Client.Update(context.TODO(), oldService); err != nil
return reconcile.Result, err
return reconcile.Result, nil
newStatus := janv1.JanStatus
Replicas: *instance.Spec.Replicas,
ReadyReplicas: instance.Status.Replicas,
if newStatus.Replicas == newStatus.ReadyReplicas
newStatus.Phase = janv1.Running
else
newStatus.Phase = janv1.NotReady
if !reflect.DeepEqual(instance.Status, newStatus)
instance.Status = newStatus
log.FromContext(ctx).Info("update game status", "name", instance.Name)
err := r.Client.Status().Update(ctx, instance)
return reconcile.Result, err
return reconcile.Result, nil
// SetupWithManager sets up the controller with the Manager.
func (r *JanReconciler) SetupWithManager(mgr ctrl.Manager) error
return ctrl.NewControllerManagedBy(mgr).
For(&janv1.Jan).
Complete(r)
偷懒抄来的:
make run :
清空原来的Jan应用
[zhangpeng@zhangpeng develop-operator]$ kubectl delete jan jan-sample
jan.jan.zhangpeng.com "jan-sample" deleted
apiVersion: jan.zhangpeng.com/v1
kind: Jan
metadata:
name: jan-sample
spec:
replicas: 2
image: nginx:1.7.9
ports:
- port: 80
targetPort: 80
type: ClusterIP
[zhangpeng@zhangpeng develop-operator]$ kubectl apply -f config/samples/jan_v1_jan.yaml
jan.jan.zhangpeng.com/jan-sample created
[zhangpeng@zhangpeng develop-operator]$ kubectl get jan
NAME PHASE DESIRED CURRENT READY AGE
jan-sample Running 2 2 2 1s
Operator3-设计一个operator
Operator3-设计一个operator二-owns的使用
Operator3-设计一个operator二-owns的使用