Private
Public Access
1
0

Add Docker, Kubernetes configurations, and CI workflows for deployment. Integrate Gin server for API, WebSocket support, and static file hosting. Refactor WebSocket gateway to use Gin router.
Some checks failed
Dynamic Branch Deploy / build-and-deploy (push) Has been cancelled

This commit is contained in:
Sebastian Unterschütz
2026-01-04 15:14:55 +01:00
parent 2fb19d314f
commit 16f683a360
13 changed files with 872 additions and 13 deletions

35
.dockerignore Normal file
View File

@@ -0,0 +1,35 @@
# Binaries
*.exe
*.exe~
*.dll
*.so
*.dylib
server
client
# Test files
*.test
*.out
# Development
.git/
.github/
.vscode/
.idea/
# Documentation
*.md
!README.md
# Temporary
tmp/
temp/
*.tmp
*.log
# OS
.DS_Store
Thumbs.db
# Dependencies (werden neu gebaut)
vendor/

68
.github/workflows/cleanup.yaml vendored Normal file
View File

@@ -0,0 +1,68 @@
name: Cleanup Environment
on: [delete]
jobs:
cleanup:
runs-on: ubuntu-latest
# Nur ausführen, wenn ein Branch gelöscht wurde (keine Tags)
if: github.event.ref_type == 'branch'
steps:
# 1. Variablen berechnen (MIT FIX FÜR REFS/HEADS & MAIN-CHECK)
- name: Prepare Variables
run: |
# Repo Name klein (z.B. "it232abschied")
REPO_LOWER=$(echo "${{ gitea.repository }}" | cut -d'/' -f2 | tr '[:upper:]' '[:lower:]')
# Branch Name aus Event (z.B. "refs/heads/feature-x")
RAW_REF="${{ github.event.ref }}"
# "refs/heads/" entfernen
BRANCH_CLEAN=${RAW_REF#refs/heads/}
# Kleinschreiben & Sonderzeichen
BRANCH_LOWER=$(echo "$BRANCH_CLEAN" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')
# Logik synchron zum Deploy:
# Main/Master -> Namespace ist nur der Repo-Name
# Anderes -> Namespace ist Repo-Branch
if [ "$BRANCH_LOWER" = "main" ] || [ "$BRANCH_LOWER" = "master" ]; then
TARGET_NS="${REPO_LOWER}"
IS_MAIN="true"
else
TARGET_NS="${REPO_LOWER}-${BRANCH_LOWER}"
IS_MAIN="false"
fi
echo "DEBUG: Clean Branch: $BRANCH_LOWER"
echo "DEBUG: Target NS: $TARGET_NS"
echo "TARGET_NS=$TARGET_NS" >> $GITHUB_ENV
echo "IS_MAIN=$IS_MAIN" >> $GITHUB_ENV
# 2. Sicherheits-Check: Niemals Main/Master löschen!
# Wir prüfen jetzt die Variable IS_MAIN, statt den Namen hart zu codieren
- name: Protect Main
if: env.IS_MAIN == 'true'
run: |
echo "❌ ABBRUCH: Der Produktions-Namespace ${{ env.TARGET_NS }} darf nicht gelöscht werden!"
exit 1
# 3. Kubectl einrichten
- name: Setup Kubectl
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG }}" > $HOME/.kube/config
chmod 600 $HOME/.kube/config
# Der Trick für interne Kommunikation
sed -i 's|server: https://.*:6443|server: https://kubernetes.default.svc:443|g' $HOME/.kube/config
# 4. Namespace löschen
- name: Delete Namespace
run: |
echo "🗑️ Lösche Namespace: ${{ env.TARGET_NS }}"
# Wir löschen den Namespace ohne zu warten (async), das geht schneller
kubectl delete namespace ${{ env.TARGET_NS }} --ignore-not-found --wait=false

128
.github/workflows/deploy.yaml vendored Normal file
View File

