在工作中经常会有使用k8s
部署站点应用,一般会创建两个编排文件:deployment.yaml
和svc.yaml
,但是有operator
概念之后,其实我们可以自定义一个operator
来帮忙创建deployment
和svc
。
此篇文章的目的就是通过编写一个自定义的website-operator
来实现此功能。只需要提供一个简单的yaml
文件,就可以实现需求。
样例YAML
:
apiVersion: website.xianyuluo.com/v1
kind: Website
metadata:
name: nginx-app
namespace: website-operator-system
spec:
size: 3
image: xianyuluo/nginx:1.12.2.website-operator
port:
- port: 80
targetPort: 80
website-operator
可以根据上面的yaml
文件自动部署Deployment
和SVC
。
使用CoreOS
公司开源的operator-sdk
框架实现。框架的内容可以参考官网,框架其实比较简单,核心的东西就是kubernetes
的golang
客户端,这里就不在赘述了。
自定义operator
的编排文件格式和实例状态
// WebsiteSpec defines the desired state of Website
type WebsiteSpec 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"`
Port []corev1.ServicePort `json:"port,omitempty"`
}
...
// WebsiteStatus defines the observed state of Website
type WebsiteStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
v1beta1.DeploymentStatus `json:",inline"`
}
所有逻辑都在由框架自动生成的Reconcile
方法中,其他的自己任意补充。
// 1、返回container
func newContainers(app *websitev1.Website) []corev1.Container {
return []corev1.Container{
{
Name: app.Name,
Image: app.Spec.Image,
Resources: app.Spec.Resources,
ImagePullPolicy: corev1.PullIfNotPresent,
},
}
}
// 2、创建Deployment
func NewDeploy(app *websitev1.Website) *appsv1beta1.Deployment {
labels := map[string]string{"app": app.Name}
selector := &metav1.LabelSelector{MatchLabels: labels}
return &appsv1beta1.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: metav1.SchemeGroupVersion.Group,
Version: metav1.SchemeGroupVersion.Version,
Kind: "Website",
}),
},
},
Spec: appsv1beta1.DeploymentSpec{
Replicas: app.Spec.Size,
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
Containers: newContainers(app),
},
},
Selector: selector,
},
}
}
// 3、创建Service
func NewService(app *websitev1.Website) *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: metav1.SchemeGroupVersion.Group,
Version: metav1.SchemeGroupVersion.Version,
Kind: "Website",
}),
},
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
Ports: app.Spec.Port,
Selector: map[string]string{
"app": app.Name,
},
},
}
}
//+kubebuilder:rbac:groups=website.xianyuluo.com,resources=websites,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=website.xianyuluo.com,resources=websites/status,verbs=get;update;patch
func (r *WebsiteReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
_ = context.Background()
_ = r.Log.WithValues("website", req.NamespacedName)
// your logic here
// Fetch the website instance
instance := &websitev1.Website{}
fmt.Println("Instance实例内容为:", instance)
err := r.Client.Get(context.TODO(), req.NamespacedName, instance)
if err != nil {
if errors.IsNotFound(err) {
return reconcile.Result{}, nil
}
return reconcile.Result{}, err
}
if instance.DeletionTimestamp != nil {
return reconcile.Result{}, err
}
// 一、如果不存在,则创建关联资源
// 二、如果存在,则判断是否需要更新
// 1、如果需要更新,则直接更新
// 2、如果不需要更新,则正常返回
deploy := &appsv1beta1.Deployment{}
if err := r.Client.Get(context.TODO(), req.NamespacedName, deploy); err != nil && errors.IsNotFound(err) {
// 没有找到相关资源,需要创建
// 1、创建 Deploy
fmt.Println("创建Deployment")
deploy := NewDeploy(instance)
if err := r.Client.Create(context.TODO(), deploy); err != nil {
return reconcile.Result{}, err
}
// 2、创建Service
fmt.Println("创建Services")
service := NewService(instance)
if err := r.Client.Create(context.TODO(), service); err != nil {
return reconcile.Result{}, err
}
// 3、关联 Annotations
fmt.Println("关联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{}, err
}
return reconcile.Result{}, err
}
oldspec := websitev1.WebsiteSpec{}
if err := json.Unmarshal([]byte(instance.Annotations["spec"]), &oldspec); err != nil {
return reconcile.Result{}, err
}
if !reflect.DeepEqual(instance.Spec, oldspec) {
// 更新关联资源
fmt.Println("更新关联资源...")
newDeploy := NewDeploy(instance)
oldDeploy := &appsv1beta1.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 ctrl.Result{}, nil
}
部署成功后,会创建出一个deployment
和一个service
,service
是用于监控应用监控,可以先不管
一、根据最开始的提到的简易yaml
来创建website
实例
test-website.yaml
apiVersion: website.xianyuluo.com/v1
kind: Website
metadata:
name: nginx-app
namespace: website-operator-system
spec:
size: 1
image: xianyuluo/nginx:1.12.2.website-operator
port:
- port: 80
targetPort: 80
二、观察集群中的deployment
和service
deployment
和service
已经由website-operator
帮我们创建出来了,Nice~(svc
默认为LoadBalancer
类型)。访问一下看看!
站点正常,Good!
https://github.com/xianyuLuo/website-operator
https://sdk.operatorframework.io/docs/building-operators/golang/tutorial/
https://www.qikqiak.com/post/k8s-operator-101/
记录的不是很详细,有些知识自己也还在琢磨当中,后面再补充!