Quantcast
Channel: 小惡魔 – 電腦技術 – 工作筆記 – AppleBOY
Viewing all articles
Browse latest Browse all 325

[Go 教學] graceful shutdown 搭配 docker-compose 實現 rolling update

$
0
0

golang logo

上一篇作者有提到『什麼是 graceful shutdown?』,本篇透過 docker-compose 方式來驗證 Go 語言的 graceful shutdown 是否可以正常運作。除了驗證之外,單機版 Docker 本身就可以設定 scale 容器數量,那這時候又該如何搭配 graceful shutdown 來實現 rolling update 呢?相信大家對於 rolling update 並不陌生,現在的 kubernetes 已經有實現這個功能,用簡單的指令就可以達到此需求,但是對於沒有在用 k8s 架構的開發者,可能網站也不大,那該如何透過單機本的 docker 來實現呢?底下先來看看為什麼會出現這樣的需求。

假設您有一個 App 服務,需要在單機版上面透過 docker-compose 同時啟動兩個容器,可以透過底下指令一次完成:

docker-compose up -d --scale app=2

其中 app 就是在 YAML 裡面的服務名稱。這時候可以看到背景就跑了兩個容器,接著要升級 App 服務,您會發現在下一次上述指令,可以看到 docker 會先把兩個容器先停止,但是容器被停止前會透過 graceful shutdown 確認背景的服務或工作需要完成結束,才可以正確停止容器並且移除,最後再啟動新的 App 容器。這時候你會發現 App 服務被終止了幾分鐘時間完全無法運作。底下來介紹該如何解決此問題,以及驗證 graceful shutdown 是否可以正常運作

教學影片

如果對於課程內容有興趣,可以參考底下課程。

graceful shutdown 範例

先簡單寫個 Go 範例:

package main

import (
    "context"
    "flag"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

var (
    listenAddr string
)

func main() {
    flag.StringVar(&listenAddr, "listen-addr", ":8080", "server listen address")
    flag.Parse()

    logger := log.New(os.Stdout, "http: ", log.LstdFlags)

    router := http.NewServeMux() // here you could also go with third party packages to create a router
    // Register your routes
    router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(15 * time.Second)
        w.WriteHeader(http.StatusOK)
    })

    router.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(10 * time.Second)
        w.WriteHeader(http.StatusOK)
    })

    server := &http.Server{
        Addr:         listenAddr,
        Handler:      router,
        ErrorLog:     logger,
        ReadTimeout:  30 * time.Second,
        WriteTimeout: 30 * time.Second,
        IdleTimeout:  30 * time.Second,
    }

    done := make(chan bool, 1)
    quit := make(chan os.Signal, 1)

    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        <-quit
        logger.Println("Server is shutting down...")

        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()

        if err := server.Shutdown(ctx); err != nil {
            logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
        }
        close(done)
    }()

    logger.Println("Server is ready to handle requests at", listenAddr)
    if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        logger.Fatalf("Could not listen on %s: %v\n", listenAddr, err)
    }

    <-done
    logger.Println("Server stopped")
}

上面程式可以知道,直接打 / 就會等待 15 秒後才能拿到回應

curl -v -H Host:app.docker.localhost http://127.0.0.1:8088

準備 docker 環境

準備 dockerfile

# build stage
FROM golang:alpine AS build-env
ADD . /src
RUN cd /src && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app

# final stage
FROM centurylink/ca-certs
COPY --from=build-env /src/app /

EXPOSE 8080

ENTRYPOINT ["/app"]

準備 docker-compose.yml,使用 Traefik v2 版本來做 Load balancer。

version: '3'

