Olá a todos! Antes do lançamento do curso da Plataforma de Infraestrutura baseado em Kubernetes, preparamos uma tradução de outro material interessante.
Este artigo é destinado a recém-chegados ao Kubernetes, interessados em entender um exemplo prático de como escrever uma API Golang para gerenciar uma lista TODO e, em seguida, como implantá-la no Kubernetes.Todo desenvolvedor adora uma boa lista de tarefas, certo? De que outra forma poderíamos nos organizar de outra maneira?
Todo desenvolvedor adora um bom aplicativo TODO, certo?Começaremos analisando a lista de componentes necessários, depois configurando o Kubernetes, fornecendo o banco de dados Postgresql e instalando uma estrutura de aplicativos que nos ajudará a implantar facilmente a API Go no Kubernetes, sem focar nos detalhes.Criaremos dois pontos de extremidade na API - um para criar um novo registro TODO, o outro para selecionar todos os registros TODO.Todo o código deste tutorial está disponível no GitHub.A lista de componentes necessários:
- Docker instalado localmente
O Kubernetes executa o código nas imagens do contêiner, portanto, você precisa instalar o Docker no seu computador.Instale o Docker: https://www.docker.comRegistre uma conta do Docker Hub para armazenar suas imagens do Docker: https://hub.docker.com/Você pode escolher um cluster local ou remoto, mas qual é o melhor? Opções simplificadas, como o k3d, funcionam em qualquer computador em que o Docker possa ser executado, portanto, a execução de um cluster local não exige mais muita RAM. Um cluster remoto também pode ser uma solução muito eficiente, mas lembre-se de que todas as suas imagens do Docker precisarão ser carregadas e carregadas para cada alteração.Instale o k3d: https://github.com/rancher/k3d Ok3d não instalará o kubectl (esta é a CLI do Kubernetes); portanto, instale-o separadamente daqui: https://kubernetes.io/docs/tasks/tools / install-kubectlVocê também precisa instalar o Golang com o IDE no seu computador. O Go é gratuito e você pode baixá-lo para MacOS, Windows ou Linux no seguinte link: https://golang.org/dlEu recomendaria usar o Visual Studio Code, é gratuito e possui um conjunto de plugins para Go que você pode adicionar. Alguns colegas preferem Goland da Jetbrains. Se você é um programador Java, provavelmente prefere pagar pelo Goland, pois ele lembrará seus outros produtos.Instale o VSCode ou Golang .Crie um cluster
Você precisa instalar todo o software especificado na seção anterior.Crie um novo cluster usando o k3d:
k3d create
Configure o kubectl para que aponte para um novo cluster. Lembre-se de que vários clusters podem ser usados em um computador, por isso é extremamente importante apontar para o correto:export KUBECONFIG="$(k3d get-kubeconfig --name='k3s-default')"
Verifique se o cluster possui pelo menos um nó. Aqui você pode ver que eu tenho o Kubernetes 1.17 instalado - uma versão relativamente nova:kubectl get node
NAME STATUS ROLES AGE VERSION
k3d-k3s-default-server Ready master 48s v1.17.0+k3s.1
Vamos armazenar nossos registros TODO na tabela do banco de dados, agora precisamos instalá-lo. O Postgresql é um banco de dados relacional popular que podemos instalar em um cluster usando o gráfico Helm.Instalar o Arkade
O arkade é um CLI Go, semelhante ao "brew" ou "apt-get", mas para aplicativos Kubernetes. Ele usa um projeto Helm, kubectl ou CLI para instalar um projeto ou produto em seu cluster.curl -sLS https:
Agora instale o Postgresqlarkade install postgresql
===================================================================== = PostgreSQL has been installed. =
=====================================================================
Você também verá informações sobre a cadeia de conexão e como iniciar a CLI do Postgresql através da imagem do Docker dentro do cluster.Você pode obter essas informações a qualquer momento usando arkade info postgresql
.Vamos projetar o layout da tabelaCREATE TABLE todo (
id INT GENERATED ALWAYS AS IDENTITY,
description text NOT NULL,
created_date timestamp NOT NULL,
completed_date timestamp NOT NULL
);
Execute arkade info postgresql
para obter informações de conexão novamente. Deve ser algo como isto: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
Agora você tem no prompt de comando :, postgres = #
você pode criar a tabela, copiando-a para dentro e, em seguida, execute \dt
para mostrar a tabela:postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | todo | table | postgres
(1 row)
Instale a estrutura do aplicativo
Assim como os desenvolvedores de PHP aceleraram seu fluxo de trabalho usando os desenvolvedores LAMP (Linux Apache + Mysql + PHP) e Rails usando uma pilha pré-preparada, os desenvolvedores do Kubernetes podem usar estruturas de aplicativos.A pilha PLONK significa Prometheus, Linux, OpenFaaS, NATS e Kubernetes.- O Prometheus fornece métricas, dimensionamento automático e observabilidade para testar a integridade do seu sistema, permite responder a picos de demanda e reduzir custos, fornecendo métricas para tomar decisões sobre o dimensionamento para zero.
- O Linux, embora não seja a única opção para executar cargas de trabalho no Kubernetes, é padrão e mais fácil de usar.
- Inicialmente, o OpenFaaS forneceu funções portáteis aos desenvolvedores, mas funciona muito bem ao implantar APIs e microsserviços. Sua versatilidade significa que qualquer contêiner Docker com um servidor HTTP pode ser implantado e gerenciado.
- NATS é um projeto CNCF popular usado para mensagens e pub / sub. Na pilha PLONK, fornece a capacidade de executar solicitações de forma assíncrona e para enfileiramento.
- Kubernetes é a razão de estarmos aqui. Ele fornece uma infraestrutura escalonável, auto-reparável e declarativa. E se você não precisar mais de toda essa grandeza, sua API será simplificada com o OpenFaaS.
Instale a pilha PLONK via arkade:arkade install openfaas
Leia a mensagem informativa e execute cada comando:- Instale o faas-cli.
- Obtenha sua senha.
- Redirecione a interface do usuário do gateway OpenFaaS (através da porta 8080)
- E faça login no sistema através da CLI.
Como antes, você pode receber uma mensagem informativa usando info openfaas
.Implante sua API First Go
Existem várias maneiras de criar uma API Go usando o PLONK. A primeira opção é usar o Dockerfile e determinar manualmente a porta TCP, a verificação de integridade, o servidor HTTP e assim por diante. Isso pode ser feito usando faas-cli new --lang dockerfile API_NAME
, mas existe uma maneira mais simples e automatizada.A segunda maneira é usar os modelos internos oferecidos pelo 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
Como queremos criar uma API tradicional no estilo HTTP, o modelo golang-middleware seria o mais apropriado.No início do tutorial, você se registrou na sua conta do Docker Hub para armazenar suas imagens do Docker. Cada carga de trabalho do Kubernetes deve ser incorporada em uma imagem do Docker antes de poder ser implantada.Puxe o padrão especial:faas-cli template store pull golang-middleware
Use o Scaffold para sua API com golang-middleware e seu nome de usuário no Docker Hub:export PREFIX=alexellis2
export LANG=golang-middleware
export API_NAME=todo
faas-cli new --lang $LANG --prefix $PREFIX $API_NAME
Você verá dois arquivos gerados:./todo.yml
- fornece uma maneira de configurar, implantar e instalar o modelo e o nome./todo/handler.go
- aqui você escreve o código e adiciona outros arquivos ou pacotes necessários
Vamos editar e expandir o código.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))
}
Se você não usar o VScode e seus plug-ins para editar e formatar o código, execute-o após cada alteração para garantir que o arquivo esteja formatado corretamente.gofmt -w -s ./todo/handler.go
Agora implante o código - primeiro crie uma nova imagem, inicie-a no Docker Hub e implante-a no cluster usando a API OpenFaaS:Invoke your endpoint when ready:
curl http:
{
"description": "Run faas-cli up"
}
Permitir que os usuários criem novos registros TODO
Agora vamos permitir que os usuários criem novas entradas na lista TODO. Primeiro, você precisa adicionar o link ou "dependência" para Ir para a biblioteca do Postgresql.Podemos obtê-lo usando os módulos de venda automática ou Go que foram introduzidos no Go 1.11 e instalados por padrão no Go 1.13.Edite handler.go
e adicione o módulo que precisamos para acessar o Postgresql:import (
"database/sql"
_ "github.com/lib/pq"
...
Para que os módulos Go detectem dependências, precisamos declarar algo dentro do arquivo, que usaremos posteriormente. Caso contrário, o VSCode excluirá essas linhas quando salvas.Adicione isso sob as importações no arquivovar db *sql.DB
Sempre execute esses comandos na todo
pasta em que está localizado handler.go
e não no nível raiz c todo.yml
.Inicialize o novo módulo Go:cd todo/
ls
handler.go
export GO111MODULE=on
go mod init
Agora atualize o arquivo usando a biblioteca 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
O que estiver dentro go.mod
, copie seu conteúdo paraGO_REPLACE.txt
cat go.mod > GO_REPLACE.txt
Agora, verifique se o assembly ainda funciona antes de adicionar o código de inserção.faas-cli build -f todo.yml --build-arg GO111MODULE=on
Você pode perceber que agora passamos --build-arg
a relatar um padrão para usar os módulos Go.Durante a montagem, você verá que os módulos são baixados conforme necessário da 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
Configurando segredos para acesso ao Postgresql
Podemos criar um pool de conexões init ()
, um método que será executado apenas uma vez quando o programa for iniciado.
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())
}
}
}
Como você notou, algumas informações são extraídas do que é os.Getenv
lido no ambiente. Eu consideraria esses valores não confidenciais, eles estão definidos no arquivo todo.y
.O restante, como a senha e o host, que são confidenciais, são mantidos em segredo do Kubernetes .Você pode criá-los completamente faas-cli secret create
ou completamente kubectl create secret generic -n openfaas-fn
.A string sdk.ReadSecret
vem de OpenFaaS Nuvem SDK, com a seguinte importação: github.com/openfaas/openfaas-cloud/sdk
. Ele lê um arquivo secreto do disco e retorna um valor ou erro.Obtenha os valores secretos de arkade info postgresql
.Agora, para cada senha, faça o seguinte: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
Verifique os segredos quanto à disponibilidade e conformidade: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
Edite nosso arquivo YAML e adicione o seguinte:secrets:
- host
- password
- username
environment:
postgres_db: postgres
postgres_sslmode: "disable"
postgres_port: 5432
Em seguida, atualize os módulos Go e execute a compilação novamente: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
A montagem funcionou como esperado, então vamos executá-la faas-cli
com os mesmos argumentos para iniciar e implantar a imagem. Se as credenciais e a configuração do SQL estiverem corretas, não veremos erros nos logs; no entanto, se estiverem incorretos, obteremos o código de pânico em init ().Verifique os logs: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
Enquanto tudo parece bom, agora vamos tentar chamar o terminal:echo | faas-cli invoke todo -f todo.yml
2020-03-26T14:11:02Z 2020/03/26 14:11:02 POST / - 200 OK - ContentLength: 35
Portanto, agora estabelecemos uma conexão bem-sucedida ao banco de dados e podemos executar a inserção. Como sabemos disso? Como db.Ping ()
retorna um erro, caso contrário, isso causaria pânico:err = db.Ping()
if err != nil {
panic(err.Error())
}
Siga o link para obter mais detalhes sobre o pacote database / sql .Escrevendo o código de inserção
Esse código insere uma nova linha na tabela todo
e usa sintaxe especial na qual o valor não é colocado entre aspas, mas é substituído pelo código db.Query. Nos "velhos tempos" da programação LAMP, um erro comum que levou muitos sistemas a se tornarem inseguros foi a falta de saneamento dos dados de entrada e a concatenação da entrada do usuário diretamente na instrução SQL.Imagine alguém digitando uma descrição; drop table todo
Não seria muito divertido.Portanto, corremos db.Query
, em seguida, passar a instrução SQL usando $1
, $2
etc. para cada valor, e então nós podemos obter o resultado e / ou de erro. Também devemos fechar esse resultado, portanto, use adiar para isso.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
}
Agora vamos conectar isso ao código.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)
}
}
}
Vamos implantá-lo e executá-lo.echo | faas-cli invoke todo -f todo.yml
curl http:
curl http:
curl http:
Verifique os logs da API:faas-cli logs todo
2020-03-26T14:35:29Z 2020/03/26 14:35:29 POST /create - 200 OK - ContentLength: 0
Verifique o conteúdo da tabela usando o pgsql:export POSTGRES_PASSWORD=$(kubectl get secret
kubectl run postgresql-client
postgres=
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 |
Parabéns, agora você tem uma API TODO que pode aceitar solicitações recebidas por meio de curl
qualquer outro cliente HTTP e gravá-las na tabela do banco de dados.Solicitação de Registro
Vamos criar uma nova função para consultar registros TODO de uma tabela:func selectTodos() ([]Todo, error) {
var error err
var todos []Todo
return todos, err
}
Não podemos nomear esse método de seleção porque é uma palavra-chave reservada para trabalhar com goroutines.Agora conecte o método ao manipulador 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)
}
Agora que existem campos adicionais para datas em nosso esquema de dados, atualize a estrutura Todo:type Todo struct {
ID int `json:"id"`
Description string `json:"description"`
CreatedDate *time.Time `json:"created_date"`
CompletedDate *time.Time `json:"completed_date"`
}
Agora vamos adicionar o selectTodos()
código de solicitação ao nosso método :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
}
Como antes, precisamos atrasar o fechamento das linhas da solicitação. Cada valor é inserido na nova estrutura usando o método lines.Scan. No final do método, temos uma parte do conteúdo Todo.Vamos tentar:faas-cli up -f todo.yml
curl http://127.0.0.1:8080/function/todo/list
Aqui está o resultado:[
{
"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
}
]
Para remover os valores, podemos atualizar as anotações da estrutura adicionando omitempty
:CompletedDate *time.Time `json:"completed_date,omitempty"`
Para resumir o trabalho realizado
Ainda não terminamos, mas este é um bom momento para parar e revisar o que alcançamos até agora. Nós:- Go, Docker, kubectl e VSCode (IDE) instalados
- Implantou o Kubernetes em nosso computador local
- Postgresql instalado usando arkade e helm3
- OpenFaaS instalado e a pilha PLONK para desenvolvedores de aplicativos Kubernetes
- Criou a API REST estática inicial usando Go e
golang-middleware
o modelo OpenFaaS - Adicionada funcionalidade "inserir" à nossa API TODO
- Adicionada funcionalidade de seleção à nossa API TODO
O exemplo de código completo que criamos até agora está disponível na minha conta do GitHub: alexellis / kubernetes-todo-go-appEntão, podemos fazer muito mais, por exemplo:- Adicionando autenticação usando um token de portador estático
- Crie uma página da Web usando um HTML estático ou um modelo React para renderizar uma lista TODO e criar novos elementos.
- Adicionando suporte multiusuário à API
E muito mais. Também podemos nos aprofundar na pilha PLONK e implantar o painel Grafana para começar a monitorar nossa API e entender quantos recursos estão sendo usados com o painel ou servidor métrico Kubernetes (instalado usando arkade install
).Saiba mais sobre o arkade: https://get-arkade.devParticipe do workshop do OpenFaaS para saber mais sobre o acima: https://github.com/openfaas/workshop/Você pode se inscrever no meu boletim informativo premium de atualizações de insiders emhttps: / /www.alexellis.io/e no meu twitter Alex Ellis
Saiba mais sobre o curso