@@ -0,0 +1,128 @@
name: Dynamic Branch Deploy
on: [push]
env:
REGISTRY: git.zb-server.de
# WICHTIG: Deine echte Haupt-Domain
BASE_DOMAIN: escape-from-school.de
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 1. Code auschecken
- name: Checkout Code
uses: actions/checkout@v3
# 2. Variablen vorbereiten (MIT HAUPT-DOMAIN LOGIK)
- name: Prepare Environment Variables
id: prep
run: |
# 1. Repo und Branch Namen säubern
# Voller Pfad für Docker Image (z.B. "user/escape-teacher")
FULL_IMAGE_PATH=$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]')
# Nur der Projektname für K8s (z.B. "escape-teacher")
REPO_LOWER=$(echo "$FULL_IMAGE_PATH" | cut -d'/' -f2)
# Branch Name säubern (Sonderzeichen zu Bindestrichen)
BRANCH_LOWER=$(echo "${{ gitea.ref_name }}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')
# 2. Logik: Ist es der Haupt-Branch?
if [ "$BRANCH_LOWER" = "main" ] || [ "$BRANCH_LOWER" = "master" ]; then
# PRODUKTION:
# URL ist direkt die Domain (ohne Subdomain)
APP_URL="${{ env.BASE_DOMAIN }}"
# Namespace ist nur der Projektname (ohne Branch-Suffix)
TARGET_NS="${REPO_LOWER}"
echo "Mode: PRODUCTION (Root Domain)"
else
# ENTWICKLUNG:
# URL ist repo-branch.domain.de
APP_URL="${REPO_LOWER}-${BRANCH_LOWER}.${{ env.BASE_DOMAIN }}"
# Namespace ist repo-branch
TARGET_NS="${REPO_LOWER}-${BRANCH_LOWER}"
echo "Mode: DEVELOPMENT (Subdomain)"
fi
# Image Tag (Commit Hash)
IMAGE_TAG="${{ gitea.sha }}"
# Debug Ausgabe
echo "DEBUG: Branch: $BRANCH_LOWER"
echo "DEBUG: Namespace: $TARGET_NS"
echo "DEBUG: URL: $APP_URL"
# In Gitea Actions Environment schreiben
echo "FULL_IMAGE_PATH=$FULL_IMAGE_PATH" >> $GITHUB_ENV
echo "REPO_NAME=$REPO_LOWER" >> $GITHUB_ENV
echo "TARGET_NS=$TARGET_NS" >> $GITHUB_ENV
echo "APP_URL=$APP_URL" >> $GITHUB_ENV
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
# 3. Kaniko Build
- name: Build and Push with Kaniko
uses: aevea/action-kaniko@v0.12.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.PACKAGE_TOKEN }}
image: ${{ env.FULL_IMAGE_PATH }}
tag: ${{ env.IMAGE_TAG }}
cache: true
extra_args: --skip-tls-verify-pull --insecure
# 4. Setup Kubectl (Interner Trick)
- name: Setup Kubectl
run: |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG }}" > $HOME/.kube/config
chmod 600 $HOME/.kube/config
# Internal DNS Trick (für Kommunikation innerhalb des Clusters)
sed -i 's|server: https://.*:6443|server: https://kubernetes.default.svc:443|g' $HOME/.kube/config
# 5. Deploy to Kubernetes
- name: Deploy to Kubernetes
run: |
# Namespace erstellen (falls nicht existiert)
kubectl create namespace ${{ env.TARGET_NS }} --dry-run=client -o yaml | kubectl apply -f -
# Vollen Image Pfad bauen
FULL_IMAGE_URL="${{ env.REGISTRY }}/${{ env.FULL_IMAGE_PATH }}:${{ env.IMAGE_TAG }}"
# 1. Ingress anpassen (Hier wird die URL eingesetzt!)
sed -i "s|\${APP_URL}|${{ env.APP_URL }}|g" k8s/ingress.yaml
# 2. App Deployment anpassen (Image)
sed -i "s|\${IMAGE_NAME}|$FULL_IMAGE_URL|g" k8s/app.yaml
# Anwenden
echo "Deploying Resources to Namespace: ${{ env.TARGET_NS }}"
kubectl apply -f k8s/pvc.yaml -n ${{ env.TARGET_NS }}
kubectl apply -f k8s/redis.yaml -n ${{ env.TARGET_NS }}
kubectl apply -f k8s/app.yaml -n ${{ env.TARGET_NS }}
kubectl apply -f k8s/ingress.yaml -n ${{ env.TARGET_NS }}
# HPA (Autoscaling) nur für Main/Master Branch aktivieren
# Wir vergleichen den Namespace mit dem Repo-Namen
# Wenn Namespace == RepoName, dann sind wir im Main Branch
if [ "${{ env.TARGET_NS }}" == "${{ env.REPO_NAME }}" ]; then
echo "Main Branch detected: Applying HPA (Autoscaling)..."
kubectl apply -f k8s/hpa.yaml -n ${{ env.TARGET_NS }}
else
echo "Feature Branch: Skipping HPA."
# Optional: HPA löschen, falls es versehentlich da ist
kubectl delete hpa escape-game-hpa -n ${{ env.TARGET_NS }} --ignore-not-found
fi
# Force Update (damit das neue Image sicher geladen wird)
kubectl rollout restart deployment/escape-game -n ${{ env.TARGET_NS }}
# 6. Summary
- name: Summary
run: echo "🚀 Deployed successfully to https://${{ env.APP_URL }}"

