diff --git a/cert-manager/README.md b/cert-manager/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/cert-manager/README.md @@ -0,0 +1 @@ + diff --git a/cert-manager/install.sh b/cert-manager/install.sh new file mode 100755 index 0000000..b512301 --- /dev/null +++ b/cert-manager/install.sh @@ -0,0 +1,233 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +CERT_MANAGER_DIR="${INSTANCE_DIR}/apps/cert-manager" + +echo "=== Setting up cert-manager ===" +echo "" + +####################### +# Dependencies +####################### + +echo "Verifying Traefik is ready (required for cert-manager)..." +kubectl wait --for=condition=Available deployment/traefik -n traefik --timeout=60s 2>/dev/null || { + echo "WARNING: Traefik not ready, but continuing with cert-manager installation" + echo "Note: cert-manager may not work properly without Traefik" +} + +if [ ! -f "${CERT_MANAGER_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${CERT_MANAGER_DIR}/" + echo "Templates should be compiled before deployment." + exit 1 +fi + +######################## +# Kubernetes components +######################## + +echo "Installing cert-manager components..." +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml || \ + kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.17.2/cert-manager.yaml + +echo "Waiting for cert-manager to be ready..." +kubectl wait --for=condition=Available deployment/cert-manager -n cert-manager --timeout=120s +kubectl wait --for=condition=Available deployment/cert-manager-cainjector -n cert-manager --timeout=120s +kubectl wait --for=condition=Available deployment/cert-manager-webhook -n cert-manager --timeout=120s + +echo "Creating Cloudflare API token secret..." +SECRETS_FILE="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}/secrets.yaml" +CLOUDFLARE_API_TOKEN=$(yq '.apps.cert-manager.cloudflareToken' "$SECRETS_FILE" 2>/dev/null) + +CLOUDFLARE_API_TOKEN=$(echo "$CLOUDFLARE_API_TOKEN") +if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ "$CLOUDFLARE_API_TOKEN" = "null" ]; then + echo "ERROR: Cloudflare API token not found" + echo "Please set: apps.cert-manager.cloudflareToken in secrets.yaml" + exit 1 +fi + +kubectl create secret generic cloudflare-api-token \ + --namespace cert-manager \ + --from-literal=api-token="${CLOUDFLARE_API_TOKEN}" \ + --dry-run=client -o yaml | kubectl apply -f - + +echo "Verifying cert-manager webhook is fully operational..." +until kubectl get validatingwebhookconfigurations cert-manager-webhook &>/dev/null; do + echo "Waiting for cert-manager webhook to register..." + sleep 5 +done + +echo "Configuring cert-manager to use external DNS servers..." +kubectl patch deployment cert-manager -n cert-manager --patch ' +spec: + template: + spec: + dnsPolicy: None + dnsConfig: + nameservers: + - "1.1.1.1" + - "8.8.8.8" + searches: + - cert-manager.svc.cluster.local + - svc.cluster.local + - cluster.local + options: + - name: ndots + value: "5"' + +echo "Waiting for cert-manager to restart with new DNS configuration..." +kubectl rollout status deployment/cert-manager -n cert-manager --timeout=120s + +######################## +# Create issuers and certificates +######################## + +echo "Creating Let's Encrypt issuers and certificates..." +kubectl apply -k ${CERT_MANAGER_DIR}/ + +echo "Waiting for Let's Encrypt issuers to be ready..." +kubectl wait --for=condition=Ready clusterissuer/letsencrypt-prod --timeout=60s || echo "WARNING: Production issuer not ready, proceeding anyway..." +kubectl wait --for=condition=Ready clusterissuer/letsencrypt-staging --timeout=60s || echo "WARNING: Staging issuer not ready, proceeding anyway..." + +sleep 5 + +###################################### +# Fix stuck certificates and cleanup +###################################### + +needs_restart=false + +echo "Checking for certificates with failed issuance attempts..." +stuck_certs=$(kubectl get certificates --all-namespaces -o json 2>/dev/null | \ + jq -r '.items[] | select(.status.conditions[]? | select(.type=="Issuing" and .status=="False" and (.message | contains("404")))) | "\(.metadata.namespace) \(.metadata.name)"') + +if [ -n "$stuck_certs" ]; then + echo "WARNING: Found certificates stuck with non-existent orders, recreating them..." + echo "$stuck_certs" | while read ns name; do + echo "Recreating certificate $ns/$name..." + cert_spec=$(kubectl get certificate "$name" -n "$ns" -o json | jq '.spec') + kubectl delete certificate "$name" -n "$ns" + echo "{\"apiVersion\":\"cert-manager.io/v1\",\"kind\":\"Certificate\",\"metadata\":{\"name\":\"$name\",\"namespace\":\"$ns\"},\"spec\":$cert_spec}" | kubectl apply -f - + done + needs_restart=true + sleep 5 +else + echo "No certificates stuck with failed orders" +fi + +echo "Checking for orphaned ACME orders..." +orphaned_orders=$(kubectl logs -n cert-manager deployment/cert-manager --tail=200 2>/dev/null | \ + grep -E "failed to retrieve the ACME order.*404" 2>/dev/null | \ + sed -n 's/.*resource_name="\([^"]*\)".*/\1/p' | \ + sort -u || true) + +if [ -n "$orphaned_orders" ]; then + echo "WARNING: Found orphaned ACME orders from logs" + for order in $orphaned_orders; do + echo "Deleting orphaned order: $order" + orders_found=$(kubectl get orders --all-namespaces 2>/dev/null | grep "$order" 2>/dev/null || true) + if [ -n "$orders_found" ]; then + echo "$orders_found" | while read ns name rest; do + kubectl delete order "$name" -n "$ns" 2>/dev/null || true + done + fi + done + needs_restart=true +else + echo "No orphaned orders found in logs" +fi + +echo "Checking for Cloudflare DNS cleanup errors..." +cloudflare_errors=$(kubectl logs -n cert-manager deployment/cert-manager --tail=200 2>/dev/null | \ + grep -c "Error: 7003.*Could not route" 2>/dev/null || echo "0") + +if [ "$cloudflare_errors" -gt "0" ]; then + echo "WARNING: Found $cloudflare_errors Cloudflare DNS cleanup errors (stale DNS record references)" + echo "Deleting stuck challenges and orders to allow fresh start" + + kubectl delete challenges --all -n cert-manager 2>/dev/null || true + kubectl delete orders --all -n cert-manager 2>/dev/null || true + + needs_restart=true +else + echo "No Cloudflare DNS cleanup errors" +fi + +if [ "$needs_restart" = true ]; then + echo "Restarting cert-manager to clear internal state..." + kubectl rollout restart deployment cert-manager -n cert-manager + kubectl rollout status deployment/cert-manager -n cert-manager --timeout=120s + echo "Waiting for cert-manager to recreate fresh challenges..." + sleep 15 +else + echo "No restart needed - cert-manager state is clean" +fi + +######################### +# Final checks +######################### + +echo "Waiting for wildcard certificates to be ready (this may take several minutes)..." + +wait_for_cert() { + local cert_name="$1" + local timeout=300 + local elapsed=0 + + echo " Checking $cert_name..." + + while [ $elapsed -lt $timeout ]; do + if kubectl get certificate "$cert_name" -n cert-manager -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | grep -q "True"; then + echo " $cert_name is ready" + return 0 + fi + + if [ $((elapsed % 30)) -eq 0 ] && [ $elapsed -gt 0 ]; then + local status=$(kubectl get certificate "$cert_name" -n cert-manager -o jsonpath='{.status.conditions[?(@.type=="Ready")].message}' 2>/dev/null || echo "Waiting...") + echo " Still waiting for $cert_name... ($elapsed/${timeout}s) - $status" + fi + + sleep 5 + elapsed=$((elapsed + 5)) + done + + echo " WARNING: Timeout waiting for $cert_name (will continue anyway)" + return 1 +} + +wait_for_cert "wildcard-internal-wild-cloud" +wait_for_cert "wildcard-wild-cloud" + +echo "Performing final cert-manager health check..." +failed_certs=$(kubectl get certificates --all-namespaces -o json 2>/dev/null | jq -r '.items[] | select(.status.conditions[]? | select(.type=="Ready" and .status!="True")) | "\(.metadata.namespace)/\(.metadata.name)"' | wc -l) +if [ "$failed_certs" -gt 0 ]; then + echo "WARNING: Found $failed_certs certificates not in Ready state" + echo "Check certificate status with: kubectl get certificates --all-namespaces" + echo "Check cert-manager logs with: kubectl logs -n cert-manager deployment/cert-manager" +else + echo "All certificates are in Ready state" +fi + +echo "" +echo "cert-manager setup complete!" +echo "" +echo "To verify the installation:" +echo " kubectl get certificates --all-namespaces" +echo " kubectl get clusterissuers" diff --git a/cert-manager/internal-wildcard-certificate.yaml b/cert-manager/internal-wildcard-certificate.yaml new file mode 100644 index 0000000..ba7854f --- /dev/null +++ b/cert-manager/internal-wildcard-certificate.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: wildcard-internal-wild-cloud + namespace: cert-manager +spec: + secretName: wildcard-internal-wild-cloud-tls + dnsNames: + - "*.{{ .internalDomain }}" + - "{{ .internalDomain }}" + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + duration: 2160h # 90 days + renewBefore: 360h # 15 days + privateKey: + algorithm: RSA + size: 2048 diff --git a/cert-manager/kustomization.yaml b/cert-manager/kustomization.yaml new file mode 100644 index 0000000..350b0a3 --- /dev/null +++ b/cert-manager/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- namespace.yaml +- letsencrypt-staging-dns01.yaml +- letsencrypt-prod-dns01.yaml +- internal-wildcard-certificate.yaml +- wildcard-certificate.yaml diff --git a/cert-manager/letsencrypt-prod-dns01.yaml b/cert-manager/letsencrypt-prod-dns01.yaml new file mode 100644 index 0000000..dfac188 --- /dev/null +++ b/cert-manager/letsencrypt-prod-dns01.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + email: {{ .email }} + privateKeySecretRef: + name: letsencrypt-prod + server: https://acme-v02.api.letsencrypt.org/directory + solvers: + # DNS-01 solver for wildcard certificates + - dns01: + cloudflare: + apiTokenSecretRef: + name: cloudflare-api-token + key: api-token + selector: + dnsZones: + - "{{ .cloudflareDomain }}" + # Keep the HTTP-01 solver for non-wildcard certificates + - http01: + ingress: + class: traefik diff --git a/cert-manager/letsencrypt-staging-dns01.yaml b/cert-manager/letsencrypt-staging-dns01.yaml new file mode 100644 index 0000000..8dc08a7 --- /dev/null +++ b/cert-manager/letsencrypt-staging-dns01.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + email: {{ .email }} + privateKeySecretRef: + name: letsencrypt-staging + server: https://acme-staging-v02.api.letsencrypt.org/directory + solvers: + # DNS-01 solver for wildcard certificates + - dns01: + cloudflare: + apiTokenSecretRef: + name: cloudflare-api-token + key: api-token + selector: + dnsZones: + - "{{ .cloudflareDomain }}" + # Keep the HTTP-01 solver for non-wildcard certificates + - http01: + ingress: + class: traefik diff --git a/cert-manager/manifest.yaml b/cert-manager/manifest.yaml new file mode 100644 index 0000000..b6e6285 --- /dev/null +++ b/cert-manager/manifest.yaml @@ -0,0 +1,15 @@ +name: cert-manager +is: cert-manager +description: X.509 certificate management for Kubernetes +version: v1.17.2 +namespace: cert-manager +category: infrastructure +requires: + - name: traefik +defaultConfig: + cloudDomain: "{{ .cloud.domain }}" + internalDomain: "{{ .cloud.internalDomain }}" + email: "{{ .operator.email }}" + cloudflareDomain: "{{ .cloud.baseDomain }}" +defaultSecrets: + - key: cloudflareToken diff --git a/cert-manager/namespace.yaml b/cert-manager/namespace.yaml new file mode 100644 index 0000000..c90416f --- /dev/null +++ b/cert-manager/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager diff --git a/cert-manager/wildcard-certificate.yaml b/cert-manager/wildcard-certificate.yaml new file mode 100644 index 0000000..734c517 --- /dev/null +++ b/cert-manager/wildcard-certificate.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: wildcard-wild-cloud + namespace: cert-manager +spec: + secretName: wildcard-wild-cloud-tls + dnsNames: + - "*.{{ .cloudDomain }}" + - "{{ .cloudDomain }}" + issuerRef: + name: letsencrypt-prod + kind: ClusterIssuer + duration: 2160h # 90 days + renewBefore: 360h # 15 days + privateKey: + algorithm: RSA + size: 2048 diff --git a/coredns/README.md b/coredns/README.md new file mode 100644 index 0000000..80116de --- /dev/null +++ b/coredns/README.md @@ -0,0 +1,45 @@ +# CoreDNS + +- https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ +- https://github.com/kubernetes/dns/blob/master/docs/specification.md +- https://coredns.io/ + +CoreDNS has the `kubernetes` plugin, so it returns all k8s service endpoints in well-known format. + +All services and pods are registered in CoreDNS. + +- ..svc.cluster.local +- . +- (if in the same namespace) + +- ..pod.cluster.local +- ...svc.cluster.local + +Any query for a resource in the `internal.$DOMAIN` domain will be given the IP of the Traefik proxy. We expose the CoreDNS server in the LAN via MetalLB just for this capability. + +## Default CoreDNS Configuration + +This is the default CoreDNS configuration, for reference: + +```txt +.:53 { + errors + health { lameduck 5s } + ready + log . { class error } + prometheus :9153 + kubernetes cluster.local in-addr.arpa ip6.arpa { + pods insecure + fallthrough in-addr.arpa ip6.arpa + ttl 30 + } + forward . /etc/resolv.conf { max_concurrent 1000 } + cache 30 { + disable success cluster.local + disable denial cluster.local + } + loop + reload + loadbalance +} +``` diff --git a/coredns/coredns-custom-config.yaml b/coredns/coredns-custom-config.yaml new file mode 100644 index 0000000..2649079 --- /dev/null +++ b/coredns/coredns-custom-config.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: coredns-custom + namespace: kube-system +data: + # Custom server block for internal domains. All internal domains should + # resolve to the cluster proxy. + internal.server: | + {{ .internalDomain }} { + errors + cache 30 + reload + template IN A { + match (.*)\.{{ .internalDomain | strings.ReplaceAll "." "\\." }}\. + answer "{{`{{ .Name }}`}} 60 IN A {{ .loadBalancerIp }}" + } + template IN AAAA { + match (.*)\.{{ .internalDomain | strings.ReplaceAll "." "\\." }}\. + rcode NXDOMAIN + } + } + # Custom override to set external resolvers. + external.override: | + forward . {{ .externalResolver }} { + max_concurrent 1000 + } diff --git a/coredns/install.sh b/coredns/install.sh new file mode 100755 index 0000000..d791b41 --- /dev/null +++ b/coredns/install.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +COREDNS_DIR="${INSTANCE_DIR}/apps/coredns" + +echo "=== Setting up CoreDNS ===" +echo "" + +echo "Using pre-compiled CoreDNS templates..." +if [ ! -f "${COREDNS_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${COREDNS_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Applying CoreDNS custom override configuration..." +kubectl apply -k "${COREDNS_DIR}/" + +echo "Restarting CoreDNS pods to apply changes..." +kubectl rollout restart deployment/coredns -n kube-system +echo "Waiting for CoreDNS rollout to complete..." +kubectl rollout status deployment/coredns -n kube-system + +echo "" +echo "CoreDNS configured successfully" +echo "" +echo "To verify the installation:" +echo " kubectl get pods -n kube-system -l k8s-app=kube-dns" +echo " kubectl get svc -n kube-system coredns" +echo " kubectl describe svc -n kube-system coredns" +echo "" +echo "To view CoreDNS logs:" +echo " kubectl logs -n kube-system -l k8s-app=kube-dns -f" diff --git a/coredns/kustomization.yaml b/coredns/kustomization.yaml new file mode 100644 index 0000000..adaf541 --- /dev/null +++ b/coredns/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - coredns-custom-config.yaml diff --git a/coredns/manifest.yaml b/coredns/manifest.yaml new file mode 100644 index 0000000..6fce55e --- /dev/null +++ b/coredns/manifest.yaml @@ -0,0 +1,12 @@ +name: coredns +is: coredns +description: DNS server for internal cluster DNS resolution +version: v1.12.0 +namespace: kube-system +category: infrastructure +requires: + - name: metallb +defaultConfig: + internalDomain: "{{ .cloud.internalDomain }}" + loadBalancerIp: "{{ .apps.metallb.loadBalancerIp }}" + externalResolver: "8.8.8.8" diff --git a/crowdsec/README.md b/crowdsec/README.md new file mode 100644 index 0000000..b819159 --- /dev/null +++ b/crowdsec/README.md @@ -0,0 +1,118 @@ +# CrowdSec Security Service + +CrowdSec is an open-source security engine that analyzes traffic patterns and blocks malicious actors. This service integrates CrowdSec with Traefik to provide automatic threat detection and rate limiting for all Wild Cloud ingresses. + +## Components + +- **CrowdSec Agent**: Analyzes traffic patterns, maintains decision lists, and connects to the CrowdSec threat intelligence network +- **Traefik Bouncer**: Integrates with Traefik via ForwardAuth to enforce CrowdSec decisions +- **Security Middlewares**: Traefik middleware for rate limiting and security headers + +## Default Protection + +After installation, **all ingresses are automatically protected** with: +- Threat detection (blocks known malicious IPs and attack patterns) +- Rate limiting (100 requests per minute per IP) +- Security headers (HSTS, XSS protection, content-type sniffing prevention) + +## Configuration + +Configuration is stored in `config.yaml` under `apps.crowdsec`: + +```yaml +apps: + crowdsec: + rateLimitAverage: "100" + rateLimitBurst: "100" +``` + +## Secrets + +Secrets are stored in `secrets.yaml` under `apps.crowdsec`: + +```yaml +apps: + crowdsec: + agentPassword: + bouncerApiKey: +``` + +## Opting Out + +To disable CrowdSec protection for a specific ingress (e.g., webhooks, health checks): + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + traefik.ingress.kubernetes.io/router.middlewares: "" +``` + +## Using Only Rate Limiting + +To use rate limiting without threat detection: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + traefik.ingress.kubernetes.io/router.middlewares: crowdsec-rate-limit@kubernetescrd +``` + +## Monitoring + +View active decisions (blocked IPs): +```bash +kubectl exec -n crowdsec deploy/crowdsec -- cscli decisions list +``` + +View registered bouncers: +```bash +kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers list +``` + +View alerts: +```bash +kubectl exec -n crowdsec deploy/crowdsec -- cscli alerts list +``` + +View metrics (Prometheus format): +```bash +kubectl port-forward -n crowdsec svc/crowdsec-lapi 6060:6060 +curl http://localhost:6060/metrics +``` + +## Threat Intelligence + +CrowdSec includes these detection collections: +- `crowdsecurity/traefik` - Traefik-specific detections +- `crowdsecurity/http-cve` - Known HTTP CVE exploits +- `crowdsecurity/whitelist-good-actors` - Whitelist for known good actors (search engines, etc.) + +Enabled scenarios: +- HTTP probing and path traversal detection +- Bad user agent detection +- Sensitive file access attempts +- HTTP crawling detection +- SSH brute force (if exposed) + +## Troubleshooting + +**Bouncer not connecting to agent:** +```bash +kubectl logs -n crowdsec deploy/traefik-crowdsec-bouncer +kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers list +``` + +**Check if middleware is applied:** +```bash +kubectl get middleware -n crowdsec +kubectl describe ingressroute -n +``` + +**View CrowdSec logs:** +```bash +kubectl logs -n crowdsec deploy/crowdsec +``` diff --git a/crowdsec/configmap.yaml b/crowdsec/configmap.yaml new file mode 100644 index 0000000..a31dad2 --- /dev/null +++ b/crowdsec/configmap.yaml @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: crowdsec-config + namespace: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud +data: + acquis.yaml: | + filenames: + - /var/log/containers/traefik-*_traefik_*.log + force_inotify: true + poll_without_inotify: true + labels: + type: containerd + program: traefik + profiles.yaml: | + name: default_ip_remediation + debug: false + filters: + - Alert.Remediation == true && Alert.GetScope() == "Ip" + decisions: + - type: ban + duration: 4h + on_success: break + --- + name: default_range_remediation + debug: false + filters: + - Alert.Remediation == true && Alert.GetScope() == "Range" + decisions: + - type: ban + duration: 4h + scope: Range + on_success: break + postoverflows.yaml: | + # Post-overflow configuration for crowdsec + name: "rdns" + debug: false + filter: "evt.Enriched.IsoCode != ''" + # Add reverse DNS enrichment diff --git a/crowdsec/crowdsec-deployment.yaml b/crowdsec/crowdsec-deployment.yaml new file mode 100644 index 0000000..5bb3ecf --- /dev/null +++ b/crowdsec/crowdsec-deployment.yaml @@ -0,0 +1,126 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: crowdsec + namespace: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud +spec: + replicas: 1 + selector: + matchLabels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud + template: + metadata: + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud + spec: + serviceAccountName: crowdsec + affinity: + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchLabels: + app: traefik + topologyKey: kubernetes.io/hostname + securityContext: + runAsUser: 0 + runAsNonRoot: false + fsGroup: 0 + seccompProfile: + type: RuntimeDefault + containers: + - name: crowdsec + image: crowdsecurity/crowdsec:v1.7.8 + env: + - name: COLLECTIONS + value: "crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/whitelist-good-actors crowdsecurity/iptables crowdsecurity/linux" + - name: PARSERS + value: "crowdsecurity/traefik-logs crowdsecurity/http-logs crowdsecurity/nginx-logs" + - name: SCENARIOS + value: "crowdsecurity/http-crawl-non_statics crowdsecurity/http-probing crowdsecurity/http-sensitive-files crowdsecurity/http-bad-user-agent crowdsecurity/http-path-traversal-probing crowdsecurity/ssh-bf crowdsecurity/ssh-slow-bf" + - name: POSTOVERFLOWS + value: "crowdsecurity/rdns crowdsecurity/cdn-whitelist" + - name: GID + value: "1000" + - name: LEVEL_TRACE + value: "false" + - name: LEVEL_DEBUG + value: "false" + - name: LEVEL_INFO + value: "true" + - name: AGENT_USERNAME + value: "kubernetes-cluster" + - name: AGENT_PASSWORD + valueFrom: + secretKeyRef: + name: crowdsec-agent-secret + key: password + ports: + - name: lapi + containerPort: 8080 + protocol: TCP + - name: prometheus + containerPort: 6060 + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + cpu: 500m + memory: 512Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsNonRoot: false + volumeMounts: + - name: crowdsec-config + mountPath: /etc/crowdsec/acquis.yaml + subPath: acquis.yaml + readOnly: true + - name: crowdsec-config + mountPath: /etc/crowdsec/profiles.yaml + subPath: profiles.yaml + readOnly: true + - name: crowdsec-data + mountPath: /var/lib/crowdsec/data + - name: crowdsec-config-dir + mountPath: /etc/crowdsec/config + - name: varlog + mountPath: /var/log + readOnly: true + volumes: + - name: crowdsec-config + configMap: + name: crowdsec-config + - name: crowdsec-data + persistentVolumeClaim: + claimName: crowdsec-data + - name: crowdsec-config-dir + emptyDir: {} + - name: varlog + hostPath: + path: /var/log diff --git a/crowdsec/crowdsec-service.yaml b/crowdsec/crowdsec-service.yaml new file mode 100644 index 0000000..02e2100 --- /dev/null +++ b/crowdsec/crowdsec-service.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Service +metadata: + name: crowdsec-lapi + namespace: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud +spec: + type: ClusterIP + selector: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud + ports: + - name: lapi + port: 8080 + targetPort: 8080 + protocol: TCP + - name: prometheus + port: 6060 + targetPort: 6060 + protocol: TCP diff --git a/crowdsec/install.sh b/crowdsec/install.sh new file mode 100755 index 0000000..4c4eebf --- /dev/null +++ b/crowdsec/install.sh @@ -0,0 +1,118 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +CROWDSEC_DIR="${INSTANCE_DIR}/apps/crowdsec" +SECRETS_FILE="${INSTANCE_DIR}/secrets.yaml" + +echo "=== Setting up CrowdSec Security Engine ===" +echo "" + +echo "Verifying Traefik is ready (required for CrowdSec bouncer)..." +kubectl wait --for=condition=Available deployment/traefik -n traefik --timeout=60s 2>/dev/null || { + echo "WARNING: Traefik not ready, but continuing with CrowdSec installation" + echo "Note: CrowdSec bouncer will not work until Traefik is available" +} + +echo "Using pre-compiled CrowdSec templates..." +if [ ! -f "${CROWDSEC_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${CROWDSEC_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Deploying CrowdSec..." +kubectl apply -k ${CROWDSEC_DIR}/ + +echo "Creating CrowdSec agent secret..." +AGENT_PASSWORD=$(yq '.apps.crowdsec.agentPassword' "$SECRETS_FILE" 2>/dev/null | tr -d '"') + +if [ -z "$AGENT_PASSWORD" ] || [ "$AGENT_PASSWORD" = "null" ]; then + echo "Generating new agent password..." + AGENT_PASSWORD=$(openssl rand -base64 32) + echo "WARNING: Agent password not found in secrets.yaml" + echo "Using generated password - you may want to persist this" +fi + +kubectl create secret generic crowdsec-agent-secret \ + --namespace crowdsec \ + --from-literal=password="${AGENT_PASSWORD}" \ + --dry-run=client -o yaml | kubectl apply -f - + +echo "Waiting for CrowdSec agent to be ready..." +kubectl rollout status deployment/crowdsec -n crowdsec --timeout=120s + +echo "Registering bouncer with CrowdSec agent..." +BOUNCER_API_KEY=$(yq '.apps.crowdsec.bouncerApiKey' "$SECRETS_FILE" 2>/dev/null | tr -d '"') + +if [ -z "$BOUNCER_API_KEY" ] || [ "$BOUNCER_API_KEY" = "null" ]; then + echo "Generating new bouncer API key from CrowdSec agent..." + kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers delete traefik-bouncer 2>/dev/null || true + BOUNCER_API_KEY=$(kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers add traefik-bouncer -o raw) + echo "Generated bouncer API key - you may want to persist this in secrets.yaml" +fi + +kubectl create secret generic crowdsec-bouncer-secret \ + --namespace crowdsec \ + --from-literal=api-key="${BOUNCER_API_KEY}" \ + --dry-run=client -o yaml | kubectl apply -f - + +echo "Copying bouncer secret to traefik namespace..." +kubectl create secret generic crowdsec-bouncer-secret \ + --namespace traefik \ + --from-literal=api-key="${BOUNCER_API_KEY}" \ + --dry-run=client -o yaml | kubectl apply -f - + +echo "Cleaning up old bouncer deployment..." +kubectl delete deployment traefik-crowdsec-bouncer -n crowdsec --ignore-not-found +kubectl delete service traefik-crowdsec-bouncer -n crowdsec --ignore-not-found + +echo "Restarting Traefik to load CrowdSec plugin..." +kubectl rollout restart deployment/traefik -n traefik +kubectl rollout status deployment/traefik -n traefik --timeout=120s + +echo "Configuring Traefik to use CrowdSec security chain by default..." +kubectl patch deployment traefik -n traefik --type='json' -p='[ + { + "op": "add", + "path": "/spec/template/spec/containers/0/args/-", + "value": "--entryPoints.websecure.http.middlewares=crowdsec-security-chain@kubernetescrd" + } +]' 2>/dev/null || { + echo "Note: Traefik may already have middleware configured or patch failed" + echo "You can manually configure default middleware if needed" +} + +echo "" +echo "CrowdSec installed successfully (using Traefik plugin)" +echo "" +echo "All ingresses are now protected by default with:" +echo " - Threat detection (CrowdSec Traefik plugin, stream mode)" +echo " - Rate limiting (100 req/min)" +echo " - Security headers (HSTS, XSS protection, etc.)" +echo "" +echo "To verify the installation:" +echo " kubectl get pods -n crowdsec" +echo " kubectl get pods -n traefik" +echo " kubectl exec -n crowdsec deploy/crowdsec -- cscli bouncers list" +echo " kubectl exec -n crowdsec deploy/crowdsec -- cscli decisions list" +echo "" +echo "To opt-out a specific ingress from CrowdSec protection:" +echo " Add annotation: traefik.ingress.kubernetes.io/router.middlewares: \"\"" +echo "" diff --git a/crowdsec/kustomization.yaml b/crowdsec/kustomization.yaml new file mode 100644 index 0000000..235425c --- /dev/null +++ b/crowdsec/kustomization.yaml @@ -0,0 +1,17 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: crowdsec +labels: + - includeSelectors: true + pairs: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud +resources: +- namespace.yaml +- serviceaccount.yaml +- configmap.yaml +- pvc.yaml +- crowdsec-deployment.yaml +- crowdsec-service.yaml +- middleware.yaml diff --git a/crowdsec/manifest.yaml b/crowdsec/manifest.yaml new file mode 100644 index 0000000..c1b7dd2 --- /dev/null +++ b/crowdsec/manifest.yaml @@ -0,0 +1,15 @@ +name: crowdsec +is: crowdsec +description: CrowdSec security engine with Traefik bouncer for threat detection and rate limiting +version: v1.7.8 +namespace: crowdsec +category: infrastructure +requires: + - name: longhorn + - name: traefik +defaultConfig: + rateLimitAverage: "100" + rateLimitBurst: "100" +defaultSecrets: + - key: agentPassword + - key: bouncerApiKey diff --git a/crowdsec/middleware.yaml b/crowdsec/middleware.yaml new file mode 100644 index 0000000..2bbc9d0 --- /dev/null +++ b/crowdsec/middleware.yaml @@ -0,0 +1,89 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: crowdsec-bouncer + namespace: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud +spec: + plugin: + bouncer: + crowdsecLapiScheme: http + crowdsecLapiHost: crowdsec-lapi.crowdsec.svc.cluster.local:8080 + crowdsecLapiKeyFile: /etc/traefik/crowdsec/api-key + crowdsecMode: stream + updateIntervalSeconds: 15 + defaultDecisionSeconds: 60 + crowdsecAppsecEnabled: false +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: rate-limit + namespace: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud +spec: + rateLimit: + average: {{ .rateLimitAverage }} + burst: {{ .rateLimitBurst }} + period: 1m +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: security-headers + namespace: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud +spec: + headers: + browserXssFilter: true + contentTypeNosniff: true + forceSTSHeader: true + frameDeny: true + sslRedirect: true + stsIncludeSubdomains: true + stsPreload: true + stsSeconds: 31536000 + addVaryHeader: true + accessControlAllowMethods: + - GET + - POST + - PUT + - DELETE + - OPTIONS + accessControlAllowOriginList: + - "*" + accessControlMaxAge: 100 + customRequestHeaders: + X-Forwarded-Proto: https + customResponseHeaders: + Server: "" + X-Robots-Tag: noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: security-chain + namespace: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud +spec: + chain: + middlewares: + - name: security-headers + namespace: crowdsec + - name: rate-limit + namespace: crowdsec + - name: crowdsec-bouncer + namespace: crowdsec diff --git a/crowdsec/namespace.yaml b/crowdsec/namespace.yaml new file mode 100644 index 0000000..6a2f796 --- /dev/null +++ b/crowdsec/namespace.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud + pod-security.kubernetes.io/enforce: privileged diff --git a/crowdsec/pvc.yaml b/crowdsec/pvc.yaml new file mode 100644 index 0000000..b9e0073 --- /dev/null +++ b/crowdsec/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: crowdsec-data +spec: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + volumeMode: Filesystem + resources: + requests: + storage: 512Mi diff --git a/crowdsec/serviceaccount.yaml b/crowdsec/serviceaccount.yaml new file mode 100644 index 0000000..f5606b4 --- /dev/null +++ b/crowdsec/serviceaccount.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: crowdsec + namespace: crowdsec + labels: + app: crowdsec + managedBy: kustomize + partOf: wild-cloud diff --git a/decidim/manifest.yaml b/decidim/manifest.yaml index 6b5178c..84bda15 100644 --- a/decidim/manifest.yaml +++ b/decidim/manifest.yaml @@ -8,6 +8,7 @@ requires: installed_as: postgres - name: redis installed_as: redis + - name: smtp defaultConfig: namespace: decidim externalDnsDomain: "{{ .cloud.domain }}" @@ -25,12 +26,12 @@ defaultConfig: tlsSecretName: wildcard-wild-cloud-tls smtp: enabled: true - host: "{{ .cloud.smtp.host }}" - port: "{{ .cloud.smtp.port }}" - user: "{{ .cloud.smtp.user }}" - from: "{{ .cloud.smtp.from }}" - tls: "{{ .cloud.smtp.tls }}" - startTls: "{{ .cloud.smtp.startTls }}" + host: "{{ .apps.smtp.host }}" + port: "{{ .apps.smtp.port }}" + user: "{{ .apps.smtp.user }}" + from: "{{ .apps.smtp.from }}" + tls: "{{ .apps.smtp.tls }}" + startTls: "{{ .apps.smtp.startTls }}" defaultSecrets: - key: systemAdminPassword - key: secretKeyBase diff --git a/discourse/manifest.yaml b/discourse/manifest.yaml index 65df970..b6d01c6 100644 --- a/discourse/manifest.yaml +++ b/discourse/manifest.yaml @@ -6,6 +6,7 @@ icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/discourse.svg requires: - name: postgres - name: redis + - name: smtp defaultConfig: namespace: discourse externalDnsDomain: "{{ .cloud.domain }}" @@ -24,12 +25,12 @@ defaultConfig: tlsSecretName: wildcard-wild-cloud-tls smtp: enabled: false - host: "{{ .cloud.smtp.host }}" - port: "{{ .cloud.smtp.port }}" - user: "{{ .cloud.smtp.user }}" - from: "{{ .cloud.smtp.from }}" - tls: "{{ .cloud.smtp.tls }}" - startTls: "{{ .cloud.smtp.startTls }}" + host: "{{ .apps.smtp.host }}" + port: "{{ .apps.smtp.port }}" + user: "{{ .apps.smtp.user }}" + from: "{{ .apps.smtp.from }}" + tls: "{{ .apps.smtp.tls }}" + startTls: "{{ .apps.smtp.startTls }}" defaultSecrets: - key: adminPassword - key: secretKeyBase diff --git a/docker-registry/deployment.yaml b/docker-registry/deployment.yaml new file mode 100644 index 0000000..7f375a4 --- /dev/null +++ b/docker-registry/deployment.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: docker-registry + labels: + app: docker-registry +spec: + replicas: 1 + selector: + matchLabels: + app: docker-registry + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app: docker-registry + spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containers: + - image: registry:3.0.0 + name: docker-registry + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + ports: + - containerPort: 5000 + protocol: TCP + volumeMounts: + - mountPath: /var/lib/registry + name: docker-registry-storage + readOnly: false + volumes: + - name: docker-registry-storage + persistentVolumeClaim: + claimName: docker-registry-pvc diff --git a/docker-registry/ingress.yaml b/docker-registry/ingress.yaml new file mode 100644 index 0000000..478f2a0 --- /dev/null +++ b/docker-registry/ingress.yaml @@ -0,0 +1,20 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: docker-registry +spec: + rules: + - host: {{ .host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: docker-registry + port: + number: 5000 + tls: + - hosts: + - {{ .host }} + secretName: wildcard-internal-wild-cloud-tls diff --git a/docker-registry/install.sh b/docker-registry/install.sh new file mode 100755 index 0000000..41757d5 --- /dev/null +++ b/docker-registry/install.sh @@ -0,0 +1,48 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +DOCKER_REGISTRY_DIR="${INSTANCE_DIR}/apps/docker-registry" + +echo "=== Setting up Docker Registry ===" +echo "" + +echo "Using pre-compiled Docker Registry templates..." +if [ ! -f "${DOCKER_REGISTRY_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${DOCKER_REGISTRY_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Deploying Docker Registry..." +kubectl apply -k "${DOCKER_REGISTRY_DIR}/" + +echo "Waiting for Docker Registry to be ready..." +kubectl wait --for=condition=available --timeout=300s deployment/docker-registry -n docker-registry + +echo "" +echo "Docker Registry installed successfully" +echo "" +echo "Deployment status:" +kubectl get pods -n docker-registry +kubectl get services -n docker-registry +echo "" +echo "To use the registry:" +echo " docker tag myimage registry.local/myimage" +echo " docker push registry.local/myimage" diff --git a/docker-registry/kustomization.yaml b/docker-registry/kustomization.yaml new file mode 100644 index 0000000..2271c5a --- /dev/null +++ b/docker-registry/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: docker-registry +labels: + - includeSelectors: true + pairs: + app: docker-registry + managedBy: wild-cloud +resources: + - deployment.yaml + - ingress.yaml + - service.yaml + - namespace.yaml + - pvc.yaml diff --git a/docker-registry/manifest.yaml b/docker-registry/manifest.yaml new file mode 100644 index 0000000..01eec7c --- /dev/null +++ b/docker-registry/manifest.yaml @@ -0,0 +1,12 @@ +name: docker-registry +is: docker-registry +description: Private Docker image registry for cluster +version: "3.0.0" +namespace: docker-registry +category: infrastructure +requires: + - name: traefik + - name: cert-manager +defaultConfig: + host: "registry.{{ .cloud.internalDomain }}" + storage: "100Gi" diff --git a/docker-registry/namespace.yaml b/docker-registry/namespace.yaml new file mode 100644 index 0000000..4cd3252 --- /dev/null +++ b/docker-registry/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: docker-registry diff --git a/docker-registry/pvc.yaml b/docker-registry/pvc.yaml new file mode 100644 index 0000000..7c57f3d --- /dev/null +++ b/docker-registry/pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: docker-registry-pvc +spec: + storageClassName: longhorn + accessModes: + - ReadWriteOnce + volumeMode: Filesystem + resources: + requests: + storage: {{ .storage }} diff --git a/docker-registry/service.yaml b/docker-registry/service.yaml new file mode 100644 index 0000000..b040967 --- /dev/null +++ b/docker-registry/service.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: docker-registry + labels: + app: docker-registry +spec: + ports: + - port: 5000 + targetPort: 5000 + selector: + app: docker-registry diff --git a/example-admin/manifest.yaml b/example-admin/manifest.yaml index 09c5988..ed7067f 100644 --- a/example-admin/manifest.yaml +++ b/example-admin/manifest.yaml @@ -1,6 +1,5 @@ name: example-admin is: example -install: true description: An example application that is deployed with internal-only access. version: 1.0.0 defaultConfig: diff --git a/example-app/manifest.yaml b/example-app/manifest.yaml index 76c4c6c..f5d566a 100644 --- a/example-app/manifest.yaml +++ b/example-app/manifest.yaml @@ -1,6 +1,5 @@ name: example-app is: example -install: true description: An example application that is deployed with public access. version: 1.0.0 defaultConfig: diff --git a/externaldns/README.md b/externaldns/README.md new file mode 100644 index 0000000..913edd9 --- /dev/null +++ b/externaldns/README.md @@ -0,0 +1,14 @@ +# External DNS + +See: https://github.com/kubernetes-sigs/external-dns + +ExternalDNS allows you to keep selected zones (via --domain-filter) synchronized with Ingresses and Services of type=LoadBalancer and nodes in various DNS providers. + +Currently, we are only configured to use CloudFlare. + +Docs: https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/cloudflare.md + +Any Ingress that has metatdata.annotions with +external-dns.alpha.kubernetes.io/hostname: `.${DOMAIN}` + +will have Cloudflare records created by External DNS. diff --git a/externaldns/externaldns-cloudflare.yaml b/externaldns/externaldns-cloudflare.yaml new file mode 100644 index 0000000..c3e3b0c --- /dev/null +++ b/externaldns/externaldns-cloudflare.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns + namespace: externaldns +spec: + selector: + matchLabels: + app: external-dns + strategy: + type: Recreate + template: + metadata: + labels: + app: external-dns + spec: + serviceAccountName: external-dns + containers: + - name: external-dns + image: registry.k8s.io/external-dns/external-dns:v0.13.4 + args: + - --source=service + - --source=ingress + - --txt-owner-id={{ .ownerId }} + - --provider=cloudflare + - --domain-filter=payne.io + #- --exclude-domains=internal.${DOMAIN} + - --cloudflare-dns-records-per-page=5000 + - --publish-internal-services + - --no-cloudflare-proxied + - --log-level=debug + env: + - name: CF_API_TOKEN + valueFrom: + secretKeyRef: + name: cloudflare-api-token + key: api-token diff --git a/externaldns/externaldns-rbac.yaml b/externaldns/externaldns-rbac.yaml new file mode 100644 index 0000000..9c558d1 --- /dev/null +++ b/externaldns/externaldns-rbac.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns + namespace: externaldns +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: external-dns +rules: + - apiGroups: [""] + resources: ["services", "endpoints", "pods"] + verbs: ["get", "watch", "list"] + - apiGroups: ["extensions", "networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "watch", "list"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["list"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: external-dns-viewer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-dns +subjects: + - kind: ServiceAccount + name: external-dns + namespace: externaldns diff --git a/externaldns/install.sh b/externaldns/install.sh new file mode 100755 index 0000000..3878204 --- /dev/null +++ b/externaldns/install.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +EXTERNALDNS_DIR="${INSTANCE_DIR}/apps/externaldns" + +echo "=== Setting up ExternalDNS ===" +echo "" + +echo "Verifying cert-manager is ready (required for ExternalDNS)..." +kubectl wait --for=condition=Available deployment/cert-manager -n cert-manager --timeout=60s 2>/dev/null && \ +kubectl wait --for=condition=Available deployment/cert-manager-webhook -n cert-manager --timeout=60s 2>/dev/null || { + echo "cert-manager not ready, but continuing with ExternalDNS installation" + echo "Note: ExternalDNS may not work properly without cert-manager" +} + +echo "Using pre-compiled ExternalDNS templates..." +if [ ! -f "${EXTERNALDNS_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${EXTERNALDNS_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Deploying ExternalDNS..." +kubectl apply -k ${EXTERNALDNS_DIR}/ + +echo "Creating Cloudflare API token secret..." +SECRETS_FILE="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}/secrets.yaml" +CLOUDFLARE_API_TOKEN=$(yq '.apps.externaldns.cert-manager\.cloudflareToken' "$SECRETS_FILE" 2>/dev/null | tr -d '"') + +if [ -z "$CLOUDFLARE_API_TOKEN" ] || [ "$CLOUDFLARE_API_TOKEN" = "null" ]; then + echo "ERROR: Cloudflare API token not found." + echo "Please ensure cert-manager has been added with a cloudflareToken secret." + exit 1 +fi +kubectl create secret generic cloudflare-api-token \ + --namespace externaldns \ + --from-literal=api-token="${CLOUDFLARE_API_TOKEN}" \ + --dry-run=client -o yaml | kubectl apply -f - + +echo "Waiting for Cloudflare ExternalDNS to be ready..." +kubectl rollout status deployment/external-dns -n externaldns --timeout=60s + +echo "" +echo "ExternalDNS installed successfully" +echo "" +echo "To verify the installation:" +echo " kubectl get pods -n externaldns" +echo " kubectl logs -n externaldns -l app=external-dns -f" +echo "" diff --git a/externaldns/kustomization.yaml b/externaldns/kustomization.yaml new file mode 100644 index 0000000..9b5c61b --- /dev/null +++ b/externaldns/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- namespace.yaml +- externaldns-rbac.yaml +- externaldns-cloudflare.yaml diff --git a/externaldns/manifest.yaml b/externaldns/manifest.yaml new file mode 100644 index 0000000..1b0da60 --- /dev/null +++ b/externaldns/manifest.yaml @@ -0,0 +1,15 @@ +name: externaldns +is: externaldns +description: Automatically configures DNS records for services +version: v0.13.4 +namespace: externaldns +deploymentName: external-dns +category: infrastructure +requires: + - name: cert-manager +defaultConfig: + ownerId: "wild-cloud-{{ .cluster.name }}" +defaultSecrets: + - key: cloudflareToken +requiredSecrets: + - cert-manager.cloudflareToken diff --git a/externaldns/namespace.yaml b/externaldns/namespace.yaml new file mode 100644 index 0000000..e9b0ed6 --- /dev/null +++ b/externaldns/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: externaldns diff --git a/ghost/manifest.yaml b/ghost/manifest.yaml index bcbe0bd..2d26cf2 100644 --- a/ghost/manifest.yaml +++ b/ghost/manifest.yaml @@ -6,6 +6,7 @@ version: 5.118.1 icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/ghost.png requires: - name: mysql +- name: smtp defaultConfig: namespace: ghost externalDnsDomain: '{{ .cloud.domain }}' @@ -23,10 +24,10 @@ defaultConfig: blogTitle: My Blog timezone: UTC smtp: - host: '{{ .cloud.smtp.host }}' - port: '{{ .cloud.smtp.port }}' - from: '{{ .cloud.smtp.from }}' - user: '{{ .cloud.smtp.user }}' + host: '{{ .apps.smtp.host }}' + port: '{{ .apps.smtp.port }}' + from: '{{ .apps.smtp.from }}' + user: '{{ .apps.smtp.user }}' defaultSecrets: - key: adminPassword - key: dbPassword diff --git a/gitea/manifest.yaml b/gitea/manifest.yaml index 27c4f8c..14bea71 100644 --- a/gitea/manifest.yaml +++ b/gitea/manifest.yaml @@ -5,6 +5,7 @@ version: 1.24.3 icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitea.svg requires: - name: postgres +- name: smtp defaultConfig: namespace: gitea externalDnsDomain: '{{ .cloud.domain }}' @@ -24,10 +25,10 @@ defaultConfig: timezone: UTC runMode: prod smtp: - host: '{{ .cloud.smtp.host }}' - port: '{{ .cloud.smtp.port }}' - user: '{{ .cloud.smtp.user }}' - from: '{{ .cloud.smtp.from }}' + host: '{{ .apps.smtp.host }}' + port: '{{ .apps.smtp.port }}' + user: '{{ .apps.smtp.user }}' + from: '{{ .apps.smtp.from }}' defaultSecrets: - key: adminPassword - key: dbPassword diff --git a/headlamp/deployment.yaml b/headlamp/deployment.yaml new file mode 100644 index 0000000..11038e5 --- /dev/null +++ b/headlamp/deployment.yaml @@ -0,0 +1,68 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: headlamp + namespace: headlamp +spec: + replicas: 1 + selector: + matchLabels: + app: headlamp + template: + metadata: + labels: + app: headlamp + spec: + serviceAccountName: headlamp-admin + securityContext: + runAsNonRoot: true + runAsUser: 100 + runAsGroup: 101 + seccompProfile: + type: RuntimeDefault + containers: + - name: headlamp + image: ghcr.io/headlamp-k8s/headlamp:v0.42.0 + args: + - "-in-cluster" + - "-plugins-dir=/headlamp/plugins" + - "-kubeconfig=/home/headlamp/.kube/config" + ports: + - containerPort: 4466 + name: http + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + readOnlyRootFilesystem: false + readinessProbe: + httpGet: + path: / + port: 4466 + initialDelaySeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: / + port: 4466 + initialDelaySeconds: 15 + timeoutSeconds: 5 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + memory: 256Mi + volumeMounts: + - name: kubeconfig + mountPath: /home/headlamp/.kube + readOnly: true + volumes: + - name: kubeconfig + configMap: + name: headlamp-kubeconfig + items: + - key: kubeconfig + path: config + nodeSelector: + kubernetes.io/os: linux diff --git a/headlamp/ingress.yaml b/headlamp/ingress.yaml new file mode 100644 index 0000000..9bd5b90 --- /dev/null +++ b/headlamp/ingress.yaml @@ -0,0 +1,64 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: internal-only + namespace: headlamp +spec: + ipWhiteList: + sourceRange: + - 127.0.0.1/32 + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: headlamp-redirect-scheme + namespace: headlamp +spec: + redirectScheme: + scheme: https + permanent: true + +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: headlamp-https + namespace: headlamp +spec: + entryPoints: + - websecure + routes: + - match: Host(`headlamp.{{ .internalDomain }}`) + kind: Rule + middlewares: + - name: internal-only + namespace: headlamp + services: + - name: headlamp + port: 80 + tls: + secretName: wildcard-internal-wild-cloud-tls + +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: headlamp-http + namespace: headlamp +spec: + entryPoints: + - web + routes: + - match: Host(`headlamp.{{ .internalDomain }}`) + kind: Rule + middlewares: + - name: headlamp-redirect-scheme + namespace: headlamp + services: + - name: headlamp + port: 80 diff --git a/headlamp/install.sh b/headlamp/install.sh new file mode 100755 index 0000000..7c523cf --- /dev/null +++ b/headlamp/install.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +HEADLAMP_DIR="${INSTANCE_DIR}/apps/headlamp" + +echo "=== Setting up Headlamp ===" +echo "" + +echo "Using pre-compiled Headlamp templates..." +if [ ! -f "${HEADLAMP_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${HEADLAMP_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Waiting for cert-manager certificates to be ready..." +kubectl wait --for=condition=Ready certificate wildcard-internal-wild-cloud -n cert-manager --timeout=300s || echo "Warning: Internal wildcard certificate not ready yet" + +NAMESPACE="headlamp" + +echo "Copying cert-manager secrets to headlamp namespace..." +kubectl create namespace ${NAMESPACE} --dry-run=client -o yaml | kubectl apply -f - + +if kubectl get secret wildcard-internal-wild-cloud-tls -n cert-manager >/dev/null 2>&1; then + kubectl get secret wildcard-internal-wild-cloud-tls -n cert-manager -o yaml | \ + sed "s/namespace: cert-manager/namespace: ${NAMESPACE}/" | \ + kubectl apply -f - +else + echo "Warning: wildcard-internal-wild-cloud-tls secret not yet available" +fi + +echo "Deploying Headlamp..." +kubectl apply -k "${HEADLAMP_DIR}/" + +echo "Waiting for Headlamp to be ready..." +kubectl rollout status deployment/headlamp -n ${NAMESPACE} --timeout=120s + +echo "" +echo "Headlamp installed successfully" +echo "" +if [ -n "${INTERNAL_DOMAIN}" ]; then + echo "Access Headlamp at: https://headlamp.${INTERNAL_DOMAIN}" +else + echo "Access Headlamp via the configured internal domain" +fi +echo "" diff --git a/headlamp/kubeconfig-cm.yaml b/headlamp/kubeconfig-cm.yaml new file mode 100644 index 0000000..589a7b0 --- /dev/null +++ b/headlamp/kubeconfig-cm.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: headlamp-kubeconfig + namespace: headlamp +data: + kubeconfig: | + apiVersion: v1 + kind: Config + clusters: + - cluster: + certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + server: https://kubernetes.default.svc + name: in-cluster + contexts: + - context: + cluster: in-cluster + user: headlamp-admin + name: in-cluster + current-context: in-cluster + users: + - name: headlamp-admin + user: + tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token diff --git a/headlamp/kustomization.yaml b/headlamp/kustomization.yaml new file mode 100644 index 0000000..af059cd --- /dev/null +++ b/headlamp/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: headlamp +labels: + - includeSelectors: true + pairs: + app: headlamp + managedBy: kustomize + partOf: wild-cloud +resources: +- namespace.yaml +- service-account.yaml +- kubeconfig-cm.yaml +- deployment.yaml +- service.yaml +- ingress.yaml diff --git a/headlamp/manifest.yaml b/headlamp/manifest.yaml new file mode 100644 index 0000000..956e2cf --- /dev/null +++ b/headlamp/manifest.yaml @@ -0,0 +1,11 @@ +name: headlamp +is: headlamp +description: Modern Kubernetes web UI (SIG UI) with in-cluster authentication +version: v0.42.0 +namespace: headlamp +category: infrastructure +requires: + - name: traefik + - name: cert-manager +defaultConfig: + internalDomain: "{{ .cloud.internalDomain }}" diff --git a/headlamp/namespace.yaml b/headlamp/namespace.yaml new file mode 100644 index 0000000..6f6c133 --- /dev/null +++ b/headlamp/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: headlamp diff --git a/headlamp/service-account.yaml b/headlamp/service-account.yaml new file mode 100644 index 0000000..3cfcfef --- /dev/null +++ b/headlamp/service-account.yaml @@ -0,0 +1,20 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: headlamp-admin + namespace: headlamp + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: headlamp-admin +subjects: + - kind: ServiceAccount + name: headlamp-admin + namespace: headlamp +roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io diff --git a/headlamp/service.yaml b/headlamp/service.yaml new file mode 100644 index 0000000..e4d0d83 --- /dev/null +++ b/headlamp/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: headlamp + namespace: headlamp +spec: + ports: + - port: 80 + targetPort: 4466 + selector: + app: headlamp diff --git a/immich/manifest.yaml b/immich/manifest.yaml index 7ddc959..8c7f64a 100644 --- a/immich/manifest.yaml +++ b/immich/manifest.yaml @@ -1,6 +1,5 @@ name: immich is: immich -install: true description: Immich is a self-hosted photo and video backup solution that allows you to store, manage, and share your media files securely. version: release diff --git a/keila/manifest.yaml b/keila/manifest.yaml index 6b8c834..36853d1 100644 --- a/keila/manifest.yaml +++ b/keila/manifest.yaml @@ -5,6 +5,7 @@ version: 0.17.1 icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/keila.svg requires: - name: postgres + - name: smtp defaultConfig: namespace: keila externalDnsDomain: "{{ .cloud.domain }}" @@ -20,12 +21,12 @@ defaultConfig: adminUser: admin@{{ .cloud.domain }} tlsSecretName: wildcard-wild-cloud-tls smtp: - host: "{{ .cloud.smtp.host }}" - port: "{{ .cloud.smtp.port }}" - from: "{{ .cloud.smtp.from }}" - user: "{{ .cloud.smtp.user }}" - tls: "{{ .cloud.smtp.tls }}" - startTls: "{{ .cloud.smtp.startTls }}" + host: "{{ .apps.smtp.host }}" + port: "{{ .apps.smtp.port }}" + from: "{{ .apps.smtp.from }}" + user: "{{ .apps.smtp.user }}" + tls: "{{ .apps.smtp.tls }}" + startTls: "{{ .apps.smtp.startTls }}" defaultSecrets: - key: secretKeyBase default: "{{ random.AlphaNum 64 }}" diff --git a/lemmy/manifest.yaml b/lemmy/manifest.yaml index f429429..befb8d4 100644 --- a/lemmy/manifest.yaml +++ b/lemmy/manifest.yaml @@ -5,6 +5,7 @@ version: 0.19.15 icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/lemmy.svg requires: - name: postgres + - name: smtp defaultConfig: namespace: lemmy backendImage: dessalines/lemmy:0.19.15 @@ -27,11 +28,11 @@ defaultConfig: dbHost: postgres.postgres.svc.cluster.local dbPort: 5432 smtp: - host: "{{ .cloud.smtp.host }}" - port: "{{ .cloud.smtp.port }}" - user: "{{ .cloud.smtp.user }}" + host: "{{ .apps.smtp.host }}" + port: "{{ .apps.smtp.port }}" + user: "{{ .apps.smtp.user }}" from: "noreply@{{ .cloud.baseDomain }}" - tls: "{{ .cloud.smtp.tls }}" + tls: "{{ .apps.smtp.tls }}" defaultSecrets: - key: dbPassword - key: adminPassword diff --git a/longhorn/README.md b/longhorn/README.md new file mode 100644 index 0000000..7b41e8b --- /dev/null +++ b/longhorn/README.md @@ -0,0 +1,20 @@ +# Longhorn Storage + +See: [Longhorn Docs v 1.8.1](https://longhorn.io/docs/1.8.1/deploy/install/install-with-kubectl/) + +## Installation Notes + +- Manifest copied from https://raw.githubusercontent.com/longhorn/longhorn/v1.8.1/deploy/longhorn.yaml +- Using kustomize to apply custom configuration (see `kustomization.yaml`) + +## Important Settings + +- **Number of Replicas**: Set to 1 (default is 3) to accommodate smaller clusters + - This avoids "degraded" volumes when fewer than 3 nodes are available + - For production with 3+ nodes, consider changing back to 3 for better availability + +## Common Operations + +- View volumes: `kubectl get volumes.longhorn.io -n longhorn-system` +- Check volume status: `kubectl describe volumes.longhorn.io -n longhorn-system` +- Access Longhorn UI: Set up port-forwarding with `kubectl -n longhorn-system port-forward service/longhorn-frontend 8080:80` diff --git a/longhorn/ingress.yaml b/longhorn/ingress.yaml new file mode 100644 index 0000000..23ca68a --- /dev/null +++ b/longhorn/ingress.yaml @@ -0,0 +1,21 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: longhorn-ingress + namespace: longhorn-system +spec: + rules: + - host: "longhorn.{{ .internalDomain }}" + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: longhorn-frontend + port: + number: 80 + tls: + - secretName: wildcard-internal-wild-cloud-tls + hosts: + - "longhorn.{{ .internalDomain }}" diff --git a/longhorn/install.sh b/longhorn/install.sh new file mode 100755 index 0000000..22b3b06 --- /dev/null +++ b/longhorn/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +LONGHORN_DIR="${INSTANCE_DIR}/apps/longhorn" + +echo "=== Setting up Longhorn ===" +echo "" + +echo "Using pre-compiled Longhorn templates..." +if [ ! -f "${LONGHORN_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${LONGHORN_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Deploying Longhorn..." +kubectl apply -k ${LONGHORN_DIR}/ + +echo "Waiting for Longhorn to be ready..." +kubectl wait --for=condition=available --timeout=300s deployment/longhorn-driver-deployer -n longhorn-system || true + +echo "" +echo "Longhorn installed successfully" +echo "" +echo "To verify the installation:" +echo " kubectl get pods -n longhorn-system" +echo " kubectl get storageclass" +echo "" +echo "To access the Longhorn UI:" +echo " kubectl port-forward -n longhorn-system svc/longhorn-frontend 8080:80" diff --git a/longhorn/kustomization.yaml b/longhorn/kustomization.yaml new file mode 100644 index 0000000..4e2b668 --- /dev/null +++ b/longhorn/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - longhorn.yaml + - ingress.yaml + - volumesnapshotclass-longhorn.yaml diff --git a/longhorn/longhorn.yaml b/longhorn/longhorn.yaml new file mode 100644 index 0000000..fabfd77 --- /dev/null +++ b/longhorn/longhorn.yaml @@ -0,0 +1,5191 @@ +--- +# Builtin: "helm template" does not respect --create-namespace +apiVersion: v1 +kind: Namespace +metadata: + name: longhorn-system + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged +--- +# Source: longhorn/templates/priorityclass.yaml +apiVersion: scheduling.k8s.io/v1 +kind: PriorityClass +metadata: + name: "longhorn-critical" + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +description: "Ensure Longhorn pods have the highest priority to prevent any unexpected eviction by the Kubernetes scheduler under node pressure" +globalDefault: false +preemptionPolicy: PreemptLowerPriority +value: 1000000000 +--- +# Source: longhorn/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: longhorn-service-account + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +--- +# Source: longhorn/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: longhorn-ui-service-account + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +--- +# Source: longhorn/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: longhorn-support-bundle + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +--- +# Source: longhorn/templates/default-resource.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: longhorn-default-resource + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +data: + default-resource.yaml: |- +--- +# Source: longhorn/templates/default-setting.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: longhorn-default-setting + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +data: + default-setting.yaml: |- + priority-class: longhorn-critical + disable-revision-counter: true + backup-target: {{ .backupTarget }} + backup-target-credential-secret: "" +--- +# Source: longhorn/templates/storageclass.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: longhorn-storageclass + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +data: + storageclass.yaml: | + kind: StorageClass + apiVersion: storage.k8s.io/v1 + metadata: + name: longhorn + annotations: + storageclass.kubernetes.io/is-default-class: "true" + provisioner: driver.longhorn.io + allowVolumeExpansion: true + reclaimPolicy: "Delete" + volumeBindingMode: Immediate + parameters: + numberOfReplicas: "3" + staleReplicaTimeout: "30" + fromBackup: "" + fsType: "ext4" + dataLocality: "disabled" + unmapMarkSnapChainRemoved: "ignored" + disableRevisionCounter: "true" + dataEngine: "v1" +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: backingimagedatasources.longhorn.io +spec: + group: longhorn.io + names: + kind: BackingImageDataSource + listKind: BackingImageDataSourceList + plural: backingimagedatasources + shortNames: + - lhbids + singular: backingimagedatasource + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The current state of the pod used to provision the backing image + file from source + jsonPath: .status.currentState + name: State + type: string + - description: The data source type + jsonPath: .spec.sourceType + name: SourceType + type: string + - description: The node the backing image file will be prepared on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk the backing image file will be prepared on + jsonPath: .spec.diskUUID + name: DiskUUID + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: BackingImageDataSource is where Longhorn stores backing image + data source object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The system generated UUID of the provisioned backing image file + jsonPath: .spec.uuid + name: UUID + type: string + - description: The current state of the pod used to provision the backing image + file from source + jsonPath: .status.currentState + name: State + type: string + - description: The data source type + jsonPath: .spec.sourceType + name: SourceType + type: string + - description: The backing image file size + jsonPath: .status.size + name: Size + type: string + - description: The node the backing image file will be prepared on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk the backing image file will be prepared on + jsonPath: .spec.diskUUID + name: DiskUUID + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: BackingImageDataSource is where Longhorn stores backing image + data source object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackingImageDataSourceSpec defines the desired state of the + Longhorn backing image data source + properties: + checksum: + type: string + diskPath: + type: string + diskUUID: + type: string + fileTransferred: + type: boolean + nodeID: + type: string + parameters: + additionalProperties: + type: string + type: object + sourceType: + enum: + - download + - upload + - export-from-volume + - restore + - clone + type: string + uuid: + type: string + type: object + status: + description: BackingImageDataSourceStatus defines the observed state of + the Longhorn backing image data source + properties: + checksum: + type: string + currentState: + type: string + ip: + type: string + message: + type: string + ownerID: + type: string + progress: + type: integer + runningParameters: + additionalProperties: + type: string + nullable: true + type: object + size: + format: int64 + type: integer + storageIP: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: backingimagemanagers.longhorn.io +spec: + group: longhorn.io + names: + kind: BackingImageManager + listKind: BackingImageManagerList + plural: backingimagemanagers + shortNames: + - lhbim + singular: backingimagemanager + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The current state of the manager + jsonPath: .status.currentState + name: State + type: string + - description: The image the manager pod will use + jsonPath: .spec.image + name: Image + type: string + - description: The node the manager is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk the manager is responsible for + jsonPath: .spec.diskUUID + name: DiskUUID + type: string + - description: The disk path the manager is using + jsonPath: .spec.diskPath + name: DiskPath + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: BackingImageManager is where Longhorn stores backing image manager + object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The current state of the manager + jsonPath: .status.currentState + name: State + type: string + - description: The image the manager pod will use + jsonPath: .spec.image + name: Image + type: string + - description: The node the manager is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk the manager is responsible for + jsonPath: .spec.diskUUID + name: DiskUUID + type: string + - description: The disk path the manager is using + jsonPath: .spec.diskPath + name: DiskPath + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: BackingImageManager is where Longhorn stores backing image manager + object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackingImageManagerSpec defines the desired state of the + Longhorn backing image manager + properties: + backingImages: + additionalProperties: + type: string + type: object + diskPath: + type: string + diskUUID: + type: string + image: + type: string + nodeID: + type: string + type: object + status: + description: BackingImageManagerStatus defines the observed state of the + Longhorn backing image manager + properties: + apiMinVersion: + type: integer + apiVersion: + type: integer + backingImageFileMap: + additionalProperties: + properties: + currentChecksum: + type: string + message: + type: string + name: + type: string + progress: + type: integer + realSize: + format: int64 + type: integer + senderManagerAddress: + type: string + sendingReference: + type: integer + size: + format: int64 + type: integer + state: + type: string + uuid: + type: string + virtualSize: + format: int64 + type: integer + type: object + nullable: true + type: object + currentState: + type: string + ip: + type: string + ownerID: + type: string + storageIP: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: backingimages.longhorn.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: BackingImage + listKind: BackingImageList + plural: backingimages + shortNames: + - lhbi + singular: backingimage + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The backing image name + jsonPath: .spec.image + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: BackingImage is where Longhorn stores backing image object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The system generated UUID + jsonPath: .status.uuid + name: UUID + type: string + - description: The source of the backing image file data + jsonPath: .spec.sourceType + name: SourceType + type: string + - description: The backing image file size in each disk + jsonPath: .status.size + name: Size + type: string + - description: The virtual size of the image (may be larger than file size) + jsonPath: .status.virtualSize + name: VirtualSize + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: BackingImage is where Longhorn stores backing image object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackingImageSpec defines the desired state of the Longhorn + backing image + properties: + checksum: + type: string + dataEngine: + default: v1 + enum: + - v1 + - v2 + type: string + diskFileSpecMap: + additionalProperties: + properties: + dataEngine: + enum: + - v1 + - v2 + type: string + evictionRequested: + type: boolean + type: object + type: object + diskSelector: + items: + type: string + type: array + disks: + additionalProperties: + type: string + description: Deprecated. We are now using DiskFileSpecMap to assign + different spec to the file on different disks. + type: object + minNumberOfCopies: + type: integer + nodeSelector: + items: + type: string + type: array + secret: + type: string + secretNamespace: + type: string + sourceParameters: + additionalProperties: + type: string + type: object + sourceType: + enum: + - download + - upload + - export-from-volume + - restore + - clone + type: string + type: object + status: + description: BackingImageStatus defines the observed state of the Longhorn + backing image status + properties: + checksum: + type: string + diskFileStatusMap: + additionalProperties: + properties: + dataEngine: + enum: + - v1 + - v2 + type: string + lastStateTransitionTime: + type: string + message: + type: string + progress: + type: integer + state: + type: string + type: object + nullable: true + type: object + diskLastRefAtMap: + additionalProperties: + type: string + nullable: true + type: object + ownerID: + type: string + realSize: + description: Real size of image in bytes, which may be smaller than + the size when the file is a sparse file. Will be zero until known + (e.g. while a backing image is uploading) + format: int64 + type: integer + size: + format: int64 + type: integer + uuid: + type: string + v2FirstCopyDisk: + type: string + v2FirstCopyStatus: + description: It is pending -> in-progress -> ready/failed + type: string + virtualSize: + description: Virtual size of image in bytes, which may be larger than + physical size. Will be zero until known (e.g. while a backing image + is uploading) + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: backupbackingimages.longhorn.io +spec: + group: longhorn.io + names: + kind: BackupBackingImage + listKind: BackupBackingImageList + plural: backupbackingimages + shortNames: + - lhbbi + singular: backupbackingimage + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The backing image name + jsonPath: .status.backingImage + name: BackingImage + type: string + - description: The backing image size + jsonPath: .status.size + name: Size + type: string + - description: The backing image backup upload finished time + jsonPath: .status.backupCreatedAt + name: BackupCreatedAt + type: string + - description: The backing image backup state + jsonPath: .status.state + name: State + type: string + - description: The last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: BackupBackingImage is where Longhorn stores backing image backup + object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackupBackingImageSpec defines the desired state of the Longhorn + backing image backup + properties: + backingImage: + description: |- + The backing image name. + type: string + backupTargetName: + description: The backup target name. + nullable: true + type: string + labels: + additionalProperties: + type: string + description: The labels of backing image backup. + type: object + syncRequestedAt: + description: The time to request run sync the remote backing image + backup. + format: date-time + nullable: true + type: string + userCreated: + description: |- + Is this CR created by user through API or UI. + type: boolean + required: + - backingImage + - userCreated + type: object + status: + description: BackupBackingImageStatus defines the observed state of the + Longhorn backing image backup + properties: + backingImage: + description: The backing image name. + type: string + backupCreatedAt: + description: The backing image backup upload finished time. + type: string + checksum: + description: The checksum of the backing image. + type: string + compressionMethod: + description: Compression method + type: string + error: + description: The error message when taking the backing image backup. + type: string + labels: + additionalProperties: + type: string + description: The labels of backing image backup. + nullable: true + type: object + lastSyncedAt: + description: The last time that the backing image backup was synced + with the remote backup target. + format: date-time + nullable: true + type: string + managerAddress: + description: The address of the backing image manager that runs backing + image backup. + type: string + messages: + additionalProperties: + type: string + description: The error messages when listing or inspecting backing + image backup. + nullable: true + type: object + ownerID: + description: The node ID on which the controller is responsible to + reconcile this CR. + type: string + progress: + description: The backing image backup progress. + type: integer + secret: + description: Record the secret if this backup backing image is encrypted + type: string + secretNamespace: + description: Record the secret namespace if this backup backing image + is encrypted + type: string + size: + description: The backing image size. + format: int64 + type: integer + state: + description: |- + The backing image backup creation state. + Can be "", "InProgress", "Completed", "Error", "Unknown". + type: string + url: + description: The backing image backup URL. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: backups.longhorn.io +spec: + group: longhorn.io + names: + kind: Backup + listKind: BackupList + plural: backups + shortNames: + - lhb + singular: backup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The snapshot name + jsonPath: .status.snapshotName + name: SnapshotName + type: string + - description: The snapshot size + jsonPath: .status.size + name: SnapshotSize + type: string + - description: The snapshot creation time + jsonPath: .status.snapshotCreatedAt + name: SnapshotCreatedAt + type: string + - description: The backup state + jsonPath: .status.state + name: State + type: string + - description: The backup last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Backup is where Longhorn stores backup object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The snapshot name + jsonPath: .status.snapshotName + name: SnapshotName + type: string + - description: The snapshot size + jsonPath: .status.size + name: SnapshotSize + type: string + - description: The snapshot creation time + jsonPath: .status.snapshotCreatedAt + name: SnapshotCreatedAt + type: string + - description: The backup target name + jsonPath: .status.backupTargetName + name: BackupTarget + type: string + - description: The backup state + jsonPath: .status.state + name: State + type: string + - description: The backup last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: Backup is where Longhorn stores backup object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackupSpec defines the desired state of the Longhorn backup + properties: + backupMode: + description: |- + The backup mode of this backup. + Can be "full" or "incremental" + enum: + - full + - incremental + - "" + type: string + labels: + additionalProperties: + type: string + description: The labels of snapshot backup. + type: object + snapshotName: + description: The snapshot name. + type: string + syncRequestedAt: + description: The time to request run sync the remote backup. + format: date-time + nullable: true + type: string + type: object + status: + description: BackupStatus defines the observed state of the Longhorn backup + properties: + backupCreatedAt: + description: The snapshot backup upload finished time. + type: string + backupTargetName: + description: The backup target name. + type: string + compressionMethod: + description: Compression method + type: string + error: + description: The error message when taking the snapshot backup. + type: string + labels: + additionalProperties: + type: string + description: The labels of snapshot backup. + nullable: true + type: object + lastSyncedAt: + description: The last time that the backup was synced with the remote + backup target. + format: date-time + nullable: true + type: string + messages: + additionalProperties: + type: string + description: The error messages when calling longhorn engine on listing + or inspecting backups. + nullable: true + type: object + newlyUploadDataSize: + description: Size in bytes of newly uploaded data + type: string + ownerID: + description: The node ID on which the controller is responsible to + reconcile this backup CR. + type: string + progress: + description: The snapshot backup progress. + type: integer + reUploadedDataSize: + description: Size in bytes of reuploaded data + type: string + replicaAddress: + description: The address of the replica that runs snapshot backup. + type: string + size: + description: The snapshot size. + type: string + snapshotCreatedAt: + description: The snapshot creation time. + type: string + snapshotName: + description: The snapshot name. + type: string + state: + description: |- + The backup creation state. + Can be "", "InProgress", "Completed", "Error", "Unknown". + type: string + url: + description: The snapshot backup URL. + type: string + volumeBackingImageName: + description: The volume's backing image name. + type: string + volumeCreated: + description: The volume creation time. + type: string + volumeName: + description: The volume name. + type: string + volumeSize: + description: The volume size. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: backuptargets.longhorn.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: BackupTarget + listKind: BackupTargetList + plural: backuptargets + shortNames: + - lhbt + singular: backuptarget + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The backup target URL + jsonPath: .spec.backupTargetURL + name: URL + type: string + - description: The backup target credential secret + jsonPath: .spec.credentialSecret + name: Credential + type: string + - description: The backup target poll interval + jsonPath: .spec.pollInterval + name: LastBackupAt + type: string + - description: Indicate whether the backup target is available or not + jsonPath: .status.available + name: Available + type: boolean + - description: The backup target last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: BackupTarget is where Longhorn stores backup target object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The backup target URL + jsonPath: .spec.backupTargetURL + name: URL + type: string + - description: The backup target credential secret + jsonPath: .spec.credentialSecret + name: Credential + type: string + - description: The backup target poll interval + jsonPath: .spec.pollInterval + name: LastBackupAt + type: string + - description: Indicate whether the backup target is available or not + jsonPath: .status.available + name: Available + type: boolean + - description: The backup target last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: BackupTarget is where Longhorn stores backup target object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackupTargetSpec defines the desired state of the Longhorn + backup target + properties: + backupTargetURL: + description: The backup target URL. + type: string + credentialSecret: + description: The backup target credential secret. + type: string + pollInterval: + description: The interval that the cluster needs to run sync with + the backup target. + type: string + syncRequestedAt: + description: The time to request run sync the remote backup target. + format: date-time + nullable: true + type: string + type: object + status: + description: BackupTargetStatus defines the observed state of the Longhorn + backup target + properties: + available: + description: Available indicates if the remote backup target is available + or not. + type: boolean + conditions: + description: Records the reason on why the backup target is unavailable. + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + lastSyncedAt: + description: The last time that the controller synced with the remote + backup target. + format: date-time + nullable: true + type: string + ownerID: + description: The node ID on which the controller is responsible to + reconcile this backup target CR. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: backupvolumes.longhorn.io +spec: + group: longhorn.io + names: + kind: BackupVolume + listKind: BackupVolumeList + plural: backupvolumes + shortNames: + - lhbv + singular: backupvolume + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The backup volume creation time + jsonPath: .status.createdAt + name: CreatedAt + type: string + - description: The backup volume last backup name + jsonPath: .status.lastBackupName + name: LastBackupName + type: string + - description: The backup volume last backup time + jsonPath: .status.lastBackupAt + name: LastBackupAt + type: string + - description: The backup volume last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: BackupVolume is where Longhorn stores backup volume object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The backup target name + jsonPath: .spec.backupTargetName + name: BackupTarget + type: string + - description: The backup volume creation time + jsonPath: .status.createdAt + name: CreatedAt + type: string + - description: The backup volume last backup name + jsonPath: .status.lastBackupName + name: LastBackupName + type: string + - description: The backup volume last backup time + jsonPath: .status.lastBackupAt + name: LastBackupAt + type: string + - description: The backup volume last synced time + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: BackupVolume is where Longhorn stores backup volume object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackupVolumeSpec defines the desired state of the Longhorn + backup volume + properties: + backupTargetName: + description: The backup target name that the backup volume was synced. + nullable: true + type: string + syncRequestedAt: + description: The time to request run sync the remote backup volume. + format: date-time + nullable: true + type: string + volumeName: + description: The volume name that the backup volume was used to backup. + type: string + type: object + status: + description: BackupVolumeStatus defines the observed state of the Longhorn + backup volume + properties: + backingImageChecksum: + description: the backing image checksum. + type: string + backingImageName: + description: The backing image name. + type: string + createdAt: + description: The backup volume creation time. + type: string + dataStored: + description: The backup volume block count. + type: string + labels: + additionalProperties: + type: string + description: The backup volume labels. + nullable: true + type: object + lastBackupAt: + description: The latest volume backup time. + type: string + lastBackupName: + description: The latest volume backup name. + type: string + lastModificationTime: + description: The backup volume config last modification time. + format: date-time + nullable: true + type: string + lastSyncedAt: + description: The last time that the backup volume was synced into + the cluster. + format: date-time + nullable: true + type: string + messages: + additionalProperties: + type: string + description: The error messages when call longhorn engine on list + or inspect backup volumes. + nullable: true + type: object + ownerID: + description: The node ID on which the controller is responsible to + reconcile this backup volume CR. + type: string + size: + description: The backup volume size. + type: string + storageClassName: + description: the storage class name of pv/pvc binding with the volume. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: engineimages.longhorn.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: EngineImage + listKind: EngineImageList + plural: engineimages + shortNames: + - lhei + singular: engineimage + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: State of the engine image + jsonPath: .status.state + name: State + type: string + - description: The Longhorn engine image + jsonPath: .spec.image + name: Image + type: string + - description: Number of resources using the engine image + jsonPath: .status.refCount + name: RefCount + type: integer + - description: The build date of the engine image + jsonPath: .status.buildDate + name: BuildDate + type: date + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: EngineImage is where Longhorn stores engine image object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Compatibility of the engine image + jsonPath: .status.incompatible + name: Incompatible + type: boolean + - description: State of the engine image + jsonPath: .status.state + name: State + type: string + - description: The Longhorn engine image + jsonPath: .spec.image + name: Image + type: string + - description: Number of resources using the engine image + jsonPath: .status.refCount + name: RefCount + type: integer + - description: The build date of the engine image + jsonPath: .status.buildDate + name: BuildDate + type: date + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: EngineImage is where Longhorn stores engine image object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: EngineImageSpec defines the desired state of the Longhorn + engine image + properties: + image: + minLength: 1 + type: string + required: + - image + type: object + status: + description: EngineImageStatus defines the observed state of the Longhorn + engine image + properties: + buildDate: + type: string + cliAPIMinVersion: + type: integer + cliAPIVersion: + type: integer + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + controllerAPIMinVersion: + type: integer + controllerAPIVersion: + type: integer + dataFormatMinVersion: + type: integer + dataFormatVersion: + type: integer + gitCommit: + type: string + incompatible: + type: boolean + noRefSince: + type: string + nodeDeploymentMap: + additionalProperties: + type: boolean + nullable: true + type: object + ownerID: + type: string + refCount: + type: integer + state: + type: string + version: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: engines.longhorn.io +spec: + group: longhorn.io + names: + kind: Engine + listKind: EngineList + plural: engines + shortNames: + - lhe + singular: engine + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The current state of the engine + jsonPath: .status.currentState + name: State + type: string + - description: The node that the engine is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The instance manager of the engine + jsonPath: .status.instanceManagerName + name: InstanceManager + type: string + - description: The current image of the engine + jsonPath: .status.currentImage + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Engine is where Longhorn stores engine object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The data engine of the engine + jsonPath: .spec.dataEngine + name: Data Engine + type: string + - description: The current state of the engine + jsonPath: .status.currentState + name: State + type: string + - description: The node that the engine is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The instance manager of the engine + jsonPath: .status.instanceManagerName + name: InstanceManager + type: string + - description: The current image of the engine + jsonPath: .status.currentImage + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Engine is where Longhorn stores engine object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: EngineSpec defines the desired state of the Longhorn engine + properties: + active: + type: boolean + backendStoreDriver: + description: Deprecated:Replaced by field `dataEngine`. + type: string + backupVolume: + type: string + dataEngine: + enum: + - v1 + - v2 + type: string + desireState: + type: string + disableFrontend: + type: boolean + engineImage: + description: 'Deprecated: Replaced by field `image`.' + type: string + frontend: + enum: + - blockdev + - iscsi + - nvmf + - "" + type: string + image: + type: string + logRequested: + type: boolean + nodeID: + type: string + replicaAddressMap: + additionalProperties: + type: string + type: object + requestedBackupRestore: + type: string + requestedDataSource: + type: string + revisionCounterDisabled: + type: boolean + salvageRequested: + type: boolean + snapshotMaxCount: + type: integer + snapshotMaxSize: + format: int64 + type: string + unmapMarkSnapChainRemovedEnabled: + type: boolean + upgradedReplicaAddressMap: + additionalProperties: + type: string + type: object + volumeName: + type: string + volumeSize: + format: int64 + type: string + type: object + status: + description: EngineStatus defines the observed state of the Longhorn engine + properties: + backupStatus: + additionalProperties: + properties: + backupURL: + type: string + error: + type: string + progress: + type: integer + replicaAddress: + type: string + snapshotName: + type: string + state: + type: string + type: object + nullable: true + type: object + cloneStatus: + additionalProperties: + properties: + error: + type: string + fromReplicaAddress: + type: string + isCloning: + type: boolean + progress: + type: integer + snapshotName: + type: string + state: + type: string + type: object + nullable: true + type: object + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + currentImage: + type: string + currentReplicaAddressMap: + additionalProperties: + type: string + nullable: true + type: object + currentSize: + format: int64 + type: string + currentState: + type: string + endpoint: + type: string + instanceManagerName: + type: string + ip: + type: string + isExpanding: + type: boolean + lastExpansionError: + type: string + lastExpansionFailedAt: + type: string + lastRestoredBackup: + type: string + logFetched: + type: boolean + ownerID: + type: string + port: + type: integer + purgeStatus: + additionalProperties: + properties: + error: + type: string + isPurging: + type: boolean + progress: + type: integer + state: + type: string + type: object + nullable: true + type: object + rebuildStatus: + additionalProperties: + properties: + error: + type: string + fromReplicaAddress: + type: string + isRebuilding: + type: boolean + progress: + type: integer + state: + type: string + type: object + nullable: true + type: object + replicaModeMap: + additionalProperties: + type: string + nullable: true + type: object + replicaTransitionTimeMap: + additionalProperties: + type: string + description: |- + ReplicaTransitionTimeMap records the time a replica in ReplicaModeMap transitions from one mode to another (or + from not being in the ReplicaModeMap to being in it). This information is sometimes required by other controllers + (e.g. the volume controller uses it to determine the correct value for replica.Spec.lastHealthyAt). + type: object + restoreStatus: + additionalProperties: + properties: + backupURL: + type: string + currentRestoringBackup: + type: string + error: + type: string + filename: + type: string + isRestoring: + type: boolean + lastRestored: + type: string + progress: + type: integer + state: + type: string + type: object + nullable: true + type: object + salvageExecuted: + type: boolean + snapshotMaxCount: + type: integer + snapshotMaxSize: + format: int64 + type: string + snapshots: + additionalProperties: + properties: + children: + additionalProperties: + type: boolean + nullable: true + type: object + created: + type: string + labels: + additionalProperties: + type: string + nullable: true + type: object + name: + type: string + parent: + type: string + removed: + type: boolean + size: + type: string + usercreated: + type: boolean + type: object + nullable: true + type: object + snapshotsError: + type: string + started: + type: boolean + storageIP: + type: string + unmapMarkSnapChainRemovedEnabled: + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: instancemanagers.longhorn.io +spec: + group: longhorn.io + names: + kind: InstanceManager + listKind: InstanceManagerList + plural: instancemanagers + shortNames: + - lhim + singular: instancemanager + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the instance manager + jsonPath: .status.currentState + name: State + type: string + - description: The type of the instance manager (engine or replica) + jsonPath: .spec.type + name: Type + type: string + - description: The node that the instance manager is running on + jsonPath: .spec.nodeID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: InstanceManager is where Longhorn stores instance manager object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The data engine of the instance manager + jsonPath: .spec.dataEngine + name: Data Engine + type: string + - description: The state of the instance manager + jsonPath: .status.currentState + name: State + type: string + - description: The type of the instance manager (engine or replica) + jsonPath: .spec.type + name: Type + type: string + - description: The node that the instance manager is running on + jsonPath: .spec.nodeID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: InstanceManager is where Longhorn stores instance manager object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: InstanceManagerSpec defines the desired state of the Longhorn + instance manager + properties: + dataEngine: + type: string + dataEngineSpec: + properties: + v2: + properties: + cpuMask: + type: string + type: object + type: object + image: + type: string + nodeID: + type: string + type: + enum: + - aio + - engine + - replica + type: string + type: object + status: + description: InstanceManagerStatus defines the observed state of the Longhorn + instance manager + properties: + apiMinVersion: + type: integer + apiVersion: + type: integer + backingImages: + additionalProperties: + properties: + currentChecksum: + type: string + diskUUID: + type: string + message: + type: string + name: + type: string + progress: + type: integer + size: + format: int64 + type: integer + state: + type: string + uuid: + type: string + type: object + nullable: true + type: object + currentState: + type: string + dataEngineStatus: + properties: + v2: + properties: + cpuMask: + type: string + type: object + type: object + instanceEngines: + additionalProperties: + properties: + spec: + properties: + backendStoreDriver: + description: Deprecated:Replaced by field `dataEngine`. + type: string + dataEngine: + type: string + name: + type: string + type: object + status: + properties: + conditions: + additionalProperties: + type: boolean + nullable: true + type: object + endpoint: + type: string + errorMsg: + type: string + listen: + type: string + portEnd: + format: int32 + type: integer + portStart: + format: int32 + type: integer + resourceVersion: + format: int64 + type: integer + state: + type: string + targetPortEnd: + format: int32 + type: integer + targetPortStart: + format: int32 + type: integer + type: + type: string + type: object + type: object + nullable: true + type: object + instanceReplicas: + additionalProperties: + properties: + spec: + properties: + backendStoreDriver: + description: Deprecated:Replaced by field `dataEngine`. + type: string + dataEngine: + type: string + name: + type: string + type: object + status: + properties: + conditions: + additionalProperties: + type: boolean + nullable: true + type: object + endpoint: + type: string + errorMsg: + type: string + listen: + type: string + portEnd: + format: int32 + type: integer + portStart: + format: int32 + type: integer + resourceVersion: + format: int64 + type: integer + state: + type: string + targetPortEnd: + format: int32 + type: integer + targetPortStart: + format: int32 + type: integer + type: + type: string + type: object + type: object + nullable: true + type: object + instances: + additionalProperties: + properties: + spec: + properties: + backendStoreDriver: + description: Deprecated:Replaced by field `dataEngine`. + type: string + dataEngine: + type: string + name: + type: string + type: object + status: + properties: + conditions: + additionalProperties: + type: boolean + nullable: true + type: object + endpoint: + type: string + errorMsg: + type: string + listen: + type: string + portEnd: + format: int32 + type: integer + portStart: + format: int32 + type: integer + resourceVersion: + format: int64 + type: integer + state: + type: string + targetPortEnd: + format: int32 + type: integer + targetPortStart: + format: int32 + type: integer + type: + type: string + type: object + type: object + description: 'Deprecated: Replaced by InstanceEngines and InstanceReplicas' + nullable: true + type: object + ip: + type: string + ownerID: + type: string + proxyApiMinVersion: + type: integer + proxyApiVersion: + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: nodes.longhorn.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: Node + listKind: NodeList + plural: nodes + shortNames: + - lhn + singular: node + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Indicate whether the node is ready + jsonPath: .status.conditions['Ready']['status'] + name: Ready + type: string + - description: Indicate whether the user disabled/enabled replica scheduling for + the node + jsonPath: .spec.allowScheduling + name: AllowScheduling + type: boolean + - description: Indicate whether Longhorn can schedule replicas on the node + jsonPath: .status.conditions['Schedulable']['status'] + name: Schedulable + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Node is where Longhorn stores Longhorn node object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Indicate whether the node is ready + jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - description: Indicate whether the user disabled/enabled replica scheduling for + the node + jsonPath: .spec.allowScheduling + name: AllowScheduling + type: boolean + - description: Indicate whether Longhorn can schedule replicas on the node + jsonPath: .status.conditions[?(@.type=='Schedulable')].status + name: Schedulable + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Node is where Longhorn stores Longhorn node object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NodeSpec defines the desired state of the Longhorn node + properties: + allowScheduling: + type: boolean + disks: + additionalProperties: + properties: + allowScheduling: + type: boolean + diskDriver: + enum: + - "" + - auto + - aio + type: string + diskType: + enum: + - filesystem + - block + type: string + evictionRequested: + type: boolean + path: + type: string + storageReserved: + format: int64 + type: integer + tags: + items: + type: string + type: array + type: object + type: object + evictionRequested: + type: boolean + instanceManagerCPURequest: + type: integer + name: + type: string + tags: + items: + type: string + type: array + type: object + status: + description: NodeStatus defines the observed state of the Longhorn node + properties: + autoEvicting: + type: boolean + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + diskStatus: + additionalProperties: + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from + one status to another. + type: string + message: + description: Human-readable message indicating details + about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the + condition's last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + diskDriver: + type: string + diskName: + type: string + diskPath: + type: string + diskType: + type: string + diskUUID: + type: string + filesystemType: + type: string + instanceManagerName: + type: string + scheduledBackingImage: + additionalProperties: + format: int64 + type: integer + nullable: true + type: object + scheduledReplica: + additionalProperties: + format: int64 + type: integer + nullable: true + type: object + storageAvailable: + format: int64 + type: integer + storageMaximum: + format: int64 + type: integer + storageScheduled: + format: int64 + type: integer + type: object + nullable: true + type: object + region: + type: string + snapshotCheckStatus: + properties: + lastPeriodicCheckedAt: + format: date-time + type: string + type: object + zone: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: orphans.longhorn.io +spec: + group: longhorn.io + names: + kind: Orphan + listKind: OrphanList + plural: orphans + shortNames: + - lho + singular: orphan + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The type of the orphan + jsonPath: .spec.orphanType + name: Type + type: string + - description: The node that the orphan is on + jsonPath: .spec.nodeID + name: Node + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: Orphan is where Longhorn stores orphan object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: OrphanSpec defines the desired state of the Longhorn orphaned + data + properties: + nodeID: + description: The node ID on which the controller is responsible to + reconcile this orphan CR. + type: string + orphanType: + description: |- + The type of the orphaned data. + Can be "replica". + type: string + parameters: + additionalProperties: + type: string + description: The parameters of the orphaned data + type: object + type: object + status: + description: OrphanStatus defines the observed state of the Longhorn orphaned + data + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + ownerID: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: recurringjobs.longhorn.io +spec: + group: longhorn.io + names: + kind: RecurringJob + listKind: RecurringJobList + plural: recurringjobs + shortNames: + - lhrj + singular: recurringjob + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Sets groupings to the jobs. When set to "default" group will be + added to the volume label when no other job label exist in volume + jsonPath: .spec.groups + name: Groups + type: string + - description: Should be one of "backup" or "snapshot" + jsonPath: .spec.task + name: Task + type: string + - description: The cron expression represents recurring job scheduling + jsonPath: .spec.cron + name: Cron + type: string + - description: The number of snapshots/backups to keep for the volume + jsonPath: .spec.retain + name: Retain + type: integer + - description: The concurrent job to run by each cron job + jsonPath: .spec.concurrency + name: Concurrency + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Specify the labels + jsonPath: .spec.labels + name: Labels + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: RecurringJob is where Longhorn stores recurring job object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Sets groupings to the jobs. When set to "default" group will be + added to the volume label when no other job label exist in volume + jsonPath: .spec.groups + name: Groups + type: string + - description: Should be one of "snapshot", "snapshot-force-create", "snapshot-cleanup", + "snapshot-delete", "backup", "backup-force-create" or "filesystem-trim" + jsonPath: .spec.task + name: Task + type: string + - description: The cron expression represents recurring job scheduling + jsonPath: .spec.cron + name: Cron + type: string + - description: The number of snapshots/backups to keep for the volume + jsonPath: .spec.retain + name: Retain + type: integer + - description: The concurrent job to run by each cron job + jsonPath: .spec.concurrency + name: Concurrency + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - description: Specify the labels + jsonPath: .spec.labels + name: Labels + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: RecurringJob is where Longhorn stores recurring job object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: RecurringJobSpec defines the desired state of the Longhorn + recurring job + properties: + concurrency: + description: The concurrency of taking the snapshot/backup. + type: integer + cron: + description: The cron setting. + type: string + groups: + description: The recurring job group. + items: + type: string + type: array + labels: + additionalProperties: + type: string + description: The label of the snapshot/backup. + type: object + name: + description: The recurring job name. + type: string + parameters: + additionalProperties: + type: string + description: |- + The parameters of the snapshot/backup. + Support parameters: "full-backup-interval". + type: object + retain: + description: The retain count of the snapshot/backup. + type: integer + task: + description: |- + The recurring job task. + Can be "snapshot", "snapshot-force-create", "snapshot-cleanup", "snapshot-delete", "backup", "backup-force-create" or "filesystem-trim" + enum: + - snapshot + - snapshot-force-create + - snapshot-cleanup + - snapshot-delete + - backup + - backup-force-create + - filesystem-trim + type: string + type: object + status: + description: RecurringJobStatus defines the observed state of the Longhorn + recurring job + properties: + executionCount: + description: The number of jobs that have been triggered. + type: integer + ownerID: + description: The owner ID which is responsible to reconcile this recurring + job CR. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: replicas.longhorn.io +spec: + group: longhorn.io + names: + kind: Replica + listKind: ReplicaList + plural: replicas + shortNames: + - lhr + singular: replica + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The current state of the replica + jsonPath: .status.currentState + name: State + type: string + - description: The node that the replica is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk that the replica is on + jsonPath: .spec.diskID + name: Disk + type: string + - description: The instance manager of the replica + jsonPath: .status.instanceManagerName + name: InstanceManager + type: string + - description: The current image of the replica + jsonPath: .status.currentImage + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Replica is where Longhorn stores replica object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The data engine of the replica + jsonPath: .spec.dataEngine + name: Data Engine + type: string + - description: The current state of the replica + jsonPath: .status.currentState + name: State + type: string + - description: The node that the replica is on + jsonPath: .spec.nodeID + name: Node + type: string + - description: The disk that the replica is on + jsonPath: .spec.diskID + name: Disk + type: string + - description: The instance manager of the replica + jsonPath: .status.instanceManagerName + name: InstanceManager + type: string + - description: The current image of the replica + jsonPath: .status.currentImage + name: Image + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Replica is where Longhorn stores replica object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ReplicaSpec defines the desired state of the Longhorn replica + properties: + active: + type: boolean + backendStoreDriver: + description: Deprecated:Replaced by field `dataEngine`. + type: string + backingImage: + type: string + dataDirectoryName: + type: string + dataEngine: + enum: + - v1 + - v2 + type: string + desireState: + type: string + diskID: + type: string + diskPath: + type: string + engineImage: + description: 'Deprecated: Replaced by field `image`.' + type: string + engineName: + type: string + evictionRequested: + type: boolean + failedAt: + description: |- + FailedAt is set when a running replica fails or when a running engine is unable to use a replica for any reason. + FailedAt indicates the time the failure occurred. When FailedAt is set, a replica is likely to have useful + (though possibly stale) data. A replica with FailedAt set must be rebuilt from a non-failed replica (or it can + be used in a salvage if all replicas are failed). FailedAt is cleared before a rebuild or salvage. FailedAt may + be later than the corresponding entry in an engine's replicaTransitionTimeMap because it is set when the volume + controller acknowledges the change. + type: string + hardNodeAffinity: + type: string + healthyAt: + description: |- + HealthyAt is set the first time a replica becomes read/write in an engine after creation or rebuild. HealthyAt + indicates the time the last successful rebuild occurred. When HealthyAt is set, a replica is likely to have + useful (though possibly stale) data. HealthyAt is cleared before a rebuild. HealthyAt may be later than the + corresponding entry in an engine's replicaTransitionTimeMap because it is set when the volume controller + acknowledges the change. + type: string + image: + type: string + lastFailedAt: + description: |- + LastFailedAt is always set at the same time as FailedAt. Unlike FailedAt, LastFailedAt is never cleared. + LastFailedAt is not a reliable indicator of the state of a replica's data. For example, a replica with + LastFailedAt may already be healthy and in use again. However, because it is never cleared, it can be compared to + LastHealthyAt to help prevent dangerous replica deletion in some corner cases. LastFailedAt may be later than the + corresponding entry in an engine's replicaTransitionTimeMap because it is set when the volume controller + acknowledges the change. + type: string + lastHealthyAt: + description: |- + LastHealthyAt is set every time a replica becomes read/write in an engine. Unlike HealthyAt, LastHealthyAt is + never cleared. LastHealthyAt is not a reliable indicator of the state of a replica's data. For example, a + replica with LastHealthyAt set may be in the middle of a rebuild. However, because it is never cleared, it can be + compared to LastFailedAt to help prevent dangerous replica deletion in some corner cases. LastHealthyAt may be + later than the corresponding entry in an engine's replicaTransitionTimeMap because it is set when the volume + controller acknowledges the change. + type: string + logRequested: + type: boolean + migrationEngineName: + description: |- + MigrationEngineName is indicating the migrating engine which current connected to this replica. This is only + used for live migration of v2 data engine + type: string + nodeID: + type: string + rebuildRetryCount: + type: integer + revisionCounterDisabled: + type: boolean + salvageRequested: + type: boolean + snapshotMaxCount: + type: integer + snapshotMaxSize: + format: int64 + type: string + unmapMarkDiskChainRemovedEnabled: + type: boolean + volumeName: + type: string + volumeSize: + format: int64 + type: string + type: object + status: + description: ReplicaStatus defines the observed state of the Longhorn + replica + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + currentImage: + type: string + currentState: + type: string + evictionRequested: + description: 'Deprecated: Replaced by field `spec.evictionRequested`.' + type: boolean + instanceManagerName: + type: string + ip: + type: string + logFetched: + type: boolean + ownerID: + type: string + port: + type: integer + salvageExecuted: + type: boolean + started: + type: boolean + storageIP: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: settings.longhorn.io +spec: + group: longhorn.io + names: + kind: Setting + listKind: SettingList + plural: settings + shortNames: + - lhs + singular: setting + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The value of the setting + jsonPath: .value + name: Value + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Setting is where Longhorn stores setting object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + value: + type: string + required: + - value + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The value of the setting + jsonPath: .value + name: Value + type: string + - description: The setting is applied + jsonPath: .status.applied + name: Applied + type: boolean + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Setting is where Longhorn stores setting object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + status: + description: The status of the setting. + properties: + applied: + description: The setting is applied. + type: boolean + required: + - applied + type: object + value: + description: The value of the setting. + type: string + required: + - value + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: sharemanagers.longhorn.io +spec: + group: longhorn.io + names: + kind: ShareManager + listKind: ShareManagerList + plural: sharemanagers + shortNames: + - lhsm + singular: sharemanager + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the share manager + jsonPath: .status.state + name: State + type: string + - description: The node that the share manager is owned by + jsonPath: .status.ownerID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: ShareManager is where Longhorn stores share manager object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The state of the share manager + jsonPath: .status.state + name: State + type: string + - description: The node that the share manager is owned by + jsonPath: .status.ownerID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: ShareManager is where Longhorn stores share manager object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ShareManagerSpec defines the desired state of the Longhorn + share manager + properties: + image: + description: Share manager image used for creating a share manager + pod + type: string + type: object + status: + description: ShareManagerStatus defines the observed state of the Longhorn + share manager + properties: + endpoint: + description: NFS endpoint that can access the mounted filesystem of + the volume + type: string + ownerID: + description: The node ID on which the controller is responsible to + reconcile this share manager resource + type: string + state: + description: The state of the share manager resource + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: snapshots.longhorn.io +spec: + group: longhorn.io + names: + kind: Snapshot + listKind: SnapshotList + plural: snapshots + shortNames: + - lhsnap + singular: snapshot + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The volume that this snapshot belongs to + jsonPath: .spec.volume + name: Volume + type: string + - description: Timestamp when the point-in-time snapshot was taken + jsonPath: .status.creationTime + name: CreationTime + type: string + - description: Indicates if the snapshot is ready to be used to restore/backup + a volume + jsonPath: .status.readyToUse + name: ReadyToUse + type: boolean + - description: Represents the minimum size of volume required to rehydrate from + this snapshot + jsonPath: .status.restoreSize + name: RestoreSize + type: string + - description: The actual size of the snapshot + jsonPath: .status.size + name: Size + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Snapshot is the Schema for the snapshots API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: SnapshotSpec defines the desired state of Longhorn Snapshot + properties: + createSnapshot: + description: require creating a new snapshot + type: boolean + labels: + additionalProperties: + type: string + description: The labels of snapshot + nullable: true + type: object + volume: + description: |- + the volume that this snapshot belongs to. + This field is immutable after creation. + type: string + required: + - volume + type: object + status: + description: SnapshotStatus defines the observed state of Longhorn Snapshot + properties: + checksum: + type: string + children: + additionalProperties: + type: boolean + nullable: true + type: object + creationTime: + type: string + error: + type: string + labels: + additionalProperties: + type: string + nullable: true + type: object + markRemoved: + type: boolean + ownerID: + type: string + parent: + type: string + readyToUse: + type: boolean + restoreSize: + format: int64 + type: integer + size: + format: int64 + type: integer + userCreated: + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: supportbundles.longhorn.io +spec: + group: longhorn.io + names: + kind: SupportBundle + listKind: SupportBundleList + plural: supportbundles + shortNames: + - lhbundle + singular: supportbundle + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the support bundle + jsonPath: .status.state + name: State + type: string + - description: The issue URL + jsonPath: .spec.issueURL + name: Issue + type: string + - description: A brief description of the issue + jsonPath: .spec.description + name: Description + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: SupportBundle is where Longhorn stores support bundle object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: SupportBundleSpec defines the desired state of the Longhorn + SupportBundle + properties: + description: + description: A brief description of the issue + type: string + issueURL: + description: The issue URL + nullable: true + type: string + nodeID: + description: The preferred responsible controller node ID. + type: string + required: + - description + type: object + status: + description: SupportBundleStatus defines the observed state of the Longhorn + SupportBundle + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + type: array + filename: + type: string + filesize: + format: int64 + type: integer + image: + description: The support bundle manager image + type: string + managerIP: + description: The support bundle manager IP + type: string + ownerID: + description: The current responsible controller node ID + type: string + progress: + type: integer + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: systembackups.longhorn.io +spec: + group: longhorn.io + names: + kind: SystemBackup + listKind: SystemBackupList + plural: systembackups + shortNames: + - lhsb + singular: systembackup + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The system backup Longhorn version + jsonPath: .status.version + name: Version + type: string + - description: The system backup state + jsonPath: .status.state + name: State + type: string + - description: The system backup creation time + jsonPath: .status.createdAt + name: Created + type: string + - description: The last time that the system backup was synced into the cluster + jsonPath: .status.lastSyncedAt + name: LastSyncedAt + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: SystemBackup is where Longhorn stores system backup object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: SystemBackupSpec defines the desired state of the Longhorn + SystemBackup + properties: + volumeBackupPolicy: + description: |- + The create volume backup policy + Can be "if-not-present", "always" or "disabled" + nullable: true + type: string + type: object + status: + description: SystemBackupStatus defines the observed state of the Longhorn + SystemBackup + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + createdAt: + description: The system backup creation time. + format: date-time + type: string + gitCommit: + description: The saved Longhorn manager git commit. + nullable: true + type: string + lastSyncedAt: + description: The last time that the system backup was synced into + the cluster. + format: date-time + nullable: true + type: string + managerImage: + description: The saved manager image. + type: string + ownerID: + description: The node ID of the responsible controller to reconcile + this SystemBackup. + type: string + state: + description: The system backup state. + type: string + version: + description: The saved Longhorn version. + nullable: true + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: systemrestores.longhorn.io +spec: + group: longhorn.io + names: + kind: SystemRestore + listKind: SystemRestoreList + plural: systemrestores + shortNames: + - lhsr + singular: systemrestore + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The system restore state + jsonPath: .status.state + name: State + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: SystemRestore is where Longhorn stores system restore object + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: SystemRestoreSpec defines the desired state of the Longhorn + SystemRestore + properties: + systemBackup: + description: The system backup name in the object store. + type: string + required: + - systemBackup + type: object + status: + description: SystemRestoreStatus defines the observed state of the Longhorn + SystemRestore + properties: + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + ownerID: + description: The node ID of the responsible controller to reconcile + this SystemRestore. + type: string + sourceURL: + description: The source system backup URL. + type: string + state: + description: The system restore state. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: volumeattachments.longhorn.io +spec: + group: longhorn.io + names: + kind: VolumeAttachment + listKind: VolumeAttachmentList + plural: volumeattachments + shortNames: + - lhva + singular: volumeattachment + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: VolumeAttachment stores attachment information of a Longhorn + volume + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VolumeAttachmentSpec defines the desired state of Longhorn + VolumeAttachment + properties: + attachmentTickets: + additionalProperties: + properties: + generation: + description: |- + A sequence number representing a specific generation of the desired state. + Populated by the system. Read-only. + format: int64 + type: integer + id: + description: The unique ID of this attachment. Used to differentiate + different attachments of the same volume. + type: string + nodeID: + description: The node that this attachment is requesting + type: string + parameters: + additionalProperties: + type: string + description: Optional additional parameter for this attachment + type: object + type: + type: string + type: object + type: object + volume: + description: The name of Longhorn volume of this VolumeAttachment + type: string + required: + - volume + type: object + status: + description: VolumeAttachmentStatus defines the observed state of Longhorn + VolumeAttachment + properties: + attachmentTicketStatuses: + additionalProperties: + properties: + conditions: + description: Record any error when trying to fulfill this attachment + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from + one status to another. + type: string + message: + description: Human-readable message indicating details + about last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the + condition's last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + generation: + description: |- + A sequence number representing a specific generation of the desired state. + Populated by the system. Read-only. + format: int64 + type: integer + id: + description: The unique ID of this attachment. Used to differentiate + different attachments of the same volume. + type: string + satisfied: + description: Indicate whether this attachment ticket has been + satisfied + type: boolean + required: + - conditions + - satisfied + type: object + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/crds.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + longhorn-manager: "" + name: volumes.longhorn.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + name: longhorn-conversion-webhook + namespace: longhorn-system + path: /v1/webhook/conversion + port: 9501 + conversionReviewVersions: + - v1beta2 + - v1beta1 + group: longhorn.io + names: + kind: Volume + listKind: VolumeList + plural: volumes + shortNames: + - lhv + singular: volume + preserveUnknownFields: false + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: The state of the volume + jsonPath: .status.state + name: State + type: string + - description: The robustness of the volume + jsonPath: .status.robustness + name: Robustness + type: string + - description: The scheduled condition of the volume + jsonPath: .status.conditions['scheduled']['status'] + name: Scheduled + type: string + - description: The size of the volume + jsonPath: .spec.size + name: Size + type: string + - description: The node that the volume is currently attaching to + jsonPath: .status.currentNodeID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Volume is where Longhorn stores volume object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + x-kubernetes-preserve-unknown-fields: true + status: + x-kubernetes-preserve-unknown-fields: true + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: The data engine of the volume + jsonPath: .spec.dataEngine + name: Data Engine + type: string + - description: The state of the volume + jsonPath: .status.state + name: State + type: string + - description: The robustness of the volume + jsonPath: .status.robustness + name: Robustness + type: string + - description: The scheduled condition of the volume + jsonPath: .status.conditions[?(@.type=='Schedulable')].status + name: Scheduled + type: string + - description: The size of the volume + jsonPath: .spec.size + name: Size + type: string + - description: The node that the volume is currently attaching to + jsonPath: .status.currentNodeID + name: Node + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta2 + schema: + openAPIV3Schema: + description: Volume is where Longhorn stores volume object. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VolumeSpec defines the desired state of the Longhorn volume + properties: + Standby: + type: boolean + accessMode: + enum: + - rwo + - rwx + type: string + backendStoreDriver: + description: Deprecated:Replaced by field `dataEngine`.' + type: string + backingImage: + type: string + backupCompressionMethod: + enum: + - none + - lz4 + - gzip + type: string + backupTargetName: + description: The backup target name that the volume will be backed + up to or is synced. + type: string + dataEngine: + enum: + - v1 + - v2 + type: string + dataLocality: + enum: + - disabled + - best-effort + - strict-local + type: string + dataSource: + type: string + disableFrontend: + type: boolean + diskSelector: + items: + type: string + type: array + encrypted: + type: boolean + engineImage: + description: 'Deprecated: Replaced by field `image`.' + type: string + freezeFilesystemForSnapshot: + description: Setting that freezes the filesystem on the root partition + before a snapshot is created. + enum: + - ignored + - enabled + - disabled + type: string + fromBackup: + type: string + frontend: + enum: + - blockdev + - iscsi + - nvmf + - "" + type: string + image: + type: string + lastAttachedBy: + type: string + migratable: + type: boolean + migrationNodeID: + type: string + nodeID: + type: string + nodeSelector: + items: + type: string + type: array + numberOfReplicas: + type: integer + replicaAutoBalance: + enum: + - ignored + - disabled + - least-effort + - best-effort + type: string + replicaDiskSoftAntiAffinity: + description: Replica disk soft anti affinity of the volume. Set enabled + to allow replicas to be scheduled in the same disk. + enum: + - ignored + - enabled + - disabled + type: string + replicaSoftAntiAffinity: + description: Replica soft anti affinity of the volume. Set enabled + to allow replicas to be scheduled on the same node. + enum: + - ignored + - enabled + - disabled + type: string + replicaZoneSoftAntiAffinity: + description: Replica zone soft anti affinity of the volume. Set enabled + to allow replicas to be scheduled in the same zone. + enum: + - ignored + - enabled + - disabled + type: string + restoreVolumeRecurringJob: + enum: + - ignored + - enabled + - disabled + type: string + revisionCounterDisabled: + type: boolean + size: + format: int64 + type: string + snapshotDataIntegrity: + enum: + - ignored + - disabled + - enabled + - fast-check + type: string + snapshotMaxCount: + type: integer + snapshotMaxSize: + format: int64 + type: string + staleReplicaTimeout: + type: integer + unmapMarkSnapChainRemoved: + enum: + - ignored + - disabled + - enabled + type: string + type: object + status: + description: VolumeStatus defines the observed state of the Longhorn volume + properties: + actualSize: + format: int64 + type: integer + cloneStatus: + properties: + attemptCount: + type: integer + nextAllowedAttemptAt: + type: string + snapshot: + type: string + sourceVolume: + type: string + state: + type: string + type: object + conditions: + items: + properties: + lastProbeTime: + description: Last time we probed the condition. + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + type: string + type: + description: Type is the type of the condition. + type: string + type: object + nullable: true + type: array + currentImage: + type: string + currentMigrationNodeID: + description: the node that this volume is currently migrating to + type: string + currentNodeID: + type: string + expansionRequired: + type: boolean + frontendDisabled: + type: boolean + isStandby: + type: boolean + kubernetesStatus: + properties: + lastPVCRefAt: + type: string + lastPodRefAt: + type: string + namespace: + description: determine if PVC/Namespace is history or not + type: string + pvName: + type: string + pvStatus: + type: string + pvcName: + type: string + workloadsStatus: + description: determine if Pod/Workload is history or not + items: + properties: + podName: + type: string + podStatus: + type: string + workloadName: + type: string + workloadType: + type: string + type: object + nullable: true + type: array + type: object + lastBackup: + type: string + lastBackupAt: + type: string + lastDegradedAt: + type: string + ownerID: + type: string + pendingNodeID: + description: Deprecated. + type: string + remountRequestedAt: + type: string + restoreInitiated: + type: boolean + restoreRequired: + type: boolean + robustness: + type: string + shareEndpoint: + type: string + shareState: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: longhorn/templates/clusterrole.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: longhorn-role + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +rules: +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - "*" +- apiGroups: [""] + resources: ["pods", "events", "persistentvolumes", "persistentvolumeclaims","persistentvolumeclaims/status", "nodes", "proxy/nodes", "pods/log", "secrets", "services", "endpoints", "configmaps", "serviceaccounts"] + verbs: ["*"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["get", "list"] +- apiGroups: ["apps"] + resources: ["daemonsets", "statefulsets", "deployments"] + verbs: ["*"] +- apiGroups: ["batch"] + resources: ["jobs", "cronjobs"] + verbs: ["*"] +- apiGroups: ["policy"] + resources: ["poddisruptionbudgets", "podsecuritypolicies"] + verbs: ["*"] +- apiGroups: ["scheduling.k8s.io"] + resources: ["priorityclasses"] + verbs: ["watch", "list"] +- apiGroups: ["storage.k8s.io"] + resources: ["storageclasses", "volumeattachments", "volumeattachments/status", "csinodes", "csidrivers"] + verbs: ["*"] +- apiGroups: ["snapshot.storage.k8s.io"] + resources: ["volumesnapshotclasses", "volumesnapshots", "volumesnapshotcontents", "volumesnapshotcontents/status"] + verbs: ["*"] +- apiGroups: ["longhorn.io"] + resources: ["volumes", "volumes/status", "engines", "engines/status", "replicas", "replicas/status", "settings", "settings/status", + "engineimages", "engineimages/status", "nodes", "nodes/status", "instancemanagers", "instancemanagers/status", + "sharemanagers", "sharemanagers/status", "backingimages", "backingimages/status", + "backingimagemanagers", "backingimagemanagers/status", "backingimagedatasources", "backingimagedatasources/status", + "backuptargets", "backuptargets/status", "backupvolumes", "backupvolumes/status", "backups", "backups/status", + "recurringjobs", "recurringjobs/status", "orphans", "orphans/status", "snapshots", "snapshots/status", + "supportbundles", "supportbundles/status", "systembackups", "systembackups/status", "systemrestores", "systemrestores/status", + "volumeattachments", "volumeattachments/status", "backupbackingimages", "backupbackingimages/status"] + verbs: ["*"] +- apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["*"] +- apiGroups: ["metrics.k8s.io"] + resources: ["pods", "nodes"] + verbs: ["get", "list"] +- apiGroups: ["apiregistration.k8s.io"] + resources: ["apiservices"] + verbs: ["list", "watch"] +- apiGroups: ["admissionregistration.k8s.io"] + resources: ["mutatingwebhookconfigurations", "validatingwebhookconfigurations"] + verbs: ["get", "list", "create", "patch", "delete"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles", "rolebindings", "clusterrolebindings", "clusterroles"] + verbs: ["*"] +--- +# Source: longhorn/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: longhorn-bind + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: longhorn-role +subjects: +- kind: ServiceAccount + name: longhorn-service-account + namespace: longhorn-system +--- +# Source: longhorn/templates/clusterrolebinding.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: longhorn-support-bundle + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: +- kind: ServiceAccount + name: longhorn-support-bundle + namespace: longhorn-system +--- +# Source: longhorn/templates/daemonset-sa.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-manager + name: longhorn-backend + namespace: longhorn-system +spec: + type: ClusterIP + selector: + app: longhorn-manager + ports: + - name: manager + port: 9500 + targetPort: manager +--- +# Source: longhorn/templates/deployment-ui.yaml +kind: Service +apiVersion: v1 +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-ui + name: longhorn-frontend + namespace: longhorn-system +spec: + type: ClusterIP + selector: + app: longhorn-ui + ports: + - name: http + port: 80 + targetPort: http + nodePort: null +--- +# Source: longhorn/templates/services.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-conversion-webhook + name: longhorn-conversion-webhook + namespace: longhorn-system +spec: + type: ClusterIP + selector: + longhorn.io/conversion-webhook: longhorn-conversion-webhook + ports: + - name: conversion-webhook + port: 9501 + targetPort: conversion-wh +--- +# Source: longhorn/templates/services.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-admission-webhook + name: longhorn-admission-webhook + namespace: longhorn-system +spec: + type: ClusterIP + selector: + longhorn.io/admission-webhook: longhorn-admission-webhook + ports: + - name: admission-webhook + port: 9502 + targetPort: admission-wh +--- +# Source: longhorn/templates/services.yaml +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-recovery-backend + name: longhorn-recovery-backend + namespace: longhorn-system +spec: + type: ClusterIP + selector: + longhorn.io/recovery-backend: longhorn-recovery-backend + ports: + - name: recovery-backend + port: 9503 + targetPort: recov-backend +--- +# Source: longhorn/templates/daemonset-sa.yaml +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-manager + name: longhorn-manager + namespace: longhorn-system +spec: + selector: + matchLabels: + app: longhorn-manager + template: + metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-manager + spec: + containers: + - name: longhorn-manager + image: longhornio/longhorn-manager:v1.8.1 + imagePullPolicy: IfNotPresent + securityContext: + privileged: true + command: + - longhorn-manager + - -d + - daemon + - --engine-image + - "longhornio/longhorn-engine:v1.8.1" + - --instance-manager-image + - "longhornio/longhorn-instance-manager:v1.8.1" + - --share-manager-image + - "longhornio/longhorn-share-manager:v1.8.1" + - --backing-image-manager-image + - "longhornio/backing-image-manager:v1.8.1" + - --support-bundle-manager-image + - "longhornio/support-bundle-kit:v0.0.52" + - --manager-image + - "longhornio/longhorn-manager:v1.8.1" + - --service-account + - longhorn-service-account + - --upgrade-version-check + ports: + - containerPort: 9500 + name: manager + - containerPort: 9501 + name: conversion-wh + - containerPort: 9502 + name: admission-wh + - containerPort: 9503 + name: recov-backend + readinessProbe: + httpGet: + path: /v1/healthz + port: 9501 + scheme: HTTPS + volumeMounts: + - name: boot + mountPath: /host/boot/ + readOnly: true + - name: dev + mountPath: /host/dev/ + - name: proc + mountPath: /host/proc/ + readOnly: true + - name: etc + mountPath: /host/etc/ + readOnly: true + - name: longhorn + mountPath: /var/lib/longhorn/ + mountPropagation: Bidirectional + - name: longhorn-grpc-tls + mountPath: /tls-files/ + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: pre-pull-share-manager-image + imagePullPolicy: IfNotPresent + image: longhornio/longhorn-share-manager:v1.8.1 + command: ["sh", "-c", "echo share-manager image pulled && sleep infinity"] + volumes: + - name: boot + hostPath: + path: /boot/ + - name: dev + hostPath: + path: /dev/ + - name: proc + hostPath: + path: /proc/ + - name: etc + hostPath: + path: /etc/ + - name: longhorn + hostPath: + path: /var/lib/longhorn/ + - name: longhorn-grpc-tls + secret: + secretName: longhorn-grpc-tls + optional: true + priorityClassName: "longhorn-critical" + serviceAccountName: longhorn-service-account + updateStrategy: + rollingUpdate: + maxUnavailable: "100%" +--- +# Source: longhorn/templates/deployment-driver.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: longhorn-driver-deployer + namespace: longhorn-system + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 +spec: + replicas: 1 + selector: + matchLabels: + app: longhorn-driver-deployer + template: + metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-driver-deployer + spec: + initContainers: + - name: wait-longhorn-manager + image: longhornio/longhorn-manager:v1.8.1 + command: ['sh', '-c', 'while [ $(curl -m 1 -s -o /dev/null -w "%{http_code}" http://longhorn-backend:9500/v1) != "200" ]; do echo waiting; sleep 2; done'] + containers: + - name: longhorn-driver-deployer + image: longhornio/longhorn-manager:v1.8.1 + imagePullPolicy: IfNotPresent + command: + - longhorn-manager + - -d + - deploy-driver + - --manager-image + - "longhornio/longhorn-manager:v1.8.1" + - --manager-url + - http://longhorn-backend:9500/v1 + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: SERVICE_ACCOUNT + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName + - name: CSI_ATTACHER_IMAGE + value: "longhornio/csi-attacher:v4.8.1" + - name: CSI_PROVISIONER_IMAGE + value: "longhornio/csi-provisioner:v5.2.0" + - name: CSI_NODE_DRIVER_REGISTRAR_IMAGE + value: "longhornio/csi-node-driver-registrar:v2.13.0" + - name: CSI_RESIZER_IMAGE + value: "longhornio/csi-resizer:v1.13.2" + - name: CSI_SNAPSHOTTER_IMAGE + value: "longhornio/csi-snapshotter:v8.2.0" + - name: CSI_LIVENESS_PROBE_IMAGE + value: "longhornio/livenessprobe:v2.15.0" + priorityClassName: "longhorn-critical" + serviceAccountName: longhorn-service-account + securityContext: + runAsUser: 0 +--- +# Source: longhorn/templates/deployment-ui.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-ui + name: longhorn-ui + namespace: longhorn-system +spec: + replicas: 2 + selector: + matchLabels: + app: longhorn-ui + template: + metadata: + labels: + app.kubernetes.io/name: longhorn + app.kubernetes.io/instance: longhorn + app.kubernetes.io/version: v1.8.1 + app: longhorn-ui + spec: + serviceAccountName: longhorn-ui-service-account + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 1 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - longhorn-ui + topologyKey: kubernetes.io/hostname + containers: + - name: longhorn-ui + image: longhornio/longhorn-ui:v1.8.1 + imagePullPolicy: IfNotPresent + volumeMounts: + - name : nginx-cache + mountPath: /var/cache/nginx/ + - name : nginx-config + mountPath: /var/config/nginx/ + - name: var-run + mountPath: /var/run/ + ports: + - containerPort: 8000 + name: http + env: + - name: LONGHORN_MANAGER_IP + value: "http://longhorn-backend:9500" + - name: LONGHORN_UI_PORT + value: "8000" + volumes: + - emptyDir: {} + name: nginx-cache + - emptyDir: {} + name: nginx-config + - emptyDir: {} + name: var-run + priorityClassName: "longhorn-critical" +--- +# Source: longhorn/templates/validate-psp-install.yaml +# diff --git a/longhorn/manifest.yaml b/longhorn/manifest.yaml new file mode 100644 index 0000000..6a002d3 --- /dev/null +++ b/longhorn/manifest.yaml @@ -0,0 +1,13 @@ +name: longhorn +is: longhorn +description: Cloud-native distributed block storage for Kubernetes +version: v1.8.1 +namespace: longhorn-system +deploymentName: longhorn-ui +category: infrastructure +requires: + - name: traefik + - name: nfs +defaultConfig: + internalDomain: "{{ .cloud.internalDomain }}" + backupTarget: "nfs://{{ .apps.nfs.host }}:/data/{{ .cluster.name }}/backups" diff --git a/longhorn/volumesnapshotclass-longhorn.yaml b/longhorn/volumesnapshotclass-longhorn.yaml new file mode 100644 index 0000000..6fbf2e0 --- /dev/null +++ b/longhorn/volumesnapshotclass-longhorn.yaml @@ -0,0 +1,8 @@ +apiVersion: snapshot.storage.k8s.io/v1 +kind: VolumeSnapshotClass +metadata: + name: longhorn-snapshot-class +driver: driver.longhorn.io +deletionPolicy: Delete +parameters: + type: snap diff --git a/loomio/manifest.yaml b/loomio/manifest.yaml index 37a4777..de23797 100644 --- a/loomio/manifest.yaml +++ b/loomio/manifest.yaml @@ -7,6 +7,7 @@ requires: - name: postgres installed_as: postgres - name: redis + - name: smtp defaultConfig: namespace: loomio externalDnsDomain: "{{ .cloud.domain }}" @@ -37,11 +38,11 @@ defaultConfig: smtp: auth: plain domain: "{{ .cloud.domain }}" - host: "{{ .cloud.smtp.host }}" - port: "{{ .cloud.smtp.port }}" - user: "{{ .cloud.smtp.user }}" - tls: "{{ .cloud.smtp.tls }}" - from: "{{ .cloud.smtp.from }}" + host: "{{ .apps.smtp.host }}" + port: "{{ .apps.smtp.port }}" + user: "{{ .apps.smtp.user }}" + tls: "{{ .apps.smtp.tls }}" + from: "{{ .apps.smtp.from }}" defaultSecrets: - key: dbPassword default: "{{ random.AlphaNum 32 }}" diff --git a/mastodon/manifest.yaml b/mastodon/manifest.yaml index e4ca378..f408265 100644 --- a/mastodon/manifest.yaml +++ b/mastodon/manifest.yaml @@ -6,6 +6,7 @@ icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mastodon.svg requires: - name: postgres - name: redis + - name: smtp defaultConfig: namespace: mastodon externalDnsDomain: "{{ .cloud.domain }}" @@ -31,14 +32,14 @@ defaultConfig: systemStorage: 100Gi # SMTP configuration smtp: - enabled: "{{ .cloud.smtp.host | ternary true false }}" - server: "{{ .cloud.smtp.host }}" - port: "{{ .cloud.smtp.port }}" + enabled: "{{ .apps.smtp.host | ternary true false }}" + server: "{{ .apps.smtp.host }}" + port: "{{ .apps.smtp.port }}" from: notifications@{{ .cloud.domain }} - user: "{{ .cloud.smtp.user }}" + user: "{{ .apps.smtp.user }}" authMethod: plain enableStarttls: auto - tls: "{{ .cloud.smtp.tls }}" + tls: "{{ .apps.smtp.tls }}" # TLS tlsSecretName: wildcard-wild-cloud-tls # Sidekiq configuration diff --git a/matrix/manifest.yaml b/matrix/manifest.yaml index f288f29..7e30603 100644 --- a/matrix/manifest.yaml +++ b/matrix/manifest.yaml @@ -1,12 +1,12 @@ name: matrix is: matrix -install: true description: Matrix is an open standard for secure, decentralized, real-time communication. This deploys the Synapse homeserver for self-hosted Matrix federation and messaging. version: v1.144.0 icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/matrix.svg requires: - name: postgres - name: redis +- name: smtp defaultConfig: namespace: matrix externalDnsDomain: '{{ .cloud.domain }}' @@ -25,11 +25,11 @@ defaultConfig: tlsSecretName: wildcard-wild-cloud-tls enableRegistration: false smtp: - host: '{{ .cloud.smtp.host }}' - port: '{{ .cloud.smtp.port }}' + host: '{{ .apps.smtp.host }}' + port: '{{ .apps.smtp.port }}' from: matrix@{{ .cloud.domain }} - user: '{{ .cloud.smtp.user }}' - requireTls: '{{ .cloud.smtp.tls }}' + user: '{{ .apps.smtp.user }}' + requireTls: '{{ .apps.smtp.tls }}' defaultSecrets: - key: dbPassword - key: registrationSharedSecret diff --git a/metallb/README.md b/metallb/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/metallb/README.md @@ -0,0 +1 @@ + diff --git a/metallb/configuration/kustomization.yaml b/metallb/configuration/kustomization.yaml new file mode 100644 index 0000000..35b3ac2 --- /dev/null +++ b/metallb/configuration/kustomization.yaml @@ -0,0 +1,3 @@ +namespace: metallb-system +resources: + - pool.yaml diff --git a/metallb/configuration/pool.yaml b/metallb/configuration/pool.yaml new file mode 100644 index 0000000..0aac88e --- /dev/null +++ b/metallb/configuration/pool.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: first-pool + namespace: metallb-system +spec: + addresses: + - {{ .ipAddressPool }} + +--- +apiVersion: metallb.io/v1beta1 +kind: L2Advertisement +metadata: + name: l2-advertisement + namespace: metallb-system +spec: + ipAddressPools: + - first-pool diff --git a/metallb/install.sh b/metallb/install.sh new file mode 100755 index 0000000..1cb0761 --- /dev/null +++ b/metallb/install.sh @@ -0,0 +1,51 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +METALLB_DIR="${INSTANCE_DIR}/apps/metallb" + +echo "=== Setting up MetalLB ===" +echo "" + +echo "Using compiled MetalLB templates..." +if [ ! -f "${METALLB_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${METALLB_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Deploying MetalLB installation..." +kubectl apply -k ${METALLB_DIR}/installation + +echo "Waiting for MetalLB controller to be ready..." +kubectl wait --for=condition=Available deployment/controller -n metallb-system --timeout=60s +echo "Extra buffer for webhook initialization..." +sleep 10 + +echo "Applying MetalLB configuration..." +kubectl apply -k ${METALLB_DIR}/configuration + +echo "" +echo "MetalLB installed and configured successfully" +echo "" +echo "To verify the installation:" +echo " kubectl get pods -n metallb-system" +echo " kubectl get ipaddresspools.metallb.io -n metallb-system" +echo "" +echo "MetalLB will now provide LoadBalancer IPs for your services" diff --git a/metallb/installation/kustomization.yaml b/metallb/installation/kustomization.yaml new file mode 100644 index 0000000..e6e0425 --- /dev/null +++ b/metallb/installation/kustomization.yaml @@ -0,0 +1,3 @@ +namespace: metallb-system +resources: + - github.com/metallb/metallb/config/native?ref=v0.15.0 diff --git a/metallb/kustomization.yaml b/metallb/kustomization.yaml new file mode 100644 index 0000000..2bd7046 --- /dev/null +++ b/metallb/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - installation + - configuration diff --git a/metallb/manifest.yaml b/metallb/manifest.yaml new file mode 100644 index 0000000..e03b7cc --- /dev/null +++ b/metallb/manifest.yaml @@ -0,0 +1,10 @@ +name: metallb +is: metallb +description: Bare metal load-balancer for Kubernetes +version: v0.15.0 +namespace: metallb-system +deploymentName: controller +category: infrastructure +defaultConfig: + ipAddressPool: "192.168.1.240-192.168.1.250" + loadBalancerIp: "192.168.1.240" diff --git a/nfs/README.md b/nfs/README.md new file mode 100644 index 0000000..12556b9 --- /dev/null +++ b/nfs/README.md @@ -0,0 +1,60 @@ +# NFS Setup (Optional) + +The infrastructure supports optional NFS (Network File System) for shared media storage across the cluster. If your config.yaml contains the `cloud.nfs` section, the NFS server will be set up automatically. + +## Host Setup + +First, set up the NFS server on your chosen host. + +```bash +./setup-nfs-host.sh +``` + +Example: + +```bash +./setup-nfs-host.sh box-01 /srv/nfs +``` + +## Cluster Integration + +Add to your `config.yaml`: + +```yaml +cloud: + nfs: + host: box-01 + mediaPath: /srv/nfs + storageCapacity: 250Gi # Max size for PersistentVolume +``` + +And now you can run the nfs cluster setup: + +```bash +setup/setup-nfs-host.sh +``` + +## Features + +- Automatic IP detection - Uses network IP even when hostname resolves to localhost +- Cluster-wide access - Any pod can mount the NFS share regardless of node placement +- Configurable capacity - Set PersistentVolume size via `NFS_STORAGE_CAPACITY` +- ReadWriteMany - Multiple pods can simultaneously access the same storage + +## Usage + +Applications can use NFS storage by setting `storageClassName: nfs` in their PVCs: + +```yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: media-pvc +spec: + accessModes: + - ReadWriteMany + storageClassName: nfs + resources: + requests: + storage: 100Gi +``` diff --git a/nfs/install.sh b/nfs/install.sh new file mode 100755 index 0000000..30b9d7c --- /dev/null +++ b/nfs/install.sh @@ -0,0 +1,229 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +CONFIG_FILE="${INSTANCE_DIR}/config.yaml" +NFS_DIR="${INSTANCE_DIR}/apps/nfs" + +echo "=== Registering NFS Server with Kubernetes Cluster ===" +echo "" + +echo "Using pre-compiled NFS templates..." +if [ ! -f "${NFS_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${NFS_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +NFS_HOST="$(yq '.apps.nfs.host' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')" +NFS_MEDIA_PATH="$(yq '.apps.nfs.mediaPath' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')" +NFS_STORAGE_CAPACITY="$(yq '.apps.nfs.storageCapacity' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')" + +echo "NFS Configuration:" +echo " Host: ${NFS_HOST}" +echo " Media path: ${NFS_MEDIA_PATH}" +echo " Storage capacity: ${NFS_STORAGE_CAPACITY}" +echo "" + +if [ -z "${NFS_HOST}" ] || [ "${NFS_HOST}" = "null" ]; then + echo "ERROR: apps.nfs.host not set in config" + exit 1 +fi +if [ -z "${NFS_MEDIA_PATH}" ] || [ "${NFS_MEDIA_PATH}" = "null" ]; then + echo "ERROR: apps.nfs.mediaPath not set in config" + exit 1 +fi +if [ -z "${NFS_STORAGE_CAPACITY}" ] || [ "${NFS_STORAGE_CAPACITY}" = "null" ]; then + echo "ERROR: apps.nfs.storageCapacity not set in config" + exit 1 +fi + +resolve_nfs_host() { + echo "Resolving NFS host: ${NFS_HOST}" + if [[ "${NFS_HOST}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + NFS_HOST_IP="${NFS_HOST}" + echo " Host is already an IP address" + else + echo " Looking up hostname..." + NFS_HOST_IP=$(getent hosts "${NFS_HOST}" 2>/dev/null | awk '{print $1}' | head -n1 || true) + echo " Resolved to: ${NFS_HOST_IP}" + if [[ -z "${NFS_HOST_IP}" ]]; then + echo "ERROR: Unable to resolve hostname ${NFS_HOST} to IP address" + echo "Make sure ${NFS_HOST} is resolvable from this cluster" + exit 1 + fi + + if [[ "${NFS_HOST_IP}" =~ ^127\. ]]; then + echo "Warning: ${NFS_HOST} resolves to localhost (${NFS_HOST_IP})" + echo "Auto-detecting network IP for cluster access..." + + local network_ip=$(ip route get 8.8.8.8 | grep -oP 'src \K\S+' 2>/dev/null) + + if [[ -n "${network_ip}" && ! "${network_ip}" =~ ^127\. ]]; then + echo "Using detected network IP: ${network_ip}" + NFS_HOST_IP="${network_ip}" + else + echo "ERROR: Could not auto-detect network IP. Available IPs:" + ip addr show | grep "inet " | grep -v "127.0.0.1" | grep -v "10.42" | grep -v "172." | awk '{print " " $2}' | cut -d/ -f1 + echo "Please set NFS_HOST to the correct IP address manually." + exit 1 + fi + fi + fi + + echo "NFS server IP: ${NFS_HOST_IP}" + export NFS_HOST_IP +} + +test_nfs_accessibility() { + echo "" + echo "Testing NFS accessibility from cluster..." + + if ! command -v showmount >/dev/null 2>&1; then + echo "Installing NFS client tools..." + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y nfs-common + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y nfs-utils + elif command -v dnf >/dev/null 2>&1; then + sudo dnf install -y nfs-utils + else + echo "Warning: Unable to install NFS client tools. Skipping accessibility test." + return 0 + fi + fi + + echo "Testing connection to NFS server..." + if timeout 10 showmount -e "${NFS_HOST_IP}" >/dev/null 2>&1; then + echo "NFS server is accessible" + echo "Available exports:" + showmount -e "${NFS_HOST_IP}" + else + echo "ERROR: Cannot connect to NFS server at ${NFS_HOST_IP}" + echo "Make sure:" + echo " 1. NFS server is running on ${NFS_HOST}" + echo " 2. Network connectivity exists between cluster and NFS host" + echo " 3. Firewall allows NFS traffic (port 2049)" + exit 1 + fi + + if showmount -e "${NFS_HOST_IP}" | grep -q "${NFS_MEDIA_PATH}"; then + echo "Media path ${NFS_MEDIA_PATH} is exported" + else + echo "ERROR: Media path ${NFS_MEDIA_PATH} is not found in exports" + echo "Available exports:" + showmount -e "${NFS_HOST_IP}" + echo "" + echo "Run setup-nfs-host.sh on ${NFS_HOST} to configure the export" + exit 1 + fi +} + +test_nfs_mount() { + echo "" + echo "Testing NFS mount functionality..." + + local test_mount="/tmp/nfs-test-$$" + mkdir -p "${test_mount}" + + if timeout 30 sudo mount -t nfs4 "${NFS_HOST_IP}:${NFS_MEDIA_PATH}" "${test_mount}"; then + echo "NFS mount successful" + + if ls "${test_mount}" >/dev/null 2>&1; then + echo "NFS read access working" + else + echo "ERROR: NFS read access failed" + fi + + sudo umount "${test_mount}" || echo "Warning: Failed to unmount test directory" + else + echo "ERROR: NFS mount failed" + echo "Check NFS server configuration and network connectivity" + exit 1 + fi + + rmdir "${test_mount}" 2>/dev/null || true +} + +create_k8s_resources() { + echo "" + echo "Creating Kubernetes NFS resources..." + + echo "Applying NFS manifests..." + kubectl apply -k "${NFS_DIR}/" + + echo "NFS PersistentVolume and StorageClass created" + + echo "Verifying Kubernetes resources..." + if kubectl get storageclass nfs >/dev/null 2>&1; then + echo "StorageClass 'nfs' created" + else + echo "ERROR: StorageClass 'nfs' not found" + exit 1 + fi + + if kubectl get pv nfs-media-pv >/dev/null 2>&1; then + echo "PersistentVolume 'nfs-media-pv' created" + kubectl get pv nfs-media-pv + else + echo "ERROR: PersistentVolume 'nfs-media-pv' not found" + exit 1 + fi +} + +show_usage_instructions() { + echo "" + echo "=== NFS Kubernetes Setup Complete ===" + echo "" + echo "NFS server ${NFS_HOST} (${NFS_HOST_IP}) has been registered with the cluster" + echo "" + echo "Kubernetes resources created:" + echo " - StorageClass: nfs" + echo " - PersistentVolume: nfs-media-pv (${NFS_STORAGE_CAPACITY}, ReadWriteMany)" + echo "" + echo "To use NFS storage in your applications:" + echo " 1. Set storageClassName: nfs in your PVC" + echo " 2. Use accessMode: ReadWriteMany for shared access" + echo "" + echo "Example PVC:" + echo "---" + echo "apiVersion: v1" + echo "kind: PersistentVolumeClaim" + echo "metadata:" + echo " name: my-nfs-pvc" + echo "spec:" + echo " accessModes:" + echo " - ReadWriteMany" + echo " storageClassName: nfs" + echo " resources:" + echo " requests:" + echo " storage: 10Gi" + echo "" +} + +main() { + resolve_nfs_host + test_nfs_accessibility + test_nfs_mount + create_k8s_resources + show_usage_instructions +} + +echo "Starting NFS setup process..." +main "$@" diff --git a/nfs/kustomization.yaml b/nfs/kustomization.yaml new file mode 100644 index 0000000..2e7be5b --- /dev/null +++ b/nfs/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - persistent-volume.yaml + - storage-class.yaml diff --git a/nfs/manifest.yaml b/nfs/manifest.yaml new file mode 100644 index 0000000..a15da31 --- /dev/null +++ b/nfs/manifest.yaml @@ -0,0 +1,12 @@ +name: nfs +is: nfs +description: NFS client provisioner for external NFS storage +version: v4.0.18 +namespace: nfs +deploymentName: "" +storageClassName: "nfs" +category: infrastructure +defaultConfig: + host: "192.168.1.100" + mediaPath: "/mnt/storage/media" + storageCapacity: "1Ti" diff --git a/nfs/persistent-volume.yaml b/nfs/persistent-volume.yaml new file mode 100644 index 0000000..e15e7e3 --- /dev/null +++ b/nfs/persistent-volume.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: nfs-media-pv + labels: + storage: nfs-media +spec: + capacity: + storage: {{ .storageCapacity }} + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + storageClassName: nfs + nfs: + server: {{ .host }} + path: {{ .mediaPath }} + mountOptions: + - nfsvers=4.1 + - rsize=1048576 + - wsize=1048576 + - hard + - intr + - timeo=600 diff --git a/nfs/setup-nfs-host.sh b/nfs/setup-nfs-host.sh new file mode 100755 index 0000000..142c9d4 --- /dev/null +++ b/nfs/setup-nfs-host.sh @@ -0,0 +1,306 @@ +#!/bin/bash +set -e +set -o pipefail + +# Navigate to script directory +SCRIPT_PATH="$(realpath "${BASH_SOURCE[0]}")" +SCRIPT_DIR="$(dirname "$SCRIPT_PATH")" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +usage() { + echo "Usage: setup-nfs-host.sh [server] [media-path] [options]" + echo "" + echo "Set up NFS server on the specified host." + echo "" + echo "Examples:" + echo " setup-nfs-host.sh box-01 /data/media" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -e, --export-options Set the NFS export options" + +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + usage + exit 0 + ;; + -e|--export-options) + if [[ -z "$2" ]]; then + echo "Error: --export-options requires a value" + exit 1 + else + NFS_EXPORT_OPTIONS="$2" + fi + shift 2 + ;; + -*) + echo "Unknown option $1" + usage + exit 1 + ;; + *) + # First non-option argument is server + if [[ -z "$NFS_HOST" ]]; then + export NFS_HOST="$1" + # Second non-option argument is media path + elif [[ -z "$NFS_MEDIA_PATH" ]]; then + export NFS_MEDIA_PATH="$1" + else + echo "Too many arguments" + usage + exit 1 + fi + shift + ;; + esac +done + +echo "Setting up NFS server on this host..." + +# Check if required NFS variables are configured +if [[ -z "${NFS_HOST}" ]]; then + echo "NFS_HOST not set. Please set NFS_HOST= in your environment" + echo "Example: export NFS_HOST=box-01" + exit 1 +fi + +# Ensure NFS_MEDIA_PATH is explicitly set +if [[ -z "${NFS_MEDIA_PATH}" ]]; then + echo "Error: NFS_MEDIA_PATH not set. Please set it in your environment" + echo "Example: export NFS_MEDIA_PATH=/data/media" + exit 1 +fi + +# Set default for NFS_EXPORT_OPTIONS if not already set +if [[ -z "${NFS_EXPORT_OPTIONS}" ]]; then + export NFS_EXPORT_OPTIONS="*(rw,sync,no_subtree_check,no_root_squash)" + echo "Using default NFS_EXPORT_OPTIONS: ${NFS_EXPORT_OPTIONS}" +fi + +echo "Target NFS host: ${NFS_HOST}" +echo "Media path: ${NFS_MEDIA_PATH}" +echo "Export options: ${NFS_EXPORT_OPTIONS}" + +# Function to check if we're running on the correct host +check_host() { + local current_hostname=$(hostname) + if [[ "${current_hostname}" != "${NFS_HOST}" ]]; then + echo "Warning: Current host (${current_hostname}) differs from NFS_HOST (${NFS_HOST})" + echo "This script should be run on ${NFS_HOST}" + read -p "Continue anyway? (y/N): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + fi +} + +# Function to install NFS server and SMB/CIFS +install_nfs_server() { + echo "Installing NFS server and SMB/CIFS packages..." + + # Detect package manager and install NFS server + Samba + if command -v apt-get >/dev/null 2>&1; then + # Debian/Ubuntu + sudo apt-get update + sudo apt-get install -y nfs-kernel-server nfs-common samba samba-common-bin + elif command -v yum >/dev/null 2>&1; then + # RHEL/CentOS + sudo yum install -y nfs-utils samba samba-client + elif command -v dnf >/dev/null 2>&1; then + # Fedora + sudo dnf install -y nfs-utils samba samba-client + else + echo "Error: Unable to detect package manager. Please install NFS server and Samba manually." + exit 1 + fi +} + +# Function to create media directory +create_media_directory() { + echo "Creating media directory: ${NFS_MEDIA_PATH}" + + # Create directory if it doesn't exist + sudo mkdir -p "${NFS_MEDIA_PATH}" + + # Set appropriate permissions + # Using 755 for directory, allowing read/execute for all, write for owner + sudo chmod 755 "${NFS_MEDIA_PATH}" + + echo "Media directory created with appropriate permissions" + echo "Directory info:" + ls -la "${NFS_MEDIA_PATH}/" +} + +# Function to configure NFS exports +configure_nfs_exports() { + echo "Configuring NFS exports..." + + local export_line="${NFS_MEDIA_PATH} ${NFS_EXPORT_OPTIONS}" + local exports_file="/etc/exports" + + # Backup existing exports file + sudo cp "${exports_file}" "${exports_file}.backup.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true + + # Check if export already exists + if sudo grep -q "^${NFS_MEDIA_PATH}" "${exports_file}" 2>/dev/null; then + echo "Export for ${NFS_MEDIA_PATH} already exists, updating..." + sudo sed -i "s|^${NFS_MEDIA_PATH}.*|${export_line}|" "${exports_file}" + else + echo "Adding new export for ${NFS_MEDIA_PATH}..." + echo "${export_line}" | sudo tee -a "${exports_file}" + fi + + # Export the filesystems + sudo exportfs -rav + + echo "NFS exports configured:" + sudo exportfs -v +} + +# Function to start and enable NFS services +start_nfs_services() { + echo "Starting NFS services..." + + # Start and enable NFS server + sudo systemctl enable nfs-server + sudo systemctl start nfs-server + + # Also enable related services + sudo systemctl enable rpcbind + sudo systemctl start rpcbind + + echo "NFS services started and enabled" + + # Show service status + sudo systemctl status nfs-server --no-pager --lines=5 +} + +# Function to configure SMB/CIFS sharing +configure_smb_sharing() { + echo "Configuring SMB/CIFS sharing..." + + local smb_config="/etc/samba/smb.conf" + local share_name="media" + + # Backup existing config + sudo cp "${smb_config}" "${smb_config}.backup.$(date +%Y%m%d-%H%M%S)" 2>/dev/null || true + + # Check if share already exists + if sudo grep -q "^\[${share_name}\]" "${smb_config}" 2>/dev/null; then + echo "SMB share '${share_name}' already exists, updating..." + # Remove existing share section + sudo sed -i "/^\[${share_name}\]/,/^\[/{ /^\[${share_name}\]/d; /^\[/!d; }" "${smb_config}" + fi + + # Add media share configuration + cat << EOF | sudo tee -a "${smb_config}" + +[${share_name}] + comment = Media files for Wild Cloud + path = ${NFS_MEDIA_PATH} + browseable = yes + read only = no + guest ok = yes + create mask = 0664 + directory mask = 0775 + force user = $(whoami) + force group = $(whoami) +EOF + + echo "SMB share configuration added" + + # Test configuration + if sudo testparm -s >/dev/null 2>&1; then + echo "✓ SMB configuration is valid" + else + echo "✗ SMB configuration has errors" + sudo testparm + exit 1 + fi +} + +# Function to start SMB services +start_smb_services() { + echo "Starting SMB services..." + + # Enable and start Samba services + sudo systemctl enable smbd + sudo systemctl start smbd + sudo systemctl enable nmbd + sudo systemctl start nmbd + + echo "SMB services started and enabled" + + # Show service status + sudo systemctl status smbd --no-pager --lines=3 +} + +# Function to test NFS setup +test_nfs_setup() { + echo "Testing NFS setup..." + + # Test if NFS is responding + if command -v showmount >/dev/null 2>&1; then + echo "Available NFS exports:" + showmount -e localhost || echo "Warning: showmount failed, but NFS may still be working" + fi + + # Check if the export directory is accessible + if [[ -d "${NFS_MEDIA_PATH}" ]]; then + echo "✓ Media directory exists and is accessible" + else + echo "✗ Media directory not accessible" + exit 1 + fi +} + +# Function to show usage instructions +show_usage_instructions() { + echo + echo "=== NFS/SMB Host Setup Complete ===" + echo + echo "NFS and SMB servers are now running on this host with media directory: ${NFS_MEDIA_PATH}" + echo + echo "Access methods:" + echo "1. NFS (for Kubernetes): Use setup-nfs-k8s.sh to register with cluster" + echo "2. SMB/CIFS (for Windows): \\\\${NFS_HOST}\\media" + echo + echo "To add media files:" + echo "- Copy directly to: ${NFS_MEDIA_PATH}" + echo "- Or mount SMB share from Windows and copy there" + echo + echo "Windows SMB mount:" + echo "- Open File Explorer" + echo "- Map network drive to: \\\\${NFS_HOST}\\media" + echo "- Or use: \\\\$(hostname -I | awk '{print $1}')\\media" + echo + echo "To verify services:" + echo "- NFS: showmount -e ${NFS_HOST}" + echo "- SMB: smbclient -L ${NFS_HOST} -N" + echo "- Status: systemctl status nfs-server smbd" + echo + echo "Current NFS exports:" + sudo exportfs -v + echo +} + +# Main execution +main() { + check_host + install_nfs_server + create_media_directory + configure_nfs_exports + start_nfs_services + configure_smb_sharing + start_smb_services + test_nfs_setup + show_usage_instructions +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/nfs/storage-class.yaml b/nfs/storage-class.yaml new file mode 100644 index 0000000..3490dbc --- /dev/null +++ b/nfs/storage-class.yaml @@ -0,0 +1,10 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: nfs +provisioner: nfs +parameters: + server: {{ .host }} + path: {{ .mediaPath }} +reclaimPolicy: Retain +allowVolumeExpansion: true diff --git a/node-feature-discovery/crds.yaml b/node-feature-discovery/crds.yaml new file mode 100644 index 0000000..9f62da6 --- /dev/null +++ b/node-feature-discovery/crds.yaml @@ -0,0 +1,711 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.3 + name: nodefeatures.nfd.k8s-sigs.io +spec: + group: nfd.k8s-sigs.io + names: + kind: NodeFeature + listKind: NodeFeatureList + plural: nodefeatures + singular: nodefeature + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + NodeFeature resource holds the features discovered for one node in the + cluster. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Specification of the NodeFeature, containing features discovered + for a node. + properties: + features: + description: Features is the full "raw" features data that has been + discovered. + properties: + attributes: + additionalProperties: + description: AttributeFeatureSet is a set of features having + string value. + properties: + elements: + additionalProperties: + type: string + description: Individual features of the feature set. + type: object + required: + - elements + type: object + description: Attributes contains all the attribute-type features + of the node. + type: object + flags: + additionalProperties: + description: FlagFeatureSet is a set of simple features only + containing names without values. + properties: + elements: + additionalProperties: + description: |- + Nil is a dummy empty struct for protobuf compatibility. + NOTE: protobuf definitions have been removed but this is kept for API compatibility. + type: object + description: Individual features of the feature set. + type: object + required: + - elements + type: object + description: Flags contains all the flag-type features of the + node. + type: object + instances: + additionalProperties: + description: InstanceFeatureSet is a set of features each of + which is an instance having multiple attributes. + properties: + elements: + description: Individual features of the feature set. + items: + description: InstanceFeature represents one instance of + a complex features, e.g. a device. + properties: + attributes: + additionalProperties: + type: string + description: Attributes of the instance feature. + type: object + required: + - attributes + type: object + type: array + required: + - elements + type: object + description: Instances contains all the instance-type features + of the node. + type: object + type: object + labels: + additionalProperties: + type: string + description: Labels is the set of node labels that are requested to + be created. + type: object + type: object + required: + - spec + type: object + served: true + storage: true +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.3 + name: nodefeaturegroups.nfd.k8s-sigs.io +spec: + group: nfd.k8s-sigs.io + names: + kind: NodeFeatureGroup + listKind: NodeFeatureGroupList + plural: nodefeaturegroups + shortNames: + - nfg + singular: nodefeaturegroup + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: NodeFeatureGroup resource holds Node pools by featureGroup + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the rules to be evaluated. + properties: + featureGroupRules: + description: List of rules to evaluate to determine nodes that belong + in this group. + items: + description: GroupRule defines a rule for nodegroup filtering. + properties: + matchAny: + description: MatchAny specifies a list of matchers one of which + must match. + items: + description: MatchAnyElem specifies one sub-matcher of MatchAny. + properties: + matchFeatures: + description: MatchFeatures specifies a set of matcher + terms all of which must match. + items: + description: |- + FeatureMatcherTerm defines requirements against one feature set. All + requirements (specified as MatchExpressions) are evaluated against each + element in the feature set. + properties: + feature: + description: Feature is the name of the feature + set to match against. + type: string + matchExpressions: + additionalProperties: + description: |- + MatchExpression specifies an expression to evaluate against a set of input + values. It contains an operator that is applied when matching the input and + an array of values that the operator evaluates the input against. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: |- + Value is the list of values that the operand evaluates the input + against. Value should be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly one element if the + operator is Gt or Lt and exactly two elements if the operator is GtLt. + In other cases Value should contain at least one element. + items: + type: string + type: array + required: + - op + type: object + description: |- + MatchExpressions is the set of per-element expressions evaluated. These + match against the value of the specified elements. + type: object + matchName: + description: |- + MatchName in an expression that is matched against the name of each + element in the feature set. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: |- + Value is the list of values that the operand evaluates the input + against. Value should be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly one element if the + operator is Gt or Lt and exactly two elements if the operator is GtLt. + In other cases Value should contain at least one element. + items: + type: string + type: array + required: + - op + type: object + required: + - feature + type: object + type: array + required: + - matchFeatures + type: object + type: array + matchFeatures: + description: MatchFeatures specifies a set of matcher terms + all of which must match. + items: + description: |- + FeatureMatcherTerm defines requirements against one feature set. All + requirements (specified as MatchExpressions) are evaluated against each + element in the feature set. + properties: + feature: + description: Feature is the name of the feature set to + match against. + type: string + matchExpressions: + additionalProperties: + description: |- + MatchExpression specifies an expression to evaluate against a set of input + values. It contains an operator that is applied when matching the input and + an array of values that the operator evaluates the input against. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: |- + Value is the list of values that the operand evaluates the input + against. Value should be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly one element if the + operator is Gt or Lt and exactly two elements if the operator is GtLt. + In other cases Value should contain at least one element. + items: + type: string + type: array + required: + - op + type: object + description: |- + MatchExpressions is the set of per-element expressions evaluated. These + match against the value of the specified elements. + type: object + matchName: + description: |- + MatchName in an expression that is matched against the name of each + element in the feature set. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: |- + Value is the list of values that the operand evaluates the input + against. Value should be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly one element if the + operator is Gt or Lt and exactly two elements if the operator is GtLt. + In other cases Value should contain at least one element. + items: + type: string + type: array + required: + - op + type: object + required: + - feature + type: object + type: array + name: + description: Name of the rule. + type: string + required: + - name + type: object + type: array + required: + - featureGroupRules + type: object + status: + description: |- + Status of the NodeFeatureGroup after the most recent evaluation of the + specification. + properties: + nodes: + description: Nodes is a list of FeatureGroupNode in the cluster that + match the featureGroupRules + items: + properties: + name: + description: Name of the node. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.3 + name: nodefeaturerules.nfd.k8s-sigs.io +spec: + group: nfd.k8s-sigs.io + names: + kind: NodeFeatureRule + listKind: NodeFeatureRuleList + plural: nodefeaturerules + shortNames: + - nfr + singular: nodefeaturerule + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + NodeFeatureRule resource specifies a configuration for feature-based + customization of node objects, such as node labeling. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the rules to be evaluated. + properties: + rules: + description: Rules is a list of node customization rules. + items: + description: Rule defines a rule for node customization such as + labeling. + properties: + annotations: + additionalProperties: + type: string + description: Annotations to create if the rule matches. + type: object + extendedResources: + additionalProperties: + type: string + description: ExtendedResources to create if the rule matches. + type: object + labels: + additionalProperties: + type: string + description: Labels to create if the rule matches. + type: object + labelsTemplate: + description: |- + LabelsTemplate specifies a template to expand for dynamically generating + multiple labels. Data (after template expansion) must be keys with an + optional value ([=]) separated by newlines. + type: string + matchAny: + description: MatchAny specifies a list of matchers one of which + must match. + items: + description: MatchAnyElem specifies one sub-matcher of MatchAny. + properties: + matchFeatures: + description: MatchFeatures specifies a set of matcher + terms all of which must match. + items: + description: |- + FeatureMatcherTerm defines requirements against one feature set. All + requirements (specified as MatchExpressions) are evaluated against each + element in the feature set. + properties: + feature: + description: Feature is the name of the feature + set to match against. + type: string + matchExpressions: + additionalProperties: + description: |- + MatchExpression specifies an expression to evaluate against a set of input + values. It contains an operator that is applied when matching the input and + an array of values that the operator evaluates the input against. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: |- + Value is the list of values that the operand evaluates the input + against. Value should be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly one element if the + operator is Gt or Lt and exactly two elements if the operator is GtLt. + In other cases Value should contain at least one element. + items: + type: string + type: array + required: + - op + type: object + description: |- + MatchExpressions is the set of per-element expressions evaluated. These + match against the value of the specified elements. + type: object + matchName: + description: |- + MatchName in an expression that is matched against the name of each + element in the feature set. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: |- + Value is the list of values that the operand evaluates the input + against. Value should be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly one element if the + operator is Gt or Lt and exactly two elements if the operator is GtLt. + In other cases Value should contain at least one element. + items: + type: string + type: array + required: + - op + type: object + required: + - feature + type: object + type: array + required: + - matchFeatures + type: object + type: array + matchFeatures: + description: MatchFeatures specifies a set of matcher terms + all of which must match. + items: + description: |- + FeatureMatcherTerm defines requirements against one feature set. All + requirements (specified as MatchExpressions) are evaluated against each + element in the feature set. + properties: + feature: + description: Feature is the name of the feature set to + match against. + type: string + matchExpressions: + additionalProperties: + description: |- + MatchExpression specifies an expression to evaluate against a set of input + values. It contains an operator that is applied when matching the input and + an array of values that the operator evaluates the input against. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: |- + Value is the list of values that the operand evaluates the input + against. Value should be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly one element if the + operator is Gt or Lt and exactly two elements if the operator is GtLt. + In other cases Value should contain at least one element. + items: + type: string + type: array + required: + - op + type: object + description: |- + MatchExpressions is the set of per-element expressions evaluated. These + match against the value of the specified elements. + type: object + matchName: + description: |- + MatchName in an expression that is matched against the name of each + element in the feature set. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: |- + Value is the list of values that the operand evaluates the input + against. Value should be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly one element if the + operator is Gt or Lt and exactly two elements if the operator is GtLt. + In other cases Value should contain at least one element. + items: + type: string + type: array + required: + - op + type: object + required: + - feature + type: object + type: array + name: + description: Name of the rule. + type: string + taints: + description: Taints to create if the rule matches. + items: + description: |- + The node this Taint is attached to has the "effect" on + any pod that does not tolerate the Taint. + properties: + effect: + description: |- + Required. The effect of the taint on pods + that do not tolerate the taint. + Valid effects are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Required. The taint key to be applied to + a node. + type: string + timeAdded: + description: |- + TimeAdded represents the time at which the taint was added. + It is only written for NoExecute taints. + format: date-time + type: string + value: + description: The taint value corresponding to the taint + key. + type: string + required: + - effect + - key + type: object + type: array + vars: + additionalProperties: + type: string + description: |- + Vars is the variables to store if the rule matches. Variables do not + directly inflict any changes in the node object. However, they can be + referenced from other rules enabling more complex rule hierarchies, + without exposing intermediary output values as labels. + type: object + varsTemplate: + description: |- + VarsTemplate specifies a template to expand for dynamically generating + multiple variables. Data (after template expansion) must be keys with an + optional value ([=]) separated by newlines. + type: string + required: + - name + type: object + type: array + required: + - rules + type: object + required: + - spec + type: object + served: true + storage: true diff --git a/node-feature-discovery/daemonset.yaml b/node-feature-discovery/daemonset.yaml new file mode 100644 index 0000000..3810914 --- /dev/null +++ b/node-feature-discovery/daemonset.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: node-feature-discovery-worker + namespace: node-feature-discovery +spec: + selector: + matchLabels: + name: node-feature-discovery-worker + template: + metadata: + labels: + name: node-feature-discovery-worker + spec: + serviceAccountName: node-feature-discovery + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: worker + image: registry.k8s.io/nfd/node-feature-discovery:v0.17.3 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + readOnlyRootFilesystem: true + runAsNonRoot: true + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + resources: + limits: + memory: 512Mi + requests: + cpu: 5m + memory: 64Mi + command: + - "nfd-worker" + args: + - "-metrics=8081" + - "-grpc-health=8082" + ports: + - containerPort: 8081 + name: metrics + - containerPort: 8082 + name: health + volumeMounts: + - name: host-boot + mountPath: "/host-boot" + readOnly: true + - name: host-os-release + mountPath: "/host-etc/os-release" + readOnly: true + - name: host-sys + mountPath: "/host-sys" + readOnly: true + - name: host-usr-lib + mountPath: "/host-usr/lib" + readOnly: true + - name: host-lib + mountPath: "/host-lib" + readOnly: true + - name: host-proc-swaps + mountPath: "/host-proc/swaps" + readOnly: true + volumes: + - name: host-boot + hostPath: + path: "/boot" + - name: host-os-release + hostPath: + path: "/etc/os-release" + - name: host-sys + hostPath: + path: "/sys" + - name: host-usr-lib + hostPath: + path: "/usr/lib" + - name: host-lib + hostPath: + path: "/lib" + - name: host-proc-swaps + hostPath: + path: "/proc/swaps" \ No newline at end of file diff --git a/node-feature-discovery/install.sh b/node-feature-discovery/install.sh new file mode 100755 index 0000000..4a7d6bb --- /dev/null +++ b/node-feature-discovery/install.sh @@ -0,0 +1,51 @@ +#!/bin/bash +set -e +set -o pipefail + +# Ensure WILD_INSTANCE is set +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +# Ensure WILD_API_DATA_DIR is set +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +# Ensure KUBECONFIG is set +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +NFD_DIR="${INSTANCE_DIR}/apps/node-feature-discovery" + +echo "🔧 === Setting up Node Feature Discovery ===" +echo "" + +# Templates should already be compiled +echo "📦 Using pre-compiled Node Feature Discovery templates..." +if [ ! -f "${NFD_DIR}/kustomization.yaml" ]; then + echo "❌ ERROR: Compiled templates not found at ${NFD_DIR}/kustomization.yaml" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "🚀 Deploying Node Feature Discovery..." +kubectl apply -k "${NFD_DIR}/" + +echo "⏳ Waiting for Node Feature Discovery DaemonSet to be ready..." +kubectl rollout status daemonset/node-feature-discovery-worker -n node-feature-discovery --timeout=300s + +echo "" +echo "✅ Node Feature Discovery installed successfully" +echo "" +echo "💡 To verify the installation:" +echo " kubectl get pods -n node-feature-discovery" +echo " kubectl get nodes --show-labels | grep feature.node.kubernetes.io" +echo "" +echo "🎮 GPU nodes should now be labeled with GPU device information:" +echo " kubectl get nodes --show-labels | grep pci-10de" diff --git a/node-feature-discovery/kustomization.yaml b/node-feature-discovery/kustomization.yaml new file mode 100644 index 0000000..7d32cf0 --- /dev/null +++ b/node-feature-discovery/kustomization.yaml @@ -0,0 +1,14 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: node-feature-discovery +labels: + - pairs: + app.kubernetes.io/name: node-feature-discovery + managedBy: kustomize + partOf: wild-cloud +resources: + - namespace.yaml + - crds.yaml + - rbac.yaml + - daemonset.yaml + - master.yaml \ No newline at end of file diff --git a/node-feature-discovery/manifest.yaml b/node-feature-discovery/manifest.yaml new file mode 100644 index 0000000..b33a73e --- /dev/null +++ b/node-feature-discovery/manifest.yaml @@ -0,0 +1,7 @@ +name: node-feature-discovery +is: node-feature-discovery +description: Detects hardware features available on each node +version: v0.17.3 +namespace: node-feature-discovery +deploymentName: node-feature-discovery-master +category: infrastructure diff --git a/node-feature-discovery/master.yaml b/node-feature-discovery/master.yaml new file mode 100644 index 0000000..c2fff33 --- /dev/null +++ b/node-feature-discovery/master.yaml @@ -0,0 +1,49 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: node-feature-discovery-master + namespace: node-feature-discovery +spec: + replicas: 1 + selector: + matchLabels: + name: node-feature-discovery-master + template: + metadata: + labels: + name: node-feature-discovery-master + spec: + serviceAccountName: node-feature-discovery + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: master + image: registry.k8s.io/nfd/node-feature-discovery:v0.17.3 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + readOnlyRootFilesystem: true + runAsNonRoot: true + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + command: + - "nfd-master" + args: + - "-metrics=8081" + - "-grpc-health=8082" + ports: + - containerPort: 8081 + name: metrics + - containerPort: 8082 + name: health + resources: + requests: + cpu: 10m + memory: 64Mi + limits: + memory: 128Mi \ No newline at end of file diff --git a/node-feature-discovery/namespace.yaml b/node-feature-discovery/namespace.yaml new file mode 100644 index 0000000..75867e7 --- /dev/null +++ b/node-feature-discovery/namespace.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: node-feature-discovery + labels: + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged \ No newline at end of file diff --git a/node-feature-discovery/rbac.yaml b/node-feature-discovery/rbac.yaml new file mode 100644 index 0000000..12359d6 --- /dev/null +++ b/node-feature-discovery/rbac.yaml @@ -0,0 +1,55 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: node-feature-discovery + namespace: node-feature-discovery +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: node-feature-discovery +rules: +- apiGroups: + - "" + resources: + - nodes + - nodes/status + verbs: + - get + - patch + - update + - list +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - nfd.k8s-sigs.io + resources: + - nodefeatures + - nodefeaturerules + - nodefeaturegroups + verbs: + - get + - list + - watch + - create + - update + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: node-feature-discovery +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: node-feature-discovery +subjects: +- kind: ServiceAccount + name: node-feature-discovery + namespace: node-feature-discovery \ No newline at end of file diff --git a/nvidia-device-plugin/README.md b/nvidia-device-plugin/README.md new file mode 100644 index 0000000..fe05b13 --- /dev/null +++ b/nvidia-device-plugin/README.md @@ -0,0 +1,98 @@ +# NVIDIA Device Plugin + +The NVIDIA Device Plugin for Kubernetes enables GPU scheduling and resource management on nodes with NVIDIA GPUs. + +## Overview + +This service deploys the official NVIDIA Device Plugin as a DaemonSet that: +- Discovers NVIDIA GPUs on worker nodes +- Labels nodes with GPU product information (e.g., `nvidia.com/gpu.product=GeForce-RTX-4090`) +- Advertises GPU resources (`nvidia.com/gpu`) to the Kubernetes scheduler +- Enables pods to request GPU resources + +## Prerequisites + +Before installing the NVIDIA Device Plugin, ensure that: + +1. **NVIDIA Drivers** are installed (>= 384.81) +2. **nvidia-container-toolkit** is installed (>= 1.7.0) +3. **nvidia-container-runtime** is configured as the default container runtime +4. Worker nodes have NVIDIA GPUs + +### Talos Linux Requirements + +For Talos Linux nodes, you need: +- NVIDIA drivers extension in the Talos schematic +- nvidia-container-toolkit extension +- Proper container runtime configuration + +## Installation + +```bash +# Configure and install the service +wild-cluster-services-configure nvidia-device-plugin +wild-cluster-install nvidia-device-plugin +``` + +## Verification + +After installation, verify the plugin is working: + +```bash +# Check plugin pods are running +kubectl get pods -n kube-system | grep nvidia + +# Verify GPU resources are advertised +kubectl get nodes -o json | jq '.items[].status.capacity | select(has("nvidia.com/gpu"))' + +# Check GPU node labels +kubectl get nodes --show-labels | grep nvidia +``` + +## Usage in Applications + +Once installed, applications can request GPU resources: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gpu-app +spec: + template: + spec: + containers: + - name: app + image: nvidia/cuda:latest + resources: + requests: + nvidia.com/gpu: 1 + limits: + nvidia.com/gpu: 1 +``` + +## Troubleshooting + +### Plugin Not Starting +- Verify NVIDIA drivers are installed on worker nodes +- Check that nvidia-container-toolkit is properly configured +- Ensure worker nodes are not tainted in a way that prevents scheduling + +### No GPU Resources Advertised +- Check plugin logs: `kubectl logs -n kube-system -l name=nvidia-device-plugin-ds` +- Verify NVIDIA runtime is the default container runtime +- Ensure GPUs are detected by the driver: check node logs for GPU detection messages + +## Configuration + +The plugin uses the following configuration: +- **Image**: `nvcr.io/nvidia/k8s-device-plugin:v0.17.1` +- **Namespace**: `kube-system` +- **Priority Class**: `system-node-critical` +- **Tolerations**: Schedules on nodes with `nvidia.com/gpu` taint + +## References + +- [Official NVIDIA Device Plugin Repository](https://github.com/NVIDIA/k8s-device-plugin) +- [Kubernetes GPU Scheduling Documentation](https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/) +- [NVIDIA Container Toolkit Documentation](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/) \ No newline at end of file diff --git a/nvidia-device-plugin/daemonset.yaml b/nvidia-device-plugin/daemonset.yaml new file mode 100644 index 0000000..e4b2ec5 --- /dev/null +++ b/nvidia-device-plugin/daemonset.yaml @@ -0,0 +1,91 @@ +# NVIDIA Device Plugin DaemonSet +# Based on official manifest from: https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.17.1/deployments/static/nvidia-device-plugin.yml +# Licensed under the Apache License, Version 2.0 + +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: nvidia-device-plugin-daemonset + namespace: kube-system + labels: + app.kubernetes.io/name: nvidia-device-plugin + app.kubernetes.io/component: device-plugin + managedBy: kustomize + partOf: wild-cloud +spec: + selector: + matchLabels: + name: nvidia-device-plugin-ds + updateStrategy: + type: RollingUpdate + template: + metadata: + labels: + name: nvidia-device-plugin-ds + app.kubernetes.io/name: nvidia-device-plugin + app.kubernetes.io/component: device-plugin + spec: + runtimeClassName: nvidia + tolerations: + - key: nvidia.com/gpu + operator: Exists + effect: NoSchedule + - key: CriticalAddonsOnly + operator: Exists + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: feature.node.kubernetes.io/pci-0300_10de.present + operator: In + values: + - "true" + # Mark this pod as a critical add-on; when enabled, the critical add-on + # scheduler reserves resources for critical add-on pods so that they can + # be rescheduled after a failure. + # See https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/ + priorityClassName: "system-node-critical" + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1 + name: nvidia-device-plugin-ctr + env: + - name: MPS_ROOT + value: /run/nvidia/mps + - name: NVIDIA_VISIBLE_DEVICES + value: all + - name: NVIDIA_DRIVER_CAPABILITIES + value: compute,utility + - name: FAIL_ON_INIT_ERROR + value: "false" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + volumeMounts: + - name: device-plugin + mountPath: /var/lib/kubelet/device-plugins + - name: mps-shm + mountPath: /dev/shm + - name: mps-root + mountPath: /mps + - name: cdi-root + mountPath: /var/run/cdi + volumes: + - name: device-plugin + hostPath: + path: /var/lib/kubelet/device-plugins + - name: mps-root + hostPath: + path: /run/nvidia/mps + type: DirectoryOrCreate + - name: mps-shm + hostPath: + path: /run/nvidia/mps/shm + - name: cdi-root + hostPath: + path: /var/run/cdi + type: DirectoryOrCreate \ No newline at end of file diff --git a/nvidia-device-plugin/install.sh b/nvidia-device-plugin/install.sh new file mode 100755 index 0000000..cad9f6d --- /dev/null +++ b/nvidia-device-plugin/install.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set -e +set -o pipefail + +# Ensure WILD_INSTANCE is set +if [ -z "${WILD_INSTANCE}" ]; then + echo "❌ ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +# Ensure WILD_API_DATA_DIR is set +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "❌ ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +# Ensure KUBECONFIG is set +if [ -z "${KUBECONFIG}" ]; then + echo "❌ ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +NVIDIA_PLUGIN_DIR="${INSTANCE_DIR}/apps/nvidia-device-plugin" + +echo "🎮 === Setting up NVIDIA Device Plugin ===" +echo "" + +# Check if we have NVIDIA GPUs in the cluster +echo "🔍 Checking for worker nodes in the cluster..." + +# Check if any worker nodes exist (device plugin only runs on worker nodes) +WORKER_NODES=$(kubectl get nodes --selector='!node-role.kubernetes.io/control-plane' -o name | wc -l) +if [ "$WORKER_NODES" -eq 0 ]; then + echo "❌ ERROR: No worker nodes found in cluster. NVIDIA Device Plugin requires worker nodes." + exit 1 +fi + +echo "✅ Found $WORKER_NODES worker node(s)" +echo "" + +# Templates should already be compiled +echo "📦 Using pre-compiled NVIDIA Device Plugin templates..." +if [ ! -f "${NVIDIA_PLUGIN_DIR}/kustomization.yaml" ]; then + echo "❌ ERROR: Compiled templates not found at ${NVIDIA_PLUGIN_DIR}/kustomization.yaml" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "🚀 Deploying NVIDIA Device Plugin..." +kubectl apply -k ${NVIDIA_PLUGIN_DIR}/ + +echo "⏳ Waiting for NVIDIA Device Plugin DaemonSet to be ready..." +kubectl rollout status daemonset/nvidia-device-plugin-daemonset -n kube-system --timeout=120s + +echo "" +echo "✅ NVIDIA Device Plugin installed successfully" +echo "" +echo "💡 To verify the installation:" +echo " kubectl get pods -n kube-system | grep nvidia" +echo " kubectl get nodes -o json | jq '.items[].status.capacity | select(has(\"nvidia.com/gpu\"))'" +echo "" +echo "🎮 GPU nodes should now be labeled with GPU product information:" +echo " kubectl get nodes --show-labels | grep nvidia" +echo "" diff --git a/nvidia-device-plugin/kustomization.yaml b/nvidia-device-plugin/kustomization.yaml new file mode 100644 index 0000000..c402fd6 --- /dev/null +++ b/nvidia-device-plugin/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: kube-system +resources: + - daemonset.yaml + - runtimeclass.yaml +labels: + - pairs: + app.kubernetes.io/name: nvidia-device-plugin + app.kubernetes.io/component: device-plugin + managedBy: kustomize + partOf: wild-cloud \ No newline at end of file diff --git a/nvidia-device-plugin/manifest.yaml b/nvidia-device-plugin/manifest.yaml new file mode 100644 index 0000000..dc16b1d --- /dev/null +++ b/nvidia-device-plugin/manifest.yaml @@ -0,0 +1,9 @@ +name: nvidia-device-plugin +is: nvidia-device-plugin +description: NVIDIA device plugin for Kubernetes +version: v0.17.1 +namespace: kube-system +deploymentName: nvidia-device-plugin-daemonset +category: infrastructure +requires: + - name: node-feature-discovery diff --git a/nvidia-device-plugin/runtimeclass.yaml b/nvidia-device-plugin/runtimeclass.yaml new file mode 100644 index 0000000..05d51eb --- /dev/null +++ b/nvidia-device-plugin/runtimeclass.yaml @@ -0,0 +1,5 @@ +apiVersion: node.k8s.io/v1 +kind: RuntimeClass +metadata: + name: nvidia +handler: nvidia \ No newline at end of file diff --git a/postgres/manifest.yaml b/postgres/manifest.yaml index 3e789a5..f042228 100644 --- a/postgres/manifest.yaml +++ b/postgres/manifest.yaml @@ -1,6 +1,5 @@ name: postgres is: postgres -install: true description: PostgreSQL is a powerful, open source object-relational database system. version: 1.0.0 icon: https://www.postgresql.org/media/img/about/press/elephant.png diff --git a/redis/manifest.yaml b/redis/manifest.yaml index 17a12b4..a9b57cd 100644 --- a/redis/manifest.yaml +++ b/redis/manifest.yaml @@ -1,6 +1,5 @@ name: redis is: redis -install: true description: Redis is an open source, in-memory data structure store, used as a database, cache and message broker. version: 1.0.0 icon: https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/redis.svg diff --git a/smtp/README.md b/smtp/README.md new file mode 100644 index 0000000..3794177 --- /dev/null +++ b/smtp/README.md @@ -0,0 +1,32 @@ +# SMTP Configuration Service + +This is a config-only package that other apps reference via the dependency system. It does not deploy any Kubernetes resources. + +## Overview + +The SMTP package configures global SMTP settings that Wild Cloud applications use for sending transactional emails (password resets, invitations, notifications). + +## Usage + +Apps that need SMTP add it as a dependency in their manifest: + +```yaml +requires: + - name: smtp +``` + +Then reference SMTP config in their defaultConfig: + +```yaml +defaultConfig: + smtpHost: "{{ .apps.smtp.host }}" + smtpPort: "{{ .apps.smtp.port }}" +``` + +## Supported Providers + +- **AWS SES**: Use your Access Key ID as user and Secret Access Key as password +- **Gmail/Google Workspace**: Use your email as user and an App Password as password +- **SendGrid**: Use `apikey` as user and your API key as password +- **Mailgun**: Use your Mailgun username and password +- **Other SMTP providers**: Use your standard SMTP credentials diff --git a/smtp/manifest.yaml b/smtp/manifest.yaml new file mode 100644 index 0000000..8832f03 --- /dev/null +++ b/smtp/manifest.yaml @@ -0,0 +1,11 @@ +name: smtp +is: smtp +description: SMTP relay service for cluster applications +category: infrastructure +defaultConfig: + host: "" + port: "465" + user: "" + from: "no-reply@{{ .cloud.domain }}" + tls: "true" + startTls: "true" diff --git a/snapshot-controller/README.md b/snapshot-controller/README.md new file mode 100644 index 0000000..e8c3e60 --- /dev/null +++ b/snapshot-controller/README.md @@ -0,0 +1,49 @@ +# Snapshot Controller + +The snapshot controller is a Kubernetes component that manages the lifecycle of volume snapshots. It works in conjunction with CSI drivers to enable backup and restore functionality for persistent volumes. + +## Architecture + +The snapshot functionality in Kubernetes is split into two components: +- **Snapshot Controller** (this service): Cluster-wide controller that manages VolumeSnapshot resources +- **CSI Snapshotter Sidecar**: Deployed with each CSI driver (e.g., Longhorn) to perform actual snapshot operations + +## Installation + +This service is installed automatically by Wild Cloud when setting up cluster services. It deploys: +- The snapshot-controller deployment (2 replicas with leader election) +- Required RBAC permissions +- VolumeSnapshot CRDs (if not already present) + +## Dependencies + +- Requires a CSI driver with snapshot support (e.g., Longhorn) +- Requires VolumeSnapshot CRDs (included with this service) + +## Verification + +After installation, verify the controller is running: +```bash +kubectl get pods -n kube-system | grep snapshot-controller +kubectl api-resources | grep snapshot +``` + +## Usage + +Once installed, you can create VolumeSnapshots for any PVC managed by a CSI driver: +```yaml +apiVersion: snapshot.storage.k8s.io/v1 +kind: VolumeSnapshot +metadata: + name: my-snapshot + namespace: default +spec: + volumeSnapshotClassName: longhorn-snapshot-class + source: + persistentVolumeClaimName: my-pvc +``` + +## References + +- [Kubernetes CSI Snapshot Documentation](https://kubernetes-csi.github.io/docs/snapshot-restore-feature.html) +- [External Snapshotter GitHub](https://github.com/kubernetes-csi/external-snapshotter) \ No newline at end of file diff --git a/snapshot-controller/deployment.yaml b/snapshot-controller/deployment.yaml new file mode 100644 index 0000000..731ee84 --- /dev/null +++ b/snapshot-controller/deployment.yaml @@ -0,0 +1,36 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: snapshot-controller + namespace: kube-system +spec: + replicas: 2 + selector: + matchLabels: + app.kubernetes.io/name: snapshot-controller + minReadySeconds: 35 + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/name: snapshot-controller + spec: + serviceAccountName: snapshot-controller + containers: + - name: snapshot-controller + image: registry.k8s.io/sig-storage/snapshot-controller:v8.1.0 + args: + - "--v=5" + - "--leader-election=true" + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 300Mi + requests: + cpu: 10m + memory: 20Mi \ No newline at end of file diff --git a/snapshot-controller/install.sh b/snapshot-controller/install.sh new file mode 100755 index 0000000..b2c03a7 --- /dev/null +++ b/snapshot-controller/install.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -e +set -o pipefail + +# Ensure WILD_INSTANCE is set +if [ -z "${WILD_INSTANCE}" ]; then + echo "❌ ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +# Ensure WILD_API_DATA_DIR is set +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "❌ ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +# Ensure KUBECONFIG is set +if [ -z "${KUBECONFIG}" ]; then + echo "❌ ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +SNAPSHOT_CONTROLLER_DIR="${INSTANCE_DIR}/apps/snapshot-controller" + +echo "🔧 === Setting up Snapshot Controller ===" +echo "" + +# Templates should already be compiled +echo "📦 Using pre-compiled snapshot-controller templates..." +if [ ! -f "${SNAPSHOT_CONTROLLER_DIR}/kustomization.yaml" ]; then + echo "❌ ERROR: Compiled templates not found at ${SNAPSHOT_CONTROLLER_DIR}/kustomization.yaml" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "🚀 Deploying Snapshot Controller..." +kubectl apply -k ${SNAPSHOT_CONTROLLER_DIR}/ + +echo "⏳ Waiting for snapshot-controller to be ready..." +kubectl wait --for=condition=available --timeout=300s deployment/snapshot-controller -n kube-system || true + +# Check if VolumeSnapshot CRDs are installed +echo "✔️ Checking VolumeSnapshot CRDs..." +kubectl api-resources | grep -q "snapshot.storage.k8s.io" && echo "✅ VolumeSnapshot CRDs found" || echo "⚠️ VolumeSnapshot CRDs not found" + +echo "" +echo "✅ Snapshot Controller installed successfully" +echo "" +echo "💡 To verify the installation:" +echo " kubectl get pods -n kube-system | grep snapshot-controller" +echo " kubectl get crd | grep snapshot" +echo "" +echo "📘 To create a snapshot:" +echo " kubectl apply -f - < 0 + required: + - source + type: object + status: + description: |- + status represents the current information of a snapshot. + Consumers must verify binding between VolumeSnapshot and + VolumeSnapshotContent objects is successful (by validating that both + VolumeSnapshot and VolumeSnapshotContent point at each other) before + using this object. + properties: + boundVolumeSnapshotContentName: + description: |- + boundVolumeSnapshotContentName is the name of the VolumeSnapshotContent + object to which this VolumeSnapshot object intends to bind to. + If not specified, it indicates that the VolumeSnapshot object has not been + successfully bound to a VolumeSnapshotContent object yet. + NOTE: To avoid possible security issues, consumers must verify binding between + VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that + both VolumeSnapshot and VolumeSnapshotContent point at each other) before using + this object. + type: string + creationTime: + description: |- + creationTime is the timestamp when the point-in-time snapshot is taken + by the underlying storage system. + In dynamic snapshot creation case, this field will be filled in by the + snapshot controller with the "creation_time" value returned from CSI + "CreateSnapshot" gRPC call. + For a pre-existing snapshot, this field will be filled with the "creation_time" + value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. + If not specified, it may indicate that the creation time of the snapshot is unknown. + format: date-time + type: string + error: + description: |- + error is the last observed error during snapshot creation, if any. + This field could be helpful to upper level controllers(i.e., application controller) + to decide whether they should continue on waiting for the snapshot to be created + based on the type of error reported. + The snapshot controller will keep retrying when an error occurs during the + snapshot creation. Upon success, this error field will be cleared. + properties: + message: + description: |- + message is a string detailing the encountered error during snapshot + creation if specified. + NOTE: message may be logged, and it should not contain sensitive + information. + type: string + time: + description: time is the timestamp when the error was encountered. + format: date-time + type: string + type: object + readyToUse: + description: |- + readyToUse indicates if the snapshot is ready to be used to restore a volume. + In dynamic snapshot creation case, this field will be filled in by the + snapshot controller with the "ready_to_use" value returned from CSI + "CreateSnapshot" gRPC call. + For a pre-existing snapshot, this field will be filled with the "ready_to_use" + value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, + otherwise, this field will be set to "True". + If not specified, it means the readiness of a snapshot is unknown. + type: boolean + restoreSize: + type: string + description: |- + restoreSize represents the minimum size of volume required to create a volume + from this snapshot. + In dynamic snapshot creation case, this field will be filled in by the + snapshot controller with the "size_bytes" value returned from CSI + "CreateSnapshot" gRPC call. + For a pre-existing snapshot, this field will be filled with the "size_bytes" + value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. + When restoring a volume from this snapshot, the size of the volume MUST NOT + be smaller than the restoreSize if it is specified, otherwise the restoration will fail. + If not specified, it indicates that the size is unknown. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + volumeGroupSnapshotName: + description: |- + VolumeGroupSnapshotName is the name of the VolumeGroupSnapshot of which this + VolumeSnapshot is a part of. + type: string + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - description: Indicates if the snapshot is ready to be used to restore a volume. + jsonPath: .status.readyToUse + name: ReadyToUse + type: boolean + - description: If a new snapshot needs to be created, this contains the name of the source PVC from which this snapshot was (or will be) created. + jsonPath: .spec.source.persistentVolumeClaimName + name: SourcePVC + type: string + - description: If a snapshot already exists, this contains the name of the existing VolumeSnapshotContent object representing the existing snapshot. + jsonPath: .spec.source.volumeSnapshotContentName + name: SourceSnapshotContent + type: string + - description: Represents the minimum size of volume required to rehydrate from this snapshot. + jsonPath: .status.restoreSize + name: RestoreSize + type: string + - description: The name of the VolumeSnapshotClass requested by the VolumeSnapshot. + jsonPath: .spec.volumeSnapshotClassName + name: SnapshotClass + type: string + - description: Name of the VolumeSnapshotContent object to which the VolumeSnapshot object intends to bind to. Please note that verification of binding actually requires checking both VolumeSnapshot and VolumeSnapshotContent to ensure both are pointing at each other. Binding MUST be verified prior to usage of this object. + jsonPath: .status.boundVolumeSnapshotContentName + name: SnapshotContent + type: string + - description: Timestamp when the point-in-time snapshot was taken by the underlying storage system. + jsonPath: .status.creationTime + name: CreationTime + type: date + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + # This indicates the v1beta1 version of the custom resource is deprecated. + # API requests to this version receive a warning in the server response. + deprecated: true + # This overrides the default warning returned to clients making v1beta1 API requests. + deprecationWarning: "snapshot.storage.k8s.io/v1beta1 VolumeSnapshot is deprecated; use snapshot.storage.k8s.io/v1 VolumeSnapshot" + schema: + openAPIV3Schema: + description: VolumeSnapshot is a user's request for either creating a point-in-time snapshot of a persistent volume, or binding to a pre-existing snapshot. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + spec: + description: 'spec defines the desired characteristics of a snapshot requested by a user. More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots Required.' + properties: + source: + description: source specifies where a snapshot will be created from. This field is immutable after creation. Required. + properties: + persistentVolumeClaimName: + description: persistentVolumeClaimName specifies the name of the PersistentVolumeClaim object representing the volume from which a snapshot should be created. This PVC is assumed to be in the same namespace as the VolumeSnapshot object. This field should be set if the snapshot does not exists, and needs to be created. This field is immutable. + type: string + volumeSnapshotContentName: + description: volumeSnapshotContentName specifies the name of a pre-existing VolumeSnapshotContent object representing an existing volume snapshot. This field should be set if the snapshot already exists and only needs a representation in Kubernetes. This field is immutable. + type: string + type: object + volumeSnapshotClassName: + description: 'VolumeSnapshotClassName is the name of the VolumeSnapshotClass requested by the VolumeSnapshot. VolumeSnapshotClassName may be left nil to indicate that the default SnapshotClass should be used. A given cluster may have multiple default Volume SnapshotClasses: one default per CSI Driver. If a VolumeSnapshot does not specify a SnapshotClass, VolumeSnapshotSource will be checked to figure out what the associated CSI Driver is, and the default VolumeSnapshotClass associated with that CSI Driver will be used. If more than one VolumeSnapshotClass exist for a given CSI Driver and more than one have been marked as default, CreateSnapshot will fail and generate an event. Empty string is not allowed for this field.' + type: string + required: + - source + type: object + status: + description: status represents the current information of a snapshot. Consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object. + properties: + boundVolumeSnapshotContentName: + description: 'boundVolumeSnapshotContentName is the name of the VolumeSnapshotContent object to which this VolumeSnapshot object intends to bind to. If not specified, it indicates that the VolumeSnapshot object has not been successfully bound to a VolumeSnapshotContent object yet. NOTE: To avoid possible security issues, consumers must verify binding between VolumeSnapshot and VolumeSnapshotContent objects is successful (by validating that both VolumeSnapshot and VolumeSnapshotContent point at each other) before using this object.' + type: string + creationTime: + description: creationTime is the timestamp when the point-in-time snapshot is taken by the underlying storage system. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "creation_time" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "creation_time" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. If not specified, it may indicate that the creation time of the snapshot is unknown. + format: date-time + type: string + error: + description: error is the last observed error during snapshot creation, if any. This field could be helpful to upper level controllers(i.e., application controller) to decide whether they should continue on waiting for the snapshot to be created based on the type of error reported. The snapshot controller will keep retrying when an error occurs during the snapshot creation. Upon success, this error field will be cleared. + properties: + message: + description: 'message is a string detailing the encountered error during snapshot creation if specified. NOTE: message may be logged, and it should not contain sensitive information.' + type: string + time: + description: time is the timestamp when the error was encountered. + format: date-time + type: string + type: object + readyToUse: + description: readyToUse indicates if the snapshot is ready to be used to restore a volume. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "ready_to_use" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "ready_to_use" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, otherwise, this field will be set to "True". If not specified, it means the readiness of a snapshot is unknown. + type: boolean + restoreSize: + type: string + description: restoreSize represents the minimum size of volume required to create a volume from this snapshot. In dynamic snapshot creation case, this field will be filled in by the snapshot controller with the "size_bytes" value returned from CSI "CreateSnapshot" gRPC call. For a pre-existing snapshot, this field will be filled with the "size_bytes" value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. When restoring a volume from this snapshot, the size of the volume MUST NOT be smaller than the restoreSize if it is specified, otherwise the restoration will fail. If not specified, it indicates that the size is unknown. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + required: + - spec + type: object + served: false + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/traefik/README.md b/traefik/README.md new file mode 100644 index 0000000..85fed8a --- /dev/null +++ b/traefik/README.md @@ -0,0 +1,31 @@ +# Traefik + +- https://doc.traefik.io/traefik/providers/kubernetes-ingress/ + +Ingress RDs can be create for any service. The routes specificed in the Ingress are added automatically to the Traefik proxy. + +Traefik serves all incoming network traffic on ports 80 and 443 to their appropriate services based on the route. + +## Notes + +These kustomize templates were created with: + +```bash +helm-chart-to-kustomize traefik/traefik traefik traefik values.yaml +``` + +With values.yaml being: + +```yaml +ingressRoute: + dashboard: + enabled: true + matchRule: Host(`dashboard.localhost`) + entryPoints: + - web +providers: + kubernetesGateway: + enabled: true +gateway: + namespacePolicy: All +``` diff --git a/traefik/deployment.yaml b/traefik/deployment.yaml new file mode 100644 index 0000000..67bf58e --- /dev/null +++ b/traefik/deployment.yaml @@ -0,0 +1,145 @@ +--- +# Source: traefik/templates/deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: traefik + namespace: traefik + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm + annotations: +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + minReadySeconds: 0 + template: + metadata: + annotations: + prometheus.io/scrape: "true" + prometheus.io/path: "/metrics" + prometheus.io/port: "9100" + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm + spec: + serviceAccountName: traefik + automountServiceAccountToken: true + terminationGracePeriodSeconds: 60 + hostNetwork: false + containers: + - image: docker.io/traefik:v3.4.1 + imagePullPolicy: IfNotPresent + name: traefik + resources: + readinessProbe: + httpGet: + path: /ping + port: 8080 + scheme: HTTP + failureThreshold: 1 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + livenessProbe: + httpGet: + path: /ping + port: 8080 + scheme: HTTP + failureThreshold: 3 + initialDelaySeconds: 2 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 2 + lifecycle: + ports: + - name: metrics + containerPort: 9100 + protocol: TCP + - name: traefik + containerPort: 8080 + protocol: TCP + - name: web + containerPort: 8000 + protocol: TCP + - name: websecure + containerPort: 8443 + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - name: data + mountPath: /data + - name: tmp + mountPath: /tmp + - name: plugins-storage + mountPath: /plugins-storage + - name: crowdsec-bouncer-key + mountPath: /etc/traefik/crowdsec + readOnly: true + args: + - "--global.checkNewVersion" + - "--experimental.plugins.bouncer.moduleName=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" + - "--experimental.plugins.bouncer.version=v1.4.2" + - "--entryPoints.metrics.address=:9100/tcp" + - "--entryPoints.traefik.address=:8080/tcp" + - "--entryPoints.web.address=:8000/tcp" + - "--entryPoints.websecure.address=:8443/tcp" + - "--api.dashboard=true" + - "--ping=true" + - "--metrics.prometheus=true" + - "--metrics.prometheus.entrypoint=metrics" + - "--providers.kubernetescrd" + - "--providers.kubernetescrd.allowEmptyServices=true" + - "--providers.kubernetesingress" + - "--providers.kubernetesingress.allowEmptyServices=true" + - "--providers.kubernetesingress.ingressendpoint.publishedservice=traefik/traefik" + - "--providers.kubernetesgateway" + - "--providers.kubernetesgateway.statusaddress.service.name=traefik" + - "--providers.kubernetesgateway.statusaddress.service.namespace=traefik" + - "--entryPoints.websecure.http.tls=true" + - "--accesslog=true" + - "--accesslog.format=json" + - "--log.level=INFO" + + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + volumes: + - name: data + emptyDir: {} + - name: tmp + emptyDir: {} + - name: plugins-storage + emptyDir: {} + - name: crowdsec-bouncer-key + secret: + secretName: crowdsec-bouncer-secret + optional: true + securityContext: + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 diff --git a/traefik/gateway.yaml b/traefik/gateway.yaml new file mode 100644 index 0000000..9a42408 --- /dev/null +++ b/traefik/gateway.yaml @@ -0,0 +1,18 @@ +--- +# Source: traefik/templates/gateway.yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: traefik-gateway + namespace: traefik + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm +spec: + gatewayClassName: traefik + listeners: + - name: web + port: 8000 + protocol: HTTP diff --git a/traefik/gatewayclass.yaml b/traefik/gatewayclass.yaml new file mode 100644 index 0000000..69487c0 --- /dev/null +++ b/traefik/gatewayclass.yaml @@ -0,0 +1,13 @@ +--- +# Source: traefik/templates/gatewayclass.yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: traefik + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm +spec: + controllerName: traefik.io/gateway-controller diff --git a/traefik/ingressclass.yaml b/traefik/ingressclass.yaml new file mode 100644 index 0000000..b283328 --- /dev/null +++ b/traefik/ingressclass.yaml @@ -0,0 +1,15 @@ +--- +# Source: traefik/templates/ingressclass.yaml +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + annotations: + ingressclass.kubernetes.io/is-default-class: "true" + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm + name: traefik +spec: + controller: traefik.io/ingress-controller diff --git a/traefik/ingressroute.yaml b/traefik/ingressroute.yaml new file mode 100644 index 0000000..7682cb5 --- /dev/null +++ b/traefik/ingressroute.yaml @@ -0,0 +1,21 @@ +--- +# Source: traefik/templates/ingressroute.yaml +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: traefik-dashboard + namespace: traefik + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm +spec: + entryPoints: + - web + routes: + - match: Host(`dashboard.localhost`) + kind: Rule + services: + - kind: TraefikService + name: api@internal diff --git a/traefik/install.sh b/traefik/install.sh new file mode 100755 index 0000000..a27c078 --- /dev/null +++ b/traefik/install.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +TRAEFIK_DIR="${INSTANCE_DIR}/apps/traefik" + +echo "=== Setting up Traefik Ingress Controller ===" +echo "" + +echo "Verifying MetalLB is ready (required for Traefik LoadBalancer service)..." +kubectl wait --for=condition=Ready pod -l component=controller -n metallb-system --timeout=60s 2>/dev/null || { + echo "MetalLB controller not ready, but continuing with Traefik installation" + echo "Note: Traefik LoadBalancer service may not get external IP without MetalLB" +} + +echo "Installing Gateway API CRDs..." +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml + +echo "Installing Traefik CRDs..." +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml + +echo "Waiting for CRDs to be established..." +kubectl wait --for condition=established crd/gateways.gateway.networking.k8s.io --timeout=60s +kubectl wait --for condition=established crd/gatewayclasses.gateway.networking.k8s.io --timeout=60s +kubectl wait --for condition=established crd/ingressroutes.traefik.io --timeout=60s +kubectl wait --for condition=established crd/middlewares.traefik.io --timeout=60s + +echo "Using pre-compiled Traefik templates..." +if [ ! -f "${TRAEFIK_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${TRAEFIK_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Deploying Traefik..." +kubectl apply -k ${TRAEFIK_DIR}/ + +echo "Waiting for Traefik to be ready..." +kubectl wait --for=condition=Available deployment/traefik -n traefik --timeout=120s + +echo "" +echo "Traefik installed successfully" +echo "" +echo "To verify the installation:" +echo " kubectl get pods -n traefik" +echo " kubectl get svc -n traefik" +echo "" diff --git a/traefik/kustomization.yaml b/traefik/kustomization.yaml new file mode 100644 index 0000000..ae16533 --- /dev/null +++ b/traefik/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- namespace.yaml +- deployment.yaml +- gatewayclass.yaml +- gateway.yaml +- ingressclass.yaml +- ingressroute.yaml +- rbac/clusterrolebinding.yaml +- rbac/clusterrole.yaml +- rbac/serviceaccount.yaml +- service.yaml diff --git a/traefik/manifest.yaml b/traefik/manifest.yaml new file mode 100644 index 0000000..0c4124d --- /dev/null +++ b/traefik/manifest.yaml @@ -0,0 +1,10 @@ +name: traefik +is: traefik +description: Cloud-native reverse proxy and ingress controller +version: v3.4 +namespace: traefik +category: infrastructure +requires: + - name: metallb +defaultConfig: + loadBalancerIp: "{{ .apps.metallb.loadBalancerIp }}" diff --git a/traefik/namespace.yaml b/traefik/namespace.yaml new file mode 100644 index 0000000..c088a91 --- /dev/null +++ b/traefik/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: traefik diff --git a/traefik/rbac/clusterrole.yaml b/traefik/rbac/clusterrole.yaml new file mode 100644 index 0000000..333fa7c --- /dev/null +++ b/traefik/rbac/clusterrole.yaml @@ -0,0 +1,108 @@ +--- +# Source: traefik/templates/rbac/clusterrole.yaml +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: traefik-traefik + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm +rules: + - apiGroups: + - "" + resources: + - configmaps + - nodes + - services + verbs: + - get + - list + - watch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch + - apiGroups: + - extensions + - networking.k8s.io + resources: + - ingressclasses + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - extensions + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - traefik.io + resources: + - ingressroutes + - ingressroutetcps + - ingressrouteudps + - middlewares + - middlewaretcps + - serverstransports + - serverstransporttcps + - tlsoptions + - tlsstores + - traefikservices + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - namespaces + - secrets + - configmaps + verbs: + - get + - list + - watch + - apiGroups: + - gateway.networking.k8s.io + resources: + - backendtlspolicies + - gatewayclasses + - gateways + - grpcroutes + - httproutes + - referencegrants + - tcproutes + - tlsroutes + verbs: + - get + - list + - watch + - apiGroups: + - gateway.networking.k8s.io + resources: + - backendtlspolicies/status + - gatewayclasses/status + - gateways/status + - grpcroutes/status + - httproutes/status + - tcproutes/status + - tlsroutes/status + verbs: + - update diff --git a/traefik/rbac/clusterrolebinding.yaml b/traefik/rbac/clusterrolebinding.yaml new file mode 100644 index 0000000..3c57229 --- /dev/null +++ b/traefik/rbac/clusterrolebinding.yaml @@ -0,0 +1,19 @@ +--- +# Source: traefik/templates/rbac/clusterrolebinding.yaml +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: traefik-traefik + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: traefik-traefik +subjects: + - kind: ServiceAccount + name: traefik + namespace: traefik diff --git a/traefik/rbac/serviceaccount.yaml b/traefik/rbac/serviceaccount.yaml new file mode 100644 index 0000000..3ab2406 --- /dev/null +++ b/traefik/rbac/serviceaccount.yaml @@ -0,0 +1,14 @@ +--- +# Source: traefik/templates/rbac/serviceaccount.yaml +kind: ServiceAccount +apiVersion: v1 +metadata: + name: traefik + namespace: traefik + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm + annotations: +automountServiceAccountToken: false diff --git a/traefik/service.yaml b/traefik/service.yaml new file mode 100644 index 0000000..73a4aa9 --- /dev/null +++ b/traefik/service.yaml @@ -0,0 +1,27 @@ +--- +# Source: traefik/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: traefik + namespace: traefik + labels: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + helm.sh/chart: traefik-36.1.0 + app.kubernetes.io/managed-by: Helm + annotations: +spec: + type: LoadBalancer + selector: + app.kubernetes.io/name: traefik + app.kubernetes.io/instance: traefik-traefik + ports: + - port: 80 + name: web + targetPort: web + protocol: TCP + - port: 443 + name: websecure + targetPort: websecure + protocol: TCP diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/utils/README.md @@ -0,0 +1 @@ + diff --git a/utils/install.sh b/utils/install.sh new file mode 100755 index 0000000..0927b96 --- /dev/null +++ b/utils/install.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -e +set -o pipefail + +if [ -z "${WILD_INSTANCE}" ]; then + echo "ERROR: WILD_INSTANCE is not set" + exit 1 +fi + +if [ -z "${WILD_API_DATA_DIR}" ]; then + echo "ERROR: WILD_API_DATA_DIR is not set" + exit 1 +fi + +if [ -z "${KUBECONFIG}" ]; then + echo "ERROR: KUBECONFIG is not set" + exit 1 +fi + +INSTANCE_DIR="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}" +UTILS_DIR="${INSTANCE_DIR}/apps/utils" + +echo "=== Setting up Cluster Utilities ===" +echo "" + +echo "Using compiled utils templates..." +if [ ! -f "${UTILS_DIR}/kustomization.yaml" ]; then + echo "ERROR: Compiled templates not found at ${UTILS_DIR}" + echo "Templates should be compiled before deployment." + exit 1 +fi + +echo "Applying utility manifests..." +kubectl apply -k ${UTILS_DIR}/ + +echo "" +echo "Cluster utilities installed successfully" +echo "" +echo "Utility resources have been deployed to the cluster" diff --git a/utils/kustomization.yaml b/utils/kustomization.yaml new file mode 100644 index 0000000..1d89209 --- /dev/null +++ b/utils/kustomization.yaml @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - netdebug.yaml diff --git a/utils/manifest.yaml b/utils/manifest.yaml new file mode 100644 index 0000000..7583646 --- /dev/null +++ b/utils/manifest.yaml @@ -0,0 +1,9 @@ +name: utils +is: utils +description: Utility tools and scripts for cluster administration +version: v1.0.1 +namespace: debug +deploymentName: netdebug +category: infrastructure +defaultConfig: + namespace: debug diff --git a/utils/netdebug.yaml b/utils/netdebug.yaml new file mode 100644 index 0000000..d126886 --- /dev/null +++ b/utils/netdebug.yaml @@ -0,0 +1,73 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: debug + labels: + pod-security.kubernetes.io/enforce: privileged +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: netdebug + namespace: debug +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: netdebug +subjects: +- kind: ServiceAccount + name: netdebug + namespace: debug +roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: netdebug + namespace: debug + labels: + app: netdebug +spec: + replicas: 1 + selector: + matchLabels: + app: netdebug + template: + metadata: + labels: + app: netdebug + spec: + serviceAccountName: netdebug + containers: + - name: netdebug + image: nicolaka/netshoot:latest + command: ["/bin/bash"] + args: ["-c", "while true; do sleep 3600; done"] + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + securityContext: + privileged: true +--- +apiVersion: v1 +kind: Service +metadata: + name: netdebug + namespace: debug +spec: + selector: + app: netdebug + ports: + - port: 22 + targetPort: 22 + name: ssh + type: ClusterIP