Utilizzo dei pacchetti di sistema


Questo tutorial mostra come creare un servizio Knative serving personalizzato. che trasforma un parametro di input per la descrizione del grafico in un diagramma nell'elemento PNG formato dell'immagine. Utilizza Graphviz che viene installato come pacchetto di sistema nell'ambiente container del servizio. Graphviz viene utilizzato tramite le utilità a riga di comando per gestire le richieste.

Obiettivi

  • Scrivi e crea un container personalizzato con un Dockerfile
  • Scrivi, crea ed esegui il deployment di un servizio Knative serving
  • Usa il punto Graphviz per generare diagrammi
  • Prova il servizio pubblicando un diagramma della sintassi DOT dalla raccolta o una tua creazione

Costi

In questo documento utilizzi i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi basata sull'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud potrebbero essere idonei per una prova gratuita.

Prima di iniziare

Recupero dell'esempio di codice in corso

Per recuperare l'esempio di codice da utilizzare:

  1. Clona il repository dell'app di esempio nella tua macchina locale:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

    Vai

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    In alternativa, puoi scarica l'esempio come file ZIP ed estrarlo.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    In alternativa, puoi scaricare l'esempio come file ZIP ed estrarlo.

  2. Passa alla directory che contiene l'esempio di Knative serving codice:

    Node.js

    cd nodejs-docs-samples/run/system-package/

    Python

    cd python-docs-samples/run/system-package/

    Vai

    cd golang-samples/run/system_package/

    Java

    cd java-docs-samples/run/system-package/

Visualizzazione dell'architettura

L'architettura di base ha questo aspetto:

Diagramma che mostra il flusso di richieste dall'utente al servizio web all'utilità graphviz dot.
Per l'origine del diagramma, consulta la Descrizione DOT

L'utente invia una richiesta HTTP al servizio Knative serving che esegue un'utilità Graphviz per trasformare la richiesta in un'immagine. L'immagine viene comunicata all'utente come risposta HTTP.

Nozioni di base sul codice

Definire la configurazione dell'ambiente con Dockerfile

Dockerfile è specifico per la lingua e l'ambiente operativo di base. come Ubuntu, che il tuo servizio utilizzerà.

Questo servizio richiede uno o più pacchetti di sistema aggiuntivi non disponibili per impostazione predefinita.

  1. Apri Dockerfile in un editor.

  2. Cerca un estratto conto Dockerfile RUN. Questa istruzione consente di eseguire comandi shell arbitrari per modificare dell'ambiente. Se Dockerfile prevede più fasi, identificate da più istruzioni FROM, lo troverai nell'ultima fase.

    I pacchetti specifici richiesti e il meccanismo per installarli variano a seconda il sistema operativo dichiarato all'interno del container.

    Per ricevere istruzioni per il tuo sistema operativo o l'immagine di base, fai clic sulla scheda appropriata.

    Debian/Ubuntu
    RUN apt-get update -y && apt-get install -y \
      graphviz \
      && apt-get clean
    Alpine
    Alps richiede un secondo pacchetto per il supporto dei caratteri.
    RUN apk --no-cache add graphviz ttf-ubuntu-font-family

    Per determinare il sistema operativo dell'immagine del contenitore, controlla il nome nell'istruzione FROM o in un file README associato all'immagine di base. Ad esempio: Se estendi da node, puoi trovare la documentazione e l'istanza principale Dockerfile su Docker Hub.

  3. Testa la personalizzazione creando l'immagine utilizzando docker build localmente o Cloud Build.

Gestione delle richieste in entrata

Il servizio di esempio utilizza i parametri della richiesta HTTP in entrata per richiamare un che esegue il comando di utilità dot appropriato.

Nel gestore HTTP di seguito, viene estratto un parametro di input per la descrizione del grafico la variabile stringa di query dot.

Le descrizioni dei grafici possono includere caratteri che devono essere Codificato come URL per l'utilizzo in una stringa di query.

Node.js

app.get('/diagram.png', (req, res) => {
  try {
    const image = createDiagram(req.query.dot);
    res.setHeader('Content-Type', 'image/png');
    res.setHeader('Content-Length', image.length);
    res.setHeader('Cache-Control', 'public, max-age=86400');
    res.send(image);
  } catch (err) {
    console.error(`error: ${err.message}`);
    const errDetails = (err.stderr || err.message).toString();
    if (errDetails.includes('syntax')) {
      res.status(400).send(`Bad Request: ${err.message}`);
    } else {
      res.status(500).send('Internal Server Error');
    }
  }
});

