如何向DigitalOcean Kubernetes部署弹性Go应用程序

在本教程中,您将构建一个示例Go应用程序,并在开发计算机上本地运行并运行。然后,您将使用Docker将应用程序容纳在一起,将其部署到Kubernetes集群,并创建一个负载均衡器,作为面向公众的应用程序入口点。

作为Write for DOnations计划的一部分,作者选择了女性 代码来接受捐赠。

介绍

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网站 ,单击页面右上角的“入门”按钮,然后按照注册说明进行操作。
  • 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函数的主体中,添加/ route到新定义的homePage函数的映射。 这告诉应用My Awesome Go App即使对未知端点发出请求,也要打印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应用程序提供服务之前调用setupRoutes函数。

去-应用程序/ 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 + XY ,然后按ENTER

接下来,使用以下go run命令运行该应用程序。 这将编译main.go文件中的代码并在开发机器上本地运行:

go run main.go
Go 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标记它。

注意 :在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所有行,按顺序执行它们,然后缓存它们,从而使未来的构建运行得更快:

. . .
Successfully built 521679ff78e5
Successfully tagged go-web-app:latest

一旦此命令完成构建,您将能够在运行docker images命令时看到您的图像,如下所示:

docker images
REPOSITORY              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
Go Web App Started on Port 3000

如果该端口上没有其他任何内容正在运行,您将能够通过打开浏览器并导航到以下URL来查看应用程序的运行情况:

http://your_server_ip:3000

注意:如果您从本地计算机而不是服务器上学习本教程,请访问应用程序,而不是访问以下URL:

http://localhost:3000

您的容器化Go App

在浏览器中检查应用程序是否按预期工作后,通过在终端中按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命令的kubectl

第3步 - 提高kubectl可用性

到目前为止,您已经创建了一个正常运行的Go应用程序,并将其与Docker集中在一起。 但是,该应用程序仍然无法公开访问。 要解决此问题,您将使用kubectl命令行工具将新的Docker映像部署到Kubernetes集群。 但是,在执行此操作之前,让我们对Kubernetes配置文件进行一些小改动,这将有助于减少运行kubectl命令的kubectl

默认情况下,使用kubectl命令行工具运行命令时,必须使用--kubeconfig标志指定集群配置文件的路径。 但是,如果您的配置文件名为config并存储在名为~/.kube的目录中,则kubectl将知道在何处查找配置文件,并且可以在没有指向它的--kubeconfig标志的情况下进行--kubeconfig

为此,如果您还没有这样做,请创建一个名为~/.kube的新目录:

mkdir ~/.kube

然后将您的群集配置文件移动到此目录,并在此过程中将其重命名为config

mv clusterconfig.yaml ~/.kube/config

继续前进,当您运行kubectl时,您不需要指定群集配置文件的位置,因为该命令现在可以在默认位置找到它。 通过运行以下get nodes命令测试此行为:

kubectl get nodes

这将显示位于Kubernetes集群中的所有节点 在Kubernetes的上下文中,节点是可以部署一个或多个pod的服务器或工作机器:

NAME                                        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对象描述文件(也称为清单) 此清单将包含将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.ymlspec块。 每个Kubernetes对象都需要一个spec字段,但其精确格式因每种类型的对象而异。 在部署的情况下,它可以包含诸如您要运行的副本数之类的信息。 在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板块。 每个部署都使用模板块中指定的标签创建一组pod。 此块中的第一个子字段是metadata ,其中包含将应用于此部署中所有窗格的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

spec的第二部分是spec块。 这与您之前添加的spec块不同,因为此块仅适用于模板块创建的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

接下来,将imagePullPolicy字段设置为IfNotPresent ,这将指示部署仅在之前尚未执行此操作的情况下拉取图像。 然后,最后添加一个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 Deployments

在下一步中,您将创建另一种Kubernetes对象,该对象将管理您如何访问新部署中存在的pod。 此服务将创建一个负载均衡器,然后将公开单个IP地址,并将对此IP地址的请求分发到部署中的副本。 此服务还将处理端口转发规则,以便您可以通过HTTP访问应用程序。

第5步 - 创建服务

现在您已经成功部署了Kubernetes,您已准备好将您的应用程序暴露给外部世界。 为此,您需要定义另一种Kubernetes对象: 服务 此服务将在您的所有群集节点上公开相同的端口。 然后,您的节点将该端口上的任何传入流量转发到运行您的应用程序的Pod。

注意:为清楚起见,我们将在单独的文件中定义此服务对象。 但是,可以在同一个YAML文件中对多个资源清单进行分组,只要它们被---分隔即可。 有关详细信息,请参阅Kubernetes文档中的此页面

创建一个名为service.yml的新文件:

nano service.yml

通过再次以与deployment.yml文件类似的方式定义apiVersionkind字段来启动此文件。 这一次,将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块将与部署中包含的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请求,并将它们转发到3000targetPort 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
NAME             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命令的输出可能会在运行kubectl apply命令后的一段时间内显示LoadBalancerEXTERNAL-IP地址保持<pending>状态。 如果是这种情况,请等待几分钟并尝试重新运行该命令以确保已创建负载均衡器并且正在按预期运行。

负载均衡器将接收端口80上的请求并将其转发到群集中运行的其中一个pod。

你工作的Go App!

有了这个,您就创建了一个Kubernetes服务,再加上一个负载均衡器,为您提供了一个稳定的应用入口点。

结论

在本教程中,您构建了Go应用程序,将其与Docker集成,然后将其部署到Kubernetes集群。 然后,您创建了一个负载均衡器,为该应用程序提供了一个弹性入口点,确保即使集群中的某个节点出现故障,它仍将保持高可用性。 您可以使用本教程将您自己的Go应用程序部署到Kubernetes集群,或继续使用您在第1步中创建的示例应用程序学习其他Kubernetes和Docker概念。

继续,您可以将负载均衡器的IP地址映射到您控制的域名,以便您可以通过人类可读的Web地址而不是负载均衡器IP访问应用程序。 此外,您可能会对以下Kubernetes教程感兴趣:

最后,如果您想了解有关Go的更多信息,我们建议您查看我们关于如何编写Go的系列文章