diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b466f81..d27d09a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: run: stackctl generate --output-dir /tmp/stacks-check - name: Upload Linux stack artifacts for parity inspection if: github.head_ref == 'chore/nightly-stack-sync-stackctl' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: linux-generated-stacks path: /tmp/stacks-check/*.yml diff --git a/stacks/infrastructure.yml b/stacks/infrastructure.yml index 46ec962..f8ed41d 100644 --- a/stacks/infrastructure.yml +++ b/stacks/infrastructure.yml @@ -1,5 +1,44 @@ # Generated by stackctl generate — do not edit manually. services: + redis: + image: 'redis:8.10-alpine' + networks: + default: + aliases: + - redis + command: redis-server --appendonly yes + env_file: ./redis/.env + labels: + - 'traefik.enable=${TRAEFIK_ENABLE}' + - 'traefik.tcp.routers.redis.rule=HostSNI(`${HOST}`)' + - traefik.tcp.routers.redis.entrypoints=websecure + - traefik.tcp.routers.redis.service=redis + - traefik.tcp.routers.redis.tls=true + - 'traefik.tcp.routers.redis.tls.certresolver=${CERT_RESOLVER}' + - 'traefik.tcp.services.redis.loadbalancer.server.port=${PORT}' + healthcheck: + test: + - CMD-SHELL + - redis-cli ping | grep -q PONG + interval: 10s + timeout: 5s + retries: 3 + start_period: 20s + logging: + options: + max-size: 10m + max-file: 3 + deploy: + resources: + reservations: + memory: 32M + limits: + memory: 128M + restart_policy: + condition: on-failure + delay: 5s + max_attempts: 0 + window: 120s apisix_dashboard: image: 'apache/apisix-dashboard:3.0.1-alpine' networks: @@ -48,53 +87,6 @@ services: delay: 10s max_attempts: 10 window: 180s - apisix_gateway: - image: 'apache/apisix:3.17.0-debian' - networks: - default: - aliases: - - apisix-gateway - env_file: - - ./apisix/api-gateway/.env - volumes: - - './apisix/api-gateway/config/apisix.yaml:/usr/local/apisix/conf/config.yaml:ro' - - 'cache:/tmp/apisix-cache/' - healthcheck: - test: - - CMD-SHELL - - test -s /usr/local/apisix/logs/nginx.pid - interval: 20s - timeout: 5s - retries: 3 - labels: - - 'traefik.enable=${TRAEFIK_ENABLE}' - - 'traefik.http.routers.apisix.rule=Host(`${HOST}`)' - - 'traefik.http.routers.apisix.entrypoints=web,websecure' - - traefik.http.routers.apisix.service=apisix - - traefik.http.routers.apisix.tls=true - - 'traefik.http.routers.apisix.tls.certresolver=${CERT_RESOLVER}' - - 'traefik.http.services.apisix.loadbalancer.server.port=${PORT}' - logging: - options: - max-size: 10m - max-file: 3 - deploy: - mode: global - placement: - constraints: - - node.role == manager - resources: - reservations: - memory: 128M - cpus: '0.10' - limits: - memory: 384M - cpus: '0.40' - restart_policy: - condition: on-failure - delay: 10s - max_attempts: 10 - window: 180s etcd: image: 'bitnamilegacy/etcd:3.6.4' networks: @@ -128,63 +120,81 @@ services: delay: 5s max_attempts: 0 window: 120s - redis: - image: 'redis:8.10-alpine' + apisix_gateway: + image: 'apache/apisix:3.17.0-debian' networks: default: aliases: - - redis - command: redis-server --appendonly yes - env_file: ./redis/.env - labels: - - 'traefik.enable=${TRAEFIK_ENABLE}' - - 'traefik.tcp.routers.redis.rule=HostSNI(`${HOST}`)' - - traefik.tcp.routers.redis.entrypoints=websecure - - traefik.tcp.routers.redis.service=redis - - traefik.tcp.routers.redis.tls=true - - 'traefik.tcp.routers.redis.tls.certresolver=${CERT_RESOLVER}' - - 'traefik.tcp.services.redis.loadbalancer.server.port=${PORT}' + - apisix-gateway + env_file: + - ./apisix/api-gateway/.env + volumes: + - './apisix/api-gateway/config/apisix.yaml:/usr/local/apisix/conf/config.yaml:ro' + - 'cache:/tmp/apisix-cache/' healthcheck: test: - CMD-SHELL - - redis-cli ping | grep -q PONG - interval: 10s + - test -s /usr/local/apisix/logs/nginx.pid + interval: 20s timeout: 5s retries: 3 - start_period: 20s + labels: + - 'traefik.enable=${TRAEFIK_ENABLE}' + - 'traefik.http.routers.apisix.rule=Host(`${HOST}`)' + - 'traefik.http.routers.apisix.entrypoints=web,websecure' + - traefik.http.routers.apisix.service=apisix + - traefik.http.routers.apisix.tls=true + - 'traefik.http.routers.apisix.tls.certresolver=${CERT_RESOLVER}' + - 'traefik.http.services.apisix.loadbalancer.server.port=${PORT}' logging: options: max-size: 10m max-file: 3 deploy: + mode: global + placement: + constraints: + - node.role == manager resources: reservations: - memory: 32M - limits: memory: 128M + cpus: '0.10' + limits: + memory: 384M + cpus: '0.40' restart_policy: condition: on-failure - delay: 5s - max_attempts: 0 - window: 120s - portainer: - image: 'portainer/portainer-ce:2.44.0' - volumes: - - '/var/run/docker.sock:/var/run/docker.sock' - - 'portainer-data:/data' - env_file: ./portainer/.env - labels: - - 'traefik.enable=${TRAEFIK_ENABLE}' - - 'traefik.http.routers.portainer.rule=Host(`${HOST}`)' - - 'traefik.http.services.portainer.loadbalancer.server.port=${PORT}' - - 'traefik.http.routers.portainer.entrypoints=web,websecure' - - traefik.http.routers.portainer.service=portainer - - traefik.http.routers.portainer.tls=true - - 'traefik.http.routers.portainer.tls.certresolver=${CERT_RESOLVER}' + delay: 10s + max_attempts: 10 + window: 180s + traefik: + image: 'traefik:v3.7' networks: default: aliases: - - portainer + - traefik + ports: + - '80:80' + - '443:443' + env_file: ./traefik/.env + volumes: + - 'traefik-ssl-certs:/etc/traefik/certs' + - './traefik/auth/htpasswd:/etc/htpasswd:ro' + - './traefik/config/traefik.yml:/etc/traefik/traefik.yml:ro' + - './traefik/config/dynamic.yml:/etc/traefik/dynamic.yml:ro' + - '/var/run/docker.sock:/var/run/docker.sock:ro' + labels: + - 'traefik.enable=${TRAEFIK_ENABLE}' + - 'traefik.http.routers.traefik-ui.rule=Host(`${HOST}`)' + - 'traefik.http.routers.traefik-ui.entrypoints=web,websecure' + - traefik.http.routers.traefik-ui.service=api@internal + - traefik.http.routers.traefik-ui.middlewares=sso + - traefik.http.routers.traefik-ui.tls=true + - 'traefik.http.routers.traefik-ui.tls.certresolver=${CERT_RESOLVER}' + - 'traefik.http.middlewares.sso.plugin.trauth.domain=${TRUEAUTH_DOMAIN}' + - 'traefik.http.middlewares.sso.plugin.trauth.cookiename=${TRUEAUTH_COOKIE}' + - 'traefik.http.middlewares.sso.plugin.trauth.users=${SSO_CREDENTIALS}' + - 'traefik.http.services.traefik-ui.loadbalancer.server.port=${PORT}' logging: options: max-size: 10m @@ -248,34 +258,24 @@ services: options: max-size: 10m max-file: 3 - traefik: - image: 'traefik:v3.7' - networks: - default: - aliases: - - traefik - ports: - - '80:80' - - '443:443' - env_file: ./traefik/.env + portainer: + image: 'portainer/portainer-ce:2.44.0' volumes: - - 'traefik-ssl-certs:/etc/traefik/certs' - - './traefik/auth/htpasswd:/etc/htpasswd:ro' - - './traefik/config/traefik.yml:/etc/traefik/traefik.yml:ro' - - './traefik/config/dynamic.yml:/etc/traefik/dynamic.yml:ro' - - '/var/run/docker.sock:/var/run/docker.sock:ro' + - '/var/run/docker.sock:/var/run/docker.sock' + - 'portainer-data:/data' + env_file: ./portainer/.env labels: - 'traefik.enable=${TRAEFIK_ENABLE}' - - 'traefik.http.routers.traefik-ui.rule=Host(`${HOST}`)' - - 'traefik.http.routers.traefik-ui.entrypoints=web,websecure' - - traefik.http.routers.traefik-ui.service=api@internal - - traefik.http.routers.traefik-ui.middlewares=sso - - traefik.http.routers.traefik-ui.tls=true - - 'traefik.http.routers.traefik-ui.tls.certresolver=${CERT_RESOLVER}' - - 'traefik.http.middlewares.sso.plugin.trauth.domain=${TRUEAUTH_DOMAIN}' - - 'traefik.http.middlewares.sso.plugin.trauth.cookiename=${TRUEAUTH_COOKIE}' - - 'traefik.http.middlewares.sso.plugin.trauth.users=${SSO_CREDENTIALS}' - - 'traefik.http.services.traefik-ui.loadbalancer.server.port=${PORT}' + - 'traefik.http.routers.portainer.rule=Host(`${HOST}`)' + - 'traefik.http.services.portainer.loadbalancer.server.port=${PORT}' + - 'traefik.http.routers.portainer.entrypoints=web,websecure' + - traefik.http.routers.portainer.service=portainer + - traefik.http.routers.portainer.tls=true + - 'traefik.http.routers.portainer.tls.certresolver=${CERT_RESOLVER}' + networks: + default: + aliases: + - portainer logging: options: max-size: 10m diff --git a/stacks/observability.yml b/stacks/observability.yml index f66ad56..8ea9ab5 100644 --- a/stacks/observability.yml +++ b/stacks/observability.yml @@ -101,54 +101,6 @@ services: limits: memory: 384M cpus: '0.40' - memcached: - image: 'memcached:1.6.45' - environment: - - MEMCACHED_MAX_MEMORY=64m - - MEMCACHED_THREADS=2 - networks: - default: - aliases: - - memcached - logging: - options: - max-size: 10m - max-file: 3 - tempo: - image: 'grafana/tempo:3.0.2' - user: tempo - command: - - '-config.file=/etc/tempo/config.yaml' - volumes: - - './observability/tempo/tempo.yaml:/etc/tempo/config.yaml' - - 'tempo-data:/var/tempo' - environment: - - TEMPO_GID=10001 - - TEMPO_UID=10001 - env_file: ./observability/tempo/.env - networks: - default: - aliases: - - tempo - logging: - options: - max-size: 10m - max-file: 3 - depends_on: - - memcached - deploy: - mode: replicated - replicas: 1 - placement: - constraints: - - node.role == manager - restart_policy: - condition: any - resources: - reservations: - memory: 256M - limits: - memory: 600M prometheus: image: 'prom/prometheus:v3.13.2' command: @@ -202,6 +154,54 @@ services: limits: memory: 1200M cpus: '0.80' + memcached: + image: 'memcached:1.6.45' + environment: + - MEMCACHED_MAX_MEMORY=64m + - MEMCACHED_THREADS=2 + networks: + default: + aliases: + - memcached + logging: + options: + max-size: 10m + max-file: 3 + tempo: + image: 'grafana/tempo:3.0.2' + user: tempo + command: + - '-config.file=/etc/tempo/config.yaml' + volumes: + - './observability/tempo/tempo.yaml:/etc/tempo/config.yaml' + - 'tempo-data:/var/tempo' + environment: + - TEMPO_GID=10001 + - TEMPO_UID=10001 + env_file: ./observability/tempo/.env + networks: + default: + aliases: + - tempo + logging: + options: + max-size: 10m + max-file: 3 + depends_on: + - memcached + deploy: + mode: replicated + replicas: 1 + placement: + constraints: + - node.role == manager + restart_policy: + condition: any + resources: + reservations: + memory: 256M + limits: + memory: 600M beszel: image: 'henrygd/beszel:0.18.7' networks: diff --git a/stacks/platform.yml b/stacks/platform.yml index 1958095..1f03392 100644 --- a/stacks/platform.yml +++ b/stacks/platform.yml @@ -39,6 +39,43 @@ services: delay: 10s max_attempts: 10 window: 180s + anitrend-edge: + image: 'ghcr.io/anitrend/on-the-edge:1.6.5' + networks: + default: + aliases: + - anitrend-edge + env_file: ./on-the-edge/.env + labels: + - 'traefik.enable=${TRAEFIK_ENABLE}' + - 'traefik.http.routers.anitrend-edge.rule=Host(`${HOST}`)' + - 'traefik.http.routers.anitrend-edge.entrypoints=web,websecure' + - traefik.http.routers.anitrend-edge.service=anitrend-edge + - traefik.http.routers.anitrend-edge.tls=true + - 'traefik.http.routers.anitrend-edge.tls.certresolver=${CERT_RESOLVER}' + - 'traefik.http.services.anitrend-edge.loadbalancer.server.port=${PORT}' + logging: + options: + max-size: 10m + max-file: 3 + deploy: + mode: replicated + replicas: 1 + placement: + constraints: + - node.role == manager + resources: + reservations: + memory: 128M + cpus: '0.10' + limits: + memory: 384M + cpus: '0.50' + restart_policy: + condition: any + delay: 10s + max_attempts: 10 + window: 180s website: image: 'ghcr.io/anitrend/website:0.4.2' networks: @@ -70,53 +107,6 @@ services: options: max-size: 10m max-file: 3 - growthbook_proxy: - image: 'growthbook/proxy:v1.3.5' - env_file: ./growthbook/proxy/.env - networks: - default: - aliases: - - growthbook-proxy - labels: - - 'traefik.enable=${TRAEFIK_ENABLE}' - - 'traefik.http.routers.growth-proxy.rule=Host(`${HOST}`)' - - 'traefik.http.services.growth-proxy.loadbalancer.server.port=${PORT}' - - 'traefik.http.routers.growth-proxy.entrypoints=web,websecure' - - traefik.http.routers.growth-proxy.service=growth-proxy - - traefik.http.routers.growth-proxy.tls=true - - 'traefik.http.routers.growth-proxy.tls.certresolver=${CERT_RESOLVER}' - logging: - options: - max-size: 10m - max-file: 3 - healthcheck: - test: - - CMD-SHELL - - |- - node -e "fetch('http://127.0.0.1:3300/healthcheck') - .then(r => r.json()) - .then(d => process.exit(d.ok ? 0 : 1)) - .catch(() => process.exit(1))" - interval: 20s - timeout: 10s - retries: 3 - start_period: 20s - deploy: - mode: replicated - replicas: 1 - placement: - constraints: - - node.role == manager - resources: - reservations: - memory: 64M - limits: - memory: 256M - restart_policy: - condition: on-failure - delay: 10s - max_attempts: 10 - window: 180s growthbook: command: - yarn @@ -176,25 +166,37 @@ services: delay: 10s max_attempts: 10 window: 180s - anitrend-edge: - image: 'ghcr.io/anitrend/on-the-edge:1.6.5' + growthbook_proxy: + image: 'growthbook/proxy:v1.3.5' + env_file: ./growthbook/proxy/.env networks: default: aliases: - - anitrend-edge - env_file: ./on-the-edge/.env + - growthbook-proxy labels: - 'traefik.enable=${TRAEFIK_ENABLE}' - - 'traefik.http.routers.anitrend-edge.rule=Host(`${HOST}`)' - - 'traefik.http.routers.anitrend-edge.entrypoints=web,websecure' - - traefik.http.routers.anitrend-edge.service=anitrend-edge - - traefik.http.routers.anitrend-edge.tls=true - - 'traefik.http.routers.anitrend-edge.tls.certresolver=${CERT_RESOLVER}' - - 'traefik.http.services.anitrend-edge.loadbalancer.server.port=${PORT}' + - 'traefik.http.routers.growth-proxy.rule=Host(`${HOST}`)' + - 'traefik.http.services.growth-proxy.loadbalancer.server.port=${PORT}' + - 'traefik.http.routers.growth-proxy.entrypoints=web,websecure' + - traefik.http.routers.growth-proxy.service=growth-proxy + - traefik.http.routers.growth-proxy.tls=true + - 'traefik.http.routers.growth-proxy.tls.certresolver=${CERT_RESOLVER}' logging: options: max-size: 10m max-file: 3 + healthcheck: + test: + - CMD-SHELL + - |- + node -e "fetch('http://127.0.0.1:3300/healthcheck') + .then(r => r.json()) + .then(d => process.exit(d.ok ? 0 : 1)) + .catch(() => process.exit(1))" + interval: 20s + timeout: 10s + retries: 3 + start_period: 20s deploy: mode: replicated replicas: 1 @@ -203,13 +205,11 @@ services: - node.role == manager resources: reservations: - memory: 128M - cpus: '0.10' + memory: 64M limits: - memory: 384M - cpus: '0.50' + memory: 256M restart_policy: - condition: any + condition: on-failure delay: 10s max_attempts: 10 window: 180s