Implementazioni Kubernetes: una guida completa

Kubernetes, molto apprezzato dalla comunità degli sviluppatori, è una piattaforma ampiamente utilizzata per l’esecuzione di contenitori Docker in cluster. Questa potente piattaforma offre diversi metodi per distribuire e aggiornare le applicazioni in esecuzione nei contenitori, garantendo così un elevato livello di flessibilità per diversi scenari. In Kubernetes, i contenitori vengono eseguiti in pod e la loro implementazione è definita dai Deployment di Kubernetes. Questo articolo del blog tratta dei Deployment di Kubernetes, delle loro tipologie, delle strategie di implementazione e delle procedure consigliate.

Prova NAKIVO Backup & Replication

Prova NAKIVO Backup & Replication

Richiedi una prova gratuita per scoprire tutte le funzionalità di protezione dei dati offerte dalla soluzione. 15 giorni gratuiti. Nessuna limitazione in termini di funzionalità o capacità. Non è obbligatoria alcuna carta di credito.

Che cos’è un’implementazione in Kubernetes?

Un’implementazione in Kubernetes è un oggetto risorsa che gestisce l’implementazione e il ciclo di vita delle applicazioni containerizzate nei cluster. Fornisce aggiornamenti alle applicazioni, garantendo che il numero necessario di pod identici sia sempre in esecuzione e disponibile. Fornisce inoltre aggiornamenti dichiarativi per i pod e i ReplicaSet. Le implementazioni sono una funzione chiave di Kubernetes per automatizzare la scalabilità, gli aggiornamenti graduali e i rollback.

L’implementazione è un oggetto di Kubernetes che specifica lo stato desiderato per i pod e crea e gestisce i ReplicaSet per garantire che tale stato venga mantenuto. Un ReplicaSet gestisce direttamente i pod, inclusi il loro stato e il loro numero. L’implementazione indica a Kubernetes come modificare o creare le istanze di pod che contengono i contenitori con le applicazioni. Lo stato dell’implementazione dei Pod è descritto in un manifesto.

Gli amministratori possono scalare in modo efficiente il numero di Pod di replica, eseguire il rollout del codice dell’applicazione aggiornato con un elevato livello di controllo ed effettuare il rollback a versioni precedenti delle implementazioni, se necessario. Per gestire le implementazioni Kubernetes, gli amministratori utilizzano lo strumento da riga di comando kubectl su Linux e altri sistemi operativi supportati. Ogni pod creato con un Deployment ha un ReplicaSet associato a tale pod. Un ReplicaSet, a sua volta, contiene un puntatore all’implementazione che lo ha creato.

Nozioni fondamentali sulle implementazioni di Kubernetes

Le implementazioni di Kubernetes sono state sviluppate per distribuire e garantire la scalabilità delle applicazioni containerizzate nei cluster. Le implementazioni offrono un modo dichiarativo per definire lo stato desiderato dell’applicazione e automatizzare il processo per raggiungere e mantenere tale stato. I concetti e i componenti fondamentali relativi alle implementazioni di Kubernetes sono:

  • Desired state. L’implementazione definisce lo stato desiderato di un’applicazione, come il numero di repliche di un pod, le immagini dei contenitori da utilizzare e le risorse assegnate a ciascun pod.
  • Declarative configuration. Le implementazioni utilizzano solitamente un approccio dichiarativo, in cui gli amministratori specificano lo stato in un file JSON o YAML. Mentre l’approccio imperativo consente agli amministratori di impostare direttamente le azioni da compiere, l’approccio dichiarativo in Kubernetes permette agli amministratori di definire il risultato obbligatorio, e Kubernetes lo realizzerà tramite i propri meccanismi interni. Il controller di implementazione di Kubernetes effettua il monitoraggio dello stato di salute dei nodi e dei pod. Se si verificano cambiamenti in tempo reale, come ad esempio un guasto di un pod, tale pod può essere sostituito. Pertanto, Kubernetes monitora lo stato del cluster in tempo reale e apporta le modifiche necessarie per allinearlo allo stato desiderato.
  • ReplicaSet. Un’implementazione gestisce un ReplicaSet. Il ReplicaSet crea ed elimina i pod secondo necessità per mantenere il numero desiderato di repliche. Questo approccio consente a Kubernetes di garantire che le repliche dei pod specificate siano in esecuzione in qualsiasi momento.
  • Rolling updates. Le implementazioni supportano gli aggiornamenti graduali (rolling updates), che consentono di aggiornare l’applicazione senza tempi di inattività. Kubernetes sostituisce gradualmente i vecchi pod con quelli nuovi, il che contribuisce a garantire che l’applicazione containerizzata rimanga disponibile durante il processo di aggiornamento dell’implementazione.
  • Rollback. Se qualcosa va storto durante un aggiornamento in Kubernetes, è possibile eseguire un rollback a una versione precedente dell’implementazione, ripristinando l’applicazione a uno stato noto e in buono stato.

