13 min czytaniaInżynieria
Kluczowa

Self-hosting Elido na k3s - kompletny poradnik

Krok po kroku: jak wdrożyć pełny stos Elido na klastrze k3s - bootstrap Helm, 14 usług, warstwa danych jako StatefulSets, Caddy on-demand TLS, kopie zapasowe i strategia aktualizacji.

Marius Voß
DevRel · edge infra
Architecture diagram of Elido on k3s: edge-redirect and api-core Deployments in front, StatefulSets for Postgres, Redis, ClickHouse, Redpanda, MinIO, and Meilisearch behind, Caddy Ingress handling on-demand TLS at the top

Zarządzana wersja Elido działa na infrastrukturze w regionie UE z konfiguracją privacy-by-default. Dla większości użytkowników jest to wystarczające. Dla niektórych - nie.

Jeśli twój zespół ds. bezpieczeństwa wymaga, aby dane o miejscach docelowych skróconych linków i zdarzenia kliknięć nigdy nie opuszczały konkretnego centrum danych, jeśli twoja polityka audytu wymaga pełnej kontroli nad serwerem bazy danych, lub jeśli integrujesz Elido we wewnętrzną platformę, która musi działać w środowisku izolowanym od sieci zewnętrznej - ścieżka self-hosted istnieje właśnie w tym celu.

Ten poradnik przeprowadza przez wdrożenie Elido na k3s - minimalnej dystrybucji Kubernetes gotowej do produkcji, która działa komfortowo na jednej maszynie wirtualnej lub małym klastrze HA. Na końcu będziesz mieć działające wszystkie usługi, wdrożone TLS, skonfigurowane kopie zapasowe i powtarzalną ścieżkę aktualizacji. Poradnik zakłada, że chcesz działający system, a nie tutorial o koncepcjach Kubernetes - wyjaśnienia są krótkie, a kroki operacyjne konkretne.

Dlaczego self-hosting#

Zanim przejdziemy do poradnika, warto precyzyjnie omówić kompromisy. Self-hosting nie jest automatycznie lepszy - przenosi ryzyko operacyjne od dostawcy na twój własny zespół.

Rezydencja danych i zgodność. Jeśli twój framework compliance (zakres ISO 27001, wewnętrzna polityka klasyfikacji danych lub umowna klauzula rezydencji danych) wymaga, aby metadane linków i zdarzenia analityczne pozostawały w infrastrukturze, którą bezpośrednio kontrolujesz, zarządzane SaaS nie może spełnić tego wymagania niezależnie od tego, gdzie fizycznie działa. Wdrożenie self-hosted na maszynie wirtualnej w regionie UE, którą posiadasz, może. Szczegóły dotyczące sposobu, w jaki architektura Elido mapuje się na popularne frameworki, znajdziesz w przeglądzie compliance.

Przewidywalność kosztów w skali. Plany zarządzane są wyceniane na podstawie aktywnych linków i wolumenu kliknięć. Powyżej pewnego progu - zazwyczaj gdzieś powyżej kilku milionów kliknięć miesięcznie - koszt per zdarzenie w planach zarządzanych przekracza koszt infrastruktury uruchomienia równoważnego obciążenia StatefulSet samodzielnie. Punkt przejścia zależy od kształtu twojego ruchu, ale istnieje.

Kontrola audytu. Niektóre organizacje wymagają dostępu do surowych tabel Postgres i danych zdarzeń kliknięć ClickHouse na potrzeby pakietowania dowodów, legal hold lub integracji z SIEM. API Elido udostępnia endpointy logów audytu i eksporty dowodów, ale bezpośredni dostęp do bazy danych jest dostępny tylko w wdrożeniach self-hosted.

Z czego rezygnujesz. k3s wymaga, aby ktoś zarządzał aktualizacjami, stanem węzłów, weryfikacją kopii zapasowych i reagowaniem na incydenty. Jeśli twój zespół nie ma doświadczenia operacyjnego z Kubernetes, zarządzana wersja we wspieranej lokalizacji hostingowej jest prawie na pewno właściwą odpowiedzią. W ostatniej sekcji tego wpisu znajdziesz bardziej bezpośrednie omówienie, kiedy self-hosting jest złym wyborem.

