APM Setup Guide — K8s Deployment, ConfigMap & Verification
Complete guide for setting up Elastic APM in any HungryHub Rails app, covering the full stack from gem installation to K8s ConfigMap deployment and verification.
For Ruby instrumentation patterns (spans, error handling, concerns), see src/PricingEngine/010-APM-MONITORING.md.
Table of Contents
- APM Server URLs
- Per-Namespace Configuration
- K8s ConfigMap Setup
- Build Pipeline Integration
- Production vs Staging Tuning
- Verification Steps
- Troubleshooting
1. APM Server URLs
Two APM servers exist — one for staging (DigitalOcean) and one for production (EKS internal).
| Environment | APM Server URL | Kibana URL |
|---|---|---|
| Staging | http://128.199.168.171:8200 | https://apm.hh-engineering.my.id |
| Production | http://20.0.30.229:8300 | https://apm.hungryhub.com |
APM Credentials (from AWS Secrets Manager):
| Env | Host | User | Password |
|---|---|---|---|
| Prod | https://apm.hungryhub.com | elastic | sGQJBy1Gbg7qfYbbFlWd |
| Staging | https://apm.hh-engineering.my.id | elastic | Lu7G8hw_lbrp8aUsgtTk |
2. Per-Namespace Configuration
Each K8s namespace has its own ConfigMap with service_name and environment. The server_url is the same within each cluster.
Staging (eks-dev-262)
Dev-preview namespaces (Pattern C — elastic_apm.yml format):
| Namespace | service_name | environment |
|---|---|---|
| dev-preview-ballbot-public | hh-server | ballbot |
| dev-preview-engineering-public | hh-server | engineering |
| dev-preview-venus-public | hh-server | venus |
Note:
dev-preview-*namespaces useservice_name: hh-server(not per-service names) and individualenvironmentvalues per namespace. These use Pattern C (elastic_apm.yml format).
Production (eks-prod-21)
| Namespace | service_name | environment |
|---|---|---|
| hungryhub | hungryhubcom | hungryhubcom |
| cosmos-public | cosmos | production |
| cosmos-private | hh-cosmos-private | production |
| end-user-public | internal-api | production |
| end-user-private | hh-end-user-private | production |
| syn-public | partners-api | production |
| syn-private | hh-syn-private | production |
| vendor-public | vendors-api | production |
Note:
hungryhubnamespace usesservice_name: hungryhubcomandenvironment: hungryhubcom(notproduction) — this is intentional.
3. K8s ConfigMap Setup
Every app deployment needs a ConfigMap with Elastic APM config. The ConfigMap is mounted as a volume and read by the elastic-apm gem.
3.1 ConfigMap Template
# base/configmaps/elastic-apm-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: elastic-apm-config
data:
ELASTIC_APM_SERVICE_NAME: "<SERVICE_NAME>"
ELASTIC_APM_SERVER_URL: "<SERVER_URL>"
ELASTIC_APM_ENVIRONMENT: "<ENVIRONMENT>"
ELASTIC_APM_ENABLED: "true"
ELASTIC_APM_LOG_LEVEL: "info"
ELASTIC_APM_POOL_SIZE: "10"
ELASTIC_APM_TRANSACTION_SAMPLE_RATE: "1"
ELASTIC_APM_API_BUFFER_SIZE: "256"
ELASTIC_APM_API_REQUEST_SIZE: "500kb"
ELASTIC_APM_API_REQUEST_TIME: "5s"
ELASTIC_APM_CAPTURE_HEADERS: "true"
ELASTIC_APM_CAPTURE_ENV: "true"
ELASTIC_APM_SPAN_FRAMES_MIN_DURATION: "5ms"
ELASTIC_APM_STACK_TRACE_LIMIT: "50"
ELASTIC_APM_TRANSACTION_MAX_SPANS: "300"
3.2 Three Templating Patterns
Pattern A — ERB (hh-server): Uses <%= ENV['...'] %> placeholders, substituted by envsubst at deploy time.
# overlays/staging/hh-engineering-public/patches/elastic-apm-config.yaml
data:
ELASTIC_APM_SERVER_URL: "<%= ENV['APM_SERVER_URL'] %>"
ELASTIC_APM_ENVIRONMENT: "engineering"
Pattern B — Placeholder (pricing-engine): Uses <PLACEHOLDER> strings, replaced by kustomize or shell scripts.
# overlays/do/prod/patches/elastic-apm-config.yaml
data:
ELASTIC_APM_SERVER_URL: "<APM_SERVER_URL>"
ELASTIC_APM_ENVIRONMENT: "<ENVIRONMENT>"
Pattern C — elastic_apm.yml (dev-preview): Uses a single YAML file in the ConfigMap data key, with ERB templating for server_url. Used in dev-preview-* namespaces.
# dev-preview-engineering-public elastic-apm-config
data:
elastic_apm.yml: |
service_name: "hh-server"
server_url: <%= ENV['APM_SERVER_URL'] %>
environment: "engineering"
log_level: info
log_path: 'log/elastic_apm.log'
api_request_size: "500kb"
api_request_time: "10s"
capture_headers: false
capture_env: false
pool_size: 5
transaction_sample_rate: 1
api_buffer_size: 512
3.3 Deployment Structure (hh-server)
hh-server/
├── manifest/
│ ├── base/
│ │ ├── staging/public/configmaps/elastic-apm-config.yaml # Base staging defaults
│ │ └── prod/public/configmaps/elastic-apm-config.yaml # Base production defaults
│ └── overlays/
│ ├── staging/
│ │ ├── hh-engineering-public/patches/elastic-apm-config.yaml # ERB override
│ │ └── hh-ballbot-public/patches/elastic-apm-config.yaml # Placeholder override
│ └── prod/
│ └── ...
3.4 Deployment Structure (pricing-engine)
pricing-engine/
├── manifest/
│ ├── base/
│ │ └── configmaps/elastic-apm-config.yaml # Base with placeholders
│ └── overlays/
│ ├── do/prod/patches/elastic-apm-config.yaml # DigitalOcean prod
│ ├── eks/dev262/patches/elastic-apm-config.yaml # EKS dev/staging
│ └── eks/prod/patches/elastic-apm-config.yaml # EKS production
4. Build Pipeline Integration
4.1 AWS Secrets Manager
APM_SERVER_URL is stored in AWS Secrets Manager and injected at deploy time via BuildSpec.
Path convention: /hungryhub/<Environment>:APM_SERVER_URL
# buildspec-hh-engineering.yaml
env:
variables:
ECR_LOGIN_ACCOUNT_ID: "627608802312"
APP_NAME: "hh-server"
parameter-store:
APM_SERVER_URL: "/hungryhub/$Environment:APM_SERVER_URL"
ELASTICSEARCH_SERVER_URL: "/hungryhub/$Environment:ELASTICSEARCH_SERVER_URL"
# ... other secrets
4.2 envsubst for Config Substitution
Some overlays use set_env.sh + envsubst to substitute environment variables into ConfigMap files at deploy time.
# set_env.sh (hh-engineering-public)
export ELASTICSEARCH_SERVER_URL="http://opendistro-es-node1:9200"
export ELASTICSEARCH_INDEX_SERVER="app-log-staging-server"
# ... more OpenSearch indices
# Substitute into fluent-bit.conf template
envsubst < fluent-bit.conf.template > fluent-bit.conf
5. Production vs Staging Tuning
| Parameter | Staging | Production |
|---|---|---|
log_level | info | warn |
pool_size | 5 | 15 |
transaction_sample_rate | 1.0 (100%) | 0.7 (70%) |
api_buffer_size | 256 | 256 |
api_request_size | 500kb | 500kb (some ns: 1500kb) |
api_request_time | 5s | 5s (some ns: 29s) |
span_frames_min_duration | 5ms | 5ms (syn: 100ms) |
stack_trace_limit | 50 | 200 (syn: 20) |
transaction_max_spans | 300 | 700 |
capture_headers | true | true |
capture_env | true | true |
Key differences:
- Staging: Full sampling (
1.0), smaller pool (5), verbose logging (info) - Production: Reduced sampling (
0.7), larger pool (15), minimal logging (warn) - Some namespaces have custom overrides (e.g.,
hh-ballbotusesapi_request_size: 1500kb,api_request_time: 29sfor longer requests)
6. Verification Steps
6.1 Check ConfigMap Values
# Staging (eks-dev-262)
kubectl config use-context eks-dev-262
kubectl get configmap elastic-apm-config -n <namespace> -o yaml
# Production (eks-prod-21)
kubectl config use-context eks-prod-21
kubectl get configmap elastic-apm-config -n <namespace> -o yaml
6.2 Verify APM Server Connectivity
# From within the cluster
kubectl run apm-test --rm -it --image=curlimages/curl -- \
curl -v http://20.0.30.229:8300
# Expected: HTTP 200 with APM server info JSON
6.3 Check APM Agent Status in Logs
# Check app logs for APM startup
kubectl logs -n <namespace> deployment/<app-name> | grep -i "elastic"
# Look for:
# "ElasticAPM started" or "Connecting to APM Server"
6.4 Verify Traces in Kibana
-
Open Kibana APM UI:
- Staging:
https://apm.hh-engineering.my.id - Production:
https://apm.hungryhub.com
- Staging:
-
Navigate to APM > Services
-
Find your
service_namein the list -
Generate some traffic to the app
-
Check for:
- Transactions (requests)
- Spans (database queries, external calls)
- Errors (if any)
6.5 Verify via Elasticsearch API
# Check APM indices exist
curl -u elastic:sGQJBy1Gbg7qfYbbFlWd \
"https://apm.hungryhub.com/_cat/indices/apm*?v"
# Search for recent transactions from your service
curl -u elastic:sGQJBy1Gbg7qfYbbFlWd \
"https://apm.hungryhub.com/apm-*/_search" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"must": [
{ "match": { "service.name": "<SERVICE_NAME>" } },
{ "range": { "@timestamp": { "gte": "now-1h" } } }
]
}
},
"size": 5
}'
7. Troubleshooting
APM agent not starting
- Check
ELASTIC_APM_AGENT=1is set in Dockerfile or deployment env - Verify
ENABLE_ELASTIC_APM=truein ConfigMap - Check initializer isn’t overriding to
enabled: false
No data in Kibana
- Verify
server_urlmatches the correct APM server for your cluster - Check
service_nameis unique and matches the Kibana service list - Ensure the namespace ConfigMap is actually mounted (check
kubectl describe pod) - Check APM server logs:
kubectl logs -n monitoring deployment/apm-server
Raw placeholders in ConfigMap
If <APM_SERVER_URL> or <%= ENV['APM_SERVER_URL'] %> appears literally:
- The
set_env.sh/envsubststep didn’t run - The kustomize overlay patch didn’t apply
- Re-run the build pipeline or manually apply the correct values
High memory usage
- Reduce
pool_size(default10, prod15) - Reduce
transaction_max_spans(prod:700, staging:300) - Set
transaction_sample_ratelower (e.g.,0.3)
Quick Reference
| What | Where |
|---|---|
| APM Credentials | knowledge-base/src/DevOps/Credential_Access_Monitoring_Logs_&_APM_Application_Performance_Monitoring_20250805105932.md |
| Ruby instrumentation guide | knowledge-base/src/PricingEngine/010-APM-MONITORING.md |
| hh-server ConfigMaps | hh-server/manifest/base/{staging,prod}/public/configmaps/elastic-apm-config.yaml |
| pricing-engine ConfigMaps | pricing-engine/manifest/base/configmaps/elastic-apm-config.yaml |
| BuildSpec (hh-server) | hh-server/buildspec-<namespace>.yaml |
| Staging APM URL | http://128.199.168.171:8200 |
| Production APM URL | http://20.0.30.229:8300 |
| Staging Kibana | https://apm.hh-engineering.my.id |
| Production Kibana | https://apm.hungryhub.com |