大纲
原文地址 https://blog.csdn.net/liuyij3430448/article/details/129534732
k8s中 “Pod Service Deployment”等都是一种资源,k8s的作用就是管理这些资源。(当资源创建,更新,删除后会有对应的操作)。
单纯的资源是没有任何意义的,需要配合对应的Controller[控制器] 来管理资源
例如
Pod 的扩容 就是使用ReplicationController等实现的,单纯的Pod只是一个存储在Etcd中的数据
当k8s 内置的资源无法满足我们的需求时,可以使用k8s提供的CustomResourceDefinition + 自定义controller实现自己的资源与资源控制
有了自定义的资源(CRD),但是没有相应的Controller对其进行操作,那这些资源也就只能简单的存储到k8s集群中了(etcd)。
因此还需要开发相应的Controller来对相应的CRD进行监听、处理业务逻辑
Controller就是一遵循k8s相关规范,然后连接到k8s master API 并监听CRD事件的进程(CRD的事件一般就是指:Add, Update, Delete)
Operator是一个更加复杂的Controller,还可以实现一些运维操作
Operator 官方资料 https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/operator/
Operator 就是一种 Controller
Kubernetes 的 Operator 模式概念允许你在不修改 Kubernetes 自身代码的情况下,
通过为一个或多个自定义资源关联控制器来扩展集群的能力。
简单讲 Operator = 资源(可以是k8s内置资源或者自定义的CRD) + controller + 一些运维操作
例如本文中最后的实例
会创建一个数据库表监控CRD资源,在创建一个自己的controller,监控表的创建,保持指定数量行,删除表。 这一系列操作就是一个 Operator
要使用自定义资源,就需要先在k8s集群中定义这个资源,定义资源就需要使用CustomResourceDefinition
注意:要使用CustomResourceDefinition, Kubernetes 服务器版本必须不低于版本 1.16.
官方文档 https://kubernetes.io/zh-cn/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/
以一个自定义的MyCrdTest 资源为例 CustomResourceDefinition.yaml文件中需要定 资源的名称,资源的组,资源的版本 以及spec等内容
# 定义自定义的 MyCrdTest 资源
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# 名字必需与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
name: mycrdtests.liuyjiang.mycrdtest.com
spec:
# 组名称,用于 REST API: /apis/<组>/<版本>
group: liuyjiang.mycrdtest.com
names:
# 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
plural: mycrdtests
# 名称的单数形式,作为命令行使用时和显示时的别名
singular: mycrdtest
# kind 通常是单数形式的驼峰命名(CamelCased)形式。你的资源清单会使用这一形式。
kind: MyCrdTest
# shortNames 允许你在命令行使用较短的字符串来匹配资源
shortNames:
- mct
# 可以是 Namespaced 或 Cluster
scope: Namespaced
versions:
- name: v1
# 每个版本都可以通过服务标志启用/禁用。
served: true
# 必须将一个且只有一个版本标记为存储版本。
storage: true
schema:
openAPIV3Schema:
type: object
properties:
#自定义CRD中的spec
spec:
type: object
properties:
# 自定义的资源spec 中的属性 mymsg
mymsg:
type: string
# 自定义的资源spec 中的属性 myarray
myarray:
type: array
items:
type: string
# 自定义的资源spec 中的属性 mynumber
mynumber:
type: integer
#自定义CRD中的status
status:
type: object
properties:
mystatus:
type: string
myip:
type: string
使用命令kubectl apply -f crd-simple.yaml 创建CRD 相关文件见 《/yaml/crd-simple.yaml》
这样自定义资源的定义就有了,接下来就可以创建自定义资源MyCrdTest
上一步中创建了自定义资源定义CustomResourceDefinition 这样就可以基于定义的内容创建自定义资源MyCrdTest
my-crd-test.yaml 文件内容如下
# 这个就是对应crd-simple.yaml 中定义的
# api使用 crd中定义的组名称(group)+ 版本号(versions name)
apiVersion: "liuyjiang.mycrdtest.com/v1"
# kind使用 crd中定义的kind名称
kind: MyCrdTest
metadata:
name: test-001
# 对应 crd-simple.yaml schema中的spec 配置
spec:
mymsg: "hello world"
mynumber: 3
myarray:
- aaa
bbb
ccc
# 对应 crd-simple.yaml schema中的status 配置
status:
mystatus: "running"
myip: "192.168.0.211"
使用kubectl apply -f my-crd-test.yaml 创建自定义资源MyCrdTest
使用kubectl get 【资源】查看创建的资源
可以自定义 kubectl get 显示字段
自定义kubectl get 显示字段各式如下
kubectl get [资源类型] [资源名称] -o custom-columns=【显示的的字段】:【.资源配置】
例如yaml中配置
metadata:
name: test-001
spec:
mymsg: "hello world"
status:
mystatus: "running"
myip: "192.168.0.211"
使用以下命令查看内容 注意.号
kubectl get mct -o custom-columns=NAME:.metadata.name,MSG:.spec.mymsg,IP:.status.myip,STATUS:.status.mystatus
也可以使用模板文件
kubectl get mct -o custom-columns-file=tpl.txt
模板文件tpl.txt内容为
NAME MSG IP STATUS
metadata.name spec.mymsg status.myip status.mystatus
这样一个自定义的资源就创建完成了,注意此时资源只存在Etcd中并没有对应的控制器来管理资源
k8s 提供了大量的 client端库来操作集群,官方资料: https://kubernetes.io/zh-cn/docs/reference/using-api/client-libraries/
这里使用fabric8io/kubernetes-client来操作k8s集群,quarkus中就是使用fabric8io/kubernetes-client这个库
fabric8io/kubernetes-client 官方地址 https://github.com/fabric8io/kubernetes-client
关于quarkus可以参考
《quarkus 搭建与基础开发环境配置总结》
《quarkus 生产环境与k8s集成总结》
使用fabric8io/kubernetes-client 需要在项目中添加依赖
io.fabric8
kubernetes-client
6.4.1
在maven pom.xml中加入依赖,就可以使用kubernetes-client 注意:可能还需要引入 jackson相关的依赖
以下方式是在集群外(程序未部署到k8s集群中)创建KubernetesClient
这样就可以拿到KubernetesClient,然后对Pod Service Deployment进行操作
private String caCertData = "LS0tLS1C***=";
private String clientCertData = "LS0tLS****LQo=";
private String clientKeyData = "LS0tLS****=";
private String host = "https://192.168.0.160:6443";
Config config = new ConfigBuilder().withMasterUrl(host)
.withCaCertData(caCertData)
.withClientCertData(clientCertData)
.withClientKeyData(clientKeyData)
.withDefaultNamespace().build();
//集群外创建client 需要指定 host 证书 私钥等
KubernetesClient client = new KubernetesClientBuilder().withConfig(config).build();
以上代码中的caCertData clientCertData clientKeyData host
可以使用k8s集群master节点 /root/.kube/config中的配置
//以下方式是在集群内(程序部署在k8s集群中)创建KubernetesClient
KubernetesClient client = new KubernetesClientBuilder().build();
不需要配置 host 证书 私钥等
然后就可以使用client
client.pods()..
client.services()..
来操作k8s集群中的资源了
此时我们只需要在自己的java springboot项目中,使用kubernetes-client 去监听自定义的资源的状态并对状态做对应的响应操作即可
对应的就是watch操作
后续实例中会详解讲解 watch操作 监控资源状态变更
把java项目部署到k8s集群中,是一个很好的选择。但是对于我们自定义的资源默认的ServiceAccount中是没有对应的定义的,所以需要创建ServiceAccount,并让ServiceAccount与ClusterRole 绑定。这样java程序能够在集群内部访问k8s api接口
参考资料《快速上手k8s权限管理 立即掌握User Role RoleBinding kubeconfig 实战教程》
相关的权限配置如下
# 创建ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: crdtest-serviceaccount
namespace: crd-tm-test
labels:
myk8s.crd-test.com: crd-tm-test
---
# 需要操作自定义的 CRD TableMonitor 需要配置对TableMonitor资源的操作权限
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: crdtest-clusterrole
rules:
- apiGroups:
- "myk8s.crd-test.com"
resources:
- tablemonitors
verbs:
- list
- watch
---
# 让ServiceAccount 与 ClusterRole绑定
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: crdtest-cluster-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: crdtest-clusterrole
subjects:
- kind: ServiceAccount
name: crdtest-serviceaccount
namespace: crd-tm-test
本例子为了简洁并利于理解,使用数据库中的表作为一个需要管理的资源。
类似Pod管理的是容器,我们创建一个TableMonitor资源来管理表
架构设计入下图
数据库表监控Operator的业务逻辑是
创建一个TableMonitor资源的定义 CRD yaml文件 crd-table-monitor.yaml 内容如下 (见my-docker-demo-k8s-operator/yaml/crd-table-monitor.yaml)
# 定义自定义的TableMonitor 资源
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# 名字必需与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
name: tablemonitors.liuyijiang.crd-tm.com
spec:
# 组名称,用于 REST API: /apis/<组>/<版本>
group: liuyijiang.crd-tm.com
names:
# 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
plural: tablemonitors
# 名称的单数形式,作为命令行使用时和显示时的别名
singular: tablemonitor
# kind 通常是单数形式的驼峰命名(CamelCased)形式。你的资源清单会使用这一形式。
kind: TableMonitor
# shortNames 允许你在命令行使用较短的字符串来匹配资源
shortNames:
- tm
scope: Namespaced
versions:
- name: v1
# 每个版本都可以通过服务标志启用/禁用。
served: true
# 必须将一个且只有一个版本标记为存储版本。
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
# 自定义的资源spec 中的属性 dbUserName 需要连接的数据库的账户名
dbUserName:
type: string
# 自定义的资源spec 中的属性 dbUserPassword 需要连接的数据库的密码
dbUserPassword:
type: string
# 自定义的资源spec 中的属性 dbUrl 需要连接的数据库的url
dbUrl:
type: string
# 自定义的资源spec 中的属性 tableName 需要操作的表名称
tableName:
type: string
# 自定义的资源spec 中的属性 tableColumns 注意类型是 array 表中的字段
tableColumns:
type: array
items:
type: string
# 自定义的资源spec 中的属性 dataNum 表中保存的数据行数
dataNum:
type: integer
status:
type: object
properties:
# 恢复次数 每调整一次表行数就加1
recoverNum:
type: integer
# 运行数据库ip
hostIp:
type: string
# 运行状态
runingStatus:
type: string
kubectl apply -f crd-table-monitor.yaml 创建CRD
程序就是一个简单的springboot 其中fabric8/kubernetes-client版本号6.4.1 项目主要结构如下
TableMonitorSpec.java TableMonitorStatus.java 都是一个实现了KubernetesResource接口的POJO类
对应crd-table-monitor.yaml中配置的spec 和 status 相关字段
TableMonitorSpec.java
TableMonitorStatus.java
TableMonitor.java 则是对应的TableMonitor资源 注意组和版本号
TableMonitorList.java 是一个继承了DefaultKubernetesResourceList的类,用于保存多个TableMonitor
TableService.java的作用就是一个数据库操作的类,用于创建表,删表,加数据,删数据等
使用spring提供的jdbctemplate来操作数据库,例如以下是一个建表操作
数据库操作的类很简单 就不多赘述
TableMonitorListener.java 是本Operator 程序的核心类
代码如下:
package com.k8s.operator.listener;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component;
import com.k8s.operator.crd.TableMonitor;
import com.k8s.operator.crd.TableMonitorList;
import com.k8s.operator.crd.TableMonitorStatus;
import com.k8s.operator.database.DataTable;
import com.k8s.operator.database.TableService;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.WatcherException;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
/**
* 核心就是这个TableMonitorListener
*
* TableMonitorListener watch 自定义的TableMonitor资源 处理 1 新建 2 删除 3 数据是否满足指定
*
* @author liuyijiang
*
*/
@Component
public class TableMonitorListener {
/**
* TableMonitor 控制器
*/
private MixedOperation> tableMonitor;
/**
* 数据库操作类
*/
private TableService tableService = new TableService();
/**
* 保存所有的k8s集中部署的TableMonitor
*/
private ConcurrentHashMap CRDS = new ConcurrentHashMap<>();
/**
* 初始化
*/
@PostConstruct
public void initListerner() {
System.out.println("start TableMonitorListener");
/**
* 初始化客户端
*/
init();
/**
* 监听表的状态变化
*/
watchTable();
/**
* 监听资源的变化
*/
watchCRD();
}
/**
* 初始化KubernetesClient 客户端
*/
private void init() {
System.out.println("init KubernetesClientBuilder !!!!!!!!!!!!!!!!");
KubernetesClient client = new KubernetesClientBuilder().build();
/**
* 获取自定义资源的客户端
*/
NonNamespaceOperation> tableMonitorNonNamespace = client.resources(TableMonitor.class, TableMonitorList.class);
//使用default 命名空间
tableMonitorNonNamespace = ((MixedOperation>) tableMonitorNonNamespace).inNamespace("default");
tableMonitor = (MixedOperation> ) tableMonitorNonNamespace;
}
/**
* 监控表 保证表中的数据始终保持在 dataNum配置的值
*/
private void watchTable() {
new Thread(() -> {
System.out.println("start watchTable ");
while (true) {
for (String id : CRDS.keySet()) {
TableMonitor tm = CRDS.get(id);
/**
* 检查是否存在表
*/
System.out.println("check if table is not EXISTS");
tableService.createTable(tm);
/**
* 查询出TableMonitor 表中存在的数据
*/
int num = tableService.getTableDataCount(tm);
if (num != tm.getSpec().getDataNum()) {
// 小于定义的值
if (num < tm.getSpec().getDataNum()) {
System.out.println("### data too little !!!!!!!!!!!!!!!!");
int flag = tm.getSpec().getDataNum() - num; // 获得少了的量
for (int i = 0; i < flag; i++) {
tableService.insertTableData(tm); // 创建少的数据
}
} else { // 大于定义的值
System.out.println("### data too more !!!!!!!!!!!!!!!!");
int flag = num - tm.getSpec().getDataNum(); // 获得多了的量
List list = tableService.listTableData(tm);
for (int i = 0; i < flag; i++) {
tableService.deleteTableData(tm, list.get(i)); // 删除多的数据
}
}
//更新资源状态 添加一次恢复次数
tm.getStatus().setRecoverNum(tm.getStatus().getRecoverNum() + 1);
tableMonitor.resource(tm).createOrReplace();
}
}
// 10秒钟监控一次
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public void watchCRD() {
/**
* 监控资源
*/
Watch watch = tableMonitor.watch(new Watcher() {
@Override
public void eventReceived(Action action, TableMonitor resource) {
// 新增
if (action.equals(Action.ADDED)) { // 新增资源
System.out.println("===============create kind=================" + resource);
// 建表
tableService.createTable(resource);
CRDS.put(resource.getMetadata().getName(), resource); // 资源放入到map中
//添加资源状态
resource.setStatus(new TableMonitorStatus());
resource.getStatus().setRuningStatus("running");
resource.getStatus().setRecoverNum(0);
String ip = resource.getSpec().getDbUrl().substring(13,26);
resource.getStatus().setHostIp(ip);
//更新资源状态
tableMonitor.resource(resource).createOrReplace();
} else if (action.equals(Action.DELETED)) { // 删除资源
System.out.println("===============delete kind=================" + resource);
tableService.dropTable(resource);
CRDS.remove(resource.getMetadata().getName());
}
}
@Override
public void onClose(WatcherException cause) {
System.out.println(cause);
}
});
}
}
以上便是 java实现Operator 的基本代码
主要事项
关于k8s权限 角色 可以参考《快速上手k8s权限管理 立即掌握User Role RoleBinding kubeconfig 实战教程》
注意:所有相关配置文件保存在 /my-docker-demo-k8s-operator/yaml/operrator相关 Docker相关
Dockerfile内容如下
FROM ascdc/jdk8
VOLUME ["/data/service/logs","/data/service/tmp"]
WORKDIR "/data/service"
EXPOSE 5533
COPY my-docker-demo-k8s-operator.jar my-docker-demo-k8s-operator.jar
ENTRYPOINT ["nohup","java","-jar","my-docker-demo-k8s-operator.jar","&"]
Dockerfile参考 《Dockerfile文件总结》
docker build -t tm-controller .
docker tag tm-controller registry.cn-hangzhou.aliyuncs.com/jimliu/tm-controller
docker push registry.cn-hangzhou.aliyuncs.com/jimliu/tm-controller
此时镜像创建完毕
springboot k8s Operator部署文件deploy.yml如下 (也可以不部署在k8s中 注意kubernetes-clinet的创建方式)
# 创建命名空间
apiVersion: v1
kind: Namespace
metadata:
name: crd-tm-test
labels:
liuyijiang.crd-tm.com: crd-tm-test
---
# 创建阿里云私库秘钥
apiVersion: v1
kind: Secret
metadata:
name: myaliyunsecret-crdtmtest
namespace: crd-tm-test
labels:
liuyijiang.crd-tm.com: crd-tm-test
data:
.dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5.....省略换成自己的
type: kubernetes.io/dockerconfigjson
---
# 创建ServiceAccount 用于 程序中访问自定义资源
apiVersion: v1
kind: ServiceAccount
metadata:
name: crdtest-serviceaccount
namespace: crd-tm-test
labels:
liuyijiang.crd-tm.com: crd-tm-test
imagePullSecrets:
- name: myaliyunsecret-crdtmtest
---
# 需要操作自定义的 CRD TableMonitor 需要配置对TableMonitor资源的操作权限
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: crdtest-clusterrole
labels:
liuyijiang.crd-tm.com: crd-tm-test
rules:
- apiGroups:
- "liuyijiang.crd-tm.com" #apiGroups crd-table-monitor.yaml中定义的 group
resources:
- tablemonitors #只操作TableMonitor资源 注意为crd-table-monitor.yaml中配置的复数名称
verbs: #可以操作的类型
- list
- watch
- get
- create
- delete
- update
- edit
- exec
---
# 让ServiceAccount 与 ClusterRole绑定
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: crdtest-cluster-role-binding
labels:
liuyijiang.crd-tm.com: crd-tm-test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: crdtest-clusterrole
subjects:
- kind: ServiceAccount
name: crdtest-serviceaccount
namespace: crd-tm-test
---
# 创建项目容器pod
apiVersion: v1
kind: Pod
metadata:
name: tm-controller-pod
namespace: crd-tm-test
labels:
liuyijiang.crd-tm.com: crd-tm-test
spec:
# 注意指定serviceAccount
serviceAccountName: crdtest-serviceaccount
restartPolicy: Always
containers:
- image: registry.cn-hangzhou.aliyuncs.com/jimliu/tm-controller:latest
name: tm-controller-runtime
执行 kubectl apply -f deploy.yml 部署operator
执行 kubectl logs -f tm-controller-pod -n crd-tm-test 查看日志 注意-n 命名空间
到此 springboot k8s Operator部署完成
现在创建一个资源来测试 springboot k8s Operator
资源创建文件 tm1.yaml 内容如下
# api使用 crd中定义的组名称
apiVersion: "liuyijiang.crd-tm.com/v1"
# kind使用 crd中定义的kind名称
kind: TableMonitor
metadata:
name: test-001
spec:
dbUserName: "root"
dbUserPassword: "123456"
dbUrl: "jdbc:mysql://192.168.0.206:3306/t0003"
tableName: "tb_my_test"
dataNum: 3
tableColumns:
- aaa
bbb
ccc
首先可以看到t0003数据库中目前还没有tb_my_test 这张表
kubectl apply -f tm1.yaml 创建资源
kubectl apply -f tm1.yaml
kubectl get tm -o custom-columns-file=tpl.txt
springboot k8s Operator 日志出现建表操作
删除表中的一条数据
10秒后 表数据恢复到dataNum指定的值3
执行 kubectl delete -f tm1.yaml 删除TableMonitor资源
原文地址 https://blog.csdn.net/liuyij3430448/article/details/129534732