Wymagania wstępne#

  • Klaster k3s. Jeden węzeł z 4 vCPU i 8 GB RAM obsługuje lekkie obciążenia. Dla HA zalecana minimalna topologia to trzy węzły control-plane plus dwa lub więcej węzłów roboczych. Wbudowany etcd k3s obsługuje HA control-plane; HA warstwy danych jest obsługiwane przez Patroni (Postgres) wewnątrz chartu Helm.
  • kubectl skonfigurowany z kontekstem wskazującym na twój klaster.
  • Helm 3.14 lub nowszy.
  • Domena, którą kontrolujesz, z możliwością tworzenia rekordów DNS. IP ingress k3s musi być dostępne na portach 80 i 443 z internetu, aby wyzwania ACME Let's Encrypt mogły zakończyć się powodzeniem.
  • Jeśli wymagania compliance tego wymagają - maszyna wirtualna w regionie UE. Hetzner (Falkenstein, Helsinki, Norymberga) i OVH (Gravelines, Roubaix) to dwaj dostawcy, z których korzysta własna infrastruktura edge Elido.

Na świeżym serwerze Hetzner CX32 (4 vCPU / 8 GB) z Ubuntu 24.04 k3s instaluje się w około 30 sekund:

curl -sfL https://get.k3s.io | sh -
# Copy the kubeconfig to your local machine:
scp root@<your-vm>:/etc/rancher/k3s/k3s.yaml ~/.kube/elido-self-host.yaml
export KUBECONFIG=~/.kube/elido-self-host.yaml
kubectl get nodes  # should show Ready

Sklonuj repozytorium Elido, aby pobrać chart Helm (chart znajduje się w deploy/helm/elido/ w repozytorium - nie ma oddzielnego publicznego repozytorium Helm):

git clone https://github.com/elidoapp/elido.git
cd elido

Szybki start#

Preset self-host (deploy/helm/elido/values-selfhost.yaml) to zalecany punkt startowy dla wdrożenia na jednym węźle k3s. Najpierw uruchom bootstrap secretów, a następnie zainstaluj:

# 1. Mint all required Kubernetes Secrets in one shot (idempotent)
./scripts/bootstrap-secrets.sh elido

# 2. Install using the self-host preset
helm -n elido upgrade --install elido ./deploy/helm/elido \
  -f ./deploy/helm/elido/values-selfhost.yaml \
  --set ingress.hosts.redirect[0]=r.example.com \
  --set ingress.hosts.api=api.example.com \
  --set ingress.hosts.dashboard=app.example.com \
  --set image.tag=$(git rev-parse --short HEAD) \
  --create-namespace \
  --wait --timeout 10m

Dla niestandardowych nadpisań (liczba replik w produkcji, zarządzane usługi zewnętrzne itp.) skopiuj values-selfhost.yaml do lokalnego my-values.yaml, edytuj i przekaż z -f. Kluczowe sekcje wartości najwyższego poziomu to ingress.hosts.* (twoje nazwy hostów), image.registry/image.tag i bloki resources per usługę. Nie używaj global.domain - chart używa ingress.hosts.* do konfiguracji domeny.

Flaga --wait blokuje do momentu, gdy wszystkie Deploymenty i StatefulSety osiągną stan gotowości. Na świeżym węźle bez buforowanych obrazów oczekuj 5–8 minut przy łączu 1 Gbps.

Sprawdź, czy wszystko się uruchomiło:

kubectl -n elido get pods
kubectl -n elido get svc

Pełne parametry chartu sprawdzisz przez helm show values ./deploy/helm/elido. deploy/helm/elido/README.md w repozytorium to miarodajne źródło dla konkretnych nazw pól i użycia skryptu bootstrap.

Przepływ wdrożenia Helm dla Elido: bootstrap-secrets tworzy Kubernetes Secrets, helm upgrade install renderuje chart z wartościami self-host, manifesty są aplikowane do serwera API k3s, klaster uzgadnia 14 Deploymentów i 6 StatefulSetów, każdy pod uruchamia migrate up przy starcie, a helm wait blokuje do momentu gotowości wszystkich podów

Architektura, którą wdrażasz#

Architektura Elido jest podzielona według budżetu opóźnień. Zrozumienie tego podziału pomaga w podejmowaniu świadomych decyzji dotyczących alokacji zasobów.

