diff --git a/deploy/bases/frontend/cronjob-warm-cache.yaml b/deploy/bases/frontend/cronjob-warm-cache.yaml new file mode 100644 index 0000000..28b5840 --- /dev/null +++ b/deploy/bases/frontend/cronjob-warm-cache.yaml @@ -0,0 +1,68 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: warm-cache +spec: + # Runs every 5 minutes + schedule: "*/5 * * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + # Cleanup after 1 hour + ttlSecondsAfterFinished: 3600 + backoffLimit: 3 + template: + metadata: + labels: + app.kubernetes.io/name: warm-cache + app.kubernetes.io/part-of: frontend + spec: + serviceAccountName: frontend + restartPolicy: OnFailure + initContainers: + - name: warm-cache-init + image: ghcr.io/stefanprodan/podinfo:6.13.0 + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - /scripts/warm-cache-init.sh + env: + - name: FRONTEND_URL + value: "http://frontend" + resources: + limits: + cpu: 100m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + volumeMounts: + - name: scripts + mountPath: /scripts + containers: + - name: warm-cache + image: ghcr.io/stefanprodan/podinfo:6.13.0 + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - /scripts/warm-cache.sh + env: + - name: FRONTEND_URL + value: "http://frontend" + resources: + limits: + cpu: 100m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + volumeMounts: + - name: scripts + mountPath: /scripts + volumes: + - name: scripts + configMap: + name: warm-cache-script + defaultMode: 0755 diff --git a/deploy/bases/frontend/deployment.yaml b/deploy/bases/frontend/deployment.yaml index 9338042..2d6a4eb 100644 --- a/deploy/bases/frontend/deployment.yaml +++ b/deploy/bases/frontend/deployment.yaml @@ -21,6 +21,7 @@ spec: labels: app.kubernetes.io/name: frontend spec: + serviceAccountName: frontend containers: - name: frontend image: ghcr.io/stefanprodan/podinfo:6.13.0 @@ -39,7 +40,7 @@ spec: - ./podinfo - --port=9898 - --port-metrics=9797 - - --level=info + - --level=debug - --backend-url=http://backend:9898/echo - --cache-server=tcp://cache:6379 env: diff --git a/deploy/bases/frontend/hpa.yaml b/deploy/bases/frontend/hpa.yaml index 03223fc..fe75bbc 100644 --- a/deploy/bases/frontend/hpa.yaml +++ b/deploy/bases/frontend/hpa.yaml @@ -7,7 +7,7 @@ spec: apiVersion: apps/v1 kind: Deployment name: frontend - minReplicas: 1 + minReplicas: 2 maxReplicas: 4 metrics: - type: Resource diff --git a/deploy/bases/frontend/kustomization.yaml b/deploy/bases/frontend/kustomization.yaml index f753976..18010fb 100644 --- a/deploy/bases/frontend/kustomization.yaml +++ b/deploy/bases/frontend/kustomization.yaml @@ -1,7 +1,15 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: + - serviceaccount.yaml - service.yaml - deployment.yaml - hpa.yaml - + - cronjob-warm-cache.yaml +configMapGenerator: + - name: warm-cache-script + files: + - scripts/warm-cache.sh + - scripts/warm-cache-init.sh + options: + disableNameSuffixHash: true diff --git a/deploy/bases/frontend/scripts/warm-cache-init.sh b/deploy/bases/frontend/scripts/warm-cache-init.sh new file mode 100755 index 0000000..2c28c26 --- /dev/null +++ b/deploy/bases/frontend/scripts/warm-cache-init.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +# Init step for the warm-cache job. +# Calls the frontend in verbose mode so the full connection, request and +# response debug data (DNS, TCP, headers, timings) is dumped to the logs. + +FRONTEND="${FRONTEND_URL:-http://frontend}" + +echo "Dumping debug data for ${FRONTEND}/api/info" +# -v writes the verbose debug data to stderr, redirect it to stdout so it +# lands in the container logs together with the response body. +curl -v "${FRONTEND}/api/info" 2>&1 diff --git a/deploy/bases/frontend/scripts/warm-cache.sh b/deploy/bases/frontend/scripts/warm-cache.sh new file mode 100755 index 0000000..87da8fa --- /dev/null +++ b/deploy/bases/frontend/scripts/warm-cache.sh @@ -0,0 +1,69 @@ +#!/bin/sh +set -e + +# This is a simulation of a cache warming process. +# It fetches the frontend info, stores it in the cache under the job pod +# hostname and reads it back to verify the round-trip. +# +# Log lines mimic the Log4j 2 default PatternLayout: +# %d{yyyy-MM-dd HH:mm:ss,SSS} [%thread] %-5level %logger{36} - %msg + +LOGGER="com.stefanprodan.podinfo.WarmCache" +THREAD="main" + +log() { + level="$1" + shift + # millisecond precision timestamp; fall back gracefully if %N is unsupported + ts="$(date '+%Y-%m-%d %H:%M:%S,%N' 2>/dev/null)" + case "$ts" in + *%N|*,) ts="$(date '+%Y-%m-%d %H:%M:%S'),000" ;; + *) ts="$(echo "$ts" | cut -c1-23)" ;; + esac + printf '%s [%s] %-5s %s - %s\n' "$ts" "$THREAD" "$level" "$LOGGER" "$*" +} + +FRONTEND="${FRONTEND_URL:-http://frontend}" +KEY="$(hostname)" +START="$(date +%s)" + +log INFO "Fetching info from ${FRONTEND}/api/info" +# Compact the (pretty-printed) JSON onto a single line so each log event +# stays on one line, as Log4j renders them. +INFO="$(curl -fsS "${FRONTEND}/api/info" | tr -d '\n' | sed 's/ */ /g')" +log INFO "Info: ${INFO}" + +# Log each field of the info response on its own line. The /api/info payload +# is a flat JSON object of string values, so we can split it without jq. +echo "$INFO" | sed 's/^{//; s/}$//' | tr ',' '\n' | while IFS= read -r field; do + field="$(echo "$field" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/"//g')" + if [ -n "$field" ]; then + log DEBUG "info ${field}" + fi +done + +log INFO "Writing info to cache key ${KEY}" +curl -fsS -X POST -H "Content-Type: application/json" -d "${INFO}" "${FRONTEND}/cache/${KEY}" + +# Verify the cached value for 1 minute, polling every 10 seconds. +attempt=1 +while [ "$attempt" -le 6 ]; do + log INFO "Reading cache key ${KEY} (attempt ${attempt}/6)" + CACHED="$(curl -fsS "${FRONTEND}/cache/${KEY}")" + + if [ "${INFO}" = "${CACHED}" ]; then + log INFO "Cache warm verified: cached value matches info" + else + log ERROR "Cache warm failed: cached value does not match info" + log ERROR "Expected: ${INFO}" + log ERROR "Got: ${CACHED}" + exit 1 + fi + + attempt=$((attempt + 1)) + if [ "$attempt" -le 6 ]; then + sleep 10 + fi +done + +log INFO "Cache warm finished in $(( $(date +%s) - START ))s" diff --git a/deploy/bases/frontend/serviceaccount.yaml b/deploy/bases/frontend/serviceaccount.yaml new file mode 100644 index 0000000..656cd4f --- /dev/null +++ b/deploy/bases/frontend/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: frontend