Python

@app.route("/diagram.png", methods=["GET"])
def index():
    """Takes an HTTP GET request with query param dot and
    returns a png with the rendered DOT diagram in a HTTP response.
    """
    try:
        image = create_diagram(request.args.get("dot"))
        response = make_response(image)
        response.headers.set("Content-Type", "image/png")
        return response

    except Exception as e:
        print(f"error: {e}")

        # If no graphviz definition or bad graphviz def, return 400
        if "syntax" in str(e):
            return f"Bad Request: {e}", 400

        return "Internal Server Error", 500

Vai


// diagramHandler renders a diagram using HTTP request parameters and the dot command.
func diagramHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		log.Printf("method not allowed: %s", r.Method)
		http.Error(w, fmt.Sprintf("HTTP Method %s Not Allowed", r.Method), http.StatusMethodNotAllowed)
		return
	}

	q := r.URL.Query()
	dot := q.Get("dot")
	if dot == "" {
		log.Print("no graphviz definition provided")
		http.Error(w, "Bad Request", http.StatusBadRequest)
		return
	}

	// Cache header must be set before writing a response.
	w.Header().Set("Cache-Control", "public, max-age=86400")

	input := strings.NewReader(dot)
	if err := createDiagram(w, input); err != nil {
		log.Printf("createDiagram: %v", err)
		// Do not cache error responses.
		w.Header().Del("Cache-Control")
		if strings.Contains(err.Error(), "syntax") {
			http.Error(w, "Bad Request: DOT syntax error", http.StatusBadRequest)
		} else {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
		}
	}
}

Java

get(
    "/diagram.png",
    (req, res) -> {
      InputStream image = null;
      try {
        String dot = req.queryParams("dot");
        image = createDiagram(dot);
        res.header("Content-Type", "image/png");
        res.header("Content-Length", Integer.toString(image.available()));
        res.header("Cache-Control", "public, max-age=86400");
      } catch (Exception e) {
        if (e.getMessage().contains("syntax")) {
          res.status(400);
          return String.format("Bad Request: %s", e.getMessage());
        } else {
          res.status(500);
          return "Internal Server Error";
        }
      }
      return image;
    });

Dovrai distinguere tra errori interni del server e input dell'utente non validi. Questo servizio di esempio restituisce un errore interno del server per tutti i punti errori della riga di comando, a meno che il messaggio di errore non contenga la stringa syntax, che indica un problema di input utente.

Generazione di un diagramma

La logica di base della generazione di diagrammi utilizza lo strumento a riga di comando dot per elaborare il parametro di input della descrizione del grafico in un diagramma nel formato immagine PNG.

Node.js

// Generate a diagram based on a graphviz DOT diagram description.
const createDiagram = dot => {
  if (!dot) {
    throw new Error('syntax: no graphviz definition provided');
  }

  // Adds a watermark to the dot graphic.
  const dotFlags = [
    '-Glabel="Made on Cloud Run"',
    '-Gfontsize=10',
    '-Glabeljust=right',
    '-Glabelloc=bottom',
    '-Gfontcolor=gray',
  ].join(' ');

  const image = execSync(`/usr/bin/dot ${dotFlags} -Tpng`, {
    input: dot,
  });
  return image;
};

Python

def create_diagram(dot):
    """Generates a diagram based on a graphviz DOT diagram description.

    Args:
        dot: diagram description in graphviz DOT syntax

    Returns:
        A diagram in the PNG image format.
    """
    if not dot:
        raise Exception("syntax: no graphviz definition provided")

    dot_args = [  # These args add a watermark to the dot graphic.
        "-Glabel=Made on Cloud Run",
        "-Gfontsize=10",
        "-Glabeljust=right",
        "-Glabelloc=bottom",
        "-Gfontcolor=gray",
        "-Tpng",
    ]

    # Uses local `dot` binary from Graphviz:
    # https://graphviz.gitlab.io
    image = subprocess.run(
        ["dot"] + dot_args, input=dot.encode("utf-8"), stdout=subprocess.PIPE
    ).stdout

    if not image:
        raise Exception("syntax: bad graphviz definition provided")
    return image

Vai


// createDiagram generates a diagram image from the provided io.Reader written to the io.Writer.
func createDiagram(w io.Writer, r io.Reader) error {
	stderr := new(bytes.Buffer)
	args := []string{
		"-Glabel=Made on Cloud Run",
		"-Gfontsize=10",
		"-Glabeljust=right",
		"-Glabelloc=bottom",
		"-Gfontcolor=gray",
		"-Tpng",
	}
	cmd := exec.Command("/usr/bin/dot", args...)
	cmd.Stdin = r
	cmd.Stdout = w
	cmd.Stderr = stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("exec(%s) failed (%w): %s", cmd.Path, err, stderr.String())
	}

	return nil
}

