Créer une API TODO pour Golang avec Kubernetes

Bonjour à tous! Avant le lancement du cours sur la plate-forme d'infrastructure basée sur Kubernetes, nous avons préparé une traduction d'un autre matériel intéressant.



Cet article est destiné aux nouveaux arrivants à Kubernetes, qui seront intéressés à comprendre un exemple pratique de la façon d'écrire une API Golang pour gérer une liste TODO, puis comment la déployer sur Kubernetes.

Chaque développeur aime une bonne liste TODO, non? Sinon, comment pourrions-nous nous organiser autrement?


Chaque développeur aime une bonne application TODO, non?

Nous allons commencer par passer en revue la liste des composants nécessaires, puis continuer à configurer Kubernetes, fournir la base de données Postgresql, puis installer un cadre d'application qui nous aidera à déployer facilement l'API Go dans Kubernetes, sans se concentrer sur les détails.

Nous allons créer deux points de terminaison dans l'API - l'un pour créer un nouvel enregistrement TODO, l'autre pour sélectionner tous les enregistrements TODO.

Tout le code de ce tutoriel est disponible sur GitHub.

La liste des composants nécessaires:


  • Docker installé localement

Kubernetes exécute du code dans les images de conteneur, vous devez donc installer Docker sur votre ordinateur.

Installez Docker: https://www.docker.com
Enregistrez un compte Docker Hub pour stocker vos images Docker: https://hub.docker.com/

  • Cluster Kubernetes


Vous pouvez choisir un cluster local ou distant, mais lequel est le meilleur? Les options simplifiées, telles que k3d, fonctionnent sur n'importe quel ordinateur sur lequel Docker peut s'exécuter, de sorte que l'exécution d'un cluster local ne nécessite plus beaucoup de RAM. Un cluster distant peut également être une solution très efficace, mais gardez à l'esprit que toutes vos images Docker devront être téléchargées et téléchargées pour chaque changement.

Installez k3d: https://github.com/rancher/k3d

k3d n'installera pas kubectl (il s'agit de l'interface CLI pour Kubernetes), alors installez-le séparément d'ici: https://kubernetes.io/docs/tasks/tools / install-kubectl

  • Golang


Vous devez également installer Golang avec l'IDE sur votre ordinateur. Go est gratuit et vous pouvez le télécharger pour MacOS, Windows ou Linux à partir du lien suivant: https://golang.org/dl

  • IDE


Je recommanderais d'utiliser Visual Studio Code, il est gratuit et dispose d'un ensemble de plugins pour Go que vous pouvez ajouter. Certains collègues préfèrent Goland de Jetbrains. Si vous êtes un programmeur Java, vous préféreriez probablement payer pour Goland, car cela vous rappellera leurs autres produits.

Installez VSCode ou Golang .

Créer un cluster


Vous devez installer tous les logiciels spécifiés dans la section précédente.

Créez un nouveau cluster à l'aide de k3d:


k3d create

Configurez kubectl pour qu'il pointe vers un nouveau cluster. N'oubliez pas que plusieurs clusters peuvent être utilisés à partir d'un même ordinateur, il est donc extrêmement important de pointer vers le bon:

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


Assurez-vous que le cluster possède au moins un nœud. Ici, vous pouvez voir que j'ai installé Kubernetes 1.17 - une version relativement nouvelle:

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


Nous allons stocker nos enregistrements TODO dans la table de base de données, nous devons donc maintenant l'installer. Postgresql est une base de données relationnelle populaire que nous pouvons installer dans un cluster à l'aide du graphique Helm.

Installer Arkade


arkade est un CLI Go, similaire à "brew" ou "apt-get", mais pour les applications Kubernetes. Il utilise un projet Helm, kubectl ou CLI pour installer un projet ou un produit dans votre cluster.

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


Installez maintenant Postgresql

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


Vous verrez également des informations sur la chaîne de connexion et comment démarrer la CLI Postgresql via l'image Docker à l'intérieur du cluster.

