Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

How do we wait for container to exit on its own? #33

Closed
jpuris opened this issue Feb 6, 2023 · 6 comments
Closed

How do we wait for container to exit on its own? #33

jpuris opened this issue Feb 6, 2023 · 6 comments

Comments

@jpuris
Copy link

jpuris commented Feb 6, 2023

Given a very simple use case: Prefect flow that runs a container that waits for 30 seconds (sleep 30), there seems to be no way for the flow or task to "wait" for the container to exit on its own.

Minimally reproducible example

python3 -m venv venv && source venv/bin/activate
python -m pip install 'prefect-docker==0.1.0' 'prefect==2.7.11'
python flows/example.py && docker ps -a --filter "name=prefect-docker-mre"

example_flow.py

from prefect import flow, get_run_logger
from prefect_docker.images import pull_docker_image
from prefect_docker.containers import (
    create_docker_container,
    start_docker_container,
    get_docker_container_logs,
    stop_docker_container,
    remove_docker_container,
)


@flow
def docker_flow():
    logger = get_run_logger()
    pull_docker_image("ubuntu", "latest")
    container = create_docker_container(
        image="ubuntu", command="sleep 30"
    )
    start_docker_container(container_id=container.id)
    logs = get_docker_container_logs(container_id=container.id)
    logger.info(logs)
    return container

docker_flow()

What happens

  1. Image is pulled
  2. Container is successfully started
  3. Logs are collected immediately after container start
  4. Container is immediately scheduled for shut down
  5. The kernel kills the container resulting in exit code 137

What is expected to happen

  1. Image is pulled
  2. Container is successfully started
  3. Prefect flow / task waits for container to exit
  4. -- there is no need for stopping container
  5. Full logs are captured

Logs

22:22:04.279 | INFO    | prefect.engine - Created flow run 'literate-manatee' for flow 'docker-flow'
22:22:05.795 | INFO    | Flow run 'literate-manatee' - Created task run 'pull_docker_image-0' for task 'pull_docker_image'
22:22:05.797 | INFO    | Flow run 'literate-manatee' - Executing 'pull_docker_image-0' immediately...
22:22:06.369 | INFO    | Task run 'pull_docker_image-0' - Pulling image: ubuntu:latest.
22:22:07.872 | INFO    | Task run 'pull_docker_image-0' - Finished in state Completed()
22:22:08.197 | INFO    | Flow run 'literate-manatee' - Created task run 'create_docker_container-0' for task 'create_docker_container'
22:22:08.199 | INFO    | Flow run 'literate-manatee' - Executing 'create_docker_container-0' immediately...
22:22:08.834 | INFO    | Task run 'create_docker_container-0' - Creating container with 'ubuntu' image.
22:22:09.062 | INFO    | Task run 'create_docker_container-0' - Finished in state Completed()
22:22:09.319 | INFO    | Flow run 'literate-manatee' - Created task run 'start_docker_container-0' for task 'start_docker_container'
22:22:09.321 | INFO    | Flow run 'literate-manatee' - Executing 'start_docker_container-0' immediately...
22:22:09.886 | INFO    | Task run 'start_docker_container-0' - Starting container '6cdba73ebbf60426f9d27a7fc4de452bc782fa8aa008c4f5ee1e777807903ca7'.
22:22:10.283 | INFO    | Task run 'start_docker_container-0' - Finished in state Completed()
22:22:10.474 | INFO    | Flow run 'literate-manatee' - Created task run 'get_docker_container_logs-0' for task 'get_docker_container_logs'
22:22:10.476 | INFO    | Flow run 'literate-manatee' - Executing 'get_docker_container_logs-0' immediately...
22:22:11.703 | INFO    | Task run 'get_docker_container_logs-0' - Retrieving logs from '6cdba73ebbf60426f9d27a7fc4de452bc782fa8aa008c4f5ee1e777807903ca7' container.
22:22:12.122 | INFO    | Task run 'get_docker_container_logs-0' - Finished in state Completed()
22:22:12.124 | INFO    | Flow run 'literate-manatee' - 
22:22:12.323 | INFO    | Flow run 'literate-manatee' - Created task run 'stop_docker_container-0' for task 'stop_docker_container'
22:22:12.324 | INFO    | Flow run 'literate-manatee' - Executing 'stop_docker_container-0' immediately...
22:22:13.050 | INFO    | Task run 'stop_docker_container-0' - Stopping container '6cdba73ebbf60426f9d27a7fc4de452bc782fa8aa008c4f5ee1e777807903ca7'.
22:22:23.401 | INFO    | Task run 'stop_docker_container-0' - Finished in state Completed()
22:22:23.707 | INFO    | Flow run 'literate-manatee' - Finished in state Completed()
CONTAINER ID   IMAGE     COMMAND      CREATED          STATUS                      PORTS     NAMES
6cdba73ebbf6   ubuntu    "sleep 30"   16 seconds ago   Exited (137) 1 second ago             prefect-docker-mre

