0

I'am currently developing a small app with Golang on macOS and it works perfectly locally.

I have made a docker image from a Dockerfile made from scratch.

My issue is that when container is being launched it hangs indefinitely, docker does not bind ports but I can still go inside the container.

Here are the running processes inside the container:

/go/src/app # ps
PID   USER     TIME  COMMAND
    1 root      0:00 ./main
   13 root      0:00 sh
   23 root      0:00 ps

Here is my docker-compose:

version: "3.3"
services:
  dns:
    build: 
      context: .
    ports:
      - "53:53"

Here is my Dokerfile

FROM golang:alpine


RUN apk add git
WORKDIR /go/src/app
COPY . .
RUN go get -d -v
RUN go build main.go
RUN chmod +x main

EXPOSE 53/udp
EXPOSE 53/tcp

CMD ["./main"]

Logs from docker trying to launch the container:

Building dns
Step 1/10 : FROM golang:alpine
 ---> 813e7bfc1890
Step 2/10 : RUN apk add git
 ---> Using cache
 ---> b796ecde3d09
Step 3/10 : WORKDIR /go/src/app
 ---> Using cache
 ---> cf5226146d6c
Step 4/10 : COPY . .
 ---> e5fd26e9ade8
Step 5/10 : RUN go get -d -v
 ---> Running in ac4c1fe7dd41
github.com/gojektech/heimdall (download)
github.com/gojektech/valkyrie (download)
github.com/pkg/errors (download)
github.com/stretchr/testify (download)
github.com/davecgh/go-spew (download)
github.com/pmezard/go-difflib (download)
github.com/stretchr/objx (download)
get "gopkg.in/yaml.v3": found meta tag get.metaImport{Prefix:"gopkg.in/yaml.v3", VCS:"git", RepoRoot:"https://gopkg.in/yaml.v3"} at //gopkg.in/yaml.v3?go-get=1
gopkg.in/yaml.v3 (download)
github.com/miekg/dns (download)
get "golang.org/x/crypto/ed25519": found meta tag get.metaImport{Prefix:"golang.org/x/crypto", VCS:"git", RepoRoot:"https://go.googlesource.com/crypto"} at //golang.org/x/crypto/ed25519?go-get=1
get "golang.org/x/crypto/ed25519": verifying non-authoritative meta tag
golang.org/x/crypto (download)
get "golang.org/x/net/ipv4": found meta tag get.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} at //golang.org/x/net/ipv4?go-get=1
get "golang.org/x/net/ipv4": verifying non-authoritative meta tag
golang.org/x/net (download)
get "golang.org/x/sys/unix": found meta tag get.metaImport{Prefix:"golang.org/x/sys", VCS:"git", RepoRoot:"https://go.googlesource.com/sys"} at //golang.org/x/sys/unix?go-get=1
get "golang.org/x/sys/unix": verifying non-authoritative meta tag
golang.org/x/sys (download)
get "golang.org/x/net/ipv6": found meta tag get.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} at //golang.org/x/net/ipv6?go-get=1
get "golang.org/x/net/ipv6": verifying non-authoritative meta tag
Removing intermediate container ac4c1fe7dd41
 ---> b9d7f7dfbd1a
Step 6/10 : RUN CGO_ENABLED=0 go build main.go
 ---> Running in f1e34c2b4ff5
Removing intermediate container f1e34c2b4ff5
 ---> 948947d5834f
Step 7/10 : RUN chmod +x main
 ---> Running in f747d80c1784
Removing intermediate container f747d80c1784
 ---> 48d77cb64ede
Step 8/10 : EXPOSE 53/udp
 ---> Running in 154f55021335
Removing intermediate container 154f55021335
 ---> 43fec258b5b7
Step 9/10 : EXPOSE 53/tcp
 ---> Running in 793767d87201
Removing intermediate container 793767d87201
 ---> 5304e6d90c07
