Installazione ambiente di Test / Sviluppo
Per una installazione di test su singolo nodo si consiglia di usare il service compose presente nei due repository principali:
Installazione ambiente di Produzione
Per un ambiente di produzione è indispensabile predisporre un ambiente con adeguati livelli di ridondanza e dimensionamento dei singoli servizi in base alla propria infrastruttura e ai propri requisiti in termini di affidabilità e livelli di servizio da garantire.
Non c'e' una ricetta valida a priori per dimensionare il sistema, si consiglia di partire da una configurazione con 2 repliche di ogni servizio stateless e un dimensionamento molto largo (es: 1GB di ram, 1 vCPU) e poi monitorare il proprio sistema per stabilire i requisiti di ogni singolo container: si potrà verificare facilmente che molti microservizi richiedono pochi Mb di ram.
Elenco dei servizi necessari
Ogni servizio si basa su una immagine docker specifica e necessita di adeguate configurazioni. Le configurazioni e i valori di default delle stesse sono documentate nei singoli repository dei microservizi.
Al fine di dare una corretta configurazione dei vari servizi vengono indicate inoltre:
la dipendenza dalle persistenze usate dalla piattaforma
la necessità di pubblicare un endpoint per quel servizio pubblicamente raggiungibile
Sito istituzionale
Servizio
Descrizione
Docker Image
Pubblico
Servizio principale del CMS, consente l'accesso della radazione per la gestione dei contenuti e fornisce l'interfaccia di navigazione per i cittadini
PostgreSQL Redis Files storage
Servizi digitali e Area Personale
Servizio
Descrizione
Docker Image
Pubblico
Persistenza
Servizio principale della piattaforma, gestisce pratiche, appuntamenti, offre interfacce di configurazione per gli amministratori e di gestione operativa per i funzionari degli enti. E' inoltre l'interfaccia principale anche per l'esperienza utente.
PostgreSQL File Storage Redis
analytics-services-aggregator
Servizio per il calcolo dei KPI sui Servizi
analytics-first-availability-aggregator
Servizio per il calcolo dei KPI sui Calendari
analytics-bookings-aggregator
Servizio per il calcolo dei KPI sui Calendari
analytics-charts-exporter
API per la generazione dei grafici degli Analytics
satisfy-ratings-aggregator
Servizio per il calcolo dei KPI della Customer Satisfaction
Esempio di un file di deployment per orchestratore Docker Swarm :
File di esempio
Copy version : "3.8"
# vim: autoindent tabstop=2 shiftwidth=2 expandtab softtabstop=2 filetype=yaml fileencoding=utf-8
networks :
oc-prod-public : # usata per pubblicare servizi
external : true
oc-prod-internal : # usata per la comunicazione tra i componenti dello stack
external : true
x-deploy : & deploy-snippet
endpoint_mode : dnsrr
placement :
max_replicas_per_node : 2
constraints : [ node.labels.apps == true ]
replicas : 1
update_config :
parallelism : 1
order : start-first
delay : 30s
failure_action : rollback
monitor : 10s
rollback_config :
parallelism : 1
delay : 10s
failure_action : pause # default: pause or continue
monitor : 10s # default: 5s
order : start-first
restart_policy :
condition : any
delay : 15s # default: 5s, between attempts
max_attempts : 50
window : 20s # time to decide if the restart has been sucessfull
resources :
limits :
cpus : '0.2'
memory : 512M
reservations :
memory : 32M
x-sdc-env : & sdc-env
ENV : prod
APP_ENV : prod
DB_DRIVER : pdo_pgsql
DB_HOST : 'postgres'
DB_PORT : 5432
DB_NAME : oc
DB_USER : oc
DB_VERSION : 14
DB_PASSWORD : 'oc'
MAILER_URL : 'smtp://mailserver:1025'
SECRET : change_me
DEFAULT_FROM_EMAIL_ADDRESS : no-reply@opencity.it
EZ_PASSWORD : change_me
OCSDC_SCHEME : https
OCSDC_HOST : app.localtest.me
LOGS_PATH : php://stderr
ENABLE_MIGRATIONS : 'false'
ENABLE_INSTANCE_CONFIG : 'false'
# accesso in scrittura da PHP
FORMSERVER_PRIVATE_URL : http://form-server:8000
# accesso con cache varnish
FORMSERVER_PUBLIC_URL : http://form.localtest.me
# accesso readonly per admin
FORMSERVER_ADMIN_URL : http://forms-readonly.localtest.me
WKHTMLTOPDF_SERVICE : http://gotenberg:3000
PHP_FPM_USER : wodby
PHP_FPM_GROUP : wodby
PHP_APCU_ENABLED : 1
PHP_OPCACHE_ENABLE : 1
PHP_FPM_CLEAR_ENV : 'no'
PHP_FPM_PM_MAX_CHILDREN : 50
PHP_FPM_PM_MAX_REQUESTS : 2000
PHP_FPM_PM_MAX_SPARE_SERVERS : 8
PHP_FPM_PM_MIN_SPARE_SERVERS : 2
PHP_FPM_REQUEST_SLOWLOG_TIMEOUT : 4000
PHP_FPM_PM_START_SERVERS : 5
PHP_DISPLAY_ERRORS : 'off'
PHP_DISPLAY_STARTUP_ERRORS : 'off'
PHP_ERROR_REPORTING : 'E_ERROR'
PHP_DATE_TIMEZONE : 'Europe/Rome'
PHP_SESSION_SAVE_HANDLER : redis
PHP_SESSION_SAVE_PATH : 'tcp://redis:6379'
PHP_SESSION_GC_MAXLIFETIME : 2880
RECAPTCHA_KEY : recaptcha_key
RECAPTCHA_SECRET : recaptcha_secret
EWZ_RECAPTCHA_SITE_KEY : recaptcha_key
EWZ_RECAPTCHA_SECRET : recaptcha_secret
FEATURE_NEW_OUTDATED_BROWSER : 'true'
FEATURE_APPLICATION_DETAIL : 'true'
FEATURE_CALENDAR_TYPE : 'true'
#FEATURE_DUE_AMOUNT: 'true'
#FEATURE_ANALYTICS: 'true'
TOKEN_TTL : 864000 # 10gg
CACHE_MAX_AGE : 3600
KAFKA_URL : vector
KSQLDB_URL : ksqldb-server:8088
SKIP_CACHE_WARMUP : 'false'
TRUSTED_PROXIES : '10.0.0.0/8,172.16.0.0/12,192.168.0.0/16'
DEFAULT_CACHE_REDIS_PROVIDER : 'redis://redis:6379'
DEFAULT_CACHE_LIFETIME : 3600
DEFAULT_CACHE_PREFIX_SEED : v-2.27.0
REGISTRY_API_URL : https://registry.qa.stanzadelcittadino.it
REGISTRY_API_KEY : change_me
API_USER_PASSWORD : change_me
# In assenza di una vera autenticazione basata su SPID si può simulare
# un login di un utente con le seguenti variabili d'ambiente:
shibb_pat_attribute_codicefiscale : CLNVTR76P01G822Q
shibb_pat_attribute_cognome : Coliandro
shibb_pat_attribute_nome : Vittorino
shibb_pat_attribute_sesso : M
shibb_pat_attribute_emailaddress : info@comune.bugliano.pi.it
shibb_pat_attribute_datanascita : 1/9/1976
shibb_pat_attribute_luogonascita : Ponsacco
shibb_pat_attribute_provincianascita : PI
shibb_pat_attribute_telefono : 003912378945
shibb_pat_attribute_cellulare : 333 444 666 99
shibb_pat_attribute_indirizzoresidenza : 'Via Gramsci, 1'
shibb_pat_attribute_capresidenza : 56056
shibb_pat_attribute_cittaresidenza : Bugliano
shibb_pat_attribute_provinciaresidenza : PI
shibb_pat_attribute_statoresidenza : Italia
shibb_pat_attribute_spidcode : 123456789
shibb_pat_attribute_x509certificate_issuerdn : FAKE_issuerdn
shibb_pat_attribute_x509certificate_subjectdn : FAKE_subjectdn
shibb_pat_attribute_x509certificate_base64: "DQpSZXN1bHQgZ29lcyBoZXJlLi4uDQpCYXNlNjQNCg0KQmFzZTY0IGlzIGEgZ2VuZXJpYyB0ZXJtIGZvciBhIG51bWJlciBvZiBzaW1pbGFyIGVuY29kaW5nIHNjaGVtZXMgdGhhdCBlbmNvZGUgYmluYXJ5IGRhdGEgYnkgdHJlYXRpbmcgaXQgbnVtZXJpY2FsbHkgYW5kIHRyYW5zbGF0aW5nIGl0IGludG8gYSBiYXNlIDY0IHJlcHJlc2VudGF0aW9uLiBUaGUgQmFzZTY0IHRlcm0gb3JpZ2luYXRlcyBmcm9tIGEgc3BlY2lmaWMgTUlNRSBjb250ZW50IHRyYW5zZmVyIGVuY29kaW5nLg=="
shibb_Shib-Session-ID : abc123abc123abc123abc123abc123abc123abc123
shibb_Shib-Session-Index : abc123abc123abc123abc123abc123abc123abc123
shibb_Shib-Authentication-Instant : 2000-01-01T00-00Z
x-registry-env : & registry-env
DJANGO_DATABASE : postgres:5432:oc_registry:oc:oc
DJANGO_SECRET_KEY : change_me
DJANGO_SETTINGS_MODULE : application_registry.settings_production
KAFKA_BOOTSTRAP_SERVERS : kafka:9092
SENTRY_DSN :
configs :
sf-config-instances :
name : oc-instances-v1
file : ./config/app/instances.yml
vector-api :
name : occ-kafka-http-v1
file : ./config/kafka/vector.toml
services :
# Symfony-core, esposto solo su rete interna
app :
image : registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
stop_grace_period : 45s
configs :
- source : sf-config-instances
target : /var/www/html/config/instances_prod.yml
networks :
- oc-prod-internal
environment :
<< : * sdc-env
healthcheck :
test : [ "CMD" , "curl" , "-f" , "http://localhost:80/health-check" ]
interval : 1m30s
timeout : 10s
retries : 6
start_period : 5s
deploy :
<< : * deploy-snippet
endpoint_mode : vip
replicas : 1
resources :
limits :
cpus : '1'
memory : 2048M
reservations :
memory : 1024M
labels :
traefik.enable : 'false'
# Frontend del symfony-core
varnish :
image : registry.gitlab.com/opencontent/varnish:1.2
stop_grace_period : 45s
networks :
- oc-prod-public
- oc-prod-internal
healthcheck :
disable : true
deploy :
<< : * deploy-snippet
endpoint_mode : dnsrr
replicas : 1
resources :
limits :
cpus : '1'
memory : 2048M
reservations :
memory : 1024M
labels :
traefik.enable : 'true'
traefik.http.services.varnish.loadbalancer.server.port : 6081
# disabilitato altrimenti quando fallisce backend non sfrutto grace
#traefik.http.services.varnish.loadbalancer.healthcheck.path: /health-check
# Security headers
traefik.http.middlewares.security.headers.frameDeny : 'true'
traefik.http.middlewares.security.headers.customFrameOptionsValue : 'SAMEORIGIN'
traefik.http.middlewares.security.headers.stsSeconds : 31536000
traefik.http.middlewares.security.headers.stsIncludeSubdomains : 'true'
traefik.http.middlewares.security.headers.forceStsHeader : 'true'
#traefik.http.middlewares.security.headers.contentSecurityPolicy: "default-src 'self' data:; img-src 'self' data: blob: c.bing.com www.googletagmanager.com *.openstreetmap.org c.clarity.ms cartodb-basemaps-b.global.ssl.fastly.net cartodb-basemaps-c.global.ssl.fastly.net cartodb-basemaps-a.global.ssl.fastly.nett; style-src 'self' 'unsafe-inline' static.opencityitalia.it satisfy.opencontent.it widget.freshworks.com stackpath.bootstrapcdn.com unpkg.com cdn.datatables.net printjs-4de6.kxcdn.com cdnjs.cloudflare.com fonts.googleapis.com; connect-src 'self' s3.eu-west-1.amazonaws.com api.opencityitalia.it qa-genova-id.boat.opencontent.io dev-genova-id.boat.opencontent.io mappe.genova.opencityitalia.it api.opencontent.it api.qa.stanzadelcittadino.it registry.qa.stanzadelcittadino.it satisfy.boat.opencontent.io static.opencityitalia.it www.giscom.cloud nominatim.openstreetmap.org a.clarity.ms d.clarity.ms k.clarity.ms i.clarity.ms h.clarity.ms widget.freshworks.com fiscalcode.opencontent.it form-qa.stanzadelcittadino.it form-readonly-qa.stanzadelcittadino.it cdn.datatables.net satisfy.opencityitalia.it satisfy.opencontent.it satisfy.hasura.app sdc-analytics-dev-charts-exporter.boat.opencontent.io efil-proxy-qa.boat.opencontent.io pmpay-proxy-qa.boat.opencontent.io iris-proxy-qa.boat.opencontent.io mypay-proxy-qa.boat.opencontent.io www.openstreetmap.org flyimg.opencontent.it; script-src 'self' 'unsafe-inline' 'unsafe-eval' static.opencityitalia.it cdn.announcekit.app widget.freshworks.com www.clarity.ms form-qa.stanzadelcittadino.it cdnjs.cloudflare.com cdn.form.io unpkg.com code.jquery.com www.googletagmanager.com cdn.datatables.net printjs-4de6.kxcdn.com satisfy.opencityitalia.it satisfy.opencontent.it www.recaptcha.net www.gstatic.com; object-src 'none'; font-src 'self' data: satisfy.opencontent.it stackpath.bootstrapcdn.com cdnjs.cloudflare.com unpkg.com fonts.gstatic.com; worker-src 'self'; report-uri https://csp-collector.opencontent.it/csp?env=production&mode=enforce&app=sdc-qa; child-src 'self' www.youtube-nocookie.com announcekit.app"
traefik.http.middlewares.security.headers.contentTypeNosniff : 'true'
traefik.http.middlewares.security.headers.permissionsPolicy : "geolocation=(self), camera=(self), microphone=(self)"
traefik.http.middlewares.security.headers.referrerPolicy : 'strict-origin-when-cross-origin'
traefik.http.middlewares.security.headers.browserXssFilter : 'true'
# Cors generali
traefik.http.routers.https.rule : 'Method(`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `PURGE`) && Host(`app.localtest.me`)'
traefik.http.routers.https.tls : 'true'
traefik.http.routers.https.tls.certresolver : 'myhttpchallenge'
traefik.http.routers.https.middlewares : 'cors, general-rl, security'
# Cors per condivisione autenticazione
traefik.http.routers.origin-https.rule : 'Method(`GET`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `PURGE`) && Host(`app.localtest.me`) && PathPrefix(`/lang/api/session-auth`)'
traefik.http.routers.origin-https.tls : 'true'
traefik.http.routers.origin-https.priority : 10001
traefik.http.routers.origin-https.tls.certresolver : 'myhttpchallenge'
traefik.http.routers.origin-https.middlewares : 'cors-origin, general-rl, security'
traefik.http.middlewares.cors.headers.accesscontrolallowcredentials : 'true'
traefik.http.middlewares.cors.headers.accesscontrolallowheaders : 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer,X-Hasura-Role,X-Hasura-Admin-Secret,X-Hasura-Allowed-Roles,X-Hasura-Default-Role,X-Hasura-User-Id,X-Hasura-Org-Id,X-Locale'
traefik.http.middlewares.cors.headers.accesscontrolallowmethods : 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist : '*'
traefik.http.middlewares.cors.headers.accesscontrolmaxage : 100
traefik.http.middlewares.cors.headers.addvaryheader : 'true'
traefik.http.middlewares.cors-origin.headers.accesscontrolallowcredentials : 'true'
traefik.http.middlewares.cors-origin.headers.accesscontrolallowheaders : 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer,X-Hasura-Role,X-Hasura-Admin-Secret,X-Hasura-Allowed-Roles,X-Hasura-Default-Role,X-Hasura-User-Id,X-Hasura-Org-Id,X-Locale'
traefik.http.middlewares.cors-origin.headers.accesscontrolallowmethods : 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
traefik.http.middlewares.cors-origin.headers.accesscontrolmaxage : 100
traefik.http.middlewares.cors-origin.headers.addvaryheader : 'true'
environment :
# https://github.com/wodby/varnish#environment-variables
VARNISH_BACKEND_GRACE : '2880m'
VARNISH_BACKEND_HOST : 'app'
VARNISH_BACKEND_PORT : '80'
VARNISH_BACKEND_PROBE : '/health-check'
VARNISH_KEEP_ALL_PARAMS : 1
VARNISH_CACHE_STATIC_FILES : 1
##VARNISH_MOBILE_SEPARATE_CASH: 1
#VARNISH_BIG_FILES_SIZE: 10485760
VARNISHD_MEMORY_SIZE : '1024m'
VARNISH_BACKEND_BETWEEN_BYTES_TIMEOUT : 30s
VARNISH_BACKEND_FIRST_BYTE_TIMEOUT : 30s
VARNISH_STRIP_COOKIES : 'cookies_consent|_[_a-z]+|wooTracker|VCKEY-[a-zA-Z0-9-_]+'
VARNISHD_PARAM_WORKSPACE_BACKEND : '256k'
VARNISHD_PARAM_WORKSPACE_CLIENT : '256k'
VARNISH_SECRET : 'change_me'
# Manager symfony, non è esposto su rete pubblica esegue migrazioni e script all'avvio
manager :
image : registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
stop_grace_period : 45s
configs :
- source : sf-config-instances
target : /var/www/html/config/instances_prod.yml
networks :
- oc-prod-internal
environment :
<< : * sdc-env
DB_USER : oc # Inserire username utente manager se configurato
DB_PASSWORD : 'oc' # Inserire password utente manager se configurato
ENABLE_MIGRATIONS : 'true'
deploy :
<< : * deploy-snippet
endpoint_mode : vip
replicas : 1
resources :
limits :
cpus : '1'
memory : 1024M
reservations :
memory : 512M
# Ci sono 3 valori da tenere sempre uguali: WORKER_NUM, replicas e swam_cronjob_replicas
worker :
image : registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
networks :
- oc-prod-internal
stop_signal : SIGQUIT
stop_grace_period : 20m
command : [ "./bin/worker-daemon.sh" ]
configs :
- source : sf-config-instances
target : /var/www/html/config/instances_prod.yml
volumes :
- ./config/worker/worker-daemon.sh:/var/www/html/bin/worker-daemon.sh:ro
healthcheck :
disable : true
environment :
<< : * sdc-env
SWARM_SERVICE : "worker"
SWARM_STACK : "oc-prod"
#DEBUG: 1
WORKER_ID : 1
WORKER_NUM : 1
MAX_EXECUTIONS : 100
MAX_ACTIONS : 50
deploy :
<< : * deploy-snippet
endpoint_mode : dnsrr
replicas : 1
update_config :
order : stop-first
restart_policy :
condition : none
resources :
limits :
cpus : '0.5'
memory : 2048M
reservations :
memory : 512M
labels :
swarm.cronjob.enable : "true"
swarm.cronjob.schedule : "*/3 * * * *"
swarm.cronjob.skip-running : "true"
swarm.cronjob.replicas : 1
# Espone delle api rest per la creazione di eventi su kafka
vector :
image : timberio/vector:0.18.1-debian
configs :
- source : vector-api
target : /etc/vector/vector.toml
healthcheck :
test : [ "CMD" , "curl" , "-f" , "http://localhost:80/health" ]
interval : 1m30s
timeout : 10s
retries : 6
start_period : 5s
environment :
VECTOR_LOG : debug
networks :
- oc-prod-internal
deploy :
<< : * deploy-snippet
labels :
traefik.enable : 'false'
# Microservizio per la creazione di pdf
gotenberg :
image : gotenberg/gotenberg:7.9.2
networks :
- oc-prod-internal
- oc-prod-public
healthcheck :
test : [ "CMD" , "curl" , "-f" , "http://localhost:3000/health" ]
interval : 1m30s
timeout : 10s
retries : 6
start_period : 5s
deploy :
<< : * deploy-snippet
labels :
prometheus.disable : 'true'
traefik.enable : 'true'
traefik.http.services.gotenberg.loadbalancer.server.port : 3000
traefik.http.routers.gotenberg.rule : 'Host(`gotenberg.localtest.me`)'
traefik.http.routers.gotenberg.tls : 'true'
traefik.http.routers.gotenberg.tls.certresolver : 'myhttpchallenge'
traefik.http.routers.gotenberg.middlewares : 'goodheaders'
# Strumento per testare la posta elettronica
mailhog :
image : mailhog/mailhog
networks :
- oc-prod-internal
deploy :
<< : * deploy-snippet
labels :
traefik.enable : 'true'
traefik.http.services.mailhog.loadbalancer.server.port : 8025
traefik.http.routers.mailhog.rule : Host(`mail.localtest.me`)
traefik.http.routers.mailhog.tls : 'true'
traefik.http.routers.mailhog.middlewares : goodheaders
traefik.http.routers.mailhog.tls.certresolver : 'myhttpchallenge'
##############################################################################
# FORMS
##############################################################################
# Frontend per il formserver
form-varnish :
image : registry.gitlab.com/opencontent/varnish:1.2
stop_grace_period : 45s
networks :
- oc-prod-internal
- oc-prod-public
deploy :
<< : * deploy-snippet
replicas : 1
resources :
limits :
cpus : '0.5'
memory : 512M
reservations :
cpus : '0.1'
memory : 128M
labels :
prometheus.enable : 'true'
prometheus.port : 9131
traefik.enable : 'true'
traefik.http.services.formvarnish.loadbalancer.server.port : 6081
traefik.http.services.formvarnish.loadbalancer.healthcheck.path : /.vchealthz
traefik.http.routers.formvarnish.rule : 'Method(`GET`, `HEAD`, `OPTIONS`) && Host(`form.localtest.me`)'
traefik.http.routers.formvarnish.tls : 'true'
traefik.http.routers.formvarnish.tls.certresolver : 'myhttpchallenge'
traefik.http.routers.formvarnish.middlewares : 'cors'
environment :
# https://github.com/wodby/varnish#environment-variables
VARNISH_BACKEND_HOST : 'form-server'
VARNISH_BACKEND_PORT : '8000'
VARNISH_BACKEND_GRACE : '2880m'
VARNISH_BACKEND_PROBE : '/form/5d7aa15b18fecd734051ae7f?varnish-health'
VARNISH_CACHE_STATIC_FILES : 1
VARNISHD_MEMORY_SIZE : '256m'
VARNISH_BACKEND_BETWEEN_BYTES_TIMEOUT : 10s
VARNISH_BACKEND_FIRST_BYTE_TIMEOUT : 30s
VARNISH_KEEP_ALL_PARAMS : 1
VARNISH_STRIP_COOKIES : 'cookie_consent|_[_a-z]+|wooTracker|VCKEY-[a-zA-Z0-9-_]+|_ga|_gid|_gat[a-zA-Z0-9-_]+'
VARNISH_SECRET : 'nvXq3CkK'
VARNISHD_PARAM_WORKSPACE_BACKEND : '256k'
VARNISHD_PARAM_WORKSPACE_CLIENT : '256k'
# Espone api rest per il crud dei form
form-server :
image : registry.gitlab.com/opencity-labs/area-personale/form-server:1.3.0
networks :
- oc-prod-public
- oc-prod-internal
environment :
DB_URL : mongodb://mongo:27017/formmanager
MAX_AGE : 5
S_MAX_AGE : 60
#healthcheck:
# test: [ "CMD", "wget", "--spider", "http://localhost:8000/form/5d7aa15b18fecd734051ae7f" ]
# start_period: 30s
# timeout: 1s
# retries: 1
# interval: 1m
deploy :
<< : * deploy-snippet
labels :
traefik.enable : 'true'
traefik.http.services.formserver.loadbalancer.server.port : 8000
# Espongo il formserver aperto in scrittura con oc-auth, sotto /api per il form-builder
traefik.http.routers.formserver.rule : 'Method(`GET`, `HEAD`, `OPTIONS`, `POST`, `PUT`, `PATCH`, `DELETE`) && Host(`form-builder.localtest.me`) && PathPrefix(`/api/`)'
traefik.http.routers.formserver.tls : 'true'
traefik.http.routers.formserver.tls.certresolver : 'myhttpchallenge'
traefik.http.middlewares.strip-forms-api.stripprefix.prefixes : '/api/'
traefik.http.routers.formserver.middlewares : 'strip-forms-api'
# Espongo anche readonly per gli admin da backend (per vedere subito aggiornamenti)
traefik.http.routers.formserver-ro.rule : 'Method(`GET`, `POST`, `HEAD`, `OPTIONS`) && Host(`form-readonly.localtest.me`)'
traefik.http.routers.formserver-ro.tls : 'true'
traefik.http.routers.formserver-ro.tls.certresolver : 'myhttpchallenge'
traefik.http.routers.formserver-ro.middlewares : 'oc-auth, cors'
# Microservizio per la gestione dei componenti comuni dei form (nested form)
form-builder :
image : registry.gitlab.com/opencity-labs/area-personale/formbuilderjs:0-5-2
healthcheck :
test : [ "NONE" ]
networks :
- oc-prod-public
deploy :
<< : * deploy-snippet
labels :
traefik.enable : 'true'
traefik.http.services.formbuilder.loadbalancer.server.port : 80
traefik.http.routers.formbuilder.rule : 'Method(`GET`, `HEAD`, `OPTIONS`) && Host(`form-builder.localtest.me`)'
traefik.http.routers.formbuilder.tls : 'true'
traefik.http.routers.formbuilder.tls.certresolver : 'myhttpchallenge'
traefik.http.routers.formbuilder.middlewares : 'oc-auth'
##############################################################################
# FORMS
##############################################################################
# Interroga la tabella PAYMENTS_STATUS su ksqldb e richiede info ai proxy sui pagamenti pendenti
payments-poller :
image : registry.gitlab.com/opencity-labs/area-personale/payments-poller:1.0.2
networks :
- oc-prod-internal
environment :
KSQLDB_SERVER : http://ksqldb-server:8088
LOOP_TIME : 900
LOG_LEVEL : DEBUG
HEALTHCHECK_ID : 88f0d2a5-0326-46a1-9093-42e83c3017c8
PYTHONDEBUG : 1
deploy :
<< : * deploy-snippet
resources :
reservations :
memory : 1G
limits :
cpus : '0.5'
memory : 4G
healthcheck :
test : [ NONE ]
# Legge gli eventi dal topic applications e crea un evento nel topic payments se necessario
payment-dispatcher :
image : registry.gitlab.com/opencity-labs/area-personale/payment-dispatcher:1.2.1
networks :
- oc-prod-internal
environment :
KAFKA_TOPIC_APPLICATIONS : applications
KAFKA_TOPIC_PAYMENTS : payments
KAFKA_BOOTSTRAP_SERVERS : kafka:9092
KAFKA_CONSUMER_GROUP_PREFIX : payment-dispatcher
PROMETHEUS_JOB_NAME : payment_dispatcher
APP_ID : payment-dispatcher:1.2.1
KSQLDB_SERVER : http://ksqldb-server:8088
LOG_LEVEL : INFO
healthcheck :
test : [ NONE ]
deploy :
<< : * deploy-snippet
resources :
limits :
cpus : '0.5'
memory : 1G
# Gateway proxy di iris (ambiente di test)
iris-proxy :
image : registry.gitlab.com/opencity-labs/area-personale/iris-payment-proxy:1.3.3
stop_grace_period : 5m
networks :
- oc-prod-public
environment :
CANCEL_PAYMENT : "true"
KAFKA_TOPIC_NAME : payments
KAFKA_BOOTSTRAP_SERVERS : kafka:9092
KAFKA_GROUP_ID : iris_payment_proxy
IRIS_OTF_PAYMENT_URL : https://apistage.regione.toscana.it/C01/ComunicazionePosizioniDebitorieOTF/v3/IdpAllineamentoPendenzeEnteOTF
IRIS_IUV_URL : https://iristest.rete.toscana.it/IdpBillerNdpServices/GenerazioneIUVService
IRIS_OUTCOME_URL : https://apistage.regione.toscana.it/C01/InvioNotificaPagamentoEsito/v3/IdpInformativaPagamento.Esito
IRIS_VERIFY_URL : https://apistage.regione.toscana.it/C01/VerificaStatoPagamento/v3/IdpVerificaStatoPagamento
IRIS_NOTICE_URL : https://iristest.rete.toscana.it/IdpBillerNdpServices/GenerazioneAvvisiService
EXTERNAL_API_URL : "https://iris-proxy.localtest.me"
INTERNAL_API_URL : "http://iris-proxy.localtest.me"
BASE_PATH_EVENT : "sdc-payments/iris-proxy/payments/"
BASE_PATH_CONFIG : "sdc-payments/iris-proxy/tenants/"
# STORAGE_TYPE="LOCAL|MINIO|S3"
STORAGE_TYPE : "LOCAL"
STORAGE_BUCKET_NAME : "payments"
APP_ID : "iris-proxy:1.3.3"
deploy :
<< : * deploy-snippet
labels :
traefik.enable : 'true'
traefik.http.services.iris-proxy-qa.loadbalancer.server.port : 8000
traefik.http.routers.iris-proxy.entrypoints : 'websecure, web'
traefik.http.routers.iris-proxy.rule : 'Host(`iris-proxy.localtest.me`) && PathPrefix(`/{op:(notice|online-payment|receipt|landing)}/{id:[^/]+}`, `/{op:(docs|redoc|openapi.json|tenants|services|notify-payment)}`, `/{op:(tenants|services)}/({id:[^/]+}|schema)`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
traefik.http.routers.iris-proxy.tls : 'true'
traefik.http.routers.iris-proxy.tls.certresolver : 'myhttpchallenge'
traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowcredentials : 'true'
traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowheaders : 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer'
traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolallowmethods : 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
# asterisco perché ognuno di essi è acceduto da tutti i domini custom che avranno gli enti
traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolalloworiginlist : '*'
traefik.http.middlewares.payment-proxy-qa-cors.headers.accesscontrolmaxage : 100
traefik.http.middlewares.payment-proxy-qa-cors.headers.addvaryheader : 'true'
traefik.http.routers.iris-proxy.middlewares : 'goodheaders, payment-proxy-qa-cors'
volumes :
- ./var/tenants:/app/payments/sdc-payments/iris-proxy/tenants
- ./var/payments:/app/payments/sdc-payments/iris-proxy/payments
# Aggiorna lo stato delle pratiche dopo che si sono verificati dei pagamenti
krun-process-payments :
image : registry.gitlab.com/opencity-labs/area-personale/core/app:2.27.0
configs :
- source : sf-config-instances
target : /var/www/html/config/instances_prod.yml
entrypoint : /bin/krun
stop_grace_period : 300s
healthcheck :
disable : true
volumes :
- ./config/krun/process-payment:/bin/process-payment
- ./config/krun/krun:/bin/krun
networks :
- oc-prod-internal
environment :
<< : * sdc-env
KAFKA_TOPIC : "payments"
KAFKA_SERVER : "kafka:9092"
KAFKA_CONSUMER_GROUP : "oc-prod-krun-process-payments"
COMMAND : "/bin/process-payment"
#DEBUG: 1
VERBOSE : 1
deploy :
<< : * deploy-snippet
resources :
limits :
cpus : '0.2'
memory : 1024M
update_config :
parallelism : 3
order : stop-first
##############################################################################
# REGISTRY
##############################################################################
# Espone le api per la configurazione dei vari sistemi di protocollazione
registry-rest-api :
image : registry.gitlab.com/opencity-labs/area-personale/stanzadelcittadino-application-registry:1.10.2
networks :
- oc-prod-public
- oc-prod-internal
environment :
<< : * registry-env
DJANGO_LOG_LEVEL : INFO
REGISTRY_API_KEY : change_me
deploy :
<< : * deploy-snippet
labels :
traefik.enable : "true"
traefik.http.services.registry.loadbalancer.server.port : 8000
traefik.http.routers.registry.rule : "Host(`registry.localtest.me`)"
traefik.http.routers.registry.tls : "true"
traefik.http.routers.registry.tls.certresolver : "myhttpchallenge"
traefik.http.middlewares.registry-qa-home-redir.redirectregex.regex : 'registry.localtest.me/$$'
traefik.http.middlewares.registry-qa-home-redir.redirectregex.replacement : "registry.localtest.me/admin"
traefik.http.middlewares.registry-redirect-tenants.replacepathregex.regex : '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/tenants/(.*)'
traefik.http.middlewares.registry-redirect-tenants.replacepathregex.replacement : '/api/v1/tenants/'
traefik.http.middlewares.registry-redirect-services.replacepathregex.regex : '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/services/'
traefik.http.middlewares.registry-redirect-services.replacepathregex.replacement : '/api/v1/services/$$1/'
traefik.http.middlewares.registry-redirect-service.replacepathregex.regex : '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/services/(.+)'
traefik.http.middlewares.registry-redirect-service.replacepathregex.replacement : '/api/v1/services/$$1/$$2/'
traefik.http.middlewares.registry-redirect-service-schema.replacepathregex.regex : '^/(d3|datagraph|dedagroup|halley|halley-cloud|hypersic|infor|insiel|maggioli|pitre|tinn)/v1/schema'
traefik.http.middlewares.registry-redirect-service-schema.replacepathregex.replacement : '/api/v1/services-schema/$$1'
traefik.http.middlewares.registry-cors.headers.accesscontrolallowcredentials : 'true'
traefik.http.middlewares.registry-cors.headers.accesscontrolallowheaders : '*'
traefik.http.middlewares.registry-cors.headers.accesscontrolallowmethods : 'GET,POST,HEAD,PUT,OPTIONS'
traefik.http.middlewares.registry-cors.headers.accesscontrolalloworiginlist : '*'
traefik.http.middlewares.registry-cors.headers.accesscontrolmaxage : 100
traefik.http.middlewares.registry-cors.headers.addvaryheader : 'true'
traefik.http.routers.registry.middlewares : "goodheaders, registry-qa-home-redir, registry-redirect-tenants, registry-redirect-services, registry-redirect-service, registry-redirect-service-schema, registry-cors"
prometheus.enable : "true"
prometheus.port : 8000
# Gestisce il sistema di retry della protocollazione
registry-retry-scheduler :
image : registry.gitlab.com/opencity-labs/area-personale/stanzadelcittadino-application-registry:1.10.2
command : /app/venv/bin/python3.9 manage.py run_retry_scheduler
networks :
- oc-prod-internal
environment :
<< : * registry-env
ENABLE_SENTRY_EVENT_FILTER : "true"
deploy :
<< : * deploy-snippet
##############################################################################
# PDND
##############################################################################
pdnd-connector :
image : registry.gitlab.com/opencity-labs/area-personale/pdnd-connector:x.x.x
networks :
- oc-prod-internal
deploy :
<< : * deploy-snippet
environment :
ENVIRONMENT : app_environment
SENTRY_TOKEN : sentry DSN
HTTP_EXTERNAL_BASEPATH : https://api.qa.stanzadelcittadino.it/pdnd
SDC_PUBLIC_KEY_ENDPOINT : https://core/.well-known/jwks.json
CACHE_EXPIRATION : 5m
CACHE_EVICTION : 10m
STORAGE_TYPE : s3
STORAGE_BUCKET : s3_bucket
STORAGE_BASE_PATH : pdnd
STORAGE_LOCAL_PATH : /data/pdnd
STORAGE_S3_KEY : S3_key
STORAGE_S3_SECRET : s3_secret
STORAGE_S3_REGION : eu-west-1
STORAGE_S3_ENDPOINT : https://s3.eu-west-1.amazonaws.com
STORAGE_S3_SSL : "false"
labels :
prometheus.enable : 'true'
prometheus.port : 8000
traefik.enable : 'true'
traefik.docker.network : 'oc-prod-internal'
traefik.http.services.pdnd-connector.loadbalancer.server.port : 8000
traefik.http.routers.pdnd-connector.entrypoints : 'websecure, web'
traefik.http.routers.pdnd-connector.rule: 'Host(`api.qa.stanzadelcittadino.it`) && PathPrefix(`/pdnd/{op:(status|metrics|docs|anpr/accertamento-residenza|anpr/stato-famiglia|tenants|keys|tenants/configs|tenants/clients|e-services)}`, `/pdnd/{op:(status|metrics|docs|anpr/accertamento-residenza|anpr/stato-famiglia|tenants|keys|tenants/configs|tenants/clients|e-services)}/({id:[^/]+})`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
traefik.http.routers.pdnd-connector.tls : 'true'
traefik.http.middlewares.pdnd-connector-stripper.stripprefix.prefixes : '/pdnd/'
traefik.http.routers.pdnd-connector.tls.certresolver : 'myhttpchallenge'
traefik.http.routers.pdnd-connector.middlewares : 'goodheaders, cors-free-all, pdnd-connector-stripper'
Prima di procedere all’avvio dei microservizi si devono compiere alcune operazioni:
Creazione del file delle istanze
Va creato un file instances.yml
sull'ambiente che ospiterà la piattaforma, questo file dovrà poi essere condiviso con i servizi che ne hanno bisogno (vedi file di esempio di distribuzione).
Il file è così formato:
Copy instances :
# dominio dell'sitanza con prefisso
stanzadelcittadino.localtest.me/comune-di-bugliano :
# codice meccanografico dell'ente
codice_meccanografico : c_cbug
# identificativo dell'istanza
identifier : comune-di-bugliano
# lingue disponibili (it, de, en)
app_locales : it|en
# tipo di login (da scegliere in base al provider dell'ente)
login_route : login_pat
Andrà creato un blocco istanza per ogni ente che si vorrà ospitare sulla piattaforma
Creazione dei database
Singole istanze degli enti (Symfony core)
Il tipo di multi-tenancy implementata in OpenCity Area personale è di singolo stack applicativo con database dedicato per tenant.
Per poter effettuare un’installazione dell’infrastruttura abbiamo quindi bisogno di un singolo database per tenant con le seguenti caratteristiche.
Postgres >= 11 con estensione postgis >= 3
Permessi
Per ridurre i rischi dovuti a errori o a compromissione delle credenziali vengono usati due utenti differenti:
un utente oc_manager che viene utilizzato per creare i database ed eseguire operazione di struttura (creazione, modifica e cancellazione di tabelle, viste, colonne ecc ecc)
un utente oc_user che viene utilizzato dall’applicativo per effettuare operazioni sui dati
Attualmente nella nostra infrastruttura la creazione del database avviene con l'utente oc_manager , a partire da un template preimpostato con i privilegi necessari.
Creazione del template
Come utente postgres si crea il database impostando i privilegi di default che verranno assegnati a tutti gli oggetti creati in seguito.
A postgres verranno dati privilegi ampi, mentre a oc_user verranno dati privilegi minimali:
Copy CREATE DATABASE _oc ENCODING = 'utf8' CONNECTION LIMIT = 50 ;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON TYPES TO postgres WITH GRANT OPTION;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON SEQUENCES TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL PRIVILEGES ON TABLES TO postgres;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT USAGE ON TYPES TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT EXECUTE ON FUNCTIONS TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT ALL ON SEQUENCES TO oc_user;
ALTER DEFAULT PRIVILEGES FOR USER oc_manager IN SCHEMA "public" GRANT SELECT,INSERT,UPDATE,DELETE ON TABLES TO oc_user;
CREATE EXTENSION IF NOT EXISTS postgis;
In seguito la proprietà del database viene assegnata ad oc_manager
Copy ALTER DATABASE _oc OWNER TO oc_manager;
ALTER SCHEMA public OWNER TO oc_manager;
Per creare database come utente postgres gli viene dato il ruolo oc_manager
Copy GRANT oc_manager TO postgres;
Creazione di un nuovo database
Per creare il database e impostarne subito come owner l'utente corretto, si esegue come utente postgres
Copy CREATE DATABASE oc_firenze TEMPLATE = _oc OWNER = 'oc_manager' ENCODING = 'utf8' CONNECTION LIMIT = 50 ;Some code
Sistema di protocollazione (registry)
Il sistema di protocollazione ha invece bisogno di un singolo database con le seguenti caratteristiche:
Postgres >= 11
Creazione di un nuovo utente
Copy CREATE USER registry_user WITH PASSWORD 'password' ;
Tuning di alcuni parametri di connessione (questi comandi sono opzionali ma suggeriti da django)
Copy ALTER ROLE registry_user SET client_encoding TO 'utf8' ;
ALTER ROLE registry_user SET default_transaction_isolation TO 'read committed' ;
ALTER ROLE registry_user SET timezone TO 'UTC' ;
Creo il database e lo assegno all'utente
Copy CREATE DATABASE oc_registry ENCODING = 'utf8' CONNECTION LIMIT = 50 ;
GRANT ALL PRIVILEGES ON DATABASE oc_registry TO registry_user;
Configurazione dei proxy di pagamento
Per garantire una corretta comunicazione tra l'area personale e i proxy di pagamento è necessaria configurare appropriatamente gli endpoint sul proxy di riferimento.
Configurazione dei CORS
Il requisito di base affinchè questi endpoint siano correttamente funzionanti è che siano correttamente racchiuse all'interno di un middleware che gestisca i CORS in maniera adeguata. In particolare questo middleware dovrà essere configurato come segue:
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer
Access-Control-Allow-Methods
GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS
Access-Control-Allow-Origin-List
Di seguito un esempio di configurazione del middleware:
Copy traefik.http.middlewares.cors.headers.accesscontrolallowcredentials : 'true'
traefik.http.middlewares.cors.headers.accesscontrolallowheaders : 'Authorization,Origin,Content-Type,Accept,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,access-control-allow-credentials,Referer'
traefik.http.middlewares.cors.headers.accesscontrolallowmethods : 'GET,POST,HEAD,PUT,DELETE,PATCH,OPTIONS'
traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist : '*'
traefik.http.middlewares.cors.headers.accesscontrolmaxage : 100
traefik.http.middlewares.cors.headers.addvaryheader : 'true'
Endpoint interni (non necessita di CORS)
L'unico endpoint interno in questo caso è quello per richiedere l'aggiornamento dello stato di un pagamento. Per garantire quindi che questo venga chiamato solo ed esclusivamente dal microservizio di polling, è necessario che questo venga configurato come endpoint raggiungibile solo internamente alla rete di docker. Di seguito un esempio:
Copy networks :
backplane-sdc :
external : true
internal :
traefik-sdc :
external : true
swarm-backplane :
external : true
...
networks :
- traefik-sdc
- backplane-sdc
deploy :
<< : * deploy-snippet
labels :
traefik.enable : 'true'
traefik.docker.network : 'traefik-sdc'
traefik.http.routers.silfi-proxy-qa-internal.entrypoints : 'backplane'
traefik.http.routers.silfi-proxy-qa-internal.rule : 'Host(`silfi-proxy-qa.boat-backplane.opencontent.io`) && PathPrefix(`/payment-proxy/silfi/update/{id:[^/]+}`) && METHOD(`GET`, `HEAD`, `OPTIONS`)'
Endpoint pubblici
Gli endpoint pubblici sono:
Pagamento online (/online-payment/{payment_id}
)
Download dell'avviso cartaceo (/offline-payment/{payment_id}
)
Download della ricevuta telematica (/receipt/{payment_id}
)
Ritorno alla area personale (detta anche landing url) (/landing/{payment_id}
)
Form schema per la configurazione del tenant (/tenants/schema
)
Form schema per la configurazione del servizio (/services/schema
)
Documentazione Swagger delle API (/docs
, facoltativo)
Status (/status
, facoltativo)
Metriche di monitoraggio (/metrics
, facoltativo)
Di seguito un esempio di configurazione degli endpoint
Copy traefik.http.routers.silfi-proxy-qa.entrypoints : 'web, websecure'
traefik.http.routers.silfi-proxy-qa.rule : 'Host(`api.qa.stanzadelcittadino.it`) && PathPrefix(`/payment-proxy/silfi/{op:(notice|online-payment|receipt|landing)}/{id:[^/]+}`, `/payment-proxy/silfi/{op:(docs|status|metrics)}`, `/payment-proxy/silfi/{op:(tenants|services)}/schema`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`, `HEAD`, `OPTIONS`)'
traefik.http.routers.silfi-proxy-qa.tls : 'true'
traefik.http.routers.silfi-proxy-qa.tls.certresolver : 'mydnschallenge'
traefik.http.routers.silfi-proxy-qa.middlewares : 'goodheaders, cors'
Endpoint protetti
Gli endpoint protetti da autenticazione sono:
Inserimento, recupero, modifica e cancellazione della configurazione del tenant (/tenants/{id}
)
Inserimento, recupero, modifica e cancellazione della configurazione del servizio (/services/{id}
)
Di seguito un esempio di configurazione degli endpoint:
Copy # Quando pronti aggiungere il middleware
# sdc-qa-jwt-decode
traefik.http.routers.silfi-proxy-qa-protected.entrypoints : 'web, websecure'
traefik.http.routers.silfi-proxy-qa-protected.rule : 'Host(`api.qa.stanzadelcittadino.it`) && PathPrefix(`/payment-proxy/silfi/{op:(tenants|services)}`, `/payment-proxy/silfi/{op:(tenants|services)}/{id:[^/]+}`) && METHOD(`GET`, `POST`, `PUT` ,`PATCH`, `DELETE`,>
traefik.http.routers.silfi-proxy-qa-protected.tls: 'true'
traefik.http.routers.silfi-proxy-qa-protected.tls.certresolver : 'mydnschallenge'
# MIDDLEWARE ABILITATO PER CONTROLLO JWT
# traefik.http.routers.silfi-page-proxy-qa-protected.middlewares: 'sdc-qa-jwt-decode, goodheaders, cors'
traefik.http.routers.silfi-proxy-qa-protected.middlewares : 'goodheaders, cors'