Files
infix/board/common/rootfs/usr/sbin/container
T
Joachim Wiberg 360d3b322d confd: use podman stop/start to prevent container corruption
Sending SIGTERM to conmon is not a safe shutdown of a podman container.
To handle gracefully handle shutdown, restarting and provide an orderly
start of dependencies, we use the Finit sysv trick via container script
wrapper to call 'podman stop foo'.

However, since podman does not support syslog as output for containers
we employ an old FIFO trick with another program, k8s-logger, to allow
logs to reach syslog.  Please note that k8s-logger must have properly
started before we call `podman start` -- this makes us fully dependent
on the 'container' wrapper script.  Hence the documentation update.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-02-25 19:49:27 +01:00

591 lines
13 KiB
Bash
Executable File

#!/bin/sh
all=""
env=""
port=""
force=
log()
{
logger -I $PPID -t container -p local1.notice -- "$*"
}
# Unpacks a given oci-archive.tar[.gz] in the current directory. Sanity
# checks, at least one index.json in the top-level dir of the archive.
# If there are more index files, this function does not handle them.
unpack_archive()
{
image=$1
name=$2
# Supported transports for load and create
case "$image" in
oci:*) # Unpacked OCI image
file=${image#oci:}
;;
oci-archive:*) # Packed OCI image, .tar or .tar.gz format
file=${image#oci-archive:}
;;
*) # docker://*, docker-archive:*, or URL
echo "$image"
return 0
;;
esac
if [ ! -e "$file" ]; then
if [ -e "/var/lib/conatainers/oci/$file" ]; then
file="/var/lib/conatainers/oci/$file"
elif [ -e "/lib/oci/$file" ]; then
file="/lib/oci/$file"
else
log "Error: cannot find OCI archive $file in search path."
exit 1
fi
fi
if [ -d "$file" ]; then
index=$(find "$file" -name index.json)
if [ -z "$index" ]; then
log "Error: cannot find index.json in OCI image $file"
exit 1
fi
else
index=$(tar tf "$file" |grep index.json)
if [ -z "$index" ]; then
log "Error: invalid OCI archive, cannot find index.json in $file"
exit 1
fi
[ -n "$quiet" ] || log "Extracting OCI archive $file ..."
tar xf "$file" || (log "Error: failed unpacking $file in $(pwd)"; exit 1)
fi
dir=$(dirname "$index")
[ -n "$quiet" ] || log "Loading OCI image $dir ..."
podman load -qi "$dir" >/dev/null
# Rename image from podman default $dir:latest
if [ -n "$name" ]; then
podman tag "$dir" "$name" >/dev/null
podman rmi "$dir" >/dev/null
else
name=$dir
fi
echo "$name"
}
running()
{
run=$(podman inspect "$1" 2>/dev/null |jq .[].State.Running)
[ "$run" = "true" ] && return 0
return 1
}
# shellcheck disable=SC2086
create()
{
name=$1
image=$2
shift 2
if [ -z "$name" ] || [ -z "$image" ]; then
echo "Usage:"
echo " container create NAME IMAGE"
exit 1
fi
# Unpack and load docker-archive/oci/oci-archive, returning image
# name, or return docker:// URL for download.
image=$(unpack_archive "$image")
if [ -z "$logging" ]; then
logging="--log-driver k8s-file --log-opt path=/run/containers/$name.fifo"
fi
args="$args --cgroup-parent=containers"
args="$args --restart=$restart --systemd=false --tz=local $privileged --replace --quiet"
args="$args $ro $vol $mount $hostname $entrypoint $env $port $logging"
pidfn=/run/container:${name}.pid
[ -n "$quiet" ] || log "---------------------------------------"
[ -n "$quiet" ] || log "Got name: $name image: $image"
[ -n "$quiet" ] || log "Got networks: $network"
if [ -n "$network" ]; then
for net in $network; do
args="$args --net=$net"
done
for srv in $dns; do
args="$args --dns=$srv"
done
for domain in $search; do
args="$args --dns-search=$domain"
done
else
network="--net none"
fi
# shellcheck disable=SC2048
if podman create --name "$name" --conmon-pidfile="$pidfn" $args "$image" $*; then
[ -n "$quiet" ] || log "Successfully created container $name from $image"
rm -f "/run/containers/env/${name}.env"
[ -n "$manual" ] || start "$name"
exit 0
fi
log "Error: failed creating container $name, please check the configuration."
exit 1
}
delete()
{
name=$1
image=$2
if [ -z "$name" ]; then
echo "Usage:"
echo " container delete NAME"
exit 1
fi
podman rm -vif "$name" >/dev/null 2>&1
[ -n "$quiet" ] || log "Container $name has been removed."
}
waitfor()
{
timeout=$2
while [ ! -f "$1" ]; do
_=$((timeout -= 1))
if [ $timeout -le 0 ]; then
log "Timeout waiting for $1, aborting!"
exit 1
fi
sleep 1;
done
}
start()
{
name=$1
if running "$name"; then
[ -n "$quiet" ] || echo "$name: already running."
return
fi
initctl -bq cond set "container:$name"
# Real work is done by wrap() courtesy of finit sysv emulation
}
stop()
{
name=$1
if ! running "$name"; then
[ -n "$quiet" ] || echo "$name: not running."
return
fi
initctl -bq cond clr "container:$name"
# Real work is done by wrap() courtesy of finit sysv emulation
}
wrap()
{
name=$1
cmd=$2
podman "$cmd" "$name"
}
# Removes network $1 from all containers
netwrm()
{
net=$1
for c in $(podman ps $all --format "{{.Names}}"); do
for n in $(podman inspect "$c" |jq -r '.[].NetworkSettings.Networks | keys[]'); do
if [ "$n" = "$net" ]; then
podman network disconnect $force "$n" "$c" >/dev/null
fi
done
done
}
usage()
{
cat <<EOF
usage:
container [opt] cmd [arg]
options:
-a, --all Show all, of something
--dns NAMESERVER Set nameserver(s) when creating a container
--dns-search LIST Set host lookup search list when creating container
-c, --creds USR[:PWD] Credentials to pass to curl -u for remote ops
-d, --detach Detach a container started with 'run IMG [CMD]'
-e, --env FILE Environment variables when creating container
--entrypoint Disable container image's ENTRYPOINT, run cmd + arg
-f, --force Force operation, e.g. remove
-h, --help Show this help text
--hostname NAME Set hostname when creating container
--net NETWORK Network interface(s) when creating or finding container
-l, --log-driver DRV Log driver to use
--log-opt OPT Logging options to log driver
--log-path PATH Path for k8s-file log pipe
-m, --mount HOST:DEST Bind mount a read-only file inside a container
--manual Do not start container automatically after creation
-n, --name NAME Alternative way of supplying name to start/stop/restart
--privileged Give container extended privileges
-p, --publish PORT Publish ports when creating container
Syntax: [[ip:][hostPort]:]containerPort[/protocol]
-q, --quiet Quiet operation, called from confd
-r, --restart POLICY One of "no", "always", or "on-failure:NUM"
--read-only Do not create a writable layer
-s, --simple Show output in simplified format
-v, --volume NAME:PATH Create named volume mounted inside container on PATH
commands:
create NAME IMAGE NET Create container NAME using IMAGE with networks NET
delete [network] NAME Remove container NAME or network NAME from all containers
exec NAME CMD Run a command inside a container
find [ifname PID] Find PID of container where '--net IFNAME' currently lives
or, find the name of our IFNAME inside the container @PID
help Show this help text
list [image | oci] List names (only) of containers, images, or OCI archives
load [NAME | URL] NM Load OCI tarball fileNAME or URL to image NM
remove IMAGE Remove an (unused) container image
restart [NAME] Restart a crashed container
run NAME [CMD] Run a container interactively, with an optional command
save IMAGE FILE Save a container image to an OCI tarball FILE[.tar.gz]
shell Start a shell inside a container
show [image | volume] Show containers, images, or volumes
stat Show continuous stats about containers (Ctrl-C aborts)
start [NAME] Start a container, see -n
stop [NAME] Stop a container, see -n
volume [prune] Prune unused volumes
EOF
}
while [ "$1" != "" ]; do
case $1 in
-a | --all)
all="-a"
;;
-c | --creds)
shift
creds="-u $1"
;;
-d | --detach)
detach="-d"
;;
--dns)
shift
dns="$dns $1"
;;
--dns-search)
shift
search="$search $1"
;;
-e | --env)
shift
env="$env --env-file=$1"
;;
--entrypoint)
entrypoint="--entrypoint=\"\""
;;
-f | --force)
force="-f"
;;
-h | --help)
usage
exit 0
;;
--hostname)
shift
hostname="--hostname $1"
;;
-l | --log-driver)
shift
logging=" --log-driver=$1"
;;
--log-opt)
shift
logging="$logging --log-opt $1"
;;
--log-path)
shift
logging="$logging --log-opt path=$1"
log_path="$1"
;;
-m | --mount)
shift
mount="--mount=$1"
;;
--manual)
manual=true
;;
-n | --name)
shift
name="$1"
;;
--net)
shift
if [ -n "$network" ]; then
network="$network $1"
else
network=$1
fi
;;
--privileged)
privileged="--privileged=true"
;;
-p | --publish)
shift
port="$port -p $1"
;;
-q | --quiet)
quiet="-q"
;;
-r | --restart)
shift
restart=$1
;;
--read-only)
ro="--read-only=true"
;;
-s | --simple)
simple=true
;;
-v | --volume)
shift
vol="$vol -v $1"
;;
*)
break
;;
esac
shift
done
cmd=$1
if [ -n "$cmd" ]; then
shift
fi
case $cmd in
# Does not work atm., cannot attach to TTY because
# we monitor 'podman start -ai foo' with Finit.
# attach)
# podman attach "$1"
# ;;
create)
[ -n "$quiet" ] || log "Got create args: $*"
create "$@"
;;
delete)
cmd=$1
name=$2
if [ "$cmd" = "network" ] && [ -n "$name" ]; then
netwrm "$name"
else
delete "$@"
fi
;;
exec)
podman exec -it "$@"
;;
find)
cmd=$1
pid=$2
if [ "$cmd" = "ifname" ] && [ -n "$pid" ]; then
nsenter -t "$pid" -n ip -d -j link | \
jq --arg ifname "$network" -r '.[] | select(.ifalias==$ifname) | .ifname'
else
containers=$(podman ps $all --format "{{.Names}}")
for c in $containers; do
json=$(podman inspect "$c")
nets=$(echo "$json" |jq -r '.[].NetworkSettings.Networks | keys[]' 2>/dev/null)
for n in $nets; do
if [ "$network" = "$n" ]; then
pid=$(echo "$json" | jq .[].State.Pid)
echo "$pid"
exit 0
fi
done
done
fi
;;
help)
usage
;;
load)
url=$1
name=$2
# shellcheck disable=SC2086
if echo "$url" | grep -q "://"; then
file=$(basename "$url")
curl -k $creds -Lo "$file" "$url"
else
file="$url"
fi
# shellcheck disable=SC2086
name=$(unpack_archive "$file" $name)
# Show resulting image(s) matching $name
if [ -n "$name" ]; then
podman images -n "$name"
else
exit 1
fi
;;
ls | list)
cmd=$1
[ -n "$cmd" ] && shift
case $cmd in
image*)
podman images $all --format "{{.Repository}}:{{.Tag}}"
;;
oci)
find /lib/oci /var/lib/containers/oci -type f 2>/dev/null
;;
*)
podman ps $all --format "{{.Names}}"
;;
esac
;;
pull)
podman pull "$@"
;;
remove)
podman rmi $all $force -i "$1"
;;
run)
img=$1
cmd=$2
[ -n "$port" ] || port="-P"
if [ -n "$cmd" ]; then
shift 2
[ -n "$detach" ] || echo "Starting $img ENTRYPOINT $cmd :: use Ctrl-p Ctrl-q to detach"
podman run -it --rm $detach $port --entrypoint="$cmd" "$img" "$@"
else
[ -n "$detach" ] || echo "Starting $img :: use Ctrl-p Ctrl-q to detach"
podman run -it --rm $detach $port "$img"
fi
;;
save)
name=$1
file=$2
if echo "$file" | grep -q ".gz"; then
file=${file%%.gz}
gzip=true
fi
if ! echo "$file" | grep -q ".tar"; then
file=${file}.tar
gzip=true
fi
podman save -o "$file" "$name"
if [ -s "$file" ] && [ -n "$gzip" ]; then
gzip "$file"
fi
;;
shell)
podman exec -it "$1" sh -l
;;
show)
cmd=$1
[ -n "$cmd" ] && shift
case $cmd in
image*)
if [ -n "$simple" ]; then
podman images $all --format "{{.Names}} {{.Size}}" \
| sed 's/\[\(.*\)\] /\1 /g' \
| awk '{ printf "%-60s %s %s\n", $1, $2, $3}'
else
podman images $all
fi
;;
volume*)
printf "%-20s CONTAINER\n" "VOLUME"
for v in $(podman volume ls --format "{{.Name}}"); do
printf "%-20s" "$v"
podman ps -a --filter volume="$v" --format '{{.Names}}' | sed 's/^/ /'
done
;;
*)
if [ -n "$simple" ]; then
podman ps $all --format "{{.ID}} {{.Names}} {{.Image}}" \
| awk '{ printf "%s %-30s %s\n", $1, $2, $3}'
else
podman ps $all
fi
;;
esac
;;
start)
if [ -n "$name" ]; then
wrap "$name" start
elif [ -n "$1" ]; then
start "$1"
else
usage
exit 1
fi
;;
restart)
if [ -n "$name" ]; then
wrap "$name" restart
elif [ -n "$1" ]; then
stop "$1"
start "$1"
else
usage
exit 1
fi
;;
stop)
if [ -n "$name" ]; then
wrap "$name" stop
elif [ -n "$1" ]; then
stop "$1"
else
usage
exit 1
fi
;;
stat*)
podman stats -i 2
;;
upgrade)
img=$(podman inspect "$1" | jq -r .[].ImageName)
if [ -z "$img" ]; then
echo "No such container ($1), or invalid ImageName. Cannot upgrade."
exit 1;
fi
podman stop "$1"
podman pull "$img" || (echo "Failed fetching $img, check your network (settings)."; exit 1)
"/var/lib/containers/active/${1}.sh" || (echo "Failed recreating container $1"; exit 1)
;;
volume)
cmd=$1
[ -n "$cmd" ] && shift
case $cmd in
prune)
podman volume $force prune
;;
*)
false
;;
esac
;;
*)
usage
exit 1
;;
esac