├── .gitignore ├── Dockerfile ├── deployments ├── go-redis-app.yml └── redis-master.yml ├── go.mod ├── go.sum ├── main.go └── quote.go /.gitignore: -------------------------------------------------------------------------------- 1 | go-redis-kubernetes 2 | out/ 3 | vendor/ 4 | _vendor-* 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile References: https://docs.docker.com/engine/reference/builder/ 2 | 3 | # Start from the latest golang base image 4 | FROM golang:latest as builder 5 | 6 | # Add Maintainer Info 7 | LABEL maintainer="Rajeev Singh " 8 | 9 | # Set the Current Working Directory inside the container 10 | WORKDIR /app 11 | 12 | # Copy go mod and sum files 13 | COPY go.mod go.sum ./ 14 | 15 | # Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed 16 | RUN go mod download 17 | 18 | # Copy the source from the current directory to the Working Directory inside the container 19 | COPY . . 20 | 21 | # Build the Go app 22 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . 23 | 24 | 25 | ######## Start a new stage from scratch ####### 26 | FROM alpine:latest 27 | 28 | RUN apk --no-cache add ca-certificates 29 | 30 | WORKDIR /root/ 31 | 32 | # Copy the Pre-built binary file from the previous stage 33 | COPY --from=builder /app/main . 34 | 35 | # Expose port 8080 to the outside world 36 | EXPOSE 8080 37 | 38 | # Command to run the executable 39 | CMD ["./main"] -------------------------------------------------------------------------------- /deployments/go-redis-app.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment # Type of Kubernetes resource 4 | metadata: 5 | name: go-redis-app # Unique name of the Kubernetes resource 6 | spec: 7 | replicas: 3 # Number of pods to run at any given time 8 | selector: 9 | matchLabels: 10 | app: go-redis-app # This deployment applies to any Pods matching the specified label 11 | template: # This deployment will create a set of pods using the configurations in this template 12 | metadata: 13 | labels: # The labels that will be applied to all of the pods in this deployment 14 | app: go-redis-app 15 | spec: 16 | containers: 17 | - name: go-redis-app 18 | image: callicoder/go-redis-app:1.0.0 19 | imagePullPolicy: IfNotPresent 20 | resources: 21 | requests: 22 | cpu: 100m 23 | memory: 100Mi 24 | ports: 25 | - containerPort: 8080 # Should match the port number that the Go application listens on 26 | env: # Environment variables passed to the container 27 | - name: REDIS_HOST 28 | value: redis-master 29 | - name: REDIS_PORT 30 | value: "6379" 31 | --- 32 | apiVersion: v1 33 | kind: Service # Type of kubernetes resource 34 | metadata: 35 | name: go-redis-app-service # Unique name of the resource 36 | spec: 37 | type: NodePort # Expose the Pods by opening a port on each Node and proxing it to the service. 38 | ports: # Take incoming HTTP requests on port 9090 and forward them to the targetPort of 8080 39 | - name: http 40 | port: 9090 41 | targetPort: 8080 42 | selector: 43 | app: go-redis-app # Map any pod with label `app=go-redis-app` to this service 44 | -------------------------------------------------------------------------------- /deployments/redis-master.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 3 | kind: Deployment 4 | metadata: 5 | name: redis-master # Unique name for the deployment 6 | labels: 7 | app: redis # Labels to be applied to this resource 8 | spec: 9 | selector: 10 | matchLabels: # This deployment applies to the Pods matching these labels 11 | app: redis 12 | role: master 13 | tier: backend 14 | replicas: 1 # Run a single pod in the deployment 15 | template: # Template for the pods that will be created by this deployment 16 | metadata: 17 | labels: # Labels to be applied to the Pods in this deployment 18 | app: redis 19 | role: master 20 | tier: backend 21 | spec: # Spec for the container which will be run inside the Pod. 22 | containers: 23 | - name: master 24 | image: redis 25 | resources: 26 | requests: 27 | cpu: 100m 28 | memory: 100Mi 29 | ports: 30 | - containerPort: 6379 31 | --- 32 | apiVersion: v1 33 | kind: Service # Type of Kubernetes resource 34 | metadata: 35 | name: redis-master # 36 | labels: 37 | app: redis 38 | role: master 39 | tier: backend 40 | spec: 41 | ports: 42 | - port: 6379 # Map incoming connections on port 6379 to the target port 6379 of the Pod 43 | targetPort: 6379 44 | selector: # Map any Pod with the specified labels to this service 45 | app: redis 46 | role: master 47 | tier: backend -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/callicoder/go-redis-kubernetes 2 | 3 | require ( 4 | github.com/callicoder/go-docker-compose v0.0.0-20190723025154-364d64b66975 5 | github.com/go-redis/redis v6.15.2+incompatible 6 | github.com/gorilla/mux v1.7.3 7 | ) 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/callicoder/go-docker-compose v0.0.0-20190723025154-364d64b66975 h1:84f4YL9aFJBE/rzljJP1UXSThWZ2JOhLXD2+2XcWhUE= 2 | github.com/callicoder/go-docker-compose v0.0.0-20190723025154-364d64b66975/go.mod h1:nnwYYbMxHZfkB/UWR73Y+uLX8DbZa3xg1mxp4jjR1AI= 3 | github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 4 | github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= 5 | github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 6 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 7 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 8 | github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= 9 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 10 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/go-redis/redis" 15 | "github.com/gorilla/mux" 16 | ) 17 | 18 | func indexHandler(w http.ResponseWriter, r *http.Request) { 19 | w.Write([]byte("Welcome! Please hit the `/qod` API to get the quote of the day.")) 20 | } 21 | 22 | func quoteOfTheDayHandler(client *redis.Client) http.HandlerFunc { 23 | return func(w http.ResponseWriter, r *http.Request) { 24 | currentTime := time.Now() 25 | date := currentTime.Format("2006-01-02") 26 | 27 | val, err := client.Get(date).Result() 28 | if err == redis.Nil { 29 | log.Println("Cache miss for date ", date) 30 | quoteResp, err := getQuoteFromAPI() 31 | if err != nil { 32 | w.Write([]byte("Sorry! We could not get the Quote of the Day. Please try again.")) 33 | return 34 | } 35 | quote := quoteResp.Contents.Quotes[0].Quote 36 | client.Set(date, quote, 24*time.Hour) 37 | w.Write([]byte(quote)) 38 | } else { 39 | log.Println("Cache Hit for date ", date) 40 | w.Write([]byte(val)) 41 | } 42 | } 43 | } 44 | 45 | func main() { 46 | // Create Redis Client 47 | var ( 48 | host = getEnv("REDIS_HOST", "localhost") 49 | port = string(getEnv("REDIS_PORT", "6379")) 50 | password = getEnv("REDIS_PASSWORD", "") 51 | ) 52 | 53 | client := redis.NewClient(&redis.Options{ 54 | Addr: host + ":" + port, 55 | Password: password, 56 | DB: 0, 57 | }) 58 | 59 | _, err := client.Ping().Result() 60 | if err != nil { 61 | log.Fatal(err) 62 | } 63 | 64 | // Create Server and Route Handlers 65 | r := mux.NewRouter() 66 | 67 | r.HandleFunc("/", indexHandler) 68 | r.HandleFunc("/qod", quoteOfTheDayHandler(client)) 69 | 70 | srv := &http.Server{ 71 | Handler: r, 72 | Addr: ":8080", 73 | ReadTimeout: 10 * time.Second, 74 | WriteTimeout: 10 * time.Second, 75 | } 76 | 77 | // Start Server 78 | go func() { 79 | log.Println("Starting Server") 80 | if err := srv.ListenAndServe(); err != nil { 81 | log.Fatal(err) 82 | } 83 | }() 84 | 85 | // Graceful Shutdown 86 | waitForShutdown(srv) 87 | } 88 | 89 | func waitForShutdown(srv *http.Server) { 90 | interruptChan := make(chan os.Signal, 1) 91 | signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) 92 | 93 | // Block until we receive our signal. 94 | <-interruptChan 95 | 96 | // Create a deadline to wait for. 97 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 98 | defer cancel() 99 | srv.Shutdown(ctx) 100 | 101 | log.Println("Shutting down") 102 | os.Exit(0) 103 | } 104 | 105 | func getQuoteFromAPI() (*QuoteResponse, error) { 106 | API_URL := "http://quotes.rest/qod.json" 107 | resp, err := http.Get(API_URL) 108 | if err != nil { 109 | return nil, err 110 | } 111 | defer resp.Body.Close() 112 | log.Println("Quote API Returned: ", resp.StatusCode, http.StatusText(resp.StatusCode)) 113 | 114 | if resp.StatusCode >= 200 && resp.StatusCode <= 299 { 115 | quoteResp := &QuoteResponse{} 116 | json.NewDecoder(resp.Body).Decode(quoteResp) 117 | return quoteResp, nil 118 | } else { 119 | return nil, errors.New("Could not get quote from API") 120 | } 121 | 122 | } 123 | 124 | func getEnv(key, defaultValue string) string { 125 | value := os.Getenv(key) 126 | if value == "" { 127 | return defaultValue 128 | } 129 | return value 130 | } 131 | -------------------------------------------------------------------------------- /quote.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type QuoteData struct { 4 | Id string `json:"id"` 5 | Quote string `json:"quote"` 6 | Length string `json:"length"` 7 | Author string `json:"author"` 8 | Tags []string `json:"tags"` 9 | Category string `json:"category"` 10 | Date string `json:"date"` 11 | Permalink string `json:"parmalink"` 12 | Title string `json:"title"` 13 | Background string `json:"Background"` 14 | } 15 | 16 | type QuoteResponse struct { 17 | Success APISuccess `json:"success"` 18 | Contents QuoteContent `json:"contents"` 19 | } 20 | 21 | type QuoteContent struct { 22 | Quotes []QuoteData `json:"quotes"` 23 | Copyright string `json:"copyright"` 24 | } 25 | 26 | type APISuccess struct { 27 | Total string `json:"total"` 28 | } 29 | --------------------------------------------------------------------------------