如何在Ubuntu18.04上使用Docker和Nginx部署GoWeb应用程序
作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。
介绍
Docker 是当今最常用的容器化软件。 它使开发人员能够轻松地将应用程序与其环境一起打包,从而实现更快的迭代周期和更好的资源效率,同时在每次运行时提供相同的所需环境。 Docker Compose 是一种容器编排工具,可满足现代应用程序的需求。 它允许您同时运行多个互连的容器。 编排工具不是手动运行容器,而是让开发人员能够同时控制、扩展和扩展容器。
使用 Nginx 作为前端 Web 服务器的好处是它的性能、可配置性和 TLS 终止,这使应用程序免于完成这些任务。 nginx-proxy 是一个用于 Docker 容器的自动化系统,它极大地简化了将 Nginx 配置为反向代理的过程。 它的 Let's Encrypt 附加组件 可以伴随 nginx-proxy
自动生成和更新代理容器的证书。
在本教程中,您将部署一个示例 Go Web 应用程序,其中 gorilla/mux 作为请求路由器,Nginx 作为 Web 服务器,所有这些都在 Docker 容器中,由 Docker Compose 编排。 您将使用 nginx-proxy
和 Let's Encrypt 插件作为反向代理。 在本教程结束时,您将部署一个可在您的域中访问的 Go Web 应用程序,该应用程序具有多个路由,使用 Docker 并使用 Let's Encrypt 证书进行保护。
先决条件
- 具有 root 权限和辅助非 root 帐户的 Ubuntu 18.04 服务器。 您可以按照 这个初始服务器设置指南 进行设置。 对于本教程,非 root 用户是
sammy
。 - 按照如何在Ubuntu 18.04上安装Docker的前两步安装Docker。
- 按照如何在Ubuntu 18.04上安装Docker Compose的第一步安装Docker Compose。
- 完全注册的域名。 本教程将自始至终使用
your_domain
。 您可以在 Freenom 上免费获得一个,或使用您选择的域名注册商。 - 带有
your_domain
的 DNS “A” 记录指向您服务器的公共 IP 地址。 您可以关注这个介绍到DigitalOcean DNS,详细了解如何添加它们。 - 了解 Docker 及其架构。 有关 Docker 的介绍,请参阅 Docker 生态系统:通用组件简介。
第 1 步 - 创建示例 Go Web 应用程序
在此步骤中,您将设置工作区并创建一个简单的 Go Web 应用程序,稍后您将对其进行容器化。 Go 应用程序将使用功能强大的 gorilla/mux 请求路由器,选择它的灵活性和速度。
对于本教程,您将在 ~/go-docker
下存储所有数据。 运行以下命令来执行此操作:
mkdir ~/go-docker
导航到它:
cd ~/go-docker
您将把示例 Go web 应用程序存储在一个名为 main.go
的文件中。 使用您的文本编辑器创建它:
nano main.go
添加以下行:
~/go-docker/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/Sammy\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-proxy
使用 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: '2' 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-proxy
,另一个用于 Let's Encrypt 附加组件 (letsencrypt-nginx-proxy-companion
)。 对于代理,您指定图像 jwilder/nginx-proxy
,公开和映射 HTTP 和 HTTPS 端口,最后定义容器可访问的卷,以保存 Nginx 相关数据。
在第二个块中,您为 Let's Encrypt 附加配置的图像命名。 然后,通过定义一个卷,然后从代理容器中继承现有卷来配置对 Docker 套接字的访问。 两个容器都将 restart
属性设置为 always
,这指示 Docker 始终保持它们运行(在崩溃或系统重新启动的情况下)。
保存并关闭文件。
通过运行部署 nginx-proxy
:
docker-compose -f nginx-proxy-compose.yaml up -d
Docker Compose 通过 -f
标志接受自定义命名文件。 up
命令运行容器,-d
标志,分离模式,指示它在后台运行容器。
您的最终输出将如下所示:
OutputCreating network "go-docker_default" with the default driver Pulling nginx-proxy (jwilder/nginx-proxy:)... latest: Pulling from jwilder/nginx-proxy a5a6f2f73cd8: Pull complete 2343eb083a4e: Pull complete ... Digest: sha256:619f390f49c62ece1f21dfa162fa5748e6ada15742e034fb86127e6f443b40bd Status: Downloaded newer image for jwilder/nginx-proxy:latest Pulling letsencrypt-nginx-proxy-companion (jrcs/letsencrypt-nginx-proxy-companion:)... latest: Pulling from jrcs/letsencrypt-nginx-proxy-companion ... Creating go-docker_nginx-proxy_1 ... done Creating go-docker_letsencrypt-nginx-proxy-companion_1 ... done
您已经使用 Docker Compose 部署了 nginx-proxy
及其 Let's Encrypt 伴侣。 接下来,您将为 Go Web 应用程序创建一个 Dockerfile。
第 3 步 — 将 Go Web 应用程序 Docker 化
在本节中,您将创建一个 Dockerfile,其中包含有关 Docker 如何为您的 Go Web 应用程序创建不可变映像的说明。 Docker 使用 Dockerfile 中的指令构建一个不可变的应用程序映像(类似于容器的快照)。 每次运行基于特定图像的容器时,图像的不变性保证了相同的环境。
使用文本编辑器创建 Dockerfile
:
nano Dockerfile
添加以下行:
~/go-docker/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.13 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 下。 您还将当前目录的内容复制到容器中。 第一阶段以递归方式从代码中获取使用的包并编译 main.go
文件以在没有符号和调试信息的情况下发布(通过传递 -ldflags="-s -w"
)结束。 当你编译一个 Go 程序时,它会保留二进制文件的一个单独部分,用于调试,但是,这些额外的信息会占用内存,并且在部署到生产环境时不需要保留。
第二阶段基于 alpine:3.13
(Alpine Linux 3.13)。 它安装受信任的 CA 证书,将编译的应用程序二进制文件从第一阶段复制到当前映像,公开端口 80
,并将应用程序二进制文件设置为映像入口点。
保存并关闭文件。
你已经为你的 Go 应用程序创建了一个 Dockerfile,它将获取它的包,编译它以发布,并在容器创建时运行它。 在下一步中,您将创建 Docker Compose yaml
文件并通过在 Docker 中运行它来测试应用程序。
第 4 步 - 创建和运行 Docker Compose 文件
现在,您将创建 Docker Compose 配置文件并编写运行在上一步中创建的 Docker 映像所需的配置。 然后,您将运行它并检查它是否正常工作。 通常,Docker Compose 配置文件指定应用程序所需的容器、它们的设置、网络和卷。 您还可以指定这些元素可以同时启动和停止。
您将把 Go Web 应用程序的 Docker Compose 配置存储在一个名为 go-app-compose.yaml
的文件中。 通过运行创建它:
nano go-app-compose.yaml
将以下行添加到此文件中:
~/go-docker/go-app-compose.yaml
version: '2' 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 Web 应用程序。 它使用您在上一步中创建的 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/12 : FROM golang:alpine AS build ---> b97a72b8e97d ... Successfully tagged go-docker_go-web-app:latest WARNING: Image for service go-web-app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Creating go-docker_go-web-app_1 ... done
如果您查看运行命令后显示的输出,Docker 会根据 Dockerfile 中的配置记录构建应用程序映像的每个步骤。
您现在可以导航到 https://your_domain/
以查看您的主页。 在您的 Web 应用程序的家庭地址中,您看到的页面是您在第一步中定义的 /
路由的结果。
现在导航到 https://your_domain/hello
。 您将看到您在代码中为步骤 1 中的 /hello
路由定义的消息。
最后,尝试将名称附加到您的 Web 应用程序的地址以测试其他路由,例如:https://your_domain/hello/Sammy
。
注意: 如果您收到有关无效 TLS 证书的错误,请等待几分钟,让 Let's Encrypt 插件配置证书。 如果您在短时间内仍然收到错误,请根据此步骤中显示的命令和配置仔细检查您输入的内容。
您已经创建了 Docker Compose 文件并编写了用于在容器中运行 Go 应用程序的配置。 最后,您导航到您的域以检查 gorilla/mux
路由器设置是否正确地为您的 Dockerized Go Web 应用程序提供请求。
结论
您现在已经在 Ubuntu 18.04 上使用 Docker 和 Nginx 成功部署了 Go Web 应用程序。 使用 Docker,维护应用程序变得不那么麻烦,因为每次运行应用程序的环境都保证是相同的。 gorilla/mux 软件包具有出色的文档并提供更复杂的功能,例如命名路由和提供静态文件。 如需对 Go HTTP 服务器模块的更多控制,例如定义自定义超时,请访问 官方文档 。