Verbindung zu einer Redis-Instanz über einen Google Kubernetes Engine-Cluster herstellen

Sie können eine Verbindung zur Redis-Instanz nur über einen Google Kubernetes Engine-Cluster herstellen, der dasselbe autorisierte Netzwerk wie die Redis-Instanz verwendet.

Einrichtung

Wenn Sie die Google Cloud CLI bereits installiert und eine Redis-Instanz erstellt haben, können Sie diese Schritte überspringen.

  1. Installieren Sie die gcloud CLI und initialisieren Sie:

    gcloud init
    
  2. Folgen Sie der Schnellstartanleitung zum Erstellen einer Redis-Instanz. Notieren Sie sich die Zone, die IP-Adresse und den Port der Redis-Instanz.

GKE-Cluster vorbereiten

  1. Wenn Sie noch keinen GKE-Cluster erstellt haben, erstellen Sie einen mit den folgenden Befehlen für die Google Cloud CLI:

    1. Legen Sie das Projekt für diese Beispielanwendung in gcloud fest:

      gcloud config set project [PROJECT_ID]

    2. Legen Sie die Konfigurationsvariable für die Compute Engine-Zone in gcloud fest.

      gcloud config set compute/zone [ZONE]

    3. Erstellen Sie einen GKE-Cluster mit dem Namen visitcount-cluster.

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

  2. Wenn Sie den Cluster nicht über gcloud erstellt haben, verwenden Sie diesen Befehl, um die Anmeldedaten des Clusters abzurufen:

    gcloud container clusters get-credentials [CLUSTER_NAME] --zone [CLUSTER_ZONE] --project [PROJECT_ID]
    
    1. CLUSTER_NAME ist der Name des GKE-Clusters.
    2. CLUSTER_ZONE ist die Zone, in der sich der Cluster befindet.
    3. PROJECT_ID ist das Projekt, in dem sich der Cluster und die Redis-Instanzen befinden.
  3. Wenn die Cluster-Version 1.8 oder höher ist und IP-Aliasse aktiviert sind, überspringen Sie diesen Schritt. Wenn Sie einen Cluster der Version 1.7 oder niedriger haben oder für Ihren Cluster der Version 1.8 oder höher keine IP-Aliasse aktiviert sind, führen Sie die folgenden Umgehungsschritte aus. Daraufhin können Sie versuchen, eine Verbindung zu Ihrer Instanz herzustellen.

    1. Führen Sie diese Befehle aus und ersetzen Sie dabei RESERVED_IP_RANGE durch den reservierten IP-Bereich Ihrer Instanz:

      git clone https://github.com/bowei/k8s-custom-iptables.git
      cd k8s-custom-iptables/
      TARGETS="RESERVED_IP_RANGE" ./install.sh
      cd ..
      
    2. Wenn Sie den reservierten IP-Bereich Ihrer Instanz nicht kennen, rufen Sie ihn über die Konsole (erweiterte Optionen) oder durch Eingabe dieses Befehls ab:

      gcloud redis instances describe INSTANCE_ID --region=REGION
      

    Weitere Informationen zu IP-Aliassen und zum Erstellen eines Clusters mit dieser Einstellung finden Sie in der Dokumentation zu IP-Aliassen.

Beispielanwendung

Diese HTTP-Server-Beispielanwendung stellt eine Verbindung zu einer Redis-Instanz aus einem Google Kubernetes Engine-Cluster her.

Klonen Sie das Repository für die gewünschte Programmiersprache und rufen Sie den Ordner auf, der den Beispielcode enthält:

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

Diese Beispielanwendung erhöht einen Redis-Zähler jedes Mal, wenn auf den Endpunkt / zugegriffen wird.

Go

Diese Anwendung verwendet den github.com/gomodule/redigo/redis-Client. Installieren Sie diesen mit dem folgenden Befehl:

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

Diese Anwendung basiert auf dem Jetty-Servlet 3.1.

Sie verwendet die Jedis-Bibliothek:

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

Die Klasse AppServletContextListener wird zum Erstellen eines langlebigen Redis-Verbindungspools verwendet:


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

Die Klasse VisitCounterServlet ist ein Web-Servlet, das einen Redis-Zähler schrittweise erhöht.


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

Diese Anwendung verwendet das Modul 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

Diese Anwendung verwendet Flask für das Web-Serving und das redis-py-Paket für die Kommunikation mit der Redis-Instanz.

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)

Container-Image erstellen

Erstellen Sie das Container-Image und verschieben Sie es in 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

Anwendung in Google Kubernetes Engine bereitstellen

Aktualisieren Sie gke_deployment/visit-counter.yaml und ersetzen Sie <PROJECT_ID> durch Ihre Google Cloud-Projekt-ID. Diese Datei enthält die Konfiguration für die Bereitstellung und den Dienst.

Go

Wenn Sie die IP-Adresse der Redis-Instanz nicht hartcodieren möchten, können Sie eine redishost-ConfigMap erstellen:

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

So überprüfen Sie die Konfiguration:

    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

Wenn Sie die IP-Adresse der Redis-Instanz nicht hartcodieren möchten, können Sie eine redishost-ConfigMap erstellen:

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

So überprüfen Sie die Konfiguration:

    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

Wenn Sie die IP-Adresse der Redis-Instanz nicht hartcodieren möchten, können Sie eine redishost-ConfigMap erstellen:

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

So überprüfen Sie die Konfiguration:

    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

Wenden Sie die Konfiguration auf Ihren Cluster an:

    kubectl apply -f gke_deployment/visit-counter.yaml

Führen Sie den folgenden Befehl aus, um die [EXTERNAL-IP]-Adresse für diese Beispielanwendung abzurufen:

    kubectl get service visit-counter

Sie können die Anwendung in Ihrem Browser unter http://[EXTERNAL-IP] aufrufen. Alternativ können Sie eine GET-Anfrage über cURL oder Ihren Browser senden:

    curl http://[EXTERNAL-IP]

IP-Tabelleneintrag für die Redis-Instanz entfernen

Wenn Sie Schritt drei des Abschnitts GKE-Cluster vorbereiten dieser Schritt-für-Schritt-Anleitung ausgeführt haben, wurde der reservierte IP-Bereich Ihrer Redis-Instanz in den IP-Tabellen der GKE-Instanz installiert. Wenn Sie diesen Redis-IP-Bereichseintrag aus den IP-Tabellen Ihrer GKE-Instanz entfernen möchten, führen Sie den folgenden Befehl im Verzeichnis k8s-custom-iptables/ aus:

    ./uninstall.sh