前文提要
本篇開始開發(fā)一個有實際作用的operator戚哎,該operator名為elasticweb,既彈性web服務嫂用,本篇參考網(wǎng)絡的文章進行匯總實踐型凳;
這將是一次完整的operator開發(fā)實戰(zhàn),設計嘱函、編碼甘畅、部署等環(huán)節(jié)都會涉及到每一個產(chǎn)品的細節(jié)和具體實現(xiàn),elasticweb從CRD設計再到controller功能都有明確的業(yè)務含義,能執(zhí)行業(yè)務邏輯疏唾。
需求背景
設計架構(gòu)
背景:做過網(wǎng)站開發(fā)的同學對橫向擴容應該都了解蓄氧,簡單的說,假設一個tomcat的QPS上限為500槐脏,如果外部訪問的QPS達到了600喉童,為了保障整個網(wǎng)站服務質(zhì)量,必須再啟動一個同樣的tomcat來共同分攤請求顿天,如下圖所示(簡單起見堂氯,假設咱們的后臺服務是無狀態(tài)的,也就是說不依賴宿主機的IP牌废、本地磁盤之類):
QPS:Queries-per-second咽白,既每秒查詢率,就是說服務器在一秒的時間內(nèi)處理了多少個請求鸟缕;
以上是橫向擴容常規(guī)做法晶框,在kubernetes環(huán)境,如果外部請求超過了單個pod的處理極限懂从,我們可以增加pod數(shù)量來達到橫向擴容的目的授段,如下圖:
需求說明
場景描述:
1 開發(fā)人員需要將 springboot 應用部署到 kubernetes 上,目前的現(xiàn)狀和面臨的問題如下:
2 springboot應用已做成docker鏡像莫绣;
3 通過壓測得出單個pod的QPS為500;
4 估算得出上線后的總QPS會在800左右悠鞍;
5 隨著運營策略變化对室,QPS還會有調(diào)整;
總的來說咖祭,開發(fā)人員目前只有三個數(shù)據(jù):docker鏡像掩宜、單個pod的QPS、總QPS么翰,需要有個方案將服務部署好牺汤,并且在運行期間能支撐外部的高并發(fā)訪問;
需求匯總:
1 需要運維開發(fā)同學設計一個operator(名為elasticweb)浩嫌,將從用戶那里獲取的三個參數(shù)(docker鏡像檐迟、單個pod的QPS、總QPS)告訴elasticweb就完事兒了码耐,從而實現(xiàn)調(diào)度追迟;
2 elasticweb在kubernetes創(chuàng)建pod,至于pod數(shù)量當然是自動算出來的骚腥,要確保能滿足QPS要求敦间,以前面的情況為例,需要兩個pod才能滿足800的QPS;
3 單個pod的QPS和總QPS都隨時可能變化廓块,一旦有變厢绝,elasticweb也要自動調(diào)整pod數(shù)量,以確保服務質(zhì)量带猴;
4 為了確保服務可以被外部調(diào)用昔汉,同時創(chuàng)建好service;
需求設計
CRD設計之Spec部分
Spec是用來保存用戶的期望值的浓利,也就是產(chǎn)品方提供的三個參數(shù)(docker鏡像挤庇、單個pod的QPS、總QPS)贷掖,再加上端口號:
1 image:業(yè)務服務對應的鏡像
2 port:service占用的宿主機端口嫡秕,外部請求通過此端口訪問pod的服務
3 singlePodQPS:單個pod的QPS上限
4 totalQPS:當前整個業(yè)務的總QPS
對應用開發(fā)人員來說,輸入這四個參數(shù)就可以了苹威;
CRD設計之Status部分
Status用來保存實際值昆咽,這里設計成只有一個字段realQPS,表示當前整個operator實際能支持的QPS牙甫,這樣無論何時掷酗,只要開發(fā)人員用kubectl describe命令就能知道當前系統(tǒng)實際上能支持多少Q(mào)PS;
CRD源碼分析
吃透如下代碼:
package v1
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strconv"
)
// 期望狀態(tài)
type ElasticWebSpec struct {
// 業(yè)務服務對應的鏡像窟哺,包括名稱:tag
Image string `json:"image"`
// service占用的宿主機端口泻轰,外部請求通過此端口訪問pod的服務
Port *int32 `json:"port"`
// 單個pod的QPS上限
SinglePodQPS *int32 `json:"singlePodQPS"`
// 當前整個業(yè)務的總QPS
TotalQPS *int32 `json:"totalQPS"`
}
// 實際狀態(tài),該數(shù)據(jù)結(jié)構(gòu)中的值都是業(yè)務代碼計算出來的
type ElasticWebStatus struct {
// 當前kubernetes中實際支持的總QPS
RealQPS *int32 `json:"realQPS"`
}
// +kubebuilder:object:root=true
// ElasticWeb is the Schema for the elasticwebs API
type ElasticWeb struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ElasticWebSpec `json:"spec,omitempty"`
Status ElasticWebStatus `json:"status,omitempty"`
}
func (in *ElasticWeb) String() string {
var realQPS string
if nil == in.Status.RealQPS {
realQPS = "nil"
} else {
realQPS = strconv.Itoa(int(*(in.Status.RealQPS)))
}
return fmt.Sprintf("Image [%s], Port [%d], SinglePodQPS [%d], TotalQPS [%d], RealQPS [%s]",
in.Spec.Image,
*(in.Spec.Port),
*(in.Spec.SinglePodQPS),
*(in.Spec.TotalQPS),
realQPS)
}
// +kubebuilder:object:root=true
// ElasticWebList contains a list of ElasticWeb
type ElasticWebList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ElasticWeb `json:"items"`
}
func init() {
SchemeBuilder.Register(&ElasticWeb{}, &ElasticWebList{})
}
業(yè)務邏輯設計
CRD的完成代表核心數(shù)據(jù)結(jié)構(gòu)已經(jīng)確定且轨,接下來是業(yè)務邏輯的設計浮声,主要是理清楚controller的Reconcile方法里面做些啥,其實核心邏輯還是非常簡單的:算出需要多少個pod旋奢,然后通過更新deployment讓pod數(shù)量達到要求泳挥,在此核心的基礎上再把創(chuàng)建deployment和service、更新status這些瑣碎的事情做好至朗,就完事兒了屉符;
業(yè)務邏輯的流程圖給出來如下所示:
接下來,將會基于上述架構(gòu)圖完成需求任務的開發(fā)工作锹引,并且基于代碼進行部署測驗工作矗钟;
operator編碼
項目elasticweb
新建名為elasticweb的文件夾,在里面執(zhí)行以下命令即可創(chuàng)建名為elasticweb的項目嫌变,domain為com.vpc123:
mkdir elasticweb
go mod init elasticweb
kubebuilder init --domain com.vpc123
創(chuàng)建CRD資源真仲,執(zhí)行以下命令即可創(chuàng)建相關資源:
kubebuilder create api \
--group elasticweb \
--version v1 \
--kind ElasticWeb
通過goland打開項目工程:
CRD編碼
打開文件api/v1/elasticweb_types.go,做以下幾步改動:
- 修改數(shù)據(jù)結(jié)構(gòu)ElasticWebSpec初澎,增加前文設計的四個字段秸应;
- 修改數(shù)據(jù)結(jié)構(gòu)ElasticWebStatus虑凛,增加前文設計的一個字段;
- 增加String方法软啼,這樣打印日志時方便我們查看桑谍,注意RealQPS字段是指針,因此可能為空祸挪,需要判空锣披;
完整的elasticweb_types.go如下所示:
package v1
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strconv"
)
// 期望狀態(tài)
type ElasticWebSpec struct {
// 業(yè)務服務對應的鏡像,包括名稱:tag
Image string `json:"image"`
// service占用的宿主機端口贿条,外部請求通過此端口訪問pod的服務
Port *int32 `json:"port"`
// 單個pod的QPS上限
SinglePodQPS *int32 `json:"singlePodQPS"`
// 當前整個業(yè)務的總QPS
TotalQPS *int32 `json:"totalQPS"`
}
// 實際狀態(tài)雹仿,該數(shù)據(jù)結(jié)構(gòu)中的值都是業(yè)務代碼計算出來的
type ElasticWebStatus struct {
// 當前kubernetes中實際支持的總QPS
RealQPS *int32 `json:"realQPS"`
}
// +kubebuilder:object:root=true
// ElasticWeb is the Schema for the elasticwebs API
type ElasticWeb struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ElasticWebSpec `json:"spec,omitempty"`
Status ElasticWebStatus `json:"status,omitempty"`
}
func (in *ElasticWeb) String() string {
var realQPS string
if nil == in.Status.RealQPS {
realQPS = "nil"
} else {
realQPS = strconv.Itoa(int(*(in.Status.RealQPS)))
}
return fmt.Sprintf("Image [%s], Port [%d], SinglePodQPS [%d], TotalQPS [%d], RealQPS [%s]",
in.Spec.Image,
*(in.Spec.Port),
*(in.Spec.SinglePodQPS),
*(in.Spec.TotalQPS),
realQPS)
}
// +kubebuilder:object:root=true
// ElasticWebList contains a list of ElasticWeb
type ElasticWebList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ElasticWeb `json:"items"`
}
func init() {
SchemeBuilder.Register(&ElasticWeb{}, &ElasticWebList{})
}
在elasticweb目錄下執(zhí)行make install即可部署CRD到kubernetes:
cd $GOPATH/src/elasticweb
[root@master elasticweb]# make install
which: no controller-gen in (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/go/bin:/usr/local/kubebuilder/bin:/root/bin)
go: creating new go.mod: module tmp
go: found sigs.k8s.io/controller-tools/cmd/controller-gen in sigs.k8s.io/controller-tools v0.2.5
/home/gowork/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/elasticwebs.elasticweb.com.vpc123 configured
部署成功后,用api-versions命令可以查到該GV:
kubectl api-versions | grep elasticweb
添加資源訪問權限
elasticweb會對service整以、deployment這兩種資源做查詢胧辽、新增、修改等操作公黑,因此需要這些資源的操作權限邑商,增加下圖紅框中的兩行注釋,這樣代碼生成工具就會在RBAC配置中增加對應的權限(更改 elasticweb_controller.go 文件內(nèi)容):
// +kubebuilder:rbac:groups=elasticweb.com.vpc123,resources=elasticwebs,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=elasticweb.com.vpc123,resources=elasticwebs/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=services,verbs=get;list;watch;create;update;patch;delete
func (r *ElasticWebReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
_ = context.Background()
_ = r.Log.WithValues("elasticweb", req.NamespacedName)
// your logic here
return ctrl.Result{}, nil
}
常量定義
先把常量準備好凡蚜,可見每個pod使用的CPU和內(nèi)存都是在此固定的人断,您也可以改成在Spec中定義,這樣就可以從外部傳入了朝蜘,另外這里為每個pod只分配了0.1個CPU恶迈,可以酌情調(diào)整該值:
const (
// deployment中的APP標簽名
APP_NAME = "elastic-app"
// tomcat容器的端口號
CONTAINER_PORT = 8080
// 單個POD的CPU資源申請
CPU_REQUEST = "100m"
// 單個POD的CPU資源上限
CPU_LIMIT = "100m"
// 單個POD的內(nèi)存資源申請
MEM_REQUEST = "512Mi"
// 單個POD的內(nèi)存資源上限
MEM_LIMIT = "512Mi"
)
方法getExpectReplicas
有個很重要的邏輯:根據(jù)單個pod的QPS和總QPS,計算需要多少個pod谱醇,咱們將這個邏輯封裝到一個方法中以便使用:
// 根據(jù)單個QPS和總QPS計算pod數(shù)量
func getExpectReplicas(elasticWeb *elasticwebv1.ElasticWeb) int32 {
// 單個pod的QPS
singlePodQPS := *(elasticWeb.Spec.SinglePodQPS)
// 期望的總QPS
totalQPS := *(elasticWeb.Spec.TotalQPS)
// Replicas就是要創(chuàng)建的副本數(shù)
replicas := totalQPS / singlePodQPS
if totalQPS%singlePodQPS > 0 {
replicas++
}
return replicas
}
方法createServiceIfNotExists
- 將創(chuàng)建service的操作封裝到一個方法中暇仲,是的主干代碼的邏輯更清晰,可讀性更強枣抱;
- 創(chuàng)建service的時候熔吗,有幾處要注意:
1 先查看service是否存在辆床,不存在才創(chuàng)建佳晶;
2 將service和CRD實例elasticWeb建立關聯(lián)(controllerutil.SetControllerReference方法),這樣當elasticWeb被刪除的時候讼载,service會被自動刪除而無需我們干預轿秧;
3 創(chuàng)建service的時候用到了client-go工具;
創(chuàng)建service的完整方法如下:
// 新建service
func createServiceIfNotExists(ctx context.Context, r *ElasticWebReconciler, elasticWeb *elasticwebv1.ElasticWeb, req ctrl.Request) error {
log := r.Log.WithValues("func", "createService")
service := &corev1.Service{}
err := r.Get(ctx, req.NamespacedName, service)
// 如果查詢結(jié)果沒有錯誤咨堤,證明service正常菇篡,就不做任何操作
if err == nil {
log.Info("service exists")
return nil
}
// 如果錯誤不是NotFound,就返回錯誤
if !errors.IsNotFound(err) {
log.Error(err, "query service error")
return err
}
// 實例化一個數(shù)據(jù)結(jié)構(gòu)
service = &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: elasticWeb.Namespace,
Name: elasticWeb.Name,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{{
Name: "http",
Port: 8080,
NodePort: *elasticWeb.Spec.Port,
},
},
Selector: map[string]string{
"app": APP_NAME,
},
Type: corev1.ServiceTypeNodePort,
},
}
// 這一步非常關鍵一喘!
// 建立關聯(lián)后驱还,刪除elasticweb資源時就會將deployment也刪除掉
log.Info("set reference")
if err := controllerutil.SetControllerReference(elasticWeb, service, r.Scheme); err != nil {
log.Error(err, "SetControllerReference error")
return err
}
// 創(chuàng)建service
log.Info("start create service")
if err := r.Create(ctx, service); err != nil {
log.Error(err, "create service error")
return err
}
log.Info("create service success")
return nil
}
方法createDeployment
- 將創(chuàng)建deployment的操作封裝在一個方法中嗜暴,同樣是為了將主干邏輯保持簡潔;
- 創(chuàng)建deployment的方法也有幾處要注意:
1 調(diào)用getExpectReplicas方法得到要創(chuàng)建的pod的數(shù)量议蟆,該數(shù)量是創(chuàng)建deployment時的一個重要參數(shù)闷沥;
2 每個pod所需的CPU和內(nèi)存資源也是deployment的參數(shù);
3 將deployment和elasticweb建立關聯(lián)咐容,這樣刪除elasticweb的時候deplyment就會被自動刪除了舆逃;
4 同樣是使用client-go客戶端工具創(chuàng)建deployment資源;
// 新建deployment
func createDeployment(ctx context.Context, r *ElasticWebReconciler, elasticWeb *elasticwebv1.ElasticWeb) error {
log := r.Log.WithValues("func", "createDeployment")
// 計算期望的pod數(shù)量
expectReplicas := getExpectReplicas(elasticWeb)
log.Info(fmt.Sprintf("expectReplicas [%d]", expectReplicas))
// 實例化一個數(shù)據(jù)結(jié)構(gòu)
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Namespace: elasticWeb.Namespace,
Name: elasticWeb.Name,
},
Spec: appsv1.DeploymentSpec{
// 副本數(shù)是計算出來的
Replicas: pointer.Int32Ptr(expectReplicas),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": APP_NAME,
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": APP_NAME,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: APP_NAME,
// 用指定的鏡像
Image: elasticWeb.Spec.Image,
ImagePullPolicy: "IfNotPresent",
Ports: []corev1.ContainerPort{
{
Name: "http",
Protocol: corev1.ProtocolSCTP,
ContainerPort: CONTAINER_PORT,
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
"cpu": resource.MustParse(CPU_REQUEST),
"memory": resource.MustParse(MEM_REQUEST),
},
Limits: corev1.ResourceList{
"cpu": resource.MustParse(CPU_LIMIT),
"memory": resource.MustParse(MEM_LIMIT),
},
},
},
},
},
},
},
}
// 這一步非常關鍵戳粒!
// 建立關聯(lián)后路狮,刪除elasticweb資源時就會將deployment也刪除掉
log.Info("set reference")
if err := controllerutil.SetControllerReference(elasticWeb, deployment, r.Scheme); err != nil {
log.Error(err, "SetControllerReference error")
return err
}
// 創(chuàng)建deployment
log.Info("start create deployment")
if err := r.Create(ctx, deployment); err != nil {
log.Error(err, "create deployment error")
return err
}
log.Info("create deployment success")
return nil
}
方法updateStatus
不論是創(chuàng)建deployment資源對象,還是對已有的deployment的pod數(shù)量做調(diào)整蔚约,這些操作完成后都要去修改Status奄妨,既實際的狀態(tài),這樣外部才能隨時隨地知道當前elasticweb支持多大的QPS炊琉,因此需要將修改Status的操作封裝到一個方法中展蒂,給多個場景使用,Status的計算邏輯很簡單:pod數(shù)量乘以每個pod的QPS就是總QPS了苔咪,代碼如下:
// 完成了pod的處理后锰悼,更新最新狀態(tài)
func updateStatus(ctx context.Context, r *ElasticWebReconciler, elasticWeb *elasticwebv1.ElasticWeb) error {
log := r.Log.WithValues("func", "updateStatus")
// 單個pod的QPS
singlePodQPS := *(elasticWeb.Spec.SinglePodQPS)
// pod總數(shù)
replicas := getExpectReplicas(elasticWeb)
// 當pod創(chuàng)建完畢后,當前系統(tǒng)實際的QPS:單個pod的QPS * pod總數(shù)
// 如果該字段還沒有初始化团赏,就先做初始化
if nil == elasticWeb.Status.RealQPS {
elasticWeb.Status.RealQPS = new(int32)
}
*(elasticWeb.Status.RealQPS) = singlePodQPS * replicas
log.Info(fmt.Sprintf("singlePodQPS [%d], replicas [%d], realQPS[%d]", singlePodQPS, replicas, *(elasticWeb.Status.RealQPS)))
if err := r.Update(ctx, elasticWeb); err != nil {
log.Error(err, "update instance error")
return err
}
return nil
}
主干代碼
前面細枝末節(jié)都處理完畢箕般,可以開始主流程了,有了前面的流程圖的賦值舔清,主流程的代碼很容就寫出來了丝里,如下所示,已經(jīng)添加了足夠的注釋体谒,就不再贅述了:
func (r *ElasticWebReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// 會用到context
ctx := context.Background()
log := r.Log.WithValues("elasticweb", req.NamespacedName)
// your logic here
log.Info("1. start reconcile logic")
// 實例化數(shù)據(jù)結(jié)構(gòu)
instance := &elasticwebv1.ElasticWeb{}
// 通過客戶端工具查詢杯聚,查詢條件是
err := r.Get(ctx, req.NamespacedName, instance)
if err != nil {
// 如果沒有實例,就返回空結(jié)果抒痒,這樣外部就不再立即調(diào)用Reconcile方法了
if errors.IsNotFound(err) {
log.Info("2.1. instance not found, maybe removed")
return reconcile.Result{}, nil
}
log.Error(err, "2.2 error")
// 返回錯誤信息給外部
return ctrl.Result{}, err
}
log.Info("3. instance : " + instance.String())
// 查找deployment
deployment := &appsv1.Deployment{}
// 用客戶端工具查詢
err = r.Get(ctx, req.NamespacedName, deployment)
// 查找時發(fā)生異常幌绍,以及查出來沒有結(jié)果的處理邏輯
if err != nil {
// 如果沒有實例就要創(chuàng)建了
if errors.IsNotFound(err) {
log.Info("4. deployment not exists")
// 如果對QPS沒有需求,此時又沒有deployment故响,就啥事都不做了
if *(instance.Spec.TotalQPS) < 1 {
log.Info("5.1 not need deployment")
// 返回
return ctrl.Result{}, nil
}
// 先要創(chuàng)建service
if err = createServiceIfNotExists(ctx, r, instance, req); err != nil {
log.Error(err, "5.2 error")
// 返回錯誤信息給外部
return ctrl.Result{}, err
}
// 立即創(chuàng)建deployment
if err = createDeployment(ctx, r, instance); err != nil {
log.Error(err, "5.3 error")
// 返回錯誤信息給外部
return ctrl.Result{}, err
}
// 如果創(chuàng)建成功就更新狀態(tài)
if err = updateStatus(ctx, r, instance); err != nil {
log.Error(err, "5.4. error")
// 返回錯誤信息給外部
return ctrl.Result{}, err
}
// 創(chuàng)建成功就可以返回了
return ctrl.Result{}, nil
} else {
log.Error(err, "7. error")
// 返回錯誤信息給外部
return ctrl.Result{}, err
}
}
// 如果查到了deployment傀广,并且沒有返回錯誤,就走下面的邏輯
// 根據(jù)單QPS和總QPS計算期望的副本數(shù)
expectReplicas := getExpectReplicas(instance)
// 當前deployment的期望副本數(shù)
realReplicas := *deployment.Spec.Replicas
log.Info(fmt.Sprintf("9. expectReplicas [%d], realReplicas [%d]", expectReplicas, realReplicas))
// 如果相等彩届,就直接返回了
if expectReplicas == realReplicas {
log.Info("10. return now")
return ctrl.Result{}, nil
}
// 如果不等伪冰,就要調(diào)整
*(deployment.Spec.Replicas) = expectReplicas
log.Info("11. update deployment's Replicas")
// 通過客戶端更新deployment
if err = r.Update(ctx, deployment); err != nil {
log.Error(err, "12. update deployment replicas error")
// 返回錯誤信息給外部
return ctrl.Result{}, err
}
log.Info("13. update status")
// 如果更新deployment的Replicas成功,就更新狀態(tài)
if err = updateStatus(ctx, r, instance); err != nil {
log.Error(err, "14. update status error")
// 返回錯誤信息給外部
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
至此樟蠕,整個elasticweb operator編碼就完成了贮聂,接下來部署靠柑、運行、鏡像制作等操作吓懈;
構(gòu)建部署運行
部署CRD
從控制臺進入Makefile所在目錄病往,執(zhí)行命令make install,即可將CRD部署到kubernetes:
[root@master elasticweb]# make install
which: no controller-gen in (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/go/bin:/usr/local/kubebuilder/bin:/root/bin)
go: creating new go.mod: module tmp
go: found sigs.k8s.io/controller-tools/cmd/controller-gen in sigs.k8s.io/controller-tools v0.2.5
/home/gowork/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/elasticwebs.elasticweb.com.vpc123 configured
從上述內(nèi)容可見骄瓣,實際上執(zhí)行的操作是用kustomize將config/crd下的yaml資源合并后在kubernetes進行創(chuàng)建,
可以用命令kubectl api-versions驗證CRD部署是否成功:
[root@master elasticweb]# kubectl api-versions|grep elasticweb
elasticweb.com.vpc123/v1
運行Controller
進入Makefile文件所在目錄停巷,執(zhí)行命令make run即可編譯運行controller:
新建elasticweb資源對象
- 負責處理elasticweb的Controller已經(jīng)運行起來了,接下來就開始創(chuàng)建elasticweb資源對象吧榕栏,用yaml文件來創(chuàng)建畔勤;
- 在config/samples目錄下,kubebuilder為咱們創(chuàng)建了demo文件elasticweb_v1_elasticweb.yaml扒磁,不過這里面spec的內(nèi)容不是咱們定義的那四個字段庆揪,需要改成以下內(nèi)容:
apiVersion: v1
kind: Namespace
metadata:
name: dev
labels:
name: dev
---
apiVersion: elasticweb.com.bolingcavalry/v1
kind: ElasticWeb
metadata:
namespace: dev
name: elasticweb-sample
spec:
# Add fields here
image: tomcat:8.0.18-jre8
port: 30003
singlePodQPS: 500
totalQPS: 600
對上述配置的幾個參數(shù)做如下說明:
1 使用的namespace為dev
2 本次測試部署的應用為tomcat
3 service使用宿主機的30003端口暴露tomcat的服務
4 假設單個pod能支撐500QPS,外部請求的QPS為600
執(zhí)行命令kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml妨托,即可在kubernetes創(chuàng)建elasticweb實例:
[root@master elasticweb]# kubectl apply -f config/samples/elasticweb_v1_elasticweb.yaml
namespace/dev created
elasticweb.elasticweb.com.vpc123/elasticweb-sample created
去controller的窗口發(fā)現(xiàn)打印了不少日志缸榛,通過分析日志發(fā)現(xiàn)Reconcile方法執(zhí)行了兩次,第一執(zhí)行時創(chuàng)建了deployment和service等資源:
瀏覽器驗證業(yè)務功能
本次部署操作使用的docker鏡像是tomcat兰伤,驗證起來非常簡單内颗,打開默認頁面能見到貓就證明tomcat啟動成功了,我這kubernetes宿主機的IP地址是192.168.xx.xx敦腔,于是用瀏覽器訪問http://192.168.xx.xx:30003均澳,如下圖,業(yè)務功能正常:
[root@master ~]# kubectl get elasticweb -n dev
NAME AGE
elasticweb-sample 2d12h
[root@master ~]#
[root@master ~]# kubectl get service -n dev
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
elasticweb-sample NodePort 10.104.224.245 <none> 8080:30003/TCP 2d12h
[root@master ~]#
[root@master ~]# kubectl get deployment -n dev
NAME READY UP-TO-DATE AVAILABLE AGE
elasticweb-sample 3/3 3 3 17h
[root@master ~]#
[root@master ~]# kubectl get pod -n dev
NAME READY STATUS RESTARTS AGE
elasticweb-sample-66947d9687-5wnbb 1/1 Running 0 4h50m
elasticweb-sample-66947d9687-v9464 1/1 Running 0 4h50m
elasticweb-sample-66947d9687-xgrl7 1/1 Running 0 4h50m
拓展閱讀
本篇博客進行的編輯是 controllers 的操作和資源部署操作符衔,實際上 kuberbilder 的二次開發(fā)將會涉及到的問題點主要集中在3方面的問題找前,自定義 api ;controllers 業(yè)務邏輯控制判族;webhook 轉(zhuǎn)碼校驗躺盛;K8s 單元測試等模塊,基本就可以完全掌握 kuberbilder 的整個開發(fā)設計流程形帮;