goroutines em containers
como não ter seu processamento interrompido
bianca rosa staff platform engineer @ homeaglow prev: lumos, redhat & stone
tudo é lindo quando criamos nossas primeiras apps em Go...
"opa que legal dá pra fazer coisa em background, mega simples, gostei....!!!!!"
package main
import (
"fmt"
"time"
)
const (
N = 10
Seconds = 10
)
func main() {
fmt.Println("Hello world")
for i := 0; i < N; i++ {
fmt.Printf("%d of %d: Scheduling go routine\n", i, N)
go func(i int) {
fmt.Printf("%d of %d: A slow running goroutine started....\n", i, N)
time.Sleep(Seconds * time.Second)
fmt.Printf("%d of %d: A slow running goroutine finished....\n", i, N)
}(i)
}
fmt.Println("Everything has shut down, goodbye")
}
não tem o que dar errado, né?
Program execution
(...)
Program execution begins by initializing the main package and then invoking the function main. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete.
molezinha então
package main
import (
"fmt"
"time"
)
const (
N = 10
Seconds = 10
)
func main() {
fmt.Println("Hello world")
// criacao das goroutines
time.Sleep(Seconds * time.Second * 2)
fmt.Println("Everything has shut down, goodbye")
}
funciona mazomenos né?
mas só porque a gente sabe o quanto esse programa vai durar
sync.WaitGroup |
(...)
"To wait for multiple goroutines to finish, we can use a wait group."
var wg sync.WaitGroup
func main() {
fmt.Println("Hello world")
for i := 0; i < N; i++ {
fmt.Printf("%d of %d: Scheduling go routine\n", i, N)
wg.Add(1)
go func(i int) {
fmt.Printf("%d of %d: A slow running goroutine started....\n", i, N)
time.Sleep(Seconds * time.Second)
fmt.Printf("%d of %d: A slow running goroutine finished....\n", i, N)
wg.Done()
}(i)
}
wg.Wait()
fmt.Println("Everything has shut down, goodbye")
}
k8s: termination grace period
apiVersion: apps/v1
kind: Deployment
metadata:
name: shutting-down-gracefully
labels:
app: shutting-down-gracefully
spec:
replicas: 1
selector:
matchLabels:
app: shutting-down-gracefully
template:
metadata:
labels:
app: shutting-down-gracefully
spec:
terminationGracePeriodSeconds: 60
containers:
- name: shutting-down-gracefully
image: docker.io/biancarosa/shutting-down-gracefully:v2.0.0
amazon ecs
[
{
"name": "string",
"image": "string",
"repositoryCredentials": {
"credentialsParameter": "string"
},
"cpu": integer,
"memory": integer,
"memoryReservation": integer,
...
"startTimeout": integer,
"stopTimeout": integer,
...
}
]
excelente, é isso
mas no mundo real, sua aplicação (provavelmente) não é um script que roda do inicio ao fim

e aí, o que você faz?
espera terminar?
em um mundo aonde lead time importa?
ela provavelmente roda recebendo requisições, consumindo mensagens etc
6h não deveria acontecer mas a vida não é o toddynho que a gente toma (ou gostaria de tomar) todo dia de manhã
e como resolve?
Sinais do Sistema Operacional
Containers recebem sinais do SO quando estão sendo encerrados
- SIGTERM - Avisa que o processo será encerrado
- SIGKILL - Mata o processo imediatamente
- SIGINT - Interrupções (ex: keyboard interrupt)
signal.Notify
Conseguimos "escutar" esse sinal do SO através de uma função da std lib do Go
A channel provides a mechanism for concurrently executing functions to communicate by sending and receiving values of a specified element type."
channels
então quando seu SO envia esse sinal, o seu programa recebe um valor nesse channel
package main
// imports, vars...
func main() {
// criacao das goroutines
gracefulStop := make(chan struct{})
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, syscall.SIGINT, syscall.SIGTERM)
<-sigint
wg.Wait()
fmt.Println("Shutting down gracefully...")
close(gracefulStop)
}()
<-gracefulStop
fmt.Println("Everything has shut down, goodbye")
}
gracefulStop := make(chan struct{})
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, syscall.SIGINT, syscall.SIGTERM)
<-sigint
// stop consuming messages
// stop http server from receiving reqs
wg.Wait() // or do something else
fmt.Println("Shutting down gracefully...")
close(gracefulStop)
}()
<-gracefulStop
fmt.Println("Everything has shut down, goodbye")
Estratégias de Persistência
quando o tempo de graceful shutdown não é suficiente
(ou seja, você não pode simplesmente esperar as tasks)
simples > complexo
qual é o mínimo que você precisa fazer?
uma das formas de garantir integridade é salvar o estado

existem várias outras formas
só cuidado com over-engineering
perguntas a se fazer
-
qual o tempo de processamento das suas goroutines?
-
custo vs beneficio pra colocar kafka / rabbitmq / sqs / similar
-
e se você fizer isso, você consegue se livrar de persistir estado?
-
e se você fizer isso, você consegue se livrar de persistir estado?
-
suas goroutines executam tasks que podem ser reprocessadas?
-
se não, quais são as unidades atômicas da sua goroutine?
levar isso em consideração na hora de "reprocessar"
-
se não, quais são as unidades atômicas da sua goroutine?
Recursos
-
Slides originais (DevConf.CZ 2022)
Obrigada!
aprendago.com.br (lista colaborativa) backend engineering adventures
go essentials & descomplicando o go: linuxtips.io
goroutines em containers
By Bianca Rosa
goroutines em containers
- 460