Un RTE in un altro datacenter

Da un host vuoto in un secondo datacenter a un RTE in uscita che esegue il suo primo playbook


Per gli amministratori: quando gli host gestiti di un secondo datacenter vanno raggiunti da un RTE in quel datacenter, non dal primo.

La strada facile. L’installer esegue ogni passaggio qui sotto sull’host dell’RTE con un solo comando: curl -fsSL https://ansibleforms.com/install | sh -s -- install --role runner, con --join afj_... per farlo approvare subito. Questo runbook fa lo stesso a mano, con Docker o Podman, così puoi vedere e adattare ogni parte.


Cosa ottieni alla fine

L’app, il suo worker e il suo database restano dove sono, nel datacenter 1. Il datacenter 2 riceve un host che esegue un container RTE in modalità in uscita (RTE_MODE=outbound, vedi Modalità): contatta l’app su HTTPS e tiene aperto un WebSocket, si unisce come runner proprio, ed esegue i playbook dei moduli che lo indicano sugli host gestiti del datacenter 2.

Dove Cosa gira lì Parla con
Datacenter 1 I nodi dell’app, il worker, il database (come oggi) Niente di nuovo: riceve il socket dell’RTE sul suo solito indirizzo HTTPS
Datacenter 2, l’host dell’RTE Docker o Podman, e il container RTE L’app in APP_URL su HTTPS, e gli host gestiti del datacenter 2

L’RTE non contiene credenziali del database né ENCRYPTION_SECRET, solo un token proprio. Nulla si connette a lui, quindi non ha bisogno di porte aperte, di un nome DNS né di un certificato. L’app invia i playbook e gli inventari con ogni job (RTE_FILES=transfer, il predefinito), quindi l’RTE non ha bisogno di una loro copia né di un disco condiviso.

Ogni comando qui sotto si esegue in un terminale sull’host dell’RTE, come utente con sudo, salvo dove indicato diversamente. Richiede circa 20 minuti, la maggior parte passati ad attendere l’immagine.


0. Prima di iniziare

Raccogli queste informazioni:

Cosa Da dove arriva
L’indirizzo dell’app APP_URL: l’indirizzo che le persone aprono in un browser, come https://forms.example.com, con il suo percorso di base se ne ha uno
Il certificato dell’app Firmato da una CA pubblica, da una CA privata (ti serve il suo certificato, passaggio 6), o autofirmato
Un proxy L’HTTPS_PROXY che questo datacenter usa per raggiungere il datacenter 1, se c’è
Il nome dell’RTE Il nome del suo runner in Connessioni > Runner, che i moduli indicano con runner:, come rte-dc2: lettere, cifre e _ . : @ -
Un join token Facoltativo: un join token (afj_...) approva subito l’RTE. Senza, lo approva un admin (passaggio 8)
La chiave ssh La chiave privata che i playbook usano per raggiungere gli host gestiti, o il diritto di crearne una

L’app deve eseguire la 7.3.0 o successiva. Per creare un join token per questo solo RTE, un admin chiama l’API una volta (vedi Join token):

curl -X POST https://forms.example.com/api/v2/runner/jointoken \
  -H "Authorization: Bearer <an admin's token>" -H "Content-Type: application/json" \
  -d '{ "name": "rte-dc2", "expiresInHours": 24, "maxUses": 1 }'

1. Preparare l’host

L’RTE è un container, quindi l’host ha bisogno di un motore dei container e di nient’altro: Node.js, python e ansible sono nell’immagine.

Requisito Perché
Linux su x86_64 (amd64) Le immagini RTE sono pubblicate solo per amd64
2 CPU e 4 GB di memoria, o di più Un RTE esegue fino a RTE_MAX_JOBS playbook insieme (10), ognuno con i suoi fork: dimensionalo per quelli
20 GB di disco libero L’immagine -rte-full occupa circa 3,3 GB, più un’immagine durante gli aggiornamenti, i file dei job che l’app invia e i file temporanei dei job
Docker, o Podman con podman-compose Per scaricare ed eseguire l’immagine
L’ora impostata via NTP Gli orari dei job vengono confrontati con quelli dell’app

Docker, su Ubuntu o Debian (altre distribuzioni: docs.docker.com/engine/install):