8
.gitignore vendored
View File

@@ -51,14 +51,6 @@ Thumbs.db
.env .env
config.local.json config.local.json
# Generated assets (if you want to regenerate them)
# Uncomment if assets should be generated, not committed
# /cmd/client/web/assets/*.png
# /cmd/client/web/assets/assets.json
# Keep chunks but ignore generated ones if needed
# /cmd/client/web/assets/chunks/*.json
# Debug files # Debug files
debug debug
__debug_bin __debug_bin

45
Dockerfile Normal file
View File

@@ -0,0 +1,45 @@
# Stage 1: Builder
FROM golang:1.25.5-alpine AS builder
# Build-Dependencies
RUN apk add --no-cache git
WORKDIR /app
# Dependencies cachen
COPY go.mod go.sum ./
RUN go mod download
# Source Code kopieren
COPY . .
# Server binary bauen
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags="-w -s" -o server ./cmd/server
# WASM Client bauen
RUN GOOS=js GOARCH=wasm go build -o cmd/client/web/main.wasm ./cmd/client
# Stage 2: Production Image
FROM alpine:latest
RUN apk --no-cache add ca-certificates curl tzdata && \
addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
WORKDIR /app
# Binary und Web-Dateien vom Builder kopieren
COPY --from=builder /app/server .
COPY --from=builder /app/cmd/client/web ./cmd/client/web
# User wechseln
USER appuser
# Port für HTTP/WebSocket
EXPOSE 8080
# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
CMD ["./server"]

296
README.md Normal file
View File

