Menghubungkan ke instance Redis dari layanan Cloud Run

Anda dapat terhubung ke instance Redis dari Cloud Run menggunakan traffic keluar VPC Langsung.

Penyiapan

Jika Anda telah menginstal Google Cloud CLI dan membuat instance Redis, Anda dapat melewati langkah-langkah ini.

  1. Instal gcloud CLI dan lakukan inisialisasi:

    gcloud init
    
  2. Ikuti Panduan Memulai Cepat untuk membuat instance Redis. Catat zona, alamat IP, dan port instance Redis.

Menyiapkan traffic keluar jaringan VPC untuk konfigurasi

Agar terhubung ke instance Redis, layanan Cloud Run Anda harus memiliki akses ke jaringan VPC resmi instance Redis.

Untuk menemukan nama jaringan ini, jalankan perintah berikut:

    gcloud redis instances describe INSTANCE_ID --region REGION --format "value(authorizedNetwork)"

Catat nama jaringan.

Contoh aplikasi

Aplikasi server HTTP contoh ini membuat koneksi ke instance Redis dari layanan Cloud Run.

Buat clone repositori untuk bahasa pemrograman yang Anda pilih dan buka folder yang berisi kode contoh:

Go

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

Java

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

Node.js

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

Python

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

Aplikasi contoh ini menambah penghitung Redis setiap kali endpoint / diakses.

Go

Aplikasi ini menggunakan klien github.com/gomodule/redigo/redis. Instal dengan menjalankan perintah berikut:

go get github.com/gomodule/redigo/redis@latest

Contoh aplikasi:


// Command redis is a basic app that connects to a managed Redis instance.
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/gomodule/redigo/redis"
)

var redisPool *redis.Pool

func incrementHandler(w http.ResponseWriter, r *http.Request) {
	conn := redisPool.Get()
	defer conn.Close()

	counter, err := redis.Int(conn.Do("INCR", "visits"))
	if err != nil {
		http.Error(w, "Error incrementing visitor counter", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Visitor number: %d", counter)
}

func main() {
	redisHost := os.Getenv("REDISHOST")
	redisPort := os.Getenv("REDISPORT")
	redisAddr := fmt.Sprintf("%s:%s", redisHost, redisPort)

	const maxConnections = 10
	redisPool = &redis.Pool{
		MaxIdle: maxConnections,
		Dial:    func() (redis.Conn, error) { return redis.Dial("tcp", redisAddr) },
	}

	http.HandleFunc("/", incrementHandler)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Printf("Listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

Java

Aplikasi ini berbasis servlet Jetty 3.1.

Library ini menggunakan library Jedis:

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>5.1.0</version>
</dependency>

Class AppServletContextListener digunakan untuk membuat kumpulan koneksi Redis yang berjalan lama:


package com.example.redis;

import java.io.IOException;
import java.util.Properties;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@WebListener
public class AppServletContextListener implements ServletContextListener {

  private Properties config = new Properties();

  private JedisPool createJedisPool() throws IOException {
    String host;
    Integer port;
    config.load(
        Thread.currentThread()
            .getContextClassLoader()
            .getResourceAsStream("application.properties"));
    host = config.getProperty("redis.host");
    port = Integer.valueOf(config.getProperty("redis.port", "6379"));

    JedisPoolConfig poolConfig = new JedisPoolConfig();
    // Default : 8, consider how many concurrent connections into Redis you will need under load
    poolConfig.setMaxTotal(128);

    return new JedisPool(poolConfig, host, port);
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool != null) {
      jedisPool.destroy();
      event.getServletContext().setAttribute("jedisPool", null);
    }
  }

  // Run this before web application is started
  @Override
  public void contextInitialized(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool == null) {
      try {
        jedisPool = createJedisPool();
        event.getServletContext().setAttribute("jedisPool", jedisPool);
      } catch (IOException e) {
        // handle exception
      }
    }
  }
}

Class VisitCounterServlet adalah servlet web yang menambah penghitung Redis:


package com.example.redis;

import java.io.IOException;
import java.net.SocketException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@WebServlet(name = "Track visits", value = "")
public class VisitCounterServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    try {
      JedisPool jedisPool = (JedisPool) req.getServletContext().getAttribute("jedisPool");

      if (jedisPool == null) {
        throw new SocketException("Error connecting to Jedis pool");
      }
      Long visits;

      try (Jedis jedis = jedisPool.getResource()) {
        visits = jedis.incr("visits");
      }

      resp.setStatus(HttpServletResponse.SC_OK);
      resp.getWriter().println("Visitor counter: " + String.valueOf(visits));
    } catch (Exception e) {
      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    }
  }
}