Java

// Generate a diagram based on a graphviz DOT diagram description.
public static InputStream createDiagram(String dot) {
  if (dot == null || dot.isEmpty()) {
    throw new NullPointerException("syntax: no graphviz definition provided");
  }
  // Adds a watermark to the dot graphic.
  List<String> args = new ArrayList<>();
  args.add("/usr/bin/dot");
  args.add("-Glabel=\"Made on Cloud Run\"");
  args.add("-Gfontsize=10");
  args.add("-Glabeljust=right");
  args.add("-Glabelloc=bottom");
  args.add("-Gfontcolor=gray");
  args.add("-Tpng");

  StringBuilder output = new StringBuilder();
  InputStream stdout = null;
  try {
    ProcessBuilder pb = new ProcessBuilder(args);
    Process process = pb.start();
    OutputStream stdin = process.getOutputStream();
    stdout = process.getInputStream();
    // The Graphviz dot program reads from stdin.
    Writer writer = new OutputStreamWriter(stdin, "UTF-8");
    writer.write(dot);
    writer.close();
    process.waitFor();
  } catch (Exception e) {
    System.out.println(e);
  }
  return stdout;
}

Progettazione di un servizio sicuro

Eventuali vulnerabilità dello strumento dot sono potenziali vulnerabilità del servizio web. Puoi mitigare il problema utilizzando versioni aggiornate del pacchettographviz rigenerando regolarmente l'immagine del contenitore.

Se estendi l'esempio corrente in modo da accettare l'input utente come riga di comando occorre proteggere attacchi di tipo command-injection. Ecco alcuni modi per impedire gli attacchi di inserimento:

  • Mappatura degli input a un dizionario di parametri supportati
  • La convalida degli input corrisponde a un intervallo di valori di sicurezza nota, magari utilizzando espressioni
  • Sequenza di escape degli input per garantire che la sintassi della shell non venga valutata

Invio del codice

Per inviare il codice, devi utilizzare Cloud Build e caricarlo sul Container Registry ed eseguire il deployment su Knative serving:

  1. Esegui il comando seguente per creare il container e pubblicarlo su Container Registry.

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz

    Dove PROJECT_ID è l'ID progetto Google Cloud e graphviz è l'ID che vuoi assegnare al servizio.

    Se l'operazione riesce, verrà visualizzato il messaggio SUCCESS contenente l'ID, la creazione data e ora e nome dell'immagine. L'immagine è archiviata in Container Registry e, se lo desideri, può essere riutilizzata.

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz

    Dove PROJECT_ID è l'ID progetto Google Cloud e graphviz è l'ID che vuoi assegnare al servizio.

    In caso di esito positivo, viene visualizzato un messaggio di successo contenente l'ID, l'ora di creazione e il nome dell'immagine. L'immagine è archiviata in Container Registry e può essere se lo desideri,

    Vai

    gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz

    dove PROJECT_ID è l'ID progetto Google Cloud e graphviz è il nome che vuoi assegnare al servizio.

    Se l'operazione riesce, verrà visualizzato il messaggio SUCCESS contenente l'ID, la creazione data e ora e nome dell'immagine. L'immagine è archiviata in Container Registry e, se lo desideri, può essere riutilizzata.

    Java

    Questo esempio utilizza Jib per creare immagini Docker utilizzando strumenti Java comuni. Jib ottimizza le build dei container senza dover disporre di un Dockerfile o di Docker. Scopri di più sulla creazione di container Java con Jib.

    1. Utilizzando il Dockerfile, configura e crea un'immagine di base con il sistema pacchetti installati per sostituire l'immagine di base predefinita di Jib:

      # Use the Official eclipse-temurin image for a lean production stage of our multi-stage build.
      # https://hub.docker.com/_/eclipse-temurin/
      FROM eclipse-temurin:17.0.12_7-jre
      
      RUN apt-get update -y && apt-get install -y \
        graphviz \
        && apt-get clean
      gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz-base

      Dove PROJECT_ID è l'ID progetto Google Cloud.

    2. Crea il tuo container finale con Jib e pubblicalo su Container Registry:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.4.0</version>
        <configuration>
          <from>
            <image>gcr.io/PROJECT_ID/graphviz-base</image>
          </from>
          <to>
            <image>gcr.io/PROJECT_ID/graphviz</image>
          </to>
        </configuration>
      </plugin>
      mvn compile jib:build \
       -Dimage=gcr.io/PROJECT_ID/graphviz \
       -Djib.from.image=gcr.io/PROJECT_ID/graphviz-base

      dove PROJECT_ID è l'ID progetto Google Cloud.

  2. Esegui il deployment utilizzando il seguente comando:

    gcloud run deploy graphviz-web --create-if-missing --image gcr.io/PROJECT_ID/graphviz

    dove PROJECT_ID è l'ID progetto Google Cloud, graphviz è il nome del contenitore riportato sopra e graphviz-web è il nome del servizio.

    Attendi il completamento del deployment, che potrebbe richiedere circa mezzo minuto.

  3. Se vuoi eseguire il deployment di un aggiornamento del codice nel servizio, ripeti la precedente passaggi. Ogni deployment in un servizio crea una nuova revisione e, e inizia a gestire il traffico quando è pronto.

