devcontainers / features

A collection of Dev Container Features managed by Dev Container spec maintainers. See https://github.com/devcontainers/feature-starter to publish your own
https://containers.dev/features
MIT License
918 stars 371 forks source link

docker-outside-of-docker: socat breaks interactive support #483

Open cboitel opened 1 year ago

cboitel commented 1 year ago

When docker-outside-of-docker feature ends up using socat to create a Unix socket ready to use by user in container, you will experience the following inside your terminal:

  1. echo "uname; exit 1" | docker run --interactive ubuntu:jammy will work as expected:
    • will otuput Linux
    • will exit with code 1
  2. echo "sleep 2; uname; exit 2" | docker run --interactive ubuntu:jammy won't work as expected:
    • won't output Linux (output lost)
    • exit code will correctly be 2
  3. (echo "sleep 2; uname; exit 2"; sleep 3) | docker run --interactive ubuntu:jammy will work as expected

This can be reproduced out of devcontainer context by starting socat:

# start socat
socat UNIX-LISTEN:$HOME/docker.sock,fork,reuseaddr UNIX-CONNECT:/var/run/docker.sock &

# using our new docker socket
export DOCKER_HOST=unix://$HOME/docker.sock

I had to dig deep but socat manual got the answer:

   -t<timeout>
         When one channel has reached EOF, the write part of the other channel is shut down. Then, socat waits <timeout> [timeval] seconds  before  terminating.  De‐
        fault  is  0.5  seconds.  This timeout only applies to addresses where write and read part can be closed independently. When during the timeout interval the
         read part gives EOF, socat terminates without awaiting the timeout.

Without tuning socat -t xxx, it will close connection after 0.5s all stdin data has been sent. docker client will then wait for container to get its exit code but will never received the subsequent output. Tuning that timeout can be a nightmare since it has to be more than the longest pause in output data.

Best is to get rid of socat and rely on group membership:

I can help if needed.

cboitel commented 1 year ago

Example of stuff to be done in docker-init.sh:

# original docker socket file (from host)
docker_host_socket_file=/var/run/docker-sock.host

# user container runs as
current_user_name=$(id --user --name)

# grab group id of docker host socket
docker_host_group_id=$(stat -c '%g' ${docker_host_socket_file})

# ensure a group exists with such id
getent group ${docker_host_group_id} || sudo groupadd -g ${docker_host_group_id} docker-host

# ensure user belongs to group and add it if missing
getent group ${docker_host_group_id} | awk -F: '{print $4}' | egrep "(^|,)${current_user_name}(,|$)" ||
        sudo usermod --append --groups ${docker_host_group_id} ${current_user_name} \
samruddhikhandale commented 1 year ago

@alexander-smolyakov Can you help investigate here? thanks!

ainz-95 commented 1 month ago

Even without --interactive, socat seems to break simple docker run commands inside the devcontainer. For e.g. this doesn't output anything:

docker run --rm ubuntu /bin/sh -c 'sleep 5; date'

whereas this does:

DOCKER_HOST='unix:///var/run/docker-host.sock' docker run --rm ubuntu /bin/sh -c 'sleep 5; date'