This solution key ideas are:
Job/Tfjob/PytorchJob Pod. Collector container collects metrics from worker container by parsing file or http based on metrics source, and persist metrics into persistent server, such as katib-db by Katib-manager or metadata server;TrialMetrics CRD to trace metrics of trial by Kubernetes native way;Katib-Metrics-Manager server to poll metrics of running Trial from metric persistent server and update related TrialMetrics CR. Trial controller updates trial's status based on corresponding TrialMetrics CR;See the architecture as below:

type ExperimentSpec struct {
...
MetricsCollectorSpec *MetricsCollectorSpec `json:"metricsCollectorSpec,omitempty"`
...
}
type MetricsCollectorSpec struct {
Source *SourceSpec `json:"source,omitempty"`
Collector *CollectorSpec `json:"collector,omitempty"`
}
type SourceSpec struct {
// maybe model-train source code exposes metrics by http, such as HTTP endpoint in prometheus metric format
HttpGet *v1.HTTPGetAction `json:"httpGet,omitempty"`
// during training model, metrics maybe be persistent into local file in source code, such as tfevent
FileSystemPath *FileSystemPath `json:"fileSystemPath,omitempty"`
Filter *FilterSpec `json:"filter,omitempty"`
}
type FilterSpec struct {
// when the metrics output follows format as this field specified, metricsCollector collects it and report to metrics server
MetricsFormat []string `json:"metricsFormat,omitempty"`
}
type FileSystemKind string
const (
DirectoryKind FileSystemKind = "diretory"
FileKind FileSystemKind = "file"
)
type FileSystemPath struct {
Path string `json:"path,omitempty"`
Kind FileSystemKind `json:"kind,omitempty"`
}
type CollectorKind string
const (
StdOutCollector CollectorKind = "stdOutCollector"
FileCollector CollectorKind = "fileCollector"
TfEventCollector CollectorKind = "tfEventCollector"
PrometheusMetricCollector CollectorKind = "prometheusMetricCollector"
CustomCollector CollectorKind = "customCollector"
NoneCollector CollectorKind = "noneCollector"
)
type CollectorSpec struct {
Kind CollectorKind `json:"kind"`
CustomCollector *v1.Container `json:"customCollector,omitempty"`
}
type TrialMetrics struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec TrialMetricsSpec `json:"spec,omitempty"`
Status TrialMetricsStatus `json:"status,omitempty"`
}
type TrialMetricsSpec struct {
MetricNames []string `json:"metricNames,omitempty"`
}
type TrialMetricsStatus struct {
StartTime *metav1.Time `json:"startTime,omitempty"`
CompletionTime *metav1.Time `json:"completionTime,omitempty"`
LastReconcileTime *metav1.Time `json:"lastReconcileTime,omitempty"`
Observation *common.Observation `json:"observation,omitempty"`
Epoch *int32 `json:"epoch,omitempty"`
Step *int32 `json:"step,omitempty"`
}
StdOutCollector, FileCollector, TfEventCollector and PrometheusMetricCollector (low priority), katib will maintain their collector implementations and build related images;StdOutCollector, FileCollector will be similar as current default metrics collector, and TfEventCollector keeps same as current tfevent metrics collector. If MetricsCollectorSpec field in Experiment is nil, we will take it as StdOutCollector collector by default.katib-config to match images for each collector kind (not including customCollector), experiment controller will inject collector sidecar container into TrialTemplate (maybe it's better trial controller injects collector sidecar container into woker job)observation_logs table as below, if epoch and step are missed, early stopping feature will not support the related experiment. By default, metricCollectors will try to parse output like {"metric": "<choose_metric_name>", "value": <int_or_float>, "epoch": <int>, "step": <int>}, and "epoch" and "step" are optional if the train doesn't need early stopping feature. (trial_name VARCHAR(255) NOT NULL,
id INT AUTO_INCREMENT PRIMARY KEY,
time DATETIME(6),
metric_name VARCHAR(255) NOT NULL,
value TEXT NOT NULL,
epoch INT,
step INT,
FOREIGN KEY (trial_name) REFERENCES trials(name) ON DELETE CASCADE)
metricsCollectorSpec is nil, we can take it as this case by default):apiVersion: "kubeflow.org/v1alpha2"
kind: Experiment
metadata:
namespace: kubeflow
name: random-experiment
spec:
...
metricsCollectorSpec:
collector:
kind: stdOutCollector
...
apiVersion: "kubeflow.org/v1alpha2"
kind: Experiment
metadata:
namespace: kubeflow
name: random-experiment
spec:
...
metricsCollectorSpec:
source:
fileSystemPath:
kind: file
path: "/var/log/train.log"
collector:
kind: fileCollector
...
apiVersion: "kubeflow.org/v1alpha2"
kind: Experiment
metadata:
namespace: kubeflow
name: random-experiment
spec:
...
metricsCollectorSpec:
source:
fileSystemPath:
kind: file
path: "/var/log/train.log"
collector:
kind: customCollector
customCollector:
image: xx-repo/xx-user/user-custom-collector-image
...
...
apiVersion: "kubeflow.org/v1alpha2"
kind: Experiment
metadata:
namespace: kubeflow
name: random-experiment
spec:
...
metricsCollectorSpec:
source:
fileSystemPath:
kind: directory
path: "/var/log/train/"
collector:
kind: tfEventCollector
apiVersion: "kubeflow.org/v1alpha2"
kind: Experiment
metadata:
namespace: kubeflow
name: random-experiment
spec:
...
metricsCollectorSpec:
source:
httpGet:
path: /metrics
port: 8080
collector:
kind: PrometheusMetricCollector
...
apiVersion: "kubeflow.org/v1alpha2"
kind: Experiment
metadata:
namespace: kubeflow
name: random-experiment
spec:
...
metricsCollectorSpec:
collector:
kind: noneCollector
...
apiVersion: "kubeflow.org/v1alpha2"
kind: Experiment
metadata:
namespace: kubeflow
name: random-experiment
spec:
...
metricsCollectorSpec:
source:
fileSystemPath:
kind: file
path: "/var/log/train.log"
filter:
metricsFormat:
- "{{metricName}}:{{metricValue}}"
- "{{metricName}} is {{metricValue}}"
- "{{metricName}} = {{metricValue}}"
collector:
kind: fileCollector
...
Considering that when run a trial whose worker is PytorchJob/TfJob, there may be multiple Pods which will run model train task and output metrics. That is, duplicate metrics may be generated. To avoid metrics duplication for a trial, metricsCollector as sidecar container will work to collect metrics and then report them into kabit-manger as below:
1). for PytorchJob, only metricsCollector sidecar container in Master Pod takes effect;
2). for TfJob, If Master exists, only metricsCollector sidecar container in Master Pod takes effect; Else only metricsCollector sidecar container in Worker Pod whose index is 0 takes effect;
MetricsCollector sidecar container should be only injected into corresponding Pod as above by MutatingWebhook for Pod level. However, since in a cluster, Pod level webhook will degrade cluster performance for frequent operations of Pod, so we will just inject MetricsCollector sidecar container into trial's Job level by TfJob/PytorchJob MutatingWebhook:
1) For PytorchJob, inject MetricsCollector sidecar container into Master template;
2) For TfJob, inject MetricsCollector sidecar container into Master template if Master exists; else inject MetricsCollector sidecar container into Worker template, and when running, MetricsCollector will do nothing if it is not like {'task': {'type': 'worker', 'index': 0}} from TF_CONFIG ENV variable.
Since metrics collector as above mentioned can not only be applied in Katib (persist metrics value into kabit data backend) but also useful in many other aspect, for exameple used to early stop for tainning, training monitor, training process visualization, etc.
So we can make metrics collector more general. For example, tfjob/pytorchjob can also add MetricsCollectorSpec field as above in spec. To increase portability of metricCollector, we need think more for metricCollector interface or API to support below configurable:
LGTM, generally. Let's discuss it today
For injecting metrics collector sidecar, @gaocegege and I have another implementation:
The MutatingWebHook of metrics collector can decide which pod to be injected sidecar based on labels, for example, the label like kubeflow.org/replica-role: master. The labels are tagged by job operators (e.x. TFjob/PyTorchjob). The sidecar will be injected only if the webhook recognize this label.
In this new design,
Can you explain what component is referred to "MutatingWebHook of metrics collector"?
@johnugeorge I think we should say Mutating Webhook Server of Katib
Summary: In the worst case, we use a pod level webhook to inject sidecar into pod. In Kubernetes 1.5, the webhook supports objectSelector, then we do not have the performance problem.
In istio solution, if a namespace has "istio-injection=enabled" label, all pods in the namespace will be injected by pod level webhook and its performance influence is little.
So I think maybe we can handle it by like this:
Yeah, I think so. In the current version, it is the best way I think.
then @wuchunghsuan please update sidecar injection solution in design doc in two levels as above. thanks
OK, I got it.
Per talk by slack, in this solution, we just focus on metricsCollector:
So periodic metrics will be discussed in early-stopping topic and TrialMetrics also be dropped here.
Pod level webhook for sidecar
Can you take the task @wuchunghsuan
Can talk to apiserver, but have some problems
Hi folks what are you planning on delivering in 0.7?
Hi folks what are you planning on delivering in 0.7?
@jlewi I open #728 to trace what we plan to deliver in 0.7
for this solution, we add a metricCollector sidecar container into worker Pod. metricCollector sidecar container need collect the metrics, it also need know that worker container has finished so that it can exit, too. otherwise the Pod will keep Running since metricCollector sidecar container is running.
So we should notify metricCollector sidecar container when to exit. maybe we can implement it by below solutions:
share process namespace upgraded to beta version when 1.12, we should also consider that katib runs on k8s version older than 1.12Pod watch/get role, since the experiment can be created in any namespace, we must create serviceAccount with the role in corresponding namespace(katib-controller need getOrCreateServiceAccount in the namespace and set it as serviceaccount of the Pod while inject metrics sidecar container, if the original Pod already have a serviceaccount with non-default service-account-token, it is not easy to handle this case since only one service-account-token will be applied in the container).watch/get Pod role, it has a rest/grpc api which accepts argument (metrics-collector-client-side, podNamespacedName), when a worker Pod starts, metrics-collector container call the rest/grpc api with argument like ($podIP:8000, kubeflow/random-experiment-xztf8nph-master-0), then katib-controller starts a goroutine to watch the kubeflow/random-experiment-xztf8nph-master-0 Pod, once it finds that any container except metricsCollectors have terminated, it will send a message to $podIP:8000 (metricsCollector listen to this port), and when metricsCollectors get this message, it exit with 0. For this solution, the long-running service(maybe katib-controller) may have many goroutine to watch Pod, it consumes much resource.exitcode=`run the original command`;
send an end message to 127.0.0.1:8000 or generate a shared file;
exit $exitcode;
@richardsliu @johnugeorge @gaocegege IMO, maybe we have to implement solution 3. but I need your suggestion, too.
Thanks for the research!
we should also consider that katib runs on k8s version older than 1.12
I am not sure about it.
Personally, I prefer the first option.
Per talk with team, we decide to choose share process namespace solution to solve this problem
Option 1 seems to the best option as it has no other dependency and it is specifically meant for the same purpose.
@hougangliu @johnugeorge any update on this?
@hougangliu @johnugeorge any update on this?
@jlewi for now, metrics collector can work well by pod level metricsCollector container sidecar injection (also included in 0.7.0 release).
considering potential performance impact, we plan to also support to inject metricsCollector container in Job/TfJob/PytorchJob level in next release (will start to discuss in katib weekly meeting)
I think we can safely remove 0.7 label and add 1.0 since high priority features are implemented now.
@gaocegege and @hougangliu should we file more fine grained issues for the remaining work and then close this issue?
@jlewi #929 and #928 trace the remaining two issue.
Most helpful comment
Per talk with team, we decide to choose share process namespace solution to solve this problem