如何在Ubuntu 22.04上使用Docker和Nginx部署Go Web应用程序
简介
Docker是一种常用的容器化软件,它使开发人员能够轻松打包应用程序和运行环境,以实现更快的迭代周期和更好的资源利用效率,同时在每次运行时提供相同的期望环境。Docker Compose是一种容器编排工具,可以实现现代应用程序的需求。它允许同时运行多个相互连接的容器。与手动运行容器不同,容器编排工具给开发人员提供了同时控制、扩展和管理容器的能力。
使用Nginx作为前端Web服务器的好处在于性能、可配置性和TLS终止功能,可使应用程序摆脱完成这些任务的负担。nginx-proxy是一个自动化的Docker容器系统,简化了配置Nginx作为反向代理的过程。它的Let’s Encrypt插件可以与nginx-proxy一起自动生成和更新代理容器的证书。
在本教程中,您将使用gorilla/mux作为请求路由器和Nginx作为Web服务器,在由Docker Compose编排的Docker容器中部署一个示例的Go Web应用程序。您将使用Let’s Encrypt插件的nginx-proxy作为反向代理。在本教程的最后,您将使用Docker部署一个可通过您的域名访问的具有多个路由的Go Web应用程序,并且使用Let’s Encrypt证书进行安全保护。
先决条件
- An Ubuntu 22.04 server with root privileges, and a secondary, non-root account. You can set this up by following this initial server setup guide. For this tutorial, the non-root user is james.
- Docker installed by following the first two steps of How To Install Docker on Ubuntu 22.04.
- Docker Compose installed by following the first step of How To Install Docker Compose on Ubuntu 22.04.
- A fully registered domain name. This tutorial will use your_domain throughout. You can get one for free on Freenom, or use the domain registrar of your choice.
- A DNS “A” record with your_domain pointing to your server’s public IP address. You can follow this introduction to Silicon Cloud DNS for details on how to add them.
- An understanding of Docker and its architecture. For an introduction to Docker, see The Docker Ecosystem: An Introduction to Common Components.
第一步:创建一个示例的Go Web应用程序。
在这一步中,您将设置您的工作空间并创建一个简单的Go Web应用程序,稍后您将对其进行容器化。Go应用程序将使用功能强大的gorilla/mux请求路由器,选择该路由器是因为它具有灵活性和速度。
对于本教程,您将把所有数据存储在~/go-docker文件夹下。运行以下命令创建该文件夹:
- mkdir ~/go-docker
前往该处:
- cd ~/go-docker
您将把您的示例Go网络应用存储在名为main.go的文件中。请使用您的文本编辑器创建它。
- nano main.go
添加以下行:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>This is the homepage. Try /hello and /hello/James\n</h1>")
})
r.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>Hello from Docker!\n</h1>")
})
r.HandleFunc("/hello/{name}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
title := vars["name"]
fmt.Fprintf(w, "<h1>Hello, %s!\n</h1>", title)
})
http.ListenAndServe(":80", r)
}
首先,您需要导入net/http和gorilla/mux包,这两个包提供了HTTP服务器功能和路由功能。
gorilla/mux包实现了一个更强大的请求路由器和分发器,同时保持与标准路由器的接口兼容性。
您需要实例化一个新的mux路由器,并将其保存在变量r中。
然后,您定义了三个路由:/、/hello 和 /hello/{name}。第一个路由(/)作为主页,并且您在其中包含了一条信息。第二个路由(/hello)返回给访问者的是问候语。对于第三个路由(/hello/{name}),您指定它应该接受一个名字作为参数,并显示一条插入名字的问候信息。
在文件的末尾,你使用http.ListenAndServe启动HTTP服务器,并指示它监听使用你配置的路由器的端口80。
保存并关闭文件。
在运行您的Go应用程序之前,您需要将其编译并打包到Docker容器中以进行执行。Go是一种编译语言,所以在程序能够运行之前,编译器会将编程代码转换为可执行的机器代码。
你已经搭建好了你的工作空间并创建了一个示例的Go Web应用程序。接下来,你将使用自动化的Let’s Encrypt证书提供部署nginx-proxy。
步骤2—使用Let’s Encrypt部署nginx代理
重要的是通过HTTPS保护你的应用程序。为了实现这一点,你将使用Docker Compose部署nginx-proxy,以及它的Let’s Encrypt附加组件。通过这种设置,可以通过nginx-proxy代理保护Docker容器,并通过自动处理TLS证书的创建和更新来确保你的应用程序的HTTPS安全。
将nginx-proxy的Docker Compose配置存储在名为nginx-proxy-compose.yaml的文件中。通过运行以下命令来创建它:
- nano nginx-proxy-compose.yaml
将以下行添加到文件中。
~/go-docker/nginx-proxy-compose.yaml
version: '3'
services:
nginx-proxy:
restart: always
image: jwilder/nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- "/etc/nginx/vhost.d"
- "/usr/share/nginx/html"
- "/var/run/docker.sock:/tmp/docker.sock:ro"
- "/etc/nginx/certs"
letsencrypt-nginx-proxy-companion:
restart: always
image: jrcs/letsencrypt-nginx-proxy-companion
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
volumes_from:
- "nginx-proxy"
在此文件中,你定义了两个容器:一个用于nginx代理,另一个用于其Let’s Encrypt附加组件(letsencrypt-nginx-proxy-companion)。对于代理,你指定了镜像jwilder/nginx-proxy,暴露并映射HTTP和HTTPS端口,并定义了卷,这些卷可用于持久化Nginx相关数据的容器访问。
在第二个模块中,您为Let’s Encrypt附加配置命名图像。然后,您通过定义一个卷以及从代理容器继承现有卷来配置对Docker套接字的访问。两个容器都将重启属性设置为always,这指示Docker始终保持它们运行(在崩溃或系统重启的情况下)。
保存并关闭文件。
运行命令部署nginx代理。
- docker compose -f nginx-proxy-compose.yaml up -d
Docker Compose可以通过-f标志接受自定义的文件名称。up命令用于运行容器,并且-d标志(后台模式)指示它在后台运行容器。
您会收到类似这样的输出:
Output[+] Running 21/21
⠿ letsencrypt-nginx-proxy-companion Pulled 6.8s
⠿ df9b9388f04a Pull complete 3.1s
⠿ 6c6cfd4eaf5b Pull complete 3.9s
⠿ 870307501973 Pull complete 4.3s
⠿ e8ff3435d14f Pull complete 4.5s
⠿ 5b78ba945919 Pull complete 4.8s
⠿ 973b2ca26006 Pull complete 5.0s
⠿ nginx-proxy Pulled 8.1s
⠿ 42c077c10790 Pull complete 3.9s
⠿ 62c70f376f6a Pull complete 5.5s
⠿ 915cc9bd79c2 Pull complete 5.6s
⠿ 75a963e94de0 Pull complete 5.7s
⠿ 7b1fab684d70 Pull complete 5.7s
⠿ db24d06d5af4 Pull complete 5.8s
⠿ e917373dbecf Pull complete 5.9s
⠿ 11e2be9775e9 Pull complete 5.9s
⠿ 9996fa75bc02 Pull complete 6.1s
⠿ d37674efdf77 Pull complete 6.3s
⠿ a45d84576e75 Pull complete 6.3s
⠿ a13c1f42faf7 Pull complete 6.4s
⠿ 4f4fb700ef54 Pull complete 6.5s
[+] Running 3/3
⠿ Network go-docker_default Created 0.1s
⠿ Container go-docker-nginx-proxy-1 Started 0.5s
⠿ Container go-docker-letsencrypt-nginx-proxy-companion-1 Started 0.8s
你已经使用Docker Compose部署了nginx-proxy及其Let’s Encrypt扩展。接下来,你将为你的Go Web应用创建一个Dockerfile。
第三步 — Docker化Go Web应用程序
在本节中,您将准备一个Dockerfile,其中包含有关Docker如何为您的Go Web应用程序创建一个不可变镜像的指令。Docker使用Dockerfile中的命令构建一个不可变的应用程序镜像,类似于容器的快照。镜像的不可变性保证了每次基于特定镜像运行容器时,都会提供相同的环境。
使用您的文本编辑器创建Dockerfile。
- nano Dockerfile
请在下面添加以下内容:
请加上以下几行:
FROM golang:alpine AS build
RUN apk --no-cache add gcc g++ make git
WORKDIR /go/src/app
COPY . .
RUN go mod init webserver
RUN go mod tidy
RUN GOOS=linux go build -ldflags="-s -w" -o ./bin/web-app ./main.go
FROM alpine:3.17
RUN apk --no-cache add ca-certificates
WORKDIR /usr/bin
COPY --from=build /go/src/app/bin /go/bin
EXPOSE 80
ENTRYPOINT /go/bin/web-app --port 80
这个Dockerfile有两个阶段。第一阶段使用了golang:alpine基础镜像,其中已经预先在Alpine Linux上安装了Go。
然后,您将gcc、g++、make和git安装为Go应用程序的必要编译工具。您将工作目录设置为/go/src/app,该目录位于默认的GOPATH下。您还将当前目录的内容复制到容器中。第一阶段通过递归获取代码中使用的软件包,并使用-ldflags=”-s -w”参数编译main.go文件,以发布时去除符号和调试信息。
Note
第二阶段以alpine:3.17(Alpine Linux 3.17)为基础。它安装了可信任的CA证书,将编译好的应用程序二进制文件从第一阶段复制到当前镜像,暴露端口80,并将应用程序二进制文件设置为镜像的入口点。
保存并关闭文件。
你已经为你的Go应用创建了一个Dockerfile,在容器创建时通过获取包、编译并运行它。下一步,你将创建Docker Compose yaml文件并在Docker中运行它来测试这个应用。
Step 4 — 创建并运行Docker Compose文件
现在,您将创建Docker Compose配置文件并编写必要的配置,以运行在前一步中创建的Docker镜像。然后,您将运行它并检查是否正常工作。一般而言,Docker Compose配置文件指定应用程序所需的容器、设置、网络和卷。您还可以指定这些元素作为一个整体启动和停止。
您将把Go Web应用的Docker Compose配置存储在名为go-app-compose.yaml的文件中。通过运行以下命令来创建它:
- nano go-app-compose.yaml
将以下内容添加到此文件中。
~/go-docker/应用组合.yaml
version: '3'
services:
go-web-app:
restart: always
build:
dockerfile: Dockerfile
context: .
environment:
- VIRTUAL_HOST=your_domain
- LETSENCRYPT_HOST=your_domain
将”your_domain”这个词两次替换为您的域名。保存并关闭文件。
这个Docker Compose的配置包含一个容器(go-web-app),它将成为你的Go网络应用程序。它使用你在前一步创建的Dockerfile构建应用程序,并采用包含源代码的当前目录作为构建的上下文。此外,它设置了两个环境变量:VIRTUAL_HOST和LETSENCRYPT_HOST。nginx-proxy使用VIRTUAL_HOST来确定从哪个域名接受请求。LETSENCRYPT_HOST指定生成TLS证书的域名,必须与VIRTUAL_HOST相同,除非你指定了通配符域名。
通过Docker Compose在后台运行你的Go web应用。
- docker compose -f go-app-compose.yaml up -d
这样的输出将被打印出来(为了易读,该输出已进行了截断)。
OutputCreating network "go-docker_default" with the default driver
Building go-web-app
Step 1/13 : FROM golang:alpine AS build
---> b97a72b8e97d
...
Successfully built 71e4b1ef2e25
Successfully tagged go-docker_go-web-app:latest
...
[+] Running 1/1
⠿ Container go-docker-go-web-app-1 Started
如果您在运行命令后查看输出,Docker根据Dockerfile中的配置记录构建应用镜像的每个步骤。
现在你可以导航至https://your_domain/来访问你的首页。在你的网络应用的主要地址,你可以通过在第一步中定义的/路由来访问该页面。
![Screencapture of the domain page, which reads](https://cdn.silicloud.com/blog-img/blog/img/65645b10a4b2f92e6c722bcf/68-0.png)
现在导航到 https://your_domain/hello。你在步骤1中为/hello路径定义的代码中的消息将会加载。
![Screencapture of the route, which reads](https://cdn.silicloud.com/blog-img/blog/img/65645b10a4b2f92e6c722bcf/70-0.png)
最后,在你的网址中添加一个名字来测试其他路由,例如:https://你的域名/hello/James。
![Screencapture of the name route, which you have input as](https://cdn.silicloud.com/blog-img/blog/img/65645b10a4b2f92e6c722bcf/72-0.png)
Note
你已经创建了Docker Compose文件,并编写了在容器内运行Go应用程序的配置。最后,你导航到你的域名上检查是否gorilla/mux路由器设置正确地为你的Docker化Go网络应用程序提供请求服务。
结论
你现在已经成功地使用Docker和Nginx在Ubuntu 22.04上部署了你的Go Web应用程序。通过使用Docker,应用程序的维护变得不那么耗费时间,因为每次运行时执行的环境都是保证一样的。gorilla/mux包有很好的文档,并提供更复杂的功能,比如命名路由和提供静态文件服务。如果你希望对Go HTTP服务器模块有更多的控制,比如定义自定义超时时间,可以查阅官方文档。