Configurazione dichiarativa

In Kubernetes, un approccio dichiarativo significa specificare lo stato desiderato del sistema, e Kubernetes intraprende le azioni necessarie per raggiungere e mantenere tale stato. Si descrive l’obiettivo finale utilizzando file di configurazione (in genere scritti in YAML o JSON) e Kubernetes lavora continuamente per garantire che lo stato effettivo corrisponda a quello desiderato.

L’approccio dichiarativo è generalmente preferito in Kubernetes perché consente di mantenere la coerenza dello stato desiderato, facilita l’automazione e supporta una migliore collaborazione e il controllo delle versioni. L’approccio imperativo può essere utile per attività rapide e ad hoc, ma è meno adatto alla gestione di implementazioni complesse e a lungo termine di applicazioni.

Come interagiscono tra loro Deployment, Pod e ReplicaSet

In Kubernetes, Deployment, Pod e ReplicaSet sono componenti strettamente correlati che gestiscono collettivamente l’implementazione, la scalabilità e il ciclo di vita delle applicazioni. È importante comprendere la relazione tra di essi e sapere come interagiscono in Kubernetes per una corretta configurazione.

  • Un Pod è l’oggetto più semplice e più piccolo in un cluster Kubernetes e rappresenta una singola istanza di un processo in esecuzione. Un Pod può contenere uno o più contenitori che condividono gli stessi volumi di storage e lo stesso namespace di rete. I Pod sono effimeri per definizione, poiché possono essere creati e distrutti secondo necessità per allinearsi allo stato desiderato specificato da oggetti di livello superiore come le implementazioni.
  • Un ReplicaSet garantisce che un numero specificato di Pod identici sia in esecuzione in un dato momento. Gestisce la creazione e l’eliminazione dei Pod per mantenere il numero desiderato di repliche. Ogni ReplicaSet utilizza selettori di etichette per identificare e gestire i Pod sotto il proprio controllo, garantendo che vengano mantenuti i Pod corretti. Sebbene sia possibile creare e gestire direttamente i ReplicaSet, questi sono in genere gestiti dalle implementazioni, che forniscono funzionalità aggiuntive.
  • Un Deployment è un oggetto Kubernetes di livello superiore che gestisce i ReplicaSet e fornisce aggiornamenti dichiarativi alle applicazioni. Le implementazioni consentono agli amministratori di definire lo stato richiesto dell’applicazione e altre impostazioni, come spiegato in precedenza.

Quando si crea o si aggiorna un’implementazione, viene automaticamente creato un nuovo ReplicaSet per gestire i Pod in base alle specifiche definite. Ogni volta che si aggiorna un Deployment, viene creato un nuovo ReplicaSet per gestire la nuova versione dei Pod, mentre il vecchio ReplicaSet rimane attivo fino a quando i nuovi Pod non vengono implementati con successo. Ciò garantisce che gli aggiornamenti vengano implementati in modo controllato, mantenendo la disponibilità dell’applicazione.

Il Deployment gestisce il ciclo di vita dei Pod indirettamente attraverso i propri ReplicaSet. Definendo lo stato desiderato nel Deployment, si specificano le caratteristiche e il numero di Pod che si desidera eseguire. L’implementazione garantisce quindi tale stato gestendo i ReplicaSet appropriati, che a loro volta gestiscono i Pod.

Pertanto, i Pod sono le unità di esecuzione che eseguono i contenitori, i ReplicaSet assicurano che sia in esecuzione il numero necessario di Pod e l’implementazione fornisce una gestione dichiarativa e gli aggiornamenti per le applicazioni controllando i ReplicaSet. Questa struttura gerarchica garantisce che le applicazioni siano scalabili, con una elevata resilienza e facili da gestire. I Deployment astraggono le complessità della gestione diretta dei ReplicaSet e dei Pod, offrendo un modo potente per gestire gli aggiornamenti e la scalabilità delle applicazioni.

