分享程式代碼相關筆記
目前文章總數:203 篇
最後更新:2025年 10月 25日
下載範例檔案來說明,執行以下在 Ubuntu 主機上
docker-compose up -d
我們選擇一個 Container 檢查
docker exec staticweb_ipinfoexample_1 ps aux
可以觀察到 PID = 1 的應用程式是我們 DockerFile 的啟動
CMD ["nginx", "-g", "daemon off;"]
執行以下刪除容器內的 Process 指令
docker exec staticweb_ipinfoexample_1 kill -15 1
指令拆解說明:
| 指令拆解 | 說明 |
|---|---|
| docker exec | 在該容器裡執行一個命令 |
| kill | 傳送訊號給某個進程。 |
| -15 | 表示訊號 SIGTERM (訊號中止, signal terminate) |
| -1 | PID 編號 1 ,在容器內該 Process 的 ID 編號為 1 |
執行後可以觀察到容器也跟著停止 - 為何呢?
在任何 Linux 中,PID = 1 具有特殊性,並且是 init 程序,Linux 內核設計使其特性如下:
| 1. 系統啟動後的第一個程序 |
| 2. 所有其他程序的祖先 |
| 3. 負責回收孤兒程序 |
| 4. 當 PID 1 程序終止時,內核會發出 SIGKILL 給所有其他程序並關閉系統 |
關鍵在於 PID 1 結束時,就會關閉系統
在 Docker Container 中,只要確保 DockerFile 實現以下,就能讓容器與其內部應用程式其中 1 項停止時,就關閉容器運行
| 1. 容器啟動時,ENTRYPOINT 或 CMD 指定的程序成為 PID 1 |
| 2. 容器擁有自己的 PID namespace,與宿主機隔離(這是必定的,與宿主機做映射) |
| 3. 當容器內的 PID 1 程序結束時,容器就會停止 |
因為容器的應用程式 PID = 1,因此容器(可以視為容器內 Linux 系統),也就會同步停止運行。
容器透過 namespace (命名空間)將容器與宿主機內核隔離。
命名空間限制每個程序能看到什麼(如其他程序、用戶、檔案系統),因此用 namespace 隔離後,雖然容器與宿主機都有 PID = 1 但並不會衝突
如下圖,宿主機有自己的 init PID = 1 並且容器在:
Linux 的核心文件:https://docs.kernel.org/
Docker .net https://docs.docker.net.tw/contribute/guides/
Docker 官網的說明,有表述容器最佳的實踐是 1 個容器只用於 1 個應用
It's best practice to separate areas of concern by using one service per container. That service may fork into multiple processes
除了符合單一職責外(1 個容器只有 1 個應用,負責 1 件事情),最大的優點是便於監控容器運行狀態與追蹤錯誤
| 好處 | 說明 |
|---|---|
| 隔離責任 (Separation of Concerns) | 每個服務/進程有自己的容器,就不會因為其中一個服務故障或更新造成其他的服務被影響。 |
| 擴展性 (Scalability) | 當某服務流量變大,可以只擴展那個服務的容器,不需要同時帶動其他不需要擴展的服務。 |
| 資源控制與配置清晰 | 比如記憶體、CPU、環境變數、網路端口等,每個容器比較容易設定與限制。 |
| 維護與佈署簡單 | 更新、回滾、測試會比較簡潔;日誌/監控也比較清晰。 |
| Docker 契合設計哲學 | Docker 的許多機制(entrypoint/CMD、資源隔離、life-cycle 管理)本身就是以單一進程為主進行設計的。 |
殭屍容器:容器還在運行,但內部的應用程式已經無法正常工作
這種現象出現在未落實容器的最佳實踐,或者在容器內的主程式未控管好子程序時發生
以下是潛在造成殭屍容器的 DockerFile 範例,通常出現在 管理多個服務、多個 Port、應用程式不是 PID=1 都是可能觸發
# ❌ 反面教材:不要這樣做!
# 這個 Dockerfile 演示了容器內運行多個應用的問題
FROM ubuntu:20.04
# 安裝多個服務
RUN apt-get update && apt-get install -y \
nginx \
redis-server \
mysql-server \
supervisor \
&& rm -rf /var/lib/apt/lists/*
# 創建 supervisor 配置目錄
RUN mkdir -p /var/log/supervisor
# 配置 supervisor 管理多個服務
COPY <<EOF /etc/supervisor/conf.d/supervisord.conf
[supervisord]
nodaemon=true
user=root
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/nginx.err.log
stdout_logfile=/var/log/supervisor/nginx.out.log
[program:redis]
command=/usr/bin/redis-server
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/redis.err.log
stdout_logfile=/var/log/supervisor/redis.out.log
[program:mysql]
command=/usr/bin/mysqld_safe
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/mysql.err.log
stdout_logfile=/var/log/supervisor/mysql.out.log
EOF
# 創建 nginx 配置
RUN echo "server { listen 80; root /var/www/html; }" > /etc/nginx/sites-available/default
# 創建測試頁面
RUN echo "<h1>Multi-App Container (BAD PRACTICE)</h1>" > /var/www/html/index.html
# 暴露多個端口(也是不好的實踐)
EXPOSE 80 6379 3306
# ❌ 問題:使用 supervisor 作為 PID 1
# 這會導致殭屍容器問題
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
如果照著上面的 DockerFile 執行,如下圖,當容器的 supervisord (PID=1)存活的情況下,但是容器內的 Nginx、Redis、Mysql 都死亡
嚴重的影響:增加排查問題的複雜度,並且會大大增加公司運營成本,原本公司只需要監控容器的存活,延伸變成還需監控到容器內每個服務的運行狀態
未來假如容器內部的服務擴增與減少,維運人員都需要同步改寫監控的腳本,因此 1個容器多個應用程式,盡量禁止