如何将弹性Go应用程序部署到DigitalOceanKubernetes
作为 Write for DOnations 计划的一部分,作者选择了 Girls Who Code 来接受捐赠。
介绍
Docker 是一个 容器化 工具,用于为应用程序提供一个文件系统,其中包含它们运行所需的一切,确保软件具有一致的运行时环境,并且在任何情况下都将以相同的方式运行它的部署位置。 Kubernetes 是一个用于自动化部署、扩展和管理容器化应用程序的云平台。
通过利用 Docker,您可以在任何支持 Docker 的系统上部署应用程序,并确信它始终按预期工作。 与此同时,Kubernetes 允许您跨集群中的多个节点部署应用程序。 此外,它还处理关键任务,例如在您的任何容器崩溃时启动新容器。 这些工具一起简化了应用程序的部署过程,让您可以专注于开发。
在本教程中,您将构建一个用 Go 编写的示例应用程序,并在您的开发机器上本地启动和运行。 然后,您将使用 Docker 将应用程序容器化,将其部署到 Kubernetes 集群,并创建一个负载均衡器,作为应用程序的面向公众的入口点。
先决条件
在开始本教程之前,您将需要以下内容:
- 您将从其部署应用程序的开发服务器或本地计算机。 尽管本指南中的说明在很大程度上适用于大多数操作系统,但本教程假设您可以访问配置有具有 sudo 权限的非 root 用户的 Ubuntu 18.04 系统,如我们的 Ubuntu 18.04 初始服务器设置中所述 教程。
- 安装在开发机器上的
docker
命令行工具。 要安装它,请按照我们关于 如何在 Ubuntu 18.04 上安装和使用 Docker 的教程的 步骤 1 和 2。 - 安装在开发机器上的
kubectl
命令行工具。 要安装它,请按照 Kubernetes 官方文档 中的 本指南进行操作。 - Docker Hub 上的免费帐户,您可以将 Docker 映像推送到该帐户。 要进行此设置,请访问 Docker Hub 网站,单击页面右上角的 Get Started 按钮,然后按照注册说明进行操作。
- 一个 Kubernetes 集群。 您可以按照我们的 Kubernetes 快速入门指南 配置 DigitalOcean Kubernetes 集群 。 如果您从其他云提供商配置集群,您仍然可以完成本教程。 无论您在何处采购集群,请务必设置配置文件并确保您可以从开发服务器连接到集群。
第 1 步 — 在 Go 中构建示例 Web 应用程序
在这一步中,您将构建一个用 Go 编写的示例应用程序。 使用 Docker 容器化此应用程序后,它将为 My Awesome Go App
服务,以响应对端口 3000
的服务器 IP 地址的请求。
如果您最近没有这样做,请先更新服务器的软件包列表:
sudo apt update
然后通过运行安装 Go:
sudo apt install golang
接下来,确保您在主目录中并创建一个包含所有项目文件的新目录:
cd && mkdir go-app
然后导航到这个新目录:
cd go-app/
使用 nano
或您喜欢的文本编辑器创建一个名为 main.go
的文件,该文件将包含您的 Go 应用程序的代码:
nano main.go
任何 Go 源文件的第一行始终是 package
语句,用于定义文件所属的代码包。 对于像这样的可执行文件,package
语句必须指向 main
包:
去应用程序/main.go
package main
之后,添加一个 import
语句,您可以在其中列出应用程序需要的所有库。 在这里,包括处理格式化文本输入和输出的 fmt
和提供 HTTP 客户端和服务器实现的 net/http
:
去应用程序/main.go
package main import ( "fmt" "net/http" )
接下来,定义一个 homePage
函数,它将接受两个参数:http.ResponseWriter
和一个指向 http.Request
的指针。 在 Go 中,ResponseWriter
接口用于构造 HTTP 响应,而 http.Request
是表示传入请求的对象。 因此,此块读取传入的 HTTP 请求,然后构造一个响应:
去应用程序/main.go
. . . import ( "fmt" "net/http" ) func homePage(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "My Awesome Go App") }
在此之后,添加一个 setupRoutes
函数,它将传入的请求映射到它们预期的 HTTP 处理函数。 在此 setupRoutes
函数的主体中,添加 /
路由到新定义的 homePage
函数的映射。 这告诉应用程序打印 My Awesome Go App
消息,即使是对未知端点的请求:
去应用程序/main.go
. . . func homePage(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "My Awesome Go App") } func setupRoutes() { http.HandleFunc("/", homePage) }
最后,添加以下 main
函数。 这将打印出一个字符串,表明您的应用程序已启动。 然后它将调用 setupRoutes
函数,然后在端口 3000
上侦听和服务您的 Go 应用程序。
去应用程序/main.go
. . . func setupRoutes() { http.HandleFunc("/", homePage) } func main() { fmt.Println("Go Web App Started on Port 3000") setupRoutes() http.ListenAndServe(":3000", nil) }
添加这些行后,最终文件的外观如下:
去应用程序/main.go
package main import ( "fmt" "net/http" ) func homePage(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "My Awesome Go App") } func setupRoutes() { http.HandleFunc("/", homePage) } func main() { fmt.Println("Go Web App Started on Port 3000") setupRoutes() http.ListenAndServe(":3000", nil) }
保存并关闭此文件。 如果您使用 nano
创建此文件,请按 CTRL + X
、Y
,然后按 ENTER
。
接下来,使用以下 go run
命令运行应用程序。 这将编译您的 main.go
文件中的代码并在您的开发机器上本地运行它:
go run main.go
OutputGo Web App Started on Port 3000
此输出确认应用程序按预期工作。 但是,它将无限期地运行,因此请按 CTRL + C
将其关闭。
在本指南中,您将使用此示例应用程序来试验 Docker 和 Kubernetes。 为此,请继续阅读以了解如何使用 Docker 将您的应用程序容器化。
第 2 步 — Docker 化您的 Go 应用程序
在当前状态下,您刚刚创建的 Go 应用程序仅在您的开发服务器上运行。 在这一步中,您将使用 Docker 将这个新应用程序容器化,从而使其具有可移植性。 这将允许它在任何支持 Docker 容器的机器上运行。 您将构建 Docker 映像并将其推送到 Docker Hub 上的中央公共存储库。 这样,您的 Kubernetes 集群可以将镜像拉回并部署为集群中的容器。
将应用程序容器化的第一步是创建一个名为 Dockerfile 的特殊脚本。 Dockerfile 通常包含一系列指令和参数,这些指令和参数按顺序运行,以便自动对基础镜像执行某些操作或创建新镜像。
注意: 在这一步中,您将配置一个简单的 Docker 容器,它将在一个阶段构建和运行您的 Go 应用程序。 如果将来您想减小 Go 应用程序在生产环境中运行的容器的大小,您可能需要研究 多阶段构建 。
创建一个名为 Dockerfile
的新文件:
nano Dockerfile
在文件的顶部,指定 Go 应用所需的基础镜像:
去应用程序/Dockerfile
FROM golang:1.12.0-alpine3.9
然后在容器中创建一个 app
目录来保存应用程序的源文件:
去应用程序/Dockerfile
FROM golang:1.12.0-alpine3.9 RUN mkdir /app
在其下方,添加以下行,将 root
目录中的所有内容复制到 app
目录中:
去应用程序/Dockerfile
FROM golang:1.12.0-alpine3.9 RUN mkdir /app ADD . /app
接下来,添加以下行,将工作目录更改为 app
,这意味着此 Dockerfile 中的以下所有命令都将从该位置运行:
去应用程序/Dockerfile
FROM golang:1.12.0-alpine3.9 RUN mkdir /app ADD . /app WORKDIR /app
添加一行指示 Docker 运行 go build -o main
命令,该命令编译 Go 应用程序的二进制可执行文件:
去应用程序/Dockerfile
FROM golang:1.12.0-alpine3.9 RUN mkdir /app ADD . /app WORKDIR /app RUN go build -o main .
然后添加最后一行,它将运行二进制可执行文件:
去应用程序/Dockerfile
FROM golang:1.12.0-alpine3.9 RUN mkdir /app ADD . /app WORKDIR /app RUN go build -o main . CMD ["/app/main"]
添加这些行后保存并关闭文件。
现在您在项目的根目录中拥有了这个 Dockerfile
,您可以使用以下 docker build
命令基于它创建一个 Docker 映像。 此命令包括 -t
标志,当传递值 go-web-app
时,将命名 Docker 映像 go-web-app
和 tag 它。
注意:在 Docker 中,标签允许您传达特定于给定映像的信息,例如其版本号。 以下命令不提供特定标签,因此 Docker 将使用其默认标签标记图像:latest
。 如果您想给图像一个自定义标签,您可以在图像名称后面加上一个冒号和您选择的标签,如下所示:
docker build -t sammy/image_name:tag_name .
像这样标记图像可以让您更好地控制图像。 例如,您可以将标记为 v1.1
的映像部署到生产环境,但将另一个标记为 v1.2
的映像部署到预生产或测试环境。
您将传递的最后一个参数是路径:.
。 这指定您希望从当前工作目录的内容构建 Docker 映像。 此外,请务必将 sammy
更新为您的 Docker Hub 用户名:
docker build -t sammy/go-web-app .
此构建命令将读取 Dockerfile
中的所有行,按顺序执行它们,然后缓存它们,从而使将来的构建运行得更快:
Output. . . Successfully built 521679ff78e5 Successfully tagged go-web-app:latest
此命令完成构建后,您将能够在运行 docker images
命令时看到您的图像,如下所示:
docker images
OutputREPOSITORY TAG IMAGE ID CREATED SIZE sammy/go-web-app latest 4ee6cf7a8ab4 3 seconds ago 355MB
接下来,使用以下命令根据刚刚构建的镜像创建并启动一个容器。 此命令包括 -it
标志,它指定容器将以交互模式运行。 它还有 -p
标志,它将 Go 应用程序在您的开发机器上运行的端口(端口 3000
)映射到 Docker 容器中的端口 3000
:
docker run -it -p 3000:3000 sammy/go-web-app
OutputGo Web App Started on Port 3000
如果该端口上没有运行其他任何东西,您将能够通过打开浏览器并导航到以下 URL 来查看正在运行的应用程序:
http://your_server_ip:3000
注意: 如果您是从本地计算机而不是服务器上学习本教程,请访问以下 URL 来访问应用程序:
http://localhost:3000
检查应用程序在浏览器中是否按预期运行后,在终端中按 CTRL + C
将其停止。
当您将容器化应用程序部署到 Kubernetes 集群时,您需要能够从一个集中位置拉取镜像。 为此,您可以将新创建的映像推送到 Docker Hub 映像存储库。
运行以下命令从终端登录到 Docker Hub:
docker login
这将提示您输入 Docker Hub 用户名和密码。 正确输入它们后,您将在命令的输出中看到 Login Succeeded
。
登录后,使用 docker push
命令将新镜像推送到 Docker Hub,如下所示:
docker push sammy/go-web-app
成功完成此命令后,您将能够打开 Docker Hub 帐户并在那里查看您的 Docker 映像。
既然您已将映像推送到中心位置,就可以将其部署到 Kubernetes 集群了。 不过,首先,我们将介绍一个简短的过程,这将使运行 kubectl
命令变得不那么乏味。
第 3 步 — 提高 kubectl
的可用性
至此,您已经创建了一个正常运行的 Go 应用程序并使用 Docker 对其进行了容器化。 但是,该应用程序仍然无法公开访问。 要解决此问题,您将使用 kubectl
命令行工具将新的 Docker 映像部署到 Kubernetes 集群。 不过,在此之前,让我们对 Kubernetes 配置文件进行一些小改动,这将有助于降低运行 kubectl
命令的难度。
默认情况下,当您使用 kubectl
命令行工具运行命令时,您必须使用 --kubeconfig
标志指定集群配置文件的路径。 但是,如果您的配置文件名为 config
并存储在名为 ~/.kube
的目录中,则 kubectl
将知道在哪里查找配置文件并能够获取它没有指向它的 --kubeconfig
标志。
为此,如果您还没有这样做,请创建一个名为 ~/.kube
的新目录:
mkdir ~/.kube
然后把你的集群配置文件移动到这个目录下,在这个过程中重命名为config
:
mv clusterconfig.yaml ~/.kube/config
继续前进,您无需在运行 kubectl
时指定集群配置文件的位置,因为该命令现在可以找到它,因为它位于默认位置。 通过运行以下 get nodes
命令测试此行为:
kubectl get nodes
这将显示驻留在 Kubernetes 集群中的所有 节点 。 在 Kubernetes 的上下文中,节点是可以部署一个或多个 Pod 的服务器或工作机器:
OutputNAME STATUS ROLES AGE VERSION k8s-1-13-5-do-0-nyc1-1554148094743-1-7lfd Ready <none> 1m v1.13.5 k8s-1-13-5-do-0-nyc1-1554148094743-1-7lfi Ready <none> 1m v1.13.5 k8s-1-13-5-do-0-nyc1-1554148094743-1-7lfv Ready <none> 1m v1.13.5
有了这个,您就可以继续前进并将您的应用程序部署到您的 Kubernetes 集群。 您将通过创建两个 Kubernetes 对象来做到这一点:一个将应用程序部署到集群中的一些 pod,另一个将创建负载均衡器,为您的应用程序提供访问点。
第 4 步 — 创建部署
RESTful 资源 构成了 Kubernetes 系统中的所有持久实体,在这种情况下,它们通常被称为 Kubernetes 对象。 将 Kubernetes 对象视为您提交给 Kubernetes 的工作订单会很有帮助:您列出您需要哪些资源以及它们应该如何工作,然后 Kubernetes 将不断工作以确保它们存在于您的集群中。
一种 Kubernetes 对象,称为 部署 ,是一组相同的、无法区分的 pod。 在 Kubernetes 中,pod 是一组一个或多个容器,它们能够通过相同的共享网络进行通信并与相同的共享存储进行交互。 部署一次运行多个父应用程序的副本,并自动替换任何失败的实例,确保您的应用程序始终可用于服务用户请求。
在此步骤中,您将为部署创建一个 Kubernetes 对象描述文件,也称为 manifest。 此清单将包含将 Go 应用程序部署到集群所需的所有配置详细信息。
首先在项目的根目录中创建部署清单:go-app/
。 对于像这样的小型项目,将它们保存在根目录中可以最大限度地降低复杂性。 但是,对于较大的项目,将清单存储在单独的子目录中可能会有所帮助,以保持一切井井有条。
创建一个名为 deployment.yml
的新文件:
nano deployment.yml
不同版本的 Kubernetes API 包含不同的对象定义,因此您必须在此文件的顶部定义用于创建此对象的 apiVersion
。 出于本教程的目的,您将使用 apps/v1
分组,因为它包含创建部署所需的许多核心 Kubernetes 对象定义。 在 apiVersion
下方添加一个字段,描述您正在创建的 Kubernetes 对象的 kind
。 在这种情况下,您正在创建一个 Deployment
:
去应用程序/deployment.yml
--- apiVersion: apps/v1 kind: Deployment
然后为您的部署定义 metadata
。 每个 Kubernetes 对象都需要一个 metadata
字段,因为它包含对象的唯一 name
等信息。 这个 name
很有用,因为它允许您将不同的部署彼此区分开来,并使用人类可读的名称来识别它们:
去应用程序/deployment.yml
--- apiVersion: apps/v1 kind: Deployment metadata: name: go-web-app
接下来,您将构建 deployment.yml
的 spec
块。 spec
字段是每个 Kubernetes 对象的要求,但其精确格式因每种类型的对象而异。 在部署的情况下,它可以包含诸如您要运行的副本的数量等信息。 在 Kubernetes 中,副本是您希望在集群中运行的 pod 数量。 此处,将 replicas
的数量设置为 5
:
去应用程序/deployment.yml
. . . metadata: name: go-web-app spec: replicas: 5
接下来,创建一个嵌套在 spec
块下的 selector
块。 这将用作您的 pod 的 标签选择器。 Kubernetes 使用标签选择器来定义部署如何找到它必须管理的 pod。
在此 selector
块中,定义 matchLabels
并添加 name
标签。 本质上,matchLabels
字段告诉 Kubernetes 部署适用于哪些 pod。 在此示例中,部署将应用于名称为 go-web-app
的任何 pod:
去应用程序/deployment.yml
. . . spec: replicas: 5 selector: matchLabels: name: go-web-app
在此之后,添加一个 template
块。 每个部署都使用 template
块中指定的标签创建一组 pod。 此块中的第一个子字段是 metadata
,其中包含将应用于此部署中的所有 pod 的 labels
。 这些标签是键/值对,用作标识 Kubernetes 对象的属性。 稍后定义服务时,您可以指定希望将具有此 name
标签的所有 pod 分组在该服务下。 将此 name
标签设置为 go-web-app
:
去应用程序/deployment.yml
. . . spec: replicas: 5 selector: matchLabels: name: go-web-app template: metadata: labels: name: go-web-app
template
块的第二部分是 spec
块。 这与您之前添加的 spec
块不同,因为它仅适用于由 template
块创建的 Pod,而不是整个部署。
在这个 spec
块中,添加一个 containers
字段并再次定义一个 name
属性。 这个 name
字段定义了由这个特定部署创建的任何容器的名称。 在此之下,定义要下拉和部署的 image
。 请务必将 sammy
更改为您自己的 Docker Hub 用户名:
去应用程序/deployment.yml
. . . template: metadata: labels: name: go-web-app spec: containers: - name: application image: sammy/go-web-app
之后,添加一个设置为 IfNotPresent
的 imagePullPolicy
字段,这将指示部署仅在之前未这样做的情况下提取图像。 然后,最后,添加一个 ports
块。 在那里,定义 containerPort
应该匹配你的 Go 应用程序监听的端口号。 在这种情况下,端口号是 3000
:
去应用程序/deployment.yml
. . . spec: containers: - name: application image: sammy/go-web-app imagePullPolicy: IfNotPresent ports: - containerPort: 3000
deployment.yml
的完整版如下所示:
去应用程序/deployment.yml
--- apiVersion: apps/v1 kind: Deployment metadata: name: go-web-app spec: replicas: 5 selector: matchLabels: name: go-web-app template: metadata: labels: name: go-web-app spec: containers: - name: application image: sammy/go-web-app imagePullPolicy: IfNotPresent ports: - containerPort: 3000
保存并关闭文件。
接下来,使用以下命令应用您的新部署:
kubectl apply -f deployment.yml
注意:有关部署可用的所有配置的更多信息,请在此处查看官方 Kubernetes 文档:Kubernetes 部署
在下一步中,您将创建另一种 Kubernetes 对象,该对象将管理您访问新部署中存在的 Pod 的方式。 该服务将创建一个负载均衡器,然后该负载均衡器将公开一个 IP 地址,并且对该 IP 地址的请求将被分发到部署中的副本。 该服务还将处理端口转发规则,以便您可以通过 HTTP 访问您的应用程序。
第 5 步 — 创建服务
现在您已经成功部署了 Kubernetes,您已准备好将您的应用程序公开给外界。 为此,您需要定义另一种 Kubernetes 对象:service。 此服务将在所有集群节点上公开相同的端口。 然后,您的节点会将该端口上的任何传入流量转发到运行您的应用程序的 pod。
注意: 为清楚起见,我们将在单独的文件中定义此服务对象。 但是,可以在同一个 YAML 文件中对多个资源清单进行分组,只要它们用 ---
分隔即可。 有关详细信息,请参阅 Kubernetes 文档 中的 此页面。
创建一个名为 service.yml
的新文件:
nano service.yml
通过以与 deployment.yml
文件类似的方式再次定义 apiVersion
和 kind
字段来启动此文件。 这次将 apiVersion
字段指向 v1
,即服务常用的 Kubernetes API:
去应用程序/service.yml
--- apiVersion: v1 kind: Service
接下来,像在 deployment.yml
中所做的那样,在 metadata
块中添加服务的名称。 这可以是您喜欢的任何内容,但为了清楚起见,我们将其称为 go-web-service
:
去应用程序/service.yml
--- apiVersion: v1 kind: Service metadata: name: go-web-service
接下来,创建一个 spec
块。 此 spec
块将与您的部署中包含的块不同,它将包含此服务的 type
,以及端口转发配置和 selector
。
添加定义此服务的 type
的字段并将其设置为 LoadBalancer
。 这将自动配置一个负载均衡器,作为应用程序的主要入口点。
警告: 此步骤中概述的创建负载均衡器的方法仅适用于从同样支持外部负载均衡器的云提供商提供的 Kubernetes 集群。 此外,请注意,从云提供商处配置负载均衡器会产生额外费用。 如果您对此感到担忧,您可能需要考虑使用 Ingress 公开外部 IP 地址。
去应用程序/service.yml
--- apiVersion: v1 kind: Service metadata: name: go-web-service spec: type: LoadBalancer
然后添加一个 ports
块,您将在其中定义您希望如何访问您的应用程序。 嵌套在此块中,添加以下字段:
name
,指向http
port
,指向端口80
targetPort
,指向端口3000
这将在端口 80
上接收传入的 HTTP 请求并将它们转发到 3000
的 targetPort
。 这个 targetPort
是你的 Go 应用程序运行的同一个端口:
去应用程序/service.yml
--- apiVersion: v1 kind: Service metadata: name: go-web-service spec: type: LoadBalancer ports: - name: http port: 80 targetPort: 3000
最后,像在 deployments.yml
文件中那样添加一个 selector
块。 这个 selector
块很重要,因为它将任何名为 go-web-app
的已部署 pod 映射到此服务:
去应用程序/service.yml
--- apiVersion: v1 kind: Service metadata: name: go-web-service spec: type: LoadBalancer ports: - name: http port: 80 targetPort: 3000 selector: name: go-web-app
添加这些行后,保存并关闭文件。 之后,再次使用 kubectl apply
命令将此服务应用于您的 Kubernetes 集群,如下所示:
kubectl apply -f service.yml
此命令将应用新的 Kubernetes 服务并创建负载均衡器。 这个负载均衡器将作为在集群中运行的应用程序的面向公众的入口点。
要查看应用程序,您将需要新负载均衡器的 IP 地址。 通过运行以下命令找到它:
kubectl get services
OutputNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE go-web-service LoadBalancer 10.245.107.189 203.0.113.20 80:30533/TCP 10m kubernetes ClusterIP 10.245.0.1 <none> 443/TCP 3h4m
您可能运行了不止一项服务,但请找到标有 go-web-service
的服务。 找到 EXTERNAL-IP
列并复制与 go-web-service
关联的 IP 地址。 在此示例输出中,此 IP 地址为 203.0.113.20
。 然后,将 IP 地址粘贴到浏览器的 URL 栏中,以查看在 Kubernetes 集群上运行的应用程序。
注意: 当 Kubernetes 以这种方式创建负载均衡器时,它是异步执行的。 因此,kubectl get services
命令的输出可能会显示 LoadBalancer
的 EXTERNAL-IP
地址在运行 kubectl apply
一段时间后仍处于 <pending>
状态] 命令。 如果是这种情况,请等待几分钟并尝试重新运行该命令,以确保负载均衡器已创建并按预期运行。
负载均衡器将接收端口 80
上的请求,并将其转发到集群中运行的其中一个 Pod。
这样,您就创建了一个与负载均衡器相结合的 Kubernetes 服务,为您提供了一个单一、稳定的应用程序入口点。
结论
在本教程中,您构建了 Go 应用程序,使用 Docker 对其进行容器化,然后将其部署到 Kubernetes 集群。 然后,您创建了一个负载均衡器,为该应用程序提供了一个弹性入口点,确保即使集群中的一个节点出现故障,它也能保持高可用性。 您可以使用本教程将您自己的 Go 应用程序部署到 Kubernetes 集群,或者使用您在步骤 1 中创建的示例应用程序继续学习其他 Kubernetes 和 Docker 概念。
展望未来,您可以 将负载均衡器的 IP 地址映射到您控制的域名 ,以便您可以通过人类可读的 Web 地址而不是负载均衡器 IP 访问应用程序。 此外,您可能会对以下 Kubernetes 教程感兴趣:
最后,如果您想了解有关 Go 的更多信息,我们鼓励您查看我们关于 如何在 Go 中编码的系列。