sudo apt-get update && sudo apt-get install -y ca-certificates curl chrony
curl -fsSL https://get.docker.com | sudo sh
sudo systemctl enable --now docker chrony
sudo usermod -aG docker $USER          # log out and in again, so docker works without sudo
docker run --rm hello-world
docker compose version

Podman, su RHEL, Rocky o Alma Linux:

sudo dnf install -y podman podman-compose chrony
sudo systemctl enable --now chronyd
podman run --rm quay.io/podman/hello

Con Podman, scrivi podman al posto di docker e podman-compose al posto di docker compose in ogni comando qui sotto.


2. Rete e firewall

L’RTE apre da solo ogni connessione. Nulla lo raggiunge dall’esterno:

Da A Porta Perché
L’host dell’RTE L’app, in APP_URL (tramite HTTPS_PROXY se impostato) La porta dell’app, 443 dietro la maggior parte dei proxy Il socket su cui passano i job, i loro file e il loro output
L’host dell’RTE Gli host gestiti del datacenter 2 ssh (22), WinRM, le loro API I playbook girano da qui
L’host dell’RTE ghcr.io 443 Per scaricare l’immagine, a meno che non arrivi dal tuo registry o da un file (passaggio 4)

Non aprire nessuna porta in entrata per l’RTE. La sua PORT (8000) serve solo /live, /ready e /metrics, per le probe su questo host.

L’RTE contatta APP_URL con aggiunto /api/v2/rte/socket, come WebSocket. Un reverse proxy davanti all’app deve far passare l’upgrade per quel percorso (vedi WebSocket dei runner). Verifica da qui che l’app risponda:

curl -sS https://forms.example.com/api/v2/version

3. Cartelle

Tutto sta sotto /opt/af-rte. L’RTE gira come utente node (uid 1000), quindi i suoi dati appartengono a quell’uid:

sudo mkdir -p /opt/af-rte/data/{ssh,certificates,ansible/roles,ansible/collections,git}
sudo touch /opt/af-rte/data/ansible/ansible.cfg /opt/af-rte/data/git/.gitconfig
Cartella Nel container Contiene
data/ /app/dist/persistent Lo spool (rte-spool: l’output e la fine di ogni job finché l’app non li ha salvati), i file dei job inviati dall’app (rte-files), una CA privata, le librerie python che installa un playbook
data/ssh/ /home/node/.ssh La chiave ssh e il known_hosts dei playbook
data/ansible/ /etc/ansible/... Un ansible.cfg, ruoli e collection tuoi, montati invece che integrati nell’immagine

Tieni data/ sull’host: un RTE riavviato invia ancora ciò che l’app non aveva salvato, e conserva la sua cache dei file dei job.

Chiave ssh. Copia la chiave che i playbook usano nel datacenter 1, o creane una per questo datacenter e dai la sua metà pubblica agli host gestiti:

sudo ssh-keygen -t ed25519 -N "" -C "ansibleforms-rte-dc2" -f /opt/af-rte/data/ssh/id_ed25519
sudo cat /opt/af-rte/data/ssh/id_ed25519.pub      # add it to ~/.ssh/authorized_keys of the managed user on each host
sudo sh -c 'ssh-keyscan host1.dc2.example.com host2.dc2.example.com >> /opt/af-rte/data/ssh/known_hosts'

Poi assegna tutto all’uid 1000, con la chiave leggibile solo dal suo proprietario:

sudo chown -R 1000:1000 /opt/af-rte/data
sudo chmod 700 /opt/af-rte/data/ssh && sudo chmod 600 /opt/af-rte/data/ssh/id_*

4. L’immagine

Scegline una delle due.

Opzione A: l’immagine ufficiale

Le immagini pubblicate sono nel GitHub Container Registry, pubbliche, senza bisogno di accesso (vedi Immagini per i tre livelli). Prendi lo stesso livello del datacenter 1. :7 segue l’ultima versione 7, come fa il :7 dell’app:

docker pull ghcr.io/ansibleforms/ansibleforms-rte-full:7

Un host senza accesso a internet riceve l’immagine da uno che ce l’ha:

# on a host with internet
docker pull ghcr.io/ansibleforms/ansibleforms-rte-full:7
docker save ghcr.io/ansibleforms/ansibleforms-rte-full:7 | gzip > rte-full-7.tar.gz
# copy the file to the RTE host, then there
docker load < rte-full-7.tar.gz

