Connessione a un'istanza Redis da un cluster Google Kubernetes Engine

Puoi connetterti alla tua istanza Redis solo da cluster Google Kubernetes Engine che usano la stessa rete autorizzata dell'istanza Redis.

Configurazione

Se hai già installato Google Cloud CLI e hai creato un'istanza Redis, puoi saltare questi passaggi.

  1. Installa l'interfaccia a riga di comando gcloud e inizializza:

    gcloud init
    
  2. Segui la guida rapida. per creare un'istanza Redis. Prendi nota della zona, dell'indirizzo IP e della porta l'istanza Redis.

Preparazione del cluster GKE

  1. Se non hai creato un cluster GKE, creane uno utilizzando seguenti comandi per Google Cloud CLI:

    1. Specifica il progetto per questa applicazione di esempio in gcloud.

      gcloud config set project [PROJECT_ID]

    2. Imposta la variabile di configurazione della zona di Compute Engine in gcloud.

      gcloud config set compute/zone [ZONE]

    3. Crea un cluster GKE denominato visitcount-cluster.

      gcloud container clusters create visitcount-cluster --num-nodes=3 --enable-ip-alias

  2. Se non hai creato il cluster tramite gcloud, utilizza questo comando per recuperare le credenziali del cluster:

    gcloud container clusters get-credentials [CLUSTER_NAME] --zone [CLUSTER_ZONE] --project [PROJECT_ID]
    
    1. CLUSTER_NAME è il nome del tuo cluster GKE.
    2. CLUSTER_ZONE è la zona in cui si trova il tuo cluster.
    3. PROJECT_ID è il progetto in cui si trovano il cluster e le istanze Redis.
  3. Se il tuo cluster è della versione 1.8 o successiva e ha gli alias IP abilitati, salta questo passaggio. Se il tuo cluster è di versione 1.7 o precedente o se il tuo cluster di versione 1.8 o successiva non ha gli alias IP abilitati, segui questa procedura di risoluzione dei problemi prima di provare a connetterti all'istanza.

    1. Esegui questi comandi, sostituendo RESERVED_IP_RANGE con l'intervallo IP riservato dell'istanza:

      git clone https://github.com/bowei/k8s-custom-iptables.git
      cd k8s-custom-iptables/
      TARGETS="RESERVED_IP_RANGE" ./install.sh
      cd ..
      
    2. Se non conosci l'intervallo IP riservato della tua istanza, puoi trovarlo tramite la console (opzioni avanzate) o inserendo questo comando:

      gcloud redis instances describe INSTANCE_ID --region=REGION
      

    Per ulteriori informazioni sugli alias IP, inclusa la creazione di un cluster con questa impostazione abilitata, consulta la documentazione sugli alias IP.

Applicazione di esempio

Questa applicazione di server HTTP di esempio stabilisce una connessione a un'istanza Redis da un cluster Google Kubernetes Engine.

Clona il repository per il linguaggio di programmazione desiderato ed esplora alla cartella che contiene il codice campione:

Vai

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

Questa applicazione di esempio incrementa un contatore Redis ogni volta che l'endpoint / a cui si accede.

Vai

Questa applicazione utilizza github.com/gomodule/redigo/redis di alto profilo. Installalo eseguendo il seguente comando:

go get github.com/gomodule/redigo/redis

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

Questa applicazione è basata su servlet Jetty 3.1.

Utilizza la libreria Jedis:

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

La classe AppServletContextListener viene utilizzata per creare un pool di connessioni Redis di lunga durata:


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 è un servlet web che incrementa un contatore 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

Questa applicazione utilizza il modulo redis.

{
  "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

Questa applicazione utilizza Flask per il servizio web e il pacchetto redis-py per comunicare con l'istanza Redis.

Flask==3.0.3
gunicorn==22.0.0
redis==5.0.1
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)

Creazione dell'immagine container

Crea ed esegui il push dell'immagine container su Container Registry:

