[PATCH] gnu: services: docker: Add docker-container-service-type

  • Done
  • quality assurance status badge
Details
4 participants
  • Ludovic Courtès
  • Maxime Devos
  • Mája Tomášek
  • Sergey Trofimov
Owner
unassigned
Submitted by
Mája Tomášek
Severity
normal
M
M
Mája Tomášek wrote on 27 Sep 2022 19:16
(address . guix-patches@gnu.org)
87r0zwr9dv.fsf@disroot.org
This patch provides a new service type, which allows the creation of shepherd
services from docker containers.
---
Hi,

I have written a definition of the docker-container-service-type. It is
a service that allows you to convert docker containers into shepherd
services.

It has some limitations:

1. Containers are deleted when stopping.
This makes it easier to manage them from shepherd. You can achieve
persistence with volume binds.
2. Containers must be either attached or detached.
Some containers simply run a command inside the container, and when
run with docker run, they stay attached and the docker run command
therefore behaves like a normal command. However, some containers use
docker's "init system", that means that they won't block the docker
run command. Sadly attached containers don't properly report that
they are initialized, so to docker they are in the "starting" state
until termination. This means that docker doesn't create a cid
file (pid file for containers). To sum it up, there is no way to tell
how will the container report it's state, and this process must be
specified by the user, if the container runs attached or detached.

3. Images are not managed.
Docker does manage images in a really stupid way. My original idea
was to have a file-like object to define an image (from an image
archive), but sadly it has been more complex than I initially
thought. Docker uses 2 versions of archive manifests, and each
archive can have multiple images, depending on the architecture. And
those images can be composed from layers, which are also images. The
daemon determines ad-hoc which images from the archive it will use,
and there is no official tool (at leats when I looked for it) to get
image ids reliably. As the docker load command can return multiple
images. I will expand on the process on how the images could be used
in a managed way, but I have to say something to the current
interface. It works by expecting an image by name from the image-name
field. That means that docker must already have the image in its
database. There is currently no way to ensure that images will be in
docker database before running shepherd services, but I expect they
should simply fail to start.

There is currently no documentation outside of docstrings, I plan to
write it, but first I would welcome comments from you, maybe this
service isn't suitable for guix, as it does imply breaking of the
declarative guix model, but that goes for docker and flatpak too, so I
thought I can try it.

Now finally to images. The idea is that all images belonging to a
docker-container-configuration are tagged (via docker tag) with the name
o the docker-container-configuration-name (as this is unique). The
activation service would get the propper image-id (which is not easy to
get from the manifest, but with some json parsing, it can be done). Then
it would load the image into the docker database with docker load, and
tag the new image with the docker-configuration-name tag. This would
automatically update the image from which the container is running
without having to modify the shepherd service.

Sadly docker does not allow for containers to be ran directly from the
image archive and this is the most straightforward workaround I could
think of.

I hope this patch finds you in good mood,

Maya

PS. I found out that the original docker-service-type wasn't
indent-region. And it got snuck into the patch, I hope this will still
be a valid patch.

gnu/services/docker.scm | 289 +++++++++++++++++++++++++++++++++-------
1 file changed, 242 insertions(+), 47 deletions(-)

