Using system packages tutorial

This tutorial shows how to build a custom Cloud Run service that transforms a graph description input parameter into a diagram in the PNG image format. It uses Graphviz and is installed as a system package in the service's container environment. Graphviz is used via command-line utilities to serve requests.

You can use this tutorial with Cloud Run or Cloud Run on GKE.

Objectives

  • Write and build a custom container with a Dockerfile
  • Write, build, and deploy a Cloud Run service
  • Use Graphviz dot utility to generate diagrams
  • Test the service by posting a DOT syntax diagram from the collection or your own creation

Costs

This tutorial uses billable components of Cloud Platform, including:

Use the Pricing Calculator to generate a cost estimate based on your projected usage.

New Cloud Platform users might be eligible for a free trial.

Before you begin

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. Select or create a GCP project.

    Go to the Project selector page

  3. Make sure that billing is enabled for your Google Cloud Platform project.

    Learn how to enable billing

  4. Enable the Cloud Run API
  5. Install and initialize the Cloud SDK.
  6. Install the gcloud beta component:
    gcloud components install beta
  7. For Cloud Run on GKE install the gcloud kubectl component:
    gcloud components install kubectl
  8. Update components:
    gcloud components update
  9. Install curl to try out the service
  10. If you are using Cloud Run on GKE, create a new cluster using the instructions in Setting up Cloud Run on GKE.

Setting up gcloud defaults

To configure gcloud with defaults for your Cloud Run service:

  1. Set your default project:

    gcloud config set project PROJECT-ID

    Replace PROJECT-ID with the name of the project you created for this tutorial.

  2. If you are using Cloud Run (fully managed ), configure the gcloud tool for your chosen region:

    gcloud config set run/region REGION

    Replace REGION with the supported Cloud Run region of your choice.

  3. If you are using Cloud Run on GKE, configure gcloud for your cluster:

    gcloud config set run/cluster CLUSTER_NAME
    gcloud config set run/cluster_location REGION

    Replace

    • CLUSTER_NAME with the name you used for your cluster,
    • REGION with the supported cluster location of your choice.

Cloud Run locations

Cloud Run is regional, which means the infrastructure that runs your Cloud Run services is located in a specific region and is managed by Google to be redundantly available across all the zones within that region.

Meeting your latency, availability, or durability requirements are primary factors for selecting the region where your Cloud Run services are run. You can generally select the region nearest to your users but you should consider the location of the other GCP products that are used by your Cloud Run service. Using GCP products together across multiple locations can affect your service's latency as well as cost.

Cloud Run is available in the following regions:

  • asia-northeast1 (Tokyo)
  • europe-west1 (Belgium)
  • us-central1 (Iowa)
  • us-east1 (South Carolina)

If you already created a Cloud Run service, you can view the region in the Cloud Run dashboard in the GCP Console.

Retrieving the code sample

To retrieve the code sample for use:

  1. Download the sample service code:

  2. Decompress the archive. For example, use these standard Linux and macOS command-line utilities:

    Tarball

    tar -xzvf FILE.tar.gz

    Zip file

    unzip FILE.zip

    Where FILE is the filename of the downloaded archive without the file extension.

Visualizing the architecture

The basic architecture looks like this:

Diagram showing request flow from user to web service to graphviz dot
    utility.
For the diagram source, see the DOT Description

The user makes an HTTP request to the Cloud Run service which executes a Graphviz utility to transform the request into an image. That image is delivered to the user as the HTTP response.

Understanding the code

Defining your environment configuration with the Dockerfile

Your Dockerfile is specific to the language and base operating environment, such as Ubuntu, that your service will use.

The Build and Deploy Quickstart shows various Dockerfiles that can be used as a starting point to build a Dockerfile for other services.

This service requires one or more additional system packages not available by default.

  1. Open the Dockerfile in an editor.

  2. Look for a Dockerfile RUN statement. This statement allows running arbitrary shell commands to modify the environment. If the Dockerfile has multiple stages, identified by finding multiple FROM statements, it will be found in the last stage.

    The specific packages required and the mechanism to install them varies by the operating system declared inside the container.

    To get instructions for your operating system or base image, click the appropriate tab.

    To determine the operating system of your container image, check the name in the FROM statement or a README associated with your base image. For example, if you extend from node, you can find documentation and the parent Dockerfile on Docker Hub.

  3. Test your customization by building the image, using docker build locally or Cloud Build.

Handling incoming requests

The sample service uses parameters from the incoming HTTP request to invoke a system call that executes the appropriate dot utility command.

In the HTTP handler below, a graph description input parameter is extracted from the dot querystring variable.

Graph descriptions can include characters which must be URL encoded for use in a querystring.

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

Go


