diff --git a/CLAUDE.md b/CLAUDE.md index 03308a4..27b7185 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,7 @@ helmfile -e devel diff ./deploy/deploy.sh devel # full deploy incl. slack, failure recovery, website check # Tests (run all of these before considering a change done) -./tests/run-golden-tests.sh # snapshot tests (5 scenarios × 3 variants) +./tests/run-golden-tests.sh # snapshot tests (6 scenarios × 3 variants) ./tests/run-golden-tests.sh --update # regenerate snapshots after INTENTIONAL changes ./tests/run-golden-tests.sh basic-production # single scenario helm unittest charts/shopsys-app charts/shopsys-infra diff --git a/charts/shopsys-app/templates/networkpolicy.yaml b/charts/shopsys-app/templates/networkpolicy.yaml new file mode 100644 index 0000000..31314ca --- /dev/null +++ b/charts/shopsys-app/templates/networkpolicy.yaml @@ -0,0 +1,46 @@ +{{- if .Values.networkPolicy.enabled }} +{{- /* The namespace-wide default deny (and the optional egress lockdown) live in the + infra chart; this chart allows traffic to its own workloads. */}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-webserver + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + podSelector: + matchLabels: + app: webserver-php-fpm + policyTypes: + - Ingress + ingress: + - from: + # e-shop and MCP ingresses + - namespaceSelector: + {{- toYaml .Values.networkPolicy.ingressControllerNamespace | nindent 12 }} + # storefront server-side requests (INTERNAL_ENDPOINT) and other app pods + - podSelector: {} + ports: + - port: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-storefront + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + podSelector: + matchLabels: + app: storefront + policyTypes: + - Ingress + ingress: + - from: + # the webserver's nginx proxies /_next/ and @storefront to the storefront; no + # shipped ingress targets the storefront directly (use networkPolicy.extraIngress + # when a project adds one) + - podSelector: {} + ports: + - port: 3000 +{{- end }} diff --git a/charts/shopsys-app/tests/networkpolicy_test.yaml b/charts/shopsys-app/tests/networkpolicy_test.yaml new file mode 100644 index 0000000..b9e52de --- /dev/null +++ b/charts/shopsys-app/tests/networkpolicy_test.yaml @@ -0,0 +1,53 @@ +suite: opt-in network policies (app) +values: + - ./values/required.yaml +templates: + - templates/networkpolicy.yaml +tests: + - it: renders nothing by default + asserts: + - hasDocuments: + count: 0 + + - it: allows the ingress controller and in-namespace pods to reach the workloads + set: + networkPolicy: + enabled: true + asserts: + - hasDocuments: + count: 2 + - equal: + path: spec.podSelector.matchLabels.app + value: webserver-php-fpm + documentIndex: 0 + - equal: + path: spec.ingress[0].ports[0].port + value: 8080 + documentIndex: 0 + - equal: + path: spec.ingress[0].from[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"] + value: ingress-nginx + documentIndex: 0 + - equal: + path: spec.ingress[0].ports[0].port + value: 3000 + documentIndex: 1 + # no shipped ingress targets the storefront - in-namespace traffic only + - equal: + path: spec.ingress[0].from + value: + - podSelector: {} + documentIndex: 1 + + - it: honors a custom ingress-controller namespace selector + set: + networkPolicy: + enabled: true + ingressControllerNamespace: + matchLabels: + role: edge + asserts: + - equal: + path: spec.ingress[0].from[0].namespaceSelector.matchLabels.role + value: edge + documentIndex: 0 diff --git a/charts/shopsys-app/values.schema.json b/charts/shopsys-app/values.schema.json index 5cc3814..a56d0e6 100644 --- a/charts/shopsys-app/values.schema.json +++ b/charts/shopsys-app/values.schema.json @@ -83,6 +83,24 @@ "existingSecret": { "type": "string" } } }, + "networkPolicy": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "ingressControllerNamespace": { "type": "object" }, + "monitoringNamespace": { "type": "object" }, + "extraIngress": { "type": "array", "items": { "type": "object" } }, + "egress": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "rules": { "type": "array", "items": { "type": "object" } } + } + } + } + }, "app": { "type": "object", "properties": { diff --git a/charts/shopsys-app/values.yaml b/charts/shopsys-app/values.yaml index 1ea3f47..171f795 100644 --- a/charts/shopsys-app/values.yaml +++ b/charts/shopsys-app/values.yaml @@ -78,6 +78,33 @@ serviceAccount: name: "" # generated from the chart name when empty automountToken: false +# Opt-in NetworkPolicies (shared values section - both charts render their part): +# default-deny ingress for the whole namespace, allows for the chart workloads (plus +# cert-manager's HTTP01 solver pods, which run in this namespace), and an optional egress +# lockdown. Roll out on a dev environment first and verify that your CNI enforces +# policies and that kubelet probes still pass. +networkPolicy: + enabled: false + # Selector of the namespace running the ingress controller + ingressControllerNamespace: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + # Selector of the namespace running Prometheus (exporter scraping) + monitoringNamespace: + matchLabels: + kubernetes.io/metadata.name: monitoring + # Escape hatch: raw NetworkPolicyIngressRule list applied to all pods in the namespace + extraIngress: [] + egress: + # When enabled, egress is denied namespace-wide except DNS, in-namespace traffic and + # the rules below. External services (PostgreSQL, Elasticsearch, S3, SMTP) and the + # Kubernetes API (needed by the cron-suspend hook) MUST be listed here. + # DNS is allowed to ANY destination (the cluster DNS location differs per cluster and + # cannot be selected generically) - DNS tunneling remains possible under the lockdown. + enabled: false + # Raw NetworkPolicyEgressRule list + rules: [] + app: # Backend environment variables (webserver, cron, consumers, migration job, cron shell). # Values MUST be strings - quote values like "479411e7" in YAML. diff --git a/charts/shopsys-infra/templates/networkpolicy.yaml b/charts/shopsys-infra/templates/networkpolicy.yaml new file mode 100644 index 0000000..ec805f6 --- /dev/null +++ b/charts/shopsys-infra/templates/networkpolicy.yaml @@ -0,0 +1,156 @@ +{{- if .Values.networkPolicy.enabled }} +{{- /* Namespace-wide policies live in the infra chart (installed first): the default + ingress deny, the redis/rabbitmq allows, the extra-ingress escape hatch and the + optional egress lockdown. The app chart adds the webserver/storefront allows. */}} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-ingress + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + podSelector: {} + policyTypes: + - Ingress +{{- if .Values.redis.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-redis + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + podSelector: + matchLabels: + app: redis + policyTypes: + - Ingress + ingress: + # application pods in this namespace + - from: + - podSelector: {} + ports: + - port: 6379 + # prometheus scraping of the redis exporter + - from: + - namespaceSelector: + {{- toYaml .Values.networkPolicy.monitoringNamespace | nindent 12 }} + ports: + - port: 9121 +{{- end }} +{{- if .Values.rabbitmq.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-rabbitmq + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + podSelector: + matchLabels: + app: rabbitmq + policyTypes: + - Ingress + ingress: + # application pods in this namespace + - from: + - podSelector: {} + ports: + - port: 5672 + {{- /* same condition as ingress-rabbitmq.yaml - no rule when no management ingress */}} + {{- if or .Values.rabbitmq.management.hostname (gt (len .Values.domains) 0) }} + # management UI through the ingress controller + - from: + - namespaceSelector: + {{- toYaml .Values.networkPolicy.ingressControllerNamespace | nindent 12 }} + ports: + - port: 15672 + {{- end }} + # prometheus scraping of the built-in exporter + - from: + - namespaceSelector: + {{- toYaml .Values.networkPolicy.monitoringNamespace | nindent 12 }} + ports: + - port: 15692 +{{- end }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-acme-solver + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + # cert-manager's HTTP01 solver pods are spawned in this namespace for the ingress + # certificates and must accept the challenge request on 8089, or every issuance and + # renewal fails under the default deny. Deliberately not restricted to the + # ingress-controller namespace: cert-manager's self-check may reach the solver with a + # different source (load-balancer/externalTrafficPolicy specifics), and the solver + # serves nothing but the public challenge token. Selects no pods on DNS01 clusters. + podSelector: + matchLabels: + acme.cert-manager.io/http01-solver: "true" + policyTypes: + - Ingress + ingress: + - ports: + - port: 8089 +{{- with .Values.networkPolicy.extraIngress }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-extra-ingress + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + {{- toYaml . | nindent 4 }} +{{- end }} +{{- if .Values.networkPolicy.egress.enabled }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-egress + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + podSelector: {} + policyTypes: + - Egress +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress + labels: + {{- include "shopsys.labels" $ | nindent 4 }} +spec: + podSelector: {} + policyTypes: + - Egress + egress: + # DNS anywhere: the cluster DNS location differs per cluster (kube-system, node-local + # caches on link-local IPs) and cannot be selected generically. Residual risk: DNS + # tunneling remains a possible exfiltration path under the lockdown. + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + # everything inside this namespace (redis, rabbitmq, webserver, storefront) + - to: + - podSelector: {} + # project-specific external services (PostgreSQL, Elasticsearch, S3, SMTP, the + # Kubernetes API for the cron-suspend hook, ...) + {{- with .Values.networkPolicy.egress.rules }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/shopsys-infra/tests/networkpolicy_test.yaml b/charts/shopsys-infra/tests/networkpolicy_test.yaml new file mode 100644 index 0000000..7acbd01 --- /dev/null +++ b/charts/shopsys-infra/tests/networkpolicy_test.yaml @@ -0,0 +1,145 @@ +suite: opt-in network policies (infra) +values: + - ./values/required.yaml +templates: + - templates/networkpolicy.yaml +tests: + - it: renders nothing by default + asserts: + - hasDocuments: + count: 0 + + - it: renders the default ingress deny, the redis/rabbitmq allows and the acme-solver allow + set: + networkPolicy: + enabled: true + asserts: + - hasDocuments: + count: 4 + - equal: + path: metadata.name + value: default-deny-ingress + documentIndex: 0 + - equal: + path: spec.podSelector + value: {} + documentIndex: 0 + - equal: + path: spec.ingress[0].ports[0].port + value: 6379 + documentIndex: 1 + - equal: + path: spec.ingress[1].ports[0].port + value: 9121 + documentIndex: 1 + - equal: + path: spec.ingress[0].ports[0].port + value: 5672 + documentIndex: 2 + - equal: + path: spec.ingress[1].ports[0].port + value: 15672 + documentIndex: 2 + - equal: + path: metadata.name + value: allow-acme-solver + documentIndex: 3 + - equal: + path: spec.podSelector.matchLabels["acme.cert-manager.io/http01-solver"] + value: "true" + documentIndex: 3 + - equal: + path: spec.ingress[0].ports[0].port + value: 8089 + documentIndex: 3 + + - it: skips the redis and rabbitmq allows when the workloads are disabled + set: + networkPolicy: + enabled: true + redis: + enabled: false + rabbitmq: + enabled: false + asserts: + - hasDocuments: + count: 2 + - equal: + path: metadata.name + value: default-deny-ingress + documentIndex: 0 + - equal: + path: metadata.name + value: allow-acme-solver + documentIndex: 1 + + - it: does not open 15672 when the management ingress does not render + set: + networkPolicy: + enabled: true + domains: [] + asserts: + - equal: + path: spec.ingress[0].ports[0].port + value: 5672 + documentIndex: 2 + - equal: + path: spec.ingress[1].ports[0].port + value: 15692 + documentIndex: 2 + + - it: keeps egress open unless explicitly locked down + set: + networkPolicy: + enabled: true + asserts: + - hasDocuments: + count: 4 + + - it: locks down egress with DNS, in-namespace and custom rules + set: + networkPolicy: + enabled: true + egress: + enabled: true + rules: + - to: + - ipBlock: + cidr: 10.0.0.5/32 + ports: + - port: 5432 + asserts: + - hasDocuments: + count: 6 + - equal: + path: metadata.name + value: default-deny-egress + documentIndex: 4 + - equal: + path: spec.egress[0].ports[0].port + value: 53 + documentIndex: 5 + - equal: + path: spec.egress[2].to[0].ipBlock.cidr + value: 10.0.0.5/32 + documentIndex: 5 + + - it: renders the extra-ingress escape hatch + set: + networkPolicy: + enabled: true + extraIngress: + - from: + - ipBlock: + cidr: 192.168.0.0/16 + asserts: + - hasDocuments: + count: 5 + - equal: + path: metadata.name + value: allow-extra-ingress + documentIndex: 4 + - equal: + path: spec.ingress[0].from[0].ipBlock.cidr + value: 192.168.0.0/16 + documentIndex: 4 diff --git a/charts/shopsys-infra/values.schema.json b/charts/shopsys-infra/values.schema.json index b187b29..19c23bb 100644 --- a/charts/shopsys-infra/values.schema.json +++ b/charts/shopsys-infra/values.schema.json @@ -12,6 +12,24 @@ "environment": { "type": "string" } } }, + "networkPolicy": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "ingressControllerNamespace": { "type": "object" }, + "monitoringNamespace": { "type": "object" }, + "extraIngress": { "type": "array", "items": { "type": "object" } }, + "egress": { + "type": "object", + "additionalProperties": false, + "properties": { + "enabled": { "type": "boolean" }, + "rules": { "type": "array", "items": { "type": "object" } } + } + } + } + }, "serviceAccount": { "type": "object", "additionalProperties": false, diff --git a/charts/shopsys-infra/values.yaml b/charts/shopsys-infra/values.yaml index f3c3182..70d1f78 100644 --- a/charts/shopsys-infra/values.yaml +++ b/charts/shopsys-infra/values.yaml @@ -39,6 +39,23 @@ serviceAccount: name: "" automountToken: false +# Opt-in NetworkPolicies - shared values section, see the app chart for documentation. +# This chart renders the namespace-wide default deny, the redis/rabbitmq allows, the +# cert-manager HTTP01 solver allow, the extra-ingress escape hatch and the optional +# egress lockdown. +networkPolicy: + enabled: false + ingressControllerNamespace: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + monitoringNamespace: + matchLabels: + kubernetes.io/metadata.name: monitoring + extraIngress: [] + egress: + enabled: false + rules: [] + redis: enabled: true image: diff --git a/docs/values.md b/docs/values.md index 95a5643..3f89163 100644 --- a/docs/values.md +++ b/docs/values.md @@ -78,6 +78,22 @@ serviceAccount: # per-chart SA the workload pods run under (no API automountToken: false # applied on the chart SA AND at pod level on every workload # pod - effective even with create=false (pod-level wins) +networkPolicy: # opt-in; default-deny ingress + per-workload allows (incl. + enabled: false # cert-manager's HTTP01 solver pods on port 8089 - they run + # in this namespace and issuance/renewal would break without + # the allow). Roll out on a dev environment first (CNI must + # enforce policies; verify kubelet probes still pass) + ingressControllerNamespace: + matchLabels: { kubernetes.io/metadata.name: ingress-nginx } + monitoringNamespace: + matchLabels: { kubernetes.io/metadata.name: monitoring } + extraIngress: [] # raw ingress rules applied to all pods (escape hatch) + egress: # optional egress lockdown: DNS + in-namespace allowed, + enabled: false # everything else must be listed in `rules` (incl. the + rules: [] # K8s API for the cron-suspend hook, DB, ES, S3, SMTP). + # DNS is allowed to ANY destination (cluster DNS location + # differs per cluster) - DNS tunneling stays possible + app: # shared backend configuration env: {} # non-sensitive backend env vars (webserver, cron, consumers, migration) secretEnv: {} # sensitive backend env vars → app-secret-env Secret + envFrom; diff --git a/tests/golden/scenarios/network-policies/description.txt b/tests/golden/scenarios/network-policies/description.txt new file mode 100644 index 0000000..4510994 --- /dev/null +++ b/tests/golden/scenarios/network-policies/description.txt @@ -0,0 +1 @@ +Production deployment with opt-in NetworkPolicies enabled, incl. extra ingress and the egress lockdown diff --git a/tests/golden/scenarios/network-policies/environments/base.yaml b/tests/golden/scenarios/network-policies/environments/base.yaml new file mode 100644 index 0000000..ea59d62 --- /dev/null +++ b/tests/golden/scenarios/network-policies/environments/base.yaml @@ -0,0 +1,71 @@ +project: + name: myproject +domains: + - hostname: www.example.com + - hostname: www.example.sk +security: + mcp: + ipWhitelist: + - 203.0.113.0/24 + - 198.51.100.10/32 +app: + env: + DATABASE_HOST: "10.0.0.100" + DATABASE_NAME: "myproject-production" + DATABASE_PORT: "5432" + DATABASE_USER: "myproject-production" + S3_ENDPOINT: "https://s3.example.com" + S3_ACCESS_KEY: "myproject-production" + S3_BUCKET_NAME: "myproject-production" + ELASTICSEARCH_HOST: "http://elasticsearch:9200" + ELASTIC_SEARCH_INDEX_PREFIX: "myproject-production" + REDIS_PREFIX: "myproject-production" + MAILER_DSN: "smtp://mailhog:1025" + TRUSTED_PROXY: "10.0.0.0/8" + MESSENGER_TRANSPORT_DSN: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + s3Endpoint: "https://s3.example.com" + secretEnv: + APP_SECRET: "test-app-secret-key" + DATABASE_PASSWORD: "test-db-password" + S3_SECRET: "test-s3-secret" +cron: + instances: + - name: cron + schedule: '*/5 * * * *' +consumers: + instances: + - name: email + transports: email_transport + replicas: 1 +webserver: + autoscaling: + enabled: true +storefront: + autoscaling: + enabled: true +rabbitmq: + management: + ipWhitelist: + - 10.0.0.0/8 +networkPolicy: + enabled: true + monitoringNamespace: + matchLabels: + kubernetes.io/metadata.name: observability + extraIngress: + - from: + - ipBlock: + cidr: 192.168.0.0/16 + egress: + enabled: true + rules: + - to: + - ipBlock: + cidr: 10.0.0.100/32 + ports: + - port: 5432 + - to: + - ipBlock: + cidr: 10.96.0.1/32 + ports: + - port: 443 diff --git a/tests/golden/scenarios/network-policies/environments/production/values.yaml b/tests/golden/scenarios/network-policies/environments/production/values.yaml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/golden/scenarios/network-policies/environments/production/values.yaml @@ -0,0 +1 @@ +{} diff --git a/tests/golden/scenarios/network-policies/expected/continuous.yaml b/tests/golden/scenarios/network-policies/expected/continuous.yaml new file mode 100644 index 0000000..4bc9514 --- /dev/null +++ b/tests/golden/scenarios/network-policies/expected/continuous.yaml @@ -0,0 +1,2285 @@ +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-ingress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Ingress + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: + matchLabels: + app: redis + policyTypes: + - Ingress + ingress: + # application pods in this namespace + - from: + - podSelector: {} + ports: + - port: 6379 + # prometheus scraping of the redis exporter + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: observability + ports: + - port: 9121 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: + matchLabels: + app: rabbitmq + policyTypes: + - Ingress + ingress: + # application pods in this namespace + - from: + - podSelector: {} + ports: + - port: 5672 + # management UI through the ingress controller + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + ports: + - port: 15672 + # prometheus scraping of the built-in exporter + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: observability + ports: + - port: 15692 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-acme-solver + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + # cert-manager's HTTP01 solver pods are spawned in this namespace for the ingress + # certificates and must accept the challenge request on 8089, or every issuance and + # renewal fails under the default deny. Deliberately not restricted to the + # ingress-controller namespace: cert-manager's self-check may reach the solver with a + # different source (load-balancer/externalTrafficPolicy specifics), and the solver + # serves nothing but the public challenge token. Selects no pods on DNS01 clusters. + podSelector: + matchLabels: + acme.cert-manager.io/http01-solver: "true" + policyTypes: + - Ingress + ingress: + - ports: + - port: 8089 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-extra-ingress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - ipBlock: + cidr: 192.168.0.0/16 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-egress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Egress + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Egress + egress: + # DNS anywhere: the cluster DNS location differs per cluster (kube-system, node-local + # caches on link-local IPs) and cannot be selected generically. Residual risk: DNS + # tunneling remains a possible exfiltration path under the lockdown. + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + # everything inside this namespace (redis, rabbitmq, webserver, storefront) + - to: + - podSelector: {} + # project-specific external services (PostgreSQL, Elasticsearch, S3, SMTP, the + # Kubernetes API for the cron-suspend hook, ...) + - ports: + - port: 5432 + to: + - ipBlock: + cidr: 10.0.0.100/32 + - ports: + - port: 443 + to: + - ipBlock: + cidr: 10.96.0.1/32 + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +# ServiceAccount used by the deploy hook Jobs of the shopsys-app release. +# Lives in the infra chart so it exists before the first application upgrade runs its hooks. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + +--- +# Source: shopsys-infra/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shopsys-infra + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +automountServiceAccountToken: false + +--- +# Source: shopsys-infra/templates/secret-rabbitmq.yaml +apiVersion: v1 +kind: Secret +metadata: + name: rabbitmq-credentials + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +type: Opaque +stringData: + user: "rabbitmq" + password: "rabbitmq-password" + +--- +# Source: shopsys-infra/templates/configmap-redis-health.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-health-configmap + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +data: + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + +--- +# Source: shopsys-infra/templates/configmap-redis.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +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 + + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +rules: + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "list", "watch", "patch"] + - apiGroups: ["apps"] + resources: ["deployments/scale"] + verbs: ["get", "patch", "update"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: deploy-hooks +subjects: + - kind: ServiceAccount + name: deploy-hooks + +--- +# Source: shopsys-infra/templates/service-rabbitmq.yaml +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + app: rabbitmq + prometheus-exporter: 'true' +spec: + clusterIP: None + selector: + app: rabbitmq + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + +--- +# Source: shopsys-infra/templates/service-redis.yaml +apiVersion: v1 +kind: Service +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + app: redis + prometheus-exporter: 'true' +spec: + selector: + app: redis + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + +--- +# Source: shopsys-infra/templates/deployment-redis.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: "redis" + project/environment: "production" + project/name: "myproject" + checksum/redis-config: 81f63d0ecd9fa1305f6c4157058b05bd93792e15148d17fc4e542b9c24e33635 + labels: + app: redis + spec: + serviceAccountName: shopsys-infra + automountServiceAccountToken: false + securityContext: + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + volumes: + - name: health + configMap: + name: redis-health-configmap + defaultMode: 0755 + - name: config + configMap: + name: redis + defaultMode: 0755 + containers: + - name: redis + image: "redis:7.4-alpine" + ports: + - name: redis + containerPort: 6379 + protocol: TCP + volumeMounts: + - name: health + mountPath: /health + - name: config + mountPath: /usr/local/etc/redis/redis.conf + subPath: redis.conf + args: + - /usr/local/etc/redis/redis.conf + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + livenessProbe: + initialDelaySeconds: 30 + timeoutSeconds: 5 + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + readinessProbe: + initialDelaySeconds: 5 + timeoutSeconds: 5 + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + - name: redis-exporter + image: "oliver006/redis_exporter:v1.89.0" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + ports: + - name: exporter + containerPort: 9121 + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + +--- +# Source: shopsys-infra/templates/statefulset-rabbitmq.yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + serviceName: rabbitmq + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + serviceAccountName: shopsys-infra + automountServiceAccountToken: false + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + containers: + - name: rabbitmq + image: "rabbitmq:4.1-management-alpine" + ports: + - name: rabbitmq + containerPort: 15672 + protocol: TCP + - name: exporter + containerPort: 15692 + protocol: TCP + env: + - name: RABBITMQ_DEFAULT_USER + valueFrom: + secretKeyRef: + name: rabbitmq-credentials + key: user + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: rabbitmq-credentials + key: password + resources: + requests: + cpu: 20m + + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: rabbitmq-data + mountPath: /var/lib/rabbitmq + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + storageClassName: nfs-client + resources: + requests: + storage: 1Gi + +--- +# Source: shopsys-infra/templates/ingress-rabbitmq.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: rabbitmq-domain + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8" +spec: + ingressClassName: nginx + tls: + - hosts: + - "rabbitmq.www.example.com" + secretName: tls-certificate + rules: + - host: "rabbitmq.www.example.com" + http: + paths: + - backend: + service: + name: rabbitmq + port: + number: 15672 + path: '/' + pathType: Prefix + +--- +# Source: shopsys-app/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-webserver + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + podSelector: + matchLabels: + app: webserver-php-fpm + policyTypes: + - Ingress + ingress: + - from: + # e-shop and MCP ingresses + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + # storefront server-side requests (INTERNAL_ENDPOINT) and other app pods + - podSelector: {} + ports: + - port: 8080 + +--- +# Source: shopsys-app/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + podSelector: + matchLabels: + app: storefront + policyTypes: + - Ingress + ingress: + - from: + # the webserver's nginx proxies /_next/ and @storefront to the storefront; no + # shipped ingress targets the storefront directly (use networkPolicy.extraIngress + # when a project adds one) + - podSelector: {} + ports: + - port: 3000 + +--- +# Source: shopsys-app/templates/pdb-storefront.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: storefront +spec: + minAvailable: 1 + selector: + matchLabels: + app: storefront + +--- +# Source: shopsys-app/templates/pdb-webserver.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: webserver-php-fpm +spec: + minAvailable: 1 + selector: + matchLabels: + app: webserver-php-fpm + +--- +# Source: shopsys-app/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shopsys-app + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +automountServiceAccountToken: false + +--- +# Source: shopsys-app/templates/secret-app-env.yaml +apiVersion: v1 +kind: Secret +metadata: + name: app-secret-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: Opaque +stringData: + APP_SECRET: "test-app-secret-key" + DATABASE_PASSWORD: "test-db-password" + S3_SECRET: "test-s3-secret" + +--- +# Source: shopsys-app/templates/secret-app-env.yaml +apiVersion: v1 +kind: Secret +metadata: + name: cron-secret-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: Opaque +stringData: + .project_secret_env.sh: | + + export APP_SECRET='test-app-secret-key' + export DATABASE_PASSWORD='test-db-password' + export S3_SECRET='test-s3-secret' + +--- +# Source: shopsys-app/templates/secret-dockerregistry.yaml +apiVersion: v1 +kind: Secret +metadata: + name: dockerregistry + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJhdXRoIjoiWkdWd2JHOTVMWFZ6WlhJNlpHVndiRzk1TFhCaGMzTjNiM0prIiwiZW1haWwiOiIiLCJwYXNzd29yZCI6ImRlcGxveS1wYXNzd29yZCIsInVzZXJuYW1lIjoiZGVwbG95LXVzZXIifX19 + +--- +# Source: shopsys-app/templates/configmap-cron-env.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cron-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + .project_env.sh: |+ + + export DATABASE_HOST='10.0.0.100' + export DATABASE_NAME='myproject-production' + export DATABASE_PORT='5432' + export DATABASE_USER='myproject-production' + export ELASTICSEARCH_HOST='http://elasticsearch:9200' + export ELASTIC_SEARCH_INDEX_PREFIX='myproject-production' + export MAILER_DSN='smtp://mailhog:1025' + export MAILER_FORCE_WHITELIST='false' + export MESSENGER_TRANSPORT_DSN='amqp://guest:guest@rabbitmq:5672/%2f/messages' + export REDIS_PREFIX='myproject-production' + export S3_ACCESS_KEY='myproject-production' + export S3_BUCKET_NAME='myproject-production' + export S3_ENDPOINT='https://s3.example.com' + export TRUSTED_PROXY='10.0.0.0/8' + +--- +# Source: shopsys-app/templates/configmap-cron-list.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cron-list + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + cron: |+ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + */5 * * * * . /root/.project_env.sh && . /root/.project_secret_env.sh && cd /var/www/html/ && ./phing cron > /dev/null 2>&1 + + +--- +# Source: shopsys-app/templates/configmap-domains-urls.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: domains-urls + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.sk + +--- +# Source: shopsys-app/templates/configmap-nginx.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-default-config + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + nginx.conf: | + # No `user` directive - nginx runs as the unprivileged nginx user (runAsUser 101) + # enforced by the container securityContext; the pid file lives on the writable /tmp + # emptyDir because the root filesystem is read-only. + worker_processes 2; + + error_log /dev/stderr warn; + pid /tmp/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 { + # Unprivileged health port - nginx runs as a non-root user and cannot bind below 1024 + listen 8081; + 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-production/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-production/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"; + } + } + + +--- +# Source: shopsys-app/templates/configmap-php-fpm.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: production-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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 + + +--- +# Source: shopsys-app/templates/configmap-php-opcache.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: production-php-opcache + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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" + + +--- +# Source: shopsys-app/templates/service-storefront.yaml +apiVersion: v1 +kind: Service +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + app: storefront + ports: + - name: storefront + port: 3000 + targetPort: 3000 + +--- +# Source: shopsys-app/templates/service-webserver-php-fpm.yaml +apiVersion: v1 +kind: Service +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + app: webserver-php-fpm + ports: + - name: http + port: 8080 + targetPort: 8080 + +--- +# Source: shopsys-app/templates/deployment-consumer.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: consumer-email + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: consumer-email +spec: + progressDeadlineSeconds: 600 + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: consumer-email + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "email" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/app-env: 89cad8f3a6a12379c4f642ee72913bf76aed6394870d016ccdacf109842b7740 + labels: + app: consumer-email + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + volumes: + - name: domains-urls + configMap: + name: domains-urls + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + terminationGracePeriodSeconds: 300 + containers: + - image: "v1.0.0" + name: consumer-email + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + command: ["/bin/sh", "-c"] + args: + - | + PIPE=/tmp/log-pipe + rm -rf $PIPE + mkfifo $PIPE + chmod 666 $PIPE + stdbuf -o0 tail -n +1 -f $PIPE & + + sleep 5 + + while [ ! -f /tmp/stop_consumer ]; do + php /var/www/html/bin/console messenger:consume email_transport --time-limit=300 --quiet + sleep 2 + done + lifecycle: + preStop: + exec: + command: ["/bin/sh", "-c", "touch /tmp/stop_consumer && php bin/console messenger:stop-workers"] + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + resources: + limits: + memory: 1Gi + requests: + cpu: 50m + memory: 300Mi + + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + +--- +# Source: shopsys-app/templates/deployment-cron.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cron + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: cron +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: cron + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "cron" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/cron: 040ad8eae03d513195f2a4d0070b0ec87393486bb59132754e9178f1812274d6 + labels: + app: cron + # Forces a fresh cron pod on every deploy (legacy `date` label) + date: "1234567890" + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + imagePullSecrets: + - name: dockerregistry + volumes: + - name: domains-urls + configMap: + name: domains-urls + - name: cron-list + configMap: + name: cron-list + - name: cron-env + configMap: + name: cron-env + - name: cron-secret-env + secret: + secretName: cron-secret-env + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + containers: + - image: "v1.0.0" + name: cron + securityContext: + runAsUser: 0 + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + command: ["/bin/sh","-c"] + 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"] + # The pod drains itself before termination: cron-lock prevents the next + # cron iteration, cron-watch waits until all running instances finish. + lifecycle: + preStop: + exec: + command: + - /bin/sh + - '-c' + - "cd /var/www/html && (./phing -S cron-lock > /dev/null 2>&1 &) && ./phing -S cron-watch" + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + resources: + limits: + memory: 1Gi + requests: + cpu: 100m + memory: 300Mi + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: cron-list + mountPath: /var/spool/cron/template + subPath: cron + - name: cron-env + mountPath: /root/.project_env.sh + subPath: .project_env.sh + - name: cron-secret-env + mountPath: /root/.project_secret_env.sh + subPath: .project_secret_env.sh + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + terminationGracePeriodSeconds: 3600 + +--- +# Source: shopsys-app/templates/deployment-storefront.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + matchLabels: + app: storefront + template: + metadata: + annotations: + logging/enabled: "false" + project/app: "storefront" + project/environment: "production" + project/name: "myproject" + labels: + app: storefront + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - storefront + topologyKey: kubernetes.io/hostname + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + containers: + - name: storefront + image: "v1.0.0" + ports: + - name: storefront + containerPort: 3000 + protocol: TCP + 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.sk/" + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_2 + value: "https://www.example.sk/graphql/" + - name: INTERNAL_ENDPOINT + value: "http://webserver-php-fpm:8080/" + lifecycle: + preStop: + exec: + command: + - sleep + - "10" + resources: + limits: + memory: 1.5Gi + requests: + cpu: 500m + memory: 800Mi + + securityContext: + allowPrivilegeEscalation: false + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + terminationGracePeriodSeconds: 60 + +--- +# Source: shopsys-app/templates/deployment-webserver-php-fpm.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: webserver-php-fpm +spec: + progressDeadlineSeconds: 1500 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: webserver-php-fpm + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "app" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/app-env: 89cad8f3a6a12379c4f642ee72913bf76aed6394870d016ccdacf109842b7740 + checksum/nginx: c44e553821bd4e376a288bfa0e75abd97e7655d0abee6bfe203340662ed81777 + checksum/php-fpm: 593102a5f1a5dc7c7a19ee1aae7bd32f6ee7011a0ed0ea732f25f8b2a8251ca7 + labels: + app: webserver-php-fpm + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + 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 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + hostAliases: + - ip: "127.0.0.1" + hostnames: + - "webserver-php-fpm" + - "php-fpm" + - "webserver" + volumes: + - name: source-codes + emptyDir: {} + - name: domains-urls + configMap: + name: domains-urls + - name: nginx-default-config + configMap: + name: nginx-default-config + - name: production-php-fpm + configMap: + name: production-php-fpm + - name: production-php-opcache + configMap: + name: production-php-opcache + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + # Writable paths for the read-only nginx root filesystem (temp dirs + pid file) + - name: nginx-cache + emptyDir: {} + - name: nginx-tmp + emptyDir: {} + initContainers: + - name: copy-source-codes-to-volume + image: "v1.0.0" + command: ["sh", "-c", "cp -r -n /var/www/html/. /tmp/source-codes"] + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: source-codes + mountPath: /tmp/source-codes + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + containers: + - image: "v1.0.0" + name: php-fpm + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + securityContext: + allowPrivilegeEscalation: false + lifecycle: + postStart: + exec: + command: ["/var/www/html/phing", "-S", "warmup"] + preStop: + exec: + command: + - sh + - '-c' + - sleep 10 && kill -SIGQUIT 1 + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: source-codes + mountPath: /var/www/html + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: production-php-fpm + mountPath: /usr/local/etc/php-fpm.d/www.conf + subPath: www.conf + - name: production-php-opcache + mountPath: /usr/local/etc/php/conf.d/php-opcache.ini + subPath: php-opcache.ini + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + resources: + limits: + memory: 2Gi + requests: + cpu: 500m + memory: 500Mi + + - image: "nginx:1.29-alpine" + name: webserver + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsGroup: 101 + runAsNonRoot: true + runAsUser: 101 + ports: + - containerPort: 8080 + name: http + - containerPort: 8081 + name: health + livenessProbe: + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + httpGet: + path: /health + port: 8081 + readinessProbe: + httpGet: + path: /health + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + volumeMounts: + - name: nginx-default-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + - name: nginx-default-config + mountPath: /etc/nginx/conf.d/default.conf + subPath: project-nginx.conf + - name: source-codes + mountPath: /var/www/html + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-tmp + mountPath: /tmp + lifecycle: + preStop: + exec: + command: [ + 'sh', '-c', + 'sleep 5 && /usr/sbin/nginx -s quit' + ] + resources: + limits: + memory: 300Mi + requests: + cpu: 50m + memory: 100Mi + terminationGracePeriodSeconds: 120 + +--- +# Source: shopsys-app/templates/hpa-storefront.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: storefront + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 120 + +--- +# Source: shopsys-app/templates/hpa-webserver.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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 + +--- +# Source: shopsys-app/templates/ingress-domains.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-domain-0 + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + 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; }" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.com" + - "example.com" + secretName: tls-www-example-com + rules: + - host: "www.example.com" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: "example.com" + +--- +# Source: shopsys-app/templates/ingress-domains.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-domain-1 + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + 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; }" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.sk" + - "example.sk" + secretName: tls-www-example-sk + rules: + - host: "www.example.sk" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: "example.sk" + +--- +# Source: shopsys-app/templates/ingress-mcp.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-mcp + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: "203.0.113.0/24,198.51.100.10/32" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.com" + secretName: tls-www-example-com + rules: + - host: "www.example.com" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/_mcp' + pathType: Prefix + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/mcp/oauth' + pathType: Prefix + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/.well-known/oauth-authorization-server' + pathType: ImplementationSpecific + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/.well-known/oauth-protected-resource' + pathType: ImplementationSpecific +--- +# Source: shopsys-app/templates/hooks/secret-app-env-hook.yaml +apiVersion: v1 +kind: Secret +metadata: + name: app-secret-env-hook + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" + helm.sh/hook-delete-policy: before-hook-creation +type: Opaque +stringData: + APP_SECRET: "test-app-secret-key" + DATABASE_PASSWORD: "test-db-password" + S3_SECRET: "test-s3-secret" + +--- +# Source: shopsys-app/templates/hooks/configmap-domains-urls-hook.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: domains-urls-hook + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" + helm.sh/hook-delete-policy: before-hook-creation +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.sk + +--- +# Source: shopsys-app/templates/hooks/job-cron-suspend.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: cron-suspend + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backoffLimit: 0 + template: + spec: + serviceAccountName: deploy-hooks + restartPolicy: Never + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: cron-suspend + image: "rancher/kubectl:v1.33.13" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + command: + - /bin/sh + - -c + - | + kubectl scale deployment/cron --replicas=0 --namespace=myproject-production 2>/dev/null || true + kubectl wait --for=delete pod -l app=cron --namespace=myproject-production --timeout=3660s || true + +--- +# Source: shopsys-app/templates/hooks/job-migrate-application.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "10" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 0 + template: + spec: + # Runs under the namespace default SA (as a pre-install hook it cannot reference the + # chart ServiceAccount, which does not exist yet); it never talks to the API, so the + # token is not mounted. + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + volumes: + - name: domains-urls + configMap: + name: domains-urls-hook + containers: + - name: migrate-application + image: "v1.0.0" + command: ["sh", "-c", "cd /var/www/html && ./phing -verbose db-migrations-count-with-maintenance build-deploy-part-2-db-dependent"] + securityContext: + allowPrivilegeEscalation: false + envFrom: + - secretRef: + name: app-secret-env-hook + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + restartPolicy: Never + imagePullSecrets: + - name: dockerregistry + +--- +# Source: shopsys-app/templates/hooks/job-post-deploy.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: post-deploy + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 0 + template: + spec: + # Runs under the namespace default SA (kept consistent with the migration hook); + # it never talks to the API, so the token is not mounted. + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + volumes: + - name: domains-urls + configMap: + name: domains-urls-hook + containers: + - name: post-deploy + image: "v1.0.0" + securityContext: + allowPrivilegeEscalation: false + command: + - sh + - -c + - | + set -e + cd /var/www/html + ./phing maintenance-off + ./phing clean-redis-old || echo "[FAILED] clean-redis-old" + ./phing clean-redis-storefront || echo "[FAILED] clean-redis-storefront" + if ./phing -l 2>/dev/null | grep -q "build-deploy-part-3-non-blocking"; then + ./phing build-deploy-part-3-non-blocking || echo "[FAILED] build-deploy-part-3-non-blocking" + fi + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + restartPolicy: Never + imagePullSecrets: + - name: dockerregistry + + diff --git a/tests/golden/scenarios/network-policies/expected/first-deploy-with-demo-data.yaml b/tests/golden/scenarios/network-policies/expected/first-deploy-with-demo-data.yaml new file mode 100644 index 0000000..e716d05 --- /dev/null +++ b/tests/golden/scenarios/network-policies/expected/first-deploy-with-demo-data.yaml @@ -0,0 +1,2285 @@ +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-ingress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Ingress + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: + matchLabels: + app: redis + policyTypes: + - Ingress + ingress: + # application pods in this namespace + - from: + - podSelector: {} + ports: + - port: 6379 + # prometheus scraping of the redis exporter + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: observability + ports: + - port: 9121 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: + matchLabels: + app: rabbitmq + policyTypes: + - Ingress + ingress: + # application pods in this namespace + - from: + - podSelector: {} + ports: + - port: 5672 + # management UI through the ingress controller + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + ports: + - port: 15672 + # prometheus scraping of the built-in exporter + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: observability + ports: + - port: 15692 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-acme-solver + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + # cert-manager's HTTP01 solver pods are spawned in this namespace for the ingress + # certificates and must accept the challenge request on 8089, or every issuance and + # renewal fails under the default deny. Deliberately not restricted to the + # ingress-controller namespace: cert-manager's self-check may reach the solver with a + # different source (load-balancer/externalTrafficPolicy specifics), and the solver + # serves nothing but the public challenge token. Selects no pods on DNS01 clusters. + podSelector: + matchLabels: + acme.cert-manager.io/http01-solver: "true" + policyTypes: + - Ingress + ingress: + - ports: + - port: 8089 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-extra-ingress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - ipBlock: + cidr: 192.168.0.0/16 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-egress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Egress + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Egress + egress: + # DNS anywhere: the cluster DNS location differs per cluster (kube-system, node-local + # caches on link-local IPs) and cannot be selected generically. Residual risk: DNS + # tunneling remains a possible exfiltration path under the lockdown. + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + # everything inside this namespace (redis, rabbitmq, webserver, storefront) + - to: + - podSelector: {} + # project-specific external services (PostgreSQL, Elasticsearch, S3, SMTP, the + # Kubernetes API for the cron-suspend hook, ...) + - ports: + - port: 5432 + to: + - ipBlock: + cidr: 10.0.0.100/32 + - ports: + - port: 443 + to: + - ipBlock: + cidr: 10.96.0.1/32 + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +# ServiceAccount used by the deploy hook Jobs of the shopsys-app release. +# Lives in the infra chart so it exists before the first application upgrade runs its hooks. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + +--- +# Source: shopsys-infra/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shopsys-infra + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +automountServiceAccountToken: false + +--- +# Source: shopsys-infra/templates/secret-rabbitmq.yaml +apiVersion: v1 +kind: Secret +metadata: + name: rabbitmq-credentials + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +type: Opaque +stringData: + user: "rabbitmq" + password: "rabbitmq-password" + +--- +# Source: shopsys-infra/templates/configmap-redis-health.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-health-configmap + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +data: + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + +--- +# Source: shopsys-infra/templates/configmap-redis.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +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 + + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +rules: + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "list", "watch", "patch"] + - apiGroups: ["apps"] + resources: ["deployments/scale"] + verbs: ["get", "patch", "update"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: deploy-hooks +subjects: + - kind: ServiceAccount + name: deploy-hooks + +--- +# Source: shopsys-infra/templates/service-rabbitmq.yaml +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + app: rabbitmq + prometheus-exporter: 'true' +spec: + clusterIP: None + selector: + app: rabbitmq + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + +--- +# Source: shopsys-infra/templates/service-redis.yaml +apiVersion: v1 +kind: Service +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + app: redis + prometheus-exporter: 'true' +spec: + selector: + app: redis + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + +--- +# Source: shopsys-infra/templates/deployment-redis.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: "redis" + project/environment: "production" + project/name: "myproject" + checksum/redis-config: 81f63d0ecd9fa1305f6c4157058b05bd93792e15148d17fc4e542b9c24e33635 + labels: + app: redis + spec: + serviceAccountName: shopsys-infra + automountServiceAccountToken: false + securityContext: + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + volumes: + - name: health + configMap: + name: redis-health-configmap + defaultMode: 0755 + - name: config + configMap: + name: redis + defaultMode: 0755 + containers: + - name: redis + image: "redis:7.4-alpine" + ports: + - name: redis + containerPort: 6379 + protocol: TCP + volumeMounts: + - name: health + mountPath: /health + - name: config + mountPath: /usr/local/etc/redis/redis.conf + subPath: redis.conf + args: + - /usr/local/etc/redis/redis.conf + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + livenessProbe: + initialDelaySeconds: 30 + timeoutSeconds: 5 + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + readinessProbe: + initialDelaySeconds: 5 + timeoutSeconds: 5 + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + - name: redis-exporter + image: "oliver006/redis_exporter:v1.89.0" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + ports: + - name: exporter + containerPort: 9121 + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + +--- +# Source: shopsys-infra/templates/statefulset-rabbitmq.yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + serviceName: rabbitmq + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + serviceAccountName: shopsys-infra + automountServiceAccountToken: false + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + containers: + - name: rabbitmq + image: "rabbitmq:4.1-management-alpine" + ports: + - name: rabbitmq + containerPort: 15672 + protocol: TCP + - name: exporter + containerPort: 15692 + protocol: TCP + env: + - name: RABBITMQ_DEFAULT_USER + valueFrom: + secretKeyRef: + name: rabbitmq-credentials + key: user + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: rabbitmq-credentials + key: password + resources: + requests: + cpu: 20m + + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: rabbitmq-data + mountPath: /var/lib/rabbitmq + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + storageClassName: nfs-client + resources: + requests: + storage: 1Gi + +--- +# Source: shopsys-infra/templates/ingress-rabbitmq.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: rabbitmq-domain + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8" +spec: + ingressClassName: nginx + tls: + - hosts: + - "rabbitmq.www.example.com" + secretName: tls-certificate + rules: + - host: "rabbitmq.www.example.com" + http: + paths: + - backend: + service: + name: rabbitmq + port: + number: 15672 + path: '/' + pathType: Prefix + +--- +# Source: shopsys-app/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-webserver + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + podSelector: + matchLabels: + app: webserver-php-fpm + policyTypes: + - Ingress + ingress: + - from: + # e-shop and MCP ingresses + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + # storefront server-side requests (INTERNAL_ENDPOINT) and other app pods + - podSelector: {} + ports: + - port: 8080 + +--- +# Source: shopsys-app/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + podSelector: + matchLabels: + app: storefront + policyTypes: + - Ingress + ingress: + - from: + # the webserver's nginx proxies /_next/ and @storefront to the storefront; no + # shipped ingress targets the storefront directly (use networkPolicy.extraIngress + # when a project adds one) + - podSelector: {} + ports: + - port: 3000 + +--- +# Source: shopsys-app/templates/pdb-storefront.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: storefront +spec: + minAvailable: 1 + selector: + matchLabels: + app: storefront + +--- +# Source: shopsys-app/templates/pdb-webserver.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: webserver-php-fpm +spec: + minAvailable: 1 + selector: + matchLabels: + app: webserver-php-fpm + +--- +# Source: shopsys-app/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shopsys-app + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +automountServiceAccountToken: false + +--- +# Source: shopsys-app/templates/secret-app-env.yaml +apiVersion: v1 +kind: Secret +metadata: + name: app-secret-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: Opaque +stringData: + APP_SECRET: "test-app-secret-key" + DATABASE_PASSWORD: "test-db-password" + S3_SECRET: "test-s3-secret" + +--- +# Source: shopsys-app/templates/secret-app-env.yaml +apiVersion: v1 +kind: Secret +metadata: + name: cron-secret-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: Opaque +stringData: + .project_secret_env.sh: | + + export APP_SECRET='test-app-secret-key' + export DATABASE_PASSWORD='test-db-password' + export S3_SECRET='test-s3-secret' + +--- +# Source: shopsys-app/templates/secret-dockerregistry.yaml +apiVersion: v1 +kind: Secret +metadata: + name: dockerregistry + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJhdXRoIjoiWkdWd2JHOTVMWFZ6WlhJNlpHVndiRzk1TFhCaGMzTjNiM0prIiwiZW1haWwiOiIiLCJwYXNzd29yZCI6ImRlcGxveS1wYXNzd29yZCIsInVzZXJuYW1lIjoiZGVwbG95LXVzZXIifX19 + +--- +# Source: shopsys-app/templates/configmap-cron-env.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cron-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + .project_env.sh: |+ + + export DATABASE_HOST='10.0.0.100' + export DATABASE_NAME='myproject-production' + export DATABASE_PORT='5432' + export DATABASE_USER='myproject-production' + export ELASTICSEARCH_HOST='http://elasticsearch:9200' + export ELASTIC_SEARCH_INDEX_PREFIX='myproject-production' + export MAILER_DSN='smtp://mailhog:1025' + export MAILER_FORCE_WHITELIST='false' + export MESSENGER_TRANSPORT_DSN='amqp://guest:guest@rabbitmq:5672/%2f/messages' + export REDIS_PREFIX='myproject-production' + export S3_ACCESS_KEY='myproject-production' + export S3_BUCKET_NAME='myproject-production' + export S3_ENDPOINT='https://s3.example.com' + export TRUSTED_PROXY='10.0.0.0/8' + +--- +# Source: shopsys-app/templates/configmap-cron-list.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cron-list + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + cron: |+ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + */5 * * * * . /root/.project_env.sh && . /root/.project_secret_env.sh && cd /var/www/html/ && ./phing cron > /dev/null 2>&1 + + +--- +# Source: shopsys-app/templates/configmap-domains-urls.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: domains-urls + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.sk + +--- +# Source: shopsys-app/templates/configmap-nginx.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-default-config + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + nginx.conf: | + # No `user` directive - nginx runs as the unprivileged nginx user (runAsUser 101) + # enforced by the container securityContext; the pid file lives on the writable /tmp + # emptyDir because the root filesystem is read-only. + worker_processes 2; + + error_log /dev/stderr warn; + pid /tmp/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 { + # Unprivileged health port - nginx runs as a non-root user and cannot bind below 1024 + listen 8081; + 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-production/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-production/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"; + } + } + + +--- +# Source: shopsys-app/templates/configmap-php-fpm.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: production-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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 + + +--- +# Source: shopsys-app/templates/configmap-php-opcache.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: production-php-opcache + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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" + + +--- +# Source: shopsys-app/templates/service-storefront.yaml +apiVersion: v1 +kind: Service +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + app: storefront + ports: + - name: storefront + port: 3000 + targetPort: 3000 + +--- +# Source: shopsys-app/templates/service-webserver-php-fpm.yaml +apiVersion: v1 +kind: Service +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + app: webserver-php-fpm + ports: + - name: http + port: 8080 + targetPort: 8080 + +--- +# Source: shopsys-app/templates/deployment-consumer.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: consumer-email + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: consumer-email +spec: + progressDeadlineSeconds: 600 + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: consumer-email + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "email" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/app-env: 89cad8f3a6a12379c4f642ee72913bf76aed6394870d016ccdacf109842b7740 + labels: + app: consumer-email + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + volumes: + - name: domains-urls + configMap: + name: domains-urls + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + terminationGracePeriodSeconds: 300 + containers: + - image: "v1.0.0" + name: consumer-email + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + command: ["/bin/sh", "-c"] + args: + - | + PIPE=/tmp/log-pipe + rm -rf $PIPE + mkfifo $PIPE + chmod 666 $PIPE + stdbuf -o0 tail -n +1 -f $PIPE & + + sleep 5 + + while [ ! -f /tmp/stop_consumer ]; do + php /var/www/html/bin/console messenger:consume email_transport --time-limit=300 --quiet + sleep 2 + done + lifecycle: + preStop: + exec: + command: ["/bin/sh", "-c", "touch /tmp/stop_consumer && php bin/console messenger:stop-workers"] + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + resources: + limits: + memory: 1Gi + requests: + cpu: 50m + memory: 300Mi + + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + +--- +# Source: shopsys-app/templates/deployment-cron.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cron + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: cron +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: cron + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "cron" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/cron: 040ad8eae03d513195f2a4d0070b0ec87393486bb59132754e9178f1812274d6 + labels: + app: cron + # Forces a fresh cron pod on every deploy (legacy `date` label) + date: "1234567890" + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + imagePullSecrets: + - name: dockerregistry + volumes: + - name: domains-urls + configMap: + name: domains-urls + - name: cron-list + configMap: + name: cron-list + - name: cron-env + configMap: + name: cron-env + - name: cron-secret-env + secret: + secretName: cron-secret-env + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + containers: + - image: "v1.0.0" + name: cron + securityContext: + runAsUser: 0 + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + command: ["/bin/sh","-c"] + 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"] + # The pod drains itself before termination: cron-lock prevents the next + # cron iteration, cron-watch waits until all running instances finish. + lifecycle: + preStop: + exec: + command: + - /bin/sh + - '-c' + - "cd /var/www/html && (./phing -S cron-lock > /dev/null 2>&1 &) && ./phing -S cron-watch" + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + resources: + limits: + memory: 1Gi + requests: + cpu: 100m + memory: 300Mi + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: cron-list + mountPath: /var/spool/cron/template + subPath: cron + - name: cron-env + mountPath: /root/.project_env.sh + subPath: .project_env.sh + - name: cron-secret-env + mountPath: /root/.project_secret_env.sh + subPath: .project_secret_env.sh + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + terminationGracePeriodSeconds: 3600 + +--- +# Source: shopsys-app/templates/deployment-storefront.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + matchLabels: + app: storefront + template: + metadata: + annotations: + logging/enabled: "false" + project/app: "storefront" + project/environment: "production" + project/name: "myproject" + labels: + app: storefront + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - storefront + topologyKey: kubernetes.io/hostname + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + containers: + - name: storefront + image: "v1.0.0" + ports: + - name: storefront + containerPort: 3000 + protocol: TCP + 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.sk/" + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_2 + value: "https://www.example.sk/graphql/" + - name: INTERNAL_ENDPOINT + value: "http://webserver-php-fpm:8080/" + lifecycle: + preStop: + exec: + command: + - sleep + - "10" + resources: + limits: + memory: 1.5Gi + requests: + cpu: 500m + memory: 800Mi + + securityContext: + allowPrivilegeEscalation: false + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + terminationGracePeriodSeconds: 60 + +--- +# Source: shopsys-app/templates/deployment-webserver-php-fpm.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: webserver-php-fpm +spec: + progressDeadlineSeconds: 1500 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: webserver-php-fpm + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "app" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/app-env: 89cad8f3a6a12379c4f642ee72913bf76aed6394870d016ccdacf109842b7740 + checksum/nginx: c44e553821bd4e376a288bfa0e75abd97e7655d0abee6bfe203340662ed81777 + checksum/php-fpm: 593102a5f1a5dc7c7a19ee1aae7bd32f6ee7011a0ed0ea732f25f8b2a8251ca7 + labels: + app: webserver-php-fpm + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + 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 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + hostAliases: + - ip: "127.0.0.1" + hostnames: + - "webserver-php-fpm" + - "php-fpm" + - "webserver" + volumes: + - name: source-codes + emptyDir: {} + - name: domains-urls + configMap: + name: domains-urls + - name: nginx-default-config + configMap: + name: nginx-default-config + - name: production-php-fpm + configMap: + name: production-php-fpm + - name: production-php-opcache + configMap: + name: production-php-opcache + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + # Writable paths for the read-only nginx root filesystem (temp dirs + pid file) + - name: nginx-cache + emptyDir: {} + - name: nginx-tmp + emptyDir: {} + initContainers: + - name: copy-source-codes-to-volume + image: "v1.0.0" + command: ["sh", "-c", "cp -r -n /var/www/html/. /tmp/source-codes"] + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: source-codes + mountPath: /tmp/source-codes + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + containers: + - image: "v1.0.0" + name: php-fpm + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + securityContext: + allowPrivilegeEscalation: false + lifecycle: + postStart: + exec: + command: ["/var/www/html/phing", "-S", "warmup"] + preStop: + exec: + command: + - sh + - '-c' + - sleep 10 && kill -SIGQUIT 1 + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: source-codes + mountPath: /var/www/html + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: production-php-fpm + mountPath: /usr/local/etc/php-fpm.d/www.conf + subPath: www.conf + - name: production-php-opcache + mountPath: /usr/local/etc/php/conf.d/php-opcache.ini + subPath: php-opcache.ini + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + resources: + limits: + memory: 2Gi + requests: + cpu: 500m + memory: 500Mi + + - image: "nginx:1.29-alpine" + name: webserver + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsGroup: 101 + runAsNonRoot: true + runAsUser: 101 + ports: + - containerPort: 8080 + name: http + - containerPort: 8081 + name: health + livenessProbe: + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + httpGet: + path: /health + port: 8081 + readinessProbe: + httpGet: + path: /health + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + volumeMounts: + - name: nginx-default-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + - name: nginx-default-config + mountPath: /etc/nginx/conf.d/default.conf + subPath: project-nginx.conf + - name: source-codes + mountPath: /var/www/html + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-tmp + mountPath: /tmp + lifecycle: + preStop: + exec: + command: [ + 'sh', '-c', + 'sleep 5 && /usr/sbin/nginx -s quit' + ] + resources: + limits: + memory: 300Mi + requests: + cpu: 50m + memory: 100Mi + terminationGracePeriodSeconds: 120 + +--- +# Source: shopsys-app/templates/hpa-storefront.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: storefront + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 120 + +--- +# Source: shopsys-app/templates/hpa-webserver.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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 + +--- +# Source: shopsys-app/templates/ingress-domains.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-domain-0 + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + 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; }" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.com" + - "example.com" + secretName: tls-www-example-com + rules: + - host: "www.example.com" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: "example.com" + +--- +# Source: shopsys-app/templates/ingress-domains.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-domain-1 + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + 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; }" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.sk" + - "example.sk" + secretName: tls-www-example-sk + rules: + - host: "www.example.sk" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: "example.sk" + +--- +# Source: shopsys-app/templates/ingress-mcp.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-mcp + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: "203.0.113.0/24,198.51.100.10/32" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.com" + secretName: tls-www-example-com + rules: + - host: "www.example.com" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/_mcp' + pathType: Prefix + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/mcp/oauth' + pathType: Prefix + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/.well-known/oauth-authorization-server' + pathType: ImplementationSpecific + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/.well-known/oauth-protected-resource' + pathType: ImplementationSpecific +--- +# Source: shopsys-app/templates/hooks/secret-app-env-hook.yaml +apiVersion: v1 +kind: Secret +metadata: + name: app-secret-env-hook + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" + helm.sh/hook-delete-policy: before-hook-creation +type: Opaque +stringData: + APP_SECRET: "test-app-secret-key" + DATABASE_PASSWORD: "test-db-password" + S3_SECRET: "test-s3-secret" + +--- +# Source: shopsys-app/templates/hooks/configmap-domains-urls-hook.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: domains-urls-hook + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" + helm.sh/hook-delete-policy: before-hook-creation +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.sk + +--- +# Source: shopsys-app/templates/hooks/job-cron-suspend.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: cron-suspend + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backoffLimit: 0 + template: + spec: + serviceAccountName: deploy-hooks + restartPolicy: Never + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: cron-suspend + image: "rancher/kubectl:v1.33.13" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + command: + - /bin/sh + - -c + - | + kubectl scale deployment/cron --replicas=0 --namespace=myproject-production 2>/dev/null || true + kubectl wait --for=delete pod -l app=cron --namespace=myproject-production --timeout=3660s || true + +--- +# Source: shopsys-app/templates/hooks/job-migrate-application.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "10" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 0 + template: + spec: + # Runs under the namespace default SA (as a pre-install hook it cannot reference the + # chart ServiceAccount, which does not exist yet); it never talks to the API, so the + # token is not mounted. + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + volumes: + - name: domains-urls + configMap: + name: domains-urls-hook + containers: + - name: migrate-application + image: "v1.0.0" + 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"] + securityContext: + allowPrivilegeEscalation: false + envFrom: + - secretRef: + name: app-secret-env-hook + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + restartPolicy: Never + imagePullSecrets: + - name: dockerregistry + +--- +# Source: shopsys-app/templates/hooks/job-post-deploy.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: post-deploy + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 0 + template: + spec: + # Runs under the namespace default SA (kept consistent with the migration hook); + # it never talks to the API, so the token is not mounted. + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + volumes: + - name: domains-urls + configMap: + name: domains-urls-hook + containers: + - name: post-deploy + image: "v1.0.0" + securityContext: + allowPrivilegeEscalation: false + command: + - sh + - -c + - | + set -e + cd /var/www/html + ./phing maintenance-off + ./phing clean-redis-old || echo "[FAILED] clean-redis-old" + ./phing clean-redis-storefront || echo "[FAILED] clean-redis-storefront" + if ./phing -l 2>/dev/null | grep -q "build-deploy-part-3-non-blocking"; then + ./phing build-deploy-part-3-non-blocking || echo "[FAILED] build-deploy-part-3-non-blocking" + fi + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + restartPolicy: Never + imagePullSecrets: + - name: dockerregistry + + diff --git a/tests/golden/scenarios/network-policies/expected/first-deploy.yaml b/tests/golden/scenarios/network-policies/expected/first-deploy.yaml new file mode 100644 index 0000000..ded595b --- /dev/null +++ b/tests/golden/scenarios/network-policies/expected/first-deploy.yaml @@ -0,0 +1,2285 @@ +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-ingress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Ingress + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: + matchLabels: + app: redis + policyTypes: + - Ingress + ingress: + # application pods in this namespace + - from: + - podSelector: {} + ports: + - port: 6379 + # prometheus scraping of the redis exporter + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: observability + ports: + - port: 9121 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: + matchLabels: + app: rabbitmq + policyTypes: + - Ingress + ingress: + # application pods in this namespace + - from: + - podSelector: {} + ports: + - port: 5672 + # management UI through the ingress controller + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + ports: + - port: 15672 + # prometheus scraping of the built-in exporter + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: observability + ports: + - port: 15692 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-acme-solver + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + # cert-manager's HTTP01 solver pods are spawned in this namespace for the ingress + # certificates and must accept the challenge request on 8089, or every issuance and + # renewal fails under the default deny. Deliberately not restricted to the + # ingress-controller namespace: cert-manager's self-check may reach the solver with a + # different source (load-balancer/externalTrafficPolicy specifics), and the solver + # serves nothing but the public challenge token. Selects no pods on DNS01 clusters. + podSelector: + matchLabels: + acme.cert-manager.io/http01-solver: "true" + policyTypes: + - Ingress + ingress: + - ports: + - port: 8089 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-extra-ingress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - ipBlock: + cidr: 192.168.0.0/16 + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-egress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Egress + +--- +# Source: shopsys-infra/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-egress + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + podSelector: {} + policyTypes: + - Egress + egress: + # DNS anywhere: the cluster DNS location differs per cluster (kube-system, node-local + # caches on link-local IPs) and cannot be selected generically. Residual risk: DNS + # tunneling remains a possible exfiltration path under the lockdown. + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + # everything inside this namespace (redis, rabbitmq, webserver, storefront) + - to: + - podSelector: {} + # project-specific external services (PostgreSQL, Elasticsearch, S3, SMTP, the + # Kubernetes API for the cron-suspend hook, ...) + - ports: + - port: 5432 + to: + - ipBlock: + cidr: 10.0.0.100/32 + - ports: + - port: 443 + to: + - ipBlock: + cidr: 10.96.0.1/32 + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +# ServiceAccount used by the deploy hook Jobs of the shopsys-app release. +# Lives in the infra chart so it exists before the first application upgrade runs its hooks. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + +--- +# Source: shopsys-infra/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shopsys-infra + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +automountServiceAccountToken: false + +--- +# Source: shopsys-infra/templates/secret-rabbitmq.yaml +apiVersion: v1 +kind: Secret +metadata: + name: rabbitmq-credentials + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +type: Opaque +stringData: + user: "rabbitmq" + password: "rabbitmq-password" + +--- +# Source: shopsys-infra/templates/configmap-redis-health.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-health-configmap + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +data: + readiness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ]; then + echo "$response" + exit 1 + fi + liveness.sh: |- + response=$( redis-cli ping ) + if [ "$response" != "PONG" ] && [ "$response" != "LOADING Redis is loading the dataset in memory" ]; then + echo "$response" + exit 1 + fi + +--- +# Source: shopsys-infra/templates/configmap-redis.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +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 + + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +rules: + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "list", "watch", "patch"] + - apiGroups: ["apps"] + resources: ["deployments/scale"] + verbs: ["get", "patch", "update"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] + +--- +# Source: shopsys-infra/templates/rbac-deploy-hooks.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: deploy-hooks + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: deploy-hooks +subjects: + - kind: ServiceAccount + name: deploy-hooks + +--- +# Source: shopsys-infra/templates/service-rabbitmq.yaml +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + app: rabbitmq + prometheus-exporter: 'true' +spec: + clusterIP: None + selector: + app: rabbitmq + ports: + - name: rabbitmq + port: 5672 + targetPort: 5672 + - name: rabbitmq-management + port: 15672 + targetPort: 15672 + - name: prometheus-exporter + port: 15692 + targetPort: 15692 + +--- +# Source: shopsys-infra/templates/service-redis.yaml +apiVersion: v1 +kind: Service +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + app: redis + prometheus-exporter: 'true' +spec: + selector: + app: redis + ports: + - name: redis + port: 6379 + targetPort: 6379 + - name: prometheus-exporter + port: 9121 + targetPort: 9121 + +--- +# Source: shopsys-infra/templates/deployment-redis.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + annotations: + logging/enabled: "false" + project/app: "redis" + project/environment: "production" + project/name: "myproject" + checksum/redis-config: 81f63d0ecd9fa1305f6c4157058b05bd93792e15148d17fc4e542b9c24e33635 + labels: + app: redis + spec: + serviceAccountName: shopsys-infra + automountServiceAccountToken: false + securityContext: + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 999 + seccompProfile: + type: RuntimeDefault + volumes: + - name: health + configMap: + name: redis-health-configmap + defaultMode: 0755 + - name: config + configMap: + name: redis + defaultMode: 0755 + containers: + - name: redis + image: "redis:7.4-alpine" + ports: + - name: redis + containerPort: 6379 + protocol: TCP + volumeMounts: + - name: health + mountPath: /health + - name: config + mountPath: /usr/local/etc/redis/redis.conf + subPath: redis.conf + args: + - /usr/local/etc/redis/redis.conf + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + livenessProbe: + initialDelaySeconds: 30 + timeoutSeconds: 5 + exec: + command: + - sh + - -c + - /health/liveness.sh 5 + readinessProbe: + initialDelaySeconds: 5 + timeoutSeconds: 5 + exec: + command: + - sh + - -c + - /health/readiness.sh 5 + resources: + limits: + memory: 2500Mi + requests: + cpu: 100m + memory: 2500Mi + - name: redis-exporter + image: "oliver006/redis_exporter:v1.89.0" + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + ports: + - name: exporter + containerPort: 9121 + protocol: TCP + resources: + limits: + memory: 128Mi + requests: + cpu: 10m + memory: 128Mi + +--- +# Source: shopsys-infra/templates/statefulset-rabbitmq.yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: rabbitmq + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 +spec: + serviceName: rabbitmq + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + serviceAccountName: shopsys-infra + automountServiceAccountToken: false + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - rabbitmq + topologyKey: kubernetes.io/hostname + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + containers: + - name: rabbitmq + image: "rabbitmq:4.1-management-alpine" + ports: + - name: rabbitmq + containerPort: 15672 + protocol: TCP + - name: exporter + containerPort: 15692 + protocol: TCP + env: + - name: RABBITMQ_DEFAULT_USER + valueFrom: + secretKeyRef: + name: rabbitmq-credentials + key: user + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: rabbitmq-credentials + key: password + resources: + requests: + cpu: 20m + + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: rabbitmq-data + mountPath: /var/lib/rabbitmq + volumeClaimTemplates: + - metadata: + name: rabbitmq-data + spec: + accessModes: + - ReadWriteOnce + storageClassName: nfs-client + resources: + requests: + storage: 1Gi + +--- +# Source: shopsys-infra/templates/ingress-rabbitmq.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: rabbitmq-domain + labels: + app.kubernetes.io/name: shopsys-infra + app.kubernetes.io/instance: shopsys-infra + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-infra-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8" +spec: + ingressClassName: nginx + tls: + - hosts: + - "rabbitmq.www.example.com" + secretName: tls-certificate + rules: + - host: "rabbitmq.www.example.com" + http: + paths: + - backend: + service: + name: rabbitmq + port: + number: 15672 + path: '/' + pathType: Prefix + +--- +# Source: shopsys-app/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-webserver + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + podSelector: + matchLabels: + app: webserver-php-fpm + policyTypes: + - Ingress + ingress: + - from: + # e-shop and MCP ingresses + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + # storefront server-side requests (INTERNAL_ENDPOINT) and other app pods + - podSelector: {} + ports: + - port: 8080 + +--- +# Source: shopsys-app/templates/networkpolicy.yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + podSelector: + matchLabels: + app: storefront + policyTypes: + - Ingress + ingress: + - from: + # the webserver's nginx proxies /_next/ and @storefront to the storefront; no + # shipped ingress targets the storefront directly (use networkPolicy.extraIngress + # when a project adds one) + - podSelector: {} + ports: + - port: 3000 + +--- +# Source: shopsys-app/templates/pdb-storefront.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: storefront +spec: + minAvailable: 1 + selector: + matchLabels: + app: storefront + +--- +# Source: shopsys-app/templates/pdb-webserver.yaml +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: webserver-php-fpm +spec: + minAvailable: 1 + selector: + matchLabels: + app: webserver-php-fpm + +--- +# Source: shopsys-app/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: shopsys-app + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +automountServiceAccountToken: false + +--- +# Source: shopsys-app/templates/secret-app-env.yaml +apiVersion: v1 +kind: Secret +metadata: + name: app-secret-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: Opaque +stringData: + APP_SECRET: "test-app-secret-key" + DATABASE_PASSWORD: "test-db-password" + S3_SECRET: "test-s3-secret" + +--- +# Source: shopsys-app/templates/secret-app-env.yaml +apiVersion: v1 +kind: Secret +metadata: + name: cron-secret-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: Opaque +stringData: + .project_secret_env.sh: | + + export APP_SECRET='test-app-secret-key' + export DATABASE_PASSWORD='test-db-password' + export S3_SECRET='test-s3-secret' + +--- +# Source: shopsys-app/templates/secret-dockerregistry.yaml +apiVersion: v1 +kind: Secret +metadata: + name: dockerregistry + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: eyJhdXRocyI6eyJyZWdpc3RyeS5leGFtcGxlLmNvbSI6eyJhdXRoIjoiWkdWd2JHOTVMWFZ6WlhJNlpHVndiRzk1TFhCaGMzTjNiM0prIiwiZW1haWwiOiIiLCJwYXNzd29yZCI6ImRlcGxveS1wYXNzd29yZCIsInVzZXJuYW1lIjoiZGVwbG95LXVzZXIifX19 + +--- +# Source: shopsys-app/templates/configmap-cron-env.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cron-env + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + .project_env.sh: |+ + + export DATABASE_HOST='10.0.0.100' + export DATABASE_NAME='myproject-production' + export DATABASE_PORT='5432' + export DATABASE_USER='myproject-production' + export ELASTICSEARCH_HOST='http://elasticsearch:9200' + export ELASTIC_SEARCH_INDEX_PREFIX='myproject-production' + export MAILER_DSN='smtp://mailhog:1025' + export MAILER_FORCE_WHITELIST='false' + export MESSENGER_TRANSPORT_DSN='amqp://guest:guest@rabbitmq:5672/%2f/messages' + export REDIS_PREFIX='myproject-production' + export S3_ACCESS_KEY='myproject-production' + export S3_BUCKET_NAME='myproject-production' + export S3_ENDPOINT='https://s3.example.com' + export TRUSTED_PROXY='10.0.0.0/8' + +--- +# Source: shopsys-app/templates/configmap-cron-list.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cron-list + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + cron: |+ + PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + */5 * * * * . /root/.project_env.sh && . /root/.project_secret_env.sh && cd /var/www/html/ && ./phing cron > /dev/null 2>&1 + + +--- +# Source: shopsys-app/templates/configmap-domains-urls.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: domains-urls + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.sk + +--- +# Source: shopsys-app/templates/configmap-nginx.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-default-config + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +data: + nginx.conf: | + # No `user` directive - nginx runs as the unprivileged nginx user (runAsUser 101) + # enforced by the container securityContext; the pid file lives on the writable /tmp + # emptyDir because the root filesystem is read-only. + worker_processes 2; + + error_log /dev/stderr warn; + pid /tmp/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 { + # Unprivileged health port - nginx runs as a non-root user and cannot bind below 1024 + listen 8081; + 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-production/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-production/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"; + } + } + + +--- +# Source: shopsys-app/templates/configmap-php-fpm.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: production-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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 + + +--- +# Source: shopsys-app/templates/configmap-php-opcache.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: production-php-opcache + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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" + + +--- +# Source: shopsys-app/templates/service-storefront.yaml +apiVersion: v1 +kind: Service +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + app: storefront + ports: + - name: storefront + port: 3000 + targetPort: 3000 + +--- +# Source: shopsys-app/templates/service-webserver-php-fpm.yaml +apiVersion: v1 +kind: Service +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + app: webserver-php-fpm + ports: + - name: http + port: 8080 + targetPort: 8080 + +--- +# Source: shopsys-app/templates/deployment-consumer.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: consumer-email + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: consumer-email +spec: + progressDeadlineSeconds: 600 + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: consumer-email + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "email" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/app-env: 89cad8f3a6a12379c4f642ee72913bf76aed6394870d016ccdacf109842b7740 + labels: + app: consumer-email + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + volumes: + - name: domains-urls + configMap: + name: domains-urls + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + terminationGracePeriodSeconds: 300 + containers: + - image: "v1.0.0" + name: consumer-email + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + command: ["/bin/sh", "-c"] + args: + - | + PIPE=/tmp/log-pipe + rm -rf $PIPE + mkfifo $PIPE + chmod 666 $PIPE + stdbuf -o0 tail -n +1 -f $PIPE & + + sleep 5 + + while [ ! -f /tmp/stop_consumer ]; do + php /var/www/html/bin/console messenger:consume email_transport --time-limit=300 --quiet + sleep 2 + done + lifecycle: + preStop: + exec: + command: ["/bin/sh", "-c", "touch /tmp/stop_consumer && php bin/console messenger:stop-workers"] + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + resources: + limits: + memory: 1Gi + requests: + cpu: 50m + memory: 300Mi + + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + +--- +# Source: shopsys-app/templates/deployment-cron.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cron + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: cron +spec: + progressDeadlineSeconds: 1500 + replicas: 1 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: cron + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "cron" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/cron: 040ad8eae03d513195f2a4d0070b0ec87393486bb59132754e9178f1812274d6 + labels: + app: cron + # Forces a fresh cron pod on every deploy (legacy `date` label) + date: "1234567890" + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + tolerations: + - effect: NoSchedule + key: workload + operator: Equal + value: background + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: workload + operator: In + values: + - background + weight: 100 + imagePullSecrets: + - name: dockerregistry + volumes: + - name: domains-urls + configMap: + name: domains-urls + - name: cron-list + configMap: + name: cron-list + - name: cron-env + configMap: + name: cron-env + - name: cron-secret-env + secret: + secretName: cron-secret-env + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + containers: + - image: "v1.0.0" + name: cron + securityContext: + runAsUser: 0 + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + command: ["/bin/sh","-c"] + 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"] + # The pod drains itself before termination: cron-lock prevents the next + # cron iteration, cron-watch waits until all running instances finish. + lifecycle: + preStop: + exec: + command: + - /bin/sh + - '-c' + - "cd /var/www/html && (./phing -S cron-lock > /dev/null 2>&1 &) && ./phing -S cron-watch" + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + resources: + limits: + memory: 1Gi + requests: + cpu: 100m + memory: 300Mi + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: cron-list + mountPath: /var/spool/cron/template + subPath: cron + - name: cron-env + mountPath: /root/.project_env.sh + subPath: .project_env.sh + - name: cron-secret-env + mountPath: /root/.project_secret_env.sh + subPath: .project_secret_env.sh + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + terminationGracePeriodSeconds: 3600 + +--- +# Source: shopsys-app/templates/deployment-storefront.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + selector: + matchLabels: + app: storefront + template: + metadata: + annotations: + logging/enabled: "false" + project/app: "storefront" + project/environment: "production" + project/name: "myproject" + labels: + app: storefront + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - storefront + topologyKey: kubernetes.io/hostname + weight: 100 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + containers: + - name: storefront + image: "v1.0.0" + ports: + - name: storefront + containerPort: 3000 + protocol: TCP + 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.sk/" + - name: PUBLIC_GRAPHQL_ENDPOINT_HOSTNAME_2 + value: "https://www.example.sk/graphql/" + - name: INTERNAL_ENDPOINT + value: "http://webserver-php-fpm:8080/" + lifecycle: + preStop: + exec: + command: + - sleep + - "10" + resources: + limits: + memory: 1.5Gi + requests: + cpu: 500m + memory: 800Mi + + securityContext: + allowPrivilegeEscalation: false + livenessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + terminationGracePeriodSeconds: 60 + +--- +# Source: shopsys-app/templates/deployment-webserver-php-fpm.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + app: webserver-php-fpm +spec: + progressDeadlineSeconds: 1500 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + selector: + matchLabels: + app: webserver-php-fpm + template: + metadata: + annotations: + logging/enabled: "true" + project/app: "app" + project/environment: "production" + project/name: "myproject" + + checksum/domains-urls: 6e0bbf6b4dbff26e8393ed633d2fd5487eb046effb22d6d7c851910aa5628ca7 + checksum/app-env: 89cad8f3a6a12379c4f642ee72913bf76aed6394870d016ccdacf109842b7740 + checksum/nginx: c44e553821bd4e376a288bfa0e75abd97e7655d0abee6bfe203340662ed81777 + checksum/php-fpm: 593102a5f1a5dc7c7a19ee1aae7bd32f6ee7011a0ed0ea732f25f8b2a8251ca7 + labels: + app: webserver-php-fpm + spec: + serviceAccountName: shopsys-app + automountServiceAccountToken: false + 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 + securityContext: + seccompProfile: + type: RuntimeDefault + imagePullSecrets: + - name: dockerregistry + hostAliases: + - ip: "127.0.0.1" + hostnames: + - "webserver-php-fpm" + - "php-fpm" + - "webserver" + volumes: + - name: source-codes + emptyDir: {} + - name: domains-urls + configMap: + name: domains-urls + - name: nginx-default-config + configMap: + name: nginx-default-config + - name: production-php-fpm + configMap: + name: production-php-fpm + - name: production-php-opcache + configMap: + name: production-php-opcache + - name: fe-api-keys-volume + secret: + secretName: fe-api-keys + defaultMode: 0644 + # Writable paths for the read-only nginx root filesystem (temp dirs + pid file) + - name: nginx-cache + emptyDir: {} + - name: nginx-tmp + emptyDir: {} + initContainers: + - name: copy-source-codes-to-volume + image: "v1.0.0" + command: ["sh", "-c", "cp -r -n /var/www/html/. /tmp/source-codes"] + securityContext: + allowPrivilegeEscalation: false + volumeMounts: + - name: source-codes + mountPath: /tmp/source-codes + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + containers: + - image: "v1.0.0" + name: php-fpm + imagePullPolicy: IfNotPresent + workingDir: /var/www/html + securityContext: + allowPrivilegeEscalation: false + lifecycle: + postStart: + exec: + command: ["/var/www/html/phing", "-S", "warmup"] + preStop: + exec: + command: + - sh + - '-c' + - sleep 10 && kill -SIGQUIT 1 + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: source-codes + mountPath: /var/www/html + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + - name: production-php-fpm + mountPath: /usr/local/etc/php-fpm.d/www.conf + subPath: www.conf + - name: production-php-opcache + mountPath: /usr/local/etc/php/conf.d/php-opcache.ini + subPath: php-opcache.ini + - name: fe-api-keys-volume + readOnly: true + mountPath: /var/www/html/config/frontend-api + resources: + limits: + memory: 2Gi + requests: + cpu: 500m + memory: 500Mi + + - image: "nginx:1.29-alpine" + name: webserver + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsGroup: 101 + runAsNonRoot: true + runAsUser: 101 + ports: + - containerPort: 8080 + name: http + - containerPort: 8081 + name: health + livenessProbe: + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 5 + httpGet: + path: /health + port: 8081 + readinessProbe: + httpGet: + path: /health + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + volumeMounts: + - name: nginx-default-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + - name: nginx-default-config + mountPath: /etc/nginx/conf.d/default.conf + subPath: project-nginx.conf + - name: source-codes + mountPath: /var/www/html + - name: nginx-cache + mountPath: /var/cache/nginx + - name: nginx-tmp + mountPath: /tmp + lifecycle: + preStop: + exec: + command: [ + 'sh', '-c', + 'sleep 5 && /usr/sbin/nginx -s quit' + ] + resources: + limits: + memory: 300Mi + requests: + cpu: 50m + memory: 100Mi + terminationGracePeriodSeconds: 120 + +--- +# Source: shopsys-app/templates/hpa-storefront.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: storefront + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: storefront + minReplicas: 2 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 120 + +--- +# Source: shopsys-app/templates/hpa-webserver.yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: webserver-php-fpm + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 +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 + +--- +# Source: shopsys-app/templates/ingress-domains.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-domain-0 + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + 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; }" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.com" + - "example.com" + secretName: tls-www-example-com + rules: + - host: "www.example.com" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: "example.com" + +--- +# Source: shopsys-app/templates/ingress-domains.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-domain-1 + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/proxy-body-size: 32m + 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; }" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.sk" + - "example.sk" + secretName: tls-www-example-sk + rules: + - host: "www.example.sk" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: / + pathType: Prefix + - host: "example.sk" + +--- +# Source: shopsys-app/templates/ingress-mcp.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: eshop-mcp + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 32m + nginx.ingress.kubernetes.io/whitelist-source-range: "203.0.113.0/24,198.51.100.10/32" +spec: + ingressClassName: nginx + tls: + - hosts: + - "www.example.com" + secretName: tls-www-example-com + rules: + - host: "www.example.com" + http: + paths: + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/_mcp' + pathType: Prefix + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/mcp/oauth' + pathType: Prefix + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/.well-known/oauth-authorization-server' + pathType: ImplementationSpecific + - backend: + service: + name: webserver-php-fpm + port: + number: 8080 + path: '/.well-known/oauth-protected-resource' + pathType: ImplementationSpecific +--- +# Source: shopsys-app/templates/hooks/secret-app-env-hook.yaml +apiVersion: v1 +kind: Secret +metadata: + name: app-secret-env-hook + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" + helm.sh/hook-delete-policy: before-hook-creation +type: Opaque +stringData: + APP_SECRET: "test-app-secret-key" + DATABASE_PASSWORD: "test-db-password" + S3_SECRET: "test-s3-secret" + +--- +# Source: shopsys-app/templates/hooks/configmap-domains-urls-hook.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: domains-urls-hook + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "5" + helm.sh/hook-delete-policy: before-hook-creation +data: + domains_urls.yaml: | + domains_urls: + - id: 1 + url: https://www.example.com + - id: 2 + url: https://www.example.sk + +--- +# Source: shopsys-app/templates/hooks/job-cron-suspend.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: cron-suspend + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + backoffLimit: 0 + template: + spec: + serviceAccountName: deploy-hooks + restartPolicy: Never + securityContext: + seccompProfile: + type: RuntimeDefault + containers: + - name: cron-suspend + image: "rancher/kubectl:v1.33.13" + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + command: + - /bin/sh + - -c + - | + kubectl scale deployment/cron --replicas=0 --namespace=myproject-production 2>/dev/null || true + kubectl wait --for=delete pod -l app=cron --namespace=myproject-production --timeout=3660s || true + +--- +# Source: shopsys-app/templates/hooks/job-migrate-application.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: migrate-application + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "10" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 0 + template: + spec: + # Runs under the namespace default SA (as a pre-install hook it cannot reference the + # chart ServiceAccount, which does not exist yet); it never talks to the API, so the + # token is not mounted. + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + volumes: + - name: domains-urls + configMap: + name: domains-urls-hook + containers: + - name: migrate-application + image: "v1.0.0" + command: ["sh", "-c", "cd /var/www/html && sleep 30 && ./phing cluster-first-deploy"] + securityContext: + allowPrivilegeEscalation: false + envFrom: + - secretRef: + name: app-secret-env-hook + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + restartPolicy: Never + imagePullSecrets: + - name: dockerregistry + +--- +# Source: shopsys-app/templates/hooks/job-post-deploy.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: post-deploy + labels: + app.kubernetes.io/name: shopsys-app + app.kubernetes.io/instance: shopsys-app + app.kubernetes.io/managed-by: Helm + helm.sh/chart: shopsys-app-1.0.0 + annotations: + helm.sh/hook: post-install,post-upgrade + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 0 + template: + spec: + # Runs under the namespace default SA (kept consistent with the migration hook); + # it never talks to the API, so the token is not mounted. + automountServiceAccountToken: false + securityContext: + seccompProfile: + type: RuntimeDefault + volumes: + - name: domains-urls + configMap: + name: domains-urls-hook + containers: + - name: post-deploy + image: "v1.0.0" + securityContext: + allowPrivilegeEscalation: false + command: + - sh + - -c + - | + set -e + cd /var/www/html + ./phing maintenance-off + ./phing clean-redis-old || echo "[FAILED] clean-redis-old" + ./phing clean-redis-storefront || echo "[FAILED] clean-redis-storefront" + if ./phing -l 2>/dev/null | grep -q "build-deploy-part-3-non-blocking"; then + ./phing build-deploy-part-3-non-blocking || echo "[FAILED] build-deploy-part-3-non-blocking" + fi + + envFrom: + - secretRef: + name: app-secret-env + env: + + - name: DATABASE_HOST + value: "10.0.0.100" + - name: DATABASE_NAME + value: "myproject-production" + - name: DATABASE_PORT + value: "5432" + - name: DATABASE_USER + value: "myproject-production" + - name: ELASTICSEARCH_HOST + value: "http://elasticsearch:9200" + - name: ELASTIC_SEARCH_INDEX_PREFIX + value: "myproject-production" + - name: MAILER_DSN + value: "smtp://mailhog:1025" + - name: MAILER_FORCE_WHITELIST + value: "false" + - name: MESSENGER_TRANSPORT_DSN + value: "amqp://guest:guest@rabbitmq:5672/%2f/messages" + - name: REDIS_PREFIX + value: "myproject-production" + - name: S3_ACCESS_KEY + value: "myproject-production" + - name: S3_BUCKET_NAME + value: "myproject-production" + - name: S3_ENDPOINT + value: "https://s3.example.com" + - name: TRUSTED_PROXY + value: "10.0.0.0/8" + volumeMounts: + - name: domains-urls + mountPath: /var/www/html/config/domains_urls.yaml + subPath: "domains_urls.yaml" + restartPolicy: Never + imagePullSecrets: + - name: dockerregistry + +