7.5 KiB · text History c5a2f92
apiVersion: v1
kind: Namespace
metadata:
  name: waxx
---
# RBAC for libcluster's Kubernetes strategy. The ServiceAccount on the
# pod gets read-only access to its own namespace's pods + endpoints so
# it can discover peer node IPs for Distributed Erlang.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: waxx
  namespace: waxx
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: waxx-pod-reader
  namespace: waxx
rules:
  - apiGroups: [""]
    resources: ["pods", "endpoints"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: waxx-pod-reader
  namespace: waxx
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: waxx-pod-reader
subjects:
  - kind: ServiceAccount
    name: waxx
    namespace: waxx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: waxx
  namespace: waxx
  labels:
    name: waxx
    keel.sh/policy: force
    keel.sh/trigger: poll
spec:
  replicas: 2
  selector:
    matchLabels:
      name: waxx
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        name: waxx
    spec:
      serviceAccountName: waxx
      containers:
        - image: r.g.gmp.io/neiam/waxx:latest
          imagePullPolicy: Always
          name: waxx
          ports:
            - containerPort: 4000
          env:
            - name: PHX_HOST
              value: waxx.neiam.co
            - name: PORT
              value: "4000"
            - name: PHX_SERVER
              value: "true"
            - name: POOL_SIZE
              value: "10"

            # Cluster glue: every pod becomes waxx@<pod-ip>.
            # libcluster (Cluster.Strategy.Kubernetes) discovers peers
            # via the K8s API (RBAC defined above); Phoenix.PubSub then
            # fans card/workflow broadcasts out across nodes.
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
            - name: RELEASE_COOKIE
              valueFrom:
                secretKeyRef:
                  name: waxx-secrets
                  key: RELEASE_COOKIE

            - name: SECRET_KEY_BASE
              valueFrom:
                secretKeyRef:
                  name: waxx-secrets
                  key: SECRET_KEY_BASE

            # Sentry DSN is optional — when the key is empty or absent
            # the pod still starts and Sentry quietly no-ops.
            - name: SENTRY_DSN
              valueFrom:
                secretKeyRef:
                  name: waxx-secrets
                  key: SENTRY_DSN
                  optional: true

            # SMTP relay. With SMTP_RELAY unset/empty the mailer falls
            # back to Swoosh.Adapters.Logger — no mail sent, but the
            # rendered magic-link email shows up in pod logs at :info.
            - name: SMTP_RELAY
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: SMTP_RELAY,        optional: true } }
            - name: SMTP_PORT
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: SMTP_PORT,         optional: true } }
            - name: SMTP_USERNAME
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: SMTP_USERNAME,     optional: true } }
            - name: SMTP_PASSWORD
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: SMTP_PASSWORD,     optional: true } }
            - name: SMTP_TLS
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: SMTP_TLS,          optional: true } }
            - name: SMTP_SSL
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: SMTP_SSL,          optional: true } }
            - name: SMTP_AUTH
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: SMTP_AUTH,         optional: true } }
            - name: SMTP_TLS_VERIFY
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: SMTP_TLS_VERIFY,   optional: true } }
            - name: MAIL_FROM_ADDRESS
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: MAIL_FROM_ADDRESS, optional: true } }
            - name: MAIL_FROM_NAME
              valueFrom: { secretKeyRef: { name: waxx-secrets, key: MAIL_FROM_NAME,    optional: true } }

            # Postgres creds come from the StackGres-managed auth secret
            # (named after the SGCluster in pg.yml). runtime.exs reads
            # these directly — no URL composition — so passwords with
            # special characters don't get mangled by URL parsing.
            - name: POSTGRES_HOST
              value: postgres-waxx
            - name: POSTGRES_PORT
              value: "5432"
            - name: POSTGRES_DB
              value: waxx
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: postgres-waxx
                  key: superuser-username
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-waxx
                  key: superuser-password
          readinessProbe:
            httpGet:
              path: /
              port: 4000
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /
              port: 4000
            initialDelaySeconds: 30
            periodSeconds: 30
      imagePullSecrets:
        - name: gitlabgmp
---
apiVersion: v1
kind: Service
metadata:
  name: waxx-svc
  namespace: waxx
spec:
  ports:
    - port: 80
      targetPort: 4000
      protocol: TCP
  selector:
    name: waxx
---
# Headless service so libcluster's Kubernetes strategy can resolve all
# pod IPs for peer discovery. Pods discover siblings via this service's
# Endpoints; the actual Erlang distribution traffic flows pod-IP →
# pod-IP (4369 = epmd, 9100 = inet_dist pinned in rel/env.sh.eex).
apiVersion: v1
kind: Service
metadata:
  name: waxx-headless
  namespace: waxx
  # Labels here propagate to the Endpoints object. libcluster's K8s
  # strategy filters endpoints by labelSelector (not by service name),
  # so without `name: waxx` here the Endpoints have no matching label
  # and discovery silently returns zero peers.
  labels:
    name: waxx
spec:
  clusterIP: None
  publishNotReadyAddresses: true
  ports:
    - { name: http, port: 4000, targetPort: 4000, protocol: TCP }
    - { name: epmd, port: 4369, targetPort: 4369, protocol: TCP }
    - { name: dist, port: 9100, targetPort: 9100, protocol: TCP }
  selector:
    name: waxx
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: redirect-https
  namespace: waxx
spec:
  redirectScheme:
    scheme: https
    permanent: true
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: waxx-ingress
  namespace: waxx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: "traefik"
    # Listen on both Traefik entrypoints. The middleware below redirects
    # http → https for the `web` (port 80) entrypoint, so the app itself
    # never has to issue a 301 (which broke the LiveView WS handshake).
    traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
    traefik.ingress.kubernetes.io/router.middlewares: waxx-redirect-https@kubernetescrd
spec:
  tls:
    - hosts:
        - waxx.neiam.co
      secretName: waxx-tls
  rules:
    - host: waxx.neiam.co
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: waxx-svc
                port:
                  number: 80