Einleitung: Warum Swift und Kubernetes?
Swift ist nicht nur eine Sprache für iOS und macOS. Dank des Swift on Server-Projekts und Frameworks wie Vapor und SwiftNIO können Swift-Entwickler leistungsstarke serverseitige Anwendungen erstellen. Damit eine solche Anwendung jedoch stabil läuft und skaliert, ist eine moderne Infrastruktur erforderlich.
Kubernetes (K8s) ist der De-facto-Standard für Container-Orchestrierung. In diesem Leitfaden zeigen wir, wie man eine Swift-Anwendung in ein Docker-Image verpackt, Kubernetes-Manifeste schreibt und in einem Cluster bereitstellt. Sie lernen, wie Sie mit SwiftNIO einen leichten HTTP-Server erstellen und für die Cloud-Bereitstellung konfigurieren.
1. Vorbereitung der Swift-Anwendung für die Containerisierung
1.1. Erstellen eines SwiftNIO-Projekts
Zur Demonstration schreiben wir einen minimalen HTTP-Server mit SwiftNIO. Dadurch werden unnötige Abhängigkeiten vermieden und es wird gezeigt, wie die Netzwerkkommunikation auf niedriger Ebene funktioniert. Öffnen Sie ein Terminal und erstellen Sie ein neues Projekt:
mkdir SwiftK8sDemocd SwiftK8sDemoswift package init --type executableFügen Sie die SwiftNIO-Abhängigkeit zu Package.swift hinzu:
// swift-tools-version:5.7import PackageDescription
let package = Package( name: "SwiftK8sDemo", dependencies: [ .package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0") ], targets: [ .executableTarget( name: "SwiftK8sDemo", dependencies: [ .product(name: "NIO", package: "swift-nio"), .product(name: "NIOHTTP1", package: "swift-nio") ] ) ])1.2. Servercode mit Health Check
Erstellen wir einen einfachen Server, der auf GET-Anfragen antwortet und einen Health-Check-Endpunkt (/health) besitzt. Dies wird für Kubernetes-Liveness-Probes benötigt. Ersetzen Sie den Inhalt von Sources/SwiftK8sDemo/main.swift:
import NIOimport NIOHTTP1
final class HTTPHandler: ChannelInboundHandler { typealias InboundIn = HTTPServerRequestPart typealias OutboundOut = HTTPServerResponsePart
func channelRead(context: ChannelHandlerContext, data: NIOAny) { let req = unwrapInboundIn(data) switch req { case .head(let header): let response: String let status: HTTPResponseStatus if header.uri == "/health" { response = "{\\"status\\":\\"ok\\"}" status = .ok } else { response = "Hello from Swift on K8s!" status = .ok } var buffer = context.channel.allocator.buffer(capacity: response.utf8.count) buffer.writeString(response) let head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: status) context.write(wrapOutboundOut(.head(head)), promise: nil) context.write(wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil) context.writeAndFlush(wrapOutboundOut(.end(nil)), promise: nil) default: break } }}
let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)defer { try! group.syncShutdownGracefully() }
let bootstrap = ServerBootstrap(group: group) .serverChannelOption(ChannelOptions.backlog, value: 256) .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) .childChannelInitializer { channel in channel.pipeline.configureHTTPServerPipeline().flatMap { channel.pipeline.addHandler(HTTPHandler()) } }
let host = ProcessInfo.processInfo.environment["HOST"] ?? "0.0.0.0"let port = Int(ProcessInfo.processInfo.environment["PORT"] ?? "8080") ?? 8080
print("Server starting on \\(host):\\(port)")try bootstrap.bind(host: host, port: port).wai