如何在 docker 中部署带有静态文件的 Web 应用程序?

How to deploy a web app with static files in docker?

我用 echo 构建了一个网络应用程序。 server.go 中的一些来源是

package main

import ...

type TemplateRenderer struct {
    templates *template.Template
}

func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    if viewContext, isMap := data.(map[string]interface{}); isMap {
        viewContext["reverse"] = c.Echo().Reverse
    }

    return t.templates.ExecuteTemplate(w, name, data)
}

func main() {
    e := echo.New()
    e.Static("/static", "static")

    renderer := &TemplateRenderer{
        templates: template.Must(template.ParseGlob("public/views/*.html")),
    }
    e.Renderer = renderer

    e.GET("/", func(c echo.Context) error {
        return c.Render(http.StatusOK, "index.html", map[string]interface{}{})
    })

    e.Logger.Fatal(e.Start(":8080"))
}

项目树

├── helper
│   └── string.go
└─── site
    ├── Dockerfile
    ├── go.mod
    ├── go.sum
    ├── server.go
    ├── public
    │   └── views
    │       └── index.html
    └── static

我可以运行 go run server.go 启动服务器,一切正常。但是如果 运行 它与 docker.

会出错

Docker 文件

FROM golang:1.15.2-alpine3.12 AS builder
WORKDIR /app
COPY . .
WORKDIR /app/site
RUN CGO_ENABLED=0 GOOS=linux go build -o server

FROM alpine:3.12
COPY --from=builder /app/site /bin/.
ENTRYPOINT [ "server" ]

构建了一个 docker 图像
docker build -t gcr.io/${PROJECT_ID}/myapp -f site/Dockerfile .

运行 应用在 docker

docker run --rm -p 8080:8080 gcr.io/${PROJECT_ID}/myapp
panic: html/template: pattern matches no files: `public/views/*.html`

goroutine 1 [running]:
html/template.Must(...)
    /usr/local/go/src/html/template/template.go:372
main.main()
    /app/site/server.go:76 +0x2af

似乎 public 文件夹没有被复制到图像中。但是构建图像时没有任何错误。怎么了?

在 Go 1.16 中,您可以将这些文件编译成二进制文件本身。因此,您需要升级主机系统上的 Go 工具链,以及 Dockerfile 构建阶段中的 FROM 行。 Go 1.16 添加了 embed 包和一个新的 //go:embed 指令来支持它。

首先,您需要告诉编译器嵌入模板文件,构建一个文件系统对象:

import "embed"

// templateFiles contains the raw text of the template files.
//go:embed public/views/*.html
var templateFiles embed.FS

然后,大家去用的时候,Go 1.16也增加了对应的("html/template").ParseFS功能:

renderer := &TemplateRenderer{
    templates: template.Must(template.ParseFS(templateFiles)),
}

现在所有文件都嵌入到二进制文件本身中,您应该不会收到“找不到文件”类型的错误。您可能会考虑仅将编译后的二进制文件复制到最终映像中,而不复制任何其他内容。

# Upgrade to Go 1.16
FROM golang:1.16-alpine3.12 AS builder
# Unchanged from original
WORKDIR /app
COPY . .
WORKDIR /app/site
RUN CGO_ENABLED=0 GOOS=linux go build -o server

FROM alpine:3.12
# Only copy the compiled binary and not the source tree
COPY --from=builder /app/site/server /bin
# Generally prefer CMD to ENTRYPOINT
CMD [ "server" ]

首先,您使用的是 multi-stage Docker build,它非常适合仅将已编译的二进制文件复制到最终映像。但是,在您的 Dockerfile 中,您正在复制整个构建目录 - 二进制 server 以及所有源代码。

其次,您的主要问题是当图像 运行 作为容器时,默认工作目录是 / - 因此您服务器中的任何路径都找不到 html 文件在 /bin/public.

如果您需要调试 docker 映像 - 特别是如果它是基于像 alpine 这样的 linux 发行版的映像 - 只需:

docker run -it myimage /bin/sh

无论如何,docker:

的 2 个简单修复
FROM golang:1.15.2-alpine3.12 AS builder
WORKDIR /app
COPY . .
WORKDIR /app/site
RUN CGO_ENABLED=0 GOOS=linux go build -o server

FROM alpine:3.12
COPY --from=builder /app/site/server /bin
COPY --from=builder /app/site/public /public
ENTRYPOINT [ "/bin/server" ]