mirror of
https://github.com/containers/podman.git
synced 2025-05-22 01:27:07 +08:00
kube play: support auto updates and rollbacks
Add auto-update support to `podman kube play`. Auto-update policies can be configured for: * the entire pod via the `io.containers.autoupdate` annotation * a specific container via the `io.containers.autoupdate/$name` annotation To make use of rollbacks, the `io.containers.sdnotify` policy should be set to `container` such that the workload running _inside_ the container can send the READY message via the NOTIFY_SOCKET once ready. For further details on auto updates and rollbacks, please refer to the specific article [1]. Since auto updates and rollbacks bases on Podman's systemd integration, the k8s YAML must be executed in the `podman-kube@` systemd template. For further details on how to run k8s YAML in systemd via Podman, please refer to the specific article [2]. An examplary k8s YAML may look as follows: ```YAML apiVersion: v1 kind: Pod metadata: annotations: io.containers.autoupdate: "local" io.containers.autoupdate/b: "registry" labels: app: test name: test_pod spec: containers: - command: - top image: alpine name: a - command: - top image: alpine name: b ``` [1] https://www.redhat.com/sysadmin/podman-auto-updates-rollbacks [2] https://www.redhat.com/sysadmin/kubernetes-workloads-podman-systemd Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
This commit is contained in:
@ -29,6 +29,18 @@ This data is then being used in the auto-update sequence to instruct systemd (vi
|
|||||||
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.
|
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.
|
Systemd units that start and stop a container cannot run a new image.
|
||||||
|
|
||||||
|
### Auto Updates and Kubernetes YAML
|
||||||
|
|
||||||
|
Podman supports auto updates for Kubernetes workloads. As mentioned above, `podman auto-update` requires the containers to be running systemd. Podman ships with a systemd template that can be instantiated with a Kubernetes YAML file, see podman-generate-systemd(1).
|
||||||
|
|
||||||
|
To enable auto updates for containers running in a Kubernetes workload, set the following Podman-specific annotations in the YAML:
|
||||||
|
* `io.containers.autoupdate: "registry|local"` to apply the auto-update policy to all containers
|
||||||
|
* `io.containers.autoupdate/$container: "registry|local"` to apply the auto-update policy to `$container` only
|
||||||
|
* `io.containers.sdnotify: "conmon|container"` to apply the sdnotify policy to all containers
|
||||||
|
* `io.containers.sdnotify/$container: "conmon|container"` to apply the sdnotify policy to `$container` only
|
||||||
|
|
||||||
|
By default, the autoupdate policy is set to "disabled", the sdnotify policy is set to "conmon".
|
||||||
|
|
||||||
### Systemd Unit and Timer
|
### Systemd Unit and Timer
|
||||||
|
|
||||||
Podman ships with a `podman-auto-update.service` systemd unit. This unit is triggered daily at midnight by the `podman-auto-update.timer` systemd timer. The timer can be altered for custom time-based updates if desired. The unit can further be invoked by other systemd units (e.g., via the dependency tree) or manually via **systemctl start podman-auto-update.service**.
|
Podman ships with a `podman-auto-update.service` systemd unit. This unit is triggered daily at midnight by the `podman-auto-update.timer` systemd timer. The timer can be altered for custom time-based updates if desired. The unit can further be invoked by other systemd units (e.g., via the dependency tree) or manually via **systemctl start podman-auto-update.service**.
|
||||||
|
@ -188,13 +188,8 @@ func AutoUpdate(ctx context.Context, runtime *libpod.Runtime, options entities.A
|
|||||||
// updateUnit auto updates the tasks in the specified systemd unit.
|
// updateUnit auto updates the tasks in the specified systemd unit.
|
||||||
func (u *updater) updateUnit(ctx context.Context, unit string, tasks []*task) []error {
|
func (u *updater) updateUnit(ctx context.Context, unit string, tasks []*task) []error {
|
||||||
var errors []error
|
var errors []error
|
||||||
// Sanity check: we'll support that in the future.
|
|
||||||
if len(tasks) != 1 {
|
|
||||||
errors = append(errors, fmt.Errorf("only 1 task per unit supported but unit %s has %d", unit, len(tasks)))
|
|
||||||
return errors
|
|
||||||
}
|
|
||||||
|
|
||||||
tasksUpdated := false
|
tasksUpdated := false
|
||||||
|
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
err := func() error { // Use an anonymous function to avoid spaghetti continue's
|
err := func() error { // Use an anonymous function to avoid spaghetti continue's
|
||||||
updateAvailable, err := task.updateAvailable(ctx)
|
updateAvailable, err := task.updateAvailable(ctx)
|
||||||
@ -255,6 +250,9 @@ func (u *updater) updateUnit(ctx context.Context, unit string, tasks []*task) []
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := u.restartSystemdUnit(ctx, unit); err != nil {
|
if err := u.restartSystemdUnit(ctx, unit); err != nil {
|
||||||
|
for _, task := range tasks {
|
||||||
|
task.status = statusFailed
|
||||||
|
}
|
||||||
err = fmt.Errorf("restarting unit %s during rollback: %w", unit, err)
|
err = fmt.Errorf("restarting unit %s during rollback: %w", unit, err)
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
return errors
|
return errors
|
||||||
@ -283,7 +281,16 @@ func (t *task) report() *entities.AutoUpdateReport {
|
|||||||
func (t *task) updateAvailable(ctx context.Context) (bool, error) {
|
func (t *task) updateAvailable(ctx context.Context) (bool, error) {
|
||||||
switch t.policy {
|
switch t.policy {
|
||||||
case PolicyRegistryImage:
|
case PolicyRegistryImage:
|
||||||
return t.registryUpdateAvailable(ctx)
|
// Errors checking for updates only should not be fatal.
|
||||||
|
// Especially on Edge systems, connection may be limited or
|
||||||
|
// there may just be a temporary downtime of the registry.
|
||||||
|
// But make sure to leave some breadcrumbs in the debug logs
|
||||||
|
// such that potential issues _can_ be analyzed if needed.
|
||||||
|
available, err := t.registryUpdateAvailable(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error checking updates for image %s: %v (ignoring error)", t.rawImageName, err)
|
||||||
|
}
|
||||||
|
return available, nil
|
||||||
case PolicyLocalImage:
|
case PolicyLocalImage:
|
||||||
return t.localUpdateAvailable()
|
return t.localUpdateAvailable()
|
||||||
default:
|
default:
|
||||||
|
@ -661,9 +661,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
|||||||
|
|
||||||
opts = append(opts, libpod.WithSdNotifyMode(sdNotifyMode))
|
opts = append(opts, libpod.WithSdNotifyMode(sdNotifyMode))
|
||||||
|
|
||||||
|
var proxy *notifyproxy.NotifyProxy
|
||||||
// Create a notify proxy for the container.
|
// Create a notify proxy for the container.
|
||||||
if sdNotifyMode != "" && sdNotifyMode != define.SdNotifyModeIgnore {
|
if sdNotifyMode != "" && sdNotifyMode != define.SdNotifyModeIgnore {
|
||||||
proxy, err := notifyproxy.New("")
|
proxy, err = notifyproxy.New("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -675,6 +676,9 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if proxy != nil {
|
||||||
|
proxy.AddContainer(ctr)
|
||||||
|
}
|
||||||
containers = append(containers, ctr)
|
containers = append(containers, ctr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -774,20 +778,25 @@ func (ic *ContainerEngine) getImageAndLabelInfo(ctx context.Context, cwd string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle kube annotations
|
// Handle kube annotations
|
||||||
|
setLabel := func(label string) {
|
||||||
|
var result string
|
||||||
|
ctrSpecific := fmt.Sprintf("%s/%s", label, container.Name)
|
||||||
for k, v := range annotations {
|
for k, v := range annotations {
|
||||||
switch k {
|
switch k {
|
||||||
// Auto update annotation without container name will apply to
|
case label:
|
||||||
// all containers within the pod
|
result = v
|
||||||
case autoupdate.Label, autoupdate.AuthfileLabel:
|
case ctrSpecific:
|
||||||
labels[k] = v
|
labels[label] = v
|
||||||
// Auto update annotation with container name will apply only
|
return
|
||||||
// to the specified container
|
|
||||||
case fmt.Sprintf("%s/%s", autoupdate.Label, container.Name),
|
|
||||||
fmt.Sprintf("%s/%s", autoupdate.AuthfileLabel, container.Name):
|
|
||||||
prefixAndCtr := strings.Split(k, "/")
|
|
||||||
labels[prefixAndCtr[0]] = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if result != "" {
|
||||||
|
labels[label] = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLabel(autoupdate.Label)
|
||||||
|
setLabel(autoupdate.AuthfileLabel)
|
||||||
|
|
||||||
return pulledImage, labels, nil
|
return pulledImage, labels, nil
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -26,6 +27,7 @@ import (
|
|||||||
"github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/api/resource"
|
"github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/api/resource"
|
||||||
"github.com/containers/podman/v4/pkg/specgen"
|
"github.com/containers/podman/v4/pkg/specgen"
|
||||||
"github.com/containers/podman/v4/pkg/specgen/generate"
|
"github.com/containers/podman/v4/pkg/specgen/generate"
|
||||||
|
systemdDefine "github.com/containers/podman/v4/pkg/systemd/define"
|
||||||
"github.com/containers/podman/v4/pkg/util"
|
"github.com/containers/podman/v4/pkg/util"
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
@ -445,6 +447,12 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the container runs in a systemd unit which is
|
||||||
|
// stored as a label at container creation.
|
||||||
|
if unit := os.Getenv(systemdDefine.EnvVariable); unit != "" {
|
||||||
|
s.Labels[systemdDefine.EnvVariable] = unit
|
||||||
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
package notifyproxy
|
package notifyproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
"github.com/coreos/go-systemd/v22/daemon"
|
"github.com/coreos/go-systemd/v22/daemon"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -39,6 +44,7 @@ func SendMessage(socketPath string, message string) error {
|
|||||||
type NotifyProxy struct {
|
type NotifyProxy struct {
|
||||||
connection *net.UnixConn
|
connection *net.UnixConn
|
||||||
socketPath string
|
socketPath string
|
||||||
|
container Container // optional
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a NotifyProxy. The specified temp directory can be left empty.
|
// New creates a NotifyProxy. The specified temp directory can be left empty.
|
||||||
@ -77,9 +83,26 @@ func (p *NotifyProxy) close() error {
|
|||||||
return p.connection.Close()
|
return p.connection.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddContainer associates a container with the proxy.
|
||||||
|
func (p *NotifyProxy) AddContainer(container Container) {
|
||||||
|
p.container = container
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoReadyMessage is returned when we are waiting for the READY message of a
|
||||||
|
// container that is not in the running state anymore.
|
||||||
|
var ErrNoReadyMessage = errors.New("container stopped running before READY message was received")
|
||||||
|
|
||||||
|
// Container avoids a circular dependency among this package and libpod.
|
||||||
|
type Container interface {
|
||||||
|
State() (define.ContainerStatus, error)
|
||||||
|
ID() string
|
||||||
|
}
|
||||||
|
|
||||||
// WaitAndClose waits until receiving the `READY` notify message and close the
|
// WaitAndClose waits until receiving the `READY` notify message and close the
|
||||||
// listener. Note that the this function must only be executed inside a systemd
|
// listener. Note that the this function must only be executed inside a systemd
|
||||||
// service which will kill the process after a given timeout.
|
// service which will kill the process after a given timeout.
|
||||||
|
// If the (optional) container stopped running before the `READY` is received,
|
||||||
|
// the waiting gets canceled and ErrNoReadyMessage is returned.
|
||||||
func (p *NotifyProxy) WaitAndClose() error {
|
func (p *NotifyProxy) WaitAndClose() error {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := p.close(); err != nil {
|
if err := p.close(); err != nil {
|
||||||
@ -87,16 +110,48 @@ func (p *NotifyProxy) WaitAndClose() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
const bufferSize = 1024
|
||||||
|
sBuilder := strings.Builder{}
|
||||||
for {
|
for {
|
||||||
buf := make([]byte, 1024)
|
// Set a read deadline of one second such that we achieve a
|
||||||
num, err := p.connection.Read(buf)
|
// non-blocking read and can check if the container has already
|
||||||
if err != nil {
|
// stopped running; in that case no READY message will be send
|
||||||
|
// and we're done.
|
||||||
|
if err := p.connection.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, s := range strings.Split(string(buf[:num]), "\n") {
|
|
||||||
if s == daemon.SdNotifyReady {
|
for {
|
||||||
|
buffer := make([]byte, bufferSize)
|
||||||
|
num, err := p.connection.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, os.ErrDeadlineExceeded) && !errors.Is(err, io.EOF) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sBuilder.Write(buffer[:num])
|
||||||
|
if num != bufferSize || buffer[num-1] == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(sBuilder.String(), "\n") {
|
||||||
|
if line == daemon.SdNotifyReady {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sBuilder.Reset()
|
||||||
|
|
||||||
|
if p.container == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := p.container.State()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if state != define.ContainerStateRunning {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNoReadyMessage, p.container.ID())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func TestWaitAndClose(t *testing.T) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessage(t, proxy, daemon.SdNotifyReady+"\nsomething else")
|
sendMessage(t, proxy, daemon.SdNotifyReady+"\nsomething else\n")
|
||||||
done := func() bool {
|
done := func() bool {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
select {
|
select {
|
||||||
|
@ -301,24 +301,16 @@ LISTEN_FDNAMES=listen_fdnames" | sort)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@test "podman-kube@.service template" {
|
@test "podman-kube@.service template" {
|
||||||
# If running from a podman source directory, build and use the source
|
install_kube_template
|
||||||
# version of the play-kube-@ unit file
|
|
||||||
unit_name="podman-kube@.service"
|
|
||||||
unit_file="contrib/systemd/system/${unit_name}"
|
|
||||||
if [[ -e ${unit_file}.in ]]; then
|
|
||||||
echo "# [Building & using $unit_name from source]" >&3
|
|
||||||
# Force regenerating unit file (existing one may have /usr/bin path)
|
|
||||||
rm -f $unit_file
|
|
||||||
BINDIR=$(dirname $PODMAN) make $unit_file
|
|
||||||
cp $unit_file $UNIT_DIR/$unit_name
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create the YAMl file
|
# Create the YAMl file
|
||||||
yaml_source="$PODMAN_TMPDIR/test.yaml"
|
yaml_source="$PODMAN_TMPDIR/test.yaml"
|
||||||
cat >$yaml_source <<EOF
|
cat >$yaml_source <<EOF
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
metadata:
|
metadata:
|
||||||
|
annotations:
|
||||||
|
io.containers.autoupdate: "local"
|
||||||
|
io.containers.autoupdate/b: "registry"
|
||||||
labels:
|
labels:
|
||||||
app: test
|
app: test
|
||||||
name: test_pod
|
name: test_pod
|
||||||
@ -327,8 +319,11 @@ spec:
|
|||||||
- command:
|
- command:
|
||||||
- top
|
- top
|
||||||
image: $IMAGE
|
image: $IMAGE
|
||||||
name: test
|
name: a
|
||||||
resources: {}
|
- command:
|
||||||
|
- top
|
||||||
|
image: $IMAGE
|
||||||
|
name: b
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Dispatch the YAML file
|
# Dispatch the YAML file
|
||||||
@ -349,6 +344,12 @@ EOF
|
|||||||
run_podman 125 container rm $service_container
|
run_podman 125 container rm $service_container
|
||||||
is "$output" "Error: container .* is the service container of pod(s) .* and cannot be removed without removing the pod(s)"
|
is "$output" "Error: container .* is the service container of pod(s) .* and cannot be removed without removing the pod(s)"
|
||||||
|
|
||||||
|
# Add a simple `auto-update --dry-run` test here to avoid too much redundancy
|
||||||
|
# with 255-auto-update.bats
|
||||||
|
run_podman auto-update --dry-run --format "{{.Unit}},{{.Container}},{{.Image}},{{.Updated}},{{.Policy}}"
|
||||||
|
is "$output" ".*$service_name,.* (test_pod-a),$IMAGE,false,local.*" "global auto-update policy gets applied"
|
||||||
|
is "$output" ".*$service_name,.* (test_pod-b),$IMAGE,false,registry.*" "container-specified auto-update policy gets applied"
|
||||||
|
|
||||||
# Kill the pod and make sure the service is not running.
|
# Kill the pod and make sure the service is not running.
|
||||||
# The restart policy is set to "never" since there is no
|
# The restart policy is set to "never" since there is no
|
||||||
# design yet for propagating exit codes up to the service
|
# design yet for propagating exit codes up to the service
|
||||||
|
@ -266,8 +266,6 @@ EOF
|
|||||||
|
|
||||||
# Generate a healthy image that will run correctly.
|
# Generate a healthy image that will run correctly.
|
||||||
run_podman build -t quay.io/libpod/$image -f $dockerfile1
|
run_podman build -t quay.io/libpod/$image -f $dockerfile1
|
||||||
podman image inspect --format "{{.ID}}" $image
|
|
||||||
oldID="$output"
|
|
||||||
|
|
||||||
generate_service $image local /runme --sdnotify=container noTag
|
generate_service $image local /runme --sdnotify=container noTag
|
||||||
_wait_service_ready container-$cname.service
|
_wait_service_ready container-$cname.service
|
||||||
@ -277,7 +275,7 @@ EOF
|
|||||||
|
|
||||||
# Generate an unhealthy image that will fail.
|
# Generate an unhealthy image that will fail.
|
||||||
run_podman build -t quay.io/libpod/$image -f $dockerfile2
|
run_podman build -t quay.io/libpod/$image -f $dockerfile2
|
||||||
podman image inspect --format "{{.ID}}" $image
|
run_podman image inspect --format "{{.ID}}" $image
|
||||||
newID="$output"
|
newID="$output"
|
||||||
|
|
||||||
run_podman auto-update --dry-run --format "{{.Unit}},{{.Image}},{{.Updated}},{{.Policy}}"
|
run_podman auto-update --dry-run --format "{{.Unit}},{{.Image}},{{.Updated}},{{.Policy}}"
|
||||||
@ -409,4 +407,97 @@ EOF
|
|||||||
_confirm_update $cname $ori_image
|
_confirm_update $cname $ori_image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "podman-kube@.service template with rollback" {
|
||||||
|
# sdnotify fails with runc 1.0.0-3-dev2 on Ubuntu. Let's just
|
||||||
|
# assume that we work only with crun, nothing else.
|
||||||
|
# [copied from 260-sdnotify.bats]
|
||||||
|
runtime=$(podman_runtime)
|
||||||
|
if [[ "$runtime" != "crun" ]]; then
|
||||||
|
skip "this test only works with crun, not $runtime"
|
||||||
|
fi
|
||||||
|
|
||||||
|
install_kube_template
|
||||||
|
|
||||||
|
dockerfile1=$PODMAN_TMPDIR/Dockerfile.1
|
||||||
|
cat >$dockerfile1 <<EOF
|
||||||
|
FROM quay.io/libpod/fedora:31
|
||||||
|
RUN echo -e "#!/bin/sh\n\
|
||||||
|
printenv NOTIFY_SOCKET; echo READY; systemd-notify --ready;\n\
|
||||||
|
trap 'echo Received SIGTERM, finishing; exit' SIGTERM; echo WAITING; while :; do sleep 0.1; done" \
|
||||||
|
>> /runme
|
||||||
|
RUN chmod +x /runme
|
||||||
|
EOF
|
||||||
|
|
||||||
|
dockerfile2=$PODMAN_TMPDIR/Dockerfile.2
|
||||||
|
cat >$dockerfile2 <<EOF
|
||||||
|
FROM quay.io/libpod/fedora:31
|
||||||
|
RUN echo -e "#!/bin/sh\n\
|
||||||
|
exit 1" >> /runme
|
||||||
|
RUN chmod +x /runme
|
||||||
|
EOF
|
||||||
|
local_image=localhost/image:$(random_string 10)
|
||||||
|
|
||||||
|
# Generate a healthy image that will run correctly.
|
||||||
|
run_podman build -t $local_image -f $dockerfile1
|
||||||
|
run_podman image inspect --format "{{.ID}}" $local_image
|
||||||
|
oldID="$output"
|
||||||
|
|
||||||
|
# Create the YAMl file
|
||||||
|
yaml_source="$PODMAN_TMPDIR/test.yaml"
|
||||||
|
cat >$yaml_source <<EOF
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
io.containers.autoupdate: "registry"
|
||||||
|
io.containers.autoupdate/b: "local"
|
||||||
|
io.containers.sdnotify/b: "container"
|
||||||
|
labels:
|
||||||
|
app: test
|
||||||
|
name: test_pod
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- command:
|
||||||
|
- top
|
||||||
|
image: $IMAGE
|
||||||
|
name: a
|
||||||
|
- command:
|
||||||
|
- /runme
|
||||||
|
image: $local_image
|
||||||
|
name: b
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Dispatch the YAML file
|
||||||
|
service_name="podman-kube@$(systemd-escape $yaml_source).service"
|
||||||
|
systemctl start $service_name
|
||||||
|
systemctl is-active $service_name
|
||||||
|
|
||||||
|
# Make sure the containers are properly configured
|
||||||
|
run_podman auto-update --dry-run --format "{{.Unit}},{{.Container}},{{.Image}},{{.Updated}},{{.Policy}}"
|
||||||
|
is "$output" ".*$service_name,.* (test_pod-a),$IMAGE,false,registry.*" "global auto-update policy gets applied"
|
||||||
|
is "$output" ".*$service_name,.* (test_pod-b),$local_image,false,local.*" "container-specified auto-update policy gets applied"
|
||||||
|
|
||||||
|
# Generate a broken image that will fail.
|
||||||
|
run_podman build -t $local_image -f $dockerfile2
|
||||||
|
run_podman image inspect --format "{{.ID}}" $local_image
|
||||||
|
newID="$output"
|
||||||
|
|
||||||
|
assert "$oldID" != "$newID" "broken image really is a new one"
|
||||||
|
|
||||||
|
# Make sure container b sees the new image
|
||||||
|
run_podman auto-update --dry-run --format "{{.Unit}},{{.Container}},{{.Image}},{{.Updated}},{{.Policy}}"
|
||||||
|
is "$output" ".*$service_name,.* (test_pod-a),$IMAGE,false,registry.*" "global auto-update policy gets applied"
|
||||||
|
is "$output" ".*$service_name,.* (test_pod-b),$local_image,pending,local.*" "container b sees the new image"
|
||||||
|
|
||||||
|
# Now update and check for the rollback
|
||||||
|
run_podman auto-update --format "{{.Unit}},{{.Container}},{{.Image}},{{.Updated}},{{.Policy}}"
|
||||||
|
is "$output" ".*$service_name,.* (test_pod-a),$IMAGE,rolled back,registry.*" "container a was rolled back as the update of b failed"
|
||||||
|
is "$output" ".*$service_name,.* (test_pod-b),$local_image,rolled back,local.*" "container b was rolled back as its update has failed"
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
systemctl stop $service_name
|
||||||
|
run_podman rmi -f $(pause_image) $local_image $newID $oldID
|
||||||
|
rm -f $UNIT_DIR/$unit_name
|
||||||
|
}
|
||||||
|
|
||||||
# vim: filetype=sh
|
# vim: filetype=sh
|
||||||
|
@ -32,3 +32,17 @@ journalctl() {
|
|||||||
systemd-run() {
|
systemd-run() {
|
||||||
command systemd-run $_DASHUSER "$@";
|
command systemd-run $_DASHUSER "$@";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_kube_template() {
|
||||||
|
# If running from a podman source directory, build and use the source
|
||||||
|
# version of the play-kube-@ unit file
|
||||||
|
unit_name="podman-kube@.service"
|
||||||
|
unit_file="contrib/systemd/system/${unit_name}"
|
||||||
|
if [[ -e ${unit_file}.in ]]; then
|
||||||
|
echo "# [Building & using $unit_name from source]" >&3
|
||||||
|
# Force regenerating unit file (existing one may have /usr/bin path)
|
||||||
|
rm -f $unit_file
|
||||||
|
BINDIR=$(dirname $PODMAN) make $unit_file
|
||||||
|
cp $unit_file $UNIT_DIR/$unit_name
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user