Tutoriel sur l'utilisation des packages système

Ce tutoriel explique comment créer un service Cloud Run personnalisé qui transforme un paramètre d'entrée de description de graphe en un schéma au format d'image PNG. Il utilise l'outil de visualisation Graphviz, qui est installé en tant que package système dans l'environnement de conteneur du service. Graphviz est accessible via les utilitaires de ligne de commande pour le traitement des requêtes.

Objectifs

  • Écrire et créer un conteneur personnalisé avec un fichier Dockerfile.
  • Écrire, créer et déployer un service Cloud Run.
  • Utiliser l'utilitaire Graphviz dot pour générer des schémas.
  • Tester le service en publiant un schéma dans la syntaxe DOT à partir de la collection ou de votre création.

Coûts

Ce tutoriel utilise des composants facturables de Google Cloud, dont :

Utilisez le Simulateur de coût pour générer une estimation des coûts en fonction de votre utilisation prévue.

Les nouveaux utilisateurs de Cloud Platform peuvent bénéficier d'un essai gratuit.

Avant de commencer

  1. Connectez-vous à votre compte Google Cloud. Si vous débutez sur Google Cloud, créez un compte pour évaluer les performances de nos produits en conditions réelles. Les nouveaux clients bénéficient également de 300 $ de crédits gratuits pour exécuter, tester et déployer des charges de travail.
  2. Dans Google Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Google Cloud.

    Accéder au sélecteur de projet

  3. Assurez-vous que la facturation est activée pour votre projet Cloud. Découvrez comment vérifier que la facturation est activée pour votre projet.

  4. Activez l'API Cloud Run.
  5. Installez et initialisez le SDK Cloud.
  6. Mettez à jour les composants :
    gcloud components update
  7. Installez curl pour tester le service

Configurer les paramètres par défaut de gcloud

Pour configurer gcloud avec les valeurs par défaut pour votre service Cloud Run, procédez comme suit :

  1. Définissez le projet par défaut :

    gcloud config set project PROJECT_ID

    Remplacez PROJECT_ID par le nom du projet que vous avez créé pour ce tutoriel.

  2. Configurez gcloud pour la région choisie :

    gcloud config set run/region REGION

    Remplacez REGION par la région Cloud Run compatible de votre choix.

Emplacements Cloud Run

Cloud Run est régional, ce qui signifie que l’infrastructure qui exécute vos services Cloud Run est située dans une région spécifique et gérée par Google pour être disponible de manière redondante dans toutes les zones de cette région.

Lors de la sélection de la région dans laquelle exécuter vos services Cloud Run, vous devez tout d'abord considérer vos exigences en matière de latence, de disponibilité et de durabilité. Vous pouvez généralement sélectionner la région la plus proche de vos utilisateurs, mais vous devez tenir compte de l'emplacement des autres produits Google Cloud utilisés par votre service Cloud Run. L'utilisation conjointe de produits Google Cloud dans plusieurs emplacements peut avoir une incidence sur la latence et le coût de votre service.

Cloud Run est disponible dans les régions suivantes :

Soumis aux tarifs de niveau 1

Soumis aux tarifs de niveau 2

  • asia-east2 (Hong Kong)
  • asia-northeast3 (Séoul, Corée du Sud)
  • asia-southeast1 (Singapour)
  • asia-southeast2 (Jakarta)
  • asia-south1 (Mumbai, Inde)
  • asia-south2 (Delhi, Inde)
  • australia-southeast1 (Sydney)
  • australia-southeast2 (Melbourne)
  • europe-central2 (Varsovie, Pologne)
  • europe-west2 (Londres, Royaume-Uni)
  • europe-west3 (Francfort, Allemagne)
  • europe-west6 (Zurich, Suisse) Icône Feuille Faibles émissions de CO2
  • northamerica-northeast1 (Montréal) Icône Feuille Faibles émissions de CO2
  • northamerica-northeast2 (Toronto)
  • southamerica-east1 (São Paulo, Brésil) Icône Feuille Faibles émissions de CO2
  • us-west2 (Los Angeles)
  • us-west3 (Las Vegas)
  • us-west4 (Salt Lake City)

Si vous avez déjà créé un service Cloud Run, vous pouvez afficher la région dans le tableau de bord Cloud Run de Cloud Console.

Récupérer l'exemple de code

Pour récupérer l’exemple de code à utiliser, procédez comme suit :

  1. Clonez le dépôt de l'exemple d'application sur votre machine locale :

    Node.js

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Python

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Go

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

    Java

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

    Vous pouvez également télécharger l'exemple en tant que fichier ZIP et l'extraire.

  2. Accédez au répertoire contenant l'exemple de code Cloud Run :

    Node.js

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

    Python

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

    Go

    cd golang-samples/run/system_package/

    Java

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

