Деплой Swift на Kubernetes: базовое руководство для разработчиков

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему 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"

Блоги

Рекомендаций по книгам