Introdução: Por que Swift e Kubernetes?
Swift não é apenas uma linguagem para iOS e macOS. Graças ao projeto Swift on Server e frameworks como Vapor e SwiftNIO, desenvolvedores Swift podem criar aplicações server-side de alta performance. No entanto, para que essa aplicação funcione de forma estável e escalável, é necessária uma infraestrutura moderna.
Kubernetes (K8s) é o padrão de facto para orquestração de containers. Neste guia, mostraremos como empacotar uma aplicação Swift em uma imagem Docker, escrever manifests para Kubernetes e implantá-la em um cluster. Você aprenderá como usar SwiftNIO para criar um servidor HTTP leve e configurá-lo para funcionar na nuvem.
1. Preparando a aplicação Swift para conteinerização
1.1. Criando o projeto com SwiftNIO
Para demonstração, escreveremos um servidor HTTP mínimo com SwiftNIO. Isso evitará dependências desnecessárias e mostrará como funciona o trabalho de rede de baixo nível. Abra o terminal e crie um novo projeto:
mkdir SwiftK8sDemocd SwiftK8sDemoswift package init --type executableAdicione a dependência SwiftNIO no 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 do servidor com health check
Criaremos um servidor simples que responde a requisições GET e possui um endpoint para verificação de saúde (/health). Isso é necessário para a liveness probe do Kubernetes. Substitua o conteúdo 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