Vous pouvez obtenir ces informations à tout moment en utilisant arkade info postgresql.

Nous concevrons la disposition de la table

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


Exécutez arkade info postgresqlpour obtenir à nouveau les informations de connexion. Ça devrait ressembler a quelque chose comme ca:

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


Maintenant, vous avez à l'invite de commande:, postgres = #vous pouvez créer la table en la copiant à l'intérieur, puis exécutez \dtpour afficher la table:

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


Installer le cadre d'application


Tout comme les développeurs PHP ont accéléré leur flux de travail en utilisant les développeurs LAMP (Linux Apache + Mysql + PHP) et Rails en utilisant une pile pré-préparée, les développeurs Kubernetes peuvent utiliser des cadres d'application.
La pile PLONK signifie Prometheus, Linux, OpenFaaS, NATS et Kubernetes.
  • Prometheus fournit des mesures, une mise à l'échelle automatique et une observabilité pour tester la santé de votre système, lui permet de répondre aux poussées de la demande et de réduire les coûts en fournissant des mesures pour prendre des décisions sur la mise à l'échelle à zéro.
  • Linux, bien que n'étant pas la seule option pour exécuter des charges de travail dans Kubernetes, est standard et plus facile à utiliser.
  • Au départ, OpenFaaS fournissait des fonctions portables aux développeurs, mais cela fonctionne très bien lors du déploiement d'API et de microservices. Sa polyvalence signifie que tous les conteneurs Docker avec un serveur HTTP peuvent être déployés et gérés.
  • NATS est un projet CNCF populaire utilisé pour la messagerie et le pub / sub. Dans la pile PLONK, il offre la possibilité d'exécuter des demandes de manière asynchrone et pour la mise en file d'attente.
  • Kubernetes est la raison pour laquelle nous sommes ici. Il fournit une infrastructure évolutive, auto-réparatrice et déclarative. Et si vous n'avez plus besoin d'une partie de toute cette grandeur, son API est simplifiée avec OpenFaaS.


Installez la pile PLONK via Arkade:

arkade install openfaas


Lisez le message d'information et exécutez chaque commande:

  • Installez faas-cli.
  • Obtenez votre mot de passe.
  • Rediriger l'interface utilisateur de la passerelle OpenFaaS (via le port 8080)
  • Et connectez-vous au système via la CLI.


Comme précédemment, vous pouvez recevoir un message d'information à l'aide de info openfaas.

Déployez votre API First Go


Il existe plusieurs façons de créer une API Go à l'aide de PLONK. La première option consiste à utiliser le Dockerfile et à déterminer manuellement le port TCP, la vérification de l'état, le serveur HTTP, etc. Cela peut être fait en utilisant faas-cli new --lang dockerfile API_NAME, mais il existe un moyen plus simple et plus automatisé.

La deuxième façon consiste à utiliser les modèles intégrés proposés par le magasin de fonctions:

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


Puisque nous voulons créer une API de style HTTP traditionnelle, le modèle de middleware golang serait le plus approprié.

Au début du didacticiel, vous vous êtes inscrit avec votre compte Docker Hub pour stocker vos images Docker. Chaque charge de travail Kubernetes doit être intégrée dans une image Docker avant de pouvoir être déployée.

Tirez le motif spécial:

faas-cli template store pull golang-middleware


Utilisez Scaffold pour votre API avec golang-middleware et votre nom d'utilisateur dans le Docker Hub:

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


Vous verrez deux fichiers générés:

  • ./todo.yml - fournit un moyen de configurer, déployer et installer le modèle et le nom
  • ./todo/handler.go - ici vous écrivez le code et ajoutez tous les autres fichiers ou packages dont vous avez besoin


Faisons quelques modifications, puis développons le code.

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))
}


Si vous n'utilisez pas VScode et ses plugins pour éditer et formater le code, exécutez-le après chaque modification pour vous assurer que le fichier est correctement formaté.

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


