Problem
Platforma sprzedająca serwery VPS zdecydowała się wdrożyć nowe środowisko wirtualizacji - PVE (Proxmox Virtual Environment). W związku z tym należało zaprojektować usługę, która weźmnie całą odpowiedzialność za zarządzanie cyklem życia maszyn wirtualnych klientów oraz ich monitorowaniem. Wymagana była niezawodność, idempotencja oraz spójność przy jednocześnie rosnącej liczbie klientów.
Kontekst
Warstwa wykonawcza opierała się na Proxmox VE. Panel klienta i panel administracyjny potrzebowały jednego, spójnego sposobu zlecania operacji na maszynach oraz wglądu w ich aktualny stan, a także możliwości monitorowania klastra — najlepiej w czasie rzeczywistym, bez ręcznego odświeżania. W celach administracyjnych wymagane było zarządzanie takimi funkcjami jak sieci wirtualne lub polityki migawek.
Podejście
Zaprojektowałem osobny moduł — „provider" — odpowiedzialny wyłącznie za komunikację z Proxmox i tłumaczenie intencji biznesowych (utwórz maszynę, zmień plan, usuń, uruchom, wykonaj migawkę) na konkretne operacje w infrastrukturze. Panel nigdy nie rozmawia z Proxmox bezpośrednio; zawsze przez jawny kontrakt providera.
Operacje długotrwałe potraktowałem jako zadania asynchroniczne z jawnym cyklem życia: zlecone, w toku, zakończone, błąd. Za ich orkiestrację w obrębie modułu odpowiada mechanizm internal commands processor. Dzięki temu interfejs mógł pokazywać rzeczywisty postęp, a nie tylko „proszę czekać".
Architektura
- Provider jako granica: cała wiedza o Proxmox zamknięta za jednym kontraktem.
- Kolejka zadań dla operacji długotrwałych, z idempotentnymi krokami.
- PostgreSQL jako źródło prawdy o stanie zamówień i maszyn.
- Redis do koordynacji zadań i cache'owania stanu odpytywanego często.
Implementacja
Kluczowa była idempotentność. Ponowienie zadania po awarii nie mogło tworzyć drugiej maszyny ani pozostawiać systemu w stanie niespójnym. Każdy krok sprawdzał najpierw, czy jego efekt już nie istnieje.
public async Task<Result> Handle(ProvisionVm task, CancellationToken ct)
{
var existing = await proxmox.FindByTagAsync(task.CorrelationId, ct);
if (existing is not null)
return Result.AlreadyDone(existing.VmId); // idempotentnie: brak duplikatu
var vm = await proxmox.CreateAsync(task.Spec, task.CorrelationId, ct);
await progress.PublishAsync(task.OrderId, VmState.Provisioned, ct);
return Result.Ok(vm.VmId);
}
Stan każdego zadania był publikowany na bieżąco, więc panel administracyjny i panel klienta widziały te same, aktualne informacje.
Stack
.NET, ASP.NET Core, PostgreSQL, Redis, Docker oraz API Proxmox VE.
Rezultat
Provisioning maszyn stał się w pełni automatyczny i powtarzalny. Czas realizacji typowego zamówienia skrócił się z minut liczonych ręcznie do operacji wykonywanej bez udziału człowieka. Panele pokazują rzeczywisty stan maszyn na żywo, a operacje można bezpiecznie ponawiać po awarii.
Lessons learned
- Idempotentność nie jest dodatkiem — przy operacjach na infrastrukturze to warunek bezpieczeństwa.
- Zamknięcie integracji za jednym kontraktem opłaciło się przy każdej późniejszej zmianie w Proxmox.
- Komunikacja w czasie rzeczywistym najbardziej pomogła nie klientom, lecz administracji — skróciła czas reakcji na problemy.