W poprzednim wpisie z tego cyklu napisałem co nieco na temat postawienia k3s na prywatnym poleasingowym terminalu Della, który służy mi wiernie jako prywatny homelab.
Zainspirowany przez znajomych, rozwinę niektóre z tematów z poprzedniego wpisu i pokażę coś więcej o usługach Cloudflare oraz o zabezpieczeniu naszych usług i tajemnic.
Cloudflare Dynamic DNS
Czasami zamiast tunelowania, chcemy skorzystać bezpośrednio z wpisów A, ale… jak w mojej i wielu innych sytuacjach, mamy zmienne IP. Wtedy z pomocą przychodzi druga pożyteczna usługa w Cloudflare (która ma też inne dynamic dns odpowiedniki, jak np. DuckDNS).
W przeciwieństwie do tunelowania, konfiguracja Dynamic DNS jest nieco bardziej manualna (choć da się ją zautomatyzować) i wymaga wygenerowania tokena API z ustawieniami DNS w panelu Cloudflare. Dodatkowo warto pobrać również zoneId
dla istniejącej domeny. Serwery gier itp napewno lepiej postawić w takiej formie
Tip:
zoneId
można pobrać w panelu Cloudflare w sekcji Overview, (Prawy dolny róg)

Konfiguracja
Na portalu Cloudflare, w sekcji subdomains ustawiamy, do jakiej subdomeny chcemy aktualizować nasze IP. Jeśli chcemy, aby połączenie szło bezpośrednio, możemy wyłączyć proxy (proxied: false
).
Przykładowy plik konfiguracyjny (config.json
):
{
"cloudflare": [
{
"authentication": {
"api_token": "apiToken"
},
"zone_id": "zoneFromCloudflare",
"subdomains": [
{
"name": "www",
"proxied": true
}
]
}
],
"a": true,
"aaaa": false,
"purgeUnknownRecords": false,
"ttl": "Auto"
}
Tak przygotowany plik należy zakodować do Base64:
cat config.json | base64
Następnie wkleić zakodowaną wartość do pliku config.yaml
używanego przez Kubernetes.
Konfiguracja Kubernetes
---
apiVersion: v1
kind: Namespace
metadata:
name: cloudflare-ddns
---
apiVersion: v1
data:
config.json: zakodowany_base64_z_poprzedniego_kroku
kind: Secret
metadata:
name: cloudflare-ddns
namespace: cloudflare-ddns
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflare-ddns
namespace: cloudflare-ddns
spec:
selector:
matchLabels:
app: cloudflare-ddns
template:
metadata:
labels:
app: cloudflare-ddns
spec:
containers:
- name: cloudflare-ddns
image: timothyjmiller/cloudflare-ddns:latest
env:
- name: CONFIG_PATH
value: '/etc/cloudflare-ddns/'
volumeMounts:
- mountPath: '/etc/cloudflare-ddns'
name: cloudflare-ddns
readOnly: true
volumes:
- name: cloudflare-ddns
secret:
secretName: cloudflare-ddns
Po zaaplikowaniu ustawień możemy skonfigurować domeny w naszym domyślnym load balancerze i podpiąć certyfikat Let’s Encrypt.
Tworzenie sekretów w Kubernetes
Aby lepiej chronić wrażliwe dane (np. klucze API lub connection strings), możemy używać sekretów zamiast zmiennych środowiskowych. Zapewni nam to mniej wycieków po stronie kodu, gdzie możemy śmielej wrzucać do repozytorium naszą konfigurację kubernetesową czy helmową, ponieważ wiemy, że nasze dane są w naszej bezpiecznej krypcie.
Tworzenie sekretu
Sekrety można stworzyć prostą komendą:
kubectl create secret generic api-secrets \
--from-literal=ConnectionStrings__Marten="wartość_marten" \
--from-literal=Media__ConnectionStrings="wartość_media"
Weryfikacja sekretów
Możesz sprawdzić utworzone sekrety w klastrze:
kubectl get secret api-secrets
Użycie sekretów w aplikacji
Dodaj do swojego pliku Deployment
odwołania do sekretów jako zmienne środowiskowe:
env:
- name: ConnectionStrings__Marten
valueFrom:
secretKeyRef:
name: api-secrets
key: ConnectionStrings__Marten
- name: Media__ConnectionStrings
valueFrom:
secretKeyRef:
name: api-secrets
key: Media__ConnectionStrings

