Usa paquetes del sistema

En este instructivo, se muestra cómo compilar un servicio personalizado de Cloud Run for Anthos en Google Cloud que transforma un parámetro de entrada de descripción de grafo en un diagrama en el formato de imagen PNG. Usa Graphviz, que se instala como un paquete del sistema en el entorno del contenedor del servicio. Graphviz se usa a través de utilidades de línea de comandos para entregar solicitudes.

Objetivos

  • Escribir y compilar un contenedor personalizado con un Dockerfile
  • Escribir, compilar y, también, implementar un servicio de Cloud Run for Anthos en Google Cloud
  • Usar la utilidad Graphviz dot para generar diagramas
  • Probar el servicio mediante la publicación de un diagrama de sintaxis DOT de la colección o de tu propia creación

Costos

En este instructivo, se usan los siguientes componentes facturables de Cloud Platform:

Usa la calculadora de precios para generar una estimación de los costos según el uso previsto.

Los usuarios nuevos de Cloud Platform pueden ser aptos para una prueba gratuita.

Antes de comenzar

  1. Accede a tu Cuenta de Google.

    Si todavía no tienes una cuenta, regístrate para obtener una nueva.

  2. En la página del selector de proyectos de Google Cloud Console, selecciona o crea un proyecto de Google Cloud.

    Ir a la página del selector de proyectos

  3. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Descubre cómo confirmar que tienes habilitada la facturación en un proyecto.

  4. Habilita Cloud Run for Anthos en la API de Google Cloud
  5. Instala e inicializa el SDK de Cloud.
  6. Instala el componente kubectl:
    gcloud components install kubectl
  7. Instala el componente beta:
    gcloud components install beta
  8. Actualiza los componentes:
    gcloud components update
  9. Instala curl para probar el servicio.
  10. Crea un clúster nuevo mediante las instrucciones en Configura Cloud Run for Anthos en Google Cloud.

Configura los valores predeterminados de gcloud

Si deseas configurar gcloud con los valores predeterminados para el servicio de Cloud Run for Anthos en Google Cloud, haz lo siguiente:

  1. Configura el proyecto predeterminado:

    gcloud config set project PROJECT_ID

    Reemplaza PROJECT_ID por el nombre del proyecto que creaste para este instructivo.

  2. Configura gcloud para tu clúster:

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

    Reemplaza los siguientes elementos:

    • CLUSTER-NAME por el nombre que usaste para el clúster
    • REGION por la ubicación de clúster compatible que elijas

Recupera la muestra de código

A fin de recuperar la muestra de código para su uso, haz lo siguiente:

  1. Clona el repositorio de la app de muestra en tu máquina local:

    Node.js

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

    De manera opcional, puedes descargar la muestra como un archivo zip y extraerla.

    Python

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

    De manera opcional, puedes descargar la muestra como un archivo zip y extraerla.

    Go

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

    De manera opcional, puedes descargar la muestra como un archivo ZIP y extraerla.

    Java

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

    De manera opcional, puedes descargar la muestra como un archivo ZIP y extraerla.

  2. Cambia al directorio que contiene el código de muestra de Cloud Run for Anthos en Google Cloud:

    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/

Visualiza la arquitectura

La arquitectura básica se ve de la siguiente manera:

Diagrama que muestra el flujo de solicitudes del usuario al servicio web a la utilidad graphviz dot.
Para obtener la fuente del diagrama, consulta la Descripción DOT

El usuario realiza una solicitud HTTP al servicio de Cloud Run for Anthos en Google Cloud que ejecuta una utilidad de Graphviz para transformar la solicitud en una imagen. Esa imagen se entrega al usuario como la respuesta HTTP.

Comprende el código

Define la configuración del entorno con el Dockerfile

Tu Dockerfile es específico al lenguaje y al entorno operativo base, como Ubuntu, que usará el servicio.

Este servicio requiere uno o más paquetes del sistema adicionales que no están disponibles de forma predeterminada.

  1. Abre Dockerfile en un editor.

  2. Busca una declaración Dockerfile RUN. Esta instrucción permite ejecutar comandos de shell arbitrarios para modificar el entorno. Si el Dockerfile tiene varias etapas, identificadas mediante la búsqueda de varias declaraciones FROM, se encontrará en la última etapa.

    Los paquetes específicos necesarios y el mecanismo para instalarlos varían según el sistema operativo que se declare dentro del contenedor.

    Para obtener instrucciones sobre el sistema operativo o la imagen base, haz clic en la pestaña correspondiente.

    Debian y Ubuntu
    RUN apt-get update -y && apt-get install -y \
      graphviz \
      && apt-get clean
    Alpine
    Alpine requiere un segundo paquete para la compatibilidad de fuentes.
    RUN apk --no-cache add graphviz ttf-ubuntu-font-family

    Para determinar el sistema operativo de la imagen de contenedor, verifica el nombre en la declaración FROM o en un README asociado con la imagen base. Por ejemplo, si extiendes desde node, puedes encontrar documentación y el Dockerfile superior en Docker Hub.

  3. Compila la imagen mediante docker build de forma local o Cloud Build para probar la personalización.

Administra las solicitudes entrantes

El servicio de muestra usa parámetros de la solicitud HTTP entrante para invocar una llamada del sistema que ejecuta el comando de utilidad dot apropiado.

En el controlador HTTP que se muestra a continuación, se extrae un parámetro de entrada de descripción de grafo de la variable de la string de consulta dot.

Las descripciones del grafo pueden incluir caracteres que deben estar codificados como URL para usarlos en una string de consulta.

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

