Skip to content

Commit

Permalink
auto-update: support pods
Browse files Browse the repository at this point in the history
Support auto updating containers running inside pods.  Similar to
containers, the systemd units need to be generated via
`podman-generate-systemd --new $POD` to generate the pod's units.

Note that auto updating a container inside a pod will restart the entire
pod.  Updates of multiple containers inside a pod are batched, such that
a pod is restarted at most once.  That is effectively the same mechanism
for auto updating containers in a K8s YAML via the `podman-kube@`
template or via Quadlet.

Updating a single container unit without restarting the entire pod is
not possible.  The reasoning behind is that pods are created with
--exit-policy=stop which will render the pod to be stopped when auto
updating the only container inside the pod.  The (reverse) dependencies
between the pod and its containers unit have been carefully selected for
robustness.  Changes may entail undesired side effects or backward
incompatibilities that I am not comfortable with.

Fixes: containers#17181
Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
  • Loading branch information
vrothberg committed Feb 16, 2023
1 parent e8a8433 commit 53755b4
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 8 deletions.
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-auto-update.1.md.in
Expand Up @@ -26,6 +26,8 @@ At container-creation time, Podman looks up the `PODMAN_SYSTEMD_UNIT` environmen
This variable is now set by all systemd units generated by **[podman-generate-systemd](podman-generate-systemd.1.md)** and is set to `%n` (i.e., the name of systemd unit starting the container).
This data is then being used in the auto-update sequence to instruct systemd (via DBUS) to restart the unit and hence to restart the container.

If a container configured for auto updates is part of a pod, the pod's systemd unit will be restarted and hence the entire pod and all containers inside the pod. Container updates are batched, such that a pod gets restarted at most once.

