Kubernetes Deployment

July 4, 2023

I wanted to document the approach taken to to various bits of the Helm charts that I've authored. This is the Helm chart for deployment of a pod which includes Istio, Apache and PHP-FPM in separate docker containers.

{{ $env := .Values.global.env }}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.global.env }}-{{ .Chart.Name }}
  labels:
    {{- include "labels.standard" . | nindent 8 }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.global.env }}-{{ .Chart.Name }}
  template:
    metadata:
      labels:
        {{- include "labels.standard" . | nindent 8 }}
        networking/allow-ingress-access: "true"
      annotations:
        "traffic.sidecar.istio.io/excludeInboundPorts": "{{ .Values.global.app.NEW_RELIC_MONITORING_PORT }}"
        proxy.istio.io/config: '{ "holdApplicationUntilProxyStarts": true }'
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: {{ .Values.global.env }}-{{ .Chart.Name }}
      {{- with .Values.global.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      imagePullSecrets:
        - name: regcred
      serviceAccountName: {{ .Values.global.env }}-{{ .Chart.Name }}
      containers:
        - name: apache
          image: "{{ .Values.global.imageRegistry}}/{{ .Values.global.imageRepositoryPrefix}}/{{ .Chart.Name }}/apache:{{ .Values.version }}"
          imagePullPolicy: IfNotPresent
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh","-c","sleep 5 && apachectl -k graceful-stop"]
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /health/live.php
              port: http
          readinessProbe:
            httpGet:
              path: /health/live.php
              port: http
            periodSeconds: 10
            timeoutSeconds: 1
          startupProbe:
            httpGet:
              path: /health/ready
              port: http
            failureThreshold: 30
            periodSeconds: 10
            timeoutSeconds: 3
          resources: {{- toYaml .Values.apache.resources | nindent 12 }}
          volumeMounts:
          - name: "{{ $env }}-{{ .Chart.Name }}-apache-config"
            mountPath: /etc/apache2/sites-enabled/my-app-apache.conf
            subPath: my-app-apache.conf
            readOnly: true
        - name: php
          image: "{{ .Values.global.imageRegistry}}/{{ .Values.global.imageRepositoryPrefix}}/{{ .Chart.Name }}/php-fpm:{{ .Values.version }}"
          imagePullPolicy: IfNotPresent
          envFrom:
            - configMapRef:
                name: {{ .Values.global.env }}-{{ .Chart.Name }}
          env:
            # php max children
            - name: PHP_MAX_CHILDREN
              {{- if hasSuffix "M" (trimSuffix "i" .Values.php.resources.requests.memory) }}
              # MBs
              value: "{{ div (sub (trimSuffix "i" .Values.php.resources.requests.memory | trimSuffix "M") .Values.php.osMemoryUsageMb) .Values.php.processMemoryUsageMb }}"
              {{- else }}
              # GBs
              value: "{{ div (sub (trimSuffix "i" .Values.php.resources.requests.memory | trimSuffix "G" | mul 1024) .Values.php.osMemoryUsageMb)  Values.php.processMemoryUsageMb }}"
               {{- end }}
            # secrets from values
            {{- range $key, $val := .Values.php.secrets }}
            - name: {{ $key }}
              valueFrom:
                secretKeyRef:
                  key: {{ $val }}
                  name: {{ $env }}-{{ $val }}
            {{- end }}

            # env vars from values
            {{- range $key, $val := .Values.php.envVars }}
            - name: {{ $key }}
              value: "{{ tpl $val $ }}"
            {{- end }}
          ports:
            - name: php-fpm
              containerPort: 9080
              protocol: TCP
          livenessProbe:
            exec:
              command:
                - sh
                - -c
                - FCGI_CONNECT=localhost:9080 php-fpm-healthcheck
            initialDelaySeconds: 0
            periodSeconds: 10
          readinessProbe:
            exec:
              command:
                - sh
                - -c
                - FCGI_CONNECT=localhost:9080 php-fpm-healthcheck
            initialDelaySeconds: 1
            periodSeconds: 5
          resources: {{- toYaml .Values.php.resources | nindent 12 }}
          volumeMounts:
          - name: "{{ $env }}-{{ .Chart.Name }}-php-fpm-config"
            mountPath: /usr/local/etc/php-fpm/pool.d/my-app-fpm.conf
            subPath: my-app-fpm.conf
            readOnly: true
      volumes:
      - name: "{{ $env }}-{{ .Chart.Name }}-apache-config"
        configMap:
          name: "{{ $env }}-{{ .Chart.Name }}-apache-config"
      - name: "{{ $env }}-{{ .Chart.Name }}-php-fpm-config"
        configMap:
         name: "{{ $env }}-{{ .Chart.Name }}-php-fpm-config"

This matches up with a values.yaml file in this format:

version: v1.0.1300
minReplicas: 1
maxReplicas: 2
apache:
  resources:
    requests:
      memory: 500Mi
      cpu: 250m
    limits:
       memory: 500Mi
php:
  resources:
    requests:
      memory: 2Gi
      cpu: 2
    limits:
      memory: 2Gi
  # max children is configured based on (amount of memory set in requests - OS memory) / php-fpm memory usage e.g.1GB - 256 / 40 = 19 max children
  # use below command to get average memory use
  # ps --no-headers -o "rss,cmd" -C php-fpm | grep $DCG_COMPONENT | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"Mb") }'
  osMemoryUsageMb: 150
  processMemoryUsageMb: 55
  maxRequests: 400
  secrets:
    SECRET_APPS: secret-apps
    ANOTHER_SECRET_APPS: another-secret-apps
  envVars:
    APP_ENVIRONMENT_NAME: "prod"
    ANOTHER_ENV_VAR: "value"

Secrets need a SecretProviderClass, and this is also generated from the same values.yaml

{{ $env := .Values.global.env }}
{{- if .Values.php.secrets }}
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: {{ .Values.global.env }}-secrets-{{ .Chart.Name }}
  labels:
    {{- include "labels.standard" . | nindent 8 }}
spec:
  provider: azure
  secretObjects:
  {{- range $key, $val := .Values.php.secrets }}
  - secretName: {{ $env }}-{{ $val }}
    labels:
      {{- include "labels.standard" $ | nindent 8 }}
    type: Opaque
    data:
    - objectName: {{ $key }}
      key: {{ $val }}
  {{- end }}
  parameters:
    usePodIdentity: "false"
    keyvaultName: "{{ .Values.global.keyVaultName }}"
    cloudName: ""
    objects:  |
      array:
        {{- range $key, $val := .Values.php.secrets }}
        - |
          objectName: {{ $val | quote }}
          objectAlias: {{ $key | quote }}
          objectType: secret
          objectVersion: ""
        {{- end }}
    tenantId: {{ .Values.global.tenantId }}
{{- end }}