Oppure caricala una volta nel tuo registry (docker tag e docker push), e usa quel nome nei passaggi successivi.

Opzione B: la tua immagine

Quando i playbook del datacenter 2 hanno bisogno di collection o librerie python che mancano all’immagine ufficiale (quelle VMware per il vCenter di questo datacenter, per esempio), parti da ansibleforms-rte-core:

sudo mkdir -p /opt/af-rte/build
sudo tee /opt/af-rte/build/Dockerfile >/dev/null <<'EOF'
FROM ghcr.io/ansibleforms/ansibleforms-rte-core:7
# the collections and python libraries these playbooks use : no root needed, both belong to node
RUN ansible-galaxy collection install community.vmware -p /usr/share/ansible/collections \
 && pip install --no-cache-dir pyvmomi
EOF
docker build --pull -t af-rte-dc2 /opt/af-rte/build

Ricostruiscila a ogni nuova versione dell’app (vedi Costruire il tuo RTE). Con più host RTE, costruiscila una volta e caricala nel tuo registry. Offline, usa docker save e docker load sull’immagine costruita come nell’opzione A.


5. Impostazioni

Un nuovo RTE_TOKEN proprio, di almeno 16 caratteri. È la password di questo RTE: l’app lo conserva nella credenziale RTE del runner, e sigilla con esso i segreti di ogni job.

openssl rand -base64 24
sudo tee /opt/af-rte/rte.env >/dev/null <<'EOF'
RTE_MODE=outbound
APP_URL=https://forms.example.com
RTE_NAME=rte-dc2
RTE_TOKEN=<the new token>
# optional, the first time only : approved at once instead of waiting for an admin
RTE_JOIN_TOKEN=<afj_..., or remove this line>
PORT=8000
PYTHONPATH=/app/dist/persistent/python/lib
EOF
sudo chmod 600 /opt/af-rte/rte.env
Variabile Significato
RTE_MODE outbound: l’RTE contatta l’app. Senza, l’RTE parte nella modalità database deprecata
APP_URL L’indirizzo dell’app, http:// o https://, con il suo percorso di base se c’è
RTE_NAME Il nome del suo runner. Se non è impostata: il suo id di nodo, rte-<hostname>-<port>, che cambia con il container
RTE_TOKEN Il suo token, di almeno 16 caratteri. Conservalo: d’ora in poi il runner accede con esso
RTE_JOIN_TOKEN Facoltativa: un join token, usato solo finché il suo runner non esiste ancora o attende l’approvazione

Aggiungi queste quando la rete ne ha bisogno:

Variabile Quando
HTTPS_PROXY, NO_PROXY L’RTE raggiunge l’app tramite un proxy: HTTPS_PROXY=http://proxy.dc2.example.com:3128. Un host elencato in NO_PROXY (separati da virgole, .example.com per un dominio) viene contattato direttamente
NODE_EXTRA_CA_CERTS Il certificato dell’app proviene da una CA privata: metti il certificato della CA in /opt/af-rte/data/certificates/ca.pem e imposta NODE_EXTRA_CA_CERTS=/app/dist/persistent/certificates/ca.pem
APP_TLS_VERIFY 0 accetta qualsiasi certificato dell’app, compreso uno autofirmato. Solo all’interno di una rete fidata: preferisci NODE_EXTRA_CA_CERTS
RTE_FILES transfer (predefinito) riceve i playbook e gli inventari con ogni job. Lascialo così, a meno che non li monti tu sull’RTE (local)

Le altre impostazioni che legge un RTE sono in Eseguire un RTE. RTE_MAX_JOBS, RTE_CACHE_MB, RTE_FILES e RTE_DRAIN_SECONDS si possono impostare anche in seguito nella sua pagina del runner, che vince sulla variabile (vedi Le impostazioni di un RTE).


6. Avviarlo

Un file compose tiene l’intera definizione in un solo posto, accanto alle impostazioni. Metti l’immagine del passaggio 4 in image::

