Skip to content

Support preserving default ENTRYPOINT/CMD for containers used for Workspaces

MR: Add support for preserving container entrypoint... (!196482 - merged)

Description

With the initial work done in Startup scripts for Remote Development workspac... (&15602) to support the devfile standard postStart hooks, we can now provide support for not overriding the default ENTRYPOINT and CMD in containers which are used for Workspaces.

Once all of the existing internal script logic is moved to postStart hooks, we will still be overriding the ENTRYPOINT/CMD to preserve existing behavior, but it will be overridden with a no-op keepalive command like tail -f /dev/null.

We needed to keep this behavior as the default when moving the internal script logic to postStart hooks.

This is because each workspace container must have a non-exiting/daemonized process run as its Kubernetes command/args. Otherwise, it will exit immediately after starting, and never reach the Running status. Eclipse Che exhibits this same behavior.

Once support is added for users to add their own postStart hooks to their devfile, as the devfile standard supports, there will be a workaround for this: The users can replicate the Entrypoint/Cmd of each container in the corresponding postStart hooks for that Workspace container.

But it is very useful for users to not have to do this, and simply be able to use existing containers' Entrypoint/Cmd behavior. In many cases, the user may not even know what Entrypoint/Cmd is being run by default by their container, nor have the technical/container expertise to find out what it is in order to copy it to the devfile postStart hook.

Thus, we want to provide a way for users to "opt-in" to preserving the existing containers' Entrypoint/Cmd.

This is already a first-class supported feature of the devcontainer standard, through the "overrideCommand": false attribute. See more details at &15602 (comment 2270811598), and [comment below for more context](See comment below for more context.

Acceptance Criteria

Implementation Plan

There are a couple options here:

  1. Hack this in via a custom attribute in the devfile, in the components.attributes field. This will default to false, which is the same as the devcontainer standard when using an image Dockerfile. For example:
components:
  - name: tooling-container
    attributes:
      overrideCommand: false
  1. Propose it as an official addition to the devfile standard, to provide feature parity with the devcontainer standard's "overrideCommand" support.
  2. Do both - support the custom attribute as a first iteration to get the support out ASAP, then coordinate with the devfile team to add it to the official devfile standard.

For the initial implementation, we will do Option 1, and then follow up with the devfile team on option 2, once we have a reference implementation to refer to.

Previous description

UPDATE 2024-11-25

Description

The current editor injector implementation specifies an entrypoint for the main container. This entrypoint starts the VS Code server.

In the future, we would like to support custom entrypoints and commands in the main Workspace containers

Vishal's description of the problem and suggested solution

make a local docker container work with local WebIDE server. (I'll use yours as an example, could you please post here a link?)

I'm not sure what you mean here. Are you referring to registry.gitlab.com/gitlab-com/create-stage/editor-poc/remote-development/gitlab-rd-web-ide-docker:0.1-alpha ?

have a look if I can find some editor injector example code that's licensed favourably

This is what we have to do, as I think -

Build a multi-architecture container image of our vscode-fork. This image would contain the package for that architecture and a script which will

  • copy the contents of the said package to a location
  • inject running the editor into the workspace's main container's entrypoint/command

e.g. We build multi-architecture container image for gitlab-web-ide-vscode-fork with the command

docker buildx build \
  --push \
  --platform linux/arm64/v8,linux/amd64 \ 
  --tag registry.gitlab.com/gitlab-org/gitlab-web-ide-vscode-fork \
  .

To recap -

  • The ENTRYPOINT specifies a command that will always be executed when the container starts.
  • The CMD specifies arguments that will be fed to the ENTRYPOINT.

Now imagine the user wants to run the docker/getting-started container image. When we inspect the image, we get the following

docker inspect docker/getting-started
[
    {
        ...
        "ContainerConfig": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": null,
            "Cmd": null,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "DockerVersion": "",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "80/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.23.3",
                "PKG_RELEASE=1",
                "NJS_VERSION=0.7.9"
            ],
            "Cmd": [
                "nginx",
                "-g",
                "daemon off;"
            ],
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": [
                "/docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {
                "maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
            },
            "StopSignal": "SIGQUIT"
        },
        "Architecture": "arm64",
        "Os": "linux",
        ...
    }
]

Calculate the final Entrypoint and Cmd of the main container by inspecting it are

Entrypoint = first-not-null-value-of( .Config.Entrypoint, ["/bin/sh","-c"] )

Cmd = .Config.Cmd

In the case of running docker/getting-started container image, the values we would from inspection are get are

Entrypoint = first-not-null-value-of( ["/docker-entrypoint.sh"], ["/bin/sh","-c"]  ) = ["/docker-entrypoint.sh"]

Cmd = ["nginx","-g","daemon off;"]

Now create a script which copies the content binaries for running the editor, runs the editor and then runs the container's entrypoint/cmd. Example would be

# Let's call this script init.sh

# copy the editor to the mounted volume location
cp source destination

# run the editor
"./${VSCODE_REH_DIR}/bin/code-server-oss" --host "0.0.0.0"

# run the container's entrypoint/command
# "$Entrypoint" "$Command"
"/docker-entrypoint.sh" "nginx" "-g" "daemon off;"

The script would be available in the multi-architecture docker image we built earlier.

Now, we will set the workspace's ContainerConfig.Entrypoint = ["/bin/sh","-c"] and ContainerConfig.Cmd = init.sh

try to create a file like this for the new docker image

Let's keep this on hold for now


P.S. - I feel like this now after writing this comment. I haven't tested it out. So let's pair on it if you'd like 😄

conspiracy


@viktomas

After having slept on it, I think we should do the following

  • Create multi-arch image which also has a script to copy the editor to a volume mount( say init.sh
  • We can use this image by setting the command of the main container of the workspace to this script (init.sh)

Once we have done that, let's figure out how Eclipse Che and DWO are taking of such scenarios - when docker image has a entrypoint and command and when that is overriden through pod-overrides and container-overrides in the devfile. Then decide how we want to do further improve it.

WDYT?

Chad's follow-up suggestion to support a `pre` and `post` hook (from this comment)

Add support in Remote Development to not completely override the default entrypoint/arg of the container (which are called command/args in Kuberntes) by doing something like this

This looks like a promising approach.

We could even make this more generic, by allowing an optional "pre" and "post" script to be run if they exist.

The script could look something like this:

# Run the pre-command hook only if it exists and is executable
if [ -f "${volume_path}/pre_command_hook.sh" ] && [ -x "${volume_path}/pre_command_hook.sh" ]; then
    "${volume_path}/pre_command_hook.sh"
fi

# Run the default command, if any
if [ -n "$*" ]; then
    exec "$@"
fi

# Run the post-command hook only if it exists and is executable
if [ -f "${volume_path}/post_command_hook.sh" ] && [ -x "${volume_path}/post_command_hook.sh" ]; then
    "${volume_path}/post_command_hook.sh"
fi

We could also optionally make the names/locations of these scripts configurable somehow.

TASKS

  • Address the following suggestion from review (!105783 (comment 1374894393)): "I suggest looking into creating children of Config::Entry::Root to define the structure of the devfile in code. This is how CI yml config files are loaded and it provides a framework for parsing config files in a standardized and understandable way."
  • Ideally, support a pre and post script hook, with configurable paths for each.
Edited by 🤖 GitLab Bot 🤖