cp gke_deployment/Dockerfile .
export PROJECT_ID="$(gcloud config get-value project -q)"
docker build -t gcr.io/${PROJECT_ID}/visit-counter:v1 .
gcloud docker -- push gcr.io/${PROJECT_ID}/visit-counter:v1

Deployment dell'applicazione in Google Kubernetes Engine

Aggiorna gke_deployment/visit-counter.yaml sostituendo <PROJECT_ID> con il tuo ID progetto Google Cloud. Questo file contiene la configurazione del deployment e del servizio.

Vai

Per evitare l'hard-coding dell'IP dell'istanza Redis, puoi creare un ConfigMap Redis:

    export REDISHOST_IP=XXX.XXX.XXX.XXX
    kubectl create configmap redishost --from-literal=REDISHOST=${REDISHOST_IP}

Verifica la configurazione utilizzando il comando seguente:

    kubectl get configmaps redishost -o yaml
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: visit-counter
  labels:
    app: visit-counter
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
    spec:
      containers:
      - name: visit-counter
        image: "gcr.io/<PROJECT_ID>/visit-counter:v1"
        env:
        - name: REDISHOST
          valueFrom:
            configMapKeyRef:
              name: redishost
              key: REDISHOST
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: visit-counter
spec:
  type: LoadBalancer
  selector:
    app: visit-counter
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Java

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: visit-counter
  labels:
    app: visit-counter
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
    spec:
      containers:
      - name: visit-counter
        image: "gcr.io/<PROJECT_ID>/visit-counter:v1"
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: visit-counter
spec:
  type: LoadBalancer
  selector:
    app: visit-counter
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Node.js

Per evitare di codificare l'IP dell'istanza Redis, puoi creare un ConfigMap redishost:

    export REDISHOST_IP=XXX.XXX.XXX.XXX
    kubectl create configmap redishost --from-literal=REDISHOST=${REDISHOST_IP}

Verifica la configurazione utilizzando il seguente comando:

    kubectl get configmaps redishost -o yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: visit-counter
  labels:
    app: visit-counter
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
    spec:
      containers:
      - name: visit-counter
        image: "gcr.io/<PROJECT_ID>/visit-counter:v1"
        env:
        - name: REDISHOST
          valueFrom:
            configMapKeyRef:
              name: redishost
              key: REDISHOST
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: visit-counter
spec:
  type: LoadBalancer
  selector:
    app: visit-counter
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Python

Per evitare di codificare l'IP dell'istanza Redis, puoi creare un ConfigMap redishost:

    export REDISHOST_IP=XXX.XXX.XXX.XXX
    kubectl create configmap redishost --from-literal=REDISHOST=${REDISHOST_IP}

Verifica la configurazione utilizzando il comando seguente:

    kubectl get configmaps redishost -o yaml
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: visit-counter
  labels:
    app: visit-counter
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
    spec:
      containers:
      - name: visit-counter
        image: "gcr.io/<PROJECT-ID>/visit-counter:v1"
        env:
        - name: REDISHOST
          valueFrom:
            configMapKeyRef:
              name: redishost
              key: REDISHOST
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: visit-counter
spec:
  type: LoadBalancer
  selector:
    app: visit-counter
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Applica la configurazione al cluster:

    kubectl apply -f gke_deployment/visit-counter.yaml

Per determinare l'indirizzo [EXTERNAL-IP] di questa app di esempio, esegui questo comando:

    kubectl get service visit-counter

Visualizza la tua app ospitata all'indirizzo http://[EXTERNAL-IP] tramite il browser oppure invia una richiesta GET tramite cURL o il browser:

    curl http://[EXTERNAL-IP]

Rimozione della voce delle tabelle IP per l'istanza Redis

Se hai seguito il passaggio 3 della sezione di questa procedura dettagliata denominata Preparazione del cluster GKE, hai installato l'intervallo IP riservato della tua istanza Redis nelle tabelle IP della tua istanza GKE. Se vuoi rimuovere questo Redis dalla voce dell'intervallo IP dalle tabelle IP dell'istanza GKE, esegui il seguente comando dalla directory k8s-custom-iptables/:

    ./uninstall.sh