Déployez maintenant le code - créez d'abord une nouvelle image, lancez-la dans le Docker Hub et déployez-la sur le cluster à l'aide de l'API OpenFaaS:

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


Autoriser les utilisateurs à créer de nouveaux enregistrements TODO


Permettons maintenant aux utilisateurs de créer de nouvelles entrées dans leur liste TODO. Vous devez d'abord ajouter le lien ou la «dépendance» pour Aller à la bibliothèque Postgresql.

Nous pouvons l'obtenir en utilisant les modules de vente ou Go qui ont été introduits dans Go 1.11 et installés par défaut dans Go 1.13.

Modifiez handler.goet ajoutez le module dont nous avons besoin pour accéder à Postgresql:

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


Pour que les modules Go détectent les dépendances, nous devons déclarer quelque chose dans le fichier, que nous utiliserons plus tard. Si ce n'est pas le cas, VSCode supprimera ces lignes lors de l'enregistrement.

Ajoutez ceci sous les importations dans le fichier

var db *sql.DB


Exécutez toujours ces commandes dans le tododossier où il se trouve handler.go, et non au niveau racine c todo.yml.

Initialisez le nouveau module Go:

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


Maintenant, mettez à jour le fichier en utilisant la bibliothèque 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


Tout ce qui se trouve à l'intérieur go.mod, copiez son contenu dansGO_REPLACE.txt

cat go.mod > GO_REPLACE.txt


Maintenant, assurons-nous que l'assembly fonctionne toujours avant d'ajouter le code d'insertion.

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


Vous remarquerez peut-être que nous transmettons maintenant --build-argun modèle d'utilisation des modules Go.

Lors de l'assemblage, vous verrez que les modules sont téléchargés au besoin sur 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


Configuration des secrets pour l'accès Postgresql


Nous pouvons créer un pool de connexions dans init (), une méthode qui ne s'exécutera qu'une seule fois au démarrage du programme.

// 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())
               }
       }
}


Comme vous l'avez remarqué, certaines informations sont extraites de ce qui est os.Getenvlu dans l'environnement. Je considérerais ces valeurs comme non confidentielles, elles sont définies dans le fichier todo.y.

Le reste, comme le mot de passe et l'hôte, qui sont confidentiels, sont conservés dans les secrets de Kubernetes .

Vous pouvez les créer à travers faas-cli secret createou à travers kubectl create secret generic -n openfaas-fn.

La chaîne sdk.ReadSecretvient de OpenFaaS CloudEND_LINK, avec l'importation suivante: github.com/openfaas/openfaas-cloud/sdk. Il lit un fichier secret sur le disque et renvoie une valeur ou une erreur.

Obtenez les valeurs secrètes de arkade info postgresql.

Maintenant, pour chaque mot de passe, procédez comme suit:

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


Vérifiez les secrets de disponibilité et de conformité:

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


Modifiez notre fichier YAML et ajoutez ce qui suit:

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


Ensuite, mettez à jour les modules Go et réexécutez la génération:

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


L'assembly a fonctionné comme prévu, exécutons-le donc faas-cliavec les mêmes arguments pour démarrer et déployer l'image. Si les informations d'identification et la configuration SQL sont correctes, nous ne verrons pas d'erreurs dans les journaux, cependant, si elles sont incorrectes, nous obtiendrons le code de panique dans init ().

Vérifiez les journaux:

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


Alors que tout semble bien, essayons maintenant d'appeler le point de terminaison:

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


Ainsi, nous avons maintenant établi une connexion réussie à la base de données et nous pouvons effectuer l'insertion. Comment savons-nous cela? Parce qu'il db.Ping ()renvoie une erreur, sinon il aurait jeté une panique:

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


Suivez le lien pour plus de détails sur le package base de données / sql .

Écrire le code d'insertion


