使用Kubernetes为Golang创建TODO API

大家好!在发布基于Kubernetes的基础架构平台课程之前,我们准备了另一篇有趣的材料的翻译。



本文面向Kubernetes的新手,他们将有兴趣了解一个实际示例,该示例如何编写Golang API来管理TODO列表,然后将其部署到Kubernetes。

每个开发人员都喜欢一个好的TODO列表,对吗?否则我们还能如何组织自己?


每个开发人员都喜欢一个好的TODO应用程序,对吗?

我们将首先查看必要组件的列表,然后继续配置Kubernetes,提供Postgresql数据库,然后安装一个应用程序框架,该框架将帮助我们轻松地在Kubernetes中部署Go API,而无需关注细节。

我们将在API中创建两个端点-一个端点创建一个新的TODO记录,另一个端点选择所有的TODO记录。

本教程中的所有代码都可以在GitHub上获得。

必要组件列表:


  • 本地安装的Docker

Kubernetes在容器映像中运行代码,因此您需要在计算机上安装Docker。

安装Docker:https
://www.docker.com注册一个Docker Hub帐户以存储您的Docker映像:https : //hub.docker.com/

  • Kubernetes集群


您可以选择本地或远程群集,但是哪个更好?简化的选项(例如k3d)可在Docker可以在其上运行的任何计算机上运行,​​因此运行本地集群不再需要大量RAM。远程集群也可以是非常有效的解决方案,但请记住,每次更改都需要上传和上传所有Docker映像。

安装k3d:https : //github.com/rancher/k3d

k3d将不会安装kubectl(这是Kubernetes的CLI),因此请从此处单独安装它:https : //kubernetes.io/docs/tasks/tools/install-kubectl

  • 高朗


您还需要在计算机上使用IDE安装Golang。Go是免费的,您可以从以下链接下载MacOS,Windows或Linux版:https//golang.org/dl

  • 集成开发环境


我建议使用Visual Studio Code,它是免费的,并且具有可以添加的Go插件集。一些同事更喜欢Jetbrains的Goland。如果您是Java程序员,那么您可能宁愿花钱购买Goland,因为它会使您想起其他产品。

安装VSCodeGolang

创建集群


您需要安装上一节中指定的所有软件。

使用k3d创建一个新集群:


k3d create

配置kubectl,使其指向新集群。请记住,可以在一台计算机上使用多个群集,因此,指向正确的群集非常重要:

export KUBECONFIG="$(k3d get-kubeconfig --name='k3s-default')"


确保群集至少有一个节点。在这里您可以看到我已经安装了Kubernetes 1.17-一个相对较新的版本:

kubectl get node
NAME                     STATUS   ROLES    AGE   VERSION
k3d-k3s-default-server   Ready    master   48s   v1.17.0+k3s.1


我们将我们的TODO记录存储在数据库表中,因此现在我们需要安装它。PostgreSQL是一种流行的关系数据库,我们可以使用Helm图表将其安装在集群中。

安装arkade


arkade是一个CLI Go,类似于“ brew”或“ apt-get”,但适用于Kubernetes应用程序。它使用Helm,kubectl或CLI项目将项目或产品安装到集群中。

curl -sLS https://dl.get-arkade.dev | sudo sh


现在安装Postgresql

arkade install postgresql
===================================================================== = PostgreSQL has been installed.                                    =
=====================================================================


您还将看到有关连接字符串以及如何通过集群内的Docker映像启动Postgresql CLI的信息。

您可以随时使用获取此信息arkade info postgresql

我们将设计表格布局

CREATE TABLE todo (
 id              INT GENERATED ALWAYS AS IDENTITY,
 description     text NOT NULL,
 created_date    timestamp NOT NULL,
 completed_date  timestamp NOT NULL
);


arkade info postgresql再次 运行以获取连接信息。它看起来应该像这样:

export POSTGRES_PASSWORD=$(kubectl get secret --namespace default postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode)
kubectl run postgresql-client --rm --tty -i --restart='Never' --namespace default --image docker.io/bitnami/postgresql:11.6.0-debian-9-r0 --env="PGPASSWORD=$POSTGRES_PASSWORD" --command -- psql --host postgresql -U postgres -d postgres -p 5432


