Menggunakan paket sistem


Tutorial ini menunjukkan cara mem-build layanan penayangan Knative kustom yang mengubah parameter input deskripsi grafik menjadi diagram dalam format gambar PNG. Library ini menggunakan Graphviz yang diinstal sebagai paket sistem di lingkungan container layanan. Graphviz digunakan melalui utilitas command line untuk menayangkan permintaan.

Tujuan

  • Menulis dan mem-build container kustom dengan Dockerfile
  • Menulis, mem-build, dan men-deploy layanan penayangan Knative
  • Menggunakan utilitas Graphviz dot untuk membuat diagram
  • Menguji layanan dengan memposting diagram sintaksis DOT dari koleksi atau kreasi Anda sendiri

Biaya

Dalam dokumen ini, Anda menggunakan komponen Google Cloud yang dapat ditagih berikut:

Untuk membuat perkiraan biaya berdasarkan proyeksi penggunaan Anda, gunakan kalkulator harga. Pengguna baru Google Cloud mungkin memenuhi syarat untuk mendapatkan uji coba gratis.

Sebelum memulai

Mengambil contoh kode

Untuk mengambil contoh kode agar dapat digunakan:

  1. Clone repositori aplikasi contoh ke komputer lokal Anda:

    Node.js

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

    Atau, Anda dapat mendownload contoh dalam file ZIP dan mengekstraknya.

    Python

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

    Atau, Anda dapat mendownload contoh dalam file ZIP dan mengekstraknya.

    Go

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

    Atau, Anda dapat mendownload contoh dalam file ZIP dan mengekstraknya.

    Java

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

    Atau, Anda dapat mendownload contoh dalam file ZIP dan mengekstraknya.

  2. Ubah ke direktori yang berisi kode contoh layanan Knative:

    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/

Memvisualisasikan arsitektur

Arsitektur dasarnya seperti ini:

Diagram yang menunjukkan alur permintaan dari pengguna ke layanan web ke utilitas
    graphviz dot.
Untuk sumber diagram, lihat Deskripsi DOT

Pengguna membuat permintaan HTTP ke layanan penayangan Knative yang menjalankan utilitas Graphviz untuk mengubah permintaan menjadi gambar. Gambar tersebut dikirimkan ke pengguna sebagai respons HTTP.

Memahami kode

Menentukan konfigurasi lingkungan Anda dengan Dockerfile

Dockerfile Anda dibuat khusus untuk bahasa dan lingkungan operasi dasar, seperti Ubuntu, yang akan digunakan oleh layanan Anda.

Layanan ini memerlukan satu atau beberapa paket sistem tambahan yang tidak tersedia secara default.

  1. Buka Dockerfile di editor.

  2. Cari pernyataan Dockerfile RUN. Pernyataan ini memungkinkan perintah shell arbitrer dijalankan untuk memodifikasi lingkungan. Jika Dockerfile memiliki beberapa tahapan, yang diidentifikasi dengan menemukan beberapa pernyataan FROM, maka tahapan tersebut akan ditemukan di tahap terakhir.

    Paket spesifik yang diperlukan dan mekanisme untuk menginstalnya bervariasi menurut sistem operasi yang dideklarasikan di dalam container.

    Untuk petunjuk sistem operasi atau image dasar Anda, klik tab yang sesuai.

    Debian/Ubuntu
    RUN apt-get update -y && apt-get install -y \
      graphviz \
      && apt-get clean
    Alpine
    Alpine memerlukan paket kedua untuk dukungan font.
    RUN apk --no-cache add graphviz ttf-ubuntu-font-family

    Untuk menentukan sistem operasi image container, periksa nama dalam pernyataan FROM atau README yang terkait dengan image dasar Anda. Misalnya, jika Anda memperluas dari node, Anda dapat menemukan dokumentasi dan induk Dockerfile di Docker Hub.

  3. Uji penyesuaian Anda dengan mem-build image, menggunakan docker build secara lokal atau Cloud Build.

Menangani permintaan masuk

Layanan contoh menggunakan parameter dari permintaan HTTP yang masuk untuk memanggil panggilan sistem yang menjalankan perintah utilitas dot yang sesuai.

Di pengendali HTTP berikut, parameter input deskripsi grafik diekstrak dari variabel string kueri dot.

Deskripsi grafik dapat menyertakan karakter yang harus dienkode URL untuk digunakan dalam string kueri.

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

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

Anda harus membedakan antara error server internal dan input pengguna yang tidak valid. Layanan contoh ini menampilkan Error Server Internal untuk semua error command line dot kecuali jika pesan error tersebut berisi string syntax, yang menunjukkan masalah input pengguna.

Membuat diagram

Logika inti pembuatan diagram menggunakan alat command line dot untuk memproses parameter input deskripsi grafik menjadi diagram dalam format gambar 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

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

Merancang layanan yang aman

Kerentanan apa pun dalam alat dot adalah potensi kerentanan untuk layanan web. Anda dapat mengurangi hal ini dengan menggunakan versi terbaru paket graphviz dengan mem-build ulang image container secara rutin.

Jika memperluas sampel saat ini untuk menerima input pengguna sebagai parameter command line, Anda harus melindungi dari serangan injection perintah. Beberapa cara untuk mencegah serangan injection meliputi:

  • Memetakan input ke kamus parameter yang didukung
  • Memvalidasi input cocok dengan rentang nilai yang diketahui aman, mungkin menggunakan ekspresi reguler
  • Meng-escape input untuk memastikan sintaksis shell tidak dievaluasi

