<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: fall1600</title>
    <description>The latest articles on DEV Community by fall1600 (@jinghong).</description>
    <link>https://dev.to/jinghong</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1568837%2F058bf36a-970a-48e9-b8dd-d618143b70e8.jpeg</url>
      <title>DEV Community: fall1600</title>
      <link>https://dev.to/jinghong</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jinghong"/>
    <language>en</language>
    <item>
      <title>Graceful Shutdown with Kubernetes</title>
      <dc:creator>fall1600</dc:creator>
      <pubDate>Thu, 23 Oct 2025 15:05:53 +0000</pubDate>
      <link>https://dev.to/jinghong/graceful-shutdown-with-kubernetes-4bi5</link>
      <guid>https://dev.to/jinghong/graceful-shutdown-with-kubernetes-4bi5</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;應用程式主流的佈署方式演進至k8s, 開關機(pod) 變得頻繁, 不像是vm 時期, 除非垂直擴展否則永不關機. VM 在不關機的情況下, 就算應用程式更新版本, 舊process 仍然可以持續執行, 直到程式結束或timeout; 而佈署在k8s 的應用程式單位是pod, 交由k8s 代為管理, 當pod 因任何因素需要被關閉時, process 面臨仍然在執行, 但宿主(即將) 停止的窘境&lt;/p&gt;

&lt;h2&gt;
  
  
  什麼是Graceful Shutdown?
&lt;/h2&gt;

&lt;p&gt;先快速且抽象地說, 便是讓process 將任務處理完/交接, 再讓它離開&lt;/p&gt;

&lt;h2&gt;
  
  
  不做Graceful Shutdown 會怎樣?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  案例：收到來自第三方金流付款狀態的webhook
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. 將收到payload 寫回關聯式資料庫
2. 發送付款成功事件(e.g. pub/sub)
3. Async Jobs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;若process 中途被殺掉，結果可能是:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DB transaction 被異常中止, 誤以為某次交易沒有收到付款成功的webhook&lt;/li&gt;
&lt;li&gt;DB 寫入成功, 但事件沒發送&lt;/li&gt;
&lt;li&gt;部分jobs 沒被執行&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  程式跑得好好的為什麼會被中斷?
&lt;/h2&gt;

&lt;p&gt;交給k8s 之後, 執行單位(pod) 掌控權已經不完全在你手上了&lt;/p&gt;

&lt;h3&gt;
  
  
  常見的pod 終止原因
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;類型&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal Pod Autoscaler&lt;/td&gt;
&lt;td&gt;離峰減少運算資源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollout / Rollback&lt;/td&gt;
&lt;td&gt;發布新版/退回舊版&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OOMKilled&lt;/td&gt;
&lt;td&gt;記憶體抵達上線&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LivenessProbe Fail&lt;/td&gt;
&lt;td&gt;偵測失敗觸發重啟(非整個 pod, 會是以container 為單位)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node Failure&lt;/td&gt;
&lt;td&gt;節點異常導致 Eviction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual Delete&lt;/td&gt;
&lt;td&gt;手動刪除 Pod&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  k8s 如何實現Graceful Shutdown?
&lt;/h2&gt;

&lt;p&gt;回想管理VM 時, 若系統相當忙碌, 大多時候我們會用top 指令去找出最忙而且判斷可殺的process, 並且毫不留情地送它一個SIGKILL(編號9)的終止訊號&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; &lt;span class="nv"&gt;$pid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但其實我們也可以改送SIGTERM(編號15) 這個終止訊號, 若該程式的作者當初有埋設SIGTERM 訊號的handler, 那代表程式的作者有考慮到這件事, 那便能讓這個process 有機會做到優雅關閉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-15&lt;/span&gt; &lt;span class="nv"&gt;$pid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  核心觀念：兩階段關閉
&lt;/h3&gt;

&lt;p&gt;k8s 也採用同樣的手法, 刪掉pod 時, k8s 不會馬上送SIGKILL, 而是設計了一連串的終止流程讓process 有機會優雅關閉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Factftx2tcstsaadqxecd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Factftx2tcstsaadqxecd.png" alt=" " width="696" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  關鍵參數
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名稱&lt;/th&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preStop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;container 關閉前的hook function&lt;/td&gt;
&lt;td&gt;如果這個container 無法註冊SIGTERM handler, 可使用&lt;code&gt;preStop&lt;/code&gt; 實現優雅關閉, 並且這個hook 是在k8s 送出SIGTERM 之前執行的&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SIGTERM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;優雅終止信號&lt;/td&gt;
&lt;td&gt;由container 的PID 1 接收, 應用程式應處理此信號&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;最大等待時間&lt;/td&gt;
&lt;td&gt;預設 30 秒, 可依需要增減&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;筆者私心覺得&lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; 的設計很像PM/EM 對RD 的靈魂拷問, "這張ticket 你還要做多久?" 有時候manager 問這個問題不一定是要壓時程, 只是單純想要透過估時, 更好地安排任務, 也許manager 希望別花太多時間在目前的任務, 趕緊往下個project 前進. 岔題了, 總之我們需要搞清楚程式&lt;strong&gt;還要跑多久&lt;/strong&gt;, k8s 最多等 &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; 時間, 這個時間包含讓還在跑的程式得以繼續執行到正確結束, 若超過, k8s 就要真的停掉這個container 了&lt;/p&gt;