services:
  app:
    image: go-training/app
    restart: always
    logging:
      options:
        max-size: "100k"
        max-file: "3"
    labels:
      - "traefik.http.routers.app.rule=Host(`app.docker.localhost`)"

  reverse-proxy:
    # The official v2.0 Traefik docker image
    image: traefik:v2.0
    # Enables the web UI and tells Traefik to listen to docker
    command: --api.insecure=true --providers.docker
    ports:
      # The HTTP port
      - "8088:80"
      # The Web UI (enabled by --api.insecure=true)
      - "8080:8080"
    volumes:
      # So that Traefik can listen to the Docker events
      - /var/run/docker.sock:/var/run/docker.sock

可以看到 8088 port 會是入口,app.docker.localhost 會是 app 網域名稱。

驗證 graceful shutdown

啟動全部服務,App 及 Traefik 都有被正式啟動

docker-compose up -d --scale app=2

接下來先修改原本的 Go 範例,在編譯一次把 Image 先產生好。另外開兩個 console 頁面直接下

curl -v -H Host:app.docker.localhost http://127.0.0.1:8088

會發現 curl 會等待 15 秒才能拿到回應,這時候直接下

docker-compose up -d --scale app=2

就可以看到

app_2   | http: 2020/02/08 14:06:20 Server is shutting down... 
app_2   | http: 2020/02/08 14:06:20 Server stopped
app_1   | http: 2020/02/08 14:06:20 Server is shutting down...
app_1   | http: 2020/02/08 14:06:20 Server stopped

這代表 graceful shutdown 可以正常運作,確保 app 連線及後續處理的動作可以正常被執行。

用 docker-compose 執行 rolling update

從上面可以看到,當執行了

docker-compose up -d --scale app=2

docker 會把目前的容器都全部停止,假設這時候都有重要的工作需要繼續執行,但是 graceful shutdown 已經將連接埠停止,造成使用者已經無法連線,這問題該如何解決呢?其實不難,只需要修正幾個指令就可以做到。由於 docker-compose up -d 會先將所有容器先停止,造成無法連線,這時候需要使用 --no-recreate flag 來避免這問題

docker-compose up -d --scale app=3 --no-recreate

將數量 + 1 的意思就是先啟動一個新的容器用來接受新的連線,接著將舊的容器移除:

docker stop -t 30 \
  $(docker ps --format "table {{.ID}}  {{.Names}}  {{.CreatedAt}}" | \
  grep app | \
  sort -k2 | \
  awk -F  "  " '{print $1}' | head -2)

其中 -t 30 一定要設定,預設會是 10 秒相當短,也就是 10 秒容器沒結束就自動 kill 了,後面的 head -2 代表移除舊的容器,原本是開兩台,就需要停止兩台。接著將已經停止的容器砍掉:

docker container prune -f

現在正在執行的容器只剩下一台,故還需要透過 scale 將不足的容器補上:

docker-compose up -d --scale app=2 --no-recreate

完成上述步驟後,就可以確保服務不會中斷。如果有更好的解法歡迎大家提供。


Viewing all articles
Browse latest Browse all 325

Trending Articles


Girasoles para colorear


mayabang Quotes, Torpe Quotes, tanga Quotes


Tagalog Quotes About Crush – Tagalog Love Quotes


OFW quotes : Pinoy Tagalog Quotes


Long Distance Relationship Tagalog Love Quotes


Tagalog Quotes To Move on and More Love Love Love Quotes


5 Tagalog Relationship Rules


Best Crush Tagalog Quotes And Sayings 2017


Re:Mutton Pies (lleechef)


FORECLOSURE OF REAL ESTATE MORTGAGE


Sapos para colorear


tagalog love Quotes – Tiwala Quotes


Break up Quotes Tagalog Love Quote – Broken Hearted Quotes Tagalog


Patama Quotes : Tagalog Inspirational Quotes


Pamatay na Banat and Mga Patama Love Quotes


Tagalog Long Distance Relationship Love Quotes


BARKADA TAGALOG QUOTES


“BAHAY KUBO HUGOT”


Vimeo 10.7.0 by Vimeo.com, Inc.


Vimeo 10.7.1 by Vimeo.com, Inc.