Cloud Run サービスから Redis インスタンスに接続する

ダイレクト VPC 下り(外向き)を使用すると、Cloud Run から Redis インスタンスに接続できます。

設定

Google Cloud CLI をインストール済みで Redis インスタンスを作成済みの場合は、次の手順をスキップできます。

  1. gcloud CLI をインストールして初期化します。

    gcloud init
    
  2. クイックスタート ガイドの手順に沿って Redis インスタンスを作成します。Redis インスタンスのゾーン、IP アドレス、ポート番号をメモしておきます。

構成用に VPC ネットワークの下り(外向き)を準備する

Redis インスタンスに接続するには、Cloud Run サービスが Redis インスタンスの承認済み VPC ネットワークにアクセスできる必要があります。

このネットワークの名前を確認するには、次のコマンドを実行します。

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

ネットワーク名をメモしておきます。

サンプル アプリケーション

このサンプル HTTP サーバー アプリケーションは、Cloud Run サービスから Redis インスタンスへの接続を確立します。

選択したプログラミング言語のリポジトリのクローンを作成し、サンプルコードを含むフォルダに移動します。

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

このサンプル アプリケーションは、/ エンドポイントがアクセスされるたびに Redis カウンタをインクリメントします。

Go

このアプリケーションは、github.com/gomodule/redigo/redis クライアントを使用します。次のコマンドを実行してインストールします。

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

アプリケーションの例:


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

このアプリケーションは、Jetty 3.1 サーブレット ベースです。

Jedis ライブラリを使用します。

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

AppServletContextListener クラスは、長期間有効な Redis 接続プールを作成するために使用されます。


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

VisitCounterServlet クラスは、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

このアプリケーションでは、redis モジュールを使用します。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"
  }
}

サンプル アプリケーション コード:

'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

このアプリケーションでは、ウェブサービスに Flask を使用し、redis-py パッケージを使用して Redis インスタンスと通信します。

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

サンプル アプリケーション コード:

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)

Cloud Run へのアプリケーションのデプロイ

アプリケーションをデプロイするには:

  1. Dockerfile をソース ディレクトリにコピーします。

    cp cloud_run_deployment/Dockerfile .
    
  2. 次のコマンドを実行して、Cloud Build を使用してコンテナ イメージをビルドします。

    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. 次のコマンドを実行して、コンテナを Cloud Run にデプロイします。

    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
    

    ここで

    • REPO_REGION は、リポジトリのリージョンです。
    • REPO_ID は、アプリケーション イメージを含むリポジトリ名です。
    • PROJECT_ID は、 Google Cloud プロジェクトの ID です。
    • REGION は Redis インスタンスが配置されているリージョンです。
    • NETWORK は、Redis インスタンスがアタッチされている承認済み VPC ネットワークの名前です。
    • SUBNET は、サブネットの名前です。サブネットは /26 以上にする必要があります。VPC 下り(外向き)は IPv4 範囲(RFC 1918RFC 6598、クラス E)をサポートします。
    • REDIS_IPREDIS_PORT は、Redis インスタンスの IP アドレスとポート番号です。

デプロイが正常に完了すると、コマンドラインに Cloud Run サービスの URL が表示されます。この URL をウェブブラウザでアクセス(または curl などのツールを使用)し、サービスがアクセスされるたびに Redis インスタンスのカウントが増加することを確認します。