GitOps
In diesem Artikel zeige ich wie man mit GitOps (FluxCD) einen Liferay Cluster in Betrieb nehmen kann. Voraussetzung ist das ein Kubernetes Cluster (z.B. mit K3D) mit FluxCD bereits läuft und ein passendes Git Repository mit den Deploy Keys zur Verfügung steht.
Es wird davon ausgegangen das die Verzeichnisse workloads und namespaces von FluxCD überwacht werden und FluxCD entsprechend konfiguriert ist.
Ferner muss die allgemeine Arbeitsweise von GitOps bekannt sein, d.h. es wird davon ausgegangen das sie wissen wie man zum Beispiel die Synchronisation manuell mit fluxctl auslösen kann, um die Arbeitsgeschwindigkeit zu erhöhen.
Ziel ist es einen kleinen Cluster mit 2 bis maximal 4 Instanzen von Liferay CE zu erzeugen.
Liferay Komponenten
Um Liferay CE bereitzustellen, benötigen wir einige Komponenten die wir nun der Reihe nach deployen werden. Wir werden zunächst einen Namespace erstellen und dann die Environment für Liferay erzeugen. Diese besteht aus Elasticsearch und der freien Datenbank MySql in der Version 8.0. Zusätzlich müssen wir für die Konfiguration von Elasticsearch und Liferay ConfigMaps erzeugen, damit alles zusammen funktioniert.
Namespace
Wir wollen simulieren, dass das Liferay in einer Prod Umgebung laufen soll und daher erstellen wir zunächst einen passenden Namespace liferay-prod.
Erstellen sie in dem Git Repository in dem Verzeichnis namespaces folgendes liferay-prod.yaml
apiVersion: v1
kind: Namespace
metadata:
labels:
name: liferay-prod
name: liferay-prod
Nach der Synchronisation durch FluxCD wird in dem Kubernetes Cluster ein neuer Namespace liferay-prod angelegt.
MySql
Für die Datenbank verwende ich ein MySql in der Version 8, da diese in der Compatibility Matrix gelistet wird.
Für die Persistenz wird ein PVC in der Größenordnung von 100MB vorgehalten.
apiVersion: v1
kind: Service
metadata:
name: database
namespace: liferay-prod
spec:
ports:
- port: 3306
selector:
app: database
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: database-data
name: database-data
namespace: liferay-prod
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: database
name: database
namespace: liferay-prod
spec:
replicas: 1
selector:
matchLabels:
app: database
strategy:
type: Recreate
template:
metadata:
labels:
app: database
spec:
containers:
- args:
- mysqld
- --character-set-server=utf8
- --collation-server=utf8_general_ci
- --character-set-filesystem=utf8
env:
- name: MYSQL_DATABASE
value: lportal
- name: MYSQL_PASSWORD
value: roto2
- name: MYSQL_ROOT_PASSWORD
value: root
- name: MYSQL_USER
value: roto2
image: mysql:8.0
resources:
limits:
memory: "512Mi"
cpu: "1"
ports:
- containerPort: 3306
name: database
volumeMounts:
- mountPath: /var/lib/mysql
name: database-data
restartPolicy: Always
volumes:
- name: database-data
persistentVolumeClaim:
claimName: database-data
Elasticsearch
apiVersion: v1
kind: Service
metadata:
name: search
namespace: liferay-prod
spec:
ports:
- port: 9300
selector:
app: search
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: search-claim
name: search-claim
namespace: liferay-prod
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: search
name: search
namespace: liferay-prod
spec:
replicas: 1
selector:
matchLabels:
app: search
strategy:
type: Recreate
template:
metadata:
labels:
app: search
spec:
containers:
- env:
- name: ES_JAVA_OPTS
value: -Xms1g -Xmx1g
- name: LCP_PROJECT_ENVIRONMENT
value: local
- name: LCP_SERVICE_ID
value: search
- name: LCP_SERVICE_SCALE
value: "1"
- name: cluster.routing.allocation.disk.threshold_enabled
value: "false"
- name: discovery.type
value: single-node
image: liferaycloud/elasticsearch:6.8.4-3.0.5
resources:
limits:
memory: "2024Mi"
cpu: "2"
ports:
- containerPort: 9300
name: search
volumeMounts:
- mountPath: /lcp-container
name: search-claim
restartPolicy: Always
volumes:
- name: search-claim
persistentVolumeClaim:
claimName: search-claim
Elasticsearch ConfigMap
Erstellen sie das Kubernetes Manifest com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.cfg in dem workloads Ordner mit folgenden Inhalt:
apiVersion: v1
kind: ConfigMap
metadata:
name: elasticsearchconfiguration.cfg
namespace: liferay-prod
data:
com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.cfg: |
operationMode="REMOTE"
indexNamePrefix="liferay-"
transportAddresses="search.liferay-prod.svc.cluster.local:9300"
clusterName="liferay_cluster"
logExceptionsOnly="false"
Wie das Inlining von Dateien in YAML funktioniert, dazu mehr in einem weiteren Artikel YAML Multiline Collections.
Die Datei wird später in dem Liferay Deployment in den Pfad /mnt/liferay/files/com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.cfg gemounted. Von hier kopiert sich der Liferay Container die Konfiguration von Elasticsearch und wendet sie beim Starten des Containers an. So ist eine Konfiguration von außen möglich.
Liferay
Die liveness und readyness Proben sind hier mit Absicht etwas höher angesetzt, da ich davon ausgehe dass es zum Testen in einer VM gestartet wird. Das heißt aber auch das eine weitere Instanz erst nach 2 Minuten frühestens bereit steht.
Es werden sofort 2 Instanzen gestartet und maximal auf 4 in HPA (Kubernetes Horizontal Pod Autoscaler) erhöht. Das heißt man sollte hier für dieses Beispiel genügend Arbeitspeicher frei haben.
apiVersion: v1
kind: Service
metadata:
labels:
app: liferay
name: liferay--cluster
namespace: liferay-prod
annotations:
traefik.ingress.kubernetes.io/affinity: "true"
traefik.ingress.kubernetes.io/session-cookie-name: "LIFERAY-STICKY"
spec:
ports:
- name: "8080"
port: 8080
selector:
app: liferay
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: liferay-data
name: liferay-data
namespace: liferay-prod
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
app: liferay-config
name: liferay-config
namespace: liferay-prod
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: liferay
name: liferay
namespace: liferay-prod
spec:
selector:
matchLabels:
app: liferay
strategy:
type: RollingUpdate # Updatestrategie auf RollingUpdate setzen
template:
metadata:
labels:
app: liferay
spec:
containers:
- env:
- name: LIFERAY_JAVA_OPTS
value: -Xms2g -Xmx2g
- name: LIFERAY_MODULE_PERIOD_FRAMEWORK_PERIOD_PROPERTIES_PERIOD_OSGI_PERIOD_CONSOLE
value: 0.0.0.0:11311
- name: LIFERAY_WEB_PERIOD_SERVER_PERIOD_DISPLAY_PERIOD_NODE # Anzeige im Footer auf welcher NODE die Seite gerendert worden ist
value: "true"
- name: LIFERAY_REDIRECT_PERIOD_URL_PERIOD_SECURITY_PERIOD_MODE #La usaremos para permitir los redirect en el cluster
value: "domain"
image: liferay/portal:7.3.2-ga3
resources:
requests: #
memory: "2048Mi"
cpu: "2"
limits: #
memory: "4098Mi"
cpu: "3"
name: liferay
ports: # Port 11311 GoGo-Shell
- containerPort: 11311
- containerPort: 8080
readinessProbe: # frühestens die Readyness Probe nach 120 Sekunden durchführen auf http:// c/portal/layout
httpGet:
path: "/c/portal/layout"
port: 8080
initialDelaySeconds: 120
periodSeconds: 15
failureThreshold: 3
successThreshold: 3
livenessProbe: # liveness Proben auch erst nach 120 Sekunden durchführen
tcpSocket:
port: 8080
initialDelaySeconds: 120
periodSeconds: 20
failureThreshold: 3
successThreshold: 1
volumeMounts: # daten und liferay Verzeichniss mounten
- mountPath: /opt/liferay/data
name: liferay-data
- mountPath: /mnt/liferay
name: liferay-config
- mountPath: /mnt/liferay/files/portal-ext.properties
subPath: portal-ext.properties
name: config-portal-ext-properties
- mountPath: /mnt/liferay/files/osgi/configs/com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.cfg
subPath: com.liferay.portal.search.elasticsearch6.configuration.ElasticsearchConfiguration.cfg
name: config-elasticsearch
restartPolicy: Always #politica de reinicio ante errores en el contenedor
volumes: # PVC mappen
- name: liferay-data
persistentVolumeClaim:
claimName: liferay-data
- name: liferay-config
persistentVolumeClaim:
claimName: liferay-config
- name: config-portal-ext-properties
configMap:
name: portal-ext.properties
- name: config-elasticsearch
configMap:
name: elasticsearchconfiguration.cfg
---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: liferay
namespace: liferay-prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: liferay
behavior:
scaleUp:
stabilizationWindowSeconds: 300
scaleDown:
selectPolicy: Disabled
minReplicas: 2
maxReplicas: 4
Portal-ext.properties
Die Konfigurationsdatei portal-ext.properties muss nach /mnt/liferay/files/portal-ext.properties gemappt werden, damit der Liferay Container sie beim Start kopieren und letztlich einbinden kann.
apiVersion: v1
kind: ConfigMap
metadata:
name: portal-ext.properties
namespace: liferay-prod
data:
portal-ext.properties: |
# This is main Liferay configuration file, common (shared) for all Liferay environments.
#
# Liferay Workspace will copy this file into Liferay bundle's root directory (= ${liferay.home})
# when Liferay bundle is being built.
##
## JDBC
##
jdbc.default.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.default.url=jdbc:mysql://database:3306/lportal?dontTrackOpenResources=true&holdResultsOpenOverStatementClose=true&useFastDateParsing=false
jdbc.default.username=roto2
jdbc.default.password=roto2
##
## Retry JDBC connection on portal startup.
##
#
# Set the number of seconds to retry getting a JDBC connection on portal
# startup.
#
retry.jdbc.on.startup.delay=5
#
# Set the max number of times to retry getting a JDBC connection on portal
# startup.
#
retry.jdbc.on.startup.max.retries=5
##
## Company
##
company.default.name=Liferay Kubernetes
#
# This sets the default web ID. Omniadmin users must belong to the company
# with this web ID.
#
company.default.web.id=liferay.com
##
## Servlet Filters
##
#
# If the user can unzip compressed HTTP content, the GZip filter will
# zip up the HTTP content before sending it to the user. This will speed up
# page rendering for users that are on dial up.
#
com.liferay.portal.servlet.filters.gzip.GZipFilter=false
#
# The NTLM filter is used to provide NTLM based single sign on.
#
com.liferay.portal.servlet.filters.sso.ntlm.NtlmFilter=false
#
# The NTLM post filter is used to fix known issues with NTLM and ajax
# requests. See LPS-3795.
#
com.liferay.portal.servlet.filters.sso.ntlm.NtlmPostFilter=false
##
# # Cluster Link
# #
#
# Set this to true to enable the cluster link. This is required if you want
# to cluster indexing and other features that depend on the cluster link.
#
cluster.link.enabled=true
ehcache.cluster.link.replication.enabled=true
#
# Set this property to autodetect the default outgoing IP address so that
# JGroups can bind to it. The property must point to an address that is
# accessible to the portal server, www.google.com, or your local gateway.
#
cluster.link.autodetect.address=database:3306
Ingress
Kubernetes nutzt für das Routing in dem Cluster einen sogenannten Ingress Controller, dieser muss wissen wenn eine HTTP Anfrage kommt an wen er diese weiterreichen muss.
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: nginx-ingress
namespace: liferay-prod
annotations:
traefik.ingress.kubernetes.io/affinity: "true"
spec:
rules:
- host: liferay.kubernetes.com
http:
paths:
- backend:
serviceName: liferay--cluster
servicePort: 8080
path: /
Session Coockie
Damit Liferay in mehren Instanzen in dem Cluster laufen kann, muss sichergestellt sein, dass die Anfragen auf dem Server wieder landen, wo der Benutzer angemeldet ist.
K3S verwendet nicht den Standard Ingress Controller, sondern Treafik als Ingress Controller. Daher weicht hier die Konfiguration für das Sticky Session Cookie etwas ab.
Affinity auf true setzen
In dem Ingress Manifest muss affinity auf true gesetzt werden (s.o.). Mit kubectl describe kann das getestet werden.
k describe ing nginx-ingress -n liferay-prod
Name: nginx-ingress
Namespace: liferay-prod
Address: 172.25.0.4
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
liferay.kubernetes.com
/ liferay--cluster:8080 (10.42.3.10:8080,10.42.3.11:8080,10.42.3.8:8080 + 1 more...)
Annotations: fluxcd.io/sync-checksum: e8c9246233bf4aadd61a46a41e2ca106d4ff6eb6
traefik.ingress.kubernetes.io/affinity: true
Events: <none>
Die Meldung das kein default Controller gesetzt ist, kann ignoriert werden.
Service
Die Konfiguration von Treafik erfolgt über Annotationen im Service. Mehr dazu in der Konfiguration Traefik.
annotations:
traefik.ingress.kubernetes.io/affinity: "true"
traefik.ingress.kubernetes.io/session-cookie-name: "LIFERAY-STICKY"
Cookies überprüfen
Zum Testen prüfen wir mit curl ob das Cookie gesetzt wird.
curl -I http://liferay.kubernetes.com | grep LIFERAY-STICKY