chore: initialize from backend template 0a6dd0e

This commit is contained in:
DongHyeonka
2026-08-13 20:31:02 +09:00
commit e64e701fe5
3223 changed files with 388401 additions and 0 deletions
@@ -0,0 +1,127 @@
# Storage certification job.
#
# A PersistentVolumeClaim is not a filesystem contract. Whether an atomic rename, a same-file-store
# guarantee, or symlink refusal actually holds depends on the CSI driver, the StorageClass, the
# access mode, the backend, and the mount options — so this job records all five alongside the probe
# result. A certification without that tuple is not transferable to another cluster.
#
# The job writes a machine-readable result to the claim itself so the evidence lives with the volume
# it describes.
#
# kubectl apply -f infra/fileserver/kubernetes/pvc-certification-job.yaml
# kubectl logs job/fileserver-pvc-certification
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fileserver-certification
labels:
app.kubernetes.io/name: fileserver
app.kubernetes.io/component: certification
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
# Left unset on purpose: the certification is only meaningful for the class it actually ran on,
# so the operator names it explicitly rather than inheriting a cluster default.
storageClassName: ""
---
apiVersion: batch/v1
kind: Job
metadata:
name: fileserver-pvc-certification
labels:
app.kubernetes.io/name: fileserver
app.kubernetes.io/component: certification
spec:
backoffLimit: 0
template:
metadata:
labels:
app.kubernetes.io/name: fileserver
app.kubernetes.io/component: certification
spec:
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
containers:
- name: certify
image: eclipse-temurin:21-jdk
env:
- name: FILESERVER_STORAGE_ROOT
value: /var/lib/backend/files
- name: KUBERNETES_VERSION
valueFrom:
fieldRef:
fieldPath: metadata.annotations['certification.fileserver/kubernetes-version']
- name: CSI_DRIVER
valueFrom:
fieldRef:
fieldPath: metadata.annotations['certification.fileserver/csi-driver']
- name: STORAGE_CLASS
valueFrom:
fieldRef:
fieldPath: metadata.annotations['certification.fileserver/storage-class']
- name: ACCESS_MODE
value: ReadWriteOnce
command:
- /bin/bash
- -c
- |
set -euo pipefail
ROOT="${FILESERVER_STORAGE_ROOT}"
mkdir -p "${ROOT}/staging" "${ROOT}/content"
# Atomic rename within one file store is the property the publish path depends on.
echo probe > "${ROOT}/staging/probe"
if mv "${ROOT}/staging/probe" "${ROOT}/content/probe" 2>/dev/null; then
ATOMIC_MOVE=true
else
ATOMIC_MOVE=false
fi
# Same device means a rename is a metadata operation rather than a copy.
STAGING_DEV=$(stat -c %d "${ROOT}/staging")
CONTENT_DEV=$(stat -c %d "${ROOT}/content")
[ "${STAGING_DEV}" = "${CONTENT_DEV}" ] && SAME_STORE=true || SAME_STORE=false
# O_EXCL create is what makes a publish create-only rather than an overwrite.
if (set -o noclobber; echo x > "${ROOT}/content/excl") 2>/dev/null; then
ATOMIC_CREATE=true
else
ATOMIC_CREATE=false
fi
cat > "${ROOT}/certification-result.json" <<RESULT
{
"kubernetesVersion": "${KUBERNETES_VERSION:-unknown}",
"csiDriver": "${CSI_DRIVER:-unknown}",
"storageClass": "${STORAGE_CLASS:-unknown}",
"accessMode": "${ACCESS_MODE}",
"backend": "$(stat -f -c %T "${ROOT}")",
"mountOptions": "$(findmnt -no OPTIONS --target "${ROOT}" || echo unknown)",
"atomicMove": ${ATOMIC_MOVE},
"sameFileStore": ${SAME_STORE},
"atomicCreate": ${ATOMIC_CREATE}
}
RESULT
cat "${ROOT}/certification-result.json"
# Fail closed: a volume that cannot publish atomically must not be certified silently.
[ "${SAME_STORE}" = "true" ] || { echo "staging and content are on different stores"; exit 1; }
volumeMounts:
- name: storage
mountPath: /var/lib/backend/files
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumes:
- name: storage
persistentVolumeClaim:
claimName: fileserver-certification
+61
View File
@@ -0,0 +1,61 @@
# Network-filesystem certification environment.
#
# A local filesystem cannot reproduce the failures this environment exists to test: a rename whose
# acknowledgement is lost, a stale file handle after the server restarts, and a client that keeps
# writing across a network cut. Those are precisely the cases where "the write failed, retry it" is
# the wrong conclusion, so they are certified against a real NFS server rather than a mock.
#
# Opt in with FILESERVER_NFS_TESTS=true; the default test run does not start this.
#
# docker compose -f infra/fileserver/nfs/compose.yml up -d
# FILESERVER_NFS_TESTS=true ./gradlew :adapter:outbound:fileserver:test
#
# To exercise the ambiguity paths:
# docker compose -f infra/fileserver/nfs/compose.yml restart nfs-server # stale handles
# docker network disconnect fileserver-nfs <client> # lost responses
services:
nfs-server:
image: erichough/nfs-server:2.2.1
container_name: fileserver-nfs-server
privileged: true
environment:
NFS_EXPORT_0: "/exports *(rw,sync,no_subtree_check,no_root_squash,fsid=0)"
NFS_VERSION: "4.2"
NFS_LOG_LEVEL: DEBUG
volumes:
- nfs-exports:/exports
ports:
- "2049:2049"
networks:
- fileserver-nfs
healthcheck:
test: ["CMD", "rpcinfo", "-t", "localhost", "nfs", "4"]
interval: 5s
timeout: 3s
retries: 10
nfs-client:
image: eclipse-temurin:21-jdk
container_name: fileserver-nfs-client
privileged: true
depends_on:
nfs-server:
condition: service_healthy
# hard,intr is the correct production mount: a soft mount turns a slow server into a silent
# short write, which is exactly the corruption the design refuses to accept.
command: >
bash -c "mkdir -p /mnt/fileserver &&
mount -t nfs4 -o hard,timeo=50,retrans=2 nfs-server:/ /mnt/fileserver &&
tail -f /dev/null"
volumes:
- ../../..:/workspace:ro
networks:
- fileserver-nfs
volumes:
nfs-exports:
networks:
fileserver-nfs:
name: fileserver-nfs
+67
View File
@@ -0,0 +1,67 @@
# Fileserver front-proxy configuration.
#
# The application authorizes every download and then hands the transfer to Nginx with
# X-Accel-Redirect. Two properties make that safe, and both are enforced here rather than assumed:
#
# 1. /__files/ is `internal`, so it is reachable ONLY through an internal redirect the application
# issued. A direct request from a client returns 404 and never touches the content root.
# 2. The application never emits an absolute path. It emits a relative URI below /__files/, and
# the alias below is the only place that prefix becomes a filesystem location.
#
# Keep `alias` in sync with the storage root's content directory. A mismatch is a startup
# misconfiguration, not a runtime fallback: the application's startup validator checks that the
# internal mapping was proven before it accepts traffic.
worker_processes auto;
events {
worker_connections 4096;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
sendfile_max_chunk 2m;
tcp_nopush on;
keepalive_timeout 65;
# Uploads stream through to the application; buffering a large body to disk here would double
# the write and defeat the streaming upload path.
proxy_request_buffering off;
client_max_body_size 0;
server {
listen 8080;
# Public API. Everything, including download authorization, is decided by the application.
location / {
proxy_pass http://app:8081;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# The application must never see a client-supplied delegation header: it would let a
# caller name an arbitrary internal object.
proxy_set_header X-Accel-Redirect "";
}
# Internal transfer location. Not reachable from outside; see property (1) above.
location /__files/ {
internal;
alias /srv/files/content/;
sendfile on;
sendfile_max_chunk 2m;
# Uploaded content is never trusted to describe itself.
add_header X-Content-Type-Options nosniff always;
add_header Content-Disposition $upstream_http_content_disposition always;
add_header Cache-Control $upstream_http_cache_control always;
add_header ETag $upstream_http_etag always;
}
}
}