现在,您可以在命令提示符下找到:postgres = #,可以通过在内部复制表来创建表,然后运行\dt以显示该表:

postgres=# \dt
List of relations
Schema | Name | Type  |  Owner
--------+------+-------+----------
public | todo | table | postgres
(1 row)


安装应用程序框架


正如PHP开发人员使用LAMP(Linux Apache + Mysql + PHP)加快工作流程,以及Rails开发人员使用预先准备好的堆栈加快工作流程一样,Kubernetes开发人员也可以使用应用程序框架。
PLONK堆栈代表Prometheus,Linux,OpenFaaS,NATS和Kubernetes。
  • Prometheus提供度量,自动扩展和可观察性来测试系统的运行状况,使其能够通过提供度量以制定有关将比例缩放为零的决策来响应需求激增并削减成本。
  • Linux虽然不是Kubernetes中运行工作负载的唯一选择,但它是标准且最易于使用的。
  • 最初,OpenFaaS为开发人员提供了可移植的功能,但在部署API和微服务时效果很好。它的多功能性意味着可以部署和管理带有HTTP服务器的任何Docker容器。
  • NATS是一个流行的CNCF项目,用于消息传递和发布/订阅。在PLONK堆栈中,它提供了异步执行请求和排队的功能。
  • Kubernetes是我们在这里的原因。它提供了可伸缩的,自我修复的声明性基础结构。而且,如果您不再需要所有这些功能,则可以使用OpenFaaS简化其API。


通过arkade安装PLONK堆栈:

arkade install openfaas


阅读参考消息并运行每个命令:

  • 安装faas-cli。
  • 获取密码。
  • 重定向OpenFaaS网关UI(通过端口8080)
  • 并通过命令行登录系统。


和以前一样,您可以使用接收参考消息 info openfaas

部署您的First Go API


有多种方法可以使用PLONK创建Go API。第一种选择是使用Dockerfile并手动确定TCP端口,运行状况检查,HTTP服务器等。可以使用来完成faas-cli new --lang dockerfile API_NAME,但是有一种更简单,更自动化的方法。

第二种方法是使用Function Store提供的内置模板:

faas-cli template store list | grep go
go                       openfaas           Classic Golang template
golang-http              openfaas-incubator Golang HTTP template
golang-middleware        openfaas-incubator Golang Middleware template


由于我们要创建传统的HTTP风格的API,因此golang-middleware模板将是最合适的。

在本教程开始时,您已注册Docker Hub帐户以存储Docker映像。必须先将每个Kubernetes工作负载嵌入Docker映像中,然后才能进行部署。

拉起特殊图案:

faas-cli template store pull golang-middleware


将Scaffold与golang-middleware和Docker Hub中的用户名一起用于您的API:

export PREFIX=alexellis2
export LANG=golang-middleware
export API_NAME=todo
faas-cli new --lang $LANG --prefix $PREFIX $API_NAME


您将看到两个生成的文件:

  • ./todo.yml -提供一种配置,部署和安装模板和名称的方法
  • ./todo/handler.go -在这里您编写代码并添加所需的任何其他文件或软件包


让我们进行一些编辑,然后扩展代码。

package function
import (
   "net/http"
   "encoding/json"
)
type Todo struct {
   Description string `json:"description"`
}
func Handle(w http.ResponseWriter, r *http.Request) {
   todos := []Todo{}
   todos = append(todos, Todo{Description: "Run faas-cli up"})
   res, _ := json.Marshal(todos)
   w.WriteHeader(http.StatusOK)
   w.Header().Set("Content-Type", "application/json")
   w.Write([]byte(res))
}


如果您不使用VScode及其插件来编辑和格式化代码,请在每次更改后运行它,以确保文件格式正确。

gofmt -w -s ./todo/handler.go


现在部署代码-首先创建一个新映像,在Docker Hub中启动它,然后使用OpenFaaS API将其部署到集群中:

Invoke your endpoint when ready:
curl http://127.0.0.1:8080/function/todo
{
"description": "Run faas-cli up"
}


允许用户创建新的TODO记录


现在,允许用户在其TODO列表中创建新条目。首先,您需要为Go添加到Postgresql库的链接或“依赖项”。

