dockernginxdocker-composewebserverbad-gateway

502 Bad gateway Nginx reversy proxy, connect() failed (111: Connection refused) while connecting to upstream


I have a project, which consist of Go application + Nginx + Db(Postgres). All are building in docker containers. It is my docker-compose.yml file:

version: "3"
services:
  db:
    image: postgres:10
    environment:
      - POSTGRES_PASSWORD=DatabasePassword
      - POSTGRES_USER=egor
      - POSTGRES_DB=postgres
    expose:
      - 5432
    
  backend:
    build: .
    environment:
      - POSTGRES_URL=postgres://egor:DatabasePassword@db:5432/postgres?sslmode=disable
      - LISTEN_ADDRESS=:5432
    depends_on:
      - db
  
  proxy:
    image: nginx
    volumes:
      - type: bind
        source: ./nginx.conf
        target: /etc/nginx/nginx.conf
    ports:
      - 80:80
    depends_on: 
      - backend
      - db

it is my go application:

package main

import (
    "database/sql"
    "fmt"
    "time"
    _ "github.com/lib/pq"
    "log"
    "net/http"

    "github.com/caarlos0/env"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

type config struct {
    PostgresUri   string `env:"POSTGRES_URL" envDefault:"postgres://root:pass@localhost:5432/postgres?sslmode=disable"`
    ListenAddress string `env:"LISTEN_ADDRESS" envDefault:":7000"`
    //PostgresHost  string `env:"POSTGRES_HOST" envDefault:":l"`
    //PostgresUser  string `env:"POSTGRES_USER" envDefault:":root"`
    //PostgresPassword string `env:"POSTGRES_PASSWD" envDefault:":qwerty"`
    //PostgresName  string `env:"POSTGRES_NAME" envDefault:":postgres"`

}

var (
    db          *sql.DB
    errorsCount = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "gocalc_errors_count",
            Help: "Gocalc Errors Count Per Type",
        },
        []string{"type"},
    )

    requestsCount = prometheus.NewCounter(
        prometheus.CounterOpts{
            Name: "gocalc_requests_count",
            Help: "Gocalc Requests Count",
        })
)

func main() {
    var err error

    // Initing prometheus
    prometheus.MustRegister(errorsCount)
    prometheus.MustRegister(requestsCount)

    // Getting env
    cfg := config{}
    if err = env.Parse(&cfg); err != nil {
        fmt.Printf("%+v\n", err)
    }
    
    time.Sleep(time.Second)
    fmt.Println("Sleep over!")
    
    // Connecting to database
    //psqlInfo := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=5432 sslmode=disable",
    //                        cfg.PostgresHost,cfg.ListenAddress,cfg.PostgresUser,cfg.PostgresPassword,cfg.PostgresName)
    
    //db, err := sql.Open("postgres", "host=db user=egor password=DatabasePassword dbname=postgres port=5432 sslmode=disable")
    db, err = sql.Open("postgres",cfg.PostgresUri)
    if err != nil {
        log.Fatalf("Can't connect to postgresql: %v", err)
    }
    defer db.Close()

    err = db.Ping()
    if err != nil {
        log.Fatalf("Can't ping database: %v", err)
    }

    http.HandleFunc("/", handler)
    http.Handle("/metrics", promhttp.Handler())
    log.Fatal(http.ListenAndServe(cfg.ListenAddress, nil))
}

func handler(w http.ResponseWriter, r *http.Request) {
    requestsCount.Inc()

    keys, ok := r.URL.Query()["q"]
    if !ok || len(keys[0]) < 1 {
        errorsCount.WithLabelValues("missing").Inc()
        log.Println("Url Param 'q' is missing")
        http.Error(w, "Bad Request", 400)
        return
    }
    q := keys[0]
    log.Println("Got query: ", q)

    var result string
    sqlStatement := fmt.Sprintf("SELECT (%s)::numeric", q)
    row := db.QueryRow(sqlStatement)
    err := row.Scan(&result)

    if err != nil {
        log.Println("Error from db: %s", err)
        errorsCount.WithLabelValues("db").Inc()
        http.Error(w, "Internal Server Error", 500)
        return
    }

    fmt.Fprintf(w, "query %s; result %s", q, result)
}

And my nginx configuration:

events{
    worker_connections 1024;
}
http{
   server {
      listen 80;
      server_name  localhost;
      location / {
        proxy_pass  http://backend:7000;
      }
  }
}

But when i'm going to try page in browser, i see error page - 502 Bad Gateway nginx. It is my log:

2022/11/08 23:41:24 [error] 29#29: *1 connect() failed (111: Connection refused) while connecting to upstream, client: xxx.xx.x.x, server: localhost, request: "GET / HTTP/1.1", upstream: "http://xxx.xx.x.x:7000/", host: "0.0.0.0"

What is problem? All services work correctly, only nginx reversy proxy has error


Solution

  • I just put together a small project that represents your scenario. This is the repository structure:

    The content of each file are as follows.

    nginx/nginx.conf

    events{}
    
    http {
       server {
          listen 80;
          location / {
            proxy_pass  http://backend:7000;
          }
      }
    }
    

    More or less is your same file.

    nginx/Dockerfile

    FROM nginx
    EXPOSE 80
    COPY nginx.conf /etc/nginx/nginx.conf
    

    Here, we specify instructions to build the nginx container. We expose only the port 80.

    web/main.go

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Hello, World!\n")
        })
    
        http.ListenAndServe(":7000", nil)
    }
    

    Simple HTTP server with a hard-coded reply. This HTTP server listens for requests on port 7000.

    web/Dockerfile

    FROM golang:1.12.7-alpine3.10 AS build
    WORKDIR /go/src/app
    COPY ./main.go ./main.go
    RUN go build -o ./bin/gowebserver ./main.go
    
    FROM alpine:latest
    COPY --from=build /go/src/app/bin /go/bin
    EXPOSE 7000
    ENTRYPOINT go/bin/gowebserver
    

    Here, we use the multi-stage build. In the first section we build the HTTP server while in the second one, we copy the executable on a leaner base image of Docker. We expose port 7000 of this container.

    docker-compose.yaml

    version: "3"
    services:
      backend:
        build: "./web"
        expose:
          - "7000"
      nginx:
        build: "./nginx"
        ports:
          - "80:80"
        depends_on:
          - "backend"
    

    Here, is the last part that connects all. We expose to the outside only the port 80. Internally, the backend service exposes port 7000 to be contacted by the nginx service.
    To spin up everything, you've to run these two commands (in the root folder of the project):

    To test this solution you've to use your internal IP address (in my case was something like 192.168.1.193) and navigate to the URL http://192.168.1.193/ which should give you an Hello, World! message.
    Let me know if this solves your issue!