From: Daan De Meyer Date: Sun, 31 Mar 2024 17:54:22 +0000 (+0200) Subject: Add ForwardJournal= to enable log forwarding of VMs and containers X-Git-Tag: v23~41^2 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F2572%2Fhead;p=thirdparty%2Fmkosi.git Add ForwardJournal= to enable log forwarding of VMs and containers In systemd v256, journald will support forwarding to systemd-journal-remote via the new journal.forward_to_socket credential. Let's expose this functionality via a new ForwardJournal= setting, which specifies a path to which logs should be forwarded. --- diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 67493ad01..135f15286 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -12,6 +12,7 @@ import os import resource import shlex import shutil +import socket import stat import subprocess import sys @@ -59,7 +60,7 @@ from mkosi.manifest import Manifest from mkosi.mounts import finalize_source_mounts, mount_overlay from mkosi.pager import page from mkosi.partition import Partition, finalize_root, finalize_roothash -from mkosi.qemu import KernelType, copy_ephemeral, run_qemu, run_ssh +from mkosi.qemu import KernelType, copy_ephemeral, run_qemu, run_ssh, start_journal_remote from mkosi.run import ( find_binary, fork_and_wait, @@ -3778,6 +3779,19 @@ def run_shell(args: Args, config: Config) -> None: os.chmod(scratch, 0o1777) cmdline += ["--bind", f"{scratch}:/var/tmp"] + if args.verb == Verb.boot and config.forward_journal: + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: + addr = Path(os.getenv("TMPDIR", "/tmp")) / f"mkosi-journal-remote-unix-{uuid.uuid4().hex[:16]}" + sock.bind(os.fspath(addr)) + sock.listen() + if config.output_format == OutputFormat.directory and (stat := os.stat(fname)).st_uid != 0: + os.chown(addr, stat.st_uid, stat.st_gid) + stack.enter_context(start_journal_remote(config, sock.fileno())) + cmdline += [ + "--bind", f"{addr}:/run/host/journal/socket", + "--set-credential=journal.forward_to_socket:/run/host/journal/socket", + ] + if args.verb == Verb.boot: # Add nspawn options first since systemd-nspawn ignores all options after the first argument. cmdline += args.cmdline diff --git a/mkosi/config.py b/mkosi/config.py index aa8696957..4fcd6e9f8 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -1434,6 +1434,7 @@ class Config: ssh_key: Optional[Path] ssh_certificate: Optional[Path] machine: Optional[str] + forward_journal: Optional[Path] vmm: Vmm # QEMU-specific options @@ -2780,6 +2781,13 @@ SETTINGS = ( section="Host", help="Set the machine name to use when booting the image", ), + ConfigSetting( + dest="forward_journal", + metavar="PATH", + section="Host", + parse=config_make_path_parser(required=False), + help="Set the path used to store forwarded machine journals", + ), ConfigSetting( dest="qemu_gui", metavar="BOOL", @@ -3892,6 +3900,7 @@ def summary(config: Config) -> str: SSH Signing Key: {none_to_none(config.ssh_key)} SSH Certificate: {none_to_none(config.ssh_certificate)} Machine: {config.machine_or_name()} + Forward Journal: {none_to_none(config.forward_journal)} Virtual Machine Monitor: {config.vmm} QEMU GUI: {yes_no(config.qemu_gui)} diff --git a/mkosi/qemu.py b/mkosi/qemu.py index 64820f7e4..3ca175d19 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -411,6 +411,48 @@ def vsock_notify_handler() -> Iterator[tuple[str, dict[str, str]]]: logging.debug(f"- {k}={v}") +@contextlib.contextmanager +def start_journal_remote(config: Config, sockfd: int) -> Iterator[None]: + assert config.forward_journal + + bin = find_binary("systemd-journal-remote", "/usr/lib/systemd/systemd-journal-remote", root=config.tools()) + if not bin: + die("systemd-journal-remote must be installed to forward logs from the virtual machine") + + d = config.forward_journal.parent if config.forward_journal.suffix == ".journal" else config.forward_journal + if not d.exists(): + d.mkdir(parents=True) + # Make sure COW is disabled so systemd-journal-remote doesn't complain on btrfs filesystems. + run(["chattr", "+C", d], check=False, stderr=subprocess.DEVNULL if not ARG_DEBUG.get() else None) + INVOKING_USER.chown(d) + + with spawn( + [ + bin, + "--output", config.forward_journal, + "--split-mode", "none" if config.forward_journal.suffix == ".journal" else "host", + ], + pass_fds=(sockfd,), + sandbox=config.sandbox(mounts=[Mount(config.forward_journal.parent, config.forward_journal.parent)]), + user=config.forward_journal.parent.stat().st_uid, + group=config.forward_journal.parent.stat().st_gid, + # If all logs go into a single file, disable compact mode to allow for journal files exceeding 4G. + env={"SYSTEMD_JOURNAL_COMPACT": "0" if config.forward_journal.suffix == ".journal" else "1"}, + ) as proc: + yield + proc.terminate() + + +@contextlib.contextmanager +def start_journal_remote_vsock(config: Config) -> Iterator[str]: + with socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) as sock: + sock.bind((socket.VMADDR_CID_ANY, socket.VMADDR_PORT_ANY)) + sock.listen() + + with start_journal_remote(config, sock.fileno()): + yield f"vsock-stream:{socket.VMADDR_CID_HOST}:{sock.getsockname()[1]}" + + @contextlib.contextmanager def copy_ephemeral(config: Config, src: Path) -> Iterator[Path]: if not config.ephemeral or config.output_format in (OutputFormat.cpio, OutputFormat.uki): @@ -957,6 +999,9 @@ def run_qemu(args: Args, config: Config) -> None: addr, notifications = stack.enter_context(vsock_notify_handler()) credentials["vmm.notify_socket"] = addr + if config.forward_journal: + credentials["journal.forward_to_socket"] = stack.enter_context(start_journal_remote_vsock(config)) + for k, v in credentials.items(): payload = base64.b64encode(v.encode()).decode() if config.architecture.supports_smbios(firmware): diff --git a/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos-fedora/mkosi.conf b/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos-fedora/mkosi.conf index 539742788..dd5a2ccc0 100644 --- a/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos-fedora/mkosi.conf +++ b/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos-fedora/mkosi.conf @@ -24,6 +24,7 @@ Packages= squashfs-tools swtpm-tools systemd-container + systemd-journal-remote systemd-udev ubu-keyring virt-firmware diff --git a/mkosi/resources/mkosi-tools/mkosi.conf.d/10-debian-ubuntu.conf b/mkosi/resources/mkosi-tools/mkosi.conf.d/10-debian-ubuntu.conf index ce27a589a..457a426f9 100644 --- a/mkosi/resources/mkosi-tools/mkosi.conf.d/10-debian-ubuntu.conf +++ b/mkosi/resources/mkosi-tools/mkosi.conf.d/10-debian-ubuntu.conf @@ -31,6 +31,7 @@ Packages= systemd-boot systemd-container systemd-coredump + systemd-journal-remote ubuntu-keyring uidmap xz-utils diff --git a/mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf b/mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf index 28bed89f6..8034481b3 100644 --- a/mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf +++ b/mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf @@ -27,5 +27,6 @@ Packages= systemd-container systemd-coredump systemd-experimental + systemd-journal-remote xz zypper diff --git a/mkosi/resources/mkosi.md b/mkosi/resources/mkosi.md index f3e1d55ed..17299b6ec 100644 --- a/mkosi/resources/mkosi.md +++ b/mkosi/resources/mkosi.md @@ -1845,6 +1845,17 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, : Note that `Ephemeral=` has to be enabled to start multiple instances of the same image. +`ForwardJournal=`, `--forward-journal=` + +: Specify the path to which journal logs from containers and virtual + machines should be forwarded. If the path has the `.journal` + extension, it is interpreted as a file to which the journal should be + written. Otherwise, the path is interpreted as a directory to which + the journal should be written. + +: Note that systemd v256 or newer is required in the virtual machine for + log forwarding to work. + ## Specifiers The current value of various settings can be accessed when parsing diff --git a/mkosi/vmspawn.py b/mkosi/vmspawn.py index f8daa5aa2..ee1701ca0 100644 --- a/mkosi/vmspawn.py +++ b/mkosi/vmspawn.py @@ -93,6 +93,9 @@ def run_vmspawn(args: Args, config: Config) -> None: else: cmdline += ["--image", fname] + if config.forward_journal: + cmdline += ["--forward-journal", config.forward_journal] + cmdline += [*args.cmdline, *config.kernel_command_line_extra] run(cmdline, stdin=sys.stdin, stdout=sys.stdout, env=os.environ | config.environment, log=False) diff --git a/tests/test_json.py b/tests/test_json.py index 768a37eb1..e36c41aed 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -138,6 +138,7 @@ def test_config() -> None: "ExtraTrees": [], "FinalizeScripts": [], "Format": "uki", + "ForwardJournal": "/mkosi.journal", "Hostname": null, "Image": "default", "ImageId": "myimage", @@ -367,6 +368,7 @@ def test_config() -> None: extra_search_paths = [], extra_trees = [], finalize_scripts = [], + forward_journal = Path("/mkosi.journal"), hostname = None, vmm = Vmm.qemu, image = "default",