Step 10/10 : CMD ["./main"]
 ---> Running in 0d6644f390d2
Removing intermediate container 0d6644f390d2
 ---> 7fc32c2c2e27

Successfully built 7fc32c2c2e27
Successfully tagged lighthouse_dns:latest
Recreating lighthouse_dns_1 ... done
Attaching to lighthouse_dns_1

It hangs at "Attaching to lighthouse_dns_1" for an eternity.

and if I manually launch the executable from the container by doing this:

docker exec -it <container id> /bin/sh

/go/src/app# ./main

Here is the project structure:

.
└── project
    ├── main.go
    └── vendor
        └── services
            ├── dns.go
            └── request.go

Here is the code:

main.go (root folder)

package main

import (
    "flag"
    "fmt"
    "services"
)

func main() {
    dnsPort := flag.Int("Port", 53, "Exposed running port")
    flag.Parse()

    fmt.Print("Starting server")
    dnsService := services.Service{
        Port:      *dnsPort,
        AccessKey: "hot-dog",
    }
    dnsService.Launch()
}

dns.go (vendor/services folder)

package services

import (
    "log"
    "net"
    "strconv"

    "github.com/miekg/dns"
)

type u struct {
    AccessKey string
}

// ServeDNS ...
func (service *u) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
    sdk := InternalSDK{}
    msg := dns.Msg{}
    msg.SetReply(r)

    switch r.Question[0].Qtype {
    case dns.TypeA:
        msg.Authoritative = true
        domain := msg.Question[0].Name
        records, getDomainsError := sdk.GetDomainRecordsByType(domain, dns.TypeA)
        if getDomainsError == nil {
            for _, record := range records {
                msg.Answer = append(msg.Answer, &dns.A{
                    Hdr: dns.RR_Header{Name: domain, Rrtype: record.Type, Class: record.Class, Ttl: record.TTL},
                    A:   net.ParseIP(record.Data),
                })
            }
        } else {
            // todo: log error
        }

    }

    w.WriteMsg(&msg)
}

type Service struct {
    Port      int
    AccessKey string
}

// LaunchDNSService ...
func (service *Service) Launch() {
    // make a new response chan

    srv := &dns.Server{Addr: ":" + strconv.Itoa(service.Port), Net: "udp"}

    srv.Handler = &u{}
    if err := srv.ListenAndServe(); err != nil {
        log.Fatalf("Failed to set udp listener %s\n", err.Error())
    }
}

request.go (vendor/services folder)

package services

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "strings"
    "time"

    "github.com/gojektech/heimdall/httpclient"
)

type InternalSDK struct {
    Timeout   uint
    Host      string
    Port      uint32
    AccessKey string
}

type DomainRecord struct {
    Domain string `json:"domain"`
    Type   uint16 `json:"type"`
    Class  uint16 `json:"class"`
    TTL    uint32 `json:"ttl"`
    Data   string `json:"data"`
}

// New ...

// GetDomainInformations ...
func (call *InternalSDK) GetDomainRecordsByType(domain string, entryType uint16) ([]DomainRecord, error) {

    // Use the clients GET method to create and execute the request
    url := fmt.Sprintf("http://localhost:3000/dns/domain/%s/type/%d", strings.TrimSuffix(domain, "."), entryType)

    timeout := 1000 * time.Millisecond
    client := httpclient.NewClient(httpclient.WithHTTPTimeout(timeout))

    // Use the clients GET method to create and execute the request
    headers := http.Header{}
    headers.Add("hug", "hh")
    res, err := client.Get(url, headers)
    if err != nil {
        return nil, err
    }

    // Heimdall returns the standard *http.Response object
    body, err := ioutil.ReadAll(res.Body)

    var domainRecord []DomainRecord
    json.Unmarshal([]byte(string(body)), &domainRecord)

    return domainRecord, nil
}

It works and as soon as I quit the container it kills the executable execution (normal behaviour)

