diff --git a/README.md b/README.md index 4279282..290096f 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,22 @@ Add new variables to `deploy-project.sh` to enable pod autoscaling: ... ``` +### Enable TTFB monitoring (Prometheus Probe) + +Add a `TTFB_PROBES` associative array to `deploy-project.sh` — key is the probe +type, value is the URL path (empty for root): + +```diff ++ declare -A TTFB_PROBES=( ++ ["Homepage"]="/" ++ ["Detail"]="/some/product-path" ++ ) ++ TTFB_PROBES_DOMAIN=DOMAIN_HOSTNAME_1 # optional; default DOMAIN_HOSTNAME_1 +``` + +Probes are generated only for production. If the domain is behind +HTTP auth, `HTTP_AUTH_CREDENTIALS` is automatically embedded into the probe URL. + ### Whitelist IP addresses There are two ways to set whitelisted IP addresses. diff --git a/UPGRADE.md b/UPGRADE.md index 9d8be56..2609ab8 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -10,6 +10,12 @@ 2. Run `composer update shopsys/deployment` 3. Check files in mentioned pull requests and if you have any of them extended in your project, apply changes manually +## Upgrade from v5.0.0 to v5.0.1 + +- added support for TTFB monitoring via Prometheus Probe objects ([#70](https://github.com/shopsys/deployment/pull/70)) + - in your `deploy-project.sh` add `source "${DEPLOY_TARGET_PATH}/parts/probes.sh"` right before the `source` of `deploy.sh` (which is always last) + - to enable probes, declare the `TTFB_PROBES` associative array in `deploy-project.sh` — see [README](README.md#enable-ttfb-monitoring-prometheus-probe) for configuration detail + ## Upgrade from v4.6.1 to v5.0.0 - remove files that are already part of project-base by default ([#66](https://github.com/shopsys/deployment/pull/66)) diff --git a/deploy/parts/probes.sh b/deploy/parts/probes.sh new file mode 100644 index 0000000..788594b --- /dev/null +++ b/deploy/parts/probes.sh @@ -0,0 +1,102 @@ +#!/bin/bash -e + +# Generates Prometheus Probe manifests for TTFB measurement via blackbox exporter. +# See README for configuration format. + +assertVariable "PROJECT_ENVIRONMENT" +assertVariable "NAME_OF_PROJECT" +assertVariable "CONFIGURATION_TARGET_PATH" +assertVariable "RUNNING_PRODUCTION" + +if [ "${PROJECT_ENVIRONMENT}" != "prod" ]; then + return 0 +fi + +# Silent skip if TTFB_PROBES is not defined at all (project doesn't use probes). +if ! declare -p TTFB_PROBES >/dev/null 2>&1; then + return 0 +fi + +if [ ${#TTFB_PROBES[@]} -eq 0 ]; then + return 0 +fi + +echo "Prepare TTFB probes" + +PROBE_TEMPLATE_PATH="${CONFIGURATION_TARGET_PATH}/manifest-templates/probe.template.yaml" +PROBES_DIR="${CONFIGURATION_TARGET_PATH}/probes" +PROBES_KUSTOMIZATION="${CONFIGURATION_TARGET_PATH}/kustomize/webserver/kustomization.yaml" + +if [ ! -f "${PROBE_TEMPLATE_PATH}" ]; then + echo -e " [${YELLOW}WARN${NO_COLOR}] probe template ${PROBE_TEMPLATE_PATH} not found - skipping all TTFB probes" + return 0 +fi + +mkdir -p "${PROBES_DIR}" + +if [ -z "${FORCE_HTTP_AUTH_IN_PRODUCTION}" ]; then + FORCE_HTTP_AUTH_IN_PRODUCTION=() +fi + +TTFB_PROBES_DOMAIN=${TTFB_PROBES_DOMAIN:-DOMAIN_HOSTNAME_1} +DOMAIN=${!TTFB_PROBES_DOMAIN} + +if [ -z "${DOMAIN}" ]; then + echo -e " [${YELLOW}WARN${NO_COLOR}] domain variable ${TTFB_PROBES_DOMAIN} is not set - skipping all TTFB probes" + return 0 +fi + +# HTTP auth — same rules as domains.sh:84. Applied once, because all probes +# target the same domain. Embed user:password@ before the hostname whenever: +# - RUNNING_PRODUCTION != 1 (whole site is behind auth), or +# - the domain is listed in FORCE_HTTP_AUTH_IN_PRODUCTION. +AUTH_PREFIX="" +if [ "${RUNNING_PRODUCTION}" != "1" ] || containsElement "${TTFB_PROBES_DOMAIN}" "${FORCE_HTTP_AUTH_IN_PRODUCTION[@]}"; then + if [ -z "${HTTP_AUTH_CREDENTIALS}" ]; then + echo -e " [${YELLOW}WARN${NO_COLOR}] HTTP auth is active for ${TTFB_PROBES_DOMAIN} but HTTP_AUTH_CREDENTIALS is not set - skipping all TTFB probes" + return 0 + fi + AUTH_PREFIX="${HTTP_AUTH_CREDENTIALS}@" +fi + +# Sort keys for deterministic output (associative array iteration order is +# implementation-defined and would make snapshots and logs non-reproducible). +readarray -t SORTED_PROBE_TYPES < <(printf '%s\n' "${!TTFB_PROBES[@]}" | sort) + +for PROBE_TYPE in "${SORTED_PROBE_TYPES[@]}"; do + PROBE_PATH="${TTFB_PROBES[${PROBE_TYPE}]}" + + # Probe type must be a single alphanumeric word — keeps resource names + # predictable and avoids slug collisions (e.g. "Home Page" vs "home-page"). + if [[ ! "${PROBE_TYPE}" =~ ^[a-zA-Z0-9]+$ ]]; then + echo -e " [${YELLOW}WARN${NO_COLOR}] invalid probe type \"${PROBE_TYPE}\" (only letters and digits are allowed, no spaces or hyphens) - skipped" + continue + fi + + # Normalize path: accept "", "/", "/foo", "foo", "foo/bar" and produce + # either "" or "/foo..." — never a bare "/" and never missing leading /. + PROBE_PATH="${PROBE_PATH#/}" + [ -n "${PROBE_PATH}" ] && PROBE_PATH="/${PROBE_PATH}" + + PROBE_SLUG=$(echo "${PROBE_TYPE}" | tr '[:upper:]' '[:lower:]') + PROBE_URL="https://${AUTH_PREFIX}${DOMAIN}${PROBE_PATH}" + PROBE_FILENAME="probe-${PROBE_SLUG}.yaml" + PROBE_FILE="${PROBES_DIR}/${PROBE_FILENAME}" + + cp "${PROBE_TEMPLATE_PATH}" "${PROBE_FILE}" + + PROBE_NAME="ttfb-${PROBE_SLUG}" \ + PROBE_URL="${PROBE_URL}" \ + PROBE_PROJECT="${NAME_OF_PROJECT}" \ + PROBE_TYPE_LABEL="${PROBE_TYPE}" \ + yq e -i ' + .metadata.name = strenv(PROBE_NAME) | + .spec.targets.staticConfig.static[0] = strenv(PROBE_URL) | + .spec.targets.staticConfig.labels.project = strenv(PROBE_PROJECT) | + .spec.targets.staticConfig.labels.type = strenv(PROBE_TYPE_LABEL) + ' "${PROBE_FILE}" + + yq e -i ".resources += [\"../../probes/${PROBE_FILENAME}\"]" "${PROBES_KUSTOMIZATION}" + + echo -e " [${GREEN}OK${NO_COLOR}] ttfb-${PROBE_SLUG} (${PROBE_TYPE}): ${DOMAIN}${PROBE_PATH}" +done diff --git a/kubernetes/manifest-templates/probe.template.yaml b/kubernetes/manifest-templates/probe.template.yaml new file mode 100644 index 0000000..b6f7f3c --- /dev/null +++ b/kubernetes/manifest-templates/probe.template.yaml @@ -0,0 +1,19 @@ +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: PLACEHOLDER +spec: + jobName: ttfb + interval: 60s + scrapeTimeout: 45s + module: http_ttfb + scrapeClass: ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + targets: + staticConfig: + static: + - PLACEHOLDER + labels: + project: PLACEHOLDER + type: PLACEHOLDER diff --git a/tests/lib/scenario-base.sh b/tests/lib/scenario-base.sh index 83fad6f..6a3a40b 100644 --- a/tests/lib/scenario-base.sh +++ b/tests/lib/scenario-base.sh @@ -52,6 +52,7 @@ function run_generate() { source "${DEPLOY_TARGET_PATH}/parts/kubernetes-variables.sh" source "${DEPLOY_TARGET_PATH}/parts/cron.sh" source "${DEPLOY_TARGET_PATH}/parts/autoscaling.sh" + source "${DEPLOY_TARGET_PATH}/parts/probes.sh" } function run_merge() { diff --git a/tests/scenarios/ttfb-custom-domain/deploy-project.sh b/tests/scenarios/ttfb-custom-domain/deploy-project.sh new file mode 100644 index 0000000..32dbcf3 --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/deploy-project.sh @@ -0,0 +1,31 @@ +#!/bin/bash -e +SCENARIO_NAME="ttfb-custom-domain" +source "$(dirname "$0")/../../lib/scenario-base.sh" + +# Scenario-specific configuration +DOMAINS=(DOMAIN_HOSTNAME_1 DOMAIN_HOSTNAME_2) +export RUNNING_PRODUCTION=1 +ENABLE_AUTOSCALING=true + +declare -A CRON_INSTANCES=( + ["cron"]='*/5 * * * *' +) + +DEFAULT_CONSUMERS=("email:email_transport:1") + +# TTFB probes target DOMAIN_HOSTNAME_2 ("www.example.com/cz") instead of the +# default. The probe paths intentionally exercise three formats: +# "" - no path (domain root, including the /cz suffix) +# "/with-slash" - path with leading slash +# "without-slash" - path without leading slash (must be auto-prefixed) +TTFB_PROBES_DOMAIN=DOMAIN_HOSTNAME_2 +declare -A TTFB_PROBES=( + ["Homepage"]="" + ["Detail"]="/sample-product" + ["Category"]="sample-category" +) + +case "$1" in + "generate") run_merge; run_generate ;; + *) echo "Usage: $0 generate"; exit 1 ;; +esac diff --git a/tests/scenarios/ttfb-custom-domain/description.txt b/tests/scenarios/ttfb-custom-domain/description.txt new file mode 100644 index 0000000..796a186 --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/description.txt @@ -0,0 +1 @@ +TTFB probes overridden to DOMAIN_HOSTNAME_2 which carries a path ("/cz") in the hostname, covering path-normalization variants diff --git a/tests/scenarios/ttfb-custom-domain/env.sh b/tests/scenarios/ttfb-custom-domain/env.sh new file mode 100644 index 0000000..a98155d --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/env.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Scenario-specific overrides (defaults are in lib/default-env.sh) + +export PROJECT_NAME="myproject-prod" +export DOMAIN_HOSTNAME_1="www.example.com" +export DOMAIN_HOSTNAME_2="www.example.com/cz" +export DOMAIN_COUNT=2 diff --git a/tests/scenarios/ttfb-custom-domain/expected/cron.yaml b/tests/scenarios/ttfb-custom-domain/expected/cron.yaml new file mode 100644 index 0000000..4941802 --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/expected/cron.yaml @@ -0,0 +1,182 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + .project_env.sh: | + export ELASTICSEARCH_HOST='http://elasticsearch:9200' + export TRUSTED_PROXY='10.0.0.0/8' + export DATABASE_NAME='myproject-prod' + export S3_ENDPOINT='https://s3.example.com' + export MAILER_FORCE_WHITELIST='false' + export DATABASE_USER='myproject-prod' + export DATABASE_PORT='5432' + export MESSENGER_TRANSPORT_DSN='amqp://guest:guest@rabbitmq:5672/%2f/messages' + export REDIS_PREFIX='myproject-prod' + export DATABASE_PASSWORD='test-db-password' + export DATABASE_HOST='10.0.0.100' + export ELASTIC_SEARCH_INDEX_PREFIX='myproject-prod' + export S3_SECRET='test-s3-secret' + export MAILER_DSN='smtp://mailhog:1025' + export S3_BUCKET_NAME='myproject-prod' + export S3_ACCESS_KEY='myproject-prod' + export APP_SECRET='test-app-secret-key' +kind: ConfigMap +metadata: + name: cron-env + namespace: myproject-prod +--- +apiVersion: v1 +data: + cron: |+ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + */5 * * * * . /root/.project_env.sh && cd /var/www/html/ && ./phing cron > /dev/null 2>&1 + +kind: ConfigMap +metadata: + name: cron-list + namespace: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: cron + name: cron + namespace: myproject-prod +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + selector: + matchLabels: + app: cron + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: cron + project/environment: prod + project/name: myproject + labels: + app: cron + date: "1234567890" + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - cd /var/www/html && ./phing warmup > /dev/null && rm -rf /tmp/log-pipe && + mkfifo /tmp/log-pipe && chmod 666 /tmp/log-pipe && crontab -u root /var/spool/cron/template + && { crond || cron; } && stdbuf -o0 tail -n +1 -f /tmp/log-pipe + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - sleep + - "5" + name: cron + securityContext: + runAsUser: 0 + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/spool/cron/template + name: cron-list + subPath: cron + - mountPath: /root/.project_env.sh + name: cron-env + subPath: .project_env.sh + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - configMap: + name: cron-list + name: cron-list + - configMap: + name: cron-env + name: cron-env + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys diff --git a/tests/scenarios/ttfb-custom-domain/expected/horizontalPodAutoscaler.yaml b/tests/scenarios/ttfb-custom-domain/expected/horizontalPodAutoscaler.yaml new file mode 100644 index 0000000..59fab22 --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/expected/horizontalPodAutoscaler.yaml @@ -0,0 +1,20 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: webserver-php-fpm + namespace: "myproject-prod" +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: webserver-php-fpm + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: php-fpm + target: + type: Utilization + averageUtilization: 120 diff --git a/tests/scenarios/ttfb-custom-domain/expected/horizontalStorefrontAutoscaler.yaml b/tests/scenarios/ttfb-custom-domain/expected/horizontalStorefrontAutoscaler.yaml new file mode 100644 index 0000000..762c898 --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/expected/horizontalStorefrontAutoscaler.yaml @@ -0,0 +1,19 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: storefront + namespace: "myproject-prod" +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: storefront + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 120 diff --git a/tests/scenarios/ttfb-custom-domain/expected/migrate-continuous-deploy.yaml b/tests/scenarios/ttfb-custom-domain/expected/migrate-continuous-deploy.yaml new file mode 100644 index 0000000..00c4f1b --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/expected/migrate-continuous-deploy.yaml @@ -0,0 +1,422 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && ./phing -verbose db-migrations-count-with-maintenance + build-deploy-part-2-db-dependent + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-custom-domain/expected/migrate-first-deploy-with-demo-data.yaml b/tests/scenarios/ttfb-custom-domain/expected/migrate-first-deploy-with-demo-data.yaml new file mode 100644 index 0000000..b302eb2 --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/expected/migrate-first-deploy-with-demo-data.yaml @@ -0,0 +1,422 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && sleep 30 && ./phing cluster-first-deploy db-fixtures-demo + plugin-demo-data-load friendly-urls-generate domains-urls-replace elasticsearch-export + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-custom-domain/expected/migrate-first-deploy.yaml b/tests/scenarios/ttfb-custom-domain/expected/migrate-first-deploy.yaml new file mode 100644 index 0000000..5478225 --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/expected/migrate-first-deploy.yaml @@ -0,0 +1,421 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && sleep 30 && ./phing cluster-first-deploy + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-custom-domain/expected/namespace.yaml b/tests/scenarios/ttfb-custom-domain/expected/namespace.yaml new file mode 100644 index 0000000..3e16dc1 --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/expected/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: "myproject-prod" diff --git a/tests/scenarios/ttfb-custom-domain/expected/webserver.yaml b/tests/scenarios/ttfb-custom-domain/expected/webserver.yaml new file mode 100644 index 0000000..a95ef2c --- /dev/null +++ b/tests/scenarios/ttfb-custom-domain/expected/webserver.yaml @@ -0,0 +1,837 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + nginx.conf: | + user nginx; + worker_processes 2; + + error_log /dev/stderr warn; + pid /var/run/nginx.pid; + + events { + # determines how much clients will be served per worker + # max clients = worker_connections * worker_processes + # max clients is also limited by the number of socket connections available on the system (~64k) + worker_connections 512; + } + + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '"host=$host" ' + 'upstream_response_time=$upstream_response_time'; + + access_log /dev/stdout main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + keepalive_timeout 65; + + server_names_hash_bucket_size 64; + + include /etc/nginx/conf.d/*.conf; + } + project-nginx.conf: | + gzip on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_vary on; + gzip_types text/plain + text/css + text/javascript + application/javascript + application/json + application/xml + application/rss+xml + image/svg+xml; + + upstream php-upstream { + server php-fpm:9000; + } + + upstream storefront-upstream { + server storefront:3000; + } + + # storefront error page if accessed directly, plain text 404 if accessed via CDN + map "$http_cdn_vshosting_real_ip$http_cdn_vshosting_real_ip_img" $custom_error_target { + default @storefront; + "~.+" @404; + } + + server { + listen 80; + root /var/www/html/web; + + location /health { + stub_status on; + access_log off; + } + } + + server { + listen 8080; + root /var/www/html/web; + server_tokens off; + proxy_ignore_client_abort on; + + proxy_buffer_size 16k; + proxy_buffers 32 16k; + + client_body_buffer_size 32k; + client_header_buffer_size 1k; + client_max_body_size 32m; + large_client_header_buffers 4 8k; + + fastcgi_buffer_size 16k; + fastcgi_buffers 32 16k; + + types_hash_max_size 2048; + + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 103.21.244.0/22; + set_real_ip_from 103.22.200.0/22; + set_real_ip_from 103.31.4.0/22; + set_real_ip_from 104.16.0.0/13; + set_real_ip_from 104.24.0.0/14; + set_real_ip_from 108.162.192.0/18; + set_real_ip_from 131.0.72.0/22; + set_real_ip_from 141.101.64.0/18; + set_real_ip_from 162.158.0.0/15; + set_real_ip_from 172.64.0.0/13; + set_real_ip_from 173.245.48.0/20; + set_real_ip_from 188.114.96.0/20; + set_real_ip_from 190.93.240.0/20; + set_real_ip_from 197.234.240.0/22; + set_real_ip_from 198.41.128.0/17; + set_real_ip_from 2400:cb00::/32; + set_real_ip_from 2606:4700::/32; + set_real_ip_from 2803:f800::/32; + set_real_ip_from 2405:b500::/32; + set_real_ip_from 2405:8100::/32; + set_real_ip_from 2a06:98c0::/29; + set_real_ip_from 2c0f:f248::/32; + + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Credentials "false" always; + add_header VSHCDN-WEBP-QUALITY 90; + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + set $request_host $http_host; + if ($http_originalhost) { + set $request_host $http_originalhost; + } + + # define code to be used to redirect to the proper upstream + error_page 470 = @app; + error_page 469 = @storefront; + error_page 468 = @imageResizer; + + location = /resolve-friendly-url { + allow 10.0.0.0/8; + allow 127.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + deny all; + + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root/resolveFriendlyUrl.php; + } + + # (?:/|$) in regexes is used to match /path, /path/, /path/subpath but not /pathology + + # location always using the app backend, no static files + location ~ ^/(?:[^/]+/)?(graphql|_profiler|_wdt|_error)(?:/|$) { + return 470; # send to @app + } + + location ~ ^/(?:[^/]+/)?order/payment-status-notify(?:/|$) { + fastcgi_intercept_errors on; + add_header "Access-Control-Allow-Origin" ""; + + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param SCRIPT_FILENAME $realpath_root/index.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + fastcgi_param HTTP_HOST $request_host; + error_page 404 = @storefront; + } + + # location for administration interface, uses admin error pages + location ~ ^/(?:[^/]+/)?(admin|ckeditor|elfinder(\.main\.js)?|efconnect|build|bundles)(?:/|$) { + # hide dotfiles (send to @app) + location ~ /\. { + return 470; # send to @app + } + + try_files $uri @app; + } + + # location for static files, uses storefront error pages + location ~ ^/(public)/ { + # hide dotfiles (send to @app) + location ~ /\. { + return 469; # send to @storefront + } + + try_files $uri $custom_error_target; + } + + location ~ ^/content/images/(?\w+)(?/\w+)?/(?(default|original|galleryThumbnail|modal|list|thumbnail|thumbnailSmall|thumbnailExtraSmall|thumbnailMedium|header|footer|productList|productListSecondRow|cartPreview|productListMiddle|productListMiddleRetina|listAside|listGrid|searchThumbnail|listBig)/)(?\d+--)?(?([\w\-]+_)?(?\d+))\.(?jpg|jpeg|png|gif) { + expires 1w; + return 301 $scheme://$http_host/content/images/$entity_name$image_type/$image_name.$image_extension$is_args$args; + } + + # location for images, strip image name and serve image by its ID (send to imageResizer if there are width/height args) + location ~ ^/(?:[^/]+/)?(content(?:-test)?/images/.+)/(?([\w\-]+_)?(?\d+))\.(?jpe?g|png|gif) { + expires 1y; + + error_page 403 404 = $custom_error_target; + # this needs to be repeated here because of nginx error_page inheritance rules + error_page 468 = @imageResizer; + + if ($is_args != '') { + return 468; # send to @imageResizer + } + + proxy_intercept_errors on; + + proxy_http_version 1.1; + proxy_set_header Authorization ""; + proxy_buffering off; + + proxy_pass https://s3.example.com/myproject-prod/web/$1/$image_id.$image_extension; + } + + location ~ ^/(content)/ { + proxy_intercept_errors on; + error_page 404 = $custom_error_target; + + proxy_http_version 1.1; + proxy_set_header Authorization ""; + proxy_buffering off; + + proxy_pass https://s3.example.com/myproject-prod/web$request_uri; + } + + # location for backend routes used by customers + # they have to force storefront 404 page if not found + # throw NotFoundRedirectToStorefrontException to trigger this behavior + location ~ ^/(?:[^/]+/)?(file|customer-file/(view|download)|personal-overview-export/xml|social-network/login|convertim)(?:/|$) { + location ~ /\. { + # hide dotfiles (send to @storefront) + return 469; + } + + try_files $uri @app; + } + + location ^~ /_next/ { + return 469; # send to @storefront + } + + # disallow access to dynamic content from CDN + location ~ / { + if ($http_cdn_vshosting_real_ip != '') { + return 403; + } + if ($http_cdn_vshosting_real_ip_img != '') { + return 403; + } + + return 469; # send to @storefront + } + + location @storefront { + internal; + proxy_hide_header Access-Control-Allow-Origin; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass http://storefront-upstream; + } + + location @app { + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param HTTP_HOST $request_host; + # use $realpath_root instead of $document_root + # because of symlink switching when deploying + fastcgi_send_timeout 120s; + fastcgi_read_timeout 120s; + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param SCRIPT_FILENAME $realpath_root/index.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + } + + location @imageResizer { + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root/imageResizer.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + fastcgi_param HTTP_HOST $request_host; + fastcgi_param REQUEST_SCHEME $http_x_forwarded_proto; + } + + # plain 404 page for missing files accessed via CDN + # to avoid displaying storefront on the CDN domain + location @404 { + internal; + types {} + default_type text/html; + return 404 "File not found"; + } + } +kind: ConfigMap +metadata: + name: nginx-default-config + namespace: myproject-prod +--- +apiVersion: v1 +data: + www.conf: | + ; The below default configuration is based on a server without much resources. + ; Don't forget to tweak it to fit expected workload and hardware. + ; + ; https://www.php.net/manual/en/install.fpm.configuration.php + [global] + + log_level = warning + + [www] + + listen = 127.0.0.1:9000 + + pm = dynamic + pm.max_children = 20 + pm.start_servers = 5 + pm.min_spare_servers = 5 + pm.max_spare_servers = 10 + pm.max_requests = 400 + + request_terminate_timeout = 60s + + access.log = /dev/null +kind: ConfigMap +metadata: + name: production-php-fpm + namespace: myproject-prod +--- +apiVersion: v1 +data: + php-opcache.ini: | + opcache.enable = 1 + opcache.fast_shutdown = true + opcache.interned_strings_buffer = 24 + opcache.max_accelerated_files = 60000 + opcache.memory_consumption = 256 + opcache.revalidate_path = 0 + opcache.revalidate_freq = 0 + opcache.validate_timestamps = 0 + opcache.use_cwd = 0 + opcache.preload = "/var/www/html/app/preload.php" +kind: ConfigMap +metadata: + name: production-php-opcache + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + name: storefront + namespace: myproject-prod +spec: + ports: + - name: storefront + port: 3000 + targetPort: 3000 + selector: + app: storefront +--- +apiVersion: v1 +kind: Service +metadata: + name: webserver-php-fpm + namespace: myproject-prod +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: webserver-php-fpm +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storefront + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: storefront + template: + metadata: + annotations: + logging/enabled: "false" + project/app: storefront + project/environment: prod + project/name: myproject + labels: + app: storefront + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - storefront + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: DOMAIN_HOSTNAME_1 + value: https://www.example.com/ + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_1 + value: https://www.example.com/graphql/ + - name: DOMAIN_HOSTNAME_2 + value: https://www.example.com/cz/ + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_2 + value: https://www.example.com/cz/graphql/ + - name: INTERNAL_ENDPOINT + value: http://webserver-php-fpm:8080/ + image: v1.0.0 + lifecycle: + preStop: + exec: + command: + - sleep + - "10" + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + name: storefront + ports: + - containerPort: 3000 + name: storefront + protocol: TCP + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 1.5Gi + requests: + cpu: 500m + memory: 800Mi + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 60 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: webserver-php-fpm + name: webserver-php-fpm + namespace: myproject-prod +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + selector: + matchLabels: + app: webserver-php-fpm + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: app + project/environment: prod + project/name: myproject + labels: + app: webserver-php-fpm + spec: + affinity: + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - redis + topologyKey: kubernetes.io/hostname + weight: 100 + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - webserver-php-fpm + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + postStart: + exec: + command: + - /var/www/html/phing + - -S + - warmup + preStop: + exec: + command: + - sh + - -c + - sleep 10 && kill -SIGQUIT 1 + name: php-fpm + resources: + limits: + memory: 2Gi + requests: + cpu: 500m + memory: 500Mi + volumeMounts: + - mountPath: /var/www/html + name: source-codes + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /usr/local/etc/php-fpm.d/www.conf + name: production-php-fpm + subPath: www.conf + - mountPath: /usr/local/etc/php/conf.d/php-opcache.ini + name: production-php-opcache + subPath: php-opcache.ini + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + - image: nginx:1.29-alpine + lifecycle: + preStop: + exec: + command: + - sh + - -c + - sleep 5 && /usr/sbin/nginx -s quit + livenessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + name: webserver + ports: + - containerPort: 8080 + name: http + readinessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 300Mi + requests: + cpu: 50m + memory: 100Mi + volumeMounts: + - mountPath: /etc/nginx/nginx.conf + name: nginx-default-config + subPath: nginx.conf + - mountPath: /etc/nginx/conf.d/default.conf + name: nginx-default-config + subPath: project-nginx.conf + - mountPath: /var/www/html + name: source-codes + hostAliases: + - hostnames: + - webserver-php-fpm + - php-fpm + - webserver + ip: 127.0.0.1 + imagePullSecrets: + - name: dockerregistry + initContainers: + - command: + - sh + - -c + - cp -r -n /var/www/html/. /tmp/source-codes + image: v1.0.0 + name: copy-source-codes-to-volume + volumeMounts: + - mountPath: /tmp/source-codes + name: source-codes + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + terminationGracePeriodSeconds: 120 + volumes: + - emptyDir: {} + name: source-codes + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - configMap: + name: nginx-default-config + name: nginx-default-config + - configMap: + name: production-php-fpm + name: production-php-fpm + - configMap: + name: production-php-opcache + name: production-php-opcache + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: ttfb-category + namespace: myproject-prod +spec: + interval: 60s + jobName: ttfb + module: http_ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + scrapeClass: ttfb + scrapeTimeout: 45s + targets: + staticConfig: + labels: + project: myproject + type: Category + static: + - https://www.example.com/cz/sample-category +--- +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: ttfb-detail + namespace: myproject-prod +spec: + interval: 60s + jobName: ttfb + module: http_ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + scrapeClass: ttfb + scrapeTimeout: 45s + targets: + staticConfig: + labels: + project: myproject + type: Detail + static: + - https://www.example.com/cz/sample-product +--- +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: ttfb-homepage + namespace: myproject-prod +spec: + interval: 60s + jobName: ttfb + module: http_ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + scrapeClass: ttfb + scrapeTimeout: 45s + targets: + staticConfig: + labels: + project: myproject + type: Homepage + static: + - https://www.example.com/cz +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/configuration-snippet: if ($scheme = http) { return + 308 https://$host$request_uri; } if ($host ~ ^(?!www\.)(?.+)$) { return + 308 https://www.$domain$request_uri; } + nginx.ingress.kubernetes.io/proxy-body-size: 32m + name: eshop-domain-0 + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: www.example.com + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: example.com + tls: + - hosts: + - www.example.com + - example.com + secretName: tls-www-example-com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/configuration-snippet: if ($scheme = http) { return + 308 https://$host$request_uri; } if ($host ~ ^(?!www\.)(?.+)$) { return + 308 https://www.$domain$request_uri; } + nginx.ingress.kubernetes.io/proxy-body-size: 32m + name: eshop-domain-1 + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: www.example.com + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: /cz + pathType: Prefix + - host: example.com + tls: + - hosts: + - www.example.com + - example.com + secretName: tls-www-example-com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: 10.0.0.0/8 + name: rabbitmq-domain + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: rabbitmq.www.example.com + http: + paths: + - backend: + service: + name: rabbitmq + port: + number: 15672 + path: / + pathType: Prefix + tls: + - hosts: + - rabbitmq.www.example.com + secretName: tls-certificate diff --git a/tests/scenarios/ttfb-default-domain/deploy-project.sh b/tests/scenarios/ttfb-default-domain/deploy-project.sh new file mode 100644 index 0000000..57e81c2 --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/deploy-project.sh @@ -0,0 +1,26 @@ +#!/bin/bash -e +SCENARIO_NAME="ttfb-default-domain" +source "$(dirname "$0")/../../lib/scenario-base.sh" + +# Scenario-specific configuration +DOMAINS=(DOMAIN_HOSTNAME_1 DOMAIN_HOSTNAME_2) +export RUNNING_PRODUCTION=1 +ENABLE_AUTOSCALING=true + +declare -A CRON_INSTANCES=( + ["cron"]='*/5 * * * *' +) + +DEFAULT_CONSUMERS=("email:email_transport:1") + +# TTFB probes against the default domain (DOMAIN_HOSTNAME_1 = www.example.com). +declare -A TTFB_PROBES=( + ["Homepage"]="/" + ["Detail"]="/sample-product" + ["Category"]="/sample-category" +) + +case "$1" in + "generate") run_merge; run_generate ;; + *) echo "Usage: $0 generate"; exit 1 ;; +esac diff --git a/tests/scenarios/ttfb-default-domain/description.txt b/tests/scenarios/ttfb-default-domain/description.txt new file mode 100644 index 0000000..637dced --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/description.txt @@ -0,0 +1 @@ +TTFB probes on the default domain (DOMAIN_HOSTNAME_1), two-domain production setup diff --git a/tests/scenarios/ttfb-default-domain/env.sh b/tests/scenarios/ttfb-default-domain/env.sh new file mode 100644 index 0000000..a98155d --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/env.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Scenario-specific overrides (defaults are in lib/default-env.sh) + +export PROJECT_NAME="myproject-prod" +export DOMAIN_HOSTNAME_1="www.example.com" +export DOMAIN_HOSTNAME_2="www.example.com/cz" +export DOMAIN_COUNT=2 diff --git a/tests/scenarios/ttfb-default-domain/expected/cron.yaml b/tests/scenarios/ttfb-default-domain/expected/cron.yaml new file mode 100644 index 0000000..4941802 --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/expected/cron.yaml @@ -0,0 +1,182 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + .project_env.sh: | + export ELASTICSEARCH_HOST='http://elasticsearch:9200' + export TRUSTED_PROXY='10.0.0.0/8' + export DATABASE_NAME='myproject-prod' + export S3_ENDPOINT='https://s3.example.com' + export MAILER_FORCE_WHITELIST='false' + export DATABASE_USER='myproject-prod' + export DATABASE_PORT='5432' + export MESSENGER_TRANSPORT_DSN='amqp://guest:guest@rabbitmq:5672/%2f/messages' + export REDIS_PREFIX='myproject-prod' + export DATABASE_PASSWORD='test-db-password' + export DATABASE_HOST='10.0.0.100' + export ELASTIC_SEARCH_INDEX_PREFIX='myproject-prod' + export S3_SECRET='test-s3-secret' + export MAILER_DSN='smtp://mailhog:1025' + export S3_BUCKET_NAME='myproject-prod' + export S3_ACCESS_KEY='myproject-prod' + export APP_SECRET='test-app-secret-key' +kind: ConfigMap +metadata: + name: cron-env + namespace: myproject-prod +--- +apiVersion: v1 +data: + cron: |+ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + */5 * * * * . /root/.project_env.sh && cd /var/www/html/ && ./phing cron > /dev/null 2>&1 + +kind: ConfigMap +metadata: + name: cron-list + namespace: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: cron + name: cron + namespace: myproject-prod +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + selector: + matchLabels: + app: cron + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: cron + project/environment: prod + project/name: myproject + labels: + app: cron + date: "1234567890" + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - cd /var/www/html && ./phing warmup > /dev/null && rm -rf /tmp/log-pipe && + mkfifo /tmp/log-pipe && chmod 666 /tmp/log-pipe && crontab -u root /var/spool/cron/template + && { crond || cron; } && stdbuf -o0 tail -n +1 -f /tmp/log-pipe + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - sleep + - "5" + name: cron + securityContext: + runAsUser: 0 + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/spool/cron/template + name: cron-list + subPath: cron + - mountPath: /root/.project_env.sh + name: cron-env + subPath: .project_env.sh + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - configMap: + name: cron-list + name: cron-list + - configMap: + name: cron-env + name: cron-env + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys diff --git a/tests/scenarios/ttfb-default-domain/expected/horizontalPodAutoscaler.yaml b/tests/scenarios/ttfb-default-domain/expected/horizontalPodAutoscaler.yaml new file mode 100644 index 0000000..59fab22 --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/expected/horizontalPodAutoscaler.yaml @@ -0,0 +1,20 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: webserver-php-fpm + namespace: "myproject-prod" +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: webserver-php-fpm + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: php-fpm + target: + type: Utilization + averageUtilization: 120 diff --git a/tests/scenarios/ttfb-default-domain/expected/horizontalStorefrontAutoscaler.yaml b/tests/scenarios/ttfb-default-domain/expected/horizontalStorefrontAutoscaler.yaml new file mode 100644 index 0000000..762c898 --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/expected/horizontalStorefrontAutoscaler.yaml @@ -0,0 +1,19 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: storefront + namespace: "myproject-prod" +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: storefront + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 120 diff --git a/tests/scenarios/ttfb-default-domain/expected/migrate-continuous-deploy.yaml b/tests/scenarios/ttfb-default-domain/expected/migrate-continuous-deploy.yaml new file mode 100644 index 0000000..00c4f1b --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/expected/migrate-continuous-deploy.yaml @@ -0,0 +1,422 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && ./phing -verbose db-migrations-count-with-maintenance + build-deploy-part-2-db-dependent + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-default-domain/expected/migrate-first-deploy-with-demo-data.yaml b/tests/scenarios/ttfb-default-domain/expected/migrate-first-deploy-with-demo-data.yaml new file mode 100644 index 0000000..b302eb2 --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/expected/migrate-first-deploy-with-demo-data.yaml @@ -0,0 +1,422 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && sleep 30 && ./phing cluster-first-deploy db-fixtures-demo + plugin-demo-data-load friendly-urls-generate domains-urls-replace elasticsearch-export + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-default-domain/expected/migrate-first-deploy.yaml b/tests/scenarios/ttfb-default-domain/expected/migrate-first-deploy.yaml new file mode 100644 index 0000000..5478225 --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/expected/migrate-first-deploy.yaml @@ -0,0 +1,421 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && sleep 30 && ./phing cluster-first-deploy + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-default-domain/expected/namespace.yaml b/tests/scenarios/ttfb-default-domain/expected/namespace.yaml new file mode 100644 index 0000000..3e16dc1 --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/expected/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: "myproject-prod" diff --git a/tests/scenarios/ttfb-default-domain/expected/webserver.yaml b/tests/scenarios/ttfb-default-domain/expected/webserver.yaml new file mode 100644 index 0000000..0750d18 --- /dev/null +++ b/tests/scenarios/ttfb-default-domain/expected/webserver.yaml @@ -0,0 +1,837 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + nginx.conf: | + user nginx; + worker_processes 2; + + error_log /dev/stderr warn; + pid /var/run/nginx.pid; + + events { + # determines how much clients will be served per worker + # max clients = worker_connections * worker_processes + # max clients is also limited by the number of socket connections available on the system (~64k) + worker_connections 512; + } + + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '"host=$host" ' + 'upstream_response_time=$upstream_response_time'; + + access_log /dev/stdout main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + keepalive_timeout 65; + + server_names_hash_bucket_size 64; + + include /etc/nginx/conf.d/*.conf; + } + project-nginx.conf: | + gzip on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_vary on; + gzip_types text/plain + text/css + text/javascript + application/javascript + application/json + application/xml + application/rss+xml + image/svg+xml; + + upstream php-upstream { + server php-fpm:9000; + } + + upstream storefront-upstream { + server storefront:3000; + } + + # storefront error page if accessed directly, plain text 404 if accessed via CDN + map "$http_cdn_vshosting_real_ip$http_cdn_vshosting_real_ip_img" $custom_error_target { + default @storefront; + "~.+" @404; + } + + server { + listen 80; + root /var/www/html/web; + + location /health { + stub_status on; + access_log off; + } + } + + server { + listen 8080; + root /var/www/html/web; + server_tokens off; + proxy_ignore_client_abort on; + + proxy_buffer_size 16k; + proxy_buffers 32 16k; + + client_body_buffer_size 32k; + client_header_buffer_size 1k; + client_max_body_size 32m; + large_client_header_buffers 4 8k; + + fastcgi_buffer_size 16k; + fastcgi_buffers 32 16k; + + types_hash_max_size 2048; + + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 103.21.244.0/22; + set_real_ip_from 103.22.200.0/22; + set_real_ip_from 103.31.4.0/22; + set_real_ip_from 104.16.0.0/13; + set_real_ip_from 104.24.0.0/14; + set_real_ip_from 108.162.192.0/18; + set_real_ip_from 131.0.72.0/22; + set_real_ip_from 141.101.64.0/18; + set_real_ip_from 162.158.0.0/15; + set_real_ip_from 172.64.0.0/13; + set_real_ip_from 173.245.48.0/20; + set_real_ip_from 188.114.96.0/20; + set_real_ip_from 190.93.240.0/20; + set_real_ip_from 197.234.240.0/22; + set_real_ip_from 198.41.128.0/17; + set_real_ip_from 2400:cb00::/32; + set_real_ip_from 2606:4700::/32; + set_real_ip_from 2803:f800::/32; + set_real_ip_from 2405:b500::/32; + set_real_ip_from 2405:8100::/32; + set_real_ip_from 2a06:98c0::/29; + set_real_ip_from 2c0f:f248::/32; + + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Credentials "false" always; + add_header VSHCDN-WEBP-QUALITY 90; + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + set $request_host $http_host; + if ($http_originalhost) { + set $request_host $http_originalhost; + } + + # define code to be used to redirect to the proper upstream + error_page 470 = @app; + error_page 469 = @storefront; + error_page 468 = @imageResizer; + + location = /resolve-friendly-url { + allow 10.0.0.0/8; + allow 127.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + deny all; + + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root/resolveFriendlyUrl.php; + } + + # (?:/|$) in regexes is used to match /path, /path/, /path/subpath but not /pathology + + # location always using the app backend, no static files + location ~ ^/(?:[^/]+/)?(graphql|_profiler|_wdt|_error)(?:/|$) { + return 470; # send to @app + } + + location ~ ^/(?:[^/]+/)?order/payment-status-notify(?:/|$) { + fastcgi_intercept_errors on; + add_header "Access-Control-Allow-Origin" ""; + + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param SCRIPT_FILENAME $realpath_root/index.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + fastcgi_param HTTP_HOST $request_host; + error_page 404 = @storefront; + } + + # location for administration interface, uses admin error pages + location ~ ^/(?:[^/]+/)?(admin|ckeditor|elfinder(\.main\.js)?|efconnect|build|bundles)(?:/|$) { + # hide dotfiles (send to @app) + location ~ /\. { + return 470; # send to @app + } + + try_files $uri @app; + } + + # location for static files, uses storefront error pages + location ~ ^/(public)/ { + # hide dotfiles (send to @app) + location ~ /\. { + return 469; # send to @storefront + } + + try_files $uri $custom_error_target; + } + + location ~ ^/content/images/(?\w+)(?/\w+)?/(?(default|original|galleryThumbnail|modal|list|thumbnail|thumbnailSmall|thumbnailExtraSmall|thumbnailMedium|header|footer|productList|productListSecondRow|cartPreview|productListMiddle|productListMiddleRetina|listAside|listGrid|searchThumbnail|listBig)/)(?\d+--)?(?([\w\-]+_)?(?\d+))\.(?jpg|jpeg|png|gif) { + expires 1w; + return 301 $scheme://$http_host/content/images/$entity_name$image_type/$image_name.$image_extension$is_args$args; + } + + # location for images, strip image name and serve image by its ID (send to imageResizer if there are width/height args) + location ~ ^/(?:[^/]+/)?(content(?:-test)?/images/.+)/(?([\w\-]+_)?(?\d+))\.(?jpe?g|png|gif) { + expires 1y; + + error_page 403 404 = $custom_error_target; + # this needs to be repeated here because of nginx error_page inheritance rules + error_page 468 = @imageResizer; + + if ($is_args != '') { + return 468; # send to @imageResizer + } + + proxy_intercept_errors on; + + proxy_http_version 1.1; + proxy_set_header Authorization ""; + proxy_buffering off; + + proxy_pass https://s3.example.com/myproject-prod/web/$1/$image_id.$image_extension; + } + + location ~ ^/(content)/ { + proxy_intercept_errors on; + error_page 404 = $custom_error_target; + + proxy_http_version 1.1; + proxy_set_header Authorization ""; + proxy_buffering off; + + proxy_pass https://s3.example.com/myproject-prod/web$request_uri; + } + + # location for backend routes used by customers + # they have to force storefront 404 page if not found + # throw NotFoundRedirectToStorefrontException to trigger this behavior + location ~ ^/(?:[^/]+/)?(file|customer-file/(view|download)|personal-overview-export/xml|social-network/login|convertim)(?:/|$) { + location ~ /\. { + # hide dotfiles (send to @storefront) + return 469; + } + + try_files $uri @app; + } + + location ^~ /_next/ { + return 469; # send to @storefront + } + + # disallow access to dynamic content from CDN + location ~ / { + if ($http_cdn_vshosting_real_ip != '') { + return 403; + } + if ($http_cdn_vshosting_real_ip_img != '') { + return 403; + } + + return 469; # send to @storefront + } + + location @storefront { + internal; + proxy_hide_header Access-Control-Allow-Origin; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass http://storefront-upstream; + } + + location @app { + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param HTTP_HOST $request_host; + # use $realpath_root instead of $document_root + # because of symlink switching when deploying + fastcgi_send_timeout 120s; + fastcgi_read_timeout 120s; + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param SCRIPT_FILENAME $realpath_root/index.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + } + + location @imageResizer { + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root/imageResizer.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + fastcgi_param HTTP_HOST $request_host; + fastcgi_param REQUEST_SCHEME $http_x_forwarded_proto; + } + + # plain 404 page for missing files accessed via CDN + # to avoid displaying storefront on the CDN domain + location @404 { + internal; + types {} + default_type text/html; + return 404 "File not found"; + } + } +kind: ConfigMap +metadata: + name: nginx-default-config + namespace: myproject-prod +--- +apiVersion: v1 +data: + www.conf: | + ; The below default configuration is based on a server without much resources. + ; Don't forget to tweak it to fit expected workload and hardware. + ; + ; https://www.php.net/manual/en/install.fpm.configuration.php + [global] + + log_level = warning + + [www] + + listen = 127.0.0.1:9000 + + pm = dynamic + pm.max_children = 20 + pm.start_servers = 5 + pm.min_spare_servers = 5 + pm.max_spare_servers = 10 + pm.max_requests = 400 + + request_terminate_timeout = 60s + + access.log = /dev/null +kind: ConfigMap +metadata: + name: production-php-fpm + namespace: myproject-prod +--- +apiVersion: v1 +data: + php-opcache.ini: | + opcache.enable = 1 + opcache.fast_shutdown = true + opcache.interned_strings_buffer = 24 + opcache.max_accelerated_files = 60000 + opcache.memory_consumption = 256 + opcache.revalidate_path = 0 + opcache.revalidate_freq = 0 + opcache.validate_timestamps = 0 + opcache.use_cwd = 0 + opcache.preload = "/var/www/html/app/preload.php" +kind: ConfigMap +metadata: + name: production-php-opcache + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + name: storefront + namespace: myproject-prod +spec: + ports: + - name: storefront + port: 3000 + targetPort: 3000 + selector: + app: storefront +--- +apiVersion: v1 +kind: Service +metadata: + name: webserver-php-fpm + namespace: myproject-prod +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: webserver-php-fpm +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storefront + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: storefront + template: + metadata: + annotations: + logging/enabled: "false" + project/app: storefront + project/environment: prod + project/name: myproject + labels: + app: storefront + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - storefront + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: DOMAIN_HOSTNAME_1 + value: https://www.example.com/ + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_1 + value: https://www.example.com/graphql/ + - name: DOMAIN_HOSTNAME_2 + value: https://www.example.com/cz/ + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_2 + value: https://www.example.com/cz/graphql/ + - name: INTERNAL_ENDPOINT + value: http://webserver-php-fpm:8080/ + image: v1.0.0 + lifecycle: + preStop: + exec: + command: + - sleep + - "10" + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + name: storefront + ports: + - containerPort: 3000 + name: storefront + protocol: TCP + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 1.5Gi + requests: + cpu: 500m + memory: 800Mi + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 60 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: webserver-php-fpm + name: webserver-php-fpm + namespace: myproject-prod +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + selector: + matchLabels: + app: webserver-php-fpm + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: app + project/environment: prod + project/name: myproject + labels: + app: webserver-php-fpm + spec: + affinity: + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - redis + topologyKey: kubernetes.io/hostname + weight: 100 + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - webserver-php-fpm + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + postStart: + exec: + command: + - /var/www/html/phing + - -S + - warmup + preStop: + exec: + command: + - sh + - -c + - sleep 10 && kill -SIGQUIT 1 + name: php-fpm + resources: + limits: + memory: 2Gi + requests: + cpu: 500m + memory: 500Mi + volumeMounts: + - mountPath: /var/www/html + name: source-codes + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /usr/local/etc/php-fpm.d/www.conf + name: production-php-fpm + subPath: www.conf + - mountPath: /usr/local/etc/php/conf.d/php-opcache.ini + name: production-php-opcache + subPath: php-opcache.ini + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + - image: nginx:1.29-alpine + lifecycle: + preStop: + exec: + command: + - sh + - -c + - sleep 5 && /usr/sbin/nginx -s quit + livenessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + name: webserver + ports: + - containerPort: 8080 + name: http + readinessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 300Mi + requests: + cpu: 50m + memory: 100Mi + volumeMounts: + - mountPath: /etc/nginx/nginx.conf + name: nginx-default-config + subPath: nginx.conf + - mountPath: /etc/nginx/conf.d/default.conf + name: nginx-default-config + subPath: project-nginx.conf + - mountPath: /var/www/html + name: source-codes + hostAliases: + - hostnames: + - webserver-php-fpm + - php-fpm + - webserver + ip: 127.0.0.1 + imagePullSecrets: + - name: dockerregistry + initContainers: + - command: + - sh + - -c + - cp -r -n /var/www/html/. /tmp/source-codes + image: v1.0.0 + name: copy-source-codes-to-volume + volumeMounts: + - mountPath: /tmp/source-codes + name: source-codes + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + terminationGracePeriodSeconds: 120 + volumes: + - emptyDir: {} + name: source-codes + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - configMap: + name: nginx-default-config + name: nginx-default-config + - configMap: + name: production-php-fpm + name: production-php-fpm + - configMap: + name: production-php-opcache + name: production-php-opcache + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: ttfb-category + namespace: myproject-prod +spec: + interval: 60s + jobName: ttfb + module: http_ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + scrapeClass: ttfb + scrapeTimeout: 45s + targets: + staticConfig: + labels: + project: myproject + type: Category + static: + - https://www.example.com/sample-category +--- +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: ttfb-detail + namespace: myproject-prod +spec: + interval: 60s + jobName: ttfb + module: http_ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + scrapeClass: ttfb + scrapeTimeout: 45s + targets: + staticConfig: + labels: + project: myproject + type: Detail + static: + - https://www.example.com/sample-product +--- +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: ttfb-homepage + namespace: myproject-prod +spec: + interval: 60s + jobName: ttfb + module: http_ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + scrapeClass: ttfb + scrapeTimeout: 45s + targets: + staticConfig: + labels: + project: myproject + type: Homepage + static: + - https://www.example.com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/configuration-snippet: if ($scheme = http) { return + 308 https://$host$request_uri; } if ($host ~ ^(?!www\.)(?.+)$) { return + 308 https://www.$domain$request_uri; } + nginx.ingress.kubernetes.io/proxy-body-size: 32m + name: eshop-domain-0 + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: www.example.com + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: example.com + tls: + - hosts: + - www.example.com + - example.com + secretName: tls-www-example-com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/configuration-snippet: if ($scheme = http) { return + 308 https://$host$request_uri; } if ($host ~ ^(?!www\.)(?.+)$) { return + 308 https://www.$domain$request_uri; } + nginx.ingress.kubernetes.io/proxy-body-size: 32m + name: eshop-domain-1 + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: www.example.com + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: /cz + pathType: Prefix + - host: example.com + tls: + - hosts: + - www.example.com + - example.com + secretName: tls-www-example-com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: 10.0.0.0/8 + name: rabbitmq-domain + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: rabbitmq.www.example.com + http: + paths: + - backend: + service: + name: rabbitmq + port: + number: 15672 + path: / + pathType: Prefix + tls: + - hosts: + - rabbitmq.www.example.com + secretName: tls-certificate diff --git a/tests/scenarios/ttfb-http-auth/deploy-project.sh b/tests/scenarios/ttfb-http-auth/deploy-project.sh new file mode 100644 index 0000000..bbddac1 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/deploy-project.sh @@ -0,0 +1,31 @@ +#!/bin/bash -e +SCENARIO_NAME="ttfb-http-auth" +source "$(dirname "$0")/../../lib/scenario-base.sh" + +# Scenario-specific configuration +DOMAINS=(DOMAIN_HOSTNAME_1 DOMAIN_HOSTNAME_2) +export RUNNING_PRODUCTION=1 +ENABLE_AUTOSCALING=true + +declare -A CRON_INSTANCES=( + ["cron"]='*/5 * * * *' +) + +DEFAULT_CONSUMERS=("email:email_transport:1") + +# DOMAIN_HOSTNAME_2 is behind HTTP basic auth even in production - probes +# targeting this domain must have HTTP_AUTH_CREDENTIALS embedded into the URL. +FORCE_HTTP_AUTH_IN_PRODUCTION=(DOMAIN_HOSTNAME_2) + +# TTFB probes target the HTTP-auth-protected domain — HTTP_AUTH_CREDENTIALS +# must be embedded into the probe URL (https://user:pass@host/...). +TTFB_PROBES_DOMAIN=DOMAIN_HOSTNAME_2 +declare -A TTFB_PROBES=( + ["Homepage"]="" + ["Detail"]="/sample-product" +) + +case "$1" in + "generate") run_merge; run_generate ;; + *) echo "Usage: $0 generate"; exit 1 ;; +esac diff --git a/tests/scenarios/ttfb-http-auth/description.txt b/tests/scenarios/ttfb-http-auth/description.txt new file mode 100644 index 0000000..2497c97 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/description.txt @@ -0,0 +1 @@ +TTFB probes against a domain behind HTTP basic auth — HTTP_AUTH_CREDENTIALS must be embedded into the probe URL diff --git a/tests/scenarios/ttfb-http-auth/env.sh b/tests/scenarios/ttfb-http-auth/env.sh new file mode 100644 index 0000000..6c6e526 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/env.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Scenario-specific overrides (defaults are in lib/default-env.sh) + +export PROJECT_NAME="myproject-prod" +export DOMAIN_HOSTNAME_1="www.example.com" +export DOMAIN_HOSTNAME_2="www.example.com/cz" +export DOMAIN_COUNT=2 + +export HTTP_AUTH_CREDENTIALS="testuser:testpass" diff --git a/tests/scenarios/ttfb-http-auth/expected/cron.yaml b/tests/scenarios/ttfb-http-auth/expected/cron.yaml new file mode 100644 index 0000000..4941802 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/expected/cron.yaml @@ -0,0 +1,182 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + .project_env.sh: | + export ELASTICSEARCH_HOST='http://elasticsearch:9200' + export TRUSTED_PROXY='10.0.0.0/8' + export DATABASE_NAME='myproject-prod' + export S3_ENDPOINT='https://s3.example.com' + export MAILER_FORCE_WHITELIST='false' + export DATABASE_USER='myproject-prod' + export DATABASE_PORT='5432' + export MESSENGER_TRANSPORT_DSN='amqp://guest:guest@rabbitmq:5672/%2f/messages' + export REDIS_PREFIX='myproject-prod' + export DATABASE_PASSWORD='test-db-password' + export DATABASE_HOST='10.0.0.100' + export ELASTIC_SEARCH_INDEX_PREFIX='myproject-prod' + export S3_SECRET='test-s3-secret' + export MAILER_DSN='smtp://mailhog:1025' + export S3_BUCKET_NAME='myproject-prod' + export S3_ACCESS_KEY='myproject-prod' + export APP_SECRET='test-app-secret-key' +kind: ConfigMap +metadata: + name: cron-env + namespace: myproject-prod +--- +apiVersion: v1 +data: + cron: |+ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + */5 * * * * . /root/.project_env.sh && cd /var/www/html/ && ./phing cron > /dev/null 2>&1 + +kind: ConfigMap +metadata: + name: cron-list + namespace: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: cron + name: cron + namespace: myproject-prod +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + selector: + matchLabels: + app: cron + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: cron + project/environment: prod + project/name: myproject + labels: + app: cron + date: "1234567890" + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - cd /var/www/html && ./phing warmup > /dev/null && rm -rf /tmp/log-pipe && + mkfifo /tmp/log-pipe && chmod 666 /tmp/log-pipe && crontab -u root /var/spool/cron/template + && { crond || cron; } && stdbuf -o0 tail -n +1 -f /tmp/log-pipe + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - sleep + - "5" + name: cron + securityContext: + runAsUser: 0 + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/spool/cron/template + name: cron-list + subPath: cron + - mountPath: /root/.project_env.sh + name: cron-env + subPath: .project_env.sh + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - configMap: + name: cron-list + name: cron-list + - configMap: + name: cron-env + name: cron-env + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys diff --git a/tests/scenarios/ttfb-http-auth/expected/horizontalPodAutoscaler.yaml b/tests/scenarios/ttfb-http-auth/expected/horizontalPodAutoscaler.yaml new file mode 100644 index 0000000..59fab22 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/expected/horizontalPodAutoscaler.yaml @@ -0,0 +1,20 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: webserver-php-fpm + namespace: "myproject-prod" +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: webserver-php-fpm + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: ContainerResource + containerResource: + name: cpu + container: php-fpm + target: + type: Utilization + averageUtilization: 120 diff --git a/tests/scenarios/ttfb-http-auth/expected/horizontalStorefrontAutoscaler.yaml b/tests/scenarios/ttfb-http-auth/expected/horizontalStorefrontAutoscaler.yaml new file mode 100644 index 0000000..762c898 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/expected/horizontalStorefrontAutoscaler.yaml @@ -0,0 +1,19 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: storefront + namespace: "myproject-prod" +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: storefront + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 120 diff --git a/tests/scenarios/ttfb-http-auth/expected/migrate-continuous-deploy.yaml b/tests/scenarios/ttfb-http-auth/expected/migrate-continuous-deploy.yaml new file mode 100644 index 0000000..00c4f1b --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/expected/migrate-continuous-deploy.yaml @@ -0,0 +1,422 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && ./phing -verbose db-migrations-count-with-maintenance + build-deploy-part-2-db-dependent + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-http-auth/expected/migrate-first-deploy-with-demo-data.yaml b/tests/scenarios/ttfb-http-auth/expected/migrate-first-deploy-with-demo-data.yaml new file mode 100644 index 0000000..b302eb2 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/expected/migrate-first-deploy-with-demo-data.yaml @@ -0,0 +1,422 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && sleep 30 && ./phing cluster-first-deploy db-fixtures-demo + plugin-demo-data-load friendly-urls-generate domains-urls-replace elasticsearch-export + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-http-auth/expected/migrate-first-deploy.yaml b/tests/scenarios/ttfb-http-auth/expected/migrate-first-deploy.yaml new file mode 100644 index 0000000..5478225 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/expected/migrate-first-deploy.yaml @@ -0,0 +1,421 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + redis.conf: | + tcp-keepalive 30 + timeout 60 + loglevel notice + maxmemory 2200mb + maxmemory-policy volatile-lru + + # Disable AOF and RDB persistence as we keep everything in memory only, see https://redis.io/topics/persistence + appendonly no + # Disable RDB persistence, AOF persistence already disabled above. + save "" + + # Enabling active memory defragmentation + activedefrag yes +kind: ConfigMap +metadata: + name: redis + namespace: myproject-prod +--- +apiVersion: v1 +data: + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi +kind: ConfigMap +metadata: + name: redis-health-configmap + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: rabbitmq + prometheus-exporter: "true" + name: rabbitmq + namespace: myproject-prod +spec: + clusterIP: None + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + selector: + app: rabbitmq +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + prometheus-exporter: "true" + name: redis + namespace: myproject-prod +spec: + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + selector: + app: redis +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: consumer-email + name: consumer-email + namespace: myproject-prod +spec: + progressDeadlineSeconds: 600 + replicas: 1 + selector: + matchLabels: + app: consumer-email + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: email + project/environment: prod + project/name: myproject + labels: + app: consumer-email + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + containers: + - args: + - "PIPE=/tmp/log-pipe\nrm -rf $PIPE\nmkfifo $PIPE\nchmod 666 $PIPE\nstdbuf + -o0 tail -n +1 -f $PIPE &\n\nsleep 5\n\nwhile [ ! -f /tmp/stop_consumer + ]; do \n php /var/www/html/bin/console messenger:consume email_transport + --time-limit=300 --quiet\n sleep 2\ndone\n" + command: + - /bin/sh + - -c + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - touch /tmp/stop_consumer && php bin/console messenger:stop-workers + name: consumer-email + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 300 + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: redis + project/environment: prod + project/name: myproject + labels: + app: redis + spec: + containers: + - image: oliver006/redis_exporter + imagePullPolicy: Always + name: redis-exporter + ports: + - containerPort: 9121 + name: exporter + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + - args: + - /usr/local/etc/redis/redis.conf + image: redis:7.4-alpine + livenessProbe: + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + initialDelaySeconds: 30 + timeoutSeconds: 5 + name: redis + ports: + - containerPort: 6379 + name: redis + protocol: TCP + readinessProbe: + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + initialDelaySeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + volumeMounts: + - mountPath: /health + name: health + - mountPath: /usr/local/etc/redis/redis.conf + name: config + subPath: redis.conf + volumes: + - configMap: + defaultMode: 493 + name: redis-health-configmap + name: health + - configMap: + defaultMode: 493 + name: redis + name: config +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + serviceName: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: RABBITMQ_DEFAULT_USER + value: rabbitmq + - name: RABBITMQ_DEFAULT_PASS + value: rabbitmq-password + image: rabbitmq:4.1-management-alpine + name: rabbitmq + ports: + - containerPort: 15672 + name: rabbitmq + protocol: TCP + - containerPort: 15692 + name: exporter + protocol: TCP + resources: + requests: + cpu: 20m + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-data + imagePullSecrets: + - name: dockerregistry + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + storageClassName: nfs-client +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + namespace: myproject-prod +spec: + backoffLimit: 0 + template: + spec: + containers: + - command: + - sh + - -c + - cd /var/www/html && sleep 30 && ./phing cluster-first-deploy + env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + name: migrate-application + volumeMounts: + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + imagePullSecrets: + - name: dockerregistry + restartPolicy: Never + volumes: + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls diff --git a/tests/scenarios/ttfb-http-auth/expected/namespace.yaml b/tests/scenarios/ttfb-http-auth/expected/namespace.yaml new file mode 100644 index 0000000..3e16dc1 --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/expected/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: "myproject-prod" diff --git a/tests/scenarios/ttfb-http-auth/expected/webserver.yaml b/tests/scenarios/ttfb-http-auth/expected/webserver.yaml new file mode 100644 index 0000000..3de251b --- /dev/null +++ b/tests/scenarios/ttfb-http-auth/expected/webserver.yaml @@ -0,0 +1,819 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: myproject-prod +--- +apiVersion: v1 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.com/cz +kind: ConfigMap +metadata: + name: domains-urls-2h56cm6hkk + namespace: myproject-prod +--- +apiVersion: v1 +data: + nginx.conf: | + user nginx; + worker_processes 2; + + error_log /dev/stderr warn; + pid /var/run/nginx.pid; + + events { + # determines how much clients will be served per worker + # max clients = worker_connections * worker_processes + # max clients is also limited by the number of socket connections available on the system (~64k) + worker_connections 512; + } + + http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '"host=$host" ' + 'upstream_response_time=$upstream_response_time'; + + access_log /dev/stdout main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + keepalive_timeout 65; + + server_names_hash_bucket_size 64; + + include /etc/nginx/conf.d/*.conf; + } + project-nginx.conf: | + gzip on; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_vary on; + gzip_types text/plain + text/css + text/javascript + application/javascript + application/json + application/xml + application/rss+xml + image/svg+xml; + + upstream php-upstream { + server php-fpm:9000; + } + + upstream storefront-upstream { + server storefront:3000; + } + + # storefront error page if accessed directly, plain text 404 if accessed via CDN + map "$http_cdn_vshosting_real_ip$http_cdn_vshosting_real_ip_img" $custom_error_target { + default @storefront; + "~.+" @404; + } + + server { + listen 80; + root /var/www/html/web; + + location /health { + stub_status on; + access_log off; + } + } + + server { + listen 8080; + root /var/www/html/web; + server_tokens off; + proxy_ignore_client_abort on; + + proxy_buffer_size 16k; + proxy_buffers 32 16k; + + client_body_buffer_size 32k; + client_header_buffer_size 1k; + client_max_body_size 32m; + large_client_header_buffers 4 8k; + + fastcgi_buffer_size 16k; + fastcgi_buffers 32 16k; + + types_hash_max_size 2048; + + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 103.21.244.0/22; + set_real_ip_from 103.22.200.0/22; + set_real_ip_from 103.31.4.0/22; + set_real_ip_from 104.16.0.0/13; + set_real_ip_from 104.24.0.0/14; + set_real_ip_from 108.162.192.0/18; + set_real_ip_from 131.0.72.0/22; + set_real_ip_from 141.101.64.0/18; + set_real_ip_from 162.158.0.0/15; + set_real_ip_from 172.64.0.0/13; + set_real_ip_from 173.245.48.0/20; + set_real_ip_from 188.114.96.0/20; + set_real_ip_from 190.93.240.0/20; + set_real_ip_from 197.234.240.0/22; + set_real_ip_from 198.41.128.0/17; + set_real_ip_from 2400:cb00::/32; + set_real_ip_from 2606:4700::/32; + set_real_ip_from 2803:f800::/32; + set_real_ip_from 2405:b500::/32; + set_real_ip_from 2405:8100::/32; + set_real_ip_from 2a06:98c0::/29; + set_real_ip_from 2c0f:f248::/32; + + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Credentials "false" always; + add_header VSHCDN-WEBP-QUALITY 90; + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + + set $request_host $http_host; + if ($http_originalhost) { + set $request_host $http_originalhost; + } + + # define code to be used to redirect to the proper upstream + error_page 470 = @app; + error_page 469 = @storefront; + error_page 468 = @imageResizer; + + location = /resolve-friendly-url { + allow 10.0.0.0/8; + allow 127.0.0.0/8; + allow 172.16.0.0/12; + allow 192.168.0.0/16; + deny all; + + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root/resolveFriendlyUrl.php; + } + + # (?:/|$) in regexes is used to match /path, /path/, /path/subpath but not /pathology + + # location always using the app backend, no static files + location ~ ^/(?:[^/]+/)?(graphql|_profiler|_wdt|_error)(?:/|$) { + return 470; # send to @app + } + + location ~ ^/(?:[^/]+/)?order/payment-status-notify(?:/|$) { + fastcgi_intercept_errors on; + add_header "Access-Control-Allow-Origin" ""; + + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param SCRIPT_FILENAME $realpath_root/index.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + fastcgi_param HTTP_HOST $request_host; + error_page 404 = @storefront; + } + + # location for administration interface, uses admin error pages + location ~ ^/(?:[^/]+/)?(admin|ckeditor|elfinder(\.main\.js)?|efconnect|build|bundles)(?:/|$) { + # hide dotfiles (send to @app) + location ~ /\. { + return 470; # send to @app + } + + try_files $uri @app; + } + + # location for static files, uses storefront error pages + location ~ ^/(public)/ { + # hide dotfiles (send to @app) + location ~ /\. { + return 469; # send to @storefront + } + + try_files $uri $custom_error_target; + } + + location ~ ^/content/images/(?\w+)(?/\w+)?/(?(default|original|galleryThumbnail|modal|list|thumbnail|thumbnailSmall|thumbnailExtraSmall|thumbnailMedium|header|footer|productList|productListSecondRow|cartPreview|productListMiddle|productListMiddleRetina|listAside|listGrid|searchThumbnail|listBig)/)(?\d+--)?(?([\w\-]+_)?(?\d+))\.(?jpg|jpeg|png|gif) { + expires 1w; + return 301 $scheme://$http_host/content/images/$entity_name$image_type/$image_name.$image_extension$is_args$args; + } + + # location for images, strip image name and serve image by its ID (send to imageResizer if there are width/height args) + location ~ ^/(?:[^/]+/)?(content(?:-test)?/images/.+)/(?([\w\-]+_)?(?\d+))\.(?jpe?g|png|gif) { + expires 1y; + + error_page 403 404 = $custom_error_target; + # this needs to be repeated here because of nginx error_page inheritance rules + error_page 468 = @imageResizer; + + if ($is_args != '') { + return 468; # send to @imageResizer + } + + proxy_intercept_errors on; + + proxy_http_version 1.1; + proxy_set_header Authorization ""; + proxy_buffering off; + + proxy_pass https://s3.example.com/myproject-prod/web/$1/$image_id.$image_extension; + } + + location ~ ^/(content)/ { + proxy_intercept_errors on; + error_page 404 = $custom_error_target; + + proxy_http_version 1.1; + proxy_set_header Authorization ""; + proxy_buffering off; + + proxy_pass https://s3.example.com/myproject-prod/web$request_uri; + } + + # location for backend routes used by customers + # they have to force storefront 404 page if not found + # throw NotFoundRedirectToStorefrontException to trigger this behavior + location ~ ^/(?:[^/]+/)?(file|customer-file/(view|download)|personal-overview-export/xml|social-network/login|convertim)(?:/|$) { + location ~ /\. { + # hide dotfiles (send to @storefront) + return 469; + } + + try_files $uri @app; + } + + location ^~ /_next/ { + return 469; # send to @storefront + } + + # disallow access to dynamic content from CDN + location ~ / { + if ($http_cdn_vshosting_real_ip != '') { + return 403; + } + if ($http_cdn_vshosting_real_ip_img != '') { + return 403; + } + + return 469; # send to @storefront + } + + location @storefront { + internal; + proxy_hide_header Access-Control-Allow-Origin; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_pass http://storefront-upstream; + } + + location @app { + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param HTTP_HOST $request_host; + # use $realpath_root instead of $document_root + # because of symlink switching when deploying + fastcgi_send_timeout 120s; + fastcgi_read_timeout 120s; + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param SCRIPT_FILENAME $realpath_root/index.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + } + + location @imageResizer { + fastcgi_pass php-upstream; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root/imageResizer.php; + fastcgi_param HTTPS $http_x_forwarded_proto; + fastcgi_param HTTP_HOST $request_host; + fastcgi_param REQUEST_SCHEME $http_x_forwarded_proto; + } + + # plain 404 page for missing files accessed via CDN + # to avoid displaying storefront on the CDN domain + location @404 { + internal; + types {} + default_type text/html; + return 404 "File not found"; + } + } +kind: ConfigMap +metadata: + name: nginx-default-config + namespace: myproject-prod +--- +apiVersion: v1 +data: + www.conf: | + ; The below default configuration is based on a server without much resources. + ; Don't forget to tweak it to fit expected workload and hardware. + ; + ; https://www.php.net/manual/en/install.fpm.configuration.php + [global] + + log_level = warning + + [www] + + listen = 127.0.0.1:9000 + + pm = dynamic + pm.max_children = 20 + pm.start_servers = 5 + pm.min_spare_servers = 5 + pm.max_spare_servers = 10 + pm.max_requests = 400 + + request_terminate_timeout = 60s + + access.log = /dev/null +kind: ConfigMap +metadata: + name: production-php-fpm + namespace: myproject-prod +--- +apiVersion: v1 +data: + php-opcache.ini: | + opcache.enable = 1 + opcache.fast_shutdown = true + opcache.interned_strings_buffer = 24 + opcache.max_accelerated_files = 60000 + opcache.memory_consumption = 256 + opcache.revalidate_path = 0 + opcache.revalidate_freq = 0 + opcache.validate_timestamps = 0 + opcache.use_cwd = 0 + opcache.preload = "/var/www/html/app/preload.php" +kind: ConfigMap +metadata: + name: production-php-opcache + namespace: myproject-prod +--- +apiVersion: v1 +kind: Service +metadata: + name: storefront + namespace: myproject-prod +spec: + ports: + - name: storefront + port: 3000 + targetPort: 3000 + selector: + app: storefront +--- +apiVersion: v1 +kind: Service +metadata: + name: webserver-php-fpm + namespace: myproject-prod +spec: + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: webserver-php-fpm +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storefront + namespace: myproject-prod +spec: + replicas: 1 + selector: + matchLabels: + app: storefront + template: + metadata: + annotations: + logging/enabled: "false" + project/app: storefront + project/environment: prod + project/name: myproject + labels: + app: storefront + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - storefront + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: DOMAIN_HOSTNAME_1 + value: https://www.example.com/ + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_1 + value: https://www.example.com/graphql/ + - name: DOMAIN_HOSTNAME_2 + value: https://www.example.com/cz/ + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_2 + value: https://www.example.com/cz/graphql/ + - name: INTERNAL_ENDPOINT + value: http://webserver-php-fpm:8080/ + image: v1.0.0 + lifecycle: + preStop: + exec: + command: + - sleep + - "10" + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + name: storefront + ports: + - containerPort: 3000 + name: storefront + protocol: TCP + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 1.5Gi + requests: + cpu: 500m + memory: 800Mi + imagePullSecrets: + - name: dockerregistry + terminationGracePeriodSeconds: 60 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: webserver-php-fpm + name: webserver-php-fpm + namespace: myproject-prod +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + selector: + matchLabels: + app: webserver-php-fpm + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + annotations: + logging/enabled: "true" + project/app: app + project/environment: prod + project/name: myproject + labels: + app: webserver-php-fpm + spec: + affinity: + podAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - redis + topologyKey: kubernetes.io/hostname + weight: 100 + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - webserver-php-fpm + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - env: + - name: ELASTICSEARCH_HOST + value: http://elasticsearch:9200 + - name: TRUSTED_PROXY + value: 10.0.0.0/8 + - name: DATABASE_NAME + value: myproject-prod + - name: S3_ENDPOINT + value: https://s3.example.com + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: DATABASE_USER + value: myproject-prod + - name: DATABASE_PORT + value: "5432" + - name: MESSENGER_TRANSPORT_DSN + value: amqp://guest:guest@rabbitmq:5672/%2f/messages + - name: REDIS_PREFIX + value: myproject-prod + - name: DATABASE_PASSWORD + value: test-db-password + - name: DATABASE_HOST + value: 10.0.0.100 + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: myproject-prod + - name: S3_SECRET + value: test-s3-secret + - name: MAILER_DSN + value: smtp://mailhog:1025 + - name: S3_BUCKET_NAME + value: myproject-prod + - name: S3_ACCESS_KEY + value: myproject-prod + - name: APP_SECRET + value: test-app-secret-key + image: v1.0.0 + imagePullPolicy: IfNotPresent + lifecycle: + postStart: + exec: + command: + - /var/www/html/phing + - -S + - warmup + preStop: + exec: + command: + - sh + - -c + - sleep 10 && kill -SIGQUIT 1 + name: php-fpm + resources: + limits: + memory: 2Gi + requests: + cpu: 500m + memory: 500Mi + volumeMounts: + - mountPath: /var/www/html + name: source-codes + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + - mountPath: /usr/local/etc/php-fpm.d/www.conf + name: production-php-fpm + subPath: www.conf + - mountPath: /usr/local/etc/php/conf.d/php-opcache.ini + name: production-php-opcache + subPath: php-opcache.ini + - mountPath: /var/www/html/config/frontend-api + name: fe-api-keys-volume + readOnly: true + workingDir: /var/www/html + - image: nginx:1.29-alpine + lifecycle: + preStop: + exec: + command: + - sh + - -c + - sleep 5 && /usr/sbin/nginx -s quit + livenessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + name: webserver + ports: + - containerPort: 8080 + name: http + readinessProbe: + httpGet: + path: /health + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + resources: + limits: + memory: 300Mi + requests: + cpu: 50m + memory: 100Mi + volumeMounts: + - mountPath: /etc/nginx/nginx.conf + name: nginx-default-config + subPath: nginx.conf + - mountPath: /etc/nginx/conf.d/default.conf + name: nginx-default-config + subPath: project-nginx.conf + - mountPath: /var/www/html + name: source-codes + hostAliases: + - hostnames: + - webserver-php-fpm + - php-fpm + - webserver + ip: 127.0.0.1 + imagePullSecrets: + - name: dockerregistry + initContainers: + - command: + - sh + - -c + - cp -r -n /var/www/html/. /tmp/source-codes + image: v1.0.0 + name: copy-source-codes-to-volume + volumeMounts: + - mountPath: /tmp/source-codes + name: source-codes + - mountPath: /var/www/html/config/domains_urls.yaml + name: domains-urls + subPath: domains_urls.yaml + terminationGracePeriodSeconds: 120 + volumes: + - emptyDir: {} + name: source-codes + - configMap: + name: domains-urls-2h56cm6hkk + name: domains-urls + - configMap: + name: nginx-default-config + name: nginx-default-config + - configMap: + name: production-php-fpm + name: production-php-fpm + - configMap: + name: production-php-opcache + name: production-php-opcache + - name: fe-api-keys-volume + secret: + defaultMode: 420 + secretName: fe-api-keys +--- +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: ttfb-detail + namespace: myproject-prod +spec: + interval: 60s + jobName: ttfb + module: http_ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + scrapeClass: ttfb + scrapeTimeout: 45s + targets: + staticConfig: + labels: + project: myproject + type: Detail + static: + - https://testuser:testpass@www.example.com/cz/sample-product +--- +apiVersion: monitoring.coreos.com/v1 +kind: Probe +metadata: + name: ttfb-homepage + namespace: myproject-prod +spec: + interval: 60s + jobName: ttfb + module: http_ttfb + prober: + url: prometheus-blackbox-exporter.monitoring.svc.cluster.local:9115 + scrapeClass: ttfb + scrapeTimeout: 45s + targets: + staticConfig: + labels: + project: myproject + type: Homepage + static: + - https://testuser:testpass@www.example.com/cz +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/configuration-snippet: if ($scheme = http) { return + 308 https://$host$request_uri; } if ($host ~ ^(?!www\.)(?.+)$) { return + 308 https://www.$domain$request_uri; } + nginx.ingress.kubernetes.io/proxy-body-size: 32m + name: eshop-domain-0 + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: www.example.com + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: example.com + tls: + - hosts: + - www.example.com + - example.com + secretName: tls-www-example-com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/auth-realm: Authentication Required - ok + nginx.ingress.kubernetes.io/auth-secret: http-auth + nginx.ingress.kubernetes.io/auth-type: basic + nginx.ingress.kubernetes.io/configuration-snippet: if ($scheme = http) { return + 308 https://$host$request_uri; } if ($host ~ ^(?!www\.)(?.+)$) { return + 308 https://www.$domain$request_uri; } + nginx.ingress.kubernetes.io/proxy-body-size: 32m + name: eshop-domain-1 + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: www.example.com + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: /cz + pathType: Prefix + - host: example.com + tls: + - hosts: + - www.example.com + - example.com + secretName: tls-www-example-com +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: 10.0.0.0/8 + name: rabbitmq-domain + namespace: myproject-prod +spec: + ingressClassName: nginx + rules: + - host: rabbitmq.www.example.com + http: + paths: + - backend: + service: + name: rabbitmq + port: + number: 15672 + path: / + pathType: Prefix + tls: + - hosts: + - rabbitmq.www.example.com + secretName: tls-certificate