Введение: Почему 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 SwiftK8sDemo
cd SwiftK8sDemo
swift package init --type executable
Добавьте зависимость SwiftNIO в Package.swift:
// swift-tools-version:5.7
import 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. Код сервера с health check
Создадим простой сервер, который отвечает на GET-запросы и имеет endpoint для проверки здоровья (/health). Это необходимо для Kubernetes liveness probe. Замените содержимое Sources/SwiftK8sDemo/main.swift:
import NIO
import 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).wait()
try group.next().onCloseFuture.wait()
Обратите внимание: мы читаем переменные окружения HOST и PORT. Это важно для гибкой конфигурации в Kubernetes.
2. Создание Docker-образа для Swift
2.1. Написание Dockerfile
Для минимального размера образа используем многостадийную сборку. Первый этап — компиляция в контейнере с Swift 5.7, второй — чистый образ на основе Ubuntu 22.04 (без инструментов Swift).
# Первый этап: сборка
FROM swift:5.7-jammy AS build
WORKDIR /app
COPY Package.swift .
RUN swift package resolve
COPY Sources Sources
RUN swift build -c release --static-swift-stdlib
# Второй этап: исполняемый образ
FROM ubuntu:jammy
RUN apt-get update && apt-get install -y libssl3 ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /app/.build/release/SwiftK8sDemo .
EXPOSE 8080
CMD ["./SwiftK8sDemo"]
Флаг --static-swift-stdlib позволяет статически слинковать стандартную библиотеку Swift, что уменьшает размер образа и ускоряет запуск.
2.2. Сборка и загрузка в registry
docker build -t your-registry/swift-k8s-demo:1.0 .
docker push your-registry/swift-k8s-demo:1.0
Замените your-registry на адрес вашего Docker Hub, GitLab Container Registry или другого хранилища.
3. Развертывание в Kubernetes
3.1. Манифест Deployment
Создайте файл deployment.yaml. В нем мы определим количество реплик, стратегию обновления и пробу жизнеспособности (liveness probe) для endpoint /health.
apiVersion: apps/v1
kind: Deployment
metadata:
name: swift-k8s-demo
labels:
app: swift-k8s-demo
spec:
replicas: 2
selector:
matchLabels:
app: swift-k8s-demo
template:
metadata:
labels:
app: swift-k8s-demo
spec:
containers:
- name: swift-app
image: your-registry/swift-k8s-demo:1.0
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"