【问题标题】:Why does my docker container executing a Golang executable hangs on right after launch? [closed]为什么执行 Golang 可执行文件的 docker 容器在启动后立即挂起? [关闭]
【发布时间】:2021-02-03 15:14:12
【问题描述】:

我目前正在 macOS 上使用 Golang 开发一个小应用程序,它可以在本地完美运行。

我从一个从头开始制作的 Dockerfile 制作了一个 docker 镜像。

我的问题是,当容器启动时它会无限期挂起,docker 没有绑​​定端口,但我仍然可以进入容器。

以下是容器内正在运行的进程:

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

这是我的 docker-compose:

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

这是我的 Dockerfile

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"]

来自 docker 尝试启动容器的日志:

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

它永远挂在“附加到 lighthouse_dns_1”处。

如果我通过这样做从容器中手动启动可执行文件:

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

/go/src/app# ./main

这是项目结构:

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

代码如下:

ma​​in.go(根文件夹)

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(供应商/服务文件夹)

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(供应商/服务文件夹)

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
}

它可以工作,一旦我退出容器,它就会终止可执行文件的执行(正常行为)

你们知道为什么吗?

【问题讨论】:

  • 分享你的 dockerfile
  • edit您的问题包含所需的信息(例如minimal reproducible example)。此外,将文本图像替换为实际文本。作为这里的新用户,也请带上tour并阅读How to Ask
  • @UlrichEckhardt,感谢您的回复,我已经完善了帖子。我希望它足够理解。
  • Stdout 说 CMD ["./launch.sh"] 你的 dockerfile 说 CMD ["./main"]... 这是怎么回事?看起来那不是实际的 dockerfile,或者不是实际的输出,无论哪种方式我们都无法调试我们看不到的东西。
  • @mkopriva 是的,抱歉,我尝试使用 ./main 和 shell 脚本来检查它是否有任何区别,已更正

标签: docker go containers alpine


【解决方案1】:

我在自己的环境中部署了它,服务器已启动并监听端口 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                                   *:*

我可以用 nslookup 打它,然后挖掘并接收响应。我怀疑你的问题是因为你没有看到Starting server 消息,为此你只需要添加一个换行符。否则,该缓冲区仅在容器停止时刷新:

fmt.Print("Starting server\n")

您将看到的另一个可能的错误是对 localhost 的网络请求:

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

在容器内部,localhost 是容器,而不是运行容器的主机。网络在 docker 中命名空间,类似于文件系统和 pid 的命名空间。这就是为什么我使用上面的--net container: 语法来运行具有相同命名空间的第二个容器并查看监听端口的原因。因此,您需要将 URL 更改为您可以从容器内部访问的内容,如果这取决于您运行它的位置,那么我经常从容器外部将其作为变量(CLI arg 或环境变量)注入,而不是硬编码到程序中。

【讨论】:

  • 是的,我知道“localhost 没有在 docker 中运行”,因为 docker 有自己的 DNS 系统。我成功了,我的一个错误是没有使用 /udp 将端口放在我的 docker-compose 文件中,因为所有请求都是通过 UDP 发出的。你帮了我很多,给了我你的时间,我很感激。非常感谢好友
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-02
  • 1970-01-01
  • 1970-01-01
  • 2022-07-11
  • 2014-11-29
  • 1970-01-01
相关资源
最近更新 更多