带你读《云原生应用开发:Operator原理与实践》——1.2.2 Operator 应用案例

简介: 带你读《云原生应用开发:Operator原理与实践》——1.2.2 Operator 应用案例

1.2.2 Operator 应用案例


前面介绍了基于 CR 和相应的自定义资源控制器,我们可以自定义扩展 Kubernetes 原生的模型元素,这样的自定义模型可以加入到原生 Kubernetes API 管理;同时 Operator开发者可以像使用原生 API 进行应用管理一样,通过声明式的方式定义一组业务应用的期状态,并且根据业务应用的自身特点编写相应控制器逻辑,以此完成对应用运行时刻生命周期的管理,并持续维护与期望状态的一致性。

在本节我们将介绍如何使用 Kubebuilder 工具,快速构建一个 Kubernetes Operator,通过创建 CRD 完成一个简单的 Web 应用部署,通过编写控制器相关业务逻辑,完成对CRD 的自动化管理。

1. Kubebuilder 介绍

Kubebuilder 是一个用 Go 语言构建 Kubernetes API 控制器和 CRD 的脚手架工具,通过使用 Kubebuilder,用户可以遵循一套简单的编程框架,编写 Operator 应用实例。

(1)依赖条件

① go version v1.13+。

② docker version 17.03+。

③ kubectl version v1.11.3+。

④ Access to a Kubernetes v1.11.3+ cluster。

(2)安装 Kubebuilder

在安装 Kubebuilder 前,首先需要安装 Go 语言环境,针对不同的操作系统,安装方法可参考 Go 语言官方文档。

安装完成后,我们通过如下命令验证是否安装完成:

$ go version

查看命令是否正确回显当前安装的 Go 语言版本,输入如下命令查看 Go 语言环境变量:

$ go env

我们可以看到 GOOS 及 GOARCH 等常用环境变量配置 ,然后安装 Kubebuilder,具体 shell 命令见代码清单 1-2。

代码清单 1-2

os=$(go env GOOS)
arch=$(go env GOARCH)
# download kubebuilder and extract it to tmp
curl -L https://go.kubebuilder.io/dl/2.3.1/${os}/${arch} | tar -xz -C /tmp/
# move to a long-term location and put it on your path
# (you'll need to set the KUBEBUILDER_ASSETS env var if you put it somewhere else)
sudo mv /tmp/kubebuilder_2.3.1_${os}_${arch} /usr/local/kubebuilder
export PATH=$PATH:/usr/local/kubebuilder/bin

执行完成后,输入如下命令检查是否正确:

$ kubebuilder version

至此 Kubebuilder 已安装完成,使用 kubebuilder -h 可以查看帮助文档。

2. Welcome 案例介绍

Welcome 案例主要实现使用 Operator 和 CRD 部署一套完整的应用环境,可以实现根据自定义类型创建资源,通过创建一个 Welcome 类型的资源,后台自动创建 Deployment和 Service,通过 Web 页面访问 Service 呈现应用部署,通过自定义控制器方式进行控制管理,整体流程如图 1-10 所示。

image.png

图 1-10 案例应用交互流程

本案例中,我们需要创建 Welcome 自定义资源及对应的 Controllers,最终我们可以通过类似代码清单 1-3 的 Yaml 文件部署简单的 Web 应用。

代码清单 1-3

apiVersion: webapp.demo.welcome.domain/v1
kind: Welcome
metadata:
 name: welcome-sample
spec:
 name: myfriends

(1) Web 应用介绍

本案例中,我们使用 Go 语言 http 模块创建了一个 Web 服务,用户访问页面后会自动加载 NAME 及 PORT 环境变量并渲染 index.html 静态文件,代码逻辑见代码清单 1-4。

代码清单 1-4

func main() {
 name := os.Getenv("NAME")
 hello := fmt.Sprintf("Hello %s ", name)
 http.Handle("/hello/", http.StripPrefifix("/hello/", http.FileServer(http.
Dir("static"))))
 f, err := os.OpenFile("./static/index.html", os.O_APPEND|os.O_
WRONLY|os.O_CREATE, 0600)
 if err != nil {
 panic(err)
 }
 defer f.Close()
 if _, err = f.WriteString(hello); err != nil {
 panic(err)
 }
 port := os.Getenv("PORT")
 if port == "" {
 port = "8080"
 }
}