Dettagli sulla configurazione delle implementazioni

L’uso di YAML per la configurazione delle implementazioni in Kubernetes è una pratica comune grazie alla sua leggibilità e semplicità. Kubernetes supporta entrambi i formati YAML e JSON per i file di configurazione, ma YAML è più ampiamente utilizzato grazie alla sua sintassi intuitiva.

YAML (YAML Ain’t Markup Language) è uno standard di serializzazione dei dati che è sia leggibile dall’utente che facile da scrivere. È comunemente utilizzato per i file di configurazione e lo scambio di dati tra linguaggi con strutture dati diverse. In Kubernetes, YAML viene utilizzato per definire lo stato desiderato di vari oggetti, tra cui implementazioni, Servizi, Pod e altro ancora. Componenti chiave di un file di implementazione YAML:

  • apiVersion viene utilizzato per specificare la versione dell’API (ad esempio, apps/v1) dell’oggetto Kubernetes.
  • kind specifica il tipo di oggetto Kubernetes (ad esempio, Deployment).
  • metadata contiene i metadati relativi all’oggetto, come il nome e le etichette.
  • spec (specification) viene utilizzato per definire lo stato desiderato dell’oggetto Kubernetes, tra cui:
    • replicas specifica il numero di repliche del pod da mantenere.
    • selector specifica come identificare i pod gestiti dall’implementazione.
    • template definisce il modello del pod, inclusi i metadati e le specifiche per i pod.
    • containers elenca i contenitori all’interno del pod, tra cui:
      • name: il nome del contenitore
      • image: l’immagine Docker da utilizzare
      • ports: le porte da esporre

Le differenze tra YAML e JSON per la sintassi di configurazione dell’implementazione di Kubernetes sono:

  • YAML è più leggibile dall’uomo, utilizzando indentazioni e coppie chiave-valore senza parentesi graffe o quadre.
  • JSON utilizza una struttura più rigida con parentesi graffe ({}) e parentesi quadre ([]), rendendolo meno leggibile per configurazioni complesse.

YAML è solitamente preferito per le implementazioni di Kubernetes.

Esempio di implementazione in YAML

Di seguito è possibile vedere un esempio di implementazione di Kubernetes in formato YAML con dettagli per pod e contenitori.

apiVersion: apps/v1

kind: Deployment

metadata:

  name: deployment-name

spec:

  replicas: 3

  selector:

    matchLabels:

      app: app-name

  template:

    metadata:

      labels:

        app: app-name

    spec:

      containers:

      - name: container-name

        image: image-name:1.0

        ports:

        - containerPort: 80

        resources:

          requests:

            memory: "128Mi"

            cpu: "250m"

          limits:

            memory: "256Mi"

            cpu: "500m"

        env:

        - name: MY_ENV_VAR

          value: "some-value"

        volumeMounts:

        - mountPath: "/path/volume"

          name: volume-name

      volumes:

      - name: volume-name

        persistentVolumeClaim:

          claimName: pvc-name

Spieghiamo ogni sezione in dettagli per renderla più chiara. Configurando queste sezioni, è possibile definire un’implementazione robusta e scalabile di un’applicazione in Kubernetes, assicurando che i pod e i contenitori siano impostati in base ai propri requisiti.

Sezioni di configurazione chiave

1. Metadati

metadata:

  name: deployment-name

Dove:

name: il nome dell’implementazione

2. Spec (Specifiche dell’implementazione)

spec:

  replicas: 3

  selector:

    matchLabels:

      app: app-name

Dove:

replicas: il numero di repliche del pod da mantenere

selector: definisce come identificare i pod gestiti dall’implementazione utilizzando le etichette

3. Modello di pod (Specifiche del pod)

template:

  metadata:

    labels:

      app: app-name

  spec:

    containers:

    - name: container-name

      image: image-name:1.0

Dove: metadata: etichette per identificare i pod

spec: configurazione dei pod e dei relativi contenitori

Configurazione dei contenitori

In questa sezione sono riportate le sezioni YAML delle implementazioni relative alla configurazione dei contenitori.

1. Immagine del contenitore