我们可以使用Go 1.11中引入并在Go 1.13中默认安装的自动售货或Go模块来获得它。

编辑handler.go并添加访问Postgresql所需的模块:

import (
   "database/sql"
   _ "github.com/lib/pq"
...


为了使Go模块能够检测依赖关系,我们需要在文件中声明一些内容,稍后我们将使用它。如果我们不这样做,则VSCode将在保存时删除这些行。

将此添加到文件中的导入下

var db *sql.DB


始终todo在其所在的文件夹中handler.go而不是在根目录c上运行这些命令todo.yml

初始化新的Go模块:

cd todo/
ls
handler.go
export GO111MODULE=on
go mod init


现在使用pq库更新文件:

go get
go mod tidy
cat go.mod
module github.com/alexellis/todo1/todo
go 1.13
require github.com/lib/pq v1.3.0


无论里面有什么go.mod,都将其内容复制到GO_REPLACE.txt

cat go.mod > GO_REPLACE.txt


现在,在添加插入代码之前,请确保该程序集仍然可以运行。

faas-cli build -f todo.yml --build-arg GO111MODULE=on


您可能会注意到,现在我们传递--build-arg了使用Go模块的报告模式。

在组装过程中,您会看到模块是根据需要从Internet下载的。

Step 16/29 : RUN go test ./... -cover
---> Running in 9a4017438500
go: downloading github.com/lib/pq v1.3.0
go: extracting github.com/lib/pq v1.3.0
go: finding github.com/lib/pq v1.3.0
?       github.com/alexellis/todo1/todo [no test files]
Removing intermediate container 9a4017438500


配置Postgresql访问的机密


我们可以在中创建一个连接池init (),该方法仅在程序启动时运行一次。

// init       .   ,     ,   /   /

func init() {
       if _, err := os.Stat("/var/openfaas/secrets/password"); err == nil {
               password, _ := sdk.ReadSecret("password")
               user, _ := sdk.ReadSecret("username")
               host, _ := sdk.ReadSecret("host")
               dbName := os.Getenv("postgres_db")
               port := os.Getenv("postgres_port")
               sslmode := os.Getenv("postgres_sslmode")
               connStr := "postgres://" + user + ":" + password + "@" + host + ":" + port + "/" + dbName + "?sslmode=" + sslmode
var err error
               db, err = sql.Open("postgres", connStr)
               if err != nil {
                       panic(err.Error())
               }
               err = db.Ping()
               if err != nil {
                       panic(err.Error())
               }
       }
}


正如您所注意到的,一些信息是从 os.Getenv从环境中读取的信息中提取的。我会认为这些值是非机密的,它们是在文件中设置的todo.y

其余的信息(例如密码和主机)是机密的,它们被保存在Kubernetes的秘密中

您可以通过faas-cli secret create或通过创建它们kubectl create secret generic -n openfaas-fn

该字符串sdk.ReadSecret来自OpenFaaS Cloud SDK,具有以下导入:github.com/openfaas/openfaas-cloud/sdk它从磁盘读取机密文件并返回值或错误。

从获取秘密值arkade info postgresql

现在,为每个密码执行以下操作:

export POSTGRES_PASSWORD=$(kubectl get secret --namespace default postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode)
export USERNAME="postgres"
export PASSWORD=$POSTGRES_PASSWORD
export HOST="postgresql.default"
faas-cli secret create username --from-literal $USERNAME
faas-cli secret create password --from-literal $PASSWORD
faas-cli secret create host --from-literal $HOST


检查机密的可用性和合规性:

faas-cli secret ls
NAME
username
password
host
# And via kubectl:
kubectl get secret -n openfaas-fn
NAME                  TYPE                                  DATA   AGE
username              Opaque                                1      13s
password              Opaque                                1      13s
host                  Opaque                                1      12s


编辑我们的YAML文件并添加以下内容:

secrets:
   - host
   - password
   - username
   environment:
     postgres_db: postgres
     postgres_sslmode: "disable"
     postgres_port: 5432


接下来,更新Go模块并再次运行构建:

cd todo
go get
go mod tidy
cd ..
faas-cli build -f todo.yml --build-arg GO111MODULE=on
Successfully built d2c609f8f559
Successfully tagged alexellis2/todo:latest
Image: alexellis2/todo:latest built.
[0] < Building todo done in 22.50s.
[0] Worker done.
Total build time: 22.50s


程序集按预期工作,因此让我们faas-cli使用相同的参数运行它以启动和部署映像。如果凭据和SQL配置正确,我们将在日志中看不到错误,但是,如果它们不正确,我们将在init()中获得紧急代码。

检查日志:

faas-cli logs todo
2020-03-26T14:10:03Z Forking - ./handler []
2020-03-26T14:10:03Z 2020/03/26 14:10:03 Started logging stderr from function.
2020-03-26T14:10:03Z 2020/03/26 14:10:03 Started logging stdout from function.
2020-03-26T14:10:03Z 2020/03/26 14:10:03 OperationalMode: http
2020-03-26T14:10:03Z 2020/03/26 14:10:03 Timeouts: read: 10s, write: 10s hard: 10s.
2020-03-26T14:10:03Z 2020/03/26 14:10:03 Listening on port: 8080
2020-03-26T14:10:03Z 2020/03/26 14:10:03 Metrics listening on port: 8081
2020-03-26T14:10:03Z 2020/03/26 14:10:03 Writing lock-file to: /tmp/.lock


虽然一切看起来不错,但是现在让我们尝试调用端点:

echo | faas-cli invoke todo -f todo.yml
2020-03-26T14:11:02Z 2020/03/26 14:11:02 POST / - 200 OK - ContentLength: 35


因此,现在我们已经建立了与数据库的成功连接,并且可以执行插入操作。我们怎么知道呢?因为它db.Ping ()返回错误,否则将引发恐慌:

err = db.Ping()
if err != nil {
   panic(err.Error())
}


单击链接以获取有关数据库/ sql软件包的更多详细信息

编写插入代码


该代码将新行插入表中,todo并使用特殊语法,其中该值不用引号引起来,而是由db.Query代码代替。在LAMP编程的“旧时代”中,导致许多系统不安全的常见错误是缺乏对输入数据的卫生性以及用户输入直接连接到SQL语句中。

想象有人输入描述;drop table todo不会很有趣。

因此,我们运行db.Query,然后通过使用SQL语句$1$2等等。对于每个值,然后我们就可以得到结果和/或错误。我们还必须关闭此结果,因此请为此使用defer。

func insert(description string) error {
       res, err := db.Query(`insert into todo (id, description, created_date) values (DEFAULT, $1, now());`,
               description)
       if err != nil {
               return err
       }
       defer res.Close()
       return nil
}


现在,将其连接到代码。

func Handle(w http.ResponseWriter, r *http.Request) {
       if r.Method == http.MethodPost && r.URL.Path == "/create" {
               defer r.Body.Close()
               body, _ := ioutil.ReadAll(r.Body)
               if err := insert(string(body)); err != nil {
                       http.Error(w, fmt.Sprintf("unable to insert todo: %s", err.Error()), http.StatusInternalServerError)
               }
       }
}


让我们部署并运行它。

echo | faas-cli invoke todo -f todo.yml
curl http://127.0.0.1:8080/function/todo/create --data "faas-cli build"
curl http://127.0.0.1:8080/function/todo/create --data "faas-cli push"
curl http://127.0.0.1:8080/function/todo/create --data "faas-cli deploy"


检查API日志:

faas-cli logs todo
2020-03-26T14:35:29Z 2020/03/26 14:35:29 POST /create - 200 OK - ContentLength: 0


使用pgsql检查表的内容:

export POSTGRES_PASSWORD=$(kubectl get secret --namespace default postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode)
kubectl run postgresql-client --rm --tty -i --restart='Never' --namespace default --image docker.io/bitnami/postgresql:11.6.0-debian-9-r0 --env="PGPASSWORD=$POSTGRES_PASSWORD" --command -- psql --host postgresql -U postgres -d postgres -p 5432
postgres=# select * from todo;
id |   description   |        created_date        | completed_date
----+-----------------+----------------------------+----------------
1 | faas-cli build  | 2020-03-26 14:36:03.367789 |
2 | faas-cli push   | 2020-03-26 14:36:03.389656 |
3 | faas-cli deploy | 2020-03-26 14:36:03.797881 |


恭喜,您现在有了一个TODO API,它可以通过curl或其他任何HTTP客户端接受传入的请求,并将它们写入数据库表。

记录请求


让我们创建一个新函数来查询表中的TODO记录:

func selectTodos() ([]Todo, error) {
   var error err
   var todos []Todo
   return todos, err
}


我们无法命名此选择方法,因为它是使用goroutines的保留关键字。

现在将方法连接到主处理程序:

} else if r.Method == http.MethodGet && r.URL.Path == "/list" {
   todos, err := selectTodos()
   if err != nil {
       http.Error(w, fmt.Sprintf("unable to get todos: %s", err.Error()), http.StatusInternalServerError)
   }
   out, _ := json.Marshal(todos)
   w.Header().Set("Content-Type", "application/json")
   w.Write(out)
}


