diff --git a/apps/mcpacks.yaml b/apps/mcpacks.yaml new file mode 100644 index 0000000..f409de4 --- /dev/null +++ b/apps/mcpacks.yaml @@ -0,0 +1,32 @@ +# This Argo CD Application makes the Minecraft pack host part of the same +# GitOps loop as the rest of the cluster. The image itself is built from the +# separate public mcpacks repository; this repository owns only the desired +# Kubernetes deployment and the exact image revision that should run. +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: mcpacks + namespace: argocd + # The service is stateless because every published pack is baked into its + # container image. Cascading deletion is therefore safe: no world data or + # user-created content would be deleted with the Application. + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + source: + repoURL: https://tea.ahmza.com/ahmza/k8s-starter-pack.git + targetRevision: main + path: apps/mcpacks + + destination: + server: https://kubernetes.default.svc + namespace: mcpacks + + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/apps/mcpacks/deployment.yaml b/apps/mcpacks/deployment.yaml new file mode 100644 index 0000000..25f4c20 --- /dev/null +++ b/apps/mcpacks/deployment.yaml @@ -0,0 +1,83 @@ +# SWS (Static Web Server) serves an image containing only committed Packwiz +# files. There is no writable volume to drift from Git and no database to back +# up; publishing a new image is the complete deployment operation. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mcpacks + namespace: mcpacks +spec: + # Two copies allow one Pod to keep serving Minecraft clients while the other + # is replaced during an image rollout or rescheduled after a node failure. + replicas: 2 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 0 + maxSurge: 1 + selector: + matchLabels: + app: mcpacks + template: + metadata: + labels: + app: mcpacks + spec: + # SWS only reads files from its image and never calls the Kubernetes API, + # so do not inject the namespace's default API credential into its Pods. + automountServiceAccountToken: false + # Keep normal replica placement balanced across nodes. maxSkew 1 still + # permits one temporary surge Pod during a zero-downtime rollout; strict + # anti-affinity would deadlock that rollout when only two workers exist. + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app: mcpacks + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: mcpacks + # The publishing workflow replaces this bootstrap tag with an + # immutable OCI digest. Never deploy the mutable latest tag after + # bootstrap: recording the digest makes rollbacks and audits reliable. + image: tea.ahmza.com/ahmza/mcpacks-site:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 80 + protocol: TCP + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + # The upstream image does not declare USER itself. Set an explicit + # unprivileged identity so runAsNonRoot is enforceable rather than + # causing Kubernetes to reject the container as potentially root. + runAsUser: 10001 + runAsGroup: 10001 + capabilities: + drop: + - ALL + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 2 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 30 + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 250m + memory: 64Mi diff --git a/apps/mcpacks/ingress.yaml b/apps/mcpacks/ingress.yaml new file mode 100644 index 0000000..69a9050 --- /dev/null +++ b/apps/mcpacks/ingress.yaml @@ -0,0 +1,28 @@ +# Route the public Packwiz URLs through ingress-nginx's shared MetalLB address +# (10.10.60.12). mc.ahmza.com already resolves to that address on the LAN. +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: mcpacks + namespace: mcpacks + annotations: + # Prove DNS-01 issuance with Let's Encrypt's untrusted staging service + # first. Change this to letsencrypt-prod only after the staging Certificate + # is Ready; production has strict rate limits for repeated mistakes. + cert-manager.io/cluster-issuer: letsencrypt-staging +spec: + tls: + - hosts: + - mc.ahmza.com + secretName: mcpacks-tls + rules: + - host: mc.ahmza.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: mcpacks + port: + name: http diff --git a/apps/mcpacks/namespace.yaml b/apps/mcpacks/namespace.yaml new file mode 100644 index 0000000..0ad3fcf --- /dev/null +++ b/apps/mcpacks/namespace.yaml @@ -0,0 +1,6 @@ +# Keep the pack-serving resources separate from applications with unrelated +# lifecycles, permissions, and troubleshooting commands. +apiVersion: v1 +kind: Namespace +metadata: + name: mcpacks diff --git a/apps/mcpacks/service.yaml b/apps/mcpacks/service.yaml new file mode 100644 index 0000000..8236716 --- /dev/null +++ b/apps/mcpacks/service.yaml @@ -0,0 +1,14 @@ +# A ClusterIP Service gives ingress-nginx a stable in-cluster destination even +# while Deployment rollouts replace the individual Pods and their addresses. +apiVersion: v1 +kind: Service +metadata: + name: mcpacks + namespace: mcpacks +spec: + selector: + app: mcpacks + ports: + - name: http + port: 80 + targetPort: http diff --git a/docs/network.md b/docs/network.md index 25d9dbf..fd88ca2 100644 --- a/docs/network.md +++ b/docs/network.md @@ -67,6 +67,7 @@ Every real DNS record pointing at ingress-nginx's shared address | Hostname | App | Namespace | TLS | Source | |---|---|---|---|---| | `demo.ahmza.com` | Demo app | `demo` | Yes — `letsencrypt-prod`, real trusted cert (see [`docs/12-cert-manager.md`](12-cert-manager.md)) | [`apps/demo/ingress.yaml`](../apps/demo/ingress.yaml) | +| `mc.ahmza.com` | Packwiz modpack files | `mcpacks` | Staging requested — promote to `letsencrypt-prod` after the staging Certificate is verified Ready | [`apps/mcpacks/ingress.yaml`](../apps/mcpacks/ingress.yaml) | Add a row here every time you point a new hostname at this cluster (see [`docs/11-ingress.md`](11-ingress.md)). The DNS record itself lives in