apiVersion: apps/v1
kind: Deployment

metadata:
  name: demoapp-sidekiq

  labels:
    app: sidekiq

# https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#deploymentspec-v1-apps
spec:
  replicas: 1

  selector:
    matchLabels:
      app: sidekiq

  template:
    metadata:
      labels:
        app: sidekiq

    spec:
      securityContext:
        # Must match Dockerfile's USER_ID for User and Group
        runAsUser: 1001
        runAsGroup: 1001
        # Set ownership of mounted volumes to the user running the container
        fsGroup: 1001

      # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#container-v1-core
      containers:
        - name: sidekiq-container
          image: ghcr.io/briansigafoos/docker-rails-webpacker-app
          command: ['bundle', 'exec', 'sidekiq']
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true

          # TODO: revisit these numbers based on actual production usage
          # and monitoring: kubectl resource-capacity --pods --util -n demoapp
          resources:
            requests:
              cpu: 50m
              memory: 300Mi
            limits:
              cpu: 500m
              memory: 500Mi

          # Pass k8s metadata to containers. Used to tag requests in AppSignal.
          env:
            - name: K8S_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: K8S_POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            # Puma is used by sidekiq_alive gem for probes
            # Puma expects this to be in tmp/pids/server.pid by default. But,
            # instead of mounting the writable /app/tmp directory, and creating
            # the tmp/pids folder, we can just configure Puma to use /app/tmp
            - name: PIDFILE
              value: tmp/server.pid
            # See Ruby's Dir.tmpdir comment below
            - name: TMPDIR
              value: /app/tmp/tmp

          envFrom:
            - secretRef:
                name: demoapp-secrets
            - configMapRef:
                name: demoapp-config

          # Naming port so that probes and service can reference it.
          ports:
            - name: probe-port
              containerPort: 7433

          livenessProbe:
            httpGet:
              path: /
              port: probe-port
            periodSeconds: 10
            failureThreshold: 3

          startupProbe:
            httpGet:
              path: /
              port: probe-port
            initialDelaySeconds: 10
            periodSeconds: 10
            failureThreshold: 30

          lifecycle:
            preStop:
              exec:
                # SIGTERM triggers a quick exit; gracefully terminate instead
                command: ['kubernetes/scripts/sidekiq_quiet']

          volumeMounts:
            # Since the container file system is read-only,
            # we'll mount /app/tmp to allow writes to it by Rails cache
            # /app/tmp/cache/
            - name: app-tmp
              mountPath: /app/tmp

      # Ruby's Dir.tmpdir won't allow us to use the mounted folder /app/tmp
      # for Tempfile.new. This initContainer creates a new /tmp folder inside
      # the mounted folder that will have the correct folder permission.
      # https://ruby-doc.org/stdlib-3.0.2/libdoc/tmpdir/rdoc/Dir.html
      initContainers:
        - name: make-tempfile-directory
          image: busybox:1.33
          # This new tmp folder must match TMPDIR env var above.
          command: ["sh", "-c", "mkdir /app/tmp/tmp"]
          volumeMounts:
            - name: app-tmp
              mountPath: /app/tmp

      # Use podAntiAffinity to distribute this pod across nodes before adding
      # more to the same node.
      # https://stackoverflow.com/questions/41159843/kubernetes-pod-distribution-amongst-nodes
      # https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                      - sidekiq
                topologyKey: kubernetes.io/hostname

      imagePullSecrets:
        - name: ghcr-k8s-pull
        - name: dockerhub-k8s-pull

      volumes:
        - name: app-tmp
          emptyDir: {}