image: image-name:1.0

Dove:

image: l’immagine del contenitore da utilizzare. Può includere un tag (ad es., 1.0) per specificare la versione.

2. Porte.

ports:

- containerPort: 80

Dove:

containerPort: la porta su cui il contenitore ascolterà il traffico

3. Richieste e limiti delle risorse

resources:

  requests:

    memory: "128Mi"

    cpu: "250m"

  limits:

    memory: "256Mi"

    cpu: "500m"

Dove:

requests: le risorse obbligatorie

limits: le risorse massime che il contenitore può utilizzare

4. Variabili d’ambiente

env:

- name: MY_ENV_VAR

  value: "some-value"

Dove:

env: definisce le variabili d’ambiente per il contenitore

5. Montaggio dei volumi

volumeMounts:

- mountPath: "/path/volume"

  name: volume-name

Dove:

volumeMounts: specifica i volumi da montare all’interno del contenitore

mountPath: il percorso all’interno del contenitore in cui verrà montato il volume

Configurazione dei volumi

La sezione dei volumi è responsabile della configurazione dei volumi.

volumes:

- name: volume-name

  persistentVolumeClaim:

    claimName: pvc-name

Dove:

volumes: definisce i volumi disponibili per il montaggio

name: il nome del volume

persistentVolumeClaim: specifica un PersistentVolumeClaim (PVC) da utilizzare per il volume

Configurazioni avanzate

1. Probe di liveness e readiness

livenessProbe:

  httpGet:

    path: /healthz

    port: 8080

  initialDelaySeconds: 3

  periodSeconds: 3

readinessProbe:

  httpGet:

    path: /ready

    port: 8080

  initialDelaySeconds: 5

  periodSeconds: 10

Dove:

livenessProbe: verifica se il contenitore è attivo

readinessProbe: viene utilizzato per verificare se il contenitore corrente è pronto ad accettare traffico

2. Comando e argomenti

command: ["my-command"]

args: ["arg1", "arg2"]

Dove:

command: sovrascrive il punto di ingresso predefinito del contenitore

args: specifica gli argomenti del comando

3. ConfigMap e secret

envFrom:

- configMapRef:

  name: my-configmap

- secretRef:

  name: my-secret

Dove:

envFrom: importa le variabili d’ambiente da una ConfigMap o da un secret

La strategia di implementazione di Kubernetes

Esistono diverse strategie (tipi) di implementazione di Kubernetes ed è possibile selezionare quella più efficace per il proprio scenario attuale. Le applicazioni aziendali presentano requisiti diversi in termini di uptime e disponibilità. Scegliere la strategia giusta consente di evitare tempi di inattività e interruzioni del servizio, oltre a utilizzare le risorse in modo efficace. Di seguito sono riportati i tipi di implementazione Kubernetes più comuni.

Aggiornamenti e rollback progressivi

Una implementazione con aggiornamento progressivo prevede la migrazione da una versione dell’applicazione a un’altra, quella più recente, secondo l’ordine definito. Viene avviato un nuovo ReplicaSet con la nuova versione dell’applicazione. Le repliche della versione precedente vengono terminate. Di conseguenza, i pod della versione precedente vengono sostituiti con quelli nuovi. L’aggiornamento graduale consente una transizione fluida dalle versioni precedenti a quelle nuove, ma l’operazione richiede un po’ di tempo per essere completata.

Ricreazione dell’implementazione

I pod attualmente in esecuzione vengono terminati e poi ricreati con una nuova versione. Questa strategia di implementazione è comunemente utilizzata negli ambienti Kubernetes destinati agli sviluppatori, dove l’attività degli utenti non rappresenta un problema. Si verifica un periodo di inattività quando il vecchio deployment viene arrestato; la strategia di ricreazione avvia le nuove istanze di implementazione e ricrea i pod e lo stato dell’applicazione.

Implementazioni Blue-Green

L’implementazione Blue-Green è un altro modo per aggiornare le applicazioni in Kubernetes, ma con una transizione rapida. L’implementazione Blue-Green di Kubernetes presuppone l’esecuzione di due ambienti: la versione precedente (blu) e quella nuova (verde). Entrambe vengono distribuite “affiancate” o in parallelo. Una volta che la nuova versione è stata testata e ne è stato confermato il corretto funzionamento (funziona come previsto), l’etichetta della versione viene sostituita aggiornando il Service Selector. Questa operazione viene eseguita su un oggetto Service di Kubernetes che gestisce il bilanciamento del carico in un cluster. Successivamente, il traffico viene immediatamente reindirizzato alla nuova versione.