As one can see, the "sleep 30" is still running, although the prefect task is done..

How do we have the prefect task "running" for the duration of the container in order to retrieve full logs of the container as well as its status for indication of success or failure?

@ahuang11
Copy link
Contributor

ahuang11 commented Feb 7, 2023

@jpuris Thank for you raising this issue! At the moment, you may hold onto the container and call the wait method. I envision eventually, we will have a flow that will run and wait for completion, like how we have in prefect-dbt.

import time
from prefect import flow, get_run_logger
from prefect_docker.images import pull_docker_image
from prefect_docker.containers import (
    create_docker_container,
    start_docker_container,
    get_docker_container_logs,
    stop_docker_container,
    remove_docker_container,
)
from prefect_docker.host import DockerHost


@flow
def docker_flow():
    logger = get_run_logger()
    docker_host = DockerHost()
    pull_docker_image("ubuntu", "latest", docker_host=docker_host)
    container = create_docker_container(
        image="ubuntu", command="sleep 10", docker_host=docker_host
    )
    container = start_docker_container(container_id=container.id, docker_host=docker_host)
    logger.info(f"Waiting for container to complete...")
    container.wait()
    logs = get_docker_container_logs(container_id=container.id, docker_host=docker_host)
    logger.info(logs)
    return container

docker_flow()

@jpuris
Copy link
Author

jpuris commented Feb 7, 2023

Seems to be working! 🎉
Thank you so much @ahuang11!

Is there any way to retrieve the exit code / error code and/or status from container object?
I'm love to be able to fail / cancel the flow accordingly (I can also create a new issue for this one, if this becomes a feature request)

@ahuang11
Copy link
Contributor

ahuang11 commented Feb 7, 2023

Perhaps container.status? Internally, it uses: https://docker-py.readthedocs.io/en/stable/containers.html#container-objects

@ahuang11
Copy link
Contributor

ahuang11 commented Feb 7, 2023

I suppose the run method could be useful too in addition to create / start.

@jpuris
Copy link
Author

jpuris commented Feb 7, 2023

Perhaps container.status? Internally, it uses: https://docker-py.readthedocs.io/en/stable/containers.html#container-objects

Thanks! I'll check it out and get back with an update.

@jpuris
Copy link
Author

jpuris commented Feb 12, 2023

Perhaps container.status? Internally, it uses: https://docker-py.readthedocs.io/en/stable/containers.html#container-objects

✅ Solved!

⚠️ I was not able to get this done with container.status as it would return created, when container fails during runtime.
Instead looking at the docs you've mentioned, I've decided to use the return of container.wait(), please see below

    logger.info("Waiting for docker container to exit")
    container_status = container.wait()
    if container_status['StatusCode']:
        return Failed(message=f"Docker container has exited with an error! "
                              f"Returned status code '{container_status['StatusCode']}' "
                              f"with error '{container_status['Error']}'")

