引言:为什么选择 Swift 和 Kubernetes?
Swift 不仅仅是 iOS 和 macOS 的专属语言。得益于 Swift on Server 项目以及 Vapor 和 SwiftNIO 等框架,Swift 开发者能够构建高性能的服务器端应用。然而,要让这类应用稳定运行并具备弹性伸缩能力,需要现代化的基础设施支持。
Kubernetes(K8s) 是容器编排领域的事实标准。在本指南中,我们将演示如何将 Swift 应用打包成 Docker 镜像、编写 Kubernetes 清单文件并将其部署到集群中。你将学习如何使用 SwiftNIO 创建轻量级 HTTP 服务器,并配置其适用于云部署。
1. 为容器化准备 Swift 应用
1.1. 创建 SwiftNIO 项目
为便于演示,我们将使用 SwiftNIO 编写一个极简 HTTP 服务器。这样可以避免不必要的依赖,并展示底层网络的工作原理。打开终端并创建新项目:
mkdir SwiftK8sDemocd SwiftK8sDemoswift package init --type executable在 Package.swift 中添加 SwiftNIO 依赖:
// 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. 带健康检查的服务器代码
让我们创建一个简单的服务器,它能响应 GET 请求并包含健康检查端点(/health)。这是 Kubernetes 存活探针所必需的。替换 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