Managing Data I/O in Container Tasks
In flyte-sdk, the ContainerTask class provides a mechanism for executing arbitrary containerized workloads. It manages the mapping between Flyte task inputs/outputs and the container's filesystem, allowing you to run existing binaries or scripts within a Flyte workflow.
Defining a Container Task
To create a container task, instantiate flyte.extras.ContainerTask. You must specify the container image, the command to run, and the expected inputs and outputs.
from flyte.extras import ContainerTask
from flyte.image import Image
import pathlib
# Define a task that runs a shell script inside a container
my_container_task = ContainerTask(
name="bash-task",
image=Image.from_base("ubuntu:latest"),
command=["/bin/bash", "-c"],
arguments=["echo 'hello world' > /var/outputs/res"],
inputs={"val": int},
outputs={"res": str},
input_data_dir="/var/inputs",
output_data_dir="/var/outputs",
metadata_format="JSON"
)
Configuring Input and Output Directories
ContainerTask uses specific directories inside the container to exchange data with the Flyte engine. By default, these are:
input_data_dir:/var/inputsoutput_data_dir:/var/outputs
When the task executes, Flyte mounts the input data into the input_data_dir. The task is expected to write its results into the output_data_dir.
Data Loading Formats
The metadata_format parameter determines how Flyte serializes and deserializes the data. Supported formats include:
JSON(Default)YAMLPROTO
This configuration is captured in the DataLoadingConfig during task serialization:
# Internal representation of data loading configuration
config = my_container_task.data_loading_config(serialization_context)
# Result: tasks_pb2.DataLoadingConfig(input_path="/var/inputs", output_path="/var/outputs", format=JSON)
Handling File and Directory Inputs
When working with flyte.io.File or flyte.io.Dir types, flyte-sdk handles the volume mounting automatically. However, you must use a specific path-like syntax in your command to reference these inputs.
Correct Path Syntax
Instead of using template syntax like {{.inputs.my_file}}, you must reference the file by its expected path within the input_data_dir.
from flyte.io import File
file_task = ContainerTask(
name="file-processor",
image=Image.from_base("alpine"),
command=["cat"],
# Use the path where the file will be mounted: input_data_dir / input_name
arguments=["/var/inputs/data_file"],
inputs={"data_file": File},
input_data_dir="/var/inputs"
)
If you use template syntax for File or Dir types, flyte-sdk will raise an AssertionError during command rendering to prevent execution failures in the Flyte backend.
Local Execution with Docker
For local testing, ContainerTask.execute uses the local Docker daemon to run the container.
- Volume Binding: flyte-sdk creates a temporary local directory and binds it to the container's
output_data_dir. - Image Management: It automatically pulls the required image if it is not present locally using
client.images.pull(uri). - Log Streaming: If
local_logs=True(default), container logs are printed to the console with the prefix[Local Container].
# Local execution requires Docker to be installed
# result = await my_container_task.execute(val=10)
Troubleshooting and Constraints
- Docker Dependency: Local execution will fail with an
ImportErrorif thedockerPython package is not installed, or an error if the Docker daemon is not reachable. - Template Syntax: Template syntax
{{.inputs.key}}is only supported for primitive types (strings, integers, etc.). ForFileandDirtypes, you must use the explicit path/var/inputs/<key>. - Execution Timeout: The current implementation of
executewaits indefinitely for the container to finish (container.wait()). Ensure your containerized process has its own internal timeouts if necessary. - Output Parsing: flyte-sdk parses outputs by reading files from the
output_data_dirnamed after the output keys. For example, ifoutputs={"res": str}, the task must write to/var/outputs/res.