// diagramHandler renders a diagram using HTTP request parameters and the dot command.
func diagramHandler(w http.ResponseWriter, r *http.Request) {
	var input io.Reader
	if r.Method == http.MethodGet {
		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)
	} else {
		log.Printf("method not allowed: %s", r.Method)
		http.Error(w, fmt.Sprintf("HTTP Method %s Not Allowed", r.Method), http.StatusMethodNotAllowed)
		return
	}

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

You'll need to differentiate between internal server errors and invalid user input. This sample service returns an Internal Server Error for all dot command-line errors unless the error message contains the string syntax, which indicates a user input problem.

Generating a diagram

The core logic of diagram generation uses the dot command-line tool to process the graph description input parameter into a diagram in the PNG image format.

Node.js


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

  const watermark = ` \
    -Glabel="Made on Cloud Run" \
    -Gfontsize=10 \
    -Glabeljust=right \
    -Glabelloc=bottom \
    -Gfontcolor=gray \
  `;
  const image = execSync(`/usr/bin/dot ${watermark} -Tpng`, {
    input: dot,
  });
  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
}

Designing a secure service

Any vulnerabilities in the dot tool are potential vulnerabilities of the web service. You can mitigate this by using up-to-date versions of the graphviz package through re-building the container image on a regular basis.

If you extend the current sample to accept user input as command-line parameters, you should protect against command-injection attacks. Some of the ways to prevent injection attacks include:

  • Mapping inputs to a dictionary of supported parameters
  • Validating inputs match a range of known-safe values, perhaps using regular expressions
  • Escaping inputs to ensure shell syntax is not evaluated

Shipping the code

To ship your code, you build with Cloud Build, and upload to Container Registry, and deploy to Cloud Run or Cloud Run on GKE:

  1. Run the following command to build your container and publish on Container Registry.

    gcloud builds submit --tag gcr.io/PROJECT-ID/graphviz

    Where PROJECT-ID is your GCP project ID, and graphviz is the name you want to give your service.

    Upon success, you will see a SUCCESS message containing the ID, creation time, and image name. The image is stored in Container Registry and can be re-used if desired.

  2. Deploy using the following command:

    gcloud beta run deploy graphviz-web --image gcr.io/PROJECT-ID/graphviz

    Where PROJECT-ID is your GCP project ID, and graphviz is the name of the container from above and graphviz-web is the name of the service.

    If deploying to Cloud Run, answer Y to the "allow unauthenticated" prompt. See Managing Access for more details on IAM-based authentication.

    Wait until the deployment is complete: this can take about half a minute. On success, the command line displays the service URL.

  3. If you want to deploy a code update to the service, repeat the previous steps. Each deployment to a service creates a new revision and automatically starts serving traffic when ready.

Try it out

Try out your service by sending HTTP POST requests with DOT syntax descriptions in the request payload.

  1. Send an HTTP request to your service.

    Copy the URL into your browser URL bar and update [SERVICE_DOMAIN]:

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

    You can embed the diagram in a web page:

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

    Services deployed on Cloud Run on GKE without a custom domain will need to modify this command.

    1. If you do not already have it, determine the ingress gateway of your cluster.

      export GATEWAY_IP="$(kubectl get svc istio-ingressgateway \
         --namespace istio-system \
         --output 'jsonpath={.status.loadBalancer.ingress[0].ip}')"
    2. Run a curl command using this GATEWAY_IP address in the URL.

      curl -G -H "Host: SERVICE_DOMAIN" https://$GATEWAY_IP/diagram.png \
         --data-urlencode "dot=digraph Run { rankdir=LR Code -> Build -> Deploy -> Run }" \
         > diagram.png
  2. Open the resulting diagram.png file in any application that supports PNG files, such as Chrome.

    It should look like this:

    Diagram showing the stage flow
  of Code to Build to Deploy to 'Run'.
    Source: DOT Description

Cleaning up

Deleting the project

The easiest way to eliminate billing is to delete the project that you created for the tutorial.

To delete the project:

  1. In the GCP Console, go to the Projects page.

    Go to the Projects page

  2. In the project list, select the project you want to delete and click Delete .
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Deleting the Cloud Run service

Note that deleting Cloud Run services do not remove any resources stored in Cloud Storage. If you want those deleted, you must delete them separately.

To delete the service you deployed in this tutorial:

gcloud beta run services delete SERVICE_NAME

Where SERVICE_NAME is your chosen service name.

You can also delete Cloud Run or Cloud Run on GKE services from the Google Cloud Platform Console.

What's next

  • Experiment with your graphviz app:
    • Add support for other graphviz utilities which apply different algorithms to diagram generation.
    • Save diagrams to Cloud Storage. Do you want to save the image or the DOT syntax?
    • Implement content abuse protection with Cloud Natural Language.
  • Try out other Google Cloud Platform features for yourself. Have a look at our tutorials.
Var denne side nyttig? Giv os en anmeldelse af den:

Send feedback om...

Cloud Run Documentation