Systempakete verwenden

In dieser Anleitung wird gezeigt, wie Sie einen benutzerdefinierten Cloud Run for Anthos-Dienst in Google Cloud erstellen, der einen Eingabeparameter für die Diagrammbeschreibung in ein Diagramm im PNG-Bildformat transformiert. Dabei kommt Graphviz zum Einsatz. Diese Software wird in der Containerumgebung des Dienstes als Systempaket installiert. Graphviz wird dann über Befehlszeilendienstprogramme zum Verarbeiten von Anfragen verwendet.

Ziele

  • Mit einem Dockerfile einen benutzerdefinierten Container schreiben und erstellen
  • Cloud Run for Anthos in Google Cloud-Dienst schreiben, erstellen und bereitstellen
  • Mit dem Graphviz dot-Dienstprogramm Diagramme generieren
  • Dienst testen, indem Sie ein DOT-Syntaxdiagramm aus der Sammlung oder ein eigens erstelltes Diagramm veröffentlichen

Kosten

In dieser Anleitung werden kostenpflichtige Komponenten der Cloud Platform verwendet, darunter:

Der Preisrechner kann eine Kostenschätzung anhand Ihrer voraussichtlichen Nutzung generieren.

Neuen Cloud Platform-Nutzern steht gegebenenfalls eine kostenlose Testversion zur Verfügung.

Hinweis

  1. Melden Sie sich bei Ihrem Google-Konto an.

    Wenn Sie noch kein Konto haben, melden Sie sich hier für ein neues Konto an.

  2. Wählen Sie in der Google Cloud Console auf der Seite der Projektauswahl ein Google Cloud-Projekt aus oder erstellen Sie eines.

    Zur Projektauswahl

  3. Die Abrechnung für das Cloud-Projekt muss aktiviert sein. So prüfen Sie, ob die Abrechnung für Ihr Projekt aktiviert ist.

  4. Cloud Run for Anthos in Google Cloud API aktivieren
  5. Installieren und initialisieren Sie das Cloud SDK.
  6. Installieren Sie die Komponente kubectl:
    gcloud components install kubectl
  7. Installieren Sie die Komponente beta:
    gcloud components install beta
  8. Aktualisieren Sie die Komponenten:
    gcloud components update
  9. Installieren Sie curl, um den Dienst zu testen.
  10. Erstellen Sie einen neuen Cluster. Folgen Sie dazu der Anleitung unter Cloud Run for Anthos in Google Cloud einrichten.

gcloud-Standardeinstellungen einrichten

So konfigurieren Sie gcloud mit Standardeinstellungen für Ihren Cloud Run for Anthos-Dienst in Google Cloud:

  1. Legen Sie ein Standardprojekt fest:

    gcloud config set project PROJECT_ID

    Ersetzen Sie PROJECT_ID durch den Namen des Projekts, das Sie für diese Anleitung verwenden.

  2. Konfigurieren Sie gcloud für Ihren Cluster:

    gcloud config set kuberun/cluster CLUSTER-NAME
    gcloud config set kuberun/cluster_location REGION

    Ersetzen Sie

    • CLUSTER-NAME durch den Namen, den Sie für den Cluster verwendet haben, und
    • REGION durch den unterstützten Clusterstandort Ihrer Wahl.

Codebeispiel abrufen

So rufen Sie das gewünschte Codebeispiel ab:

  1. Klonen Sie das Repository der Beispiel-App auf Ihren lokalen Computer:

    Node.js

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Python

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Go

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

    Java

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

    Sie können auch das Beispiel als ZIP-Datei herunterladen und extrahieren.

  2. Wechseln Sie in das Verzeichnis, das den Beispielcode für Cloud Run for Anthos in Google Cloud enthält:

    Node.js

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

    Python

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

    Go

    cd golang-samples/kuberun/system_package/

    Java

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

Architektur visualisieren

Die grundlegende Architektur sieht so aus:

Diagramm, das den Anfragefluss vom Nutzer zum Webdienst und vom Webdienst zum Graphviz-DOT-Dienstprogramm zeigt
Die Diagrammquelle finden Sie in der DOT-Beschreibung.

Der Nutzer stellt eine HTTP-Anfrage an den Cloud Run for Anthos-Dienst in Google Cloud, der ein Graphviz-Dienstprogramm ausführt, um die Anfrage in ein Bild zu transformieren. Dieses Bild wird an den Nutzer als die HTTP-Antwort auf seine Anfrage gesendet.

Code verstehen

Umgebungskonfiguration mit dem Dockerfile definieren

Ihr Dockerfile ist spezifisch für die Sprache und die grundlegende Betriebsumgebung, z. B. Ubuntu, die Ihr Dienst verwenden wird.