Visualiser l'architecture

L'architecture de base est semblable à ceci :

Schéma montrant le parcours du flux de requêtes d'un utilisateur vers le service Web et l'utilitaire graphviz dot.
Pour accéder à la source du schéma, consultez la page Description DOT

L'utilisateur adresse une requête HTTP au service Cloud Run, lequel exécute un utilitaire Graphviz pour transformer la requête en image. Cette image est transmise à l'utilisateur sous forme de réponse HTTP.

Comprendre le code

Définir la configuration de votre environnement avec le fichier Dockerfile

Le fichier Dockerfile est spécifique au langage et à l'environnement d'exploitation de base que votre service utilisera, par exemple Ubuntu.

La page Démarrage rapide : Construire et déployer montre divers fichiers Dockerfiles pouvant être utilisés comme point de départ afin de créer un fichier Dockerfile pour d'autres services.

Ce service nécessite un ou plusieurs packages système supplémentaires qui ne sont pas disponibles par défaut.

  1. Ouvrez le fichier Dockerfile dans un éditeur.

  2. Recherchez une instruction Dockerfile RUN. Cette instruction permet d'exécuter des commandes d'interface système arbitraires pour modifier l'environnement. Si le fichier Dockerfile comporte plusieurs étapes, identifiées par plusieurs instructions FROM, vous le trouverez à la dernière étape.

    Les packages spécifiques requis et le mécanisme pour les installer varient en fonction du système d'exploitation déclaré dans le conteneur.

    Pour obtenir les instructions relatives à votre système d'exploitation ou à votre image de base, cliquez sur l'onglet correspondant.

    Pour déterminer le système d'exploitation de votre image de conteneur, vérifiez le nom dans l'instruction FROM ou dans un fichier README associé à votre image de base. Par exemple, en cas d'extension depuis node, vous trouverez la documentation et le fichier Dockerfile parent sur Docker Hub.

  3. Créez l'image pour tester votre personnalisation, en utilisant docker build localement ou Cloud Build.

Traiter les requêtes entrantes

L'exemple de service utilise les paramètres de la requête HTTP entrante pour effectuer un appel système qui exécute la commande appropriée de l'utilitaire dot.

Dans le gestionnaire HTTP ci-dessous, un paramètre d'entrée de description de graphe est extrait de la variable de chaîne de requête dot.

Les descriptions de graphe peuvent inclure des caractères qui doivent être encodés au format URL pour pouvoir être utilisés dans une chaîne de requête.

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

Vous devrez faire la distinction entre les erreurs de serveur internes et les entrées utilisateur non valides. Cet exemple de service renvoie une erreur de serveur interne pour toutes les erreurs de ligne de commande dot, sauf si le message d'erreur contient la chaîne syntax, ce qui indique un problème de saisie de la part de l'utilisateur.

Générer un schéma

La logique de base de la génération de schéma utilise l'outil de ligne de commande dot pour traiter le paramètre d'entrée de description de graphe dans un schéma au format d'image 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.
    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

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 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 (%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 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;
}

Concevoir un service sécurisé

Toutes les failles de l'outil dot constituent des failles potentielles du service Web. Pour réduire ces risques, utilisez des versions à jour du package graphviz en recréant régulièrement l'image de conteneur.

Si vous étendez l'exemple actuel pour accepter les entrées utilisateur en tant que paramètres de ligne de commande, vous devez fournir une protection contre les attaques par injection de commande. Des moyens existent pour prévenir les attaques par injection, parmi lesquels :

  • Mapper les entrées dans un dictionnaire de paramètres compatibles.
  • Valider les entrées correspondant à une plage de valeurs connues, en utilisant éventuellement des expressions régulières.
  • Échapper les entrées pour s'assurer que la syntaxe de l'interface système n'est pas évaluée.

Transmettre le code

