Compare commits
3 Commits
351dff14d4
...
46002ff273
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46002ff273 | ||
|
|
acec744df8 | ||
|
|
12e87635c6 |
@@ -62,13 +62,32 @@ requiredSecrets:
|
||||
| `name` | Yes | App identifier (must match directory name) |
|
||||
| `is` | Yes | Unique id for this app. Used for `requires` mapping |
|
||||
| `description` | Yes | Brief app description shown in listings |
|
||||
| `version` | Yes | App version (follow upstream versioning) |
|
||||
| `version` | Yes | App version (see Versioning Convention below) |
|
||||
| `icon` | No | URL to app icon for UI display |
|
||||
| `requires` | No | List of dependency apps with optional aliases |
|
||||
| `defaultConfig` | Yes | Default configuration values merged into operator's `config.yaml` |
|
||||
| `defaultSecrets` | No | This app's secrets (no 'default' = auto-generated) |
|
||||
| `requiredSecrets` | No | List of secrets from dependency apps (format: `<app-ref>.<key>`) |
|
||||
|
||||
### Versioning Convention
|
||||
|
||||
Wild Cloud uses a two-part version scheme inspired by Debian packaging: `<upstream>-<revision>`.
|
||||
|
||||
- **Upstream version** tracks the third-party software version (e.g., `v4.0.18`, `1.120.2`)
|
||||
- **Packaging revision** tracks Wild Cloud packaging changes (template fixes, manifest cleanup, config restructuring) that don't change the upstream software version
|
||||
|
||||
**Examples:**
|
||||
- `v4.0.18` — initial packaging of upstream v4.0.18
|
||||
- `v4.0.18-1` — first packaging fix (no upstream change)
|
||||
- `v4.0.18-2` — second packaging fix
|
||||
- `v4.0.19` — upstream version bump, revision resets
|
||||
|
||||
**When to bump the packaging revision:** Any change to the app package that doesn't correspond to an upstream software update — manifest field changes, template improvements, kustomize restructuring, security context fixes, label corrections, etc.
|
||||
|
||||
**When to bump the upstream version:** When updating the container image tag or deploying a new version of the third-party software.
|
||||
|
||||
The web UI uses version comparison to detect available updates. If the deployed version differs from the wild-directory version, operators see an update indicator and can apply it from the app detail panel.
|
||||
|
||||
### Dependency Configuration
|
||||
|
||||
- Each dependency in `requires` can have:
|
||||
@@ -121,15 +140,6 @@ Here's a comprehensive rundown of all config variables that get set during clust
|
||||
|
||||
- cloud.dockerRegistryHost - Docker registry hostname (e.g., "registry.internal.cloud2.payne.io")
|
||||
|
||||
##### SMTP Configuration (SMTP Service):
|
||||
|
||||
- cloud.smtp.host - SMTP server hostname
|
||||
- cloud.smtp.port - SMTP port (typically "465" or "587")
|
||||
- cloud.smtp.user - SMTP username
|
||||
- cloud.smtp.from - Default 'from' email address
|
||||
- cloud.smtp.tls - Enable TLS (true/false)
|
||||
- cloud.smtp.startTls - Enable STARTTLS (true/false)
|
||||
|
||||
###### Backup Configuration:
|
||||
|
||||
- cloud.backup.root - Root path for backups
|
||||
@@ -214,8 +224,7 @@ Configuration Flow
|
||||
- ExternalDNS → cluster.externalDns.ownerId
|
||||
- NFS → cloud.nfs.*
|
||||
- Docker Registry → cloud.dockerRegistryHost, cluster.dockerRegistry.storage
|
||||
- SMTP → cloud.smtp.*
|
||||
4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest
|
||||
4. Apps: Each app adds its configuration under apps.<name>.* based on its manifest (including SMTP as an infrastructure app at apps.smtp.*)
|
||||
|
||||
#### Manifest App Reference Resolution:
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# NFS Setup (Optional)
|
||||
# NFS Client Provisioner
|
||||
|
||||
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.
|
||||
Provides shared NFS storage to the cluster by creating a StorageClass and PersistentVolume backed by an external NFS server. This is an infrastructure app — it has no pods or namespace, just cluster-scoped resources.
|
||||
|
||||
## Host Setup
|
||||
## Prerequisites
|
||||
|
||||
First, set up the NFS server on your chosen host.
|
||||
You need an NFS server already running and exporting a path. To set one up on a host:
|
||||
|
||||
```bash
|
||||
./setup-nfs-host.sh <host> <media-path>
|
||||
@@ -16,30 +16,33 @@ Example:
|
||||
./setup-nfs-host.sh box-01 /srv/nfs
|
||||
```
|
||||
|
||||
## Cluster Integration
|
||||
This SSHs into the host, installs `nfs-kernel-server`, and configures the export.
|
||||
|
||||
Add to your `config.yaml`:
|
||||
## Configuration
|
||||
|
||||
When added to an instance, the default config is merged into `config.yaml`:
|
||||
|
||||
```yaml
|
||||
cloud:
|
||||
apps:
|
||||
nfs:
|
||||
host: box-01
|
||||
mediaPath: /srv/nfs
|
||||
storageCapacity: 250Gi # Max size for PersistentVolume
|
||||
namespace: nfs
|
||||
host: "192.168.1.100"
|
||||
mediaPath: "/mnt/storage/media"
|
||||
storageCapacity: "1Ti"
|
||||
```
|
||||
|
||||
And now you can run the nfs cluster setup:
|
||||
Update `host` and `mediaPath` to match your NFS server before deploying.
|
||||
|
||||
```bash
|
||||
setup/setup-nfs-host.sh
|
||||
```
|
||||
## What Gets Deployed
|
||||
|
||||
## Features
|
||||
- **StorageClass** (`nfs`) — allows PVCs to request NFS-backed storage
|
||||
- **PersistentVolume** (`nfs-media-pv`) — a cluster-wide volume pointing to the NFS export
|
||||
|
||||
- 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
|
||||
No namespace, pods, or services are created.
|
||||
|
||||
## Scripts
|
||||
|
||||
- **check-nfs** — Verifies the NFS server is reachable, the export path exists, and checks whether the StorageClass and PersistentVolume are present in the cluster. Run from the app detail panel in the web UI.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -58,3 +61,10 @@ spec:
|
||||
requests:
|
||||
storage: 100Gi
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Cluster-wide access — any pod can mount the NFS share regardless of node placement
|
||||
- ReadWriteMany — multiple pods can simultaneously read and write
|
||||
- Configurable capacity — set PersistentVolume size via `storageCapacity`
|
||||
- Retain policy — data is preserved when volumes are released
|
||||
|
||||
229
nfs/install.sh
229
nfs/install.sh
@@ -1,229 +0,0 @@
|
||||
#!/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 "$@"
|
||||
@@ -1,10 +1,12 @@
|
||||
name: nfs
|
||||
is: nfs
|
||||
description: NFS client provisioner for external NFS storage
|
||||
version: v4.0.18
|
||||
deploymentName: ""
|
||||
storageClassName: "nfs"
|
||||
version: v4.0.18-3
|
||||
category: infrastructure
|
||||
scripts:
|
||||
- name: check-nfs
|
||||
path: scripts/check-nfs.sh
|
||||
description: Verify NFS server is reachable and the export path is available
|
||||
defaultConfig:
|
||||
namespace: nfs
|
||||
host: "192.168.1.100"
|
||||
|
||||
79
nfs/scripts/check-nfs.sh
Executable file
79
nfs/scripts/check-nfs.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
# Verify NFS server is reachable and the export path is available.
|
||||
# Run before or after deployment to validate NFS connectivity.
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
if [ -z "${WILD_INSTANCE}" ] || [ -z "${WILD_API_DATA_DIR}" ]; then
|
||||
echo "ERROR: WILD_INSTANCE and WILD_API_DATA_DIR must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONFIG_FILE="${WILD_API_DATA_DIR}/instances/${WILD_INSTANCE}/config.yaml"
|
||||
|
||||
NFS_HOST="$(yq '.apps.nfs.host' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
|
||||
NFS_PATH="$(yq '.apps.nfs.mediaPath' "${CONFIG_FILE}" 2>/dev/null | tr -d '"')"
|
||||
|
||||
if [ -z "${NFS_HOST}" ] || [ "${NFS_HOST}" = "null" ]; then
|
||||
echo "ERROR: apps.nfs.host not set in config"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${NFS_PATH}" ] || [ "${NFS_PATH}" = "null" ]; then
|
||||
echo "ERROR: apps.nfs.mediaPath not set in config"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "NFS host: ${NFS_HOST}"
|
||||
echo "NFS path: ${NFS_PATH}"
|
||||
|
||||
# Resolve hostname to IP
|
||||
if [[ "${NFS_HOST}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
NFS_IP="${NFS_HOST}"
|
||||
else
|
||||
NFS_IP=$(getent hosts "${NFS_HOST}" 2>/dev/null | awk '{print $1}' | head -n1 || true)
|
||||
if [ -z "${NFS_IP}" ]; then
|
||||
echo "ERROR: Cannot resolve hostname ${NFS_HOST}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Resolved to: ${NFS_IP}"
|
||||
fi
|
||||
|
||||
# Check showmount
|
||||
if ! command -v showmount >/dev/null 2>&1; then
|
||||
echo "WARNING: showmount not available, skipping export check"
|
||||
else
|
||||
echo ""
|
||||
echo "Checking NFS exports..."
|
||||
if timeout 10 showmount -e "${NFS_IP}" >/dev/null 2>&1; then
|
||||
if showmount -e "${NFS_IP}" | grep -q "${NFS_PATH}"; then
|
||||
echo "OK: ${NFS_PATH} is exported"
|
||||
else
|
||||
echo "ERROR: ${NFS_PATH} not found in NFS exports:"
|
||||
showmount -e "${NFS_IP}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "ERROR: Cannot reach NFS server at ${NFS_IP}:2049"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check k8s resources if KUBECONFIG is available
|
||||
if [ -n "${KUBECONFIG}" ]; then
|
||||
echo ""
|
||||
echo "Checking Kubernetes resources..."
|
||||
if kubectl get storageclass nfs >/dev/null 2>&1; then
|
||||
echo "OK: StorageClass 'nfs' exists"
|
||||
else
|
||||
echo "WARNING: StorageClass 'nfs' not found (deploy NFS first)"
|
||||
fi
|
||||
if kubectl get pv nfs-media-pv >/dev/null 2>&1; then
|
||||
echo "OK: PersistentVolume 'nfs-media-pv' exists"
|
||||
kubectl get pv nfs-media-pv --no-headers
|
||||
else
|
||||
echo "WARNING: PersistentVolume 'nfs-media-pv' not found (deploy NFS first)"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "NFS check complete."
|
||||
Reference in New Issue
Block a user