sudo tee /opt/af-rte/compose.yml >/dev/null <<'EOF'
services:
  rte:
    image: ghcr.io/ansibleforms/ansibleforms-rte-full:7   # or af-rte-dc2, the image you built
    container_name: af_rte_dc2
    restart: unless-stopped
    # the running playbooks get RTE_DRAIN_SECONDS (25) to finish when the container stops
    stop_grace_period: 40s
    env_file: rte.env
    # the probes, on this host only : nothing connects to an outbound RTE
    ports:
      - "127.0.0.1:8000:8000"
    healthcheck:
      test: ["CMD", "node", "./healthcheck.js"]
      interval: 10s
      timeout: 5s
      retries: 30
      start_period: 30s
    volumes:
      - ./data:/app/dist/persistent
      - ./data/ssh:/home/node/.ssh
      - ./data/git/.gitconfig:/home/node/.gitconfig
      - ./data/ansible/ansible.cfg:/etc/ansible/ansible.cfg
      - ./data/ansible/roles:/etc/ansible/roles
      - ./data/ansible/collections:/etc/ansible/collections
EOF
cd /opt/af-rte && docker compose up -d

Docker lo riavvia dopo un reboot (unless-stopped). Podman non ha un demone che lo faccia: abilita podman-restart.service (sudo systemctl enable --now podman-restart), o esegui il container come servizio systemd con un file Quadlet. Su un host con SELinux in modalità enforcing, aggiungi :Z a ogni volume (./data:/app/dist/persistent:Z).

Senza compose, lo stesso container con un solo comando:

docker run -d --name af_rte_dc2 --restart unless-stopped --stop-timeout 40 \
  --env-file /opt/af-rte/rte.env -p 127.0.0.1:8000:8000 \
  -v /opt/af-rte/data:/app/dist/persistent \
  -v /opt/af-rte/data/ssh:/home/node/.ssh \
  -v /opt/af-rte/data/git/.gitconfig:/home/node/.gitconfig \
  -v /opt/af-rte/data/ansible/ansible.cfg:/etc/ansible/ansible.cfg \
  -v /opt/af-rte/data/ansible/roles:/etc/ansible/roles \
  -v /opt/af-rte/data/ansible/collections:/etc/ansible/collections \
  ghcr.io/ansibleforms/ansibleforms-rte-full:7

7. Verificarlo sull’host

docker ps --filter name=af_rte_dc2        # STATUS ends in (healthy): the process listens
docker logs -f af_rte_dc2                 # Ctrl+C to stop following
curl -s http://127.0.0.1:8000/ready       # {"status":"ready"} once the socket to the app is open

Il log mostra l’RTE che parte, contatta l’app e si connette:

RTE 'rte-dc2' 7.3.0 (outbound, files transfer) listening on http port 8000, token fingerprint abcd-ef12
RTE 'rte-dc2': dialing the app at wss://forms.example.com/api/v2/rte/socket
RTE 'rte-dc2': connected to the app (node af-app1-8000)

Senza un join token, un’altra riga dice che è in attesa: RTE 'rte-dc2': waiting for an admin to approve it under Connections > Runners (fingerprint abcd-ef12). Annota l’impronta.

/ready risponde 503 con not connected to the app finché il socket non è aperto. L’healthcheck dice solo che il processo è in ascolto.


8. Approvarlo

Senza un join token, l’RTE ha chiesto di unirsi: Connessioni > Runner mostra un avviso arancione sopra la lista, 1 Runner attende l’approvazione. Aprilo, verifica che l’Impronta sia quella del log dell’RTE e che Modalità dica In uscita, poi fai clic su Approva (vedi Approvare un RTE). Solo un admin approva. Una richiesta che nessuno gestisce entro 24 ore viene rimossa, e l’RTE chiede di nuovo la prossima volta che contatta l’app.

Con un join token, il runner viene approvato subito e la sua descrizione indica il join token. In entrambi i casi l’app conserva il token dell’RTE in una nuova credenziale RTE, rte-dc2-rte, e da quel momento l’RTE accede con il proprio RTE_TOKEN: RTE_JOIN_TOKEN non viene più usato, e puoi rimuoverlo da rte.env.


9. Verificarlo in AnsibleForms

  1. Connessioni > Runner elenca il runner rte-dc2, tipo RTE. Attiva Colonne > Connessione per vedere Connesso nella lista
  2. Aprilo. La sua scheda RTE mostra Modalità In uscita e il badge Connessione Connesso; al passaggio del puntatore indica il nodo dell’app che detiene il socket, la versione dell’RTE e quanti dei suoi job sono in esecuzione
  3. Fai clic su Testa connessione: dice Runner connection is OK. Non chiama nulla: legge quale nodo dell’app detiene il socket dell’RTE (runner_leases) e verifica il contratto che l’RTE ha parlato quando si è connesso
  4. Dagli una descrizione, come Datacenter 2
  5. Lascia Predefinito sul runner del datacenter 1, a meno che ogni modulo di playbook senza runner: non debba girare nel datacenter 2

