fabric8(kubernetes java client) 自定义开发实战
前言本文章主要针对需要对fabric8 java client 进行修改的读者,对fabric8进行自定义开发,调用SDK对k8s CRD(CustomResourceDefinition) 资源进行操作。本文章根据作者自己的实际开发内容为主线理清开发思路,并不会特别全面的去讲解所有知识点,需要详细了解还需要自行查阅官网,同时我也会在相应的内容里面贴出一些参考地址。如果存在说错的地方,还请指正!!
前言
本文章主要针对需要对fabric8 java client 进行修改的读者,对fabric8进行自定义开发,调用SDK对k8s CRD(CustomResourceDefinition) 资源进行操作。本文章根据作者自己的实际开发内容为主线理清开发思路,并不会特别全面的去讲解所有知识点,需要详细了解还需要自行查阅官网,同时我也会在相应的内容里面贴出一些参考地址。如果存在说错的地方,还请指正!!
github 下载相关代码以及工程简单解析
首先我们需要从fabric8 中去下载源代码(地址为:https://github.com/fabric8io/kubernetes-client)。如果需要了解 什么是fabric8,以及详细使用方法、详细内容都可以在这个地方查看。
工程目录结构如图:
简单介绍一下工程中各个模块的作用:
kubernetes-client:这是最主要的模块最终打包出来的sdk jar包就是这个模块生成的。在该工程下最主要的就是DefaultKubernetesClient.class 因为后续操作k8s的client 就是由这个DefaultKubernetesClient 提供,同时可以从这个类中看出这个类提供k8s 系统级别的所有组件资源,这个也是整个sdk的入口。作者后续的代码便是从AppsAPIGroupClient开始添加自定义资源。如图:
为了方便理解贴出一段生成client的代码。可以从代码中看出sdk client 就是从DefaultKubernetesClient类中new了一个对象。” KubernetesClient client = new DefaultKubernetesClient(config)“
private K8sClientEntity createK8sClient(String clusterName){
K8sClientEntity k8sClientEntity = null;
try {
String token = "";
String ip = "";
ClusterEntity clusterEntity = clusterService.getClusterDetail(clusterName);
List<ClusterNodeEntity> nodeEntities = clusterService.getClusterNodes(clusterName);
if (Objects.nonNull(clusterEntity)) {
token = clusterService.getClusterToken(clusterEntity.getId());
}
if (!CollectionUtils.isEmpty(nodeEntities)) {
for (ClusterNodeEntity node : nodeEntities) {
if (CommonUtils.isContainString(node.getName(), "master1." + clusterName)) {
ip = node.getIp();
break;
}
}
}
if (!StringUtils.isEmpty(token) && !StringUtils.isEmpty(ip)) {
Config config = new ConfigBuilder().withMasterUrl("https://" + ip + ":6443")
.withOauthToken(token)
.withTrustCerts(true)
.build();
KubernetesClient client = new DefaultKubernetesClient(config);//使用默认的就足够了
k8sClientEntity = new K8sClientEntity();
k8sClientEntity.setTime(System.currentTimeMillis());
k8sClientEntity.setKubernetesClient(client);
k8sClientEntity.setIp(ip);
//获取prometheus port
io.fabric8.kubernetes.api.model.Service promeSvc = client.services().inNamespace("kube-operator").withName("f2c-prometheus-server").get();
if(Objects.nonNull(promeSvc)){
ServicePort servicePort = promeSvc.getSpec().getPorts().get(0);
k8sClientEntity.setPromePort(servicePort.getNodePort().toString());
}
//获取loki port
io.fabric8.kubernetes.api.model.Service lokiSvc = client.services().inNamespace("kube-operator").withName("f2c-loki").get();
if(Objects.nonNull(lokiSvc)){
ServicePort servicePort = lokiSvc.getSpec().getPorts().get(0);
k8sClientEntity.setLokiPort(servicePort.getNodePort().toString());
}
}
} catch (Exception e) {
logger.error("createK8sClient fail ====>",e);
}
return k8sClientEntity;
}
kubernetes-model-generator:是client 依赖的实际资源类的生成模块,该模块是通过maven jsonschema2pojo 插件自动生成client依赖类,由于k8s类型众多重复的字段也非常多,这样避免了简单而又繁琐的代码重复书写,后续的client会用到一个模式,我姑且叫“包装模式”很是该工程的巧妙之处。生成class的文件来至kube-schema.json(该文件又可以通过go写的工具来生成json,目前支持k8s原生资源,自定义的需要手动在里面添加)如图:
其它模块在该处就省略了,由兴趣的可以自己去看看!!!(^v^)
作者本次开发的内容TiDB on K8S 以及 K8S CRD(CustomResourceDefinition)
作者本次开发的内容是自定义fabric8里面的client 提供TiDB集群的部署功能,部署的流程pingcap 的官网有提供(地址: https://docs.pingcap.com/tidb-in-kubernetes/stable/get-started)。感兴趣的可以通过地址去了解。同时先把 K8S CRD(CustomResourceDefinition)的地址也贴上(https://kubernetes.io/zh/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/),为了防止一脸懵逼说明一下,TiDB是一种nosql数据库。
CustomResourceDefinition:这是k8s 自定义资源端点,用于扩展k8s api。实际定义的就是注册到k8s server的api endpoint的信息,operator开发的 自定义controller会根据endpoint来做具体的操作, 可以理解为自定义controller是实际的执行者。
operator: operator可以理解为扩展k8s api 的实际控制器,功能是利用自定义controller调用控制k8s 原生资源(deployment statefulset )的一套k8s 扩展自定义控制器 。operator会注册一个webhook 到api server ,如果发现资源是自定义的那么就会调用operator自定义的controller进行解析和操作。CRD官网也有,这里贴一个大佬写的地址(https://www.infoq.cn/article/MliPUPFhiESWyKyv8Aq8)
这里简单说说TiDB是如何部署到k8s的,TiDB自己开发了一套operator,然后定义了crd.yaml,要部署TiDB 需要先部署一套tidb的operator,最后通过operator 部署TiDB集群,由于TiDB 这些资源非k8s 原生 所以接下来就有了作者的下一步工作。
为了方便理解这里贴一段CustomResourceDefinition 的官网demo。
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# 名字必需与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
name: crontabs.stable.example.com
spec:
# 组名称,用于 REST API: /apis/<组>/<版本>
group: stable.example.com
# 列举此 CustomResourceDefinition 所支持的版本
versions:
- name: v1
# 每个版本都可以通过 served 标志来独立启用或禁止
served: true
# 其中一个且只有一个版本必需被标记为存储版本
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
# 可以是 Namespaced 或 Cluster
scope: Namespaced
names:
# 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
plural: crontabs
# 名称的单数形式,作为命令行使用时和显示时的别名
singular: crontab
# kind 通常是单数形式的驼峰编码(CamelCased)形式。你的资源清单会使用这一形式。
kind: CronTab
# shortNames 允许你在命令行使用较短的字符串来匹配资源
shortNames:
- ct
fabric8 代码开发说明
需求:由于fabric8 支持原生k8s操作,不能够满足自定义资源的操作,所以需要自定义fabric中的代码。由于deployment 这些原生k8s 资源是通过AppsAPIGroupClient 进行操作,所以作者选择在AppsAPIGroupClient 中自定义。
书接第一小节,fabric8 是通过给操作对象封装一层接口和抽象类达到调用client的某个动作从而操作该对象资源的功能。为了方便理解贴一段创建TiDB的代码。
@GetMapping("/test")
public void test() {
try {
KubernetesClient client = kubernetesService.getKubernetesClient("k8s-1");
TidbCluster tidbCluster = kubernetesTranslateService.translateToTidbCluster();
client.apps().tidbClusters().inNamespace("tiger").create(tidbCluster);
} catch (Exception e) {
e.printStackTrace();
}
}
上面代码中就是在k8s tiger 分区创建一个TiDB 集群,下面的代码就是构建TiDB 集群定义的java资源 对象代码,生成的对象就是tidbCluster, 该对象构建用到的TidbClusterBuilder就是通过kube-schema.json 自动生成的。
@Override
public TidbCluster translateToTidbCluster() {
return new TidbClusterBuilder()
.withMetadata(new ObjectMetaBuilder()
.withName("tiger")
.build())
.withSpec(new TidbClusterSpecBuilder()
.withPd(new TidbTemplateBuilder()
.withBaseImage("110.0.100.59:5000/market/pd")
.withRequests(new TidbRequestsBuilder()
.build())
.withConfig(new TidbTemConfigBuilder().build())
.build())
.withTikv(new TidbTemplateBuilder()
.withBaseImage("110.0.100.59:5000/market/tikv")
.withRequests(new TidbRequestsBuilder()
.build())
.withConfig(new TidbTemConfigBuilder().build())
.build())
.withTidb(new TidbTemplateBuilder()
.withBaseImage("110.0.100.59:5000/market/tidb")
.withReplicas(3)
.withRequests(new TidbRequestsBuilder()
.build())
.withConfig(new TidbTemConfigBuilder().build())
.withService(new TidbServiceBuilder()
.withType("NodePort")
.build())
.build())
.build())
.build();
}
下面介绍整个自定义开发流程:
- 在kubernetes-model-generator中的kubernetes-model-apps模块的kube-schema.json添加操作资源对象的定义(jsonschema2pojo 规则参考 https://blog.csdn.net/github_35758702/article/details/52662676)如图:
2. 在kubernetes-client 工程中如图显示的目录中创建资源操作对象,图中的对象用于操作关联的资源对象,可以重写其中的全部或者部分接口(按需求),但是必须重写“newInstance”方法请见下面代码片段。
public class TidbClusterOperationsImpl extends RollableScalableResourceOperation<TidbCluster, TidbClusterList, DoneableTidbCluster, RollableScalableResource<TidbCluster, DoneableTidbCluster>>
implements TimeoutImageEditReplacePatchable<TidbCluster, TidbCluster, DoneableTidbCluster>
{
public TidbClusterOperationsImpl(RollingOperationContext context) {
super(context.withApiGroupName("pingcap.com")
.withApiGroupVersion("pingcap.com/v1alpha1")
.withPlural("tidbclusters"));
this.type = TidbCluster.class;
this.listType = TidbClusterList.class;
this.doneableType = DoneableTidbCluster.class;
}
public TidbClusterOperationsImpl(OkHttpClient client, Config config) {
this(client, config, null);
}
public TidbClusterOperationsImpl(OkHttpClient client, Config config, String namespace) {
this(new RollingOperationContext().withOkhttpClient(client).withConfig(config).withNamespace(namespace).withPropagationPolicy(DEFAULT_PROPAGATION_POLICY));
}
@Override
public BaseOperation<TidbCluster, TidbClusterList, DoneableTidbCluster, RollableScalableResource<TidbCluster, DoneableTidbCluster>> newInstance(OperationContext context) {
return new TidbClusterOperationsImpl((RollingOperationContext) context);
}
@Override
public TidbCluster updateImage(Map<String, String> containerToImageMap) {
return null;
}
@Override
public TidbCluster updateImage(String image) {
return null;
}
@Override
public ImageEditReplacePatchable<TidbCluster, TidbCluster, DoneableTidbCluster> withTimeout(long timeout, TimeUnit unit) {
return null;
}
@Override
public ImageEditReplacePatchable<TidbCluster, TidbCluster, DoneableTidbCluster> withTimeoutInMillis(long timeoutInMillis) {
return null;
}
/**
* just edit tidb replicas
* @param count
* @return
*/
@Override
protected TidbCluster withReplicas(int count) {
return cascading(false).edit().editSpec().editTidb().withReplicas(count).endTidb().endSpec().done();
}
@Override
protected RollingUpdater<TidbCluster, TidbClusterList, DoneableTidbCluster> getRollingUpdater(long rollingTimeout, TimeUnit rollingTimeUnit) {
return null;
}
@Override
protected int getCurrentReplicas(TidbCluster current) {
return 0;
}
@Override
protected int getDesiredReplicas(TidbCluster item) {
return 0;
}
@Override
protected long getObservedGeneration(TidbCluster current) {
return 0;
}
@Override
public String getLog() {
return null;
}
@Override
public String getLog(Boolean isPretty) {
return null;
}
@Override
public NonNamespaceOperation<TidbCluster, TidbClusterList, DoneableTidbCluster, RollableScalableResource<TidbCluster, DoneableTidbCluster>> inNamespace(String namespace) {
return super.inNamespace(namespace);
}
}
3. 在AppsAPIGroupDSL接口中添加自定义的方法:
public interface AppsAPIGroupDSL extends Client {
MixedOperation<DaemonSet, DaemonSetList, DoneableDaemonSet, Resource<DaemonSet, DoneableDaemonSet>> daemonSets();
MixedOperation<Deployment, DeploymentList, DoneableDeployment, RollableScalableResource<Deployment, DoneableDeployment>> deployments();
MixedOperation<ReplicaSet, ReplicaSetList, DoneableReplicaSet, RollableScalableResource<ReplicaSet, DoneableReplicaSet>> replicaSets();
MixedOperation<StatefulSet, StatefulSetList, DoneableStatefulSet, RollableScalableResource<StatefulSet, DoneableStatefulSet>> statefulSets();
MixedOperation<TidbCluster, TidbClusterList, DoneableTidbCluster, RollableScalableResource<TidbCluster, DoneableTidbCluster>> tidbClusters();
}
4. 在AppsAPIGroupClient中实现第三步添加的接口
public class AppsAPIGroupClient extends BaseClient implements AppsAPIGroupDSL {
public AppsAPIGroupClient() throws KubernetesClientException {
super();
}
@Override
public MixedOperation<TidbCluster, TidbClusterList, DoneableTidbCluster, RollableScalableResource<TidbCluster, DoneableTidbCluster>> tidbClusters() {
return new TidbClusterOperationsImpl(httpClient, getConfiguration());
}
}
5. 打包收工
开发过程中遇到的问题
发先调用的接口报错404,这个很明显就是 请求的地址和CRD 定义的端点对不上所以导致的。
在排查问题的时候发现api service 存在自定义的api 端点,但是发生了疑问 api server 和api service 的区别,api service 就是资源端点的统称,用于查看k8s api 端点(endpoint)的。
问题原因是在初始化TidbClusterOperationsImpl中配置的地址错误的,如图:
我们需要执行“curl https://10.0.100.51:6443/apis --cacert /etc/kubernetes/ssl/ca.pem --key /etc/kubernetes/ssl/ca-key.pem --cert /etc/kubernetes/ssl/ca.pem” 找到自定义的端点然后执行
“curl https://10.0.100.51:6443/apis/pingcap.com/v1alpha1 --cacert /etc/kubernetes/ssl/ca.pem --key /etc/kubernetes/ssl/ca-key.pem --cert /etc/kubernetes/ssl/ca.pem ” 找到具体的端点, 代码中的
withPlural 字段对应如下请求返回结果内容的name字段。
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "pingcap.com/v1alpha1",
"resources": [
{
"name": "tidbinitializers",
"singularName": "tidbinitializer",
"namespaced": true,
"kind": "TidbInitializer",
"verbs": [
"delete",
"deletecollection",
"get",
"list",
"patch",
"create",
"update",
"watch"
],
"shortNames": [
"ti"
],
"storageVersionHash": "CaLrWV3R+xo="
},
{
"name": "tidbclusters",
"singularName": "tidbcluster",
"namespaced": true,
"kind": "TidbCluster",
"verbs": [
"delete",
"deletecollection",
"get",
"list",
"patch",
"create",
"update",
"watch"
],
"shortNames": [
"tc"
],
"storageVersionHash": "2dlERqlmc8s="
},
{
"name": "backupschedules",
"singularName": "backupschedule",
"namespaced": true,
"kind": "BackupSchedule",
"verbs": [
"delete",
"deletecollection",
"get",
"list",
"patch",
"create",
"update",
"watch"
],
"shortNames": [
"bks"
],
"storageVersionHash": "AxduQBp2nn8="
},
{
"name": "tidbmonitors",
"singularName": "tidbmonitor",
"namespaced": true,
"kind": "TidbMonitor",
"verbs": [
"delete",
"deletecollection",
"get",
"list",
"patch",
"create",
"update",
"watch"
],
"shortNames": [
"tm"
],
"storageVersionHash": "2FNzNjpjaWs="
},
{
"name": "tidbclusterautoscalers",
"singularName": "tidbclusterautoscaler",
"namespaced": true,
"kind": "TidbClusterAutoScaler",
"verbs": [
"delete",
"deletecollection",
"get",
"list",
"patch",
"create",
"update",
"watch"
],
"shortNames": [
"ta"
],
"storageVersionHash": "zzMOZyvnKbk="
},
{
"name": "backups",
"singularName": "backup",
"namespaced": true,
"kind": "Backup",
"verbs": [
"delete",
"deletecollection",
"get",
"list",
"patch",
"create",
"update",
"watch"
],
"shortNames": [
"bk"
],
"storageVersionHash": "1U4MtjV1NUI="
},
{
"name": "restores",
"singularName": "restore",
"namespaced": true,
"kind": "Restore",
"verbs": [
"delete",
"deletecollection",
"get",
"list",
"patch",
"create",
"update",
"watch"
],
"shortNames": [
"rt"
],
"storageVersionHash": "CJXYT9H0VM4="
}
]
}
奉上源码地址:https://github.com/XWTiger/kubernetes-client/blob/main/README.md
更多推荐
所有评论(0)