Pour transmettre votre code, créez un conteneur avec Cloud Build, importez-le dans Container Registry, puis déployez-le sur Cloud Run :

  1. Exécutez la commande suivante pour créer votre conteneur et publier sur Container Registry.

    Node.js

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

    PROJECT_ID est l'ID de votre projet GCP et graphviz le nom que vous souhaitez attribuer à votre service.

    En cas de réussite, un message SUCCESS apparaît contenant l'ID, l'heure de création et le nom de l'image. Celle-ci est stockée dans Container Registry et peut être réutilisée au besoin.

    Python

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

    PROJECT_ID est l'ID de votre projet GCP et graphviz le nom que vous souhaitez attribuer à votre service.

    En cas de réussite, un message SUCCESS apparaît contenant l'ID, l'heure de création et le nom de l'image. Celle-ci est stockée dans Container Registry et peut être réutilisée au besoin.

    Go

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

    PROJECT_ID est l'ID de votre projet GCP et graphviz le nom que vous souhaitez attribuer à votre service.

    En cas de réussite, un message SUCCESS apparaît contenant l'ID, l'heure de création et le nom de l'image. Celle-ci est stockée dans Container Registry et peut être réutilisée au besoin.

    Java

    Cet exemple utilise Jib pour créer des images Docker à l'aide d'outils Java courants. Jib optimise les builds de conteneurs sans requérir de fichier Dockerfile ni d'installation Docker. Découvrez comment créer des conteneurs Java avec Jib.

    1. À l'aide du fichier Dockerfile, configurez et créez une image de base avec les packages système installés pour remplacer l'image de base par défaut de Jib :

      # 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-jre
      
      RUN apk --no-cache add graphviz ttf-ubuntu-font-family
      gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz-base

      PROJECT_ID est l'ID de votre projet GCP.

    2. Utilisez l'assistant d'identification gcloud pour autoriser Docker à transférer du contenu vers votre registre de conteneurs.

      gcloud auth configure-docker

    3. Créez votre conteneur final avec Jib et publiez-le sur Container Registry :

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

      PROJECT_ID est l'ID de votre projet GCP.

  2. Utilisez la commande suivante pour effectuer le déploiement :

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

    PROJECT_ID est l'ID de votre projet GCP, graphviz est le nom du conteneur ci-dessus et graphviz-web est le nom du service.

    Si vous déployez sur Cloud Run, répondez Y à l'invite "Autoriser sans authentification". Pour en savoir plus sur l'authentification basée sur IAM, consultez la page Gérer les accès.

    Patientez jusqu'à la fin du déploiement, soit environ 30 secondes. En cas de réussite, la ligne de commande affiche l'URL du service.

  3. Si vous souhaitez déployer une mise à jour de code sur le service, répétez les opérations précédentes. Chaque déploiement sur un service crée une nouvelle révision et commence automatiquement à acheminer le trafic une fois prêt.

Essayer

Testez votre service en envoyant des requêtes HTTP POST contenant des descriptions de syntaxe DOT dans la charge utile de la requête.

  1. Envoyez une requête HTTP à votre service.

    Copiez l’URL dans la barre d'adresse de votre navigateur et mettez à jour [SERVICE_DOMAIN] :

    https://SERVICE_DOMAIN/diagram.png?dot=digraph Run { rankdir=LR Code -> Build -> Deploy -> Run }

    Le schéma peut être intégré dans une page Web :

    <img src="https://SERVICE_DOMAIN/diagram.png?dot=digraph Run { rankdir=LR Code -> Build -> Deploy -> Run }" />
  2. Ouvrez le fichier diagram.png obtenu dans une application compatible avec les fichiers PNG, telle que Chrome.

    Elle devrait se présenter comme ceci :

    Schéma montrant le flux des différentes étapes : Coder, Créer, Déployer et Exécuter.
    Source : Description DOT

Vous pouvez explorer une petite collection de descriptions de schéma prêtes à l'emploi.

  1. Copiez le contenu du fichier .dot sélectionné.
  2. Collez-le dans une commande curl semblable à celle indiquée ci-dessus :

    https://SERVICE_DOMAIN/diagram.png?dot=SELECTED DOTFILE CONTENTS

Nettoyer

Si vous avez créé un projet pour ce tutoriel, supprimez-le. Si vous avez utilisé un projet existant et que vous souhaitez le conserver sans les modifications du présent tutoriel, supprimez les ressources créées pour ce tutoriel.

Supprimer le projet

Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour ce tutoriel.

Pour supprimer le projet :

  1. Dans Cloud Console, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Supprimer les ressources du tutoriel

  1. Supprimez le service Cloud Run que vous avez déployé dans ce tutoriel :

    gcloud run services delete SERVICE-NAME

    SERVICE-NAME est le nom de service que vous avez choisi.

    Vous pouvez également supprimer des services Cloud Run à partir de Google Cloud Console.

  2. Supprimez la configuration régionale gcloud par défaut que vous avez ajoutée lors de la configuration du tutoriel :

     gcloud config unset run/region
    
  3. Supprimez la configuration du projet :

     gcloud config unset project
    
  4. Supprimez les autres ressources Google Cloud créées dans ce tutoriel :

Étape suivante

  • Testez votre application graphviz :
  • Explorez des architectures de référence, des schémas, des tutoriels et des bonnes pratiques concernant Google Cloud. Consultez notre Centre d'architecture cloud.