Note that **podman auto-update** relies on systemd. The systemd units are expected to be generated with **[podman-generate-systemd --new](podman-generate-systemd.1.md#--new)**, or similar units that create new containers in order to run the updated images.
Systemd units that start and stop a container cannot run a new image.

Expand Down
10 changes: 9 additions & 1 deletion pkg/systemd/generate/containers.go
Expand Up @@ -42,6 +42,7 @@ type containerInfo struct {
containerEnv []string
ExtraEnvs []string
EnvVariable string
EnvValue string
AdditionalEnvVariables []string
ExecStart string
TimeoutStartSec uint
Expand Down Expand Up @@ -79,7 +80,7 @@ Requires={{{{- range $index, $value := .Requires }}}}{{{{ if $index}}}} {{{{end}
{{{{- end}}}}
[Service]
Environment={{{{.EnvVariable}}}}=%n{{{{- if (eq .IdentifySpecifier true) }}}}-%i {{{{- end}}}}
Environment={{{{.EnvVariable}}}}={{{{.EnvValue}}}}{{{{- if (eq .IdentifySpecifier true) }}}}-%i {{{{- end}}}}
{{{{- if .ExtraEnvs}}}}
Environment={{{{- range $index, $value := .ExtraEnvs -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}}
{{{{- end}}}}
Expand Down Expand Up @@ -302,6 +303,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst

info.Type = "forking"
info.EnvVariable = define.EnvVariable
info.EnvValue = "%n" // Name of the service
info.ExecStart = "{{{{.Executable}}}} start {{{{.ContainerNameOrID}}}}"
info.ExecStop = formatOptionsString("{{{{.Executable}}}} stop {{{{if (ge .StopTimeout 0)}}}} -t {{{{.StopTimeout}}}}{{{{end}}}} {{{{.ContainerNameOrID}}}}")
info.ExecStopPost = formatOptionsString("{{{{.Executable}}}} stop {{{{if (ge .StopTimeout 0)}}}} -t {{{{.StopTimeout}}}}{{{{end}}}} {{{{.ContainerNameOrID}}}}")
Expand All @@ -317,6 +319,12 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
// invalid `info.CreateCommand`. Hence, we're doing a best effort unit
// generation and don't try aiming at completeness.
if options.New {
if info.Pod != nil {
// If the container is part of pod, set the env
// variable to the pod's service to make sure auto
// updates function properly (see #17181).
info.EnvValue = info.Pod.ServiceName + ".service"
}
info.Type = "notify"
info.NotifyAccess = "all"
info.PIDFile = ""
Expand Down
10 changes: 6 additions & 4 deletions pkg/systemd/generate/containers_test.go
Expand Up @@ -427,7 +427,7 @@ After=network-online.target
RequiresMountsFor=/var/run/containers/storage
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Environment=PODMAN_SYSTEMD_UNIT=test-pod.service
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman run \
Expand Down Expand Up @@ -796,7 +796,7 @@ After=network-online.target
RequiresMountsFor=/var/run/containers/storage
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Environment=PODMAN_SYSTEMD_UNIT=test-pod.service
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman run \
Expand Down Expand Up @@ -1312,7 +1312,8 @@ WantedBy=default.target
GraphRoot: "/var/lib/containers/storage",
RunRoot: "/var/run/containers/storage",
Pod: &podInfo{
PodIDFile: "%t/pod-foobar.pod-id-file",
PodIDFile: "%t/pod-foobar.pod-id-file",
ServiceName: "test-pod",
},
},
goodNameNewWithPodFile,
Expand Down Expand Up @@ -1562,7 +1563,8 @@ WantedBy=default.target
CreateCommand: []string{"I'll get stripped", "create", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo", "--pod", "test", "awesome-image:latest", "podman", "run", "--cgroups=foo", "--conmon-pidfile=foo", "--cidfile=foo", "--pod-id-file", "/tmp/pod-foobar.pod-id-file", "alpine"},
EnvVariable: define.EnvVariable,
Pod: &podInfo{
PodIDFile: "%t/pod-foobar.pod-id-file",
PodIDFile: "%t/pod-foobar.pod-id-file",
ServiceName: "test-pod",
},
},
goodNewWithPodIDFiles,
Expand Down
6 changes: 3 additions & 3 deletions test/system/250-systemd.bats
Expand Up @@ -189,10 +189,10 @@ function check_listen_env() {
local stdenv="$1"
local context="$2"
if is_remote; then
is "$output" "$stdenv" "LISTEN Environment did not pass: $context"
is "$output" "$stdenv" "LISTEN Environment did not pass: $context"
else
out=$(for o in $output; do echo $o; done| sort)
std=$(echo "$stdenv
out=$(for o in $output; do echo $o; done| sort)
std=$(echo "$stdenv
LISTEN_PID=1
LISTEN_FDS=1
LISTEN_FDNAMES=listen_fdnames" | sort)
Expand Down
69 changes: 69 additions & 0 deletions test/system/255-auto-update.bats
Expand Up @@ -525,4 +525,73 @@ EOF
rm -f $UNIT_DIR/$unit_name
}

@test "podman auto-update - pod" {
dockerfile=$PODMAN_TMPDIR/Dockerfile
cat >$dockerfile <<EOF
FROM $IMAGE
RUN touch /123
EOF

podname=$(random_string)
ctrname=$(random_string)
podunit="$UNIT_DIR/pod-$podname.service.*"
ctrunit="$UNIT_DIR/container-$ctrname.service.*"
local_image=localhost/image:$(random_string 10)

run_podman tag $IMAGE $local_image

run_podman pod create --name=$podname
run_podman create --label "io.containers.autoupdate=local" --pod=$podname --name=$ctrname $local_image top

# cd into the unit dir to generate the two files.
pushd "$UNIT_DIR"
run_podman generate systemd --name --new --files $podname
is "$output" ".*$podunit.*"
is "$output" ".*$ctrunit.*"
# TODO: make sure the ctrunit points to the podunit
popd

systemctl daemon-reload

run systemctl start pod-$podname.service
assert $status -eq 0 "Error starting pod systemd unit: $output"
_wait_service_ready container-$ctrname.service

run_podman pod inspect --format "{{.State}}" $podname
is "$output" "Running" "pod is in running state"
run_podman container inspect --format "{{.State.Status}}" $ctrname
is "$output" "running" "container is in running state"

run_podman pod inspect --format "{{.ID}}" $podname
podid="$output"
run_podman container inspect --format "{{.ID}}" $ctrname
ctrid="$output"

# Note that the pod's unit is listed below, not the one of the container.
run_podman auto-update --dry-run --format "{{.Unit}},{{.Image}},{{.Updated}},{{.Policy}}"
is "$output" ".*pod-$podname.service,$local_image,false,local.*" "No update available"

run_podman build -t $local_image -f $dockerfile

run_podman auto-update --dry-run --format "{{.Unit}},{{.Image}},{{.Updated}},{{.Policy}}"
is "$output" ".*pod-$podname.service,$local_image,pending,local.*" "Image updated is pending"

run_podman auto-update --format "{{.Unit}},{{.Image}},{{.Updated}},{{.Policy}}"
is "$output" ".*pod-$podname.service,$local_image,true,local.*" "Service has been restarted"
_wait_service_ready container-$ctrname.service

run_podman pod inspect --format "{{.ID}}" $podname
assert "$output" != "$podid" "pod has been recreated"
run_podman container inspect --format "{{.ID}}" $ctrname
assert "$output" != "$ctrid" "container has been recreated"

run systemctl stop pod-$podname.service
assert $status -eq 0 "Error stopping pod systemd unit: $output"

run_podman pod rm -f $podname
run_podman rmi $local_image
rm -f $podunit $ctrunit
systemctl daemon-reload
}

# vim: filetype=sh

0 comments on commit 53755b4

Please sign in to comment.