Введение: почему C++ и Kubernetes — это мощный тандем
Современная облачная инфраструктура всё чаще строится на микросервисной архитектуре и контейнеризации. Kubernetes стал стандартом де-факто для оркестрации контейнеров. Однако многие разработчики ошибочно полагают, что Kubernetes — это удел только интерпретируемых языков (Python, JavaScript) или JVM. На самом деле, деплой C++ приложений в Kubernetes не только возможен, но и даёт значительные преимущества: высокая производительность, минимальное потребление памяти и полный контроль над ресурсами.
В этом руководстве мы разберём, как упаковать C++ сервер в Docker-образ, создать необходимые манифесты для Kubernetes и запустить его в кластере. Вы узнаете, как обрабатывать конфигурации и секреты, а также как правильно настроить Health Checks для надёжной работы приложения.
1. Подготовка C++ приложения для контейнеризации
Прежде чем говорить о Kubernetes, нужно подготовить само C++ приложение. Для примера возьмём простой HTTP-сервер на базе библиотеки libmicrohttpd. Этот сервер будет отвечать на запросы и читать конфигурацию из переменных окружения — это стандартный подход для 12-factor приложений.
1.1. Код простого HTTP-сервера на C++
Создадим файл main.cpp:
#include <iostream>
#include <string>
#include <microhttpd.h>
#include <cstdlib>
static enum MHD_Result answer_to_connection(void *cls, struct MHD_Connection *connection,
const char *url, const char *method,
const char *version, const char *upload_data,
size_t *upload_data_size, void **con_cls) {
const char *page = "<html><body>Hello from C++ on Kubernetes!</body></html>";
struct MHD_Response *response = MHD_create_response_from_buffer(strlen(page),
(void*)page,
MHD_RESPMEM_PERSISTENT);
int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret == MHD_YES ? MHD_OK : MHD_NO;
}
int main() {
const char* port_str = std::getenv("PORT");
unsigned int port = port_str ? std::stoul(port_str) : 8080;
std::cout << "Starting server on port " << port << std::endl;
struct MHD_Daemon *daemon = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
port, nullptr, nullptr,
&answer_to_connection, nullptr,
MHD_OPTION_END);
if (nullptr == daemon) {
std::cerr << "Failed to start daemon" << std::endl;
return 1;
}
// Бесконечное ожидание
while (true) {
sleep(1);
}
MHD_stop_daemon(daemon);
return 0;
}
Этот код читает порт из переменной окружения PORT (значение по умолчанию — 8080) и запускает HTTP-сервер. Обратите внимание: мы используем бесконечный цикл, чтобы процесс не завершался — это важно для работы в контейнере.
1.2. Сборка приложения
Для сборки нам понадобится CMake. Создадим CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(cpp-k8s-server)
find_package(libmicrohttpd REQUIRED)
add_executable(server main.cpp)
target_link_libraries(server ${LIBMICROHTTPD_LIBRARIES})
target_include_directories(server PRIVATE ${LIBMICROHTTPD_INCLUDE_DIRS})
Теперь приложение готово к контейнеризации.
2. Создание Docker-образа для C++ приложения
Kubernetes работает с контейнерами, поэтому нам нужен Docker-образ. Для C++ приложений критически важен выбор базового образа: мы хотим минимизировать размер и повысить безопасность.
2.1. Многоступенчатая сборка (Multi-stage build)
Лучшая практика — использовать многоступенчатую сборку. На первом этапе мы компилируем приложение со всеми инструментами, на втором — копируем только готовый бинарник в чистый образ.
# Этап 1: Сборка
FROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y \
cmake \
g++ \
libmicrohttpd-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY CMakeLists.txt main.cpp ./
RUN cmake . && make
# Этап 2: Финальный образ
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
libmicrohttpd12 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/server /usr/local/bin/server
EXPOSE 8080
CMD ["server"]
В финальном образе остаётся только бинарник и минимальный набор рантайм-зависимостей. Размер такого образа будет около 50-70 МБ, что значительно меньше, чем если бы мы использовали полный образ с компилятором.
3. Деплой C++ приложения в Kubernetes
Теперь, когда у нас есть Docker-образ (например, myrepo/cpp-server:latest), мы можем развернуть его в Kubernetes. Нам понадобятся два основных манифеста: Deployment и Service.
3.1. Манифест Deployment
Deployment управляет репликами подов. Создадим файл deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: cpp-server-deployment
spec:
replicas: 3
selector:
matchLabels:
app: cpp-server
template:
metadata:
labels:
app: cpp-server
spec:
containers:
- name: cpp-server
image: myrepo/cpp-server:latest
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
- name: GREETING
valueFrom:
configMapKeyRef:
name: app-config
key: greeting
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"