5
votes

I'm new to Docker. This project is just for my own understanding. It's likely that I use incorrect terminology and/or am more confused than I think I am. Corrections are gratefully accepted.

I'm using two docker images: the official postgres image and my own Go app and Dockerfile. Using docker-compose up I get the connection refused error.

I think that there are potentially two different problems:

  • One, the database is not running when the app is trying to connect.

  • Two, the app is simply using an invalid IP.

I have app code that should be giving the database time to start up to address the first potential problem (see code below). Judging by the error message, I don't think I'm even getting that far.

I have two services: db-access (that's the Go app) and postgres-db.

I have tried using these host names in the app connection string:

  • "localhost",

  • "postgres-db" (as it's named in the docker-compose.yml),

  • "0.0.0.0".

Using the postgres-db as the hostname:

  • The app container is trying: dial tcp 172.22.0.2:5432.

  • Postgres is saying: listening on IPv4 address "0.0.0.0", port 5432.

In the docker-compose.yml I have tried using these statements:

depends_on:
      - postgres-db

and

links:
      - postgres-db

I have tried reversing the order of the services in the docker-compose.yml but they appear to start up in the same order either way.

When I run the postgres container and the Go app separately I get the expected behavior. To run them separately I'm using these commands:

docker run --rm --name postgres-db -e POSTGRES_PASSWORD=docker -d -p 5432:5432 -v /Users/ForeignFood/Development/go/src/github.com/skillitzimberg/docker/volumes/postgres:/var/lib/postgresql/data postgres

followed by

go run basicapi

I can also run docker-compose up, which gives the connection refused error, then ctrl+C, then run go run basicapi and get the expected behavior.

Here are the project files . . .

main.go:

package main

import (
    "basicapi/models"
    "fmt"
    "net/http"

    _ "github.com/lib/pq"
)

const (
    host     = "postgres-db"
    port     = 5432
    user     = "postgres"
    password = "docker"
    dbname   = "myfirstdb"
)

var psqlDatabaseConnectionString = fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
    host, port, user, password, dbname)

func main() {
    models.InitDB(psqlDatabaseConnectionString)
    http.HandleFunc("/users", usersList)
    http.ListenAndServe(":3000", nil)
}

func usersList(w http.ResponseWriter, r *http.Request) {
    if r.Method != "GET" {
        http.Error(w, http.StatusText(405), 405)
        return
    }

    usrs, err := models.AllUsers()
    if err != nil {
        fmt.Println(err)
        http.Error(w, http.StatusText(500), 500)
        return
    }

    for _, usr := range usrs {
        fmt.Fprintf(w, "%d, %s, %s, %.s\n", usr.ID, usr.FirstName, usr.LastName, usr.Email)
    }
}

models/db.go:

package models

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/lib/pq"
)

var db *sql.DB

func InitDB(dataSourceName string) {
    var err error

    for i := 0; i < 10; i++ {
        db, err = sql.Open("postgres", dataSourceName)

        if err != nil {
            fmt.Println(i)
            fmt.Println(err)
            time.Sleep(time.Second * 10)
        }
    }
    if err != nil {
        log.Panic(err)
    }
    if err = db.Ping(); err != nil {
        log.Panic(err)
    }
    fmt.Printf("Connection to database successful!\n")
}

models/users.go:

package models

import "fmt"

type User struct {
    ID        int
    Age       int
    FirstName string
    LastName  string
    Email     string
}

func AllUsers() ([]*User, error) {
    fmt.Println("Got to AllUsers")
    rows, err := db.Query("SELECT * FROM users")

    if err != nil {
        fmt.Println(err)
        return nil, err
    }
    defer rows.Close()

    users := make([]*User, 0)
    for rows.Next() {
        user := new(User)
        err := rows.Scan(&user.ID, &user.Age, &user.FirstName, &user.LastName, &user.Email)
        if err != nil {
            return nil, err
        }
        users = append(users, user)
    }
    if err = rows.Err(); err != nil {
        return nil, err
    }
    return users, nil
}

Dockerfile:

FROM golang

WORKDIR /app

COPY ./go.mod ./go.mod
COPY ./go.sum ./go.sum