Full MRE

from prefect import flow, get_run_logger
from prefect.states import Failed
from prefect_docker.containers import (
    create_docker_container,
    start_docker_container,
    get_docker_container_logs,
    remove_docker_container,
)
from prefect_docker.images import pull_docker_image


@flow
def prefect_docker_test():
    logger = get_run_logger()

    docker_image = 'python:alpine3.16'
    logger.info(f"Pulling docker image {docker_image}")
    pull_docker_image(
        repository=docker_image.split(':')[0],
        tag=docker_image.split(':')[1]
    )

    logger.info(f"Starting docker container from '{docker_image}' image")
    container = create_docker_container(
        image=docker_image,
        command="python -c 'import does_not_exist'"
    )
    start_docker_container(container_id=container.id)

    logger.info("Waiting for docker container to exit")
    container_status = container.wait()
    if container_status['StatusCode']:
        logger.error(container.status)
        return Failed(message=f"Docker container has exited with an error! "
                              f"Returned status code '{container_status['StatusCode']}' "
                              f"with error '{container_status['Error']}'")

    logs = get_docker_container_logs(container_id=container.id)
    if not logs:
        logger.warning('Docker container did not emit any log entries')
    else:
        logger.info('Docker container logs:')
        logger.info(logs)

    logger.info("Removing docker container")
    remove_docker_container(container_id=container.id)


if __name__ == "__main__":
    prefect_docker_test()

would then output following logs

11:17:49.242 | INFO    | prefect.engine - Created flow run 'finicky-lyrebird' for flow 'prefect-docker-test'
11:17:50.247 | INFO    | Flow run 'finicky-lyrebird' - Pulling docker image python:alpine3.16
11:17:50.447 | INFO    | Flow run 'finicky-lyrebird' - Created task run 'pull_docker_image-0' for task 'pull_docker_image'
11:17:50.448 | INFO    | Flow run 'finicky-lyrebird' - Executing 'pull_docker_image-0' immediately...
11:17:50.967 | INFO    | Task run 'pull_docker_image-0' - Pulling image: python:alpine3.16.
11:17:52.448 | INFO    | Task run 'pull_docker_image-0' - Finished in state Completed()
11:17:52.450 | INFO    | Flow run 'finicky-lyrebird' - Starting docker container from 'python:alpine3.16' image
11:17:52.640 | INFO    | Flow run 'finicky-lyrebird' - Created task run 'create_docker_container-0' for task 'create_docker_container'
11:17:52.642 | INFO    | Flow run 'finicky-lyrebird' - Executing 'create_docker_container-0' immediately...
11:17:53.152 | INFO    | Task run 'create_docker_container-0' - Creating container with 'python:alpine3.16' image.
11:17:53.364 | INFO    | Task run 'create_docker_container-0' - Finished in state Completed()
11:17:53.547 | INFO    | Flow run 'finicky-lyrebird' - Created task run 'start_docker_container-0' for task 'start_docker_container'
11:17:53.548 | INFO    | Flow run 'finicky-lyrebird' - Executing 'start_docker_container-0' immediately...
11:17:54.059 | INFO    | Task run 'start_docker_container-0' - Starting container '2a071929846db88b01e9a23808bf2135e2587450cd3ddee69a622c9aa229a322'.
11:17:54.517 | INFO    | Task run 'start_docker_container-0' - Finished in state Completed()
11:17:54.519 | INFO    | Flow run 'finicky-lyrebird' - Waiting for docker container to exit
11:17:54.525 | ERROR   | Flow run 'finicky-lyrebird' - created
11:17:54.822 | ERROR   | Flow run 'finicky-lyrebird' - Finished in state Failed("Docker container has exited with an error! Returned status code '1' with error 'None'")
Traceback (most recent call last):
[..]

Since both of my inquiries have been solved, I'm closing this issue. Thank you very much, @ahuang11!

@jpuris jpuris closed this as completed Feb 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants