mirror of
https://github.com/apple/container.git
synced 2026-10-15 10:39:21 +00:00
LinuxPod: Add back top-level dns/hosts (#501)
Upon further thought, we should probably support this at the pod level and allow per container overrides. I've changed my mind on how tedious it is to specify it on a per-container basis.
This commit is contained in:
1 parent
c8ddc8a826
commit
a8758a6ab3
3 files changed
+272
-3
No files matched your search
@@ -51,6 +51,12 @@ public final class LinuxPod: Sendable {
|
||||
/// Whether containers in the pod should share a PID namespace.
|
||||
/// When enabled, all containers can see each other's processes.
|
||||
public var shareProcessNamespace: Bool = false
|
||||
/// The default DNS configuration for all containers in the pod.
|
||||
/// Individual containers can override this by setting their own `dns` configuration.
|
||||
public var dns: DNS?
|
||||
/// The default hosts file configuration for all containers in the pod.
|
||||
/// Individual containers can override this by setting their own `hosts` configuration.
|
||||
public var hosts: Hosts?
|
||||
|
||||
public init() {}
|
||||
}
|
||||
@@ -435,15 +441,16 @@ extension LinuxPod {
|
||||
}
|
||||
}
|
||||
|
||||
// Setup /etc/resolv.conf and /etc/hosts for each container
|
||||
// Setup /etc/resolv.conf and /etc/hosts for each container.
|
||||
// Container-level config takes precedence over pod-level config.
|
||||
for (_, container) in containers {
|
||||
if let dns = container.config.dns {
|
||||
if let dns = container.config.dns ?? self.config.dns {
|
||||
try await agent.configureDNS(
|
||||
config: dns,
|
||||
location: Self.guestRootfsPath(container.id)
|
||||
)
|
||||
}
|
||||
if let hosts = container.config.hosts {
|
||||
if let hosts = container.config.hosts ?? self.config.hosts {
|
||||
try await agent.configureHosts(
|
||||
config: hosts,
|
||||
location: Self.guestRootfsPath(container.id)
|
||||
|
||||
@@ -1089,4 +1089,262 @@ extension IntegrationSuite {
|
||||
throw IntegrationError.assert(msg: "container2 should NOT have service-a entry, got: \(output2)")
|
||||
}
|
||||
}
|
||||
|
||||
func testPodLevelDNS() async throws {
|
||||
let id = "test-pod-level-dns"
|
||||
|
||||
let bs = try await bootstrap(id)
|
||||
let pod = try LinuxPod(id, vmm: bs.vmm) { config in
|
||||
config.cpus = 4
|
||||
config.memoryInBytes = 1024.mib()
|
||||
config.bootLog = bs.bootLog
|
||||
// Set DNS at the pod level
|
||||
config.dns = DNS(nameservers: ["9.9.9.9", "149.112.112.112"])
|
||||
}
|
||||
|
||||
let buffer1 = BufferWriter()
|
||||
let buffer2 = BufferWriter()
|
||||
|
||||
// Neither container specifies DNS. We should inherit from pod
|
||||
try await pod.addContainer("container1", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container1")) { config in
|
||||
config.process.arguments = ["cat", "/etc/resolv.conf"]
|
||||
config.process.stdout = buffer1
|
||||
}
|
||||
|
||||
try await pod.addContainer("container2", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container2")) { config in
|
||||
config.process.arguments = ["cat", "/etc/resolv.conf"]
|
||||
config.process.stdout = buffer2
|
||||
}
|
||||
|
||||
try await pod.create()
|
||||
|
||||
try await pod.startContainer("container1")
|
||||
let status1 = try await pod.waitContainer("container1")
|
||||
|
||||
try await pod.startContainer("container2")
|
||||
let status2 = try await pod.waitContainer("container2")
|
||||
|
||||
try await pod.stop()
|
||||
|
||||
guard status1.exitCode == 0 else {
|
||||
throw IntegrationError.assert(msg: "container1 cat failed with status \(status1)")
|
||||
}
|
||||
guard status2.exitCode == 0 else {
|
||||
throw IntegrationError.assert(msg: "container2 cat failed with status \(status2)")
|
||||
}
|
||||
|
||||
guard let output1 = String(data: buffer1.data, encoding: .utf8) else {
|
||||
throw IntegrationError.assert(msg: "failed to convert container1 stdout to UTF8")
|
||||
}
|
||||
guard let output2 = String(data: buffer2.data, encoding: .utf8) else {
|
||||
throw IntegrationError.assert(msg: "failed to convert container2 stdout to UTF8")
|
||||
}
|
||||
|
||||
// Both containers should have the pod-level DNS
|
||||
guard output1.contains("9.9.9.9") && output1.contains("149.112.112.112") else {
|
||||
throw IntegrationError.assert(msg: "container1 should have pod-level DNS (9.9.9.9), got: \(output1)")
|
||||
}
|
||||
guard output2.contains("9.9.9.9") && output2.contains("149.112.112.112") else {
|
||||
throw IntegrationError.assert(msg: "container2 should have pod-level DNS (9.9.9.9), got: \(output2)")
|
||||
}
|
||||
}
|
||||
|
||||
func testPodLevelDNSWithContainerOverride() async throws {
|
||||
let id = "test-pod-level-dns-override"
|
||||
|
||||
let bs = try await bootstrap(id)
|
||||
let pod = try LinuxPod(id, vmm: bs.vmm) { config in
|
||||
config.cpus = 4
|
||||
config.memoryInBytes = 1024.mib()
|
||||
config.bootLog = bs.bootLog
|
||||
// Set DNS at the pod level
|
||||
config.dns = DNS(nameservers: ["9.9.9.9"])
|
||||
}
|
||||
|
||||
let buffer1 = BufferWriter()
|
||||
let buffer2 = BufferWriter()
|
||||
|
||||
// Container1 does NOT specify DNS. It should inherit from pod
|
||||
try await pod.addContainer("container1", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container1")) { config in
|
||||
config.process.arguments = ["cat", "/etc/resolv.conf"]
|
||||
config.process.stdout = buffer1
|
||||
}
|
||||
|
||||
// Container2 specifies its own DNS. It should override pod-level
|
||||
try await pod.addContainer("container2", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container2")) { config in
|
||||
config.process.arguments = ["cat", "/etc/resolv.conf"]
|
||||
config.process.stdout = buffer2
|
||||
config.dns = DNS(nameservers: ["8.8.8.8"])
|
||||
}
|
||||
|
||||
try await pod.create()
|
||||
|
||||
try await pod.startContainer("container1")
|
||||
let status1 = try await pod.waitContainer("container1")
|
||||
|
||||
try await pod.startContainer("container2")
|
||||
let status2 = try await pod.waitContainer("container2")
|
||||
|
||||
try await pod.stop()
|
||||
|
||||
guard status1.exitCode == 0 else {
|
||||
throw IntegrationError.assert(msg: "container1 cat failed with status \(status1)")
|
||||
}
|
||||
guard status2.exitCode == 0 else {
|
||||
throw IntegrationError.assert(msg: "container2 cat failed with status \(status2)")
|
||||
}
|
||||
|
||||
guard let output1 = String(data: buffer1.data, encoding: .utf8) else {
|
||||
throw IntegrationError.assert(msg: "failed to convert container1 stdout to UTF8")
|
||||
}
|
||||
guard let output2 = String(data: buffer2.data, encoding: .utf8) else {
|
||||
throw IntegrationError.assert(msg: "failed to convert container2 stdout to UTF8")
|
||||
}
|
||||
|
||||
// Container1 should have pod-level DNS
|
||||
guard output1.contains("9.9.9.9") && !output1.contains("8.8.8.8") else {
|
||||
throw IntegrationError.assert(msg: "container1 should have pod-level DNS (9.9.9.9), got: \(output1)")
|
||||
}
|
||||
// Container2 should have its own DNS, not pod-level
|
||||
guard output2.contains("8.8.8.8") && !output2.contains("9.9.9.9") else {
|
||||
throw IntegrationError.assert(msg: "container2 should have container-level DNS (8.8.8.8), got: \(output2)")
|
||||
}
|
||||
}
|
||||
|
||||
func testPodLevelHosts() async throws {
|
||||
let id = "test-pod-level-hosts"
|
||||
|
||||
let bs = try await bootstrap(id)
|
||||
let pod = try LinuxPod(id, vmm: bs.vmm) { config in
|
||||
config.cpus = 4
|
||||
config.memoryInBytes = 1024.mib()
|
||||
config.bootLog = bs.bootLog
|
||||
// Set hosts at the pod level
|
||||
config.hosts = Hosts(entries: [
|
||||
Hosts.Entry.localHostIPV4(),
|
||||
Hosts.Entry(ipAddress: "10.0.0.100", hostnames: ["shared-service.local"]),
|
||||
])
|
||||
}
|
||||
|
||||
let buffer1 = BufferWriter()
|
||||
let buffer2 = BufferWriter()
|
||||
|
||||
// Neither container specifies hosts. It should inherit from pod
|
||||
try await pod.addContainer("container1", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container1")) { config in
|
||||
config.process.arguments = ["cat", "/etc/hosts"]
|
||||
config.process.stdout = buffer1
|
||||
}
|
||||
|
||||
try await pod.addContainer("container2", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container2")) { config in
|
||||
config.process.arguments = ["cat", "/etc/hosts"]
|
||||
config.process.stdout = buffer2
|
||||
}
|
||||
|
||||
try await pod.create()
|
||||
|
||||
try await pod.startContainer("container1")
|
||||
let status1 = try await pod.waitContainer("container1")
|
||||
|
||||
try await pod.startContainer("container2")
|
||||
let status2 = try await pod.waitContainer("container2")
|
||||
|
||||
try await pod.stop()
|
||||
|
||||
guard status1.exitCode == 0 else {
|
||||
throw IntegrationError.assert(msg: "container1 cat failed with status \(status1)")
|
||||
}
|
||||
guard status2.exitCode == 0 else {
|
||||
throw IntegrationError.assert(msg: "container2 cat failed with status \(status2)")
|
||||
}
|
||||
|
||||
guard let output1 = String(data: buffer1.data, encoding: .utf8) else {
|
||||
throw IntegrationError.assert(msg: "failed to convert container1 stdout to UTF8")
|
||||
}
|
||||
guard let output2 = String(data: buffer2.data, encoding: .utf8) else {
|
||||
throw IntegrationError.assert(msg: "failed to convert container2 stdout to UTF8")
|
||||
}
|
||||
|
||||
// Both containers should have the pod-level hosts entry
|
||||
guard output1.contains("10.0.0.100") && output1.contains("shared-service.local") else {
|
||||
throw IntegrationError.assert(msg: "container1 should have pod-level hosts entry, got: \(output1)")
|
||||
}
|
||||
guard output2.contains("10.0.0.100") && output2.contains("shared-service.local") else {
|
||||
throw IntegrationError.assert(msg: "container2 should have pod-level hosts entry, got: \(output2)")
|
||||
}
|
||||
}
|
||||
|
||||
func testPodLevelHostsWithContainerOverride() async throws {
|
||||
let id = "test-pod-level-hosts-override"
|
||||
|
||||
let bs = try await bootstrap(id)
|
||||
let pod = try LinuxPod(id, vmm: bs.vmm) { config in
|
||||
config.cpus = 4
|
||||
config.memoryInBytes = 1024.mib()
|
||||
config.bootLog = bs.bootLog
|
||||
// Set hosts at the pod level
|
||||
config.hosts = Hosts(entries: [
|
||||
Hosts.Entry.localHostIPV4(),
|
||||
Hosts.Entry(ipAddress: "10.0.0.100", hostnames: ["shared-service.local"]),
|
||||
])
|
||||
}
|
||||
|
||||
let buffer1 = BufferWriter()
|
||||
let buffer2 = BufferWriter()
|
||||
|
||||
// Container1 does NOT specify hosts. It should inherit from pod
|
||||
try await pod.addContainer("container1", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container1")) { config in
|
||||
config.process.arguments = ["cat", "/etc/hosts"]
|
||||
config.process.stdout = buffer1
|
||||
}
|
||||
|
||||
// Container2 specifies its own hosts. It should override pod-level
|
||||
try await pod.addContainer("container2", rootfs: try cloneRootfs(bs.rootfs, testID: id, containerID: "container2")) { config in
|
||||
config.process.arguments = ["cat", "/etc/hosts"]
|
||||
config.process.stdout = buffer2
|
||||
config.hosts = Hosts(entries: [
|
||||
Hosts.Entry.localHostIPV4(),
|
||||
Hosts.Entry(ipAddress: "10.0.0.200", hostnames: ["container-specific.local"]),
|
||||
])
|
||||
}
|
||||
|
||||
try await pod.create()
|
||||
|
||||
try await pod.startContainer("container1")
|
||||
let status1 = try await pod.waitContainer("container1")
|
||||
|
||||
try await pod.startContainer("container2")
|
||||
let status2 = try await pod.waitContainer("container2")
|
||||
|
||||
try await pod.stop()
|
||||
|
||||
guard status1.exitCode == 0 else {
|
||||
throw IntegrationError.assert(msg: "container1 cat failed with status \(status1)")
|
||||
}
|
||||
guard status2.exitCode == 0 else {
|
||||
throw IntegrationError.assert(msg: "container2 cat failed with status \(status2)")
|
||||
}
|
||||
|
||||
guard let output1 = String(data: buffer1.data, encoding: .utf8) else {
|
||||
throw IntegrationError.assert(msg: "failed to convert container1 stdout to UTF8")
|
||||
}
|
||||
guard let output2 = String(data: buffer2.data, encoding: .utf8) else {
|
||||
throw IntegrationError.assert(msg: "failed to convert container2 stdout to UTF8")
|
||||
}
|
||||
|
||||
// Container1 should have pod-level hosts entry
|
||||
guard output1.contains("10.0.0.100") && output1.contains("shared-service.local") else {
|
||||
throw IntegrationError.assert(msg: "container1 should have pod-level hosts entry, got: \(output1)")
|
||||
}
|
||||
guard !output1.contains("10.0.0.200") && !output1.contains("container-specific.local") else {
|
||||
throw IntegrationError.assert(msg: "container1 should NOT have container2's hosts entry, got: \(output1)")
|
||||
}
|
||||
|
||||
// Container2 should have its own hosts entry, not pod-level
|
||||
guard output2.contains("10.0.0.200") && output2.contains("container-specific.local") else {
|
||||
throw IntegrationError.assert(msg: "container2 should have container-level hosts entry, got: \(output2)")
|
||||
}
|
||||
guard !output2.contains("10.0.0.100") && !output2.contains("shared-service.local") else {
|
||||
throw IntegrationError.assert(msg: "container2 should NOT have pod-level hosts entry, got: \(output2)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,6 +358,10 @@ struct IntegrationSuite: AsyncParsableCommand {
|
||||
Test("pod container hosts config", testPodContainerHostsConfig),
|
||||
Test("pod multiple containers different DNS", testPodMultipleContainersDifferentDNS),
|
||||
Test("pod multiple containers different hosts", testPodMultipleContainersDifferentHosts),
|
||||
Test("pod level DNS", testPodLevelDNS),
|
||||
Test("pod level DNS with container override", testPodLevelDNSWithContainerOverride),
|
||||
Test("pod level hosts", testPodLevelHosts),
|
||||
Test("pod level hosts with container override", testPodLevelHostsWithContainerOverride),
|
||||
] + macOS26Tests()
|
||||
|
||||
let filteredTests: [Test]
|
||||
|
||||
Reference in new issue
Block a user