Arsitektur Deployment Apache Fineract
Ringkasan Eksekutif
Apache Fineract mendukung berbagai model deployment yang fleksibel untuk memenuhi berbagai kebutuhan organisasi, dari startup hingga enterprise level. Arsitektur deployment ini dirancang untuk memberikan scalability, reliability, security, dan ease of management dalam berbagai environment dari development hingga production.
Model Deployment Overview
Deployment Strategies Comparison
1. Traditional Deployment (Monolith)
WAR File Deployment
Apache Tomcat Deployment
# Build WAR file
./gradlew :fineract-provider:war
# Deploy to Tomcat
cp fineract-provider/build/libs/fineract-provider.war /opt/tomcat/webapps/fineract.war
# Configure Tomcat context
# $CATALINA_HOME/conf/Catalina/localhost/fineract.xml
<Context path="/fineract"
docBase="/opt/tomcat/webapps/fineract.war"
reloadable="true"
swallowOutput="true">
<Environment name="fineract.datasource.url"
value="jdbc:mysql://localhost:3306/fineract"
type="java.lang.String"/>
<Environment name="fineract.datasource.username"
value="fineract"
type="java.lang.String"/>
<Environment name="fineract.datasource.password"
value="${TOMCAT_PASS}"
type="java.lang.String"/>
<Environment name="fineract.datasource.driver"
value="com.mysql.cj.jdbc.Driver"
type="java.lang.String"/>
<Resource name="jdbc/FineractDS"
auth="Container"
type="javax.sql.DataSource"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/fineract"
username="fineract"
password="${TOMCAT_PASS}"
maxActive="100"
maxIdle="30"
maxWait="10000"/>
<!-- SSL Configuration -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true"
keystoreFile="/opt/ssl/keystore.jks"
keystorePass="changeit"
truststoreFile="/opt/ssl/truststore.jks"
truststorePass="changeit"
clientAuth="false"
maxThreads="200"
scheme="https"
secure="true"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json"/>
</Context>
JBoss/WildFly Deployment
<!-- standalone.xml configuration -->
<subsystem xmlns="urn:jboss:domain:datasources:7.1">
<datasources>
<datasource jndi-name="java:/FineractDS"
pool-name="FineractPool">
<connection-url>jdbc:mysql://localhost:3306/fineract</connection-url>
<driver>mysql</driver>
<pool>
<min-pool-size>10</min-pool-size>
<max-pool-size>100</max-pool-size>
<prefill>true</prefill>
</pool>
<security>
<user-name>fineract</user-name>
<password>${env.DB_PASSWORD}</password>
</security>
<validation>
<valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySqlValidConnectionChecker"/>
<validate-on-match>true</validate-on-match>
<background-validation>true</background-validation>
<background-validation-millis>30000</background-validation-millis>
</validation>
</datasource>
<drivers>
<driver name="mysql" module="com.mysql">
<driver-class>com.mysql.cj.jdbc.Driver</driver-class>
<xa-datasource-class>com.mysql.cj.jdbc.MysqlXADataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>
</subsystem>
<subsystem xmlns="urn:jboss:domain:web:3.0" default-virtual-server="default-host">
<server name="default-server">
<http-listener name="default"
socket-binding="http"
redirect-socket="https"
max-post-size="52428800"/>
<https-listener name="https-listener"
socket-binding="https"
max-post-size="52428800"
verify-client="REQUESTED"/>
<virtual-server name="default-host"
default-web-module="fineract.war">
<alias name="fineract.local"/>
</virtual-server>
</server>
</subsystem>
Database Configuration
-- MySQL Database Setup
CREATE DATABASE fineract CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'fineract'@'%' IDENTIFIED BY '${DB_PASSWORD}';
GRANT ALL PRIVILEGES ON fineract.* TO 'fineract'@'%';
FLUSH PRIVILEGES;
-- Configuration for optimal performance
-- my.cnf
[mysqld]
# Basic settings
port = 3306
bind-address = 127.0.0.1
# Character set
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# InnoDB settings
innodb_buffer_pool_size = 2G
innodb_log_file_size = 256M
innodb_log_buffer_size = 64M
innodb_flush_log_at_trx_commit = 2
innodb_lock_wait_timeout = 50
# Connection settings
max_connections = 200
max_user_connections = 180
wait_timeout = 28800
interactive_timeout = 28800
# Query cache
query_cache_type = 1
query_cache_size = 256M
query_cache_limit = 2M
# Binary logging
log-bin = mysql-bin
binlog_format = ROW
expire_logs_days = 7
sync_binlog = 1
# Slow query log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 2
# Security
local_infile = 0
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO
2. Containerized Deployment
Docker Deployment
Dockerfile
# Multi-stage build for smaller final image
FROM openjdk:21-jdk-slim AS builder
# Set working directory
WORKDIR /app
# Copy Gradle wrapper and dependencies
COPY gradle/ gradle/
COPY gradlew .
COPY buildSrc/ buildSrc/
COPY settings.gradle .
COPY build.gradle .
COPY fineract-provider/build.gradle fineract-provider/
# Make gradlew executable
RUN chmod +x gradlew
# Download dependencies
RUN ./gradlew dependencies --no-daemon
# Build application
COPY . .
RUN ./gradlew :fineract-provider:bootJar --no-daemon
# Production stage
FROM openjdk:21-jre-slim AS production
# Install security updates
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r fineract && \
useradd -r -g fineract fineract
# Set working directory
WORKDIR /app
# Copy JAR from builder stage
COPY --from=builder /app/fineract-provider/build/libs/*.jar app.jar
# Copy configuration files
COPY docker/fineract/application-production.properties application.properties
COPY docker/fineract/logback-spring.xml logback-spring.xml
# Create directories for runtime
RUN mkdir -p /app/logs /app/uploads && \
chown -R fineract:fineract /app
# Switch to non-root user
USER fineract
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/fineract-provider/actuator/health || exit 1
# Expose port
EXPOSE 8080
# JVM options for production
ENV JAVA_OPTS="-Xms512m -Xmx2g -XX:+UseG1GC -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
# Start application
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
Docker Compose Setup
# docker-compose.yml
version: '3.8'
services:
fineract:
build:
context: .
dockerfile: Dockerfile
container_name: fineract
restart: unless-stopped
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=production
- FINERACT_DB_HOST=mysql
- FINERACT_DB_PORT=3306
- FINERACT_DB_NAME=fineract
- FINERACT_DB_USERNAME=fineract
- FINERACT_DB_PASSWORD=${DB_PASSWORD}
- FINERACT_CACHE_HOST=redis
- FINERACT_CACHE_PORT=6379
- FINERACT_MESSAGE_QUEUE_HOST=rabbitmq
- FINERACT_MESSAGE_QUEUE_PORT=5672
volumes:
- fineract_logs:/app/logs
- fineract_uploads:/app/uploads
depends_on:
- mysql
- redis
- rabbitmq
networks:
- fineract-network
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 512M
mysql:
image: mysql:8.0
container_name: fineract-mysql
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=fineract
- MYSQL_USER=fineract
- MYSQL_PASSWORD=${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
- docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
- docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3306:3306"
networks:
- fineract-network
command: --default-authentication-plugin=mysql_native_password
redis:
image: redis:7-alpine
container_name: fineract-redis
restart: unless-stopped
volumes:
- redis_data:/data
- docker/redis/redis.conf:/usr/local/etc/redis/redis.conf
ports:
- "6379:6379"
networks:
- fineract-network
command: redis-server /usr/local/etc/redis/redis.conf
rabbitmq:
image: rabbitmq:3-management-alpine
container_name: fineract-rabbitmq
restart: unless-stopped
environment:
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD}
volumes:
- rabbitmq_data:/var/lib/rabbitmq
- docker/rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins
ports:
- "5672:5672"
- "15672:15672"
networks:
- fineract-network
nginx:
image: nginx:alpine
container_name: fineract-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/ssl:/etc/nginx/ssl
- nginx_logs:/var/log/nginx
depends_on:
- fineract
networks:
- fineract-network
volumes:
mysql_data:
driver: local
redis_data:
driver: local
rabbitmq_data:
driver: local
nginx_logs:
driver: local
fineract_logs:
driver: local
fineract_uploads:
driver: local
networks:
fineract-network:
driver: bridge
Production Dockerfile
# Production-optimized Dockerfile
FROM gcr.io/distroless/java21-debian11 AS production
# Create application user
USER nonroot:nonroot
# Set working directory
WORKDIR /app
# Copy application JAR
COPY --from=builder /app/fineract-provider/build/libs/*.jar app.jar
# Copy configuration
COPY docker/fineract/application-production.properties application.properties
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/fineract-provider/actuator/health || exit 1
# Expose port
EXPOSE 8080
# Non-root execution
USER nonroot
# Start application
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Docker Configuration Files
Application Properties
# docker/fineract/application-production.properties
# Database Configuration
spring.datasource.url=jdbc:mysql://${FINERACT_DB_HOST}:${FINERACT_DB_PORT}/${FINERACT_DB_NAME}?useSSL=true&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useConfigs=maxPerformance
spring.datasource.username=${FINERACT_DB_USERNAME}
spring.datasource.password=${FINERACT_DB_PASSWORD}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=1200000
spring.datasource.hikari.connection-timeout=30000
# JPA/Hibernate Configuration
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.batch_size=25
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.generate_statistics=true
# Cache Configuration
spring.cache.type=redis
spring.redis.host=${FINERACT_CACHE_HOST}
spring.redis.port=${FINERACT_CACHE_PORT}
spring.redis.password=${FINERACT_CACHE_PASSWORD}
spring.redis.timeout=2000ms
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
# Message Queue Configuration
spring.rabbitmq.host=${FINERACT_MESSAGE_QUEUE_HOST}
spring.rabbitmq.port=${FINERACT_MESSAGE_QUEUE_PORT}
spring.rabbitmq.username=${FINERACT_MESSAGE_QUEUE_USERNAME}
spring.rabbitmq.password=${FINERACT_MESSAGE_QUEUE_PASSWORD}
spring.rabbitmq.connection-timeout=30000
# Logging Configuration
logging.level.org.apache.fineract=INFO
logging.level.org.springframework.security=INFO
logging.level.org.hibernate.SQL=INFO
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=INFO
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
logging.file.name=/app/logs/application.log
logging.logback.rollingpolicy.max-file-size=100MB
logging.logback.rollingpolicy.max-history=30
# Management Endpoints
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=when-authorized
management.endpoint.health.show-components=always
management.metrics.export.prometheus.enabled=true
# Security Configuration
server.ssl.enabled=true
server.ssl.key-store=/etc/ssl/keystore.jks
server.ssl.key-store-password=${SSL_KEYSTORE_PASSWORD}
server.ssl.key-store-type=JKS
server.ssl.protocol=TLS
server.ssl.enabled-protocols=TLSv1.2,TLSv1.3
server.ssl.ciphers=TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_GCM_SHA256
# File Upload Configuration
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
file.upload.dir=/app/uploads
# Multi-tenancy Configuration
fineract.multi-tenancy.enabled=true
fineract.multi-tenancy.tenant.store.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
Nginx Configuration
# docker/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log main;
# Basic Settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
# Gzip Settings
gzip on;
gzip_vary on;
gzip_min_length 10240;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json
application/xml;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; font-src 'self' data:; object-src 'none'; frame-src 'none';" always;
# Rate Limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Upstream Configuration
upstream fineract_backend {
least_conn;
server fineract:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
# HTTP Server (Redirect to HTTPS)
server {
listen 80;
server_name _;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS Server
server {
listen 443 ssl http2;
server_name fineract.local localhost;
# SSL Configuration
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# Modern configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
# Load Balancer Configuration
location /fineract-provider/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://fineract_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# Buffering
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# Health Check Endpoint
location /health {
access_log off;
proxy_pass http://fineract_backend/fineract-provider/actuator/health;
}
# Static Assets
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
gzip_static on;
}
# API Rate Limiting for Login
location /fineract-provider/api/v1/authentication {
limit_req zone=login burst=5 nodelay;
proxy_pass http://fineract_backend;
}
}
}
3. Kubernetes Deployment
Kubernetes Manifests
Deployment
# kubernetes/fineract-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: fineract
namespace: fineract
labels:
app: fineract
version: v1.0
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: fineract
template:
metadata:
labels:
app: fineract
version: v1.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
serviceAccountName: fineract
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
containers:
- name: fineract
image: apache/fineract:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: SPRING_PROFILES_ACTIVE
value: "production"
- name: FINERACT_DB_HOST
valueFrom:
configMapKeyRef:
name: fineract-config
key: db.host
- name: FINERACT_DB_PORT
valueFrom:
configMapKeyRef:
name: fineract-config
key: db.port
- name: FINERACT_DB_NAME
valueFrom:
configMapKeyRef:
name: fineract-config
key: db.name
- name: FINERACT_DB_USERNAME
valueFrom:
secretKeyRef:
name: fineract-secrets
key: db.username
- name: FINERACT_DB_PASSWORD
valueFrom:
secretKeyRef:
name: fineract-secrets
key: db.password
- name: FINERACT_CACHE_HOST
valueFrom:
configMapKeyRef:
name: fineract-config
key: cache.host
- name: JAVA_OPTS
value: "-Xms512m -Xmx2g -XX:+UseG1GC -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
livenessProbe:
httpGet:
path: /fineract-provider/actuator/health/liveness
port: http
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /fineract-provider/actuator/health/readiness
port: http
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
volumeMounts:
- name: application-config
mountPath: /app/config
readOnly: true
- name: logs-volume
mountPath: /app/logs
- name: uploads-volume
mountPath: /app/uploads
volumes:
- name: application-config
configMap:
name: fineract-config
- name: logs-volume
persistentVolumeClaim:
claimName: fineract-logs-pvc
- name: uploads-volume
persistentVolumeClaim:
claimName: fineract-uploads-pvc
restartPolicy: Always
Service
# kubernetes/fineract-service.yaml
apiVersion: v1
kind: Service
metadata:
name: fineract-service
namespace: fineract
labels:
app: fineract
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: fineract
---
# Headless service for StatefulSet
apiVersion: v1
kind: Service
metadata:
name: fineract-headless
namespace: fineract
labels:
app: fineract
spec:
clusterIP: None
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: http
selector:
app: fineract
Ingress
# kubernetes/fineract-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fineract-ingress
namespace: fineract
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout invalid_header http_500 http_502 http_503"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- fineract.example.com
secretName: fineract-tls
rules:
- host: fineract.example.com
http:
paths:
- path: /fineract-provider/(.*)
pathType: Prefix
backend:
service:
name: fineract-service
port:
number: 80
ConfigMap
# kubernetes/fineract-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fineract-config
namespace: fineract
data:
db.host: "mysql-service"
db.port: "3306"
db.name: "fineract"
cache.host: "redis-service"
cache.port: "6379"
message.queue.host: "rabbitmq-service"
message.queue.port: "5672"
application.properties: |
# Database Configuration
spring.datasource.url=jdbc:mysql://${FINERACT_DB_HOST}:${FINERACT_DB_PORT}/${FINERACT_DB_NAME}?useSSL=true&serverTimezone=UTC
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.batch_size=25
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
# Cache Configuration
spring.cache.type=redis
spring.redis.host=${FINERACT_CACHE_HOST}
spring.redis.port=${FINERACT_CACHE_PORT}
# Monitoring
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=when-authorized
# Logging
logging.level.org.apache.fineract=INFO
logging.level.org.springframework.security=INFO
Secrets
# kubernetes/fineract-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: fineract-secrets
namespace: fineract
type: Opaque
data:
db.username: ZmluZXJhY3Q=
db.password: cGFzc3dvcmQxMjM=
cache.password: cmVkaXNwYXNzd29yZA==
---
apiVersion: v1
kind: Secret
metadata:
name: fineract-tls
namespace: fineract
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
PersistentVolumeClaim
# kubernetes/fineract-storage.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fineract-logs-pvc
namespace: fineract
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: fast-ssd
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fineract-uploads-pvc
namespace: fineract
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: standard
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data-pvc
namespace: fineract
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: fast-ssd
MySQL StatefulSet
# kubernetes/mysql-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: fineract
spec:
serviceName: mysql-service
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secrets
key: root-password
- name: MYSQL_DATABASE
value: "fineract"
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secrets
key: username
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secrets
key: password
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
- name: mysql-config
mountPath: /etc/mysql/conf.d
readOnly: true
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "4Gi"
cpu: "2000m"
livenessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
command:
- mysqladmin
- ping
- -h
- localhost
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 2
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
storageClassName: fast-ssd
---
apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: fineract
spec:
clusterIP: None
ports:
- port: 3306
targetPort: 3306
selector:
app: mysql
Redis Deployment
# kubernetes/redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: fineract
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
name: redis
command:
- redis-server
- /etc/redis/redis.conf
- --requirepass
- $(REDIS_PASSWORD)
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-secrets
key: password
volumeMounts:
- name: redis-config
mountPath: /etc/redis
- name: redis-data
mountPath: /data
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: redis-config
configMap:
name: redis-config
- name: redis-data
persistentVolumeClaim:
claimName: redis-data-pvc
---
apiVersion: v1
kind: Service
metadata:
name: redis-service
namespace: fineract
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
protocol: TCP
selector:
app: redis
Horizontal Pod Autoscaler
# kubernetes/fineract-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: fineract-hpa
namespace: fineract
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: fineract
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
ServiceMonitor for Prometheus
# kubernetes/servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: fineract-monitor
namespace: fineract
labels:
app: fineract
spec:
selector:
matchLabels:
app: fineract
endpoints:
- port: http
path: /actuator/prometheus
interval: 30s
scrapeTimeout: 10s
4. Cloud Native Deployment
AWS EKS Deployment
EKS Cluster Setup
#!/bin/bash
# setup-eks-cluster.sh
# Variables
CLUSTER_NAME="fineract-eks"
REGION="us-west-2"
NODE_GROUP_NAME="fineract-nodes"
NODE_INSTANCE_TYPE="m5.large"
MIN_NODES=3
MAX_NODES=10
DESIRED_NODES=3
# Create EKS cluster
aws eks create-cluster \
--name $CLUSTER_NAME \
--region $REGION \
--role-arn arn:aws:iam::123456789012:role/eksServiceRole \
--resources-vpc-config subnetIds=subnet-12345,subnet-67890,securityGroupIds=sg-12345
# Create node group
aws eks create-nodegroup \
--cluster-name $CLUSTER_NAME \
--nodegroup-name $NODE_GROUP_NAME \
--node-role arn:aws:iam::123456789012:role/eksNodeRole \
--instance-types $NODE_INSTANCE_TYPE \
--capacity-type ON_DEMAND \
--scaling-config minSize=$MIN_NODES,maxSize=$MAX_NODES,desiredSize=$DESIRED_NODES \
--subnets subnet-12345 subnet-67890
# Update kubeconfig
aws eks update-kubeconfig --region $REGION --name $CLUSTER_NAME
# Install AWS Load Balancer Controller
kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds"
# Install AWS EBS CSI Driver
kubectl apply -k "github.com/kubernetes-sigs/aws-ebs-csi-driver/deploy/kubernetes/overlays/stable/?ref=master"
Terraform Infrastructure
# main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
}
}
provider "aws" {
region = var.region
}
# EKS Cluster
resource "aws_eks_cluster" "fineract" {
name = var.cluster_name
role_arn = aws_iam_role.eks_cluster.arn
version = "1.27"
vpc_config {
subnet_ids = [
aws_subnet.private_subnets[0].id,
aws_subnet.private_subnets[1].id,
aws_subnet.public_subnets[0].id,
aws_subnet.public_subnets[1].id,
]
endpoint_private_access = true
endpoint_public_access = true
public_access_cidrs = ["0.0.0.0/0"]
}
encryption_config {
provider {
key_arn = aws_kms_key.eks.arn
}
resources = ["secrets"]
}
tags = {
Name = var.cluster_name
}
}
# EKS Node Group
resource "aws_eks_node_group" "fineract" {
cluster_name = aws_eks_cluster.fineract.name
node_group_name = "fineract-nodes"
node_role = aws_iam_role.eks_node_group.arn
subnet_ids = aws_subnet.private_subnets[*].id
instance_types = [var.instance_type]
capacity_type = "ON_DEMAND"
scaling_config {
desired_size = var.desired_capacity
max_size = var.max_capacity
min_size = var.min_capacity
}
update_config {
max_unavailable_percentage = 25
}
labels = {
Environment = var.environment
Application = "fineract"
}
taint {
key = "dedicated"
value = "fineract"
effect = "NO_SCHEDULE"
}
tags = {
Environment = var.environment
}
}
# RDS MySQL Instance
resource "aws_db_instance" "fineract" {
identifier = "fineract-mysql"
engine = "mysql"
engine_version = "8.0.35"
instance_class = "db.r6g.large"
allocated_storage = 100
max_allocated_storage = 1000
storage_type = "gp3"
storage_encrypted = true
db_name = var.database_name
username = var.database_username
password = var.database_password
vpc_security_group_ids = [aws_security_group.rds.id]
db_subnet_group_name = aws_db_subnet_group.fineract.name
backup_retention_period = 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
performance_insights_enabled = true
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_enhanced_monitoring.arn
skip_final_snapshot = var.environment != "production" ? true : false
final_snapshot_identifier = var.environment == "production" ? "fineract-final-snapshot-${formatdate("YYYY-MM-DD-hhmm", timestamp())}" : null
tags = {
Name = "fineract-mysql"
Environment = var.environment
}
}
# ElastiCache Redis
resource "aws_elasticache_subnet_group" "fineract" {
name = "fineract-redis-subnet-group"
subnet_ids = aws_subnet.private_subnets[*].id
}
resource "aws_elasticache_replication_group" "fineract" {
replication_group_id = "fineract-redis"
description = "Redis cluster for Fineract"
port = 6379
parameter_group_name = "default.redis7"
node_type = "cache.r6g.large"
num_cache_clusters = 2
subnet_group_name = aws_elasticache_subnet_group.fineract.name
security_group_ids = [aws_security_group.redis.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = var.redis_auth_token
backup_retention_period = 7
snapshot_retention_limit = 5
snapshot_window = "03:00-05:00"
log_delivery_configuration {
destination = aws_cloudwatch_log_group.redis_slow.name
destination_type = "cloudwatch-logs"
log_format = "text"
log_type = "slow-log"
}
tags = {
Name = "fineract-redis"
Environment = var.environment
}
}
# Application Load Balancer
resource "aws_lb" "fineract" {
name = "fineract-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public_subnets[*].id
enable_deletion_protection = var.environment == "production" ? true : false
tags = {
Name = "fineract-alb"
Environment = var.environment
}
}
# RDS Subnet Group
resource "aws_db_subnet_group" "fineract" {
name = "fineract-db-subnet-group"
subnet_ids = aws_subnet.private_subnets[*].id
tags = {
Name = "fineract-db-subnet-group"
Environment = var.environment
}
}
# Variables
variable "region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "cluster_name" {
description = "EKS cluster name"
type = string
default = "fineract-eks"
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment must be dev, staging, or production."
}
}
variable "database_password" {
description = "Database password"
type = string
sensitive = true
}
variable "redis_auth_token" {
description = "Redis auth token"
type = string
sensitive = true
}
Helm Charts
Chart.yaml
# helm/fineract/Chart.yaml
apiVersion: v2
name: fineract
description: Apache Fineract - Open Source Microfinance Platform
type: application
version: 1.0.0
appVersion: "1.0"
home: https://fineract.apache.org/
sources:
- https://github.com/apache/fineract
maintainers:
- name: Apache Fineract
email: dev@fineract.apache.org
keywords:
- microfinance
- banking
- financial-services
- loan-management
- savings
- accounting
Values.yaml
# helm/fineract/values.yaml
replicaCount: 3
image:
repository: apache/fineract
pullPolicy: IfNotPresent
tag: "latest"
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
podSecurityContext:
fsGroup: 1001
runAsNonRoot: true
runAsUser: 1001
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1001
capabilities:
drop:
- ALL
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
hosts:
- host: fineract.example.com
paths:
- path: /fineract-provider
pathType: Prefix
tls:
- secretName: fineract-tls
hosts:
- fineract.example.com
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
# Database Configuration
database:
enabled: true
type: "mysql"
host: "mysql-service"
port: 3306 content
name: "fineract"
username: "fineract"
passwordSecret:
name: "fineract-secrets"
key: "db-password"
# Cache Configuration
cache:
enabled: true
type: "redis"
host: "redis-service"
port: 6379
passwordSecret:
name: "fineract-secrets"
key: "redis-password"
# Storage Configuration
storage:
logs:
enabled: true
size: "10Gi"
uploads:
enabled: true
size: "50Gi"
# Monitoring Configuration
monitoring:
enabled: true
serviceMonitor:
enabled: true
interval: 30s
# Security Configuration
security:
tls:
enabled: true
secrets:
create: true
networkPolicy:
enabled: true
Deployment Template
# helm/fineract/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "fineract.fullname" . }}
labels:
{{- include "fineract.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "fineract.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "fineract.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "fineract.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 8080
protocol: TCP
env:
- name: SPRING_PROFILES_ACTIVE
value: "production"
- name: FINERACT_DB_HOST
value: {{ .Values.database.host | quote }}
- name: FINERACT_DB_PORT
value: {{ .Values.database.port | quote }}
- name: FINERACT_DB_NAME
value: {{ .Values.database.name | quote }}
- name: FINERACT_DB_USERNAME
value: {{ .Values.database.username | quote }}
- name: FINERACT_DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.database.passwordSecret.name }}
key: {{ .Values.database.passwordSecret.key }}
- name: FINERACT_CACHE_HOST
value: {{ .Values.cache.host | quote }}
- name: FINERACT_CACHE_PORT
value: {{ .Values.cache.port | quote }}
- name: FINERACT_CACHE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.cache.passwordSecret.name }}
key: {{ .Values.cache.passwordSecret.key }}
- name: JAVA_OPTS
value: "-Xms512m -Xmx2g -XX:+UseG1GC -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
livenessProbe:
httpGet:
path: /fineract-provider/actuator/health/liveness
port: http
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /fineract-provider/actuator/health/readiness
port: http
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
- name: logs
mountPath: /app/logs
{{- if .Values.storage.logs.enabled }}
- name: logs-volume
mountPath: /app/logs
{{- end }}
{{- if .Values.storage.uploads.enabled }}
- name: uploads-volume
mountPath: /app/uploads
{{- end }}
volumes:
- name: config
configMap:
name: {{ include "fineract.fullname" . }}-config
- name: logs
emptyDir: {}
{{- if .Values.storage.logs.enabled }}
- name: logs-volume
persistentVolumeClaim:
claimName: {{ include "fineract.fullname" . }}-logs
{{- end }}
{{- if .Values.storage.uploads.enabled }}
- name: uploads-volume
persistentVolumeClaim:
claimName: {{ include "fineract.fullname" . }}-uploads
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
Helm Installation
#!/bin/bash
# install-fineract-helm.sh
# Add Helm repository
helm repo add apache-fineract https://fineract.github.io/helm-charts
helm repo update
# Create namespace
kubectl create namespace fineract
# Create secrets
kubectl create secret generic fineract-secrets \
--from-literal=db-password=your-secure-password \
--from-literal=redis-password=your-redis-password \
--namespace=fineract
# Create values file
cat << EOF > fineract-values.yaml
replicaCount: 3
image:
repository: apache/fineract
tag: "latest"
ingress:
enabled: true
hosts:
- host: fineract.example.com
paths:
- path: /fineract-provider
pathType: Prefix
tls:
- secretName: fineract-tls
hosts:
- fineract.example.com
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
database:
host: "mysql-service"
port: 3306
name: "fineract"
username: "fineract"
passwordSecret:
name: "fineract-secrets"
key: "db-password"
cache:
host: "redis-service"
port: 6379
passwordSecret:
name: "fineract-secrets"
key: "redis-password"
storage:
logs:
enabled: true
size: "10Gi"
uploads:
enabled: true
size: "50Gi"
EOF
# Install Fineract
helm install fineract apache-fineract/fineract \
--namespace fineract \
--values fineract-values.yaml \
--timeout 10m
# Check status
helm status fineract -n fineract
kubectl get all -n fineract
5. Serverless Deployment
AWS Lambda with API Gateway
Lambda Function
package com.apache.fineract.serverless;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
public class FineractLambdaHandler implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
private static ApplicationContext applicationContext;
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
// Initialize Spring Boot context
System.setProperty("server.port", "8080");
System.setProperty("spring.profiles.active", "serverless");
applicationContext = SpringApplication.run(FineractApplication.class);
}
@Override
public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent event, Context context) {
try {
context.getLogger().log("Processing request: " + event.getHttpMethod() + " " + event.getPath());
// Get the Fineract service from Spring context
FineractApiService apiService = applicationContext.getBean(FineractApiService.class);
// Process the request
FineractResponse response = apiService.processRequest(event);
return APIGatewayProxyResponseEvent.builder()
.withStatusCode(response.getStatusCode())
.withHeaders(response.getHeaders())
.withBody(response.getBody())
.build();
} catch (Exception e) {
context.getLogger().log("Error processing request: " + e.getMessage());
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("error", "Internal Server Error");
errorResponse.put("message", e.getMessage());
return APIGatewayProxyResponseEvent.builder()
.withStatusCode(500)
.withHeaders(createCorsHeaders())
.withBody(objectMapper.writeValueAsString(errorResponse))
.build();
}
}
private Map<String, String> createCorsHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("Access-Control-Allow-Origin", "*");
headers.put("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
headers.put("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key");
headers.put("Access-Control-Max-Age", "86400");
return headers;
}
}
Serverless Framework Configuration
# serverless.yml
service: fineract-serverless
frameworkVersion: '3'
provider:
name: aws
runtime: java21
region: us-west-2
environment:
SPRING_PROFILES_ACTIVE: serverless
FINERACT_DB_HOST: ${ssm:/fineract/database/host~true}
FINERACT_DB_USERNAME: ${ssm:/fineract/database/username~true}
FINERACT_DB_PASSWORD: ${ssm:/fineract/database/password~true}
FINERACT_CACHE_HOST: ${ssm:/fineract/cache/host~true}
FINERACT_CACHE_PASSWORD: ${ssm:/fineract/cache/password~true}
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- arn:aws:dynamodb:${self:provider.region}:*:table/fineract-*
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: arn:aws:secretsmanager:${self:provider.region}:*:secret:fineract/*
timeout: 30
memorySize: 1024
functions:
fineractApi:
handler: com.apache.fineract.serverless.FineractLambdaHandler
events:
- http:
path: /api/v1/{proxy+}
method: ANY
cors: true
- http:
path: /api/v1/
method: ANY
cors: true
environment:
FINERACT_API_VERSION: v1
FINERACT_TENANT_HEADER: Fineract-Platform-TenantId
reservedConcurrency: 10
resources:
Resources:
# DynamoDB Table for Serverless Data
FineractDataTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: fineract-serverless-data
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
- AttributeName: type
AttributeType: S
- AttributeName: tenantId
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
GlobalSecondaryIndexes:
- IndexName: TypeIndex
KeySchema:
- AttributeName: type
KeyType: HASH
- AttributeName: tenantId
KeyType: RANGE
Projection:
ProjectionType: ALL
plugins:
- serverless-offline
custom:
serverless-offline:
httpPort: 3000
Spring Boot Configuration for Serverless
# src/main/resources/application-serverless.properties
# AWS Lambda specific configuration
spring.main.banner-mode=off
spring.application.name=fineract-serverless
# Server configuration
server.port=8080
server.servlet.context-path=/fineract-provider
server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
server.compression.min-response-size=1024
# Database configuration (DynamoDB instead of MySQL)
spring.data.dynamodb.entity-path=com.apache.fineract.serverless.dynamo
spring.data.dynamodb.table-name-prefix=fineract_
aws.dynamodb.endpoint=${FINERACT_DYNAMODB_ENDPOINT:}
aws.region.auto=${AWS_REGION:us-west-2}
# Cache configuration
spring.cache.type=caffeine
spring.cache.caffeine.spec=maximumSize=1000,expireAfterWrite=300s
# JPA configuration for DynamoDB
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=false
# Actuator configuration
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=when-authorized
management.health.dynamodb.enabled=true
# Logging configuration
logging.level.com.amazonaws=INFO
logging.level.org.springframework=WARN
logging.level.org.apache.fineract=INFO
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{AWSRequestId:-}] [%thread] %-5level %logger{36} - %msg%n
# CORS configuration
cors.allowed-origins=*
cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS
cors.allowed-headers=Content-Type,Authorization,X-API-Key,Fineract-Platform-TenantId
cors.max-age=86400
# Serverless specific settings
serverless.max.memory=1024
serverless.timeout=30
serverless.connection.timeout=5000
serverless.read.timeout=30000
Azure Functions Deployment
// FineractAzureFunction.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Fineract.AzureFunctions
{
public class FineractAzureFunction
{
private readonly FineractApiService _apiService;
private readonly ILogger<FineractAzureFunction> _logger;
public FineractAzureFunction(FineractApiService apiService, ILogger<FineractAzureFunction> logger)
{
_apiService = apiService;
_logger = logger;
}
[FunctionName("fineract-api")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", "put", "delete", Route = "api/v1/{*path}")] HttpRequest req,
string path)
{
_logger.LogInformation($"Processing {req.Method} request to /api/v1/{path}");
try
{
// Get tenant ID from header
string tenantId = req.Headers["Fineract-Platform-TenantId"].ToString();
// Get API key from header
string apiKey = req.Headers["X-API-Key"].ToString();
// Create request context
var requestContext = new RequestContext
{
Method = req.Method,
Path = path,
QueryString = req.QueryString.ToString(),
TenantId = tenantId,
ApiKey = apiKey
};
// Read request body for POST/PUT requests
if (req.Method == "POST" || req.Method == "PUT")
{
using (var reader = new StreamReader(req.Body))
{
requestContext.Body = await reader.ReadToEndAsync();
}
}
// Process request
var response = await _apiService.ProcessRequestAsync(requestContext);
return new ContentResult
{
Content = response.Body,
ContentType = response.ContentType ?? "application/json",
StatusCode = (int)response.StatusCode
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing request");
var errorResponse = new
{
error = "Internal Server Error",
message = ex.Message
};
return new ContentResult
{
Content = System.Text.Json.JsonSerializer.Serialize(errorResponse),
ContentType = "application/json",
StatusCode = (int)HttpStatusCode.InternalServerError
};
}
}
}
}
6. Monitoring dan Observability
Prometheus Configuration
# monitoring/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "fineract_rules.yml"
scrape_configs:
- job_name: 'fineract'
static_configs:
- targets: ['fineract:8080']
metrics_path: '/fineract-provider/actuator/prometheus'
scrape_interval: 30s
scrape_timeout: 10s
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']
- job_name: 'rabbitmq'
static_configs:
- targets: ['rabbitmq-exporter:9419']
- job_name: 'nginx'
static_configs:
- targets: ['nginx-exporter:9113']
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
Grafana Dashboard
{
"dashboard": {
"id": null,
"title": "Apache Fineract - System Overview",
"tags": ["fineract", "microfinance", "banking"],
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "System Health",
"type": "stat",
"targets": [
{
"expr": "up{job=\"fineract\"}",
"legendFormat": "Fineract Status"
}
],
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"thresholds": {
"steps": [
{"color": "red", "value": 0},
{"color": "green", "value": 1}
]
}
}
},
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}
},
{
"id": 2,
"title": "Request Rate",
"type": "graph",
"targets": [
{
"expr": "rate(http_requests_total{job=\"fineract\"}[5m])",
"legendFormat": "{{method}} {{status}}"
}
],
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}
},
{
"id": 3,
"title": "Response Time",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{job=\"fineract\"}[5m]))",
"legendFormat": "95th percentile"
},
{
"expr": "histogram_quantile(0.50, rate(http_request_duration_seconds_bucket{job=\"fineract\"}[5m]))",
"legendFormat": "50th percentile"
}
],
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 8}
}
],
"time": {"from": "now-1h", "to": "now"},
"refresh": "30s"
}
}
Alerting Rules
# monitoring/fineract_rules.yml
groups:
- name: fineract.rules
rules:
- alert: FineractDown
expr: up{job="fineract"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Apache Fineract is down"
description: "Fineract has been down for more than 1 minute"
- alert: HighResponseTime
expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{job="fineract"}[5m])) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "High response time detected"
description: "95th percentile response time is {{ $value }}s"
- alert: HighErrorRate
expr: rate(http_requests_total{job="fineract",status=~"5.."}[5m]) > 0.1
for: 2m
labels:
severity: warning
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value }} requests/second"
- alert: DatabaseConnectionsHigh
expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "Database connections high"
description: "MySQL connections are at {{ $value | humanizePercentage }} of maximum"
- alert: DiskSpaceLow
expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) < 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "Disk space low"
description: "Disk space is less than 10% on {{ $labels.instance }}"
7. CI/CD Pipeline
GitHub Actions Workflow
# .github/workflows/deploy.yml
name: Deploy Apache Fineract
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Run tests
run: ./gradlew test
- name: Run integration tests
run: ./gradlew integrationTest
env:
SPRING_PROFILES_ACTIVE: test
- name: Generate test report
uses: dorny/test-reporter@v1
if: success() || failure()
with:
name: Test Results
path: build/test-results/test/TEST-*.xml
reporter: java-junit
build:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')
outputs:
image: ${{ steps.image.outputs.image }}
tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Build JAR
run: ./gradlew :fineract-provider:bootJar
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
id: image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
security-scan:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v2
if: always()
with:
sarif_file: 'trivy-results.sarif'
deploy-staging:
needs: [build, security-scan]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- uses: actions/checkout@v3
- name: Deploy to staging
run: |
echo "Deploying to staging environment"
kubectl config use-context staging-context
kubectl set image deployment/fineract fineract=${{ needs.build.outputs.image }} -n fineract-staging
kubectl rollout status deployment/fineract -n fineract-staging
- name: Run smoke tests
run: |
echo "Running smoke tests"
./scripts/smoke-tests.sh staging
- name: Notify deployment
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
channel: '#deployments'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
deploy-production:
needs: [build, security-scan]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v3
- name: Deploy to production
run: |
echo "Deploying to production environment"
kubectl config use-context production-context
kubectl set image deployment/fineract fineract=${{ needs.build.outputs.image }} -n fineract
kubectl rollout status deployment/fineract -n fineract
- name: Run health checks
run: |
echo "Running health checks"
./scripts/health-checks.sh production
- name: Notify deployment
if: always()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
channel: '#deployments'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
text: |
Production deployment ${{ job.status }}
Image: ${{ needs.build.outputs.tag }}
Commit: ${{ github.sha }}
Jenkins Pipeline
// Jenkinsfile
pipeline {
agent any
environment {
DOCKER_REGISTRY = 'docker-registry.example.com'
IMAGE_NAME = 'apache/fineract'
KUBECTL_CONTEXT_STAGING = 'staging'
KUBECTL_CONTEXT_PRODUCTION = 'production'
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 60, unit: 'MINUTES')
timestamps()
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
script {
dockerImage = docker.build("${IMAGE_NAME}:${env.BUILD_ID}")
}
}
}
stage('Test') {
steps {
sh './gradlew test'
sh './gradlew integrationTest'
}
}
stage('Security Scan') {
steps {
sh 'trivy fs --format sarif -o trivy-results.sarif .'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'trivy-results.sarif',
reportName: 'Trivy Security Scan'
])
}
}
stage('Push Image') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
dockerImage.push("${env.BUILD_ID}")
dockerImage.push('latest')
}
}
}
}
stage('Deploy Staging') {
when {
branch 'develop'
}
steps {
script {
sh """
kubectl config use-context ${KUBECTL_CONTEXT_STAGING}
kubectl set image deployment/fineract fineract=${IMAGE_NAME}:${env.BUILD_ID} -n fineract-staging
kubectl rollout status deployment/fineract -n fineract-staging --timeout=300s
"""
}
}
}
stage('Deploy Production') {
when {
branch 'main'
}
steps {
script {
def shouldDeploy = input(
id: 'Deploy', message: 'Deploy to production?',
parameters: [choice(choices: ['Yes', 'No'], description: 'Choose deploy option', name: 'deploy')]
)
if (shouldDeploy == 'Yes') {
sh """
kubectl config use-context ${KUBECTL_CONTEXT_PRODUCTION}
kubectl set image deployment/fineract fineract=${IMAGE_NAME}:${env.BUILD_ID} -n fineract
kubectl rollout status deployment/fineract -n fineract --timeout=300s
"""
} else {
error('Production deployment cancelled')
}
}
}
}
stage('Smoke Tests') {
steps {
script {
if (env.BRANCH_NAME == 'main' || env.BRANCH_NAME == 'develop') {
sh './scripts/smoke-tests.sh'
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'build/libs/*.jar', fingerprint: true
junit 'build/test-results/test/*.xml'
}
success {
slackSend(
color: 'good',
message: "Build succeeded: ${env.JOB_NAME} - ${env.BUILD_NUMBER} (${env.BUILD_URL})"
)
}
failure {
slackSend(
color: 'danger',
message: "Build failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER} (${env.BUILD_URL})"
)
}
unstable {
slackSend(
color: 'warning',
message: "Build unstable: ${env.JOB_NAME} - ${env.BUILD_NUMBER} (${env.BUILD_URL})"
)
}
}
}
Kesimpulan
Arsitektur deployment Apache Fineract menyediakan fleksibilitas penuh untuk berbagai kebutuhan organisasi:
Deployment Models Summary:
- Traditional Monolith: Ideal untuk organizations yang sudah familiar dengan application servers
- Containerized: Modern approach dengan Docker untuk easier deployment dan scaling
- Kubernetes: Enterprise-grade orchestration dengan advanced scaling dan management
- Cloud Native: Fully managed cloud services untuk reduced operational overhead
- Serverless: Cost-effective option untuk event-driven workloads
Key Deployment Benefits:
- Multi-Cloud Support: Deploy di AWS, Azure, GCP, atau on-premises
- Scalability: Automatic scaling dari single instance hingga thousands of pods
- High Availability: Built-in failover dan disaster recovery capabilities
- Security: Comprehensive security configurations dan compliance features
- Monitoring: Integrated monitoring, alerting, dan observability
- CI/CD: Automated deployment pipelines dengan quality gates
Production Considerations:
- Performance: Optimized configurations untuk high-throughput scenarios
- Security: SSL/TLS, secrets management, network policies
- Backup: Automated database backups dan point-in-time recovery
- Monitoring: Comprehensive monitoring dengan custom dashboards
- Scaling: Horizontal dan vertical scaling strategies
- Maintenance: Blue-green deployments dan rolling updates
Best Practices:
- Infrastructure as Code: All infrastructure defined dalam code
- Environment Parity: Consistent configurations across environments
- Automated Testing: Integration tests dalam CI/CD pipeline
- Security Scanning: Vulnerability scanning dalam build process
- Health Checks: Comprehensive health dan readiness probes
- Documentation: Complete deployment documentation dan runbooks
Arsitektur deployment ini memastikan bahwa Apache Fineract dapat di-deploy dan di-operate secara reliable, secure, dan scalable di berbagai environments.
Dokumentasi ini menjelaskan berbagai deployment options untuk Apache Fineract. Specific configurations harus disesuaikan dengan organizational requirements dan infrastructure constraints.