diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 68a055a..b79b6a6 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -82,61 +82,43 @@ steps: args: - cluster-info - # Show kubernetes manifest with values substituted - - id: "show kubernetes manifest" + # 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: - - template + - upgrade + - --install - ${_APP_NAME}-${_DEPLOYMENT_TYPE} + - --create-namespace - --namespace=${_NAMESPACE} + - --atomic + - --dry-run + - --debug - --values - values.yaml - --set - - namespace=${_NAMESPACE} - - --set - - django.image.repository=${_IMAGE_NAME} + - common.project_id=$PROJECT_ID - --set - - django.image.tag=$COMMIT_SHA + - common.cluster_name=${_GKE_CLUSTER} - --set - - ingress.networking.domain=${_DOMAIN_NAME} + - common.compute_zone=${_GKE_COMPUTE_ZONE} - --set - - ingress.networking.issuer.name=${_LETSENCRYPT_SERVER_TYPE} + - common.cluster_namespace=${_NAMESPACE} - --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} + - django.image.repository=${_IMAGE_NAME} - --set - - cloud_sql.env.cloudsql_connection_instance=${_CLOUDSQL_INSTANCE_CONNECTION_NAME} + - django.image.tag=$COMMIT_SHA - --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 + - secret_manager.file_name=${_SETTINGS_NAME} - --set - - namespace=${_NAMESPACE} + - django.env.django_settings_module=${_K8TS_DJANGO_SETTINGS_MODULE} - --set - - django.image.repository=${_IMAGE_NAME} + - django.env.postgres_host=${_K8TS_PG_HOST} - --set - - django.image.tag=$COMMIT_SHA + - django.env.postgres_port=${_K8TS_PG_PORT} - --set - ingress.networking.domain=${_DOMAIN_NAME} - --set @@ -153,6 +135,8 @@ steps: - cloud_sql.env.cloudsql_connection_instance=${_CLOUDSQL_INSTANCE_CONNECTION_NAME} - --set - django.configmap.config_name=${_CONFIGMAP_FILE} + - --set + - common.service_account_key=${_IAM_SERVICE_ACCOUNT_KEY} - . images: diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..8286404 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,6 @@ +### Introduction +**TODO:** +- pre-requisites +- Env vars that must be overriden +- ...manifest files are executed `lexicographically` hence the naming + in the order of dependencies. diff --git a/deploy/ReadMe.md b/deploy/ReadMe.md deleted file mode 100644 index e69de29..0000000 diff --git a/deploy/templates/01_common/01_service_account.yaml b/deploy/templates/01_common/01_service_account.yaml new file mode 100644 index 0000000..8bd3cb6 --- /dev/null +++ b/deploy/templates/01_common/01_service_account.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.common.service_account_name }} + namespace: {{ .Values.common.cluster_namespace }} +type: Opaque +data: + # base64 -w 0 /path/to/sa_key.json + service_account.json: {{ .Values.common.service_account_key }} diff --git a/deploy/templates/pg-bouncer/dbuser_configmap.yml b/deploy/templates/02_pg_bouncer/01_dbuser_configmap.yml similarity index 84% rename from deploy/templates/pg-bouncer/dbuser_configmap.yml rename to deploy/templates/02_pg_bouncer/01_dbuser_configmap.yml index ffa2141..11f23a2 100644 --- a/deploy/templates/pg-bouncer/dbuser_configmap.yml +++ b/deploy/templates/02_pg_bouncer/01_dbuser_configmap.yml @@ -2,6 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: {{ .Values.pg_bouncer.app_name }}-user-config + namespace: {{ .Values.common.cluster_namespace }} data: # echo -n '"user" "pa$$worLd"' | base64 userlist.txt: | diff --git a/deploy/templates/pg-bouncer/db_configmap.yml b/deploy/templates/02_pg_bouncer/02_db_configmap.yml similarity index 88% rename from deploy/templates/pg-bouncer/db_configmap.yml rename to deploy/templates/02_pg_bouncer/02_db_configmap.yml index 18ebc82..a1f53fc 100644 --- a/deploy/templates/pg-bouncer/db_configmap.yml +++ b/deploy/templates/02_pg_bouncer/02_db_configmap.yml @@ -2,6 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: {{ .Values.pg_bouncer.app_name }}-db-config + namespace: {{ .Values.common.cluster_namespace }} data: POSTGRESQL_DB: {{ .Values.pg_bouncer.env.db_name }} POSTGRESQL_USERNAME: {{ .Values.pg_bouncer.env.db_user }} diff --git a/deploy/templates/pg-bouncer/pgb_configmap.yml b/deploy/templates/02_pg_bouncer/03_pgb_configmap.yml similarity index 92% rename from deploy/templates/pg-bouncer/pgb_configmap.yml rename to deploy/templates/02_pg_bouncer/03_pgb_configmap.yml index d6b5da9..87f1266 100644 --- a/deploy/templates/pg-bouncer/pgb_configmap.yml +++ b/deploy/templates/02_pg_bouncer/03_pgb_configmap.yml @@ -2,6 +2,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: {{ .Values.pg_bouncer.app_name }}-pgb-config + namespace: {{ .Values.common.cluster_namespace }} data: pgbouncer.ini: | [databases] diff --git a/deploy/templates/pg-bouncer/deployment.yaml b/deploy/templates/02_pg_bouncer/04_deployment.yaml similarity index 84% rename from deploy/templates/pg-bouncer/deployment.yaml rename to deploy/templates/02_pg_bouncer/04_deployment.yaml index d5a3c0c..4d29ce2 100644 --- a/deploy/templates/pg-bouncer/deployment.yaml +++ b/deploy/templates/02_pg_bouncer/04_deployment.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.pg_bouncer.app_name }} - namespace: {{ .Values.namespace }} + namespace: {{ .Values.common.cluster_namespace }} spec: replicas: {{ .Values.pg_bouncer.replica_count }} selector: @@ -34,12 +34,12 @@ spec: - "--structured-logs" - "--port=5432" - "{{ .Values.cloud_sql.env.cloudsql_connection_instance }}" - - "--credentials-file=/secrets/service_account_secrets/service_account.json" + - "--credentials-file=/secrets/{{ .Values.common.service_account_name }}/service_account.json" securityContext: runAsNonRoot: true volumeMounts: - name: sa-secret-vol - mountPath: /secrets/service_account_secrets + mountPath: "/secrets/{{ .Values.common.service_account_name }}" readOnly: true volumes: - name: user-config @@ -52,4 +52,4 @@ spec: - name: sa-secret-vol secret: - secretName: {{ .Values.namespace }}-sa-secrets + secretName: {{ .Values.common.service_account_name }} diff --git a/deploy/templates/pg-bouncer/service.yaml b/deploy/templates/02_pg_bouncer/05_service.yaml similarity index 90% rename from deploy/templates/pg-bouncer/service.yaml rename to deploy/templates/02_pg_bouncer/05_service.yaml index 9aae673..7009a55 100644 --- a/deploy/templates/pg-bouncer/service.yaml +++ b/deploy/templates/02_pg_bouncer/05_service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: name: {{ .Values.pg_bouncer.app_name }}-service - namespace: {{ .Values.namespace }} + namespace: {{ .Values.common.cluster_namespace }} annotations: cloud.google.com/load-balancer-type: "Internal" labels: diff --git a/deploy/templates/03_django/01_confimap_job.yml b/deploy/templates/03_django/01_confimap_job.yml new file mode 100644 index 0000000..4719ac3 --- /dev/null +++ b/deploy/templates/03_django/01_confimap_job.yml @@ -0,0 +1,104 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: configmap-creator + namespace: {{ .Values.common.cluster_namespace }} +spec: + template: + metadata: + name: configmap-creator + spec: + restartPolicy: OnFailure + containers: + - name: configmap-creator + image: gcr.io/google.com/cloudsdktool/cloud-sdk:459.0.0 + env: + - name: PROJECT_ID + value: {{ .Values.common.project_id }} + - name: CLUSTER_NAME + value: {{ .Values.common.cluster_name }} + - name: COMPUTE_ZONE + value: {{ .Values.common.compute_zone }} + - name: K8TS_DJANGO_SETTINGS_MODULE + value: {{ .Values.django.env.django_settings_module }} + - name: K8TS_POSTGRES_HOST + value: {{ .Values.django.env.postgres_host }} + - name: K8TS_POSTGRES_PORT + value: {{ .Values.django.env.postgres_port | quote }} + # Avoid issues with passwords with special characters + - name: K8TS_POSTGRES_PASSWORD + value: {{ .Values.pg_bouncer.env.db_password }} + - name: SECRET_MANAGER_FILE_NAME + value: {{ .Values.secret_manager.file_name }} + - name: CONFIGMAP_NAME + value: {{ .Values.django.configmap.config_name }} + - name: SERVICE_ACCOUNT_NAME + value: {{ .Values.common.service_account_name }} + command: ["/bin/sh", "-c"] + args: + - | + set -e + # Step 1: Authenticate with Google Cloud using the provided service account key + gcloud auth activate-service-account --key-file=/secrets/${SERVICE_ACCOUNT_NAME}/service_account.json + + # Step 2: Get the credentials for the GKE cluster and set up kubectl configuration. + gcloud container clusters get-credentials ${CLUSTER_NAME} --zone=${COMPUTE_ZONE} --project=$PROJECT_ID + + # Step 3: Fetch the latest version of the specified secret from Secret Manager + gcloud secrets versions access latest --secret=${SECRET_MANAGER_FILE_NAME} > /secrets/test_settings.txt + ENV_VARS="/secrets/test_settings.txt" + . "$ENV_VARS" + MY_CONTEXT=$(kubectl config get-contexts) + CONFIG_VIEW=$(kubectl config view) + echo "MY_CONTEXT: $MY_CONTEXT" + echo "CONFIG_VIEW: $CONFIG_VIEW" + echo "Override some variables that was used to create containers at cloudbuild" + kubectl create configmap $CONFIGMAP_NAME \ + --from-literal=PORT="8080" \ + --from-literal=CONN_MAX_AGE="$CONN_MAX_AGE" \ + --from-literal=COMPRESS_ENABLED="$COMPRESS_ENABLED" \ + --from-literal=DJANGO_ACCOUNT_ALLOW_REGISTRATION="$DJANGO_ACCOUNT_ALLOW_REGISTRATION" \ + --from-literal=DJANGO_ADMIN_URL="$DJANGO_ADMIN_URL" \ + --from-literal=DJANGO_ALLOWED_HOSTS="$DJANGO_ALLOWED_HOSTS" \ + --from-literal=DJANGO_DEBUG="$DJANGO_DEBUG" \ + --from-literal=DJANGO_DEFAULT_FROM_EMAIL="$DJANGO_DEFAULT_FROM_EMAIL" \ + --from-literal=DJANGO_EMAIL_SUBJECT_PREFIX="$DJANGO_EMAIL_SUBJECT_PREFIX" \ + --from-literal=DJANGO_GCP_STORAGE_BUCKET_NAME="$DJANGO_GCP_STORAGE_BUCKET_NAME" \ + --from-literal=DJANGO_READ_DOT_ENV_FILE="$DJANGO_READ_DOT_ENV_FILE" \ + --from-literal=DJANGO_SECRET_KEY="$DJANGO_SECRET_KEY" \ + --from-literal=DJANGO_SECURE_BROWSER_XSS_FILTER="$DJANGO_SECURE_BROWSER_XSS_FILTER" \ + --from-literal=DJANGO_SECURE_CONTENT_TYPE_NOSNIFF="$DJANGO_SECURE_CONTENT_TYPE_NOSNIFF" \ + --from-literal=DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS="$DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS" \ + --from-literal=DJANGO_SECURE_FRAME_DENY="$DJANGO_SECURE_FRAME_DENY" \ + --from-literal=DJANGO_SECURE_SSL_REDIRECT="$DJANGO_SECURE_SSL_REDIRECT" \ + --from-literal=DJANGO_SERVER_EMAIL="$DJANGO_SERVER_EMAIL" \ + --from-literal=DJANGO_SESSION_COOKIE_HTTPONLY="$DJANGO_SESSION_COOKIE_HTTPONLY" \ + --from-literal=DJANGO_SESSION_COOKIE_SECURE="$DJANGO_SESSION_COOKIE_SECURE" \ + --from-literal=DJANGO_SETTINGS_MODULE=${K8TS_DJANGO_SETTINGS_MODULE} \ + --from-literal=GOOGLE_ANALYTICS_ID="$GOOGLE_ANALYTICS_ID" \ + --from-literal=GOOGLE_APPLICATION_CREDENTIALS_KEY="$GOOGLE_APPLICATION_CREDENTIALS_KEY" \ + --from-literal=GOOGLE_CLOUD_PROJECT="$GOOGLE_CLOUD_PROJECT" \ + --from-literal=INSTANCE_CONNECTION_NAME="$INSTANCE_CONNECTION_NAME" \ + --from-literal=MAILGUN_DOMAIN="$MAILGUN_DOMAIN" \ + --from-literal=MAILGUN_API_URL="$MAILGUN_API_URL" \ + --from-literal=MAILGUN_API_KEY="$MAILGUN_API_KEY" \ + --from-literal=POSTGRES_DB="$POSTGRES_DB" \ + --from-literal=POSTGRES_HOST="${K8TS_POSTGRES_HOST}" \ + --from-literal=POSTGRES_PORT="${K8TS_POSTGRES_PORT}" \ + --from-literal=POSTGRES_PASSWORD="${K8TS_POSTGRES_PASSWORD}" \ + --from-literal=POSTGRES_USER="$POSTGRES_USER" \ + --from-literal=PUB_SUB_TOPIC="$PUB_SUB_TOPIC" \ + --from-literal=SENTRY_DSN="$SENTRY_DSN" \ + --from-literal=SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT" \ + --from-literal=USE_DOCKER="$USE_DOCKER" \ + --from-literal=WEB_CONCURRENCY="$WEB_CONCURRENCY" \ + --from-literal=WHITELISTED_DOMAINS="$WHITELISTED_DOMAINS" + + volumeMounts: + - name: service-account-vol + mountPath: "/secrets/{{ .Values.common.service_account_name }}" + readOnly: true + volumes: + - name: service-account-vol + secret: + secretName: {{ .Values.common.service_account_name }} diff --git a/deploy/templates/django/deployment.yaml b/deploy/templates/03_django/02_deployment.yml similarity index 88% rename from deploy/templates/django/deployment.yaml rename to deploy/templates/03_django/02_deployment.yml index a3102ec..566ed6f 100644 --- a/deploy/templates/django/deployment.yaml +++ b/deploy/templates/03_django/02_deployment.yml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.django.app_name }} - namespace: {{ .Values.namespace }} + namespace: {{ .Values.common.cluster_namespace }} labels: app: {{ .Values.django.app_name }} spec: @@ -28,7 +28,7 @@ spec: name: {{ .Values.django.configmap.config_name }} volumeMounts: - name: env-var-vol - mountPath: /secrets/environment_variables + mountPath: "/secrets/{{ .Values.django.configmap.config_name }}" readOnly: true volumes: diff --git a/deploy/templates/django/service.yaml b/deploy/templates/03_django/03_service.yaml similarity index 88% rename from deploy/templates/django/service.yaml rename to deploy/templates/03_django/03_service.yaml index f894eb7..f94cd54 100644 --- a/deploy/templates/django/service.yaml +++ b/deploy/templates/03_django/03_service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: name: {{ .Values.django.app_name }}-service - namespace: {{ .Values.namespace }} + namespace: {{ .Values.common.cluster_namespace }} labels: app: {{ .Values.django.app_name }} spec: diff --git a/deploy/templates/ingress/ingress.yaml b/deploy/templates/04_ingress/01_ingress.yaml similarity index 94% rename from deploy/templates/ingress/ingress.yaml rename to deploy/templates/04_ingress/01_ingress.yaml index 0d401f8..e634ffa 100644 --- a/deploy/templates/ingress/ingress.yaml +++ b/deploy/templates/04_ingress/01_ingress.yaml @@ -2,7 +2,7 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Values.ingress.app_name }} - namespace: {{ .Values.namespace }} + namespace: {{ .Values.common.cluster_namespace }} annotations: cert-manager.io/issuer: {{ .Values.ingress.networking.issuer.name }} konghq.com/protocols: "http,https" diff --git a/deploy/templates/ingress/cert_issuer.yaml b/deploy/templates/04_ingress/02_cert_issuer.yaml similarity index 91% rename from deploy/templates/ingress/cert_issuer.yaml rename to deploy/templates/04_ingress/02_cert_issuer.yaml index 11b4f41..9566b5c 100644 --- a/deploy/templates/ingress/cert_issuer.yaml +++ b/deploy/templates/04_ingress/02_cert_issuer.yaml @@ -2,7 +2,7 @@ apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: {{ .Values.ingress.networking.issuer.name }} - namespace: {{ .Values.namespace }} + namespace: {{ .Values.common.cluster_namespace }} spec: acme: server: {{ .Values.ingress.networking.issuer.acme.server }} diff --git a/deploy/values.yaml b/deploy/values.yaml index 5e66c1f..179df06 100644 --- a/deploy/values.yaml +++ b/deploy/values.yaml @@ -1,4 +1,10 @@ -namespace: cluster_namespace +common: + project_id: "project-id" + cluster_name: "cluster-name" + compute_zone: "compute-zone" + cluster_namespace: "work-namespace" + service_account_key: "base64_encoded_sa_key" + service_account_name: "iam-service-account-key" django: app_name: django @@ -15,6 +21,10 @@ django: config_name: "config_name" secrets: secrets_name: "secrets_name" + env: + django_settings_module: django_settings_module + postgres_host: postgres_host + postgres_port: postgres_port cloud_sql: app_name: cloudsql-proxy @@ -41,6 +51,9 @@ pg_bouncer: db_user: "database_user" db_password: "database_password" +secret_manager: + file_name: secret_manager_file_name + ingress: app_name: ingress networking: