英文:
When I dockerize my Go app, it's not seeing HTML templates
问题
我正在尝试使用gomail编写通知服务。当我在本地运行应用程序时,一切都正常工作,但是当我将应用程序容器化时,我会遇到空指针错误,因为gomail找不到HTML模板。
错误信息:
app_1 | panic: runtime error: invalid memory address or nil pointer dereference
app_1 | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x41dc7c]
这是我的Dockerfile:
FROM golang@.. as builder
RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
RUN adduser \
--disabled-password \
--gecos "" \
--home "/noneexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go mod verify
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM scratch
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --from=builder /app/main .
EXPOSE 8000
ENTRYPOINT ["./main"]
我的文件夹结构:
notification-service
├── Makefile
├── README.md
├── common
│ ├── constants.go
│ ├── env.go
│ └── logger.go
├── docker-compose.yml
├── dockerfile
├── entities
│ └── invite_struct.go
├── go.mod
├── go.sum
├── main.go
├── router.go
├── services
│ └── mail.go
├── templates
│ ├── info.html
│ └── invite.html
├── kafka
│ ├── consumer.go
│ └── producer.go
代码:
kafka/consumer.go
if ok := mailService.Send("templates/info.html", inviteMessage.Email, "Account Created", content); !ok {
continue
}
services/mail.go
type MailServiceInterface interface {
Send(template string, to string, subject string, data interface{}) bool
}
type Mail struct {
}
func NewMailServiceClient() MailServiceInterface {
return &Mail{}
}
func (m *Mail) Send(template string, to string, subject string, data interface{}) bool {
t, _ := m.parseTemplate(template, data)
if ok := m.sendMail(*t, to, subject); ok {
log.Println(fmt.Sprintf("Email has been sent to %s", to))
return true
} else {
log.Println(fmt.Sprintf("Failed to send the email to %s", to))
return false
}
}
func (m *Mail) parseTemplate(templateFileName string, data interface{}) (*string, error) {
t, err := template.ParseFiles(templateFileName)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
if err = t.Execute(buf, data); err != nil {
return nil, err
}
body := buf.String()
return &body, nil
}
func (m *Mail) sendMail(template, to, subject string) bool {
env := common.GetEnvironment()
from := env.MailUsername
password := env.MailPassword
port, _ := strconv.Atoi(env.MailPort)
mailServer := env.MailServer
mail := gomail.NewMessage()
mail.SetHeader("From", from)
mail.SetHeader("To", to)
mail.SetHeader("Subject", subject)
mail.SetBody("text/html", template)
d := gomail.NewDialer(mailServer, port, from, password)
if err := d.DialAndSend(mail); err != nil {
zap.S().Error(err)
return false
}
return true
}
英文:
I'm trying to write notification service with gomail. When I run my app locally everything works just fine but when I dockerize my app I get nil pointer error because gomail cannot find html templates.
Error:
app_1 | panic: runtime error: invalid memory address or nil pointer dereference
app_1 | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x41dc7c]
Here is my dockerfile:
FROM golang@.. as builder
RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
RUN adduser \
--disabled-password \
--gecos "" \
--home "/noneexistent" \
--shell "/sbin/nologin" \
--no-create-home \
--uid "${UID}" \
"${USER}"
WORKDIR /app
COPY go.mod .
COPY go.sum .
RUN go mod download
RUN go mod verify
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM scratch
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --from=builder /app/main .
EXPOSE 8000
ENTRYPOINT ["./main"]
My folder structure:
notification-service
├── Makefile
├── README.md
├── common
│   ├── constants.go
│   ├── env.go
│   └── logger.go
├── docker-compose.yml
├── dockerfile
├── entities
│   └── invite_struct.go
├── go.mod
├── go.sum
├── main.go
├── router.go
├── services
│   └── mail.go
├── templates
│   ├── info.html
│   └── invite.html
├── kafka
│   ├── consumer.go
│   └── producer.go
Code:
kafka/consumer.go
if ok := mailService.Send("templates/info.html", inviteMessage.Email, "Account Created", content); !ok {
continue
}
services/mail.go
type MailServiceInterface interface {
Send(template string, to string, subject string, data interface{}) bool
}
type Mail struct {
}
func NewMailServiceClient() MailServiceInterface {
return &Mail{}
}
func (m *Mail) Send(template string, to string, subject string, data interface{}) bool {
t, _ := m.parseTemplate(template, data)
if ok := m.sendMail(*t, to, subject); ok {
log.Println(fmt.Sprintf("Email has been sent to %s", to))
return true
} else {
log.Println(fmt.Sprintf("Failed to send the email to %s", to))
return false
}
}
func (m *Mail) parseTemplate(templateFileName string, data interface{}) (*string, error) {
t, err := template.ParseFiles(templateFileName)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
if err = t.Execute(buf, data); err != nil {
return nil, err
}
body := buf.String()
return &body, nil
}
func (m *Mail) sendMail(template, to, subject string) bool {
env := common.GetEnvironment()
from := env.MailUsername
password := env.MailPassword
port, _ := strconv.Atoi(env.MailPort)
mailServer := env.MailServer
mail := gomail.NewMessage()
mail.SetHeader("From", from)
mail.SetHeader("To", to)
mail.SetHeader("Subject", subject)
mail.SetBody("text/html", template)
d := gomail.NewDialer(mailServer, port, from, password)
if err := d.DialAndSend(mail); err != nil {
zap.S().Error(err)
return false
}
return true
}
答案1
得分: 4
在你的Dockerfile的末尾,你运行了COPY --from=builder /app/main .
,这样你只复制了可执行文件。
然而,template.ParseFiles(templateFileName)
的实现使用了os.ReadFile(file)
,这意味着它在文件系统中查找文件。这些文件并不存在,因为你只复制了可执行文件。
如果你不想改变构建过程,并且使用的是 Go 1.16 或更高版本,可以将 HTML 文件embed
到可执行文件中:
文件夹结构:
├── templates
│ ├── info.html
│ └── invite.html
│ └── templates.go <--- 新文件
templates.go
package templates
import _ "embed"
//go:embed info.html
var Info string
//go:embed invite.html
var Invite string
services/mail.go
t, err := template.New("").Parse(templates.Info)
英文:
At the end of your dockerfile you run COPY --from=builder /app/main .
so you're copying only the executable.
However template.ParseFiles(templateFileName)
implementation uses os.ReadFile(file)
, which means it looks for files in the file system. Those files are not there because you copied only the executable.
If you don't want to change your build process and are on Go 1.16 or above, embed
the html files into your executable:
Folder structure:
├── templates
│ ├── info.html
│ └── invite.html
│ └── templates.go <-- new file
templates.go
package templates
import _ "embed"
//go:embed info.html
var Info string
//go:embed invite.html
var Invite string
services/mail.go
t, err := template.New("").Parse(templates.Info)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论