La strategia di implementazione Blue-Green di Kubernetes consente agli amministratori di eseguire un rollout rapido senza i problemi causati dalle diverse versioni durante la transizione tra di esse. Si tenga presente che l’utilizzo delle risorse è maggiore poiché due ambienti sono in esecuzione in parallelo per un certo periodo.

Implementazioni Canary

L’implementazione Canary di Kubernetes prevede l’instradamento di solo un piccolo gruppo di utenti verso la nuova versione di un’applicazione containerizzata. La nuova versione viene eseguita su un sottoinsieme più ristretto di pod rispetto alla versione precedente che era in esecuzione fino a quel momento. Lo scopo principale delle implementazioni canary è testare la funzionalità delle nuove versioni delle applicazioni in un ambiente di produzione. Se nella nuova versione non si riscontrano errori, gli amministratori ne aumentano la scalabilità e la versione precedente viene sostituita secondo l’ordine appropriato.

Se qualcosa va storto dopo l’implementazione della nuova versione per un piccolo gruppo di utenti, gli amministratori possono ripristinare la versione precedente tramite rollback delle implementazioni canary. Il vantaggio è la possibilità di testare la nuova funzionalità su un piccolo gruppo di utenti senza il rischio di effetti negativi sul funzionamento complessivo del sistema.

Kubernetes: Ricreazione dell’implementazione

Quando si utilizza la ricreazione dell’implementazione, tutti i pod vengono terminati e sostituiti con la nuova versione. Questa strategia può essere utilizzata quando la versione precedente e quella nuova non possono essere eseguite contemporaneamente. Il tempo di inattività dipende dal tempo necessario per arrestare la vecchia applicazione e avviare la nuova applicazione nei contenitori. Al termine, lo stato dell’applicazione viene completamente rinnovato.

Scalabilità e gestione

Scalabilità e gestione delle implementazioni di Kubernetes sono fondamentali per garantire che le vostre applicazioni containerizzate possano funzionare con carichi di lavoro variabili e mantenere un’elevata disponibilità. Kubernetes fornisce meccanismi robusti sia per la scalabilità manuale che automatizzata, oltre a strumenti per gestire le implementazioni in modo efficiente.

Scalabilità manuale

La scalabilità manuale viene utilizzata per regolare manualmente il numero di repliche (istanze) dell’applicazione tramite lo kubectl strumento da riga di comando.

  • Aumento della scalabilità:

    kubectl scale deployment deployment-name --replicas=10

    Questo comando aumenta il numero di repliche per my-implementazione a 10.

  • Riduzione della scalabilità:

    kubectl scale deployment deployment-name --replicas=2

    Questo comando riduce il numero di repliche per my-implementazione a 2.

Horizontal Pod Autoscaler (HPA)

L’Horizontal Pod Autoscaler (HPA) regola automaticamente il numero di repliche dei pod in base all’utilizzo della CPU osservato o ad altre metriche selezionate.

  1. Il comando per creare un HPA è:

    kubectl autoscale deployment deployment-name --cpu-percent=50 --min=2 --max=10

    Questo comando configura un HPA per deployment-name in modo da mantenere l’utilizzo della CPU intorno al 50%, con una scalabilità tra 2 e 10 repliche.

  2. La configurazione HPA in YAML rappresenta un approccio più avanzato. Di seguito viene illustrato un esempio di configurazione di implementazione YAML per l’autoscaling orizzontale.

    apiVersion: autoscaling/v1

    kind: HorizontalPodAutoscaler

    metadata:

      name: deployment-hpa-name

    spec:

      scaleTargetRef:

        apiVersion: apps/v1

        kind: Deployment

        name: deployment-name

      minReplicas: 2

      maxReplicas: 10

      targetCPUUtilizationPercentage: 50

    Applica la configurazione YAML con:

    ubectl apply -f hpa.yaml

Vertical Pod Autoscaler (VPA)

Il Vertical Pod Autoscaler (VPA) regola automaticamente le richieste e i limiti delle risorse dei pod in base all’utilizzo effettivo. La configurazione VPA in YAML è la seguente:

