diff --git a/k3s/apps/gitea/gitea-pv-target.yaml b/k3s/apps/gitea/gitea-pv-target.yaml new file mode 100644 index 0000000..f720239 --- /dev/null +++ b/k3s/apps/gitea/gitea-pv-target.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: gitea-pv-target +spec: + storageClassName: nfs + capacity: + storage: 30Gi + accessModes: + - ReadWriteMany + persistentVolumeReclaimPolicy: Retain + nfs: + server: 192.168.178.186 + path: /volume1/giteaRepos +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: gitea-pvc-target + namespace: gitea +spec: + storageClassName: nfs + accessModes: + - ReadWriteMany + resources: + requests: + storage: 30Gi diff --git a/k3s/apps/gitea/gitea.yaml b/k3s/apps/gitea/gitea.yaml index 6ca7a97..6351661 100644 --- a/k3s/apps/gitea/gitea.yaml +++ b/k3s/apps/gitea/gitea.yaml @@ -5,72 +5,12 @@ kind: Namespace metadata: name: gitea -# PV + PVC: Gitea (NFS) ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: gitea-pv -spec: - storageClassName: nfs - capacity: - storage: 30Gi - accessModes: - - ReadWriteMany - persistentVolumeReclaimPolicy: Retain - nfs: - server: 192.168.178.166 - path: /export/fastData/gitea/repos ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: gitea-pvc - namespace: gitea -spec: - storageClassName: nfs - accessModes: - - ReadWriteMany - resources: - requests: - storage: 30Gi - -# PV + PVC: PostgreSQL (NFS) ---- -apiVersion: v1 -kind: PersistentVolume -metadata: - name: postgres-pv -spec: - storageClassName: nfs - capacity: - storage: 10Gi - accessModes: - - ReadWriteOnce - persistentVolumeReclaimPolicy: Retain - nfs: - server: 192.168.178.166 - path: /export/fastData/gitea/postgres ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: postgres-pvc - namespace: gitea -spec: - storageClassName: nfs - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi - # PVC: PostgreSQL (Longhorn target) --- apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: postgres-longhorn-pvc + name: postgres-longhorn-pvc-3g namespace: gitea spec: storageClassName: longhorn @@ -78,7 +18,7 @@ spec: - ReadWriteOnce resources: requests: - storage: 10Gi + storage: 3Gi # Deployment: PostgreSQL --- @@ -119,7 +59,7 @@ spec: volumes: - name: postgres-storage persistentVolumeClaim: - claimName: postgres-longhorn-pvc + claimName: postgres-longhorn-pvc-3g # Service: PostgreSQL --- @@ -153,14 +93,16 @@ spec: labels: app: gitea spec: + nodeSelector: + kubernetes.io/hostname: knode1 containers: - name: gitea image: gitea/gitea:latest env: - name: USER_UID - value: "1000" + value: "1024" - name: USER_GID - value: "1000" + value: "100" - name: GITEA__database__DB_TYPE value: postgres - name: GITEA__database__HOST @@ -187,18 +129,20 @@ spec: value: "32000" - name: GITEA__packages__ENABLED value: "true" + - name: GITEA__repository__ROOT + value: /data/gitea/git/repositories + - name: GITEA__lfs__PATH + value: /data/gitea/git/lfs ports: - containerPort: 3000 # HTTP - containerPort: 32000 # SSH volumeMounts: - name: gitea-storage mountPath: /data - securityContext: - fsGroup: 1000 volumes: - name: gitea-storage persistentVolumeClaim: - claimName: gitea-pvc + claimName: gitea-pvc-target # Service: Gitea (inkl. SSH) --- diff --git a/k3s/apps/gitea/migrate-postgres-to-longhorn.sh b/k3s/apps/gitea/migrate-postgres-to-longhorn.sh deleted file mode 100644 index 72d93ad..0000000 --- a/k3s/apps/gitea/migrate-postgres-to-longhorn.sh +++ /dev/null @@ -1,335 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -NAMESPACE="gitea" -GITEA_DEPLOYMENT="gitea" -POSTGRES_DEPLOYMENT="postgres" -POSTGRES_VOLUME_NAME="postgres-storage" -JOB_NAME="gitea-postgres-migrate-to-longhorn" - -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" -TARGET_PVC_MANIFEST="${SCRIPT_DIR}/postgres-longhorn-pvc.yaml" -JOB_MANIFEST="${SCRIPT_DIR}/migrate-postgres-to-longhorn.yaml" - -SOURCE_PVC="postgres-pvc" -TARGET_PVC="postgres-longhorn-pvc" - -PVC_BIND_TIMEOUT_SECONDS=900 -POD_STOP_TIMEOUT_SECONDS=300 -POSTGRES_READY_TIMEOUT_SECONDS=300 -JOB_TIMEOUT_SECONDS=3600 - -ORIGINAL_GITEA_REPLICAS="1" -ORIGINAL_POSTGRES_REPLICAS="1" -SOURCE_NODE_NAME="" -SCALED_DOWN_GITEA="false" -SCALED_DOWN_POSTGRES="false" -POSTGRES_CLAIM_SWITCHED="false" -DRY_RUN="false" -AUTO_CONFIRM="false" - -info() { - echo "[INFO] $*" -} - -warn() { - echo "[WARN] $*" >&2 -} - -err() { - echo "[ERROR] $*" >&2 - exit 1 -} - -usage() { - cat <<'EOF' -Usage: migrate-postgres-to-longhorn.sh [--dry-run] [--yes] [--help] - -Options: - --dry-run Show planned actions without changing cluster state. - --yes Skip interactive confirmation before scaling down Gitea. - --help Show this help. -EOF -} - -parse_args() { - while [[ $# -gt 0 ]]; do - case "$1" in - --dry-run) - DRY_RUN="true" - ;; - --yes) - AUTO_CONFIRM="true" - ;; - --help|-h) - usage - exit 0 - ;; - *) - err "Unknown argument: $1" - ;; - esac - shift - done -} - -require_tool() { - local tool="$1" - command -v "${tool}" >/dev/null 2>&1 || err "Required tool not found: ${tool}" -} - -require_file() { - local path="$1" - [[ -f "${path}" ]] || err "Required file not found: ${path}" -} - -wait_for_pvc_bound() { - local pvc_name="$1" - local timeout="$2" - local elapsed=0 - local interval=5 - - info "Waiting for PVC ${pvc_name} to become Bound..." - while true; do - local phase - phase="$(kubectl -n "${NAMESPACE}" get pvc "${pvc_name}" -o jsonpath='{.status.phase}' 2>/dev/null || true)" - - if [[ "${phase}" == "Bound" ]]; then - info "PVC ${pvc_name} is Bound." - return 0 - fi - - if (( elapsed >= timeout )); then - err "Timeout while waiting for PVC ${pvc_name} to bind." - fi - - sleep "${interval}" - elapsed=$((elapsed + interval)) - done -} - -wait_for_no_pods() { - local selector="$1" - local timeout="$2" - local elapsed=0 - local interval=5 - - info "Waiting for pods with selector ${selector} to stop..." - while true; do - local count - count="$(kubectl -n "${NAMESPACE}" get pods -l "${selector}" --no-headers 2>/dev/null | awk '$3 != "Completed" && $3 != "Succeeded" {count++} END {print count+0}')" - - if [[ "${count}" == "0" ]]; then - info "No pods left for selector ${selector}." - return 0 - fi - - if (( elapsed >= timeout )); then - err "Timeout while waiting for pods with selector ${selector} to stop." - fi - - sleep "${interval}" - elapsed=$((elapsed + interval)) - done -} - -discover_source_node() { - SOURCE_NODE_NAME="$(kubectl -n "${NAMESPACE}" get pod -l app=postgres -o jsonpath='{.items[0].spec.nodeName}' 2>/dev/null || true)" - [[ -n "${SOURCE_NODE_NAME}" ]] || err "Could not determine the current PostgreSQL node before shutdown." - info "Using source node ${SOURCE_NODE_NAME} for the migration job." -} - -apply_migration_job() { - sed "/restartPolicy: Never/a\ nodeName: ${SOURCE_NODE_NAME}" "${JOB_MANIFEST}" | kubectl apply -f - >/dev/null -} - -wait_for_deployment_ready() { - local deployment_name="$1" - local timeout="$2" - - info "Waiting for deployment ${deployment_name} rollout to finish..." - kubectl -n "${NAMESPACE}" rollout status "deployment/${deployment_name}" --timeout="${timeout}s" >/dev/null -} - -wait_for_job_complete() { - local timeout="$1" - - info "Waiting for migration job ${JOB_NAME} to complete..." - if kubectl -n "${NAMESPACE}" wait --for=condition=complete "job/${JOB_NAME}" --timeout="${timeout}s" >/dev/null; then - info "Migration job completed successfully." - return 0 - fi - - warn "Migration job did not complete in time or failed." - kubectl -n "${NAMESPACE}" describe job "${JOB_NAME}" || true - kubectl -n "${NAMESPACE}" logs "job/${JOB_NAME}" --tail=-1 || true - err "Migration job failed." -} - -verify_target_data() { - local verification - verification="$(kubectl -n "${NAMESPACE}" get job "${JOB_NAME}" -o jsonpath='{.status.succeeded}' 2>/dev/null || true)" - [[ "${verification}" == "1" ]] || err "Migration job did not report success." - - info "Verifying target PVC content markers before starting Gitea." - kubectl -n "${NAMESPACE}" run gitea-postgres-verify \ - --rm --restart=Never --image=alpine:3.20 \ - --overrides='{ - "apiVersion":"v1", - "spec":{ - "containers":[{ - "name":"verify", - "image":"alpine:3.20", - "command":["sh","-c","set -e; test -f /target/PG_VERSION; test -d /target/base; find /target -mindepth 1 | wc -l; ls -la /target | head -n 20"], - "volumeMounts":[{ - "name":"target", - "mountPath":"/target" - }] - }], - "volumes":[{ - "name":"target", - "persistentVolumeClaim":{ - "claimName":"postgres-longhorn-pvc" - } - }] - } - }' \ - --attach=true >/dev/null -} - -patch_postgres_claim() { - local claim_name="$1" - - kubectl -n "${NAMESPACE}" patch deployment "${POSTGRES_DEPLOYMENT}" \ - --type=strategic \ - -p "{\"spec\":{\"template\":{\"spec\":{\"volumes\":[{\"name\":\"${POSTGRES_VOLUME_NAME}\",\"persistentVolumeClaim\":{\"claimName\":\"${claim_name}\"}}]}}}}" >/dev/null -} - -restore_on_error() { - if [[ "${POSTGRES_CLAIM_SWITCHED}" == "true" ]]; then - warn "Migration failed after deployment patch. Switching PostgreSQL back to ${SOURCE_PVC}." - patch_postgres_claim "${SOURCE_PVC}" || true - POSTGRES_CLAIM_SWITCHED="false" - fi - - if [[ "${SCALED_DOWN_POSTGRES}" == "true" ]]; then - warn "Restoring PostgreSQL replicas to ${ORIGINAL_POSTGRES_REPLICAS}." - kubectl -n "${NAMESPACE}" scale deployment "${POSTGRES_DEPLOYMENT}" --replicas="${ORIGINAL_POSTGRES_REPLICAS}" >/dev/null || true - fi - - if [[ "${SCALED_DOWN_GITEA}" == "true" ]]; then - warn "Restoring Gitea replicas to ${ORIGINAL_GITEA_REPLICAS}." - kubectl -n "${NAMESPACE}" scale deployment "${GITEA_DEPLOYMENT}" --replicas="${ORIGINAL_GITEA_REPLICAS}" >/dev/null || true - fi -} - -on_exit() { - local exit_code=$? - if [[ ${exit_code} -ne 0 ]]; then - restore_on_error - warn "Script failed with exit code ${exit_code}." - fi -} -trap on_exit EXIT - -validate_prerequisites() { - require_tool kubectl - require_file "${TARGET_PVC_MANIFEST}" - require_file "${JOB_MANIFEST}" - - kubectl get namespace "${NAMESPACE}" >/dev/null 2>&1 || err "Namespace does not exist: ${NAMESPACE}" - kubectl -n "${NAMESPACE}" get deployment "${GITEA_DEPLOYMENT}" >/dev/null 2>&1 || err "Deployment not found: ${GITEA_DEPLOYMENT}" - kubectl -n "${NAMESPACE}" get deployment "${POSTGRES_DEPLOYMENT}" >/dev/null 2>&1 || err "Deployment not found: ${POSTGRES_DEPLOYMENT}" - kubectl -n "${NAMESPACE}" get pvc "${SOURCE_PVC}" >/dev/null 2>&1 || err "Source PVC not found: ${SOURCE_PVC}" -} - -confirm_scale_down() { - if [[ "${AUTO_CONFIRM}" == "true" ]]; then - return 0 - fi - - echo - warn "Gitea and PostgreSQL in namespace ${NAMESPACE} will be stopped for the migration." - read -r -p "Type MIGRATE to continue: " confirmation - if [[ "${confirmation}" != "MIGRATE" ]]; then - err "Confirmation failed. Aborted by user." - fi -} - -print_dry_run_plan() { - info "Dry-run mode active. No cluster changes will be made." - info "Planned steps:" - info "1) Apply ${TARGET_PVC_MANIFEST}" - info "2) Wait for PVC ${TARGET_PVC} to be Bound" - info "3) Scale deployments ${GITEA_DEPLOYMENT} and ${POSTGRES_DEPLOYMENT} to 0" - info "4) Run job ${JOB_NAME} from ${JOB_MANIFEST}" - info "5) Verify PG_VERSION and base/ exist on ${TARGET_PVC}" - info "6) Patch ${POSTGRES_DEPLOYMENT} to use ${TARGET_PVC}" - info "7) Start PostgreSQL and wait until ready" - info "8) Start Gitea after PostgreSQL is ready" -} - -run_migration() { - ORIGINAL_GITEA_REPLICAS="$(kubectl -n "${NAMESPACE}" get deployment "${GITEA_DEPLOYMENT}" -o jsonpath='{.spec.replicas}')" - ORIGINAL_POSTGRES_REPLICAS="$(kubectl -n "${NAMESPACE}" get deployment "${POSTGRES_DEPLOYMENT}" -o jsonpath='{.spec.replicas}')" - ORIGINAL_GITEA_REPLICAS="${ORIGINAL_GITEA_REPLICAS:-1}" - ORIGINAL_POSTGRES_REPLICAS="${ORIGINAL_POSTGRES_REPLICAS:-1}" - discover_source_node - - if [[ "${DRY_RUN}" == "true" ]]; then - print_dry_run_plan - return 0 - fi - - confirm_scale_down - - info "Applying Longhorn target PVC manifest." - kubectl apply -f "${TARGET_PVC_MANIFEST}" >/dev/null - wait_for_pvc_bound "${TARGET_PVC}" "${PVC_BIND_TIMEOUT_SECONDS}" - - info "Scaling Gitea down first to stop writes." - kubectl -n "${NAMESPACE}" scale deployment "${GITEA_DEPLOYMENT}" --replicas=0 >/dev/null - SCALED_DOWN_GITEA="true" - wait_for_no_pods "app=gitea" "${POD_STOP_TIMEOUT_SECONDS}" - - info "Scaling PostgreSQL down for a consistent filesystem copy." - kubectl -n "${NAMESPACE}" scale deployment "${POSTGRES_DEPLOYMENT}" --replicas=0 >/dev/null - SCALED_DOWN_POSTGRES="true" - wait_for_no_pods "app=postgres" "${POD_STOP_TIMEOUT_SECONDS}" - - info "Recreating migration job ${JOB_NAME}." - kubectl -n "${NAMESPACE}" delete job "${JOB_NAME}" --ignore-not-found >/dev/null - apply_migration_job - - info "Streaming migration job logs." - kubectl -n "${NAMESPACE}" logs -f "job/${JOB_NAME}" || true - - wait_for_job_complete "${JOB_TIMEOUT_SECONDS}" - verify_target_data - - info "Switching PostgreSQL deployment to Longhorn PVC ${TARGET_PVC}." - patch_postgres_claim "${TARGET_PVC}" - POSTGRES_CLAIM_SWITCHED="true" - - info "Starting PostgreSQL on Longhorn." - kubectl -n "${NAMESPACE}" scale deployment "${POSTGRES_DEPLOYMENT}" --replicas="${ORIGINAL_POSTGRES_REPLICAS}" >/dev/null - SCALED_DOWN_POSTGRES="false" - wait_for_deployment_ready "${POSTGRES_DEPLOYMENT}" "${POSTGRES_READY_TIMEOUT_SECONDS}" - - info "Starting Gitea after PostgreSQL verification succeeded." - kubectl -n "${NAMESPACE}" scale deployment "${GITEA_DEPLOYMENT}" --replicas="${ORIGINAL_GITEA_REPLICAS}" >/dev/null - SCALED_DOWN_GITEA="false" - wait_for_deployment_ready "${GITEA_DEPLOYMENT}" "${POSTGRES_READY_TIMEOUT_SECONDS}" - - info "Migration finished successfully." - info "Source PVC ${SOURCE_PVC} remains untouched as rollback source." -} - -main() { - parse_args "$@" - info "Starting Gitea PostgreSQL migration from NFS to Longhorn." - validate_prerequisites - run_migration -} - -main "$@" \ No newline at end of file diff --git a/k3s/apps/gitea/migrate-postgres-to-longhorn.yaml b/k3s/apps/gitea/migrate-postgres-to-longhorn.yaml deleted file mode 100644 index 09e7e6d..0000000 --- a/k3s/apps/gitea/migrate-postgres-to-longhorn.yaml +++ /dev/null @@ -1,50 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: gitea-postgres-migrate-to-longhorn - namespace: gitea -spec: - backoffLimit: 2 - template: - metadata: - name: gitea-postgres-migrate-to-longhorn - spec: - restartPolicy: Never - containers: - - name: migrate - image: alpine:3.20 - command: - - sh - - -c - - | - set -euo pipefail - apk add --no-cache rsync findutils coreutils - - src_count="$(find /source -mindepth 1 | wc -l | tr -d ' ')" - echo "Source entries before copy: ${src_count}" - - rsync -aHAX --numeric-ids --delete /source/ /target/ - - target_count="$(find /target -mindepth 1 | wc -l | tr -d ' ')" - echo "Target entries after copy: ${target_count}" - - test -f /target/PG_VERSION - test -d /target/base - test "${target_count}" -gt 0 - - echo "Top-level target contents:" - ls -la /target - echo "Migration verification successful" - volumeMounts: - - name: source - mountPath: /source - readOnly: true - - name: target - mountPath: /target - volumes: - - name: source - persistentVolumeClaim: - claimName: postgres-pvc - - name: target - persistentVolumeClaim: - claimName: postgres-longhorn-pvc \ No newline at end of file diff --git a/k3s/apps/gitea/postgres-longhorn-pvc.yaml b/k3s/apps/gitea/postgres-longhorn-pvc.yaml deleted file mode 100644 index 4d3573c..0000000 --- a/k3s/apps/gitea/postgres-longhorn-pvc.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: postgres-longhorn-pvc - namespace: gitea -spec: - storageClassName: longhorn - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi \ No newline at end of file