Skip to content

Commit 22f7b6a

Browse files
authored
LinuxContainer: Add bootlog as configuration field (#344)
Closes #227 Previously, the bootlog was supplied once in the constructor to VZVirtualMachineManager which meant that if you used this same manager for multiple ctrs that all logs would end up going to the same file, which becomes quite cumbersome to follow.. This change moves bootlog to be a container configuration param and also moves it to be a VMConfiguration param, so it can be threaded through from LinuxContainer -> vmm.create() and be truly container unique now. The largest driver for this was the integration tests which today every single test spits out logs to a singular file, making guest investigations tricky to actually look into. Result after: ``` ➜ containerization git:(bootlog-per-ctr) ✗ ls -alh bin/bootlogs total 1520 drwxr-xr-x@ 24 dcantah staff 768B Oct 22 17:34 . drwxr-xr-x@ 8 dcantah staff 256B Oct 22 17:34 .. -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-cat-mount.log -rw-------@ 1 dcantah staff 22K Oct 22 17:34 test-cgroup-limits.log -rw-------@ 1 dcantah staff 249K Oct 22 17:34 test-concurrent-processes-output-stress.log -rw-------@ 1 dcantah staff 167K Oct 22 17:34 test-concurrent-processes.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-devconsole.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-hostname.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-hosts-file.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-manager.log -rw-------@ 1 dcantah staff 22K Oct 22 17:34 test-container-reuse.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-statistics.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-container-stdin.log -rw-------@ 1 dcantah staff 0B Oct 22 17:34 test-nested-virt.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-pause-resume-io.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-pause-resume-wait.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-pause-resume.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-custom-home-envvar.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-echo-hi.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-false.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-home-envvar.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-true.log -rw-------@ 1 dcantah staff 11K Oct 22 17:34 test-process-tty-envvar.log -rw-------@ 1 dcantah staff 38K Oct 22 17:34 test-process-user.log ```
1 parent 1fa051b commit 22f7b6a

File tree

6 files changed

+44
-52
lines changed

6 files changed

+44
-52
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ ifeq (,$(wildcard bin/vmlinux))
111111
@exit 1
112112
endif
113113
@echo Running the integration tests...
114-
@./bin/containerization-integration --bootlog ./bin/boot.log
114+
@./bin/containerization-integration
115115

116116
.PHONY: fetch-default-kernel
117117
fetch-default-kernel:

Sources/Containerization/ContainerManager.swift

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ public struct ContainerManager: Sendable {
213213
self.vmm = VZVirtualMachineManager(
214214
kernel: kernel,
215215
initialFilesystem: initfs,
216-
bootlog: imageStore.path.appendingPathComponent("bootlog.log").absolutePath(),
217216
rosetta: rosetta,
218217
nestedVirtualization: nestedVirtualization
219218
)
@@ -240,7 +239,6 @@ public struct ContainerManager: Sendable {
240239
self.vmm = VZVirtualMachineManager(
241240
kernel: kernel,
242241
initialFilesystem: initfs,
243-
bootlog: imageStore.path.appendingPathComponent("bootlog.log").absolutePath(),
244242
rosetta: rosetta,
245243
nestedVirtualization: nestedVirtualization
246244
)
@@ -282,7 +280,6 @@ public struct ContainerManager: Sendable {
282280
self.vmm = VZVirtualMachineManager(
283281
kernel: kernel,
284282
initialFilesystem: initfs,
285-
bootlog: self.imageStore.path.appendingPathComponent("bootlog.log").absolutePath(),
286283
rosetta: rosetta,
287284
nestedVirtualization: nestedVirtualization
288285
)
@@ -327,7 +324,6 @@ public struct ContainerManager: Sendable {
327324
self.vmm = VZVirtualMachineManager(
328325
kernel: kernel,
329326
initialFilesystem: initfs,
330-
bootlog: self.imageStore.path.appendingPathComponent("bootlog.log").absolutePath(),
331327
rosetta: rosetta,
332328
nestedVirtualization: nestedVirtualization
333329
)
@@ -421,46 +417,11 @@ public struct ContainerManager: Sendable {
421417
config.interfaces = [interface]
422418
config.dns = .init(nameservers: [interface.gateway!])
423419
}
420+
config.bootlog = self.containerRoot.appendingPathComponent(id).appendingPathComponent("bootlog.log")
424421
try configuration(&config)
425422
}
426423
}
427424

428-
/// Returns an existing container from the provided image and root filesystem mount.
429-
/// - Parameters:
430-
/// - id: The container ID.
431-
/// - image: The image.
432-
public mutating func get(
433-
_ id: String,
434-
image: Image,
435-
) async throws -> LinuxContainer {
436-
let path = containerRoot.appendingPathComponent(id)
437-
guard FileManager.default.fileExists(atPath: path.absolutePath()) else {
438-
throw ContainerizationError(.notFound, message: "\(id) does not exist")
439-
}
440-
441-
let rootfs: Mount = .block(
442-
format: "ext4",
443-
source: path.appendingPathComponent("rootfs.ext4").absolutePath(),
444-
destination: "/",
445-
options: []
446-
)
447-
448-
let imageConfig = try await image.config(for: .current).config
449-
return try LinuxContainer(
450-
id,
451-
rootfs: rootfs,
452-
vmm: self.vmm
453-
) { config in
454-
if let imageConfig {
455-
config.process = .init(from: imageConfig)
456-
}
457-
if let interface = try self.network?.create(id) {
458-
config.interfaces = [interface]
459-
config.dns = .init(nameservers: [interface.gateway!])
460-
}
461-
}
462-
}
463-
464425
/// Performs the cleanup of a container.
465426
/// - Parameter id: The container ID.
466427
public mutating func delete(_ id: String) throws {

Sources/Containerization/LinuxContainer.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ public final class LinuxContainer: Container, Sendable {
6060
public var hosts: Hosts?
6161
/// Enable nested virtualization support.
6262
public var virtualization: Bool = false
63+
/// Optional file path to store serial boot logs.
64+
public var bootlog: URL?
6365

6466
public init() {}
6567
}
@@ -306,6 +308,7 @@ extension LinuxContainer {
306308
memoryInBytes: self.memoryInBytes,
307309
interfaces: self.interfaces,
308310
mountsByID: [self.id: [self.rootfs] + self.config.mounts],
311+
bootlog: self.config.bootlog,
309312
nestedVirtualization: self.config.virtualization
310313
)
311314
let creationConfig = StandardVMConfig(configuration: vmConfig)

Sources/Containerization/VZVirtualMachineManager.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,19 @@ import Logging
2424
public struct VZVirtualMachineManager: VirtualMachineManager {
2525
private let kernel: Kernel
2626
private let initialFilesystem: Mount
27-
private let bootlog: String?
2827
private let rosetta: Bool
2928
private let nestedVirtualization: Bool
3029
private let logger: Logger?
3130

3231
public init(
3332
kernel: Kernel,
3433
initialFilesystem: Mount,
35-
bootlog: String? = nil,
3634
rosetta: Bool = false,
3735
nestedVirtualization: Bool = false,
3836
logger: Logger? = nil
3937
) {
4038
self.kernel = kernel
4139
self.initialFilesystem = initialFilesystem
42-
self.bootlog = bootlog
4340
self.rosetta = rosetta
4441
self.nestedVirtualization = nestedVirtualization
4542
self.logger = logger
@@ -60,12 +57,11 @@ public struct VZVirtualMachineManager: VirtualMachineManager {
6057
instanceConfig.kernel = self.kernel
6158
instanceConfig.initialFilesystem = self.initialFilesystem
6259

63-
instanceConfig.interfaces = vmConfig.interfaces
6460
if let bootlog = vmConfig.bootlog {
6561
instanceConfig.bootlog = bootlog
66-
} else if let bootlog = self.bootlog {
67-
instanceConfig.bootlog = URL(filePath: bootlog)
6862
}
63+
64+
instanceConfig.interfaces = vmConfig.interfaces
6965
instanceConfig.rosetta = self.rosetta
7066
instanceConfig.nestedVirtualization = useNestedVirtualization
7167

Sources/Integration/ContainerTests.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ extension IntegrationSuite {
3030
let bs = try await bootstrap(id)
3131
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
3232
config.process.arguments = ["/bin/true"]
33+
config.bootlog = bs.bootlog
3334
}
3435

3536
try await container.create()
@@ -49,6 +50,7 @@ extension IntegrationSuite {
4950
let bs = try await bootstrap(id)
5051
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
5152
config.process.arguments = ["/bin/false"]
53+
config.bootlog = bs.bootlog
5254
}
5355

5456
try await container.create()
@@ -101,6 +103,7 @@ extension IntegrationSuite {
101103
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
102104
config.process.arguments = ["/bin/echo", "hi"]
103105
config.process.stdout = buffer
106+
config.bootlog = bs.bootlog
104107
}
105108

106109
do {
@@ -130,6 +133,7 @@ extension IntegrationSuite {
130133
let bs = try await bootstrap(id)
131134
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
132135
config.process.arguments = ["/bin/sleep", "1000"]
136+
config.bootlog = bs.bootlog
133137
}
134138

135139
do {
@@ -172,6 +176,7 @@ extension IntegrationSuite {
172176
let bs = try await bootstrap(id)
173177
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
174178
config.process.arguments = ["/bin/sleep", "1000"]
179+
config.bootlog = bs.bootlog
175180
}
176181

177182
do {
@@ -244,6 +249,7 @@ extension IntegrationSuite {
244249
config.process.arguments = ["/usr/bin/id"]
245250
config.process.user = .init(uid: 1, gid: 1, additionalGids: [1])
246251
config.process.stdout = buffer
252+
config.bootlog = bs.bootlog
247253
}
248254

249255
try await container.create()
@@ -268,6 +274,7 @@ extension IntegrationSuite {
268274
// Try some uid that doesn't exist. This is supported.
269275
config.process.user = .init(uid: 40000, gid: 40000)
270276
config.process.stdout = buffer
277+
config.bootlog = bs.bootlog
271278
}
272279

273280
try await container.create()
@@ -292,6 +299,7 @@ extension IntegrationSuite {
292299
// Try some uid that doesn't exist. This is supported.
293300
config.process.user = .init(username: "40000:40000")
294301
config.process.stdout = buffer
302+
config.bootlog = bs.bootlog
295303
}
296304

297305
try await container.create()
@@ -316,6 +324,7 @@ extension IntegrationSuite {
316324
// Now for our final trick, try and run a username that doesn't exist.
317325
config.process.user = .init(username: "thisdoesntexist")
318326
config.process.stdout = buffer
327+
config.bootlog = bs.bootlog
319328
}
320329

321330
try await container.create()
@@ -337,6 +346,7 @@ extension IntegrationSuite {
337346
config.process.arguments = ["env"]
338347
config.process.terminal = true
339348
config.process.stdout = buffer
349+
config.bootlog = bs.bootlog
340350
}
341351

342352
try await container.create()
@@ -371,6 +381,7 @@ extension IntegrationSuite {
371381
config.process.arguments = ["env"]
372382
config.process.user = .init(uid: 0, gid: 0)
373383
config.process.stdout = buffer
384+
config.bootlog = bs.bootlog
374385
}
375386

376387
try await container.create()
@@ -406,6 +417,7 @@ extension IntegrationSuite {
406417
config.process.environmentVariables.append(customHomeEnvvar)
407418
config.process.user = .init(uid: 0, gid: 0)
408419
config.process.stdout = buffer
420+
config.bootlog = bs.bootlog
409421
}
410422

411423
try await container.create()
@@ -436,6 +448,7 @@ extension IntegrationSuite {
436448
config.process.arguments = ["/bin/hostname"]
437449
config.hostname = "foo-bar"
438450
config.process.stdout = buffer
451+
config.bootlog = bs.bootlog
439452
}
440453

441454
try await container.create()
@@ -465,6 +478,7 @@ extension IntegrationSuite {
465478
config.process.arguments = ["cat", "/etc/hosts"]
466479
config.hosts = Hosts(entries: [entry])
467480
config.process.stdout = buffer
481+
config.bootlog = bs.bootlog
468482
}
469483

470484
try await container.create()
@@ -493,6 +507,7 @@ extension IntegrationSuite {
493507
config.process.arguments = ["cat"]
494508
config.process.stdin = StdinBuffer(data: "Hello from test".data(using: .utf8)!)
495509
config.process.stdout = buffer
510+
config.bootlog = bs.bootlog
496511
}
497512

498513
try await container.create()
@@ -522,6 +537,7 @@ extension IntegrationSuite {
522537
config.process.arguments = ["/bin/cat", "/mnt/hi.txt"]
523538
config.mounts.append(.share(source: directory.path, destination: "/mnt"))
524539
config.process.stdout = buffer
540+
config.bootlog = bs.bootlog
525541
}
526542

527543
try await container.create()
@@ -548,6 +564,7 @@ extension IntegrationSuite {
548564
let bs = try await bootstrap(id)
549565
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
550566
config.process.arguments = ["sleep", "infinity"]
567+
config.bootlog = bs.bootlog
551568
}
552569

553570
try await container.create()
@@ -569,6 +586,7 @@ extension IntegrationSuite {
569586
let bs = try await bootstrap(id)
570587
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
571588
config.process.arguments = ["sleep", "2"]
589+
config.bootlog = bs.bootlog
572590
}
573591

574592
try await container.create()
@@ -601,6 +619,7 @@ extension IntegrationSuite {
601619
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
602620
config.process.arguments = ["ping", "-c", "5", "localhost"]
603621
config.process.stdout = buffer
622+
config.bootlog = bs.bootlog
604623
}
605624

606625
try await container.create()
@@ -634,6 +653,7 @@ extension IntegrationSuite {
634653
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
635654
config.process.arguments = ["/bin/true"]
636655
config.virtualization = true
656+
config.bootlog = bs.bootlog
637657
}
638658

639659
do {
@@ -675,6 +695,7 @@ extension IntegrationSuite {
675695
) { config in
676696
config.process.arguments = ["/bin/echo", "ContainerManager test"]
677697
config.process.stdout = buffer
698+
config.bootlog = bs.bootlog
678699
}
679700

680701
// Start the container
@@ -716,6 +737,7 @@ extension IntegrationSuite {
716737
) { config in
717738
config.process.arguments = ["/bin/echo", "please stop me"]
718739
config.process.stdout = buffer
740+
config.bootlog = bs.bootlog
719741
}
720742

721743
// Start the container
@@ -759,6 +781,7 @@ extension IntegrationSuite {
759781
) { config in
760782
config.process.arguments = ["/bin/echo", "ContainerManager test"]
761783
config.process.stdout = buffer
784+
config.bootlog = bs.bootlog
762785
}
763786

764787
// Start the container
@@ -815,6 +838,7 @@ extension IntegrationSuite {
815838
config.process.arguments = ["mount"]
816839
config.process.terminal = true
817840
config.process.stdout = buffer
841+
config.bootlog = bs.bootlog
818842
}
819843

820844
try await container.create()
@@ -844,6 +868,7 @@ extension IntegrationSuite {
844868
let bs = try await bootstrap(id)
845869
let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in
846870
config.process.arguments = ["sleep", "infinity"]
871+
config.bootlog = bs.bootlog
847872
}
848873

849874
do {
@@ -889,6 +914,7 @@ extension IntegrationSuite {
889914
config.process.arguments = ["sleep", "infinity"]
890915
config.cpus = 2
891916
config.memoryInBytes = 512.mib()
917+
config.bootlog = bs.bootlog
892918
}
893919

894920
do {

Sources/Integration/Suite.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ struct IntegrationSuite: AsyncParsableCommand {
144144

145145
private static let unpackCoordinator = UnpackCoordinator()
146146

147-
@Option(name: .shortAndLong, help: "Path to a log file")
148-
var bootlog: String
147+
@Option(name: .shortAndLong, help: "Path to a directory for boot logs")
148+
var bootlogDir: String = "./bin/integration-bootlogs"
149149

150150
@Option(name: .shortAndLong, help: "Path to a kernel binary")
151151
var kernel: String = "./bin/vmlinux"
@@ -159,7 +159,7 @@ struct IntegrationSuite: AsyncParsableCommand {
159159
.appendingPathComponent(name)
160160
}
161161

162-
func bootstrap(_ testID: String) async throws -> (rootfs: Containerization.Mount, vmm: VirtualMachineManager, image: Containerization.Image) {
162+
func bootstrap(_ testID: String) async throws -> (rootfs: Containerization.Mount, vmm: VirtualMachineManager, image: Containerization.Image, bootlog: URL) {
163163
let reference = "ghcr.io/linuxcontainers/alpine:3.20"
164164
let store = Self.imageStore
165165

@@ -210,14 +210,20 @@ struct IntegrationSuite: AsyncParsableCommand {
210210
try? FileManager.default.removeItem(atPath: clPath)
211211

212212
let cl = try fs.clone(to: clPath)
213+
214+
// Create bootlog directory and per-container bootlog path
215+
let bootlogDirURL = URL(filePath: bootlogDir)
216+
try? FileManager.default.createDirectory(at: bootlogDirURL, withIntermediateDirectories: true)
217+
let bootlogURL = bootlogDirURL.appendingPathComponent("\(testID).log")
218+
213219
return (
214220
cl,
215221
VZVirtualMachineManager(
216222
kernel: testKernel,
217223
initialFilesystem: initfs,
218-
bootlog: bootlog
219224
),
220-
image
225+
image,
226+
bootlogURL
221227
)
222228
}
223229

0 commit comments

Comments
 (0)