diff --git a/docs/administration/backup.md b/docs/administration/backup.md index e9d16ace7..de039e67c 100644 --- a/docs/administration/backup.md +++ b/docs/administration/backup.md @@ -6,9 +6,31 @@ Persistent data of squest are: - media folder (used to store images) An integrated backup solution based on [django-dbbackup](https://django-dbbackup.readthedocs.io/en/master/) is -available. Once enabled, backups are placed in the `/app/backup` folder of the `celery-beat` container. +available. Once enabled, backups are placed in the `/app/backup` folder of one of the django based container. -## Execute a backup manually +!!! note + + Get more info on dbrestore and mediarestore command arguments on the + [official doc](https://django-dbbackup.readthedocs.io/en/master/commands.html#). + +## Using Docker compose + +### Enable automatic backup + +Enable automatic backup by updating your environment configuration file `docker/environment_variables/squest.env`: +```bash +BACKUP_ENABLED=True +``` + +By default, backup is performed every day at 1 AM. + +!!! note + + Follow the full [configuration documentation](../configuration/squest_settings.md) to know all available flags + for the backup service. + + +### Execute a backup manually Execute the command below against the celery-beat container: ```bash @@ -57,22 +79,7 @@ Output example: In this example, data are placed in the mount point `/var/lib/docker/volumes/squest_backup/_data` on the host. Files in this path need to be placed in a safe place. -## Enable automatic backup - -Enable automatic backup by updating your environment configuration file `docker/environment_variables/squest.env`: -```bash -BACKUP_ENABLED=True -``` - -By default, backup is performed every day at 1 AM. - -!!! note - - Follow the full [configuration documentation](../configuration/squest_settings.md) to know all available flags - for the backup service. - - -## Restore +### Restore Start Squest services like for the initial deployment ```bash @@ -100,7 +107,65 @@ docker-compose exec celery-beat python manage.py dbrestore docker-compose exec celery-beat python manage.py mediarestore ``` -!!! note +## Using Kubernetes - Get more info on dbrestore and mediarestore command arguments on the - [official doc](https://django-dbbackup.readthedocs.io/en/master/commands.html#). +### Enable automatic backup + +Enable the backup in `squest.yml` inventory: +```yaml +squest_django: + backup: + enabled: true + crontab: "0 1 * * *" +``` + +Run the deployment playbook +```bash +ansible-playbook -v -i inventory deploy.yml --tags backup +``` + +### Externalize backup via SSH + +This feature is optional. By default, the backup cronjob will place backup file into a PVC. Depending on your K8S environment, you might want to externalize them. +If you want to push those files into an external ssh server you can use the integrated rsync solution. + +```yaml +squest_django: + externalize_backup_via_rsync: # rsync backup files into and external server + enabled: true + crontab: "30 1 * * *" + private_ssh_key: "{{ lookup('file', '/path/to/id_ed25519_squest_k8s_dev') + '\n' }}" + ssh_user: "squest_k8s_dev" + ssh_server: "remote.server.ssh.net" + remote_path: "/backup/squest_k8s_dev/" +``` + +### Execute a backup manually + +Run the `backup.yml` playbook +```bash +ansible-playbook -v -i inventory backup.yml +``` + +This command will execute a job in K8S that add a backup of the database and media files into a PVC. + +### Restore + +To restore Squest. First, deploy the app like for the first deployment using the playbook. + +Once Squest is available, copy backup files into django pod +```bash +kubectl -n squest cp ~/path/to/db-2023-12-06-182115.dump django-54b69fbb48-wrt9j:/app/backup +kubectl -n squest cp ~/path/to/media-2023-12-06-182117.tar django-54b69fbb48-wrt9j:/app/backup +``` + +Check backup is listed +```bash +kubectl -n squest exec -it django-54b69fbb48-wrt9j python manage.py listbackups +``` + +Restore by passing backup file name +```bash +kubectl -n squest exec -it django-54b69fbb48-wrt9j -- python manage.py dbrestore --database default -i db-2023-12-06-182115.dump +kubectl -n squest exec -it django-54b69fbb48-wrt9j -- python manage.py mediarestore -i media-2023-12-06-182117.tar +``` diff --git a/docs/administration/upgrade.md b/docs/administration/upgrade.md index 32b12ed76..8441b4f61 100644 --- a/docs/administration/upgrade.md +++ b/docs/administration/upgrade.md @@ -4,12 +4,14 @@ This documentation aims at explaining how to perform an upgrade of squest on new !!! note - Read the changelog of the version before performing any update to know what are the breaking changes or specific requirements of the new release. + Read the changelog and release note of the version before performing any update to know what are the breaking changes or specific requirements of the new release. !!! note We recommend performing a manual backup before any upgrade. See the dedicated [backup doc](backup.md) +## Using Docker compose + Stop all containers that use the Squest image ```bash docker-compose kill django celery-worker celery-beat @@ -31,3 +33,23 @@ Start back containers ```bash docker-compose start django celery-worker celery-beat ``` + +## Using Kubernetes + +Change the Squest image version in the inventory +```yaml +squest_django: + image: quay.io/hewlettpackardenterprise/squest: +``` + +Run the update playbook +```bash +ansible-playbook -v -i inventory update.yml +``` + +The playbook will: + +- Redirect the traffic to maintenance page +- Rollout Django containers with the new image +- Execute database migration +- Restore traffic to Squest once the app is back available diff --git a/docs/configuration/openid.md b/docs/configuration/openid.md deleted file mode 100644 index 62baa98dc..000000000 --- a/docs/configuration/openid.md +++ /dev/null @@ -1,16 +0,0 @@ -# OpenID Connect authentication backend - -## Default configuration - -The configuration is loaded from environment variables file placed in the folder `docker/environment_variables`. - -Retrieve environment variables from the [Squest configuration settings documentation](../configuration/squest_settings.md#openid-connect) - -Configuration example: -```bash -SOCIAL_AUTH_OIDC_ENABLED=True -SOCIAL_AUTH_OIDC_BTN_TEXT="OpenID Login" -SOCIAL_AUTH_OIDC_OIDC_ENDPOINT="https://example.com/" -SOCIAL_AUTH_OIDC_KEY="client_id" -SOCIAL_AUTH_OIDC_SECRET="secret" -``` \ No newline at end of file diff --git a/docs/configuration/squest_settings.md b/docs/configuration/squest_settings.md index aca0cf5e3..4abdc0913 100644 --- a/docs/configuration/squest_settings.md +++ b/docs/configuration/squest_settings.md @@ -3,7 +3,9 @@ Default settings are configured to provide a testing/development environment. For a production setup it is recommended to adjust them following your target environment. -The configuration is loaded from environment variables file placed in the folder `docker/environment_variables`. +When using docker-compose, the configuration is loaded from environment variables file placed in the folder `docker/environment_variables`. + +When using Kubernetes, the configuration need to be placed in the `squest.yml` inventory file in the variable `squest_django/env`. ## Database @@ -359,4 +361,3 @@ Redis hostname. **Default:** `6379` Redis port. - diff --git a/docs/getting_started.md b/docs/getting_started.md index 860454aab..66d5700e1 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -1,29 +1,4 @@ -Pre-requisites: - -- docker -- docker-compose -- Instance of Red Hat Ansible Automation Platform or AWX - -## Deploy Squest - -The current deployment is based on Docker Compose. To run the application, execute the `docker-compose.yml` file: -```bash -docker-compose up -``` - -Then connect with your web browser to [http://127.0.0.1:8080](http://127.0.0.1:8080) -The default admin account is `admin // admin` - -The default export the port 8080. If you want to use the standard HTTP port 80, update the -file `docker-compose.override.yml`. -```yaml -services: - nginx: - ports: - - "80:8080" -``` - ## Connect Squest to your controller The first step consist into adding a **backend controller** (RHAAP/AWX). diff --git a/docs/configuration/tls.md b/docs/installation/docker-compose.md similarity index 70% rename from docs/configuration/tls.md rename to docs/installation/docker-compose.md index f5f3f895d..305e20433 100644 --- a/docs/configuration/tls.md +++ b/docs/installation/docker-compose.md @@ -1,13 +1,36 @@ -# TLS +# Docker Compose deployment -This section explains how to add TLS support on Squest. +## Deploy Squest app + +The docker-compose based deployment is a good way to easily and quickly test Squest. This way of deploying is also stable enough to be used in production. + +To run the Squest application, execute the `docker-compose.yml` file: +```bash +docker-compose up +``` + +Then connect with your web browser to [http://127.0.0.1:8080](http://127.0.0.1:8080) +The default admin account is `admin // admin` + +The default export the port 8080. If you want to use the standard HTTP port 80, update the +file `docker-compose.override.yml`. +```yaml +services: + nginx: + ports: + - "80:8080" +``` + +## TLS + +This section explains how to add TLS support on Squest when using docker-compose based deployment. The TLS endpoint is managed by a reverse proxy on top of the default web server. This is not the only way to handle this part. Many tools like Nginx, Apache or Traefik could be used, and you are free to use the one you want instead of this proposed configuration. The only recommendation we have is to keep the default nginx web server as main http entrypoint. -## TLS using Caddy +### Using Caddy [Caddy](https://caddyserver.com/) is a powerful webserver written in Go which provide a [reverse proxy](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#reverse-proxy) feature. diff --git a/docs/installation/kubernetes.md b/docs/installation/kubernetes.md new file mode 100644 index 000000000..fe26bc03d --- /dev/null +++ b/docs/installation/kubernetes.md @@ -0,0 +1,100 @@ +# Kubernetes deployment + +!!!warning + + This deployment is still a beta feature. Feel free to send pull requests to enhance the deployment or give us feedback though Gitter chat or GitHub discutions. + +## Pre-requisites + +The Kubernetes deployment is wrapped by Ansible. The code has been tested with Ansible version `2.15.5`. + +### Ansible + +Install Ansible dependencies: + +```bash +ansible-galaxy install -r k8s/requirements.yml +``` + +### Python + +Install Python dependencies: +```bash +pip3 install -r k8s/requirements.txt +``` + +### Helm + +Operators installation is handled by Helm. Follow [the official documentation](https://helm.sh/docs/intro/install/) to install Helm on your workstation. + +## Ansible inventory + +An example inventory file is present in `k8s/inventory/group_vars/all/squest.yml`. + +For a minimal installation you need to at least provide information concerning your Kubernetes environment +```yaml +k8s_kubeconfig_path: "/path/to/kubeconfig" +k8s_cluster_fqdn: "k8s.domain.local" +squest_namespace: "squest" +k8s_storage_class: "thin" +``` + +## Deploy Squest using Ansible + +Run the `deploy` playbook against your inventory config file: + +```bash +cd k8s +ansible-playbook -v -i inventory deploy.yml +``` + +**Tags:** + +| Name | Description | +|-------------|---------------------------------------------| +| namespace | Create the Squest namespace | +| utils | Install CRD utils (certmanager, Prometheus) | +| db | Deploy mariadb CRDs, operator and server | +| rabbitmq | Deploy rabbitmq CRDs, operator and service | +| redis | Deploy redis CRDs, operator and service | +| django | Deploy Squest application | +| celery | Deploy Celery components (worker and beat) | +| maintenance | Deploy nginx maintenance pod | +| backup | Deploy backup cron jobs | + + +!!! note + + By default, the deployment uses **nginx ingress controller** to configure the Squest external access on `squest.{{ k8s_cluster_fqdn }}`. + +## Configuration + +### Squest config + +The [Squest configuration](../configuration/squest_settings.md) is injected as environment variables. The environment is placed in `squest.yml` as `env` flag like the following: +```yaml +squest_django: + env: + TZ: "Europe/Paris" + DB_HOST: "mariadb" + DB_PORT: "3306" + REDIS_CACHE_HOST: "rfrm-redis" + DEBUG: "true" + DB_USER: "{{ squest_db.user }}" + DB_PASSWORD: "{{ squest_db.password }}" + WAIT_HOSTS: "mariadb:3306,rabbitmq:5672" +``` + +### Use your own ingress + +By default, the playbook will configure an ingress that point to `squest.{{ k8s_cluster_fqdn }}` based on the [nginx ingress controller](https://docs.nginx.com/nginx-ingress-controller/). + +To expose the Squest URL by using your own ingress controller, you can either update `annotations` (when the target controller can be managed by annotations) or disable the default ingress to declare then your ingress rules on your own. + +To disable the default ingress configuration, in the `squest.yml` inventory file: +```yaml +squest_django: + image: "quay.io/hewlettpackardenterprise/squest:latest" + ingress: + enabled: false +``` diff --git a/docs/configuration/ldap.md b/docs/manual/advanced/ldap.md similarity index 81% rename from docs/configuration/ldap.md rename to docs/manual/advanced/ldap.md index 6bd48f8ea..5c997c83a 100644 --- a/docs/configuration/ldap.md +++ b/docs/manual/advanced/ldap.md @@ -1,9 +1,9 @@ -# LDAP authentication backend +# LDAP ## Default configuration The configuration is loaded from environment variables file placed in the folder `docker/environment_variables`. -Retrieve environment variables from the [Squest configuration settings documentation](../configuration/squest_settings.md#ldap) +Retrieve environment variables from the [Squest configuration settings documentation](../../configuration/squest_settings.md#ldap) ## Advanced configuration @@ -45,6 +45,9 @@ AUTH_LDAP_USER_ATTR_MAP = { } ``` +## Use custom config + +### Docker compose Update the `ldap.docker-compose.yml` file to mount your configuration file and the CA certificate of the LDAP server (if LDAPS is used) in django and celery containers: ```yaml @@ -66,3 +69,23 @@ Run docker compose with the ldap config ```bash docker-compose -f docker-compose.yml -f docker-compose.override.yml -f ldap.docker-compose.yml up ``` + +### Kubernetes + +Declare your custom configuration file in the `squest_django` section of `squest.yml` inventory: + +```yaml +squest_django: + ldap: # extra ldap config + ldap_config_file: "{{ lookup('file', playbook_dir + '/../Squest/ldap_config.py') }}" +``` + +Push the new configuration +``` +ansible-playbook -v -i inventory deploy.yml --tags django +``` + +Rollout django pod +``` +kubectl rollout restart -n squest deployment/django +``` diff --git a/k8s/backup.yml b/k8s/backup.yml new file mode 100644 index 000000000..15e3b87c0 --- /dev/null +++ b/k8s/backup.yml @@ -0,0 +1,68 @@ +# HOW TO RESTORE +# copy backup into django +# kubectl -n squest cp ~/Desktop/db-2023-12-06-182115.dump django-54b69fbb48-wrt9j:/app/backup +# check backup is listed +# kubectl -n squest exec -it django-54b69fbb48-wrt9j python manage.py listbackups +# restore by passing backup file name +# kubectl -n squest exec -it django-54b69fbb48-wrt9j -- python manage.py dbrestore --database default -i db-2023-12-06-182115.dump + +- name: "Execute a one shot backup of Squest" + hosts: localhost + gather_facts: true + + tasks: + - name: Generate job name + ansible.builtin.set_fact: + job_name: "backup-{{ ansible_date_time.iso8601_basic_short }}" + + - name: Create Job from backup cronjob + shell: "kubectl --namespace {{ squest_namespace }} create job --from=cronjob/squest-backup {{ job_name }}" + environment: + KUBECONFIG: "{{ k8s_kubeconfig_path }}" + register: register_backup + failed_when: + - register_backup.rc != 0 + - '"already exists" not in register_backup.stdout' + + - name: Wait until backup complete + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "batch/v1" + kind: Job + name: "{{ job_name }}" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 120 + wait_condition: + type: Complete + status: "True" + + - when: squest_django.externalize_backup_via_rsync.enabled + block: + - name: Generate job name + ansible.builtin.set_fact: + job_name: "rsync-backup-{{ ansible_date_time.iso8601_basic_short }}" + + - name: Create Job from rsync backup cronjob + shell: "kubectl --namespace {{ squest_namespace }} create job --from=cronjob/squest-rsync-backup {{ job_name }}" + environment: + KUBECONFIG: "{{ k8s_kubeconfig_path }}" + register: register_backup + failed_when: + - register_backup.rc != 0 + - '"already exists" not in register_backup.stdout' + + - name: Wait until rsync backup complete + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "batch/v1" + kind: Job + name: "{{ job_name }}" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 120 + wait_condition: + type: Complete + status: "True" diff --git a/k8s/deploy.yml b/k8s/deploy.yml new file mode 100644 index 000000000..162ede13b --- /dev/null +++ b/k8s/deploy.yml @@ -0,0 +1,6 @@ +- name: "Deploy Squest" + hosts: localhost + gather_facts: False + + roles: + - role: squest_k8s diff --git a/k8s/inventory/group_vars/all/squest.yml b/k8s/inventory/group_vars/all/squest.yml new file mode 100644 index 000000000..a34bd4ecf --- /dev/null +++ b/k8s/inventory/group_vars/all/squest.yml @@ -0,0 +1,69 @@ +# Kubernetes +k8s_kubeconfig_path: "/path/to/kubeconfig" +k8s_cluster_fqdn: "k8s.domain.local" +squest_namespace: "squest" +k8s_storage_class: "thin" + +# DATABASE +squest_db: + database: "squest_db" + user: "squest_user" + password: "squest_password" + root_password: "p@ssw0rd" + +# PHPMYADMIN +squest_phpmyadmin: + enabled: true + ingress: + enabled: true + host: "phpmyadmin.{{ k8s_cluster_fqdn }}" + annotation: + kubernetes.io/ingress.class: "nginx" + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + env: + TZ: Europe/Paris + PMA_HOST: mariadb + PMA_ARBITRARY: "1" + MYSQL_USERNAME: root + MYSQL_ROOT_PASSWORD: "{{ squest_db.root_password }}" + +# RABBITMQ +squest_rabbitmq: + user: rabbitmq + password: rabbitmq + +squest_redis: + password: redis_secret_password + +# Django +squest_django: + image: "quay.io/hewlettpackardenterprise/squest:latest" + ingress: # default ingress based on nginx controller + enabled: true + host: "squest.{{ k8s_cluster_fqdn }}" + annotations: + kubernetes.io/ingress.class: "nginx" + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +# ldap: # extra ldap config +# ldap_config_file: "{{ lookup('file', playbook_dir + '/../Squest/ldap_config.py') }}" + env: # squest settings + TZ: "Europe/Paris" + DB_HOST: "mariadb" + DB_PORT: "3306" + REDIS_CACHE_HOST: "redis-standalone" + DEBUG: "true" + DB_USER: "{{ squest_db.user }}" + DB_PASSWORD: "{{ squest_db.password }}" + WAIT_HOSTS: "mariadb:3306,rabbitmq:5672" + backup: # backup squest db and media to a PVC + enabled: false + crontab: "0 1 * * *" + externalize_backup_via_rsync: # rsync backup files into and external server + enabled: false + crontab: "30 1 * * *" + private_ssh_key: "{{ lookup('file', '/path/to/id_ed25519_squest_k8s_dev') + '\n' }}" + ssh_user: "squest_k8s_dev" + ssh_server: "remote.server.ssh.net" + remote_path: "/backup/squest_k8s_dev/" diff --git a/k8s/requirements.txt b/k8s/requirements.txt new file mode 100644 index 000000000..2860d7e21 --- /dev/null +++ b/k8s/requirements.txt @@ -0,0 +1 @@ +kubernetes==24.2.0 diff --git a/k8s/requirements.yml b/k8s/requirements.yml new file mode 100644 index 000000000..59fe50a98 --- /dev/null +++ b/k8s/requirements.yml @@ -0,0 +1,3 @@ +collections: + - name: kubernetes.core + version: 2.3.2 diff --git a/k8s/squest_k8s/defaults/main.yml b/k8s/squest_k8s/defaults/main.yml new file mode 100644 index 000000000..ba643f617 --- /dev/null +++ b/k8s/squest_k8s/defaults/main.yml @@ -0,0 +1,13 @@ +prometheus_operator_crds_chart_version: "8.0.1" + +cert_manager_chart_version: "v1.13.3" + +mariadb_operator_chart_version: "0.23.1" + +rabbitmq_operator_chart_version: "3.10.7" + +rabbitmq_topology_operator_manifest_version: "v1.12.2" +rabbitmq_topology_operator_manifest_url: "https://github.com/rabbitmq/messaging-topology-operator/releases/download/{{ rabbitmq_topology_operator_manifest_version }}/messaging-topology-operator-with-certmanager.yaml" + +# Redis operator: https://github.com/OT-CONTAINER-KIT/redis-operator +redis_operator_chart_version: "0.15.9" diff --git a/k8s/squest_k8s/tasks/01-utils.yml b/k8s/squest_k8s/tasks/01-utils.yml new file mode 100644 index 000000000..8b9eae8f3 --- /dev/null +++ b/k8s/squest_k8s/tasks/01-utils.yml @@ -0,0 +1,72 @@ +- name: Add helm repo for Prometheus + kubernetes.core.helm_repository: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: prometheus-community + repo_url: "https://prometheus-community.github.io/helm-charts" + +- name: Install Prometheus operator CRDs + kubernetes.core.helm: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: prometheus-operator-crds + release_namespace: kube-system + chart_version: "{{ prometheus_operator_crds_chart_version }}" + chart_ref: prometheus-community/prometheus-operator-crds + +- name: Wait until CSV installed + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apiextensions.k8s.io/v1" + kind: CustomResourceDefinition + name: "prometheuses.monitoring.coreos.com" + namespace: "default" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Established + status: "True" + +- name: Add helm repo for Cert Manager + kubernetes.core.helm_repository: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: "jetstack" + repo_url: "https://charts.jetstack.io" + +- name: Install Cert Manager + kubernetes.core.helm: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: cert-manager + release_namespace: cert-manager + create_namespace: true + chart_version: "{{ cert_manager_chart_version }}" + chart_ref: jetstack/cert-manager + values: + installCRDs: true + +- name: Wait until CSV installed + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apiextensions.k8s.io/v1" + kind: CustomResourceDefinition + name: "certificates.cert-manager.io" + namespace: "default" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Established + status: "True" + +- name: Wait until Cert manager deployment available + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apps/v1" + kind: Deployment + name: "cert-manager" + namespace: "cert-manager" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Available + status: "True" diff --git a/k8s/squest_k8s/tasks/02-db.yml b/k8s/squest_k8s/tasks/02-db.yml new file mode 100644 index 000000000..66c8f9809 --- /dev/null +++ b/k8s/squest_k8s/tasks/02-db.yml @@ -0,0 +1,234 @@ +- name: Add helm repo for MariaDB operator + kubernetes.core.helm_repository: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: mariadb-operator + repo_url: "https://mariadb-operator.github.io/mariadb-operator" + +- name: Install MariaDB operator + kubernetes.core.helm: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: mariadb-operator + release_namespace: "{{ squest_namespace }}" + chart_version: "{{ mariadb_operator_chart_version }}" + chart_ref: mariadb-operator/mariadb-operator + +- name: Wait until CSV installed + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apiextensions.k8s.io/v1" + kind: CustomResourceDefinition + name: "mariadbs.mariadb.mmontes.io" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Established + status: "True" + +- name: Wait for MariaDB operator components to be available + loop: + - mariadb-operator + - mariadb-operator-webhook + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apps/v1" + kind: Deployment + name: "{{ item }}" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Available + status: "True" + +- name: Create a secrets for mariadb + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + kind: Secret + apiVersion: v1 + metadata: + name: "mariadb" + labels: + app: squest + service: mariadb + data: + root-password: "{{ squest_db.root_password |b64encode }}" + password: "{{ squest_db.password |b64encode }}" + +- name: Deploy Maria DB + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: mariadb.mmontes.io/v1alpha1 + kind: MariaDB + metadata: + name: mariadb + labels: + app: squest + service: mariadb + spec: + rootPasswordSecretKeyRef: + name: mariadb + key: root-password + database: "{{ squest_db.database }}" + username: "{{ squest_db.user }}" + passwordSecretKeyRef: + name: mariadb + key: password + image: mariadb:11.0.3 + imagePullPolicy: IfNotPresent + port: 3306 + volumeClaimTemplate: + resources: + requests: + storage: 1Gi + accessModes: + - ReadWriteOnce + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 300m + memory: 512Mi + livenessProbe: + exec: + command: + - bash + - -c + - mariadb -u root -p"${MARIADB_ROOT_PASSWORD}" -e "SELECT 1;" + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - bash + - -c + - mariadb -u root -p"${MARIADB_ROOT_PASSWORD}" -e "SELECT 1;" + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + +- name: Wait until MariaDB deployment available + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "mariadb.mmontes.io/v1alpha1" + kind: "MariaDB" + name: "mariadb" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Ready + status: "True" + + +- when: squest_phpmyadmin.enabled + block: + - name: Deploy PHPMyAdmin configmap environment + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: "{{ squest_namespace }}" + labels: + app: squest + service: phpmyadmin + name: phpmyadmin-env + data: "{{ squest_phpmyadmin.env }}" + + - name: PHPMyAdmin deployment + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + definition: + apiVersion: apps/v1 + kind: Deployment + metadata: + namespace: "{{ squest_namespace }}" + labels: + app: squest + service: phpmyadmin + name: phpmyadmin + spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: squest-phpmyadmin + strategy: + type: Recreate + template: + metadata: + labels: + app: squest + app.kubernetes.io/name: squest-phpmyadmin + spec: + containers: + - name: phymyadmin + image: phpmyadmin/phpmyadmin:5.1.3 + envFrom: + - configMapRef: + name: phpmyadmin-env + ports: + - containerPort: 80 + + - name: PHPMyAdmin service + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + definition: + apiVersion: v1 + kind: Service + metadata: + namespace: "{{ squest_namespace }}" + labels: + app: squest + service: phpmyadmin + name: phpmyadmin-service + spec: + ports: + - name: "http" + port: 80 + targetPort: 80 + selector: + app.kubernetes.io/name: squest-phpmyadmin + + - when: squest_phpmyadmin is defined and squest_phpmyadmin.ingress is defined and squest_phpmyadmin.ingress.enabled + name: PHPMyAdmin ingress + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + definition: + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + labels: + app: squest + service: phpmyadmin + namespace: "{{ squest_namespace }}" + name: phpmyadmin-ingress + annotations: "{{ squest_phpmyadmin.ingress.annotation }}" + spec: + rules: + - host: "{{ squest_phpmyadmin.ingress.host }}" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: phpmyadmin-service + port: + number: 80 diff --git a/k8s/squest_k8s/tasks/03-rabbitmq.yml b/k8s/squest_k8s/tasks/03-rabbitmq.yml new file mode 100644 index 000000000..1344f90b3 --- /dev/null +++ b/k8s/squest_k8s/tasks/03-rabbitmq.yml @@ -0,0 +1,179 @@ +- name: Add helm repo for RabbitMQ operator + kubernetes.core.helm_repository: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: bitnami + repo_url: "https://charts.bitnami.com/bitnami" + +- name: Install RabbitMQ operator + kubernetes.core.helm: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: rabbitmq-cluster-operator + release_namespace: "{{ squest_namespace }}" + chart_version: "{{ rabbitmq_operator_chart_version }}" + chart_ref: bitnami/rabbitmq-cluster-operator + +- name: Wait until CSV installed + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apiextensions.k8s.io/v1" + kind: CustomResourceDefinition + name: "rabbitmqclusters.rabbitmq.com" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Established + status: "True" + +- name: Download RabbitMQ messaging-topology-operator manifest + ansible.builtin.get_url: + url: "{{ rabbitmq_topology_operator_manifest_url }}" + dest: "/tmp/rabbitmq_topology_operator_manifest.yaml" + mode: '0664' + +- name: Deploy RabbitMQ messaging-topology-operator + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + src: "/tmp/rabbitmq_topology_operator_manifest.yaml" + +- name: Wait until CSV installed + loop: + - users.rabbitmq.com + - vhosts.rabbitmq.com + - rabbitmqclusters.rabbitmq.com + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apiextensions.k8s.io/v1" + kind: CustomResourceDefinition + name: "{{ item }}" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Established + status: "True" + +- name: RabbitMQ user password secret + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: v1 + kind: Secret + metadata: + name: rabbitmq-user-secret + labels: + app: squest + service: rabbitmq + type: Opaque + stringData: + username: "{{ squest_rabbitmq.user }}" + password: "{{ squest_rabbitmq.password }}" + +- name: RabbitMQ user + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: rabbitmq.com/v1beta1 + kind: User + metadata: + name: rabbitmq-user + labels: + app: squest + service: rabbitmq + spec: + tags: + - administrator + rabbitmqClusterReference: + name: rabbitmq + importCredentialsSecret: + name: rabbitmq-user-secret + +- name: Deploy RabbitMQ cluster + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: rabbitmq.com/v1beta1 + kind: RabbitmqCluster + metadata: + labels: + app: squest + service: rabbitmq + name: rabbitmq + spec: + replicas: 3 + image: rabbitmq:3.10.23-management + service: + type: ClusterIP + persistence: + storageClassName: "{{ k8s_storage_class }}" + storage: 10Gi + resources: + requests: + cpu: 256m + memory: 1Gi + limits: + cpu: 256m + memory: 1Gi + rabbitmq: + additionalPlugins: + - rabbitmq_management + - rabbitmq_peer_discovery_k8s + additionalConfig: | + cluster_formation.peer_discovery_backend = rabbit_peer_discovery_k8s + cluster_formation.k8s.host = kubernetes.default + cluster_formation.k8s.address_type = hostname + vm_memory_high_watermark_paging_ratio = 0.85 + cluster_formation.node_cleanup.interval = 10 + cluster_partition_handling = autoheal + queue_master_locator = min-masters + loopback_users.guest = false + advancedConfig: "" + +- name: RabbitMQ Squest vhost + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: rabbitmq.com/v1beta1 + kind: Vhost + metadata: + name: squest-vhost + labels: + app: squest + service: rabbitmq + spec: + name: squest # vhost name + defaultQueueType: quorum # default queue type for this vhost; require RabbitMQ version 3.11.12 or above + rabbitmqClusterReference: + name: rabbitmq # rabbitmqCluster must exist in the same namespace as this resource + +- name: Squest vHost permissions + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: rabbitmq.com/v1beta1 + kind: Permission + metadata: + name: squest-vhost-permission + spec: + vhost: squest + userReference: + name: rabbitmq-user + permissions: + write: ".*" + configure: ".*" + read: ".*" + rabbitmqClusterReference: + name: rabbitmq diff --git a/k8s/squest_k8s/tasks/04-redis.yml b/k8s/squest_k8s/tasks/04-redis.yml new file mode 100644 index 000000000..a16494033 --- /dev/null +++ b/k8s/squest_k8s/tasks/04-redis.yml @@ -0,0 +1,167 @@ +- name: Add helm repo for Redis operator + kubernetes.core.helm_repository: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: ot-helm + repo_url: "https://ot-container-kit.github.io/helm-charts/" + +- name: Install Redis operator + kubernetes.core.helm: + kubeconfig: "{{ k8s_kubeconfig_path }}" + name: redis-operator + release_namespace: "redis-operator" + create_namespace: true + chart_version: "{{ redis_operator_chart_version }}" + chart_ref: ot-helm/redis-operator + +- name: Wait until CSV installed + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apiextensions.k8s.io/v1" + kind: CustomResourceDefinition + name: "redisreplications.redis.redis.opstreelabs.in" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Established + status: "True" + +- name: Wait until deployment available + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apps/v1" + kind: Deployment + name: "redis-operator" + namespace: "redis-operator" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Available + status: "True" + +- name: Redis password secret + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: v1 + kind: Secret + metadata: + name: redis-secret + labels: + app: squest + service: redis + data: + password: "{{ squest_redis.password | b64encode }}" + +- name: Deploy Redis standalone server + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: redis.redis.opstreelabs.in/v1beta2 + kind: Redis + metadata: + name: redis-standalone + spec: + podSecurityContext: + runAsUser: 1000 + fsGroup: 1000 + kubernetesConfig: + image: quay.io/opstree/redis:v7.0.12 + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 101m + memory: 128Mi + limits: + cpu: 101m + memory: 128Mi + redisSecret: + name: redis-secret + key: password + redisExporter: + enabled: false + image: quay.io/opstree/redis-exporter:v1.44.0 + imagePullPolicy: Always + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 100m + memory: 128Mi + storage: + volumeClaimTemplate: + spec: + # storageClassName: standard + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + +- name: Wait for StatefulSet to be ready + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: v1 + kind: StatefulSet + namespace: "{{ squest_namespace }}" + name: redis-standalone + register: output_info + until: "'readyReplicas' in output_info.resources[0].status and output_info.resources[0].status.readyReplicas >= 1" + delay: 2 + retries: 10 + +#- name: Deploy Redis cluster +# kubernetes.core.k8s: +# kubeconfig: "{{ k8s_kubeconfig_path }}" +# state: present +# namespace: "{{ squest_namespace }}" +# definition: +# apiVersion: redis.redis.opstreelabs.in/v1beta2 +# kind: RedisCluster +# metadata: +# name: redis-cluster +# spec: +# clusterSize: 3 +# clusterVersion: v7 +# podSecurityContext: +# runAsUser: 1000 +# fsGroup: 1000 +# persistenceEnabled: false +# kubernetesConfig: +# image: quay.io/opstree/redis:v7.0.12 +# imagePullPolicy: IfNotPresent +# redisSecret: +# name: redis-secret +# key: password +# redisExporter: +# enabled: false +# image: quay.io/opstree/redis-exporter:v1.44.0 +# storage: +# nodeConfVolume: true +# volumeMount: +# volume: +# - name: data +# emptyDir: +# sizeLimit: 1Gi +# mountPath: +# - mountPath: /data +# name: data +# volumeClaimTemplate: +# spec: +# # storageClassName: standard +# accessModes: ["ReadWriteOnce"] +# resources: +# requests: +# storage: 1Gi +# nodeConfVolumeClaimTemplate: +# spec: +# accessModes: ["ReadWriteOnce"] +# resources: +# requests: +# storage: 1Gi diff --git a/k8s/squest_k8s/tasks/05-django.yml b/k8s/squest_k8s/tasks/05-django.yml new file mode 100644 index 000000000..65986e3bb --- /dev/null +++ b/k8s/squest_k8s/tasks/05-django.yml @@ -0,0 +1,356 @@ +- name: Create a service account for Squest + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + namespace: "{{ squest_namespace }}" + state: present + definition: + kind: ServiceAccount + apiVersion: v1 + metadata: + name: squest-sa + labels: + app: squest + service: django + automountServiceAccountToken: true + +- name: Create a role allowed to get info on jobs + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + namespace: "{{ squest_namespace }}" + state: present + definition: + kind: Role + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: role-get-jobs + labels: + app: squest + service: django + rules: + - verbs: + - list + - watch + - get + apiGroups: + - batch + resources: + - jobs + +- name: Link Squest service account to role + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + namespace: "{{ squest_namespace }}" + state: present + definition: + kind: RoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: django-role-binding + labels: + app: squest + service: django + subjects: + - kind: ServiceAccount + name: squest-sa + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: role-get-jobs + +- name: Django env as config map + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + namespace: "{{ squest_namespace }}" + state: present + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + labels: + app: squest + service: django + name: django-env + data: "{{ squest_django.env }}" + +- name: Persistent volume for static, media and backup + loop: + - "django-static" + - "django-media" + - "squest-backup" + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + namespace: "{{ squest_namespace }}" + state: present + definition: + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + labels: + app: squest + service: django + name: "{{ item }}" + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + +- name: Django database migration job + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: batch/v1 + kind: Job + metadata: + name: django-migrations + labels: + app: squest + service: django + spec: + template: + backoffLimit: 3 + spec: + securityContext: + fsGroup: 999 + containers: + - name: django + image: "{{ squest_django.image }}" + command: ['/bin/bash', '-c'] + args: + - | + echo $UID + echo $USER + echo "" + echo "-----------------------------------------------------" + echo "Wait for required services to start" + /wait + echo "Applying database migration" + python manage.py migrate --database=${DATABASE:-default} + echo "" + echo "-----------------------------------------------------" + echo "Collect static files" + python manage.py collectstatic --noinput + echo "" + echo "-----------------------------------------------------" + echo "Inserting default data" + python manage.py insert_default_data + envFrom: + - configMapRef: + name: django-env + volumeMounts: + - mountPath: /app/static + name: django-static + restartPolicy: Never + volumes: + - name: django-static + readOnly: true + persistentVolumeClaim: + claimName: django-static + +- name: Wait until migration job done + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "batch/v1" + kind: Job + name: "django-migrations" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Complete + status: "True" + +- name: Nginx config + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: "{{ squest_namespace }}" + labels: + app: squest + service: nginx + name: nginx-config + data: + nginx.conf: "{{ lookup('file', playbook_dir + '/../docker/nginx.conf') }}" + +- name: Set the path of the ldap config from the user config or use default + ansible.builtin.set_fact: + ldap_config_file_content: "{% if 'ldap' in squest_django %}{{ squest_django.ldap.ldap_config_file }}{% else %}{{ lookup('file', playbook_dir + '/../Squest/ldap_config.py') }}{% endif %}" + +- name: LDAP config + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + labels: + app: squest + service: ldap + name: ldap-config + data: + ldap_config.py: "{{ ldap_config_file_content }}" + +- name: Deploy Django app + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app: squest + service: django + name: django + spec: + replicas: 1 + selector: + matchLabels: + app: squest + service: django + strategy: + type: Recreate + template: + metadata: + labels: + app: squest + service: django + spec: + serviceAccountName: squest-sa + dnsConfig: + options: + - name: ndots + value: "1" + hostAliases: + - ip: "127.0.0.1" + hostnames: + - "django" # to match nginx config + initContainers: + - name: wait-for-migration + image: ghcr.io/groundnuty/k8s-wait-for:v2.0 + imagePullPolicy: Always + args: + - "job" + - "django-migrations" + containers: + - name: django + image: "{{ squest_django.image }}" + imagePullPolicy: Always + ports: + - containerPort: 8000 + command: ['/bin/bash', '-c'] + args: + - | + echo "Wait for required services to start" + /wait + echo "Starting web server" + gunicorn --bind 0.0.0.0:8000 --pythonpath /app/squest Squest.wsgi + envFrom: + - configMapRef: + name: django-env + - name: nginx + image: nginx:1.23.4-alpine + command: ["nginx", "-c", "/etc/nginx/squest/nginx.conf"] + ports: + - containerPort: 8080 + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx/squest + - mountPath: /app/static + name: django-static + - mountPath: /app/Squest/ldap_config.py + name: ldap-config + restartPolicy: Always + volumes: + - name: django-media + persistentVolumeClaim: + claimName: django-media + - name: django-static + readOnly: true + persistentVolumeClaim: + claimName: django-static + - name: nginx-config + configMap: + name: nginx-config + items: + - key: nginx.conf + path: nginx.conf + - name: ldap-config + configMap: + name: ldap-config + items: + - key: ldap_config.py + path: ldap_config.py + +- name: Django service + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + kind: Service + apiVersion: v1 + metadata: + name: django + spec: + selector: + app: squest + service: django + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + +- when: squest_django.ingress.enabled + name: Squest ingress + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + labels: + app: squest + service: django + name: squest-ingress + annotations: "{{ squest_django.ingress.annotations }}" + spec: + rules: + - host: "{{ squest_django.ingress.host }}" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: django + port: + number: 8080 + +- name: Wait until Django deployment available + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apps/v1" + kind: Deployment + name: "django" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Available + status: "True" diff --git a/k8s/squest_k8s/tasks/06-celery.yml b/k8s/squest_k8s/tasks/06-celery.yml new file mode 100644 index 000000000..cc27a8598 --- /dev/null +++ b/k8s/squest_k8s/tasks/06-celery.yml @@ -0,0 +1,109 @@ +- name: Deploy celery worker + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app: squest + service: celery-worker + name: celery-worker + spec: + replicas: 1 + selector: + matchLabels: + app: squest + service: celery-worker + strategy: + type: Recreate + template: + metadata: + labels: + app: squest + service: celery-worker + spec: + serviceAccountName: squest-sa + dnsConfig: + options: + - name: ndots + value: "1" + initContainers: + - name: wait-for-migration + image: ghcr.io/groundnuty/k8s-wait-for:v2.0 + imagePullPolicy: Always + args: + - "job" + - "django-migrations" + containers: + - name: celery-worker + image: "{{ squest_django.image }}" + imagePullPolicy: Always + command: ['/bin/bash', '-c'] + args: + - | + echo "Wait for required services to start" + /wait + echo "Starting celery worker" + celery -A service_catalog worker -l info + envFrom: + - configMapRef: + name: django-env + restartPolicy: Always + +- name: Deploy celery beat + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app: squest + service: celery-beat + name: celery-beat + spec: + replicas: 1 + selector: + matchLabels: + app: squest + service: celery-beat + strategy: + type: Recreate + template: + metadata: + labels: + app: squest + service: celery-beat + spec: + serviceAccountName: squest-sa + dnsConfig: + options: + - name: ndots + value: "1" + initContainers: + - name: wait-for-migration + image: ghcr.io/groundnuty/k8s-wait-for:v2.0 + imagePullPolicy: Always + args: + - "job" + - "django-migrations" + containers: + - name: celery-beat + image: "{{ squest_django.image }}" + imagePullPolicy: Always + command: ['/bin/bash', '-c'] + args: + - | + echo "Wait for required services to start" + /wait + echo "Starting celery beat" + celery -A service_catalog beat -l info + envFrom: + - configMapRef: + name: django-env + restartPolicy: Always diff --git a/k8s/squest_k8s/tasks/07-maintenance.yml b/k8s/squest_k8s/tasks/07-maintenance.yml new file mode 100644 index 000000000..51add00aa --- /dev/null +++ b/k8s/squest_k8s/tasks/07-maintenance.yml @@ -0,0 +1,93 @@ +- name: Nginx maintenance config + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + namespace: "{{ squest_namespace }}" + labels: + app: squest + service: maintenance + name: nginx-config-maintenance + data: + nginx.conf: "{{ lookup('file', playbook_dir + '/../docker/maintenance.nginx.conf') }}" + maintenance.html: "{{ lookup('file', playbook_dir + '/../docker/maintenance.html') }}" + binaryData: + squest_logo_v2_300_300.png: "{{ lookup('file', playbook_dir + '/../project-static/squest/img/squest_logo_v2_300_300.png') |b64encode }}" + +- name: Deploy maintenance static page + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app: squest + service: maintenance + name: maintenance + spec: + replicas: 1 + selector: + matchLabels: + app: squest + service: maintenance + strategy: + type: Recreate + template: + metadata: + labels: + app: squest + service: maintenance + spec: + containers: + - name: nginx + image: nginx:1.23.4-alpine + ports: + - containerPort: 80 + volumeMounts: + - name: nginx-config-maintenance + mountPath: /etc/nginx/conf.d/default.conf + subPath: nginx.conf + - name: nginx-config-maintenance + mountPath: /usr/share/nginx/html/index.html + subPath: maintenance.html + - name: nginx-config-maintenance + mountPath: /usr/share/nginx/html/squest_logo_v2_300_300.png + subPath: squest_logo_v2_300_300.png + restartPolicy: Always + volumes: + - name: nginx-config-maintenance + configMap: + name: nginx-config-maintenance + items: + - key: nginx.conf + path: nginx.conf + - key: maintenance.html + path: maintenance.html + - key: squest_logo_v2_300_300.png + path: squest_logo_v2_300_300.png + +- name: Maintenance service + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + kind: Service + apiVersion: v1 + metadata: + name: maintenance + spec: + selector: + app: squest + service: maintenance + ports: + - protocol: TCP + port: 80 + targetPort: 80 diff --git a/k8s/squest_k8s/tasks/08-backup.yml b/k8s/squest_k8s/tasks/08-backup.yml new file mode 100644 index 000000000..9e99ecbcd --- /dev/null +++ b/k8s/squest_k8s/tasks/08-backup.yml @@ -0,0 +1,121 @@ +- when: squest_django.backup.enabled + name: Squest backup + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: batch/v1 + kind: CronJob + metadata: + name: squest-backup + labels: + app: squest + service: backup + spec: + schedule: "{{ squest_django.backup.crontab }}" + jobTemplate: + spec: + template: + spec: + serviceAccountName: squest-sa + restartPolicy: Never + initContainers: + - name: wait-for-migration + image: ghcr.io/groundnuty/k8s-wait-for:v2.0 + imagePullPolicy: Always + args: + - "job" + - "django-migrations" + securityContext: + fsGroup: 999 + containers: + - name: django + image: "{{ squest_django.image }}" + imagePullPolicy: Always + command: ['/bin/bash', '-c'] + args: + - | + echo "Wait for required services to start" + /wait + echo "Run backup" + python manage.py dbbackup --clean + python manage.py mediabackup --clean + envFrom: + - configMapRef: + name: django-env + volumeMounts: + - name: squest-backup + mountPath: /app/backup + volumes: + - name: squest-backup + persistentVolumeClaim: + claimName: squest-backup + +- when: squest_django.externalize_backup_via_rsync.enabled + block: + - name: Create a secret with the private ssh key + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: v1 + kind: Secret + metadata: + name: squest-rsync-ssh-key + labels: + app: squest + service: backup + type: Opaque + data: + ssh.key: "{{ squest_django.externalize_backup_via_rsync.private_ssh_key |b64encode }}" + + - name: Create a cronjob for rsync external copy + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: batch/v1 + kind: CronJob + metadata: + name: squest-rsync-backup + labels: + app: squest + service: backup + spec: + schedule: "{{ squest_django.externalize_backup_via_rsync.crontab }}" + jobTemplate: + spec: + template: + spec: + restartPolicy: Never + dnsConfig: + options: + - name: ndots + value: "1" + containers: + - name: rsync + image: instrumentisto/rsync-ssh:alpine3.18 + imagePullPolicy: Always + command: ["/bin/sh","-c", "-x"] + args: + - | + rsync \ + -avz --ignore-existing --delete \ + -e "ssh -i /root/.ssh/ssh.key -o StrictHostKeyChecking=no" \ + /app/backup {{ squest_django.externalize_backup_via_rsync.ssh_user }}@{{ squest_django.externalize_backup_via_rsync.ssh_server }}:{{ squest_django.externalize_backup_via_rsync.remote_path }} + volumeMounts: + - name: squest-rsync-ssh-key + mountPath: /root/.ssh/ + - name: squest-backup + mountPath: /app/backup + volumes: + - name: squest-backup + persistentVolumeClaim: + claimName: squest-backup + - name: squest-rsync-ssh-key + secret: + defaultMode: 0600 + secretName: squest-rsync-ssh-key diff --git a/k8s/squest_k8s/tasks/main.yml b/k8s/squest_k8s/tasks/main.yml new file mode 100644 index 000000000..5f5b62546 --- /dev/null +++ b/k8s/squest_k8s/tasks/main.yml @@ -0,0 +1,66 @@ +- name: Create a dedicated namespace for Squest + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + definition: + apiVersion: v1 + kind: Namespace + metadata: + name: "{{ squest_namespace }}" + tags: ["namespace"] + +- name: K8S utils + ansible.builtin.include_tasks: + file: 01-utils.yml + apply: + tags: ["utils"] + tags: ["utils"] + +- name: Squest db + ansible.builtin.include_tasks: + file: 02-db.yml + apply: + tags: ["db"] + tags: ["db"] + +- name: Squest rabbitmq + ansible.builtin.include_tasks: + file: 03-rabbitmq.yml + apply: + tags: ["rabbitmq"] + tags: ["rabbitmq"] + +- name: Squest redis + ansible.builtin.include_tasks: + file: 04-redis.yml + apply: + tags: ["redis"] + tags: ["redis"] + +- name: Squest Django + ansible.builtin.include_tasks: + file: 05-django.yml + apply: + tags: ["django"] + tags: ["django"] + +- name: Squest Celery + ansible.builtin.include_tasks: + file: 06-celery.yml + apply: + tags: ["celery"] + tags: ["celery"] + +- name: Squest maintenance nginx + ansible.builtin.include_tasks: + file: 07-maintenance.yml + apply: + tags: ["maintenance"] + tags: ["maintenance"] + +- name: Squest backup + ansible.builtin.include_tasks: + file: 08-backup.yml + apply: + tags: ["backup"] + tags: ["backup"] diff --git a/k8s/update.yml b/k8s/update.yml new file mode 100644 index 000000000..737a4f164 --- /dev/null +++ b/k8s/update.yml @@ -0,0 +1,180 @@ +- name: "Update Squest" + hosts: localhost + gather_facts: False + + tasks: + - name: Update ingress to point to maintenance pod + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: patched + namespace: "{{ squest_namespace }}" + definition: + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: squest-ingress + annotations: + kubernetes.io/ingress.class: "nginx" + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + spec: + rules: + - host: "{{ squest_django.ingress.host }}" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: maintenance + port: + number: 80 + + - name: Update Django env config map + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + namespace: "{{ squest_namespace }}" + state: patched + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + labels: + app: squest + service: django + name: django-env + data: "{{ squest_django.env }}" + + - name: Delete migration job + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + namespace: "{{ squest_namespace }}" + kind: Job + api_version: batch/v1 + name: "django-migrations" + state: absent + + - name: Create back Django database migration job + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: present + namespace: "{{ squest_namespace }}" + definition: + apiVersion: batch/v1 + kind: Job + metadata: + name: django-migrations + labels: + app: squest + service: django + spec: + template: + backoffLimit: 3 + spec: + securityContext: + fsGroup: 999 + containers: + - name: django + image: "{{ squest_django.image }}" + command: ['/bin/bash', '-c'] + args: + - | + echo $UID + echo $USER + echo "" + echo "-----------------------------------------------------" + echo "Wait for required services to start" + /wait + echo "Applying database migration" + python manage.py migrate --database=${DATABASE:-default} + echo "" + echo "-----------------------------------------------------" + echo "Collect static files" + python manage.py collectstatic --noinput + echo "" + echo "-----------------------------------------------------" + echo "Inserting default data" + python manage.py insert_default_data + envFrom: + - configMapRef: + name: django-env + volumeMounts: + - mountPath: /app/static + name: django-static + - mountPath: /app/media + name: django-media + restartPolicy: Never + volumes: + - name: django-media + persistentVolumeClaim: + claimName: django-media + - name: django-static + persistentVolumeClaim: + claimName: django-static + + - name: Update Squest images and force rollout + loop: + - django + - celery-worker + - celery-beat + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + namespace: "{{ squest_namespace }}" + kind: Deployment + name: "{{ item }}" + state: patched + definition: + spec: + template: + metadata: + annotations: + kubectl.kubernetes.io/restartedAt: "{{ now(fmt='%Y-%m-%dT%H:%M:%SZ') }}" + spec: + containers: + - name: "{{ item }}" + image: "{{ squest_django.image }}" + + - name: Wait until deployments back available + loop: + - django + - celery-worker + - celery-beat + kubernetes.core.k8s_info: + kubeconfig: "{{ k8s_kubeconfig_path }}" + api_version: "apps/v1" + kind: Deployment + name: "{{ item }}" + namespace: "{{ squest_namespace }}" + wait: yes + wait_sleep: 10 + wait_timeout: 600 + wait_condition: + type: Available + status: "True" + + - name: Restore ingress + kubernetes.core.k8s: + kubeconfig: "{{ k8s_kubeconfig_path }}" + state: patched + namespace: "{{ squest_namespace }}" + definition: + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: squest-ingress + annotations: + kubernetes.io/ingress.class: "nginx" + ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + spec: + rules: + - host: "{{ squest_django.ingress.host }}" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: django + port: + number: 8080 diff --git a/mkdocs.yml b/mkdocs.yml index 0b9b79287..5abb5f6e0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -28,12 +28,11 @@ markdown_extensions: nav: - Home: index.md + - Installation: + - Docker compose: installation/docker-compose.md + - Kubernetes: installation/kubernetes.md - Getting Started: getting_started.md - - Configuration: - - Squest: configuration/squest_settings.md - - LDAP: configuration/ldap.md - - TLS: configuration/tls.md - - OpenID Connect: configuration/openid.md + - Configuration: configuration/squest_settings.md - Manual: - Service catalog: - Concept: manual/service_catalog/concept.md @@ -55,6 +54,7 @@ nav: - manual/advanced/filters.md - manual/advanced/jinja.md - manual/advanced/validators.md + - manual/advanced/ldap.md - Notifications: manual/notifications.md - Administration: - Backup: administration/backup.md