&lt;h3&gt;
  
  
  HTTP 服務的實作建議
&lt;/h3&gt;

&lt;h4&gt;
  
  
  常見的架構
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client -&amp;gt; cloudflare -&amp;gt; GCP Load Balancer -&amp;gt; k8s -&amp;gt; nginx -&amp;gt; php-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  這個架構下, client 發送RESTful api 時所使用的http 協議, 其tcp 連線是與哪個服務連接呢?
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;當client 從cloudflare 這個DNS 服務取得網址的ip 後, client 便會與此ip 進行通訊, 在GCP Load Balancer 服務中, 其frontend 便是用來處理與client 的連線&lt;/li&gt;
&lt;li&gt;LB frontend 收到請求後, 依照規則proxy 給對應backend, 不過這條連線GCP 沒有開放出來讓我們進行設定, 不用太在意&lt;/li&gt;
&lt;li&gt;LB backend 可以是k8s(或VM group), LB backend 會再與pod 中的nginx 進行tcp 連線&lt;/li&gt;
&lt;li&gt;nginx 將請求proxy 給php-fpm, 這裡也有一條tcp 連線&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;tcp1: client &amp;lt;-&amp;gt; GCP LB frontend&lt;/li&gt;
&lt;li&gt;tcp2: GCP LB frontend &amp;lt;-&amp;gt; GCP LB backend&lt;/li&gt;
&lt;li&gt;tcp3: GCP LB backend &amp;lt;-&amp;gt; nginx &lt;/li&gt;
&lt;li&gt;tcp4: nginx &amp;lt;-&amp;gt; php-fpm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;p.s. GCP 與k8s 內部的連線管理相當複雜, 我們可以關注在能控制的連線即可, 以上四條 tcp 連線中, 實際可調整timeout 的是tcp3與 tcp4&lt;br&gt;
p.p.s. 若cloudflare 作為CDN 用途時, 便是只有client 與cloudflare 進行tcp 連線而已&lt;/p&gt;

&lt;p&gt;簡單說完了這個架構的TCP 連線的概念後, 便能來討論這些連線的timeout 設定&lt;/p&gt;
&lt;h4&gt;
  
  
  Timeout 設計原則
&lt;/h4&gt;

&lt;p&gt;假設讓PHP worker 最多可以執行60 秒, 那nginx 與php-fpm 的連線可以設定成65 秒, 而LB backend 等待nginx 的回覆可以設定為70 秒&lt;br&gt;
&lt;strong&gt;增加考慮優雅關閉&lt;/strong&gt;: &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; 可以設定為 65~70 秒之間, 讓pod 的關閉在nginx 正確收到回傳之後&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LB: 70s &amp;gt; Nginx: 65s &amp;gt; PHP: 60s
terminationGracePeriodSeconds: 67s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  為什麼Timeout 要這樣設計呢?
&lt;/h4&gt;

&lt;p&gt;簡單地說, 這個架構下的所有服務並不是使用同一個時鐘, 若是設定成恰恰好都是60 秒, 實際上哪一條tcp 連線先斷沒有人說得準, 會演變成相當混亂的情況, 所以設定上應該讓外層皆略大於內層, 也就是外層要盡可能地等內層服務的回傳&lt;/p&gt;

&lt;h4&gt;
  
  
  為什麼要討論架構與Timeout 呢?
&lt;/h4&gt;

&lt;p&gt;一樣是那個靈魂拷問, 你還要做多久? 也就是api 還要執行多久? &lt;/p&gt;

&lt;p&gt;實際去測量與統計是個不錯的辦法, 但太耗時費工了, 往infra 一點的層面思考, 只要知道整段request chain 的timeout 設定, 將&lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; 定義在合理的數值便可, 以優雅關閉來說最極端的情況, 便是pod 剛標記為terminating 的前一刻, 時間花費最長的api 剛開始執行, 這支api 最久也就是執行到timeout 為止, 那麼為了要優雅地關閉, 便可將 &lt;code&gt;terminationGracePeriodSeconds&lt;/code&gt; 設定為比timeout 再稍長一點的時間&lt;/p&gt;

&lt;h4&gt;
  
  
  php-fpm 實作方法
&lt;/h4&gt;

&lt;p&gt;php-fpm 是php 的process manager, 架構上會有一個master process 對應多個worker process, master 不負責處理程式, 而是與nginx 溝通, 接收nginx proxy 過來的請求, 轉派給worker&lt;/p&gt;

