diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4795b0..79a0fba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: max-parallel: 4 matrix: python-version: ["3.11"] - + # Service containers to run with `container-job` services: postgres: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2dff50a..5a69168 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,7 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml + exclude: ^deploy/ - id: check-added-large-files - repo: https://github.com/psf/black diff --git a/cloudbuild.yaml b/cloudbuild.yaml index f427397..68a055a 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -6,7 +6,7 @@ steps: [ "build", "-t", - "europe-west1-docker.pkg.dev/$PROJECT_ID/fyj/idr-server-${_DEPLOYMENT_TYPE}:$COMMIT_SHA", + "${_IMAGE_NAME}:$COMMIT_SHA", ".", ] @@ -16,7 +16,7 @@ steps: args: [ "push", - "europe-west1-docker.pkg.dev/$PROJECT_ID/fyj/idr-server-${_DEPLOYMENT_TYPE}:$COMMIT_SHA" + "${_IMAGE_NAME}:$COMMIT_SHA" ] # Apply the latest migrations @@ -24,7 +24,7 @@ steps: name: "gcr.io/google-appengine/exec-wrapper" args: [ - "-i", "europe-west1-docker.pkg.dev/$PROJECT_ID/fyj/idr-server-${_DEPLOYMENT_TYPE}:$COMMIT_SHA", + "-i", "${_IMAGE_NAME}:$COMMIT_SHA", "-s", "${_CLOUDSQL_INSTANCE_CONNECTION_NAME}", "-e", "DJANGO_SETTINGS_MODULE=config.settings.production", "-e", "GOOGLE_CLOUD_PROJECT=$PROJECT_ID", @@ -37,7 +37,7 @@ steps: name: "gcr.io/google-appengine/exec-wrapper" args: [ - "-i", "europe-west1-docker.pkg.dev/$PROJECT_ID/fyj/idr-server-${_DEPLOYMENT_TYPE}:$COMMIT_SHA", + "-i", "${_IMAGE_NAME}:$COMMIT_SHA", "-s", "${_CLOUDSQL_INSTANCE_CONNECTION_NAME}", "-e", "DJANGO_SETTINGS_MODULE=config.settings.production", "-e", "GOOGLE_CLOUD_PROJECT=$PROJECT_ID", @@ -50,7 +50,7 @@ steps: name: "gcr.io/google-appengine/exec-wrapper" args: [ - "-i", "europe-west1-docker.pkg.dev/$PROJECT_ID/fyj/idr-server-${_DEPLOYMENT_TYPE}:$COMMIT_SHA", + "-i", "${_IMAGE_NAME}:$COMMIT_SHA", "-s", "${_CLOUDSQL_INSTANCE_CONNECTION_NAME}", "-e", "DJANGO_SETTINGS_MODULE=config.settings.production", "-e", "GOOGLE_CLOUD_PROJECT=$PROJECT_ID", @@ -58,12 +58,12 @@ steps: "--", "python", "/app/manage.py", "collectstatic", "--noinput" ] - # Collect static files + # Compress static assets - id: "compress static assets" name: "gcr.io/google-appengine/exec-wrapper" args: [ - "-i", "europe-west1-docker.pkg.dev/$PROJECT_ID/fyj/idr-server-${_DEPLOYMENT_TYPE}:$COMMIT_SHA", + "-i", "${_IMAGE_NAME}:$COMMIT_SHA", "-s", "${_CLOUDSQL_INSTANCE_CONNECTION_NAME}", "-e", "DJANGO_SETTINGS_MODULE=config.settings.production", "-e", "GOOGLE_CLOUD_PROJECT=$PROJECT_ID", @@ -71,30 +71,92 @@ steps: "--", "python", "/app/manage.py", "compress" ] - # Deploy an image from Container Registry to Cloud Run - - id: "deploy to cloud run" - name: "gcr.io/cloud-builders/gcloud" - args: [ - "beta", - "run", - "deploy", - "${_SERVICE_NAME}", - "--image", "europe-west1-docker.pkg.dev/$PROJECT_ID/fyj/idr-server-${_DEPLOYMENT_TYPE}:$COMMIT_SHA", - "--region", "europe-west1", - "--platform", "managed", - "--allow-unauthenticated", - "--add-cloudsql-instances", "${_CLOUDSQL_INSTANCE_CONNECTION_NAME}", - "--set-env-vars", "GOOGLE_CLOUD_PROJECT=$PROJECT_ID,SETTINGS_NAME=${_SETTINGS_NAME},DJANGO_SETTINGS_MODULE=config.settings.production,ENV_PATH=/tmp/secrets/.env", - "--min-instances", "1", - "--max-instances", "8", - "--memory", "512M", - "--cpu", "1", - "--set-secrets", "/tmp/secrets/.env=${_SETTINGS_NAME}:latest", - "--timeout", "59m59s" - ] + # Get credentials for the cluster + - id: "get credentials for the cluster" + name: "gcr.io/cloud-builders/kubectl" + env: + - CLOUDSDK_CORE_PROJECT=$PROJECT_ID + - CLOUDSDK_COMPUTE_ZONE=${_GKE_COMPUTE_ZONE} + - CLOUDSDK_CONTAINER_CLUSTER=${_GKE_CLUSTER} + - KUBECONFIG=/workspace/.kube/config + args: + - cluster-info + + # Show kubernetes manifest with values substituted + - id: "show kubernetes manifest" + dir: "deploy" + name: "gcr.io/$PROJECT_ID/helm:3.7.0" + env: + - KUBECONFIG=/workspace/.kube/config + args: + - template + - ${_APP_NAME}-${_DEPLOYMENT_TYPE} + - --namespace=${_NAMESPACE} + - --values + - values.yaml + - --set + - namespace=${_NAMESPACE} + - --set + - django.image.repository=${_IMAGE_NAME} + - --set + - django.image.tag=$COMMIT_SHA + - --set + - ingress.networking.domain=${_DOMAIN_NAME} + - --set + - ingress.networking.issuer.name=${_LETSENCRYPT_SERVER_TYPE} + - --set + - ingress.networking.static_ip_name=${_STATIC_IP_NAME} + - --set + - pg_bouncer.env.db_name=${_PG_NAME} + - --set + - pg_bouncer.env.db_user=${_PG_USER} + - --set + - pg_bouncer.env.db_password=${_PG_PASSWORD} + - --set + - cloud_sql.env.cloudsql_connection_instance=${_CLOUDSQL_INSTANCE_CONNECTION_NAME} + - --set + - django.configmap.config_name=${_CONFIGMAP_FILE} + - . + + # Update essential Variables and Deploy to cluster + - id: "update essential variables and start deployment to cluster" + dir: "deploy" + name: "gcr.io/$PROJECT_ID/helm:3.7.0" + env: + - KUBECONFIG=/workspace/.kube/config + args: + - upgrade + - --install + - ${_APP_NAME}-${_DEPLOYMENT_TYPE} + - --namespace=${_NAMESPACE} + - --values + - values.yaml + - --set + - namespace=${_NAMESPACE} + - --set + - django.image.repository=${_IMAGE_NAME} + - --set + - django.image.tag=$COMMIT_SHA + - --set + - ingress.networking.domain=${_DOMAIN_NAME} + - --set + - ingress.networking.issuer.name=${_LETSENCRYPT_SERVER_TYPE} + - --set + - ingress.networking.static_ip_name=${_STATIC_IP_NAME} + - --set + - pg_bouncer.env.db_name=${_PG_NAME} + - --set + - pg_bouncer.env.db_user=${_PG_USER} + - --set + - pg_bouncer.env.db_password=${_PG_PASSWORD} + - --set + - cloud_sql.env.cloudsql_connection_instance=${_CLOUDSQL_INSTANCE_CONNECTION_NAME} + - --set + - django.configmap.config_name=${_CONFIGMAP_FILE} + - . images: - - "europe-west1-docker.pkg.dev/$PROJECT_ID/fyj/idr-server-${_DEPLOYMENT_TYPE}:$COMMIT_SHA" + - "${_IMAGE_NAME}:$COMMIT_SHA" timeout: 1200s queueTtl: 3600s diff --git a/config/settings/gcp_k8ts.py b/config/settings/gcp_k8ts.py new file mode 100644 index 0000000..7592903 --- /dev/null +++ b/config/settings/gcp_k8ts.py @@ -0,0 +1,228 @@ +import json +import logging + +import sentry_sdk +from google.oauth2 import service_account +from sentry_sdk.integrations.django import DjangoIntegration +from sentry_sdk.integrations.logging import LoggingIntegration + +from .base import * # noqa +from .base import env + +# ----------------------------------------------------------- +# NOTE: Environment Variables have been sourced as configmaps +# ----------------------------------------------------------- + + +############################################################################### +# LOAD GOOGLE CREDENTIALS +############################################################################### + +# Note that when this is not provided and the production environment is Google +# Cloud Run, you will not be able to perform some actions such as signing GCS +# blob URLs. +# See the link below for an example of such an issue: +# https://stackoverflow.com/questions/64234214/how-to-generate-a-blob-signed-url-in-google-cloud-run +GOOGLE_APPLICATION_CREDENTIALS_KEY = env.str( + "GOOGLE_APPLICATION_CREDENTIALS_KEY", default="" +) + +if GOOGLE_APPLICATION_CREDENTIALS_KEY: + GCS_CREDENTIALS = service_account.Credentials.from_service_account_info( + json.loads(GOOGLE_APPLICATION_CREDENTIALS_KEY) + ) + # Set variables that define Google Services Credentials + GS_CREDENTIALS = GCS_CREDENTIALS + + +############################################################################### +# DJANGO DEV PANEL RECOMMENDATIONS AND OTHER SECURITY +############################################################################### + +ALLOWED_HOSTS = env.list( + "DJANGO_ALLOWED_HOSTS", + default=[ + ".fahariyajamii.org", + "cbs.fahariyajamii.org", + "idr.fahariyajamii.org", + "icdr.fahariyajamii.org", + ], +) + +DEBUG = False + +SECRET_KEY = env.str("DJANGO_SECRET_KEY") + + +############################################################################### +# DATABASE CONFIG +############################################################################### + +DATABASES = { + "default": { + "NAME": env.str("POSTGRES_DB"), + "USER": env.str("POSTGRES_USER"), + "PASSWORD": env.str("POSTGRES_PASSWORD"), + "HOST": env.str("POSTGRES_HOST"), + "PORT": env.str("POSTGRES_PORT", default=None), + "ENGINE": "django.contrib.gis.db.backends.postgis", + "ATOMIC_REQUESTS": False, + "CONN_HEALTH_CHECKS": True, + "CONN_MAX_AGE": env.int("CONN_MAX_AGE", default=60), + }, +} + + +############################################################################### +# DATABASE CONFIG +############################################################################### + +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.db.DatabaseCache", + "LOCATION": "cache_table", + } +} + + +############################################################################### +# GOOGLE ANALYTICS +############################################################################### + +GOOGLE_ANALYTICS_ID = env.str("GOOGLE_ANALYTICS_ID") + + +############################################################################### +# SECURITY +############################################################################### + +CSRF_COOKIE_SECURE = True +SECURE_CONTENT_TYPE_NOSNIFF = env.bool( + "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True +) +SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( + "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True +) +SECURE_HSTS_SECONDS = 518400 +SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True) +SESSION_COOKIE_SECURE = True + + +############################################################################### +# STORAGES +############################################################################### + +INSTALLED_APPS += ["storages"] # noqa: F405 +GS_BUCKET_NAME = env.str("DJANGO_GCP_STORAGE_BUCKET_NAME") +GS_DEFAULT_ACL = "projectPrivate" + + +############################################################################### +# STATIC ASSETS AND MEDIA FILES +############################################################################### + +MEDIA_URL = "https://storage.googleapis.com/%s/media/" % GS_BUCKET_NAME +STATIC_URL = "https://storage.googleapis.com/%s/static/" % GS_BUCKET_NAME +STORAGES = { + "default": {"BACKEND": "utils.storages.MediaRootGoogleCloudStorage"}, + "staticfiles": {"BACKEND": "utils.storages.StaticRootGoogleCloudStorage"}, +} + + +############################################################################### +# DJANGO COMPRESSOR +############################################################################### + +COMPRESS_ENABLED = True +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_FILTERS +COMPRESS_FILTERS = { + "css": [ + "compressor.filters.css_default.CssAbsoluteFilter", + "compressor.filters.cssmin.rCSSMinFilter", + ], + "js": ["compressor.filters.jsmin.JSMinFilter"], +} +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_OFFLINE +COMPRESS_OFFLINE = True +# https://django-compressor.readthedocs.io/en/stable/settings.html#django.conf.settings.COMPRESS_OFFLINE_MANIFEST_STORAGE +COMPRESS_OFFLINE_MANIFEST_STORAGE = ( + "utils.storages.StaticRootGoogleCloudStorage" +) +# https://django-compressor.readthedocs.io/en/stable/settings.html#django.conf.settings.COMPRESS_STORAGE +COMPRESS_STORAGE = "utils.storages.StaticRootGoogleCloudStorage" +# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL +COMPRESS_URL = STATIC_URL + + +############################################################################### +# LOGGING +############################################################################### + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": ( + "{levelname}: {asctime} - - {message}" + ), + "style": "{", + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "verbose", + "level": "DEBUG", + } + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "WARNING", + "propagate": True, + }, + "django.db.backends": { + "level": "WARNING", + "handlers": ["console"], + "propagate": False, + }, + # Errors logged by the SDK itself + "sentry_sdk": { + "level": "WARNING", + "handlers": ["console"], + "propagate": False, + }, + "django.security.DisallowedHost": { + "level": "ERROR", + "handlers": ["console"], + "propagate": False, + }, + }, +} + + +############################################################################### +# SENTRY +############################################################################### + +SENTRY_DSN = env.str("SENTRY_DSN") +SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO) + +sentry_logging = LoggingIntegration( + level=SENTRY_LOG_LEVEL, # Capture info and above as breadcrumbs + event_level=logging.ERROR, # Send errors as events +) +integrations = [ + sentry_logging, + DjangoIntegration(), +] +sentry_sdk.init( + dsn=SENTRY_DSN, + integrations=integrations, + environment=env.str("SENTRY_ENVIRONMENT", default="production"), + traces_sample_rate=env.float("SENTRY_TRACES_SAMPLE_RATE", default=1.0), +) diff --git a/config/settings/local.py b/config/settings/local.py index 3a07c76..b5d97fb 100644 --- a/config/settings/local.py +++ b/config/settings/local.py @@ -16,7 +16,7 @@ "NAME": env.str("POSTGRES_DB"), "USER": env.str("POSTGRES_USER"), "PASSWORD": env.str("POSTGRES_PASSWORD"), - "HOST": env.str("POSTGRES_HOST", defualt="localhost"), + "HOST": env.str("POSTGRES_HOST", default="localhost"), "PORT": env.int("POSTGRES_PORT", default=5432), "ENGINE": "django.contrib.gis.db.backends.postgis", "ATOMIC_REQUESTS": True, diff --git a/deploy/Chart.yaml b/deploy/Chart.yaml new file mode 100644 index 0000000..711fa90 --- /dev/null +++ b/deploy/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: idr-server +sources: + - https://github.com/savannahghi/idr-server +description: IDR-Server Deployment Chart + +type: application +version: 0.1.7 +appVersion: "v1.6.5" diff --git a/deploy/ReadMe.md b/deploy/ReadMe.md new file mode 100644 index 0000000..e69de29 diff --git a/deploy/templates/django/deployment.yaml b/deploy/templates/django/deployment.yaml new file mode 100644 index 0000000..a3102ec --- /dev/null +++ b/deploy/templates/django/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.django.app_name }} + namespace: {{ .Values.namespace }} + labels: + app: {{ .Values.django.app_name }} +spec: + replicas: {{ .Values.django.replica_count }} + selector: + matchLabels: + app: {{ .Values.django.app_name }} + template: + metadata: + labels: + app: {{ .Values.django.app_name }} + spec: + containers: + - name: {{ .Values.django.app_name }} + image: {{ .Values.django.image.repository }}:{{ .Values.django.image.tag }} + imagePullPolicy: Always + ports: + - name: http + protocol: TCP + containerPort: {{ .Values.django.image.container_port }} + envFrom: + - configMapRef: + name: {{ .Values.django.configmap.config_name }} + volumeMounts: + - name: env-var-vol + mountPath: /secrets/environment_variables + readOnly: true + + volumes: + - name: env-var-vol + configMap: + name: {{ .Values.django.configmap.config_name }} diff --git a/deploy/templates/django/service.yaml b/deploy/templates/django/service.yaml new file mode 100644 index 0000000..f894eb7 --- /dev/null +++ b/deploy/templates/django/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.django.app_name }}-service + namespace: {{ .Values.namespace }} + labels: + app: {{ .Values.django.app_name }} +spec: + type: {{ .Values.django.service.type }} + selector: + app: {{ .Values.django.app_name }} + ports: + - name: http + protocol: TCP + port: {{ .Values.django.image.container_port }} + targetPort: {{ .Values.django.image.container_port }} diff --git a/deploy/templates/ingress/cert_issuer.yaml b/deploy/templates/ingress/cert_issuer.yaml new file mode 100644 index 0000000..11b4f41 --- /dev/null +++ b/deploy/templates/ingress/cert_issuer.yaml @@ -0,0 +1,20 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ .Values.ingress.networking.issuer.name }} + namespace: {{ .Values.namespace }} +spec: + acme: + server: {{ .Values.ingress.networking.issuer.acme.server }} + email: {{ .Values.ingress.networking.issuer.acme.email }} + privateKeySecretRef: + name: {{ .Values.ingress.networking.issuer.name }} + solvers: + - http01: + ingress: + podTemplate: + metadata: + annotations: + kuma.io/sidecar-injection: 'false' + sidecar.istio.io/inject: 'false' + class: kong diff --git a/deploy/templates/ingress/ingress.yaml b/deploy/templates/ingress/ingress.yaml new file mode 100644 index 0000000..0d401f8 --- /dev/null +++ b/deploy/templates/ingress/ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Values.ingress.app_name }} + namespace: {{ .Values.namespace }} + annotations: + cert-manager.io/issuer: {{ .Values.ingress.networking.issuer.name }} + konghq.com/protocols: "http,https" + konghq.com/https-redirect-status-code: "308" +spec: + ingressClassName: kong + tls: + - secretName: {{ .Values.ingress.app_name }}-tls + hosts: + - {{ .Values.ingress.networking.domain }} + rules: + - host: {{ .Values.ingress.networking.domain }} + http: + paths: + - path: / + pathType: ImplementationSpecific + backend: + service: + name: {{ .Values.django.app_name }}-service + port: + number: {{ .Values.django.image.container_port }} diff --git a/deploy/templates/pg-bouncer/db_configmap.yml b/deploy/templates/pg-bouncer/db_configmap.yml new file mode 100644 index 0000000..18ebc82 --- /dev/null +++ b/deploy/templates/pg-bouncer/db_configmap.yml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.pg_bouncer.app_name }}-db-config +data: + POSTGRESQL_DB: {{ .Values.pg_bouncer.env.db_name }} + POSTGRESQL_USERNAME: {{ .Values.pg_bouncer.env.db_user }} + POSTGRESQL_PASSWORD: {{ .Values.pg_bouncer.env.db_password }} + POSTGRESQL_HOST: {{ .Values.pg_bouncer.env.db_host }} + POSTGRESQL_PORT: {{ .Values.pg_bouncer.env.db_port | toString | quote }} diff --git a/deploy/templates/pg-bouncer/dbuser_configmap.yml b/deploy/templates/pg-bouncer/dbuser_configmap.yml new file mode 100644 index 0000000..ffa2141 --- /dev/null +++ b/deploy/templates/pg-bouncer/dbuser_configmap.yml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.pg_bouncer.app_name }}-user-config +data: + # echo -n '"user" "pa$$worLd"' | base64 + userlist.txt: | + {{ .Values.pg_bouncer.env.db_user | toString | quote }} {{ .Values.pg_bouncer.env.db_password | toString | quote }} diff --git a/deploy/templates/pg-bouncer/deployment.yaml b/deploy/templates/pg-bouncer/deployment.yaml new file mode 100644 index 0000000..d5a3c0c --- /dev/null +++ b/deploy/templates/pg-bouncer/deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.pg_bouncer.app_name }} + namespace: {{ .Values.namespace }} +spec: + replicas: {{ .Values.pg_bouncer.replica_count }} + selector: + matchLabels: + app: {{ .Values.pg_bouncer.app_name }} + template: + metadata: + labels: + app: {{ .Values.pg_bouncer.app_name }} + spec: + containers: + - name: {{ .Values.pg_bouncer.app_name }} + image: {{ .Values.pg_bouncer.image.repository }}:{{ .Values.pg_bouncer.image.tag }} + ports: + - containerPort: {{ .Values.pg_bouncer.service.pgb_port }} + envFrom: + - configMapRef: + name: {{ .Values.pg_bouncer.app_name }}-db-config + volumeMounts: + - name: user-config + mountPath: /bitnami/pgbouncer/conf/userlist.txt + subPath: userlist.txt + - name: pgbouncer-config + mountPath: /bitnami/pgbouncer/conf/pgbouncer.ini + subPath: pgbouncer.ini + - name: cloud-sql-proxy + image: {{ .Values.cloud_sql.image.repository }}:{{ .Values.cloud_sql.image.tag }} + args: + - "--structured-logs" + - "--port=5432" + - "{{ .Values.cloud_sql.env.cloudsql_connection_instance }}" + - "--credentials-file=/secrets/service_account_secrets/service_account.json" + securityContext: + runAsNonRoot: true + volumeMounts: + - name: sa-secret-vol + mountPath: /secrets/service_account_secrets + readOnly: true + volumes: + - name: user-config + configMap: + name: {{ .Values.pg_bouncer.app_name }}-user-config + + - name: pgbouncer-config + configMap: + name: {{ .Values.pg_bouncer.app_name }}-pgb-config + + - name: sa-secret-vol + secret: + secretName: {{ .Values.namespace }}-sa-secrets diff --git a/deploy/templates/pg-bouncer/pgb_configmap.yml b/deploy/templates/pg-bouncer/pgb_configmap.yml new file mode 100644 index 0000000..d6b5da9 --- /dev/null +++ b/deploy/templates/pg-bouncer/pgb_configmap.yml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.pg_bouncer.app_name }}-pgb-config +data: + pgbouncer.ini: | + [databases] + {{ .Values.pg_bouncer.env.db_name }} = host={{ .Values.pg_bouncer.env.db_host }} port=5432 dbname={{ .Values.pg_bouncer.env.db_name }} + + [pgbouncer] + listen_addr = 0.0.0.0 + listen_port = {{ .Values.pg_bouncer.service.pgb_port }} + auth_type = md5 + auth_file = /bitnami/pgbouncer/conf/userlist.txt + pool_mode = transaction + max_client_conn = 2000 + autodb_idle_timeout = 3600 + default_pool_size = 20 + max_db_connections = 200 + max_user_connections = 200 diff --git a/deploy/templates/pg-bouncer/service.yaml b/deploy/templates/pg-bouncer/service.yaml new file mode 100644 index 0000000..9aae673 --- /dev/null +++ b/deploy/templates/pg-bouncer/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.pg_bouncer.app_name }}-service + namespace: {{ .Values.namespace }} + annotations: + cloud.google.com/load-balancer-type: "Internal" + labels: + app: {{ .Values.pg_bouncer.app_name }} +spec: + type: {{ .Values.pg_bouncer.service.type }} + selector: + app: {{ .Values.pg_bouncer.app_name }} + ports: + - name: pgbouncer + protocol: TCP + port: {{ .Values.pg_bouncer.service.pgb_port }} + targetPort: {{ .Values.pg_bouncer.service.pgb_target_port }} diff --git a/deploy/values.yaml b/deploy/values.yaml new file mode 100644 index 0000000..5e66c1f --- /dev/null +++ b/deploy/values.yaml @@ -0,0 +1,53 @@ +namespace: cluster_namespace + +django: + app_name: django + replica_count: 2 + image: + repository: repo + tag: commit_sha + pull_policy: Always + container_port: 8080 + service: + port: 80 + type: NodePort + configmap: + config_name: "config_name" + secrets: + secrets_name: "secrets_name" + +cloud_sql: + app_name: cloudsql-proxy + image: + repository: gcr.io/cloud-sql-connectors/cloud-sql-proxy + tag: "2.6.0" + env: + cloudsql_connection_instance: "cloudsql_connection_instance" + +pg_bouncer: + app_name: pgbouncer + replica_count: 2 + image: + repository: bitnami/pgbouncer + tag: "1.19.1" + service: + type: LoadBalancer + pgb_port: 6432 + pgb_target_port: 6432 + env: + db_host: "127.0.0.1" + db_port: 5432 + db_name: "database_name" + db_user: "database_user" + db_password: "database_password" + +ingress: + app_name: ingress + networking: + domain: "" + static_ip_name: "" + issuer: + name: "letsencrypt-staging" + acme: + server: "https://acme-v02.api.letsencrypt.org/directory" + email: "support@mail.fahariyajamii.org" diff --git a/requirements/production.txt b/requirements/production.txt index f5f57fb..f80d472 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -16,6 +16,8 @@ grpcio~=1.53.0 + + # Others # ----------------------------------------------------------------------------- gunicorn~=20.1.0