Introducción: ¿Por qué Swift y Kubernetes?
Swift no es solo un lenguaje para iOS y macOS. Gracias al proyecto Swift on Server y frameworks como Vapor y SwiftNIO, los desarrolladores de Swift pueden crear aplicaciones de servidor de alto rendimiento. Sin embargo, para que dicha aplicación funcione de manera estable y escalable, se requiere una infraestructura moderna.
Kubernetes (K8s) es el estándar de facto para la orquestación de contenedores. En esta guía, mostraremos cómo empaquetar una aplicación Swift en una imagen Docker, escribir manifiestos para Kubernetes y desplegarla en un clúster. Aprenderá a usar SwiftNIO para crear un servidor HTTP ligero y configurarlo para funcionar en la nube.
1. Preparación de la aplicación Swift para contenerización
1.1. Creación del proyecto en SwiftNIO
Para la demostración, escribiremos un servidor HTTP mínimo en SwiftNIO. Esto evitará dependencias innecesarias y mostrará cómo funciona el trabajo de red de bajo nivel. Abra la terminal y cree un nuevo proyecto:
mkdir SwiftK8sDemocd SwiftK8sDemoswift package init --type executableAgregue la dependencia SwiftNIO en Package.swift:
// 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. Código del servidor con health check
Crearemos un servidor simple que responda a solicitudes GET y tenga un endpoint para verificar el estado (/health). Esto es necesario para la liveness probe de Kubernetes. Reemplace el contenido de 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