La pagina Stato elenca il nuovo RTE con la sua versione, accanto agli altri.


10. Un primo job

Prova l’intero percorso con un playbook che non cambia nulla. Nei playbook dell’app (la sua cartella dei playbook o il suo repository, come nel datacenter 1), aggiungi ping-dc2.yaml:

- name: Reach the hosts of datacenter 2
  hosts: all
  gather_facts: false
  tasks:
    - name: Ping
      ansible.builtin.ping:

Poi aggiungi nel designer un modulo che indica il nuovo runner:

name: Ping datacenter 2
type: ansible
playbook: ping-dc2.yaml
runner: rte-dc2
inventory: <an inventory with the hosts of datacenter 2>
roles:
  - admin
categories:
  - Default
fields: []

Avvialo. L’app invia la cartella dei playbook e l’inventario con il job, e l’RTE li conserva in base al loro hash, così una cartella invariata non viene mai inviata due volte. L’output inizia con Running on RTE rte-dc2 (outbound), mostra pong per ogni host, e il job termina con successo. Una cartella più grande di RTE_FILES_MAX_MB (512 per impostazione predefinita) non viene inviata: il job fallisce e lo segnala.


11. Mandargli i moduli

Un modulo gira sull’RTE che indica, quindi aggiungi la stessa riga a ogni modulo per il datacenter 2:

runner: rte-dc2

Vedi Runner per il runner predefinito e gli altri tipi di runner.


Quando qualcosa non funziona

L’RTE scrive in docker logs da notice in su. Una connessione che fallisce prima che il socket si apra (un nome DNS, una porta rifiutata, un certificato) compare solo come RTE: dialing the app again in <n> seconds, a livello info: imposta LOG_CONSOLE_LEVEL=debug in rte.env e riavvialo per vedere RTE: socket to wss://...: <the error>.

Cosa vedi Dove Cosa fare
RTE: set RTE_TOKEN (at least 16 characters) ; refusing to start docker logs RTE_TOKEN manca o è troppo corto in rte.env
RTE: RTE_MODE=outbound needs APP_URL, the app's address (https://forms.example.com) ; refusing to start docker logs Imposta APP_URL in rte.env
RTE: APP_URL must be an http or https address, not ... ; refusing to start docker logs APP_URL deve iniziare con https:// (o http://)
[DEPRECATED] This RTE runs in database mode (RTE_MODE is not set) docker logs Manca RTE_MODE=outbound in rte.env
Nessuna riga connected to the app, /ready resta 503 docker logs, /ready La rete del passaggio 2: fai curl all’app da questo host, il proxy (HTTPS_PROXY, NO_PROXY), il certificato dell’app (NODE_EXTRA_CA_CERTS, passaggio 5). Imposta LOG_CONSOLE_LEVEL=debug per vedere l’errore
the app refused the socket (404) o un altro stato diverso da 401, 403 e 409 docker logs Un reverse proxy non fa passare il WebSocket su /api/v2/rte/socket, o ad APP_URL manca il percorso di base dell’app
the app refused the socket (401): ... the token of RTE 'rte-dc2' is not the one stored for its runner: check its RTE_TOKEN docker logs Esiste un runner rte-dc2 con un altro token. Dai all’RTE quel token (la password della credenziale del runner), o elimina il runner così che chieda di unirsi
the app refused the socket (401): ... the join token (RTE_JOIN_TOKEN) is unknown, revoked, expired or used up docker logs Crea un nuovo join token, o rimuovi RTE_JOIN_TOKEN e approva l’RTE (passaggio 8)
the app refused the socket (409): ... runner 'rte-dc2' exists and is approved: a join token cannot give it a new token ... docker logs L’RTE è stato reinstallato con un nuovo token: usa il token della credenziale del suo runner, o fallo cambiare a un admin
the app refused this RTE (401): dialing again in 10 minutes, or restart it once an admin acted docker logs Segue un rifiuto. Correggine la causa, poi docker restart af_rte_dc2 invece di aspettare
waiting for an admin to approve it under Connections > Runners docker logs Approvalo (passaggio 8). È connesso, ma fino ad allora non riceve job
the app speaks another contract (an upgrade in progress?): dialing again in 60 seconds docker logs L’app e l’RTE eseguono versioni con un altro contratto: aggiorna l’RTE alla versione dell’app (vedi Aggiornarlo)
Non connesso, o Testa connessione dice the RTE ‘rte-dc2’ (outbound) is not connected: it has not dialed the app L’app Nessun nodo dell’app detiene il suo socket: l’RTE è fermo, o non riesce a raggiungere l’app. Leggi i suoi docker logs
Un job attende, poi fallisce con no app node holds a socket to RTE ‘rte-dc2’ and it took no job for 60 minutes (RTE_QUEUE_MINUTES): is it connected? L’output del job L’RTE non era connesso mentre il job attendeva
Permission denied sulla chiave ssh L’output del job I file di data/ devono appartenere all’uid 1000 (passaggio 3)
UNREACHABLE per gli host gestiti L’output del job ssh da questo host: la chiave, known_hosts, e il firewall verso gli host