Let’s Encrypt na Homelab
Jeśli chcemy zautomatyzować zarządzanie certyfikatami SSL/TLS, możemy użyć narzędzia Cert Manager w Kubernetes.
Cert Manager integruje się z Let’s Encrypt, umożliwiając automatyczne wystawianie i odnawianie certyfikatów, przez co deploy trwa o wiele krócej. Nic nie stoi na przeszkodzie, by kupić certyfikat i go wstawić jako nowy secret w naszym środowisku.
- Instalacja Cert Managera za pomocą Helm:
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
- Utworzenie sekreta z API tokenem Cloudflare:
kubectl create secret generic cloudflare-api-token \
--namespace cert-manager \
--from-literal=api-token=<YOUR_CLOUDFLARE_API_TOKEN>
- Tworzenie ClusterIssuer:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: lifelike-cert
spec:
acme:
email: <your-email@example.com>
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: cluster-issuer-account-key
solvers:
- dns01:
cloudflare:
email: <your-cloudflare-email@example.com>
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
- Podpięcie certyfikatu w Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: coffeerecipes-api-ingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: lifelike-cert
spec:
tls:
- hosts:
- dev-api-recipes.lifelike.cloud
secretName: lifelike-cloud-tls
rules:
- host: dev-api-recipes.lifelike.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: coffeerecipes-api-service
port:
number: 80


Kod na koniec:
Aby podsumować działanie w tej części i nie irytować się, gdzie kod i wszystko
apiVersion: apps/v1
kind: Deployment
metadata:
name: coffeerecipes-api
labels:
app: coffeerecipes-api
spec:
replicas: 1
selector:
matchLabels:
app: coffeerecipes-api
template:
metadata:
labels:
app: coffeerecipes-api
spec:
containers:
- name: coffeerecipes-api
image: aluspl/coffeerecipesapi:develop
imagePullPolicy: Always
ports:
- containerPort: 80
env:
- name: ConnectionStrings__Marten
valueFrom:
secretKeyRef:
name: api-secrets
key: ConnectionStrings__Marten
- name: Media__ConnectionStrings
valueFrom:
secretKeyRef:
name: api-secrets
key: Media__ConnectionStrings
- name: ASPNETCORE_URLS
value: "http://*:80"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
resources:
limits:
memory: "1024Mi"
cpu: "500m"
requests:
memory: "512Mi"
cpu: "250m"
---
# Service dla aplikacji .NET
apiVersion: v1
kind: Service
metadata:
name: coffeerecipes-api-service
spec:
selector:
app: coffeerecipes-api
ports:
- protocol: TCP
port: 80
targetPort: 80
name: "http"
---
# Ingress dla aplikacji z użyciem Traefik
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: coffeerecipes-api-ingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: lifelike.cloud
spec:
tls:
- hosts:
- dev-api-recipes.lifelike.cloud
secretName: lifelike-cloud-tls
rules:
- host: dev-api-recipes.lifelike.cloud
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: coffeerecipes-api-service
port:
number: 80
Podsumowanie
Mam nadzieję, że wprowadzone zmiany i dodatkowe wskazówki będą pomocne w zabezpieczeniu Waszych homelabów. Dzięki za rady po poprzednim wpisie – dzięki nim udało mi się dopracować ten cykl.
Jak zwykle, zapraszam do spojrzenia, pouczenia – bo ja sam się dopiero uczę.
Źródła
Poniżej źródła, które pomogły mi w tym artykule.
https://cavecafe.medium.com/homelab-cloudflare-ddns-setup-09b37b54a7fb