Topologia klastra k3s dla Elido: Caddy ingress na górze, Deployment gorącej ścieżki edge-redirect i Deploymenty API ciepłej ścieżki poniżej, Deploymenty workerów zimnej ścieżki z boku oraz warstwa danych StatefulSet (Postgres, Redis, ClickHouse, Redpanda, MinIO, Meilisearch) na dole

Gorąca ścieżka: edge-redirect#

edge-redirect to jedyna usługa na synchronicznej ścieżce żądania przekierowania. Jest napisana w Go z fasthttp i ma twardy budżet opóźnień: p50 5 ms, p95 15 ms przy trafieniu w cache. Usługa utrzymuje dwupoziomowy cache: in-process LRU (L1) wsparty Redis Cluster (L2). Przy chybieniu cache przechodzi do wywołania gRPC do api-core. Zdarzenia kliknięć są emitowane fire-and-forget do Redpanda - odpowiedź przekierowania nigdy nie czeka na zakończenie zapisu zdarzenia.

Chart Helm wdraża edge-redirect jako Deployment z HorizontalPodAutoscaler. W konfiguracji self-hosted z jednym regionem, dwie repliki to rozsądny punkt startowy. Cache Redis L2 jest współdzielony między replikami, więc rozgrzewanie cache jest szybkie po aktualizacji rolling.

Ciepła ścieżka: powierzchnia API#

Pięć usług obsługuje synchroniczną pracę API i logikę biznesową:

  • api-core - Go + chi, REST i gRPC. Źródło prawdy dla linków, obszarów roboczych, członkostw, domen niestandardowych, zdarzeń audytu.
  • api-bff - warstwa Node/Hono BFF dla dashboardu webowego i klientów mobilnych. Agreguje dane z api-core i analytics-api.
  • analytics-api - Go, zapytania ClickHouse. Obsługuje dashboardy analityczne.
  • billing - Go + chi + sqlc. Silnik VAT UE, integracja płatności LiqPay, generowanie faktur.
  • search - Go, proxy zapytań wyszukiwania linków do Meilisearch.

Auth jest obsługiwany przez Ory Kratos (tożsamość, sesje, weryfikacja e-mail) i Ory Hydra (tokeny OAuth2/OIDC dla integracji zewnętrznych i rozszerzenia przeglądarki). Obydwa są wdrażane jako Deploymenty w charcie.

Zimna ścieżka: asynchroniczne workery#

Sześć usług konsumuje zdarzenia z tematów Redpanda i nie ma budżetu czasowego odpowiedzi:

  • click-ingester - konsumuje zdarzenia kliknięć, zapisuje do ClickHouse.
  • webhook-dispatcher - rozgłasza podpisane ładunki webhook do endpointów klientów.
  • notification - powiadomienia e-mail i in-app (zdarzenia kont, alerty linków).
  • url-scanner - uruchamia skanowanie docelowych URL pod kątem Google Safe Browsing, PhishTank, SURBL.
  • metadata-fetcher - pobiera metadane Open Graph dla podglądów linków.
  • domain-manager - weryfikacja DNS i provisionowanie Caddy on-demand TLS dla domen niestandardowych.

Warstwa danych: StatefulSets#

Sześć systemów stanowych wspiera powyższe usługi:

SystemRolaZasób chartu
Postgres (Patroni)Źródło prawdy dla linków, użytkowników, rozliczeńStatefulSet, 3 repliki w trybie HA
Redis ClusterCache gorącej ścieżki linkówStatefulSet
ClickHousePrzechowywanie zdarzeń kliknięć i zapytania analityczneStatefulSet
RedpandaMagistrala zdarzeń między usługamiStatefulSet
MinIOZasoby przesyłane przez użytkowników (obrazy QR, eksporty)StatefulSet
MeilisearchWyszukiwanie linków in-appStatefulSet

W wdrożeniu na jednym węźle każdy StatefulSet działa z jedną repliką. W trybie HA (włączanym przez ustawienie ha.enabled: true w values) Postgres skaluje się do trzech replik pod Patroni, Redis do sześciu (trzy podstawowe, trzy repliki), a Redpanda do trzech brokerów.