现在,我们的数据模式中还有日期的其他字段,请更新Todo结构:

type Todo struct {
   ID int `json:"id"`
   Description   string `json:"description"`
   CreatedDate   *time.Time `json:"created_date"`
   CompletedDate *time.Time `json:"completed_date"`
}


现在,将selectTodos()请求代码添加到我们的方法中

func selectTodos() ([]Todo, error) {
       rows, getErr := db.Query(`select id, description, created_date, completed_date from todo;`)
   if getErr != nil {
       return []Todo{}, errors.Wrap(getErr, "unable to get from todo table")
   }
   todos := []Todo{}
   defer rows.Close()
   for rows.Next() {
       result := Todo{}
       scanErr := rows.Scan(&result.ID, &result.Description, &result.CreatedDate, &result.CompletedDate)
       if scanErr != nil {
           log.Println("scan err:", scanErr)
       }
       todos = append(todos, result)
   }
   return todos, nil
}


和以前一样,我们需要延迟关闭请求的行。使用rows.Scan方法将每个值插入到新结构中。在方法的最后,我们有一个Todo内容。

我们试试吧:

faas-cli up -f todo.yml --build-arg GO111MODULE=on
curl http://127.0.0.1:8080/function/todo/list


结果如下:

[
 {
   "id": 2,
   "description": "faas-cli build",
   "created_date": "2020-03-26T14:36:03.367789Z",
   "completed_date": null
 },
 {
   "id": 3,
   "description": "faas-cli push",
   "created_date": "2020-03-26T14:36:03.389656Z",
   "completed_date": null
 },
 {
   "id": 4,
   "description": "faas-cli deploy",
   "created_date": "2020-03-26T14:36:03.797881Z",
   "completed_date": null
 }
]