apiVersion: autoscaling.k8s.io/v1

kind: VerticalPodAutoscaler

metadata:

  name: deployment-vpa-name

spec:

  targetRef:

    apiVersion: "apps/v1"

    kind: Deployment

    name: deployment-name

  updatePolicy:

    updateMode: "Auto"

Per applicare la configurazione YAML, utilizzare il comando: kubectl apply -f vpa.yaml

Procedure consigliate per le implementazioni Kubernetes

Una corretta configurazione delle implementazioni Kubernetes garantisce un ambiente affidabile e funzionante per l’esecuzione di applicazioni containerizzate. Una configurazione errata o una strategia di gestione delle implementazioni inadeguata può causare tempi di inattività, perdita di dati e altri problemi. Le procedure consigliate per le implementazioni Kubernetes contribuiscono a garantire che le vostre applicazioni siano resilienti, scalabili e facili da mantenere.

  • Use declarative configuration. Memorizzate le vostre configurazioni di Kubernetes in file sottoposti a controllo delle versioni nei formati YAML/JSON. Ciò semplifica la gestione delle modifiche e il rollback, se necessario. Utilizzate kubectl apply -f per applicare queste configurazioni, poiché consente operazioni idempotenti, garantendo che lo stato del cluster corrisponda ai file di configurazione.
  • Use namespace isolation. Utilizzate i namespace per isolare logicamente diversi ambienti (ad esempio, sviluppo, staging, produzione) e team. Ciò aiuta a gestire le risorse e le autorizzazioni in modo più efficace.
  • Resource requests and limits. Definisci le richieste e i limiti delle risorse per i tuoi pod per garantire che dispongano delle risorse necessarie e per prevenire il contenzioso sulle risorse.
  • Liveness and readiness probes. Configura i liveness probe per riavviare i contenitori non funzionanti e i readiness probe per controllare il traffico verso i contenitori.
  • Use labels and selectors per organizzare e selezionare le risorse. Le etichette possono essere utilizzate per raggruppare le risorse per applicazione, ambiente, versione, ecc.
  • Use ConfigMaps and secrets. Archivia i dati di configurazione non sensibili in ConfigMap. Archiviate i dati sensibili, incluse password e chiavi API, nei Secrets.
  • Monitor and log your environment. Implementate il monitoraggio utilizzando strumenti come Grafana e Prometheus per verificare le prestazioni e lo stato di salute delle vostre applicazioni containerizzate. Utilizzate soluzioni di logging centralizzate come lo stack ELK (Elasticsearch, Logstash, Kibana) o Fluentd per raccogliere e analizzare i log.
  • Follow security best practices. Implementate le politiche di sicurezza dei pod (Pod Security Policies) per applicare gli standard di sicurezza ai vostri pod. Utilizzate i criteri di rete per le implementazioni Kubernetes per controllare il traffico tra i pod.
  • Prepare for backups and disaster recovery. Eseguite backup regolari delle vostre risorse Kubernetes e dei dati persistenti. Pianificate e testate strategie di ripristino di emergenza per garantire che le applicazioni e i servizi possano essere ripristinati rapidamente in caso di guasto.

Conclusione

Le implementazioni Kubernetes svolgono un ruolo cruciale nella gestione del ciclo di vita delle applicazioni all’interno di un cluster Kubernetes. Forniscono un approccio dichiarativo alla definizione dello stato desiderato delle applicazioni, compreso il numero di repliche, le immagini dei contenitori e le impostazioni di configurazione. Attraverso l’orchestrazione dei ReplicaSet, i Deployment garantiscono che il numero specificato di Pod sia in esecuzione e gestiscono automaticamente gli aggiornamenti e i rollback in modo controllato e senza interruzioni. Ciò si traduce in una maggiore scalabilità, resilienza e facilità di gestione delle applicazioni, rendendo i Deployment di Kubernetes uno strumento essenziale per la distribuzione e la gestione delle applicazioni moderne.

Prova NAKIVO Backup & Replication

Prova NAKIVO Backup & Replication

Richiedi una prova gratuita per scoprire tutte le funzionalità di protezione dei dati offerte dalla soluzione. 15 giorni gratuiti. Nessuna limitazione in termini di funzionalità o capacità. Non è obbligatoria alcuna carta di credito.

Le persone leggono anche