Okablowanie warstwy danych dla self-hostowanego Elido: edge-redirect odczytuje cache Redis L2, api-core używa Postgres jako źródła prawdy i emituje zdarzenia do Redpanda, click-ingester konsumuje Redpanda i zapisuje ClickHouse, analytics-api odpytuje ClickHouse, search odpytuje Meilisearch, a api-core przechowuje zasoby w MinIO

Caddy ingress#

Caddy obsługuje terminację TLS i on-demand TLS dla domen niestandardowych najemców. Gdy operator obszaru roboczego dodaje niestandardową domenę przez dashboard (lub API), domain-manager weryfikuje własność DNS, rejestruje nazwę hosta jako dozwoloną, a Caddy provisionuje certyfikat Let's Encrypt przy pierwszym żądaniu HTTP do tej nazwy hosta. Chart wdraża Caddy jako DaemonSet na węzłach oznaczonych elido.app/ingress=true lub jako Deployment w domyślnej konfiguracji jednego węzła.

Przepływ dla niestandardowej domeny najemcy go.company.com:

  1. Operator tworzy CNAME: go.company.com → <your-k3s-ingress-ip> (lub rekord A wskazujący bezpośrednio).
  2. Operator wywołuje POST /v1/workspaces/{id}/domains lub klika „Dodaj domenę" w dashboardzie.
  3. domain-manager odpytuje DNS, aby potwierdzić, że CNAME rozwiązuje się do IP ingress.
  4. Caddy otrzymuje pierwsze żądanie HTTPS, sprawdza listę dozwolonych wydaną przez Elido i prosi o certyfikat od Let's Encrypt przez ACME HTTP-01.
  5. Certyfikat jest przechowywany w wolumenie stanu Caddy i odnawiany automatycznie.

Bootstrap przy pierwszym uruchomieniu#

Po zakończeniu helm install admin API jest dostępne, ale nie istnieje żadne konto użytkownika.

Bootstrap konta administratora#

# Port-forward to the api-core admin interface (not exposed by default)
kubectl -n elido port-forward svc/api-core 8081:8081 &

# Create the first admin account
curl -X POST http://localhost:8081/internal/admin/bootstrap \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "[email protected]",
    "password": "your-secure-password"
  }'

Endpoint bootstrap jest wywoływalny tylko z sieci klastra i jest wyłączany po pierwszym pomyślnym wywołaniu. Gdy konto administratora istnieje, zaloguj się przez dashboard pod skonfigurowaną domeną.

Tworzenie pierwszego obszaru roboczego#

Po zalogowaniu dashboard przy pierwszym użyciu wyświetli monit o utworzenie obszaru roboczego. Alternatywnie przez API:

curl -X POST https://links.example.com/v1/workspaces \
  -H "Authorization: Bearer <your-session-token>" \
  -H 'Content-Type: application/json' \
  -d '{"name": "My Workspace", "slug": "my-workspace"}'

Dodawanie niestandardowej domeny#

W ustawieniach obszaru roboczego przejdź do Domeny i dodaj domenę skróconych linków. Ustaw rekord DNS (CNAME lub A) przed kliknięciem „Weryfikuj" - domain-manager natychmiast sprawdza DNS i zwraca błąd, jeśli rekord jeszcze się nie propagował. Po pomyślnej weryfikacji Caddy zaprovisionuje certyfikat przy pierwszym żądaniu przekierowania do tej domeny. Wystawienie certyfikatu zazwyczaj zajmuje 10–30 sekund przy pierwszym żądaniu.

Sprawdź, czy TLS działa:

curl -I https://go.company.com/healthz
# Expect: HTTP/2 200

Kwestie operacyjne#

Kopie zapasowe#

Postgres. Chart zawiera CronJob, który uruchamia pg_dump według konfigurowalnego harmonogramu (domyślnie: codziennie o 02:00 UTC) i wgrywa skompresowany dump do zasobnika MinIO skonfigurowanego w values. W przypadku wdrożeń HA pg_dump działa na replice, aby uniknąć wpływu na podstawową instancję. Włącz w values:

backups:
  postgres:
    enabled: true
    schedule: "0 2 * * *"
    retentionDays: 30
    s3Bucket: "elido-backups"

Dla point-in-time recovery włącz archiwizację WAL (postgres.walArchive.enabled: true w values), która nieprzerwanie wysyła segmenty WAL do MinIO. Połącz codzienne pg_dump z archiwizacją WAL dla RPO poniżej 5 minut.