Node.js

Aplikasi ini menggunakan modul redis. Berikut adalah contoh file package.json:

{
  "name": "memorystore-redis",
  "description": "An example of using Memorystore(Redis) with Node.js",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": ">=16.0.0"
  },
  "dependencies": {
    "redis": "^4.0.0"
  }
}

Contoh kode aplikasi:

'use strict';
const http = require('http');
const redis = require('redis');

const REDISHOST = process.env.REDISHOST || 'localhost';
const REDISPORT = process.env.REDISPORT || 6379;

const client = redis.createClient(REDISPORT, REDISHOST);
client.on('error', err => console.error('ERR:REDIS:', err));

// create a server
http
  .createServer((req, res) => {
    // increment the visit counter
    client.incr('visits', (err, reply) => {
      if (err) {
        console.log(err);
        res.status(500).send(err.message);
        return;
      }
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end(`Visitor number: ${reply}\n`);
    });
  })
  .listen(8080);

Python

Aplikasi ini menggunakan Flask untuk penayangan web dan paket redis-py untuk berkomunikasi dengan instance Redis.

Flask==3.0.3
gunicorn==23.0.0
redis==6.0.0
Werkzeug==3.0.3

Contoh kode aplikasi:

import logging
import os

from flask import Flask
import redis

app = Flask(__name__)

redis_host = os.environ.get("REDISHOST", "localhost")
redis_port = int(os.environ.get("REDISPORT", 6379))
redis_client = redis.StrictRedis(host=redis_host, port=redis_port)


@app.route("/")
def index():
    value = redis_client.incr("counter", 1)
    return f"Visitor number: {value}"


@app.errorhandler(500)
def server_error(e):
    logging.exception("An error occurred during a request.")
    return (
        """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(
            e
        ),
        500,
    )


if __name__ == "__main__":
    # This is used when running locally. Gunicorn is used to run the
    # application on Google App Engine and Cloud Run.
    # See entrypoint in app.yaml or Dockerfile.
    app.run(host="127.0.0.1", port=8080, debug=True)

Men-deploy aplikasi ke Cloud Run

Untuk men-deploy aplikasi:

  1. Salin Dockerfile ke direktori sumber:

    cp cloud_run_deployment/Dockerfile .
    
  2. Bangun image container menggunakan Cloud Build dengan menjalankan perintah berikut:

    gcloud artifacts repositories create --location REPO_REGION --repository-format=docker REPO_ID
    gcloud builds submit --pack image=REPO_REGION-docker.pkg.dev/PROJECT_ID/REPO_ID/visit-counter:v1
    
  3. Deploy container ke Cloud Run dengan menjalankan perintah berikut:

    gcloud run deploy \
      --image REPO_REGION-docker.pkg.dev/PROJECT_ID/REPO_ID/visit-counter:v1 \
      --allow-unauthenticated \
      --region REGION \
      --network NETWORK \
      --subnet SUBNET \
      --set-env-vars REDISHOST=REDIS_IP,REDISPORT=REDIS_PORT
    

    dengan:

    • REPO_REGION adalah region repositori.
    • REPO_ID adalah nama repositori yang berisi image aplikasi.
    • PROJECT_ID adalah ID project Google Cloud Anda.
    • REGION adalah region tempat instance Redis Anda berada.
    • NETWORK adalah nama jaringan VPC resmi yang terpasang ke instance Redis Anda.
    • SUBNET adalah nama subnet Anda. Subnet harus berukuran /26 atau lebih besar. Traffic keluar VPC langsung mendukung rentang IPv4 RFC 1918, RFC 6598, dan Class E.
    • REDIS_IP dan REDIS_PORT adalah alamat IP dan nomor port instance Redis Anda.

Setelah deployment berhasil diselesaikan, command line akan menampilkan URL layanan Cloud Run Anda. Buka URL ini di browser web (atau gunakan alat seperti curl) dan lihat jumlah di instance Redis Anda bertambah setiap kali layanan dikunjungi.