Laravel na k3s — Część 1 · Część 2 · Część 3 · Część 4
k3s jest lekką dystrybucją Kubernetes odpowiednią dla pojedynczego VPS, gdy potrzebujesz deklaratywnych workloadów, probe'ów i kontrolowanych rolloutów. Nie jest skrótem wokół backupów, planowania pojemności ani administracji Linuxem.
Utwórz namespace, wdrażaj niemutowalne obrazy aplikacji i dodaj readiness oraz liveness probe, zanim wystawisz ruch. Readiness probe decyduje, czy pod otrzymuje ruch; niech będzie tania i zależna wyłącznie od wymagań obsłużenia requestu. Trwałe dane umieszczaj na jawnie wybranym storage i backupuj niezależnie od manifestów. Ingress Traefik kończy TLS i routuje hostname'y; kontener Laravel powinien obsługiwać wyłącznie odpowiedzialność aplikacji.
Pierwszy kamień milowy jest celowo mały: jedno zdrowe wdrożenie, usługa, ingress i udokumentowany rollback przez kubectl rollout undo. Nie dodawaj baz i usług pomocniczych, dopóki ta ścieżka nie jest obserwowalna.
Kiedy k3s jest właściwą granicą
k3s uzasadnia koszt operacyjny, gdy kilka niezależnie wdrażanych workloadów potrzebuje jednego modelu rolloutów, discovery usług i izolacji. Pojedynczy kontener Laravel z zarządzaną bazą często będzie prostszy na Docker Compose lub platformie zarządzanej. Kubernetes nie czyni jedno-węzłowego VPS wysokodostępnym: węzeł, dysk i sieć nadal są jednym punktem awarii. Wybieraj go dla powtarzalnej platformy, nie dlatego, że manifest wygląda bardziej profesjonalnie.
Załóż Ubuntu VPS z firewallem otwierającym tylko SSH i HTTPS. Zainstaluj k3s z oficjalnej dokumentacji wydania, chroń wygenerowany kubeconfig i od razu sprawdź węzeł — poprawny kod wyjścia instalatora nie jest dowodem działania:
curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodes -o wide
sudo k3s kubectl get pods -A
sudo install -m 600 /etc/rancher/k3s/k3s.yaml /root/kubeconfig.yaml
Nie wklejaj kubeconfigu do CI ani nie commituj go. Później CI dostanie token
dedykowanego service accountu z wąsko ograniczoną rolą. Teraz utwórz namespace
aplikacji i niemutowalną referencję obrazu. registry.example.com/acme/store:3f18d7c
musi wskazywać obraz, który już istnieje; latest nie daje prawdziwego rollbacku.
Mały, ale poprawny workload
Obraz poniżej wystawia trasę health Laravel bez wołania zewnętrznego dostawcy płatności. Readiness dowodzi, że pod może przyjąć ruch; liveness jedynie, że proces PHP nie utknął. Nie każ liveness odpytwać bazy: awaria bazy ma odebrać ruch przez readiness, a nie restartować wszystkie zdrowe procesy web.
apiVersion: v1
kind: Namespace
metadata:
name: storefront
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: storefront
spec:
replicas: 2
selector:
matchLabels: { app: storefront-web }
template:
metadata:
labels: { app: storefront-web }
spec:
containers:
- name: web
image: registry.example.com/acme/store:3f18d7c
imagePullPolicy: IfNotPresent
ports: [{ containerPort: 8080, name: http }]
envFrom: [{ secretRef: { name: storefront-runtime } }]
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
readinessProbe:
httpGet: { path: /up, port: http }
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet: { path: /up, port: http }
initialDelaySeconds: 20
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata: { name: web, namespace: storefront }
spec:
selector: { app: storefront-web }
ports: [{ port: 80, targetPort: http, name: http }]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: storefront
namespace: storefront
spec:
ingressClassName: traefik
rules:
- host: shop.example.com
http:
paths:
- path: /
pathType: Prefix
backend: { service: { name: web, port: { name: http } } }
Wdróż i obserwuj rollout, a nie tylko odpowiedź API:
kubectl apply -f k8s/base.yaml
kubectl -n storefront rollout status deployment/web --timeout=120s
kubectl -n storefront get pods,svc,ingress
kubectl -n storefront describe deployment/web
envFrom jest celowo referencją, nie manifestem sekretu. Twórz wartości
runtime poza Gitem przez manager sekretów albo chroniony krok wdrożenia. Sekrety
Kubernetes są domyślnie tylko kodowane base64, nie są bezpiecznym miejscem dla
nieograniczonych poświadczeń produkcyjnych.
Ścieżka awarii przed kolejną funkcją
Udany rollout daje rewizję. Sprawdź rollback, zanim incydent będzie od niego zależał:
kubectl -n storefront rollout history deployment/web
kubectl -n storefront rollout undo deployment/web --to-revision=1
kubectl -n storefront rollout status deployment/web --timeout=120s
Gdy pod pozostaje Pending, obejrzyj kubectl describe pod i events przed
zmianą liczby replik. Zwykle winny jest niedostępny obraz, brak sekretu, zły
status probe albo requesty większe niż pojemność węzła. Restart ukrywa trop.
Celowo nie wprowadzamy jeszcze MySQL, Redis, Horizon ani uploadów. Usługi stanowe potrzebują historii backupu i odtworzenia, nie samego PVC. Część 2 dodaje śledzalny pipeline, a część 3 — kontrole operacyjne i dowody recovery.
Kontener jest częścią kontraktu
Manifest zakłada, że obraz uruchamia serwer web jako użytkownik niebędący rootem
i nie wykonuje php artisan migrate przy starcie procesu. Pod web może zostać
odtworzony w każdej chwili; związanie migracji z jego startem powoduje wyścig
między replikami i czyni rollback niebezpiecznym. Zbuduj assety i zależności
Composera w obrazie, a konfigurację pozostaw środowisku:
FROM php:8.5-cli-alpine AS application
WORKDIR /var/www/html
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY composer.json composer.lock ./
RUN composer install --no-dev --prefer-dist --no-interaction --no-scripts
COPY . .
RUN composer dump-autoload --classmap-authoritative --no-dev \
&& addgroup -S laravel && adduser -S laravel -G laravel \
&& chown -R laravel:laravel storage bootstrap/cache
USER laravel
EXPOSE 8080
CMD ["php", "artisan", "octane:start", "--server=frankenphp", "--host=0.0.0.0", "--port=8080"]
To przykład, nie nakaz wdrożenia Octane. PHP-FPM za nginx jest równie poprawne. Liczy się jeden proces na pierwszym planie, udokumentowany port, katalogi zapisywalne z minimalnymi potrzebnymi uprawnieniami oraz obraz uruchamialny lokalnie pod tymi samymi nazwami zmiennych. Jeśli aplikacja używa Octane, sprawdź singletony i statyczny stan przed skalowaniem; repliki Kubernetes nie naprawią wycieku stanu między requestami.
Sprawdź ścieżkę ruchu i jej granice
Skieruj DNS dopiero, gdy ingress ma adres, i testuj hostname, nie IP poda. Bezpośredni curl do poda może przejść, choć TLS, dopasowanie hosta lub nagłówki proxy są błędne:
kubectl -n storefront port-forward service/web 8080:80
curl --fail http://127.0.0.1:8080/up
curl --fail --resolve shop.example.com:443:203.0.113.10 https://shop.example.com/up
kubectl -n storefront logs deployment/web --tail=100
Przed przyjęciem ruchu klientów ustaw prawdziwego wystawcę TLS i politykę redirectów; dokładna konfiguracja zależy od tego, czy certyfikatami zarządza Traefik, cert-manager, czy zewnętrzny load balancer. Ustal też harmonogram backupów, patchowania i przeglądu dostępu. Zdrowy deployment jest pierwszą obserwowalną jednostką, nie dowodem disaster recovery ani bezpieczeństwa.
Jawnie określ zachowanie rolloutu
Domyślne ustawienia rolling update łatwo przeoczyć, dopóki mały węzeł nie
potrafi zaplanować dodatkowego poda. Zapisz więc kompromis dostępności w
Deployment. Przy dwóch replikach maxUnavailable: 0 utrzymuje pojemność
podczas normalnej zmiany obrazu, a maxSurge: 1 pozwala na jeden dodatkowy pod
tylko wtedy, gdy węzeł ma miejsce na jego requesty zasobów. Na pojedynczym VPS
rollout może przez to poczekać, zamiast wyłączyć zdrowego poda. To zwykle
bezpieczniejszy sposób porażki.
spec:
revisionHistoryLimit: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: web
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"]
Te pola dopisz do wcześniejszego Deployment, a nie aplikuj sam fragment.
Krótki preStop daje kontrolerowi endpoints chwilę na zaprzestanie kierowania
nowych requestów, zanim proces się zakończy. Nie zapewnia trwałości requestów
już obsługiwanych. Długie zapytanie, job kolejki albo połączenie WebSocket
potrzebują własnego projektu zamykania i timeoutu dopasowanego do aplikacji.
Utwórz wskazywany sekret runtime przez chroniony mechanizm wdrożeniowy. To polecenie jest celowo przykładem dla terminala, w którym wartości dostarcza manager sekretów; nie umieszcza poświadczeń w YAML:
kubectl -n storefront create secret generic storefront-runtime \
--from-literal=APP_ENV=production \
--from-literal=APP_KEY="$APP_KEY" \
--from-literal=DB_CONNECTION=mysql \
--from-literal=DB_HOST="$DB_HOST" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl -n storefront get secret storefront-runtime
Nie używaj formy --from-literal w historii shella na współdzielonym bastionie.
Wybierz raczej kontroler external-secrets, integrację CI maskującą wartości lub
lokalnie zabezpieczony mechanizm wejścia. Pamiętaj też, że aktualizacja Secret
nie restartuje podów, które pobrały go jako zmienne środowiskowe. Po rotacji
poświadczeń świadomie wymuś rollout i sprawdź, czy nowe pody stają się gotowe.
Runbook pierwszego wdrożenia
Zanim uznasz ten kamień milowy za zakończony, wykonaj te same kontrole, które
operator wykona w zły dzień. Potwierdź gotowość każdej repliki, endpoints dla
Service i to, że zewnętrzny request trafia do oczekiwanego obrazu. ID obrazu z
describe ma znaczenie: HTTP 200 ze starego poda nie oznacza udanego deployu.
kubectl -n storefront get deployment web
kubectl -n storefront get endpoints web
kubectl -n storefront get pods -l app=storefront-web \
-o custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[0].ready,IMAGE:.spec.containers[0].image
kubectl -n storefront get events --sort-by=.lastTimestamp | tail -n 20
curl --fail --show-error https://shop.example.com/up
Gdy weryfikacja nie przejdzie, nie zmieniaj naraz wielu zmiennych. Zapisz obraz
Deployment, Events, opis poda i ostatnie logi; potem napraw manifest w Git i
zbuduj nowy niemutowalny obraz albo wróć do znanej dobrej rewizji. kubectl rollout undo odwraca wyłącznie szablon Deployment. Nie odtworzy skasowanego
PVC, nie cofnie nieodwracalnej migracji ani nie odzyska danych zapisanych przez
wadliwy release. Z tego powodu komponenty stanowe i migracje baz danych należą
do późniejszego, osobno przećwiczonego kroku.
Kiedy to nie jest właściwe pierwsze wdrożenie
Nie wprowadzaj k3s tylko po to, by uruchomić jedną zwykłą stronę Laravel. Docker Compose z zarządzaną bazą i przetestowaną procedurą backupu jest prostszy w operowaniu, ma mniej uprawnień i często lepiej pasuje do pierwszego systemu produkcyjnego. Zarządzana platforma kontenerowa jest dobrą alternatywą, gdy zespół potrzebuje zarządzanego TLS, autoscalingu i patchowania węzłów bardziej niż API Kubernetes.
k3s staje się sensowną granicą, gdy zespół akceptuje odpowiedzialność za aktualizacje, politykę sieci, retencję obrazów, monitoring węzła i ćwiczenia odzyskiwania — oraz korzysta z wdrażania wielu usług według tego samego kontraktu. Zacznij od tak wąskiego workloadu, aby wybór potwierdzały obserwacje, a nie efektowny diagram klastra.