Do you guys have any idea why ?

  • Share your dockerfile – Ashok Oct 20 '20 at 18:33
  • 1
    Please [edit] your question to include required info (e.g. a [mcve]). Also, replace the image of text with actual text. As a new user here, please also take the [tour] and read [ask]. – Ulrich Eckhardt Oct 20 '20 at 19:32
  • @UlrichEckhardt, thanks for your reply, I have refined the post. I hope it is enough understandable. – Yan Philippe Oct 20 '20 at 19:46
  • Stdout says `CMD ["./launch.sh"]` your dockerfile says `CMD ["./main"]`... what's up with that? Looks like that's not the actual dockerfile, or not the actual output, either way we can't debug what we can't see. – mkopriva Oct 20 '20 at 19:52
  • @mkopriva Yeah sorry, I tried both with ./main and shell script to check if it makes any difference, it has been corrected – Yan Philippe Oct 20 '20 at 19:59
  • What processes are running in the container when you exec into it? Please include your debugging steps and their output in the question. – BMitch Oct 20 '20 at 20:18
  • Also include the docker-compose.yml file in your question, assume external links will go bad. – BMitch Oct 20 '20 at 20:20
  • @BMitch, thanks for the advice -> I removed the external link. By looking the running processes, I noticed that the executable is running but I still don't understand why it hangs – Yan Philippe Oct 20 '20 at 20:25
  • @BMitch, could my code makes Docker container hangs on launch, i'am asking this question because your profile mentions you do golang development !? – Yan Philippe Oct 20 '20 at 20:30
  • Can't say without seeing the code. As Ulrich points out, a [mcve] is really needed so we can try creating the issue in our own environment. – BMitch Oct 20 '20 at 20:32
  • @BMitch I have updated the post with my code and the project structure – Yan Philippe Oct 20 '20 at 20:40

1 Answers1

1

I deployed this in my own environment and the server is up and listening on port 53:

Removing intermediate container 9ca44a8e9e1c
 ---> 50ac6085b9d6
Step 10/10 : CMD ["./main"]
 ---> Running in f031cb3bb632
Removing intermediate container f031cb3bb632
 ---> 61f8a889d84d

Successfully built 61f8a889d84d
Successfully tagged test-64451146:latest
Recreating 64451146_dns_1 ... done

$ docker run -it --rm --net container:64451146_dns_1 nicolaka/netshoot bash
bash-5.0# ss -lnu
State           Recv-Q          Send-Q                    Local Address:Port                      Peer Address:Port
UNCONN          0               0                            127.0.0.11:45648                          0.0.0.0:*
UNCONN          0               0                                     *:53                                   *:*

I can hit it with nslookup and dig and receive responses. I suspect your question is because you don't see the Starting server message, and for that you just need to add a linefeed. Otherwise that buffer is only flushed when the container stops:

fmt.Print("Starting server\n")

The other likely error you'll see is the network request to localhost:

url := fmt.Sprintf("http://localhost:3000/dns/domain/%s/type/%d", strings.TrimSuffix(domain, "."), entryType)

Inside of a container, localhost is the container, not the host running the container. Networking is namespaced in docker, similar to how the filesystem and pids are namespaced. And that's why I used the --net container: syntax above to run a second container with the same namespace and view the listening ports. Therefore you'll want to change the URL to something you can reach from inside the container, and if this depends on where you run it, then I often inject that as a variable (CLI arg or environment var) from outside of the container, rather than hardcode it into the program.

BMitch
  • 231,797
  • 42
  • 475
  • 450
  • Yup I know for the "localhost not running in docker" since docker has its own DNS system. I made it work, one of my mistakes was not to put ports in my docker-compose file with /udp since all requests are made over UDP. You helped me a lot, and gave me your time and I'am grateful for this. Thanks a lot Buddy – Yan Philippe Oct 21 '20 at 10:03