Für diesen Dienst sind ein oder mehrere zusätzliche Systempakete erforderlich, die standardmäßig nicht verfügbar sind.

  1. Öffnen Sie die Dockerfile in einem Editor.

  2. Suchen Sie nach der Anweisung Dockerfile RUN. Mit dieser Anweisung können Sie beliebige Shell-Befehle ausführen, um die Umgebung zu verändern. Wenn das Dockerfile über mehrere Phasen verfügt, also mehrere FROM-Anweisungen aufweist, finden Sie die Anweisung in der letzten Phase.

    Es hängt von dem im Container angegebenen Betriebssystem ab, welche spezifischen Pakete erforderlich sind und mit welchem Mechanismus diese installiert werden.

    Klicken Sie auf den entsprechenden Tab, um Anweisungen für Ihr Betriebssystem oder Basis-Image zu erhalten.

    Debian/Ubuntu
    RUN apt-get update -y && apt-get install -y \
      graphviz \
      && apt-get clean
    Alpine
    Alpine erfordert ein zweites Paket zur Unterstützung von Schriftarten.
    RUN apk --no-cache add graphviz ttf-ubuntu-font-family

    Wenn Sie das Betriebssystem Ihres Container-Images ermitteln möchten, sehen Sie nach, welcher Name in der FROM-Anweisung oder in einer README-Datei für Ihr Basis-Image verwendet wird. Wenn Sie beispielsweise von node erweitern, finden Sie die Dokumentation und das übergeordnete Element Dockerfile auf Docker Hub.

  3. Testen Sie Ihre Anpassung, indem Sie das Image erstellen. Verwenden Sie dazu lokal docker build oder Cloud Build.

Eingehende Anfragen verarbeiten

Der Beispieldienst verwendet Parameter aus der eingegangenen HTTP-Anfrage, um einen Systemaufruf durchzuführen, damit der entsprechende Befehl des dot-Dienstprogramms ausgeführt wird.

Im folgenden HTTP-Handler wird aus der dot-Abfragestringvariable ein Eingabeparameter für die Diagrammbeschreibung extrahiert.

Diagrammbeschreibungen können Zeichen enthalten, die für die Verwendung in einem Abfragestring URL-codiert sein müssen.

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("error: {}".format(e))

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

        return "Internal Server Error", 500

Go


// 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;
    });

Es muss zwischen internen Serverfehlern und ungültigen Nutzereingaben unterschieden werden. Der Beispieldienst gibt für alle dot-Befehlszeilenfehler einen internen Serverfehler zurück, außer wenn die Fehlermeldung den String syntax enthält, was auf ein Problem mit der Nutzereingabe hinweist.

Diagramm generieren

Die Kernlogik der Diagrammgenerierung verwendet das dot-Befehlszeilentool, um den Eingabeparameter für die Diagrammbeschreibung zu einem Diagramm im PNG-Bildformat zu verarbeiten.

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 KubeRun"',
    '-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.
    if not dot:
        raise Exception("syntax: no graphviz definition provided")

    dot_args = [  # These args add a watermark to the dot graphic.
        "-Glabel=Made on KubeRun",
        "-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

Go


// 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 KubeRun",
		"-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 (%v): %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<String>();
  args.add("/usr/bin/dot");
  args.add("-Glabel=\"Made on KubeRun\"");
  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;
}

Sicheren Dienst entwerfen

Alle eventuell bestehenden Sicherheitslücken im dot-Tool sind potenzielle Sicherheitslücken im Webdienst. Sie können dies umgehen, indem Sie aktuelle Versionen des graphviz-Pakets verwenden und das Container-Image regelmäßig neu erstellen.

Wenn Sie das aktuelle Beispiel so erweitern möchten, dass als Befehlszeilenparameter auch Nutzereingaben zulässig sind, sollten Sie gewisse Vorsichtsmaßnahmen treffen, um Angriffe durch Befehlsinjektion zu vermeiden. Auf folgende Arten können Injektionsangriffe beispielsweise verhindert werden:

  • Zuordnen von Eingaben zu den Einträgen eines Wörterbuchs unterstützter Parameter
  • Validierungseingaben entsprechen einer Reihe als sicher eingestufter Werte, möglicherweise unter Verwendung regulärer Ausdrücke
  • Maskieren von Eingaben, damit Shell-Syntax nicht ausgewertet wird

Code versenden