其中,NAME 环境变量通过我们在 Welcome 中定义的 name 字段获取,我们在下面的控制器编写中会详细介绍获取字段的详细方法。我们将 index.html 放在 Static 文件夹下,并将工程文件打包为 Docker 镜像,Dockerfile 见代码清单 1-5。

代码清单 1-5

FROM golang:1.12 as builder
# Copy local code to the container image.
WORKDIR /
COPY . .
COPY static
# Build the command inside the container.
RUN CGO_ENABLED=0 GOOS=linux go build -v -o main
# Use a Docker multi-stage build to create a lean production image.
FROM alpine
RUN apk add --no-cache ca-certifificates
# Copy the binary to the production image from the builder stage.
COPY --from=builder /main /usr/local/main
COPY --from=builder static /static
# Run the web service on container startup.
CMD ["/usr/local/main"]

本案例中 Docker 镜像文件已上传至 dockerhub,可以通过 docker pull sdfcdwefe/welcome demo:v1 进行下载。

(2)项目初始化

接下来,我们使用代码清单 1-6 中的 Kubebuilder 命令进行项目初始化工作。

代码清单 1-6

$ mkdir demo
$ cd demo
$ go mod init welcome_demo.domain
$ kubebuilder init --domain demo.welcome.domain

初始化项目后,Kubebuilder 会自动生成 main.go 文件等一系列配置和代码框架(见代码清单 1-7)。

代码清单 1-7

.
├── bin
│ └── manager
├── confifig
│ ├── certmanager
│ │ ├── certifificate.yaml
│ │ ├── kustomization.yaml
│ │ └── kustomizeconfifig.yaml
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ ├── manager_webhook_patch.yaml
│ │ └── webhookcainjection_patch.yaml
│ ├── manager
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── auth_proxy_client_clusterrole.yaml
│ │ ├── auth_proxy_role_binding.yaml
│ │ ├── auth_proxy_role.yaml
│ │ ├── auth_proxy_service.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── leader_election_role.yaml
│ │ └── role_binding.yaml
│ └── webhook
│ ├── kustomization.yaml
│ ├── kustomizeconfifig.yaml
│ └── service.yaml
├── Dockerfifile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go
├── Makefifile
└── PROJECT

接下来我们使用代码清单 1-8 创建“Welcome”Kind 和其对应的控制器。

代码清单 1-8

$ kubebuilder create api --group webapp --kind Welcome --version v1
Create Resource [y/n]
y
Create Controller [y/n]
y

输入两次 y,Kubebuilder 分别创建了资源和控制器的模板,此处的 group、version、kind 这 3 个属性组合起来标识一个 k8s 的 CRD,创建完成后,Kubebuilder 添加文件见代码清单 1-9。

代码清单 1-9

├── api
│ └── v1
│ ├── groupversion_info.go 
│ ├── welcome_types.go // 自定义 CRD 结构需修改的文件
│ └── zz_generated.deepcopy.go
├── bin
│ └── manager
├── config
│ ├── certmanager
│ │ ├── certificate.yaml
│ │ ├── kustomization.yaml
│ │ └── kustomizeconfig.yaml
│ ├── crd
│ │ ├── bases
│ │ │ └── webapp.demo.welcome.domain_welcomes.yaml
│ │ ├── kustomization.yaml
│ │ ├── kustomizeconfig.yaml
│ │ └── patches
│ │ ├── cainjection_in_welcomes.yaml
│ │ └── webhook_in_welcomes.yaml
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ ├── manager_webhook_patch.yaml
│ │ └── webhookcainjection_patch.yaml
│ ├── manager
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── auth_proxy_client_clusterrole.yaml
│ │ ├── auth_proxy_role_binding.yaml
│ │ ├── auth_proxy_role.yaml
│ │ ├── auth_proxy_service.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── leader_election_role.yaml
│ │ ├── role_binding.yaml
│ │ ├── role.yaml
│ │ ├── welcome_editor_role.yaml
│ │ └── welcome_viewer_role.yaml
│ ├── samples
│ │ └── webapp_v1_welcome.yaml // 简单的自定义资源 Yaml 文件
│ └── webhook
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ └── service.yaml
├── controllers
│ ├── suite_test.go 
│ └── welcome_controller.go // CRD Controller 核心逻辑
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT

后续需要执行两步操作:

① 修改 Resource Type;

② 修改 Controller 逻辑。

(3)修改 Resource Type

此处 Resource Type 为需要定义的资源字段,用于在 Yaml 文件中进行声明,本案例中需要新增 name 字段用于“Welcome”Kind 中的 Web 应用,见代码清单 1-10。