&lt;p&gt;master process 通常會是container 的PID1, 但我們寫的php 程式碼是給worker process 執行的, 代表我們無法寫程式給PID1 註冊終止訊號處理SIGTERM&lt;/p&gt;

&lt;p&gt;依照php 官方說明, 可以對fpm process 送QUIT 訊號即可優雅關閉master 與其worker process&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-QUIT&lt;/span&gt; &amp;lt;php-fpm_master_pid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;筆者實測版本為php7.4, 認定有bug, 實際上php 會提早與nginx 斷連線*&lt;br&gt;
若使用 php8.x 以上版本, 該問題雖部分修正, 但仍建議自控流程以確保穩定&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bugs.php.net/bug.php?id=76930" rel="noopener noreferrer"&gt;註&lt;/a&gt;: 這是官方 issue（PHP Bug #76930, 原因是 QUIT signal 對 idle worker 行為不一致&lt;/p&gt;

&lt;p&gt;故自定義了兩階段process 關閉的方法&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;等有效worker process 都執行完畢&lt;/li&gt;
&lt;li&gt;關閉fpm master process&lt;/li&gt;
&lt;/ol&gt;


&lt;h3&gt;
  
  
  PHP CLI 模式的實做範例
&lt;/h3&gt;
&lt;h4&gt;
  
  
  CronJob
&lt;/h4&gt;

&lt;p&gt;執行程式碼的process (通常)就是PID1&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;找出最耗時任務&lt;/li&gt;
&lt;li&gt;用pcntl_signal() 註冊SIGTERM handler, 確保可優雅中斷
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;pcntl_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Shutting down gracefully...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;finishPendingJobs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Laravel Horizon
&lt;/h4&gt;

&lt;p&gt;執行程式碼的process (通常)就是PID1&lt;/p&gt;

&lt;p&gt;框架內建 worker pool 管理, 可透過:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;horizon:pause → 停止接新任務&lt;/li&gt;
&lt;li&gt;horizon:terminate → 等任務結束後關閉&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Daemon / Custom Worker
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;用signal + 狀態控制實現兩階段結束&lt;/li&gt;
&lt;li&gt;記得根據最長任務時間設定 grace period&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  其他語言的對照 - Node.js Cluster 模式
&lt;/h3&gt;

&lt;p&gt;nodejs 的cluster 模式曾是處理高併發情境相當熱門的作法, 其概念大致上會是primary 程式中會依照執行單位有多少CPU 便啟動多少child process, 並且在primary 收到請求後將流量分配給child process 執行&lt;/p&gt;

&lt;p&gt;當使用nodejs cluster mode 的程式要做優雅關閉時, primary 就是PID1, 可註冊終止訊號, 只要將終止訊號轉送給child 即可&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// primary.js&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// child.js&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGTERM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;handleGracefulShutdown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  其他語言的對照 - Golang
&lt;/h3&gt;

&lt;p&gt;main.go (通常) 就是PID1, 可註冊終止訊號, 若有使用sub goroutine, 可用context.Context/chan 將終止訊號往goroutine 傳&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用 os/signal + context.Context + chan 管理關閉&lt;/li&gt;
&lt;li&gt;cancel() 放置位置要慎重, 確保 goroutine 有機會釋放資源
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// main goroutine&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c"&gt;// 使用watiGroup 來等待sub goroutine&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;

&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;HandleWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrServerClosed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"listen: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;

&lt;span class="n"&gt;quit&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;quit&lt;/span&gt; &lt;span class="c"&gt;// 阻塞直到收到 signal&lt;/span&gt;

&lt;span class="c"&gt;// main goroutine 執行到此處代表收到終止訊號&lt;/span&gt;
&lt;span class="c"&gt;// 便可用cancel function 傳遞結束訊號給sub goroutine&lt;/span&gt;
&lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"main: Server shutdown: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"main: Server shutdown gracefully"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// sub goroutine&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;HandleWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// 收到終止訊號, 做終止要做的事 e.g. 標出現在處理到哪&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo: shutdown received"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo: handling i"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="c"&gt;// 正常情況下 e.g. 每秒處理一件事&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"foo: normal goroutine"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  以後開發需要注意什麼呢?
&lt;/h2&gt;

&lt;p&gt;回頭檢視, 我們該如何確保程式真正理解自己的生命週期?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;既有功能需注意執行時間與 timeout&lt;/li&gt;
&lt;li&gt;新服務需明確規劃關閉策略

&lt;ul&gt;
&lt;li&gt;業務週期&lt;/li&gt;
&lt;li&gt;process model&lt;/li&gt;
&lt;li&gt;signal 支援度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Pod 外部的 timeout 也需考慮

&lt;ul&gt;
&lt;li&gt;LB timeout&lt;/li&gt;
&lt;li&gt;Connection pool 設定&lt;/li&gt;
&lt;li&gt;Queue worker timeout&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;一起做個負責任的工程師吧😉&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>gcp</category>
      <category>gke</category>
    </item>
  </channel>
</rss>
