From 5d78d71dbb3713f9fd759a2f29cb2b54b4cc98a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20G=C3=B3recki?= Date: Wed, 19 Aug 2026 04:50:39 +0200 Subject: [PATCH 1/3] Add opt-in NetworkPolicies for the application namespace The namespace previously had no NetworkPolicies - every pod could talk to every other pod and the whole cluster network, which matters doubly with the unauthenticated redis. Off by default for parity (networkPolicy.enabled). When enabled, the infra chart (installed first) renders the namespace-wide default ingress deny, the redis (6379 in-namespace, 9121 monitoring) and rabbitmq (5672 in-namespace, 15672 ingress controller, 15692 monitoring) allows, an extra-ingress escape hatch and an optional egress lockdown (DNS + in-namespace + explicit rules); the app chart allows the ingress controller and in-namespace pods to reach the webserver (8080) and storefront (3000). The ingress-controller and monitoring namespace selectors are configurable. Co-Authored-By: Claude Fable 5 --- .../shopsys-app/templates/networkpolicy.yaml | 47 +++++++ .../shopsys-app/tests/networkpolicy_test.yaml | 47 +++++++ charts/shopsys-app/values.yaml | 24 ++++ .../templates/networkpolicy.yaml | 125 ++++++++++++++++++ .../tests/networkpolicy_test.yaml | 98 ++++++++++++++ charts/shopsys-infra/values.yaml | 16 +++ docs/values.md | 11 ++ 7 files changed, 368 insertions(+) create mode 100644 charts/shopsys-app/templates/networkpolicy.yaml create mode 100644 charts/shopsys-app/tests/networkpolicy_test.yaml create mode 100644 charts/shopsys-infra/templates/networkpolicy.yaml create mode 100644 charts/shopsys-infra/tests/networkpolicy_test.yaml diff --git a/charts/shopsys-app/templates/networkpolicy.yaml b/charts/shopsys-app/templates/networkpolicy.yaml new file mode 100644 index 0000000..7e770b1 --- /dev/null +++ b/charts/shopsys-app/templates/networkpolicy.yaml @@ -0,0 +1,47 @@ +{{- 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: + # e-shop ingresses + - namespaceSelector: + {{- toYaml .Values.networkPolicy.ingressControllerNamespace | nindent 12 }} + # the webserver's nginx proxies /_next/ and @storefront to the storefront + - 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..3716449 --- /dev/null +++ b/charts/shopsys-app/tests/networkpolicy_test.yaml @@ -0,0 +1,47 @@ +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 + + - 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.yaml b/charts/shopsys-app/values.yaml index 9dcce5d..0c12e48 100644 --- a/charts/shopsys-app/values.yaml +++ b/charts/shopsys-app/values.yaml @@ -75,6 +75,30 @@ 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, 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. + 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..7bb6509 --- /dev/null +++ b/charts/shopsys-infra/templates/networkpolicy.yaml @@ -0,0 +1,125 @@ +{{- 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 +--- +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 +--- +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 + # management UI through the ingress controller + - from: + - namespaceSelector: + {{- toYaml .Values.networkPolicy.ingressControllerNamespace | nindent 12 }} + ports: + - port: 15672 + # prometheus scraping of the built-in exporter + - from: + - namespaceSelector: + {{- toYaml .Values.networkPolicy.monitoringNamespace | nindent 12 }} + ports: + - port: 15692 +{{- 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 (cluster DNS location differs per cluster) + - 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..fa4ff39 --- /dev/null +++ b/charts/shopsys-infra/tests/networkpolicy_test.yaml @@ -0,0 +1,98 @@ +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 and the redis/rabbitmq allows + set: + networkPolicy: + enabled: true + asserts: + - hasDocuments: + count: 3 + - 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 + + - it: keeps egress open unless explicitly locked down + set: + networkPolicy: + enabled: true + asserts: + - hasDocuments: + count: 3 + + - 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: 5 + - equal: + path: metadata.name + value: default-deny-egress + documentIndex: 3 + - equal: + path: spec.egress[0].ports[0].port + value: 53 + documentIndex: 4 + - equal: + path: spec.egress[2].to[0].ipBlock.cidr + value: 10.0.0.5/32 + documentIndex: 4 + + - it: renders the extra-ingress escape hatch + set: + networkPolicy: + enabled: true + extraIngress: + - from: + - ipBlock: + cidr: 192.168.0.0/16 + asserts: + - hasDocuments: + count: 4 + - equal: + path: metadata.name + value: allow-extra-ingress + documentIndex: 3 + - equal: + path: spec.ingress[0].from[0].ipBlock.cidr + value: 192.168.0.0/16 + documentIndex: 3 diff --git a/charts/shopsys-infra/values.yaml b/charts/shopsys-infra/values.yaml index c23144f..ec3caf0 100644 --- a/charts/shopsys-infra/values.yaml +++ b/charts/shopsys-infra/values.yaml @@ -38,6 +38,22 @@ 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 +# 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 fe5268a..0e34a46 100644 --- a/docs/values.md +++ b/docs/values.md @@ -71,6 +71,17 @@ serviceAccount: # per-chart SA the workload pods run under (no API # an explicit name would collide between the two releases) automountToken: false +networkPolicy: # opt-in; default-deny ingress + per-workload allows. + enabled: false # Roll out on a dev environment first (CNI must enforce + ingressControllerNamespace: # policies; verify kubelet probes still pass) + 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) + app: # shared backend configuration env: {} # non-sensitive backend env vars (webserver, cron, consumers, migration) secretEnv: {} # sensitive backend env vars → app-secret-env Secret + envFrom; From ffc27adaa77f33c07e3ca18f2175c0d4db7f7d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20G=C3=B3recki?= Date: Thu, 20 Aug 2026 12:26:07 +0200 Subject: [PATCH 2/3] Address NetworkPolicy review findings - Allow cert-manager's HTTP01 solver pods (port 8089): the solvers run in this namespace and every certificate issuance/renewal would fail under the default deny - only surfacing weeks later at renewal time. Unrestricted source on purpose (self-check source varies with LB/externalTrafficPolicy; the solver serves only the public challenge token) and a no-op on DNS01 clusters. - Declare networkPolicy in both values.schema.json (additionalProperties: false): a typo in a security toggle must fail the deploy, not silently render nothing while the operator believes the namespace is locked down. - Drop the ingress-controller peer from allow-storefront: no shipped ingress targets storefront:3000 - all storefront traffic arrives via the webserver's nginx (in-namespace); a project-specific direct ingress can use extraIngress. - Open RabbitMQ 15672 from the ingress controller only under the same condition that renders the management ingress; gate allow-redis/allow-rabbitmq on the workloads being enabled. - Document the DNS-anywhere residual risk (DNS tunneling) in values + docs. - Add a network-policies golden scenario locking the enabled state (incl. egress lockdown and extra ingress) under the full helmfile state-values path. Co-Authored-By: Claude Fable 5 --- CLAUDE.md | 2 +- .../shopsys-app/templates/networkpolicy.yaml | 7 +- .../shopsys-app/tests/networkpolicy_test.yaml | 6 + charts/shopsys-app/values.schema.json | 18 + charts/shopsys-app/values.yaml | 9 +- .../templates/networkpolicy.yaml | 33 +- .../tests/networkpolicy_test.yaml | 67 +- charts/shopsys-infra/values.schema.json | 18 + charts/shopsys-infra/values.yaml | 3 +- docs/values.md | 13 +- .../network-policies/description.txt | 1 + .../network-policies/environments/base.yaml | 71 + .../environments/production/values.yaml | 1 + .../network-policies/expected/continuous.yaml | 2279 +++++++++++++++++ .../expected/first-deploy-with-demo-data.yaml | 2279 +++++++++++++++++ .../expected/first-deploy.yaml | 2279 +++++++++++++++++ 16 files changed, 7062 insertions(+), 24 deletions(-) create mode 100644 tests/golden/scenarios/network-policies/description.txt create mode 100644 tests/golden/scenarios/network-policies/environments/base.yaml create mode 100644 tests/golden/scenarios/network-policies/environments/production/values.yaml create mode 100644 tests/golden/scenarios/network-policies/expected/continuous.yaml create mode 100644 tests/golden/scenarios/network-policies/expected/first-deploy-with-demo-data.yaml create mode 100644 tests/golden/scenarios/network-policies/expected/first-deploy.yaml 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 index 7e770b1..31314ca 100644 --- a/charts/shopsys-app/templates/networkpolicy.yaml +++ b/charts/shopsys-app/templates/networkpolicy.yaml @@ -37,10 +37,9 @@ spec: - Ingress ingress: - from: - # e-shop ingresses - - namespaceSelector: - {{- toYaml .Values.networkPolicy.ingressControllerNamespace | nindent 12 }} - # the webserver's nginx proxies /_next/ and @storefront to the storefront + # 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 diff --git a/charts/shopsys-app/tests/networkpolicy_test.yaml b/charts/shopsys-app/tests/networkpolicy_test.yaml index 3716449..b9e52de 100644 --- a/charts/shopsys-app/tests/networkpolicy_test.yaml +++ b/charts/shopsys-app/tests/networkpolicy_test.yaml @@ -32,6 +32,12 @@ tests: 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: diff --git a/charts/shopsys-app/values.schema.json b/charts/shopsys-app/values.schema.json index 51d2a9c..dd8e3b6 100644 --- a/charts/shopsys-app/values.schema.json +++ b/charts/shopsys-app/values.schema.json @@ -74,6 +74,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 0c12e48..2a8ced6 100644 --- a/charts/shopsys-app/values.yaml +++ b/charts/shopsys-app/values.yaml @@ -76,9 +76,10 @@ serviceAccount: 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, 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. +# 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 @@ -95,6 +96,8 @@ networkPolicy: # 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: [] diff --git a/charts/shopsys-infra/templates/networkpolicy.yaml b/charts/shopsys-infra/templates/networkpolicy.yaml index 7bb6509..ec805f6 100644 --- a/charts/shopsys-infra/templates/networkpolicy.yaml +++ b/charts/shopsys-infra/templates/networkpolicy.yaml @@ -12,6 +12,7 @@ spec: podSelector: {} policyTypes: - Ingress +{{- if .Values.redis.enabled }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy @@ -37,6 +38,8 @@ spec: {{- toYaml .Values.networkPolicy.monitoringNamespace | nindent 12 }} ports: - port: 9121 +{{- end }} +{{- if .Values.rabbitmq.enabled }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy @@ -56,18 +59,44 @@ spec: - 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 @@ -107,7 +136,9 @@ spec: policyTypes: - Egress egress: - # DNS anywhere (cluster DNS location differs per cluster) + # 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 diff --git a/charts/shopsys-infra/tests/networkpolicy_test.yaml b/charts/shopsys-infra/tests/networkpolicy_test.yaml index fa4ff39..7acbd01 100644 --- a/charts/shopsys-infra/tests/networkpolicy_test.yaml +++ b/charts/shopsys-infra/tests/networkpolicy_test.yaml @@ -9,13 +9,13 @@ tests: - hasDocuments: count: 0 - - it: renders the default ingress deny and the redis/rabbitmq allows + - it: renders the default ingress deny, the redis/rabbitmq allows and the acme-solver allow set: networkPolicy: enabled: true asserts: - hasDocuments: - count: 3 + count: 4 - equal: path: metadata.name value: default-deny-ingress @@ -40,6 +40,53 @@ tests: 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: @@ -47,7 +94,7 @@ tests: enabled: true asserts: - hasDocuments: - count: 3 + count: 4 - it: locks down egress with DNS, in-namespace and custom rules set: @@ -63,19 +110,19 @@ tests: - port: 5432 asserts: - hasDocuments: - count: 5 + count: 6 - equal: path: metadata.name value: default-deny-egress - documentIndex: 3 + documentIndex: 4 - equal: path: spec.egress[0].ports[0].port value: 53 - documentIndex: 4 + documentIndex: 5 - equal: path: spec.egress[2].to[0].ipBlock.cidr value: 10.0.0.5/32 - documentIndex: 4 + documentIndex: 5 - it: renders the extra-ingress escape hatch set: @@ -87,12 +134,12 @@ tests: cidr: 192.168.0.0/16 asserts: - hasDocuments: - count: 4 + count: 5 - equal: path: metadata.name value: allow-extra-ingress - documentIndex: 3 + documentIndex: 4 - equal: path: spec.ingress[0].from[0].ipBlock.cidr value: 192.168.0.0/16 - documentIndex: 3 + documentIndex: 4 diff --git a/charts/shopsys-infra/values.schema.json b/charts/shopsys-infra/values.schema.json index da60154..b48f3ca 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" } } + } + } + } + }, "redis": { "type": "object", "properties": { diff --git a/charts/shopsys-infra/values.yaml b/charts/shopsys-infra/values.yaml index ec3caf0..dd4ac63 100644 --- a/charts/shopsys-infra/values.yaml +++ b/charts/shopsys-infra/values.yaml @@ -40,7 +40,8 @@ serviceAccount: # 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 -# extra-ingress escape hatch and the optional egress lockdown. +# cert-manager HTTP01 solver allow, the extra-ingress escape hatch and the optional +# egress lockdown. networkPolicy: enabled: false ingressControllerNamespace: diff --git a/docs/values.md b/docs/values.md index 0e34a46..22ca3b7 100644 --- a/docs/values.md +++ b/docs/values.md @@ -71,16 +71,21 @@ serviceAccount: # per-chart SA the workload pods run under (no API # an explicit name would collide between the two releases) automountToken: false -networkPolicy: # opt-in; default-deny ingress + per-workload allows. - enabled: false # Roll out on a dev environment first (CNI must enforce - ingressControllerNamespace: # policies; verify kubelet probes still pass) +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) + 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) 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..f0f1355 --- /dev/null +++ b/tests/golden/scenarios/network-policies/expected/continuous.yaml @@ -0,0 +1,2279 @@ +--- +# 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 + 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 + 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 + 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 + 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 + 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 + 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..278afdb --- /dev/null +++ b/tests/golden/scenarios/network-policies/expected/first-deploy-with-demo-data.yaml @@ -0,0 +1,2279 @@ +--- +# 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 + 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 + 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 + 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 + 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 + 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 + 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..ab533a8 --- /dev/null +++ b/tests/golden/scenarios/network-policies/expected/first-deploy.yaml @@ -0,0 +1,2279 @@ +--- +# 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 + 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 + 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 + 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 + 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 + 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 + 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 + + From a8fb219d81bfef26d29ce49a62580e07f15fdfc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20G=C3=B3recki?= Date: Thu, 20 Aug 2026 12:34:08 +0200 Subject: [PATCH 3/3] Refresh network-policies golden scenario after parent merge The scenario was generated before the parent branch gained the pod-level automountServiceAccountToken line and the values-driven hook contexts. Co-Authored-By: Claude Fable 5 --- .../scenarios/network-policies/expected/continuous.yaml | 6 ++++++ .../expected/first-deploy-with-demo-data.yaml | 6 ++++++ .../scenarios/network-policies/expected/first-deploy.yaml | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/tests/golden/scenarios/network-policies/expected/continuous.yaml b/tests/golden/scenarios/network-policies/expected/continuous.yaml index f0f1355..4bc9514 100644 --- a/tests/golden/scenarios/network-policies/expected/continuous.yaml +++ b/tests/golden/scenarios/network-policies/expected/continuous.yaml @@ -403,6 +403,7 @@ spec: app: redis spec: serviceAccountName: shopsys-infra + automountServiceAccountToken: false securityContext: runAsGroup: 1000 runAsNonRoot: true @@ -504,6 +505,7 @@ spec: app: rabbitmq spec: serviceAccountName: shopsys-infra + automountServiceAccountToken: false affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -1266,6 +1268,7 @@ spec: app: consumer-email spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false tolerations: - effect: NoSchedule key: workload @@ -1409,6 +1412,7 @@ spec: date: "1234567890" spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false tolerations: - effect: NoSchedule key: workload @@ -1545,6 +1549,7 @@ spec: app: storefront spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -1650,6 +1655,7 @@ spec: app: webserver-php-fpm spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: 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 index 278afdb..e716d05 100644 --- 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 @@ -403,6 +403,7 @@ spec: app: redis spec: serviceAccountName: shopsys-infra + automountServiceAccountToken: false securityContext: runAsGroup: 1000 runAsNonRoot: true @@ -504,6 +505,7 @@ spec: app: rabbitmq spec: serviceAccountName: shopsys-infra + automountServiceAccountToken: false affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -1266,6 +1268,7 @@ spec: app: consumer-email spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false tolerations: - effect: NoSchedule key: workload @@ -1409,6 +1412,7 @@ spec: date: "1234567890" spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false tolerations: - effect: NoSchedule key: workload @@ -1545,6 +1549,7 @@ spec: app: storefront spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -1650,6 +1655,7 @@ spec: app: webserver-php-fpm spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: diff --git a/tests/golden/scenarios/network-policies/expected/first-deploy.yaml b/tests/golden/scenarios/network-policies/expected/first-deploy.yaml index ab533a8..ded595b 100644 --- a/tests/golden/scenarios/network-policies/expected/first-deploy.yaml +++ b/tests/golden/scenarios/network-policies/expected/first-deploy.yaml @@ -403,6 +403,7 @@ spec: app: redis spec: serviceAccountName: shopsys-infra + automountServiceAccountToken: false securityContext: runAsGroup: 1000 runAsNonRoot: true @@ -504,6 +505,7 @@ spec: app: rabbitmq spec: serviceAccountName: shopsys-infra + automountServiceAccountToken: false affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -1266,6 +1268,7 @@ spec: app: consumer-email spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false tolerations: - effect: NoSchedule key: workload @@ -1409,6 +1412,7 @@ spec: date: "1234567890" spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false tolerations: - effect: NoSchedule key: workload @@ -1545,6 +1549,7 @@ spec: app: storefront spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: @@ -1650,6 +1655,7 @@ spec: app: webserver-php-fpm spec: serviceAccountName: shopsys-app + automountServiceAccountToken: false affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: