From 1a950f0e8997563488e67af25dcbb395aaccc903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel=20Ortu=C3=B1o?= Date: Fri, 27 May 2022 15:30:57 +0200 Subject: [PATCH] added helm chart (#217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miguel Ángel Ortuño --- .gitignore | 4 + CHANGELOG.md | 4 - README.md | 22 ++ config/example.config.yaml | 9 +- helm/Chart.yaml | 26 +++ helm/scripts/install.sh | 12 ++ helm/scripts/uninstall.sh | 6 + helm/scripts/upgrade.sh | 21 ++ helm/sql/postgres.up.psql | 172 ++++++++++++++++ helm/templates/_config-render.tpl | 72 +++++++ helm/templates/_helpers.tpl | 6 + .../clusterrolebinding-default-view.yaml | 13 ++ helm/templates/configmap-jackal.yaml | 8 + .../configmap-pgsql-init-script.yaml | 8 + helm/templates/deployment-jackal.yaml | 146 +++++++++++++ helm/templates/deployment-redis.yaml | 32 +++ helm/templates/service-jackal-internal.yaml | 21 ++ helm/templates/service-redis.yaml | 18 ++ helm/values.yaml | 193 ++++++++++++++++++ pkg/storage/pgsql/blocklist.go | 3 + pkg/storage/pgsql/capabilities.go | 1 + pkg/storage/pgsql/last.go | 2 + pkg/storage/pgsql/locker.go | 2 +- pkg/storage/pgsql/offline.go | 2 + pkg/storage/pgsql/private.go | 2 + pkg/storage/pgsql/repository.go | 2 + pkg/storage/pgsql/roster.go | 7 + pkg/storage/pgsql/user.go | 2 + pkg/storage/pgsql/vcard.go | 2 + 29 files changed, 809 insertions(+), 9 deletions(-) create mode 100644 helm/Chart.yaml create mode 100644 helm/scripts/install.sh create mode 100644 helm/scripts/uninstall.sh create mode 100644 helm/scripts/upgrade.sh create mode 100644 helm/sql/postgres.up.psql create mode 100644 helm/templates/_config-render.tpl create mode 100644 helm/templates/_helpers.tpl create mode 100644 helm/templates/clusterrolebinding-default-view.yaml create mode 100644 helm/templates/configmap-jackal.yaml create mode 100644 helm/templates/configmap-pgsql-init-script.yaml create mode 100644 helm/templates/deployment-jackal.yaml create mode 100644 helm/templates/deployment-redis.yaml create mode 100644 helm/templates/service-jackal-internal.yaml create mode 100644 helm/templates/service-redis.yaml create mode 100644 helm/values.yaml diff --git a/.gitignore b/.gitignore index 8891a815a..02bb93207 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,7 @@ build # BoltDB default database file .jackal.db +# Helm +charts/ +requirements.lock + diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f0058e36..32e97869d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,6 @@ ## jackal - main / unreleased -## 0.59.0 (2022/03/26) - -* [FEATURE] Improve k8s compatibility. [#219](https://github.com/ortuman/jackal/pull/219), [#220](https://github.com/ortuman/jackal/pull/220), - ## 0.58.0 (2022/03/04) * [FEATURE] Added BoltDB repository type. [#212](https://github.com/ortuman/jackal/pull/212) diff --git a/README.md b/README.md index bde1055e1..7dc4f772c 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,28 @@ or environment variable: $ env JACKAL_CONFIG_FILE=/your-custom-path/your-config.yaml jackal ``` +### Helm chart + +To make it easy to install jackal via Helm in Kubernetes a chart has been included into this repository.
+ +After customizing your own [values.yaml](helm/values.yaml) file run the following command to install and configure all required components under `jackal` namespace. + +```sh +sh ./helm/scripts/install .yaml +``` + +In turn, an active chart can be updated by running the upgrade script as follows: + +```sh +sh ./helm/scripts/upgrade .yaml +``` + +On the other hand, you can also remove the jackal chart from your Kubernetes cluster by running the uninstall script: + +```sh +sh ./helm/scripts/uninstall +``` + ### PostgreSQL database creation Create a user and a database for that user: diff --git a/config/example.config.yaml b/config/example.config.yaml index 523ff211e..3e02cc491 100644 --- a/config/example.config.yaml +++ b/config/example.config.yaml @@ -29,7 +29,7 @@ # pgsql: # host: 127.0.0.1:5432 # user: jackal -# password: password +# password: a-secret-key # database: jackal # max_open_conns: 16 # @@ -44,11 +44,12 @@ # kv: # type: etcd # etcd: +# username: root # endpoints: # - http://127.0.0.1:2379 -# -# server: -# port: 14369 + + server: + port: 14369 shapers: - name: super diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 000000000..940ce6575 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: "v1" +name: jackal +version: 1.0.0 +appVersion: v0.59.0 +kubeVersion: "^1.10.0-0" +description: "Instant messaging server for the Extensible Messaging and Presence Protocol (XMPP)." +home: https://github.com/ortuman/jackal +icon: https://raw.githubusercontent.com/ortuman/jackal/main/logos/logo-0.png +sources: + - https://github.com/ortuman/jackal +keywords: + - jackal + - xmpp + - chat + - asynchronous + - messaging +maintainers: + - name: Jackal Maintainers + email: ortuman@gmail.com +dependencies: + - name: etcd + version: 7.0.2 + repository: https://charts.bitnami.com/bitnami + - name: postgresql-ha + version: 8.6.13 + repository: https://charts.bitnami.com/bitnami diff --git a/helm/scripts/install.sh b/helm/scripts/install.sh new file mode 100644 index 000000000..7b3ba59f5 --- /dev/null +++ b/helm/scripts/install.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eufo pipefail + +command -v helm >/dev/null 2>&1 || { echo "helm not installed, aborting." >&2; exit 1; } + +if [ "$#" -eq 0 ] || [ -z "$1" ]; then + echo "A custom values.yaml file must be provided" + exit 1; +fi + +helm install jackal helm/ --dependency-update --create-namespace --namespace=jackal -f "$1" + diff --git a/helm/scripts/uninstall.sh b/helm/scripts/uninstall.sh new file mode 100644 index 000000000..2cbd541ee --- /dev/null +++ b/helm/scripts/uninstall.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eufo pipefail + +command -v helm >/dev/null 2>&1 || { echo "helm not installed, aborting." >&2; exit 1; } + +helm uninstall jackal --namespace=jackal diff --git a/helm/scripts/upgrade.sh b/helm/scripts/upgrade.sh new file mode 100644 index 000000000..527cb9670 --- /dev/null +++ b/helm/scripts/upgrade.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -eufo pipefail + +command -v kubectl >/dev/null 2>&1 || { echo "kubectl not installed, aborting." >&2; exit 1; } +command -v helm >/dev/null 2>&1 || { echo "helm not installed, aborting." >&2; exit 1; } + +if [ $# -eq 0 ] || [ -z $1 ]; then + echo "A custom values.yaml file must be provided" + exit 1; +fi + +export POSTGRESQL_PASSWORD=$(kubectl get secret --namespace "jackal" jackal-postgresql-ha-postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode) +export REPMGR_PASSWORD=$(kubectl get secret --namespace "jackal" jackal-postgresql-ha-postgresql -o jsonpath="{.data.repmgr-password}" | base64 --decode) +export ADMIN_PASSWORD=$(kubectl get secret --namespace "jackal" jackal-postgresql-ha-pgpool -o jsonpath="{.data.admin-password}" | base64 --decode) + +helm upgrade jackal helm/ --dependency-update \ +--set postgresql-ha.postgresql.password=$POSTGRESQL_PASSWORD \ +--set postgresql-ha.postgresql.repmgrPassword=$REPMGR_PASSWORD \ +--set postgresql-ha.pgpool.adminPassword=$ADMIN_PASSWORD \ +--namespace=jackal \ +-f "$1" diff --git a/helm/sql/postgres.up.psql b/helm/sql/postgres.up.psql new file mode 100644 index 000000000..3be5cf8da --- /dev/null +++ b/helm/sql/postgres.up.psql @@ -0,0 +1,172 @@ +/* + Copyright 2022 The jackal Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +-- Functions to manage updated_at timestamps + +CREATE OR REPLACE FUNCTION enable_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- users + +CREATE TABLE IF NOT EXISTS users ( + username VARCHAR(1023) PRIMARY KEY, + h_sha_1 TEXT NOT NULL, + h_sha_256 TEXT NOT NULL, + h_sha_512 TEXT NOT NULL, + h_sha3_512 TEXT NOT NULL, + salt TEXT NOT NULL, + iteration_count INT NOT NULL, + pepper_id VARCHAR(1023) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); + +SELECT enable_updated_at('users'); + +-- last + +CREATE TABLE IF NOT EXISTS last ( + username VARCHAR(1023) PRIMARY KEY, + status TEXT NOT NULl, + seconds BIGINT NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); + +SELECT enable_updated_at('last'); + +-- capabilities + +CREATE TABLE IF NOT EXISTS capabilities ( + node VARCHAR(1023) NOT NULL, + ver VARCHAR(1023) NOT NULL, + features TEXT ARRAY, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + PRIMARY KEY (node, ver) +); + +SELECT enable_updated_at('capabilities'); + +-- offline_messages + +CREATE TABLE IF NOT EXISTS offline_messages ( + id SERIAL PRIMARY KEY, + username VARCHAR(1023) NOT NULL, + message BYTEA NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS i_offline_messages_username ON offline_messages(username); + +-- blocklist_items + +CREATE TABLE IF NOT EXISTS blocklist_items ( + username VARCHAR(1023) NOT NULL, + jid TEXT NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + PRIMARY KEY(username, jid) +); + +SELECT enable_updated_at('blocklist_items'); + +-- private_storage + +CREATE TABLE IF NOT EXISTS private_storage ( + username VARCHAR(1023) NOT NULL, + namespace VARCHAR(512) NOT NULL, + data BYTEA NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + PRIMARY KEY (username, namespace) +); + +SELECT enable_updated_at('private_storage'); + +-- roster_notifications + +CREATE TABLE IF NOT EXISTS roster_notifications ( + contact VARCHAR(1023) NOT NULL, + jid TEXT NOT NULL, + presence BYTEA NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + PRIMARY KEY (contact, jid) +); + +SELECT enable_updated_at('roster_notifications'); + +-- roster_items + +CREATE TABLE IF NOT EXISTS roster_items ( + username VARCHAR(1023) NOT NULL, + jid TEXT NOT NULL, + name TEXT NOT NULL, + subscription TEXT NOT NULL, + groups TEXT ARRAY, + ask BOOL NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + PRIMARY KEY (username, jid) +); + +SELECT enable_updated_at('roster_items'); + +-- roster_versions + +CREATE TABLE IF NOT EXISTS roster_versions ( + username VARCHAR(1023) NOT NULL, + ver INT NOT NULL DEFAULT 1, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + + PRIMARY KEY (username) +); + +SELECT enable_updated_at('roster_versions'); + +-- vcards + +CREATE TABLE IF NOT EXISTS vcards ( + username VARCHAR(1023) PRIMARY KEY, + vcard BYTEA NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() +); + +SELECT enable_updated_at('vcards'); diff --git a/helm/templates/_config-render.tpl b/helm/templates/_config-render.tpl new file mode 100644 index 000000000..f76657636 --- /dev/null +++ b/helm/templates/_config-render.tpl @@ -0,0 +1,72 @@ +###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~### +### jackal configuration file ### +###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~### + +logger: + level: {{ .Values.jackal.config.logger.level }} + +http: + port: {{ .Values.jackal.config.http.port }} + +admin: + port: {{ .Values.jackal.config.admin.port }} + +{{ if .Values.jackal.config.domains }} +hosts: +{{ toYaml .Values.jackal.config.domains | indent 6 }} +{{ end }} + +{{ if .Values.jackal.config.peppers }} +peppers: +{{ toYaml .Values.jackal.config.peppers | indent 6 }} +{{ end }} + +storage: + type: pgsql + pgsql: + host: jackal-postgresql-ha-pgpool.{{ .Release.Namespace }}.svc.cluster.local:5432 + user: jackal + database: jackal + max_open_conns: {{ .Values.jackal.config.storage.maxConns }} + max_idle_conns: {{ .Values.jackal.config.storage.maxIdleConns }} + conn_max_lifetime: {{ .Values.jackal.config.storage.connMaxLifetime }} + conn_max_idle_time: {{ .Values.jackal.config.storage.connMaxIdleTime }} + +{{ if .Values.redis.enabled }} + cache: + type: redis + redis: + srv: _redis._tcp.redis-headless.{{ .Release.Namespace }}.svc.cluster.local +{{ end }} + +cluster: + type: kv + kv: + type: etcd + etcd: + endpoints: + - http://jackal-etcd.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.etcd.containerPorts.client }} + + server: + port: {{ .Values.jackal.config.cluster.server.port }} + +{{ if .Values.jackal.config.shapers }} +shapers: +{{ toYaml .Values.jackal.config.shapers | indent 2 }} +{{ end }} + +c2s: +{{ toYaml .Values.jackal.config.c2s | indent 2 }} + +s2s: +{{ toYaml .Values.jackal.config.s2s | indent 2 }} + +{{ if .Values.jackal.config.modules }} +modules: +{{ toYaml .Values.jackal.config.modules | indent 2 }} +{{ end }} + +{{ if .Values.jackal.config.components }} +components: +{{ toYaml .Values.jackal.config.components | indent 2 }} +{{ end }} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl new file mode 100644 index 000000000..915717edc --- /dev/null +++ b/helm/templates/_helpers.tpl @@ -0,0 +1,6 @@ +{{/* +Calculate the config from structured and unstructred text input +*/}} +{{- define "jackal.calculatedConfig" -}} +{{ include (print $.Template.BasePath "/_config-render.tpl") . }} +{{- end -}} diff --git a/helm/templates/clusterrolebinding-default-view.yaml b/helm/templates/clusterrolebinding-default-view.yaml new file mode 100644 index 000000000..a47bb1f01 --- /dev/null +++ b/helm/templates/clusterrolebinding-default-view.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: jackal-view + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view +subjects: + - kind: ServiceAccount + name: default + namespace: {{ .Release.Namespace }} diff --git a/helm/templates/configmap-jackal.yaml b/helm/templates/configmap-jackal.yaml new file mode 100644 index 000000000..ae0838a0b --- /dev/null +++ b/helm/templates/configmap-jackal.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: jackal-config + namespace: {{ .Release.Namespace }} +data: + config.yaml: |- +{{ include "jackal.calculatedConfig" . | indent 4 }} diff --git a/helm/templates/configmap-pgsql-init-script.yaml b/helm/templates/configmap-pgsql-init-script.yaml new file mode 100644 index 000000000..9288a0676 --- /dev/null +++ b/helm/templates/configmap-pgsql-init-script.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: pgsql-init-script + namespace: {{ .Release.Namespace }} +data: + pgsql.up.sql: |- +{{ .Files.Get "sql/postgres.up.psql" | indent 4 }} diff --git a/helm/templates/deployment-jackal.yaml b/helm/templates/deployment-jackal.yaml new file mode 100644 index 000000000..537f320eb --- /dev/null +++ b/helm/templates/deployment-jackal.yaml @@ -0,0 +1,146 @@ +{{- $replicasCount := int .Values.jackal.replicasCount -}} +{{- $baseNodePort := 30000 -}} +{{- range $i := until $replicasCount }} +--- +apiVersion: v1 +kind: Service +metadata: + name: jackal-{{ $i }} + namespace: {{ $.Release.Namespace }} + labels: + app: jackal-{{ $i }} +spec: + type: NodePort + selector: + app: jackal-{{ $i }} + ports: + {{- range $j, $ln := $.Values.jackal.config.c2s.listeners }} + - port: {{ $ln.port }} + name: c2s-{{ $j }} + targetPort: {{ $ln.port }} + nodePort: {{ $baseNodePort }} + {{- $baseNodePort = add1 $baseNodePort -}} + {{- end }} + {{- range $j, $ln := $.Values.jackal.config.s2s.listeners }} + - port: {{ $ln.port }} + name: s2s-{{ $j }} + targetPort: {{ $ln.port }} + nodePort: {{ $baseNodePort }} + {{- $baseNodePort = add1 $baseNodePort -}} + {{- end }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jackal-{{ $i }} + namespace: {{ $.Release.Namespace }} + labels: + app: jackal-{{ $i }} + heritage: {{ $.Release.Service }} + release: {{ $.Release.Name }} +spec: + replicas: 1 + selector: + matchLabels: + app: jackal-{{ $i }} + strategy: {} + template: + metadata: + annotations: + rollme: {{ randAlphaNum 5 | quote }} + labels: + app: jackal-{{ $i }} + component: jackal + spec: + nodeSelector: + {{- toYaml $.Values.jackal.nodeSelector | nindent 8 }} + affinity: + {{- toYaml $.Values.jackal.affinity | nindent 8 }} + tolerations: + {{- toYaml $.Values.jackal.tolerations | nindent 8 }} + initContainers: + - name: wait-for-etcd + image: groundnuty/k8s-wait-for:v1.3 + imagePullPolicy: Always + args: + - "pod" + - "-lapp.kubernetes.io/name=etcd" + + - name: wait-for-pgpool + image: groundnuty/k8s-wait-for:v1.3 + imagePullPolicy: Always + args: + - "pod" + - "-lapp.kubernetes.io/name=postgresql-ha" + + - name: wait-for-redis + image: groundnuty/k8s-wait-for:v1.3 + imagePullPolicy: Always + args: + - "pod" + - "-lapp=redis" + + containers: + - name: jackal-{{ $i }} + args: + - ./jackal + - -config=/etc/jackal/config.yaml + image: {{ $.Values.jackal.image.repository }}:{{ $.Values.jackal.image.tag }} + imagePullPolicy: {{ $.Values.jackal.image.pullPolicy }} + ports: + {{- range $.Values.jackal.config.c2s.listeners }} + - containerPort: {{ .port }} + {{- end }} + {{- range $.Values.jackal.config.s2s.listeners }} + - containerPort: {{ .port }} + {{- end }} + - containerPort: {{ $.Values.jackal.config.http.port }} + name: http + - containerPort: {{ $.Values.jackal.config.admin.port }} + name: admin + - containerPort: {{ $.Values.jackal.config.cluster.server.port }} + name: cluster + + readinessProbe: + httpGet: + path: /healthz + port: {{ $.Values.jackal.config.http.port }} + periodSeconds: 10 + initialDelaySeconds: 15 + + resources: + {{- toYaml $.Values.jackal.resources | nindent 12 }} + + env: + {{- if $.Values.jackal.env }} + {{ toYaml $.Values.jackal.env | nindent 12}} + {{- end }} + + - name: JACKAL_STORAGE_PGSQL_PASSWORD + valueFrom: + secretKeyRef: + name: jackal-postgresql-ha-postgresql + key: postgresql-password + optional: false + + - name: JACKAL_S2S_DIALBACK_SECRET + value: {{ randAlphaNum 16 }} + + volumeMounts: + {{- if $.Values.jackal.extraVolumeMounts }} + {{ toYaml $.Values.jackal.extraVolumeMounts | nindent 12}} + {{- end }} + + - mountPath: /etc/jackal + name: config-volume + readOnly: true + + volumes: + {{- if $.Values.jackal.extraVolumes }} + {{ toYaml $.Values.jackal.extraVolumes | nindent 8}} + {{- end }} + + - name: config-volume + configMap: + name: jackal-config +{{- end }} diff --git a/helm/templates/deployment-redis.yaml b/helm/templates/deployment-redis.yaml new file mode 100644 index 000000000..1d3656b69 --- /dev/null +++ b/helm/templates/deployment-redis.yaml @@ -0,0 +1,32 @@ +{{ if index .Values.redis.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: {{ $.Release.Namespace }} + labels: + app: redis + heritage: {{ $.Release.Service }} + release: {{ $.Release.Name }} +spec: + replicas: {{ .Values.redis.replicasCount }} + selector: + matchLabels: + app: redis + strategy: {} + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: {{ $.Values.redis.image.repository }}:{{ $.Values.redis.image.tag }} + imagePullPolicy: {{ $.Values.redis.image.pullPolicy }} + ports: + - containerPort: {{ $.Values.redis.port }} + name: redis + + resources: + {{- toYaml $.Values.redis.resources | nindent 12 }} +{{ end }} diff --git a/helm/templates/service-jackal-internal.yaml b/helm/templates/service-jackal-internal.yaml new file mode 100644 index 000000000..f65a66090 --- /dev/null +++ b/helm/templates/service-jackal-internal.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: jackal-internal + namespace: {{ .Release.Namespace }} + labels: + app: jackal +spec: + type: ClusterIP + selector: + component: jackal + ports: + - port: {{ .Values.jackal.config.http.port }} + name: http + targetPort: {{ .Values.jackal.config.http.port }} + - port: {{ .Values.jackal.config.admin.port }} + name: admin + targetPort: {{ .Values.jackal.config.admin.port }} + - port: {{ .Values.jackal.config.cluster.server.port }} + name: cluster + targetPort: {{ .Values.jackal.config.cluster.server.port }} diff --git a/helm/templates/service-redis.yaml b/helm/templates/service-redis.yaml new file mode 100644 index 000000000..c3c0bbb3b --- /dev/null +++ b/helm/templates/service-redis.yaml @@ -0,0 +1,18 @@ +{{ if index .Values.redis.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: redis-headless + namespace: {{ .Release.Namespace }} + labels: + app: redis +spec: + clusterIP: None + type: ClusterIP + selector: + app: redis + ports: + - port: {{ .Values.redis.port }} + protocol: TCP + name: redis +{{ end }} diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 000000000..d437fe9ae --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,193 @@ +# Default values for jackal. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# ////////////// +# Jackal +# ////////////// +jackal: + replicasCount: 2 + + nodeSelector: {} + affinity: {} + tolerations: [] + + extraVolumes: [] + extraVolumeMounts: [] + env: [] + + image: + repository: ortuman/jackal + tag: 0.59.0 + pullPolicy: IfNotPresent + resources: + requests: + cpu: 120m + memory: 64Mi + + config: + logger: + level: debug + + http: + port: 6060 + + admin: + port: 15280 + + #domains: + # - domain: jackal.im + # tls: + # cert_file: /var/jackal/cert/tls.crt + # privkey_file: /var/jackal/cert/tls.key + + #peppers: + # keys: + # v1: a-super-secret-key + # use: v1 + + storage: + maxConns: 16 + maxIdleConns: 0 + connMaxLifetime: 0 + connMaxIdleTime: 0 + + cluster: + server: + port: 14369 + + shapers: + - name: normal + max_sessions: 25 + rate: + limit: 131072 + burst: 65536 + + c2s: + listeners: + - port: 5222 + req_timeout: 60s + transport: socket + sasl: + mechanisms: + - scram_sha_1 + - scram_sha_256 + - scram_sha_512 + - scram_sha3_512 + + - port: 5223 + direct_tls: true + req_timeout: 60s + transport: socket + sasl: + mechanisms: + - scram_sha_1 + - scram_sha_256 + - scram_sha_512 + - scram_sha3_512 + + s2s: + listeners: + - port: 5269 + req_timeout: 60s + max_stanza_size: 131072 + + - port: 5270 + direct_tls: true + req_timeout: 60s + max_stanza_size: 131072 + + out: + dial_timeout: 5s + req_timeout: 60s + max_stanza_size: 131072 + + modules: + enabled: + - roster + - offline + - last # XEP-0012: Last Activity + - disco # XEP-0030: Service Discovery + - private # XEP-0049: Private XML Storage + - vcard # XEP-0054: vcard-temp + - version # XEP-0092: Software Version + - caps # XEP-0115: Entity Capabilities + - blocklist # XEP-0191: Blocking Command + - stream_mgmt # XEP-0198: Stream Management + - ping # XEP-0199: XMPP Ping + - time # XEP-0202: Entity Time + - carbons # XEP-0280: Message Carbons + + version: + show_os: true + + offline: + queue_size: 300 + + ping: + ack_timeout: 90s + interval: 3m + send_pings: true + timeout_action: kill + + components: + # listeners: + # - port: 5275 + # secret: a-super-secret-key + +# ////////////// +# etcd +# ////////////// +etcd: + auth: + rbac: + create: false + persistence: + enabled: false + replicaCount: 2 + resources: + requests: + cpu: 120m + memory: 64Mi + +# ////////////// +# Redis +# ////////////// +redis: + enabled: true + replicasCount: 2 + + image: + repository: redis + tag: 6.2.7 + pullPolicy: IfNotPresent + + resources: + requests: + cpu: 100m + memory: 64Mi + + port: 6379 + +# ////////////// +# PostgresSQL +# ////////////// +postgresql-ha: + postgresql: + syncReplication: true + initdbScriptsCM: pgsql-init-script + replicaCount: 2 + username: jackal + database: jackal + resources: + requests: + cpu: 250m + memory: 256Mi + pgpool: + replicaCount: 2 + resources: + requests: + cpu: 120m + memory: 256Mi + persistence: + size: 2Gi diff --git a/pkg/storage/pgsql/blocklist.go b/pkg/storage/pgsql/blocklist.go index bfdf03e20..d9d8a3410 100644 --- a/pkg/storage/pgsql/blocklist.go +++ b/pkg/storage/pgsql/blocklist.go @@ -33,6 +33,7 @@ type pgSQLBlockListRep struct { func (r *pgSQLBlockListRep) UpsertBlockListItem(ctx context.Context, item *blocklistmodel.Item) error { _, err := sq.Insert(blockListsTableName). + Prefix(noLoadBalancePrefix). Columns("username", "jid"). Values(item.Username, item.Jid). Suffix("ON CONFLICT (username, jid) DO NOTHING"). @@ -43,6 +44,7 @@ func (r *pgSQLBlockListRep) UpsertBlockListItem(ctx context.Context, item *block func (r *pgSQLBlockListRep) DeleteBlockListItem(ctx context.Context, item *blocklistmodel.Item) error { _, err := sq.Delete(blockListsTableName). + Prefix(noLoadBalancePrefix). Where(sq.And{sq.Eq{"username": item.Username}, sq.Eq{"jid": item.Jid}}). RunWith(r.conn). ExecContext(ctx) @@ -66,6 +68,7 @@ func (r *pgSQLBlockListRep) FetchBlockListItems(ctx context.Context, username st func (r *pgSQLBlockListRep) DeleteBlockListItems(ctx context.Context, username string) error { _, err := sq.Delete(blockListsTableName). + Prefix(noLoadBalancePrefix). Where(sq.Eq{"username": username}). RunWith(r.conn). ExecContext(ctx) diff --git a/pkg/storage/pgsql/capabilities.go b/pkg/storage/pgsql/capabilities.go index 2f116a68f..e407f584c 100644 --- a/pkg/storage/pgsql/capabilities.go +++ b/pkg/storage/pgsql/capabilities.go @@ -36,6 +36,7 @@ type pgSQLCapabilitiesRep struct { func (r *pgSQLCapabilitiesRep) UpsertCapabilities(ctx context.Context, caps *capsmodel.Capabilities) error { _, err := sq.Insert(capsTableName). + Prefix(noLoadBalancePrefix). Columns("node", "ver", "features"). Values(caps.Node, caps.Ver, pq.Array(caps.Features)). Suffix("ON CONFLICT (node, ver) DO UPDATE SET features = $3"). diff --git a/pkg/storage/pgsql/last.go b/pkg/storage/pgsql/last.go index 8d1bfe935..eae2b4ff1 100644 --- a/pkg/storage/pgsql/last.go +++ b/pkg/storage/pgsql/last.go @@ -35,6 +35,7 @@ type pgSQLLastRep struct { func (r *pgSQLLastRep) UpsertLast(ctx context.Context, last *lastmodel.Last) error { _, err := sq.Insert(lastTableName). + Prefix(noLoadBalancePrefix). Columns("username", "seconds", "status"). Values(last.Username, last.Seconds, last.Status). Suffix("ON CONFLICT (username) DO UPDATE SET seconds = $2, status = $3"). @@ -63,6 +64,7 @@ func (r *pgSQLLastRep) FetchLast(ctx context.Context, username string) (*lastmod func (r *pgSQLLastRep) DeleteLast(ctx context.Context, username string) error { _, err := sq.Delete(lastTableName). + Prefix(noLoadBalancePrefix). Where(sq.Eq{"username": username}). RunWith(r.conn). ExecContext(ctx) diff --git a/pkg/storage/pgsql/locker.go b/pkg/storage/pgsql/locker.go index af5c1709d..fa1f6713d 100644 --- a/pkg/storage/pgsql/locker.go +++ b/pkg/storage/pgsql/locker.go @@ -32,7 +32,7 @@ func (l *pgSQLLocker) Lock(ctx context.Context, lockID string) error { } var acquired bool - err := l.conn.QueryRowContext(ctx, "SELECT pg_try_advisory_lock(hashtext($1))", lockID).Scan(&acquired) + err := l.conn.QueryRowContext(ctx, "/*NO LOAD BALANCE*/ SELECT pg_try_advisory_lock(hashtext($1))", lockID).Scan(&acquired) switch err { case nil: if acquired { diff --git a/pkg/storage/pgsql/offline.go b/pkg/storage/pgsql/offline.go index 69a1b4b81..89dee42e0 100644 --- a/pkg/storage/pgsql/offline.go +++ b/pkg/storage/pgsql/offline.go @@ -35,6 +35,7 @@ func (r *pgSQLOfflineRep) InsertOfflineMessage(ctx context.Context, message *str return err } q := sq.Insert(offlineMessagesTableName). + Prefix(noLoadBalancePrefix). Columns("username", "message"). Values(username, b) @@ -88,6 +89,7 @@ func (r *pgSQLOfflineRep) FetchOfflineMessages(ctx context.Context, username str func (r *pgSQLOfflineRep) DeleteOfflineMessages(ctx context.Context, username string) error { q := sq.Delete(offlineMessagesTableName). + Prefix(noLoadBalancePrefix). Where(sq.Eq{"username": username}) _, err := q.RunWith(r.conn).ExecContext(ctx) return err diff --git a/pkg/storage/pgsql/private.go b/pkg/storage/pgsql/private.go index 2429ccd3e..b9ffd1a84 100644 --- a/pkg/storage/pgsql/private.go +++ b/pkg/storage/pgsql/private.go @@ -59,6 +59,7 @@ func (r *pgSQLPrivateRep) UpsertPrivate(ctx context.Context, private stravaganza return err } q := sq.Insert(privateStorageTableName). + Prefix(noLoadBalancePrefix). Columns("username", "namespace", "data"). Values(username, namespace, b). Suffix("ON CONFLICT (username, namespace) DO UPDATE SET data = $3") @@ -69,6 +70,7 @@ func (r *pgSQLPrivateRep) UpsertPrivate(ctx context.Context, private stravaganza func (r *pgSQLPrivateRep) DeletePrivates(ctx context.Context, username string) error { _, err := sq.Delete(privateStorageTableName). + Prefix(noLoadBalancePrefix). Where(sq.Eq{"username": username}). RunWith(r.conn). ExecContext(ctx) diff --git a/pkg/storage/pgsql/repository.go b/pkg/storage/pgsql/repository.go index 359ee3c16..3a0acb9f9 100644 --- a/pkg/storage/pgsql/repository.go +++ b/pkg/storage/pgsql/repository.go @@ -28,6 +28,8 @@ import ( "github.com/ortuman/jackal/pkg/storage/repository" ) +const noLoadBalancePrefix = "/*NO LOAD BALANCE*/" + func init() { sq.StatementBuilder = sq.StatementBuilder.PlaceholderFormat(sq.Dollar) } diff --git a/pkg/storage/pgsql/roster.go b/pkg/storage/pgsql/roster.go index 0adfa3385..900a4a973 100644 --- a/pkg/storage/pgsql/roster.go +++ b/pkg/storage/pgsql/roster.go @@ -39,6 +39,7 @@ type pgSQLRosterRep struct { func (r *pgSQLRosterRep) TouchRosterVersion(ctx context.Context, username string) (int, error) { b := sq.Insert(rosterVersionsTableName). + Prefix(noLoadBalancePrefix). Columns("username"). Values(username). Suffix("ON CONFLICT (username) DO UPDATE SET ver = roster_versions.ver + 1"). @@ -71,6 +72,7 @@ func (r *pgSQLRosterRep) FetchRosterVersion(ctx context.Context, username string func (r *pgSQLRosterRep) UpsertRosterItem(ctx context.Context, ri *rostermodel.Item) error { q := sq.Insert(rosterItemsTableName). + Prefix(noLoadBalancePrefix). Columns("username", "jid", "name", "subscription", "groups", "ask"). Values(ri.Username, ri.Jid, ri.Name, ri.Subscription, pq.Array(ri.Groups), ri.Ask). Suffix("ON CONFLICT (username, jid) DO UPDATE SET name = $3, subscription = $4, groups = $5, ask = $6") @@ -81,6 +83,7 @@ func (r *pgSQLRosterRep) UpsertRosterItem(ctx context.Context, ri *rostermodel.I func (r *pgSQLRosterRep) DeleteRosterItem(ctx context.Context, username, jid string) error { _, err := sq.Delete(rosterItemsTableName). + Prefix(noLoadBalancePrefix). Where(sq.And{sq.Eq{"username": username}, sq.Eq{"jid": jid}}). RunWith(r.conn).ExecContext(ctx) return err @@ -88,6 +91,7 @@ func (r *pgSQLRosterRep) DeleteRosterItem(ctx context.Context, username, jid str func (r *pgSQLRosterRep) DeleteRosterItems(ctx context.Context, username string) error { _, err := sq.Delete(rosterItemsTableName). + Prefix(noLoadBalancePrefix). Where(sq.Eq{"username": username}). RunWith(r.conn).ExecContext(ctx) return err @@ -145,6 +149,7 @@ func (r *pgSQLRosterRep) UpsertRosterNotification(ctx context.Context, rn *roste return err } q := sq.Insert(rosterNotificationsTableName). + Prefix(noLoadBalancePrefix). Columns("contact", "jid", "presence"). Values(rn.Contact, rn.Jid, prBytes). Suffix("ON CONFLICT (contact, jid) DO UPDATE SET presence = $3") @@ -155,6 +160,7 @@ func (r *pgSQLRosterRep) UpsertRosterNotification(ctx context.Context, rn *roste func (r *pgSQLRosterRep) DeleteRosterNotification(ctx context.Context, contact, jid string) error { q := sq.Delete(rosterNotificationsTableName). + Prefix(noLoadBalancePrefix). Where(sq.And{sq.Eq{"contact": contact}, sq.Eq{"jid": jid}}) _, err := q.RunWith(r.conn).ExecContext(ctx) return err @@ -162,6 +168,7 @@ func (r *pgSQLRosterRep) DeleteRosterNotification(ctx context.Context, contact, func (r *pgSQLRosterRep) DeleteRosterNotifications(ctx context.Context, contact string) error { q := sq.Delete(rosterNotificationsTableName). + Prefix(noLoadBalancePrefix). Where(sq.Eq{"contact": contact}) _, err := q.RunWith(r.conn).ExecContext(ctx) return err diff --git a/pkg/storage/pgsql/user.go b/pkg/storage/pgsql/user.go index 4041b0921..4df98b694 100644 --- a/pkg/storage/pgsql/user.go +++ b/pkg/storage/pgsql/user.go @@ -56,6 +56,7 @@ func (r *pgSQLUserRep) UpsertUser(ctx context.Context, user *usermodel.User) err user.Scram.PepperId, } q := sq.Insert(usersTableName). + Prefix(noLoadBalancePrefix). Columns(cols...). Values(vals...). Suffix("ON CONFLICT (username) DO UPDATE SET h_sha_1 = $2, h_sha_256 = $3, h_sha_512 = $4, h_sha3_512 = $5, salt = $6, iteration_count = $7, pepper_id = $8") @@ -66,6 +67,7 @@ func (r *pgSQLUserRep) UpsertUser(ctx context.Context, user *usermodel.User) err func (r *pgSQLUserRep) DeleteUser(ctx context.Context, username string) error { _, err := sq.Delete(usersTableName). + Prefix(noLoadBalancePrefix). Where(sq.Eq{"username": username}). RunWith(r.conn). ExecContext(ctx) diff --git a/pkg/storage/pgsql/vcard.go b/pkg/storage/pgsql/vcard.go index dd7d29336..fd5c8856e 100644 --- a/pkg/storage/pgsql/vcard.go +++ b/pkg/storage/pgsql/vcard.go @@ -38,6 +38,7 @@ func (r *pgSQLVCardRep) UpsertVCard(ctx context.Context, vCard stravaganza.Eleme return err } q := sq.Insert(vCardsTableName). + Prefix(noLoadBalancePrefix). Columns("username", "vcard"). Values(username, b). Suffix("ON CONFLICT (username) DO UPDATE SET vcard = $2") @@ -71,6 +72,7 @@ func (r *pgSQLVCardRep) FetchVCard(ctx context.Context, username string) (strava func (r *pgSQLVCardRep) DeleteVCard(ctx context.Context, username string) error { _, err := sq.Delete(vCardsTableName). + Prefix(noLoadBalancePrefix). Where(sq.Eq{"username": username}). RunWith(r.conn). ExecContext(ctx)