RUN go mod download

COPY . .

RUN go build -o /bin/app

CMD [ "app" ]

docker-compose.yml

services:
  db-access:
    build: .
    depends_on:
      - postgres-db
    ports:
      - "3000:3000"
  postgres-db:
    image: postgres
    volumes:
      - /Users/ForeignFood/Development/go/src/github.com/skillitzimberg/docker/volumes/postgres:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "docker"
      POSTGRES_DATABASE: "myfirstdb"

Expected results: Navigating to localhost:3000/users shows:

1, Someone, Alastname, 
2, SomeoneElse, AnotherLastName,
etc...

Actual results:

  • browser: This site can't be reached

  • terminal:

~/ >> docker-compose up                                                 
Starting basicapi_postgres-db_1 ... done
Starting basicapi_db-access_1   ... done
Attaching to basicapi_postgres-db_1, basicapi_db-access_1
db-access_1    | 2019/05/17 16:53:54 dial tcp 172.22.0.2:5432: connect: connection refused
db-access_1    | panic: dial tcp 172.22.0.2:5432: connect: connection refused
db-access_1    | 
db-access_1    | goroutine 1 [running]:
db-access_1    | log.Panic(0xc0000c3f40, 0x1, 0x1)
db-access_1    |    /usr/local/go/src/log/log.go:333 +0xac
db-access_1    | basicapi/models.InitDB(0xc000062120, 0x55)
db-access_1    |    /app/models/db.go:30 +0x27c
db-access_1    | main.main()
db-access_1    |    /app/main.go:23 +0x3d
basicapi_db-access_1 exited with code 2
postgres-db_1  | 2019-05-17 16:53:58.770 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
postgres-db_1  | 2019-05-17 16:53:58.770 UTC [1] LOG:  listening on IPv6 address "::", port 5432
postgres-db_1  | 2019-05-17 16:53:58.776 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
postgres-db_1  | 2019-05-17 16:53:58.905 UTC [22] LOG:  database system was shut down at 2019-05-17 16:53:23 UTC
postgres-db_1  | 2019-05-17 16:53:58.952 UTC [1] LOG:  database system is ready to accept connections

Thank you for any insights.

2
The app container config for the database host should be host.docker.internal not localhostKinaneD

2 Answers

1
votes

depends_on doesn't guarantee to wait till the database starts successfully. So you will have to change your approach and implement your usecase using shell file that will wait for database to start successfully and will then proceed the execution further.

For this you will have to add this tool in your app Dockerfile that will do the actual wait job.

ENV DOCKERIZE_VERSION v0.6.0
RUN wget 
https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
&& tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
&& rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz

And then you will have to create a shell file say run.sh with following line to wait for the database start.

dockerize -wait http://$DB_HOST:$DB_PORT -timeout 3000s

and then you can call your actual go application command to run your go application from within run.sh file.

Do not forget to add the ENTRYPOINT in Dockerfile to run.sh file

0
votes

I believe the fix to this problem is having better connection wait/retry logic. It seems that the database was being pinged and panicking at the first failure. The only changes made to the code was in the models/db.go file.

package models

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/lib/pq"
)

var db *sql.DB

// InitDB initializes a connection to the database.
func InitDB(dataSourceName string) {
    var err error
    fmt.Println("Initializing database connection . . .")

    for i := 0; i < 10; i++ {
        db, err = sql.Open("postgres", dataSourceName)

        if err != nil {
            fmt.Printf("Unable to Open DB: %s... Retrying\n", err.Error())
            time.Sleep(time.Second * 2)
        } else if err = db.Ping(); err != nil {
            fmt.Printf("Unable to Ping DB: %s... Retrying\n", err.Error())
            time.Sleep(time.Second * 2)
        } else {
            err = nil
            break
        }
    }
    if err != nil {
        log.Panic(err)
    }

    fmt.Printf("Connection to database successful!\n")
}

Another possibility for what fixed the problem was that I needed to use docker-compose up --build because my binary was stale (built on older code).

Sadly, the problem was fixed but I was not keeping track of the changes in code or process. But these are the only two things I found that were different between my having the problem and the problem being fixed.