Ce code insère une nouvelle ligne dans la table todoet utilise une syntaxe spéciale dans laquelle la valeur n'est pas placée entre guillemets, mais est remplacée à la place par le code db.Query. Dans "l'ancien temps" de la programmation LAMP, une erreur courante qui a rendu de nombreux systèmes dangereux: le manque de nettoyage des données d'entrée et la concaténation des entrées utilisateur directement dans l'instruction SQL.

Imaginez quelqu'un entrer une description; drop table todoCe ne serait pas très amusant.

Par conséquent, nous exécutons db.Query, puis passons l'instruction SQL à l'aide de $1, $2etc. pour chaque valeur, puis nous pouvons obtenir le résultat et / ou l'erreur. Nous devons également clôturer ce résultat, alors utilisez le report pour cela.

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
}


Maintenant, connectons cela au code.

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)
               }
       }
}


Déployons-le et exécutons-le.

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"


Vérifiez les journaux de l'API:

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


Vérifiez le contenu du tableau à l'aide de 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 |


Félicitations, vous disposez désormais d'une API TODO qui peut accepter les demandes entrantes via curlou tout autre client HTTP et les écrire dans la table de base de données.

Demande d'enregistrement


Créons une nouvelle fonction pour interroger les enregistrements TODO à partir d'une table:

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


Nous ne pouvons pas nommer cette méthode de sélection car il s'agit d'un mot clé réservé pour travailler avec des goroutines.

Connectez maintenant la méthode au gestionnaire principal:

} 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)
}


Maintenant qu'il y a des champs supplémentaires pour les dates dans notre schéma de données, mettez à jour la structure Todo:

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


Ajoutons maintenant le selectTodos()code de requête à notre méthode :

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
}


Comme précédemment, nous devons retarder la fermeture des lignes de la demande. Chaque valeur est insérée dans la nouvelle structure à l'aide de la méthode row.Scan. À la fin de la méthode, nous avons un morceau de contenu Todo.

Essayons:

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


Voici le résultat:

[
 {
   "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
 }
]


Pour supprimer les valeurs, nous pouvons mettre à jour les annotations de structure en ajoutant omitempty:

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


Pour résumer le travail accompli


Nous n'avons pas encore fini, mais c'est le bon moment pour nous arrêter et revoir ce que nous avons accompli jusqu'à présent. Nous:

  • Go, Docker, kubectl et VSCode (IDE) installés
  • Déployer Kubernetes sur notre ordinateur local
  • Postgresql installé à l'aide d'Arkade et de Helm3
  • OpenFaaS installé et la pile PLONK pour les développeurs d'applications Kubernetes
  • Création de l'API REST statique initiale à l'aide de Go et golang-middlewaredu modèle OpenFaaS
  • Ajout de la fonctionnalité «insérer» à notre API TODO
  • Ajout d'une fonctionnalité de sélection à notre API TODO


L'exemple de code complet que nous avons créé jusqu'à présent est disponible sur mon compte GitHub: alexellis / kubernetes-todo-go-app

Ensuite, nous pouvons faire beaucoup plus, par exemple:

  • Ajout d'authentification à l'aide d'un jeton de support statique
  • Créez une page Web à l'aide d'un modèle HTML statique ou React pour afficher une liste TODO et créer de nouveaux éléments.
  • Ajout de la prise en charge multi-utilisateurs à l'API


Et beaucoup plus. Nous pourrions également nous plonger dans la pile PLONK et déployer le tableau de bord Grafana pour commencer à surveiller notre API et comprendre combien de ressources sont utilisées avec le tableau de bord Kubernetes ou le serveur métrique (installé à l'aide arkade install).

En savoir plus sur arkade: https://get-arkade.dev

Assistez à l'atelier OpenFaaS pour en savoir plus sur ce qui précède: https://github.com/openfaas/workshop/

Vous pouvez vous abonner à ma newsletter premium Insiders Updates sur
https: / /www.alexellis.io/
et sur mon twitter Alex Ellis



En savoir plus sur le cours



All Articles