如何在 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" ]
我用 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" ]