@@ -0,0 +1,296 @@
# 🏃 Escape From Teacher
Ein Endless-Runner-Spiel entwickelt als Schulprojekt. Renne vor dem Lehrer davon, sammle Münzen, nutze Power-Ups und klettere an Wänden!
![License](https://img.shields.io/badge/license-Proprietary-red)
![Go Version](https://img.shields.io/badge/go-1.21+-blue.svg)
![Platform](https://img.shields.io/badge/platform-Web%20%7C%20Desktop-green)
## 📋 Inhaltsverzeichnis
- [Features](#-features)
- [Technologie-Stack](#-technologie-stack)
- [Installation](#-installation)
- [Spielmodi](#-spielmodi)
- [Entwicklung](#-entwicklung)
- [Architektur](#-architektur)
- [Tools](#-tools)
- [Lizenz](#-lizenz)
## ✨ Features
### Gameplay
- 🏃 **Endless Runner**: Unendliches Spiel mit prozedural generierten Levels
- 🧗 **Wall Climbing**: Klettere an Wänden hoch für neue Strategien
- 💰 **Coins & Power-Ups**: Sammle Münzen und nutze Power-Ups wie Double Jump und Godmode
- 🎯 **Hindernisse**: Weiche bewegenden Plattformen und Objekten aus
-**Dynamische Schwierigkeit**: Das Spiel wird mit der Zeit schneller
### Modi
- 👤 **Solo-Modus**: Spiele alleine und sammle Highscores
- 👥 **Coop-Modus**: Spiele mit Freunden im Team
- Team-Namen für Leaderboard
- Multiplizierter Score basierend auf lebenden Spielern
- Host-Controls für Lobby-Management
### Features
- 🏆 **Leaderboard**: Redis-basiertes globales Leaderboard mit Proof-Codes
- 🎮 **Cross-Platform**: Web (WASM) und Desktop (Native)
- 🎨 **Level Editor**: Erstelle eigene Levels mit dem integrierten Level Builder
- 🔊 **Audio**: Musik und Sound-Effekte mit Lautstärkeregelung
- 📱 **Mobile Support**: Touch-Controls für mobile Geräte
## 🛠 Technologie-Stack
### Backend
- **Go 1.25+**: Server und Game-Logik
- **NATS**: Message Broker für Echtzeit-Kommunikation (Legacy)
- **WebSocket**: Direkte WebSocket-Verbindungen für niedrige Latenz
- **Redis**: Persistentes Leaderboard mit Proof-Code-Validierung
### Frontend
- **Go + Ebiten**: Desktop-Client (Native)
- **Go + WASM**: Web-Client kompiliert zu WebAssembly
- **HTML/CSS/JavaScript**: Web-UI und Menüs
### Architektur
- **Client-Side Prediction**: Flüssiges Gameplay trotz Netzwerk-Latenz
- **Server-Authoritative**: Server validiert alle Spielaktionen
- **Deterministische Physik**: Gleiche Physik auf Client und Server
## 📦 Installation
### Voraussetzungen
```bash
# Go 1.25 oder höher
go version
# Redis für Leaderboard
redis-server --version
```
### Repository klonen
```bash
git clone https://git.zb-server.de/ZB-Server/EscapeFromTeacher.git
cd EscapeFromTeacher
```
### Assets generieren
Die Assets müssen aus den Raw-Dateien generiert werden:
```bash
# Asset-Builder starten (GUI)
go run ./cmd/builder
# Im GUI: Assets laden, anpassen und speichern
# Dies erstellt cmd/client/web/assets/assets.json und die PNG-Dateien
```
### Server starten
```bash
# Redis starten (Terminal 1)
redis-server
# Server starten (Terminal 2)
go run ./cmd/server
```
Server läuft auf `http://localhost:8080`
### Clients starten
#### Web-Client (WASM)
```bash
# WASM kompilieren
GOOS=js GOARCH=wasm go build -o cmd/client/web/main.wasm ./cmd/client
# Web-Server starten (z.B. mit Python)
cd cmd/client/web
python3 -m http.server 8000
# Browser öffnen: http://localhost:8000
```
#### Desktop-Client (Native)
```bash
go run ./cmd/client
```
## 🎮 Spielmodi
### Solo-Modus
- Starte direkt aus dem Hauptmenü
- Automatischer Room-Erstellen
- Score basiert auf zurückgelegter Distanz
- Eigener Highscore im Leaderboard
### Coop-Modus
1. **Raum erstellen** (Host):
- "RAUM ERSTELLEN" klicken
- Team-Namen festlegen (optional)
- Raum-Code mit Freunden teilen
2. **Raum beitreten**:
- Raum-Code eingeben
- "RAUM BEITRETEN" klicken
3. **Gameplay**:
- Host startet das Spiel wenn alle bereit sind
- Team-Score = Distanz × Anzahl lebender Spieler
- Beispiel: 100 Tiles mit 3 Spielern = 300 Punkte pro Spieler
### Steuerung
**Desktop:**
- `Leertaste` / `W` / `↑`: Springen
- `A` / `←`: Links bewegen
- `D` / `→`: Rechts bewegen
- `S` / `↓`: Schneller fallen
**Mobile:**
- Touch-Buttons auf dem Bildschirm
**Wall Climbing:**
- Laufe gegen eine Wand
- Halte Richtungstaste gedrückt zum Hochklettern
- Lasse los zum Herunterrutschen
## 🔧 Entwicklung
### Projekt-Struktur
```
EscapeFromTeacher/
├── cmd/
│ ├── server/ # Game Server
│ ├── client/ # Desktop & WASM Client
│ │ └── web/ # Web Assets & HTML
│ ├── builder/ # Asset Builder Tool
│ └── levelbuilder/ # Level Editor
├── pkg/
│ ├── game/ # Gemeinsame Game-Logik
│ ├── server/ # Server-spezifische Logik
│ └── config/ # Konfiguration
└── assets_raw/ # Rohe Asset-Dateien
```
### Neue Features entwickeln
1. **Server-Logik** in `pkg/server/`
2. **Game-Logik** (gemeinsam) in `pkg/game/`
3. **Client-Rendering** in `cmd/client/`
4. **Web-UI** in `cmd/client/web/`
### Build-Befehle
```bash
# Server
go build -o server ./cmd/server
# Desktop-Client
go build -o client ./cmd/client
# WASM-Client
GOOS=js GOARCH=wasm go build -o cmd/client/web/main.wasm ./cmd/client
# Level Builder
go build -o levelbuilder ./cmd/levelbuilder
```
## 🧰 Tools
### Level Builder
Erstelle eigene Levels mit dem visuellen Editor:
```bash
go run ./cmd/levelbuilder
```
**Features:**
- Drag & Drop von Assets
- Bewegende Plattformen konfigurieren
- Chunk-basiertes Level-Design
- JSON-Export in `cmd/client/web/assets/chunks/`
### Asset Builder
Konvertiere Raw-Assets zu optimierten Spiel-Assets:
```bash
go run ./cmd/builder
```
**Features:**
- PNG-Kompression und -Skalierung
- Hitbox-Editor
- Asset-Metadaten (Typ, Offsets, etc.)
- Generiert `assets.json`
## 📐 Architektur
### Client-Server Kommunikation
```
Client Server
| |
|------ WebSocket ------->|
| (Input Events) |
| |
|<----- Broadcast --------|
| (Game State) |
| |
|-- Score Submission ---->|
| |
|<--- Proof Code ---------|
```
### Game Loop
**Server (60 FPS):**
1. Verarbeite Inputs von allen Clients
2. Update Physik (Gravity, Kollision, etc.)
3. Update Map (Chunk spawning/despawning)
4. Broadcast Game State an alle Clients
**Client:**
1. Sende lokale Inputs an Server
2. Empfange Game State vom Server
3. Render Game State
4. (Optional) Client-Side Prediction für lokalen Spieler
### Score-System
**Solo:**
- Score = Zurückgelegte Distanz (in Tiles)
- Coins: +200 Punkte
**Coop:**
- Distanz-Score akkumuliert pro Tick
- Pro Tick: `Score += Anzahl lebender Spieler`
- Beispiel: 3 Spieler → +3 Punkte/Tick (180 Punkte/Sekunde)
- Coins werden geteilt: Alle Spieler bekommen +200
## 📄 Lizenz
Dies ist ein **Schulprojekt**. Kommerzielle Nutzung und Veränderung des Quellcodes sind ausdrücklich untersagt. Alle Rechte liegen bei den Urhebern.
**Projektleitung & Code:** Sebastian Unterschütz
**Musik & Sound Design:** Max E.
## 🙏 Credits
- **Ebiten**: Go 2D Game Engine
- **NATS**: Message Broker
- **Redis**: In-Memory Database
- **Go WASM**: WebAssembly Support
---
**Entwickelt als Abschlussprojekt für IT232 (2025/2026)**

88
cmd/server/gin_server.go Normal file
View File

@@ -0,0 +1,88 @@
package main
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/nats-io/nats.go"
)
// SetupGinServer erstellt und konfiguriert den Gin-Server
func SetupGinServer(ec *nats.EncodedConn, port string) *gin.Engine {
// Production mode für bessere Performance
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
// Logging Middleware (bereits in gin.Default())
// Custom Logger für bessere Übersicht
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return "🌐 " + param.TimeStamp.Format("2006-01-02 15:04:05") +
" | " + param.Method +
" | " + string(rune(param.StatusCode)) +
" | " + param.Latency.String() +
" | " + param.ClientIP +
" | " + param.Path + "\n"
}))
// Recovery Middleware
r.Use(gin.Recovery())
// Health Check Endpoint
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",
"service": "EscapeFromTeacher",
"version": "1.0.0",
})
})
// Metrics Endpoint (optional für Kubernetes)
r.GET("/metrics", func(c *gin.Context) {
mu.RLock()
roomCount := len(rooms)
playerCount := len(playerSessions)
mu.RUnlock()
c.JSON(200, gin.H{
"rooms": roomCount,
"players": playerCount,
})
})
// WebSocket Endpoint
r.GET("/ws", func(c *gin.Context) {
handleWebSocketGin(c.Writer, c.Request, ec)
})
// Static Files - Serve Web Client
r.Static("/assets", "./cmd/client/web/assets")
r.StaticFile("/", "./cmd/client/web/index.html")
r.StaticFile("/index.html", "./cmd/client/web/index.html")
r.StaticFile("/game.js", "./cmd/client/web/game.js")
r.StaticFile("/style.css", "./cmd/client/web/style.css")
r.StaticFile("/wasm_exec.js", "./cmd/client/web/wasm_exec.js")
r.StaticFile("/main.wasm", "./cmd/client/web/main.wasm")
r.StaticFile("/background.jpg", "./cmd/client/web/background.jpg")
// 404 Handler
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{
"error": "Route not found",
})
})
log.Printf("🚀 Gin-Server konfiguriert auf Port %s", port)
log.Printf("📁 Statische Dateien: ./cmd/client/web/")
log.Printf("🌐 WebSocket Endpoint: /ws")
log.Printf("❤️ Health Check: /health")
return r
}
// handleWebSocketGin verwaltet WebSocket-Verbindungen über Gin
func handleWebSocketGin(w http.ResponseWriter, r *http.Request, ec *nats.EncodedConn) {
// Verwende die handleWebSocket Funktion aus websocket_gateway.go
handleWebSocket(w, r, ec)
}

