Log Streaming Architecture
Flyte provides a real-time log streaming architecture through its DataProxy service, allowing users to monitor task execution as it happens. This system abstracts the underlying log source, primarily supporting direct streaming from Kubernetes pods while providing a consistent gRPC interface for clients.
The LogStreamer Abstraction
The architecture is built around the LogStreamer interface defined in dataproxy/logs/log_streamer.go. This interface decouples the DataProxy service from specific log backends, enabling the system to support different execution environments.
type LogStreamer interface {
TailLogs(ctx context.Context, logContext *core.LogContext, stream *connect.ServerStream[dataproxy.TailLogsResponse]) error
}
The LogContext parameter contains the necessary metadata to locate logs, such as pod names, namespaces, and container identifiers.
Kubernetes Log Streaming
The K8sLogStreamer class in dataproxy/logs/k8s_log_streamer.go is the primary implementation of this interface. It interacts directly with the Kubernetes API to fetch and stream logs from active or completed pods.
Identifying Targets with LogContext
Before streaming begins, the streamer must identify which pod and container to target. The helper function GetPrimaryPodAndContainer (found in dataproxy/logs/log_streamer.go) extracts this information from the LogContext. It ensures that the PrimaryPodName and PrimaryContainerName specified in the context actually exist within the provided pod and container lists.
Stream Configuration and Lifecycle
The K8sLogStreamer dynamically configures the Kubernetes log request based on the state of the task:
- Follow Logic: The
Followoption is only enabled if the pod is in thePodRunningphase. For pods that arePending,Succeeded, orFailed, the streamer disables following to ensure that existing logs are returned immediately and the stream closes gracefully once the buffer is exhausted. - Time-based Offsets: If the
LogContextprovides aContainerStartTime, the streamer uses theSinceTimeoption in the Kubernetes API. In this case, it clears theTailLinessetting to ensure all logs from the start of the process are captured. Otherwise, it defaults to a tail of 1000 lines (defined bydefaultInitialLines).
Context Management and Timeouts
A critical design choice in K8sLogStreamer is how it handles gRPC deadlines. Standard gRPC requests often have short timeouts, but log "following" can last for hours. To prevent the stream from being killed by the incoming request's deadline, the streamer creates a decoupled context:
// Create a context without the incoming gRPC deadline so long-lived follow
// streams are not killed by a short client/proxy timeout.
streamCtx, streamCancel := context.WithCancel(context.Background())
defer streamCancel()
stop := context.AfterFunc(ctx, streamCancel)
defer stop()
By using context.AfterFunc, the streamer ensures that if the original request context (ctx) is cancelled (e.g., the user closes their browser or CLI), the internal streamCtx is also cancelled, terminating the Kubernetes API request and cleaning up resources.
Data Flow Integration
The log streaming process involves coordination between the DataProxy and the RunService.
- Context Retrieval: When a client calls
TailLogson theDataProxyService(indataproxy/service/dataproxy_service.go), the service first calls theRunService.GetActionLogContextmethod. This retrieves theLogContextwhich contains the Kubernetes-specific identifiers for the task. - Streaming: Once the context is obtained, the
DataProxyServicedelegates the streaming to theLogStreamer. - Batching: The
K8sLogStreameruses thepodlogs.Streamutility fromflytestdlibto read from the Kubernetesio.ReadCloser. This utility batches log lines into chunks (usingpodlogs.DefaultBatchSize) before sending them over the gRPC stream, which reduces the overhead of frequent small network packets.
err = podlogs.Stream(ctx, logStream, podlogs.DefaultBatchSize, func(lines []*dataplane.LogLine) error {
return stream.Send(&dataproxy.TailLogsResponse{
Logs: []*dataproxy.TailLogsResponse_Logs{{Lines: lines}},
})
})
Initialization and Configuration
The K8sLogStreamer is initialized during the DataProxy setup phase. It requires a Kubernetes REST configuration. Notably, the NewK8sLogStreamer constructor explicitly sets the client timeout to zero to support long-lived streams:
func NewK8sLogStreamer(k8sConfig *rest.Config) (*K8sLogStreamer, error) {
cfg := rest.CopyConfig(k8sConfig)
cfg.Timeout = 0 // Ensure long-lived streams are not interrupted
clientset, err := kubernetes.NewForConfig(cfg)
// ...
}