Zum Versenden des Codes führen Sie in Cloud Build einen Build aus, laden ihn in Container Registry hoch und stellen ihn in Cloud Run for Anthos in Google Cloud bereit:

  1. Führen Sie den folgenden Befehl aus, um den Container zu erstellen und ihn in Container Registry zu veröffentlichen:

    Node.js

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

    Dabei ist PROJECT_ID Ihre GCP-Projekt-ID und graphviz der Name, den Sie Ihrem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Python

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

    Dabei ist PROJECT_ID Ihre GCP-Projekt-ID und graphviz der Name, den Sie Ihrem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Go

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

    Dabei ist PROJECT_ID Ihre GCP-Projekt-ID und graphviz der Name, den Sie Ihrem Dienst geben möchten.

    Bei Erfolg wird eine SUCCESS-Meldung mit der ID, Erstellungszeit und dem Image-Namen angezeigt. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Java

    In diesem Beispiel wird Jib verwendet, um Docker-Images mit gängigen Java-Tools zu erstellen. Jib optimiert Container-Builds, ohne dass ein Dockerfile erforderlich ist oder Docker installiert sein muss. Weitere Informationen zum Erstellen von Java-Containern mit Jib

    1. Konfigurieren und erstellen Sie mit dem Dockerfile ein Basis-Image mit den installierten Systempaketen, um das Standard-Basis-Image von Jib zu überschreiben:

      # Use the Official OpenJDK image for a lean production stage of our multi-stage build.
      # https://hub.docker.com/r/adoptopenjdk/openjdk11/
      FROM adoptopenjdk/openjdk11:alpine
      
      RUN apk --no-cache add graphviz ttf-ubuntu-font-family
      gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz-base

      Dabei ist PROJECT_ID die ID des GCP-Projekts.

    2. Erstellen Sie Ihren endgültigen Container mit Jib und veröffentlichen Sie ihn in Container Registry:

      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>2.7.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

      Dabei ist PROJECT_ID die ID des GCP-Projekts.

  2. Stellen Sie es mit dem folgenden Befehl bereit:

    gcloud kuberun core services update graphviz-web --create-if-missing --image gcr.io/PROJECT_ID/graphviz

    Dabei ist PROJECT_ID Ihre GCP-Projekt-ID, graphviz der Name des Containers von oben und graphviz-web der Name des Dienstes.

    Warten Sie, bis die Bereitstellung abgeschlossen ist. Dies kann ungefähr eine halbe Minute dauern.

  3. Wenn Sie eine Codeaktualisierung für den Dienst bereitstellen möchten, wiederholen Sie die vorherigen Schritte. Bei jeder Bereitstellung für einen Dienst wird eine neue Version erstellt und der Traffic wird automatisch verarbeitet, sobald der Dienst bereit ist.

Testen

Testen Sie den Dienst. Dazu senden Sie in der Anfragenutzlast HTTP-POST-Anfragen mit DOT-Syntaxbeschreibungen.

  1. Senden Sie eine HTTP-Anfrage an den Dienst.

    Sie können das Diagramm in eine Webseite einbetten:

    1. So rufen Sie die externe IP-Adresse für das Istio-Ingress-Gateway ab:
      kubectl get svc istio-ingress -n gke-system
      
      Die resultierende Ausgabe sieht in etwa so aus:
      NAME            TYPE           CLUSTER-IP     EXTERNAL-IP  PORT(S)
      istio-ingress   LoadBalancer   XX.XX.XXX.XX   pending      80:32380/TCP,443:32390/TCP,32400:32400/TCP
      
      Die EXTERNAL-IP für den Load-Balancer ist die zu verwendende IP-Adresse.
    2. Führen Sie einen curl-Befehl aus, der die ermittelte EXTERNAL-IP-Adresse in der URL enthält. Lassen Sie dabei das Protokoll weg (z.B.: http://) in 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. Öffnen Sie die ausgegebene diagram.png-Datei in einer Anwendung, die PNG-Dateien unterstützt, beispielsweise Chrome.

    Sie sollte so aussehen:

    Diagramm, das den Phasenablauf vom Code zur Erstellung, dann zur Bereitstellung und schließlich zum „Ausführen“ zeigt
    Quelle: DOT-Beschreibung

Sie können sich eine kleine Sammlung von vorgefertigten Diagrammbeschreibungen ansehen.

  1. Kopieren Sie den Inhalt der ausgewählten .dot-Datei
  2. Fügen Sie ihn in einen curl-Befehl ein:

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

Bereinigen

Wenn Sie ein neues Projekt für diese Anleitung erstellt haben, löschen Sie das Projekt. Wenn Sie ein vorhandenes Projekt verwendet haben und es beibehalten möchten, ohne die Änderungen in dieser Anleitung hinzuzufügen, löschen Sie die für die Anleitung erstellten Ressourcen.

Projekt löschen

Am einfachsten vermeiden Sie weitere Kosten, wenn Sie das zum Ausführen der Anleitung erstellte Projekt löschen.

So löschen Sie das Projekt:

  1. Wechseln Sie in der Cloud Console zur Seite Ressourcen verwalten.

    Zur Seite „Ressourcen verwalten“

  2. Wählen Sie in der Projektliste das Projekt aus, das Sie löschen möchten, und klicken Sie dann auf Löschen.
  3. Geben Sie im Dialogfeld die Projekt-ID ein und klicken Sie auf Shut down (Beenden), um das Projekt zu löschen.

Anleitungsressourcen löschen

  1. Löschen Sie den Cloud Run for Anthos in Google Cloud-Dienst, den Sie in dieser Anleitung bereitgestellt haben:

    gcloud kuberun core services delete SERVICE-NAME

    Dabei ist SERVICE-NAME der von Ihnen ausgewählte Dienstname.

    Sie können Cloud Run for Anthos in Google Cloud-Dienste auch über die Google Cloud Console löschen.

  2. Entfernen Sie die gcloud-Standardkonfigurationen, die Sie während der Einrichtung der Anleitung hinzugefügt haben.

     gcloud config unset kuberun/cluster
     gcloud config unset kuberun/cluster_location
    
  3. Entfernen Sie die Projektkonfiguration:

     gcloud config unset project
    
  4. Löschen Sie sonstige Google Cloud-Ressourcen, die in dieser Anleitung erstellt wurden:

Weitere Informationen