Sul lato dell’app, un RTE rifiutato compare nel log dell’app come Refused an RTE socket from <address>: <why>.


Aggiornarlo

Quando l’app passa a una nuova versione, seguila anche su questo host. Un RTE con lo stesso contratto continua a funzionare nel frattempo; quando una versione cambia il contratto, le sue note di rilascio lo dicono (vedi Il contratto). Con :7 in compose.yml, o nel FROM del tuo Dockerfile:

cd /opt/af-rte
docker compose pull                 # option A; for option B: docker build --pull -t af-rte-dc2 build
docker compose up -d
docker image prune -f               # removes the image it no longer uses

Aggiornalo quando non c’è nessun playbook in esecuzione su di esso: un arresto attende RTE_DRAIN_SECONDS (25) per i playbook in esecuzione, e quelli ancora in esecuzione terminano allora come abbandonati. Ciò che hanno stampato prima dell’arresto viene conservato nello spool e inviato quando l’RTE torna. Offline, esegui docker load della nuova immagine (passaggio 4) prima di docker compose up -d.


Tornare indietro

cd /opt/af-rte && docker compose down     # or: docker rm -f af_rte_dc2

Il socket si chiude, e il runner mostra subito Non connesso: nessun nodo dell’app detiene più il suo socket (runner_leases). Un host che se ne va senza chiuderlo mostra Non connesso entro circa un minuto, quando il nodo dell’app non sente più nulla sul socket. Il runner resta: un runner che si è unito tramite un socket non viene mai rimosso da solo. Eliminalo in Connessioni > Runner, e la sua credenziale rte-dc2-rte in Connessioni > Credenziali. Rimuovi prima la riga runner: dai moduli che lo indicavano: fino ad allora i loro job attendono l’RTE, fino a RTE_QUEUE_MINUTES, e falliscono.


Più capacità nel datacenter 2

Ripeti i passaggi da 1 a 9 su altri host del datacenter 2, ognuno con il proprio RTE_NAME e RTE_TOKEN, come rte-dc2-b: ognuno si unisce come runner proprio e riceve fino a RTE_MAX_JOBS playbook. Un modulo ne sceglie uno con runner:. Due RTE con lo stesso RTE_NAME non si dividono il lavoro: il secondo prende il socket al primo (vedi Più RTE).


Un RTE in modalità database

Prima della 7.3.0 questo runbook configurava un RTE in modalità database: conteneva le credenziali del database (DB_*, ENCRYPTION_SECRET), si connetteva al database del datacenter 1 sulla porta 3306, e l’app lo chiamava sulla sua porta 8000. Quella modalità è deprecata dalla 7.3.0 e verrà rimossa nella 8.0.0. Per passare un RTE del genere alla modalità in uscita senza un nuovo runner, imposta su di esso RTE_MODE=outbound, APP_URL e RTE_NAME (il nome del suo runner), mantieni il suo RTE_TOKEN, e rimuovi DB_*, ENCRYPTION_SECRET e RTE_REGISTER: vedi Database (deprecata). Poi chiudi la porta 8000 e la porta del database di cui non ha più bisogno.