Making task log storage optional for Kubernetes Runner - #18341
Conversation
There was a problem hiding this comment.
Since this effort is more related to have task logs being managed externally I purpose we create a new class like
public class ExternalTaskLogs implements TaskLogs {
private TaskLogs delegate;
private ExternalLogStreamer externalLogStreamer;
@Inject
public ExternalLogStreamer(TaskLogs taskLogs, ExternalLogStreamer externalLogStreamer){
this.delegate = taskLogs;
this.externalLogStreamer = externalLogStreamer;
}
@Override
Optional<InputStream> streamTaskLog(String taskid, long offset) throws IOException {
return externalLogStreamer.streamTaskLog();
};
@Override
Optional<InputStream> streamTaskReports(final String taskid) throws IOException {
return delegate.streamTaskReports();
}
@Override
Optional<InputStream> streamTaskStatus(final String taskid) throws IOException {
return delegate.streamTaskStatus();
}
@Override
void pushTaskLog(String taskid, File logFile) throws IOException {
// do nothing.
};
@Override
void pushTaskReports(String taskid, File reportFile) throws IOException {
delegate.pushTaskReports();
}
@Override
void pushTaskStatus(String taskid, File reportFile) throws IOException {
delegate.pushTaskStatus();
}
{
}
@Override
void killAll() throws IOException {
delegate.killAll();
};
@Override
void killOlderThan(long timestamp) throws IOException {
delegate.killOlderThan();
};
@Override
void pushTaskPayload(String taskid, File taskPayloadFile) throws IOException {
delegate.pushTaskPayload();
}
@Override
Optional<InputStream> streamTaskPayload(String taskid) throws IOException {
return delegate.streamTaskPayload();
}
}
public interface ExternalLogStreamer {
Optional<InputStream> streamTaskLog(String taskid) throws IOException;
}
We can then bind the implementation of the ExternalLogStreamer as we like it.
| public void configure(Binder binder) | ||
| { | ||
| PolyBind.createChoice(binder, "druid.indexer.logs.type", Key.get(TaskLogs.class), Key.get(FileTaskLogs.class)); | ||
| PolyBind.createChoice(binder, "druid.indexer.logs.delegate.type", Key.get(TaskLogs.class, Names.named("delegate")), Key.get(FileTaskLogs.class)); |
There was a problem hiding this comment.
It can be a little confusing to have say these props set on the Overlord:
druid.indexer.logs.type=external
druid.indexer.logs.delegate.type=fileIt is unclear why we need a delegate if we just wanted FileTaskLogs.
I think the intention is to direct task reports, status and payload to one place and logs to another.
So we need some kind of switching or composite task logs type, which would give us these props:
druid.indexer.logs.type=switching
druid.indexer.logs.switching.reportsType=file
druid.indexer.logs.switching.logsType=hdfsWith this, we just need a SwitchingTaskLogs implementation which takes in a reportsType and a logsType, both of which would be TaskLogs implementations.
There was a problem hiding this comment.
To ensure that extensions register themselves correctly as a valid type for say druid.indexer.logs.switching.logsType, you would need to add a utility method in Binders, say bindTaskLogs and invoke it from all the relevant extension modules.
public static <T extends TaskLogs> void bindTaskLogs(Binder binder, String type, Class<T> clazz) {
// bind to `druid.indexer.logs.type`
// bind to `druid.indexer.logs.switching.reportsType`
// bind to `druid.indexer.logs.switching.logsType`
}There was a problem hiding this comment.
druid.indexer.logs.switching.logsType=hdfs
The options I am trying to provide here is different implementations of logStorage vs logStreaming, maybe I could just extend this to druid.indexer.logs.switching.logs.storage and druid.indexer.logs.switching.streaming ?
There was a problem hiding this comment.
I see. Yes, the same switching concept can be extended for that use case as well.
druid.indexer.logs.switching.streaming.logsPushType=noop
druid.indexer.logs.switching.streaming.logsStreamType=filewhere logsPushType would bind to a TaskLogPusher object.
and logsStreamType would bind to a TaskLogStreamer object.
Would you still need to distinguish between reports and logs though?
Because I assume reports might need to be pushed to a different place.
If yes, you would still need a
druid.indexer.logs.switching.streaming.reportsType=noop/file/hdfsThere was a problem hiding this comment.
This sounds good, in my head I am looking to provide the flexibility just for log types for now. The delegated / reportType could take care of other task responsibilities. So finally, imo it should look like one of these.
Option 1 [asking for explicit types for each responsibility]
druid.indexer.logs.type=switching
druid.indexer.logs.switching.reportsType=file
druid.indexer.logs.switching.payloadManagerType=file
druid.indexer.logs.switching.logsType=switching
druid.indexer.logs.switching.logsType.logsPushType=noop
druid.indexer.logs.switching.logsType.logsStreamType=file
translating to
public class SwitchingTaskLogs implements TaskLogs
{
private final TaskLogs reportDelegate;
private final TaskLogs payloadDelegate;
private final TaskLogs logDelegate;
@Inject
public SwitchingTaskLogs(
@Named("report") TaskLogs reportDelegate,
@Named("payload") TaskLogs payloadDelegate,
@Named("log") TaskLogs logDelegate
)
{
this.reportDelegate = reportDelegate;
this.payloadDelegate = payloadDelegate;
this.logDelegate = logDelegate;
}
}
public class TaskLogManager implements TaskLogs // this / some flavour of switching task log will be injected as switching log delegate to handle the task related responsibilities with an appropriate factory placed in front.
{
private final TaskLogStreamer streamer;
private final TaskLogPusher pusher;
@Inject
public TaskLogManager(
@Named("taskLogPusher") TaskLogs pusher,
@Named("taskLogStreamer") TaskLogs streamer,
)
{
this.pusher = pusher;
this.streamer = streamer;
}
}or we could create one like
Option 2 [just keeping log handler separate while keeping the rest of the responsibilities with the delegate class]
druid.indexer.logs.type=switching
druid.indexer.logs.switching.delegate=file
druid.indexer.logs.switching.logsHandlerType=switching
druid.indexer.logs.switching.logHandler.logsPushType=noop
druid.indexer.logs.switching.logHandler.logsStreamType=file
public class SwitchingTaskLogs implements TaskLogs
{
private final TaskLogs delegate;
private final TaskLogs switchingLogDelegate;
@Inject
public SwitchingTaskLogs(
@Named("default") TaskLogs defaultDelegate,
@Named("log") TaskLogs logDelegate,
)
{
this.defaultDelegate = defaultDelegate;
this.logDelegate = logDelegate;
}
}
// the TaskLogManager stays the same.I am personally leaning towards option 2 given the usecase in hand. what do you folks think?
EDIT: managed to make it work with a single SwitchingTaskLogs with factory. I've added the implementation in this PR for the same.
There was a problem hiding this comment.
@uds5501 , I don't think we need two levels of a switching implementation. That would really complicate the configs.
You can provide some decent defaults
When druid.indexer.logs.type=switching
| Property | Default |
|---|---|
druid.indexer.logs.switching.defaultType |
file |
druid.indexer.logs.switching.logPushType |
use defaultType |
druid.indexer.logs.switching.logStreamType |
use defaultType |
druid.indexer.logs.switching.reportsType |
use defaultType |
I don't think report and payload will ever need to be different.
If needed, we can add it in the future.
There was a problem hiding this comment.
Hmm, I see, will use this.
There was a problem hiding this comment.
Using this combination noew.
|
Thanks for the update, @uds5501 ! I will try to take a look at the changes today. |
| { | ||
| } | ||
|
|
||
| default boolean logPushEnabled() |
There was a problem hiding this comment.
It seems weird to have a flag to disable the one thing that this interface is meant to do.
Given that we already have a noop impl, where do we need this?
There was a problem hiding this comment.
The TaskLogPusher has varied responsibilities internally (reports, logs, statuses etc). This boolean helps in the scenario of K8S runners where it creates a temp file before pushing the logs to deep storage. This method helps skip the entire step
| if (adapter != null && !MultiContainerTaskAdapter.TYPE.equals(adapter) && kubernetesTaskRunnerConfig.isSidecarSupport()) { | ||
| if (adapter != null | ||
| && !MultiContainerTaskAdapter.TYPE.equals(adapter) | ||
| && kubernetesTaskRunnerConfig.isSidecarSupport()) { |
Check notice
Code scanning / CodeQL
Deprecated method or constructor invocation Note
| private Properties props; | ||
|
|
||
| @Inject | ||
| public IndexingServiceTaskLogsModule(Properties props) |
There was a problem hiding this comment.
This will help you retain the zero arg constructor.
| public IndexingServiceTaskLogsModule(Properties props) | |
| public setProperties(Properties props) |
There was a problem hiding this comment.
This didn't workout. It seems that configure() happens first and then the @inject is triggered for Modules.
Keeping it as is for the time being.
|
Thanks for fixing up the docker test job, @uds5501 ! As a follow up to this PR, could you please take up the following:
|
…ports (apache#18341) Changes: --------- - Add implementation `SwitchingTaskLogs` - Add properties `druid.indexer.logs.switching.*` - Allow a different target for reports and task logs by using properties `defaultType`, `reportsType`, `logPushType`, `logStreamType`.



Problem Statement
In current task runner setups, we utilize a common
TaskLoginterface to perform all the operations like log pushes, log streams, payload and report pushes. In future, we want to provide the flexibility to the end user to be able to perform the following things simultaneously:Description
The approach in general is this:
SwitchingTaskLogsimplementation that picks up appropriate options from indexer configuration set in overlord and uses a default type to push and stream reports / statuses etc while uses specific implementations for log push. / log stream if configured.configs as suggested by : @kfaraz ^
CliOverlord.javaKubernetesOverlordModule.javaIndexingServiceTaskLogsModule.javaKey changed/added classes in this PR
KubernetesPeonLifecycleCliOverlord.javaKubernetesOverlordModule.javaIndexingServiceTaskLogsModule.javaTODO(s) [notes for author]
Call the binder from all the deep-storage extensions.Verify switching setup with K8S Runners.