Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
+ )
Comment thread
henzigo marked this conversation as resolved.
+ 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.
Expand Down
6 changes: 6 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
102 changes: 102 additions & 0 deletions deploy/parts/probes.sh
Original file line number Diff line number Diff line change
@@ -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
Comment thread
henzigo marked this conversation as resolved.

# 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}"
Comment thread
henzigo marked this conversation as resolved.
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}"
Comment thread
henzigo marked this conversation as resolved.
done
19 changes: 19 additions & 0 deletions kubernetes/manifest-templates/probe.template.yaml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tests/lib/scenario-base.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
31 changes: 31 additions & 0 deletions tests/scenarios/ttfb-custom-domain/deploy-project.sh
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tests/scenarios/ttfb-custom-domain/description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TTFB probes overridden to DOMAIN_HOSTNAME_2 which carries a path ("/cz") in the hostname, covering path-normalization variants
7 changes: 7 additions & 0 deletions tests/scenarios/ttfb-custom-domain/env.sh
Original file line number Diff line number Diff line change
@@ -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
182 changes: 182 additions & 0 deletions tests/scenarios/ttfb-custom-domain/expected/cron.yaml
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading