当我将我的Go应用程序进行Docker化时,它无法识别HTML模板。

huangapple go评论121阅读模式
英文:

When I dockerize my Go app, it's not seeing HTML templates

问题

我正在尝试使用gomail编写通知服务。当我在本地运行应用程序时,一切都正常工作,但是当我将应用程序容器化时,我会遇到空指针错误,因为gomail找不到HTML模板。

错误信息:

  1. app_1 | panic: runtime error: invalid memory address or nil pointer dereference
  2. app_1 | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x41dc7c]

这是我的Dockerfile:

  1. FROM golang@.. as builder
  2. RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
  3. RUN adduser \
  4. --disabled-password \
  5. --gecos "" \
  6. --home "/noneexistent" \
  7. --shell "/sbin/nologin" \
  8. --no-create-home \
  9. --uid "${UID}" \
  10. "${USER}"
  11. WORKDIR /app
  12. COPY go.mod .
  13. COPY go.sum .
  14. RUN go mod download
  15. RUN go mod verify
  16. COPY . .
  17. RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
  18. FROM scratch
  19. COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
  20. COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
  21. COPY --from=builder /etc/passwd /etc/passwd
  22. COPY --from=builder /etc/group /etc/group
  23. COPY --from=builder /app/main .
  24. EXPOSE 8000
  25. ENTRYPOINT ["./main"]

我的文件夹结构:

  1. notification-service
  2. ├── Makefile
  3. ├── README.md
  4. ├── common
  5. ├── constants.go
  6. ├── env.go
  7. └── logger.go
  8. ├── docker-compose.yml
  9. ├── dockerfile
  10. ├── entities
  11. └── invite_struct.go
  12. ├── go.mod
  13. ├── go.sum
  14. ├── main.go
  15. ├── router.go
  16. ├── services
  17. └── mail.go
  18. ├── templates
  19. ├── info.html
  20. └── invite.html
  21. ├── kafka
  22. ├── consumer.go
  23. └── producer.go

代码:

kafka/consumer.go

  1. if ok := mailService.Send("templates/info.html", inviteMessage.Email, "Account Created", content); !ok {
  2. continue
  3. }

services/mail.go

  1. type MailServiceInterface interface {
  2. Send(template string, to string, subject string, data interface{}) bool
  3. }
  4. type Mail struct {
  5. }
  6. func NewMailServiceClient() MailServiceInterface {
  7. return &Mail{}
  8. }
  9. func (m *Mail) Send(template string, to string, subject string, data interface{}) bool {
  10. t, _ := m.parseTemplate(template, data)
  11. if ok := m.sendMail(*t, to, subject); ok {
  12. log.Println(fmt.Sprintf("Email has been sent to %s", to))
  13. return true
  14. } else {
  15. log.Println(fmt.Sprintf("Failed to send the email to %s", to))
  16. return false
  17. }
  18. }
  19. func (m *Mail) parseTemplate(templateFileName string, data interface{}) (*string, error) {
  20. t, err := template.ParseFiles(templateFileName)
  21. if err != nil {
  22. return nil, err
  23. }
  24. buf := new(bytes.Buffer)
  25. if err = t.Execute(buf, data); err != nil {
  26. return nil, err
  27. }
  28. body := buf.String()
  29. return &body, nil
  30. }
  31. func (m *Mail) sendMail(template, to, subject string) bool {
  32. env := common.GetEnvironment()
  33. from := env.MailUsername
  34. password := env.MailPassword
  35. port, _ := strconv.Atoi(env.MailPort)
  36. mailServer := env.MailServer
  37. mail := gomail.NewMessage()
  38. mail.SetHeader("From", from)
  39. mail.SetHeader("To", to)
  40. mail.SetHeader("Subject", subject)
  41. mail.SetBody("text/html", template)
  42. d := gomail.NewDialer(mailServer, port, from, password)
  43. if err := d.DialAndSend(mail); err != nil {
  44. zap.S().Error(err)
  45. return false
  46. }
  47. return true
  48. }
英文:

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:

  1. app_1 | panic: runtime error: invalid memory address or nil pointer dereference
  2. app_1 | [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x41dc7c]

Here is my dockerfile:

  1. FROM golang@.. as builder
  2. RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
  3. RUN adduser \
  4. --disabled-password \
  5. --gecos "" \
  6. --home "/noneexistent" \
  7. --shell "/sbin/nologin" \
  8. --no-create-home \
  9. --uid "${UID}" \
  10. "${USER}"
  11. WORKDIR /app
  12. COPY go.mod .
  13. COPY go.sum .
  14. RUN go mod download
  15. RUN go mod verify
  16. COPY . .
  17. RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
  18. FROM scratch
  19. COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
  20. COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
  21. COPY --from=builder /etc/passwd /etc/passwd
  22. COPY --from=builder /etc/group /etc/group
  23. COPY --from=builder /app/main .
  24. EXPOSE 8000
  25. ENTRYPOINT ["./main"]

My folder structure:

  1. notification-service
  2. ├── Makefile
  3. ├── README.md
  4. ├── common
  5. │   ├── constants.go
  6. │   ├── env.go
  7. │   └── logger.go
  8. ├── docker-compose.yml
  9. ├── dockerfile
  10. ├── entities
  11. │   └── invite_struct.go
  12. ├── go.mod
  13. ├── go.sum
  14. ├── main.go
  15. ├── router.go
  16. ├── services
  17. │   └── mail.go
  18. ├── templates
  19. │   ├── info.html
  20. │   └── invite.html
  21. ├── kafka
  22. │   ├── consumer.go
  23. │   └── producer.go

Code:

kafka/consumer.go

  1. if ok := mailService.Send("templates/info.html", inviteMessage.Email, "Account Created", content); !ok {
  2. continue
  3. }

services/mail.go

  1. type MailServiceInterface interface {
  2. Send(template string, to string, subject string, data interface{}) bool
  3. }
  4. type Mail struct {
  5. }
  6. func NewMailServiceClient() MailServiceInterface {
  7. return &Mail{}
  8. }
  9. func (m *Mail) Send(template string, to string, subject string, data interface{}) bool {
  10. t, _ := m.parseTemplate(template, data)
  11. if ok := m.sendMail(*t, to, subject); ok {
  12. log.Println(fmt.Sprintf("Email has been sent to %s", to))
  13. return true
  14. } else {
  15. log.Println(fmt.Sprintf("Failed to send the email to %s", to))
  16. return false
  17. }
  18. }
  19. func (m *Mail) parseTemplate(templateFileName string, data interface{}) (*string, error) {
  20. t, err := template.ParseFiles(templateFileName)
  21. if err != nil {
  22. return nil, err
  23. }
  24. buf := new(bytes.Buffer)
  25. if err = t.Execute(buf, data); err != nil {
  26. return nil, err
  27. }
  28. body := buf.String()
  29. return &body, nil
  30. }
  31. func (m *Mail) sendMail(template, to, subject string) bool {
  32. env := common.GetEnvironment()
  33. from := env.MailUsername
  34. password := env.MailPassword
  35. port, _ := strconv.Atoi(env.MailPort)
  36. mailServer := env.MailServer
  37. mail := gomail.NewMessage()
  38. mail.SetHeader("From", from)
  39. mail.SetHeader("To", to)
  40. mail.SetHeader("Subject", subject)
  41. mail.SetBody("text/html", template)
  42. d := gomail.NewDialer(mailServer, port, from, password)
  43. if err := d.DialAndSend(mail); err != nil {
  44. zap.S().Error(err)
  45. return false
  46. }
  47. return true
  48. }

答案1

得分: 4

在你的Dockerfile的末尾,你运行了COPY --from=builder /app/main .,这样你只复制了可执行文件。

然而,template.ParseFiles(templateFileName)的实现使用了os.ReadFile(file),这意味着它在文件系统中查找文件。这些文件并不存在,因为你只复制了可执行文件。

如果你不想改变构建过程,并且使用的是 Go 1.16 或更高版本,可以将 HTML 文件embed到可执行文件中:

文件夹结构:

  1. ├── templates
  2. ├── info.html
  3. └── invite.html
  4. └── templates.go <--- 新文件

templates.go

  1. package templates
  2. import _ "embed"
  3. //go:embed info.html
  4. var Info string
  5. //go:embed invite.html
  6. var Invite string

services/mail.go

  1. 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:

  1. ├── templates
  2. ├── info.html
  3. └── invite.html
  4. └── templates.go &lt;-- new file

templates.go

  1. package templates
  2. import _ &quot;embed&quot;
  3. //go:embed info.html
  4. var Info string
  5. //go:embed invite.html
  6. var Invite string

services/mail.go

  1. t, err := template.New(&quot;&quot;).Parse(templates.Info)

huangapple
  • 本文由 发表于 2021年6月24日 23:14:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/68118416.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定