Slide 1

Slide 1 text

Подход к Graceful Shutdown в Go И почему это не работает в Kubernetes Артемий Рябинков Avito

Slide 2

Slide 2 text

Что такое Graceful Shutdown и зачем он нужен? ● Завершаемся предсказуемо ● Не теряем данные

Slide 3

Slide 3 text

https://12factor.net/disposability

Slide 4

Slide 4 text

Бесшовная выкатка ● Выкатываем новую версию сервиса ● Переключаем на неё трафик ● Отключаем старую версию сервиса

Slide 5

Slide 5 text

Время ответа Кол-во ошибок

Slide 6

Slide 6 text

Идеально: время ответа не скачет и ошибок нет! ms

Slide 7

Slide 7 text

Как программе узнать о необходимости завершиться? Сигналы! SIGTERM, SIGINT, SIGQUIT, SIGKILL

Slide 8

Slide 8 text

План Пишем на PHP ● Слушаем сигнал ● Просим потоки завершиться ● Ждем завершения всех потоков ● Выходим из процесса

Slide 9

Slide 9 text

main SIGTERM

Slide 10

Slide 10 text

Ловим сигнал OS // Register signal handler sigc := make(chan os.Signal, 1) signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) // Wait for signal <-sigc

Slide 11

Slide 11 text

Завершаем Goroutine

Slide 12

Slide 12 text

func main() { done := make(chan struct{}) go listen(done) // .. SIGTERM close(done) // .. wait for listen }

Slide 13

Slide 13 text

func main() { ctx, cancel := context.WithCancel(...) go listen(ctx) // .. SIGTERM cancel() // .. wait for listen }

Slide 14

Slide 14 text

func listen(ctx context.Context) { for { select { case <-ctx.Done(): return default: // .. do hard work } } }

Slide 15

Slide 15 text

Используем context.Context для завершения

Slide 16

Slide 16 text

Ждем завершения Goroutine

Slide 17

Slide 17 text

func main() { done := make(chan struct{}) go listen(done) <-done // waiting }

Slide 18

Slide 18 text

func listen(done chan struct{}) { // .. do hard work done <- struct{}{} }

Slide 19

Slide 19 text

Проблемы канала ● Boilerplate код в Listen ● Нет уверенности в том, что функция действительно завершилась

Slide 20

Slide 20 text

Используем sync.WaitGroup для ожидания

Slide 21

Slide 21 text

func main() { var wg sync.WaitGroup wg.Add(1) go func() { listen() // blocking wg.Done() }() wg.Wait() // wait them all }

Slide 22

Slide 22 text

context.Context + sync.WaitGroup

Slide 23

Slide 23 text

func main() { ctx, cancel := context.WithCancel(...) var wg sync.WaitGroup wg.Add(1) go func() { listen(ctx) wg.Done() }() // .. SIGTERM cancel() wg.Wait() }

Slide 24

Slide 24 text

golang.org/x/sync/errgroup

Slide 25

Slide 25 text

func main() { ctx, cancel := context.WithCancel(...) var g errgroup.Group g.Go(func() error { return listen(ctx) } // .. SIGTERM cancel() if err := g.Wait(); err != nil { // Handle error } }

Slide 26

Slide 26 text

errgroup.WithContext

Slide 27

Slide 27 text

Server CRON SIG Trap Consumer

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

Как на самом деле происходит выкатка в Kubernetes?

Slide 32

Slide 32 text

S1 S2 Kubelet Kube-Proxy Kubelet Kube-Proxy Master Kubelet Kubelet Kube-Proxy Kube-Proxy

Slide 33

Slide 33 text

Sleep 5 @see https://github.com/kubernetes/contrib/issues/1140

Slide 34

Slide 34 text

containers: - name: mycontainer # .. lifecycle: preStop: exec: command: ["sleep","5"]

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

Спасибо! Рябинков Артемий github.com/furdarius [email protected]