ClickHouse. ClickHouse przechowuje zdarzenia kliknięć, które są tylko do dołączania i mogą być odtworzone z Redpanda, jeśli retencja na to pozwala. Chart zawiera CronJob, który uruchamia SQL BACKUP TABLE do MinIO za pomocą natywnego interfejsu kopii zapasowych ClickHouse. Włącz przez backups.clickhouse.enabled: true.

Redpanda. Redpanda to magistrala strumieniowania, nie baza danych. Retencja jest czasowa (domyślnie: 7 dni) i konfigurowana w values pod redpanda.retention. Jeśli twój click-ingester zaleg ponad okno retencji, zdarzenia są tracone. Monitoruj lag grupy konsumentów - chart zawiera alert Prometheus (ElidoRedpandaConsumerLag), który uruchamia się, gdy jakakolwiek grupa konsumentów jest ponad 100K wiadomości za.

Weryfikacja kopii zapasowych. Kopia zapasowa, która nigdy nie była testowana, to kopia zapasowa, na której nie możesz polegać. Przeprowadzaj ćwiczenie przywracania do osobnego namespace co najmniej kwartalnie:

kubectl -n elido-restore create ns elido-restore || true
helm install elido-restore ./deploy/helm/elido \
  --namespace elido-restore \
  --values ./my-values.yaml \
  --set restore.fromBackup=true \
  --set restore.backupDate="2026-05-01"

Monitoring#

Chart zawiera ServiceMonitor dla każdej usługi, jeśli CRD Prometheus Operator jest obecne w klastrze. Kluczowe metryki do alarmowania:

monitoring:
  prometheus:
    enabled: true # requires prometheus-operator in cluster
  grafana:
    enabled: true # deploys bundled dashboards
    adminPassword: "change-me"

Bundlowane dashboardy Grafana obejmują:

  • edge-redirect opóźnienia p50/p95, współczynnik trafień cache, wolumen kliknięć
  • api-core wskaźnik żądań, wskaźnik błędów, opóźnienia gRPC
  • click-ingester lag konsumenta per partycję Redpanda
  • opóźnienie replikacji primary/replica Postgres (Patroni)
  • wskaźnik eksmisji i presja pamięci Redis

Jeśli masz już stos Prometheus w klastrze, ustaw monitoring.prometheus.install: false i wskaż istniejący stos na ServiceMonitory.

Patroni HA dla Postgres#

W trybie HA Patroni zarządza wyborem lidera i failoverem. Chart konfiguruje Patroni z rozproszonym magazynem konfiguracji kubernetes (używa Kubernetes ConfigMaps, nie potrzeba oddzielnego etcd). Failover zazwyczaj kończy się w 15–30 sekund. Podczas failoveru api-core i billing doświadczają krótkich błędów zapisu; obydwie usługi ponawiają próbę na 5xx z wykładniczym wycofaniem.

Aby sprawdzić stan klastra Patroni:

kubectl -n elido exec -it postgres-0 -- patronictl -c /etc/patroni/config.yml list

Proces aktualizacji#

Elido stosuje semantic versioning. Wydania patch zawierają poprawki błędów i nie wymagają ręcznych kroków migracji. Wydania minor i major mogą zawierać migracje schematu bazy danych, które są osadzone w binarnych plikach usług i uruchamiane automatycznie przy starcie poda przez runner migracji.

Zalecana ścieżka aktualizacji:

# 1. Pull the latest chart (git pull in your cloned repo)
git pull origin main

# 2. Review the upgrade notes
helm show chart ./deploy/helm/elido | grep -A10 'version\|appVersion'

# 3. Diff the values changes (requires helm-diff plugin)
helm diff upgrade elido ./deploy/helm/elido \
  --namespace elido \
  --values ./my-values.yaml

# 4. Upgrade
helm upgrade elido ./deploy/helm/elido \
  --namespace elido \
  --values ./my-values.yaml \
  --wait --timeout 10m