要删除这些值,我们可以通过添加来更新结构注释omitempty

CompletedDate *time.Time `json:"completed_date,omitempty"`


总结完成的工作


我们尚未完成,但是现在是停止并回顾我们到目前为止所取得成就的好时机。我们:

  • 已安装Go,Docker,kubectl和VSCode(IDE)
  • 在我们的本地计算机上部署Kubernetes
  • 使用arkade和helm3安装了Postgresql
  • 为Kubernetes应用程序开发人员安装了OpenFaaS和PLONK堆栈
  • 使用Go和golang-middlewareOpenFaaS模板创建了初始静态REST API
  • 在我们的TODO API中添加了“插入”功能
  • 向我们的TODO API添加了选择功能


到目前为止,我们创建的完整代码示例可在我的GitHub帐户上找到:alexellis / kubernetes-todo-go-app

然后,我们可以做更多的事情,例如:

  • 使用静态承载令牌添加身份验证
  • 使用静态HTML或React模板创建网页以呈现TODO列表并创建新元素。
  • 向API添加多用户支持


以及更多。我们还可以深入研究PLONK堆栈,并部署Grafana仪表板以开始监视我们的API,并查看Kubernetes仪表板或指标服务器(已安装arkade install正在使用多少资源

了解有关arkade的更多信息:https ://get-arkade.dev

参加OpenFaaS研讨会以了解有关上述内容的更多信息:https : //github.com/openfaas/workshop/

您可以通过以下网址订阅我的高级Insiders Updates新闻通讯
:/ /www.alexellis.io/
和我的推特Alex Ellis



了解有关该课程的更多信息



All Articles