مقدمة: لماذا Swift وKubernetes؟
Swift ليس فقط لغة لتطبيقات iOS وmacOS. بفضل مشروع Swift on Server وأطر العمل مثل Vapor وSwiftNIO، يمكن لمطوري Swift إنشاء تطبيقات خادم عالية الأداء. ومع ذلك، لكي يعمل هذا التطبيق بشكل مستقر وقابل للتوسع، يتطلب بنية تحتية حديثة.
Kubernetes (K8s) هو المعيار الفعلي لتنسيق الحاويات. في هذا الدليل، سنوضح كيفية تعبئة تطبيق Swift في صورة Docker، وكتابة ملفات تعريف Kubernetes، ونشره في مجموعة. ستتعلم كيفية استخدام SwiftNIO لإنشاء خادم HTTP خفيف الوزن وتكوينه للعمل في السحابة.
1. تحضير تطبيق Swift للحاوية
1.1. إنشاء مشروع باستخدام SwiftNIO
للتوضيح، سنكتب خادم HTTP بسيط باستخدام SwiftNIO. سيتجنب هذا الاعتماديات غير الضرورية ويظهر كيفية عمل الشبكات على مستوى منخفض. افتح الطرفية وأنشئ مشروعًا جديدًا:
mkdir SwiftK8sDemocd SwiftK8sDemoswift package init --type executableأضف اعتمادية SwiftNIO في 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. كود الخادم مع فحص الصحة
لننشئ خادمًا بسيطًا يستجيب لطلبات GET ويحتوي على نقطة نهاية لفحص الصحة (/health). هذا ضروري لـ Kubernetes liveness probe. استبدل محتوى 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