Se connecter à une instance Redis à partir d'un service Cloud Run

Vous pouvez vous connecter à une instance Redis à partir de Cloud Run à l'aide de la sortie VPC directe.

Configuration

Si vous avez déjà installé Google Cloud CLI et créé une instance Redis, vous pouvez ignorer ces étapes.

  1. Installez la gcloud CLI et initialisez-la :

    gcloud init
    
  2. Suivez le Guide de démarrage rapide pour créer une instance Redis. Retenez la zone, l'adresse IP et le port de l'instance Redis.

Préparer la sortie réseau VPC pour la configuration

Pour se connecter à votre instance Redis, votre service Cloud Run doit avoir accès au réseau VPC autorisé de votre instance Redis.

Pour trouver le nom de ce réseau, exécutez la commande suivante :

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

Notez le nom du réseau.

Exemple d'application

Cet exemple d'application de serveur HTTP établit une connexion à une instance Redis à partir d'un service Cloud Run.

Clonez le dépôt correspondant au langage de programmation de votre choix et accédez au dossier contenant l'exemple de code :

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

Cet exemple de code incrémente un compteur Redis à chaque accès au point de terminaison /.

Go

Cette application utilise le client github.com/gomodule/redigo/redis. Installez-le en exécutant la commande suivante :

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

Exemple d'application :


// 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

Cette application est basée sur un servlet Jetty 3.1.

Il utilise la bibliothèque Jedis :

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

La classe AppServletContextListener permet de créer un pool de connexions Redis longue durée :


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

La classe VisitCounterServlet est un servlet Web qui incrémente un compteur 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

Cette application utilise le module redis. Voici un exemple de fichier 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"
  }
}

Exemple de code d'application :

'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

Cette application utilise Flask pour la diffusion Web et le package redis-py pour communiquer avec l'instance Redis.

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

Exemple de code d'application :

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)

Déployer l'application dans Cloud Run

Pour déployer l'application, procédez comme suit :

  1. Copiez le fichier Dockerfile dans le répertoire source :

    cp cloud_run_deployment/Dockerfile .
    
  2. Créez une image de conteneur à l'aide de Cloud Build en exécutant la commande suivante :

    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. Déployez le conteneur sur Cloud Run en exécutant la commande suivante :

    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
    

    où :

    • REPO_REGION est la région du dépôt.
    • REPO_ID est le nom du dépôt contenant l'image de l'application.
    • PROJECT_ID est l'ID de votre projet Google Cloud .
    • REGION est la région dans laquelle se trouve votre instance Redis.
    • NETWORK est le nom du réseau VPC autorisé auquel votre instance Redis est associée.
    • SUBNET est le nom de votre sous-réseau. Le sous-réseau doit être /26 ou supérieur. La sortie VPC directe est compatible avec les plages IPv4 RFC 1918, RFC 6598 et Classe E.
    • REDIS_IP et REDIS_PORT sont l'adresse IP et le numéro de port de votre instance Redis.

Une fois le déploiement terminé, la ligne de commande affiche l'URL de votre service Cloud Run. Accédez à cette URL dans un navigateur Web (ou utilisez un outil tel que curl) et observez le compteur sur votre instance Redis à chaque visite du service.