Mengirimkan kode

Untuk mengirim kode, build kode dengan Cloud Build, lalu upload ke Container Registry, dan deploy ke penayangan Knative:

  1. Jalankan perintah berikut untuk mem-build container dan memublikasikannya di Container Registry.

    Node.js

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

    Dengan PROJECT_ID sebagai project ID Google Cloud Anda, dan graphviz merupakan nama yang ingin Anda berikan ke layanan.

    Setelah berhasil, Anda akan melihat pesan SUCCESS yang berisi ID, waktu pembuatan, dan nama image. Image tersebut disimpan di Container Registry dan dapat digunakan kembali bila diinginkan.

    Python

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

    Dengan PROJECT_ID sebagai project ID Google Cloud Anda, dan graphviz merupakan nama yang ingin Anda berikan ke layanan.

    Setelah berhasil, Anda akan melihat pesan SUCCESS yang berisi ID, waktu pembuatan, dan nama image. Image tersebut disimpan di Container Registry dan dapat digunakan kembali bila diinginkan.

    Go

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

    Dengan PROJECT_ID sebagai project ID Google Cloud Anda, dan graphviz merupakan nama yang ingin Anda berikan ke layanan.

    Setelah berhasil, Anda akan melihat pesan SUCCESS yang berisi ID, waktu pembuatan, dan nama image. Image tersebut disimpan di Container Registry dan dapat digunakan kembali bila diinginkan.

    Java

    Contoh ini menggunakan Jib untuk mem-build image Docker menggunakan alat Java umum. Jib mengoptimalkan build container tanpa memerlukan Dockerfile atau menginstal Docker. Pelajari lebih lanjut cara mem-build container Java dengan Jib.

    1. Dengan Dockerfile, konfigurasikan dan build image dasar menggunakan paket sistem yang diinstal untuk menggantikan image dasar default 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

      Dengan PROJECT_ID sebagai project ID Google Cloud Anda.

    2. Build container akhir Anda dengan Jib dan publikasikan di 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

      Dengan PROJECT_ID sebagai project ID Google Cloud Anda.

  2. Deploy menggunakan perintah berikut:

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

    Dengan PROJECT_ID sebagai project ID Google Cloud Anda, dan graphviz adalah nama penampung dari contoh di atas, dan graphviz-web adalah nama layanan.

    Tunggu hingga deployment selesai: proses ini memerlukan waktu sekitar setengah menit.

  3. Jika Anda ingin men-deploy pembaruan kode ke layanan, ulangi langkah-langkah sebelumnya. Setiap deployment ke layanan akan membuat revisi baru dan otomatis melayani lalu lintas jika sudah siap.

Cobalah

Coba layanan Anda dengan mengirimkan permintaan POST HTTP dengan deskripsi sintaksis DOT dalam payload permintaan.

  1. Kirim permintaan HTTP ke layanan Anda.

    Anda dapat menyematkan diagram di halaman web:

    1. Untuk mendapatkan IP eksternal untuk Load Balancer, jalankan perintah berikut:

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

      Ganti ASM-INGRESS-NAMESPACE dengan namespace tempat ingress Cloud Service Mesh Anda berada. Tentukan istio-system jika Anda menginstal Cloud Service Mesh menggunakan konfigurasi defaultnya.

      Output yang dihasilkan akan terlihat mirip dengan berikut ini:

      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

      dengan nilai EXTERNAL-IP adalah alamat IP eksternal Load Balancer Anda.

    2. Jalankan perintah curl menggunakan alamat EXTERNAL-IP ini di URL. Jangan sertakan protokol (misalnya: http://) di 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. Buka file diagram.png yang dihasilkan di aplikasi apa pun yang mendukung file PNG, seperti Chrome.

    Kodenya akan terlihat seperti berikut:

    Diagram menunjukkan alur tahap
  Code ke Build ke Deploy ke Run.
    Sumber: Deskripsi DOT

Anda dapat menjelajahi koleksi deskripsi diagram siap pakai.

  1. Salin konten file .dot yang dipilih
  2. Tempelkan ke dalam perintah 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

Pembersihan

Anda dapat menghapus resource yang dibuat untuk tutorial ini agar tidak menimbulkan biaya.

Menghapus resource tutorial

  1. Hapus layanan penayangan Knative yang Anda deploy dalam tutorial ini:

    gcloud run services delete SERVICE-NAME

    Dengan SERVICE-NAME adalah nama layanan pilihan Anda.

    Anda juga dapat menghapus layanan penayangan Knative dari konsol Google Cloud:

    Buka Inferensi Knative

  2. Hapus konfigurasi default gcloud yang Anda tambahkan selama penyiapan tutorial:

     gcloud config unset run/platform
     gcloud config unset run/cluster
     gcloud config unset run/cluster_location
    
  3. Hapus konfigurasi project:

     gcloud config unset project
    
  4. Hapus resource Google Cloud lain yang dibuat dalam tutorial ini:

Langkah selanjutnya

  • Bereksperimen dengan aplikasi graphviz:
    • Menambahkan dukungan untuk utilitas graphviz lainnya yang menerapkan berbagai algoritma untuk pembuatan diagram.
    • Simpan diagram ke Cloud Storage. Anda ingin menyimpan gambar atau sintaks DOT?
    • Menerapkan perlindungan terhadap penyalahgunaan konten dengan Cloud Natural Language API.
  • Pelajari arsitektur referensi, diagram, dan praktik terbaik tentang Google Cloud. Lihat Cloud Architecture Center kami.