Strimzi-Kafka-Operator

从不同的角度看下Operator解决的问题

Kafka管理Operator-https://github.com/strimzi/strimzi-kafka-operator

部署安装Operator https://strimzi.io/downloads/

部署完成后生成一堆的CRD和一个Controller,基本Operator都是一样套路,strimzi-cluster-operator已具备了对各个CRD的操作监管

接下来就是部署Kafka集群了,当前是只能在内部访问的例子

apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: my-cluster
spec:
kafka:
version: 3.4.0
replicas: 2
listeners:
- name: plain
port: 9092
type: internal
tls: false
- name: tls
port: 9093
type: internal
tls: true
config:
offsets.topic.replication.factor: 1
transaction.state.log.replication.factor: 1
transaction.state.log.min.isr: 1
default.replication.factor: 1
min.insync.replicas: 1
inter.broker.protocol.version: "3.4"
storage:
type: jbod
volumes:
- id: 0
type: persistent-claim
size: 10Gi
class: disk
deleteClaim: true
zookeeper:
replicas: 1
storage:
type: persistent-claim
size: 10Gi
class: disk
deleteClaim: true
entityOperator:
topicOperator: {}
userOperator: {}

创建完之后等一会儿,将会看到zk以及若干的容器、服务被创建

如果需要通过Nodeport访问,listeners修改为以下配置,增加external相关配置

    listeners:
- name: plain
port: 9092
type: internal
tls: false
- name: tls
port: 9093
type: internal
tls: true
- name: external
port: 9094
type: nodeport
tls: false
configuration:
bootstrap:
nodePort: 32094

可以统一看下各个资源的情况

这里看到已经生成了对应的Nodeport类型的服务

查看创建的Kafka对象状态,可以看到具体的状态信息

这里可以看到地址列表已经是NodeportIP地址了

如果Nodeport需要制定外部IP

    listeners:
- name: plain
port: 9092
tls: false
type: internal
- name: tls
port: 9093
tls: true
type: internal
- configuration:
bootstrap:
nodePort: 32094
brokers:
- advertisedHost: 47.100.168.xxx
broker: 0
name: external
port: 9094
tls: false
type: nodeport

-------

切换到Kafka容器内部看下,cat custom-config/server.config

通过advertised.listeners配置对外开放地址端口,这里Nodeport类型对外开放默认地址就是Nodeip

./bin/kafka-run-class.sh org.apache.zookeeper.ZooKeeperMainWithTlsSupportForKafka -server localhost:12181

查看zk broker监听,可以看到external

Nodeport访问IP是通过Operator+kafka自身的advertised.listeners实现的,与前文中redis集群逻辑不同,已经从自身解决了podIP外部不可访问的问题

下图参考自:https://blog.csdn.net/weixin_45505313/article/details/122155973#_2,这位网友画的很好

由于要有状态,所以需要先准备云盘,设置默认存储
kubectl patch storageclass alicloud-disk-ssd -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

-----

从Operator的角度,扫一眼代码,与功能相对应,逻辑主要是按包分成三块儿:cluster-operator(管理集群)/topic-operator(管理主题)/user-operator(管理用户)

都有一定的相似性,下面主要看下 cluster-operator(逻辑代码在cluster-operator,部署Yaml在install/cluster-operator)

cluster-operator部署模版在 060-Deployment-strimzi-cluster-operator.yaml 其中指定了启动脚本为cluster_operator_run.sh->launch_java.sh 启动Main

注册CRD,读取配置,启动监控采集相关组件,安装必要组件,启动operator

static {
try {
Crds.registerCustomKinds();
} catch (Error | RuntimeException t) {
t.printStackTrace();
}
} /**
* The main method used to run the Cluster Operator
*
* @param args The command line arguments
*/
public static void main(String[] args) {
final String strimziVersion = Main.class.getPackage().getImplementationVersion();
LOGGER.info("ClusterOperator {} is starting", strimziVersion);
Util.printEnvInfo(); // Prints configured environment variables
ClusterOperatorConfig config = ClusterOperatorConfig.fromMap(System.getenv());
LOGGER.info("Cluster Operator configuration is {}", config);
// 启动采集、监控相关组件
KubernetesClient client = new OperatorKubernetesClientBuilder("strimzi-cluster-operator", strimziVersion).build(); maybeCreateClusterRoles(vertx, config, client)
.compose(i -> startHealthServer(vertx, metricsProvider))
.compose(i -> leaderElection(client, config))
.compose(i -> createPlatformFeaturesAvailability(vertx, client))
.compose(pfa -> deployClusterOperatorVerticles(vertx, client, metricsProvider, pfa, config))
.onComplete(res -> {
if (res.failed()) {
LOGGER.error("Unable to start operator for 1 or more namespace", res.cause());
System.exit(1);
}
});
}