Fai una prova

Prova il tuo servizio inviando richieste POST HTTP con sintassi DOT nel payload della richiesta.

  1. Invia una richiesta HTTP al tuo servizio.

    Puoi incorporare il diagramma in una pagina web:

    1. Per ottenere l'IP esterno per il bilanciatore del carico, esegui questo comando:

      kubectl get svc istio-ingressgateway -n ASM-INGRESS-NAMESPACE

      Sostituisci ASM-INGRESS-NAMESPACE con lo spazio dei nomi in cui si trova l'ingresso Cloud Service Mesh. Specifica istio-system se hai installato Cloud Service Mesh utilizzando la configurazione predefinita.

      L'output risultante è simile al seguente:

      NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP  PORT(S)
      istio-ingressgateway   LoadBalancer   XX.XX.XXX.XX   pending      80:32380/TCP,443:32390/TCP,32400:32400/TCP

      dove il valore EXTERNAL-IP è l'indirizzo IP esterno del bilanciatore del carico.

    2. Esegui un comando curl utilizzando questo indirizzo EXTERNAL-IP nell'URL. Azioni sconsigliate includi il protocollo (ad es. http://) a SERVICE_DOMAIN.

      curl -G -H "Host: SERVICE_DOMAIN" http://EXTERNAL-IP/diagram.png \
         --data-urlencode "dot=digraph Run { rankdir=LR Code -> Build -> Deploy -> Run }" \
         > diagram.png
  2. Apri il file diagram.png risultante in qualsiasi applicazione che supporti PNG file, come Chrome.

    Dovrebbe avere il seguente aspetto:

    Diagramma che mostra il flusso di fasi
  dal codice da compilare al deployment in &quot;Esegui&quot;.
    Fonte: Descrizione DOT

Puoi esplorare una piccola raccolta di descrizioni di diagrammi già pronti.

  1. Copia i contenuti del file .dot selezionato
  2. Incollalo in un comando curl:

    curl -G -H "Host: SERVICE_DOMAIN" http://EXTERNAL-IP/diagram.png \
    --data-urlencode "dot=digraph Run { rankdir=LR Code -> Build -> Deploy -> Run }" \
    > diagram.png

Esegui la pulizia

Puoi eliminare le risorse create per questo tutorial per evitare di sostenere costi.

Eliminazione delle risorse dei tutorial

  1. Elimina il servizio Knative serving di cui hai eseguito il deployment in questo tutorial:

    gcloud run services delete SERVICE-NAME

    dove SERVICE-NAME è il nome del servizio scelto.

    Puoi anche eliminare i servizi Knative serving dalla Console Google Cloud:

    Vai a Knative serving

  2. Rimuovi le configurazioni predefinite di gcloud che hai aggiunto durante la configurazione del tutorial:

     gcloud config unset run/platform
     gcloud config unset run/cluster
     gcloud config unset run/cluster_location
    
  3. Rimuovi la configurazione del progetto:

     gcloud config unset project
    
  4. Elimina altre risorse Google Cloud create in questo tutorial:

Passaggi successivi

  • Fai esperimenti con l'app graphviz:
    • È stato aggiunto il supporto di altre utilità di Graphviz che applicano algoritmi diversi a la generazione di diagrammi.
    • Salva i diagrammi in Cloud Storage. Vuoi salvare l'immagine? o la sintassi DOT?
    • Implementa la protezione dall'abuso dei contenuti con l'API Cloud Natural Language.
  • Esplora le architetture di riferimento, i diagrammi e le best practice su Google Cloud. Consulta il nostro Cloud Architecture Center.