代码清单 1-10

/api/v1/welcome_types.go
type WelcomeSpec struct {
 // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
 // Important: Run "make" to regenerate code after modifying this file
 // Foo is an example field of Welcome. Edit Welcome_types.go to remove/update
 // Foo string `json:"foo,omitempty"`
 Name string `json:"name,omitempty"`
}

(4)修改 Controller 逻辑

在 Controller 中需要通过 Reconcile 方法完成 Deployment 和 Service 部署,并最终达到期望的状态。

Controller 中的代码见代码清单 1-11,我们需要在其中加入业务逻辑。

代码清单 1-11

// +kubebuilder:rbac:groups=webapp.demo.welcome.domain,resources=welcomes,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=webapp.demo.welcome.domain,resources=welcomes/
status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;
patch;create;update
// +kubebuilder:rbac:groups=core,resources=services,verbs=list;watch;get;patch;
create;update
func (r *WelcomeReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 ctx := context.Background()
 log := r.Log.WithValues("welcome", req.NamespacedName)
 log.Info("reconciling welcome")

此处有两组“+”标识,第一组用于 Operator 更新 Welcome 资源对象,第二组用于创

建 Deployment 和 Service。接下来完成 Welcome 类型控制器的部分代码的实现(见

代码清单 1-12)。

代码清单 1-12

deployment, err := r.createWelcomeDeployment(welcome)
 if err != nil {
 return ctrl.Result{}, err
 }
 log.Info("create deployment success!")
 svc, err := r.createService(welcome)
 if err != nil {
 return ctrl.Result{}, err
 }
 log.Info("create service success!")
 applyOpts := []client.PatchOption{client.ForceOwnership, client.
FieldOwner("welcome_controller")}
 err = r.Patch(ctx, &deployment, client.Apply, applyOpts...)
 if err != nil {
 return ctrl.Result{}, err
 }
 err = r.Patch(ctx, &svc, client.Apply, applyOpts...)
 if err != nil {
 return ctrl.Result{}, err
 }

在控制器部分需要完成 Deployment 和 Service 的创建,并完成两者的关联,在上述代码中,我们分别通过调用 createWelcomeDeployment 和 createService 方法完成对象的创建,接下来我们完成上述方法的具体实现(见代码清单 1-13)。

代码清单 1-13

func (r *WelcomeReconciler) createWelcomeDeployment(welcome webappv1.Welcome) 
(appsv1.Deployment, error) {
 defOne := int32(1)
 name := welcome.Spec.Name
 if name == "" {
 name = "world"
 }
 depl := appsv1.Deployment{
 TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.
String(), Kind: "Deployment"},
 ObjectMeta: metav1.ObjectMeta{
 Name: welcome.Name,
 Namespace: welcome.Namespace,
 },
 Spec: appsv1.DeploymentSpec{
 Replicas: &defOne,
 Selector: &metav1.LabelSelector{
 MatchLabels: map[string]string{"welcome": welcome.Name},
 },
 Template: corev1.PodTemplateSpec{
 ObjectMeta: metav1.ObjectMeta{
 Labels: map[string]string{"welcome": welcome.Name},
 },
 Spec: corev1.PodSpec{
 Containers: []corev1.Container{
 {
 Name: "welcome",
 Env: []corev1.EnvVar{
  {Name: "NAME", Value: name},
 },
 Ports: []corev1.ContainerPort{
 {ContainerPort: 8080, Name: "http", 
Protocol: "TCP"},
 },
 Image: "sdfcdwefe/operatordemo:v1",
 Resources: corev1.ResourceRequirements{
 Requests: corev1.ResourceList{
 corev1.ResourceCPU: *resource.
NewMilliQuantity(100, resource.DecimalSI),
 corev1.ResourceMemory: *resource.
NewMilliQuantity(100000, resource.BinarySI),

在上述代码中,我们在 Deployment 使用了之前制作的 Docker 镜像,将 Types 中获得的 NAME 字段作为环境变量传入镜像中,在镜像执行 main 函数时,即可获得 NAME字段并修改 index 文件,在文件中插入 NAME,并默认开启 8080 监听端口,用户通过Web 访问时即可获得最终的期望值。接下来,我们完成 Service 部分代码的实现(见代码清单 1-14)。

代码清单 1-14

func (r *WelcomeReconciler) createService(welcome webappv1.Welcome) (corev1.
Service, error) {
 svc := corev1.Service{
 TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.
String(), Kind: "Service"},
 ObjectMeta: metav1.ObjectMeta{
 Name: welcome.Name,
 Namespace: welcome.Namespace,
 },
 Spec: corev1.ServiceSpec{
 Ports: []corev1.ServicePort{
 {Name: "http", Port: 8080, Protocol: "TCP", TargetPort: 
intstr.FromString("http")},
 },
 Selector: map[string]string{"welcome": welcome.Name},
 Type: corev1.ServiceTypeLoadBalancer,
 },
 }

在本例中,我们创建了 LoadBalancer 类型的 Service。通过 kubectl get svc 命令可以获取 URL 地址,也可以访问 Web 应用。

(5) Welcome 应用部署

接下来,我们部署前面步骤中更新的 Type 和 Controller 文件,并创建 Welcome 类型资源(见代码清单 1-15)。

代码清单 1-15

$ kubectl create -f config/crd/bases/
$ kubectl create -f config/samples/webapp_v1_welcome.yaml

此时,我们通过kubectl get crd命令可以看到自定义对象已经生效(见代码清单1-16)。

代码清单 1-16

$ kubectl get crd
NAME CREATED AT
crontabs.stable.example.com 2021-02-18T06:23:11Z
welcomes.webapp.demo.welcome.domain 2021-03-10T13:06:37Z

通过 kubectl get welcome 命令可以看到创建的 welcome 对象(见代码清单 1-17)。

代码清单 1-17

$ kubectl get welcome
NAME AGE
welcome-sample 3s

此时 CRD 并不会完成任何工作,只是在 ETCD 中创建了一条记录,我们需要运行

Controller 才能帮助我们完成调谐工作并最终达到 welcome 定义的状态。

$ make run

以上方式在本地启动控制器,方便调试和验证,最终显示见代码清单 1-18。

代码清单 1-18

2021-03-11T21:04:56.904+0800 INFO controller-runtime.metrics metrics 
server is starting to listen {"addr": ":8080"}
2021-03-11T21:04:56.904+0800 INFO setup starting manager
2021-03-11T21:04:56.904+0800 INFO controller-runtime.manager starting 
metrics server {"path": "/metrics"}
2021-03-11T21:04:56.905+0800 INFO controller-runtime.controller 
 Starting EventSource {"controller": "welcome", "source": "kind source:
 /, Kind="}
2021-03-11T21:04:57.005+0800 INFO controller-runtime.controller 
 Starting Controller {"controller": "welcome"}
2021-03-11T21:04:57.005+0800 INFO controller-runtime.controller 
 Starting workers {"controller": "welcome", "worker count": 1}
2021-03-11T21:04:57.006+0800 INFO controllers.Welcome reconciling welcome 
{"welcome": "default/welcome-sample"}
2021-03-11T21:04:57.006+0800 INFO controllers.Welcome create deployment success! 
{"welcome": "default/welcome-sample"}
2021-03-11T21:04:57.056+0800 INFO controllers.Welcome create service success! 
{"welcome": "default/welcome-sample"}
2021-03-11T21:04:57.056+0800 INFO controllers.Welcome create deploy and service 
success! {"welcome": "default/welcome-sample"}
2021-03-11T21:04:57.056+0800 DEBUG controller-runtime.controller 
 Successfully Reconciled {"controller": "welcome", "request": "default/
welcome-sample"}

此时我们通过代码清单 1-19 验证控制器是否完成对象创建及状态更新。

代码清单 1-19

$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
welcome-sample 1/1 1 1 3m2s

通过代码清单 1-20 可以看到,Deployment 已经创建成功,并且达到期望的副本数量。

代码清单 1-20

$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) 
AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 
212d
welcome-sample LoadBalancer 10.96.242.198 <pending> 8080:32181/
TCP 4m12s

通过代码清单 1-21 可以看到,此时 Service 已经创建成功,并且分配到集群 IP,我

们通过集群 IP 访问应用查看。

代码清单 1-21

$ curl -L 10.96.242.198:8080/hello/
<html ng-app="redis">
 <head>
 <title>Hello </title>
 </head>
 <body>
 <div style="width: 50%; margin-left: 20px">
 <h2>Welcome! This is an Opetator Demo</h2>
 </div>
 </body>
</html>
Hello myfriends


相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
1月前
|
运维 Cloud Native 持续交付
深入理解云原生架构及其在现代企业中的应用
随着数字化转型的浪潮席卷全球,企业正面临着前所未有的挑战与机遇。云计算技术的迅猛发展,特别是云原生架构的兴起,正在重塑企业的IT基础设施和软件开发模式。本文将深入探讨云原生的核心概念、关键技术以及如何在企业中实施云原生策略,以实现更高效的资源利用和更快的市场响应速度。通过分析云原生架构的优势和面临的挑战,我们将揭示它如何助力企业在激烈的市场竞争中保持领先地位。
|
1月前
|
运维 Cloud Native 安全
云原生技术在现代企业中的应用与挑战####
本文探讨了云原生技术在现代企业IT架构中的关键作用,分析了其带来的优势和面临的主要挑战。通过实际案例分析,揭示了如何有效应对这些挑战,以实现业务敏捷性和技术创新的平衡。 ####
|
29天前
|
Cloud Native 持续交付 开发者
云原生技术在现代企业中的应用与实践####
本文深入探讨了云原生技术的核心概念及其在现代企业IT架构转型中的关键作用,通过具体案例分析展示了云原生如何促进企业的敏捷开发、高效运维及成本优化。不同于传统摘要仅概述内容,本部分旨在激发读者对云原生领域的兴趣,强调其在加速数字化转型过程中的不可或缺性,为后续详细论述奠定基础。 ####
|
1月前
|
Kubernetes Cloud Native 物联网
云原生技术在现代软件开发中的应用与挑战####
本文探讨了云原生技术的兴起背景、核心理念及其在现代软件开发中的广泛应用。通过具体案例分析,揭示了云原生架构如何促进企业数字化转型,并指出了在实施过程中面临的主要挑战及应对策略。 ####
|
4天前
|
运维 Cloud Native Serverless
Serverless Argo Workflows大规模计算工作流平台荣获信通院“云原生技术创新标杆案例”
2024年12月24日,阿里云Serverless Argo Workflows大规模计算工作流平台荣获由中国信息通信研究院颁发的「云原生技术创新案例」奖。
|
2天前
|
监控 安全 Cloud Native
阿里云容器服务&云安全中心团队荣获信通院“云原生安全标杆案例”奖
2024年12月24日,阿里云容器服务团队与云安全中心团队获得中国信息通信研究院「云原生安全标杆案例」奖。
|
24天前
|
人工智能 缓存 异构计算
云原生AI加速生成式人工智能应用的部署构建
本文探讨了云原生技术背景下,尤其是Kubernetes和容器技术的发展,对模型推理服务带来的挑战与优化策略。文中详细介绍了Knative的弹性扩展机制,包括HPA和CronHPA,以及针对传统弹性扩展“滞后”问题提出的AHPA(高级弹性预测)。此外,文章重点介绍了Fluid项目,它通过分布式缓存优化了模型加载的I/O操作,显著缩短了推理服务的冷启动时间,特别是在处理大规模并发请求时表现出色。通过实际案例,展示了Fluid在vLLM和Qwen模型推理中的应用效果,证明了其在提高模型推理效率和响应速度方面的优势。
云原生AI加速生成式人工智能应用的部署构建
|
29天前
|
Cloud Native JavaScript Docker
云原生技术:构建现代应用的基石
在数字化转型的浪潮中,云原生技术如同一艘承载梦想的航船,引领企业驶向创新与效率的新海域。本文将深入探索云原生技术的核心价值,揭示其如何重塑软件开发、部署和运维模式,同时通过一个简易代码示例,展现云原生应用的构建过程,让读者领略到云原生技术的魅力所在。
|
2月前
|
消息中间件 Cloud Native 持续交付
云原生技术在现代企业中的应用与优势###
本文深入探讨了云原生技术在现代企业中的具体应用及其带来的显著优势。随着云计算的普及,云原生作为一种新兴的技术架构,正逐渐成为企业数字化转型的关键驱动力。文章将详细介绍云原生的核心概念、主要技术组件以及在实际业务场景中的成功案例,旨在为读者提供一个全面且实用的参考框架,以便更好地理解和应用云原生技术。 ###
|
2月前
|
Cloud Native JavaScript Devops
云原生技术在现代软件开发中的应用与实践
本文将深入探讨云原生技术如何改变现代软件开发的格局。通过分析云原生的核心概念、优势以及在实际开发中的应用案例,我们将了解这一前沿技术如何助力企业快速适应市场变化,提高开发效率和系统稳定性。文章还将提供实用的代码示例,帮助开发者更好地理解和运用云原生技术。