构建Operator,初始化ClusterOperator(extends AbstractVerticle)

 /**
* Deploys the ClusterOperator verticles responsible for the actual Cluster Operator functionality. One verticle is
* started for each namespace the operator watched. In case of watching the whole cluster, only one verticle is started.
*
* @param vertx Vertx instance
* @param client Kubernetes client instance
* @param metricsProvider Metrics provider instance
* @param pfa PlatformFeaturesAvailability instance describing the Kubernetes cluster
* @param config Cluster Operator configuration
*
* @return Future which completes when all Cluster Operator verticles are started and running
*/
static CompositeFuture deployClusterOperatorVerticles(Vertx vertx, KubernetesClient client, MetricsProvider metricsProvider, PlatformFeaturesAvailability pfa, ClusterOperatorConfig config) {
ResourceOperatorSupplier resourceOperatorSupplier = new ResourceOperatorSupplier(
vertx,
client,
metricsProvider,
pfa,
config.getOperationTimeoutMs(),
config.getOperatorName()
); // Initialize the PodSecurityProvider factory to provide the user configured provider
PodSecurityProviderFactory.initialize(config.getPodSecurityProviderClass(), pfa); KafkaAssemblyOperator kafkaClusterOperations = null;
KafkaConnectAssemblyOperator kafkaConnectClusterOperations = null;
KafkaMirrorMaker2AssemblyOperator kafkaMirrorMaker2AssemblyOperator = null;
KafkaMirrorMakerAssemblyOperator kafkaMirrorMakerAssemblyOperator = null;
KafkaBridgeAssemblyOperator kafkaBridgeAssemblyOperator = null;
KafkaRebalanceAssemblyOperator kafkaRebalanceAssemblyOperator = null; if (!config.isPodSetReconciliationOnly()) {
OpenSslCertManager certManager = new OpenSslCertManager();
PasswordGenerator passwordGenerator = new PasswordGenerator(12,
"abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"0123456789"); kafkaClusterOperations = new KafkaAssemblyOperator(vertx, pfa, certManager, passwordGenerator, resourceOperatorSupplier, config);
kafkaConnectClusterOperations = new KafkaConnectAssemblyOperator(vertx, pfa, resourceOperatorSupplier, config);
kafkaMirrorMaker2AssemblyOperator = new KafkaMirrorMaker2AssemblyOperator(vertx, pfa, resourceOperatorSupplier, config);
kafkaMirrorMakerAssemblyOperator = new KafkaMirrorMakerAssemblyOperator(vertx, pfa, certManager, passwordGenerator, resourceOperatorSupplier, config);
kafkaBridgeAssemblyOperator = new KafkaBridgeAssemblyOperator(vertx, pfa, certManager, passwordGenerator, resourceOperatorSupplier, config);
kafkaRebalanceAssemblyOperator = new KafkaRebalanceAssemblyOperator(vertx, resourceOperatorSupplier, config);
} @SuppressWarnings({ "rawtypes" })
List<Future> futures = new ArrayList<>(config.getNamespaces().size());
for (String namespace : config.getNamespaces()) {
Promise<String> prom = Promise.promise();
futures.add(prom.future());
ClusterOperator operator = new ClusterOperator(namespace,
config,
client,
kafkaClusterOperations,
kafkaConnectClusterOperations,
kafkaMirrorMakerAssemblyOperator,
kafkaMirrorMaker2AssemblyOperator,
kafkaBridgeAssemblyOperator,
kafkaRebalanceAssemblyOperator,
resourceOperatorSupplier);
vertx.deployVerticle(operator,
res -> {
if (res.succeeded()) {
if (config.getCustomResourceSelector() != null) {
LOGGER.info("Cluster Operator verticle started in namespace {} with label selector {}", namespace, config.getCustomResourceSelector());
} else {
LOGGER.info("Cluster Operator verticle started in namespace {} without label selector", namespace);
}
} else {
LOGGER.error("Cluster Operator verticle in namespace {} failed to start", namespace, res.cause());
}
prom.handle(res);
});
}
return CompositeFuture.join(futures);
}

启动Operator

@Override
public void start(Promise<Void> start) {
LOGGER.info("Starting ClusterOperator for namespace {}", namespace); // Configure the executor here, but it is used only in other places
sharedWorkerExecutor = getVertx().createSharedWorkerExecutor("kubernetes-ops-pool", config.getOperationsThreadPoolSize(), TimeUnit.SECONDS.toNanos(120)); @SuppressWarnings({ "rawtypes" })
List<Future> startFutures = new ArrayList<>(8);
startFutures.add(maybeStartStrimziPodSetController()); if (!config.isPodSetReconciliationOnly()) {
List<AbstractOperator<?, ?, ?, ?>> operators = new ArrayList<>(asList(
kafkaAssemblyOperator, kafkaMirrorMakerAssemblyOperator,
kafkaConnectAssemblyOperator, kafkaBridgeAssemblyOperator, kafkaMirrorMaker2AssemblyOperator));
for (AbstractOperator<?, ?, ?, ?> operator : operators) {
startFutures.add(operator.createWatch(namespace, operator.recreateWatch(namespace)).compose(w -> {
LOGGER.info("Opened watch for {} operator", operator.kind());
watchByKind.put(operator.kind(), w);
return Future.succeededFuture();
}));
} startFutures.add(AbstractConnectOperator.createConnectorWatch(kafkaConnectAssemblyOperator, namespace, config.getCustomResourceSelector()));
startFutures.add(kafkaRebalanceAssemblyOperator.createRebalanceWatch(namespace));
} CompositeFuture.join(startFutures)
.compose(f -> {
LOGGER.info("Setting up periodic reconciliation for namespace {}", namespace);
this.reconcileTimer = vertx.setPeriodic(this.config.getReconciliationIntervalMs(), res2 -> {
if (!config.isPodSetReconciliationOnly()) {
LOGGER.info("Triggering periodic reconciliation for namespace {}", namespace);
reconcileAll("timer");
}
}); return Future.succeededFuture((Void) null);
})
.onComplete(start);
}

监听器事件处理逻辑

    @Override
public void eventReceived(Action action, T resource) {
String name = resource.getMetadata().getName();
String namespace = resource.getMetadata().getNamespace();
switch (action) {
case ADDED:
case DELETED:
case MODIFIED:
Reconciliation reconciliation = new Reconciliation("watch", operator.kind(), namespace, name);
LOGGER.infoCr(reconciliation, "{} {} in namespace {} was {}", operator.kind(), name, namespace, action);
operator.reconcile(reconciliation);
break;
case ERROR:
LOGGER.errorCr(new Reconciliation("watch", operator.kind(), namespace, name), "Failed {} {} in namespace{} ", operator.kind(), name, namespace);
operator.reconcileAll("watch error", namespace, ignored -> { });
break;
default:
LOGGER.errorCr(new Reconciliation("watch", operator.kind(), namespace, name), "Unknown action: {} in namespace {}", name, namespace);
operator.reconcileAll("watch unknown", namespace, ignored -> { });
}
}

定期处理reconcile,如注解描述~

    /**
Periodical reconciliation (in case we lost some event)
*/
private void reconcileAll(String trigger) {
if (!config.isPodSetReconciliationOnly()) {
Handler<AsyncResult<Void>> ignore = ignored -> {
};
kafkaAssemblyOperator.reconcileAll(trigger, namespace, ignore);
kafkaMirrorMakerAssemblyOperator.reconcileAll(trigger, namespace, ignore);
kafkaConnectAssemblyOperator.reconcileAll(trigger, namespace, ignore);
kafkaMirrorMaker2AssemblyOperator.reconcileAll(trigger, namespace, ignore);
kafkaBridgeAssemblyOperator.reconcileAll(trigger, namespace, ignore);
kafkaRebalanceAssemblyOperator.reconcileAll(trigger, namespace, ignore);
}
}

KafkaAssemblyOperator

 /**
* Run the reconciliation pipeline for Kafka
*
* @param clock The clock for supplying the reconciler with the time instant of each reconciliation cycle.
* That time is used for checking maintenance windows
*
* @return Future with Reconciliation State
*/
Future<ReconciliationState> reconcileKafka(Clock clock) {
return kafkaReconciler()
.compose(reconciler -> reconciler.reconcile(kafkaStatus, clock))
.map(this);
}

执行流

    /**
* The main reconciliation method which triggers the whole reconciliation pipeline. This is the method which is
* expected to be called from the outside to trigger the reconciliation.
*
* @param kafkaStatus The Kafka Status class for adding conditions to it during the reconciliation
* @param clock The clock for supplying the reconciler with the time instant of each reconciliation cycle.
* That time is used for checking maintenance windows
*
* @return Future which completes when the reconciliation completes
*/
public Future<Void> reconcile(KafkaStatus kafkaStatus, Clock clock) {
return modelWarnings(kafkaStatus)
.compose(i -> manualPodCleaning())
.compose(i -> networkPolicy())
.compose(i -> manualRollingUpdate())
.compose(i -> pvcs())
.compose(i -> serviceAccount())
.compose(i -> initClusterRoleBinding())
.compose(i -> scaleDown())
.compose(i -> listeners())
.compose(i -> certificateSecret(clock))
.compose(i -> brokerConfigurationConfigMaps())
.compose(i -> jmxSecret())
.compose(i -> podDisruptionBudget())
.compose(i -> podDisruptionBudgetV1Beta1())
.compose(i -> migrateFromStatefulSetToPodSet())
.compose(i -> migrateFromPodSetToStatefulSet())
.compose(i -> statefulSet())
.compose(i -> podSet())
.compose(i -> rollToAddOrRemoveVolumes())
.compose(i -> rollingUpdate())
.compose(i -> scaleUp())
.compose(i -> podsReady())
.compose(i -> serviceEndpointsReady())
.compose(i -> headlessServiceEndpointsReady())
.compose(i -> clusterId(kafkaStatus))
.compose(i -> deletePersistentClaims())
.compose(i -> brokerConfigurationConfigMapsCleanup())
// This has to run after all possible rolling updates which might move the pods to different nodes
.compose(i -> nodePortExternalListenerStatus())
.compose(i -> addListenersToKafkaStatus(kafkaStatus));
}

具体进去就是各种状态判断,协调最终一致

其中配置生成在KafkaBrokerConfigurationBuilder里

根据CR内容生成对应的配置文件;

在Reconcile后更新CR状态信息

/**
* Updates the Status field of the Kafka CR. It diffs the desired status against the current status and calls
* the update only when there is any difference in non-timestamp fields.
*
* @param desiredStatus The KafkaStatus which should be set
*
* @return Future which completes when the status subresource is updated
*/
Future<Void> updateStatus(KafkaStatus desiredStatus) {
Promise<Void> updateStatusPromise = Promise.promise(); crdOperator.getAsync(namespace, name).onComplete(getRes -> {
if (getRes.succeeded()) {
Kafka kafka = getRes.result(); if (kafka != null) {
if ((Constants.RESOURCE_GROUP_NAME + "/" + Constants.V1ALPHA1).equals(kafka.getApiVersion())) {
LOGGER.warnCr(reconciliation, "The resource needs to be upgraded from version {} to 'v1beta1' to use the status field", kafka.getApiVersion());
updateStatusPromise.complete();
} else {
KafkaStatus currentStatus = kafka.getStatus(); StatusDiff ksDiff = new StatusDiff(currentStatus, desiredStatus); if (!ksDiff.isEmpty()) {
Kafka resourceWithNewStatus = new KafkaBuilder(kafka).withStatus(desiredStatus).build(); crdOperator.updateStatusAsync(reconciliation, resourceWithNewStatus).onComplete(updateRes -> {
if (updateRes.succeeded()) {
LOGGER.debugCr(reconciliation, "Completed status update");
updateStatusPromise.complete();
} else {
LOGGER.errorCr(reconciliation, "Failed to update status", updateRes.cause());
updateStatusPromise.fail(updateRes.cause());
}
});
} else {
LOGGER.debugCr(reconciliation, "Status did not change");
updateStatusPromise.complete();
}
}
} else {
LOGGER.errorCr(reconciliation, "Current Kafka resource not found");
updateStatusPromise.fail("Current Kafka resource not found");
}
} else {
LOGGER.errorCr(reconciliation, "Failed to get the current Kafka resource and its status", getRes.cause());
updateStatusPromise.fail(getRes.cause());
}
}); return updateStatusPromise.future();
}

其他好文

http://www.javajun.net/posts/55588/

https://blog.csdn.net/weixin_39766667/article/details/128177436

https://blog.csdn.net/prefect_start/article/details/124183531

Strimzi-Kafka-Operator外围小记的更多相关文章

  1. Kafka Ecosystem(Kafka生态)

    http://kafka.apache.org/documentation/#ecosystem https://cwiki.apache.org/confluence/display/KAFKA/E ...

  2. OpenShift 4.1 演示

    功能演示主要包含三个方面. 1. 管理控制台 push镜像发布应用 podman build -t mytomcat:slim . podman tag localhost/mytomcat:slim ...

  3. 基于Apache Hudi和Debezium构建CDC入湖管道

    从 Hudi v0.10.0 开始,我们很高兴地宣布推出适用于 Deltastreamer 的 Debezium 源,它提供从 Postgres 和 MySQL 数据库到数据湖的变更捕获数据 (CDC ...

  4. Kafka小记

    kafka简介 kafka是由LinkedIn开发,主要是用来处理Linkedin的大面积活跃数据流处理(activity stream).  此类的数据经常用来反映网站的一些有用的信息,比如PV,页 ...

  5. [operator]windows10 + zookeeper + kafka

    软件版本 jdk-8u131-windows-x64 zookeeper-3.4.10.tar.gz kafka_2.11-2.0.1.tgz jdk直接就有exe安装版本,不做介绍 安装zookee ...

  6. 小记---------kafka理论及命令行操作

    kafka-0.10.1.X版本之前: auto.offset.reset 的值为smallest,和,largest.(offest保存在zk中)   kafka-0.10.1.X版本之后: aut ...

  7. 小记---------maxwell 一个可以实时读取mysql二进制日志binlog,并生成JSON格式的消息,作为生产者发送给kafka,Redis,文件或其他平台的应用程序

    maxwell主要提供了下列功能     支持 SELECT * FROM table 的方式进行全量数据初始化     支持在主库发生failover后,自动回复binlog位置(GTID)     ...

  8. Kafka【第一篇】Kafka集群搭建

    Kafka初识 1.Kafka使用背景 在我们大量使用分布式数据库.分布式计算集群的时候,是否会遇到这样的一些问题: 我们想分析下用户行为(pageviews),以便我们设计出更好的广告位 我想对用户 ...

  9. sphinx使用小记之使用小结

    sphinx使用小记之使用小结 摘自:http://www.68idc.cn/help/jiabenmake/qita/20150124187789.html 在使用sphinx的过程中有出现一些问题 ...

  10. 实习小记-python 内置函数__eq__函数引发的探索

    乱写__eq__会发生啥?请看代码.. >>> class A: ... def __eq__(self, other): # 不论发生什么,只要有==做比较,就返回True ... ...

随机推荐

  1. Vue项目打包报错 error TS6504

    此处提醒:项目是vite还是vue/cli,打包有区别 打包报错问题: 原因: package.json中,build配置vue-tsc的问题,把对应的命令给删掉: . 语法检查问题: 要么<s ...

  2. C语言printf输出32位十六进制

    long c = 0X1DAB83; //十六进制数字 printf("c=%lx\n", c); //以十六进制形式输出(字母小写) printf("c=%lX\n&q ...

  3. CF850F 题解

    题意 传送门 有一袋 \(n\) 个颜色球,第 \(i\) 个颜色的球有 \(a_i\) 个. 当袋子里至少有两个不同颜色的球时,执行以下步骤: 一个接一个的按照顺序随机取出两个的球,这些球的颜色可能 ...

  4. window.open在打开pdf时直接下载而不是查看

    一般这是url请求的原因导致的, 可以考虑这种写法 window.open(link+'?response-content-type=application/pdf') 加上后面这段可以转为查看

  5. Centos8——Nginx下载安装 & 部署项目

    Centos8--Nginx下载安装 & 部署项目 官网:http://nginx.org/ 官网下载:http://nginx.org/en/download.html 创建文件夹 ps: ...

  6. 集群笔记-fence

    fence机制: 隔离主机到存储的连接 配置fence_xvm步骤 KVM fence 请问物理机器需要真实的fence 设备吗? 否 一.将物理机器(宿主机)f0配置成fence设备 1. 安装fe ...

  7. Expected indentation of 2 spaces but found 4

    预期缩进2个空格,但发现4个 把缩进空格修改后如图

  8. 20191317Exp3-免杀原理与实践

    20191317Exp3-免杀原理与实践 基础问题回答 1.1 杀软是如何检测出恶意代码的? 基于特征码进行检测:杀毒软件的病毒库记录了一些恶意软件的特征码,一段特征码就是一段或多段数据.如果一个可执 ...

  9. Jmeter添加JSR223对Python的支持

    通过下载:org.python : jython-standalone : 2.7.2 - Maven Central Repository Search jython-standalone-2.7. ...

  10. 项目实训DAY6

    今天主要的工作是把功能界面丰富了一下,查阅了一下论文,将页面中添加了可视化元素:同时决定了最后几天的工作计划.