Для чего нужна данная статья? :
- Создать оператор в Kubernetes кластере.
Зачем Вам это уметь? :
Научиться управлять пользовательскими ресурсами (CRD) в кластере Kubernetes.
Создайте новый проект на Rust:
cargo new my-k8s-operator
cd my-k8s-operator
Откройте файл Cargo.toml и добавьте следующие зависимости:
[dependencies]
tokio = { version = "1", features = ["full"] }
kube = { version = "0.80", features = ["runtime", "derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
log = "0.4"
env_logger = "0.9"
Давайте определим нашу пользовательскую структуру данных (CRD), которая будет управляться оператором.
crd.rs
use kube::CustomResource;
use serde::{Deserialize, Serialize};
#[derive(CustomResource, Debug, Clone, Serialize, Deserialize, Default)]
#[kube(group = "example.com", version = "v1", kind = "App", namespaced)]
#[kube(status = "AppStatus")]
pub struct AppSpec {
pub name: String,
pub replicas: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AppStatus {
pub available_replicas: i32,
}
Теперь реализуем логику оператора, которая будет отслеживать изменения в CRD и выполнять соответствующие действия.
main.rs
mod crd;
use crate::crd::{App, AppSpec, AppStatus};
use kube::{
api::{Api, Patch, PatchParams},
runtime::{controller::{self, Controller}, reflector::ObjectRef,
watcher::Config},
Client, ResourceExt,
};
use serde_json::json;
use std::sync::Arc;
use tokio::time::{sleep, Duration};
use tracing::{info, instrument};
#[instrument(skip(apps))]
async fn reconcile(app: Arc<App>, apps: Api<App>) -> Result<(),
controller::Error> {
let name = app.name_any();
let ns = app.namespace().unwrap();
info!("Reconciling {}/{}", ns, name);
let spec = app.spec.clone();
let status = AppStatus {
available_replicas: spec.replicas,
// Простая логика: установка статуса равным желаемому количеству реплик
};
let patch = json!({
"status": status,
});
apps.patch_status(&name, &PatchParams::apply("kube-rs"),
&Patch::Merge(&patch)).await?;
Ok(())
}
async fn error_policy(_app: Arc<App>, _error: &controller::Error, _ctx:
controller::Context<()>) -> controller::ReconcilerAction {
sleep(Duration::from_secs(5)).await;
controller::ReconcilerAction {
requeue_after: Some(Duration::from_secs(5)),
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let client = Client::try_default().await?;
let apps: Api<App> = Api::all(client.clone());
let context = controller::Context::new(());
Controller::new(apps.clone(), Config::default())
.run(reconcile, error_policy, context)
.for_each(|res| async move {
match res {
Ok(o) => info!("Reconciled {:?}", o),
Err(e) => info!("Reconcile failed: {:?}", e),
}
})
.await;
Ok(())
}
reconcile— Основная функция, которая возникает при каждом выступе CRD. В ней определены, какие действия необходимо активировать в зависимости от текущего состояния ресурса.
error_policy— функция, которая определяет поведение оператора при устранении ошибок. В данном случае она просто повторяет это через 5 секунд.
Controller::new— Создаёт контроллер, который будет отслеживать изменения ресурсов CRD и сохранять функцию reconcile.
Для сборки и запуска оператора:
cargo build --release
Dockerfile для оператора:
FROM rust:slim-buster AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:buster-slim
COPY --from=builder /app/target/release/k8s-operator /usr/local/bin/
ENTRYPOINT ["k8s-operator"]
Возьмите Docker-образ:
docker build -t your-username/k8s-operator:latest .
Загрузите образ в реестр Docker:
docker push your-username/k8s-operator:latest
Создайте манифест для оператора развертывания в Kubernetes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-operator
spec:
replicas: 1
selector:
matchLabels:
app: k8s-operator
template:
metadata:
labels:
app: k8s-operator
spec:
containers:
- name: k8s-operator
image: your-username/k8s-operator:latest
imagePullPolicy: Always
Примените манифесты и разверните оператора:
kubectl apply -f operator-deployment.yaml
Создайте объект вашего пользовательского ресурса (CRD) и наблюдайте за действиями оператора - my-app.yaml:
apiVersion: example.com/v1
kind: App
metadata:
name: my-app
namespace: default
spec:
name: "My Rust App"
replicas: 3
Замените этот файл:
kubectl apply -f my-app.yaml
Теперь ваш оператор будет следить за типом объектов App и обновлять статус на основе количества реплик, указанного в характеристиках.