Obsługa migracji. Każdy binarny plik usługi Go zawiera migracje schematu (używając golang-migrate w stosunku do osadzonego katalogu migracji). Przy starcie poda binarka uruchamia migrate up przed obsługą żądań. W aktualizacji rolling nowy pod stosuje wszelkie oczekujące migracje przed zakończeniem starego poda. Migracje muszą być wstecznie kompatybilne z poprzednią wersją minor - nowa kolumna dodana w v1.5 musi być nullable lub mieć domyślną wartość, aby binarka v1.4 działająca obok niej podczas okna rollout nie generowała błędów.

Blue/green dla głównych aktualizacji. Dla aktualizacji wersji major, gdzie zmiany schematu nie są wstecznie kompatybilne, użyj strategii blue/green: zainstaluj nową wersję pod osobną nazwą wydania Helm w staging namespace, zmigruj snapshot produkcyjnej bazy danych do niej, przeprowadź testy smoke, a następnie wykonaj przełączenie DNS na poziomie ingress. Po walidacji zielonego stosu usuń niebieskie wydanie.

# Install green stack
helm install elido-green ./deploy/helm/elido \
  --namespace elido-green \
  --create-namespace \
  --values ./green-values.yaml

# After validation, cut over DNS at your registrar
# Then decommission blue
helm uninstall elido --namespace elido

Kiedy nie self-hostować#

Self-hosting jest właściwym wyborem w ograniczonym zestawie okoliczności. W większej liczbie przypadków, niż możesz się spodziewać, jest złym wyborem.

Twoje obciążenie jest małe. Jeśli tworzysz mniej niż 100K linków miesięcznie i nie masz ścisłych wymagań dotyczących rezydencji danych, zarządzane Elido kosztuje mniej - zarówno finansowo, jak i operacyjnie - niż uruchamianie k3s. Hostowany poziom obejmuje kopie zapasowe, aktualizacje i dyżur dla infrastruktury, którą w innym przypadku musiałbyś sam prowadzić.

Nie masz doświadczenia operacyjnego z Kubernetes. k3s jest minimalny, ale Kubernetes nie jest prosty. Jeśli nikt z twojego zespołu nie zarządzał StatefulSetami, nie obsługiwał backup/restore etcd ani nie debugował CrashLoopBackOff w Patroni o 2 w nocy, self-hosting dodaje kategorię ryzyka operacyjnego, której oszczędności kosztów infrastruktury nie kompensują.

Twoje wymaganie compliance to rezydencja danych w UE, a nie konkretna dzierżawa. Rezydencja danych w UE oznacza dane przechowywane i przetwarzane w UE. Zarządzana infrastruktura Elido działa na Hetzner FRA i OVH GRA, obydwa spełniają wymagania art. 44 RODO bez transferu transgranicznego. Jeśli faktycznym wymaganiem twojego zespołu compliance jest „dane w UE", zarządzany produkt już to spełnia bez wdrożenia self-hosted - sprawdź stronę cennikową dla funkcji compliance dostępnych w każdym planie.

Chcesz wydajności edge w wielu regionach. Zarządzana usługa działa z POPami edge we Frankfurcie, Ashburn i Singapurze, z wdrożonym edge-redirect na gorącej ścieżce w każdym. Klaster k3s self-hosted w jednym regionie ma opóźnienie przekierowania ograniczone przez geograficzną odległość maszyny wirtualnej od użytkownika końcowego. Self-hosting w wielu regionach jest możliwy, ale znacznie zwiększa powierzchnię operacyjną - to inne przedsięwzięcie niż to, co obejmuje ten poradnik.


Pytania dotyczące wartości chartu Helm, wymagań zasobów StatefulSet dla konkretnych wolumenów ruchu lub ścieżek aktualizacji dla konkretnej wersji należy kierować na tablicę GitHub Discussions dla edycji self-hosted. Dla pytań związanych z compliance - konkretnie co konfiguracja self-hosted zapewnia ponad rezydencję danych w UE - strona compliance omawia log audytu, eksport dowodów i kontrole RBAC szczegółowo.

Powiązane na blogu#

Wypróbuj Elido

Wklej URL, otrzymaj krótki link

Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.

Za darmo, bez rejestracji · 2 dziennie

Wypróbuj Elido

Skracarka URL hostowana w UE: własne domeny, głęboka analityka i otwarte API. Darmowy plan - bez karty kredytowej.

Tagi
self hosted url shortener
k3s
kubernetes url shortener
helm
self host elido
url shortener kubernetes
data residency
eu compliance

Czytaj dalej