Toggle diff (222 lines)
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 741bab5a8c..b05804aa16 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,6 +5,7 @@
;;; Copyright © 2020 Efraim Flashner <efraim@flashner.co.il>
;;; Copyright © 2020 Jesse Dowell <jessedowell@gmail.com>
;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re>
+;;; Copyright © 2022 Maya Tomasek <maya.omase@disroot.org>
;;;
;;; This file is part of GNU Guix.
;;;
@@ -37,6 +38,8 @@ (define-module (gnu services docker)
#:export (docker-configuration
docker-service-type
+ docker-container-configuration
+ docker-container-service-type
singularity-service-type))
(define-configuration docker-configuration
@@ -164,6 +167,198 @@ (define docker-service-type
(const %docker-accounts))))
(default-value (docker-configuration))))
+(define (pair-of-strings? val)
+ (and (pair val)
+ (string? (car val))
+ (string? (cdr val))))
+
+(define (list-of-pair-of-strings? val)
+ (list-of pair-of-strings?))
+
+(define-configuration/no-serialization docker-container-configuration
+ (name
+ (symbol '())
+ "Name of the docker container. Will be used to denote service to Shepherd and must be unique!
+We recommend, that the name of the container is prefixed with @code{docker-}.")
+ (comment
+ (string "")
+ "A documentation on the docker container.")
+ (image-name
+ (string)
+ "A name of the image that will be used. (Note that the existence of the image
+is not guaranteed by this daemon.)")
+ (volumes
+ (list-of-pair-of-strings '())
+ "A list of volume binds. In (HOST_PATH CONTAINER_PATH) format.")
+ (ports
+ (list-of-pair-of-strings '())
+ "A list of port binds. In (HOST_PORT CONTAINER_PORT) or (HOST_PORT CONTAINER_PORT OPTIONS) format.
+For example, both port bindings are valid:
+
+@lisp
+(ports '((\"2222\" \"22\") (\"21\" \"21\" \"tcp\")))
+@end lisp"
+ (environments
+ (list-of-pair-of-strings '())
+ "A list of variable binds, inside the container enviornment. In (VARIABLE VALUE) format."))
+ (network
+ (string "none")
+ "Network type.")
+ (additional-arguments
+ (list-of-strings '())
+ "Additional arguments to the docker command line interface.")
+ (container-command
+ (list-of-strings '())
+ "Command to send into the container.")
+ (attached?
+ (boolean #t)
+ "Run the container as an normal attached process (sending SIGTERM).
+Or run the container as a isolated environment that must be stopped with @code{docker stop}.
+
+Please verify first, that you container is indeed not attached, otherwise @code{shepherd} might
+assume the process is dead, even when it is not.
+
+You can do that, by first running your container with @code{docker run image-name}.
+
+Then check @code{docker ps}, if the command shows beside your container the word @code{running}.
+Your container is indeed detached, but if it shows @code{starting}, and it doesn't flip to
+@code{running} after a while, it means that you container is attached, and you need to keep this
+option turned @code{#t}."))
+
+(define (serialize-volumes config)
+ "Serialize list of pairs into flat list of @code{(\"-v\" \"HOST_PATH:CONTAINER_PATH\" ...)}"
+ (append-map
+ (lambda (volume-bind)
+ (list "-v" (format #f "~?" "~a:~a" volume-bind)))
+ (docker-container-configuration-volumes config)))
+
+(define (serialize-ports config)
+ "Serialize list of either pairs, or lists into flat list of
+@code{(\"-p\" \"NUMBER:NUMBER\" \"-p\" \"NUMBER:NUMBER/PROTOCOL\" ...)}"
+ (append-map
+ (lambda (port-bind)
+ (list "-p" (format #f "~?" "~a:~a~^/~a" port-bind)))
+ (docker-container-configuration-ports config)))
+
+(define (serialized-environments config)
+ "Serialize list of pairs into flat list of @code{(\"-e\" \"VAR=val\" \"-e\" \"VAR=val\" ...)}."
+ (append-map
+ (lambda (env-bind)
+ (list "-e" (format #f "~?" "~a=~a" env-bind)))
+ (docker-container-configuration-environments config)))
+
+(define (docker-container-startup-script docker-cli container-name config)
+ "Return a program file, that executes the startup sequence of the @code{docker-container-shepherd-service}."
+ (let* ((attached? (docker-container-configuration-attached? config))
+ (image-name (docker-container-configuration-image config))
+ (volumes (serialize-volumes config))
+ (ports (serialize-ports config))
+ (envs (serialize-environments config))
+ (network (docker-container-configuration-network config))
+ (additional-arguments (docker-container-configuration-additional-arguments config))
+ (container-command (docker-container-configuration-container-command config)))
+ (program-file
+ (string-append "start-" container-name "-container")
+ #~(let ((docker (string-append #$docker-cli "/bin/docker")))
+ (system* docker "stop" #$container-name)
+ (system* docker "rm" #$container-name)
+ (apply system* `(,docker
+ "run"
+ ,(string-append "--name=" #$container-name)
+ ;; Automatically remove the container when stopping
+ ;; If you want persistent data, you need to use
+ ;; volume binds or other methods.
+ "--rm"
+ ,(string-append "--network=" #$network)
+ ;; TODO:
+ ;; Write to a cid file the container id, this allows
+ ;; for shepherd to manage container even when the process
+ ;; itself gets detached from the container
+ ,@(if (not #$attached) '("--cidfile" #$cid-file) '())
+ #$@volumes
+ #$@ports
+ #$@envs
+ #$@additional-arguments
+ ,#$image-name
+ #$@container-command))))))
+
+(define (docker-container-shepherd-service docker-cli config)
+ "Return a shepherd-service that runs CONTAINER."
+ (let* ((container-name (symbol->string (docker-container-configuration-name config)))
+ (cid-file (string-append "/var/run/docker/" container-name ".pid"))
+ (attached? (docker-container-configuration-attached? config)))
+ (shepherd-service
+ (provision (list (docker-container-configuration-name config)))
+ (requirement `(dockerd))
+ (start #~(make-forkexec-constructor
+ (list #$(docker-container-startup-script docker-cli container-name config))
+ ;; Watch the cid-file instead of the docker run command, as the daemon can
+ ;; still be running even when the command terminates
+ (if (not #$attached?)
+ #:pid-file #$cid-file)))
+ (stop (if #$attached?
+ #~(make-kill-destructor)
+ #~(lambda _
+ (exec-command (list
+ (string-append #$docker-cli "/bin/docker")
+ "stop" #$container-name))
+ #f))))))
+
+
+(define (list-of-docker-container-configurations? val)
+ (list-of docker-container-configuration?))
+
+(define-configuration/no-serialization docker-container-service-configuration
+ (docker-cli
+ (file-like docker-cli)
+ "The docker package to use.")
+ (containers
+ (list-of-docker-container-configurations '())
+ "The docker containers to run."))
+
+(define (docker-container-shepherd-services config)
+ "Return shepherd services for all containers inside config."
+ (let ((docker-cli (docker-container-service-configuration-docker-cli config)))
+ (map
+ (lambda (container)
+ (docker-container-shepherd-service
+ docker-cli
+ container))
+ (docker-container-service-configuration-containers config))))
+
+(define docker-container-service-type
+ "This is the type of the service that runs docker containers using GNU Shepherd.
+It allows for declarative management of running containers inside the Guix System.
+
+This service can be extended with list of @code{<docker-container-configuration>} objects.
+
+The following is an example @code{docker-container-service-type} configuration.
+
+@lisp
+(service docker-container-service-type
+ (containers (list
+ (docker-container-configuration
+ (name 'docker-example)
+ (comment \"An example docker container configuration\")
+ (image-name \"example/example:latest\") ;; Note that images must be provided separately.
+ (volumes '((\"/mnt\" \"/\") (\"/home/example\" \"/home/user\")))
+ (ports '((\"21\" \"21\" \"tcp\") (\"22\" \"22\")))
+ (network \"host\")))))
+@end lisp"
+ (service-type
+ (name 'docker-container)
+ (description "Manage docker containers with shepherd.")
+ (extensions
+ (list (service-extension shepherd-root-service-type docker-container-shepherd-services)))
+ (compose concatenate)
+ (extend (lambda (config containers)
+ (let ((docker-cli (docker-container-service-configuration-docker-cli config))
+ (initial-containers (docker-container-service-configuration-containers config)))
+ (docker-container-service-configuration
+ (docker-cli docker-cli)
+ (containers (append initial-containers containers))))))
+ (default-value (docker-container-service-configuration))))
+
;;;
;;; Singularity.
--
2.37.3
M
M
Maxime Devos wrote on 29 Sep 2022 20:31
dd0d0076-39e1-5934-4304-1fae7ed5e042@telenet.be
On 27-09-2022 19:16, guix-patches--- via wrote:
Toggle quote (15 lines)
>
> This patch provides a new service type, which allows the creation of shepherd
> services from docker containers.
> ---
> Hi,
>
> I have written a definition of the docker-container-service-type. It is
> a service that allows you to convert docker containers into shepherd
> services. [...]
>
> There is currently no documentation outside of docstrings, I plan to
> write it, but first I would welcome comments from you, maybe this
> service isn't suitable for guix, as it does imply breaking of the
> declarative guix model, but that goes for docker and flatpak too, so I
> thought I can try it.
We already have a docker-service-type, why not a
docker-container-service-type, though I wouldn't recommend docker
myself. Can't really document on the docker bits, but some mostly
superficial comments:
Toggle quote (3 lines)
>
> +(define (pair-of-strings? val)
> + (and (pair val)
I think you meant 'pair?' here, not 'pair'.
Toggle quote (14 lines)
> + (string? (car val))
> + (string? (cdr val))))
> +
> +(define (list-of-pair-of-strings? val)
> + (list-of pair-of-strings?))
> +
> +(define-configuration/no-serialization docker-container-configuration
> + (name
> + (symbol '())
> + "Name of the docker container. Will be used to denote service to Shepherd and must be unique!
> +We recommend, that the name of the container is prefixed with @code{docker-}.")
> + (comment
> + (string "")
> + "A documentation on the docker container.")
I don't think documentation is countable, maybe
"Documentation on the Docker container (optional)."
? (I don't know what casing is appropriate here).
Toggle quote (7 lines)
> + (image-name
> + (string)
> + "A name of the image that will be used. (Note that the existence of the image
> +is not guaranteed by this daemon.)")
> + (volumes
> + (list-of-pair-of-strings '())
> + "A list of volume binds. In (HOST_PATH CONTAINER_PATH) format.")
binds -> bindings and HOST_PATH CONTAINER_PATH -> (HOST-PATH
CONTAINER-PATH) per Scheme conventions.
Toggle quote (8 lines)
> + (ports
> + (list-of-pair-of-strings '())
> + "A list of port binds. In (HOST_PORT CONTAINER_PORT) or (HOST_PORT CONTAINER_PORT OPTIONS) format.
> +For example, both port bindings are valid:
> +
> +@lisp
> +(ports '((\"2222\" \"22\") (\"21\" \"21\" \"tcp\")))
> +@end lisp"
* binds -> bindings
Toggle quote (3 lines)
> + (environments
> + (list-of-pair-of-strings '())
> + "A list of variable binds, inside the container enviornment. In (VARIABLE VALUE) format."))
'enviornment' -> 'environment', 'variable binds' -> 'environment
variables', '. In' -> ', in'.
Toggle quote (3 lines)
> + (network
> + (string "none")
> + "Network type.")
Can the available network types be listed or a reference to the Docker
documentation be added, to help users with determining what to set it to?
Toggle quote (27 lines)
> + (additional-arguments
> + (list-of-strings '())
> + "Additional arguments to the docker command line interface.")
> + (container-command
> + (list-of-strings '())
> + "Command to send into the container.")
> + (attached?
> + (boolean #t)
> + "Run the container as an normal attached process (sending SIGTERM).
> +Or run the container as a isolated environment that must be stopped with @code{docker stop}.
> +
> +Please verify first, that you container is indeed not attached, otherwise @code{shepherd} might
> +assume the process is dead, even when it is not.
> +
> +You can do that, by first running your container with @code{docker run image-name}.
> +
> +Then check @code{docker ps}, if the command shows beside your container the word @code{running}.
> +Your container is indeed detached, but if it shows @code{starting}, and it doesn't flip to
> +@code{running} after a while, it means that you container is attached, and you need to keep this
> +option turned @code{#t}."))
> +
> +(define (serialize-volumes config)
> + "Serialize list of pairs into flat list of @code{(\"-v\" \"HOST_PATH:CONTAINER_PATH\" ...)}"
> + (append-map
> + (lambda (volume-bind)
> + (list "-v" (format #f "~?" "~a:~a" volume-bind)))
> + (docker-container-configuration-volumes config)))
See following about pairs and simplification.
Toggle quote (8 lines)
> +
> +(define (serialize-ports config)
> + "Serialize list of either pairs, or lists into flat list of
> +@code{(\"-p\" \"NUMBER:NUMBER\" \"-p\" \"NUMBER:NUMBER/PROTOCOL\" ...)}"
> + (append-map
> + (lambda (port-bind)
> + (list "-p" (format #f "~?" "~a:~a~^/~a" port-bind)))
> + (docker-container-configuration-ports config)))
See following about pairs and simplification.
Toggle quote (7 lines)
> +
> +(define (serialized-environments config)
> + "Serialize list of pairs into flat list of @code{(\"-e\" \"VAR=val\" \"-e\" \"VAR=val\" ...)}."
> + (append-map
> + (lambda (env-bind)
> + (list "-e" (format #f "~?" "~a=~a" env-bind)))
> + (docker-container-configuration-environments config)))
I tried this out in a REPL, but found that it doesn't accept pairs but
2-element lists:
scheme@(guile-user)> (format #f "~?" "~a=~a" '("x" . "y"))
ice-9/boot-9.scm:1685:16: In procedure raise-exception:
In procedure length: Wrong type argument in position 1: ("x" . "y")
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,q
scheme@(guile-user)> (format #f "~?" "~a=~a" '("x" "y"))
$1 = "x=y"
Also, the 'format' can be simplified:
(apply format #f "~a=~a" env-bind)
Toggle quote (34 lines)
> +
> +(define (docker-container-startup-script docker-cli container-name config)
> + "Return a program file, that executes the startup sequence of the @code{docker-container-shepherd-service}."
> + (let* ((attached? (docker-container-configuration-attached? config))
> + (image-name (docker-container-configuration-image config))
> + (volumes (serialize-volumes config))
> + (ports (serialize-ports config))
> + (envs (serialize-environments config))
> + (network (docker-container-configuration-network config))
> + (additional-arguments (docker-container-configuration-additional-arguments config))
> + (container-command (docker-container-configuration-container-command config)))
> + (program-file
> + (string-append "start-" container-name "-container")
> + #~(let ((docker (string-append #$docker-cli "/bin/docker")))
> + (system* docker "stop" #$container-name)
> + (system* docker "rm" #$container-name) > + (apply system* `(,docker
> + "run"
> + ,(string-append "--name=" #$container-name)
> + ;; Automatically remove the container when stopping
> + ;; If you want persistent data, you need to use
> + ;; volume binds or other methods.
> + "--rm"
> + ,(string-append "--network=" #$network)
> + ;; TODO:
> + ;; Write to a cid file the container id, this allows
> + ;; for shepherd to manage container even when the process
> + ;; itself gets detached from the container
> + ,@(if (not #$attached) '("--cidfile" #$cid-file) '())
> + #$@volumes
> + #$@ports
> + #$@envs
> + #$@additional-arguments
> + ,#$image-name
> + #$@container-command))))))
'system*' can fail, which it does by returning some number (and not by
an exception). I recommend using 'invoke' from (guix build utils)
(which uses exceptions) instead; you may need use-modules +
with-imported-modules to use that module.
Toggle quote (5 lines)
> +
> +(define (docker-container-shepherd-service docker-cli config)
> + "Return a shepherd-service that runs CONTAINER."
> + (let* ((container-name (symbol->string (docker-container-configuration-name config)))
> + (cid-file (string-append "/var/run/docker/" container-name ".pid"))
This sounds like ".", ".." and anything containing a "/" or "\x00" would
be invalid container names, I recommend refining the type check for
'container-name' a little. It also looks like container names must be
unique within a system, that sounds like something to mention in its
docstring to me.
Toggle quote (10 lines)
> + (attached? (docker-container-configuration-attached? config)))
> + (shepherd-service
> + (provision (list (docker-container-configuration-name config)))
> + (requirement `(dockerd))
> + (start #~(make-forkexec-constructor
> + (list #$(docker-container-startup-script docker-cli container-name config))
> + ;; Watch the cid-file instead of the docker run command, as the daemon can
> + ;; still be running even when the command terminates
> + (if (not #$attached?)
> + #:pid-file #$cid-file)))
I don't think this does what you want it to do -- when attached, it will
evaluate to #$cid-file, when not, it will evaluate to #:pid-flile.
Try apply+list instead:
(apply
make-forkexec-constructor
(list ...)
#$(if $attached
#~()
#~(list #:pid-file #$cid-file)))
(Changing the staging is not required, though myself I prefer it this way.)
I recommend writing a system test (in gnu/tests/docker.scm), to prevent
such problems, though I don't know how feasible it would be.
Toggle quote (7 lines)
> + (stop (if #$attached?
> + #~(make-kill-destructor)
> + #~(lambda _
> + (exec-command (list
> + (string-append #$docker-cli "/bin/docker")
> + "stop" #$container-name))
> + #f))))))
Not very familiar with how Shepherd works here, but I think that the
'return #false' dseserves a command.
Also, on (string-append #$docker-cli "/bin/docker"): we have
#$(file-append docker-cli "/bin/docker") for that, though in this case
it doesn't matter (for uses inside 'quote', it does, but here, not really).
Greetings,
Maxime.
Attachment: OpenPGP_signature
M
M
Mája Tomášek wrote on 30 Sep 2022 15:40
87edvt9e16.fsf@disroot.org
Hi

Toggle quote (22 lines)
> On 27-09-2022 19:16, guix-patches--- via wrote:
>>
>> This patch provides a new service type, which allows the creation of shepherd
>> services from docker containers.
>> ---
>> Hi,
>>
>> I have written a definition of the docker-container-service-type. It is
>> a service that allows you to convert docker containers into shepherd
>> services. [...]
>>
>> There is currently no documentation outside of docstrings, I plan to
>> write it, but first I would welcome comments from you, maybe this
>> service isn't suitable for guix, as it does imply breaking of the
>> declarative guix model, but that goes for docker and flatpak too, so I
>> thought I can try it.
>
> We already have a docker-service-type, why not a
> docker-container-service-type, though I wouldn't recommend docker
> myself. Can't really document on the docker bits, but some mostly
> superficial comments:

I was thinking that this isn't exactly a "needed" use and can be thought
of as separate, but probably I was wrong.

Toggle quote (7 lines)
>>
>> +(define (pair-of-strings? val)
>> + (and (pair val)
>
> I think you meant 'pair?' here, not 'pair'.
>

Yes.

Toggle quote (177 lines)
>> + (string? (car val))
>> + (string? (cdr val))))
>> +
>> +(define (list-of-pair-of-strings? val)
>> + (list-of pair-of-strings?))
>> +
>> +(define-configuration/no-serialization docker-container-configuration
>> + (name
>> + (symbol '())
>> + "Name of the docker container. Will be used to denote service to Shepherd and must be unique!
>> +We recommend, that the name of the container is prefixed with @code{docker-}.")
>> + (comment
>> + (string "")
>> + "A documentation on the docker container.")
>
> I don't think documentation is countable, maybe
>
> "Documentation on the Docker container (optional)."
>
> ? (I don't know what casing is appropriate here).
>
>> + (image-name
>> + (string)
>> + "A name of the image that will be used. (Note that the existence of the image
>> +is not guaranteed by this daemon.)")
>> + (volumes
>> + (list-of-pair-of-strings '())
>> + "A list of volume binds. In (HOST_PATH CONTAINER_PATH) format.")
>
> binds -> bindings and HOST_PATH CONTAINER_PATH -> (HOST-PATH
> CONTAINER-PATH) per Scheme conventions.
>
>> + (ports
>> + (list-of-pair-of-strings '())
>> + "A list of port binds. In (HOST_PORT CONTAINER_PORT) or (HOST_PORT CONTAINER_PORT OPTIONS) format.
>> +For example, both port bindings are valid:
>> +
>> +@lisp
>> +(ports '((\"2222\" \"22\") (\"21\" \"21\" \"tcp\")))
>> +@end lisp"
>
> * binds -> bindings
>
>> + (environments
>> + (list-of-pair-of-strings '())
>> + "A list of variable binds, inside the container enviornment. In (VARIABLE VALUE) format."))
>
> 'enviornment' -> 'environment', 'variable binds' -> 'environment
> variables', '. In' -> ', in'.
>
>> + (network
>> + (string "none")
>> + "Network type.")
>
> Can the available network types be listed or a reference to the Docker
> documentation be added, to help users with determining what to set it to?
>
>> + (additional-arguments
>> + (list-of-strings '())
>> + "Additional arguments to the docker command line interface.")
>> + (container-command
>> + (list-of-strings '())
>> + "Command to send into the container.")
>> + (attached?
>> + (boolean #t)
>> + "Run the container as an normal attached process (sending SIGTERM).
>> +Or run the container as a isolated environment that must be stopped with @code{docker stop}.
>> +
>> +Please verify first, that you container is indeed not attached, otherwise @code{shepherd} might
>> +assume the process is dead, even when it is not.
>> +
>> +You can do that, by first running your container with @code{docker run image-name}.
>> +
>> +Then check @code{docker ps}, if the command shows beside your container the word @code{running}.
>> +Your container is indeed detached, but if it shows @code{starting}, and it doesn't flip to
>> +@code{running} after a while, it means that you container is attached, and you need to keep this
>> +option turned @code{#t}."))
>> +
>> +(define (serialize-volumes config)
>> + "Serialize list of pairs into flat list of @code{(\"-v\" \"HOST_PATH:CONTAINER_PATH\" ...)}"
>> + (append-map
>> + (lambda (volume-bind)
>> + (list "-v" (format #f "~?" "~a:~a" volume-bind)))
>> + (docker-container-configuration-volumes config)))
>
> See following about pairs and simplification.
>
>> +
>> +(define (serialize-ports config)
>> + "Serialize list of either pairs, or lists into flat list of
>> +@code{(\"-p\" \"NUMBER:NUMBER\" \"-p\" \"NUMBER:NUMBER/PROTOCOL\" ...)}"
>> + (append-map
>> + (lambda (port-bind)
>> + (list "-p" (format #f "~?" "~a:~a~^/~a" port-bind)))
>> + (docker-container-configuration-ports config)))
>
> See following about pairs and simplification.
>
>> +
>> +(define (serialized-environments config)
>> + "Serialize list of pairs into flat list of @code{(\"-e\" \"VAR=val\" \"-e\" \"VAR=val\" ...)}."
>> + (append-map
>> + (lambda (env-bind)
>> + (list "-e" (format #f "~?" "~a=~a" env-bind)))
>> + (docker-container-configuration-environments config)))
>
> I tried this out in a REPL, but found that it doesn't accept pairs but
> 2-element lists:
>
> scheme@(guile-user)> (format #f "~?" "~a=~a" '("x" . "y"))
> ice-9/boot-9.scm:1685:16: In procedure raise-exception:
> In procedure length: Wrong type argument in position 1: ("x" . "y")
>
> Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
> scheme@(guile-user) [1]> ,q
> scheme@(guile-user)> (format #f "~?" "~a=~a" '("x" "y"))
> $1 = "x=y"
>
> Also, the 'format' can be simplified:
>
> (apply format #f "~a=~a" env-bind)
>
>
>> +
>> +(define (docker-container-startup-script docker-cli container-name config)
>> + "Return a program file, that executes the startup sequence of the @code{docker-container-shepherd-service}."
>> + (let* ((attached? (docker-container-configuration-attached? config))
>> + (image-name (docker-container-configuration-image config))
>> + (volumes (serialize-volumes config))
>> + (ports (serialize-ports config))
>> + (envs (serialize-environments config))
>> + (network (docker-container-configuration-network config))
>> + (additional-arguments (docker-container-configuration-additional-arguments config))
>> + (container-command (docker-container-configuration-container-command config)))
>> + (program-file
>> + (string-append "start-" container-name "-container")
>> + #~(let ((docker (string-append #$docker-cli "/bin/docker")))
>> + (system* docker "stop" #$container-name)
>> + (system* docker "rm" #$container-name) > + (apply system* `(,docker
>> + "run"
>> + ,(string-append "--name=" #$container-name)
>> + ;; Automatically remove the container when stopping
>> + ;; If you want persistent data, you need to use
>> + ;; volume binds or other methods.
>> + "--rm"
>> + ,(string-append "--network=" #$network)
>> + ;; TODO:
>> + ;; Write to a cid file the container id, this allows
>> + ;; for shepherd to manage container even when the process
>> + ;; itself gets detached from the container
>> + ,@(if (not #$attached) '("--cidfile" #$cid-file) '())
>> + #$@volumes
>> + #$@ports
>> + #$@envs
>> + #$@additional-arguments
>> + ,#$image-name
>> + #$@container-command))))))
>
> 'system*' can fail, which it does by returning some number (and not by
> an exception). I recommend using 'invoke' from (guix build utils)
> (which uses exceptions) instead; you may need use-modules +
> with-imported-modules to use that module.
>
>
>> +
>> +(define (docker-container-shepherd-service docker-cli config)
>> + "Return a shepherd-service that runs CONTAINER."
>> + (let* ((container-name (symbol->string (docker-container-configuration-name config)))
>> + (cid-file (string-append "/var/run/docker/" container-name ".pid"))
>
> This sounds like ".", ".." and anything containing a "/" or "\x00" would
> be invalid container names, I recommend refining the type check for
> 'container-name' a little. It also looks like container names must be
> unique within a system, that sounds like something to mention in its
> docstring to me.
>

There actually is mention of it!

"Name of the docker container. Will be used to denote service to Shepherd and must be unique!
We recommend, that the name of the container is prefixed with
@code{docker-}."

But I agree I need to modify it a bit.

Toggle quote (29 lines)
>> + (attached? (docker-container-configuration-attached? config)))
>> + (shepherd-service
>> + (provision (list (docker-container-configuration-name config)))
>> + (requirement `(dockerd))
>> + (start #~(make-forkexec-constructor
>> + (list #$(docker-container-startup-script docker-cli container-name config))
>> + ;; Watch the cid-file instead of the docker run command, as the daemon can
>> + ;; still be running even when the command terminates
>> + (if (not #$attached?)
>> + #:pid-file #$cid-file)))
>
> I don't think this does what you want it to do -- when attached, it will
> evaluate to #$cid-file, when not, it will evaluate to #:pid-flile.
>
> Try apply+list instead:
>
> (apply
> make-forkexec-constructor
> (list ...)
> #$(if $attached
> #~()
> #~(list #:pid-file #$cid-file)))
>
> (Changing the staging is not required, though myself I prefer it this way.)
>
> I recommend writing a system test (in gnu/tests/docker.scm), to prevent
> such problems, though I don't know how feasible it would be.
>

I overlooked some issues, as I was sending the patch after last minute
changes and I forgot to check it, thank you for noticing that mistake.

Toggle quote (12 lines)
>> + (stop (if #$attached?
>> + #~(make-kill-destructor)
>> + #~(lambda _
>> + (exec-command (list
>> + (string-append #$docker-cli "/bin/docker")
>> + "stop" #$container-name))
>> + #f))))))
>
> Not very familiar with how Shepherd works here, but I think that the
> 'return #false' dseserves a command.
>

Well, I looked through the source code, and this is shepherd's own
definition:


(define* (make-kill-destructor #:optional (signal SIGTERM))
"Return a procedure that sends SIGNAL to the process group of the PID given
as argument, where SIGNAL defaults to `SIGTERM'."
(lambda (pid . args)
;; Kill the whole process group PID belongs to. Don't assume that PID is
;; a process group ID: that's not the case when using #:pid-file, where
;; the process group ID is the PID of the process that "daemonized". If
;; this procedure is called, between the process fork and exec, the PGID
;; will still be zero (the Shepherd PGID). In that case, use the PID.
(let ((pgid (getpgid pid)))
(if (= (getpgid 0) pgid)
(kill pid signal) ;don't kill ourself
(kill (- pgid) signal)))
#f))


So I think that returning #f works. docker stop will send SIGKILL if the
container takes too long, so it should succeed.

I will send a new patch with this service moved into the
docker-service-type and addressed your criticisms.

Maya
M
M
Maxime Devos wrote on 30 Sep 2022 20:47
b4cfc59a-470a-14a1-6437-49995ca0f2a3@telenet.be
Toggle quote (18 lines)
>>> +
>>> +(define (docker-container-shepherd-service docker-cli config)
>>> + "Return a shepherd-service that runs CONTAINER."
>>> + (let* ((container-name (symbol->string (docker-container-configuration-name config)))
>>> + (cid-file (string-append "/var/run/docker/" container-name ".pid"))
>>
>> This sounds like ".", ".." and anything containing a "/" or "\x00" would
>> be invalid container names, I recommend refining the type check for
>> 'container-name' a little. It also looks like container names must be
>> unique within a system, that sounds like something to mention in its
>> docstring to me.
>>
>
> There actually is mention of it!
>
> "Name of the docker container. Will be used to denote service to Shepherd and must be unique!
> We recommend, that the name of the container is prefixed with
> @code{docker-}."
Oops, didn't notice that. However, you could insert a check somewhere
for uniqueness, to avoid accidents.
Toggle quote (34 lines)
>>> + (stop (if #$attached?
>>> + #~(make-kill-destructor)
>>> + #~(lambda _
>>> + (exec-command (list
>>> + (string-append #$docker-cli "/bin/docker")
>>> + "stop" #$container-name))
>>> + #f))))))
>>
>> Not very familiar with how Shepherd works here, but I think that the
>> 'return #false' dseserves a command.
>>
>
> Well, I looked through the source code, and this is shepherd's own
> definition:
>
>
> (define* (make-kill-destructor #:optional (signal SIGTERM))
> "Return a procedure that sends SIGNAL to the process group of the PID given
> as argument, where SIGNAL defaults to `SIGTERM'."
> (lambda (pid . args)
> ;; Kill the whole process group PID belongs to. Don't assume that PID is
> ;; a process group ID: that's not the case when using #:pid-file, where
> ;; the process group ID is the PID of the process that "daemonized". If
> ;; this procedure is called, between the process fork and exec, the PGID
> ;; will still be zero (the Shepherd PGID). In that case, use the PID.
> (let ((pgid (getpgid pid)))
> (if (= (getpgid 0) pgid)
> (kill pid signal) ;don't kill ourself
> (kill (- pgid) signal)))
> #f))
>
>
> So I think that returning #f works. docker stop will send SIGKILL if the
> container takes too long, so it should succeed.
Not saying it won't work, just that it deserves a comment (though
apparently I misspelled 'comment'), even if it's only something like
"return #false as done by 'make-kill-destructor'".
However, 'exec-command' runs 'exec' (replacing the shepherd process), I
think you need something like 'invoke' or 'system*' instead.
Greetings,
MaximE.
Attachment: OpenPGP_signature
M
M
Maxime Devos wrote on 30 Sep 2022 20:48
6c9813c0-ea43-3238-2b6d-376307017e9c@telenet.be
On 30-09-2022 15:40, Mája Tomášek wrote:
Toggle quote (5 lines)
> +(define-configuration/no-serialization docker-container-configuration
> + (name
> + (symbol '())
> + "Name of the docker container. Will be used to denote service to Shepherd and must be unique!
> +We recommend, that the name of the container is prefixed with @code{docker-}.")
On uniqueness: on second thought, that's probably already handled by the
general service code, probably no need for an additional check here.
Greetings,
Maxime.
Attachment: OpenPGP_signature
M
M
Mája Tomášek wrote on 2 Oct 2022 22:38
87mtae9d0t.fsf@disroot.org
I have applied the changes as you suggested. Thank you for your (as you
said) "superficial comments", they were really helpful! And I am happy
that you made them, as I'm sometimes too happy that I have made a
contribution and I forget that I don't write only for myself, but for
others.
L
L
Ludovic Courtès wrote on 9 Oct 2022 22:31
(name . Mája Tomášek)(address . maya.tomasek@disroot.org)
874jwc4u3k.fsf_-_@gnu.org
Hi Mája,

Mája Tomášek <maya.tomasek@disroot.org> skribis:

Toggle quote (6 lines)
> I have applied the changes as you suggested. Thank you for your (as you
> said) "superficial comments", they were really helpful! And I am happy
> that you made them, as I'm sometimes too happy that I have made a
> contribution and I forget that I don't write only for myself, but for
> others.

Thanks for the nice and useful service!

It looks pretty good already (in part thanks to Maxime’s guidance :-)).
I would have two more asks:

1. Could you update doc/guix.texi to document the new service? You
can mostly use ‘generate-documentation’ to produce the reference of
the configuration record, and then add a paragraph giving some
context and a documented example.

2. Could you add a test under (gnu tests *)? That would ensure the
service does not bitrot going forward.

Let us know if you need guidance on these things. When you’re done,
please send an updated patch with those changes here.

Thank you!

Ludo’.
M
M
Mája Tomášek wrote on 11 Oct 2022 20:04
(name . Ludovic Courtès)(address . ludo@gnu.org)
87czaygrt8.fsf@disroot.org
Hi Ludo',

Toggle quote (16 lines)
>> I have applied the changes as you suggested. Thank you for your (as you
>> said) "superficial comments", they were really helpful! And I am happy
>> that you made them, as I'm sometimes too happy that I have made a
>> contribution and I forget that I don't write only for myself, but for
>> others.
>
> Thanks for the nice and useful service!
>
> It looks pretty good already (in part thanks to Maxime’s guidance :-)).
> I would have two more asks:
>
> 1. Could you update doc/guix.texi to document the new service? You
> can mostly use ‘generate-documentation’ to produce the reference of
> the configuration record, and then add a paragraph giving some
> context and a documented example.

Is that a command from make? I'm sorry I have never used it, I can
update it if I can generate it :)

Toggle quote (3 lines)
> 2. Could you add a test under (gnu tests *)? That would ensure the
> service does not bitrot going forward.

I'm not exactly sure what would that mean. Test that creates a container
and then runs it or...?

Toggle quote (3 lines)
> Let us know if you need guidance on these things. When you’re done,
> please send an updated patch with those changes here.

Will do!

Maya
L
L
Ludovic Courtès wrote on 13 Oct 2022 15:05
(name . Mája Tomášek)(address . maya.tomasek@disroot.org)
87k053ri04.fsf@gnu.org
Hello,

Mája Tomášek <maya.tomasek@disroot.org> skribis:

Toggle quote (10 lines)
>> I would have two more asks:
>>
>> 1. Could you update doc/guix.texi to document the new service? You
>> can mostly use ‘generate-documentation’ to produce the reference of
>> the configuration record, and then add a paragraph giving some
>> context and a documented example.
>
> Is that a command from make? I'm sorry I have never used it, I can
> update it if I can generate it :)

‘generate-documentation’ is a procedure in (gnu services configuration):


Toggle quote (6 lines)
>> 2. Could you add a test under (gnu tests *)? That would ensure the
>> service does not bitrot going forward.
>
> I'm not exactly sure what would that mean. Test that creates a container
> and then runs it or...?

I guess the test would start the Shepherd service, ensure it’s running,
and attempt to talk to the running container one way or another.

Does that make sense?

Thanks,
Ludo’.
L
L
Ludovic Courtès wrote on 1 Dec 2022 16:59
(name . Mája Tomášek)(address . maya.tomasek@disroot.org)
87a647yv1h.fsf_-_@gnu.org
Hi Mája,

Did you have a chance to look into this?


Thanks in advance,
Ludo’.

Ludovic Courtès <ludo@gnu.org> skribis:

Toggle quote (31 lines)
> Hello,
>
> Mája Tomášek <maya.tomasek@disroot.org> skribis:
>
>>> I would have two more asks:
>>>
>>> 1. Could you update doc/guix.texi to document the new service? You
>>> can mostly use ‘generate-documentation’ to produce the reference of
>>> the configuration record, and then add a paragraph giving some
>>> context and a documented example.
>>
>> Is that a command from make? I'm sorry I have never used it, I can
>> update it if I can generate it :)
>
> ‘generate-documentation’ is a procedure in (gnu services configuration):
>
> https://guix.gnu.org/manual/devel/en/html_node/Complex-Configurations.html#index-generate_002ddocumentation
>
>>> 2. Could you add a test under (gnu tests *)? That would ensure the
>>> service does not bitrot going forward.
>>
>> I'm not exactly sure what would that mean. Test that creates a container
>> and then runs it or...?
>
> I guess the test would start the Shepherd service, ensure it’s running,
> and attempt to talk to the running container one way or another.
>
> Does that make sense?
>
> Thanks,
> Ludo’.
M
M
Mája Tomášek wrote on 15 Dec 2022 22:07
(name . Ludovic Courtès)(address . ludo@gnu.org)
87r0x0jrz3.fsf@disroot.org
Hi Ludo',

Ludovic Courtès <ludo@gnu.org> writes:

Toggle quote (5 lines)
> Hi Mája,
>
> Did you have a chance to look into this?
>

I am sorry I have been busy lately. I have briefly looked into (gnu
tests docker) but I need to study it more closely. I hope that I will
have more time to spend on this during the holidays.

I haven't forgot about this! I have been using it on my machines all
this time, so this isn't something I want to abandon, but I need to test
it more thoroughly and really study how tests work. The docker one is
pretty complex, and I need to do something at least that complicated.

Best regards,
Maya
S
S
Sergey Trofimov wrote on 20 Dec 2023 22:48
Re: [bug#58123] [PATCH] gnu: services: docker: Add docker-container-service-type
(name . Mája Tomášek)(address . maya.tomasek@disroot.org)
87edfg5yte.fsf@sarg.org.ru
Mája Tomášek <maya.tomasek@disroot.org> writes:

Hi, guix has oci-container-service-type nowadays. Is this patch
now obsolete?

Toggle quote (26 lines)
> Hi Ludo',
>
> Ludovic Courtès <ludo@gnu.org> writes:
>
>> Hi Mája,
>>
>> Did you have a chance to look into this?
>>
>
> I am sorry I have been busy lately. I have briefly looked into
> (gnu
> tests docker) but I need to study it more closely. I hope that I
> will
> have more time to spend on this during the holidays.
>
> I haven't forgot about this! I have been using it on my machines
> all
> this time, so this isn't something I want to abandon, but I need
> to test
> it more thoroughly and really study how tests work. The docker
> one is
> pretty complex, and I need to do something at least that
> complicated.
>
> Best regards,
> Maya
L
L
Ludovic Courtès wrote on 8 Jan 17:29 +0100
(name . Sergey Trofimov)(address . sarg@sarg.org.ru)
87zfxf4wlx.fsf@gnu.org
Hi Sergey and all,

Sergey Trofimov <sarg@sarg.org.ru> skribis:

Toggle quote (5 lines)
> Mája Tomášek <maya.tomasek@disroot.org> writes:
>
> Hi, guix has oci-container-service-type nowadays. Is this patch now
> obsolete?

Yes, looks like it (I had completely forgotten about it!).

I’m closing this issue but Mája, please let us know if you think of
changes that should be made to ‘oci-container-service-type’ to match
what you had proposed.

Thanks,
Ludo’.
Closed
?