View File

@@ -172,11 +172,11 @@ func main() {
log.Println("✅ Server bereit. Warte auf Spieler...") log.Println("✅ Server bereit. Warte auf Spieler...")
// 9. WEBSOCKET-GATEWAY STARTEN (für Browser-Clients) // 9. GIN-SERVER STARTEN (statische Dateien + WebSocket)
go StartWebSocketGateway("8080", ec) router := SetupGinServer(ec, "8080")
if err := router.Run(":8080"); err != nil {
// Block forever log.Fatal("❌ Gin-Server Fehler:", err)
select {} }
} }
func loadServerAssets(w *game.World) { func loadServerAssets(w *game.World) {

72
k8s/app.yaml Normal file
View File

@@ -0,0 +1,72 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: escape-game
labels:
app: escape-game
spec:
replicas: 2
selector:
matchLabels:
app: escape-game
template:
metadata:
labels:
app: escape-game
spec:
containers:
- name: server
image: ${IMAGE_NAME}
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1000m"
volumeMounts:
- name: assets
mountPath: /root/cmd/client/web/assets
volumes:
- name: assets
persistentVolumeClaim:
claimName: game-assets-pvc
---
apiVersion: v1
kind: Service
metadata:
name: escape-game
labels:
app: escape-game
spec:
type: ClusterIP
selector:
app: escape-game
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600

24
k8s/hpa.yaml Normal file
View File

@@ -0,0 +1,24 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: escape-game-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: escape-game
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

25
k8s/ingress.yaml Normal file
View File

@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: game-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: web, websecure
traefik.ingress.kubernetes.io/router.middlewares: gitea-redirect-https@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- ${APP_URL}
secretName: game-tls-secret
rules:
- host: ${APP_URL}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: escape-game
port:
number: 80

11
k8s/pvc.yaml Normal file
View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: game-assets-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: longhorn
resources:
requests:
storage: 2Gi

75
k8s/redis.yaml Normal file
View File

@@ -0,0 +1,75 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: redis-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: redis
spec:
ports:
- port: 6379
selector:
app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
labels:
app: redis
spec:
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
initContainers:
- name: fix-permissions
image: busybox
command: ["sh", "-c", "chown -R 999:999 /data"]
volumeMounts:
- name: data
mountPath: /data
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
volumeMounts:
- name: data
mountPath: /data
resources:
requests:
memory: "64Mi"
cpu: "50m" # 0.05 CPU Cores
limits:
memory: "256Mi"
cpu: "1000m" # 0.5 CPU Cores
livenessProbe:
exec:
command: ["redis-cli", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command: ["redis-cli", "ping"]
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: redis-data