Deberás diferenciar los errores internos del servidor y las entradas de usuario no válidas. En este servicio de ejemplo, se muestra un error interno del servidor para todos los errores de la línea de comandos de dot, a menos que el mensaje de error contenga la string syntax, que indica un problema de entrada del usuario.

Genera un diagrama

La lógica central de la generación de diagramas usa la herramienta de línea de comandos de dot para procesar el parámetro de entrada de descripción de grafo en un diagrama en el formato de imagen 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 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;
}

Diseña un servicio seguro

Las vulnerabilidades de la herramienta de dot son vulnerabilidades potenciales del servicio web. Puedes mitigar esto mediante el uso de versiones actualizadas del paquete de graphviz. Para ello, debes compilar la imagen del contenedor con regularidad.

Si extiendes la muestra actual para aceptar las entradas del usuario como parámetros de línea de comandos, debes protegerte contra los ataques de inyección de comandos. Estas son algunas de las formas de evitar los ataques de inyección:

  • Asigna entradas a un diccionario de parámetros compatibles.
  • Valida entradas coincidentes con un rango de valores seguros conocidos, quizás mediante expresiones regulares.
  • Escapa entradas para garantizar que no se evalúe la sintaxis de shell.

Envía el código

A fin de enviar el código, debes compilar con Cloud Build, subir a Container Registry e implementar en Cloud Run for Anthos en Google Cloud:

  1. Ejecuta el siguiente comando para compilar el contenedor y publicar en Container Registry.

    Node.js

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

    En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP y graphviz es el nombre que deseas darle al servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Python

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

    En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP y graphviz es el nombre que deseas darle al servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Go

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

    En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP y graphviz es el nombre que deseas darle al servicio.

    Si la operación se completa de manera correcta, verás un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Java

    En esta muestra, se usa Jib para compilar imágenes de Docker mediante herramientas de Java comunes. Jib optimiza las compilaciones de contenedores sin la necesidad de tener un Dockerfile o tener Docker instalado. Obtén más información sobre la compilación de contenedores de Java con Jib.

    1. Mediante Dockerfile, configura y compila una imagen base con los paquetes de sistema instalados a fin de anular la imagen base predeterminada 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
      
      RUN apk --no-cache add graphviz ttf-ubuntu-font-family
      gcloud builds submit --tag gcr.io/PROJECT_ID/graphviz-base

      En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP.

    2. Compila el contenedor final con Jib y publica en 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

      En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP.

  2. Realiza la implementación con el siguiente comando:

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

    En el ejemplo anterior, PROJECT_ID es el ID del proyecto de GCP, graphviz es el nombre del contenedor anterior y graphviz-web es el nombre del servicio.

    Espera hasta que finalice la implementación; esto puede tomar alrededor de medio minuto.

  3. Si deseas implementar una actualización de código en el servicio, repite los pasos anteriores. Cada implementación en un servicio crea una revisión nueva y comienza a entregar tráfico de forma automática cuando está lista.

Realiza una prueba

Para probar el servicio, envía solicitudes HTTP POST con descripciones de sintaxis DOT en la carga útil de la solicitud.

  1. Envía una solicitud HTTP al servicio.

    Puedes incorporar el diagrama en una página web:

    1. Para obtener la IP externa de la puerta de enlace de entrada de Istio, ejecuta lo siguiente:
      kubectl get svc istio-ingress -n gke-system
      
      El resultado se ve de la siguiente manera:
      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
      
      La EXTERNAL-IP del balanceador de cargas es la dirección IP que debes usar.
    2. Ejecuta un comando curl mediante esta dirección EXTERNAL-IP en la URL. No incluyas el protocolo (p. ej.: http://) en 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. Abre el archivo diagram.png resultante en cualquier aplicación que admita archivos PNG, como Chrome.

    Se verá de la siguiente manera:

    Diagrama que muestra el flujo de etapas: de código a compilación, de esta a implementación, y de implementación a ejecución.
    Fuente: Descripción DOT

Puedes explorar una pequeña colección de descripciones de diagramas listas para usar.

  1. Copia el contenido del archivo .dot seleccionado.
  2. Pégalo en 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

Realiza una limpieza

Si creaste un proyecto nuevo para este instructivo, bórralo. Si usaste un proyecto existente y deseas conservarlo sin los cambios que se agregaron en este instructivo, borra los recursos creados para el instructivo.

Borra el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, sigue estos pasos:

  1. En Cloud Console, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

Borra los recursos del instructivo

  1. Borra el servicio de Cloud Run for Anthos en Google Cloud que implementaste en este instructivo:

    gcloud kuberun core services delete SERVICE-NAME

    En el ejemplo anterior, SERVICE-NAME es el nombre del servicio que elegiste.

    También puedes borrar Cloud Run for Anthos en los servicios de Google Cloud desde Google Cloud Console.

  2. Quita las opciones de configuración predeterminadas de gcloud que agregaste durante la configuración del instructivo.

     gcloud config unset kuberun/cluster
     gcloud config unset kuberun/cluster_location
    
  3. Quita la configuración del proyecto:

     gcloud config unset project
    
  4. Borra otros recursos de Google Cloud que creaste en este instructivo:

Próximos pasos

  • Experimenta con tu app de graphviz:
    • Agrega compatibilidad con otras utilidades de graphviz que aplican diferentes algoritmos a la generación de diagramas.
    • Guarda diagramas en Cloud Storage. ¿Deseas guardar la imagen o la sintaxis de DOT?
    • Implementa la protección contra el abuso de contenido con la API de Cloud Natural Language.
    • Consulta otro ejemplo de un paquete del sistema en el Procesamiento de imágenes.
  • Prueba otras funciones de Google Cloud. Consulta nuestros instructivos.